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

De Wiki Gla
Ir para navegação Ir para pesquisar
Linha 1: Linha 1:
<script>
<includeonly>
  (function() {
<script type="text/javascript">
  console.log('carregou mapviewer');
/**
     // Verificar se já foi carregado
* MapViewer Widget para MediaWiki - Versão Simplificada
* Uso: {{#widget:MapViewer|json=JSON_do_mapa|width=800|height=600}}
*/
 
(function() {
     // Evitar múltiplas execuções
     if (window.MWMapViewerLoaded) return;
     if (window.MWMapViewerLoaded) return;
     window.MWMapViewerLoaded = true;
     window.MWMapViewerLoaded = true;
      
      
     // Estilos do widget
     // ==================== ESTILOS ====================
     const styles = `
     const styles = `
         <style>
         .mw-map-container {
            .mw-map-viewer {
            position: relative;
                position: relative;
            background: #1e1e2e;
                background: #0f172a;
            border-radius: 8px;
                border-radius: 12px;
            overflow: hidden;
                overflow: hidden;
            box-shadow: 0 2px 8px rgba(0,0,0,0.2);
                box-shadow: 0 4px 20px rgba(0,0,0,0.2);
        }
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        .mw-map-container .mw-viewport {
            }
             width: 100%;
             .mw-map-viewer .map-viewport {
            height: 100%;
                width: 100%;
            overflow: auto;
                height: 100%;
            cursor: grab;
                overflow: auto;
            background: #0f0f1a;
                cursor: grab;
        }
                background: #0f172a;
        .mw-map-container .mw-viewport:active {
            }
            cursor: grabbing;
            .mw-map-viewer .map-viewport:active {
        }
                cursor: grabbing;
        .mw-map-container .mw-layers {
            }
            position: relative;
            .mw-map-viewer .map-layers {
            min-width: 100%;
                position: relative;
            min-height: 100%;
                min-width: 100%;
        }
                min-height: 100%;
        .mw-map-container .mw-layer {
            }
            position: absolute;
            .mw-map-viewer .map-layer {
            top: 0;
                position: absolute;
            left: 0;
                top: 0;
        }
                left: 0;
        .mw-map-container .mw-image {
                transform-origin: 0 0;
            display: block;
            }
            pointer-events: none;
            .mw-map-viewer .map-image {
        }
                display: block;
        .mw-map-container .mw-marker {
                transform-origin: 0 0;
            position: absolute;
                pointer-events: none;
            cursor: pointer;
                border-radius: 4px;
            transform: translate(-50%, -50%);
            }
            transition: transform 0.1s;
            .mw-map-viewer .map-marker {
             z-index: 100;
                position: absolute;
        }
                cursor: pointer;
        .mw-map-container .mw-marker:hover {
                z-index: 100;
            transform: translate(-50%, -50%) scale(1.2);
                transform: translate(-50%, -50%);
            z-index: 101;
                transition: transform 0.1s ease;
        }
             }
        .mw-map-container .mw-marker-icon {
            .mw-map-viewer .map-marker:hover {
            font-size: 20px;
                transform: translate(-50%, -50%) scale(1.15);
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
                z-index: 102;
        }
            }
        .mw-map-container .mw-marker-tooltip {
            .mw-map-viewer .marker-icon {
            position: absolute;
                display: flex;
            bottom: 100%;
                align-items: center;
            left: 50%;
                justify-content: center;
            transform: translateX(-50%);
                filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
            background: #1e1e2e;
            }
            color: white;
            .mw-map-viewer .marker-tooltip {
            padding: 4px 8px;
                position: absolute;
            border-radius: 6px;
                bottom: 100%;
            font-size: 11px;
                left: 50%;
            white-space: nowrap;
                transform: translateX(-50%) translateY(-8px);
            opacity: 0;
                background: #1e293b;
            visibility: hidden;
                color: #e2e8f0;
            transition: 0.2s;
                padding: 4px 10px;
            pointer-events: none;
                border-radius: 8px;
            margin-bottom: 5px;
                font-size: 11px;
        }
                white-space: nowrap;
        .mw-map-container .mw-marker:hover .mw-marker-tooltip {
                opacity: 0;
            opacity: 1;
                visibility: hidden;
            visibility: visible;
                transition: all 0.2s;
        }
                pointer-events: none;
        .mw-map-container .mw-badge {
                box-shadow: 0 2px 8px rgba(0,0,0,0.2);
            position: absolute;
                border: 1px solid #334155;
            top: -8px;
            }
            right: -8px;
            .mw-map-viewer .map-marker:hover .marker-tooltip {
            background: #ef4444;
                opacity: 1;
            color: white;
                visibility: visible;
            font-size: 9px;
                transform: translateX(-50%) translateY(-12px);
            min-width: 16px;
            }
            height: 16px;
            .mw-map-viewer .marker-badge {
            border-radius: 10px;
                position: absolute;
            display: flex;
                top: -8px;
            align-items: center;
                right: -8px;
            justify-content: center;
                background: #ef4444;
            padding: 0 4px;
                color: white;
        }
                font-size: 9px;
        .mw-map-container .mw-badge.hidden {
                font-weight: bold;
            display: none;
                min-width: 16px;
        }
                height: 16px;
        .mw-map-container .mw-toolbar {
                border-radius: 10px;
            position: absolute;
                display: flex;
            top: 10px;
                align-items: center;
            left: 10px;
                justify-content: center;
            right: 10px;
                padding: 0 4px;
            display: flex;
                font-family: monospace;
            justify-content: space-between;
                box-shadow: 0 1px 3px rgba(0,0,0,0.2);
            z-index: 100;
            }
            gap: 10px;
            .mw-map-viewer .marker-badge.hidden {
        }
                display: none;
        .mw-map-container .mw-controls {
            }
            display: flex;
            .mw-map-viewer .viewer-toolbar {
            gap: 5px;
                position: absolute;
            background: rgba(0,0,0,0.6);
                top: 12px;
            padding: 5px 10px;
                left: 12px;
            border-radius: 30px;
                right: 12px;
        }
                display: flex;
        .mw-map-container .mw-controls button {
                justify-content: space-between;
            background: #334155;
                z-index: 101;
            border: none;
                gap: 10px;
            color: white;
                flex-wrap: wrap;
            width: 32px;
            }
            height: 32px;
            .mw-map-viewer .viewer-controls {
            border-radius: 50%;
                display: flex;
            cursor: pointer;
                gap: 5px;
            font-size: 16px;
                background: rgba(0,0,0,0.6);
        }
                backdrop-filter: blur(8px);
        .mw-map-container .mw-controls button:hover {
                padding: 5px 10px;
            background: #6366f1;
                border-radius: 40px;
        }
            }
        .mw-map-container .mw-floor-info {
            .mw-map-viewer .viewer-controls button {
            background: rgba(0,0,0,0.6);
                background: #334155;
            padding: 5px 15px;
                border: none;
            border-radius: 30px;
                color: white;
            color: white;
                width: 32px;
            font-size: 12px;
                height: 32px;
        }
                border-radius: 50%;
        .mw-map-container .mw-zoom-level {
                cursor: pointer;
            background: rgba(0,0,0,0.6);
                font-size: 16px;
            padding: 5px 12px;
                transition: all 0.2s;
            border-radius: 30px;
            }
            color: #a5b4fc;
            .mw-map-viewer .viewer-controls button:hover {
            font-size: 12px;
                background: #6366f1;
        }
                transform: scale(1.05);
        .mw-map-container .mw-nav {
            }
            position: absolute;
            .mw-map-viewer .floor-info {
            bottom: 20px;
                background: rgba(0,0,0,0.6);
            left: 20px;
                backdrop-filter: blur(8px);
            display: flex;
                padding: 5px 15px;
            gap: 8px;
                border-radius: 40px;
            z-index: 100;
                color: white;
        }
                font-size: 12px;
        .mw-map-container .mw-nav button {
            }
            background: rgba(0,0,0,0.7);
            .mw-map-viewer .zoom-level {
            border: none;
                background: rgba(0,0,0,0.6);
             color: white;
                backdrop-filter: blur(8px);
             width: 40px;
                padding: 5px 12px;
            height: 40px;
                border-radius: 40px;
            border-radius: 50%;
                color: #a5b4fc;
            cursor: pointer;
                font-size: 12px;
            font-size: 18px;
                font-family: monospace;
        }
            }
        .mw-map-container .mw-nav button:hover {
            .mw-map-viewer .coordinates-panel {
            background: #6366f1;
                position: absolute;
        }
                bottom: 10px;
        .mw-map-container .mw-coords {
                right: 10px;
            position: absolute;
                background: rgba(0,0,0,0.6);
            bottom: 10px;
                backdrop-filter: blur(8px);
            right: 10px;
                padding: 4px 10px;
            background: rgba(0,0,0,0.5);
                border-radius: 20px;
            padding: 4px 10px;
                color: #a5b4fc;
            border-radius: 20px;
                font-size: 10px;
            color: #a5b4fc;
                font-family: monospace;
            font-size: 10px;
                z-index: 100;
            font-family: monospace;
                pointer-events: none;
             z-index: 100;
             }
        }
             .mw-map-viewer .nav-controls {
        .mw-map-popup {
                position: absolute;
            display: none;
                bottom: 20px;
            position: fixed;
                left: 20px;
             z-index: 10000;
                display: flex;
            left: 0;
                gap: 8px;
            top: 0;
                z-index: 101;
            width: 100%;
            }
            height: 100%;
            .mw-map-viewer .nav-controls button {
            background: rgba(0,0,0,0.7);
                background: rgba(0,0,0,0.7);
            justify-content: center;
                backdrop-filter: blur(8px);
            align-items: center;
                border: none;
        }
                color: white;
        .mw-map-popup .mw-popup-content {
                width: 40px;
            background: #1e1e2e;
                height: 40px;
            border-radius: 12px;
                border-radius: 50%;
            max-width: 350px;
                cursor: pointer;
            width: 90%;
                font-size: 18px;
            padding: 20px;
                transition: all 0.2s;
            position: relative;
             }
            border: 1px solid #334155;
            .mw-map-viewer .nav-controls button:hover {
        }
                background: #6366f1;
        .mw-map-popup .mw-popup-close {
                transform: scale(1.05);
            position: absolute;
             }
            right: 15px;
            .mw-map-viewer-popup {
            top: 10px;
                display: none;
            font-size: 24px;
                position: fixed;
            cursor: pointer;
                z-index: 10000;
             color: #94a3b8;
                left: 0;
        }
                top: 0;
        .mw-map-popup .mw-popup-close:hover {
                width: 100%;
            color: #ef4444;
                height: 100%;
        }
                background: rgba(0,0,0,0.7);
        .mw-map-popup h3 {
                justify-content: center;
            color: #a5b4fc;
                align-items: center;
            margin: 0 0 10px 0;
            }
        }
            .mw-map-viewer-popup .popup-content {
        .mw-map-popup p {
                background: #1e293b;
            color: #cbd5e1;
                border-radius: 16px;
             margin: 0;
                max-width: 350px;
             line-height: 1.5;
                width: 90%;
        }
                padding: 20px;
                position: relative;
                border: 1px solid #334155;
                animation: popupSlide 0.3s ease;
            }
            @keyframes popupSlide {
                from { transform: translateY(-30px); opacity: 0; }
                to { transform: translateY(0); opacity: 1; }
             }
            .mw-map-viewer-popup .popup-close {
                position: absolute;
                right: 15px;
                top: 10px;
                font-size: 24px;
                cursor: pointer;
                color: #94a3b8;
            }
            .mw-map-viewer-popup .popup-close:hover {
                color: #ef4444;
             }
             .mw-map-viewer-popup h3 {
                color: #a5b4fc;
                margin-bottom: 12px;
            }
            .mw-map-viewer-popup p {
                color: #cbd5e1;
                line-height: 1.5;
                font-size: 14px;
            }
            .mw-map-viewer-popup img {
                max-width: 100%;
                border-radius: 12px;
                margin-bottom: 16px;
            }
            @media (max-width: 768px) {
                .mw-map-viewer .viewer-controls button {
                    width: 28px;
                    height: 28px;
                    font-size: 14px;
                }
                .mw-map-viewer .floor-info {
                    font-size: 10px;
                    padding: 4px 12px;
                }
                .mw-map-viewer .zoom-level {
                    font-size: 10px;
                    padding: 4px 10px;
                }
            }
        </style>
     `;
     `;
      
      
     class MWMapViewer {
    // ==================== CLASSE DO MAPA ====================
         constructor(container, config, options = {}) {
     class MWMap {
         constructor(container, config) {
             this.container = container;
             this.container = container;
             this.config = this.normalizeConfig(config);
             this.config = config;
            this.width = options.width || '100%';
            this.height = options.height || '500px';
              
              
             this.currentZoom = this.config.mapConfig?.defaultZoom || 1;
            // Configurações do mapa
             this.currentFloor = this.config.mapConfig?.initialFloor || 0;
             this.zoom = config.mapConfig?.defaultZoom || 1;
             this.minZoom = this.config.mapConfig?.minZoom || 0.5;
             this.currentFloor = config.mapConfig?.initialFloor || 0;
             this.maxZoom = this.config.mapConfig?.maxZoom || 3;
             this.minZoom = config.mapConfig?.minZoom || 0.5;
             this.zoomStep = this.config.mapConfig?.zoomStep || 0.1;
             this.maxZoom = config.mapConfig?.maxZoom || 3;
             this.zoomStep = config.mapConfig?.zoomStep || 0.1;
              
              
             this.layers = [];
             this.layers = [];
             this.markers = [];
             this.markers = [];
             this.isPanning = false;
             this.isPanning = false;
             this.panStart = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 };
             this.panStart = { x: 0, y: 0, left: 0, top: 0 };
              
              
             this.init();
             this.init();
        }
       
        normalizeConfig(config) {
            if (!config.mapConfig) {
                config.mapConfig = { initialFloor: 0, defaultZoom: 1, minZoom: 0.5, maxZoom: 3, zoomStep: 0.1 };
            }
            if (!config.layers) config.layers = [];
           
            config.layers.forEach(layer => {
                if (!layer.alignment) layer.alignment = { offsetX: 0, offsetY: 0, scale: 1 };
                if (layer.opacity === undefined) layer.opacity = 100;
                if (!layer.markers) layer.markers = [];
               
                layer.markers.forEach(marker => {
                    if (!marker.actionData) marker.actionData = {};
                    if (marker.action === 'gotoFloor' && marker.actionData.floorId === undefined) marker.actionData.floorId = 0;
                    if (marker.action === 'link' && !marker.actionData.url) marker.actionData.url = '#';
                    if (marker.action === 'popup' && !marker.actionData.text) marker.actionData.text = 'Sem informações';
                });
            });
            return config;
         }
         }
          
          
         init() {
         init() {
             this.createDOM();
             this.createDOM();
             this.renderLayers();
             this.render();
             this.setupEvents();
             this.setupEvents();
             this.updateMarkersPosition();
             this.updateMarkersPosition();
             this.initPopup();
             this.createPopup();
         }
         }
          
          
         createDOM() {
         createDOM() {
            // Limpar container
             this.container.innerHTML = '';
             this.container.innerHTML = '';
             this.container.className = 'mw-map-viewer';
             this.container.className = 'mw-map-container';
            this.container.style.width = this.width;
            this.container.style.height = this.height;
            this.container.style.position = 'relative';
            this.container.style.overflow = 'hidden';
              
              
            // Criar estrutura
             this.container.innerHTML = `
             this.container.innerHTML = `
                 <div class="viewer-toolbar">
                 <div class="mw-toolbar">
                     <div class="viewer-controls">
                     <div class="mw-controls">
                         <button class="mw-zoom-in" title="Zoom In (+ / Ctrl+Scroll)">+</button>
                         <button class="mw-zoom-in" title="Zoom In">+</button>
                         <button class="mw-zoom-out" title="Zoom Out (- / Ctrl+Scroll)">-</button>
                         <button class="mw-zoom-out" title="Zoom Out">-</button>
                         <button class="mw-reset" title="Resetar Visualização (R)">⟳</button>
                         <button class="mw-reset" title="Reset">⟳</button>
                     </div>
                     </div>
                     <div class="floor-info">
                     <div class="mw-floor-info">
                         <span class="mw-floor-name">Carregando...</span>
                         <span class="mw-floor-name">Carregando...</span>
                     </div>
                     </div>
                     <div class="zoom-level">
                     <div class="mw-zoom-level">
                         <span class="mw-zoom-level">100%</span>
                         <span class="mw-zoom-text">100%</span>
                     </div>
                     </div>
                 </div>
                 </div>
                 <div class="map-viewport">
                 <div class="mw-viewport">
                     <div class="map-layers"></div>
                     <div class="mw-layers"></div>
                 </div>
                 </div>
                 <div class="coordinates-panel">
                 <div class="mw-coords">
                     <span class="mw-coords">📍 0, 0</span>
                     <span class="mw-coords-text">📍 0, 0</span>
                 </div>
                 </div>
                 <div class="nav-controls">
                 <div class="mw-nav">
                     <button class="mw-prev-floor" title="Andar Anterior (PageUp)">▲</button>
                     <button class="mw-prev-floor" title="Andar Anterior">▲</button>
                     <button class="mw-next-floor" title="Próximo Andar (PageDown)">▼</button>
                     <button class="mw-next-floor" title="Próximo Andar">▼</button>
                 </div>
                 </div>
             `;
             `;
              
              
             this.viewport = this.container.querySelector('.map-viewport');
             this.viewport = this.container.querySelector('.mw-viewport');
             this.layersContainer = this.container.querySelector('.map-layers');
             this.layersContainer = this.container.querySelector('.mw-layers');
             this.floorNameSpan = this.container.querySelector('.mw-floor-name');
             this.floorNameSpan = this.container.querySelector('.mw-floor-name');
             this.zoomLevelSpan = this.container.querySelector('.mw-zoom-level');
             this.zoomTextSpan = this.container.querySelector('.mw-zoom-text');
             this.coordsSpan = this.container.querySelector('.mw-coords');
             this.coordsSpan = this.container.querySelector('.mw-coords-text');
              
              
             // Bind events
             // Botões
             this.container.querySelector('.mw-zoom-in')?.addEventListener('click', () => this.zoomIn());
             this.container.querySelector('.mw-zoom-in')?.addEventListener('click', () => this.zoomIn());
             this.container.querySelector('.mw-zoom-out')?.addEventListener('click', () => this.zoomOut());
             this.container.querySelector('.mw-zoom-out')?.addEventListener('click', () => this.zoomOut());
             this.container.querySelector('.mw-reset')?.addEventListener('click', () => this.resetView());
             this.container.querySelector('.mw-reset')?.addEventListener('click', () => this.reset());
             this.container.querySelector('.mw-prev-floor')?.addEventListener('click', () => this.prevFloor());
             this.container.querySelector('.mw-prev-floor')?.addEventListener('click', () => this.prevFloor());
             this.container.querySelector('.mw-next-floor')?.addEventListener('click', () => this.nextFloor());
             this.container.querySelector('.mw-next-floor')?.addEventListener('click', () => this.nextFloor());
           
            this.setupInteractions();
            this.setupKeyboard();
         }
         }
          
          
         setupInteractions() {
         setupEvents() {
             // Pan
             // Pan
             this.viewport.addEventListener('mousedown', (e) => {
             this.viewport.addEventListener('mousedown', (e) => {
                 if (e.target.closest('.map-marker')) return;
                 if (e.target.closest('.mw-marker')) return;
                 this.isPanning = true;
                 this.isPanning = true;
                 this.panStart = {
                 this.panStart = {
                     x: e.clientX,
                     x: e.clientX,
                     y: e.clientY,
                     y: e.clientY,
                     scrollLeft: this.viewport.scrollLeft,
                     left: this.viewport.scrollLeft,
                     scrollTop: this.viewport.scrollTop
                     top: this.viewport.scrollTop
                 };
                 };
                 this.viewport.style.cursor = 'grabbing';
                 this.viewport.style.cursor = 'grabbing';
Linha 381: Linha 310:
             window.addEventListener('mousemove', (e) => {
             window.addEventListener('mousemove', (e) => {
                 if (!this.isPanning) return;
                 if (!this.isPanning) return;
                 this.viewport.scrollLeft = this.panStart.scrollLeft - (e.clientX - this.panStart.x);
                 const dx = e.clientX - this.panStart.x;
                 this.viewport.scrollTop = this.panStart.scrollTop - (e.clientY - this.panStart.y);
                const dy = e.clientY - this.panStart.y;
                 this.viewport.scrollLeft = this.panStart.left - dx;
                this.viewport.scrollTop = this.panStart.top - dy;
             });
             });
              
              
Linha 401: Linha 332:
             this.viewport.addEventListener('mousemove', (e) => {
             this.viewport.addEventListener('mousemove', (e) => {
                 const rect = this.viewport.getBoundingClientRect();
                 const rect = this.viewport.getBoundingClientRect();
                 const x = (e.clientX - rect.left + this.viewport.scrollLeft) / this.currentZoom;
                 const x = (e.clientX - rect.left + this.viewport.scrollLeft) / this.zoom;
                 const y = (e.clientY - rect.top + this.viewport.scrollTop) / this.currentZoom;
                 const y = (e.clientY - rect.top + this.viewport.scrollTop) / this.zoom;
                 if (this.coordsSpan) {
                 if (this.coordsSpan) {
                     this.coordsSpan.textContent = `📍 ${Math.round(x)}, ${Math.round(y)} | ${Math.round(this.currentZoom * 100)}%`;
                     this.coordsSpan.textContent = `📍 ${Math.round(x)}, ${Math.round(y)} | ${Math.round(this.zoom * 100)}%`;
                 }
                 }
             });
             });
        }
           
       
            // Teclado
        setupKeyboard() {
            document.addEventListener('keydown', (e) => {
            const handler = (e) => {
                 if (e.target.tagName === 'INPUT') return;
                 if (e.target.tagName === 'INPUT') return;
                 switch(e.key) {
                 switch(e.key) {
                     case '+': case '=': e.preventDefault(); this.zoomIn(); break;
                     case '+': case '=': e.preventDefault(); this.zoomIn(); break;
                     case '-': case '_': e.preventDefault(); this.zoomOut(); break;
                     case '-': case '_': e.preventDefault(); this.zoomOut(); break;
                     case 'r': case 'R': e.preventDefault(); this.resetView(); break;
                     case 'r': case 'R': e.preventDefault(); this.reset(); break;
                     case 'ArrowUp': case 'PageUp': e.preventDefault(); this.prevFloor(); break;
                     case 'ArrowUp': case 'PageUp': e.preventDefault(); this.prevFloor(); break;
                     case 'ArrowDown': case 'PageDown': e.preventDefault(); this.nextFloor(); break;
                     case 'ArrowDown': case 'PageDown': e.preventDefault(); this.nextFloor(); break;
                 }
                 }
             };
             });
            document.addEventListener('keydown', handler);
         }
         }
          
          
         renderLayers() {
         render() {
             if (!this.layersContainer) return;
             if (!this.layersContainer) return;
             this.layersContainer.innerHTML = '';
             this.layersContainer.innerHTML = '';
Linha 430: Linha 359:
              
              
             if (!this.config.layers || this.config.layers.length === 0) {
             if (!this.config.layers || this.config.layers.length === 0) {
                 this.layersContainer.innerHTML = '<div style="text-align:center; padding:50px; color:#64748b;">Nenhuma camada carregada</div>';
                 this.layersContainer.innerHTML = '<div style="padding:50px; text-align:center; color:#64748b;">Nenhum andar configurado</div>';
                 return;
                 return;
             }
             }
              
              
            // Verificar se o andar atual existe
             if (!this.config.layers.find(l => l.id === this.currentFloor)) {
             if (!this.config.layers.find(l => l.id === this.currentFloor)) {
                 this.currentFloor = this.config.layers[0]?.id || 0;
                 this.currentFloor = this.config.layers[0]?.id || 0;
Linha 443: Linha 373:
             }
             }
              
              
             this.config.layers.forEach((layer) => {
             this.config.layers.forEach(layer => {
                 const $layer = document.createElement('div');
                 const $layer = document.createElement('div');
                 $layer.className = 'map-layer';
                 $layer.className = 'mw-layer';
                 $layer.setAttribute('data-floor', layer.id);
                 $layer.setAttribute('data-floor', layer.id);
                 $layer.style.display = layer.id === this.currentFloor ? 'block' : 'none';
                 $layer.style.display = layer.id === this.currentFloor ? 'block' : 'none';
                 $layer.style.transform = `translate(${layer.alignment?.offsetX || 0}px, ${layer.alignment?.offsetY || 0}px)`;
                 $layer.style.transform = `translate(${layer.alignment?.offsetX || 0}px, ${layer.alignment?.offsetY || 0}px)`;
                $layer.style.opacity = (layer.opacity || 100) / 100;
                  
                  
                 const $img = document.createElement('img');
                 const $img = document.createElement('img');
                 $img.className = 'map-image';
                 $img.className = 'mw-image';
                 $img.style.transform = `scale(${this.currentZoom})`;
                 $img.style.transform = `scale(${this.zoom})`;
                 $img.style.transformOrigin = '0 0';
                 $img.style.transformOrigin = '0 0';
                  
                  
Linha 459: Linha 388:
                     $img.src = layer.imagePath;
                     $img.src = layer.imagePath;
                 } else {
                 } else {
                    // Imagem placeholder
                     const canvas = document.createElement('canvas');
                     const canvas = document.createElement('canvas');
                     canvas.width = 800;
                     canvas.width = 800;
Linha 478: Linha 408:
                 $layer.appendChild($img);
                 $layer.appendChild($img);
                  
                  
                 const $markersContainer = document.createElement('div');
                // Adicionar marcadores
                 $markersContainer.style.position = 'absolute';
                 const $markersDiv = document.createElement('div');
                 $markersContainer.style.top = '0';
                 $markersDiv.style.position = 'absolute';
                 $markersContainer.style.left = '0';
                 $markersDiv.style.top = '0';
                 $markersContainer.style.width = '100%';
                 $markersDiv.style.left = '0';
                 $markersContainer.style.height = '100%';
                 $markersDiv.style.width = '100%';
                 $markersContainer.style.pointerEvents = 'none';
                 $markersDiv.style.height = '100%';
                 $markersDiv.style.pointerEvents = 'none';
                  
                  
                 if (layer.markers && layer.markers.length > 0) {
                 if (layer.markers) {
                     layer.markers.forEach(marker => {
                     layer.markers.forEach(marker => {
                         const $marker = this.createMarker(marker, layer.id);
                         const $marker = this.createMarker(marker, layer.id);
                         $markersContainer.appendChild($marker);
                         $markersDiv.appendChild($marker);
                         this.markers.push({ element: $marker, data: marker });
                         this.markers.push($marker);
                     });
                     });
                 }
                 }
                  
                  
                 $layer.appendChild($markersContainer);
                 $layer.appendChild($markersDiv);
                 this.layersContainer.appendChild($layer);
                 this.layersContainer.appendChild($layer);
                 this.layers.push($layer);
                 this.layers.push($layer);
Linha 502: Linha 433:
         createMarker(marker, floorId) {
         createMarker(marker, floorId) {
             const $marker = document.createElement('div');
             const $marker = document.createElement('div');
             $marker.className = 'map-marker';
             $marker.className = 'mw-marker';
             $marker.setAttribute('data-marker-id', marker.id);
             $marker.setAttribute('data-marker-id', marker.id);
             $marker.setAttribute('data-floor', floorId);
             $marker.setAttribute('data-floor', floorId);
Linha 508: Linha 439:
             $marker.setAttribute('data-y', marker.y);
             $marker.setAttribute('data-y', marker.y);
              
              
            $marker.style.position = 'absolute';
             $marker.style.left = `${marker.x * this.zoom}px`;
             $marker.style.left = `${marker.x * this.currentZoom}px`;
             $marker.style.top = `${marker.y * this.zoom}px`;
             $marker.style.top = `${marker.y * this.currentZoom}px`;
            $marker.style.zIndex = '100';
            $marker.style.transform = 'translate(-50%, -50%)';
              
              
             const hasAction = marker.action && marker.action !== 'none';
             // Ícone
            $marker.style.cursor = hasAction ? 'pointer' : 'default';
             const iconSize = Math.max(20, Math.floor(20 * this.zoom));
           
             let iconHtml = marker.iconBase64 ?
             const iconSize = Math.max(20, Math.floor(20 * this.currentZoom));
             let iconHtml = marker.iconBase64 ?  
                 `<img src="${marker.iconBase64}" style="width:${iconSize}px; height:${iconSize}px;">` :
                 `<img src="${marker.iconBase64}" style="width:${iconSize}px; height:${iconSize}px;">` :
                 `<span style="font-size:${iconSize}px;">📍</span>`;
                 `<span style="font-size:${iconSize}px;">📍</span>`;
              
              
             let actionText = '';
            // Tooltip
             let actionLabel = '';
             switch(marker.action) {
             switch(marker.action) {
                 case 'popup': actionText = '📋 Informações'; break;
                 case 'popup': actionLabel = '📋 Informações'; break;
                 case 'nextFloor': actionText = '⬆️ Próximo andar'; break;
                 case 'nextFloor': actionLabel = '⬆️ Próximo andar'; break;
                 case 'prevFloor': actionText = '⬇️ Andar anterior'; break;
                 case 'prevFloor': actionLabel = '⬇️ Andar anterior'; break;
                 case 'gotoFloor': actionText = '🎯 Ir para andar'; break;
                 case 'gotoFloor': actionLabel = '🎯 Ir para andar'; break;
                 case 'link': actionText = '🔗 Link externo'; break;
                 case 'link': actionLabel = '🔗 Link'; break;
                 default: actionText = '📍 Clique';
                 default: actionLabel = 'Clique';
             }
             }
              
              
             $marker.innerHTML = `
             $marker.innerHTML = `
                 <div class="marker-icon">${iconHtml}</div>
                 <div class="mw-marker-icon">${iconHtml}</div>
                 <div class="marker-tooltip">
                 <div class="mw-marker-tooltip">
                     <strong>${marker.name || 'Marcador'}</strong><br>
                     <strong>${marker.name || 'Marcador'}</strong><br>
                     <small>${actionText}</small>
                     <small>${actionLabel}</small>
                 </div>
                 </div>
                 <div class="marker-badge ${!marker.hasBadge ? 'hidden' : ''}">${marker.hasBadge ? (marker.badgeNumber || marker.number || '') : ''}</div>
                 <div class="mw-badge ${!marker.hasBadge ? 'hidden' : ''}">${marker.hasBadge ? (marker.badgeNumber || marker.number || '') : ''}</div>
             `;
             `;
              
              
             // Hover effects
             // Evento de clique
             $marker.addEventListener('mouseenter', () => {
             if (marker.action !== 'none') {
                $marker.style.transform = 'translate(-50%, -50%) scale(1.15)';
            });
            $marker.addEventListener('mouseleave', () => {
                $marker.style.transform = 'translate(-50%, -50%) scale(1)';
            });
           
            // Clique action
            if (hasAction) {
                 $marker.addEventListener('click', (e) => {
                 $marker.addEventListener('click', (e) => {
                     e.stopPropagation();
                     e.stopPropagation();
Linha 561: Linha 480:
          
          
         executeAction(marker) {
         executeAction(marker) {
            console.log('Ação:', marker.action, marker.name);
           
             switch(marker.action) {
             switch(marker.action) {
                 case 'popup':
                 case 'popup':
Linha 584: Linha 505:
         }
         }
          
          
         initPopup() {
         createPopup() {
             if (document.querySelector('.mw-map-viewer-popup')) return;
             if (document.querySelector('.mw-map-popup')) return;
              
              
             const popupHtml = `
             const popupHtml = `
                 <div class="mw-map-viewer-popup" style="display:none;">
                 <div class="mw-map-popup">
                     <div class="popup-content">
                     <div class="mw-popup-content">
                         <span class="popup-close">&times;</span>
                         <span class="mw-popup-close">&times;</span>
                         <div class="popup-body"></div>
                         <div class="mw-popup-body"></div>
                     </div>
                     </div>
                 </div>
                 </div>
Linha 597: Linha 518:
             document.body.insertAdjacentHTML('beforeend', popupHtml);
             document.body.insertAdjacentHTML('beforeend', popupHtml);
              
              
             this.popup = document.querySelector('.mw-map-viewer-popup');
             this.popup = document.querySelector('.mw-map-popup');
             this.popupBody = this.popup?.querySelector('.popup-body');
             this.popupBody = this.popup?.querySelector('.mw-popup-body');
              
              
             this.popup?.querySelector('.popup-close')?.addEventListener('click', () => this.closePopup());
             this.popup?.querySelector('.mw-popup-close')?.addEventListener('click', () => this.closePopup());
             this.popup?.addEventListener('click', (e) => {
             this.popup?.addEventListener('click', (e) => {
                 if (e.target === this.popup) this.closePopup();
                 if (e.target === this.popup) this.closePopup();
Linha 607: Linha 528:
          
          
         showPopup(marker) {
         showPopup(marker) {
             if (!this.popup) this.initPopup();
             if (!this.popup) this.createPopup();
              
              
             const iconHtml = marker.iconBase64 ?
             const iconHtml = marker.iconBase64 ?
                 `<img src="${marker.iconBase64}" style="width:32px; height:32px; vertical-align:middle; margin-right:8px;">` :
                 `<img src="${marker.iconBase64}" style="width:24px; height:24px; vertical-align:middle; margin-right:8px;">` :
                 `<span style="font-size:24px; margin-right:8px;">📍</span>`;
                 `<span style="font-size:20px; margin-right:8px;">📍</span>`;
              
              
             this.popupBody.innerHTML = `
             this.popupBody.innerHTML = `
                 ${marker.actionData?.image ? `<img src="${marker.actionData.image}" alt="${marker.name}">` : ''}
                 ${marker.actionData?.image ? `<img src="${marker.actionData.image}" style="max-width:100%; border-radius:8px; margin-bottom:15px;">` : ''}
                 <div style="display:flex; align-items:center; margin-bottom:12px;">
                 <div style="display:flex; align-items:center;">
                     ${iconHtml}
                     ${iconHtml}
                     <h3 style="margin:0;">${marker.name}</h3>
                     <h3 style="margin:0;">${marker.name}</h3>
                 </div>
                 </div>
                 <p>${marker.actionData?.text || 'Sem informações'}</p>
                 <p style="margin-top:10px;">${marker.actionData?.text || 'Sem informações'}</p>
             `;
             `;
              
              
Linha 630: Linha 551:
          
          
         updateMarkersPosition() {
         updateMarkersPosition() {
             this.markers.forEach(({ element, data }) => {
             this.markers.forEach($marker => {
                 if (parseInt(element.getAttribute('data-floor')) === this.currentFloor) {
                 const floorId = parseInt($marker.getAttribute('data-floor'));
                     element.style.left = `${data.x * this.currentZoom}px`;
                if (floorId === this.currentFloor) {
                     element.style.top = `${data.y * this.currentZoom}px`;
                     const x = parseFloat($marker.getAttribute('data-x'));
                    const y = parseFloat($marker.getAttribute('data-y'));
                    $marker.style.left = `${x * this.zoom}px`;
                     $marker.style.top = `${y * this.zoom}px`;
                      
                      
                     const iconSize = Math.max(20, Math.floor(20 * this.currentZoom));
                     const iconSize = Math.max(20, Math.floor(20 * this.zoom));
                     const iconDiv = element.querySelector('.marker-icon');
                     const iconDiv = $marker.querySelector('.mw-marker-icon');
                     if (iconDiv) {
                     if (iconDiv) {
                         if (data.iconBase64) {
                        const hasImage = $marker.getAttribute('data-has-image');
                             iconDiv.innerHTML = `<img src="${data.iconBase64}" style="width:${iconSize}px; height:${iconSize}px;">`;
                         if (hasImage) {
                             // manter imagem
                         } else {
                         } else {
                             iconDiv.innerHTML = `<span style="font-size:${iconSize}px;">📍</span>`;
                             iconDiv.innerHTML = `<span style="font-size:${iconSize}px;">📍</span>`;
Linha 649: Linha 574:
          
          
         zoomIn() {
         zoomIn() {
             if (this.currentZoom < this.maxZoom) {
             if (this.zoom < this.maxZoom) {
                 const oldZoom = this.currentZoom;
                 const oldZoom = this.zoom;
                 this.currentZoom = Math.min(this.currentZoom + this.zoomStep, this.maxZoom);
                 this.zoom = Math.min(this.zoom + this.zoomStep, this.maxZoom);
                 this.applyZoom(oldZoom);
                 this.applyZoom(oldZoom);
             }
             }
Linha 657: Linha 582:
          
          
         zoomOut() {
         zoomOut() {
             if (this.currentZoom > this.minZoom) {
             if (this.zoom > this.minZoom) {
                 const oldZoom = this.currentZoom;
                 const oldZoom = this.zoom;
                 this.currentZoom = Math.max(this.currentZoom - this.zoomStep, this.minZoom);
                 this.zoom = Math.max(this.zoom - this.zoomStep, this.minZoom);
                 this.applyZoom(oldZoom);
                 this.applyZoom(oldZoom);
             }
             }
Linha 665: Linha 590:
          
          
         applyZoom(oldZoom) {
         applyZoom(oldZoom) {
             this.container.querySelectorAll('.map-image').forEach(img => {
            // Aplicar zoom nas imagens
                 img.style.transform = `scale(${this.currentZoom})`;
             this.container.querySelectorAll('.mw-image').forEach(img => {
                 img.style.transform = `scale(${this.zoom})`;
             });
             });
              
              
            // Ajustar scroll para manter centro
             const rect = this.viewport.getBoundingClientRect();
             const rect = this.viewport.getBoundingClientRect();
             const cx = rect.width / 2, cy = rect.height / 2;
             const cx = rect.width / 2;
            const cy = rect.height / 2;
             const sx = (this.viewport.scrollLeft + cx) / oldZoom;
             const sx = (this.viewport.scrollLeft + cx) / oldZoom;
             const sy = (this.viewport.scrollTop + cy) / oldZoom;
             const sy = (this.viewport.scrollTop + cy) / oldZoom;
              
              
             this.viewport.scrollLeft = sx * this.currentZoom - cx;
             this.viewport.scrollLeft = sx * this.zoom - cx;
             this.viewport.scrollTop = sy * this.currentZoom - cy;
             this.viewport.scrollTop = sy * this.zoom - cy;
              
              
             this.updateMarkersPosition();
             this.updateMarkersPosition();
             if (this.zoomLevelSpan) {
             if (this.zoomTextSpan) {
                 this.zoomLevelSpan.textContent = `${Math.round(this.currentZoom * 100)}%`;
                 this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
             }
             }
         }
         }
          
          
         resetView() {
         reset() {
             this.currentZoom = this.config.mapConfig?.defaultZoom || 1;
             this.zoom = this.config.mapConfig?.defaultZoom || 1;
             this.container.querySelectorAll('.map-image').forEach(img => {
             this.container.querySelectorAll('.mw-image').forEach(img => {
                 img.style.transform = `scale(${this.currentZoom})`;
                 img.style.transform = `scale(${this.zoom})`;
             });
             });
             this.viewport.scrollLeft = 0;
             this.viewport.scrollLeft = 0;
             this.viewport.scrollTop = 0;
             this.viewport.scrollTop = 0;
             this.updateMarkersPosition();
             this.updateMarkersPosition();
             if (this.zoomLevelSpan) {
             if (this.zoomTextSpan) {
                 this.zoomLevelSpan.textContent = `${Math.round(this.currentZoom * 100)}%`;
                 this.zoomTextSpan.textContent = `${Math.round(this.zoom * 100)}%`;
             }
             }
         }
         }
Linha 725: Linha 653:
             setTimeout(() => this.updateMarkersPosition(), 50);
             setTimeout(() => this.updateMarkersPosition(), 50);
         }
         }
    }
   
    // ==================== INICIALIZAÇÃO DO WIDGET ====================
    function initWidget() {
        // Procurar todos os containers do widget
        const containers = document.querySelectorAll('[data-mw-map]');
          
          
         // Método para atualizar configuração dinamicamente
         containers.forEach(container => {
        updateConfig(config) {
            const jsonStr = container.getAttribute('data-mw-config');
            this.config = this.normalizeConfig(config);
            if (!jsonStr) return;
             this.renderLayers();
           
             this.updateMarkersPosition();
            try {
         }
                const config = JSON.parse(jsonStr);
                new MWMap(container, config);
             } catch(e) {
                console.error('Erro ao criar mapa:', e);
                container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">Erro ao carregar mapa</div>';
             }
        });
    }
   
    // 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();
     }
     }
      
      
     // Registrar widget globalmente
     // Expor para uso global
     window.MWMapViewer = MWMapViewer;
     window.MWMap = MWMap;
    window.MWMapViewerStyles = styles;
})();
})();
</script>
</script>
</includeonly>

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