feat: Implement complete layer management UI with all required features

This commit is contained in:
2026-06-22 20:56:23 +00:00
parent d39b1d84cd
commit 7727f5f31f
+325 -67
View File
@@ -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 (
<div style={{ padding: '0 10px 10px' }}>
<div style={{ padding: '0 10px 10px', height: '100%', display: 'flex', flexDirection: 'column' }}>
<div style={{ display: 'flex', gap: 5, marginBottom: 10 }}>
<input
type="text"
@@ -32,66 +216,140 @@ const LayerPanel = ({ layers, setLayers, activeLayer, setActiveLayer }) => {
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()}
/>
<button onClick={addLayer} style={{ padding: '4px 8px', background: '#1890ff', color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer' }}>+</button>
</div>
{layers.map((layer, idx) => (
<div
key={idx}
onClick={() => 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,
}}
>
<span style={{ flex: 1 }}>{layer.name}</span>
<span style={{ display: 'flex', gap: 4 }}>
<button
onClick={(e) => { e.stopPropagation(); toggleVisible(layer.name); }}
style={{
width: 24, height: 24, fontSize: 12,
background: layer.visible ? '#52c41a' : '#d9d9d9',
color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
}}
title={layer.visible ? 'Sichtbar' : 'Ausgeblendet'}
>
{layer.visible ? '👁' : ''}
</button>
<button
onClick={(e) => { e.stopPropagation(); toggleLocked(layer.name); }}
style={{
width: 24, height: 24, fontSize: 12,
background: layer.locked ? '#faad14' : '#d9d9d9',
color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
}}
title={layer.locked ? 'Gesperrt' : 'Entsperrt'}
>
{layer.locked ? '🔒' : '🔓'}
</button>
<button
onClick={(e) => { e.stopPropagation(); deleteLayer(layer.name); }}
style={{
width: 24, height: 24, fontSize: 12,
background: '#ff4d4f', color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
visibility: layers.length > 1 ? 'visible' : 'hidden',
}}
title="Löschen"
>
</button>
</span>
</div>
))}
<div style={{ display: 'flex', gap: 5, marginBottom: 10 }}>
<input
type="text"
placeholder="Layer filtern..."
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
style={{ flex: 1, padding: 4, borderRadius: 3, border: '1px solid #ccc' }}
/>
<button onClick={exportLayers} style={{ padding: '4px 8px', background: '#52c41a', color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer' }} title="Exportieren"></button>
<button onClick={handleImportClick} style={{ padding: '4px 8px', background: '#faad14', color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer' }} title="Importieren"></button>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept=".json"
style={{ display: 'none' }}
/>
</div>
<div style={{ flex: 1, overflowY: 'auto' }}>
{filteredLayers.map((layer) => (
<div
key={layer.id}
onClick={() => 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,
}}
>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8 }}>
<div
style={{
width: 16,
height: 16,
backgroundColor: layer.color || '#000000',
border: '1px solid #ccc',
borderRadius: 2
}}
/>
{editingLayerId === layer.id ? (
<input
type="text"
value={editingName}
onChange={(e) => 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' }}
/>
) : (
<span
onDoubleClick={() => startEditing(layer.id, layer.name)}
style={{ flex: 1 }}
>
{layer.name}
</span>
)}
</div>
<span style={{ display: 'flex', gap: 4 }}>
<select
value={layer.lineType || 'solid'}
onChange={(e) => setLineType(layer.id, e.target.value)}
onClick={(e) => e.stopPropagation()}
style={{
padding: '2px 4px',
borderRadius: 2,
border: '1px solid #ccc',
fontSize: 10,
background: 'white'
}}
>
<option value="solid"></option>
<option value="dashed"></option>
<option value="dotted"></option>
</select>
<button
onClick={(e) => { e.stopPropagation(); toggleVisible(layer.id); }}
style={{
width: 24, height: 24, fontSize: 12,
background: layer.visible ? '#52c41a' : '#d9d9d9',
color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
}}
title={layer.visible ? 'Sichtbar' : 'Ausgeblendet'}
>
{layer.visible ? '👁' : '—'}
</button>
<button
onClick={(e) => { e.stopPropagation(); toggleLocked(layer.id); }}
style={{
width: 24, height: 24, fontSize: 12,
background: layer.locked ? '#faad14' : '#d9d9d9',
color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
}}
title={layer.locked ? 'Gesperrt' : 'Entsperrt'}
>
{layer.locked ? '🔒' : '🔓'}
</button>
<button
onClick={(e) => { e.stopPropagation(); deleteLayer(layer.id); }}
style={{
width: 24, height: 24, fontSize: 12,
background: '#ff4d4f', color: 'white', border: 'none', borderRadius: 3, cursor: 'pointer',
visibility: layers.length > 1 ? 'visible' : 'hidden',
}}
title="Löschen"
>
</button>
</span>
</div>
))}
</div>
<div style={{ marginTop: 10, fontSize: 12, color: '#666', textAlign: 'center' }}>
Doppelklick auf Layername zum Umbenennen
</div>
</div>
);
};
export default LayerPanel;
export default LayerPanel;