/** * OEDB Embed Script * Script d'intégration pour afficher les événements OEDB sur des sites externes */ (function() { 'use strict'; // Configuration par défaut const defaultConfig = { apiUrl: 'https://api.openenventdatabase.org', homeUrl: '/', // URL de la page home pour les liens vers les détails d'événements theme: 'auto', // 'auto', 'light', ou 'dark' - auto détecte depuis la préférence système limit: 50, width: '100%', height: '400px', showMap: true, showList: true, autoRefresh: false, refreshInterval: 300000 // 5 minutes }; // Thèmes CSS const themes = { light: { background: '#ffffff', text: '#2c3e50', border: '#ecf0f1', primary: '#3498db', secondary: '#95a5a6' }, dark: { background: '#2c3e50', text: '#ecf0f1', border: '#34495e', primary: '#3498db', secondary: '#7f8c8d' } }; class OEDBEmbed { constructor(container, config) { this.container = typeof container === 'string' ? document.querySelector(container) : container; this.config = { ...defaultConfig, ...config }; // Fusionner les params si présents if (config && config.params) { this.config = { ...this.config, ...config.params }; } // Détecter le thème système si 'auto' ou si non spécifié const themeWasAuto = this.config.theme === 'auto' || !this.config.theme; if (this.config.theme === 'auto' || !this.config.theme) { this.config.theme = this.detectSystemTheme(); } this.events = []; this.isLoading = false; this.refreshTimer = null; this.map = null; this.themeMediaQuery = null; this.handleThemeChange = null; if (!this.container) { console.error('OEDB Embed: Container not found'); return; } this.init(); // Écouter les changements de thème si auto if (themeWasAuto) { this.setupThemeListener(); } } detectSystemTheme() { if (typeof window !== 'undefined' && window.matchMedia) { return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } return 'light'; } setupThemeListener() { if (typeof window !== 'undefined' && window.matchMedia) { this.themeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); this.handleThemeChange = (e) => { this.config.theme = e.matches ? 'dark' : 'light'; this.injectStyles(); if (this.map) { // Mettre à jour le style de la carte si nécessaire const mapStyle = this.config.theme === 'dark' ? 'https://tiles.openfreemap.org/styles/dark' : 'https://tiles.openfreemap.org/styles/liberty'; try { if (this.map.getStyle() && this.map.getStyle().name !== mapStyle) { this.map.setStyle(mapStyle); } } catch (err) { // Si on ne peut pas obtenir le style, réinitialiser la carte if (this.map.isStyleLoaded()) { this.map.setStyle(mapStyle); } } } }; // Support moderne et ancien if (this.themeMediaQuery.addEventListener) { this.themeMediaQuery.addEventListener('change', this.handleThemeChange); } else if (this.themeMediaQuery.addListener) { this.themeMediaQuery.addListener(this.handleThemeChange); } } } init() { this.injectStyles(); this.render(); this.loadEvents(); if (this.config.autoRefresh) { this.startAutoRefresh(); } } injectStyles() { // Supprimer les anciens styles s'ils existent const oldStyles = document.getElementById('oedb-embed-styles'); if (oldStyles) { oldStyles.remove(); } const theme = themes[this.config.theme] || themes.light; const styles = ` `; document.head.insertAdjacentHTML('beforeend', styles); } render() { this.container.innerHTML = `
Événements OEDB
${this.config.showMap ? '
' : ''} ${this.config.showList ? `
` : ''}
`; } async loadEvents() { if (this.isLoading) return; this.isLoading = true; this.showLoading(); try { const params = new URLSearchParams(); if (this.config.what) params.set('what', this.config.what); if (this.config.start) params.set('start', this.config.start); if (this.config.end) params.set('end', this.config.end); if (this.config.limit) params.set('limit', this.config.limit.toString()); if (this.config.bbox) params.set('bbox', this.config.bbox); const response = await fetch(`${this.config.apiUrl}/event?${params.toString()}`); const data = await response.json(); this.events = data.features || []; this.renderEvents(); this.renderMap(); } catch (error) { console.error('OEDB Embed: Error loading events', error); this.showError('Erreur lors du chargement des événements'); } finally { this.isLoading = false; } } showLoading() { const listContainer = document.getElementById('oedb-list'); if (listContainer) { listContainer.innerHTML = '
Chargement des événements...
'; } } showError(message) { const listContainer = document.getElementById('oedb-list'); if (listContainer) { listContainer.innerHTML = `
${message}
`; } } renderEvents() { const listContainer = document.getElementById('oedb-list'); if (!listContainer) return; if (this.events.length === 0) { listContainer.innerHTML = '
Aucun événement trouvé
'; return; } const eventsHtml = this.events.map(event => { const title = event.properties?.label || event.properties?.name || 'Événement sans nom'; const date = event.properties?.start || event.properties?.when || ''; const location = event.properties?.where || ''; const type = event.properties?.what || ''; // L'ID peut être à la racine du Feature ou dans properties.id const eventId = event.id || event.properties?.id || ''; const eventUrl = eventId ? `${this.config.homeUrl}?id=${encodeURIComponent(eventId)}` : '#'; return `
${this.escapeHtml(title)}
${date ? `
📅 ${this.formatDate(date)}
` : ''} ${location ? `
📍 ${this.escapeHtml(location)}
` : ''} ${type ? `
🏷️ ${this.escapeHtml(type)}
` : ''}
`; }).join(''); listContainer.innerHTML = eventsHtml; // Ajouter les événements de clic pour l'événement personnalisé (optionnel) listContainer.querySelectorAll('.oedb-event-item').forEach(item => { item.addEventListener('click', (e) => { const eventId = item.dataset.eventId; if (eventId) { // Émettre un événement personnalisé pour permettre l'écoute externe const customEvent = new CustomEvent('oedb-event-click', { detail: { eventId, event: this.events.find(e => e.id === eventId) } }); this.container.dispatchEvent(customEvent); } }); }); } renderMap() { const mapContainer = document.getElementById('oedb-map'); if (!mapContainer) return; if (this.events.length === 0) { mapContainer.innerHTML = '
Aucun événement à afficher sur la carte
'; return; } // Initialiser MapLibre GL JS si disponible if (typeof maplibregl === 'undefined') { this.initMapLibre().then(() => { this.createMap(mapContainer); }).catch(err => { console.error('Failed to load MapLibre GL:', err); mapContainer.innerHTML = `
Carte interactive
${this.events.length} événement(s) trouvé(s)
Chargement de la bibliothèque de cartes...
`; }); } else { this.createMap(mapContainer); } } async initMapLibre() { return new Promise((resolve, reject) => { // Vérifier si MapLibre est déjà chargé if (typeof maplibregl !== 'undefined') { return resolve(); } // Charger le CSS const css = document.createElement('link'); css.rel = 'stylesheet'; css.href = 'https://unpkg.com/maplibre-gl@3.6.0/dist/maplibre-gl.css'; document.head.appendChild(css); // Charger le JavaScript const script = document.createElement('script'); script.src = 'https://unpkg.com/maplibre-gl@3.6.0/dist/maplibre-gl.js'; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load MapLibre GL')); document.head.appendChild(script); }); } createMap(mapContainer) { // Nettoyer le conteneur mapContainer.innerHTML = ''; // Créer la FeatureCollection GeoJSON // Filtrer les événements qui ont des coordonnées valides const geojson = { type: 'FeatureCollection', features: this.events .filter(event => { // Vérifier que l'événement a une géométrie valide avec des coordonnées if (!event.geometry || !event.geometry.coordinates) return false; const coords = event.geometry.coordinates; return Array.isArray(coords) && coords.length >= 2 && typeof coords[0] === 'number' && typeof coords[1] === 'number' && !isNaN(coords[0]) && !isNaN(coords[1]) && coords[0] !== 0 && coords[1] !== 0; // Exclure les coordonnées 0,0 par défaut }) .map(event => { // L'ID peut être à la racine du Feature ou dans properties.id const eventId = event.id || event.properties?.id || ''; return { type: 'Feature', geometry: event.geometry, properties: { id: eventId, label: event.properties?.label || event.properties?.name || 'Événement', what: event.properties?.what || '', start: event.properties?.start || event.properties?.when || '' } }; }) }; // Calculer les bounds si on a des événements avec coordonnées valides let bounds = null; if (geojson.features.length > 0) { const coordinates = geojson.features .map(f => f.geometry?.coordinates) .filter(c => c && Array.isArray(c) && c.length >= 2); if (coordinates.length > 0) { const lons = coordinates.map(c => c[0]); const lats = coordinates.map(c => c[1]); bounds = [ [Math.min(...lons), Math.min(...lats)], [Math.max(...lons), Math.max(...lats)] ]; } } // Centre par défaut (Paris) const center = bounds ? [(bounds[0][0] + bounds[1][0]) / 2, (bounds[0][1] + bounds[1][1]) / 2] : [2.3522, 48.8566]; // Déterminer le style de carte selon le thème const mapStyle = this.config.theme === 'dark' ? 'https://tiles.openfreemap.org/styles/dark' : 'https://tiles.openfreemap.org/styles/liberty'; // Créer la carte this.map = new maplibregl.Map({ container: mapContainer, style: mapStyle, center: center, zoom: bounds ? this.calculateZoom(bounds) : 5 }); this.map.on('load', () => { // Ajouter la source GeoJSON this.map.addSource('events', { type: 'geojson', data: geojson }); // Ajouter les marqueurs this.map.addLayer({ id: 'events-points', type: 'circle', source: 'events', paint: { 'circle-radius': 6, 'circle-color': '#667eea', 'circle-stroke-width': 2, 'circle-stroke-color': '#ffffff' } }); // Ajouter les popups avec lien vers la page home this.map.on('click', 'events-points', (e) => { const feature = e.features[0]; const props = feature.properties; const eventId = props.id || ''; const eventUrl = eventId ? `${this.config.homeUrl}?id=${encodeURIComponent(eventId)}` : '#'; const popupContent = eventId ? `
${props.label || 'Événement'}
${props.what ? `${props.what}
` : ''} ${props.start ? `📅 ${this.formatDate(props.start)}` : ''} ${eventId ? `
Voir les détails →` : ''}
` : `
${props.label || 'Événement'}
${props.what ? `${props.what}
` : ''} ${props.start ? `📅 ${this.formatDate(props.start)}` : ''}
`; new maplibregl.Popup() .setLngLat(e.lngLat) .setHTML(popupContent) .addTo(this.map); }); // Changer le curseur au survol this.map.on('mouseenter', 'events-points', () => { this.map.getCanvas().style.cursor = 'pointer'; }); this.map.on('mouseleave', 'events-points', () => { this.map.getCanvas().style.cursor = ''; }); // Ajuster la vue si on a des bounds if (bounds) { this.map.fitBounds(bounds, { padding: 50, maxZoom: 15 }); } }); } calculateZoom(bounds) { const width = bounds[1][0] - bounds[0][0]; const height = bounds[1][1] - bounds[0][1]; const maxDimension = Math.max(width, height); if (maxDimension > 10) return 4; if (maxDimension > 5) return 5; if (maxDimension > 2) return 6; if (maxDimension > 1) return 7; if (maxDimension > 0.5) return 8; if (maxDimension > 0.2) return 9; if (maxDimension > 0.1) return 10; if (maxDimension > 0.05) return 11; return 12; } onEventClick(eventId) { // Émettre un événement personnalisé (déjà fait dans renderEvents) // Cette méthode peut être utilisée pour des actions personnalisées const event = new CustomEvent('oedb-event-click', { detail: { eventId, event: this.events.find(e => e.id === eventId) } }); this.container.dispatchEvent(event); } getEventUrl(eventId) { if (!eventId) return '#'; return `${this.config.homeUrl}?id=${encodeURIComponent(eventId)}`; } startAutoRefresh() { if (this.refreshTimer) { clearInterval(this.refreshTimer); } this.refreshTimer = setInterval(() => { this.loadEvents(); }, this.config.refreshInterval); } stopAutoRefresh() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } } destroy() { this.stopAutoRefresh(); if (this.map) { this.map.remove(); this.map = null; } // Supprimer le listener de thème if (this.themeMediaQuery && this.handleThemeChange) { if (this.themeMediaQuery.removeEventListener) { this.themeMediaQuery.removeEventListener('change', this.handleThemeChange); } else if (this.themeMediaQuery.removeListener) { this.themeMediaQuery.removeListener(this.handleThemeChange); } } this.container.innerHTML = ''; } // Utilitaires escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatDate(dateString) { try { const date = new Date(dateString); return date.toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch { return dateString; } } } // API publique window.OEDBEmbed = { init: function(config) { return new OEDBEmbed(config.container, config); } }; // Auto-initialisation si des éléments avec data-oedb-embed sont présents document.addEventListener('DOMContentLoaded', function() { const embedElements = document.querySelectorAll('[data-oedb-embed]'); embedElements.forEach(element => { const config = { container: element, ...JSON.parse(element.dataset.oedbEmbed || '{}') }; new OEDBEmbed(config.container, config); }); }); })();