diff --git a/src/extensions/core/contextMenuFilter.ts b/src/extensions/core/contextMenuFilter.ts index 5cb0e98c5..4b166506a 100644 --- a/src/extensions/core/contextMenuFilter.ts +++ b/src/extensions/core/contextMenuFilter.ts @@ -1,179 +1,185 @@ import { + ContextMenu, LGraphCanvas, LiteGraph, isComboWidget } from '@/lib/litegraph/src/litegraph' +import type { + IContextMenuOptions, + IContextMenuValue +} from '@/lib/litegraph/src/litegraph' import { app } from '../../scripts/app' // Adds filtering to combo context menus +class FilteredContextMenu extends ContextMenu { + constructor( + values: readonly (string | IContextMenuValue | null)[], + options: IContextMenuOptions + ) { + super(values, options) + + // If we are a dark menu (only used for combo boxes) then add a filter input + if (options?.className === 'dark' && values?.length > 4) { + const filter = document.createElement('input') + filter.classList.add('comfy-context-menu-filter') + filter.placeholder = 'Filter list' + + this.root.prepend(filter) + + const items = Array.from( + this.root.querySelectorAll('.litemenu-entry') + ) + let displayedItems = [...items] + let itemCount = displayedItems.length + + // We must request an animation frame for the current node of the active canvas to update. + requestAnimationFrame(() => { + const currentNode = LGraphCanvas.active_canvas.current_node + const clickedComboValue = currentNode?.widgets + ?.filter( + (w) => + isComboWidget(w) && w.options.values?.length === values.length + ) + .find((w) => { + const widgetValues = w.options.values + return ( + Array.isArray(widgetValues) && + widgetValues.every((v, i) => v === values[i]) + ) + })?.value + + let selectedIndex = clickedComboValue + ? values.findIndex((v) => v === clickedComboValue) + : 0 + if (selectedIndex < 0) { + selectedIndex = 0 + } + let selectedItem = displayedItems[selectedIndex] + updateSelected() + + // Apply highlighting to the selected item + function updateSelected() { + selectedItem?.style.setProperty('background-color', '') + selectedItem?.style.setProperty('color', '') + selectedItem = displayedItems[selectedIndex] + selectedItem?.style.setProperty( + 'background-color', + '#ccc', + 'important' + ) + selectedItem?.style.setProperty('color', '#000', 'important') + } + + const positionList = () => { + const rect = this.root.getBoundingClientRect() + + // If the top is off-screen then shift the element with scaling applied + if (rect.top < 0) { + const scale = + 1 - + this.root.getBoundingClientRect().height / this.root.clientHeight + + const shift = (this.root.clientHeight * scale) / 2 + + this.root.style.top = -shift + 'px' + } + } + + // Arrow up/down to select items + filter.addEventListener('keydown', (event) => { + switch (event.key) { + case 'ArrowUp': + event.preventDefault() + if (selectedIndex === 0) { + selectedIndex = itemCount - 1 + } else { + selectedIndex-- + } + updateSelected() + break + case 'ArrowRight': + event.preventDefault() + selectedIndex = itemCount - 1 + updateSelected() + break + case 'ArrowDown': + event.preventDefault() + if (selectedIndex === itemCount - 1) { + selectedIndex = 0 + } else { + selectedIndex++ + } + updateSelected() + break + case 'ArrowLeft': + event.preventDefault() + selectedIndex = 0 + updateSelected() + break + case 'Enter': + selectedItem?.click() + break + case 'Escape': + this.close() + break + } + }) + + filter.addEventListener('input', () => { + // Hide all items that don't match our filter + const term = filter.value.toLocaleLowerCase() + // When filtering, recompute which items are visible for arrow up/down and maintain selection. + displayedItems = items.filter((item) => { + const isVisible = + !term || item.textContent?.toLocaleLowerCase().includes(term) + item.style.display = isVisible ? 'block' : 'none' + return isVisible + }) + + selectedIndex = 0 + if (displayedItems.includes(selectedItem)) { + selectedIndex = displayedItems.findIndex((d) => d === selectedItem) + } + itemCount = displayedItems.length + + updateSelected() + + // If we have an event then we can try and position the list under the source + if (options.event) { + let top = options.event.clientY - 10 + + const bodyRect = document.body.getBoundingClientRect() + + const rootRect = this.root.getBoundingClientRect() + if ( + bodyRect.height && + top > bodyRect.height - rootRect.height - 10 + ) { + top = Math.max(0, bodyRect.height - rootRect.height - 10) + } + + this.root.style.top = top + 'px' + positionList() + } + }) + + requestAnimationFrame(() => { + // Focus the filter box when opening + filter.focus() + + positionList() + }) + }) + } + } +} + const ext = { name: 'Comfy.ContextMenuFilter', init() { - const ctxMenu = LiteGraph.ContextMenu - - // @ts-expect-error TODO Very hacky way to modify Litegraph behaviour. Fix ctx later. - LiteGraph.ContextMenu = function (values, options) { - const ctx = new ctxMenu(values, options) - - // If we are a dark menu (only used for combo boxes) then add a filter input - if (options?.className === 'dark' && values?.length > 4) { - const filter = document.createElement('input') - filter.classList.add('comfy-context-menu-filter') - filter.placeholder = 'Filter list' - - ctx.root.prepend(filter) - - const items = Array.from( - ctx.root.querySelectorAll('.litemenu-entry') - ) as HTMLElement[] - let displayedItems = [...items] - let itemCount = displayedItems.length - - // We must request an animation frame for the current node of the active canvas to update. - requestAnimationFrame(() => { - const currentNode = LGraphCanvas.active_canvas.current_node - const clickedComboValue = currentNode?.widgets - ?.filter( - (w) => - isComboWidget(w) && w.options.values?.length === values.length - ) - .find((w) => - // @ts-expect-error Poorly typed; filter above "should" mitigate exceptions - w.options.values?.every((v, i) => v === values[i]) - )?.value - - let selectedIndex = clickedComboValue - ? values.findIndex((v: string) => v === clickedComboValue) - : 0 - if (selectedIndex < 0) { - selectedIndex = 0 - } - let selectedItem = displayedItems[selectedIndex] - updateSelected() - - // Apply highlighting to the selected item - function updateSelected() { - selectedItem?.style.setProperty('background-color', '') - selectedItem?.style.setProperty('color', '') - selectedItem = displayedItems[selectedIndex] - selectedItem?.style.setProperty( - 'background-color', - '#ccc', - 'important' - ) - selectedItem?.style.setProperty('color', '#000', 'important') - } - - const positionList = () => { - const rect = ctx.root.getBoundingClientRect() - - // If the top is off-screen then shift the element with scaling applied - if (rect.top < 0) { - const scale = - 1 - - ctx.root.getBoundingClientRect().height / ctx.root.clientHeight - - const shift = (ctx.root.clientHeight * scale) / 2 - - ctx.root.style.top = -shift + 'px' - } - } - - // Arrow up/down to select items - filter.addEventListener('keydown', (event) => { - switch (event.key) { - case 'ArrowUp': - event.preventDefault() - if (selectedIndex === 0) { - selectedIndex = itemCount - 1 - } else { - selectedIndex-- - } - updateSelected() - break - case 'ArrowRight': - event.preventDefault() - selectedIndex = itemCount - 1 - updateSelected() - break - case 'ArrowDown': - event.preventDefault() - if (selectedIndex === itemCount - 1) { - selectedIndex = 0 - } else { - selectedIndex++ - } - updateSelected() - break - case 'ArrowLeft': - event.preventDefault() - selectedIndex = 0 - updateSelected() - break - case 'Enter': - selectedItem?.click() - break - case 'Escape': - ctx.close() - break - } - }) - - filter.addEventListener('input', () => { - // Hide all items that don't match our filter - const term = filter.value.toLocaleLowerCase() - // When filtering, recompute which items are visible for arrow up/down and maintain selection. - displayedItems = items.filter((item) => { - const isVisible = - !term || item.textContent?.toLocaleLowerCase().includes(term) - item.style.display = isVisible ? 'block' : 'none' - return isVisible - }) - - selectedIndex = 0 - if (displayedItems.includes(selectedItem)) { - selectedIndex = displayedItems.findIndex( - (d) => d === selectedItem - ) - } - itemCount = displayedItems.length - - updateSelected() - - // If we have an event then we can try and position the list under the source - if (options.event) { - let top = options.event.clientY - 10 - - const bodyRect = document.body.getBoundingClientRect() - - const rootRect = ctx.root.getBoundingClientRect() - if ( - bodyRect.height && - top > bodyRect.height - rootRect.height - 10 - ) { - top = Math.max(0, bodyRect.height - rootRect.height - 10) - } - - ctx.root.style.top = top + 'px' - positionList() - } - }) - - requestAnimationFrame(() => { - // Focus the filter box when opening - filter.focus() - - positionList() - }) - }) - } - - return ctx - } - - LiteGraph.ContextMenu.prototype = ctxMenu.prototype + LiteGraph.ContextMenu = FilteredContextMenu } } diff --git a/src/extensions/core/editAttention.ts b/src/extensions/core/editAttention.ts index 119b27758..07ca38d64 100644 --- a/src/extensions/core/editAttention.ts +++ b/src/extensions/core/editAttention.ts @@ -78,11 +78,12 @@ app.registerExtension({ } function editAttention(event: KeyboardEvent) { - // @ts-expect-error Runtime narrowing not impl. - const inputField: HTMLTextAreaElement = event.composedPath()[0] - const delta = parseFloat(editAttentionDelta.value) + const composedPath = event.composedPath() + const target = composedPath[0] + if (!(target instanceof HTMLTextAreaElement)) return - if (inputField.tagName !== 'TEXTAREA') return + const inputField = target + const delta = parseFloat(editAttentionDelta.value) if (!(event.key === 'ArrowUp' || event.key === 'ArrowDown')) return if (!event.ctrlKey && !event.metaKey) return diff --git a/src/extensions/core/groupOptions.ts b/src/extensions/core/groupOptions.ts index 7e3240bcf..c41f42e96 100644 --- a/src/extensions/core/groupOptions.ts +++ b/src/extensions/core/groupOptions.ts @@ -25,11 +25,10 @@ function addNodesToGroup(group: LGraphGroup, items: Iterable) { const ext: ComfyExtension = { name: 'Comfy.GroupOptions', - getCanvasMenuItems(canvas: LGraphCanvas): IContextMenuValue[] { - const items: IContextMenuValue[] = [] + getCanvasMenuItems(canvas: LGraphCanvas): (IContextMenuValue | null)[] { + const items: (IContextMenuValue | null)[] = [] - // @ts-expect-error fixme ts strict error - const group = canvas.graph.getGroupOnPos( + const group = canvas.graph?.getGroupOnPos( canvas.graph_mouse[0], canvas.graph_mouse[1] ) @@ -41,10 +40,8 @@ const ext: ComfyExtension = { callback: () => { const group = new LGraphGroup() addNodesToGroup(group, canvas.selectedItems) - // @ts-expect-error fixme ts strict error - canvas.graph.add(group) - // @ts-expect-error fixme ts strict error - canvas.graph.change() + canvas.graph?.add(group) + canvas.graph?.change() group.recomputeInsideNodes() } @@ -63,8 +60,7 @@ const ext: ComfyExtension = { disabled: !canvas.selectedItems?.size, callback: () => { addNodesToGroup(group, canvas.selectedItems) - // @ts-expect-error fixme ts strict error - canvas.graph.change() + canvas.graph?.change() } }) @@ -73,7 +69,6 @@ const ext: ComfyExtension = { return items } else { // Add a separator between the default options and the group options - // @ts-expect-error fixme ts strict error items.push(null) } @@ -94,8 +89,7 @@ const ext: ComfyExtension = { 'Comfy.GroupSelectedNodes.Padding' ) group.resizeTo(group.children, padding) - // @ts-expect-error fixme ts strict error - canvas.graph.change() + canvas.graph?.change() } }) @@ -103,8 +97,7 @@ const ext: ComfyExtension = { content: 'Select Nodes', callback: () => { canvas.selectNodes(nodesInGroup) - // @ts-expect-error fixme ts strict error - canvas.graph.change() + canvas.graph?.change() canvas.canvas.focus() } }) diff --git a/src/extensions/core/load3d.ts b/src/extensions/core/load3d.ts index 55358d62c..5de1b53a8 100644 --- a/src/extensions/core/load3d.ts +++ b/src/extensions/core/load3d.ts @@ -225,7 +225,6 @@ useExtensionService().registerExtension({ if (!isLoad3dNode(selectedNode)) return ComfyApp.copyToClipspace(selectedNode) - // @ts-expect-error clipspace_return_node is an extension property added at runtime ComfyApp.clipspace_return_node = selectedNode const props = { node: selectedNode } @@ -412,9 +411,8 @@ useExtensionService().registerExtension({ name: 'Comfy.Preview3D', async beforeRegisterNodeDef(_nodeType, nodeData) { - if ('Preview3D' === nodeData.name) { - // @ts-expect-error InputSpec is not typed correctly - nodeData.input.required.image = ['PREVIEW_3D'] + if ('Preview3D' === nodeData.name && nodeData.input?.required) { + nodeData.input.required.image = ['PREVIEW_3D', {}] } }, diff --git a/src/extensions/core/nodeTemplates.ts b/src/extensions/core/nodeTemplates.ts index 040b54526..5e2595490 100644 --- a/src/extensions/core/nodeTemplates.ts +++ b/src/extensions/core/nodeTemplates.ts @@ -32,11 +32,15 @@ import { GroupNodeConfig, GroupNodeHandler } from './groupNode' const id = 'Comfy.NodeTemplates' const file = 'comfy.templates.json' +interface NodeTemplate { + name: string + data: string +} + class ManageTemplates extends ComfyDialog { - // @ts-expect-error fixme ts strict error - templates: any[] + templates: NodeTemplate[] = [] draggedEl: HTMLElement | null - saveVisualCue: number | null + saveVisualCue: ReturnType | null emptyImg: HTMLImageElement importInput: HTMLInputElement @@ -67,8 +71,9 @@ class ManageTemplates extends ComfyDialog { const btns = super.createButtons() btns[0].textContent = 'Close' btns[0].onclick = () => { - // @ts-expect-error fixme ts strict error - clearTimeout(this.saveVisualCue) + if (this.saveVisualCue !== null) { + clearTimeout(this.saveVisualCue) + } this.close() } btns.unshift( @@ -109,14 +114,17 @@ class ManageTemplates extends ComfyDialog { await api.storeUserData(file, templates, { stringify: false }) } catch (error) { console.error(error) - // @ts-expect-error fixme ts strict error - useToastStore().addAlert(error.message) + useToastStore().addAlert( + error instanceof Error ? error.message : String(error) + ) } } async importAll() { - // @ts-expect-error fixme ts strict error - for (const file of this.importInput.files) { + const files = this.importInput.files + if (!files) return + + for (const file of files) { if (file.type === 'application/json' || file.name.endsWith('.json')) { const reader = new FileReader() reader.onload = async () => { @@ -134,8 +142,7 @@ class ManageTemplates extends ComfyDialog { } } - // @ts-expect-error fixme ts strict error - this.importInput.value = null + this.importInput.value = '' this.close() } @@ -158,8 +165,7 @@ class ManageTemplates extends ComfyDialog { 'div', {}, this.templates.flatMap((t, i) => { - // @ts-expect-error fixme ts strict error - let nameInput + let nameInput: HTMLInputElement | undefined return [ $el( 'div', @@ -173,55 +179,56 @@ class ManageTemplates extends ComfyDialog { gap: '5px', backgroundColor: 'var(--comfy-menu-bg)' }, - // @ts-expect-error fixme ts strict error - ondragstart: (e) => { - this.draggedEl = e.currentTarget - e.currentTarget.style.opacity = '0.6' - e.currentTarget.style.border = '1px dashed yellow' - e.dataTransfer.effectAllowed = 'move' - e.dataTransfer.setDragImage(this.emptyImg, 0, 0) + ondragstart: (e: DragEvent) => { + const target = e.currentTarget + if (!(target instanceof HTMLElement)) return + this.draggedEl = target + target.style.opacity = '0.6' + target.style.border = '1px dashed yellow' + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'move' + e.dataTransfer.setDragImage(this.emptyImg, 0, 0) + } }, - // @ts-expect-error fixme ts strict error - ondragend: (e) => { - e.target.style.opacity = '1' - e.currentTarget.style.border = '1px dashed transparent' - e.currentTarget.removeAttribute('draggable') + ondragend: (e: DragEvent) => { + const target = e.currentTarget + if (!(target instanceof HTMLElement)) return + target.style.opacity = '1' + target.style.border = '1px dashed transparent' + target.removeAttribute('draggable') // rearrange the elements this.element .querySelectorAll('.templateManagerRow') - // @ts-expect-error fixme ts strict error - .forEach((el: HTMLElement, i) => { - // @ts-expect-error fixme ts strict error - var prev_i = Number.parseInt(el.dataset.id) + .forEach((el, index) => { + if (!(el instanceof HTMLElement)) return + const prev_i = Number.parseInt(el.dataset.id ?? '0') - if (el == this.draggedEl && prev_i != i) { + if (el === this.draggedEl && prev_i !== index) { this.templates.splice( - i, + index, 0, this.templates.splice(prev_i, 1)[0] ) } - el.dataset.id = i.toString() + el.dataset.id = index.toString() }) this.store() }, - // @ts-expect-error fixme ts strict error - ondragover: (e) => { + ondragover: (e: DragEvent) => { e.preventDefault() - if (e.currentTarget == this.draggedEl) return + const target = e.currentTarget + if (!(target instanceof HTMLElement)) return + if (target === this.draggedEl) return - let rect = e.currentTarget.getBoundingClientRect() + const rect = target.getBoundingClientRect() if (e.clientY > rect.top + rect.height / 2) { - e.currentTarget.parentNode.insertBefore( - this.draggedEl, - e.currentTarget.nextSibling + target.parentNode?.insertBefore( + this.draggedEl!, + target.nextSibling ) } else { - e.currentTarget.parentNode.insertBefore( - this.draggedEl, - e.currentTarget - ) + target.parentNode?.insertBefore(this.draggedEl!, target) } } }, @@ -233,11 +240,18 @@ class ManageTemplates extends ComfyDialog { style: { cursor: 'grab' }, - // @ts-expect-error fixme ts strict error - onmousedown: (e) => { + onmousedown: (e: MouseEvent) => { // enable dragging only from the label - if (e.target.localName == 'label') - e.currentTarget.parentNode.draggable = 'true' + const target = e.target + const currentTarget = e.currentTarget + if ( + target instanceof HTMLElement && + target.localName === 'label' && + currentTarget instanceof HTMLElement && + currentTarget.parentNode instanceof HTMLElement + ) { + currentTarget.parentNode.draggable = true + } } }, [ @@ -248,33 +262,39 @@ class ManageTemplates extends ComfyDialog { transitionProperty: 'background-color', transitionDuration: '0s' }, - // @ts-expect-error fixme ts strict error - onchange: (e) => { - // @ts-expect-error fixme ts strict error - clearTimeout(this.saveVisualCue) - var el = e.target - var row = el.parentNode.parentNode - this.templates[row.dataset.id].name = - el.value.trim() || 'untitled' + onchange: (e: Event) => { + if (this.saveVisualCue !== null) { + clearTimeout(this.saveVisualCue) + } + const el = e.target + if (!(el instanceof HTMLInputElement)) return + const row = el.parentNode?.parentNode + if (!(row instanceof HTMLElement) || !row.dataset.id) + return + const idx = Number.parseInt(row.dataset.id) + this.templates[idx].name = el.value.trim() || 'untitled' this.store() el.style.backgroundColor = 'rgb(40, 95, 40)' el.style.transitionDuration = '0s' - // @ts-expect-error - // In browser env the return value is number. this.saveVisualCue = setTimeout(function () { el.style.transitionDuration = '.7s' el.style.backgroundColor = 'var(--comfy-input-bg)' }, 15) }, - // @ts-expect-error fixme ts strict error - onkeypress: (e) => { - var el = e.target - // @ts-expect-error fixme ts strict error - clearTimeout(this.saveVisualCue) + onkeypress: (e: KeyboardEvent) => { + const el = e.target + if (!(el instanceof HTMLInputElement)) return + if (this.saveVisualCue !== null) { + clearTimeout(this.saveVisualCue) + } el.style.transitionDuration = '0s' el.style.backgroundColor = 'var(--comfy-input-bg)' }, - $: (el) => (nameInput = el) + $: (el) => { + if (el instanceof HTMLInputElement) { + nameInput = el + } + } }) ] ), @@ -286,12 +306,11 @@ class ManageTemplates extends ComfyDialog { fontWeight: 'normal' }, onclick: () => { - const json = JSON.stringify({ templates: [t] }, null, 2) // convert the data to a JSON string + const json = JSON.stringify({ templates: [t] }, null, 2) const blob = new Blob([json], { type: 'application/json' }) - // @ts-expect-error fixme ts strict error - const name = (nameInput.value || t.name) + '.json' + const name = (nameInput?.value || t.name) + '.json' downloadBlob(name, blob) } }), @@ -302,20 +321,23 @@ class ManageTemplates extends ComfyDialog { color: 'red', fontWeight: 'normal' }, - // @ts-expect-error fixme ts strict error - onclick: (e) => { - const item = e.target.parentNode.parentNode - item.parentNode.removeChild(item) - this.templates.splice(item.dataset.id * 1, 1) + onclick: (e: MouseEvent) => { + const target = e.target + if (!(target instanceof HTMLElement)) return + const item = target.parentNode?.parentNode + if (!(item instanceof HTMLElement) || !item.dataset.id) + return + item.parentNode?.removeChild(item) + this.templates.splice(Number.parseInt(item.dataset.id), 1) this.store() // update the rows index, setTimeout ensures that the list is updated - var that = this - setTimeout(function () { - that.element + setTimeout(() => { + this.element .querySelectorAll('.templateManagerRow') - // @ts-expect-error fixme ts strict error - .forEach((el: HTMLElement, i) => { - el.dataset.id = i.toString() + .forEach((el, index) => { + if (el instanceof HTMLElement) { + el.dataset.id = index.toString() + } }) }, 0) } @@ -332,23 +354,24 @@ class ManageTemplates extends ComfyDialog { const manage = new ManageTemplates() -// @ts-expect-error fixme ts strict error -const clipboardAction = async (cb) => { +const clipboardAction = async (cb: () => void | Promise) => { // We use the clipboard functions but dont want to overwrite the current user clipboard // Restore it after we've run our callback const old = localStorage.getItem('litegrapheditor_clipboard') await cb() - // @ts-expect-error fixme ts strict error - localStorage.setItem('litegrapheditor_clipboard', old) + if (old !== null) { + localStorage.setItem('litegrapheditor_clipboard', old) + } else { + localStorage.removeItem('litegrapheditor_clipboard') + } } const ext: ComfyExtension = { name: id, - getCanvasMenuItems(_canvas: LGraphCanvas): IContextMenuValue[] { - const items: IContextMenuValue[] = [] + getCanvasMenuItems(_canvas: LGraphCanvas): (IContextMenuValue | null)[] { + const items: (IContextMenuValue | null)[] = [] - // @ts-expect-error fixme ts strict error items.push(null) items.push({ content: `Save Selected as Template`, @@ -363,8 +386,11 @@ const ext: ComfyExtension = { clipboardAction(() => { app.canvas.copyToClipboard() - let data = localStorage.getItem('litegrapheditor_clipboard') - data = JSON.parse(data || '{}') + const rawData = localStorage.getItem('litegrapheditor_clipboard') + const data = JSON.parse(rawData || '{}') as { + groupNodes?: Record + nodes?: Array<{ type: string }> + } const nodeIds = Object.keys(app.canvas.selected_nodes) for (let i = 0; i < nodeIds.length; i++) { const node = app.canvas.graph?.getNodeById(nodeIds[i]) @@ -374,16 +400,14 @@ const ext: ComfyExtension = { const groupConfig = GroupNodeHandler.getGroupData(node) if (groupConfig) { const groupData = groupConfig.nodeData - // @ts-expect-error if (!data.groupNodes) { - // @ts-expect-error data.groupNodes = {} } if (nodeData == null) throw new TypeError('nodeData is not set') - // @ts-expect-error data.groupNodes[nodeData.name] = groupData - // @ts-expect-error - data.nodes[i].type = nodeData.name + if (data.nodes?.[i]) { + data.nodes[i].type = nodeData.name + } } } @@ -397,7 +421,7 @@ const ext: ComfyExtension = { }) // Map each template to a menu item - const subItems = manage.templates.map((t) => { + const subItems: (IContextMenuValue | null)[] = manage.templates.map((t) => { return { content: t.name, callback: () => { @@ -420,7 +444,6 @@ const ext: ComfyExtension = { } }) - // @ts-expect-error fixme ts strict error subItems.push(null, { content: 'Manage', callback: () => manage.show() diff --git a/src/extensions/core/saveImageExtraOutput.ts b/src/extensions/core/saveImageExtraOutput.ts index f216f31a7..00f69df55 100644 --- a/src/extensions/core/saveImageExtraOutput.ts +++ b/src/extensions/core/saveImageExtraOutput.ts @@ -1,3 +1,4 @@ +import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { applyTextReplacements } from '@/utils/searchAndReplace' import { app } from '../../scripts/app' @@ -25,18 +26,15 @@ app.registerExtension({ if (saveNodeTypes.has(nodeData.name)) { const onNodeCreated = nodeType.prototype.onNodeCreated // When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? // @ts-expect-error fixme ts strict error - onNodeCreated.apply(this, arguments) - : undefined + nodeType.prototype.onNodeCreated = function (this: LGraphNode) { + const r = onNodeCreated?.call(this) - // @ts-expect-error fixme ts strict error - const widget = this.widgets.find((w) => w.name === 'filename_prefix') - // @ts-expect-error fixme ts strict error - widget.serializeValue = () => { - // @ts-expect-error fixme ts strict error - return applyTextReplacements(app.graph, widget.value) + const widget = this.widgets?.find((w) => w.name === 'filename_prefix') + if (widget) { + widget.serializeValue = () => { + const value = typeof widget.value === 'string' ? widget.value : '' + return applyTextReplacements(app.rootGraph, value) + } } return r @@ -44,11 +42,8 @@ app.registerExtension({ } else { // When any other node is created add a property to alias the node const onNodeCreated = nodeType.prototype.onNodeCreated - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated - ? // @ts-expect-error fixme ts strict error - onNodeCreated.apply(this, arguments) - : undefined + nodeType.prototype.onNodeCreated = function (this: LGraphNode) { + const r = onNodeCreated?.call(this) if (!this.properties || !('Node name for S&R' in this.properties)) { this.addProperty('Node name for S&R', this.constructor.type, 'string') diff --git a/src/extensions/core/saveMesh.ts b/src/extensions/core/saveMesh.ts index 9fb33ef94..2ca613eb4 100644 --- a/src/extensions/core/saveMesh.ts +++ b/src/extensions/core/saveMesh.ts @@ -21,9 +21,8 @@ useExtensionService().registerExtension({ name: 'Comfy.SaveGLB', async beforeRegisterNodeDef(_nodeType, nodeData) { - if ('SaveGLB' === nodeData.name) { - // @ts-expect-error InputSpec is not typed correctly - nodeData.input.required.image = ['PREVIEW_3D'] + if ('SaveGLB' === nodeData.name && nodeData.input?.required) { + nodeData.input.required.image = ['PREVIEW_3D', {}] } }, diff --git a/src/extensions/core/slotDefaults.ts b/src/extensions/core/slotDefaults.ts index f7ca293a4..7fa7266ac 100644 --- a/src/extensions/core/slotDefaults.ts +++ b/src/extensions/core/slotDefaults.ts @@ -55,10 +55,11 @@ app.registerExtension({ if (!(lowerType in LiteGraph.registered_slot_in_types)) { LiteGraph.registered_slot_in_types[lowerType] = { nodes: [] } } - LiteGraph.registered_slot_in_types[lowerType].nodes.push( - // @ts-expect-error ComfyNode - nodeType.comfyClass - ) + if ('comfyClass' in nodeType && typeof nodeType.comfyClass === 'string') { + LiteGraph.registered_slot_in_types[lowerType].nodes.push( + nodeType.comfyClass + ) + } } var outputs = nodeData['output'] ?? [] @@ -75,8 +76,11 @@ app.registerExtension({ if (!(type in LiteGraph.registered_slot_out_types)) { LiteGraph.registered_slot_out_types[type] = { nodes: [] } } - // @ts-expect-error ComfyNode - LiteGraph.registered_slot_out_types[type].nodes.push(nodeType.comfyClass) + if ('comfyClass' in nodeType && typeof nodeType.comfyClass === 'string') { + LiteGraph.registered_slot_out_types[type].nodes.push( + nodeType.comfyClass + ) + } if (!LiteGraph.slot_types_out.includes(type)) { LiteGraph.slot_types_out.push(type) diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index 71c2295d1..75e45514d 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -47,10 +47,9 @@ async function uploadFile( let path = data.name if (data.subfolder) path = data.subfolder + '/' + path - // @ts-expect-error fixme ts strict error - if (!audioWidget.options.values.includes(path)) { - // @ts-expect-error fixme ts strict error - audioWidget.options.values.push(path) + const values = audioWidget.options.values + if (Array.isArray(values) && !values.includes(path)) { + values.push(path) } if (updateNode) { @@ -66,8 +65,9 @@ async function uploadFile( useToastStore().addAlert(resp.status + ' - ' + resp.statusText) } } catch (error) { - // @ts-expect-error fixme ts strict error - useToastStore().addAlert(error) + useToastStore().addAlert( + error instanceof Error ? error.message : String(error) + ) } } @@ -83,13 +83,11 @@ app.registerExtension({ 'PreviewAudio', 'SaveAudioMP3', 'SaveAudioOpus' - ].includes( - // @ts-expect-error fixme ts strict error - nodeType.prototype.comfyClass - ) + ].includes(nodeType.prototype.comfyClass ?? '') ) { - // @ts-expect-error fixme ts strict error - nodeData.input.required.audioUI = ['AUDIO_UI', {}] + if (nodeData.input?.required) { + nodeData.input.required.audioUI = ['AUDIO_UI', {}] + } } }, getCustomWidgets() { @@ -113,8 +111,7 @@ app.registerExtension({ // Populate the audio widget UI on node execution. const onExecuted = node.onExecuted node.onExecuted = function (message: any) { - // @ts-expect-error fixme ts strict error - onExecuted?.apply(this, arguments) + onExecuted?.call(this, message) const audios = message.audio if (!audios) return const audio = audios[0] @@ -145,10 +142,10 @@ app.registerExtension({ const node = getNodeByLocatorId(app.rootGraph, nodeLocatorId) if (!node) continue - // @ts-expect-error fixme ts strict error - const audioUIWidget = node.widgets.find( + const audioUIWidget = node.widgets?.find( (w) => w.name === 'audioUI' - ) as unknown as DOMWidget + ) as DOMWidget | undefined + if (!audioUIWidget) continue const audio = output.audio[0] audioUIWidget.element.src = api.apiURL( getResourceURL(audio.subfolder, audio.filename, audio.type) @@ -170,14 +167,16 @@ app.registerExtension({ return { AUDIOUPLOAD(node, inputName: string) { // The widget that allows user to select file. - // @ts-expect-error fixme ts strict error - const audioWidget = node.widgets.find( - (w) => w.name === 'audio' - ) as IStringWidget - // @ts-expect-error fixme ts strict error - const audioUIWidget = node.widgets.find( + const audioWidget = node.widgets?.find((w) => w.name === 'audio') as + | IStringWidget + | undefined + const audioUIWidget = node.widgets?.find( (w) => w.name === 'audioUI' - ) as unknown as DOMWidget + ) as DOMWidget | undefined + + if (!audioWidget || !audioUIWidget) { + throw new Error('Required audio widgets not found') + } audioUIWidget.options.canvasOnly = true const onAudioWidgetUpdate = () => { @@ -195,8 +194,7 @@ app.registerExtension({ // Load saved audio file widget values if restoring from workflow const onGraphConfigured = node.onGraphConfigured node.onGraphConfigured = function () { - // @ts-expect-error fixme ts strict error - onGraphConfigured?.apply(this, arguments) + onGraphConfigured?.call(this) if (audioWidget.value) { onAudioWidgetUpdate() } diff --git a/src/scripts/app.ts b/src/scripts/app.ts index edcc64517..454dabc5b 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -149,7 +149,7 @@ export class ComfyApp { static clipspace_invalidate_handler: (() => void) | null = null static open_maskeditor: (() => void) | null = null static maskeditor_is_opended: (() => void) | null = null - static clipspace_return_node = null + static clipspace_return_node: LGraphNode | null = null vueAppReady: boolean api: ComfyApi