Mudanças entre as edições de "Widget:MapViewer.js"
Ir para navegação
Ir para pesquisar
| (31 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
| Linha 1: | Linha 1: | ||
<includeonly><div id="mapa-viewer-<!--{$id|escape:'quotes'}-->" style="width:<!--{$largura|escape:'quotes'|default:'100%'}-->; height:<!--{$altura|escape:'quotes'|default:'500px'}-->; background:#0f172a; border-radius:12px; overflow:hidden; position:relative;"> | <includeonly><div id="mapa-viewer-<!--{$id|escape:'quotes'|default:'mapa1'}-->" style="width:<!--{$largura|escape:'quotes'|default:'100%'}-->; height:<!--{$altura|escape:'quotes'|default:'500px'}-->; background:#0f172a; border-radius:12px; overflow:hidden; position:relative;"> | ||
<div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#64748b; text-align:center;"> | <div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#64748b; text-align:center;"> | ||
🗺️ Carregando mapa... | |||
</div> | </div> | ||
</div> | </div> | ||
<script> | <script> | ||
// @noescape | // @noescape | ||
(function() { | (function() { | ||
var id = '<!--{$id|escape:'quotes'}-->'; | var id = '<!--{$id|escape:'quotes'|default:'mapa1'}-->'; | ||
var | var container = document.getElementById('mapa-viewer-' + id); | ||
if (!container) return; | if (!container) return; | ||
// | // JSON vem URL encoded para não quebrar o parser | ||
var | var jsonEncoded = '<!--{$json|escape:'quotes'}-->'; | ||
var jsonString = decodeURIComponent(jsonEncoded); | |||
var mapConfig; | var mapConfig; | ||
try { | try { | ||
mapConfig = JSON.parse(jsonString); | mapConfig = JSON.parse(jsonString); | ||
} catch(e) { | } catch(e) { | ||
container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">❌ Erro no JSON: ' + e.message + '</div>'; | container.innerHTML = '<div style="padding:20px; text-align:center; color:#ef4444;">❌ Erro no JSON: ' + e.message + '</div>'; | ||
return; | return; | ||
} | } | ||
| Linha 40: | Linha 29: | ||
} | } | ||
// Iniciar o | // Iniciar o visualizador | ||
iniciarMapaViewer( | iniciarMapaViewer(container, mapConfig); | ||
function iniciarMapaViewer( | function iniciarMapaViewer(container, config) { | ||
container.innerHTML = ''; | container.innerHTML = ''; | ||
container.style.position = 'relative'; | container.style.position = 'relative'; | ||
container.style.overflow = 'hidden'; | container.style.overflow = 'hidden'; | ||
// Toolbar | // Toolbar | ||
var toolbar = document.createElement('div'); | var toolbar = document.createElement('div'); | ||
toolbar.style.cssText = 'position:absolute; top:10px; left:10px; right:10px; z-index:100; display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap;'; | toolbar.style.cssText = 'position:absolute; top:10px; left:10px; right:10px; z-index:100; display:flex; justify-content:space-between; gap:8px; flex-wrap:wrap;'; | ||
toolbar.innerHTML = | toolbar.innerHTML = '<div style="display:flex; gap:5px; background:rgba(0,0,0,0.7); padding:5px 10px; border-radius:30px;">' + | ||
'<button id="zoom-in-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">+</button>' + | |||
'<button id="zoom-out-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">-</button>' + | |||
'<button id="reset-' + id + '" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">⟳</button>' + | |||
'</div>' + | |||
</div> | '<div style="background:rgba(0,0,0,0.7); padding:5px 15px; border-radius:30px; color:white; font-size:12px;"><span id="floor-name-' + id + '">' + (config.layers[0]?.name || 'Mapa') + '</span></div>' + | ||
<div style="background:rgba(0,0,0,0.7); padding:5px 15px; border-radius:30px; color:white; font-size:12px;"> | '<div id="zoom-level-' + id + '" style="background:rgba(0,0,0,0.7); padding:5px 12px; border-radius:30px; color:#a5b4fc; font-size:12px;">100%</div>'; | ||
<div style="background:rgba(0,0,0,0.7); padding:5px 12px; border-radius:30px; color:#a5b4fc; font-size:12px; | |||
// Viewport | // Viewport | ||
var viewport = document.createElement('div'); | var viewport = document.createElement('div'); | ||
viewport.style.cssText = 'width:100%; height:100%; overflow:auto; cursor:grab; background:#0f172a;'; | viewport.style.cssText = 'width:100%; height:100%; overflow:auto; cursor:grab; background:#0f172a;'; | ||
var camadasDiv = document.createElement('div'); | |||
camadasDiv.style.cssText = 'position:relative; min-width:100%; min-height:100%;'; | |||
viewport.appendChild(camadasDiv); | |||
var | // Navegação | ||
var navDiv = document.createElement('div'); | |||
navDiv.style.cssText = '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;'; | |||
navDiv.innerHTML = '<button id="prev-' + id + '" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer;">▲</button>' + | |||
'<button id="next-' + id + '" style="background:#334155; border:none; color:white; width:36px; height:36px; border-radius:50%; cursor:pointer;">▼</button>'; | |||
// Coordenadas | // Coordenadas | ||
| Linha 80: | Linha 65: | ||
coordsDiv.style.cssText = '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;'; | coordsDiv.style.cssText = '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;'; | ||
coordsDiv.textContent = '📍 0, 0'; | coordsDiv.textContent = '📍 0, 0'; | ||
container.appendChild(toolbar); | container.appendChild(toolbar); | ||
| Linha 95: | Linha 72: | ||
// Variáveis | // Variáveis | ||
var | var zoomAtual = config.mapConfig.defaultZoom || 1; | ||
var | var andarAtual = config.mapConfig.initialFloor || 0; | ||
var | var zoomMin = config.mapConfig.minZoom || 0.5; | ||
var | var zoomMax = config.mapConfig.maxZoom || 3; | ||
var | var zoomPasso = config.mapConfig.zoomStep || 0.1; | ||
var | var camadas = []; | ||
var | var marcadores = []; | ||
var | var arrastando = false; | ||
var | var arrasteInicio = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 }; | ||
function renderizarCamadas() { | function renderizarCamadas() { | ||
camadasDiv.innerHTML = ''; | |||
camadas = []; | |||
marcadores = []; | |||
var | if (!config.layers || config.layers.length === 0) return; | ||
var andarExiste = false; | |||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
if (config.layers[i].id === | if (config.layers[i].id === andarAtual) andarExiste = true; | ||
} | } | ||
if (!andarExiste && config.layers.length > 0) andarAtual = config.layers[0].id; | |||
var | var andarInfo = null; | ||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
if (config.layers[i].id === | if (config.layers[i].id === andarAtual) andarInfo = config.layers[i]; | ||
} | } | ||
var nomeSpan = document.getElementById('floor-name-' + id); | |||
if (nomeSpan && andarInfo) nomeSpan.textContent = andarInfo.name; | |||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
| Linha 134: | Linha 107: | ||
divLayer.className = 'mapa-layer'; | divLayer.className = 'mapa-layer'; | ||
divLayer.setAttribute('data-floor', layer.id); | divLayer.setAttribute('data-floor', layer.id); | ||
divLayer.style.display = layer.id === | divLayer.style.display = layer.id === andarAtual ? 'block' : 'none'; | ||
divLayer.style.position = 'absolute'; | divLayer.style.position = 'absolute'; | ||
divLayer.style.top = '0'; | divLayer.style.top = '0'; | ||
| Linha 143: | Linha 116: | ||
var img = document.createElement('img'); | var img = document.createElement('img'); | ||
img.style.display = 'block'; | img.style.display = 'block'; | ||
img.style.transform = 'scale(' + | img.style.transform = 'scale(' + zoomAtual + ')'; | ||
img.style.transformOrigin = '0 0'; | img.style.transformOrigin = '0 0'; | ||
img.src = layer.imagePath || ''; | img.src = layer.imagePath || ''; | ||
img.onload = function() { camadasDiv.style.width = this.width + 'px'; camadasDiv.style.height = this.height + 'px'; }; | |||
img.onload = function() { | img.onerror = function() { this.src = 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22800%22 height=%22600%22%3E%3Crect width=%22100%25%22 height=%22100%25%22 fill=%22%23334155%22/%3E%3Ctext x=%2250%25%22 y=%2250%25%22 text-anchor=%22middle%22 fill=%22white%22%3ESem imagem%3C/text%3E%3C/svg%3E'; }; | ||
img.onerror = | |||
divLayer.appendChild(img); | divLayer.appendChild(img); | ||
| Linha 182: | Linha 136: | ||
var markerDiv = criarMarcador(marker, layer.id); | var markerDiv = criarMarcador(marker, layer.id); | ||
markersDiv.appendChild(markerDiv); | markersDiv.appendChild(markerDiv); | ||
marcadores.push({ el: markerDiv, data: marker }); | |||
} | } | ||
} | } | ||
divLayer.appendChild(markersDiv); | divLayer.appendChild(markersDiv); | ||
camadasDiv.appendChild(divLayer); | |||
camadas.push(divLayer); | |||
} | } | ||
} | } | ||
| Linha 199: | Linha 152: | ||
div.setAttribute('data-x', marker.x); | div.setAttribute('data-x', marker.x); | ||
div.setAttribute('data-y', marker.y); | div.setAttribute('data-y', marker.y); | ||
div.style.position = 'absolute'; | div.style.position = 'absolute'; | ||
div.style.left = (marker.x * | div.style.left = (marker.x * zoomAtual) + 'px'; | ||
div.style.top = (marker.y * | div.style.top = (marker.y * zoomAtual) + 'px'; | ||
div.style.zIndex = '100'; | div.style.zIndex = '100'; | ||
div.style.transform = 'translate(-50%, -50%)'; | div.style.transform = 'translate(-50%, -50%)'; | ||
div.style.cursor = (marker.action && marker.action !== 'none') ? 'pointer' : 'default'; | div.style.cursor = (marker.action && marker.action !== 'none') ? 'pointer' : 'default'; | ||
var tamanhoIcone = Math.max(20, Math.floor(20 * | var tamanhoIcone = Math.max(20, Math.floor(20 * zoomAtual)); | ||
var htmlIcone = marker.iconBase64 ? | var htmlIcone = marker.iconBase64 ? '<img src="' + marker.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">' : '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>'; | ||
var textoAcao = ''; | var textoAcao = ''; | ||
if (marker.action === 'popup') textoAcao = '📋 Informações'; | |||
else if (marker.action === 'nextFloor') textoAcao = '⬆️ Próximo andar'; | |||
else if (marker.action === 'prevFloor') textoAcao = '⬇️ Andar anterior'; | |||
else if (marker.action === 'gotoFloor') textoAcao = '🎯 Ir para andar'; | |||
else if (marker.action === 'link') textoAcao = '🔗 Link externo'; | |||
else textoAcao = '📍 Clique'; | |||
div.innerHTML = | div.innerHTML = '<div style="display:flex; align-items:center; justify-content:center;">' + htmlIcone + '</div>' + | ||
'<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;">' + | |||
<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;"> | '<strong>' + (marker.name || 'Marcador') + '</strong><br><small>' + textoAcao + '</small></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>'; | |||
<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; | |||
div.addEventListener('mouseenter', function() { | div.addEventListener('mouseenter', function() { this.style.transform = 'translate(-50%, -50%) scale(1.15)'; var tp = this.children[1]; if (tp) { tp.style.opacity = '1'; tp.style.visibility = 'visible'; } }); | ||
div.addEventListener('mouseleave', function() { this.style.transform = 'translate(-50%, -50%) scale(1)'; var tp = this.children[1]; if (tp) { tp.style.opacity = '0'; tp.style.visibility = 'hidden'; } }); | |||
div.addEventListener('mouseleave', function() { | |||
if (marker.action && marker.action !== 'none') { | if (marker.action && marker.action !== 'none') { | ||
div.addEventListener('click', function(e) { | div.addEventListener('click', function(e) { | ||
e.stopPropagation(); | e.stopPropagation(); | ||
if (marker.action === 'popup') alert(marker.name + '\n\n' + (marker.actionData?.text || 'Sem informações')); | |||
else if (marker.action === 'nextFloor') irProximoAndar(); | |||
else if (marker.action === 'prevFloor') irAndarAnterior(); | |||
else if (marker.action === 'gotoFloor' && marker.actionData?.floorId !== undefined) irParaAndar(marker.actionData.floorId); | |||
else if (marker.action === 'link' && marker.actionData?.url && marker.actionData.url !== '#') window.open(marker.actionData.url, marker.actionData.target || '_blank'); | |||
}); | }); | ||
} | } | ||
return div; | return div; | ||
} | } | ||
function | function atualizarPosicaoMarcadores() { | ||
for (var i = 0; i < marcadores.length; i++) { | |||
var item = marcadores[i]; | |||
if (parseInt(item.el.getAttribute('data-floor')) === andarAtual) { | |||
item.el.style.left = (item.data.x * zoomAtual) + 'px'; | |||
item.el.style.top = (item.data.y * zoomAtual) + 'px'; | |||
var ts = Math.max(20, Math.floor(20 * zoomAtual)); | |||
var idiv = item.el.children[0]; | |||
if (idiv) { | |||
if (item.data.iconBase64) idiv.innerHTML = '<img src="' + item.data.iconBase64 + '" style="width:' + ts + 'px; height:' + ts + 'px;">'; | |||
else idiv.innerHTML = '<span style="font-size:' + ts + 'px;">📍</span>'; | |||
if ( | |||
} | } | ||
} | |||
} | } | ||
} | } | ||
function | function irProximoAndar() { | ||
var idx = -1; | var idx = -1; | ||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
if (config.layers[i].id === | if (config.layers[i].id === andarAtual) idx = i; | ||
} | } | ||
if (idx < config.layers.length - 1) irParaAndar(config.layers[idx + 1].id); | |||
} | } | ||
function | function irAndarAnterior() { | ||
var idx = -1; | var idx = -1; | ||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
if (config.layers[i].id === | if (config.layers[i].id === andarAtual) idx = i; | ||
} | } | ||
if (idx > 0) irParaAndar(config.layers[idx - 1].id); | |||
} | } | ||
function irParaAndar(floorId) { | function irParaAndar(floorId) { | ||
andarAtual = floorId; | |||
for (var i = 0; i < | for (var i = 0; i < camadas.length; i++) { | ||
var | var lf = parseInt(camadas[i].getAttribute('data-floor')); | ||
camadas[i].style.display = lf === floorId ? 'block' : 'none'; | |||
} | } | ||
var | var fd = null; | ||
for (var i = 0; i < config.layers.length; i++) { | for (var i = 0; i < config.layers.length; i++) { | ||
if (config.layers[i].id === floorId) | if (config.layers[i].id === floorId) fd = config.layers[i]; | ||
} | } | ||
var nomeSpan = document.getElementById('floor-name-' + id); | |||
if (nomeSpan && fd) nomeSpan.textContent = fd.name; | |||
atualizarPosicaoMarcadores(); | |||
} | } | ||
function zoomIn() { | function zoomIn() { | ||
if ( | if (zoomAtual < zoomMax) { | ||
var | var old = zoomAtual; | ||
zoomAtual = Math.min(zoomAtual + zoomPasso, zoomMax); | |||
aplicarZoom( | aplicarZoom(old); | ||
} | } | ||
} | } | ||
function zoomOut() { | function zoomOut() { | ||
if ( | if (zoomAtual > zoomMin) { | ||
var | var old = zoomAtual; | ||
zoomAtual = Math.max(zoomAtual - zoomPasso, zoomMin); | |||
aplicarZoom( | aplicarZoom(old); | ||
} | } | ||
} | } | ||
function aplicarZoom(oldZoom) { | function aplicarZoom(oldZoom) { | ||
var | var imgs = document.querySelectorAll('.mapa-image'); | ||
for (var i = 0; i < | for (var i = 0; i < imgs.length; i++) imgs[i].style.transform = 'scale(' + zoomAtual + ')'; | ||
var rect = viewport.getBoundingClientRect(); | var rect = viewport.getBoundingClientRect(); | ||
var cx = rect.width / 2, cy = rect.height / 2; | var cx = rect.width / 2, cy = rect.height / 2; | ||
var sx = (viewport.scrollLeft + cx) / oldZoom | var sx = (viewport.scrollLeft + cx) / oldZoom, sy = (viewport.scrollTop + cy) / oldZoom; | ||
viewport.scrollLeft = sx * zoomAtual - cx; | |||
viewport.scrollLeft = sx * | viewport.scrollTop = sy * zoomAtual - cy; | ||
viewport.scrollTop = sy * | atualizarPosicaoMarcadores(); | ||
var zoomSpan = document.getElementById('zoom-level-' + id); | |||
if ( | if (zoomSpan) zoomSpan.textContent = Math.round(zoomAtual * 100) + '%'; | ||
} | } | ||
function resetarView() { | function resetarView() { | ||
zoomAtual = config.mapConfig.defaultZoom || 1; | |||
var | var imgs = document.querySelectorAll('.mapa-image'); | ||
for (var i = 0; i < | for (var i = 0; i < imgs.length; i++) imgs[i].style.transform = 'scale(' + zoomAtual + ')'; | ||
viewport.scrollLeft = 0; | viewport.scrollLeft = 0; | ||
viewport.scrollTop = 0; | viewport.scrollTop = 0; | ||
atualizarPosicaoMarcadores(); | |||
if ( | var zoomSpan = document.getElementById('zoom-level-' + id); | ||
if (zoomSpan) zoomSpan.textContent = Math.round(zoomAtual * 100) + '%'; | |||
} | } | ||
// | // Conectar botões | ||
document.getElementById('zoom-in-' + id).addEventListener('click', zoomIn); | |||
document.getElementById('zoom-out-' + id).addEventListener('click', zoomOut); | |||
document.getElementById('reset-' + id).addEventListener('click', resetarView); | |||
document.getElementById('prev-' + id).addEventListener('click', irAndarAnterior); | |||
document.getElementById('next-' + id).addEventListener('click', irProximoAndar); | |||
// Arrastar | |||
// | |||
viewport.addEventListener('mousedown', function(e) { | viewport.addEventListener('mousedown', function(e) { | ||
if (e.target.closest('.mapa-marker')) return; | if (e.target.closest('.mapa-marker')) return; | ||
arrastando = true; | |||
arrasteInicio = { x: e.clientX, y: e.clientY, scrollLeft: viewport.scrollLeft, scrollTop: viewport.scrollTop }; | |||
viewport.style.cursor = 'grabbing'; | viewport.style.cursor = 'grabbing'; | ||
e.preventDefault(); | e.preventDefault(); | ||
| Linha 402: | Linha 295: | ||
window.addEventListener('mousemove', function(e) { | window.addEventListener('mousemove', function(e) { | ||
if (! | if (!arrastando) return; | ||
viewport.scrollLeft = | viewport.scrollLeft = arrasteInicio.scrollLeft - (e.clientX - arrasteInicio.x); | ||
viewport.scrollTop = | viewport.scrollTop = arrasteInicio.scrollTop - (e.clientY - arrasteInicio.y); | ||
}); | }); | ||
window.addEventListener('mouseup', function() { | window.addEventListener('mouseup', function() { arrastando = false; viewport.style.cursor = 'grab'; }); | ||
viewport.addEventListener('wheel', function(e) { | viewport.addEventListener('wheel', function(e) { | ||
if (e.ctrlKey) { | if (e.ctrlKey) { e.preventDefault(); e.deltaY < 0 ? zoomIn() : zoomOut(); } | ||
}); | }); | ||
viewport.addEventListener('mousemove', function(e) { | viewport.addEventListener('mousemove', function(e) { | ||
var rect = viewport.getBoundingClientRect(); | var rect = viewport.getBoundingClientRect(); | ||
var x = (e.clientX - rect.left + viewport.scrollLeft) / | var x = (e.clientX - rect.left + viewport.scrollLeft) / zoomAtual; | ||
var y = (e.clientY - rect.top + viewport.scrollTop) / | var y = (e.clientY - rect.top + viewport.scrollTop) / zoomAtual; | ||
coordsDiv.textContent = '📍 ' + Math.round(x) + ', ' + Math.round(y) + ' | ' + Math.round( | coordsDiv.textContent = '📍 ' + Math.round(x) + ', ' + Math.round(y) + ' | ' + Math.round(zoomAtual * 100) + '%'; | ||
}); | }); | ||