[CodeHealth] Replace temporary TS conversion widget types (#1019)

This commit is contained in:
filtered
2025-05-06 19:53:49 +10:00
committed by GitHub
parent a74df42cce
commit c6df437662
13 changed files with 77 additions and 123 deletions

View File

@@ -73,9 +73,8 @@ import {
} from "./types/globalEnums" } from "./types/globalEnums"
import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange" import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange"
import { findFirstNode, getAllNestedItems } from "./utils/collections" import { findFirstNode, getAllNestedItems } from "./utils/collections"
import { toClass } from "./utils/type"
import { BaseWidget } from "./widgets/BaseWidget" import { BaseWidget } from "./widgets/BaseWidget"
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" import { toConcreteWidget } from "./widgets/widgetMap"
interface IShowSearchOptions { interface IShowSearchOptions {
node_to?: LGraphNode | null node_to?: LGraphNode | null
@@ -2397,9 +2396,8 @@ export class LGraphCanvas {
const x = pos[0] - node.pos[0] const x = pos[0] - node.pos[0]
const y = pos[1] - node.pos[1] const y = pos[1] - node.pos[1]
const WidgetClass = WIDGET_TYPE_MAP[widget.type] const widgetInstance = toConcreteWidget(widget)
if (WidgetClass) { if (widgetInstance) {
const widgetInstance = toClass(WidgetClass, widget)
pointer.onClick = () => widgetInstance.onClick({ pointer.onClick = () => widgetInstance.onClick({
e, e,
node, node,

View File

@@ -20,6 +20,9 @@ export type WhenNullish<T, Result> = T & {} | (T extends null ? Result : T exten
/** A type with each of the {@link Properties} made optional. */ /** A type with each of the {@link Properties} made optional. */
export type OptionalProps<T, Properties extends keyof T> = Omit<T, Properties> & { [K in Properties]?: T[K] } export type OptionalProps<T, Properties extends keyof T> = Omit<T, Properties> & { [K in Properties]?: T[K] }
/** A type with each of the {@link Properties} marked as required. */
export type RequiredProps<T, Properties extends keyof T> = Omit<T, Properties> & { [K in Properties]-?: T[K] }
/** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */ /** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */
export type SharedIntersection<T1, T2> = { export type SharedIntersection<T1, T2> = {
[P in keyof T1 as P extends keyof T2 ? P : never]: T1[P] [P in keyof T1 as P extends keyof T2 ? P : never]: T1[P]

View File

@@ -1,9 +1,9 @@
import type { CanvasPointer, LGraphCanvas, LGraphNode } from "../litegraph" import type { CanvasPointer, LGraphCanvas, LGraphNode } from "../litegraph"
import type { CanvasMouseEvent, CanvasPointerEvent } from "./events" import type { CanvasMouseEvent, CanvasPointerEvent } from "./events"
import { CanvasColour, Point, Size } from "../interfaces" import { CanvasColour, Point, type RequiredProps, Size } from "../interfaces"
export interface IWidgetOptions<TValue = unknown> extends Record<string, unknown> { export interface IWidgetOptions<TValues = unknown[]> {
on?: string on?: string
off?: string off?: string
max?: number max?: number
@@ -27,7 +27,7 @@ export interface IWidgetOptions<TValue = unknown> extends Record<string, unknown
/** If `true`, an input socket will not be created for this widget. */ /** If `true`, an input socket will not be created for this widget. */
socketless?: boolean socketless?: boolean
values?: TValue[] values?: TValues
callback?: IWidget["callback"] callback?: IWidget["callback"]
} }
@@ -60,71 +60,61 @@ export type IWidget =
| IBooleanWidget | IBooleanWidget
| INumericWidget | INumericWidget
| IStringWidget | IStringWidget
| IMultilineStringWidget
| IComboWidget | IComboWidget
| ICustomWidget | ICustomWidget
| ISliderWidget | ISliderWidget
| IButtonWidget | IButtonWidget
| IKnobWidget | IKnobWidget
export interface IBooleanWidget extends IBaseWidget { export interface IBooleanWidget extends IBaseWidget<boolean, "toggle", IWidgetOptions<boolean>> {
type: "toggle" type: "toggle"
value: boolean value: boolean
} }
/** Any widget that uses a numeric backing */ /** Any widget that uses a numeric backing */
export interface INumericWidget extends IBaseWidget { export interface INumericWidget extends IBaseWidget<number, "number", IWidgetOptions<number>> {
type: "number" type: "number"
value: number value: number
} }
export interface ISliderWidget extends IBaseWidget { export interface ISliderWidget extends IBaseWidget<number, "slider", IWidgetSliderOptions> {
type: "slider" type: "slider"
value: number value: number
options: IWidgetSliderOptions
marker?: number marker?: number
} }
export interface IKnobWidget extends IBaseWidget { export interface IKnobWidget extends IBaseWidget<number, "knob", IWidgetKnobOptions> {
type: "knob" type: "knob"
value: number value: number
options: IWidgetKnobOptions options: IWidgetKnobOptions
} }
type ComboWidgetValues = string[] | Record<string, string> | ((widget?: IComboWidget, node?: LGraphNode) => string[])
/** A combo-box widget (dropdown, select, etc) */ /** A combo-box widget (dropdown, select, etc) */
export interface IComboWidget extends IBaseWidget { export interface IComboWidget extends IBaseWidget<
string | number,
"combo",
RequiredProps<IWidgetOptions<ComboWidgetValues>, "values">
> {
type: "combo" type: "combo"
value: string | number value: string | number
options: IWidgetOptions<string>
} }
export type IStringWidgetType = IStringWidget["type"] | IMultilineStringWidget["type"]
/** A widget with a string value */ /** A widget with a string value */
export interface IStringWidget extends IBaseWidget { export interface IStringWidget extends IBaseWidget<string, "string" | "text", IWidgetOptions<string>> {
type: "string" | "text" type: "string" | "text"
value: string value: string
} }
export interface IButtonWidget extends IBaseWidget { export interface IButtonWidget extends IBaseWidget<undefined, "button", IWidgetOptions<undefined>> {
type: "button" type: "button"
value: undefined value: undefined
clicked: boolean clicked: boolean
} }
/** A widget with a string value and a multiline text input */
export interface IMultilineStringWidget<TElement extends HTMLElement = HTMLTextAreaElement> extends
IBaseWidget {
type: "multiline"
value: string
/** HTML textarea element */
element?: TElement
}
/** A custom widget - accepts any value and has no built-in special handling */ /** A custom widget - accepts any value and has no built-in special handling */
export interface ICustomWidget extends IBaseWidget { export interface ICustomWidget extends IBaseWidget<string | object, "custom", IWidgetOptions<string | object>> {
type: "custom" type: "custom"
value: string | object value: string | object
} }
@@ -141,16 +131,16 @@ export type TWidgetValue = IWidget["value"]
* The base type for all widgets. Should not be implemented directly. * The base type for all widgets. Should not be implemented directly.
* @see IWidget * @see IWidget
*/ */
export interface IBaseWidget { export interface IBaseWidget<TValue = unknown, TType = string, TOptions = unknown> {
linkedWidgets?: IWidget[] linkedWidgets?: IWidget[]
name: string name: string
options: IWidgetOptions options: TOptions
label?: string label?: string
/** Widget type (see {@link TWidgetType}) */ /** Widget type (see {@link TWidgetType}) */
type: TWidgetType type: TType
value?: TWidgetValue value?: TValue
/** /**
* Whether the widget value should be serialized on node serialization. * Whether the widget value should be serialized on node serialization.

View File

@@ -1,9 +1,11 @@
import type { IWidget } from "@/types/widgets"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
/** /**
* Base class for widgets that have increment and decrement buttons. * Base class for widgets that have increment and decrement buttons.
*/ */
export abstract class BaseSteppedWidget extends BaseWidget { export abstract class BaseSteppedWidget<TWidget extends IWidget> extends BaseWidget<TWidget> {
/** /**
* Whether the widget can increment its value * Whether the widget can increment its value
* @returns `true` if the widget can increment its value, otherwise `false` * @returns `true` if the widget can increment its value, otherwise `false`

View File

@@ -1,6 +1,6 @@
import type { CanvasPointer, LGraphCanvas, LGraphNode, Size } from "@/litegraph" import type { CanvasPointer, LGraphCanvas, LGraphNode, Size } from "@/litegraph"
import type { CanvasMouseEvent, CanvasPointerEvent } from "@/types/events" import type { CanvasMouseEvent, CanvasPointerEvent } from "@/types/events"
import type { IBaseWidget, IWidget, IWidgetOptions, TWidgetType, TWidgetValue } from "@/types/widgets" import type { IBaseWidget, IWidget } from "@/types/widgets"
import { drawTextInArea } from "@/draw" import { drawTextInArea } from "@/draw"
import { Rectangle } from "@/infrastructure/Rectangle" import { Rectangle } from "@/infrastructure/Rectangle"
@@ -29,7 +29,7 @@ export interface WidgetEventOptions {
canvas: LGraphCanvas canvas: LGraphCanvas
} }
export abstract class BaseWidget implements IBaseWidget { export abstract class BaseWidget<TWidget extends IWidget = IWidget> implements IBaseWidget {
/** From node edge to widget edge */ /** From node edge to widget edge */
static margin = 15 static margin = 15
/** From widget edge to tip of arrow button */ /** From widget edge to tip of arrow button */
@@ -43,10 +43,10 @@ export abstract class BaseWidget implements IBaseWidget {
linkedWidgets?: IWidget[] linkedWidgets?: IWidget[]
name: string name: string
options: IWidgetOptions<unknown> options: TWidget["options"]
label?: string label?: string
type: TWidgetType type: TWidget["type"]
value?: TWidgetValue value: TWidget["value"]
y: number = 0 y: number = 0
last_y?: number last_y?: number
width?: number width?: number
@@ -74,11 +74,12 @@ export abstract class BaseWidget implements IBaseWidget {
computeSize?(width?: number): Size computeSize?(width?: number): Size
onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean
constructor(widget: IBaseWidget) { constructor(widget: TWidget) {
Object.assign(this, widget) Object.assign(this, widget)
this.name = widget.name this.name = widget.name
this.options = widget.options this.options = widget.options
this.type = widget.type this.type = widget.type
this.value = widget.value
} }
get outline_color() { get outline_color() {
@@ -232,7 +233,7 @@ export abstract class BaseWidget implements IBaseWidget {
* @param value The value to set * @param value The value to set
* @param options The options for setting the value * @param options The options for setting the value
*/ */
setValue(value: TWidgetValue, { e, node, canvas }: WidgetEventOptions) { setValue(value: TWidget["value"], { e, node, canvas }: WidgetEventOptions) {
const oldValue = this.value const oldValue = this.value
if (value === this.value) return if (value === this.value) return

View File

@@ -2,16 +2,8 @@ import type { IBooleanWidget } from "@/types/widgets"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class BooleanWidget extends BaseWidget implements IBooleanWidget { export class BooleanWidget extends BaseWidget<IBooleanWidget> implements IBooleanWidget {
// IBooleanWidget properties override type = "toggle" as const
declare type: "toggle"
declare value: boolean
constructor(widget: IBooleanWidget) {
super(widget)
this.type = "toggle"
this.value = widget.value
}
override drawWidget(ctx: CanvasRenderingContext2D, { override drawWidget(ctx: CanvasRenderingContext2D, {
width, width,

View File

@@ -1,18 +1,14 @@
import type { IButtonWidget, IWidgetOptions } from "@/types/widgets" import type { IButtonWidget } from "@/types/widgets"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class ButtonWidget extends BaseWidget implements IButtonWidget { export class ButtonWidget extends BaseWidget<IButtonWidget> implements IButtonWidget {
// IButtonWidget properties override type = "button" as const
declare type: "button" clicked: boolean
declare options: IWidgetOptions<boolean>
declare clicked: boolean
declare value: undefined
constructor(widget: IButtonWidget) { constructor(widget: IButtonWidget) {
super(widget) super(widget)
this.type = "button" this.clicked ??= false
this.clicked = widget.clicked ?? false
} }
/** /**

View File

@@ -1,6 +1,6 @@
import type { WidgetEventOptions } from "./BaseWidget" import type { WidgetEventOptions } from "./BaseWidget"
import type { LGraphNode } from "@/LGraphNode" import type { LGraphNode } from "@/LGraphNode"
import type { IComboWidget, IWidgetOptions } from "@/types/widgets" import type { IComboWidget } from "@/types/widgets"
import { clamp, LiteGraph } from "@/litegraph" import { clamp, LiteGraph } from "@/litegraph"
import { warnDeprecated } from "@/utils/feedback" import { warnDeprecated } from "@/utils/feedback"
@@ -19,12 +19,8 @@ function toArray(values: Values): string[] {
return Array.isArray(values) ? values : Object.keys(values) return Array.isArray(values) ? values : Object.keys(values)
} }
export class ComboWidget extends BaseSteppedWidget implements IComboWidget { export class ComboWidget extends BaseSteppedWidget<IComboWidget> implements IComboWidget {
// IComboWidget properties override type = "combo" as const
declare type: "combo"
declare value: string | number
// @ts-expect-error Workaround for Record<string, string> not being typed in IWidgetOptions
declare options: Omit<IWidgetOptions<string>, "values"> & { values: Values }
override get displayValue() { override get displayValue() {
const { values: rawValues } = this.options const { values: rawValues } = this.options
@@ -38,12 +34,6 @@ export class ComboWidget extends BaseSteppedWidget implements IComboWidget {
return typeof this.value === "number" ? String(this.value) : this.value return typeof this.value === "number" ? String(this.value) : this.value
} }
constructor(widget: IComboWidget) {
super(widget)
this.type = "combo"
this.value = widget.value
}
#getValues(node: LGraphNode): Values { #getValues(node: LGraphNode): Values {
const { values } = this.options const { values } = this.options
if (values == null) throw new Error("[ComboWidget]: values is required") if (values == null) throw new Error("[ComboWidget]: values is required")

View File

@@ -1,21 +1,12 @@
import type { IKnobWidget, IWidgetKnobOptions } from "@/types/widgets" import type { IKnobWidget } from "@/types/widgets"
import { clamp } from "@/litegraph" import { clamp } from "@/litegraph"
import { getWidgetStep } from "@/utils/widget" import { getWidgetStep } from "@/utils/widget"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class KnobWidget extends BaseWidget implements IKnobWidget { export class KnobWidget extends BaseWidget<IKnobWidget> implements IKnobWidget {
declare type: "knob" override type = "knob" as const
declare value: number
declare options: IWidgetKnobOptions
constructor(widget: IKnobWidget) {
super(widget)
this.type = "knob"
this.value = widget.value
this.options = widget.options
}
computedHeight?: number computedHeight?: number
/** /**

View File

@@ -1,15 +1,12 @@
import type { WidgetEventOptions } from "./BaseWidget" import type { WidgetEventOptions } from "./BaseWidget"
import type { INumericWidget, IWidgetOptions } from "@/types/widgets" import type { INumericWidget } from "@/types/widgets"
import { getWidgetStep } from "@/utils/widget" import { getWidgetStep } from "@/utils/widget"
import { BaseSteppedWidget } from "./BaseSteppedWidget" import { BaseSteppedWidget } from "./BaseSteppedWidget"
export class NumberWidget extends BaseSteppedWidget implements INumericWidget { export class NumberWidget extends BaseSteppedWidget<INumericWidget> implements INumericWidget {
// INumberWidget properties override type = "number" as const
declare type: "number"
declare value: number
declare options: IWidgetOptions<number>
override get displayValue() { override get displayValue() {
return Number(this.value).toFixed( return Number(this.value).toFixed(
@@ -19,12 +16,6 @@ export class NumberWidget extends BaseSteppedWidget implements INumericWidget {
) )
} }
constructor(widget: INumericWidget) {
super(widget)
this.type = "number"
this.value = widget.value
}
override canIncrement(): boolean { override canIncrement(): boolean {
const { max } = this.options const { max } = this.options
return max == null || this.value < max return max == null || this.value < max

View File

@@ -1,23 +1,13 @@
import type { ISliderWidget, IWidgetSliderOptions } from "@/types/widgets" import type { ISliderWidget } from "@/types/widgets"
import { clamp } from "@/litegraph" import { clamp } from "@/litegraph"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class SliderWidget extends BaseWidget implements ISliderWidget { export class SliderWidget extends BaseWidget<ISliderWidget> implements ISliderWidget {
// ISliderWidget properties override type = "slider" as const
declare type: "slider"
declare value: number
declare options: IWidgetSliderOptions
marker?: number
constructor(widget: ISliderWidget) { marker?: number
super(widget)
this.type = "slider"
this.value = widget.value
this.options = widget.options
this.marker = widget.marker
}
/** /**
* Draws the widget * Draws the widget

View File

@@ -1,16 +1,11 @@
import type { IStringWidget, IWidgetOptions } from "@/types/widgets" import type { IStringWidget } from "@/types/widgets"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class TextWidget extends BaseWidget implements IStringWidget { export class TextWidget extends BaseWidget<IStringWidget> implements IStringWidget {
// IStringWidget properties
declare type: "text" | "string"
declare value: string
declare options: IWidgetOptions<string>
constructor(widget: IStringWidget) { constructor(widget: IStringWidget) {
super(widget) super(widget)
this.type = widget.type ?? "string" this.type ??= "string"
this.value = widget.value?.toString() ?? "" this.value = widget.value?.toString() ?? ""
} }

View File

@@ -1,4 +1,4 @@
import type { IBaseWidget } from "@/types/widgets" import type { IBaseWidget, IWidget } from "@/types/widgets"
import { BaseWidget } from "./BaseWidget" import { BaseWidget } from "./BaseWidget"
import { BooleanWidget } from "./BooleanWidget" import { BooleanWidget } from "./BooleanWidget"
@@ -9,6 +9,21 @@ import { NumberWidget } from "./NumberWidget"
import { SliderWidget } from "./SliderWidget" import { SliderWidget } from "./SliderWidget"
import { TextWidget } from "./TextWidget" import { TextWidget } from "./TextWidget"
export function toConcreteWidget(widget: IWidget): BaseWidget | undefined {
if (widget instanceof BaseWidget) return widget
switch (widget.type) {
case "button": return new ButtonWidget(widget)
case "toggle": return new BooleanWidget(widget)
case "slider": return new SliderWidget(widget)
case "knob": return new KnobWidget(widget)
case "combo": return new ComboWidget(widget)
case "number": return new NumberWidget(widget)
case "string": return new TextWidget(widget)
case "text": return new TextWidget(widget)
}
}
type WidgetConstructor = { type WidgetConstructor = {
new (plain: IBaseWidget): BaseWidget new (plain: IBaseWidget): BaseWidget
} }