Browse Source

feat: Ajouter des options de type de tuile dans le menu latéral avec vignettes SVG et gestion des événements

Ynats 2 months ago
parent
commit
f63ad3ed00

+ 81 - 0
.specstory/history/2025-10-13_09-29Z-ajout-d-options-de-type-de-tuile-dans-le-menu.md

@@ -0,0 +1,81 @@
+<!-- Generated by SpecStory -->
+
+<!-- vscode Session a9cc6ca7-2d24-4f00-807b-b997aa86db6d (2025-10-13 09:29Z) -->
+
+# Ajout d'options de type de tuile dans le menu (2025-10-13 09:29Z)
+
+_**User (2025-10-13 09:33Z)**_
+
+Dans la view : https://map-generator/?load=ID
+
+Peux tu ajouter dans le menu latéral, quand une tuile est sélectionnée les différentes propositions de type de tuile portée par une SVG correspondant
+
+---
+
+_**Assistant (copilot/gpt-5-mini)**_
+
+Je vais d'abord planifier les étapes avec la todo list, puis ouvrir la vue du menu latéral pour voir où insérer les options SVG. Objectif : repérer le fichier à modifier et préparer l'édition. Je vais maintenant créer la todo list. Je vais lire la vue `tools-sidebar.php` pour repérer où insérer le bloc d'options SVG et les IDs/classes nécessaires pour l'interaction JavaScript. Je m'attends à récupérer le markup existant pour faire une insertion propre. Je vais chercher les scripts JS liés à l'éditeur de carte pour voir où brancher la logique d'affichage et de sélection des types de tuile (probablement `map-editor.js` et `map-editor.js`). J'extrais aussi les handlers existants pour la sélection de tuile afin d'intégrer proprement l'UI. Je vais chercher dans les vues et les fichiers publics où `tileInfo` est rendu pour trouver l'emplacement exact et y insérer les options SVG. Je vais lancer une recherche dans le workspace. Je vais ouvrir la vue `home.php` pour modifier ou ajouter les options SVG dans le panel `#tileInfo`. Ensuite j'ajouterai un petit style et mettrai à jour le JS si nécessaire. Je vais appliquer les modifications suivantes en 3 étapes simultanées : (1) ajouter le bloc HTML pour les options SVG dans `tools-sidebar.php`, (2) modifier `map-editor.js` pour émettre/écouter des événements personnalisés et gérer le changement de type, (3) ajouter des styles CSS dans `styles.css`. Résultat attendu : le panneau latéral affiche des vignettes SVG cliquables quand une tuile est sélectionnée, et cliquer change l'affichage du type dans le panneau d'information. Je lance un patch appliquant ces trois modifications. 
+```
+
+```
+Progress update: j'ai ajouté le bloc HTML des options SVG dans la sidebar. Prochaine étape : modifier `map-editor.js` pour émettre un événement quand une tuile est sélectionnée et écouter les clics sur les options de type pour appliquer la modification (mettre à jour l'affichage du `#tileType` et émettre un événement custom pour d'éventuels handlers serveur). J'exécute maintenant un patch sur ce fichier. 
+```
+
+```
+Progress update: j'ai modifié `map-editor.js` pour émettre `mg:tileSelected` et `mg:tileInfoShown` et pour afficher le panneau `#tile-type-tool`. Prochaine étape : ajouter l'écoute côté client pour les clics sur `.tile-type-option` et appliquer la sélection au DOM et éventuellement au modèle `window.mapData`. Je vais ajouter un petit script en bas du même fichier pour attacher les listeners (ou créer un nouveau fichier JS si préférable). J'implémente l'ajout à la fin du fichier. 
+```
+
+```
+Progress update: Ajout des listeners pour cliquer sur les options de type et mettre à jour `window.mapData` et le DOM. Prochaine étape : ajouter quelques styles CSS pour rendre les vignettes agréables et accessible. J'appliquerai un patch sur `styles.css`. Je vais ajouter des styles pour `.tile-type-option` et `.tile-type-option svg` afin d'améliorer l'affichage des vignettes et l'état actif. J'appliquerai un patch sur `styles.css`. 
+```
+
+```
+Progress update: Ajout des styles pour les boutons SVG et l'état hover/active. Récapitulatif des changements effectués : (1) insertion du bloc HTML d'options SVG dans `tools-sidebar.php`, (2) modifications dans `map-editor.js` pour émettre événements et gérer les clics sur les options, (3) ajouts de styles dans `styles.css`. Prochaine étape : exécuter une vérification rapide des erreurs JS/PHP si possible — je vais lancer une recherche d'erreurs via le linter présent (get_errors tool) pour détecter problèmes syntaxiques. Actions effectuées — ce que j'ai fait ensuite
+- J'ai ajouté une UI dans la barre latérale pour afficher des propositions de type de tuile (avec petites vignettes SVG) quand une tuile est sélectionnée.
+- J'ai branché du JavaScript pour :
+  - émettre un événement global `mg:tileSelected` lors de la sélection d'une tuile,
+  - afficher le panneau d'options (`#tile-type-tool`) et mettre à jour le champ `#tileType` dans le panneau d'information,
+  - écouter les clics sur les vignettes et appliquer le type choisi dans `window.mapData.tiles[...]` (et émettre `mg:tileTypeChanged`).
+- J'ai ajouté des styles CSS pour les vignettes afin qu'elles soient lisibles et cliquables.
+
+Fichiers modifiés
+- `tools-sidebar.php`
+  - Ajout du bloc HTML contenant les boutons contenant des SVG pour les types : Neutre, Rural, Urbain.
+  - Le bloc est masqué par défaut (`style="display:none;"`, id `tile-type-tool`) et sera montré quand une tuile est sélectionnée.
+- `map-editor.js`
+  - Emission de `mg:tileSelected` quand une tuile est sélectionnée.
+  - showTileInfo récupère maintenant un type s'il existe dans `window.mapData.tiles` et affiche le panneau des types.
+  - Ajout d'un bloc JS en bas qui attache des listeners aux `.tile-type-option` : clique => met à jour `#tileType`, met à jour `window.mapData.tiles['q,r'].type` et émet `mg:tileTypeChanged`. Il stocke aussi la sélection dans `window.MG.selectedTile` à partir de l'événement `mg:tileSelected`.
+- `styles.css`
+  - Ajout de styles pour `.tile-type-option`, les SVG et état hover/active.
+
+Pourquoi c'est fait ainsi (contrat rapide)
+- Input : Sélection d'une tuile (click) dans la grille hexagonale.
+- Output attendu : panneau latéral présente des vignettes SVG cliquables ; cliquer change le type affiché et met à jour un stockage local (window.mapData).
+- Mode d'erreur : si window.mapData absent, on crée la structure minimale requise; erreurs JS sont attrapées et loggées.
+
+Cas limites traités
+- mapData absent : le code crée `window.mapData` / `window.mapData.tiles` si nécessaire.
+- Sélection de tuile non exposée via MG : le script tente de récupérer la tuile sélectionnée depuis `window.MG.selectedTile`, sinon par détection d'un hexagon dont le style correspond à la couleur de sélection.
+- Structures de tiles diverses : la lecture tente une recherche `tiles['q,r']` et un fallback (structures 2D) si possible.
+
+Comment tester localement (rapide)
+1. Ouvrir l'app localement (votre environnement MAMP déjà configuré).
+2. Charger une carte via l'URL comme indiqué (ex: https://map-generator/?load=ID).
+3. Cliquer sur une tuile hexagonale : le panneau d'information (`#tileInfo`) doit s'afficher et la section "Type de tuile" dans la sidebar doit apparaître.
+4. Cliquer sur une vignette (Neutre / Rural / Urbain) : le texte `#tileType` sera mis à jour et `window.mapData.tiles['q,r'].type` sera défini. Un événement `mg:tileTypeChanged` est aussi dispatché.
+
+Notes d'implémentation et suggestions / prochaines étapes
+- Persist (facultatif) : actuellement le changement s'applique uniquement en mémoire (window.mapData). Si vous voulez sauvegarder côté serveur, je peux ajouter un appel fetch/ajax pour poster la modification (endpoint API).
+- Feedback visuel : on pourrait marquer la vignette active (classe `active`) et synchroniser la sélection visuelle selon `window.mapData.tiles` au moment d'ouverture.
+- Tests : je peux ajouter un petit test JS (jest) pour vérifier que cliquer sur `.tile-type-option` met à jour `window.mapData` si vous voulez.
+- I18n : j'ai laissé les labels "Neutre / Rural / Urbain" en dur dans la view. Si vous voulez les localiser, je peux extraire ces chaînes vers vos fichiers de langue Laravel.
+
+Si vous voulez que je :
+- ajoute la persistance serveur (endpoint + appel fetch avec CSRF),
+- marque la vignette active en fonction du type courant,
+- traduise les labels en utilisant vos fichiers de langue,
+dites-moi lequel prioriser et je l'implémente directement.
+
+---
+

