merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes
This commit is contained in:
@@ -32,6 +32,17 @@ const layerIcon = (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const groupIcon = (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h7a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||||
<rect x="7" y="11" width="4" height="4" rx="0.5" />
|
||||
<rect x="13" y="11" width="4" height="4" rx="0.5" />
|
||||
<line x1="9" y1="15" x2="9" y2="17" />
|
||||
<line x1="15" y1="15" x2="15" y2="17" />
|
||||
<line x1="9" y1="17" x2="15" y2="17" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const elementIcons: Record<string, React.ReactNode> = {
|
||||
line: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="4" y1="20" x2="20" y2="4"/></svg>,
|
||||
rect: <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="4" y="4" width="16" height="16"/></svg>,
|
||||
@@ -58,7 +69,10 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
layers,
|
||||
elements = [],
|
||||
activeLayerId,
|
||||
selectedElementIds = [],
|
||||
groups = [],
|
||||
onSelectLayer,
|
||||
onSelectElement,
|
||||
onAddLayer,
|
||||
onToggleLayer,
|
||||
onDeleteLayer,
|
||||
@@ -76,22 +90,71 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
.sort((a, b) => a.sortOrder - b.sortOrder)
|
||||
.map((l) => {
|
||||
const layerElements = elements.filter((e) => e.layerId === l.id);
|
||||
const elementNodes: TreeNode[] = layerElements.map((e) => ({
|
||||
|
||||
// Build group nodes for this layer
|
||||
const layerGroups = groups.filter((g) =>
|
||||
g.elementIds.some((eid) => layerElements.some((e) => e.id === eid))
|
||||
);
|
||||
const groupNodes: TreeNode[] = layerGroups.map((g) => {
|
||||
const groupElementIds = g.elementIds.filter((eid) =>
|
||||
layerElements.some((e) => e.id === eid)
|
||||
);
|
||||
const groupElementsList = groupElementIds
|
||||
.map((eid) => elements.find((e) => e.id === eid))
|
||||
.filter((e): e is CADElement => e !== undefined);
|
||||
|
||||
const groupChildNodes: TreeNode[] = groupElementsList.map((e) => ({
|
||||
id: e.id,
|
||||
name: elementTypeLabels[e.type] || e.type,
|
||||
icon: elementIcons[e.type] || defaultIcon,
|
||||
expanded: false,
|
||||
active: selectedElementIds.includes(e.id),
|
||||
children: [],
|
||||
}));
|
||||
|
||||
// A group is "active" if any of its elements are selected
|
||||
const groupActive = groupElementIds.some((eid) => selectedElementIds.includes(eid));
|
||||
|
||||
return {
|
||||
id: g.id,
|
||||
name: g.name,
|
||||
icon: groupIcon,
|
||||
expanded: false,
|
||||
active: groupActive,
|
||||
children: groupChildNodes,
|
||||
count: groupElementIds.length > 0 ? groupElementIds.length : undefined,
|
||||
} as TreeNode;
|
||||
});
|
||||
|
||||
// Build element nodes — only elements NOT in any group go directly under layer
|
||||
const groupedElementIds = new Set<string>();
|
||||
for (const g of layerGroups) {
|
||||
for (const eid of g.elementIds) {
|
||||
if (layerElements.some((e) => e.id === eid)) {
|
||||
groupedElementIds.add(eid);
|
||||
}
|
||||
}
|
||||
}
|
||||
const ungroupedElements = layerElements.filter((e) => !groupedElementIds.has(e.id));
|
||||
const elementNodes: TreeNode[] = ungroupedElements.map((e) => ({
|
||||
id: e.id,
|
||||
name: elementTypeLabels[e.type] || e.type,
|
||||
icon: elementIcons[e.type] || defaultIcon,
|
||||
expanded: false,
|
||||
active: false,
|
||||
active: selectedElementIds.includes(e.id),
|
||||
children: [],
|
||||
}));
|
||||
|
||||
const subLayerNodes = buildTree(l.id);
|
||||
|
||||
// Groups appear above elements, sublayers appear first
|
||||
return {
|
||||
id: l.id,
|
||||
name: l.name,
|
||||
icon: layerIcon,
|
||||
expanded: false,
|
||||
active: l.id === activeLayerId,
|
||||
children: [...subLayerNodes, ...elementNodes],
|
||||
children: [...subLayerNodes, ...groupNodes, ...elementNodes],
|
||||
count: layerElements.length > 0 ? layerElements.length : undefined,
|
||||
} as TreeNode;
|
||||
});
|
||||
@@ -99,6 +162,24 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
|
||||
const tree = buildTree(null);
|
||||
|
||||
// Determine which IDs are layer IDs vs element IDs vs group IDs for dispatch
|
||||
const layerIds = new Set(layers.map((l) => l.id));
|
||||
const groupIds = new Set(groups.map((g) => g.id));
|
||||
|
||||
const handleSelect = (id: string) => {
|
||||
if (layerIds.has(id)) {
|
||||
onSelectLayer(id);
|
||||
} else if (groupIds.has(id)) {
|
||||
const group = groups.find((g) => g.id === id);
|
||||
if (group && group.elementIds.length > 0) {
|
||||
onSelectElement?.(group.elementIds[0]);
|
||||
}
|
||||
} else {
|
||||
// It's an element ID
|
||||
onSelectElement?.(id);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddSubLayer = (parentId: string) => {
|
||||
if (onAddSubLayer) {
|
||||
onAddSubLayer(parentId);
|
||||
@@ -110,8 +191,10 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px 12px', borderBottom: '1px solid var(--border-color, #333)' }}>
|
||||
<span style={{ fontWeight: 600, fontSize: '13px' }}>Ebenen</span>
|
||||
<button
|
||||
className="add-layer-btn"
|
||||
onClick={onAddLayer}
|
||||
title="Neue Ebene"
|
||||
aria-label="Neue Ebene"
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-color, #ccc)', padding: '4px' }}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
@@ -129,7 +212,8 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
<TreeView
|
||||
nodes={tree}
|
||||
selectedId={activeLayerId}
|
||||
onSelect={onSelectLayer}
|
||||
selectedIds={selectedElementIds}
|
||||
onSelect={handleSelect}
|
||||
onReorder={onReorder}
|
||||
draggable={true}
|
||||
renderIcon={(node) => node.icon}
|
||||
@@ -141,6 +225,7 @@ const LayerPanel: React.FC<LayerPanelProps> = ({
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onToggleLayer(layer.id); }}
|
||||
title={layer.visible ? 'Sichtbar' : 'Versteckt'}
|
||||
aria-label={layer.visible ? 'Verstecken' : 'Anzeigen'}
|
||||
style={{ background: 'none', border: 'none', cursor: 'pointer', padding: '2px', color: layer.visible ? 'var(--text-color, #ccc)' : 'var(--text-muted, #666)', display: 'flex', alignItems: 'center' }}
|
||||
>
|
||||
{layer.visible ? <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg> : <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>}
|
||||
|
||||
Reference in New Issue
Block a user