merge: combine all features from both codebases - mobile dashboard + layer panel + notifications + shares + all fixes

This commit is contained in:
Leopoldadmin
2026-07-04 16:43:55 +02:00
parent 0606dbb501
commit be62470b53
79 changed files with 9562 additions and 3132 deletions
+89 -4
View File
@@ -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>}