mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-02 06:19:58 +00:00
fix(types): remove @ts-expect-error suppressions from src/extensions/core
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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', {}]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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', {}]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user