GeoLeaf.Themes – Documentation du module Themes
Product Version: GeoLeaf Platform V2 Version: 2.0.0 Fichiers:
src/modules/built-in/themes/(themes-api, theme-selector, theme-loader, theme-applier/) Dernière mise à jour: mars 2026
📌 Vue d'ensemble
Le module GeoLeaf.Themes fournit un système avancé de gestion des thèmes visuels avec support des thèmes principaux (primaires) et thèmes secondaires. Il permet aux utilisateurs de personnaliser l'apparence de la carte via une interface intuitive (dropdown ou boutons).
Responsabilités principales
- ✅ Gestion multi-thèmes - Thèmes primaires + secondaires
- ✅ Chargement dynamique - Depuis la configuration de profil
- ✅ Application de styles - Modification des classes CSS de la carte
- ✅ UI intégrée - Sélecteurs de thèmes automatiques
- ✅ Persistance - Sauvegarde du thème choisi (localStorage)
- ✅ Événements - Notifications des changements de thème
🏗️ Architecture
Le module Themes est composé de 3 sous-modules complémentaires :
1. theme-selector.ts (561 lignes)
Contrôleur principal :
- Orchestration du système
- Gestion de l'état
- Rendu de l'interface utilisateur
- Gestion des événements UI
2. theme-loader.ts
Chargement de la configuration :
- Lecture des thèmes depuis
profile.json - Validation de la structure
- Classification (primaire/secondaire)
- Cache des thèmes
3. theme-applier/
Application des thèmes, éclaté en :
- core.js : État, init/cleanup, applyTheme orchestrator
- visibility.js : Visibilité couches, styles, labels
- deferred.js : Chargement différé, résolution URLs
- ui-sync.js : Synchronisation UI (sélecteur style, légende, fitBounds)
📚 API Publique
Themes.init(options?)
Initialise le système de thèmes pour une carte.
Signature :
GeoLeaf.Themes.init({ rememberChoice: true });Paramètres :
options(Object, optionnel) - options globales du module (ex:rememberChoice)
Retour :
- (Boolean) -
truesi initialisation réussie
Exemple :
GeoLeaf.Themes.init({ rememberChoice: true });Themes.applyTheme(layerId, themeId)
Change le thème actif.
Signature :
await GeoLeaf.Themes.applyTheme(layerId, themeId);Paramètres :
layerId(String) - ID de la couche ciblethemeId(String) - ID du thème à activer
Retour :
Promise<Boolean>-truesi changement réussi
Événements :
theme:changed- Émis après changement de thème
Exemple :
// Changer vers un thème spécifique
await GeoLeaf.Themes.applyTheme("poi", "dark");
// Écouter les changements
document.addEventListener("theme:changed", (e) => {
console.log("Nouveau thème:", e.detail.themeId);
});Themes.getCurrentTheme(layerId)
Récupère le thème actuellement actif.
Signature :
GeoLeaf.Themes.getCurrentTheme(layerId) : String|nullRetour :
- (String|null) - ID du thème actif, ou
nullsi aucun
Exemple :
const currentTheme = GeoLeaf.Themes.getCurrentTheme("poi");
console.log("Thème actif:", currentTheme);Themes.getAvailableThemes(layerId, options?)
Liste tous les thèmes disponibles.
Signature :
await GeoLeaf.Themes.getAvailableThemes(layerId, options?) : Theme[]Retour :
- (Array) - Liste des thèmes disponibles pour la couche
Exemple :
const themes = await GeoLeaf.Themes.getAvailableThemes("poi");
console.log("Thèmes disponibles:", themes);Themes.initializeLayerTheme(layerId, options?)
Nettoie le module et supprime les écouteurs.
Signature :
await GeoLeaf.Themes.initializeLayerTheme("poi", { rememberChoice: true });Exemple :
const selected = await GeoLeaf.Themes.initializeLayerTheme("poi", {
defaultTheme: "light",
rememberChoice: true,
});
console.log("Thème initial:", selected);
---
### `Themes.toggleTheme(layerId)`
Passe au thème suivant disponible pour la couche.
**Signature** :
```js
await GeoLeaf.Themes.toggleTheme(layerId);Themes.invalidateCache() et Themes.clearRememberedThemes()
Utilitaires de maintenance du cache et des préférences persistées.
Signatures :
GeoLeaf.Themes.invalidateCache();
GeoLeaf.Themes.clearRememberedThemes();
---
## 🎨 Configuration des thèmes (`themes.json`)
Les thèmes sont définis dans le fichier `profiles/{id}/themes.json` :
> **Note v2.0.0 :** Les thèmes sont externalisés dans `themes.json`. L'inline dans `profile.json` reste accepté pour la rétrocompatibilité.
### Structure complète
```json
{
"config": {
"defautTheme": "light",
"primaryThemes": true,
"secondaryThemes": true
},
"themes": [
{
"id": "light",
"label": "Défaut",
"type": "primary",
"layers": [
{ "id": "couche_a", "visible": true },
{ "id": "couche_b", "visible": false }
]
},
{
"id": "forets",
"label": "Forêts",
"type": "secondary",
"layers": [
{ "id": "couche_a", "visible": true },
{ "id": "couche_b", "visible": true }
]
}
]
}Propriétés config
| Propriété | Type | Description |
|---|---|---|
defautTheme | string | ID du thème actif au démarrage |
primaryThemes | boolean | Afficher les thèmes primaires dans l'UI |
secondaryThemes | boolean | Afficher les thèmes secondaires dans l'UI |
Propriétés d'un thème
| Propriété | Type | Description |
|---|---|---|
id | string | Identifiant unique du thème |
label | string | Nom affiché dans le sélecteur |
type | "primary" | "secondary" | Type du thème |
layers | array | Visibilité des couches (id, visible) |
💡 Exemples d'utilisation
Exemple 1 : Configuration simple (2 thèmes)
{
"themes": {
"default": "light",
"primary": [
{
"id": "light",
"label": "Jour",
"icon": "☀️",
"cssClass": "theme-light"
},
{
"id": "dark",
"label": "Nuit",
"icon": "🌙",
"cssClass": "theme-dark"
}
],
"ui": {
"position": "topright",
"style": "buttons"
}
}
}Exemple 2 : Thèmes avec hiérarchie
{
"themes": {
"default": "standard",
"primary": [
{
"id": "standard",
"label": "Standard",
"cssClass": "theme-standard"
}
],
"secondary": [
{
"id": "large-text",
"label": "Gros texte",
"cssClass": "theme-large-text",
"basedOn": "standard"
},
{
"id": "high-contrast",
"label": "Fort contraste",
"cssClass": "theme-high-contrast",
"basedOn": "standard"
}
]
}
}Exemple 3 : Changement programmatique
// Initialiser avec détection automatique
GeoLeaf.Themes.init(map, "tourism");
// Changer de thème selon l'heure
const hour = new Date().getHours();
const theme = hour >= 6 && hour < 18 ? "light" : "dark";
GeoLeaf.Themes.setTheme(theme);
// Changer selon préférence système
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
GeoLeaf.Themes.setTheme("dark");
}Exemple 4 : Thème avec options utilisateur
// Panneau de préférences personnalisé
const userPrefs = {
theme: "light",
largeText: false,
highContrast: false,
};
// Appliquer les préférences
let themeId = userPrefs.theme;
if (userPrefs.highContrast) {
themeId = "high-contrast";
} else if (userPrefs.largeText) {
themeId = "large-text";
}
GeoLeaf.Themes.setTheme(themeId);
// Sauvegarder les préférences
document.addEventListener("theme:changed", (e) => {
userPrefs.theme = e.detail.themeId;
localStorage.setItem("userPrefs", JSON.stringify(userPrefs));
});🎨 Création de thèmes CSS
Structure CSS recommandée
/* themes/my-theme.css */
/* Variables CSS pour le thème */
.theme-my-theme {
/* Couleurs principales */
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--color-background: #ffffff;
--color-text: #1f2937;
/* Couleurs de la carte */
--map-background: #f3f4f6;
--map-water: #93c5fd;
--map-land: #e5e7eb;
/* UI éléments */
--ui-panel-bg: rgba(255, 255, 255, 0.95);
--ui-panel-border: #d1d5db;
--ui-button-bg: #3b82f6;
--ui-button-text: #ffffff;
}
/* Styles spécifiques au thème */
.theme-my-theme .maplibregl-canvas-container {
background-color: var(--map-background);
}
.theme-my-theme .geoleaf-panel {
background: var(--ui-panel-bg);
border-color: var(--ui-panel-border);
color: var(--color-text);
}
.theme-my-theme .geoleaf-button {
background: var(--ui-button-bg);
color: var(--ui-button-text);
}
/* Marqueurs et POI */
.theme-my-theme .marker-icon {
filter: hue-rotate(30deg);
}Bonnes pratiques CSS
- Variables CSS : Utilisez des custom properties pour faciliter les ajustements
- Transitions : Ajoutez des transitions douces pour les changements
- Accessibilité : Assurez un contraste suffisant (WCAG AA minimum)
- Performance : Évitez les sélecteurs trop complexes
- Scope : Préfixez toutes les règles avec la classe du thème
🔧 Fonctionnement interne
1. État du module
const _state = {
initialized: false,
profileId: null,
config: null,
themes: [],
primaryThemes: [],
secondaryThemes: [],
currentTheme: null,
// Références UI
primaryContainer: null,
secondaryContainer: null,
dropdown: null,
// Event cleanup
_eventCleanups: [],
};2. Flux d'initialisation
sequenceDiagram
participant User
participant Themes
participant ThemeLoader
participant ThemeApplier
participant Profile
User->>Themes: init(map, profileId)
Themes->>Profile: getActiveProfile()
Profile-->>Themes: profileData
Themes->>ThemeLoader: loadThemes(profileData)
ThemeLoader-->>Themes: themes config
Themes->>Themes: _renderUI()
Themes->>ThemeApplier: apply(defaultTheme)
ThemeApplier-->>User: Theme applied3. Application d'un thème
// Pseudo-code simplifié
function applyTheme(themeId) {
const theme = findTheme(themeId);
if (!theme) return false;
// 1. Enlever les anciennes classes
_state.themes.forEach((t) => {
mapContainer.classList.remove(t.cssClass);
});
// 2. Appliquer la nouvelle classe
mapContainer.classList.add(theme.cssClass);
// 3. Charger CSS additionnel si nécessaire
if (theme.cssFile) {
loadStylesheet(theme.cssFile);
}
// 4. Mettre à jour l'état
_state.currentTheme = themeId;
localStorage.setItem("geoleaf-theme", themeId);
// 5. Émettre événement
document.dispatchEvent(
new CustomEvent("theme:changed", {
detail: { themeId, theme },
})
);
return true;
}🎛️ Rendu de l'interface
Style "dropdown" (menu déroulant)
<div class="maplibregl-ctrl theme-selector-control">
<button class="theme-selector-toggle">
<span class="theme-icon">☀️</span>
<span class="theme-label">Clair</span>
<span class="dropdown-arrow">▼</span>
</button>
<div class="theme-dropdown" style="display: none;">
<div class="theme-group primary">
<div class="theme-option" data-theme-id="light">☀️ Clair</div>
<div class="theme-option" data-theme-id="dark">🌙 Sombre</div>
</div>
<div class="theme-group secondary">
<div class="theme-option" data-theme-id="high-contrast">👁️ Contraste élevé</div>
</div>
</div>
</div>Style "buttons" (boutons individuels)
<div class="maplibregl-ctrl theme-selector-control buttons">
<button class="theme-button active" data-theme-id="light">
<span class="theme-icon">☀️</span>
<span class="theme-label">Clair</span>
</button>
<button class="theme-button" data-theme-id="dark">
<span class="theme-icon">🌙</span>
<span class="theme-label">Sombre</span>
</button>
</div>⚠️ Limitations et notes
1. Performance
- ✅ Changement de thème instantané (pas de rechargement)
- ⚠️ Le chargement de CSS externes peut prendre 100-300ms
2. Compatibilité
- ✅ Fonctionne avec tous les navigateurs modernes
- ✅ Support des thèmes système (prefers-color-scheme)
- ⚠️ Les thèmes personnalisés doivent être testés sur tous les navigateurs
3. Persistance
- ✅ Thème sauvegardé dans localStorage
- ✅ Récupération automatique au rechargement
- ⚠️ Effacé si l'utilisateur vide le cache
🔗 Modules liés
- GeoLeaf.Core - Fournit l'instance de carte
- GeoLeaf.UI - Intégration dans l'interface utilisateur
- GeoLeaf.Config - Lecture de la configuration de profil
📈 Améliorations futures
Prévues
- [ ] Import/export de thèmes personnalisés
- [ ] Éditeur de thèmes visuel
- [ ] Prévisualisation avant application
- [ ] Thèmes adaptatifs selon l'heure
En discussion
- [ ] Thèmes animés (transitions élaborées)
- [ ] Synchronisation multi-appareils
- [ ] Marketplace de thèmes communautaires
📝 Exemple complet
// 1. Configuration dans profile.json
const profile = {
themes: {
default: "light",
primary: [
{ id: "light", label: "Jour", icon: "☀️", cssClass: "theme-light" },
{ id: "dark", label: "Nuit", icon: "🌙", cssClass: "theme-dark" },
],
secondary: [
{
id: "sunset",
label: "Coucher de soleil",
icon: "🌅",
cssClass: "theme-sunset",
basedOn: "light",
},
],
ui: {
position: "topright",
style: "dropdown",
showLabels: true,
},
},
};
// 2. Initialiser GeoLeaf avec le profil
GeoLeaf.Config.load(profile);
// 3. Initialiser le sélecteur de thèmes
const map = GeoLeaf.Core.getMap();
GeoLeaf.Themes.init(map, "tourism");
// 4. Écouter les changements de thème
document.addEventListener("theme:changed", (e) => {
console.log("Thème changé:", e.detail.themeId);
// Synchroniser avec d'autres composants
updateChartColors(e.detail.theme);
});
// 5. Changer le thème selon l'heure
function autoSwitchTheme() {
const hour = new Date().getHours();
let themeId;
if (hour >= 6 && hour < 12) {
themeId = "light";
} else if (hour >= 12 && hour < 18) {
themeId = "light";
} else if (hour >= 18 && hour < 20) {
themeId = "sunset";
} else {
themeId = "dark";
}
GeoLeaf.Themes.setTheme(themeId);
}
// Exécuter au chargement et toutes les heures
autoSwitchTheme();
setInterval(autoSwitchTheme, 3600000);Dernière mise à jour : mars 2026 Version GeoLeaf : 2.0.0
