[TS] Use strict nullability in LGraphCanvas (#665)

- Adds runtime null checking
- Converts canvas context to non-nullable
- Adds explicit throws for some edge cases
- Improves TS types
This commit is contained in:
filtered
2025-03-02 09:44:34 +11:00
committed by GitHub
parent 539fa91b0d
commit 6a42484669

View File

@@ -75,9 +75,9 @@ import { toClass } from "./utils/type"
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
interface IShowSearchOptions { interface IShowSearchOptions {
node_to?: LGraphNode node_to?: LGraphNode | null
node_from?: LGraphNode node_from?: LGraphNode | null
slot_from: number | INodeOutputSlot | INodeInputSlot slot_from: number | INodeOutputSlot | INodeInputSlot | null | undefined
type_filter_in?: ISlotType type_filter_in?: ISlotType
type_filter_out?: ISlotType | false type_filter_out?: ISlotType | false
@@ -471,7 +471,7 @@ export class LGraphCanvas implements ConnectionColorContext {
graph: LGraph | null graph: LGraph | null
canvas: HTMLCanvasElement canvas: HTMLCanvasElement
bgcanvas: HTMLCanvasElement bgcanvas: HTMLCanvasElement
ctx?: CanvasRenderingContext2D | null ctx: CanvasRenderingContext2D
_events_binded?: boolean _events_binded?: boolean
_mousedown_callback?(e: PointerEvent): void _mousedown_callback?(e: PointerEvent): void
_mousewheel_callback?(e: WheelEvent): void _mousewheel_callback?(e: WheelEvent): void
@@ -522,11 +522,11 @@ export class LGraphCanvas implements ConnectionColorContext {
/** The start position of the drag zoom. */ /** The start position of the drag zoom. */
#dragZoomStart: { pos: Point, scale: number } | null = null #dragZoomStart: { pos: Point, scale: number } | null = null
getMenuOptions?(): IContextMenuValue[] getMenuOptions?(): IContextMenuValue<string>[]
getExtraMenuOptions?( getExtraMenuOptions?(
canvas: LGraphCanvas, canvas: LGraphCanvas,
options: IContextMenuValue[], options: IContextMenuValue<string>[],
): IContextMenuValue[] ): IContextMenuValue<string>[]
static active_node: LGraphNode static active_node: LGraphNode
/** called before modifying the graph */ /** called before modifying the graph */
onBeforeChange?(graph: LGraph): void onBeforeChange?(graph: LGraph): void
@@ -676,6 +676,7 @@ export class LGraphCanvas implements ConnectionColorContext {
// TypeScript strict workaround: cannot use method to initialize properties. // TypeScript strict workaround: cannot use method to initialize properties.
this.canvas = undefined! this.canvas = undefined!
this.bgcanvas = undefined! this.bgcanvas = undefined!
this.ctx = undefined!
this.setCanvas(canvas, options.skip_events) this.setCanvas(canvas, options.skip_events)
this.clear() this.clear()
@@ -805,10 +806,10 @@ export class LGraphCanvas implements ConnectionColorContext {
} }
static onMenuAdd( static onMenuAdd(
node: LGraphNode, value: unknown,
options: IContextMenuOptions, options: unknown,
e: MouseEvent, e: MouseEvent,
prev_menu: ContextMenu<string>, prev_menu?: ContextMenu<string>,
callback?: (node: LGraphNode | null) => void, callback?: (node: LGraphNode | null) => void,
): boolean | undefined { ): boolean | undefined {
const canvas = LGraphCanvas.active_canvas const canvas = LGraphCanvas.active_canvas
@@ -1663,14 +1664,14 @@ export class LGraphCanvas implements ConnectionColorContext {
this.bgcanvas.width = this.canvas.width this.bgcanvas.width = this.canvas.width
this.bgcanvas.height = this.canvas.height this.bgcanvas.height = this.canvas.height
if (element.getContext == null) { const ctx = element.getContext?.("2d")
if (ctx == null) {
if (element.localName != "canvas") { if (element.localName != "canvas") {
throw `Element supplied for LGraphCanvas must be a <canvas> element, you passed a ${element.localName}` throw `Element supplied for LGraphCanvas must be a <canvas> element, you passed a ${element.localName}`
} }
throw "This browser doesn't support Canvas" throw "This browser doesn't support Canvas"
} }
this.ctx = ctx
this.ctx = element.getContext("2d")
if (!skip_events) this.bindEvents() if (!skip_events) this.bindEvents()
} }
@@ -2050,26 +2051,28 @@ export class LGraphCanvas implements ConnectionColorContext {
// clone node ALT dragging // clone node ALT dragging
if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && !e.ctrlKey && node && this.allow_interaction) { if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && !e.ctrlKey && node && this.allow_interaction) {
const node_data = node.clone()?.serialize() const node_data = node.clone()?.serialize()
const cloned = LiteGraph.createNode(node_data.type) if (node_data?.type != null) {
if (cloned) { const cloned = LiteGraph.createNode(node_data.type)
cloned.configure(node_data) if (cloned) {
cloned.pos[0] += 5 cloned.configure(node_data)
cloned.pos[1] += 5 cloned.pos[0] += 5
cloned.pos[1] += 5
if (this.allow_dragnodes) { if (this.allow_dragnodes) {
pointer.onDragStart = (pointer) => { pointer.onDragStart = (pointer) => {
graph.add(cloned, false) graph.add(cloned, false)
this.#startDraggingItems(cloned, pointer) this.#startDraggingItems(cloned, pointer)
} }
pointer.onDragEnd = e => this.#processDraggedItems(e) pointer.onDragEnd = e => this.#processDraggedItems(e)
} else { } else {
// TODO: Check if before/after change are necessary here. // TODO: Check if before/after change are necessary here.
graph.beforeChange() graph.beforeChange()
graph.add(cloned, false) graph.add(cloned, false)
graph.afterChange() graph.afterChange()
} }
return return
}
} }
} }
@@ -2083,9 +2086,15 @@ export class LGraphCanvas implements ConnectionColorContext {
if (reroute) { if (reroute) {
if (e.shiftKey) { if (e.shiftKey) {
// Connect new link from reroute // Connect new link from reroute
const link = graph._links.get(reroute.linkIds.values().next().value) const linkId = reroute.linkIds.values().next().value
if (linkId == null) return
const link = graph._links.get(linkId)
if (!link) return
const outputNode = graph.getNodeById(link.origin_id) const outputNode = graph.getNodeById(link.origin_id)
if (!outputNode) return
const slot = link.origin_slot const slot = link.origin_slot
const connecting: ConnectingLink = { const connecting: ConnectingLink = {
node: outputNode, node: outputNode,
@@ -2130,6 +2139,9 @@ export class LGraphCanvas implements ConnectionColorContext {
if (e.shiftKey && !e.altKey) { if (e.shiftKey && !e.altKey) {
const slot = linkSegment.origin_slot const slot = linkSegment.origin_slot
if (slot == null) return console.warn("Connecting link from corrupt link segment: `slot` null", linkSegment)
if (linkSegment.origin_id == null) return console.warn("Connecting link from corrupt link segment: `origin_id` null", linkSegment)
const originNode = graph._nodes_by_id[linkSegment.origin_id] const originNode = graph._nodes_by_id[linkSegment.origin_id]
const connecting: ConnectingLink = { const connecting: ConnectingLink = {
@@ -2168,7 +2180,7 @@ export class LGraphCanvas implements ConnectionColorContext {
// Groups // Groups
const group = graph.getGroupOnPos(x, y) const group = graph.getGroupOnPos(x, y)
this.selected_group = group this.selected_group = group ?? null
if (group) { if (group) {
if (group.isInResize(x, y)) { if (group.isInResize(x, y)) {
// Resize group // Resize group
@@ -2186,7 +2198,7 @@ export class LGraphCanvas implements ConnectionColorContext {
eMove.canvasY - group.pos[1] - offsetY, eMove.canvasY - group.pos[1] - offsetY,
] ]
// Unless snapping. // Unless snapping.
snapPoint(pos, this.#snapToGrid) if (this.#snapToGrid) snapPoint(pos, this.#snapToGrid)
const resized = group.resize(pos[0], pos[1]) const resized = group.resize(pos[0], pos[1])
if (resized) this.dirty_bgcanvas = true if (resized) this.dirty_bgcanvas = true
@@ -2301,7 +2313,7 @@ export class LGraphCanvas implements ConnectionColorContext {
eMove.canvasY - node.pos[1] - offsetY, eMove.canvasY - node.pos[1] - offsetY,
] ]
// Unless snapping. // Unless snapping.
snapPoint(pos, this.#snapToGrid) if (this.#snapToGrid) snapPoint(pos, this.#snapToGrid)
const min = node.computeSize() const min = node.computeSize()
pos[0] = Math.max(min[0], pos[0]) pos[0] = Math.max(min[0], pos[0])
@@ -2328,10 +2340,12 @@ export class LGraphCanvas implements ConnectionColorContext {
const link_pos = node.getConnectionPos(false, i) const link_pos = node.getConnectionPos(false, i)
if (isInRectangle(x, y, link_pos[0] - 15, link_pos[1] - 10, 30, 20)) { if (isInRectangle(x, y, link_pos[0] - 15, link_pos[1] - 10, 30, 20)) {
// Drag multiple output links // Drag multiple output links
if (e.shiftKey && output.links?.length > 0) { if (e.shiftKey && output.links?.length) {
this.connecting_links = [] this.connecting_links = []
for (const linkId of output.links) { for (const linkId of output.links) {
const link = graph._links.get(linkId) const link = graph._links.get(linkId)
if (!link) continue
const slot = link.target_slot const slot = link.target_slot
const linked_node = graph._nodes_by_id[link.target_id] const linked_node = graph._nodes_by_id[link.target_id]
const input = linked_node.inputs[slot] const input = linked_node.inputs[slot]
@@ -4079,10 +4093,8 @@ export class LGraphCanvas implements ConnectionColorContext {
drawFrontCanvas(): void { drawFrontCanvas(): void {
this.dirty_canvas = false this.dirty_canvas = false
const ctx = this.ctx const { ctx, canvas } = this
if (!ctx) return
const canvas = this.canvas
// @ts-expect-error // @ts-expect-error
if (ctx.start2D && !this.viewport) { if (ctx.start2D && !this.viewport) {
// @ts-expect-error // @ts-expect-error
@@ -6542,7 +6554,7 @@ export class LGraphCanvas implements ConnectionColorContext {
node: LGraphNode, node: LGraphNode,
property: string, property: string,
options: IDialogOptions, options: IDialogOptions,
): IDialog { ): IDialog | undefined {
if (!node || node.properties[property] === undefined) return if (!node || node.properties[property] === undefined) return
options = options || {} options = options || {}
@@ -7102,8 +7114,8 @@ export class LGraphCanvas implements ConnectionColorContext {
} }
} }
getCanvasMenuOptions(): IContextMenuValue[] { getCanvasMenuOptions(): IContextMenuValue<string>[] {
let options: IContextMenuValue[] = null let options: IContextMenuValue<string>[]
if (this.getMenuOptions) { if (this.getMenuOptions) {
options = this.getMenuOptions() options = this.getMenuOptions()
} else { } else {
@@ -7260,7 +7272,8 @@ export class LGraphCanvas implements ConnectionColorContext {
return options return options
} }
getGroupMenuOptions(group: LGraphGroup): IContextMenuValue[] { /** @deprecated */
getGroupMenuOptions(group: LGraphGroup) {
console.warn("LGraphCanvas.getGroupMenuOptions is deprecated, use LGraphGroup.getMenuOptions instead") console.warn("LGraphCanvas.getGroupMenuOptions is deprecated, use LGraphGroup.getMenuOptions instead")
return group.getMenuOptions() return group.getMenuOptions()
} }
@@ -7369,13 +7382,15 @@ export class LGraphCanvas implements ConnectionColorContext {
) )
const setDirty = () => this.setDirty(true) const setDirty = () => this.setDirty(true)
function inner_option_clicked(v, options) { function inner_option_clicked(v: IContextMenuValue<unknown>, options: IDialogOptions) {
if (!v) return if (!v) return
if (v.content == "Remove Slot") { if (v.content == "Remove Slot") {
if (!node.graph) throw new NullGraphError() if (!node?.graph) throw new NullGraphError()
const info = v.slot const info = v.slot
if (!info) throw new TypeError("Found-slot info was null when processing context menu.")
node.graph.beforeChange() node.graph.beforeChange()
if (info.input) { if (info.input) {
node.removeInput(info.slot) node.removeInput(info.slot)
@@ -7385,9 +7400,11 @@ export class LGraphCanvas implements ConnectionColorContext {
node.graph.afterChange() node.graph.afterChange()
return return
} else if (v.content == "Disconnect Links") { } else if (v.content == "Disconnect Links") {
if (!node.graph) throw new NullGraphError() if (!node?.graph) throw new NullGraphError()
const info = v.slot const info = v.slot
if (!info) throw new TypeError("Found-slot info was null when processing context menu.")
node.graph.beforeChange() node.graph.beforeChange()
if (info.output) { if (info.output) {
node.disconnectOutput(info.slot) node.disconnectOutput(info.slot)
@@ -7397,7 +7414,11 @@ export class LGraphCanvas implements ConnectionColorContext {
node.graph.afterChange() node.graph.afterChange()
return return
} else if (v.content == "Rename Slot") { } else if (v.content == "Rename Slot") {
if (!node) throw new TypeError("`node` was null when processing the context menu.")
const info = v.slot const info = v.slot
if (!info) throw new TypeError("Found-slot info was null when processing context menu.")
const slot_info = info.input const slot_info = info.input
? node.getInputInfo(info.slot) ? node.getInputInfo(info.slot)
: node.getOutputInfo(info.slot) : node.getOutputInfo(info.slot)
@@ -7411,7 +7432,7 @@ export class LGraphCanvas implements ConnectionColorContext {
if (!node.graph) throw new NullGraphError() if (!node.graph) throw new NullGraphError()
node.graph.beforeChange() node.graph.beforeChange()
if (input.value) { if (input?.value) {
if (slot_info) { if (slot_info) {
slot_info.label = input.value slot_info.label = input.value
} }
@@ -7420,7 +7441,9 @@ export class LGraphCanvas implements ConnectionColorContext {
dialog.close() dialog.close()
node.graph.afterChange() node.graph.afterChange()
} }
dialog.querySelector("button").addEventListener("click", inner) dialog.querySelector("button")?.addEventListener("click", inner)
if (!input) throw new TypeError("Input element was null when processing context menu.")
input.addEventListener("keydown", function (e) { input.addEventListener("keydown", function (e) {
dialog.is_modified = true dialog.is_modified = true
if (e.key == "Escape") { if (e.key == "Escape") {