diff --git a/app/assets/css/floor-infrastructure-list.css b/app/assets/css/floor-infrastructure-list.css index 45e8617..2d77cf4 100644 --- a/app/assets/css/floor-infrastructure-list.css +++ b/app/assets/css/floor-infrastructure-list.css @@ -30,6 +30,22 @@ border-radius: 6px; } +.infra-plan-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.infra-plan-header h2 { + margin: 0; +} + +.infra-plan-tools { + display: flex; + gap: 6px; +} + .infra-floor-canvas { position: relative; margin-top: 12px; @@ -39,6 +55,19 @@ border-radius: 8px; background: #fff; overflow: hidden; + touch-action: none; + cursor: grab; +} + +.infra-floor-canvas.is-dragging { + cursor: grabbing; +} + +.infra-floor-scene { + position: absolute; + inset: 0; + transform-origin: center center; + will-change: transform; } .infra-floor-svg { @@ -47,6 +76,7 @@ width: 100%; height: 100%; object-fit: contain; + object-position: center center; pointer-events: none; opacity: 0.85; } diff --git a/app/assets/js/floor-infrastructure-list.js b/app/assets/js/floor-infrastructure-list.js index e488af9..e350aee 100644 --- a/app/assets/js/floor-infrastructure-list.js +++ b/app/assets/js/floor-infrastructure-list.js @@ -12,8 +12,9 @@ document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('infra-floor-canvas'); const overlay = document.getElementById('infra-floor-overlay'); + const scene = document.getElementById('infra-floor-scene'); const floorSvg = canvas ? canvas.querySelector('.infra-floor-svg') : null; - if (!canvas || !overlay || !floorSvg) { + if (!canvas || !overlay || !floorSvg || !scene) { return; } @@ -58,6 +59,94 @@ document.addEventListener('DOMContentLoaded', () => { patchPanels.forEach((entry) => createMarker(entry, 'patchpanel')); outlets.forEach((entry) => createMarker(entry, 'outlet')); + const camera = { + scale: 1, + tx: 0, + ty: 0 + }; + const SCALE_MIN = 0.6; + const SCALE_MAX = 3.5; + const SCALE_STEP = 0.15; + let drag = null; + + const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); + + const applyCamera = () => { + scene.style.transform = `translate(${camera.tx}px, ${camera.ty}px) scale(${camera.scale})`; + }; + + const zoomAtCenter = (factor) => { + const nextScale = clamp(camera.scale * factor, SCALE_MIN, SCALE_MAX); + if (Math.abs(nextScale - camera.scale) < 0.0001) { + return; + } + camera.scale = nextScale; + applyCamera(); + }; + + const resetCamera = () => { + camera.scale = 1; + camera.tx = 0; + camera.ty = 0; + applyCamera(); + }; + + canvas.addEventListener('wheel', (event) => { + event.preventDefault(); + zoomAtCenter(event.deltaY < 0 ? (1 + SCALE_STEP) : (1 - SCALE_STEP)); + }, { passive: false }); + + canvas.addEventListener('pointerdown', (event) => { + drag = { + x: event.clientX, + y: event.clientY, + baseX: camera.tx, + baseY: camera.ty + }; + canvas.classList.add('is-dragging'); + canvas.setPointerCapture(event.pointerId); + }); + + canvas.addEventListener('pointermove', (event) => { + if (!drag) { + return; + } + camera.tx = drag.baseX + (event.clientX - drag.x); + camera.ty = drag.baseY + (event.clientY - drag.y); + applyCamera(); + }); + + const stopDrag = (event) => { + if (!drag) { + return; + } + drag = null; + canvas.classList.remove('is-dragging'); + if (event && typeof event.pointerId === 'number') { + canvas.releasePointerCapture(event.pointerId); + } + }; + + canvas.addEventListener('pointerup', stopDrag); + canvas.addEventListener('pointercancel', stopDrag); + + document.querySelectorAll('[data-infra-zoom]').forEach((button) => { + button.addEventListener('click', () => { + const action = button.getAttribute('data-infra-zoom'); + if (action === 'in') { + zoomAtCenter(1 + SCALE_STEP); + return; + } + if (action === 'out') { + zoomAtCenter(1 - SCALE_STEP); + return; + } + resetCamera(); + }); + }); + + applyCamera(); + const loadPlanDimensions = async (svgUrl) => { if (!svgUrl) { updateOverlayViewBox(); diff --git a/app/modules/floor_infrastructure/list.php b/app/modules/floor_infrastructure/list.php index e751af0..79df32f 100644 --- a/app/modules/floor_infrastructure/list.php +++ b/app/modules/floor_infrastructure/list.php @@ -133,7 +133,14 @@ if ($editorFloor) {
-

Stockwerkskarte

+
+

Stockwerkskarte

+
+ + + +
+

Bitte ein Stockwerk auswählen, um die Karte anzuzeigen.

@@ -149,8 +156,10 @@ if ($editorFloor) { class="infra-floor-canvas" data-patchpanels="" data-outlets=""> - Stockwerksplan - +
+ Stockwerksplan + +

Blau: Patchpanel | Grün: Wandbuchse. Hover zeigt Name, Raum und Ports (Browser-Tooltip).