mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 15:40:10 +00:00
[docs] Add Vue node system architecture and implementation plans
- Implementation plan for Vue-based node rendering system - Migration strategy from canvas to Vue components - Widget system integration documentation
This commit is contained in:
401
VUE_NODE_MIGRATION_PLAN.md
Normal file
401
VUE_NODE_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,401 @@
|
||||
# Vue Node Migration Plan
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This plan outlines a phased migration from LiteGraph canvas rendering to Vue-based DOM rendering for ComfyUI nodes. Vue-based nodes allows access to component libraries (PrimeVue) and CSS frameworks, which increases iteration speed significantly. The migration preserves LiteGraph as the source of truth for graph logic while leveraging Vue components for rich, accessible node interfaces. Increased development speed will facilitate next gen UI/UX changes: .
|
||||
|
||||
## Goals and Objectives
|
||||
|
||||
### Primary Goals
|
||||
|
||||
- Enable rapid UI iteration with Vue's component model, PrimeVue, and CSS frameworks
|
||||
- Maintain performance for workflows with 100+ nodes
|
||||
- Preserve extension compatibility (90%+ without changes)
|
||||
- Improve developer experience
|
||||
|
||||
### Success Metrics
|
||||
|
||||
- New components implementable in <1 hour (vs. current DOM manipulation)
|
||||
- Performance regression <25% for 100-node workflows
|
||||
- 90% of existing extensions work unmodified
|
||||
- 3x faster UI development iteration
|
||||
- Memory usage within 1.5x of canvas-only approach
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- Complete canvas replacement (connections remain canvas-rendered)
|
||||
- Mobile/touch optimization (separate initiative)
|
||||
- Workflow format changes (must remain compatible)
|
||||
- Extension API redesign (compatibility layer only)
|
||||
|
||||
### Definition of Done
|
||||
|
||||
- All core node types render via Vue components
|
||||
- Canvas handles only connections and viewport
|
||||
- Performance benchmarks meet targets
|
||||
- Extension compatibility layer tested with top 20 extensions
|
||||
- Migration guide published for extension developers whose extensions are broken by the migration
|
||||
- Feature flag allows instant rollback
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Current State
|
||||
|
||||
LiteGraph with mixed rendering approaches:
|
||||
|
||||
- **Canvas**: node bodies, connections, grid, selection
|
||||
- **Widgets**: Three types coexist:
|
||||
- Canvas widgets (drawn in 2D context)
|
||||
- DOM widgets (manually positioned HTML elements)
|
||||
- Vue widgets (components positioned via DOM)
|
||||
- **Events**: Canvas handles most interactions, delegates to widgets
|
||||
- **State**: Stored in LiteGraph node/graph objects
|
||||
|
||||
### Target State
|
||||
|
||||
Hybrid rendering with clear separation:
|
||||
|
||||
- **Canvas**: connections, grid, viewport pan/zoom, selection rectangles
|
||||
- **DOM (Vue)**: all node contents and widgets unified as Vue components
|
||||
- **Transform Pane**: single Vue-managed container synchronized with canvas transforms
|
||||
- **State**: LiteGraph remains source of truth, Vue observes changes
|
||||
|
||||
### Hybrid Approach
|
||||
|
||||
During migration, both systems coexist:
|
||||
|
||||
- Feature flag controls Vue rendering per node type
|
||||
- Canvas nodes and Vue nodes can connect normally
|
||||
- Shared event system handles both rendering modes
|
||||
- Progressive migration allows testing at each phase
|
||||
|
||||
## Technical Design
|
||||
|
||||
### Component Architecture
|
||||
|
||||
```
|
||||
GraphCanvas.vue
|
||||
└── TransformPane.vue (synchronized with canvas transforms)
|
||||
└── LGraphNode.vue (v-for visible nodes)
|
||||
├── NodeHeader.vue (title, controls)
|
||||
├── NodeSlots.vue (input/output connection points)
|
||||
│ ├── InputSlot.vue (connection target)
|
||||
│ └── OutputSlot.vue (connection source)
|
||||
├── NodeWidgets.vue (parameter controls)
|
||||
│ └── [Widget components rendered here]
|
||||
│ ├── NumberWidget.vue
|
||||
│ ├── StringWidget.vue
|
||||
│ ├── ComboWidget.vue
|
||||
│ └── [etc...]
|
||||
└── NodeContent.vue (custom content area)
|
||||
```
|
||||
|
||||
Slots = connection points for node edges
|
||||
Widgets = UI controls for node parameters
|
||||
|
||||
### State Management
|
||||
|
||||
- **One-way data flow**: LiteGraph → Vue components (props down, events up)
|
||||
- Widget values flow from LiteGraph to Vue as props
|
||||
- User interactions emit events that update LiteGraph
|
||||
- Updated values flow back to Vue, completing the cycle
|
||||
- **LiteGraph as source of truth**: All node/graph state remains in LiteGraph
|
||||
- **Vue as view layer**: Components observe and reflect LiteGraph state
|
||||
|
||||
### Event System
|
||||
|
||||
- **Canvas events**: Pan, zoom, connection dragging, box selection
|
||||
- **DOM events**: Node interactions, widget inputs, context menus
|
||||
- **Transform sync**: No coordinate mapping needed - transforms handle positioning
|
||||
- **(Future) Event delegation**: Single listener on TransformPane for efficiency
|
||||
- **(Future) Touch handling**: Unified pointer events for mouse/touch consistency
|
||||
|
||||
### Positioning Strategy: CSS Transforms
|
||||
|
||||
For positioning nodes in the DOM, we'll use CSS transforms rather than absolute positioning with top/left. This decision is based on significant performance benefits validated by industry leaders (React Flow, Excalidraw, tldraw, Figma).
|
||||
|
||||
#### Core Implementation
|
||||
|
||||
```vue
|
||||
<!-- TransformPane synchronized with canvas -->
|
||||
<div class="transform-pane" :style="{ transform: `scale(${zoom}) translate(${panX}px, ${panY}px)` }">
|
||||
<!-- Individual nodes use simple translate -->
|
||||
<div v-for="node in visibleNodes"
|
||||
:style="{ transform: `translate(${node.x}px, ${node.y}px)` }">
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Key Optimizations
|
||||
|
||||
- **CSS Containment**: `contain: layout style paint` isolates node rendering
|
||||
- **GPU Acceleration**: `will-change: transform` during interactions only
|
||||
- **Batched Updates**: CSS custom properties for efficient updates
|
||||
|
||||
#### Handling PrimeVue Overlays
|
||||
|
||||
Portal strategy for components with fixed positioning:
|
||||
|
||||
```vue
|
||||
<Teleport to="body" v-if="showOverlay">
|
||||
<Popover :style="{ position: 'fixed', left: `${coords.x}px`, top: `${coords.y}px` }">
|
||||
</Teleport>
|
||||
```
|
||||
|
||||
#### Alternative Approaches
|
||||
|
||||
| Approach | Performance | Complexity | Use Case |
|
||||
|----------|------------|------------|-----------|
|
||||
| **CSS Transforms** | Excellent (GPU) | Medium | ✅ Our choice |
|
||||
| **Absolute Position** | Poor (reflow) | Low | Small node counts |
|
||||
| **Canvas Rendering** | Best | High | Not compatible with Vue |
|
||||
| **SVG** | Good | Medium | Better for connections |
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Phase 1: Widget Migration
|
||||
|
||||
For each widget:
|
||||
|
||||
- Create a new Vue component for the widget, using the API defined here: https://www.notion.so/drip-art/Widget-Componet-APIs-2126d73d365080b0bf30f241c09dd756
|
||||
- If the widget existed before, alias the constructor to the new component (q: why not just replace entirely? any reason to keep the old constructor?)
|
||||
- If the widget is new, create a new constructor for the widget and add to widgets.ts
|
||||
- Implement the existing widget interface in the new component (i.e., create Vue-compatible mappings of the LG widget's props and events)
|
||||
- Avoid components that use things like fixed positioning, teleport, `fill-available`, in the widget component (e.g., PrimeVue's Popover, Tooltip, Select) as they will require a portal strategy to work with transforms
|
||||
|
||||
### Phase 2: Node Migration
|
||||
|
||||
- Create a new Vue component for the node
|
||||
- [maybe later] Create conditional render for LOD, distance cull, and viewport cull
|
||||
- Implement the existing node interface in the new node component (i.e., create Vue-compatible mappings of the LG node's props and events)
|
||||
|
||||
### Phase 3: Transform Pane
|
||||
|
||||
- Create the transform pane
|
||||
- Synchronize the transform pane with the canvas transforms
|
||||
- Use `transform-origin` to position the transform pane in accordance with the canvas
|
||||
- Use `will-change: transform` and verify with DevTools that nodes are on a single layer and not being promoted
|
||||
- NOTE: in future, we need to actively prevent layer promotion (see promotion conditions: https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/platform/graphics/compositing_reasons.h;l=18;drc=4e8e81f6eeb6969973f3ec97132d80339b92d227)
|
||||
|
||||
### Phase 4: Interaction System
|
||||
|
||||
- Map all existing events from previous interface
|
||||
- Map all lifecycle hooks
|
||||
- Add event delegation for transform pane events
|
||||
- Restrict touch events to the transform pane
|
||||
- For any event (except those affecting the entire transform pane) that affects compositing (e.g., moving nodes, resizing nodes), batch in RAF
|
||||
|
||||
### Phase 5: Portals
|
||||
|
||||
- Create a portal component that can be used to render components that use fixed positioning, teleport, `fill-available`, etc.
|
||||
|
||||
### Phase 6: Performance Optimizations
|
||||
|
||||
- Create baseline performance metrics
|
||||
- For each optimization, test and iterate while comparing against baseline:
|
||||
- Implement viewport culling or LOD
|
||||
- It may be less efficient to cull nodes if we are only doing compositing and not actually recalc and reflow. This must be manually verified. In any case, there is still probably a very large threshold to implement viewport culling by, but it may be the case that it's not worth the effort to do.
|
||||
- Implement distance culling and LOD
|
||||
- (optional) Implement virtualization, prefetching, prerendering, preloading, preconnecting if still necessary
|
||||
|
||||
### Phase 7: Extension Migration
|
||||
|
||||
- Review all of the most common touch points from extension space
|
||||
- Determine if any compatibility mappings are still needed, then implement them and add to public API
|
||||
- Gradually test with more and more frontend extensions
|
||||
|
||||
## Performance
|
||||
|
||||
### Benchmarking Strategy
|
||||
|
||||
- Use existing performance wrapper playwright testing strategy created in https://www.notion.so/drip-art/Analyze-Performance-Impact-of-only-using-Vue-widgets-20b6d73d36508080a14cea0b8dce7073?source=copy_link#20d6d73d365080409a8ccc68f501284e
|
||||
- Or, a subset of it
|
||||
|
||||
### Optimization Techniques
|
||||
|
||||
- Use `will-change: transform` and `transform: translateZ(0)` to force GPU acceleration
|
||||
- Use `contain: layout style paint` to tell the browser this element won't affect outside layout
|
||||
- Use `transform-origin` to position the transform pane in accordance with the canvas
|
||||
- ~~Use `transform: translate3d(round(var(--x), 1px), round(var(--y), 1px), 0)` to snap to pixels during non-animated states~~
|
||||
|
||||
### Scaling Targets
|
||||
|
||||
- 256 nodes full LOD
|
||||
- 1000 nodes culled
|
||||
|
||||
### Production Monitoring
|
||||
|
||||
For desktop users who have consented to telemetry:
|
||||
|
||||
- **Mixpanel Events**: Track migration feature adoption and performance metrics
|
||||
- Node rendering time percentiles (p50, p90, p99)
|
||||
- Frame rate during node interactions
|
||||
- Memory usage with different node counts
|
||||
- **Sentry Performance**: Monitor real-world performance regressions
|
||||
- Transaction traces for node operations
|
||||
- Custom performance marks for render phases
|
||||
- Error rates specific to Vue node rendering
|
||||
|
||||
## Extension Compatibility Plan
|
||||
|
||||
### Migration Guide
|
||||
|
||||
Comprehensive migration documentation will be published at https://docs.comfy.org including:
|
||||
|
||||
- Step-by-step migration instructions for common patterns
|
||||
- Code examples for converting canvas widgets to Vue components
|
||||
- API compatibility reference
|
||||
- Performance optimization guidelines
|
||||
|
||||
### Compatibility Layer
|
||||
|
||||
[Placeholder: Supporting both rendering modes]
|
||||
|
||||
### Deprecation Timeline
|
||||
|
||||
[Placeholder: List things that will be deprecated fully]
|
||||
|
||||
### Developer Communication
|
||||
|
||||
- **Email notifications** via existing developer mailing list
|
||||
- **Discord announcements** in dedicated devrel channel
|
||||
- **Automated PRs** to affected repositories (if breaking changes required)
|
||||
|
||||
## Testing
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
#### Component Tests
|
||||
|
||||
- Create component tests for each widget and node
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
- Create integration tests for canvas and transform pane synchronization
|
||||
|
||||
#### Performance Tests
|
||||
|
||||
maybe
|
||||
|
||||
### Migration of Existing Test Suites
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
[Placeholder: strategy for migrating unit tests]
|
||||
|
||||
|
||||
#### Browser Tests
|
||||
|
||||
[Placeholder: strategy for migrating browser tests]
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### Technical Risks
|
||||
|
||||
#### Performance Degradation
|
||||
|
||||
- **Risk**: DOM nodes significantly slower than canvas rendering
|
||||
- **Mitigation**: Aggressive viewport culling, CSS containment, GPU acceleration
|
||||
- **Monitoring**: Automated performance benchmarks on each PR
|
||||
|
||||
#### Memory Usage
|
||||
|
||||
- **Risk**: 1000+ DOM nodes consume excessive memory
|
||||
- **Mitigation**: Component pooling, virtualization for large workflows
|
||||
- **Detection**: Memory profiling in browser tests
|
||||
|
||||
#### Extension Breaking Changes
|
||||
|
||||
- **Risk**: Popular extensions stop working
|
||||
- **Mitigation**: Compatibility layer maintaining critical APIs
|
||||
- **Testing**: Top 20 extensions tested before each phase
|
||||
|
||||
#### State Synchronization Bugs
|
||||
|
||||
- **Risk**: LiteGraph and Vue state diverge
|
||||
- **Mitigation**: Strict one-way data flow, comprehensive event testing
|
||||
- **Prevention**: State invariant checks in development mode
|
||||
|
||||
### Rollback Plan
|
||||
|
||||
1. **Feature flag**: `enable_vue_nodes` setting (default: false)
|
||||
2. **Gradual rollout**: Enable for specific node types first
|
||||
3. **Quick revert**: Single flag disables all Vue rendering
|
||||
4. **Data compatibility**: No changes to workflow format ensures backward compatibility
|
||||
|
||||
## Timeline and Milestones
|
||||
|
||||
### Week 1
|
||||
|
||||
[Placeholder: Initial milestones]
|
||||
|
||||
### Week 2
|
||||
|
||||
[Placeholder: Mid-term milestones]
|
||||
|
||||
### Week 3
|
||||
|
||||
[Placeholder: Final milestones]
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Widget Constructor Aliasing
|
||||
|
||||
- Why keep old constructors vs. full replacement? (Phase 1, line 53)
|
||||
- Is this for backwards compatibility with existing extensions?
|
||||
|
||||
### Viewport Culling Efficiency
|
||||
|
||||
- At what node count does viewport culling become beneficial? (Phase 6, line 89)
|
||||
- Does the compositing-only benefit outweigh the Vue mount/unmount cost?
|
||||
|
||||
### Extension Compatibility
|
||||
|
||||
- How much of extension surface area to attempt to cover in compatibility layer?
|
||||
- Which extension APIs are most critical to preserve?
|
||||
|
||||
### Transform Pane Synchronization
|
||||
|
||||
- How to handle canvas zoom/pan events?
|
||||
- Should transform sync be RAF-batched or immediate?
|
||||
|
||||
### Event Delegation
|
||||
|
||||
- Which events stay on canvas vs. move to DOM?
|
||||
- **Option A**: Canvas handles all drag/pan, DOM handles clicks/inputs
|
||||
- **Option B**: DOM handles everything except connection dragging
|
||||
- *Note: This option provides the best UX - users expect DOM elements to be fully interactive while keeping complex connection logic in canvas*
|
||||
- **Option C**: Context-aware delegation based on interaction type
|
||||
|
||||
### LOD (Level of Detail) System
|
||||
|
||||
- What defines each LOD level?
|
||||
- **High**: Full widgets and styling
|
||||
- **Medium**: Simplified widgets, reduced effects?
|
||||
- **Low**: Title and connections only?
|
||||
- Transition triggers: zoom level, node count, or performance metrics?
|
||||
- *Note: Zoom level as primary trigger is most predictable for users, with node count as override for performance protection*
|
||||
|
||||
### Interaction System Migration
|
||||
|
||||
- How to maintain gesture consistency between canvas and DOM nodes?
|
||||
- Multi-select behavior across rendering boundaries?
|
||||
|
||||
## Appendices
|
||||
|
||||
### A. Prototype Learnings
|
||||
|
||||
Detailed findings and discoveries from process of developing Vue widgets and Vue nodes prototype (in vue-node-test branch)
|
||||
|
||||
- Constructing components easy, difficulty is performance and compatibility
|
||||
|
||||
### B. Performance and Browser Rendering
|
||||
|
||||
- https://www.notion.so/drip-art/Analyze-Performance-Impact-of-only-using-Vue-widgets-20b6d73d36508080a14cea0b8dce7073?source=copy_link#20d6d73d365080409a8ccc68f501284e
|
||||
- https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/blink/renderer/platform/graphics/compositing_reasons.h;l=18;drc=4e8e81f6eeb6969973f3ec97132d80339b92d227
|
||||
- https://webperf.tips/tip/browser-rendering-pipeline/
|
||||
- https://webperf.tips/tip/layers-and-compositing/
|
||||
- https://webperf.tips/tip/layout-thrashing/
|
||||
|
||||
### C. API Design
|
||||
|
||||
- https://www.notion.so/drip-art/Widget-Componet-APIs-2126d73d365080b0bf30f241c09dd756
|
||||
287
vue-node-implementation-plan.md
Normal file
287
vue-node-implementation-plan.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Vue Node Component Implementation Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This plan outlines the implementation of Vue node components that will integrate with the existing LiteGraph system. These components are designed to work within a future transform/sync system while focusing purely on the component architecture.
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. **LGraphNode.vue** - Main Node Container
|
||||
- Receives LGraphNode as prop
|
||||
- Renders node layout: header, slots, widgets, content
|
||||
- Positioned absolutely using node.pos
|
||||
- CSS containment for performance
|
||||
- Integrates all sub-components
|
||||
|
||||
### 2. **NodeHeader.vue** - Node Title & Controls
|
||||
- Node title display
|
||||
- Collapse/expand button
|
||||
- Node color/styling based on type
|
||||
- Designed for future drag handle integration
|
||||
|
||||
### 3. **NodeSlots.vue** - Connection Points Container
|
||||
- Renders input/output slot visual indicators
|
||||
- Shows slot names and types
|
||||
- Visual-only - LiteGraph handles actual connections
|
||||
- Contains InputSlot and OutputSlot sub-components
|
||||
- Handles slot layout (vertical/horizontal)
|
||||
|
||||
#### 3.1 **InputSlot.vue** - Input Connection Point
|
||||
- Visual representation of input slots
|
||||
- Shows connection state
|
||||
- Click events for future connection logic
|
||||
- Type-based color coding (mirrors LiteGraph's slot_default_color_by_type)
|
||||
- Connection dot positioning
|
||||
- Hover states for connection compatibility
|
||||
|
||||
#### 3.2 **OutputSlot.vue** - Output Connection Point
|
||||
- Visual representation of output slots
|
||||
- Shows connection state
|
||||
- Click events for future connection logic
|
||||
- Type-based color coding (mirrors LiteGraph's slot_default_color_by_type)
|
||||
- Multiple connection support visualization
|
||||
- Hover states for connection compatibility
|
||||
|
||||
### 4. **NodeWidgets.vue** - Widget Container
|
||||
- Integrates with existing widget system
|
||||
- Maps LGraphNode.widgets to Vue widget components
|
||||
- Uses widget registry for dynamic rendering
|
||||
- Handles widget layout within node
|
||||
|
||||
### 5. **NodeContent.vue** - Custom Content Area
|
||||
- Extensible area for node-specific content
|
||||
- Slot-based for future customization
|
||||
- Allows for specialized node types
|
||||
|
||||
### 6. **Node Registry System**
|
||||
- Maps LGraphNode types to Vue components
|
||||
- Similar pattern to widget registry
|
||||
- Enables dynamic node component rendering
|
||||
- Type-safe component resolution
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
src/components/graph/vueNodes/
|
||||
├── LGraphNode.vue # Main node component
|
||||
├── NodeHeader.vue # Title/controls
|
||||
├── NodeSlots.vue # Connection points container
|
||||
│ ├── InputSlot.vue # Individual input slot
|
||||
│ └── OutputSlot.vue # Individual output slot
|
||||
├── NodeWidgets.vue # Widget integration
|
||||
├── NodeContent.vue # Custom content area
|
||||
├── nodeRegistry.ts # Node type registry
|
||||
└── index.ts # Component exports
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **Use Existing LGraphNode Interface** - No new interfaces needed, work with existing LiteGraph node structure
|
||||
2. **Pure Component Design** - Components receive LGraphNode as props, emit events up
|
||||
3. **Widget System Integration** - NodeWidgets.vue leverages existing widget registry
|
||||
4. **Visual-Only Slots** - Connection logic stays in LiteGraph entirely
|
||||
5. **Transform-Ready** - Designed to work with absolute positioning in future transform container
|
||||
6. **Registry Pattern** - Consistent with widget system for dynamic rendering
|
||||
|
||||
## Component Props Pattern
|
||||
|
||||
Each component follows a consistent prop pattern:
|
||||
|
||||
```typescript
|
||||
// Base props for all node components
|
||||
interface BaseNodeProps {
|
||||
node: LGraphNode
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
// Extended props for main node component
|
||||
interface LGraphNodeProps extends BaseNodeProps {
|
||||
selected?: boolean // Selection state from graph
|
||||
executing?: boolean // Execution state
|
||||
progress?: number // Execution progress (0-1)
|
||||
error?: string | null // Error state message
|
||||
zoomLevel?: number // For LOD calculations
|
||||
}
|
||||
|
||||
// Props for slot components
|
||||
interface SlotProps extends BaseNodeProps {
|
||||
slot: INodeSlot
|
||||
index: number
|
||||
type: 'input' | 'output'
|
||||
connected?: boolean // Has active connection
|
||||
compatible?: boolean // For hover states during dragging
|
||||
}
|
||||
|
||||
// Usage
|
||||
defineProps<LGraphNodeProps>()
|
||||
```
|
||||
|
||||
## Error Handling Pattern
|
||||
|
||||
Each component should implement error boundaries for graceful failure:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div v-if="renderError" class="node-error p-2 text-red-500 text-sm">
|
||||
⚠️ Node Render Error
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- Normal component content -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onErrorCaptured } from 'vue'
|
||||
|
||||
const renderError = ref<string | null>(null)
|
||||
|
||||
onErrorCaptured((error) => {
|
||||
renderError.value = error.message
|
||||
console.error('Vue node component error:', error)
|
||||
return false // Prevent error propagation
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
This ensures that if any node component encounters an error, it fails gracefully without breaking the entire graph.
|
||||
|
||||
## Performance Optimization with v-memo
|
||||
|
||||
Vue's `v-memo` directive can prevent unnecessary re-renders by memoizing template sections. This is particularly valuable for nodes with many widgets or during graph manipulation.
|
||||
|
||||
### Simple Implementation
|
||||
|
||||
Start with basic memoization on the most expensive parts:
|
||||
|
||||
```vue
|
||||
<!-- In LGraphNode.vue -->
|
||||
<template>
|
||||
<div class="vue-node">
|
||||
<!-- Memoize widgets - only re-render when count or values change -->
|
||||
<NodeWidgets
|
||||
v-memo="[node.widgets?.length, ...node.widgets?.map(w => w.value) ?? []]"
|
||||
:node="node"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Long-term Implementation
|
||||
|
||||
As the system scales, add more granular memoization:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="vue-node">
|
||||
<!-- Header only updates on title/color changes -->
|
||||
<NodeHeader
|
||||
v-memo="[node.title, node.color]"
|
||||
:node="node"
|
||||
/>
|
||||
|
||||
<!-- Slots only update when connections change -->
|
||||
<NodeSlots
|
||||
v-memo="[node.inputs?.length, node.outputs?.length]"
|
||||
:node="node"
|
||||
/>
|
||||
|
||||
<!-- Widgets update on value changes -->
|
||||
<NodeWidgets
|
||||
v-memo="[node.widgets?.length, ...node.widgets?.map(w => w.value) ?? []]"
|
||||
:node="node"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### Pros
|
||||
- Prevents widget re-renders during node dragging (position changes don't affect content)
|
||||
- Scales better with 100+ nodes containing multiple widgets
|
||||
- Significantly reduces render time for complex node graphs
|
||||
|
||||
### Cons
|
||||
- May miss updates if LiteGraph mutates objects in-place
|
||||
- Adds memory overhead from cached VDOM
|
||||
- Can make debugging harder ("why isn't my node updating?")
|
||||
|
||||
### Recommendation
|
||||
Start without v-memo, then add it selectively after profiling identifies performance bottlenecks. The widgets container is the most likely candidate for optimization since widgets are the most complex child components.
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Phase 1: Core Structure
|
||||
1. Create LGraphNode.vue base component with layout structure
|
||||
2. Implement NodeHeader.vue with basic title/controls
|
||||
3. Build NodeSlots.vue container with visual slot rendering
|
||||
|
||||
### Phase 2: Widget Integration
|
||||
4. Implement NodeWidgets.vue to integrate with existing widget system
|
||||
5. Create NodeContent.vue for extensibility
|
||||
|
||||
### Phase 3: Registry System
|
||||
6. Build node registry for dynamic component resolution
|
||||
7. Add index.ts exports
|
||||
|
||||
## Visual State Management
|
||||
|
||||
Node components will use Tailwind classes dynamically based on state:
|
||||
|
||||
```vue
|
||||
<!-- In LGraphNode.vue -->
|
||||
<template>
|
||||
<div :class="[
|
||||
'absolute border-2 rounded bg-surface-0',
|
||||
selected ? 'border-primary-500 ring-2 ring-primary-300' : 'border-surface-300',
|
||||
executing ? 'animate-pulse' : '',
|
||||
node.mode === 4 ? 'opacity-50' : '', // bypassed
|
||||
error ? 'border-red-500 bg-red-50' : ''
|
||||
]">
|
||||
<!-- Node content -->
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
Visual states to support:
|
||||
- **Selected**: Border color change and ring effect
|
||||
- **Executing**: Pulse animation
|
||||
- **Bypassed**: Reduced opacity
|
||||
- **Error**: Red border and background tint
|
||||
- **Collapsed**: Height reduction (handled by v-if on body content)
|
||||
|
||||
## CSS Performance Strategy
|
||||
|
||||
Nodes will use CSS containment for optimal performance:
|
||||
|
||||
```css
|
||||
.lg-node {
|
||||
position: absolute;
|
||||
contain: layout style paint;
|
||||
/* will-change only added during drag via class */
|
||||
}
|
||||
|
||||
.lg-node--dragging {
|
||||
will-change: transform;
|
||||
}
|
||||
```
|
||||
|
||||
Key performance considerations:
|
||||
- Position absolutely within transform pane (no individual transforms)
|
||||
- CSS containment prevents layout thrashing
|
||||
- GPU acceleration only during drag operations
|
||||
- No complex calculations in Vue components
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **Separation of Concerns**: Each component handles one aspect of node rendering
|
||||
- **LiteGraph Integration**: Components designed to work with existing LiteGraph data structures
|
||||
- **Performance First**: CSS containment, efficient rendering patterns
|
||||
- **Future-Ready**: Architecture supports transform container and event delegation
|
||||
- **Consistent Patterns**: Follows same patterns as widget system implementation
|
||||
- **Tailwind-First**: Use utility classes for all styling, no custom CSS
|
||||
|
||||
## Expected Outcomes
|
||||
|
||||
- Clean, modular Vue components for node rendering
|
||||
- Seamless integration with existing widget system
|
||||
- Foundation ready for transform/sync layer implementation
|
||||
- Maintainable and extensible architecture
|
||||
203
vue-widget-implementation-plan.md
Normal file
203
vue-widget-implementation-plan.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Vue Widget Implementation Plan
|
||||
|
||||
## Overview
|
||||
This document outlines the implementation plan for creating simplified Vue-based widget components that will work with the new Vue node rendering system.
|
||||
|
||||
## Directory Structure
|
||||
```
|
||||
src/components/graph/vueWidgets/ # New directory alongside existing widgets
|
||||
├── WidgetButton.vue
|
||||
├── WidgetInputText.vue
|
||||
├── WidgetSelect.vue
|
||||
├── WidgetColorPicker.vue
|
||||
├── WidgetMultiSelect.vue
|
||||
├── WidgetSelectButton.vue
|
||||
├── WidgetSlider.vue
|
||||
├── WidgetTextarea.vue
|
||||
├── WidgetToggleSwitch.vue
|
||||
├── WidgetChart.vue
|
||||
├── WidgetImage.vue
|
||||
├── WidgetImageCompare.vue
|
||||
├── WidgetGalleria.vue
|
||||
├── WidgetFileUpload.vue
|
||||
└── WidgetTreeSelect.vue
|
||||
```
|
||||
|
||||
## Prop Filtering Utility
|
||||
Create a utility file for prop filtering:
|
||||
```typescript
|
||||
// src/utils/widgetPropFilter.ts
|
||||
|
||||
// Props to exclude based on the widget interface specifications
|
||||
export const STANDARD_EXCLUDED_PROPS = ['style', 'class', 'dt', 'pt', 'ptOptions', 'unstyled'] as const
|
||||
|
||||
export const INPUT_EXCLUDED_PROPS = [
|
||||
...STANDARD_EXCLUDED_PROPS,
|
||||
'inputClass',
|
||||
'inputStyle'
|
||||
] as const
|
||||
|
||||
export const PANEL_EXCLUDED_PROPS = [
|
||||
...STANDARD_EXCLUDED_PROPS,
|
||||
'panelClass',
|
||||
'panelStyle',
|
||||
'overlayClass'
|
||||
] as const
|
||||
|
||||
export const IMAGE_EXCLUDED_PROPS = [
|
||||
...STANDARD_EXCLUDED_PROPS,
|
||||
'imageClass',
|
||||
'imageStyle'
|
||||
] as const
|
||||
|
||||
export const GALLERIA_EXCLUDED_PROPS = [
|
||||
...STANDARD_EXCLUDED_PROPS,
|
||||
'thumbnailsPosition',
|
||||
'verticalThumbnailViewPortHeight',
|
||||
'indicatorsPosition',
|
||||
'maskClass',
|
||||
'containerStyle',
|
||||
'containerClass',
|
||||
'galleriaClass'
|
||||
] as const
|
||||
|
||||
// Utility function to filter props
|
||||
export function filterWidgetProps<T extends Record<string, any>>(
|
||||
props: T | undefined,
|
||||
excludeList: readonly string[]
|
||||
): Partial<T> {
|
||||
if (!props) return {}
|
||||
|
||||
const filtered: Record<string, any> = {}
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (!excludeList.includes(key)) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
return filtered as Partial<T>
|
||||
}
|
||||
```
|
||||
|
||||
## Component Template Pattern
|
||||
Each widget follows this structure without style tags:
|
||||
```vue
|
||||
<template>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label v-if="widget.name" class="text-sm opacity-80">{{ widget.name }}</label>
|
||||
<ComponentName
|
||||
v-model="value"
|
||||
v-bind="filteredProps"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import ComponentName from 'primevue/componentname'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import { filterWidgetProps, STANDARD_EXCLUDED_PROPS } from '@/utils/widgetPropFilter'
|
||||
|
||||
const value = defineModel<T>({ required: true })
|
||||
|
||||
const props = defineProps<{
|
||||
widget: SimplifiedWidget<T>
|
||||
readonly?: boolean
|
||||
}>()
|
||||
|
||||
const filteredProps = computed(() =>
|
||||
filterWidgetProps(props.widget.options, STANDARD_EXCLUDED_PROPS)
|
||||
)
|
||||
</script>
|
||||
```
|
||||
|
||||
## Complete Widget List (All 15 from the spec)
|
||||
|
||||
### Input Components:
|
||||
1. **WidgetInputText** - Single line text input
|
||||
2. **WidgetTextarea** - Multiline text input
|
||||
3. **WidgetSlider** - Numeric range slider
|
||||
4. **WidgetToggleSwitch** - Boolean on/off switch
|
||||
|
||||
### Selection Components:
|
||||
5. **WidgetSelect** - Dropdown selection
|
||||
6. **WidgetMultiSelect** - Multiple item selection
|
||||
7. **WidgetSelectButton** - Button group selection
|
||||
8. **WidgetTreeSelect** - Hierarchical selection
|
||||
|
||||
### Visual Components:
|
||||
9. **WidgetColorPicker** - Color picker
|
||||
10. **WidgetImage** - Single image display
|
||||
11. **WidgetImageCompare** - Before/after image comparison
|
||||
12. **WidgetGalleria** - Image gallery/carousel
|
||||
13. **WidgetChart** - Chart display (using Chart.js)
|
||||
|
||||
### Action Components:
|
||||
14. **WidgetButton** - Button with actions
|
||||
15. **WidgetFileUpload** - File upload interface
|
||||
|
||||
## Implementation Details
|
||||
|
||||
Each widget will:
|
||||
- Use `defineModel` for two-way binding
|
||||
- Import the appropriate PrimeVue component
|
||||
- Use the prop filtering utility with the correct exclusion list
|
||||
- Apply minimal Tailwind classes for basic layout
|
||||
- Handle the `readonly` prop to disable interaction when needed
|
||||
|
||||
## Widget Type Mapping
|
||||
When integrating with the node system, we'll need to map widget types to components:
|
||||
```typescript
|
||||
// src/components/graph/vueWidgets/widgetRegistry.ts
|
||||
export enum WidgetType {
|
||||
BUTTON = 'BUTTON',
|
||||
STRING = 'STRING',
|
||||
INT = 'INT',
|
||||
FLOAT = 'FLOAT',
|
||||
NUMBER = 'NUMBER',
|
||||
BOOLEAN = 'BOOLEAN',
|
||||
COMBO = 'COMBO',
|
||||
COLOR = 'COLOR',
|
||||
MULTISELECT = 'MULTISELECT',
|
||||
SELECTBUTTON = 'SELECTBUTTON',
|
||||
SLIDER = 'SLIDER',
|
||||
TEXTAREA = 'TEXTAREA',
|
||||
TOGGLESWITCH = 'TOGGLESWITCH',
|
||||
CHART = 'CHART',
|
||||
IMAGE = 'IMAGE',
|
||||
IMAGECOMPARE = 'IMAGECOMPARE',
|
||||
GALLERIA = 'GALLERIA',
|
||||
FILEUPLOAD = 'FILEUPLOAD',
|
||||
TREESELECT = 'TREESELECT'
|
||||
}
|
||||
|
||||
export const widgetTypeToComponent: Record<string, Component> = {
|
||||
[WidgetType.BUTTON]: WidgetButton,
|
||||
[WidgetType.STRING]: WidgetInputText,
|
||||
[WidgetType.INT]: WidgetSlider,
|
||||
[WidgetType.FLOAT]: WidgetSlider,
|
||||
[WidgetType.NUMBER]: WidgetSlider, // For compatibility
|
||||
[WidgetType.BOOLEAN]: WidgetToggleSwitch,
|
||||
[WidgetType.COMBO]: WidgetSelect,
|
||||
// ... other mappings
|
||||
}
|
||||
```
|
||||
|
||||
## SimplifiedWidget Interface
|
||||
Based on the simplification plan:
|
||||
```typescript
|
||||
interface SimplifiedWidget<T = any> {
|
||||
name: string
|
||||
type: string
|
||||
value: T
|
||||
options?: Record<string, any> // Contains filtered PrimeVue props
|
||||
callback?: (value: T) => void
|
||||
}
|
||||
```
|
||||
|
||||
## Key Differences from Current System
|
||||
- No DOM manipulation or positioning logic
|
||||
- No visibility/zoom management
|
||||
- No canvas interaction
|
||||
- Purely focused on value display and user input
|
||||
- Relies on parent Vue components for layout and positioning
|
||||
Reference in New Issue
Block a user