// Logik für den SVG-Port-Editor (Klicks, Drag & Drop, Speichern) /** * svg-editor.js * * Logik für den SVG-Port-Editor: * - Ports per Klick anlegen * - Ports auswählen * - Ports verschieben (Drag & Drop) * - Ports löschen * - Ports laden / speichern * * Abhängigkeiten: keine (Vanilla JS) */ (() => { /* ========================= * Konfiguration * ========================= */ // TODO: vom Backend setzen (z. B. via data-Attribut) const DEVICE_TYPE_ID = null; // TODO: API-Endpunkte festlegen const API_LOAD_PORTS = '/api/device_type_ports.php?action=load'; const API_SAVE_PORTS = '/api/device_type_ports.php?action=save'; /* ========================= * State * ========================= */ let svgElement = null; let ports = []; let selectedPortId = null; let isDragging = false; let dragOffset = { x: 0, y: 0 }; /* ========================= * Initialisierung * ========================= */ document.addEventListener('DOMContentLoaded', () => { svgElement = document.querySelector('#device-svg'); if (!svgElement) { console.warn('SVG Editor: #device-svg nicht gefunden'); return; } bindSvgEvents(); loadPorts(); }); /* ========================= * SVG Events * ========================= */ function bindSvgEvents() { svgElement.addEventListener('click', onSvgClick); svgElement.addEventListener('mousemove', onSvgMouseMove); svgElement.addEventListener('mouseup', onSvgMouseUp); } /* ========================= * Port-Erstellung * ========================= */ function onSvgClick(event) { // Klick auf bestehenden Port? if (event.target.classList.contains('port-point')) { selectPort(event.target.dataset.id); return; } // TODO: Modifier-Key prüfen (z. B. nur mit SHIFT neuen Port erstellen?) const point = getSvgCoordinates(event); createPort(point.x, point.y); } function createPort(x, y) { const id = generateTempId(); const port = { id: id, name: `Port ${ports.length + 1}`, port_type_id: null, // TODO: Default-Porttyp? x: x, y: y, comment: '' }; ports.push(port); renderPort(port); selectPort(id); } /* ========================= * Rendering * ========================= */ 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(p => p.remove()); ports.forEach(renderPort); } /* ========================= * Auswahl * ========================= */ function selectPort(id) { selectedPortId = id; document.querySelectorAll('.port-point').forEach(el => { el.classList.toggle('selected', el.dataset.id === id); }); // TODO: Sidebar-Felder mit Portdaten füllen } /* ========================= * Drag & Drop * ========================= */ 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; } /* ========================= * Löschen * ========================= */ function deleteSelectedPort() { if (!selectedPortId) return; // TODO: Sicherheitsabfrage (confirm) ports = ports.filter(p => p.id !== selectedPortId); selectedPortId = null; rerenderPorts(); // TODO: Sidebar zurücksetzen } /* ========================= * Laden / Speichern * ========================= */ function loadPorts() { if (!DEVICE_TYPE_ID) { console.warn('DEVICE_TYPE_ID nicht gesetzt'); return; } fetch(`${API_LOAD_PORTS}&device_type_id=${DEVICE_TYPE_ID}`) .then(res => res.json()) .then(data => { // TODO: Datenformat validieren ports = data; 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: ports }) }) .then(res => res.json()) .then(data => { // TODO: Erfolg / Fehler anzeigen console.log('Ports gespeichert', data); }) .catch(err => { console.error('Fehler beim Speichern', err); }); } /* ========================= * Hilfsfunktionen * ========================= */ 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(p => p.id === id); } function generateTempId() { return 'tmp_' + Math.random().toString(36).substr(2, 9); } /* ========================= * Keyboard Shortcuts * ========================= */ document.addEventListener('keydown', (e) => { if (e.key === 'Delete') { deleteSelectedPort(); } }); })();