Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/composables/graph/useWidgetRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import {
WidgetType,
widgetTypeToComponent
} from '@/components/graph/vueWidgets/widgetRegistry'
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'

/**
* Static mapping of LiteGraph widget types to Vue widget component names
Expand Down
2 changes: 1 addition & 1 deletion src/composables/node/useNodeCanvasImagePreview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useImagePreviewWidget } from '@/composables/widgets/useImagePreviewWidget'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useImagePreviewWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useImagePreviewWidget'

const CANVAS_IMAGE_PREVIEW_WIDGET = '$$canvas-image-preview'

Expand Down
2 changes: 1 addition & 1 deletion src/composables/node/useNodeChatHistory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
import { useChatHistoryWidget } from '@/composables/widgets/useChatHistoryWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useChatHistoryWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useChatHistoryWidget'

const CHAT_HISTORY_WIDGET_NAME = '$$node-chat-history'

Expand Down
2 changes: 1 addition & 1 deletion src/composables/node/useNodeProgressText.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useTextPreviewWidget } from '@/composables/widgets/useProgressTextWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useTextPreviewWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget'

const TEXT_PREVIEW_WIDGET_NAME = '$$node-text-preview'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ import { computed, onErrorCaptured, ref, toRef, watch } from 'vue'

// Import the VueNodeData type
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { LODLevel, useLOD } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useNodeLayout } from '@/renderer/extensions/vue-nodes/composables/useNodeLayout'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'

import { LiteGraph } from '../../../lib/litegraph/src/litegraph'
import NodeContent from './NodeContent.vue'
import NodeHeader from './NodeHeader.vue'
import NodeSlots from './NodeSlots.vue'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@
import { onErrorCaptured, ref } from 'vue'

import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'

import type { LGraphNode } from '../../../lib/litegraph/src/litegraph'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'

interface NodeContentProps {
node?: LGraphNode // For backwards compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ import { computed, onErrorCaptured, ref, watch } from 'vue'

import EditableText from '@/components/common/EditableText.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'

import type { LGraphNode } from '../../../lib/litegraph/src/litegraph'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'

interface NodeHeaderProps {
node?: LGraphNode // For backwards compatibility
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,11 @@ import { computed, onErrorCaptured, onUnmounted, ref } from 'vue'

import { useEventForwarding } from '@/composables/graph/useEventForwarding'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
import type { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph'
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
import { isSlotObject } from '@/utils/typeGuardUtil'

import type {
INodeSlot,
LGraphNode
} from '../../../lib/litegraph/src/litegraph'
import InputSlot from './InputSlot.vue'
import OutputSlot from './OutputSlot.vue'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,23 @@
<script setup lang="ts">
import { computed, onErrorCaptured, onUnmounted, ref } from 'vue'

// Import widget components directly
import WidgetInputText from '@/components/graph/vueWidgets/WidgetInputText.vue'
import { widgetTypeToComponent } from '@/components/graph/vueWidgets/widgetRegistry'
import { useEventForwarding } from '@/composables/graph/useEventForwarding'
import type {
SafeWidgetData,
VueNodeData
} from '@/composables/graph/useGraphNodeManager'
import { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
// Import widget components directly
import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue'
import {
ESSENTIAL_WIDGET_TYPES,
useWidgetRenderer
} from '@/composables/graph/useWidgetRenderer'
import { useErrorHandling } from '@/composables/useErrorHandling'
} from '@/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer'
import { widgetTypeToComponent } from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'

import type { LGraphNode } from '../../../lib/litegraph/src/litegraph'
import InputSlot from './InputSlot.vue'

interface NodeWidgetsProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,8 @@ import { computed, onErrorCaptured, ref } from 'vue'

import { useErrorHandling } from '@/composables/useErrorHandling'
import { getSlotColor } from '@/constants/slotColors'

import type {
INodeSlot,
LGraphNode
} from '../../../lib/litegraph/src/litegraph'
import { COMFY_VUE_NODE_DIMENSIONS } from '../../../lib/litegraph/src/litegraph'
import type { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph'
import { COMFY_VUE_NODE_DIMENSIONS } from '@/lib/litegraph/src/litegraph'

interface OutputSlotProps {
node?: LGraphNode
Expand Down
34 changes: 34 additions & 0 deletions src/renderer/extensions/vueNodes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* Vue Nodes Renderer Extension
*
* This extension provides Vue-based node rendering capabilities for ComfyUI.
* Domain-driven architecture organizing concerns by function rather than technical layers.
*
* Architecture:
* - components/ - Vue node UI components (LGraphNode, NodeHeader, etc.)
* - widgets/ - Widget rendering system (components, composables, registry)
* - lod/ - Level of Detail system for performance
* - layout/ - Node positioning and layout logic
* - interaction/ - User interaction handling (planned)
*/

// Main node components
export { default as LGraphNode } from './components/LGraphNode.vue'
export { default as NodeHeader } from './components/NodeHeader.vue'
export { default as NodeContent } from './components/NodeContent.vue'
export { default as NodeSlots } from './components/NodeSlots.vue'
export { default as NodeWidgets } from './components/NodeWidgets.vue'
export { default as InputSlot } from './components/InputSlot.vue'
export { default as OutputSlot } from './components/OutputSlot.vue'

// Widget system exports
export * from './widgets/registry/widgetRegistry'
export * from './widgets/composables/useWidgetRenderer'
export * from './widgets/composables/useWidgetValue'
export * from './widgets/useNodeWidgets'

// Level of Detail system
export * from './lod/useLOD'

