From eed92864f275f0d4f53ff2d99a213639af0cbb42 Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Sun, 27 Oct 2024 16:20:32 -0400 Subject: [PATCH] Enable ts-strict for changeTracker (#1338) * Fix app getter * nit * nit * nit * Fix rest of errors * nit --- src/scripts/changeTracker.ts | 113 +++++++++++++++++++---------------- src/types/apiTypes.ts | 3 +- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/scripts/changeTracker.ts b/src/scripts/changeTracker.ts index 73aded6d8..18376a125 100644 --- a/src/scripts/changeTracker.ts +++ b/src/scripts/changeTracker.ts @@ -1,33 +1,33 @@ -// @ts-strict-ignore import type { ComfyApp } from './app' import { api } from './api' import { clone } from './utils' import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph' import { ComfyWorkflow } from './workflows' import type { ComfyWorkflowJSON } from '@/types/comfyWorkflow' +import { LGraphNode } from '@comfyorg/litegraph' +import { ExecutedWsMessage } from '@/types/apiTypes' +import { useExecutionStore } from '@/stores/executionStore' export class ChangeTracker { static MAX_HISTORY = 50 - #app: ComfyApp - undoQueue = [] - redoQueue = [] + #app?: ComfyApp + undoQueue: ComfyWorkflowJSON[] = [] + redoQueue: ComfyWorkflowJSON[] = [] activeState: ComfyWorkflowJSON | null = null - isOurLoad = false - workflow: ComfyWorkflow | null - changeCount = 0 + isOurLoad: boolean = false + changeCount: number = 0 - ds: { scale: number; offset: [number, number] } - nodeOutputs: any + ds?: { scale: number; offset: [number, number] } + nodeOutputs?: Record - get app() { + get app(): ComfyApp { + // Global tracker has #app set, while other trackers have workflow bounded return this.#app ?? this.workflow.manager.app } - constructor(workflow: ComfyWorkflow) { - this.workflow = workflow - } + constructor(public workflow: ComfyWorkflow) {} - #setApp(app) { + #setApp(app: ComfyApp) { this.#app = app } @@ -70,10 +70,10 @@ export class ChangeTracker { } } - async updateState(source, target) { + async updateState(source: ComfyWorkflowJSON[], target: ComfyWorkflowJSON[]) { const prevState = source.pop() if (prevState) { - target.push(this.activeState) + target.push(this.activeState!) this.isOurLoad = true await this.app.loadGraphData(prevState, false, false, this.workflow, { showMissingModelsDialog: false, @@ -91,7 +91,7 @@ export class ChangeTracker { await this.updateState(this.redoQueue, this.undoQueue) } - async undoRedo(e) { + async undoRedo(e: KeyboardEvent) { if (e.ctrlKey || e.metaKey) { if (e.key === 'y' || e.key == 'Z') { await this.redo() @@ -119,8 +119,8 @@ export class ChangeTracker { globalTracker.#setApp(app) const loadGraphData = app.loadGraphData - app.loadGraphData = async function () { - const v = await loadGraphData.apply(this, arguments) + app.loadGraphData = async function (...args) { + const v = await loadGraphData.apply(this, args) const ct = changeTracker() if (ct.isOurLoad) { ct.isOurLoad = false @@ -140,12 +140,12 @@ export class ChangeTracker { const activeEl = document.activeElement requestAnimationFrame(async () => { - let bindInputEl + let bindInputEl: Element | null = null // If we are auto queue in change mode then we do want to trigger on inputs if (!app.ui.autoQueueEnabled || app.ui.autoQueueMode === 'instant') { if ( activeEl?.tagName === 'INPUT' || - activeEl?.['type'] === 'textarea' + (activeEl && 'type' in activeEl && activeEl.type === 'textarea') ) { // Ignore events on inputs, they have their native history return @@ -195,21 +195,26 @@ export class ChangeTracker { // Handle litegraph clicks const processMouseUp = LGraphCanvas.prototype.processMouseUp LGraphCanvas.prototype.processMouseUp = function (e) { - const v = processMouseUp.apply(this, arguments) + const v = processMouseUp.apply(this, [e]) changeTracker().checkState() return v } const processMouseDown = LGraphCanvas.prototype.processMouseDown LGraphCanvas.prototype.processMouseDown = function (e) { - const v = processMouseDown.apply(this, arguments) + const v = processMouseDown.apply(this, [e]) changeTracker().checkState() return v } // Handle litegraph dialog popup for number/string widgets const prompt = LGraphCanvas.prototype.prompt - LGraphCanvas.prototype.prompt = function (title, value, callback, event) { - const extendedCallback = (v) => { + LGraphCanvas.prototype.prompt = function ( + title: string, + value: any, + callback: (v: any) => void, + event: any + ) { + const extendedCallback = (v: any) => { callback(v) changeTracker().checkState() } @@ -218,16 +223,16 @@ export class ChangeTracker { // Handle litegraph context menu for COMBO widgets const close = LiteGraph.ContextMenu.prototype.close - LiteGraph.ContextMenu.prototype.close = function (e) { - const v = close.apply(this, arguments) + LiteGraph.ContextMenu.prototype.close = function (e: MouseEvent) { + const v = close.apply(this, [e]) changeTracker().checkState() return v } // Detects nodes being added via the node search dialog const onNodeAdded = LiteGraph.LGraph.prototype.onNodeAdded - LiteGraph.LGraph.prototype.onNodeAdded = function () { - const v = onNodeAdded?.apply(this, arguments) + LiteGraph.LGraph.prototype.onNodeAdded = function (node: LGraphNode) { + const v = onNodeAdded?.apply(this, [node]) if (!app?.configuringGraph) { const ct = changeTracker() if (!ct.isOurLoad) { @@ -238,20 +243,24 @@ export class ChangeTracker { } // Handle multiple commands as a single transaction - document.addEventListener('litegraph:canvas', (e: CustomEvent) => { - if (e.detail.subType === 'before-change') { + document.addEventListener('litegraph:canvas', (e: Event) => { + const detail = (e as CustomEvent).detail + if (detail.subType === 'before-change') { changeTracker().beforeChange() - } else if (e.detail.subType === 'after-change') { + } else if (detail.subType === 'after-change') { changeTracker().afterChange() } }) // Store node outputs - api.addEventListener('executed', ({ detail }) => { - const prompt = - app.workflowManager.executionStore.queuedPrompts[detail.prompt_id] - if (!prompt?.workflow) return - const nodeOutputs = (prompt.workflow.changeTracker.nodeOutputs ??= {}) + api.addEventListener('executed', (e: CustomEvent) => { + const detail = e.detail + const workflow = + useExecutionStore().queuedPrompts[detail.prompt_id]?.workflow + const changeTracker = workflow?.changeTracker + if (!changeTracker) return + changeTracker.nodeOutputs ??= {} + const nodeOutputs = changeTracker.nodeOutputs const output = nodeOutputs[detail.node] if (detail.merge && output) { for (const k in detail.output ?? {}) { @@ -268,26 +277,30 @@ export class ChangeTracker { }) } - static bindInput(app, activeEl) { + static bindInput(app: ComfyApp, activeEl: Element | null): boolean { if ( - activeEl && - activeEl.tagName !== 'CANVAS' && - activeEl.tagName !== 'BODY' + !activeEl || + activeEl.tagName === 'CANVAS' || + activeEl.tagName === 'BODY' ) { - for (const evt of ['change', 'input', 'blur']) { - if (`on${evt}` in activeEl) { - const listener = () => { - app.workflowManager.activeWorkflow.changeTracker.checkState() - activeEl.removeEventListener(evt, listener) - } - activeEl.addEventListener(evt, listener) - return true + return false + } + + for (const evt of ['change', 'input', 'blur']) { + const htmlElement = activeEl as HTMLElement + if (`on${evt}` in htmlElement) { + const listener = () => { + app.workflowManager.activeWorkflow?.changeTracker?.checkState() + htmlElement.removeEventListener(evt, listener) } + htmlElement.addEventListener(evt, listener) + return true } } + return false } - static graphEqual(a, b, path = '') { + static graphEqual(a: any, b: any, path = '') { if (a === b) return true if (typeof a == 'object' && a && typeof b == 'object' && b) { diff --git a/src/types/apiTypes.ts b/src/types/apiTypes.ts index fced6cc35..82edba787 100644 --- a/src/types/apiTypes.ts +++ b/src/types/apiTypes.ts @@ -47,7 +47,8 @@ const zExecutingWsMessage = z.object({ }) const zExecutedWsMessage = zExecutingWsMessage.extend({ - outputs: zOutputs + output: zOutputs, + merge: z.boolean().optional() }) const zExecutionWsMessageBase = z.object({