feat: add collapsible/expandable zones for canvas decluttering
- Add collapsed state to NodeData.custom_colors (type=groupRect only) - Implement toggleNodeCollapsed action in canvasStore (Zustand) - Extend GroupRectNode UI with smooth chevron toggle button - Rotating chevron icon (↓ → when collapsed) - Shows '+N' badge when zone is hidden - Reduces zone opacity to 0.6 when collapsed - All transitions target 60 FPS (ease-out 200ms) - Filter child nodes/edges in CanvasContainer based on parent collapse state - Breadth-first traversal handles multi-level nesting - Connecting edges to hidden nodes are automatically hidden - Add comprehensive test coverage - Store: toggleNodeCollapsed state mutation, unsaved flag - Component: chevron rendering, click handlers, opacity transitions - Persist collapsed state via YAML serialization (part of custom_colors) Benefits: - Declutter large Zigbee meshes, multi-building networks - Preserve layout structure without deleting nodes - Smooth 60 FPS transitions for UX polish CONTRIBUTING.md compliance: - Strict TypeScript types, no 'any' - Zustand store pattern, no prop drilling - Tests for store logic and component behavior - Frontend linting requirements met Co-authored-by: CyberClaw <noreply@openclaw.ai>
This commit is contained in:
@@ -55,6 +55,25 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
|
||||
const activeTheme = useThemeStore((s) => s.activeTheme)
|
||||
const theme = THEMES[activeTheme]
|
||||
|
||||
// Filter nodes and edges based on collapsed state
|
||||
const getVisibleNodeIds = (): Set<string> => {
|
||||
const visible = new Set<string>()
|
||||
const queue = nodes.filter((n) => !n.parentId).map((n) => n.id)
|
||||
while (queue.length > 0) {
|
||||
const id = queue.shift()!
|
||||
visible.add(id)
|
||||
const node = nodes.find((n) => n.id === id)
|
||||
if (node && !node.data.custom_colors?.collapsed) {
|
||||
const children = nodes.filter((n) => n.parentId === id).map((n) => n.id)
|
||||
queue.push(...children)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
const visibleNodeIds = getVisibleNodeIds()
|
||||
const visibleNodes = nodes.filter((n) => visibleNodeIds.has(n.id))
|
||||
const visibleEdges = edges.filter((e) => visibleNodeIds.has(e.source) && visibleNodeIds.has(e.target))
|
||||
|
||||
const onNodeClick = useCallback((e: React.MouseEvent, node: Node<NodeData>) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
setSelectedNode(null)
|
||||
@@ -90,8 +109,8 @@ export function CanvasContainer({ onConnect: onConnectProp, onEdgeDoubleClick, o
|
||||
return (
|
||||
<div className="w-full h-full" style={{ background: theme.colors.canvasBackground }}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
nodes={visibleNodes}
|
||||
edges={visibleEdges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnectProp}
|
||||
|
||||
Reference in New Issue
Block a user