// Layout system exports
export * from './layout/useNodeLayout'
186 changes: 186 additions & 0 deletions src/renderer/extensions/vueNodes/lod/useLOD.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* Level of Detail (LOD) composable for Vue-based node rendering
*
* Provides dynamic quality adjustment based on zoom level to maintain
* performance with large node graphs. Uses zoom thresholds to determine
* how much detail to render for each node component.
*
* ## LOD Levels
*
* - **FULL** (zoom > 0.8): Complete rendering with all widgets, slots, and content
* - **REDUCED** (0.4 < zoom <= 0.8): Essential widgets only, simplified slots
* - **MINIMAL** (zoom <= 0.4): Title only, no widgets or slots
*
* ## Performance Benefits
*
* - Reduces DOM element count by up to 80% at low zoom levels
* - Minimizes layout calculations and paint operations
* - Enables smooth performance with 1000+ nodes
* - Maintains visual fidelity when detail is actually visible
*
* @example
* ```typescript
* const { lodLevel, shouldRenderWidgets, shouldRenderSlots } = useLOD(zoomRef)
*
* // In template
* <NodeWidgets v-if="shouldRenderWidgets" />
* <NodeSlots v-if="shouldRenderSlots" />
* ```
*/
import { type Ref, computed, readonly } from 'vue'

export enum LODLevel {
MINIMAL = 'minimal', // zoom <= 0.4
REDUCED = 'reduced', // 0.4 < zoom <= 0.8
FULL = 'full' // zoom > 0.8
}

export interface LODConfig {
renderWidgets: boolean
renderSlots: boolean
renderContent: boolean
renderSlotLabels: boolean
renderWidgetLabels: boolean
cssClass: string
}

// LOD configuration for each level
const LOD_CONFIGS: Record<LODLevel, LODConfig> = {
[LODLevel.FULL]: {
renderWidgets: true,
renderSlots: true,
renderContent: true,
renderSlotLabels: true,
renderWidgetLabels: true,
cssClass: 'lg-node--lod-full'
},
[LODLevel.REDUCED]: {
renderWidgets: true,
renderSlots: true,
renderContent: false,
renderSlotLabels: false,
renderWidgetLabels: false,
cssClass: 'lg-node--lod-reduced'
},
[LODLevel.MINIMAL]: {
renderWidgets: false,
renderSlots: false,
renderContent: false,
renderSlotLabels: false,
renderWidgetLabels: false,
cssClass: 'lg-node--lod-minimal'
}
}

/**
* Create LOD (Level of Detail) state based on zoom level
*
* @param zoomRef - Reactive reference to current zoom level (camera.z)
* @returns LOD state and configuration
*/
export function useLOD(zoomRef: Ref<number>) {
// Continuous LOD score (0-1) for smooth transitions
const lodScore = computed(() => {
const zoom = zoomRef.value
return Math.max(0, Math.min(1, zoom))
})

// Determine current LOD level based on zoom
const lodLevel = computed<LODLevel>(() => {
const zoom = zoomRef.value

if (zoom > 0.8) return LODLevel.FULL
if (zoom > 0.4) return LODLevel.REDUCED
return LODLevel.MINIMAL
})

// Get configuration for current LOD level
const lodConfig = computed<LODConfig>(() => LOD_CONFIGS[lodLevel.value])

// Convenience computed properties for common rendering decisions
const shouldRenderWidgets = computed(() => lodConfig.value.renderWidgets)
const shouldRenderSlots = computed(() => lodConfig.value.renderSlots)
const shouldRenderContent = computed(() => lodConfig.value.renderContent)
const shouldRenderSlotLabels = computed(
() => lodConfig.value.renderSlotLabels
)
const shouldRenderWidgetLabels = computed(
() => lodConfig.value.renderWidgetLabels
)

// CSS class for styling based on LOD level
const lodCssClass = computed(() => lodConfig.value.cssClass)

// Get essential widgets for reduced LOD (only interactive controls)
const getEssentialWidgets = (widgets: unknown[]): unknown[] => {
if (lodLevel.value === LODLevel.FULL) return widgets
if (lodLevel.value === LODLevel.MINIMAL) return []

// For reduced LOD, filter to essential widget types only
return widgets.filter((widget: any) => {
const type = widget?.type?.toLowerCase()
return [
'combo',
'select',
'toggle',
'boolean',
'slider',
'number'
].includes(type)
})
}

// Performance metrics for debugging
const lodMetrics = computed(() => ({
level: lodLevel.value,
zoom: zoomRef.value,
widgetCount: shouldRenderWidgets.value ? 'full' : 'none',
slotCount: shouldRenderSlots.value ? 'full' : 'none'
}))

return {
// Core LOD state
lodLevel: readonly(lodLevel),
lodConfig: readonly(lodConfig),
lodScore: readonly(lodScore),

// Rendering decisions
shouldRenderWidgets,
shouldRenderSlots,
shouldRenderContent,
shouldRenderSlotLabels,
shouldRenderWidgetLabels,

// Styling
lodCssClass,

// Utilities
getEssentialWidgets,
lodMetrics
}
}

/**
* Get LOD level thresholds for configuration or debugging
*/
export const LOD_THRESHOLDS = {
FULL_THRESHOLD: 0.8,
REDUCED_THRESHOLD: 0.4,
MINIMAL_THRESHOLD: 0.0
} as const

/**
* Check if zoom level supports a specific feature
*/
export function supportsFeatureAtZoom(
zoom: number,
feature: keyof LODConfig
): boolean {
const level =
zoom > 0.8
? LODLevel.FULL
: zoom > 0.4
? LODLevel.REDUCED
: LODLevel.MINIMAL
return LOD_CONFIGS[level][feature] as boolean
}
Loading
Loading