feat: delete function, sidebar collapse, layer drag&drop, library tree

- Delete: trash button in PropertiesPanel + existing Delete key support
- Left sidebar: desktop collapse toggle button
- Right sidebar: desktop collapse toggle + SVG icon size fix (replaced emoji)
- Layer panel: drag&drop enabled (reorder, move to layers, sub-layers)
- Library tree: BlockLibrary added to left sidebar
- App.tsx: handleReorderLayer, handleAddSubLayer, handleUpdateElement
- styles.css: collapse + drag&drop visual indicators
This commit is contained in:
2026-06-29 10:44:53 +02:00
parent 6b0b637636
commit ba978cd13c
7 changed files with 222 additions and 21 deletions
+103 -1
View File
@@ -147,6 +147,9 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
// Mobile drawers
const [mobileLeftOpen, setMobileLeftOpen] = useState(false);
const [mobileRightOpen, setMobileRightOpen] = useState(false);
// Desktop sidebar collapse
const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false);
const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false);
const [settingsOpen, setSettingsOpen] = useState(false);
const [activeDrawerTab, setActiveDrawerTab] = useState<DrawerTab>('tool');
@@ -792,6 +795,89 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Zoom: an Ansicht angepasst', type: 'info' }]);
}, []);
// Layer reorder (drag & drop)
const handleReorderLayer = useCallback((draggedId: string, targetId: string, position: 'before' | 'after' | 'inside') => {
setLayers((prev) => {
const dragged = prev.find((l) => l.id === draggedId);
if (!dragged) return prev;
if (position === 'inside') {
// Make dragged a child of target
const updated = prev.map((l) =>
l.id === draggedId ? { ...l, parentId: targetId } : l
);
return updated;
}
// before/after: reorder at same level
const target = prev.find((l) => l.id === targetId);
if (!target) return prev;
const parentId = target.parentId;
const siblings = prev
.filter((l) => l.parentId === parentId && l.id !== draggedId)
.sort((a, b) => a.sortOrder - b.sortOrder);
const targetIdx = siblings.findIndex((l) => l.id === targetId);
const insertIdx = position === 'before' ? targetIdx : targetIdx + 1;
siblings.splice(insertIdx, 0, { ...dragged, parentId });
// Reassign sortOrder
const reordered = siblings.map((l, i) => ({ ...l, sortOrder: i }));
// Merge back: non-siblings keep original, siblings get reordered
const nonSiblings = prev.filter((l) => l.parentId !== parentId || l.id === draggedId);
const result = [...nonSiblings.filter((l) => l.id !== draggedId), ...reordered];
return result;
});
setCommandHistory((prev) => [...prev, { prefix: '·', text: 'Ebene verschoben', type: 'info' }]);
}, []);
// Add sub-layer
const handleAddSubLayer = useCallback((parentId: string) => {
setLayers((prev) => {
const parent = prev.find((l) => l.id === parentId);
if (!parent) return prev;
const newId = `layer-${Date.now()}`;
const siblings = prev.filter((l) => l.parentId === parentId);
const newLayer: CADLayer = {
id: newId, name: `Teilebene ${siblings.length + 1}`, visible: true, locked: false,
color: parent.color, lineType: 'solid', transparency: 0,
sortOrder: siblings.length, parentId,
};
if (drawingId && token) {
createLayerTyped(token, drawingId, newLayer).catch((err) => {
console.error('Failed to save sub-layer:', err);
});
}
return [...prev, newLayer];
});
}, [drawingId, token]);
// Update single element (from properties panel)
const handleUpdateElement = useCallback((updated: CADElement) => {
setElements((prev) => {
const newElements = prev.map((e) => (e.id === updated.id ? updated : e));
historyManagerRef.current.pushSnapshot({
elements: newElements, layers, blocks, groups, bgConfig,
}, 'Element geändert');
return newElements;
});
syncHistory();
collab.setElement(updated);
if (token) {
setSavedStatus('Speichert…');
updateElement(token, updated.id, updated).then(() => {
setSavedStatus('gespeichert');
}).catch((err) => {
console.error('Failed to update element:', err);
setSavedStatus('Fehler beim Speichern');
});
}
setSelectedElement(updated);
}, [layers, blocks, groups, bgConfig, syncHistory, token, collab]);
const handleBgApply = useCallback((config: BackgroundConfig, _image: HTMLImageElement | null) => {
setBgConfig(config);
setBgImportOpen(false);
@@ -950,7 +1036,7 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onTabChange={setActiveRibbonTab}
onAction={handleRibbonAction}
/>
<div className="app-body">
<div className={`app-body${leftSidebarCollapsed ? ' left-collapsed' : ''}${rightSidebarCollapsed ? ' right-collapsed' : ''}`}>
<LeftSidebar
activeTool={activeTool}
onToolChange={handleToolChange}
@@ -958,6 +1044,17 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
onTemplateSelect={handleTemplateSelect}
className={mobileLeftOpen ? 'open' : ''}
onCollapse={() => setMobileLeftOpen(false)}
collapsed={leftSidebarCollapsed}
onToggleCollapse={() => setLeftSidebarCollapsed((p) => !p)}
blocks={blocks}
onDragBlock={handleDragBlock}
onBlockCategoryChange={handleBlockCategoryChange}
onBlockSearch={handleBlockSearch}
onRenameBlock={handleRenameBlock}
onDuplicateBlock={handleDuplicateBlock}
onDeleteBlock={handleDeleteBlock}
onSvgImport={handleSvgImport}
onSaveGroupAsBlock={handleSaveGroupAsBlock}
/>
<CanvasArea
cursorPos={cursorPos}
@@ -1022,6 +1119,11 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
kiLoading={kiLoading}
className={mobileRightOpen ? 'open' : ''}
onCollapse={() => setMobileRightOpen(false)}
collapsed={rightSidebarCollapsed}
onToggleCollapse={() => setRightSidebarCollapsed((p) => !p)}
onReorder={handleReorderLayer}
onAddSubLayer={handleAddSubLayer}
onUpdateElement={handleUpdateElement}
/>
</div>
<CommandLine