Files
freepress-map/fp-geo-content/assets/js/map.js
2026-02-13 13:14:41 +01:00

631 lines
22 KiB
JavaScript

/**
* Script del mapa - FP Geo Content
*/
(function() {
'use strict';
// Almacenar instancias de mapas
const mapInstances = {};
/**
* Inicializar cuando el DOM esté listo
*/
document.addEventListener('DOMContentLoaded', function() {
// Buscar todos los wrappers de mapas
document.querySelectorAll('.fp-geo-wrapper').forEach(function(wrapper) {
const instance = wrapper.dataset.instance;
const configName = 'fpGeoConfig_' + instance;
if (window[configName]) {
initMap(window[configName]);
}
});
});
/**
* Inicializar un mapa
*/
function initMap(config) {
const mapEl = document.getElementById(config.mapId);
if (!mapEl) return;
// Configurar opciones de scroll wheel zoom
let scrollWheelZoomOption = false;
if (config.scrollWheelZoom === 'always') {
scrollWheelZoomOption = true;
} else if (config.scrollWheelZoom === 'ctrl') {
scrollWheelZoomOption = 'ctrl';
}
// Crear mapa Leaflet
const map = L.map(mapEl, {
center: config.center,
zoom: config.zoom,
minZoom: config.minZoom,
maxZoom: config.maxZoom,
scrollWheelZoom: scrollWheelZoomOption === 'ctrl' ? false : scrollWheelZoomOption,
});
// Añadir capa de tiles
L.tileLayer(config.tileUrl, {
attribution: config.tileAttribution,
subdomains: config.tileSubdomains || 'abc',
maxZoom: 19,
}).addTo(map);
// Crear grupo de marcadores (con o sin cluster)
let markersLayer;
if (config.clusterEnabled) {
markersLayer = L.markerClusterGroup({
showCoverageOnHover: false,
maxClusterRadius: 50,
});
} else {
markersLayer = L.layerGroup();
}
// Almacenar datos
const mapData = {
map: map,
markersLayer: markersLayer,
allMarkers: [],
config: config,
activeFilters: {},
};
mapInstances[config.mapId] = mapData;
// Configurar control de scroll con Ctrl
if (config.scrollWheelZoom === 'ctrl') {
initCtrlScrollZoom(mapData);
}
// Añadir marcadores iniciales
addMarkers(mapData, config.markers);
// Añadir capa al mapa
map.addLayer(markersLayer);
// Ajustar zoom para mostrar todos los marcadores
fitMapToMarkers(mapData);
// Inicializar filtros
initFilters(mapData);
// Inicializar panel de detalle
initDetailPanel(mapData);
}
/**
* Inicializar control de zoom con Ctrl+scroll
*/
function initCtrlScrollZoom(mapData) {
const { map, config } = mapData;
const mapEl = document.getElementById(config.mapId);
let hintTimeout;
// Crear elemento de aviso
const scrollHint = document.createElement('div');
scrollHint.className = 'fp-geo-scroll-hint';
scrollHint.textContent = config.i18n.scrollZoomHint || 'Usa Ctrl + scroll para hacer zoom';
mapEl.parentElement.appendChild(scrollHint);
// Detectar scroll sin Ctrl
mapEl.addEventListener('wheel', function(e) {
if (e.ctrlKey) {
// Habilitar zoom cuando Ctrl está presionado
map.scrollWheelZoom.enable();
scrollHint.classList.remove('visible');
} else {
// Mostrar aviso
map.scrollWheelZoom.disable();
scrollHint.classList.add('visible');
clearTimeout(hintTimeout);
hintTimeout = setTimeout(function() {
scrollHint.classList.remove('visible');
}, 1500);
}
});
// Deshabilitar zoom al soltar Ctrl
document.addEventListener('keyup', function(e) {
if (e.key === 'Control') {
map.scrollWheelZoom.disable();
}
});
}
/**
* Ajustar el zoom del mapa para mostrar todos los marcadores
*/
function fitMapToMarkers(mapData) {
const { map, markersLayer, config } = mapData;
// Obtener los límites del grupo de marcadores
const layers = markersLayer.getLayers();
if (layers.length === 0) {
// Sin marcadores, usar centro y zoom por defecto
return;
}
if (layers.length === 1) {
// Un solo marcador: centrar en él con zoom razonable
const marker = layers[0];
map.setView(marker.getLatLng(), Math.min(config.zoom || 14, config.maxZoom || 18));
return;
}
// Varios marcadores: ajustar para mostrar todos
const bounds = markersLayer.getBounds();
if (bounds.isValid()) {
map.fitBounds(bounds, {
padding: [50, 50], // Margen de 50px
maxZoom: config.maxZoom || 18,
});
}
}
/**
* Crear icono de marcador circular SVG
*/
function createCircleMarkerIcon(color, isPilot = false) {
const size = 36;
const strokeWidth = 3;
// Crear SVG simple y limpio
let svg;
if (isPilot) {
// Marcador con estrella para pilotos
svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + (size + 12) + '" viewBox="0 0 ' + size + ' ' + (size + 12) + '">' +
'<circle cx="' + (size/2) + '" cy="' + (size/2) + '" r="' + (size/2 - 2) + '" fill="rgba(0,0,0,0.3)" transform="translate(2, 2)"/>' +
'<circle cx="' + (size/2) + '" cy="' + (size/2) + '" r="' + (size/2 - strokeWidth) + '" fill="' + color + '" stroke="white" stroke-width="' + strokeWidth + '"/>' +
'<polygon points="' + (size/2) + ',' + (size + 2) + ' ' + (size/2 + 5) + ',' + (size + 12) + ' ' + (size/2 - 5) + ',' + (size + 12) + '" fill="' + color + '" stroke="white" stroke-width="1"/>' +
'<text x="' + (size/2) + '" y="' + (size/2 + 4) + '" text-anchor="middle" fill="white" font-size="14" font-weight="bold">★</text>' +
'</svg>';
return L.divIcon({
className: 'fp-geo-circle-marker fp-geo-pilot-marker',
html: svg,
iconSize: [size, size + 12],
iconAnchor: [size/2, size + 12],
popupAnchor: [0, -(size + 12)],
});
} else {
// Marcador circular normal con punta
svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + size + '" height="' + (size + 12) + '" viewBox="0 0 ' + size + ' ' + (size + 12) + '">' +
'<circle cx="' + (size/2) + '" cy="' + (size/2) + '" r="' + (size/2 - 2) + '" fill="rgba(0,0,0,0.3)" transform="translate(2, 2)"/>' +
'<circle cx="' + (size/2) + '" cy="' + (size/2) + '" r="' + (size/2 - strokeWidth) + '" fill="' + color + '" stroke="white" stroke-width="' + strokeWidth + '"/>' +
'<polygon points="' + (size/2) + ',' + (size + 10) + ' ' + (size/2 + 6) + ',' + (size - 4) + ' ' + (size/2 - 6) + ',' + (size - 4) + '" fill="' + color + '" stroke="white" stroke-width="2" stroke-linejoin="round"/>' +
'</svg>';
return L.divIcon({
className: 'fp-geo-circle-marker',
html: svg,
iconSize: [size, size + 12],
iconAnchor: [size/2, size + 10],
popupAnchor: [0, -(size + 10)],
});
}
}
/**
* Añadir marcadores al mapa
*/
function addMarkers(mapData, markers) {
const { markersLayer, config } = mapData;
// Limpiar marcadores existentes
markersLayer.clearLayers();
mapData.allMarkers = [];
// Color por defecto
const defaultColor = config.markerDefaultColor || '#F97316';
markers.forEach(function(markerData) {
let icon;
let markerColor = defaultColor;
// es_piloto viene directamente del post (actuación), no de los términos
let isPilot = markerData.es_piloto === true;
// Buscar color de la taxonomía configurada para colores
if (config.useCategoryColors && markerData.taxonomies) {
// Primero intentar con la taxonomía de leyenda configurada
if (config.legendTaxonomy) {
const categoryTerms = markerData.taxonomies[config.legendTaxonomy];
if (categoryTerms && categoryTerms.length > 0) {
if (categoryTerms[0].color) {
markerColor = categoryTerms[0].color;
}
}
}
// Si no hay color, buscar en cualquier taxonomía
if (markerColor === defaultColor) {
for (const taxonomy in markerData.taxonomies) {
const terms = markerData.taxonomies[taxonomy];
if (terms && terms.length > 0 && terms[0].color) {
markerColor = terms[0].color;
break;
}
}
}
}
// Usar icono personalizado si existe, o círculo de color
if (config.markerIcon && config.markerIcon !== '') {
icon = L.icon({
iconUrl: config.markerIcon,
iconSize: [40, 50],
iconAnchor: [20, 50],
popupAnchor: [0, -50],
});
} else {
// Crear marcador circular con el color
icon = createCircleMarkerIcon(markerColor, isPilot);
}
// Crear marcador
const marker = L.marker([markerData.lat, markerData.lng], { icon: icon });
// Guardar datos en el marcador
marker.markerData = markerData;
marker.markerColor = markerColor;
// Evento de clic
marker.on('click', function() {
showDetail(mapData, markerData);
});
// Añadir al grupo
markersLayer.addLayer(marker);
mapData.allMarkers.push(marker);
});
// Actualizar contador
updateResultsCount(mapData, markers.length);
// Debug: mostrar en consola cuántos marcadores se añadieron
console.log('[FP Geo] Añadidos ' + markers.length + ' marcadores con color por defecto: ' + defaultColor);
}
/**
* Actualizar contador de resultados
*/
function updateResultsCount(mapData, count) {
const { config } = mapData;
const wrapper = document.getElementById(config.mapId + '-wrapper');
if (!wrapper) return;
const countEl = wrapper.querySelector('.fp-geo-results-number');
const labelEl = wrapper.querySelector('.fp-geo-results-label');
if (countEl) {
countEl.textContent = count;
}
if (labelEl) {
// Si solo hay un tipo de contenido, mostrar su nombre
if (config.postTypes && config.postTypes.length === 1) {
const postType = config.postTypes[0];
// Usar el nombre del post type en plural/singular
const labels = config.postTypeLabels || {};
if (count === 1) {
labelEl.textContent = labels[postType]?.singular || postType;
} else {
labelEl.textContent = labels[postType]?.plural || postType + 's';
}
} else {
labelEl.textContent = count === 1 ? 'resultado' : 'resultados';
}
}
}
/**
* Inicializar filtros
*/
function initFilters(mapData) {
const { config } = mapData;
const wrapper = document.getElementById(config.mapId + '-wrapper');
if (!wrapper) return;
const filtersEl = wrapper.querySelector('.fp-geo-filters');
if (!filtersEl) return;
// Botones de filtro
filtersEl.querySelectorAll('.fp-geo-filter-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
const taxonomy = btn.dataset.taxonomy;
const slug = btn.dataset.slug;
// Toggle activo
btn.classList.toggle('active');
// Actualizar filtros activos
if (!mapData.activeFilters[taxonomy]) {
mapData.activeFilters[taxonomy] = [];
}
const index = mapData.activeFilters[taxonomy].indexOf(slug);
if (index > -1) {
mapData.activeFilters[taxonomy].splice(index, 1);
} else {
mapData.activeFilters[taxonomy].push(slug);
}
// Aplicar filtros
applyFilters(mapData);
});
});
// Botón limpiar
const clearBtn = filtersEl.querySelector('.fp-geo-clear-filters');
if (clearBtn) {
clearBtn.addEventListener('click', function() {
// Limpiar todos los filtros
mapData.activeFilters = {};
filtersEl.querySelectorAll('.fp-geo-filter-btn.active').forEach(function(btn) {
btn.classList.remove('active');
});
applyFilters(mapData);
});
}
}
/**
* Aplicar filtros a los marcadores
*/
function applyFilters(mapData) {
const { markersLayer, allMarkers, activeFilters, config } = mapData;
const filterCombine = config.filterCombine || 'OR';
// Verificar si hay filtros activos
const hasActiveFilters = Object.values(activeFilters).some(arr => arr.length > 0);
let visibleCount = 0;
allMarkers.forEach(function(marker) {
const data = marker.markerData;
let shouldShow = true;
if (hasActiveFilters) {
if (filterCombine === 'AND') {
// Debe cumplir TODOS los filtros
shouldShow = Object.entries(activeFilters).every(function([taxonomy, slugs]) {
if (slugs.length === 0) return true;
const markerTerms = data.taxonomies && data.taxonomies[taxonomy];
if (!markerTerms) return false;
return slugs.some(function(slug) {
return markerTerms.some(function(term) {
return term.slug === slug;
});
});
});
} else {
// Debe cumplir AL MENOS UN filtro (OR)
shouldShow = Object.entries(activeFilters).some(function([taxonomy, slugs]) {
if (slugs.length === 0) return false;
const markerTerms = data.taxonomies && data.taxonomies[taxonomy];
if (!markerTerms) return false;
return slugs.some(function(slug) {
return markerTerms.some(function(term) {
return term.slug === slug;
});
});
});
// Si no hay filtros activos en ninguna taxonomía, mostrar todo
if (!Object.values(activeFilters).some(arr => arr.length > 0)) {
shouldShow = true;
}
}
}
if (shouldShow) {
markersLayer.addLayer(marker);
visibleCount++;
} else {
markersLayer.removeLayer(marker);
}
});
// Actualizar contador de resultados
updateResultsCount(mapData, visibleCount);
}
/**
* Inicializar panel de detalle
*/
function initDetailPanel(mapData) {
const { config } = mapData;
const wrapper = document.getElementById(config.mapId + '-wrapper');
if (!wrapper) return;
const detailEl = document.getElementById(config.mapId + '-detail');
const overlayEl = document.getElementById(config.mapId + '-overlay');
if (!detailEl) return;
// Botón cerrar
const closeBtn = detailEl.querySelector('.fp-geo-detail-close');
if (closeBtn) {
closeBtn.addEventListener('click', function() {
hideDetail(mapData);
});
}
// Cerrar al hacer clic en overlay (modal)
if (overlayEl) {
overlayEl.addEventListener('click', function() {
hideDetail(mapData);
});
}
// Cerrar con Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
hideDetail(mapData);
}
});
}
/**
* Mostrar detalle de un marcador
*/
function showDetail(mapData, data) {
const { config } = mapData;
const detailEl = document.getElementById(config.mapId + '-detail');
const overlayEl = document.getElementById(config.mapId + '-overlay');
if (!detailEl) return;
// Rellenar taxonomías
const taxEl = detailEl.querySelector('.fp-geo-detail-taxonomies');
if (taxEl) {
taxEl.innerHTML = '';
if (data.taxonomies) {
for (const taxonomy in data.taxonomies) {
data.taxonomies[taxonomy].forEach(function(term) {
const tag = document.createElement('span');
tag.className = 'fp-geo-detail-tax-tag';
tag.style.cssText = term.color ? 'background-color: ' + term.color + ';' : '';
let html = '';
if (term.icono) {
html += '<img src="' + term.icono + '" alt="" class="fp-geo-detail-tax-icon">';
}
html += '<span>' + term.name + '</span>';
tag.innerHTML = html;
taxEl.appendChild(tag);
});
}
}
}
// Thumbnail
const thumbEl = detailEl.querySelector('.fp-geo-detail-thumbnail');
if (thumbEl) {
if (data.thumbnail) {
thumbEl.innerHTML = '<img src="' + data.thumbnail + '" alt="' + data.title + '">';
thumbEl.classList.add('has-image');
} else {
thumbEl.innerHTML = '';
thumbEl.classList.remove('has-image');
}
}
// Título
const titleEl = detailEl.querySelector('.fp-geo-detail-title');
if (titleEl) {
titleEl.textContent = data.title;
}
// Excerpt
const excerptEl = detailEl.querySelector('.fp-geo-detail-excerpt');
if (excerptEl) {
excerptEl.textContent = data.excerpt || '';
}
// Ubicación
const locationEl = detailEl.querySelector('.fp-geo-detail-location');
if (locationEl) {
const locationParts = [data.direccion, data.localidad].filter(Boolean);
if (locationParts.length > 0) {
locationEl.innerHTML = '<span>' + locationParts.join(', ') + '</span>';
locationEl.style.display = 'flex';
} else {
locationEl.style.display = 'none';
}
}
// Contacto
const contactEl = detailEl.querySelector('.fp-geo-detail-contact');
if (contactEl) {
let contactHtml = '';
if (data.telefono) {
contactHtml += '<div class="fp-geo-detail-contact-item">📞 <a href="tel:' + data.telefono.replace(/\s/g, '') + '">' + data.telefono + '</a></div>';
}
if (data.email) {
contactHtml += '<div class="fp-geo-detail-contact-item">✉️ <a href="mailto:' + data.email + '">' + data.email + '</a></div>';
}
if (data.web) {
contactHtml += '<div class="fp-geo-detail-contact-item">🌐 <a href="' + data.web + '" target="_blank">Sitio web</a></div>';
}
contactEl.innerHTML = contactHtml;
}
// Link - actualizar texto si está configurado
const linkEl = detailEl.querySelector('.fp-geo-detail-link');
if (linkEl) {
linkEl.href = data.url;
if (config.detailButtonText) {
linkEl.textContent = config.detailButtonText;
}
}
// Footer - mostrar/ocultar según configuración
const footerEl = detailEl.querySelector('.fp-geo-detail-footer');
if (footerEl) {
footerEl.style.display = config.showDetailButton ? 'block' : 'none';
}
// Mostrar panel
detailEl.classList.add('visible');
if (overlayEl) {
overlayEl.classList.add('visible');
}
}
/**
* Ocultar panel de detalle
*/
function hideDetail(mapData) {
const { config } = mapData;
const detailEl = document.getElementById(config.mapId + '-detail');
const overlayEl = document.getElementById(config.mapId + '-overlay');
if (detailEl) {
detailEl.classList.remove('visible');
}
if (overlayEl) {
overlayEl.classList.remove('visible');
}
}
// Exponer API pública
window.FPGeoContent = {
getMap: function(mapId) {
return mapInstances[mapId];
},
refreshMarkers: function(mapId, markers) {
const mapData = mapInstances[mapId];
if (mapData) {
addMarkers(mapData, markers);
}
},
applyFilters: function(mapId) {
const mapData = mapInstances[mapId];
if (mapData) {
applyFilters(mapData);
}
},
fitToMarkers: function(mapId) {
const mapData = mapInstances[mapId];
if (mapData) {
fitMapToMarkers(mapData);
}
},
};
})();