Mudanças entre as edições de "Widget:MapViewer.js"

De Wiki Gla
Ir para navegação Ir para pesquisar
(reajuste do script)
Linha 1: Linha 1:
<includeonly>
<includeonly><div id="mapa-viewer-{{{id|mapa1}}}" class="mapa-container" style="width:{{{largura|100%}}}; height:{{{altura|500px}}}; background:#0f172a; border-radius:12px; overflow:hidden; position:relative;">
<script type="text/javascript">
    <div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#64748b; text-align:center;">
/**
        <div>🗺️ Carregando mapa...</div>
* MapViewer Widget para MediaWiki - Versão Simplificada
        <div style="font-size:12px; margin-top:8px;">{{{nome|Mapa}}}</div>
* Uso: {{#widget:MapViewer|json=JSON_do_mapa|width=800|height=600}}
    </div>
*/
</div>


<script>
// @noescape
(function() {
(function() {
     // Evitar múltiplas execuções
     var containerId = 'mapa-viewer-{{{id|mapa1}}}';
     if (window.MWMapViewerLoaded) return;
     var container = document.getElementById(containerId);
     window.MWMapViewerLoaded = true;
     if (!container) return;
      
      
     // ==================== ESTILOS ====================
     // Receber configuração do template
     const styles = `
     var configJSON = `{{{json}}}`;
        .mw-map-container {
    var mapConfig;
            position: relative;
   
            background: #1e1e2e;
    try {
            border-radius: 8px;
         mapConfig = JSON.parse(configJSON);
            overflow: hidden;
    } catch(e) {
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
         container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">❌ Erro na configuração do mapa</div>';
        }
         console.error('Erro ao parsear JSON:', e);
        .mw-map-container .mw-viewport {
         return;
            width: 100%;
    }
            height: 100%;
   
            overflow: auto;
    // Inicializar o viewer
            cursor: grab;
    iniciarMapaViewer(containerId, mapConfig);
            background: #0f0f1a;
        }
        .mw-map-container .mw-viewport:active {
            cursor: grabbing;
        }
        .mw-map-container .mw-layers {
            position: relative;
            min-width: 100%;
            min-height: 100%;
        }
        .mw-map-container .mw-layer {
            position: absolute;
            top: 0;
            left: 0;
        }
        .mw-map-container .mw-image {
            display: block;
            pointer-events: none;
         }
        .mw-map-container .mw-marker {
            position: absolute;
            cursor: pointer;
            transform: translate(-50%, -50%);
            transition: transform 0.1s;
            z-index: 100;
        }
        .mw-map-container .mw-marker:hover {
            transform: translate(-50%, -50%) scale(1.2);
            z-index: 101;
        }
        .mw-map-container .mw-marker-icon {
            font-size: 20px;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
        }
         .mw-map-container .mw-marker-tooltip {
            position: absolute;
            bottom: 100%;
            left: 50%;
            transform: translateX(-50%);
            background: #1e1e2e;
            color: white;
            padding: 4px 8px;
            border-radius: 6px;
            font-size: 11px;
            white-space: nowrap;
            opacity: 0;
            visibility: hidden;
            transition: 0.2s;
            pointer-events: none;
            margin-bottom: 5px;
        }
        .mw-map-container .mw-marker:hover .mw-marker-tooltip {
            opacity: 1;
            visibility: visible;
        }
        .mw-map-container .mw-badge {
            position: absolute;
            top: -8px;
            right: -8px;
            background: #ef4444;
            color: white;
            font-size: 9px;
            min-width: 16px;
            height: 16px;
            border-radius: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 0 4px;
        }
        .mw-map-container .mw-badge.hidden {
            display: none;
        }
        .mw-map-container .mw-toolbar {
            position: absolute;
            top: 10px;
            left: 10px;
            right: 10px;
            display: flex;
            justify-content: space-between;
            z-index: 100;
            gap: 10px;
        }
        .mw-map-container .mw-controls {
            display: flex;
            gap: 5px;
            background: rgba(0,0,0,0.6);
            padding: 5px 10px;
            border-radius: 30px;
        }
        .mw-map-container .mw-controls button {
            background: #334155;
            border: none;
            color: white;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 16px;
        }
        .mw-map-container .mw-controls button:hover {
            background: #6366f1;
        }
        .mw-map-container .mw-floor-info {
            background: rgba(0,0,0,0.6);
            padding: 5px 15px;
            border-radius: 30px;
            color: white;
            font-size: 12px;
        }
         .mw-map-container .mw-zoom-level {
            background: rgba(0,0,0,0.6);
            padding: 5px 12px;
            border-radius: 30px;
            color: #a5b4fc;
            font-size: 12px;
        }
        .mw-map-container .mw-nav {
            position: absolute;
            bottom: 20px;
            left: 20px;
            display: flex;
            gap: 8px;
            z-index: 100;
        }
        .mw-map-container .mw-nav button {
            background: rgba(0,0,0,0.7);
            border: none;
            color: white;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            cursor: pointer;
            font-size: 18px;
         }
        .mw-map-container .mw-nav button:hover {
            background: #6366f1;
        }
        .mw-map-container .mw-coords {
            position: absolute;
            bottom: 10px;
            right: 10px;
            background: rgba(0,0,0,0.5);
            padding: 4px 10px;
            border-radius: 20px;
            color: #a5b4fc;
            font-size: 10px;
            font-family: monospace;
            z-index: 100;
        }
        .mw-map-popup {
            display: none;
            position: fixed;
            z-index: 10000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background: rgba(0,0,0,0.7);
            justify-content: center;
            align-items: center;
        }
        .mw-map-popup .mw-popup-content {
            background: #1e1e2e;
            border-radius: 12px;
            max-width: 350px;
            width: 90%;
            padding: 20px;
            position: relative;
            border: 1px solid #334155;
        }
        .mw-map-popup .mw-popup-close {
            position: absolute;
            right: 15px;
            top: 10px;
            font-size: 24px;
            cursor: pointer;
            color: #94a3b8;
        }
        .mw-map-popup .mw-popup-close:hover {
            color: #ef4444;
        }
        .mw-map-popup h3 {
            color: #a5b4fc;
            margin: 0 0 10px 0;
        }
        .mw-map-popup p {
            color: #cbd5e1;
            margin: 0;
            line-height: 1.5;
        }
    `;
      
      
     // ==================== CLASSE DO MAPA ====================
     // ==================== VIEWER COMPLETO ====================
     class MWMap {
     function iniciarMapaViewer(containerId, config) {
        constructor(container, config) {
        var container = document.getElementById(containerId);
            this.container = container;
        if (!container) return;
            this.config = config;
           
            // Configurações do mapa
            this.zoom = config.mapConfig?.defaultZoom || 1;
            this.currentFloor = config.mapConfig?.initialFloor || 0;
            this.minZoom = config.mapConfig?.minZoom || 0.5;
            this.maxZoom = config.mapConfig?.maxZoom || 3;
            this.zoomStep = config.mapConfig?.zoomStep || 0.1;
           
            this.layers = [];
            this.markers = [];
            this.isPanning = false;
            this.panStart = { x: 0, y: 0, left: 0, top: 0 };
           
            this.init();
        }
          
          
         init() {
         // Limpar container
            this.createDOM();
        container.innerHTML = '';
            this.render();
        container.style.position = 'relative';
            this.setupEvents();
        container.style.overflow = 'hidden';
            this.updateMarkersPosition();
            this.createPopup();
        }
          
          
         createDOM() {
         // Criar estrutura do viewer
            // Limpar container
        container.innerHTML = `
            this.container.innerHTML = '';
            <div style="position:absolute; top:10px; left:10px; right:10px; z-index:100; display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap;">
            this.container.className = 'mw-map-container';
                <div style="display:flex; gap:5px; background:rgba(0,0,0,0.7); padding:5px 10px; border-radius:30px;">
           
                    <button class="mapa-zoom-in" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer; font-size:18px;">+</button>
            // Criar estrutura
                    <button class="mapa-zoom-out" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer; font-size:18px;">-</button>
            this.container.innerHTML = `
                    <button class="mapa-reset" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">⟳</button>
                <div class="mw-toolbar">
                    <div class="mw-controls">
                        <button class="mw-zoom-in" title="Zoom In">+</button>
                        <button class="mw-zoom-out" title="Zoom Out">-</button>
                        <button class="mw-reset" title="Reset">⟳</button>
                    </div>
                    <div class="mw-floor-info">
                        <span class="mw-floor-name">Carregando...</span>
                    </div>
                    <div class="mw-zoom-level">
                        <span class="mw-zoom-text">100%</span>
                    </div>
                 </div>
                 </div>
                 <div class="mw-viewport">
                 <div style="background:rgba(0,0,0,0.7); padding:5px 15px; border-radius:30px; color:white; font-size:12px;">
                     <div class="mw-layers"></div>
                     <span class="mapa-floor-name">Carregando...</span>
                 </div>
                 </div>
                 <div class="mw-coords">
                 <div style="background:rgba(0,0,0,0.7); padding:5px 12px; border-radius:30px; color:#a5b4fc; font-size:12px;" class="mapa-zoom-level">100%</div>
                    <span class="mw-coords-text">📍 0, 0</span>
            </div>
                </div>
            <div class="mapa-viewport" style="width:100%; height:100%; overflow:auto; cursor:grab; background:#0f172a;">
                <div class="mw-nav">
                <div class="mapa-layers" style="position:relative; min-width:100%; min-height:100%;"></div>
                    <button class="mw-prev-floor" title="Andar Anterior">▲</button>
            </div>
                    <button class="mw-next-floor" title="Próximo Andar">▼</button>
            <div style="position:absolute; bottom:10px; right:10px; background:rgba(0,0,0,0.6); padding:4px 10px; border-radius:20px; color:#a5b4fc; font-size:10px; font-family:monospace;" class="mapa-coords">📍 0, 0</div>
                </div>
            <div style="position:absolute; bottom:20px; left:20px; z-index:100; display:flex; gap:8px; background:rgba(0,0,0,0.7); padding:8px; border-radius:40px;">
            `;
                <button class="mapa-prev-floor" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer; font-size:18px;">▲</button>
           
                <button class="mapa-next-floor" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer; font-size:18px;">▼</button>
            this.viewport = this.container.querySelector('.mw-viewport');
            </div>
            this.layersContainer = this.container.querySelector('.mw-layers');
        `;
            this.floorNameSpan = this.container.querySelector('.mw-floor-name');
       
            this.zoomTextSpan = this.container.querySelector('.mw-zoom-text');
        // Referências DOM
            this.coordsSpan = this.container.querySelector('.mw-coords-text');
        var viewport = container.querySelector('.mapa-viewport');
           
        var layersContainer = container.querySelector('.mapa-layers');
            // Botões
        var floorNameSpan = container.querySelector('.mapa-floor-name');
            this.container.querySelector('.mw-zoom-in')?.addEventListener('click', () => this.zoomIn());
        var zoomLevelSpan = container.querySelector('.mapa-zoom-level');
            this.container.querySelector('.mw-zoom-out')?.addEventListener('click', () => this.zoomOut());
            this.container.querySelector('.mw-reset')?.addEventListener('click', () => this.reset());
            this.container.querySelector('.mw-prev-floor')?.addEventListener('click', () => this.prevFloor());
            this.container.querySelector('.mw-next-floor')?.addEventListener('click', () => this.nextFloor());
        }
          
          
         setupEvents() {
         // Variáveis de estado
            // Pan
        var currentZoom = config.mapConfig.defaultZoom || 1;
            this.viewport.addEventListener('mousedown', (e) => {
        var currentFloor = config.mapConfig.initialFloor || 0;
                if (e.target.closest('.mw-marker')) return;
        var minZoom = config.mapConfig.minZoom || 0.5;
                this.isPanning = true;
        var maxZoom = config.mapConfig.maxZoom || 3;
                this.panStart = {
        var zoomStep = config.mapConfig.zoomStep || 0.1;
                    x: e.clientX,
        var layers = [];
                    y: e.clientY,
        var markers = [];
                    left: this.viewport.scrollLeft,
        var isPanning = false;
                    top: this.viewport.scrollTop
        var panStart = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 };
                };
                this.viewport.style.cursor = 'grabbing';
                e.preventDefault();
            });
           
            window.addEventListener('mousemove', (e) => {
                if (!this.isPanning) return;
                const dx = e.clientX - this.panStart.x;
                const dy = e.clientY - this.panStart.y;
                this.viewport.scrollLeft = this.panStart.left - dx;
                this.viewport.scrollTop = this.panStart.top - dy;
            });
           
            window.addEventListener('mouseup', () => {
                this.isPanning = false;
                this.viewport.style.cursor = 'grab';
            });
           
            // Zoom com scroll
            this.viewport.addEventListener('wheel', (e) => {
                if (e.ctrlKey) {
                    e.preventDefault();
                    e.deltaY < 0 ? this.zoomIn() : this.zoomOut();
                }
            });
           
            // Coordenadas
            this.viewport.addEventListener('mousemove', (e) => {
                const rect = this.viewport.getBoundingClientRect();
                const x = (e.clientX - rect.left + this.viewport.scrollLeft) / this.zoom;
                const y = (e.clientY - rect.top + this.viewport.scrollTop) / this.zoom;
                if (this.coordsSpan) {
                    this.coordsSpan.textContent = `📍 ${Math.round(x)}, ${Math.round(y)} | ${Math.round(this.zoom * 100)}%`;
                }
            });
           
            // Teclado
            document.addEventListener('keydown', (e) => {
                if (e.target.tagName === 'INPUT') return;
                switch(e.key) {
                    case '+': case '=': e.preventDefault(); this.zoomIn(); break;
                    case '-': case '_': e.preventDefault(); this.zoomOut(); break;
                    case 'r': case 'R': e.preventDefault(); this.reset(); break;
                    case 'ArrowUp': case 'PageUp': e.preventDefault(); this.prevFloor(); break;
                    case 'ArrowDown': case 'PageDown': e.preventDefault(); this.nextFloor(); break;
                }
            });
        }
          
          
         render() {
         // Renderizar camadas
             if (!this.layersContainer) return;
        function renderizarCamadas() {
             this.layersContainer.innerHTML = '';
             if (!layersContainer) return;
             this.layers = [];
             layersContainer.innerHTML = '';
             this.markers = [];
             layers = [];
             markers = [];
              
              
             if (!this.config.layers || this.config.layers.length === 0) {
             if (!config.layers || config.layers.length === 0) {
                 this.layersContainer.innerHTML = '<div style="padding:50px; text-align:center; color:#64748b;">Nenhum andar configurado</div>';
                 layersContainer.innerHTML = '<div style="text-align:center; color:#64748b; padding:50px;">Nenhuma camada carregada</div>';
                 return;
                 return;
             }
             }
              
              
             // Verificar se o andar atual existe
             // Verificar se o andar atual existe
             if (!this.config.layers.find(l => l.id === this.currentFloor)) {
             if (!config.layers.find(function(l) { return l.id === currentFloor; })) {
                 this.currentFloor = this.config.layers[0]?.id || 0;
                 currentFloor = config.layers[0]?.id || 0;
             }
             }
              
              
             const currentFloorData = this.config.layers.find(l => l.id === this.currentFloor);
             var floorAtual = config.layers.find(function(l) { return l.id === currentFloor; });
             if (currentFloorData && this.floorNameSpan) {
             if (floorAtual && floorNameSpan) {
                 this.floorNameSpan.textContent = currentFloorData.name;
                 floorNameSpan.textContent = floorAtual.name;
             }
             }
              
              
             this.config.layers.forEach(layer => {
             // Percorrer camadas
                 const $layer = document.createElement('div');
            for (var i = 0; i < config.layers.length; i++) {
                 $layer.className = 'mw-layer';
                var layer = config.layers[i];
                 $layer.setAttribute('data-floor', layer.id);
                 var divLayer = document.createElement('div');
                 $layer.style.display = layer.id === this.currentFloor ? 'block' : 'none';
                 divLayer.className = 'mapa-layer';
                 $layer.style.transform = `translate(${layer.alignment?.offsetX || 0}px, ${layer.alignment?.offsetY || 0}px)`;
                 divLayer.setAttribute('data-floor', layer.id);
                 divLayer.style.display = layer.id === currentFloor ? 'block' : 'none';
                 divLayer.style.position = 'absolute';
                divLayer.style.top = '0';
                divLayer.style.left = '0';
                divLayer.style.transform = 'translate(' + (layer.alignment?.offsetX || 0) + 'px, ' + (layer.alignment?.offsetY || 0) + 'px)';
                divLayer.style.opacity = (layer.opacity || 100) / 100;
                  
                  
                 const $img = document.createElement('img');
                 var img = document.createElement('img');
                 $img.className = 'mw-image';
                 img.className = 'mapa-image';
                 $img.style.transform = `scale(${this.zoom})`;
                 img.style.display = 'block';
                 $img.style.transformOrigin = '0 0';
                img.style.transform = 'scale(' + currentZoom + ')';
                 img.style.transformOrigin = '0 0';
                img.src = layer.imagePath || '';
                  
                  
                 if (layer.imagePath) {
                 img.onload = (function(img, layersContainer) {
                    $img.src = layer.imagePath;
                     return function() {
                } else {
                        layersContainer.style.width = this.width + 'px';
                     // Imagem placeholder
                        layersContainer.style.height = this.height + 'px';
                    const canvas = document.createElement('canvas');
                     };
                    canvas.width = 800;
                })(img, layersContainer);
                    canvas.height = 600;
                    const ctx = canvas.getContext('2d');
                    ctx.fillStyle = '#334155';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = 'white';
                     ctx.font = '20px Arial';
                    ctx.fillText(layer.name || `Andar ${layer.id + 1}`, 50, 100);
                    $img.src = canvas.toDataURL();
                }
                  
                  
                 $img.onload = () => {
                 img.onerror = (function(layer, img) {
                     this.layersContainer.style.width = `${$img.width}px`;
                     return function() {
                    this.layersContainer.style.height = `${$img.height}px`;
                        var canvas = document.createElement('canvas');
                 };
                        canvas.width = 800;
                        canvas.height = 600;
                        var ctx = canvas.getContext('2d');
                        var cores = ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0'];
                        ctx.fillStyle = cores[layer.id % cores.length];
                        ctx.fillRect(0, 0, canvas.width, canvas.height);
                        ctx.fillStyle = 'white';
                        ctx.font = 'bold 24px Arial';
                        ctx.fillText(layer.name || 'Andar ' + (layer.id + 1), 50, 100);
                        ctx.fillStyle = '#e0e0e0';
                        ctx.font = '14px Arial';
                        ctx.fillText('Clique em "Escolher imagem" no editor', 50, 150);
                        img.src = canvas.toDataURL();
                    };
                 })(layer, img);
                  
                  
                 $layer.appendChild($img);
                 divLayer.appendChild(img);
                  
                  
                 // Adicionar marcadores
                 var markersDiv = document.createElement('div');
                const $markersDiv = document.createElement('div');
                 markersDiv.style.position = 'absolute';
                 $markersDiv.style.position = 'absolute';
                 markersDiv.style.top = '0';
                 $markersDiv.style.top = '0';
                 markersDiv.style.left = '0';
                 $markersDiv.style.left = '0';
                 markersDiv.style.width = '100%';
                 $markersDiv.style.width = '100%';
                 markersDiv.style.height = '100%';
                 $markersDiv.style.height = '100%';
                 markersDiv.style.pointerEvents = 'none';
                 $markersDiv.style.pointerEvents = 'none';
                  
                  
                 if (layer.markers) {
                 if (layer.markers && layer.markers.length > 0) {
                     layer.markers.forEach(marker => {
                     for (var j = 0; j < layer.markers.length; j++) {
                         const $marker = this.createMarker(marker, layer.id);
                         var marker = layer.markers[j];
                         $markersDiv.appendChild($marker);
                        var markerDiv = criarMarcador(marker, layer.id);
                         this.markers.push($marker);
                         markersDiv.appendChild(markerDiv);
                     });
                         markers.push({ el: markerDiv, data: marker });
                     }
                 }
                 }
                  
                  
                 $layer.appendChild($markersDiv);
                 divLayer.appendChild(markersDiv);
                 this.layersContainer.appendChild($layer);
                 layersContainer.appendChild(divLayer);
                 this.layers.push($layer);
                 layers.push(divLayer);
             });
             }
         }
         }
          
          
         createMarker(marker, floorId) {
         // Criar marcador
             const $marker = document.createElement('div');
        function criarMarcador(marker, floorId) {
             $marker.className = 'mw-marker';
             var div = document.createElement('div');
             $marker.setAttribute('data-marker-id', marker.id);
             div.className = 'mapa-marker';
             $marker.setAttribute('data-floor', floorId);
             div.setAttribute('data-marker-id', marker.id);
             $marker.setAttribute('data-x', marker.x);
             div.setAttribute('data-floor', floorId);
             $marker.setAttribute('data-y', marker.y);
             div.setAttribute('data-x', marker.x);
             div.setAttribute('data-y', marker.y);
           
            div.style.position = 'absolute';
            div.style.left = (marker.x * currentZoom) + 'px';
            div.style.top = (marker.y * currentZoom) + 'px';
            div.style.zIndex = '100';
            div.style.transform = 'translate(-50%, -50%)';
           
            // Cursor baseado na ação
            var temAcao = marker.action && marker.action !== 'none';
            div.style.cursor = temAcao ? 'pointer' : 'default';
              
              
             $marker.style.left = `${marker.x * this.zoom}px`;
             var tamanhoIcone = Math.max(20, Math.floor(20 * currentZoom));
             $marker.style.top = `${marker.y * this.zoom}px`;
             var htmlIcone;
              
              
             // Ícone
             if (marker.iconBase64) {
            const iconSize = Math.max(20, Math.floor(20 * this.zoom));
                 htmlIcone = '<img src="' + marker.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">';
            let iconHtml = marker.iconBase64 ?
            } else {
                 `<img src="${marker.iconBase64}" style="width:${iconSize}px; height:${iconSize}px;">` :
                 htmlIcone = '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>';
                 `<span style="font-size:${iconSize}px;">📍</span>`;
            }
              
              
             // Tooltip
             var textoAcao = '';
            let actionLabel = '';
             switch(marker.action) {
             switch(marker.action) {
                 case 'popup': actionLabel = '📋 Informações'; break;
                 case 'popup': textoAcao = '📋 Informações'; break;
                 case 'nextFloor': actionLabel = '⬆️ Próximo andar'; break;
                 case 'nextFloor': textoAcao = '⬆️ Próximo andar'; break;
                 case 'prevFloor': actionLabel = '⬇️ Andar anterior'; break;
                 case 'prevFloor': textoAcao = '⬇️ Andar anterior'; break;
                 case 'gotoFloor': actionLabel = '🎯 Ir para andar'; break;
                 case 'gotoFloor': textoAcao = '🎯 Ir para andar'; break;
                 case 'link': actionLabel = '🔗 Link'; break;
                 case 'link': textoAcao = '🔗 Link externo'; break;
                 default: actionLabel = 'Clique';
                 default: textoAcao = '📍 Clique';
             }
             }
              
              
             $marker.innerHTML = `
             div.innerHTML = `
                 <div class="mw-marker-icon">${iconHtml}</div>
                 <div style="display:flex; align-items:center; justify-content:center;">${htmlIcone}</div>
                 <div class="mw-marker-tooltip">
                 <div style="position:absolute; bottom:100%; left:50%; transform:translateX(-50%); background:#1e293b; color:white; padding:4px 8px; border-radius:6px; font-size:10px; white-space:nowrap; opacity:0; visibility:hidden; transition:0.2s; pointer-events:none; z-index:101;">
                     <strong>${marker.name || 'Marcador'}</strong><br>
                     <strong>${marker.name || 'Marcador'}</strong><br>
                     <small>${actionLabel}</small>
                     <small>${textoAcao}</small>
                 </div>
                 </div>
                 <div class="mw-badge ${!marker.hasBadge ? 'hidden' : ''}">${marker.hasBadge ? (marker.badgeNumber || marker.number || '') : ''}</div>
                 <div style="position:absolute; top:-8px; right:-8px; background:#ef4444; color:white; font-size:9px; min-width:16px; height:16px; border-radius:10px; display:flex; align-items:center; justify-content:center; padding:0 4px; ${!marker.hasBadge ? 'display:none;' : ''}">${marker.hasBadge ? (marker.badgeNumber || marker.number || '') : ''}</div>
             `;
             `;
              
              
             // Evento de clique
             // Evento hover
             if (marker.action !== 'none') {
            div.addEventListener('mouseenter', function() {
                 $marker.addEventListener('click', (e) => {
                this.style.transform = 'translate(-50%, -50%) scale(1.15)';
                var tooltip = this.children[1];
                if (tooltip) {
                    tooltip.style.opacity = '1';
                    tooltip.style.visibility = 'visible';
                }
            });
           
             div.addEventListener('mouseleave', function() {
                this.style.transform = 'translate(-50%, -50%) scale(1)';
                var tooltip = this.children[1];
                if (tooltip) {
                    tooltip.style.opacity = '0';
                    tooltip.style.visibility = 'hidden';
                }
            });
           
            // Evento clique
            if (temAcao) {
                 div.addEventListener('click', function(e) {
                     e.stopPropagation();
                     e.stopPropagation();
                     this.executeAction(marker);
                     executarAcao(marker);
                 });
                 });
             }
             }
              
              
             return $marker;
             return div;
         }
         }
          
          
         executeAction(marker) {
         // Executar ação do marcador
            console.log('Ação:', marker.action, marker.name);
        function executarAcao(marker) {
           
             switch(marker.action) {
             switch(marker.action) {
                 case 'popup':
                 case 'popup':
                     this.showPopup(marker);
                     var popupText = marker.actionData?.text || 'Sem informações';
                    alert(marker.name + '\n\n' + popupText);
                     break;
                     break;
                 case 'nextFloor':
                 case 'nextFloor':
                     this.nextFloor();
                     proximoAndar();
                     break;
                     break;
                 case 'prevFloor':
                 case 'prevFloor':
                     this.prevFloor();
                     andarAnterior();
                     break;
                     break;
                 case 'gotoFloor':
                 case 'gotoFloor':
                     if (marker.actionData?.floorId !== undefined) {
                     if (marker.actionData?.floorId !== undefined) {
                         this.goToFloor(marker.actionData.floorId);
                         irParaAndar(marker.actionData.floorId);
                     }
                     }
                     break;
                     break;
Linha 505: Linha 274:
         }
         }
          
          
         createPopup() {
         // Navegação
             if (document.querySelector('.mw-map-popup')) return;
        function proximoAndar() {
              
             var idx = -1;
            const popupHtml = `
             for (var i = 0; i < config.layers.length; i++) {
                 <div class="mw-map-popup">
                 if (config.layers[i].id === currentFloor) {
                     <div class="mw-popup-content">
                     idx = i;
                        <span class="mw-popup-close">&times;</span>
                     break;
                        <div class="mw-popup-body"></div>
                 }
                     </div>
             }
                 </div>
             if (idx < config.layers.length - 1) {
             `;
                irParaAndar(config.layers[idx + 1].id);
             document.body.insertAdjacentHTML('beforeend', popupHtml);
             }
              
        }
             this.popup = document.querySelector('.mw-map-popup');
       
             this.popupBody = this.popup?.querySelector('.mw-popup-body');
        function andarAnterior() {
           
             var idx = -1;
            this.popup?.querySelector('.mw-popup-close')?.addEventListener('click', () => this.closePopup());
             for (var i = 0; i < config.layers.length; i++) {
             this.popup?.addEventListener('click', (e) => {
                if (config.layers[i].id === currentFloor) {
                 if (e.target === this.popup) this.closePopup();
                    idx = i;
             });
                    break;
                }
             }
            if (idx > 0) {
                 irParaAndar(config.layers[idx - 1].id);
             }
         }
         }
          
          
         showPopup(marker) {
         function irParaAndar(floorId) {
             if (!this.popup) this.createPopup();
             currentFloor = floorId;
              
              
             const iconHtml = marker.iconBase64 ?
             for (var i = 0; i < layers.length; i++) {
                 `<img src="${marker.iconBase64}" style="width:24px; height:24px; vertical-align:middle; margin-right:8px;">` :
                var layer = layers[i];
                 `<span style="font-size:20px; margin-right:8px;">📍</span>`;
                 var layerFloor = parseInt(layer.getAttribute('data-floor'));
                 layer.style.display = layerFloor === floorId ? 'block' : 'none';
            }
              
              
             this.popupBody.innerHTML = `
             var floorData = null;
                 ${marker.actionData?.image ? `<img src="${marker.actionData.image}" style="max-width:100%; border-radius:8px; margin-bottom:15px;">` : ''}
            for (var i = 0; i < config.layers.length; i++) {
                 <div style="display:flex; align-items:center;">
                 if (config.layers[i].id === floorId) {
                    ${iconHtml}
                    floorData = config.layers[i];
                    <h3 style="margin:0;">${marker.name}</h3>
                    break;
                 </div>
                 }
                <p style="margin-top:10px;">${marker.actionData?.text || 'Sem informações'}</p>
            }
             `;
            if (floorData && floorNameSpan) {
                 floorNameSpan.textContent = floorData.name;
             }
              
              
             this.popup.style.display = 'flex';
             atualizarPosicaoMarkers();
        }
       
        closePopup() {
            if (this.popup) this.popup.style.display = 'none';
         }
         }
          
          
         updateMarkersPosition() {
         // Atualizar posição dos marcadores
             this.markers.forEach($marker => {
        function atualizarPosicaoMarkers() {
                 const floorId = parseInt($marker.getAttribute('data-floor'));
             for (var i = 0; i < markers.length; i++) {
                if (floorId === this.currentFloor) {
                 var item = markers[i];
                     const x = parseFloat($marker.getAttribute('data-x'));
                if (parseInt(item.el.getAttribute('data-floor')) === currentFloor) {
                    const y = parseFloat($marker.getAttribute('data-y'));
                     item.el.style.left = (item.data.x * currentZoom) + 'px';
                     $marker.style.left = `${x * this.zoom}px`;
                     item.el.style.top = (item.data.y * currentZoom) + 'px';
                    $marker.style.top = `${y * this.zoom}px`;
                      
                      
                     const iconSize = Math.max(20, Math.floor(20 * this.zoom));
                     var tamanhoIcone = Math.max(20, Math.floor(20 * currentZoom));
                     const iconDiv = $marker.querySelector('.mw-marker-icon');
                     var iconDiv = item.el.children[0];
                     if (iconDiv) {
                     if (iconDiv) {
                         const hasImage = $marker.getAttribute('data-has-image');
                         if (item.data.iconBase64) {
                        if (hasImage) {
                             iconDiv.innerHTML = '<img src="' + item.data.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">';
                             // manter imagem
                         } else {
                         } else {
                             iconDiv.innerHTML = `<span style="font-size:${iconSize}px;">📍</span>`;
                             iconDiv.innerHTML = '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>';
                         }
                         }
                     }
                     }
                 }
                 }
             });
             }
         }
         }
          
          
         zoomIn() {
         // Zoom
             if (this.zoom < this.maxZoom) {
        function zoomIn() {
                 const oldZoom = this.zoom;
             if (currentZoom < maxZoom) {
                 this.zoom = Math.min(this.zoom + this.zoomStep, this.maxZoom);
                 var oldZoom = currentZoom;
                 this.applyZoom(oldZoom);
                 currentZoom = Math.min(currentZoom + zoomStep, maxZoom);
                 aplicarZoom(oldZoom);
             }
             }
         }
         }
          
          
         zoomOut() {
         function zoomOut() {
             if (this.zoom > this.minZoom) {
             if (currentZoom > minZoom) {
                 const oldZoom = this.zoom;
                 var oldZoom = currentZoom;
                 this.zoom = Math.max(this.zoom - this.zoomStep, this.minZoom);
                 currentZoom = Math.max(currentZoom - zoomStep, minZoom);
                 this.applyZoom(oldZoom);
                 aplicarZoom(oldZoom);
             }
             }
         }
         }
          
          
         applyZoom(oldZoom) {
         function aplicarZoom(oldZoom) {
             // Aplicar zoom nas imagens
             var images = document.querySelectorAll('.mapa-image');
            this.container.querySelectorAll('.mw-image').forEach(img => {
            for (var i = 0; i < images.length; i++) {
                 img.style.transform = `scale(${this.zoom})`;
                 images[i].style.transform = 'scale(' + currentZoom + ')';
             });
             }
              
              
             // Ajustar scroll para manter centro
             var rect = viewport.getBoundingClientRect();
            const rect = this.viewport.getBoundingClientRect();
             var cx = rect.width / 2;
             const cx = rect.width / 2;
             var cy = rect.height / 2;
             const cy = rect.height / 2;
             var sx = (viewport.scrollLeft + cx) / oldZoom;
             const sx = (this.viewport.scrollLeft + cx) / oldZoom;
             var sy = (viewport.scrollTop + cy) / oldZoom;
             const sy = (this.viewport.scrollTop + cy) / oldZoom;
              
              
             this.viewport.scrollLeft = sx * this.zoom - cx;
             viewport.scrollLeft = sx * currentZoom - cx;
             this.viewport.scrollTop = sy * this.zoom - cy;
             viewport.scrollTop = sy * currentZoom - cy;
              
              
             this.updateMarkersPosition();
             atualizarPosicaoMarkers();
             if (this.zoomTextSpan) {
             if (zoomLevelSpan) {
                 this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
                 zoomLevelSpan.textContent = Math.round(currentZoom * 100) + '%';
             }
             }
         }
         }
          
          
         reset() {
         function resetarView() {
             this.zoom = this.config.mapConfig?.defaultZoom || 1;
             currentZoom = config.mapConfig.defaultZoom || 1;
             this.container.querySelectorAll('.mw-image').forEach(img => {
             var images = document.querySelectorAll('.mapa-image');
                 img.style.transform = `scale(${this.zoom})`;
            for (var i = 0; i < images.length; i++) {
             });
                 images[i].style.transform = 'scale(' + currentZoom + ')';
             this.viewport.scrollLeft = 0;
             }
             this.viewport.scrollTop = 0;
             viewport.scrollLeft = 0;
             this.updateMarkersPosition();
             viewport.scrollTop = 0;
             if (this.zoomTextSpan) {
             atualizarPosicaoMarkers();
                 this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
             if (zoomLevelSpan) {
                 zoomLevelSpan.textContent = Math.round(currentZoom * 100) + '%';
             }
             }
         }
         }
          
          
         nextFloor() {
         // Configurar eventos dos botões
            const idx = this.config.layers.findIndex(l => l.id === this.currentFloor);
        var btnZoomIn = container.querySelector('.mapa-zoom-in');
             if (idx < this.config.layers.length - 1) {
        var btnZoomOut = container.querySelector('.mapa-zoom-out');
                 this.goToFloor(this.config.layers[idx + 1].id);
        var btnReset = container.querySelector('.mapa-reset');
             }
        var btnPrev = container.querySelector('.mapa-prev-floor');
         }
        var btnNext = container.querySelector('.mapa-next-floor');
       
        if (btnZoomIn) btnZoomIn.addEventListener('click', zoomIn);
        if (btnZoomOut) btnZoomOut.addEventListener('click', zoomOut);
        if (btnReset) btnReset.addEventListener('click', resetarView);
        if (btnPrev) btnPrev.addEventListener('click', andarAnterior);
        if (btnNext) btnNext.addEventListener('click', proximoAndar);
       
        // Eventos de pan (arrastar)
        viewport.addEventListener('mousedown', function(e) {
             if (e.target.closest('.mapa-marker')) return;
            isPanning = true;
            panStart = {
                 x: e.clientX,
                y: e.clientY,
                scrollLeft: viewport.scrollLeft,
                scrollTop: viewport.scrollTop
            };
            viewport.style.cursor = 'grabbing';
            e.preventDefault();
        });
       
        window.addEventListener('mousemove', function(e) {
            if (!isPanning) return;
            viewport.scrollLeft = panStart.scrollLeft - (e.clientX - panStart.x);
             viewport.scrollTop = panStart.scrollTop - (e.clientY - panStart.y);
         });
          
          
         prevFloor() {
         window.addEventListener('mouseup', function() {
             const idx = this.config.layers.findIndex(l => l.id === this.currentFloor);
             isPanning = false;
             if (idx > 0) {
             viewport.style.cursor = 'grab';
                this.goToFloor(this.config.layers[idx - 1].id);
         });
            }
         }
          
          
         goToFloor(floorId) {
         // Zoom com scroll
             this.currentFloor = floorId;
        viewport.addEventListener('wheel', function(e) {
           
             if (e.ctrlKey) {
            this.layers.forEach(layer => {
                 e.preventDefault();
                 const layerFloor = parseInt(layer.getAttribute('data-floor'));
                 if (e.deltaY < 0) {
                 layer.style.display = layerFloor === floorId ? 'block' : 'none';
                    zoomIn();
            });
                } else {
           
                    zoomOut();
            const floorData = this.config.layers.find(l => l.id === floorId);
                 }
            if (floorData && this.floorNameSpan) {
                 this.floorNameSpan.textContent = floorData.name;
             }
             }
           
         });
            setTimeout(() => this.updateMarkersPosition(), 50);
         }
    }
   
    // ==================== INICIALIZAÇÃO DO WIDGET ====================
    function initWidget() {
        // Procurar todos os containers do widget
        const containers = document.querySelectorAll('[data-mw-map]');
          
          
         containers.forEach(container => {
         // Tracking de coordenadas
             const jsonStr = container.getAttribute('data-mw-config');
        viewport.addEventListener('mousemove', function(e) {
             if (!jsonStr) return;
             var rect = viewport.getBoundingClientRect();
              
             var x = (e.clientX - rect.left + viewport.scrollLeft) / currentZoom;
            try {
             var y = (e.clientY - rect.top + viewport.scrollTop) / currentZoom;
                const config = JSON.parse(jsonStr);
            var coordsDiv = container.querySelector('.mapa-coords');
                new MWMap(container, config);
             if (coordsDiv) {
             } catch(e) {
                 coordsDiv.textContent = '📍 ' + Math.round(x) + ', ' + Math.round(y) + ' | ' + Math.round(currentZoom * 100) + '%';
                 console.error('Erro ao criar mapa:', e);
                container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">Erro ao carregar mapa</div>';
             }
             }
         });
         });
       
        // Inicializar
        renderizarCamadas();
     }
     }
   
    // Injetar estilos
    if (!document.getElementById('mw-map-styles')) {
        const styleTag = document.createElement('style');
        styleTag.id = 'mw-map-styles';
        styleTag.textContent = styles;
        document.head.appendChild(styleTag);
    }
   
    // Aguardar DOM e inicializar
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initWidget);
    } else {
        initWidget();
    }
   
    // Expor para uso global
    window.MWMap = MWMap;
})();
})();
 
</script></includeonly>
</script>
</includeonly>

Edição das 09h47min de 8 de abril de 2026