From 7727f5f31f90495ad49af3e07f305b749ec79248 Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Mon, 22 Jun 2026 20:56:23 +0000 Subject: [PATCH] feat: Implement complete layer management UI with all required features --- frontend/src/components/LayerPanel.jsx | 392 ++++++++++++++++++++----- 1 file changed, 325 insertions(+), 67 deletions(-) diff --git a/frontend/src/components/LayerPanel.jsx b/frontend/src/components/LayerPanel.jsx index 132518a..ad20a8b 100644 --- a/frontend/src/components/LayerPanel.jsx +++ b/frontend/src/components/LayerPanel.jsx @@ -1,30 +1,214 @@ -import React, { useState } from 'react'; +import React, { useState, useRef } from 'react'; -const LayerPanel = ({ layers, setLayers, activeLayer, setActiveLayer }) => { +const LayerPanel = ({ layers, setLayers, activeLayer, setActiveLayer, yjsDoc }) => { const [newLayerName, setNewLayerName] = useState(''); + const [editingLayerId, setEditingLayerId] = useState(null); + const [editingName, setEditingName] = useState(''); + const [filterText, setFilterText] = useState(''); + const [showImportDialog, setShowImportDialog] = useState(false); + const [importFile, setImportFile] = useState(null); + const fileInputRef = useRef(null); const addLayer = () => { if (!newLayerName.trim()) return; - setLayers([...layers, { name: newLayerName, visible: true, locked: false }]); + + const newLayer = { + id: Date.now().toString(), + name: newLayerName, + visible: true, + locked: false, + color: '#000000', + lineType: 'solid', + transparency: 0, + sortOrder: layers.length + }; + + // Add to Yjs document + if (yjsDoc) { + yjsDoc.layers.set(newLayer.id, newLayer); + } + + setLayers([...layers, newLayer]); setNewLayerName(''); }; - const deleteLayer = (name) => { + const deleteLayer = (id) => { if (layers.length <= 1) return; - setLayers(layers.filter(l => l.name !== name)); - if (activeLayer === name) setActiveLayer(layers[0].name); + + // Confirmation + if (!window.confirm('Sind Sie sicher, dass Sie diesen Layer löschen möchten?')) { + return; + } + + // Remove from Yjs document + if (yjsDoc) { + yjsDoc.layers.delete(id); + } + + const updatedLayers = layers.filter(l => l.id !== id); + setLayers(updatedLayers); + + if (activeLayer === id) { + setActiveLayer(updatedLayers[0]?.id || null); + } }; - const toggleVisible = (name) => { - setLayers(layers.map(l => l.name === name ? { ...l, visible: !l.visible } : l)); + const toggleVisible = (id) => { + const updatedLayers = layers.map(l => + l.id === id ? { ...l, visible: !l.visible } : l + ); + + // Update in Yjs document + if (yjsDoc) { + const layer = yjsDoc.layers.get(id); + if (layer) { + layer.visible = !layer.visible; + yjsDoc.layers.set(id, layer); + } + } + + setLayers(updatedLayers); }; - const toggleLocked = (name) => { - setLayers(layers.map(l => l.name === name ? { ...l, locked: !l.locked } : l)); + const toggleLocked = (id) => { + const updatedLayers = layers.map(l => + l.id === id ? { ...l, locked: !l.locked } : l + ); + + // Update in Yjs document + if (yjsDoc) { + const layer = yjsDoc.layers.get(id); + if (layer) { + layer.locked = !layer.locked; + yjsDoc.layers.set(id, layer); + } + } + + setLayers(updatedLayers); + }; + + const startEditing = (id, name) => { + setEditingLayerId(id); + setEditingName(name); + }; + + const saveEditing = (id) => { + if (!editingName.trim()) return; + + const updatedLayers = layers.map(l => + l.id === id ? { ...l, name: editingName } : l + ); + + // Update in Yjs document + if (yjsDoc) { + const layer = yjsDoc.layers.get(id); + if (layer) { + layer.name = editingName; + yjsDoc.layers.set(id, layer); + } + } + + setLayers(updatedLayers); + setEditingLayerId(null); + setEditingName(''); + }; + + const cancelEditing = () => { + setEditingLayerId(null); + setEditingName(''); + }; + + const setColor = (id, color) => { + const updatedLayers = layers.map(l => + l.id === id ? { ...l, color } : l + ); + + // Update in Yjs document + if (yjsDoc) { + const layer = yjsDoc.layers.get(id); + if (layer) { + layer.color = color; + yjsDoc.layers.set(id, layer); + } + } + + setLayers(updatedLayers); + }; + + const setLineType = (id, lineType) => { + const updatedLayers = layers.map(l => + l.id === id ? { ...l, lineType } : l + ); + + // Update in Yjs document + if (yjsDoc) { + const layer = yjsDoc.layers.get(id); + if (layer) { + layer.lineType = lineType; + yjsDoc.layers.set(id, layer); + } + } + + setLayers(updatedLayers); + }; + + const filteredLayers = layers.filter(layer => + layer.name.toLowerCase().includes(filterText.toLowerCase()) + ); + + const exportLayers = () => { + const dataStr = JSON.stringify(layers, null, 2); + const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); + + const exportFileDefaultName = 'layers.json'; + + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); + }; + + const handleImportClick = () => { + fileInputRef.current?.click(); + }; + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + try { + const importedLayers = JSON.parse(e.target.result); + + // Validate imported layers + if (Array.isArray(importedLayers)) { + // Add to Yjs document + if (yjsDoc) { + importedLayers.forEach(layer => { + yjsDoc.layers.set(layer.id, layer); + }); + } + + setLayers(importedLayers); + + // Set first layer as active if none is active + if (!activeLayer && importedLayers.length > 0) { + setActiveLayer(importedLayers[0].id); + } + } + } catch (error) { + alert('Fehler beim Importieren der Layer: ' + error.message); + } + }; + reader.readAsText(file); + + // Reset file input + event.target.value = ''; }; return ( -
+
{ value={newLayerName} onChange={(e) => setNewLayerName(e.target.value)} style={{ flex: 1, padding: 4, borderRadius: 3, border: '1px solid #ccc' }} + onKeyDown={(e) => e.key === 'Enter' && addLayer()} />
- {layers.map((layer, idx) => ( -
setActiveLayer(layer.name)} - style={{ - padding: '6px 8px', - marginBottom: 4, - background: activeLayer === layer.name ? '#e6f7ff' : '#fff', - border: activeLayer === layer.name ? '1px solid #1890ff' : '1px solid #ddd', - borderRadius: 4, - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - fontSize: 13, - }} - > - {layer.name} - - - - - -
- ))} + +
+ setFilterText(e.target.value)} + style={{ flex: 1, padding: 4, borderRadius: 3, border: '1px solid #ccc' }} + /> + + + +
+ +
+ {filteredLayers.map((layer) => ( +
setActiveLayer(layer.id)} + style={{ + padding: '6px 8px', + marginBottom: 4, + background: activeLayer === layer.id ? '#e6f7ff' : '#fff', + border: activeLayer === layer.id ? '1px solid #1890ff' : '1px solid #ddd', + borderRadius: 4, + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + fontSize: 13, + }} + > +
+
+ {editingLayerId === layer.id ? ( + setEditingName(e.target.value)} + onBlur={() => saveEditing(layer.id)} + onKeyDown={(e) => { + if (e.key === 'Enter') saveEditing(layer.id); + if (e.key === 'Escape') cancelEditing(); + }} + autoFocus + style={{ flex: 1, padding: 2, borderRadius: 2, border: '1px solid #1890ff' }} + /> + ) : ( + startEditing(layer.id, layer.name)} + style={{ flex: 1 }} + > + {layer.name} + + )} +
+ + + + + + +
+ ))} +
+ +
+ Doppelklick auf Layername zum Umbenennen +
); }; -export default LayerPanel; +export default LayerPanel; \ No newline at end of file