From 290906e7ccac4cf4830ca70b799fbecf4ea717bf Mon Sep 17 00:00:00 2001 From: bymyself Date: Sat, 5 Jul 2025 02:31:42 -0700 Subject: [PATCH] [refactor] Improve type safety across Vue node widget system - Create WidgetValue union type for all valid widget values - Replace 'any' types with proper generic constraints in SimplifiedWidget - Add runtime validation for widget values in useGraphNodeManager - Update WidgetSelect to use constrained generics instead of any - Fix type assertions with proper validation functions - Ensure SafeWidgetData uses WidgetValue type consistently This eliminates most 'any' usage while maintaining runtime safety through validation. --- src/components/graph/vueNodes/NodeWidgets.vue | 4 +- .../graph/vueWidgets/WidgetSelect.vue | 6 +- src/composables/graph/useGraphNodeManager.ts | 59 ++++++++++++++++--- src/composables/graph/useWidgetValue.ts | 14 +++-- src/types/simplifiedWidget.ts | 16 ++++- 5 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/components/graph/vueNodes/NodeWidgets.vue b/src/components/graph/vueNodes/NodeWidgets.vue index c68c47b2b..0837729ac 100644 --- a/src/components/graph/vueNodes/NodeWidgets.vue +++ b/src/components/graph/vueNodes/NodeWidgets.vue @@ -31,7 +31,7 @@ import type { import { LODLevel } from '@/composables/graph/useLOD' import { useWidgetRenderer } from '@/composables/graph/useWidgetRenderer' import { useErrorHandling } from '@/composables/useErrorHandling' -import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' interface NodeWidgetsProps { node?: LGraphNode @@ -106,7 +106,7 @@ const getVueComponent = (widget: SafeWidgetData) => { return component || WidgetInputText // Fallback to text input } -const getWidgetValue = (widget: SafeWidgetData): unknown => { +const getWidgetValue = (widget: SafeWidgetData): WidgetValue => { return widget.value } diff --git a/src/components/graph/vueWidgets/WidgetSelect.vue b/src/components/graph/vueWidgets/WidgetSelect.vue index 33321ded6..57f3d7569 100644 --- a/src/components/graph/vueWidgets/WidgetSelect.vue +++ b/src/components/graph/vueWidgets/WidgetSelect.vue @@ -25,13 +25,13 @@ import { } from '@/utils/widgetPropFilter' const props = defineProps<{ - widget: SimplifiedWidget - modelValue: any + widget: SimplifiedWidget + modelValue: string | number | undefined readonly?: boolean }>() const emit = defineEmits<{ - 'update:modelValue': [value: any] + 'update:modelValue': [value: string | number | undefined] }>() // Use the composable for consistent widget value handling diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 62214bbef..037f021ef 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -5,6 +5,9 @@ import type { LGraph, LGraphNode } from '@comfyorg/litegraph' import { nextTick, reactive, readonly } from 'vue' +import type { WidgetValue } from '@/types/simplifiedWidget' +import type { SpatialIndexDebugInfo } from '@/types/spatialIndex' + import { type Bounds, QuadTree } from '../../utils/spatial/QuadTree' export interface NodeState { @@ -18,7 +21,7 @@ export interface NodeMetadata { lastRenderTime: number cachedBounds: DOMRect | null lodLevel: 'high' | 'medium' | 'low' - spatialIndex?: any + spatialIndex?: QuadTree } export interface PerformanceMetrics { @@ -35,7 +38,7 @@ export interface PerformanceMetrics { export interface SafeWidgetData { name: string type: string - value: unknown + value: WidgetValue options?: Record callback?: ((value: unknown) => void) | undefined } @@ -87,7 +90,7 @@ export interface GraphNodeManager { spatialMetrics: SpatialMetrics // Debug - getSpatialIndexDebugInfo(): any | null + getSpatialIndexDebugInfo(): SpatialIndexDebugInfo | null } export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { @@ -182,7 +185,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { return { name: widget.name || 'unknown', type: widget.type || 'text', - value: undefined, + value: undefined, // Already a valid WidgetValue options: undefined, callback: undefined } @@ -207,6 +210,33 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { return nodeRefs.get(id) } + /** + * Validates that a value is a valid WidgetValue type + */ + const validateWidgetValue = (value: unknown): WidgetValue => { + if (value === null || value === undefined || value === void 0) { + return undefined + } + if ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' + ) { + return value + } + if (typeof value === 'object') { + // Check if it's a File array + if (Array.isArray(value) && value.every((item) => item instanceof File)) { + return value as File[] + } + // Otherwise it's a generic object + return value as object + } + // If none of the above, return undefined + console.warn(`Invalid widget value type: ${typeof value}`, value) + return undefined + } + /** * Updates Vue state when widget values change */ @@ -220,7 +250,7 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { if (!currentData?.widgets) return const updatedWidgets = currentData.widgets.map((w) => - w.name === widgetName ? { ...w, value: value } : w + w.name === widgetName ? { ...w, value: validateWidgetValue(value) } : w ) vueNodeData.set(nodeId, { ...currentData, @@ -236,13 +266,25 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { * Creates a wrapped callback for a widget that maintains LiteGraph/Vue sync */ const createWrappedWidgetCallback = ( - widget: any, + widget: { value?: unknown; name: string }, // LiteGraph widget with minimal typing originalCallback: ((value: unknown) => void) | undefined, nodeId: string ) => { return (value: unknown) => { // 1. Update the widget value in LiteGraph (critical for LiteGraph state) - widget.value = value as string | number | boolean | object | undefined + // Validate that the value is of an acceptable type + if ( + value !== null && + value !== undefined && + typeof value !== 'string' && + typeof value !== 'number' && + typeof value !== 'boolean' && + typeof value !== 'object' + ) { + console.warn(`Invalid widget value type: ${typeof value}`) + return + } + widget.value = value // 2. Call the original callback if it exists if (originalCallback) { @@ -362,6 +404,9 @@ export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => { // Store non-reactive reference nodeRefs.set(id, node) + // Set up widget callbacks BEFORE extracting data (critical order) + setupNodeWidgetCallbacks(node) + // Extract and store safe data for Vue vueNodeData.set(id, extractVueNodeData(node)) diff --git a/src/composables/graph/useWidgetValue.ts b/src/composables/graph/useWidgetValue.ts index 68bc7a003..ea957308e 100644 --- a/src/composables/graph/useWidgetValue.ts +++ b/src/composables/graph/useWidgetValue.ts @@ -4,9 +4,12 @@ */ import { type Ref, ref, watch } from 'vue' -import type { SimplifiedWidget } from '@/types/simplifiedWidget' +import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget' -export interface UseWidgetValueOptions { +export interface UseWidgetValueOptions< + T extends WidgetValue = WidgetValue, + U = T +> { /** The widget configuration from LiteGraph */ widget: SimplifiedWidget /** The current value from parent component */ @@ -19,7 +22,10 @@ export interface UseWidgetValueOptions { transform?: (value: U) => T } -export interface UseWidgetValueReturn { +export interface UseWidgetValueReturn< + T extends WidgetValue = WidgetValue, + U = T +> { /** Local value for immediate UI updates */ localValue: Ref /** Handler for user interactions */ @@ -38,7 +44,7 @@ export interface UseWidgetValueReturn { * }) * ``` */ -export function useWidgetValue({ +export function useWidgetValue({ widget, modelValue, defaultValue, diff --git a/src/types/simplifiedWidget.ts b/src/types/simplifiedWidget.ts index 7c755917d..27be67f11 100644 --- a/src/types/simplifiedWidget.ts +++ b/src/types/simplifiedWidget.ts @@ -3,7 +3,21 @@ * Removes all DOM manipulation and positioning concerns */ -export interface SimplifiedWidget> { +/** Valid types for widget values */ +export type WidgetValue = + | string + | number + | boolean + | object + | undefined + | null + | void + | File[] + +export interface SimplifiedWidget< + T extends WidgetValue = WidgetValue, + O = Record +> { /** Display name of the widget */ name: string