mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 15:40:10 +00:00
[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.
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -25,13 +25,13 @@ import {
|
||||
} from '@/utils/widgetPropFilter'
|
||||
|
||||
const props = defineProps<{
|
||||
widget: SimplifiedWidget<any>
|
||||
modelValue: any
|
||||
widget: SimplifiedWidget<string | number | undefined>
|
||||
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
|
||||
|
||||
@@ -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<string>
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
@@ -35,7 +38,7 @@ export interface PerformanceMetrics {
|
||||
export interface SafeWidgetData {
|
||||
name: string
|
||||
type: string
|
||||
value: unknown
|
||||
value: WidgetValue
|
||||
options?: Record<string, unknown>
|
||||
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))
|
||||
|
||||
|
||||
@@ -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<T, U = T> {
|
||||
export interface UseWidgetValueOptions<
|
||||
T extends WidgetValue = WidgetValue,
|
||||
U = T
|
||||
> {
|
||||
/** The widget configuration from LiteGraph */
|
||||
widget: SimplifiedWidget<T>
|
||||
/** The current value from parent component */
|
||||
@@ -19,7 +22,10 @@ export interface UseWidgetValueOptions<T, U = T> {
|
||||
transform?: (value: U) => T
|
||||
}
|
||||
|
||||
export interface UseWidgetValueReturn<T, U = T> {
|
||||
export interface UseWidgetValueReturn<
|
||||
T extends WidgetValue = WidgetValue,
|
||||
U = T
|
||||
> {
|
||||
/** Local value for immediate UI updates */
|
||||
localValue: Ref<T>
|
||||
/** Handler for user interactions */
|
||||
@@ -38,7 +44,7 @@ export interface UseWidgetValueReturn<T, U = T> {
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function useWidgetValue<T, U = T>({
|
||||
export function useWidgetValue<T extends WidgetValue = WidgetValue, U = T>({
|
||||
widget,
|
||||
modelValue,
|
||||
defaultValue,
|
||||
|
||||
@@ -3,7 +3,21 @@
|
||||
* Removes all DOM manipulation and positioning concerns
|
||||
*/
|
||||
|
||||
export interface SimplifiedWidget<T = any, O = Record<string, any>> {
|
||||
/** 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<string, any>
|
||||
> {
|
||||
/** Display name of the widget */
|
||||
name: string
|
||||
|
||||
|
||||
Reference in New Issue
Block a user