|
|
@@ -17,51 +17,128 @@ include __DIR__ . '/partials/header.php';
|
|
|
Éditeur de carte
|
|
|
</h5>
|
|
|
</div>
|
|
|
- <div class="card-body">
|
|
|
- <!-- Zone indiquant qu'aucun projet n'est chargé -->
|
|
|
- <div class="editor-empty border d-flex align-items-center justify-content-center" style="width:100%; height:600px; background-color:#e9ecef;">
|
|
|
- <div class="text-center">
|
|
|
- <!-- Illustration SVG représentant une carte vide -->
|
|
|
- <svg role="img" aria-label="Illustration : cadriage avec point d'attention" width="200" height="140" viewBox="0 0 200 140" xmlns="http://www.w3.org/2000/svg" class="mx-auto d-block">
|
|
|
- <defs>
|
|
|
- <linearGradient id="g" x1="0" x2="1">
|
|
|
- <stop offset="0" stop-color="#f8f9fa"/>
|
|
|
- <stop offset="1" stop-color="#e9ecef"/>
|
|
|
- </linearGradient>
|
|
|
- </defs>
|
|
|
- <rect x="8" y="12" width="184" height="116" rx="8" fill="url(#g)" stroke="#ced4da" />
|
|
|
- <!-- Grid lines -->
|
|
|
- <g stroke="#adb5bd" stroke-width="1">
|
|
|
- <line x1="20" y1="20" x2="180" y2="20"/>
|
|
|
- <line x1="20" y1="40" x2="180" y2="40"/>
|
|
|
- <line x1="20" y1="60" x2="180" y2="60"/>
|
|
|
- <line x1="20" y1="80" x2="180" y2="80"/>
|
|
|
- <line x1="20" y1="100" x2="180" y2="100"/>
|
|
|
- <line x1="20" y1="120" x2="180" y2="120"/>
|
|
|
- <line x1="20" y1="20" x2="20" y2="120"/>
|
|
|
- <line x1="60" y1="20" x2="60" y2="120"/>
|
|
|
- <line x1="100" y1="20" x2="100" y2="120"/>
|
|
|
- <line x1="140" y1="20" x2="140" y2="120"/>
|
|
|
- <line x1="180" y1="20" x2="180" y2="120"/>
|
|
|
- </g>
|
|
|
- <!-- Point d'attention -->
|
|
|
- <rect x="95" y="55" width="10" height="10" fill="#dc3545" stroke="#b02a37" rx="2"/>
|
|
|
- <text x="100" y="50" text-anchor="middle" font-size="8" fill="#dc3545" font-weight="bold">!</text>
|
|
|
- </svg>
|
|
|
+ <div class="card-body">
|
|
|
+ <?php
|
|
|
+ $loaded_map = $loaded_map ?? ($GLOBALS['view_data']['loaded_map'] ?? null);
|
|
|
+ $map_data = $map_data ?? ($GLOBALS['view_data']['map_data'] ?? null);
|
|
|
+ ?>
|
|
|
+ <?php if (isset($loaded_map) && $loaded_map): ?>
|
|
|
+ <!-- Interface de la carte chargée -->
|
|
|
+ <div class="alert alert-success">Carte chargée avec succès !</div>
|
|
|
+ <!-- Interface de la carte chargée -->
|
|
|
+ <div class="map-editor-container">
|
|
|
+ <!-- Barre d'outils de la carte -->
|
|
|
+ <div class="map-toolbar d-flex justify-content-between align-items-center mb-3">
|
|
|
+ <div class="d-flex align-items-center gap-2">
|
|
|
+ <h6 class="mb-0">
|
|
|
+ <i class="bi bi-map me-2"></i><?php echo htmlspecialchars($map_data['name']); ?>
|
|
|
+ </h6>
|
|
|
+ <span class="badge bg-info">
|
|
|
+ <?php echo $map_data['width']; ?> × <?php echo $map_data['height']; ?>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="d-flex align-items-center gap-2">
|
|
|
+ <small class="text-muted">
|
|
|
+ Zoom: <span id="zoomLevel">100</span>%
|
|
|
+ </small>
|
|
|
+ <div class="btn-group btn-group-sm">
|
|
|
+ <button class="btn btn-outline-secondary" id="zoomOut" title="Zoom arrière">
|
|
|
+ <i class="bi bi-zoom-out"></i>
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-outline-secondary" id="zoomIn" title="Zoom avant">
|
|
|
+ <i class="bi bi-zoom-in"></i>
|
|
|
+ </button>
|
|
|
+ <button class="btn btn-outline-secondary" id="fitToScreen" title="Adapter à l'écran">
|
|
|
+ <i class="bi bi-arrows-fullscreen"></i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Zone de la carte interactive -->
|
|
|
+ <div class="map-canvas-container border" style="position: relative; overflow: hidden; height: 600px; background-color: #f8f9fa;">
|
|
|
+ <div id="mapCanvas" style="position: absolute; top: 0; left: 0; cursor: grab;">
|
|
|
+ <!-- La carte sera rendue ici via JavaScript -->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Indicateur de chargement -->
|
|
|
+ <div id="mapLoading" class="d-flex align-items-center justify-content-center" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(255,255,255,0.8);">
|
|
|
+ <div class="text-center">
|
|
|
+ <div class="spinner-border text-primary mb-2" role="status">
|
|
|
+ <span class="visually-hidden">Chargement...</span>
|
|
|
+ </div>
|
|
|
+ <p class="mb-0">Chargement de la carte...</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- Informations de la tuile sélectionnée -->
|
|
|
+ <div id="tileInfo" class="mt-3 p-3 bg-light rounded" style="display: none;">
|
|
|
+ <h6 class="mb-2">
|
|
|
+ <i class="bi bi-info-circle me-1"></i>Propriétés de la parcelle
|
|
|
+ </h6>
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-md-6">
|
|
|
+ <small class="text-muted">Position</small>
|
|
|
+ <p class="mb-1" id="tilePosition">Q: 0, R: 0</p>
|
|
|
+ </div>
|
|
|
+ <div class="col-md-6">
|
|
|
+ <small class="text-muted">Type</small>
|
|
|
+ <p class="mb-1" id="tileType">Vide</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div id="tileProperties" class="mt-2">
|
|
|
+ <!-- Les propriétés de la tuile seront affichées ici -->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <?php else: ?>
|
|
|
+ <!-- Zone indiquant qu'aucun projet n'est chargé -->
|
|
|
+ <div class="alert alert-info">Aucune carte chargée</div>
|
|
|
+ <div class="editor-empty border d-flex align-items-center justify-content-center" style="width:100%; height:600px; background-color:#e9ecef;">
|
|
|
+ <div class="text-center">
|
|
|
+ <!-- Illustration SVG représentant une carte vide -->
|
|
|
+ <svg role="img" aria-label="Illustration : cadriage avec point d'attention" width="200" height="140" viewBox="0 0 200 140" xmlns="http://www.w3.org/2000/svg" class="mx-auto d-block">
|
|
|
+ <defs>
|
|
|
+ <linearGradient id="g" x1="0" x2="1">
|
|
|
+ <stop offset="0" stop-color="#f8f9fa"/>
|
|
|
+ <stop offset="1" stop-color="#e9ecef"/>
|
|
|
+ </linearGradient>
|
|
|
+ </defs>
|
|
|
+ <rect x="8" y="12" width="184" height="116" rx="8" fill="url(#g)" stroke="#ced4da" />
|
|
|
+ <!-- Grid lines -->
|
|
|
+ <g stroke="#adb5bd" stroke-width="1">
|
|
|
+ <line x1="20" y1="20" x2="180" y2="20"/>
|
|
|
+ <line x1="20" y1="40" x2="180" y2="40"/>
|
|
|
+ <line x1="20" y1="60" x2="180" y2="60"/>
|
|
|
+ <line x1="20" y1="80" x2="180" y2="80"/>
|
|
|
+ <line x1="20" y1="100" x2="180" y2="100"/>
|
|
|
+ <line x1="20" y1="120" x2="180" y2="120"/>
|
|
|
+ <line x1="20" y1="20" x2="20" y2="120"/>
|
|
|
+ <line x1="60" y1="20" x2="60" y2="120"/>
|
|
|
+ <line x1="100" y1="20" x2="100" y2="120"/>
|
|
|
+ <line x1="140" y1="20" x2="140" y2="120"/>
|
|
|
+ <line x1="180" y1="20" x2="180" y2="120"/>
|
|
|
+ </g>
|
|
|
+ <!-- Point d'attention -->
|
|
|
+ <rect x="95" y="55" width="10" height="10" fill="#dc3545" stroke="#b02a37" rx="2"/>
|
|
|
+ <text x="100" y="50" text-anchor="middle" font-size="8" fill="#dc3545" font-weight="bold">!</text>
|
|
|
+ </svg>
|
|
|
|
|
|
- <!-- Texte principal -->
|
|
|
- <h5 class="mt-3 mb-1">Aucun projet chargé</h5>
|
|
|
+ <!-- Texte principal -->
|
|
|
+ <h5 class="mt-3 mb-1">Aucun projet chargé</h5>
|
|
|
|
|
|
- <!-- Explication -->
|
|
|
- <p class="text-muted small mb-3">Créez un nouveau projet ou chargez-en un existant pour commencer à éditer une carte.</p>
|
|
|
+ <!-- Explication -->
|
|
|
+ <p class="text-muted small mb-3">Créez un nouveau projet ou chargez-en un existant pour commencer à éditer une carte.</p>
|
|
|
|
|
|
- <!-- Actions rapides -->
|
|
|
- <div class="d-flex justify-content-center gap-2">
|
|
|
- <a href="/projects/new" class="btn btn-primary btn-sm">Nouveau projet</a>
|
|
|
- <a href="/projects" class="btn btn-outline-secondary btn-sm">Charger un projet</a>
|
|
|
+ <!-- Actions rapides -->
|
|
|
+ <div class="d-flex justify-content-center gap-2">
|
|
|
+ <a href="/projects/new" class="btn btn-primary btn-sm">Nouveau projet</a>
|
|
|
+ <a href="/projects" class="btn btn-outline-secondary btn-sm">Charger un projet</a>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ <?php endif; ?>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -119,6 +196,257 @@ include __DIR__ . '/partials/header.php';
|
|
|
</div>
|
|
|
</main>
|
|
|
|
|
|
+<?php if ($loaded_map): ?>
|
|
|
+<script>
|
|
|
+// Données de la carte
|
|
|
+const mapData = <?php echo json_encode($map_data); ?>;
|
|
|
+const mapWidth = mapData.width;
|
|
|
+const mapHeight = mapData.height;
|
|
|
+
|
|
|
+// Configuration de l'éditeur de carte
|
|
|
+class MapEditor {
|
|
|
+ constructor() {
|
|
|
+ this.canvas = document.getElementById('mapCanvas');
|
|
|
+ this.container = document.querySelector('.map-canvas-container');
|
|
|
+ this.zoomLevel = 1;
|
|
|
+ this.panOffset = { x: 0, y: 0 };
|
|
|
+ this.isDragging = false;
|
|
|
+ this.lastMousePos = { x: 0, y: 0 };
|
|
|
+ this.selectedTile = null;
|
|
|
+ this.tileSize = 30; // Taille de base d'une tuile hexagonale
|
|
|
+
|
|
|
+ this.init();
|
|
|
+ }
|
|
|
+
|
|
|
+ init() {
|
|
|
+ this.setupCanvas();
|
|
|
+ this.renderMap();
|
|
|
+ this.setupEventListeners();
|
|
|
+ this.hideLoading();
|
|
|
+ this.fitToScreen();
|
|
|
+ }
|
|
|
+
|
|
|
+ setupCanvas() {
|
|
|
+ // Calculer la taille du canvas
|
|
|
+ const hexWidth = this.tileSize * 1.5;
|
|
|
+ const hexHeight = this.tileSize * Math.sqrt(3);
|
|
|
+
|
|
|
+ const canvasWidth = (mapWidth + 0.5) * hexWidth;
|
|
|
+ const canvasHeight = (mapHeight + 0.5) * hexHeight;
|
|
|
+
|
|
|
+ this.canvas.style.width = canvasWidth + 'px';
|
|
|
+ this.canvas.style.height = canvasHeight + 'px';
|
|
|
+ this.canvas.style.transformOrigin = '0 0';
|
|
|
+ }
|
|
|
+
|
|
|
+ renderMap() {
|
|
|
+ this.canvas.innerHTML = '';
|
|
|
+
|
|
|
+ for (let q = 0; q < mapWidth; q++) {
|
|
|
+ for (let r = 0; r < mapHeight; r++) {
|
|
|
+ this.renderHexagon(q, r);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ renderHexagon(q, r) {
|
|
|
+ const hex = document.createElement('div');
|
|
|
+ hex.className = 'hexagon';
|
|
|
+ hex.dataset.q = q;
|
|
|
+ hex.dataset.r = r;
|
|
|
+
|
|
|
+ // Positionner l'hexagone
|
|
|
+ const x = q * this.tileSize * 1.5;
|
|
|
+ const y = r * this.tileSize * Math.sqrt(3) + (q % 2) * this.tileSize * Math.sqrt(3) / 2;
|
|
|
+
|
|
|
+ hex.style.left = x + 'px';
|
|
|
+ hex.style.top = y + 'px';
|
|
|
+ hex.style.width = this.tileSize + 'px';
|
|
|
+ hex.style.height = this.tileSize + 'px';
|
|
|
+
|
|
|
+ // Style de base
|
|
|
+ hex.style.position = 'absolute';
|
|
|
+ hex.style.border = '1px solid #ddd';
|
|
|
+ hex.style.backgroundColor = '#fff';
|
|
|
+ hex.style.cursor = 'pointer';
|
|
|
+ hex.style.transition = 'background-color 0.2s';
|
|
|
+
|
|
|
+ // Créer la forme hexagonale avec CSS
|
|
|
+ hex.style.clipPath = 'polygon(50% 0%, 93.3% 25%, 93.3% 75%, 50% 100%, 6.7% 75%, 6.7% 25%)';
|
|
|
+
|
|
|
+ // Gestionnaire de clic
|
|
|
+ hex.addEventListener('click', (e) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ this.selectTile(q, r);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Gestionnaire de survol
|
|
|
+ hex.addEventListener('mouseenter', () => {
|
|
|
+ if (!this.selectedTile || this.selectedTile.q !== q || this.selectedTile.r !== r) {
|
|
|
+ hex.style.backgroundColor = '#e3f2fd';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ hex.addEventListener('mouseleave', () => {
|
|
|
+ if (!this.selectedTile || this.selectedTile.q !== q || this.selectedTile.r !== r) {
|
|
|
+ hex.style.backgroundColor = '#fff';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this.canvas.appendChild(hex);
|
|
|
+ }
|
|
|
+
|
|
|
+ selectTile(q, r) {
|
|
|
+ // Désélectionner la tuile précédente
|
|
|
+ if (this.selectedTile) {
|
|
|
+ const prevHex = this.canvas.querySelector(`[data-q="${this.selectedTile.q}"][data-r="${this.selectedTile.r}"]`);
|
|
|
+ if (prevHex) {
|
|
|
+ prevHex.style.backgroundColor = '#fff';
|
|
|
+ prevHex.style.borderColor = '#ddd';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Sélectionner la nouvelle tuile
|
|
|
+ this.selectedTile = { q, r };
|
|
|
+ const hex = this.canvas.querySelector(`[data-q="${q}"][data-r="${r}"]`);
|
|
|
+ if (hex) {
|
|
|
+ hex.style.backgroundColor = '#2196f3';
|
|
|
+ hex.style.borderColor = '#1976d2';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Afficher les informations de la tuile
|
|
|
+ this.showTileInfo(q, r);
|
|
|
+ }
|
|
|
+
|
|
|
+ showTileInfo(q, r) {
|
|
|
+ const tileInfo = document.getElementById('tileInfo');
|
|
|
+ const tilePosition = document.getElementById('tilePosition');
|
|
|
+ const tileType = document.getElementById('tileType');
|
|
|
+ const tileProperties = document.getElementById('tileProperties');
|
|
|
+
|
|
|
+ tilePosition.textContent = `Q: ${q}, R: ${r}`;
|
|
|
+ tileType.textContent = 'Vide'; // Par défaut
|
|
|
+
|
|
|
+ // TODO: Récupérer les vraies propriétés de la tuile depuis les données
|
|
|
+ tileProperties.innerHTML = `
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-md-6">
|
|
|
+ <label class="form-label small">Type de terrain</label>
|
|
|
+ <select class="form-select form-select-sm">
|
|
|
+ <option>Vide</option>
|
|
|
+ <option>Forêt</option>
|
|
|
+ <option>Montagne</option>
|
|
|
+ <option>Eau</option>
|
|
|
+ <option>Ville</option>
|
|
|
+ <option>Route</option>
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+ <div class="col-md-6">
|
|
|
+ <label class="form-label small">Élévation</label>
|
|
|
+ <input type="number" class="form-control form-control-sm" placeholder="0">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-2">
|
|
|
+ <label class="form-label small">Ressources</label>
|
|
|
+ <div class="input-group input-group-sm">
|
|
|
+ <input type="text" class="form-control" placeholder="Ajouter une ressource...">
|
|
|
+ <button class="btn btn-outline-secondary" type="button">+</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+
|
|
|
+ tileInfo.style.display = 'block';
|
|
|
+ }
|
|
|
+
|
|
|
+ setupEventListeners() {
|
|
|
+ // Zoom
|
|
|
+ document.getElementById('zoomIn').addEventListener('click', () => this.zoom(1.2));
|
|
|
+ document.getElementById('zoomOut').addEventListener('click', () => this.zoom(0.8));
|
|
|
+ document.getElementById('fitToScreen').addEventListener('click', () => this.fitToScreen());
|
|
|
+
|
|
|
+ // Déplacement (pan)
|
|
|
+ this.container.addEventListener('mousedown', (e) => {
|
|
|
+ if (e.target === this.container) {
|
|
|
+ this.isDragging = true;
|
|
|
+ this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
|
+ this.canvas.style.cursor = 'grabbing';
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.addEventListener('mousemove', (e) => {
|
|
|
+ if (this.isDragging) {
|
|
|
+ const deltaX = e.clientX - this.lastMousePos.x;
|
|
|
+ const deltaY = e.clientY - this.lastMousePos.y;
|
|
|
+
|
|
|
+ this.panOffset.x += deltaX;
|
|
|
+ this.panOffset.y += deltaY;
|
|
|
+
|
|
|
+ this.updateTransform();
|
|
|
+
|
|
|
+ this.lastMousePos = { x: e.clientX, y: e.clientY };
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ document.addEventListener('mouseup', () => {
|
|
|
+ this.isDragging = false;
|
|
|
+ this.canvas.style.cursor = 'grab';
|
|
|
+ });
|
|
|
+
|
|
|
+ // Zoom avec la molette
|
|
|
+ this.container.addEventListener('wheel', (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+ const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
|
|
|
+ this.zoom(zoomFactor);
|
|
|
+ });
|
|
|
+
|
|
|
+ // Désélectionner en cliquant sur le fond
|
|
|
+ this.container.addEventListener('click', () => {
|
|
|
+ if (this.selectedTile) {
|
|
|
+ this.selectTile(null, null);
|
|
|
+ document.getElementById('tileInfo').style.display = 'none';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ zoom(factor) {
|
|
|
+ this.zoomLevel *= factor;
|
|
|
+ this.zoomLevel = Math.max(0.1, Math.min(5, this.zoomLevel)); // Limiter le zoom entre 0.1x et 5x
|
|
|
+ this.updateTransform();
|
|
|
+ document.getElementById('zoomLevel').textContent = Math.round(this.zoomLevel * 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ fitToScreen() {
|
|
|
+ const containerRect = this.container.getBoundingClientRect();
|
|
|
+ const canvasRect = this.canvas.getBoundingClientRect();
|
|
|
+
|
|
|
+ const scaleX = containerRect.width / canvasRect.width;
|
|
|
+ const scaleY = containerRect.height / canvasRect.height;
|
|
|
+ const scale = Math.min(scaleX, scaleY) * 0.9; // Laisser un peu de marge
|
|
|
+
|
|
|
+ this.zoomLevel = scale;
|
|
|
+ this.panOffset = { x: 0, y: 0 };
|
|
|
+ this.updateTransform();
|
|
|
+ document.getElementById('zoomLevel').textContent = Math.round(this.zoomLevel * 100);
|
|
|
+ }
|
|
|
+
|
|
|
+ updateTransform() {
|
|
|
+ this.canvas.style.transform = `translate(${this.panOffset.x}px, ${this.panOffset.y}px) scale(${this.zoomLevel})`;
|
|
|
+ }
|
|
|
+
|
|
|
+ hideLoading() {
|
|
|
+ document.getElementById('mapLoading').style.display = 'none';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Initialiser l'éditeur de carte quand le DOM est chargé
|
|
|
+document.addEventListener('DOMContentLoaded', function() {
|
|
|
+ if (typeof mapData !== 'undefined') {
|
|
|
+ new MapEditor();
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+<?php endif; ?>
|
|
|
+
|
|
|
<?php
|
|
|
include __DIR__ . '/partials/footer.php';
|
|
|
?>
|