Mudanças entre as edições de "Widget:MapViewer.js"
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;"> | ||
< | <div style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#64748b; text-align:center;"> | ||
/ | <div>🗺️ Carregando mapa...</div> | ||
<div style="font-size:12px; margin-top:8px;">{{{nome|Mapa}}}</div> | |||
</div> | |||
</div> | |||
<script> | |||
// @noescape | |||
(function() { | (function() { | ||
var containerId = 'mapa-viewer-{{{id|mapa1}}}'; | |||
var container = document.getElementById(containerId); | |||
if (!container) return; | |||
// | // Receber configuração do template | ||
var configJSON = `{{{json}}}`; | |||
var mapConfig; | |||
try { | |||
mapConfig = JSON.parse(configJSON); | |||
} catch(e) { | |||
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); | |||
return; | |||
} | |||
// Inicializar o viewer | |||
iniciarMapaViewer(containerId, mapConfig); | |||
. | |||
// ==================== | // ==================== VIEWER COMPLETO ==================== | ||
function iniciarMapaViewer(containerId, config) { | |||
var container = document.getElementById(containerId); | |||
if (!container) return; | |||
// Limpar container | |||
container.innerHTML = ''; | |||
container.style.position = 'relative'; | |||
container.style.overflow = 'hidden'; | |||
// Criar estrutura do viewer | |||
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;"> | |||
<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> | |||
<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> | |||
<button class="mapa-reset" style="background:#334155; border:none; color:white; width:32px; height:32px; border-radius:50%; cursor:pointer;">⟳</button> | |||
</div> | </div> | ||
<div | <div style="background:rgba(0,0,0,0.7); padding:5px 15px; border-radius:30px; color:white; font-size:12px;"> | ||
< | <span class="mapa-floor-name">Carregando...</span> | ||
</div> | </div> | ||
<div class=" | <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> | ||
</div> | |||
<div class="mapa-viewport" style="width:100%; height:100%; overflow:auto; cursor:grab; background:#0f172a;"> | |||
<div class="mapa-layers" style="position:relative; min-width:100%; min-height:100%;"></div> | |||
</div> | |||
<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 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> | |||
</div> | |||
`; | |||
// Referências DOM | |||
var viewport = container.querySelector('.mapa-viewport'); | |||
var layersContainer = container.querySelector('.mapa-layers'); | |||
var floorNameSpan = container.querySelector('.mapa-floor-name'); | |||
var zoomLevelSpan = container.querySelector('.mapa-zoom-level'); | |||
// Variáveis de estado | |||
var currentZoom = config.mapConfig.defaultZoom || 1; | |||
var currentFloor = config.mapConfig.initialFloor || 0; | |||
var minZoom = config.mapConfig.minZoom || 0.5; | |||
var maxZoom = config.mapConfig.maxZoom || 3; | |||
var zoomStep = config.mapConfig.zoomStep || 0.1; | |||
var layers = []; | |||
var markers = []; | |||
var isPanning = false; | |||
var panStart = { x: 0, y: 0, scrollLeft: 0, scrollTop: 0 }; | |||
// Renderizar camadas | |||
if (! | function renderizarCamadas() { | ||
if (!layersContainer) return; | |||
layersContainer.innerHTML = ''; | |||
layers = []; | |||
markers = []; | |||
if (! | if (!config.layers || config.layers.length === 0) { | ||
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 (! | if (!config.layers.find(function(l) { return l.id === currentFloor; })) { | ||
currentFloor = config.layers[0]?.id || 0; | |||
} | } | ||
var floorAtual = config.layers.find(function(l) { return l.id === currentFloor; }); | |||
if ( | if (floorAtual && floorNameSpan) { | ||
floorNameSpan.textContent = floorAtual.name; | |||
} | } | ||
// Percorrer camadas | |||
for (var i = 0; i < config.layers.length; i++) { | |||
var layer = config.layers[i]; | |||
var divLayer = document.createElement('div'); | |||
divLayer.className = 'mapa-layer'; | |||
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; | |||
var img = document.createElement('img'); | |||
img.className = 'mapa-image'; | |||
img.style.display = 'block'; | |||
img.style.transform = 'scale(' + currentZoom + ')'; | |||
img.style.transformOrigin = '0 0'; | |||
img.src = layer.imagePath || ''; | |||
img.onload = (function(img, layersContainer) { | |||
return function() { | |||
layersContainer.style.width = this.width + 'px'; | |||
layersContainer.style.height = this.height + 'px'; | |||
}; | |||
})(img, layersContainer); | |||
img.onerror = (function(layer, img) { | |||
return function() { | |||
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); | |||
divLayer.appendChild(img); | |||
var markersDiv = document.createElement('div'); | |||
markersDiv.style.position = 'absolute'; | |||
markersDiv.style.top = '0'; | |||
markersDiv.style.left = '0'; | |||
markersDiv.style.width = '100%'; | |||
markersDiv.style.height = '100%'; | |||
markersDiv.style.pointerEvents = 'none'; | |||
if (layer.markers) { | if (layer.markers && layer.markers.length > 0) { | ||
layer.markers. | for (var j = 0; j < layer.markers.length; j++) { | ||
var marker = layer.markers[j]; | |||
var markerDiv = criarMarcador(marker, layer.id); | |||
markersDiv.appendChild(markerDiv); | |||
} | markers.push({ el: markerDiv, data: marker }); | ||
} | |||
} | } | ||
divLayer.appendChild(markersDiv); | |||
layersContainer.appendChild(divLayer); | |||
layers.push(divLayer); | |||
} | } | ||
} | } | ||
// Criar marcador | |||
function criarMarcador(marker, floorId) { | |||
var div = document.createElement('div'); | |||
div.className = 'mapa-marker'; | |||
div.setAttribute('data-marker-id', marker.id); | |||
div.setAttribute('data-floor', floorId); | |||
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'; | |||
var tamanhoIcone = Math.max(20, Math.floor(20 * currentZoom)); | |||
var htmlIcone; | |||
if (marker.iconBase64) { | |||
htmlIcone = '<img src="' + marker.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">'; | |||
} else { | |||
htmlIcone = '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>'; | |||
} | |||
var textoAcao = ''; | |||
switch(marker.action) { | switch(marker.action) { | ||
case 'popup': | case 'popup': textoAcao = '📋 Informações'; break; | ||
case 'nextFloor': | case 'nextFloor': textoAcao = '⬆️ Próximo andar'; break; | ||
case 'prevFloor': | case 'prevFloor': textoAcao = '⬇️ Andar anterior'; break; | ||
case 'gotoFloor': | case 'gotoFloor': textoAcao = '🎯 Ir para andar'; break; | ||
case 'link': | case 'link': textoAcao = '🔗 Link externo'; break; | ||
default: | default: textoAcao = '📍 Clique'; | ||
} | } | ||
div.innerHTML = ` | |||
<div | <div style="display:flex; align-items:center; justify-content:center;">${htmlIcone}</div> | ||
<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; z-index:101;"> | ||
<strong>${marker.name || 'Marcador'}</strong><br> | <strong>${marker.name || 'Marcador'}</strong><br> | ||
<small>${ | <small>${textoAcao}</small> | ||
</div> | </div> | ||
<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 | // Evento hover | ||
if ( | div.addEventListener('mouseenter', function() { | ||
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(); | ||
executarAcao(marker); | |||
}); | }); | ||
} | } | ||
return | return div; | ||
} | } | ||
// Executar ação do marcador | |||
function executarAcao(marker) { | |||
switch(marker.action) { | switch(marker.action) { | ||
case 'popup': | case 'popup': | ||
var popupText = marker.actionData?.text || 'Sem informações'; | |||
alert(marker.name + '\n\n' + popupText); | |||
break; | break; | ||
case 'nextFloor': | case 'nextFloor': | ||
proximoAndar(); | |||
break; | break; | ||
case 'prevFloor': | case 'prevFloor': | ||
andarAnterior(); | |||
break; | break; | ||
case 'gotoFloor': | case 'gotoFloor': | ||
if (marker.actionData?.floorId !== undefined) { | if (marker.actionData?.floorId !== undefined) { | ||
irParaAndar(marker.actionData.floorId); | |||
} | } | ||
break; | break; | ||
| Linha 505: | Linha 274: | ||
} | } | ||
// Navegação | |||
function proximoAndar() { | |||
var idx = -1; | |||
for (var i = 0; i < config.layers.length; i++) { | |||
if (config.layers[i].id === currentFloor) { | |||
idx = i; | |||
break; | |||
} | |||
} | |||
if (idx < config.layers.length - 1) { | |||
irParaAndar(config.layers[idx + 1].id); | |||
} | |||
} | |||
function andarAnterior() { | |||
var idx = -1; | |||
for (var i = 0; i < config.layers.length; i++) { | |||
if (config.layers[i].id === currentFloor) { | |||
idx = i; | |||
} | break; | ||
} | |||
} | |||
if (idx > 0) { | |||
irParaAndar(config.layers[idx - 1].id); | |||
} | |||
} | } | ||
function irParaAndar(floorId) { | |||
currentFloor = floorId; | |||
for (var i = 0; i < layers.length; i++) { | |||
var layer = layers[i]; | |||
var layerFloor = parseInt(layer.getAttribute('data-floor')); | |||
layer.style.display = layerFloor === floorId ? 'block' : 'none'; | |||
} | |||
var floorData = null; | |||
for (var i = 0; i < config.layers.length; i++) { | |||
if (config.layers[i].id === floorId) { | |||
floorData = config.layers[i]; | |||
break; | |||
} | |||
} | |||
if (floorData && floorNameSpan) { | |||
floorNameSpan.textContent = floorData.name; | |||
} | |||
atualizarPosicaoMarkers(); | |||
} | } | ||
// Atualizar posição dos marcadores | |||
function atualizarPosicaoMarkers() { | |||
for (var i = 0; i < markers.length; i++) { | |||
var item = markers[i]; | |||
if (parseInt(item.el.getAttribute('data-floor')) === currentFloor) { | |||
item.el.style.left = (item.data.x * currentZoom) + 'px'; | |||
item.el.style.top = (item.data.y * currentZoom) + 'px'; | |||
var tamanhoIcone = Math.max(20, Math.floor(20 * currentZoom)); | |||
var iconDiv = item.el.children[0]; | |||
if (iconDiv) { | if (iconDiv) { | ||
if (item.data.iconBase64) { | |||
iconDiv.innerHTML = '<img src="' + item.data.iconBase64 + '" style="width:' + tamanhoIcone + 'px; height:' + tamanhoIcone + 'px;">'; | |||
} else { | } else { | ||
iconDiv.innerHTML = | iconDiv.innerHTML = '<span style="font-size:' + tamanhoIcone + 'px;">📍</span>'; | ||
} | } | ||
} | } | ||
} | } | ||
} | } | ||
} | } | ||
zoomIn() { | // Zoom | ||
if ( | function zoomIn() { | ||
if (currentZoom < maxZoom) { | |||
var oldZoom = currentZoom; | |||
currentZoom = Math.min(currentZoom + zoomStep, maxZoom); | |||
aplicarZoom(oldZoom); | |||
} | } | ||
} | } | ||
zoomOut() { | function zoomOut() { | ||
if ( | if (currentZoom > minZoom) { | ||
var oldZoom = currentZoom; | |||
currentZoom = Math.max(currentZoom - zoomStep, minZoom); | |||
aplicarZoom(oldZoom); | |||
} | } | ||
} | } | ||
function aplicarZoom(oldZoom) { | |||
var images = document.querySelectorAll('.mapa-image'); | |||
for (var i = 0; i < images.length; i++) { | |||
images[i].style.transform = 'scale(' + currentZoom + ')'; | |||
} | } | ||
var rect = viewport.getBoundingClientRect(); | |||
var cx = rect.width / 2; | |||
var cy = rect.height / 2; | |||
var sx = (viewport.scrollLeft + cx) / oldZoom; | |||
var sy = (viewport.scrollTop + cy) / oldZoom; | |||
viewport.scrollLeft = sx * currentZoom - cx; | |||
viewport.scrollTop = sy * currentZoom - cy; | |||
atualizarPosicaoMarkers(); | |||
if ( | if (zoomLevelSpan) { | ||
zoomLevelSpan.textContent = Math.round(currentZoom * 100) + '%'; | |||
} | } | ||
} | } | ||
function resetarView() { | |||
currentZoom = config.mapConfig.defaultZoom || 1; | |||
var images = document.querySelectorAll('.mapa-image'); | |||
for (var i = 0; i < images.length; i++) { | |||
} | images[i].style.transform = 'scale(' + currentZoom + ')'; | ||
} | |||
viewport.scrollLeft = 0; | |||
viewport.scrollTop = 0; | |||
if ( | atualizarPosicaoMarkers(); | ||
if (zoomLevelSpan) { | |||
zoomLevelSpan.textContent = Math.round(currentZoom * 100) + '%'; | |||
} | } | ||
} | } | ||
// Configurar eventos dos botões | |||
var btnZoomIn = container.querySelector('.mapa-zoom-in'); | |||
if ( | var btnZoomOut = container.querySelector('.mapa-zoom-out'); | ||
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); | |||
}); | |||
window.addEventListener('mouseup', function() { | |||
isPanning = false; | |||
viewport.style.cursor = 'grab'; | |||
}); | |||
} | |||
// Zoom com scroll | |||
viewport.addEventListener('wheel', function(e) { | |||
if (e.ctrlKey) { | |||
e.preventDefault(); | |||
if (e.deltaY < 0) { | |||
zoomIn(); | |||
} else { | |||
zoomOut(); | |||
} | |||
} | } | ||
}); | |||
} | |||
// Tracking de coordenadas | |||
viewport.addEventListener('mousemove', function(e) { | |||
var rect = viewport.getBoundingClientRect(); | |||
var x = (e.clientX - rect.left + viewport.scrollLeft) / currentZoom; | |||
var y = (e.clientY - rect.top + viewport.scrollTop) / currentZoom; | |||
var coordsDiv = container.querySelector('.mapa-coords'); | |||
if (coordsDiv) { | |||
coordsDiv.textContent = '📍 ' + Math.round(x) + ', ' + Math.round(y) + ' | ' + Math.round(currentZoom * 100) + '%'; | |||
} | } | ||
}); | }); | ||
// Inicializar | |||
renderizarCamadas(); | |||
} | } | ||
})(); | })(); | ||
</script></includeonly> | |||
</script> | |||
</includeonly> | |||