(() => { const svgElement = document.querySelector('#device-svg'); if (!svgElement) { return; } const DEVICE_TYPE_ID = Number(svgElement.dataset.deviceTypeId || 0); const API_LOAD_PORTS = '/api/device_type_ports.php?action=load'; const API_SAVE_PORTS = '/api/device_type_ports.php?action=save'; const DEFAULT_PORT_TYPE_ID = null; let ports = []; let selectedPortId = null; let isDragging = false; let dragOffset = { x: 0, y: 0 }; bindSvgEvents(); loadPorts(); function bindSvgEvents() { svgElement.addEventListener('click', onSvgClick); svgElement.addEventListener('mousemove', onSvgMouseMove); svgElement.addEventListener('mouseup', onSvgMouseUp); const saveButton = document.querySelector('[data-save-svg-ports]'); if (saveButton) { saveButton.addEventListener('click', (event) => { event.preventDefault(); savePorts(); }); } } function onSvgClick(event) { if (event.target.classList.contains('port-point')) { selectPort(event.target.dataset.id); return; } // New ports are only created while SHIFT is held. if (!event.shiftKey) { return; } const point = getSvgCoordinates(event); createPort(point.x, point.y); } function createPort(x, y) { const id = generateTempId(); const port = { id, name: `Port ${ports.length + 1}`, port_type_id: DEFAULT_PORT_TYPE_ID, x, y, metadata: null }; ports.push(port); renderPort(port); selectPort(id); } function renderPort(port) { const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); circle.setAttribute('cx', port.x); circle.setAttribute('cy', port.y); circle.setAttribute('r', 6); circle.classList.add('port-point'); circle.dataset.id = port.id; circle.addEventListener('mousedown', (e) => { startDrag(e, port.id); e.stopPropagation(); }); svgElement.appendChild(circle); } function rerenderPorts() { svgElement.querySelectorAll('.port-point').forEach((node) => node.remove()); ports.forEach(renderPort); if (selectedPortId !== null) { selectPort(selectedPortId); } } function selectPort(id) { selectedPortId = id; document.querySelectorAll('.port-point').forEach((el) => { el.classList.toggle('selected', el.dataset.id === String(id)); }); const selected = getPortById(id); fillSidebar(selected); } function fillSidebar(port) { const nameField = document.querySelector('[data-port-name]'); const typeField = document.querySelector('[data-port-type-id]'); const xField = document.querySelector('[data-port-x]'); const yField = document.querySelector('[data-port-y]'); if (nameField) nameField.value = port?.name || ''; if (typeField) typeField.value = port?.port_type_id || ''; if (xField) xField.value = port ? Math.round(port.x) : ''; if (yField) yField.value = port ? Math.round(port.y) : ''; } function resetSidebar() { fillSidebar(null); } function startDrag(event, portId) { const port = getPortById(portId); if (!port) return; isDragging = true; selectedPortId = portId; const point = getSvgCoordinates(event); dragOffset.x = port.x - point.x; dragOffset.y = port.y - point.y; } function onSvgMouseMove(event) { if (!isDragging || !selectedPortId) return; const port = getPortById(selectedPortId); if (!port) return; const point = getSvgCoordinates(event); port.x = point.x + dragOffset.x; port.y = point.y + dragOffset.y; rerenderPorts(); } function onSvgMouseUp() { isDragging = false; } function deleteSelectedPort() { if (!selectedPortId) { return; } if (!confirm('Ausgewaehlten Port loeschen?')) { return; } ports = ports.filter((port) => String(port.id) !== String(selectedPortId)); selectedPortId = null; rerenderPorts(); resetSidebar(); } function loadPorts() { if (!DEVICE_TYPE_ID) { console.warn('SVG Editor: DEVICE_TYPE_ID fehlt auf #device-svg'); return; } fetch(`${API_LOAD_PORTS}&device_type_id=${DEVICE_TYPE_ID}`) .then((res) => res.json()) .then((data) => { if (!Array.isArray(data)) { throw new Error('Antwortformat ungueltig'); } ports = data .filter((entry) => entry && typeof entry === 'object') .map((entry) => ({ id: entry.id, name: String(entry.name || ''), port_type_id: entry.port_type_id ? Number(entry.port_type_id) : null, x: Number(entry.x || 0), y: Number(entry.y || 0), metadata: entry.metadata || null })); rerenderPorts(); }) .catch((err) => { console.error('Fehler beim Laden der Ports', err); }); } function savePorts() { if (!DEVICE_TYPE_ID) { return; } fetch(API_SAVE_PORTS, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ device_type_id: DEVICE_TYPE_ID, ports }) }) .then((res) => res.json()) .then((data) => { if (data?.error) { throw new Error(data.error); } alert('Ports gespeichert'); }) .catch((err) => { alert('Speichern fehlgeschlagen: ' + err.message); }); } function getSvgCoordinates(event) { const pt = svgElement.createSVGPoint(); pt.x = event.clientX; pt.y = event.clientY; const transformed = pt.matrixTransform(svgElement.getScreenCTM().inverse()); return { x: transformed.x, y: transformed.y }; } function getPortById(id) { return ports.find((port) => String(port.id) === String(id)); } function generateTempId() { return 'tmp_' + Math.random().toString(36).slice(2, 11); } document.addEventListener('keydown', (event) => { if (event.key === 'Delete') { deleteSelectedPort(); } }); })();