fix(types): remove @ts-expect-error suppressions from src/extensions/core

This commit is contained in:
DrJKL
2026-01-11 16:40:47 -08:00
parent 3ca02ad67c
commit 7b69b404be
10 changed files with 348 additions and 331 deletions

View File

@@ -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<TValue = unknown> extends ContextMenu<TValue> {
constructor(
values: readonly (string | IContextMenuValue<TValue> | null)[],
options: IContextMenuOptions<TValue>
) {
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<HTMLElement>('.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
}
}

View File

@@ -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

View File

@@ -25,11 +25,10 @@ function addNodesToGroup(group: LGraphGroup, items: Iterable<Positionable>) {
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()
}
})

View File

@@ -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', {}]
}
},

View File

@@ -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<typeof setTimeout> | 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<void>) => {
// 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<string, unknown>
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()

View File

@@ -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')

View File

@@ -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', {}]
}
},

View File

@@ -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)

View File

@@ -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<HTMLAudioElement, string>
) as DOMWidget<HTMLAudioElement, string> | 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<HTMLAudioElement, string>
) as DOMWidget<HTMLAudioElement, string> | 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()
}

View File

@@ -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