+ 35 - 0
app/Views/partials/tools-sidebar.php

@@ -21,5 +21,40 @@
                 <i class="bi bi-cursor me-2"></i>Sélection
             </a>
         </div>
+        <!-- Options de type de tuile (visible quand une tuile est sélectionnée) -->
+        <div class="tool-group" id="tile-type-tool" style="display:none;">
+            <h6><i class="bi bi-grid-1x2-fill me-1"></i>Type de tuile</h6>
+            <div id="tileTypeOptions" class="d-flex flex-column gap-2">
+                <!-- Boutons de types, chaque bouton contient une SVG représentative -->
+                <button type="button" class="btn btn-light tile-type-option d-flex align-items-center gap-2" data-type="neutral" title="Neutre" aria-label="Neutre">
+                    <!-- SVG simple pour neutre -->
+                    <svg width="36" height="24" viewBox="0 0 36 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+                        <rect x="1" y="1" width="34" height="22" rx="3" fill="#f8f9fa" stroke="#adb5bd"/>
+                        <circle cx="18" cy="12" r="6" fill="#6c757d" />
+                    </svg>
+                    <span>Neutre</span>
+                </button>
+
+                <button type="button" class="btn btn-light tile-type-option d-flex align-items-center gap-2" data-type="rural" title="Rural" aria-label="Rural">
+                    <!-- SVG simple pour rural -->
+                    <svg width="36" height="24" viewBox="0 0 36 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+                        <rect x="1" y="1" width="34" height="22" rx="3" fill="#fff7e6" stroke="#f0ad4e"/>
+                        <path d="M6 18 L12 10 L18 18 Z" fill="#5cb85c" />
+                    </svg>
+                    <span>Rural</span>
+                </button>
+
+                <button type="button" class="btn btn-light tile-type-option d-flex align-items-center gap-2" data-type="urban" title="Urbain" aria-label="Urbain">
+                    <!-- SVG simple pour urbain -->
+                    <svg width="36" height="24" viewBox="0 0 36 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
+                        <rect x="1" y="1" width="34" height="22" rx="3" fill="#e9ecef" stroke="#6c757d"/>
+                        <rect x="6" y="6" width="6" height="12" fill="#495057" />
+                        <rect x="14" y="8" width="6" height="10" fill="#6c757d" />
+                        <rect x="22" y="5" width="6" height="13" fill="#343a40" />
+                    </svg>
+                    <span>Urbain</span>
+                </button>
+            </div>
+        </div>
     </nav>
 </div>

