[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"
import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange"
import { findFirstNode, getAllNestedItems } from "./utils/collections"
import { toClass } from "./utils/type"
import { BaseWidget } from "./widgets/BaseWidget"
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
import { toConcreteWidget } from "./widgets/widgetMap"
interface IShowSearchOptions {
node_to?: LGraphNode | null
@@ -2397,9 +2396,8 @@ export class LGraphCanvas {
const x = pos[0] - node.pos[0]
const y = pos[1] - node.pos[1]
const WidgetClass = WIDGET_TYPE_MAP[widget.type]
if (WidgetClass) {
const widgetInstance = toClass(WidgetClass, widget)
const widgetInstance = toConcreteWidget(widget)
if (widgetInstance) {
pointer.onClick = () => widgetInstance.onClick({
e,
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. */
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. */
export type SharedIntersection<T1, T2> = {
[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 { 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
off?: string
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. */
socketless?: boolean
values?: TValue[]
values?: TValues
callback?: IWidget["callback"]
}
@@ -60,71 +60,61 @@ export type IWidget =
| IBooleanWidget
| INumericWidget
| IStringWidget
| IMultilineStringWidget
| IComboWidget
| ICustomWidget
| ISliderWidget
| IButtonWidget
| IKnobWidget
export interface IBooleanWidget extends IBaseWidget {
export interface IBooleanWidget extends IBaseWidget<boolean, "toggle", IWidgetOptions<boolean>> {
type: "toggle"
value: boolean
}
/** Any widget that uses a numeric backing */
export interface INumericWidget extends IBaseWidget {
export interface INumericWidget extends IBaseWidget<number, "number", IWidgetOptions<number>> {
type: "number"
value: number
}
export interface ISliderWidget extends IBaseWidget {
export interface ISliderWidget extends IBaseWidget<number, "slider", IWidgetSliderOptions> {
type: "slider"
value: number
options: IWidgetSliderOptions
marker?: number
}
export interface IKnobWidget extends IBaseWidget {
export interface IKnobWidget extends IBaseWidget<number, "knob", IWidgetKnobOptions> {
type: "knob"
value: number
options: IWidgetKnobOptions
}
type ComboWidgetValues = string[] | Record<string, string> | ((widget?: IComboWidget, node?: LGraphNode) => string[])
/** 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"
value: string | number
options: IWidgetOptions<string>
}
export type IStringWidgetType = IStringWidget["type"] | IMultilineStringWidget["type"]
/** A widget with a string value */
export interface IStringWidget extends IBaseWidget {
export interface IStringWidget extends IBaseWidget<string, "string" | "text", IWidgetOptions<string>> {
type: "string" | "text"
value: string
}
export interface IButtonWidget extends IBaseWidget {
export interface IButtonWidget extends IBaseWidget<undefined, "button", IWidgetOptions<undefined>> {
type: "button"
value: undefined
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 */
export interface ICustomWidget extends IBaseWidget {
export interface ICustomWidget extends IBaseWidget<string | object, "custom", IWidgetOptions<string | object>> {
type: "custom"
value: string | object
}
@@ -141,16 +131,16 @@ export type TWidgetValue = IWidget["value"]
* The base type for all widgets. Should not be implemented directly.
* @see IWidget
*/
export interface IBaseWidget {
export interface IBaseWidget<TValue = unknown, TType = string, TOptions = unknown> {
linkedWidgets?: IWidget[]
name: string
options: IWidgetOptions
options: TOptions
label?: string
/** Widget type (see {@link TWidgetType}) */
type: TWidgetType
value?: TWidgetValue
type: TType
value?: TValue
/**
* 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"
/**
* 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
* @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 { 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 { Rectangle } from "@/infrastructure/Rectangle"
@@ -29,7 +29,7 @@ export interface WidgetEventOptions {
canvas: LGraphCanvas
}
export abstract class BaseWidget implements IBaseWidget {
export abstract class BaseWidget<TWidget extends IWidget = IWidget> implements IBaseWidget {
/** From node edge to widget edge */
static margin = 15
/** From widget edge to tip of arrow button */
@@ -43,10 +43,10 @@ export abstract class BaseWidget implements IBaseWidget {
linkedWidgets?: IWidget[]
name: string
options: IWidgetOptions<unknown>
options: TWidget["options"]
label?: string
type: TWidgetType
value?: TWidgetValue
type: TWidget["type"]
value: TWidget["value"]
y: number = 0
last_y?: number
width?: number
@@ -74,11 +74,12 @@ export abstract class BaseWidget implements IBaseWidget {
computeSize?(width?: number): Size
onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean
constructor(widget: IBaseWidget) {
constructor(widget: TWidget) {
Object.assign(this, widget)
this.name = widget.name
this.options = widget.options
this.type = widget.type
this.value = widget.value
}
get outline_color() {
@@ -232,7 +233,7 @@ export abstract class BaseWidget implements IBaseWidget {
* @param value The value to set
* @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
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"
export class BooleanWidget extends BaseWidget implements IBooleanWidget {
// IBooleanWidget properties
declare type: "toggle"
declare value: boolean
constructor(widget: IBooleanWidget) {
super(widget)
this.type = "toggle"
this.value = widget.value
}
export class BooleanWidget extends BaseWidget<IBooleanWidget> implements IBooleanWidget {
override type = "toggle" as const
override drawWidget(ctx: CanvasRenderingContext2D, {
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"
export class ButtonWidget extends BaseWidget implements IButtonWidget {
// IButtonWidget properties
declare type: "button"
declare options: IWidgetOptions<boolean>
declare clicked: boolean
declare value: undefined
export class ButtonWidget extends BaseWidget<IButtonWidget> implements IButtonWidget {
override type = "button" as const
clicked: boolean
constructor(widget: IButtonWidget) {
super(widget)
this.type = "button"
this.clicked = widget.clicked ?? false
this.clicked ??= false
}
/**

View File

@@ -1,6 +1,6 @@
import type { WidgetEventOptions } from "./BaseWidget"
import type { LGraphNode } from "@/LGraphNode"
import type { IComboWidget, IWidgetOptions } from "@/types/widgets"
import type { IComboWidget } from "@/types/widgets"
import { clamp, LiteGraph } from "@/litegraph"
import { warnDeprecated } from "@/utils/feedback"
@@ -19,12 +19,8 @@ function toArray(values: Values): string[] {
return Array.isArray(values) ? values : Object.keys(values)
}
export class ComboWidget extends BaseSteppedWidget implements IComboWidget {
// IComboWidget properties
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 }
export class ComboWidget extends BaseSteppedWidget<IComboWidget> implements IComboWidget {
override type = "combo" as const
override get displayValue() {
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
}
constructor(widget: IComboWidget) {
super(widget)
this.type = "combo"
this.value = widget.value
}
#getValues(node: LGraphNode): Values {
const { values } = this.options
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 { getWidgetStep } from "@/utils/widget"
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class KnobWidget extends BaseWidget implements IKnobWidget {
declare type: "knob"
declare value: number
declare options: IWidgetKnobOptions
constructor(widget: IKnobWidget) {
super(widget)
this.type = "knob"
this.value = widget.value
this.options = widget.options
}
export class KnobWidget extends BaseWidget<IKnobWidget> implements IKnobWidget {
override type = "knob" as const
computedHeight?: number
/**

View File

@@ -1,15 +1,12 @@
import type { WidgetEventOptions } from "./BaseWidget"
import type { INumericWidget, IWidgetOptions } from "@/types/widgets"
import type { INumericWidget } from "@/types/widgets"
import { getWidgetStep } from "@/utils/widget"
import { BaseSteppedWidget } from "./BaseSteppedWidget"
export class NumberWidget extends BaseSteppedWidget implements INumericWidget {
// INumberWidget properties
declare type: "number"
declare value: number
declare options: IWidgetOptions<number>
export class NumberWidget extends BaseSteppedWidget<INumericWidget> implements INumericWidget {
override type = "number" as const
override get displayValue() {
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 {
const { max } = this.options
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 { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
export class SliderWidget extends BaseWidget implements ISliderWidget {
// ISliderWidget properties
declare type: "slider"
declare value: number
declare options: IWidgetSliderOptions
marker?: number
export class SliderWidget extends BaseWidget<ISliderWidget> implements ISliderWidget {
override type = "slider" as const
constructor(widget: ISliderWidget) {
super(widget)
this.type = "slider"
this.value = widget.value
this.options = widget.options
this.marker = widget.marker
}
marker?: number
/**
* 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"
export class TextWidget extends BaseWidget implements IStringWidget {
// IStringWidget properties
declare type: "text" | "string"
declare value: string
declare options: IWidgetOptions<string>
export class TextWidget extends BaseWidget<IStringWidget> implements IStringWidget {
constructor(widget: IStringWidget) {
super(widget)
this.type = widget.type ?? "string"
this.type ??= "string"
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 { BooleanWidget } from "./BooleanWidget"
@@ -9,6 +9,21 @@ import { NumberWidget } from "./NumberWidget"
import { SliderWidget } from "./SliderWidget"
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 = {
new (plain: IBaseWidget): BaseWidget
}