div TODOs

This commit is contained in:
2026-02-16 13:56:01 +01:00
parent 12141485ae
commit 510a248edb
36 changed files with 1500 additions and 1533 deletions

View File

@@ -1,92 +1,60 @@
// 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
* ========================= */
const svgElement = document.querySelector('#device-svg');
if (!svgElement) {
return;
}
// TODO: vom Backend setzen (z. B. via data-Attribut)
const DEVICE_TYPE_ID = null;
// TODO: API-Endpunkte festlegen
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;
/* =========================
* 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
* ========================= */
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();
});
}
}
/* =========================
* 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);
// 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: id,
id,
name: `Port ${ports.length + 1}`,
port_type_id: null, // TODO: Default-Porttyp?
x: x,
y: y,
comment: ''
port_type_id: DEFAULT_PORT_TYPE_ID,
x,
y,
metadata: null
};
ports.push(port);
@@ -94,13 +62,8 @@ function createPort(x, y) {
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);
@@ -116,27 +79,39 @@ function renderPort(port) {
}
function rerenderPorts() {
svgElement.querySelectorAll('.port-point').forEach(p => p.remove());
svgElement.querySelectorAll('.port-point').forEach((node) => node.remove());
ports.forEach(renderPort);
if (selectedPortId !== null) {
selectPort(selectedPortId);
}
}
/* =========================
* Auswahl
* ========================= */
function selectPort(id) {
selectedPortId = id;
document.querySelectorAll('.port-point').forEach(el => {
el.classList.toggle('selected', el.dataset.id === id);
document.querySelectorAll('.port-point').forEach((el) => {
el.classList.toggle('selected', el.dataset.id === String(id));
});
// TODO: Sidebar-Felder mit Portdaten füllen
const selected = getPortById(id);
fillSidebar(selected);
}
/* =========================
* Drag & Drop
* ========================= */
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);
@@ -157,7 +132,6 @@ function onSvgMouseMove(event) {
if (!port) return;
const point = getSvgCoordinates(event);
port.x = point.x + dragOffset.x;
port.y = point.y + dragOffset.y;
@@ -168,92 +142,95 @@ function onSvgMouseUp() {
isDragging = false;
}
/* =========================
* Löschen
* ========================= */
function deleteSelectedPort() {
if (!selectedPortId) return;
if (!selectedPortId) {
return;
}
// TODO: Sicherheitsabfrage (confirm)
ports = ports.filter(p => p.id !== selectedPortId);
if (!confirm('Ausgewaehlten Port loeschen?')) {
return;
}
ports = ports.filter((port) => String(port.id) !== String(selectedPortId));
selectedPortId = null;
rerenderPorts();
// TODO: Sidebar zurücksetzen
resetSidebar();
}
/* =========================
* Laden / Speichern
* ========================= */
function loadPorts() {
if (!DEVICE_TYPE_ID) {
console.warn('DEVICE_TYPE_ID nicht gesetzt');
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 => {
// TODO: Datenformat validieren
ports = data;
.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 => {
.catch((err) => {
console.error('Fehler beim Laden der Ports', err);
});
}
function savePorts() {
if (!DEVICE_TYPE_ID) return;
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
ports
})
})
.then(res => res.json())
.then(data => {
// TODO: Erfolg / Fehler anzeigen
console.log('Ports gespeichert', data);
})
.catch(err => {
console.error('Fehler beim Speichern', err);
});
.then((res) => res.json())
.then((data) => {
if (data?.error) {
throw new Error(data.error);
}
alert('Ports gespeichert');
})
.catch((err) => {
alert('Speichern fehlgeschlagen: ' + err.message);
});
}
/* =========================
* 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);
return ports.find((port) => String(port.id) === String(id));
}
function generateTempId() {
return 'tmp_' + Math.random().toString(36).substr(2, 9);
return 'tmp_' + Math.random().toString(36).slice(2, 11);
}
/* =========================
* Keyboard Shortcuts
* ========================= */
document.addEventListener('keydown', (e) => {
if (e.key === 'Delete') {
document.addEventListener('keydown', (event) => {
if (event.key === 'Delete') {
deleteSelectedPort();
}
});