From ba978cd13c65ad64dae505d1a88f6851bb242a4b Mon Sep 17 00:00:00 2001 From: Leopoldadmin Date: Mon, 29 Jun 2026 10:44:53 +0200 Subject: [PATCH] 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 --- frontend/src/App.tsx | 104 +++++++++++++++++++- frontend/src/components/LayerPanel.tsx | 25 ++--- frontend/src/components/LeftSidebar.tsx | 28 +++++- frontend/src/components/PropertiesPanel.tsx | 20 +++- frontend/src/components/RightSidebar.tsx | 11 ++- frontend/src/styles.css | 40 ++++++++ frontend/src/types/ui.types.ts | 15 +++ 7 files changed, 222 insertions(+), 21 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2b77f01..02f2376 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -147,6 +147,9 @@ const CADEditor: React.FC = ({ 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('tool'); @@ -792,6 +795,89 @@ const CADEditor: React.FC = ({ 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 = ({ projectId, token, onNavigateBack onTabChange={setActiveRibbonTab} onAction={handleRibbonAction} /> -
+
= ({ 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} /> = ({ projectId, token, onNavigateBack kiLoading={kiLoading} className={mobileRightOpen ? 'open' : ''} onCollapse={() => setMobileRightOpen(false)} + collapsed={rightSidebarCollapsed} + onToggleCollapse={() => setRightSidebarCollapsed((p) => !p)} + onReorder={handleReorderLayer} + onAddSubLayer={handleAddSubLayer} + onUpdateElement={handleUpdateElement} />
= ({ selectedId={activeLayerId} onSelect={onSelectLayer} onReorder={onReorder} + draggable={true} renderIcon={(node) => node.icon} renderActions={(node) => { const layer = layers.find((l) => l.id === node.id); @@ -140,16 +141,16 @@ const LayerPanel: React.FC = ({
); @@ -183,16 +184,16 @@ const LayerPanel: React.FC = ({ ); diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx index 4963703..2a1428d 100644 --- a/frontend/src/components/LeftSidebar.tsx +++ b/frontend/src/components/LeftSidebar.tsx @@ -1,6 +1,7 @@ import React from 'react'; import type { LeftSidebarProps } from '../types/ui.types'; import { SEATING_TEMPLATES } from '../services/seatingService'; +import BlockLibrary from './BlockLibrary'; interface ToolDef { tool: string; @@ -56,9 +57,9 @@ const sections: Array<{ label: string; tools: ToolDef[] }> = [ { label: 'Bestuhlung', tools: sectionBestuhlung }, ]; -const LeftSidebar: React.FC = ({ activeTool, onToolChange, selectedTemplate, onTemplateSelect, onCollapse, className }) => { +const LeftSidebar: React.FC = ({ activeTool, onToolChange, selectedTemplate, onTemplateSelect, onCollapse, className, collapsed, onToggleCollapse, blocks, onDragBlock, onBlockCategoryChange, onBlockSearch, onRenameBlock, onDuplicateBlock, onDeleteBlock, onSvgImport, onSaveGroupAsBlock }) => { return ( -