@@ -30,6 +30,22 @@
|
|||||||
border-radius: 6px;
|
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 {
|
.infra-floor-canvas {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
@@ -39,6 +55,19 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
overflow: hidden;
|
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 {
|
.infra-floor-svg {
|
||||||
@@ -47,6 +76,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
object-position: center center;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
const canvas = document.getElementById('infra-floor-canvas');
|
const canvas = document.getElementById('infra-floor-canvas');
|
||||||
const overlay = document.getElementById('infra-floor-overlay');
|
const overlay = document.getElementById('infra-floor-overlay');
|
||||||
|
const scene = document.getElementById('infra-floor-scene');
|
||||||
const floorSvg = canvas ? canvas.querySelector('.infra-floor-svg') : null;
|
const floorSvg = canvas ? canvas.querySelector('.infra-floor-svg') : null;
|
||||||
if (!canvas || !overlay || !floorSvg) {
|
if (!canvas || !overlay || !floorSvg || !scene) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +59,94 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));
|
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));
|
||||||
outlets.forEach((entry) => createMarker(entry, 'outlet'));
|
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) => {
|
const loadPlanDimensions = async (svgUrl) => {
|
||||||
if (!svgUrl) {
|
if (!svgUrl) {
|
||||||
updateOverlayViewBox();
|
updateOverlayViewBox();
|
||||||
|
|||||||
@@ -133,7 +133,14 @@ if ($editorFloor) {
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<section class="infra-plan">
|
<section class="infra-plan">
|
||||||
<h2>Stockwerkskarte</h2>
|
<div class="infra-plan-header">
|
||||||
|
<h2>Stockwerkskarte</h2>
|
||||||
|
<div class="infra-plan-tools">
|
||||||
|
<button type="button" class="button button-small" data-infra-zoom="in">+</button>
|
||||||
|
<button type="button" class="button button-small" data-infra-zoom="out">-</button>
|
||||||
|
<button type="button" class="button button-small" data-infra-zoom="reset">Reset</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<?php if ($floorId <= 0): ?>
|
<?php if ($floorId <= 0): ?>
|
||||||
<p class="empty-state">Bitte ein Stockwerk auswählen, um die Karte anzuzeigen.</p>
|
<p class="empty-state">Bitte ein Stockwerk auswählen, um die Karte anzuzeigen.</p>
|
||||||
<?php elseif (!$editorFloor): ?>
|
<?php elseif (!$editorFloor): ?>
|
||||||
@@ -149,8 +156,10 @@ if ($editorFloor) {
|
|||||||
class="infra-floor-canvas"
|
class="infra-floor-canvas"
|
||||||
data-patchpanels="<?php echo htmlspecialchars(json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
data-patchpanels="<?php echo htmlspecialchars(json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
data-outlets="<?php echo htmlspecialchars(json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
data-outlets="<?php echo htmlspecialchars(json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
<img src="<?php echo htmlspecialchars((string)$editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
|
<div class="infra-floor-scene" id="infra-floor-scene">
|
||||||
<svg id="infra-floor-overlay" class="infra-floor-overlay" viewBox="0 0 1 1" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
|
<img src="<?php echo htmlspecialchars((string)$editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
|
||||||
|
<svg id="infra-floor-overlay" class="infra-floor-overlay" viewBox="0 0 1 1" preserveAspectRatio="xMidYMid meet" aria-hidden="true"></svg>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="floor-plan-hint">Blau: Patchpanel | Grün: Wandbuchse. Hover zeigt Name, Raum und Ports (Browser-Tooltip).</p>
|
<p class="floor-plan-hint">Blau: Patchpanel | Grün: Wandbuchse. Hover zeigt Name, Raum und Ports (Browser-Tooltip).</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user