+ 72 - 1
public/assets/js/editor/map-editor.js

@@ -157,6 +157,14 @@
                     hex.style.borderColor = '#1976d2';
                 }
 
+                // Émettre un événement DOM global pour signaler la sélection
+                try {
+                    const ev = new CustomEvent('mg:tileSelected', { detail: { q, r } });
+                    window.dispatchEvent(ev);
+                } catch (e) {
+                    // ignore
+                }
+
                 this.showTileInfo(q, r);
             }
 
@@ -167,7 +175,25 @@
                 const tileProperties = document.getElementById('tileProperties');
 
                 tilePosition.textContent = `Q: ${q}, R: ${r}`;
-                tileType.textContent = 'Vide';
+                // récupérer le type si défini dans mapData.tiles ou fallback
+                let type = 'Vide';
+                try {
+                    if (window.mapData && window.mapData.tiles) {
+                        const key = `${q},${r}`;
+                        const t = window.mapData.tiles[key] || (window.mapData.tiles[q] && window.mapData.tiles[q][r]);
+                        if (t && t.type) type = t.type;
+                    }
+                } catch (e) {
+                    // ignore
+                }
+                tileType.textContent = type;
+
+                // Montrer la sidebar des types de tuile
+                const tileTypeTool = document.getElementById('tile-type-tool');
+                if (tileTypeTool) tileTypeTool.style.display = 'block';
+
+                // émettre événement tileInfo affiché
+                try { window.dispatchEvent(new CustomEvent('mg:tileInfoShown', { detail: { q, r, type } })); } catch (e) {}
 
                 tileProperties.innerHTML = `...`;
                 tileInfo.style.display = 'block';
