Mudanças entre as edições de "Widget:MapaViewer"
Ir para navegação
Ir para pesquisar
| (7 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 1: | Linha 1: | ||
<includeonly><div id="mapa-viewer-{ | <includeonly> | ||
<div id="mapa-viewer-<!--{$id|escape:'quotes'|default:'mapa-default'}-->" style="width: <!--{$largura|escape:'quotes'|default:'100%'}-->; height: <!--{$altura|escape:'quotes'|default:'500px'}-->;"></div> | |||
<script> | <script> | ||
class MapaViewer { | |||
constructor(containerId, configOrUrl) { | constructor(containerId, configOrUrl) { | ||
this.container = document.getElementById(containerId); | this.container = document.getElementById(containerId); | ||
| Linha 27: | Linha 26: | ||
if (typeof configOrUrl === 'string') { | if (typeof configOrUrl === 'string') { | ||
if (configOrUrl.startsWith('http | if (configOrUrl.startsWith('http')) { | ||
this.loadJSON(configOrUrl); | this.loadJSON(configOrUrl); | ||
} else { | } else { | ||
| Linha 48: | Linha 47: | ||
this.viewport = document.createElement('div'); | this.viewport = document.createElement('div'); | ||
this.viewport.style.cssText = | this.viewport.style.cssText = 'width:100%;height:100%;overflow:hidden;cursor:grab;position:relative;'; | ||
this.layersContainer = document.createElement('div'); | this.layersContainer = document.createElement('div'); | ||
this.layersContainer.style.cssText = | this.layersContainer.style.cssText = 'position:relative;min-width:100%;min-height:100%;'; | ||
this.viewport.appendChild(this.layersContainer); | this.viewport.appendChild(this.layersContainer); | ||
| Linha 175: | Linha 164: | ||
this.layers = []; | this.layers = []; | ||
this.markers = []; | this.markers = []; | ||
this.config.layers.forEach((layer) => { | this.config.layers.forEach((layer) => { | ||
const $layer = document.createElement('div'); | const $layer = document.createElement('div'); | ||
| Linha 185: | Linha 175: | ||
$layer.style.transform = `translate(${layer.offsetX}px, ${layer.offsetY}px)`; | $layer.style.transform = `translate(${layer.offsetX}px, ${layer.offsetY}px)`; | ||
$layer.style.opacity = (layer.opacity || 100) / 100; | $layer.style.opacity = (layer.opacity || 100) / 100; | ||
const $img = document.createElement('img'); | const $img = document.createElement('img'); | ||
$img.className = 'mapa-image'; | $img.className = 'mapa-image'; | ||
| Linha 192: | Linha 183: | ||
$img.style.pointerEvents = 'none'; | $img.style.pointerEvents = 'none'; | ||
$img.src = layer.imageUrl || ''; | $img.src = layer.imageUrl || ''; | ||
$img.onerror = () => { | $img.onerror = () => { | ||
const canvas = document.createElement('canvas'); | const canvas = document.createElement('canvas'); | ||
| Linha 204: | Linha 196: | ||
$img.src = canvas.toDataURL(); | $img.src = canvas.toDataURL(); | ||
}; | }; | ||
$img.onload = () => { | $img.onload = () => { | ||
this.layersContainer.style.width = `${$img.width}px`; | this.layersContainer.style.width = `${$img.width}px`; | ||
| Linha 209: | Linha 202: | ||
this.centerMap(); | this.centerMap(); | ||
}; | }; | ||
$layer.appendChild($img); | $layer.appendChild($img); | ||
const $markersContainer = document.createElement('div'); | const $markersContainer = document.createElement('div'); | ||
$markersContainer.style.position = 'absolute'; | $markersContainer.style.position = 'absolute'; | ||
| Linha 217: | Linha 212: | ||
$markersContainer.style.height = '100%'; | $markersContainer.style.height = '100%'; | ||
$markersContainer.style.pointerEvents = 'none'; | $markersContainer.style.pointerEvents = 'none'; | ||
if (layer.markers && layer.markers.length) { | if (layer.markers && layer.markers.length) { | ||
layer.markers.forEach(marker => { | layer.markers.forEach(marker => { | ||
| Linha 224: | Linha 220: | ||
}); | }); | ||
} | } | ||
$layer.appendChild($markersContainer); | $layer.appendChild($markersContainer); | ||
this.layersContainer.appendChild($layer); | this.layersContainer.appendChild($layer); | ||
| Linha 237: | Linha 234: | ||
$marker.setAttribute('data-marker-id', marker.id); | $marker.setAttribute('data-marker-id', marker.id); | ||
$marker.setAttribute('data-floor', floorId); | $marker.setAttribute('data-floor', floorId); | ||
$marker.style.position = 'absolute'; | $marker.style.position = 'absolute'; | ||
$marker.style.left = `${marker.x * this.currentZoom}px`; | $marker.style.left = `${marker.x * this.currentZoom}px`; | ||
| Linha 245: | Linha 240: | ||
$marker.style.transform = 'translate(-50%, -50%)'; | $marker.style.transform = 'translate(-50%, -50%)'; | ||
$marker.style.pointerEvents = 'auto'; | $marker.style.pointerEvents = 'auto'; | ||
const hasAction = marker.action && marker.action !== 'none'; | const hasAction = marker.action && marker.action !== 'none'; | ||
$marker.style.cursor = hasAction ? 'pointer' : 'default'; | $marker.style.cursor = hasAction ? 'pointer' : 'default'; | ||
const iconSize = Math.max(16, Math.floor(16 * this.currentZoom)); | const iconSize = Math.max(16, Math.floor(16 * this.currentZoom)); | ||
let iconHtml = ''; | let iconHtml = ''; | ||
if (marker.iconUrl && marker.iconUrl !== '') { | if (marker.iconUrl && marker.iconUrl !== '') { | ||
iconHtml = `<img src="${marker.iconUrl}" style="width:${iconSize}px; height:${iconSize}px; object-fit:contain;">`; | iconHtml = `<img src="${marker.iconUrl}" style="width:${iconSize}px; height:${iconSize}px; object-fit:contain;">`; | ||
| Linha 254: | Linha 252: | ||
iconHtml = `<i class="fas fa-map-marker-alt" style="font-size:${iconSize}px;"></i>`; | iconHtml = `<i class="fas fa-map-marker-alt" style="font-size:${iconSize}px;"></i>`; | ||
} | } | ||
$marker.innerHTML = ` | $marker.innerHTML = ` | ||
<div class="mapa-marker-icon" style="display:flex; align-items:center; justify-content:center; pointer-events:none;">${iconHtml}</div> | <div class="mapa-marker-icon" style="display:flex; align-items:center; justify-content:center; pointer-events:none;">${iconHtml}</div> | ||
<div class="mapa-marker-tooltip" 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;"> | <div class="mapa-marker-tooltip" 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 | <strong>${marker.name || 'Marcador'}</strong> | ||
</div> | </div> | ||
`; | `; | ||
$marker.addEventListener('mouseenter', () => { | $marker.addEventListener('mouseenter', () => { | ||
$marker.style.transform = 'translate(-50%, -50%) scale(1.1)'; | $marker.style.transform = 'translate(-50%, -50%) scale(1.1)'; | ||
| Linha 270: | Linha 268: | ||
} | } | ||
}); | }); | ||
$marker.addEventListener('mouseleave', () => { | $marker.addEventListener('mouseleave', () => { | ||
$marker.style.transform = 'translate(-50%, -50%) scale(1)'; | $marker.style.transform = 'translate(-50%, -50%) scale(1)'; | ||
| Linha 278: | Linha 277: | ||
} | } | ||
}); | }); | ||
if (hasAction) { | if (hasAction) { | ||
$marker.addEventListener('click', (e) => { | $marker.addEventListener('click', (e) => { | ||
| Linha 284: | Linha 284: | ||
}); | }); | ||
} | } | ||
return $marker; | return $marker; | ||
} | } | ||
| Linha 343: | Linha 332: | ||
}); | }); | ||
} | } | ||
const content = modal.querySelector('#mapa-viewer-popup-content'); | const content = modal.querySelector('#mapa-viewer-popup-content'); | ||
content.innerHTML = ` | content.innerHTML = ` | ||
<div style="display:flex; align-items:center; margin-bottom:12px;"> | <div style="display:flex; align-items:center; margin-bottom:12px;"> | ||
<h3 style="margin:0; color:#a5b4fc;">${marker.name}</h3> | <h3 style="margin:0; color:#a5b4fc;">${marker.name}</h3> | ||
</div> | </div> | ||
| Linha 365: | Linha 350: | ||
element.style.left = `${zoomedX}px`; | element.style.left = `${zoomedX}px`; | ||
element.style.top = `${zoomedY}px`; | element.style.top = `${zoomedY}px`; | ||
const iconSize = Math.max(24, Math.floor(24 * this.currentZoom)); | const iconSize = Math.max(24, Math.floor(24 * this.currentZoom)); | ||
const iconDiv = element.querySelector('.mapa-marker-icon'); | const iconDiv = element.querySelector('.mapa-marker-icon'); | ||
| Linha 373: | Linha 359: | ||
img.style.width = `${iconSize}px`; | img.style.width = `${iconSize}px`; | ||
img.style.height = `${iconSize}px`; | img.style.height = `${iconSize}px`; | ||
} | } | ||
} else { | } else { | ||
| Linha 380: | Linha 364: | ||
if (icon) { | if (icon) { | ||
icon.style.fontSize = `${iconSize}px`; | icon.style.fontSize = `${iconSize}px`; | ||
} | } | ||
} | } | ||
| Linha 424: | Linha 406: | ||
const scrollX = (this.viewport.scrollLeft + centerX) / oldZoom; | const scrollX = (this.viewport.scrollLeft + centerX) / oldZoom; | ||
const scrollY = (this.viewport.scrollTop + centerY) / oldZoom; | const scrollY = (this.viewport.scrollTop + centerY) / oldZoom; | ||
document.querySelectorAll('.mapa-image').forEach(img => { | document.querySelectorAll('.mapa-image').forEach(img => { | ||
img.style.transform = `scale(${this.currentZoom})`; | img.style.transform = `scale(${this.currentZoom})`; | ||
}); | }); | ||
this.updateMarkersPosition(); | this.updateMarkersPosition(); | ||
this.viewport.scrollLeft = scrollX * this.currentZoom - centerX; | this.viewport.scrollLeft = scrollX * this.currentZoom - centerX; | ||
| Linha 442: | Linha 426: | ||
prevFloor() { | prevFloor() { | ||
const | const layers = this.config.layers.sort((a,b) => a.id - b.id); | ||
if ( | const currentIndex = layers.findIndex(l => l.id === this.currentFloor); | ||
this.goToFloor( | if (currentIndex > 0) { | ||
this.goToFloor(layers[currentIndex - 1].id); | |||
} | } | ||
} | } | ||
nextFloor() { | nextFloor() { | ||
const | const layers = this.config.layers.sort((a,b) => a.id - b.id); | ||
if ( | const currentIndex = layers.findIndex(l => l.id === this.currentFloor); | ||
this.goToFloor( | if (currentIndex < layers.length - 1) { | ||
this.goToFloor(layers[currentIndex + 1].id); | |||
} | } | ||
} | } | ||
| Linha 481: | Linha 467: | ||
} | } | ||
} | } | ||
const viewportWidth = this.viewport.clientWidth; | const viewportWidth = this.viewport.clientWidth; | ||
const viewportHeight = this.viewport.clientHeight; | const viewportHeight = this.viewport.clientHeight; | ||
let screenX = | let screenX = x * this.currentZoom; | ||
let screenY = | let screenY = y * this.currentZoom; | ||
let targetScrollX = screenX - (viewportWidth / 2); | let targetScrollX = screenX - (viewportWidth / 2); | ||
let targetScrollY = screenY - (viewportHeight / 2); | let targetScrollY = screenY - (viewportHeight / 2); | ||
this.viewport.scrollLeft = Math.max(0, targetScrollX); | this.viewport.scrollLeft = Math.max(0, targetScrollX); | ||
this.viewport.scrollTop = Math.max(0, targetScrollY); | this.viewport.scrollTop = Math.max(0, targetScrollY); | ||
this.showTemporaryHighlight(x, y); | |||
} | } | ||
showTemporaryHighlight(x, y) { | |||
const highlight = document.createElement('div'); | |||
highlight.style.cssText = ` | |||
position: absolute; | |||
width: 60px; | |||
height: 60px; | |||
left: ${(x + (this.currentFloor?.offsetX || 0)) * this.currentZoom}px; | |||
top: ${(y + (this.currentFloor?.offsetY || 0)) * this.currentZoom}px; | |||
transform: translate(-50%, -50%); | |||
background: radial-gradient(circle, rgba(255,215,0,0.8) 0%, rgba(255,215,0,0) 70%); | |||
border-radius: 50%; | |||
pointer-events: none; | |||
z-index: 200; | |||
animation: mapa-highlight-pulse 0.6s ease-out 3; | |||
`; | |||
this.layersContainer.appendChild(highlight); | |||
setTimeout(() => { | |||
highlight.remove(); | |||
}, 1800); | |||
} | |||
setupKeyboardShortcuts() { | setupKeyboardShortcuts() { | ||
const handler = (e) => { | const handler = (e) => { | ||
| Linha 544: | Linha 533: | ||
} | } | ||
(function() { | |||
var containerId = 'mapa-viewer-<!--{$id|escape:'quotes'|default:'mapa-default'}-->'; | |||
var configJSON = <!--{$configJSON|default:'null'}-->; // Recebe o JSON | |||
var urlJSON = '<!--{$url|escape:'quotes'}-->'; // Recebe URL do JSON | |||
// Nome da variável global única para este mapa | |||
var globalVarName = 'mapaViewer_<!--{$id|escape:'quotes'|default:'mapa-default'}-->'; | |||
function iniciarMapa() { | |||
var container = document.getElementById(containerId); | |||
if (!container) return; | |||
if (container.hasAttribute('data-iniciado')) return; | |||
container.setAttribute('data-iniciado', 'true'); | |||
var viewer = null; | |||
if (configJSON && configJSON !== 'null') { | |||
// Configuração inline | |||
viewer = new MapaViewer(containerId, configJSON); | |||
} else if (urlJSON) { | |||
// Configuração via URL | |||
fetch(urlJSON) | |||
.then(response => response.json()) | |||
.then(config => { | |||
window[globalVarName] = new MapaViewer(containerId, config); | |||
}) | |||
.catch(error => { | |||
console.error('Erro ao carregar mapa:', error); | |||
container.innerHTML = '<div style="color:red;padding:20px;">❌ Erro ao carregar mapa: ' + error.message + '</div>'; | |||
}); | |||
return; // Sai porque o fetch é assíncrono | |||
} else { | |||
container.innerHTML = '<div style="color:red;padding:20px;">❌ Nenhuma configuração fornecida</div>'; | |||
return; | |||
} | } | ||
// Guarda a instância globalmente para acesso externo | |||
if (viewer) { | |||
window[globalVarName] = viewer; | |||
console.log('Mapa inicializado! Use window.' + globalVarName + ' para controlar'); | |||
console.log('Exemplo: window.' + globalVarName + '.nextFloor()'); | |||
} | } | ||
} | } | ||
if (document.readyState === 'loading') { | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', | document.addEventListener('DOMContentLoaded', iniciarMapa); | ||
} else { | } else { | ||
iniciarMapa(); | |||
} | } | ||
})(); | })(); | ||
</script></includeonly> | </script> | ||
</includeonly> | |||