up embed, add export docs
This commit is contained in:
parent
912b4da08e
commit
b18c2c5aec
7 changed files with 414 additions and 33 deletions
|
|
@ -9,7 +9,8 @@
|
||||||
// Configuration par défaut
|
// Configuration par défaut
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
apiUrl: 'https://api.openenventdatabase.org',
|
apiUrl: 'https://api.openenventdatabase.org',
|
||||||
theme: 'light',
|
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,
|
limit: 50,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '400px',
|
height: '400px',
|
||||||
|
|
@ -42,12 +43,22 @@
|
||||||
this.container = typeof container === 'string' ? document.querySelector(container) : container;
|
this.container = typeof container === 'string' ? document.querySelector(container) : container;
|
||||||
this.config = { ...defaultConfig, ...config };
|
this.config = { ...defaultConfig, ...config };
|
||||||
// Fusionner les params si présents
|
// Fusionner les params si présents
|
||||||
if (config.params) {
|
if (config && config.params) {
|
||||||
this.config = { ...this.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.events = [];
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.refreshTimer = null;
|
this.refreshTimer = null;
|
||||||
|
this.map = null;
|
||||||
|
this.themeMediaQuery = null;
|
||||||
|
this.handleThemeChange = null;
|
||||||
|
|
||||||
if (!this.container) {
|
if (!this.container) {
|
||||||
console.error('OEDB Embed: Container not found');
|
console.error('OEDB Embed: Container not found');
|
||||||
|
|
@ -55,6 +66,51 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
this.init();
|
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() {
|
init() {
|
||||||
|
|
@ -68,7 +124,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
injectStyles() {
|
injectStyles() {
|
||||||
if (document.getElementById('oedb-embed-styles')) return;
|
// 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 theme = themes[this.config.theme] || themes.light;
|
||||||
const styles = `
|
const styles = `
|
||||||
|
|
@ -103,7 +163,7 @@
|
||||||
.oedb-embed-map {
|
.oedb-embed-map {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
background: #f8f9fa;
|
background: ${this.config.theme === 'dark' ? '#1a1a1a' : '#f8f9fa'};
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,14 +183,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.oedb-event-item {
|
.oedb-event-item {
|
||||||
|
display: block;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-bottom: 1px solid ${theme.border};
|
border-bottom: 1px solid ${theme.border};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oedb-event-item:hover {
|
.oedb-event-item:hover {
|
||||||
background: ${theme.border};
|
background: ${theme.border};
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oedb-event-title {
|
.oedb-event-title {
|
||||||
|
|
@ -158,8 +222,8 @@
|
||||||
.oedb-error {
|
.oedb-error {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #e74c3c;
|
color: ${this.config.theme === 'dark' ? '#ff6b6b' : '#e74c3c'};
|
||||||
background: #fdf2f2;
|
background: ${this.config.theme === 'dark' ? '#3d2525' : '#fdf2f2'};
|
||||||
}
|
}
|
||||||
|
|
||||||
.oedb-no-events {
|
.oedb-no-events {
|
||||||
|
|
@ -173,7 +237,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #f8f9fa;
|
background: ${this.config.theme === 'dark' ? '#1a1a1a' : '#f8f9fa'};
|
||||||
color: ${theme.secondary};
|
color: ${theme.secondary};
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
@ -253,26 +317,35 @@
|
||||||
const date = event.properties?.start || event.properties?.when || '';
|
const date = event.properties?.start || event.properties?.when || '';
|
||||||
const location = event.properties?.where || '';
|
const location = event.properties?.where || '';
|
||||||
const type = event.properties?.what || '';
|
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 `
|
return `
|
||||||
<div class="oedb-event-item" data-event-id="${event.id || ''}">
|
<a href="${eventUrl}" class="oedb-event-item" data-event-id="${eventId}" ${eventId ? '' : 'onclick="return false;"'}>
|
||||||
<div class="oedb-event-title">${this.escapeHtml(title)}</div>
|
<div class="oedb-event-title">${this.escapeHtml(title)}</div>
|
||||||
<div class="oedb-event-meta">
|
<div class="oedb-event-meta">
|
||||||
${date ? `<div>📅 ${this.formatDate(date)}</div>` : ''}
|
${date ? `<div>📅 ${this.formatDate(date)}</div>` : ''}
|
||||||
${location ? `<div>📍 ${this.escapeHtml(location)}</div>` : ''}
|
${location ? `<div>📍 ${this.escapeHtml(location)}</div>` : ''}
|
||||||
${type ? `<div>🏷️ ${this.escapeHtml(type)}</div>` : ''}
|
${type ? `<div>🏷️ ${this.escapeHtml(type)}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
`;
|
`;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
listContainer.innerHTML = eventsHtml;
|
listContainer.innerHTML = eventsHtml;
|
||||||
|
|
||||||
// Ajouter les événements de clic
|
// Ajouter les événements de clic pour l'événement personnalisé (optionnel)
|
||||||
listContainer.querySelectorAll('.oedb-event-item').forEach(item => {
|
listContainer.querySelectorAll('.oedb-event-item').forEach(item => {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('click', (e) => {
|
||||||
const eventId = item.dataset.eventId;
|
const eventId = item.dataset.eventId;
|
||||||
this.onEventClick(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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -286,24 +359,210 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pour l'instant, afficher un placeholder
|
// Initialiser MapLibre GL JS si disponible
|
||||||
// Dans une vraie implémentation, on utiliserait Leaflet ou une autre librairie de cartes
|
if (typeof maplibregl === 'undefined') {
|
||||||
mapContainer.innerHTML = `
|
this.initMapLibre().then(() => {
|
||||||
<div class="oedb-map-placeholder">
|
this.createMap(mapContainer);
|
||||||
Carte interactive<br>
|
}).catch(err => {
|
||||||
${this.events.length} événement(s) trouvé(s)
|
console.error('Failed to load MapLibre GL:', err);
|
||||||
</div>
|
mapContainer.innerHTML = `
|
||||||
`;
|
<div class="oedb-map-placeholder">
|
||||||
|
Carte interactive<br>
|
||||||
|
${this.events.length} événement(s) trouvé(s)<br>
|
||||||
|
<small>Chargement de la bibliothèque de cartes...</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} 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
|
||||||
|
? `<div style="padding: 8px;">
|
||||||
|
<strong><a href="${eventUrl}" style="color: inherit; text-decoration: none;">${props.label || 'Événement'}</a></strong><br>
|
||||||
|
${props.what ? `<small>${props.what}</small><br>` : ''}
|
||||||
|
${props.start ? `<small>📅 ${this.formatDate(props.start)}</small>` : ''}
|
||||||
|
${eventId ? `<br><small><a href="${eventUrl}" style="color: #667eea;">Voir les détails →</a></small>` : ''}
|
||||||
|
</div>`
|
||||||
|
: `<div style="padding: 8px;">
|
||||||
|
<strong>${props.label || 'Événement'}</strong><br>
|
||||||
|
${props.what ? `<small>${props.what}</small><br>` : ''}
|
||||||
|
${props.start ? `<small>📅 ${this.formatDate(props.start)}</small>` : ''}
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
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) {
|
onEventClick(eventId) {
|
||||||
// Émettre un événement personnalisé
|
// É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', {
|
const event = new CustomEvent('oedb-event-click', {
|
||||||
detail: { eventId, event: this.events.find(e => e.id === eventId) }
|
detail: { eventId, event: this.events.find(e => e.id === eventId) }
|
||||||
});
|
});
|
||||||
this.container.dispatchEvent(event);
|
this.container.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEventUrl(eventId) {
|
||||||
|
if (!eventId) return '#';
|
||||||
|
return `${this.config.homeUrl}?id=${encodeURIComponent(eventId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
startAutoRefresh() {
|
startAutoRefresh() {
|
||||||
if (this.refreshTimer) {
|
if (this.refreshTimer) {
|
||||||
clearInterval(this.refreshTimer);
|
clearInterval(this.refreshTimer);
|
||||||
|
|
@ -323,6 +582,18 @@
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.stopAutoRefresh();
|
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 = '';
|
this.container.innerHTML = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,21 @@ export class AllEvents implements OnInit, OnDestroy {
|
||||||
if (this.map) this.map.flyTo({ center: this.originalCoords, zoom: Math.max(this.map.getZoom() || 12, 12) });
|
if (this.map) this.map.flyTo({ center: this.originalCoords, zoom: Math.max(this.map.getZoom() || 12, 12) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centre la carte sur un point avec zoom
|
||||||
|
* @param coords Coordonnées [longitude, latitude]
|
||||||
|
* @param zoom Niveau de zoom (défaut: 14)
|
||||||
|
*/
|
||||||
|
centerOn(coords: [number, number], zoom: number = 14) {
|
||||||
|
if (this.map && coords && coords.length >= 2) {
|
||||||
|
this.map.flyTo({
|
||||||
|
center: coords,
|
||||||
|
zoom: zoom,
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateUrlFromMap() {
|
private updateUrlFromMap() {
|
||||||
if (!this.map) return;
|
if (!this.map) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="embed-view-container">
|
<div class="embed-view-container" [class.dark-theme]="isDarkTheme">
|
||||||
<div class="embed-header">
|
<div class="embed-header">
|
||||||
<h2>Événements OEDB</h2>
|
<h2>Événements OEDB</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<div class="embed-events-list">
|
<div class="embed-events-list">
|
||||||
@for (event of events; track event.id) {
|
@for (event of events; track event.id || event.properties?.id) {
|
||||||
<div class="embed-event-item">
|
<a [href]="getEventUrl(event)" class="embed-event-item" [attr.aria-label]="event.properties?.label || event.properties?.name || 'Événement sans nom'">
|
||||||
<div class="embed-event-header">
|
<div class="embed-event-header">
|
||||||
<h3 class="embed-event-title">
|
<h3 class="embed-event-title">
|
||||||
{{event.properties?.label || event.properties?.name || 'Événement sans nom'}}
|
{{event.properties?.label || event.properties?.name || 'Événement sans nom'}}
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
transition: background-color 0.3s ease, color 0.3s ease;
|
||||||
|
|
||||||
|
&.dark-theme {
|
||||||
|
background: #2c3e50;
|
||||||
|
color: #ecf0f1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-header {
|
.embed-header {
|
||||||
|
|
@ -40,6 +46,11 @@
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: spin 1s linear infinite;
|
animation: spin 1s linear infinite;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
border-color: #34495e;
|
||||||
|
border-top-color: #667eea;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
|
|
@ -50,10 +61,19 @@
|
||||||
.embed-error {
|
.embed-error {
|
||||||
color: #e74c3c;
|
color: #e74c3c;
|
||||||
background: #fdf2f2;
|
background: #fdf2f2;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
color: #ff6b6b;
|
||||||
|
background: #3d2525;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-empty {
|
.embed-empty {
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-events-list {
|
.embed-events-list {
|
||||||
|
|
@ -66,18 +86,33 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-event-item {
|
.embed-event-item {
|
||||||
|
display: block;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border: 1px solid #e9ecef;
|
border: 1px solid #e9ecef;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border-left: 4px solid #667eea;
|
border-left: 4px solid #667eea;
|
||||||
}
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
.embed-event-item:hover {
|
.dark-theme & {
|
||||||
background: #ffffff;
|
background: #34495e;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
border-color: #4a5568;
|
||||||
transform: translateX(2px);
|
|
||||||
|
&:hover {
|
||||||
|
background: #3d4a5e;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transform: translateX(2px);
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-event-header {
|
.embed-event-header {
|
||||||
|
|
@ -94,6 +129,10 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
color: #ecf0f1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-event-type {
|
.embed-event-type {
|
||||||
|
|
@ -115,12 +154,20 @@
|
||||||
.embed-event-date,
|
.embed-event-date,
|
||||||
.embed-event-location {
|
.embed-event-location {
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
color: #95a5a6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embed-event-description {
|
.embed-event-description {
|
||||||
color: #495057;
|
color: #495057;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.dark-theme & {
|
||||||
|
color: #bdc3c7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ import { ActivatedRoute } from '@angular/router';
|
||||||
import { OedbApi } from '../../../services/oedb-api';
|
import { OedbApi } from '../../../services/oedb-api';
|
||||||
|
|
||||||
interface OedbEvent {
|
interface OedbEvent {
|
||||||
id: string;
|
id?: string;
|
||||||
properties: {
|
properties: {
|
||||||
|
id?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
what?: string;
|
what?: string;
|
||||||
|
|
@ -31,13 +32,40 @@ export class EmbedView implements OnInit {
|
||||||
events: OedbEvent[] = [];
|
events: OedbEvent[] = [];
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
error: string | null = null;
|
error: string | null = null;
|
||||||
|
isDarkTheme = false;
|
||||||
|
homeUrl = '/';
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.queryParams.subscribe(params => {
|
this.route.queryParams.subscribe(params => {
|
||||||
|
// Détecter le thème depuis les query params ou préférence système
|
||||||
|
this.detectTheme(params);
|
||||||
this.loadEvents(params);
|
this.loadEvents(params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detectTheme(params: any) {
|
||||||
|
if (params.theme) {
|
||||||
|
this.isDarkTheme = params.theme === 'dark';
|
||||||
|
} else {
|
||||||
|
// Détecter depuis la préférence système
|
||||||
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||||
|
this.isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appliquer le thème au body
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.body.classList.toggle('dark-theme', this.isDarkTheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEventUrl(event: OedbEvent): string {
|
||||||
|
// L'ID peut être à la racine du Feature ou dans properties.id
|
||||||
|
const eventId = event.id || event.properties?.id;
|
||||||
|
if (!eventId) return '#';
|
||||||
|
return `${this.homeUrl}?id=${encodeURIComponent(eventId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
loadEvents(params: any) {
|
loadEvents(params: any) {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
}
|
}
|
||||||
@if (!showTable && !showUnlocatedList) {
|
@if (!showTable && !showUnlocatedList) {
|
||||||
<div class="map">
|
<div class="map">
|
||||||
<app-all-events [features]="filteredFeatures" [selected]="selected" [selectMode]="selectionMode"
|
<app-all-events #allEventsMap [features]="filteredFeatures" [selected]="selected" [selectMode]="selectionMode"
|
||||||
(selection)="onSelection($event)" (select)="onSelect($event)" (pickCoords)="onPickCoords($event)"
|
(selection)="onSelection($event)" (select)="onSelect($event)" (pickCoords)="onPickCoords($event)"
|
||||||
(mapMove)="onMapMove($event)"></app-all-events>
|
(mapMove)="onMapMove($event)"></app-all-events>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {Component, inject, signal, OnDestroy, OnInit} from '@angular/core';
|
import {Component, inject, signal, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {Router, RouterLink} from '@angular/router';
|
import {Router, RouterLink} from '@angular/router';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormsModule} from '@angular/forms';
|
||||||
import {Menu} from './menu/menu';
|
import {Menu} from './menu/menu';
|
||||||
|
|
@ -38,6 +38,8 @@ export class Home implements OnInit, OnDestroy {
|
||||||
router = inject(Router);
|
router = inject(Router);
|
||||||
private osmAuth = inject(OsmAuth);
|
private osmAuth = inject(OsmAuth);
|
||||||
|
|
||||||
|
@ViewChild('allEventsMap') allEventsMap!: AllEvents;
|
||||||
|
|
||||||
features: Array<any> = [];
|
features: Array<any> = [];
|
||||||
filteredFeatures: Array<any> = [];
|
filteredFeatures: Array<any> = [];
|
||||||
selected: any | null = null;
|
selected: any | null = null;
|
||||||
|
|
@ -273,6 +275,24 @@ export class Home implements OnInit, OnDestroy {
|
||||||
this.features = f ? [f] : [];
|
this.features = f ? [f] : [];
|
||||||
this.filteredFeatures = this.features;
|
this.filteredFeatures = this.features;
|
||||||
this.updateAvailableWhatTypes();
|
this.updateAvailableWhatTypes();
|
||||||
|
// Sélectionner automatiquement l'événement et ouvrir le panel d'édition
|
||||||
|
if (f) {
|
||||||
|
this.selected = f;
|
||||||
|
this.showEditForm = true;
|
||||||
|
this.showOptions = true; // Afficher aussi le panel d'options
|
||||||
|
|
||||||
|
// Zoomer sur le marqueur de l'événement si la géométrie est disponible
|
||||||
|
const geometry = f.geometry;
|
||||||
|
if (geometry && geometry.type === 'Point' && geometry.coordinates && geometry.coordinates.length >= 2) {
|
||||||
|
const [lng, lat] = geometry.coordinates;
|
||||||
|
// Attendre que la carte soit initialisée avant de zoomer
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.allEventsMap) {
|
||||||
|
this.allEventsMap.centerOn([lng, lat], 16); // Zoom élevé pour voir le marqueur de près
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue