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(); });