Verhalten im Infrastruktur-Modul ist jetzt wie gewünscht:
Nur das aktiv bearbeitete Objekt ist verschiebbar. Alle anderen Objekte auf derselben Etage werden als halbtransparente Referenz angezeigt. In der Listenansicht sind Marker nur noch Vorschau (nicht verschiebbar). Geänderte Dateien: edit.php Referenzdaten für Patchpanels und Wandbuchsen geladen. Diese Daten als data-* am Canvas hinterlegt. Hinweise im UI angepasst. Styles ergänzt: .floor-plan-marker.is-active .floor-plan-reference (+ Varianten für Panel/Outlet) Inline-Script entfernt und auf externe JS-Datei umgestellt (CSP-sicher): <script src="/assets/js/floor-infrastructure-edit.js" defer></script> floor-infrastructure-edit.js (neu) Drag/Drop nur für den aktiven Marker. Referenzmarker je Stockwerk rendern (halbtransparent, nicht interaktiv). Aktiven Marker aus Referenzmenge ausschließen. Referenzen bei Stockwerk-/Raumwechsel neu rendern. list.php Kartenansicht auf reine Vorschau umgestellt. Drag/Save-Logik entfernt. Marker per CSS nicht interaktiv (pointer-events: none, cursor: default). Hinweistext entsprechend angepasst.
This commit is contained in:
294
app/assets/js/floor-infrastructure-edit.js
Normal file
294
app/assets/js/floor-infrastructure-edit.js
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const canvas = document.getElementById('floor-plan-canvas');
|
||||||
|
const marker = document.getElementById('floor-plan-marker');
|
||||||
|
const positionLabel = document.getElementById('floor-plan-position');
|
||||||
|
if (!canvas || !marker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const xFieldName = canvas.dataset.xField;
|
||||||
|
const yFieldName = canvas.dataset.yField;
|
||||||
|
const xField = xFieldName ? document.querySelector(`input[name="${xFieldName}"]`) : null;
|
||||||
|
const yField = yFieldName ? document.querySelector(`input[name="${yFieldName}"]`) : null;
|
||||||
|
if (!xField || !yField) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerWidth = Math.max(1, Number(canvas.dataset.markerWidth) || marker.offsetWidth);
|
||||||
|
const markerHeight = Math.max(1, Number(canvas.dataset.markerHeight) || marker.offsetHeight);
|
||||||
|
const markerType = canvas.dataset.markerType || '';
|
||||||
|
const activeId = Number(canvas.dataset.activeId || 0);
|
||||||
|
const panelReferences = JSON.parse(canvas.dataset.referencePanels || '[]');
|
||||||
|
const outletReferences = JSON.parse(canvas.dataset.referenceOutlets || '[]');
|
||||||
|
|
||||||
|
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
||||||
|
|
||||||
|
marker.classList.add('is-active');
|
||||||
|
|
||||||
|
const updatePositionLabel = (x, y) => {
|
||||||
|
if (positionLabel) {
|
||||||
|
positionLabel.textContent = `${Math.round(x)} x ${Math.round(y)}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMarkerPosition = (rawX, rawY) => {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
const maxX = Math.max(0, rect.width - markerWidth);
|
||||||
|
const maxY = Math.max(0, rect.height - markerHeight);
|
||||||
|
const left = clamp(rawX, 0, maxX);
|
||||||
|
const top = clamp(rawY, 0, maxY);
|
||||||
|
marker.style.left = `${left}px`;
|
||||||
|
marker.style.top = `${top}px`;
|
||||||
|
xField.value = Math.round(left);
|
||||||
|
yField.value = Math.round(top);
|
||||||
|
updatePositionLabel(left, top);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFromInputs = () => {
|
||||||
|
setMarkerPosition(Number(xField.value) || 0, Number(yField.value) || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
updateFromInputs();
|
||||||
|
|
||||||
|
let dragging = false;
|
||||||
|
let offsetX = 0;
|
||||||
|
let offsetY = 0;
|
||||||
|
|
||||||
|
const startDrag = (clientX, clientY) => {
|
||||||
|
const markerRect = marker.getBoundingClientRect();
|
||||||
|
offsetX = clientX - markerRect.left;
|
||||||
|
offsetY = clientY - markerRect.top;
|
||||||
|
};
|
||||||
|
|
||||||
|
marker.addEventListener('pointerdown', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
dragging = true;
|
||||||
|
startDrag(event.clientX, event.clientY);
|
||||||
|
marker.setPointerCapture(event.pointerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
marker.addEventListener('pointermove', (event) => {
|
||||||
|
if (!dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
setMarkerPosition(event.clientX - rect.left - offsetX, event.clientY - rect.top - offsetY);
|
||||||
|
});
|
||||||
|
|
||||||
|
const stopDrag = (event) => {
|
||||||
|
if (!dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragging = false;
|
||||||
|
if (marker.hasPointerCapture(event.pointerId)) {
|
||||||
|
marker.releasePointerCapture(event.pointerId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
['pointerup', 'pointercancel', 'pointerleave'].forEach((evt) => {
|
||||||
|
marker.addEventListener(evt, stopDrag);
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('pointerdown', (event) => {
|
||||||
|
if (event.target !== canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
setMarkerPosition(event.clientX - rect.left - markerWidth / 2, event.clientY - rect.top - markerHeight / 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
[xField, yField].forEach((input) => {
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
updateFromInputs();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
updateFromInputs();
|
||||||
|
});
|
||||||
|
|
||||||
|
const panelLocationSelect = document.getElementById('panel-location-select');
|
||||||
|
const panelBuildingSelect = document.getElementById('panel-building-select');
|
||||||
|
const panelFloorSelect = document.getElementById('panel-floor-select');
|
||||||
|
const outletRoomSelect = document.getElementById('outlet-room-select');
|
||||||
|
const floorPlanSvg = document.getElementById('floor-plan-svg');
|
||||||
|
const panelPlacementFields = document.getElementById('panel-placement-fields');
|
||||||
|
const panelFloorPlanGroup = document.getElementById('panel-floor-plan-group');
|
||||||
|
const panelFloorMissingHint = document.getElementById('panel-floor-missing-hint');
|
||||||
|
|
||||||
|
const buildingOptions = panelBuildingSelect ? Array.from(panelBuildingSelect.options).filter((option) => option.value !== '') : [];
|
||||||
|
const floorOptions = panelFloorSelect ? Array.from(panelFloorSelect.options).filter((option) => option.value !== '') : [];
|
||||||
|
|
||||||
|
const getCurrentFloorId = () => {
|
||||||
|
const floorOption = panelFloorSelect?.selectedOptions?.[0];
|
||||||
|
if (floorOption?.value) {
|
||||||
|
return Number(floorOption.value);
|
||||||
|
}
|
||||||
|
const roomOption = outletRoomSelect?.selectedOptions?.[0];
|
||||||
|
return Number(roomOption?.dataset?.floorId || 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderReferenceMarkers = () => {
|
||||||
|
canvas.querySelectorAll('.floor-plan-reference').forEach((node) => node.remove());
|
||||||
|
const currentFloorId = getCurrentFloorId();
|
||||||
|
if (!currentFloorId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appendReference = (entry, cssClass, width, height) => {
|
||||||
|
const markerRef = document.createElement('div');
|
||||||
|
markerRef.className = `floor-plan-reference ${cssClass}`;
|
||||||
|
markerRef.style.left = `${Number(entry.x || entry.pos_x || 0)}px`;
|
||||||
|
markerRef.style.top = `${Number(entry.y || entry.pos_y || 0)}px`;
|
||||||
|
if (width > 0) {
|
||||||
|
markerRef.style.width = `${width}px`;
|
||||||
|
}
|
||||||
|
if (height > 0) {
|
||||||
|
markerRef.style.height = `${height}px`;
|
||||||
|
}
|
||||||
|
markerRef.title = entry.name || '';
|
||||||
|
canvas.appendChild(markerRef);
|
||||||
|
};
|
||||||
|
|
||||||
|
panelReferences.forEach((entry) => {
|
||||||
|
if (Number(entry.floor_id) !== currentFloorId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (markerType === 'patchpanel' && Number(entry.id) === activeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appendReference(entry, 'panel-marker', Math.max(1, Number(entry.width) || 140), Math.max(1, Number(entry.height) || 40));
|
||||||
|
});
|
||||||
|
|
||||||
|
outletReferences.forEach((entry) => {
|
||||||
|
if (Number(entry.floor_id) !== currentFloorId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (markerType === 'outlet' && Number(entry.id) === activeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appendReference(entry, 'outlet-marker', 32, 32);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateFloorPlanImage = () => {
|
||||||
|
if (!floorPlanSvg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const floorOption = panelFloorSelect?.selectedOptions?.[0];
|
||||||
|
const roomOption = outletRoomSelect?.selectedOptions?.[0];
|
||||||
|
const svgUrl = floorOption?.dataset?.svgUrl || roomOption?.dataset?.floorSvgUrl || '';
|
||||||
|
|
||||||
|
if (svgUrl) {
|
||||||
|
floorPlanSvg.src = svgUrl;
|
||||||
|
floorPlanSvg.hidden = false;
|
||||||
|
} else {
|
||||||
|
floorPlanSvg.removeAttribute('src');
|
||||||
|
floorPlanSvg.hidden = true;
|
||||||
|
}
|
||||||
|
renderReferenceMarkers();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (floorPlanSvg) {
|
||||||
|
floorPlanSvg.addEventListener('error', () => {
|
||||||
|
floorPlanSvg.removeAttribute('src');
|
||||||
|
floorPlanSvg.hidden = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePanelPlacementVisibility = () => {
|
||||||
|
if (!panelFloorSelect || !panelPlacementFields || !panelFloorPlanGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasFloor = !!panelFloorSelect.value;
|
||||||
|
panelPlacementFields.hidden = !hasFloor;
|
||||||
|
panelFloorPlanGroup.hidden = !hasFloor;
|
||||||
|
if (panelFloorMissingHint) {
|
||||||
|
panelFloorMissingHint.hidden = hasFloor;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterFloorOptions = () => {
|
||||||
|
if (!panelFloorSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const buildingValue = panelBuildingSelect?.value || '';
|
||||||
|
let firstMatch = '';
|
||||||
|
floorOptions.forEach((option) => {
|
||||||
|
const matches = !buildingValue || option.dataset.buildingId === buildingValue;
|
||||||
|
option.hidden = !matches;
|
||||||
|
option.disabled = !matches;
|
||||||
|
if (matches && !firstMatch) {
|
||||||
|
firstMatch = option.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedOption = panelFloorSelect.querySelector(`option[value="${panelFloorSelect.value}"]`);
|
||||||
|
const isSelectedHidden = selectedOption ? selectedOption.hidden : true;
|
||||||
|
if (buildingValue && (!panelFloorSelect.value || isSelectedHidden) && firstMatch) {
|
||||||
|
panelFloorSelect.value = firstMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePanelPlacementVisibility();
|
||||||
|
updateFloorPlanImage();
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterBuildingOptions = () => {
|
||||||
|
if (!panelBuildingSelect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const locationValue = panelLocationSelect?.value || '';
|
||||||
|
let firstMatch = '';
|
||||||
|
buildingOptions.forEach((option) => {
|
||||||
|
const matches = !locationValue || option.dataset.locationId === locationValue;
|
||||||
|
option.hidden = !matches;
|
||||||
|
option.disabled = !matches;
|
||||||
|
if (matches && !firstMatch) {
|
||||||
|
firstMatch = option.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedOption = panelBuildingSelect.querySelector(`option[value="${panelBuildingSelect.value}"]`);
|
||||||
|
const isSelectedHidden = selectedOption ? selectedOption.hidden : true;
|
||||||
|
if (locationValue && (!panelBuildingSelect.value || isSelectedHidden) && firstMatch) {
|
||||||
|
panelBuildingSelect.value = firstMatch;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (panelLocationSelect) {
|
||||||
|
panelLocationSelect.addEventListener('change', () => {
|
||||||
|
filterBuildingOptions();
|
||||||
|
filterFloorOptions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelBuildingSelect) {
|
||||||
|
panelBuildingSelect.addEventListener('change', () => {
|
||||||
|
filterFloorOptions();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelFloorSelect) {
|
||||||
|
panelFloorSelect.addEventListener('change', () => {
|
||||||
|
updatePanelPlacementVisibility();
|
||||||
|
updateFloorPlanImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outletRoomSelect) {
|
||||||
|
outletRoomSelect.addEventListener('change', () => {
|
||||||
|
updateFloorPlanImage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelLocationSelect) {
|
||||||
|
filterBuildingOptions();
|
||||||
|
filterFloorOptions();
|
||||||
|
} else {
|
||||||
|
updateFloorPlanImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePanelPlacementVisibility();
|
||||||
|
});
|
||||||
@@ -103,6 +103,22 @@ if ($type === 'patchpanel') {
|
|||||||
|
|
||||||
$markerWidth = $type === 'patchpanel' ? $panel['width'] : $defaultOutletSize;
|
$markerWidth = $type === 'patchpanel' ? $panel['width'] : $defaultOutletSize;
|
||||||
$markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
$markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
||||||
|
|
||||||
|
$mapPatchPanels = $sql->get(
|
||||||
|
"SELECT id, floor_id, name, pos_x, pos_y, width, height
|
||||||
|
FROM floor_patchpanels
|
||||||
|
ORDER BY floor_id, name",
|
||||||
|
"",
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
$mapOutlets = $sql->get(
|
||||||
|
"SELECT o.id, r.floor_id, o.name, o.x, o.y
|
||||||
|
FROM network_outlets o
|
||||||
|
JOIN rooms r ON r.id = o.room_id
|
||||||
|
ORDER BY r.floor_id, o.name",
|
||||||
|
"",
|
||||||
|
[]
|
||||||
|
);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="floor-infra-edit">
|
<div class="floor-infra-edit">
|
||||||
@@ -188,12 +204,15 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
data-marker-height="<?php echo $markerHeight; ?>"
|
data-marker-height="<?php echo $markerHeight; ?>"
|
||||||
data-marker-type="patchpanel"
|
data-marker-type="patchpanel"
|
||||||
data-x-field="pos_x"
|
data-x-field="pos_x"
|
||||||
data-y-field="pos_y">
|
data-y-field="pos_y"
|
||||||
|
data-active-id="<?php echo (int)($panel['id'] ?? 0); ?>"
|
||||||
|
data-reference-panels="<?php echo htmlspecialchars(json_encode($mapPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
data-reference-outlets="<?php echo htmlspecialchars(json_encode($mapOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
||||||
<div id="floor-plan-marker" class="floor-plan-marker panel-marker"
|
<div id="floor-plan-marker" class="floor-plan-marker panel-marker"
|
||||||
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
|
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="floor-plan-hint">Ziehe das Patchpanel oder klicke auf den Plan, um die Position zu setzen.</p>
|
<p class="floor-plan-hint">Nur das aktuell bearbeitete Patchpanel ist verschiebbar. Andere Objekte werden als Referenz halbtransparent angezeigt.</p>
|
||||||
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -204,7 +223,7 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p id="panel-floor-missing-hint" class="info" <?php echo $showPanelPlacementFields ? 'hidden' : ''; ?>>
|
<p id="panel-floor-missing-hint" class="info" <?php echo $showPanelPlacementFields ? 'hidden' : ''; ?>>
|
||||||
Position, Groesse und Stockwerkskarte werden erst angezeigt, sobald ein Stockwerk ausgewaehlt ist.
|
Position, Größe und Stockwerkskarte werden erst angezeigt, sobald ein Stockwerk ausgewählt ist.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -253,12 +272,15 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
data-marker-height="<?php echo $markerHeight; ?>"
|
data-marker-height="<?php echo $markerHeight; ?>"
|
||||||
data-marker-type="outlet"
|
data-marker-type="outlet"
|
||||||
data-x-field="x"
|
data-x-field="x"
|
||||||
data-y-field="y">
|
data-y-field="y"
|
||||||
|
data-active-id="<?php echo (int)($outlet['id'] ?? 0); ?>"
|
||||||
|
data-reference-panels="<?php echo htmlspecialchars(json_encode($mapPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>"
|
||||||
|
data-reference-outlets="<?php echo htmlspecialchars(json_encode($mapOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8'); ?>">
|
||||||
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
<img id="floor-plan-svg" class="floor-plan-svg" alt="Stockwerksplan">
|
||||||
<div id="floor-plan-marker" class="floor-plan-marker outlet-marker"
|
<div id="floor-plan-marker" class="floor-plan-marker outlet-marker"
|
||||||
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
|
style="--marker-width: <?php echo $markerWidth; ?>px; --marker-height: <?php echo $markerHeight; ?>px;"></div>
|
||||||
</div>
|
</div>
|
||||||
<p class="floor-plan-hint">Klicke oder ziehe die Wandbuchse auf dem Plan. Die Größe bleibt quadratisch.</p>
|
<p class="floor-plan-hint">Nur die aktuell bearbeitete Wandbuchse ist verschiebbar. Andere Objekte werden als Referenz halbtransparent angezeigt.</p>
|
||||||
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
<p class="floor-plan-position">Koordinate: <span id="floor-plan-position"></span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,6 +372,10 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
touch-action: none;
|
touch-action: none;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
.floor-plan-marker.is-active {
|
||||||
|
cursor: move;
|
||||||
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.8), 0 0 0 4px rgba(13, 110, 253, 0.35);
|
||||||
|
}
|
||||||
.floor-plan-marker.panel-marker {
|
.floor-plan-marker.panel-marker {
|
||||||
background: rgba(13, 110, 253, 0.25);
|
background: rgba(13, 110, 253, 0.25);
|
||||||
border: 2px solid #0d6efd;
|
border: 2px solid #0d6efd;
|
||||||
@@ -360,6 +386,26 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
border: 2px solid #198754;
|
border: 2px solid #198754;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
.floor-plan-reference {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.35;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.floor-plan-reference.panel-marker {
|
||||||
|
background: rgba(13, 110, 253, 0.22);
|
||||||
|
border: 2px solid rgba(13, 110, 253, 0.7);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.floor-plan-reference.outlet-marker {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background: rgba(25, 135, 84, 0.22);
|
||||||
|
border: 2px solid rgba(25, 135, 84, 0.7);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
.floor-plan-hint {
|
.floor-plan-hint {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: #444;
|
color: #444;
|
||||||
@@ -372,241 +418,5 @@ $markerHeight = $type === 'patchpanel' ? $panel['height'] : $defaultOutletSize;
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script src="/assets/js/floor-infrastructure-edit.js" defer></script>
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const canvas = document.getElementById('floor-plan-canvas');
|
|
||||||
const marker = document.getElementById('floor-plan-marker');
|
|
||||||
const positionLabel = document.getElementById('floor-plan-position');
|
|
||||||
if (!canvas || !marker) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const xFieldName = canvas.dataset.xField;
|
|
||||||
const yFieldName = canvas.dataset.yField;
|
|
||||||
const xField = xFieldName ? document.querySelector(`input[name="${xFieldName}"]`) : null;
|
|
||||||
const yField = yFieldName ? document.querySelector(`input[name="${yFieldName}"]`) : null;
|
|
||||||
if (!xField || !yField) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const markerWidth = Math.max(1, Number(canvas.dataset.markerWidth) || marker.offsetWidth);
|
|
||||||
const markerHeight = Math.max(1, Number(canvas.dataset.markerHeight) || marker.offsetHeight);
|
|
||||||
|
|
||||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
||||||
|
|
||||||
const updatePositionLabel = (x, y) => {
|
|
||||||
if (positionLabel) {
|
|
||||||
positionLabel.textContent = `${Math.round(x)} x ${Math.round(y)}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setMarkerPosition = (rawX, rawY) => {
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
const maxX = Math.max(0, rect.width - markerWidth);
|
|
||||||
const maxY = Math.max(0, rect.height - markerHeight);
|
|
||||||
const left = clamp(rawX, 0, maxX);
|
|
||||||
const top = clamp(rawY, 0, maxY);
|
|
||||||
marker.style.left = `${left}px`;
|
|
||||||
marker.style.top = `${top}px`;
|
|
||||||
xField.value = Math.round(left);
|
|
||||||
yField.value = Math.round(top);
|
|
||||||
updatePositionLabel(left, top);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateFromInputs = () => {
|
|
||||||
setMarkerPosition(Number(xField.value) || 0, Number(yField.value) || 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
updateFromInputs();
|
|
||||||
|
|
||||||
let dragging = false;
|
|
||||||
let offsetX = 0;
|
|
||||||
let offsetY = 0;
|
|
||||||
|
|
||||||
const startDrag = (clientX, clientY) => {
|
|
||||||
const markerRect = marker.getBoundingClientRect();
|
|
||||||
offsetX = clientX - markerRect.left;
|
|
||||||
offsetY = clientY - markerRect.top;
|
|
||||||
};
|
|
||||||
|
|
||||||
marker.addEventListener('pointerdown', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
dragging = true;
|
|
||||||
startDrag(event.clientX, event.clientY);
|
|
||||||
marker.setPointerCapture(event.pointerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
marker.addEventListener('pointermove', (event) => {
|
|
||||||
if (!dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
setMarkerPosition(event.clientX - rect.left - offsetX, event.clientY - rect.top - offsetY);
|
|
||||||
});
|
|
||||||
|
|
||||||
const stopDrag = (event) => {
|
|
||||||
if (!dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dragging = false;
|
|
||||||
if (marker.hasPointerCapture(event.pointerId)) {
|
|
||||||
marker.releasePointerCapture(event.pointerId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
['pointerup', 'pointercancel', 'pointerleave'].forEach((evt) => {
|
|
||||||
marker.addEventListener(evt, stopDrag);
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('pointerdown', (event) => {
|
|
||||||
if (event.target !== canvas) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
setMarkerPosition(event.clientX - rect.left - markerWidth / 2, event.clientY - rect.top - markerHeight / 2);
|
|
||||||
});
|
|
||||||
|
|
||||||
[xField, yField].forEach((input) => {
|
|
||||||
input.addEventListener('input', () => {
|
|
||||||
updateFromInputs();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
updateFromInputs();
|
|
||||||
});
|
|
||||||
|
|
||||||
const panelLocationSelect = document.getElementById('panel-location-select');
|
|
||||||
const panelBuildingSelect = document.getElementById('panel-building-select');
|
|
||||||
const panelFloorSelect = document.getElementById('panel-floor-select');
|
|
||||||
const outletRoomSelect = document.getElementById('outlet-room-select');
|
|
||||||
const floorPlanSvg = document.getElementById('floor-plan-svg');
|
|
||||||
const panelPlacementFields = document.getElementById('panel-placement-fields');
|
|
||||||
const panelFloorPlanGroup = document.getElementById('panel-floor-plan-group');
|
|
||||||
const panelFloorMissingHint = document.getElementById('panel-floor-missing-hint');
|
|
||||||
|
|
||||||
const buildingOptions = panelBuildingSelect ? Array.from(panelBuildingSelect.options).filter((option) => option.value !== '') : [];
|
|
||||||
const floorOptions = panelFloorSelect ? Array.from(panelFloorSelect.options).filter((option) => option.value !== '') : [];
|
|
||||||
|
|
||||||
const updateFloorPlanImage = () => {
|
|
||||||
if (!floorPlanSvg) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const floorOption = panelFloorSelect?.selectedOptions?.[0];
|
|
||||||
const roomOption = outletRoomSelect?.selectedOptions?.[0];
|
|
||||||
const svgUrl = floorOption?.dataset?.svgUrl || roomOption?.dataset?.floorSvgUrl || '';
|
|
||||||
|
|
||||||
if (svgUrl) {
|
|
||||||
floorPlanSvg.src = svgUrl;
|
|
||||||
floorPlanSvg.hidden = false;
|
|
||||||
} else {
|
|
||||||
floorPlanSvg.removeAttribute('src');
|
|
||||||
floorPlanSvg.hidden = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (floorPlanSvg) {
|
|
||||||
floorPlanSvg.addEventListener('error', () => {
|
|
||||||
floorPlanSvg.removeAttribute('src');
|
|
||||||
floorPlanSvg.hidden = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePanelPlacementVisibility = () => {
|
|
||||||
if (!panelFloorSelect || !panelPlacementFields || !panelFloorPlanGroup) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasFloor = !!panelFloorSelect.value;
|
|
||||||
panelPlacementFields.hidden = !hasFloor;
|
|
||||||
panelFloorPlanGroup.hidden = !hasFloor;
|
|
||||||
if (panelFloorMissingHint) {
|
|
||||||
panelFloorMissingHint.hidden = hasFloor;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterFloorOptions = () => {
|
|
||||||
if (!panelFloorSelect) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const buildingValue = panelBuildingSelect?.value || '';
|
|
||||||
let firstMatch = '';
|
|
||||||
floorOptions.forEach((option) => {
|
|
||||||
const matches = !buildingValue || option.dataset.buildingId === buildingValue;
|
|
||||||
option.hidden = !matches;
|
|
||||||
option.disabled = !matches;
|
|
||||||
if (matches && !firstMatch) {
|
|
||||||
firstMatch = option.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedOption = panelFloorSelect.querySelector(`option[value="${panelFloorSelect.value}"]`);
|
|
||||||
const isSelectedHidden = selectedOption ? selectedOption.hidden : true;
|
|
||||||
if (buildingValue && (!panelFloorSelect.value || isSelectedHidden) && firstMatch) {
|
|
||||||
panelFloorSelect.value = firstMatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePanelPlacementVisibility();
|
|
||||||
updateFloorPlanImage();
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterBuildingOptions = () => {
|
|
||||||
if (!panelBuildingSelect) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const locationValue = panelLocationSelect?.value || '';
|
|
||||||
let firstMatch = '';
|
|
||||||
buildingOptions.forEach((option) => {
|
|
||||||
const matches = !locationValue || option.dataset.locationId === locationValue;
|
|
||||||
option.hidden = !matches;
|
|
||||||
option.disabled = !matches;
|
|
||||||
if (matches && !firstMatch) {
|
|
||||||
firstMatch = option.value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const selectedOption = panelBuildingSelect.querySelector(`option[value="${panelBuildingSelect.value}"]`);
|
|
||||||
const isSelectedHidden = selectedOption ? selectedOption.hidden : true;
|
|
||||||
if (locationValue && (!panelBuildingSelect.value || isSelectedHidden) && firstMatch) {
|
|
||||||
panelBuildingSelect.value = firstMatch;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (panelLocationSelect) {
|
|
||||||
panelLocationSelect.addEventListener('change', () => {
|
|
||||||
filterBuildingOptions();
|
|
||||||
filterFloorOptions();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panelBuildingSelect) {
|
|
||||||
panelBuildingSelect.addEventListener('change', () => {
|
|
||||||
filterFloorOptions();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panelFloorSelect) {
|
|
||||||
panelFloorSelect.addEventListener('change', () => {
|
|
||||||
updatePanelPlacementVisibility();
|
|
||||||
updateFloorPlanImage();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outletRoomSelect) {
|
|
||||||
outletRoomSelect.addEventListener('change', () => {
|
|
||||||
updateFloorPlanImage();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panelLocationSelect) {
|
|
||||||
filterBuildingOptions();
|
|
||||||
filterFloorOptions();
|
|
||||||
} else {
|
|
||||||
updateFloorPlanImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePanelPlacementVisibility();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* app/modules/floor_infrastructure/list.php
|
* app/modules/floor_infrastructure/list.php
|
||||||
*
|
*
|
||||||
* Uebersicht ueber Patchpanels und Netzwerkbuchsen auf Stockwerken
|
* Übersicht über Patchpanels und Netzwerkbuchsen auf Stockwerken
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$floorId = (int)($_GET['floor_id'] ?? 0);
|
$floorId = (int)($_GET['floor_id'] ?? 0);
|
||||||
@@ -101,10 +101,10 @@ if ($editorFloorId > 0) {
|
|||||||
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<a href="?module=floor_infrastructure&action=edit&type=patchpanel" class="button button-primary">
|
<a href="?module=floor_infrastructure&action=edit&type=patchpanel" class="button button-primary">
|
||||||
+ Patchpanel hinzufuegen
|
+ Patchpanel hinzufügen
|
||||||
</a>
|
</a>
|
||||||
<a href="?module=floor_infrastructure&action=edit&type=outlet" class="button">
|
<a href="?module=floor_infrastructure&action=edit&type=outlet" class="button">
|
||||||
+ Wandbuchse hinzufuegen
|
+ Wandbuchse hinzufügen
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ if ($editorFloorId > 0) {
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<button class="button">Filter</button>
|
<button class="button">Filter</button>
|
||||||
<a href="?module=floor_infrastructure&action=list" class="button">Zuruecksetzen</a>
|
<a href="?module=floor_infrastructure&action=list" class="button">Zurücksetzen</a>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<section class="infra-section">
|
<section class="infra-section">
|
||||||
@@ -134,7 +134,7 @@ if ($editorFloorId > 0) {
|
|||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Stockwerk</th>
|
<th>Stockwerk</th>
|
||||||
<th>Position</th>
|
<th>Position</th>
|
||||||
<th>Groesse</th>
|
<th>Größe</th>
|
||||||
<th>Ports</th>
|
<th>Ports</th>
|
||||||
<th>Aktionen</th>
|
<th>Aktionen</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -196,18 +196,18 @@ if ($editorFloorId > 0) {
|
|||||||
<section class="infra-plan">
|
<section class="infra-plan">
|
||||||
<h2>Stockwerksplan-Verortung</h2>
|
<h2>Stockwerksplan-Verortung</h2>
|
||||||
<?php if (!$editorFloor): ?>
|
<?php if (!$editorFloor): ?>
|
||||||
<p class="empty-state">Kein Stockwerk fuer die Planansicht verfuegbar.</p>
|
<p class="empty-state">Kein Stockwerk für die Planansicht verfügbar.</p>
|
||||||
<?php elseif (($editorFloor['svg_url'] ?? '') === ''): ?>
|
<?php elseif (($editorFloor['svg_url'] ?? '') === ''): ?>
|
||||||
<p class="empty-state">Das gewaehlte Stockwerk hat noch keinen SVG-Plan hinterlegt.</p>
|
<p class="empty-state">Das gewählte Stockwerk hat noch keinen SVG-Plan hinterlegt.</p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p>Drag & Drop ist aktiv fuer <strong><?php echo htmlspecialchars($editorFloor['name']); ?></strong>. Aenderungen werden direkt gespeichert.</p>
|
<p>Vorschau für <strong><?php echo htmlspecialchars($editorFloor['name']); ?></strong>. Positionen bearbeitest du über den jeweiligen „Bearbeiten“-Button.</p>
|
||||||
<div id="infra-floor-canvas"
|
<div id="infra-floor-canvas"
|
||||||
class="infra-floor-canvas"
|
class="infra-floor-canvas"
|
||||||
data-save-url="?module=floor_infrastructure&action=save"
|
data-save-url="?module=floor_infrastructure&action=save"
|
||||||
data-floor-id="<?php echo (int)$editorFloor['id']; ?>">
|
data-floor-id="<?php echo (int)$editorFloor['id']; ?>">
|
||||||
<img src="<?php echo htmlspecialchars($editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
|
<img src="<?php echo htmlspecialchars($editorFloor['svg_url']); ?>" class="infra-floor-svg" alt="Stockwerksplan">
|
||||||
</div>
|
</div>
|
||||||
<p class="floor-plan-hint">Patchpanels: blau, Wandbuchsen: gruen. Ziehen und loslassen zum Speichern.</p>
|
<p class="floor-plan-hint">Patchpanels: blau, Wandbuchsen: grün. In dieser Ansicht sind Marker nicht verschiebbar.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -277,8 +277,9 @@ if ($editorFloorId > 0) {
|
|||||||
left: 0;
|
left: 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
cursor: move;
|
cursor: default;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.infra-marker.patchpanel {
|
.infra-marker.patchpanel {
|
||||||
background: rgba(13, 110, 253, 0.25);
|
background: rgba(13, 110, 253, 0.25);
|
||||||
@@ -292,9 +293,6 @@ if ($editorFloorId > 0) {
|
|||||||
border: 2px solid #198754;
|
border: 2px solid #198754;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.infra-marker.is-saving {
|
|
||||||
opacity: 0.65;
|
|
||||||
}
|
|
||||||
.floor-plan-hint {
|
.floor-plan-hint {
|
||||||
margin: 8px 0 0;
|
margin: 8px 0 0;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
@@ -319,95 +317,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveUrl = canvas.dataset.saveUrl;
|
|
||||||
const floorId = Number(canvas.dataset.floorId || 0);
|
|
||||||
const outletSize = 32;
|
const outletSize = 32;
|
||||||
const patchPanels = <?php echo json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
const patchPanels = <?php echo json_encode($editorPatchPanels, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||||
const outlets = <?php echo json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
const outlets = <?php echo json_encode($editorOutlets, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
|
||||||
|
|
||||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
||||||
|
|
||||||
const savePosition = async (entry, type, marker) => {
|
|
||||||
const form = new FormData();
|
|
||||||
|
|
||||||
if (type === 'patchpanel') {
|
|
||||||
form.append('type', 'patchpanel');
|
|
||||||
form.append('id', String(entry.id));
|
|
||||||
form.append('name', entry.name || '');
|
|
||||||
form.append('floor_id', String(floorId));
|
|
||||||
form.append('pos_x', String(entry.x));
|
|
||||||
form.append('pos_y', String(entry.y));
|
|
||||||
form.append('width', String(entry.width));
|
|
||||||
form.append('height', String(entry.height));
|
|
||||||
form.append('port_count', String(entry.port_count || 0));
|
|
||||||
form.append('comment', entry.comment || '');
|
|
||||||
} else {
|
|
||||||
form.append('type', 'outlet');
|
|
||||||
form.append('id', String(entry.id));
|
|
||||||
form.append('name', entry.name || '');
|
|
||||||
form.append('room_id', String(entry.room_id || 0));
|
|
||||||
form.append('x', String(entry.x));
|
|
||||||
form.append('y', String(entry.y));
|
|
||||||
form.append('comment', entry.comment || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
marker.classList.add('is-saving');
|
|
||||||
try {
|
|
||||||
await fetch(saveUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
body: form,
|
|
||||||
credentials: 'same-origin'
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
marker.classList.remove('is-saving');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bindDrag = (marker, entry, type) => {
|
|
||||||
let dragging = false;
|
|
||||||
let offsetX = 0;
|
|
||||||
let offsetY = 0;
|
|
||||||
|
|
||||||
marker.addEventListener('pointerdown', (event) => {
|
|
||||||
dragging = true;
|
|
||||||
const rect = marker.getBoundingClientRect();
|
|
||||||
offsetX = event.clientX - rect.left;
|
|
||||||
offsetY = event.clientY - rect.top;
|
|
||||||
marker.setPointerCapture(event.pointerId);
|
|
||||||
});
|
|
||||||
|
|
||||||
marker.addEventListener('pointermove', (event) => {
|
|
||||||
if (!dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
const width = type === 'patchpanel' ? entry.width : outletSize;
|
|
||||||
const height = type === 'patchpanel' ? entry.height : outletSize;
|
|
||||||
const maxX = Math.max(0, rect.width - width);
|
|
||||||
const maxY = Math.max(0, rect.height - height);
|
|
||||||
|
|
||||||
entry.x = Math.round(clamp(event.clientX - rect.left - offsetX, 0, maxX));
|
|
||||||
entry.y = Math.round(clamp(event.clientY - rect.top - offsetY, 0, maxY));
|
|
||||||
marker.style.left = `${entry.x}px`;
|
|
||||||
marker.style.top = `${entry.y}px`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const stopDrag = (event) => {
|
|
||||||
if (!dragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dragging = false;
|
|
||||||
if (marker.hasPointerCapture(event.pointerId)) {
|
|
||||||
marker.releasePointerCapture(event.pointerId);
|
|
||||||
}
|
|
||||||
savePosition(entry, type, marker);
|
|
||||||
};
|
|
||||||
|
|
||||||
marker.addEventListener('pointerup', stopDrag);
|
|
||||||
marker.addEventListener('pointercancel', stopDrag);
|
|
||||||
};
|
|
||||||
|
|
||||||
const createMarker = (entry, type) => {
|
const createMarker = (entry, type) => {
|
||||||
const marker = document.createElement('div');
|
const marker = document.createElement('div');
|
||||||
marker.className = `infra-marker ${type}`;
|
marker.className = `infra-marker ${type}`;
|
||||||
@@ -423,8 +336,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
marker.style.left = `${entry.x}px`;
|
marker.style.left = `${entry.x}px`;
|
||||||
marker.style.top = `${entry.y}px`;
|
marker.style.top = `${entry.y}px`;
|
||||||
canvas.appendChild(marker);
|
canvas.appendChild(marker);
|
||||||
|
|
||||||
bindDrag(marker, entry, type);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));
|
patchPanels.forEach((entry) => createMarker(entry, 'patchpanel'));
|
||||||
|
|||||||
Reference in New Issue
Block a user