[Refactor] Convert to generic CustomEventTarget (#983)

Converts LinkConnector event target to a generic class.

Subgraph pre-requisite.
This commit is contained in:
filtered
2025-04-29 08:19:26 +10:00
committed by GitHub
parent be92f5bdbb
commit 64d1225037
11 changed files with 139 additions and 135 deletions

View File

@@ -1,5 +1,6 @@
import type { RenderLink } from "./RenderLink"
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeOutputSlot, LinkNetwork } from "@/interfaces"
import type { INodeInputSlot } from "@/interfaces"
import type { Point } from "@/interfaces"
@@ -113,7 +114,7 @@ export class FloatingRenderLink implements RenderLink {
return true
}
connectToInput(node: LGraphNode, input: INodeInputSlot, _events?: LinkConnectorEventTarget): void {
connectToInput(node: LGraphNode, input: INodeInputSlot, _events?: CustomEventTarget<LinkConnectorEventMap>): void {
const floatingLink = this.link
floatingLink.target_id = node.id
floatingLink.target_slot = node.inputs.indexOf(input)
@@ -125,7 +126,7 @@ export class FloatingRenderLink implements RenderLink {
input._floatingLinks.add(floatingLink)
}
connectToOutput(node: LGraphNode, output: INodeOutputSlot, _events?: LinkConnectorEventTarget): void {
connectToOutput(node: LGraphNode, output: INodeOutputSlot, _events?: CustomEventTarget<LinkConnectorEventMap>): void {
const floatingLink = this.link
floatingLink.origin_id = node.id
floatingLink.origin_slot = node.outputs.indexOf(output)
@@ -138,7 +139,7 @@ export class FloatingRenderLink implements RenderLink {
connectToRerouteInput(
reroute: Reroute,
{ node: inputNode, input }: { node: LGraphNode, input: INodeInputSlot },
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
) {
const floatingLink = this.link
floatingLink.target_id = inputNode.id
@@ -155,7 +156,7 @@ export class FloatingRenderLink implements RenderLink {
reroute: Reroute,
outputNode: LGraphNode,
output: INodeOutputSlot,
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
) {
const floatingLink = this.link
floatingLink.origin_id = outputNode.id

View File

@@ -6,7 +6,8 @@ import type { Reroute } from "@/Reroute"
import type { CanvasPointerEvent } from "@/types/events"
import type { IWidget } from "@/types/widgets"
import { LinkConnectorEventMap, LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import { LLink } from "@/LLink"
import { LinkDirection } from "@/types/globalEnums"
@@ -67,7 +68,7 @@ export class LinkConnector {
snapLinksPos: undefined,
}
readonly events = new LinkConnectorEventTarget()
readonly events = new CustomEventTarget<LinkConnectorEventMap>()
/** Contains information for rendering purposes only. */
readonly renderLinks: RenderLinkUnion[] = []

View File

@@ -1,4 +1,5 @@
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { LLink } from "@/LLink"
@@ -39,7 +40,7 @@ export class MovingInputLink extends MovingLinkBase {
return reroute.origin_id !== this.inputNode.id
}
connectToInput(inputNode: LGraphNode, input: INodeInputSlot, events: LinkConnectorEventTarget): LLink | null | undefined {
connectToInput(inputNode: LGraphNode, input: INodeInputSlot, events: CustomEventTarget<LinkConnectorEventMap>): LLink | null | undefined {
if (input === this.inputSlot) return
this.inputNode.disconnectInput(this.inputIndex, true)
@@ -55,7 +56,7 @@ export class MovingInputLink extends MovingLinkBase {
connectToRerouteInput(
reroute: Reroute,
{ node: inputNode, input, link: existingLink }: { node: LGraphNode, input: INodeInputSlot, link: LLink },
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
originalReroutes: Reroute[],
): void {
const { outputNode, outputSlot, fromReroute } = this

View File

@@ -1,5 +1,6 @@
import type { RenderLink } from "./RenderLink"
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode, NodeId } from "@/LGraphNode"
import type { LLink } from "@/LLink"
@@ -79,10 +80,10 @@ export abstract class MovingLinkBase implements RenderLink {
this.inputPos = inputNode.getInputPos(inputIndex)
}
abstract connectToInput(node: LGraphNode, input: INodeInputSlot, events?: LinkConnectorEventTarget): void
abstract connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: LinkConnectorEventTarget): void
abstract connectToRerouteInput(reroute: Reroute, { node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, events: LinkConnectorEventTarget, originalReroutes: Reroute[]): void
abstract connectToRerouteOutput(reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget): void
abstract connectToInput(node: LGraphNode, input: INodeInputSlot, events?: CustomEventTarget<LinkConnectorEventMap>): void
abstract connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: CustomEventTarget<LinkConnectorEventMap>): void
abstract connectToRerouteInput(reroute: Reroute, { node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink }, events: CustomEventTarget<LinkConnectorEventMap>, originalReroutes: Reroute[]): void
abstract connectToRerouteOutput(reroute: Reroute, outputNode: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget<LinkConnectorEventMap>): void
abstract disconnect(): boolean
}

View File

@@ -1,4 +1,5 @@
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { LLink } from "@/LLink"
@@ -43,7 +44,7 @@ export class MovingOutputLink extends MovingLinkBase {
throw new Error("MovingOutputLink cannot connect to an input.")
}
connectToOutput(outputNode: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget): LLink | null | undefined {
connectToOutput(outputNode: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget<LinkConnectorEventMap>): LLink | null | undefined {
if (output === this.outputSlot) return
const link = outputNode.connectSlots(output, this.inputNode, this.inputSlot, this.link.parentId)
@@ -59,7 +60,7 @@ export class MovingOutputLink extends MovingLinkBase {
reroute: Reroute,
outputNode: LGraphNode,
output: INodeOutputSlot,
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
): void {
// Moving output side of links
const { inputNode, inputSlot, fromReroute } = this

View File

@@ -1,4 +1,5 @@
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { INodeInputSlot, INodeOutputSlot, LLink, Reroute } from "@/litegraph"
@@ -25,13 +26,13 @@ export interface RenderLink {
/** The reroute that the link is being connected from. */
readonly fromReroute?: Reroute
connectToInput(node: LGraphNode, input: INodeInputSlot, events?: LinkConnectorEventTarget): void
connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: LinkConnectorEventTarget): void
connectToInput(node: LGraphNode, input: INodeInputSlot, events?: CustomEventTarget<LinkConnectorEventMap>): void
connectToOutput(node: LGraphNode, output: INodeOutputSlot, events?: CustomEventTarget<LinkConnectorEventMap>): void
connectToRerouteInput(
reroute: Reroute,
{ node, input, link }: { node: LGraphNode, input: INodeInputSlot, link: LLink },
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
originalReroutes: Reroute[],
): void
@@ -39,6 +40,6 @@ export interface RenderLink {
reroute: Reroute,
outputNode: LGraphNode,
output: INodeOutputSlot,
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
): void
}

View File

@@ -1,5 +1,6 @@
import type { RenderLink } from "./RenderLink"
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { LLink } from "@/LLink"
@@ -39,7 +40,7 @@ export class ToInputRenderLink implements RenderLink {
return false
}
connectToInput(node: LGraphNode, input: INodeInputSlot, events: LinkConnectorEventTarget) {
connectToInput(node: LGraphNode, input: INodeInputSlot, events: CustomEventTarget<LinkConnectorEventMap>) {
const { node: outputNode, fromSlot, fromReroute } = this
if (node === outputNode) return
@@ -54,7 +55,7 @@ export class ToInputRenderLink implements RenderLink {
input,
link,
}: { node: LGraphNode, input: INodeInputSlot, link: LLink },
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
originalReroutes: Reroute[],
) {
const { node: outputNode, fromSlot, fromReroute } = this

View File

@@ -1,5 +1,6 @@
import type { RenderLink } from "./RenderLink"
import type { LinkConnectorEventTarget } from "@/infrastructure/LinkConnectorEventTarget"
import type { CustomEventTarget } from "@/infrastructure/CustomEventTarget"
import type { LinkConnectorEventMap } from "@/infrastructure/LinkConnectorEventMap"
import type { INodeInputSlot, INodeOutputSlot, LinkNetwork, Point } from "@/interfaces"
import type { LGraphNode } from "@/LGraphNode"
import type { Reroute } from "@/Reroute"
@@ -43,7 +44,7 @@ export class ToOutputRenderLink implements RenderLink {
return true
}
connectToOutput(node: LGraphNode, output: INodeOutputSlot, events: LinkConnectorEventTarget) {
connectToOutput(node: LGraphNode, output: INodeOutputSlot, events: CustomEventTarget<LinkConnectorEventMap>) {
const { node: inputNode, fromSlot, fromReroute } = this
if (!inputNode) return
@@ -55,7 +56,7 @@ export class ToOutputRenderLink implements RenderLink {
reroute: Reroute,
outputNode: LGraphNode,
output: INodeOutputSlot,
events: LinkConnectorEventTarget,
events: CustomEventTarget<LinkConnectorEventMap>,
): void {
const { node: inputNode, fromSlot } = this
const newLink = outputNode.connectSlots(output, inputNode, fromSlot, reroute?.id)

View File

@@ -0,0 +1,55 @@
/** {@link Omit} all properties that evaluate to `never`. */
type NeverNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K]
}
/** {@link Pick} only properties that evaluate to `never`. */
type PickNevers<T> = {
[K in keyof T as T[K] extends never ? K : never]: T[K]
}
type EventListeners<T> = {
readonly [K in keyof T]: ((this: EventTarget, ev: CustomEvent<T[K]>) => any) | EventListenerObject | null
}
export class CustomEventTarget<
EventMap extends Record<Keys, unknown>,
Keys extends keyof EventMap & string = keyof EventMap & string,
> extends EventTarget {
/**
* Type-safe event dispatching.
* @see {@link EventTarget.dispatchEvent}
* @param type Name of the event to dispatch
* @param detail A custom object to send with the event
* @returns `true` if the event was dispatched successfully, otherwise `false`.
*/
dispatch<T extends keyof NeverNever<EventMap>>(type: T, detail: EventMap[T]): boolean
dispatch<T extends keyof PickNevers<EventMap>>(type: T): boolean
dispatch<T extends keyof EventMap>(type: T, detail?: EventMap[T]) {
const event = new CustomEvent(type as string, { detail, cancelable: true })
return super.dispatchEvent(event)
}
override addEventListener<K extends Keys>(
type: K,
listener: EventListeners<EventMap>[K],
options?: boolean | AddEventListenerOptions,
): void {
// Assertion: Contravariance on CustomEvent => Event
super.addEventListener(type as string, listener as EventListener, options)
}
override removeEventListener<K extends Keys>(
type: K,
listener: EventListeners<EventMap>[K],
options?: boolean | EventListenerOptions,
): void {
// Assertion: Contravariance on CustomEvent => Event
super.removeEventListener(type as string, listener as EventListener, options)
}
/** @deprecated Use {@link dispatch}. */
override dispatchEvent(event: never): boolean {
return super.dispatchEvent(event)
}
}

View File

@@ -0,0 +1,47 @@
import type { FloatingRenderLink } from "@/canvas/FloatingRenderLink"
import type { MovingInputLink } from "@/canvas/MovingInputLink"
import type { MovingOutputLink } from "@/canvas/MovingOutputLink"
import type { RenderLink } from "@/canvas/RenderLink"
import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink"
import type { LGraphNode } from "@/LGraphNode"
import type { LLink } from "@/LLink"
import type { Reroute } from "@/Reroute"
import type { CanvasPointerEvent } from "@/types/events"
import type { IWidget } from "@/types/widgets"
export interface LinkConnectorEventMap {
"reset": boolean
"before-drop-links": {
renderLinks: RenderLink[]
event: CanvasPointerEvent
}
"after-drop-links": {
renderLinks: RenderLink[]
event: CanvasPointerEvent
}
"before-move-input": MovingInputLink | FloatingRenderLink
"before-move-output": MovingOutputLink | FloatingRenderLink
"input-moved": MovingInputLink | FloatingRenderLink
"output-moved": MovingOutputLink | FloatingRenderLink
"link-created": LLink | null | undefined
"dropped-on-reroute": {
reroute: Reroute
event: CanvasPointerEvent
}
"dropped-on-node": {
node: LGraphNode
event: CanvasPointerEvent
}
"dropped-on-canvas": CanvasPointerEvent
"dropped-on-widget": {
link: ToInputRenderLink
node: LGraphNode
widget: IWidget
}
}

View File

@@ -1,106 +0,0 @@
import type { FloatingRenderLink } from "@/canvas/FloatingRenderLink"
import type { MovingInputLink } from "@/canvas/MovingInputLink"
import type { MovingOutputLink } from "@/canvas/MovingOutputLink"
import type { RenderLink } from "@/canvas/RenderLink"
import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink"
import type { LGraphNode } from "@/LGraphNode"
import type { LLink } from "@/LLink"
import type { Reroute } from "@/Reroute"
import type { CanvasPointerEvent } from "@/types/events"
import type { IWidget } from "@/types/widgets"
export interface LinkConnectorEventMap {
"reset": boolean
"before-drop-links": {
renderLinks: RenderLink[]
event: CanvasPointerEvent
}
"after-drop-links": {
renderLinks: RenderLink[]
event: CanvasPointerEvent
}
"before-move-input": MovingInputLink | FloatingRenderLink
"before-move-output": MovingOutputLink | FloatingRenderLink
"input-moved": MovingInputLink | FloatingRenderLink
"output-moved": MovingOutputLink | FloatingRenderLink
"link-created": LLink | null | undefined
"dropped-on-reroute": {
reroute: Reroute
event: CanvasPointerEvent
}
"dropped-on-node": {
node: LGraphNode
event: CanvasPointerEvent
}
"dropped-on-canvas": CanvasPointerEvent
"dropped-on-widget": {
link: ToInputRenderLink
node: LGraphNode
widget: IWidget
}
}
/** {@link Omit} all properties that evaluate to `never`. */
type NeverNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K]
}
/** {@link Pick} only properties that evaluate to `never`. */
type PickNevers<T> = {
[K in keyof T as T[K] extends never ? K : never]: T[K]
}
type LinkConnectorEventListeners = {
readonly [K in keyof LinkConnectorEventMap]: ((this: EventTarget, ev: CustomEvent<LinkConnectorEventMap[K]>) => any) | EventListenerObject | null
}
/** Events that _do not_ pass a {@link CustomEvent} `detail` object. */
type SimpleEvents = keyof PickNevers<LinkConnectorEventMap>
/** Events that pass a {@link CustomEvent} `detail` object. */
type ComplexEvents = keyof NeverNever<LinkConnectorEventMap>
export class LinkConnectorEventTarget extends EventTarget {
/**
* Type-safe event dispatching.
* @see {@link EventTarget.dispatchEvent}
* @param type Name of the event to dispatch
* @param detail A custom object to send with the event
* @returns `true` if the event was dispatched successfully, otherwise `false`.
*/
dispatch<T extends ComplexEvents>(type: T, detail: LinkConnectorEventMap[T]): boolean
dispatch<T extends SimpleEvents>(type: T): boolean
dispatch<T extends keyof LinkConnectorEventMap>(type: T, detail?: LinkConnectorEventMap[T]) {
const event = new CustomEvent(type, { detail, cancelable: true })
return super.dispatchEvent(event)
}
override addEventListener<K extends keyof LinkConnectorEventMap>(
type: K,
listener: LinkConnectorEventListeners[K],
options?: boolean | AddEventListenerOptions,
): void {
// Assertion: Contravariance on CustomEvent => Event
super.addEventListener(type, listener as EventListener, options)
}
override removeEventListener<K extends keyof LinkConnectorEventMap>(
type: K,
listener: LinkConnectorEventListeners[K],
options?: boolean | EventListenerOptions,
): void {
// Assertion: Contravariance on CustomEvent => Event
super.removeEventListener(type, listener as EventListener, options)
}
/** @deprecated Use {@link dispatch}. */
override dispatchEvent(event: never): boolean {
return super.dispatchEvent(event)
}
}