fix: layer panel collapsible tree + element actions (visibility toggle, delete) + RenderEngine skips invisible elements
This commit is contained in:
@@ -331,6 +331,20 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
}
|
}
|
||||||
}, [layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab, activeLayerId]);
|
}, [layers, blocks, groups, bgConfig, syncHistory, drawingId, token, collab, activeLayerId]);
|
||||||
|
|
||||||
|
const handleToggleElementVisible = useCallback((id: string) => {
|
||||||
|
setElements((prev) => prev.map((e) => {
|
||||||
|
if (e.id === id) {
|
||||||
|
const newEl = { ...e, properties: { ...e.properties, visible: e.properties?.visible === false ? true : false } };
|
||||||
|
if (token) {
|
||||||
|
setSavedStatus("Speichert…");
|
||||||
|
updateElement(token, newEl.id, newEl).then(() => setSavedStatus("gespeichert")).catch(() => setSavedStatus("Fehler"));
|
||||||
|
}
|
||||||
|
return newEl;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}));
|
||||||
|
}, [token]);
|
||||||
|
|
||||||
const handleElementsDeleted = useCallback((ids: string[]) => {
|
const handleElementsDeleted = useCallback((ids: string[]) => {
|
||||||
setElements((prev) => {
|
setElements((prev) => {
|
||||||
const newElements = prev.filter((e) => !ids.includes(e.id));
|
const newElements = prev.filter((e) => !ids.includes(e.id));
|
||||||
@@ -980,6 +994,8 @@ const CADEditor: React.FC<CADEditorProps> = ({ projectId, token, onNavigateBack
|
|||||||
onRenameLayer={handleRenameLayer}
|
onRenameLayer={handleRenameLayer}
|
||||||
onDuplicateLayer={handleDuplicateLayer}
|
onDuplicateLayer={handleDuplicateLayer}
|
||||||
onToggleLock={handleToggleLock}
|
onToggleLock={handleToggleLock}
|
||||||
|
onElementsDeleted={handleElementsDeleted}
|
||||||
|
onToggleElementVisible={handleToggleElementVisible}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
onRenameBlock={handleRenameBlock}
|
onRenameBlock={handleRenameBlock}
|
||||||
onDuplicateBlock={handleDuplicateBlock}
|
onDuplicateBlock={handleDuplicateBlock}
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class RenderEngine {
|
|||||||
const layers = this.layerManager.getLayers();
|
const layers = this.layerManager.getLayers();
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
if (!layer.visible) continue;
|
if (!layer.visible) continue;
|
||||||
const els = visibleElements.filter(e => e.layerId === layer.id);
|
const els = visibleElements.filter(e => e.layerId === layer.id && e.properties?.visible !== false);
|
||||||
for (const el of els) {
|
for (const el of els) {
|
||||||
this.drawElement(el, layer);
|
this.drawElement(el, layer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import type { LayerPanelProps } from '../types/ui.types';
|
import type { LayerPanelProps } from '../types/ui.types';
|
||||||
import type { CADLayer, CADElement } from '../types/cad.types';
|
import type { CADLayer, CADElement } from '../types/cad.types';
|
||||||
import type { TreeNode } from '../types/ui.types';
|
import type { TreeNode } from '../types/ui.types';
|
||||||
@@ -67,6 +67,8 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
|||||||
onToggleLock,
|
onToggleLock,
|
||||||
onReorder,
|
onReorder,
|
||||||
onAddSubLayer,
|
onAddSubLayer,
|
||||||
|
onElementsDeleted,
|
||||||
|
onToggleElementVisible,
|
||||||
}) => {
|
}) => {
|
||||||
const buildTree = (parentId: string | null): TreeNode[] => {
|
const buildTree = (parentId: string | null): TreeNode[] => {
|
||||||
return layers
|
return layers
|
||||||
@@ -87,7 +89,7 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
|||||||
id: l.id,
|
id: l.id,
|
||||||
name: l.name,
|
name: l.name,
|
||||||
icon: layerIcon,
|
icon: layerIcon,
|
||||||
expanded: true,
|
expanded: false,
|
||||||
active: l.id === activeLayerId,
|
active: l.id === activeLayerId,
|
||||||
children: [...subLayerNodes, ...elementNodes],
|
children: [...subLayerNodes, ...elementNodes],
|
||||||
count: layerElements.length > 0 ? layerElements.length : undefined,
|
count: layerElements.length > 0 ? layerElements.length : undefined,
|
||||||
@@ -132,46 +134,70 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
|||||||
renderIcon={(node) => node.icon}
|
renderIcon={(node) => node.icon}
|
||||||
renderActions={(node) => {
|
renderActions={(node) => {
|
||||||
const layer = layers.find((l) => l.id === node.id);
|
const layer = layers.find((l) => l.id === node.id);
|
||||||
if (!layer) return null;
|
if (layer) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: '2px', opacity: 0.6 }}>
|
<div style={{ display: 'flex', gap: '2px', opacity: 0.6 }}>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); onToggleLayer(layer.id); }}
|
onClick={(e) => { e.stopPropagation(); onToggleLayer(layer.id); }}
|
||||||
title={layer.visible ? 'Sichtbar' : 'Versteckt'}
|
title={layer.visible ? 'Sichtbar' : 'Versteckt'}
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: layer.visible ? 'var(--text-color, #ccc)' : 'var(--text-muted, #666)' }}
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: layer.visible ? 'var(--text-color, #ccc)' : 'var(--text-muted, #666)' }}
|
||||||
>
|
>
|
||||||
{layer.visible ? '👁' : '🚫'}
|
{layer.visible ? '👁' : '🚫'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); onToggleLock?.(layer.id); }}
|
onClick={(e) => { e.stopPropagation(); onToggleLock?.(layer.id); }}
|
||||||
title={layer.locked ? 'Gesperrt' : 'Entsperrt'}
|
title={layer.locked ? 'Gesperrt' : 'Entsperrt'}
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: layer.locked ? 'var(--accent, #f59e0b)' : 'var(--text-muted, #666)' }}
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: layer.locked ? 'var(--accent, #f59e0b)' : 'var(--text-muted, #666)' }}
|
||||||
>
|
>
|
||||||
{layer.locked ? '🔒' : '🔓'}
|
{layer.locked ? '🔒' : '🔓'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); handleAddSubLayer(layer.id); }}
|
onClick={(e) => { e.stopPropagation(); handleAddSubLayer(layer.id); }}
|
||||||
title="Teilebene hinzufugen"
|
title="Teilebene hinzufugen"
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: 'var(--text-color, #ccc)' }}
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: 'var(--text-color, #ccc)' }}
|
||||||
>
|
>
|
||||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); onDuplicateLayer?.(layer.id); }}
|
onClick={(e) => { e.stopPropagation(); onDuplicateLayer?.(layer.id); }}
|
||||||
title="Duplizieren"
|
title="Duplizieren"
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: 'var(--text-color, #ccc)' }}
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: 'var(--text-color, #ccc)' }}
|
||||||
>
|
>
|
||||||
⧉
|
⧉
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); onDeleteLayer?.(layer.id); }}
|
onClick={(e) => { e.stopPropagation(); onDeleteLayer?.(layer.id); }}
|
||||||
title="Loschen"
|
title="Loschen"
|
||||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: '#f44336' }}
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: '#f44336' }}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
const element = elements.find((el) => el.id === node.id);
|
||||||
|
if (element) {
|
||||||
|
const isVisible = element.properties?.visible !== false;
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', gap: '2px', opacity: 0.6 }}>
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); onToggleElementVisible?.(element.id); }}
|
||||||
|
title={isVisible ? 'Sichtbar' : 'Versteckt'}
|
||||||
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: isVisible ? 'var(--text-color, #ccc)' : 'var(--text-muted, #666)' }}
|
||||||
|
>
|
||||||
|
{isVisible ? '👁' : '🚫'}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); onElementsDeleted?.([element.id]); }}
|
||||||
|
title="Loschen"
|
||||||
|
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: '#f44336' }}
|
||||||
|
>
|
||||||
|
✕
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
|
|||||||
activeLayerId, onSelectLayer, onAddLayer, onToggleLayer,
|
activeLayerId, onSelectLayer, onAddLayer, onToggleLayer,
|
||||||
onDeleteLayer, onRenameLayer, onDuplicateLayer, onToggleLock,
|
onDeleteLayer, onRenameLayer, onDuplicateLayer, onToggleLock,
|
||||||
onReorder, onAddSubLayer,
|
onReorder, onAddSubLayer,
|
||||||
|
onElementsDeleted, onToggleElementVisible,
|
||||||
elements,
|
elements,
|
||||||
onRenameBlock, onDuplicateBlock, onDeleteBlock, onSvgImport, onSaveGroupAsBlock,
|
onRenameBlock, onDuplicateBlock, onDeleteBlock, onSvgImport, onSaveGroupAsBlock,
|
||||||
onBlockCategoryChange, onBlockSearch, onDragBlock,
|
onBlockCategoryChange, onBlockSearch, onDragBlock,
|
||||||
@@ -57,7 +58,7 @@ const RightSidebar: React.FC<RightSidebarProps> = ({
|
|||||||
}} />}
|
}} />}
|
||||||
</div>
|
</div>
|
||||||
<div className={`rightbar-panel${activePanel === 'layer' ? ' active' : ''}`} data-panel-content="layer">
|
<div className={`rightbar-panel${activePanel === 'layer' ? ' active' : ''}`} data-panel-content="layer">
|
||||||
{activePanel === 'layer' && <LayerPanel layers={layers} elements={elements} activeLayerId={activeLayerId} onSelectLayer={onSelectLayer ?? (() => {})} onAddLayer={onAddLayer ?? (() => {})} onToggleLayer={onToggleLayer ?? (() => {})} onDeleteLayer={onDeleteLayer} onRenameLayer={onRenameLayer} onDuplicateLayer={onDuplicateLayer} onToggleLock={onToggleLock} onReorder={onReorder} onAddSubLayer={onAddSubLayer} />}
|
{activePanel === 'layer' && <LayerPanel layers={layers} elements={elements} onElementsDeleted={onElementsDeleted} onToggleElementVisible={onToggleElementVisible} activeLayerId={activeLayerId} onSelectLayer={onSelectLayer ?? (() => {})} onAddLayer={onAddLayer ?? (() => {})} onToggleLayer={onToggleLayer ?? (() => {})} onDeleteLayer={onDeleteLayer} onRenameLayer={onRenameLayer} onDuplicateLayer={onDuplicateLayer} onToggleLock={onToggleLock} onReorder={onReorder} onAddSubLayer={onAddSubLayer} />}
|
||||||
</div>
|
</div>
|
||||||
<div className={`rightbar-panel${activePanel === 'library' ? ' active' : ''}`} data-panel-content="library">
|
<div className={`rightbar-panel${activePanel === 'library' ? ' active' : ''}`} data-panel-content="library">
|
||||||
{activePanel === 'library' && <BlockLibrary blocks={blocks} category="Alle" onCategoryChange={onBlockCategoryChange ?? (() => {})} onSearch={onBlockSearch ?? (() => {})} onDragBlock={onDragBlock ?? (() => {})} onRenameBlock={onRenameBlock} onDuplicateBlock={onDuplicateBlock} onDeleteBlock={onDeleteBlock} onSvgImport={onSvgImport} onSaveGroupAsBlock={onSaveGroupAsBlock} />}
|
{activePanel === 'library' && <BlockLibrary blocks={blocks} category="Alle" onCategoryChange={onBlockCategoryChange ?? (() => {})} onSearch={onBlockSearch ?? (() => {})} onDragBlock={onDragBlock ?? (() => {})} onRenameBlock={onRenameBlock} onDuplicateBlock={onDuplicateBlock} onDeleteBlock={onDeleteBlock} onSvgImport={onSvgImport} onSaveGroupAsBlock={onSaveGroupAsBlock} />}
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ export interface RightSidebarProps {
|
|||||||
onUpdateLayerLineType?: (id: string, lineType: 'solid' | 'dashed' | 'dotted') => void;
|
onUpdateLayerLineType?: (id: string, lineType: 'solid' | 'dashed' | 'dotted') => void;
|
||||||
onUpdateLayerTransparency?: (id: string, transparency: number) => void;
|
onUpdateLayerTransparency?: (id: string, transparency: number) => void;
|
||||||
onAddSubLayer?: (parentId: string) => void;
|
onAddSubLayer?: (parentId: string) => void;
|
||||||
|
onElementsDeleted?: (ids: string[]) => void;
|
||||||
|
onToggleElementVisible?: (id: string) => void;
|
||||||
onRenameBlock?: (id: string, name: string) => void;
|
onRenameBlock?: (id: string, name: string) => void;
|
||||||
onDuplicateBlock?: (id: string) => void;
|
onDuplicateBlock?: (id: string) => void;
|
||||||
onDeleteBlock?: (id: string) => void;
|
onDeleteBlock?: (id: string) => void;
|
||||||
@@ -176,6 +178,8 @@ export interface LayerPanelProps {
|
|||||||
onUpdateLayerLineType?: (id: string, lineType: 'solid' | 'dashed' | 'dotted') => void;
|
onUpdateLayerLineType?: (id: string, lineType: 'solid' | 'dashed' | 'dotted') => void;
|
||||||
onUpdateLayerTransparency?: (id: string, transparency: number) => void;
|
onUpdateLayerTransparency?: (id: string, transparency: number) => void;
|
||||||
onAddSubLayer?: (parentId: string) => void;
|
onAddSubLayer?: (parentId: string) => void;
|
||||||
|
onElementsDeleted?: (ids: string[]) => void;
|
||||||
|
onToggleElementVisible?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockLibraryProps {
|
export interface BlockLibraryProps {
|
||||||
|
|||||||
Reference in New Issue
Block a user