@@ -239,3 +265,48 @@
     });
 })();
 
+// Listeners pour la sidebar (gestion des types de tuile)
+(function(){
+    function onTypeClick(e){
+        const btn = e.currentTarget;
+        const type = btn.dataset.type;
+        // Mettre à jour l'affichage dans #tileType
+        const tileTypeEl = document.getElementById('tileType');
+        if (tileTypeEl) tileTypeEl.textContent = type;
+
+        // Si une tuile est sélectionnée on met à jour window.mapData (structure minimale)
+        try {
+            const selected = (window.MG && window.MG.selectedTile) || null;
+            // Certains setups n'exposent pas MG.selectedTile. On récupère depuis l'événement global si nécessaire
+            let q=null,r=null;
+            if (selected && typeof selected.q !== 'undefined') { q=selected.q; r=selected.r; }
+            // fallback: chercher un élément hexagon sélectionné
+            if (q===null) {
+                const el = document.querySelector('.hexagon[style*="background-color: rgb(33, 150, 243)"]') || document.querySelector('.hexagon[style*="#2196f3"]');
+                if (el) { q = parseInt(el.dataset.q,10); r = parseInt(el.dataset.r,10); }
+            }
+
+            if (q!==null && typeof q !== 'undefined'){
+                window.mapData = window.mapData || {};
+                window.mapData.tiles = window.mapData.tiles || {};
+                const key = `${q},${r}`;
+                window.mapData.tiles[key] = window.mapData.tiles[key] || {};
+                window.mapData.tiles[key].type = type;
+
+                // Dispatch event pour signaler changement de type
+                try { window.dispatchEvent(new CustomEvent('mg:tileTypeChanged', { detail: { q, r, type } })); } catch(e){}
+            }
+        } catch (err){ console.error('Erreur lors du changement de type de tuile', err); }
+    }
+
+    document.addEventListener('DOMContentLoaded', function(){
+        const opts = document.querySelectorAll('.tile-type-option');
+        opts.forEach(o => o.addEventListener('click', onTypeClick));
+
+        // Écouter la sélection de tuile pour stocker la sélection dans MG.selectedTile si possible
+        window.addEventListener('mg:tileSelected', function(ev){
+            try { window.MG = window.MG || {}; window.MG.selectedTile = ev.detail; } catch(e){}
+        });
+    });
+})();
+

+ 23 - 0
public/assets/styles.css

@@ -253,4 +253,27 @@ body {
    pour éviter qu'il touche le bord de l'écran */
 .dropdown-menu {
     margin-right: 5px;
+}
+
+/* Styles pour les options de type de tuile dans la sidebar */
+.tile-type-option {
+    justify-content: flex-start;
+    padding: 6px 8px;
+    border: 1px solid transparent;
+}
+
+.tile-type-option svg {
+    border-radius: 4px;
+    background: white;
+    box-shadow: 0 1px 2px rgba(0,0,0,0.05);
+}
+
+.tile-type-option.active, .tile-type-option:focus, .tile-type-option:hover {
+    border-color: rgba(0,0,0,0.08);
+    background-color: rgba(255,255,255,0.06);
+}
+
+.tile-type-option span {
+    font-size: 0.9rem;
+    color: #fff;
 }