mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 16:10:09 +00:00
fix(types): add null guards to browser_tests fixtures
This commit is contained in:
@@ -81,11 +81,14 @@ class ComfyMenu {
|
||||
await this.themeToggleButton.click()
|
||||
await this.page.evaluate(() => {
|
||||
return new Promise((resolve) => {
|
||||
window['app'].ui.settings.addEventListener(
|
||||
'Comfy.ColorPalette.change',
|
||||
resolve,
|
||||
{ once: true }
|
||||
)
|
||||
const app = window.app
|
||||
if (!app) {
|
||||
resolve(undefined)
|
||||
return
|
||||
}
|
||||
app.ui.settings.addEventListener('Comfy.ColorPalette.change', resolve, {
|
||||
once: true
|
||||
})
|
||||
|
||||
setTimeout(resolve, 5000)
|
||||
})
|
||||
@@ -94,9 +97,9 @@ class ComfyMenu {
|
||||
|
||||
async getThemeId() {
|
||||
return await this.page.evaluate(async () => {
|
||||
return await window['app'].ui.settings.getSettingValue(
|
||||
'Comfy.ColorPalette'
|
||||
)
|
||||
const app = window.app
|
||||
if (!app) return undefined
|
||||
return await app.ui.settings.getSettingValue('Comfy.ColorPalette')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -138,7 +141,14 @@ class ConfirmDialog {
|
||||
|
||||
// Wait for workflow service to finish if it's busy
|
||||
await this.page.waitForFunction(
|
||||
() => window['app']?.extensionManager?.workflow?.isBusy === false,
|
||||
() => {
|
||||
const app = window.app
|
||||
if (!app) return true
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { isBusy?: boolean }
|
||||
}
|
||||
return extMgr.workflow?.isBusy === false
|
||||
},
|
||||
undefined,
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
@@ -256,7 +266,12 @@ export class ComfyPage {
|
||||
}
|
||||
|
||||
await this.page.evaluate(async () => {
|
||||
await window['app'].extensionManager.workflow.syncWorkflows()
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { syncWorkflows: () => Promise<void> }
|
||||
}
|
||||
if (extMgr.workflow) await extMgr.workflow.syncWorkflows()
|
||||
})
|
||||
|
||||
// Wait for Vue to re-render the workflow list
|
||||
@@ -360,7 +375,9 @@ export class ComfyPage {
|
||||
|
||||
async executeCommand(commandId: string) {
|
||||
await this.page.evaluate((id: string) => {
|
||||
return window['app'].extensionManager.command.execute(id)
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
return app.extensionManager.command.execute(id)
|
||||
}, commandId)
|
||||
}
|
||||
|
||||
@@ -370,7 +387,8 @@ export class ComfyPage {
|
||||
) {
|
||||
await this.page.evaluate(
|
||||
({ commandId, commandStr }) => {
|
||||
const app = window['app']
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
||||
const extensionName = `TestExtension_${randomSuffix}`
|
||||
|
||||
@@ -391,7 +409,8 @@ export class ComfyPage {
|
||||
async registerKeybinding(keyCombo: KeyCombo, command: () => void) {
|
||||
await this.page.evaluate(
|
||||
({ keyCombo, commandStr }) => {
|
||||
const app = window['app']
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
||||
const extensionName = `TestExtension_${randomSuffix}`
|
||||
const commandId = `TestCommand_${randomSuffix}`
|
||||
@@ -419,7 +438,9 @@ export class ComfyPage {
|
||||
async setSetting(settingId: string, settingValue: any) {
|
||||
return await this.page.evaluate(
|
||||
async ({ id, value }) => {
|
||||
await window['app'].extensionManager.setting.set(id, value)
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
await app.extensionManager.setting.set(id, value)
|
||||
},
|
||||
{ id: settingId, value: settingValue }
|
||||
)
|
||||
@@ -427,7 +448,9 @@ export class ComfyPage {
|
||||
|
||||
async getSetting(settingId: string) {
|
||||
return await this.page.evaluate(async (id) => {
|
||||
return await window['app'].extensionManager.setting.get(id)
|
||||
const app = window.app
|
||||
if (!app) return undefined
|
||||
return await app.extensionManager.setting.get(id)
|
||||
}, settingId)
|
||||
}
|
||||
|
||||
@@ -873,8 +896,10 @@ export class ComfyPage {
|
||||
const foundSlot = await this.page.evaluate(
|
||||
async (params) => {
|
||||
const { slotType, action, targetSlotName } = params
|
||||
const app = window['app']
|
||||
const app = window.app
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const currentGraph = app.canvas.graph
|
||||
if (!currentGraph) throw new Error('No graph available')
|
||||
|
||||
// Check if we're in a subgraph
|
||||
if (currentGraph.constructor.name !== 'Subgraph') {
|
||||
@@ -883,13 +908,24 @@ export class ComfyPage {
|
||||
)
|
||||
}
|
||||
|
||||
// Get the appropriate node and slots
|
||||
// Get the appropriate node and slots (these are Subgraph-specific properties)
|
||||
const subgraph = currentGraph as {
|
||||
inputNode?: { onPointerDown?: (...args: unknown[]) => void }
|
||||
outputNode?: { onPointerDown?: (...args: unknown[]) => void }
|
||||
inputs?: Array<{
|
||||
name: string
|
||||
pos?: number[]
|
||||
boundingRect?: number[]
|
||||
}>
|
||||
outputs?: Array<{
|
||||
name: string
|
||||
pos?: number[]
|
||||
boundingRect?: number[]
|
||||
}>
|
||||
}
|
||||
const node =
|
||||
slotType === 'input'
|
||||
? currentGraph.inputNode
|
||||
: currentGraph.outputNode
|
||||
const slots =
|
||||
slotType === 'input' ? currentGraph.inputs : currentGraph.outputs
|
||||
slotType === 'input' ? subgraph.inputNode : subgraph.outputNode
|
||||
const slots = slotType === 'input' ? subgraph.inputs : subgraph.outputs
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`No ${slotType} node found in subgraph`)
|
||||
@@ -970,6 +1006,7 @@ export class ComfyPage {
|
||||
}
|
||||
|
||||
if (node.onPointerDown) {
|
||||
// Call onPointerDown for test simulation
|
||||
node.onPointerDown(
|
||||
event,
|
||||
app.canvas.pointer,
|
||||
@@ -977,8 +1014,11 @@ export class ComfyPage {
|
||||
)
|
||||
|
||||
// Trigger double-click
|
||||
if (app.canvas.pointer.onDoubleClick) {
|
||||
app.canvas.pointer.onDoubleClick(event)
|
||||
const onDoubleClick = app.canvas.pointer.onDoubleClick as
|
||||
| ((e: unknown) => void)
|
||||
| undefined
|
||||
if (onDoubleClick) {
|
||||
onDoubleClick(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1574,7 +1614,9 @@ export class ComfyPage {
|
||||
|
||||
async convertOffsetToCanvas(pos: [number, number]) {
|
||||
return this.page.evaluate((pos) => {
|
||||
return window['app'].canvas.ds.convertOffsetToCanvas(pos)
|
||||
const app = window.app
|
||||
if (!app) return pos
|
||||
return app.canvas.ds.convertOffsetToCanvas(pos)
|
||||
}, pos)
|
||||
}
|
||||
|
||||
@@ -1588,14 +1630,18 @@ export class ComfyPage {
|
||||
}
|
||||
async getNodes(): Promise<LGraphNode[]> {
|
||||
return await this.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
const app = window.app
|
||||
if (!app?.graph?.nodes) return []
|
||||
return app.graph.nodes
|
||||
})
|
||||
}
|
||||
async getNodeRefsByType(type: string): Promise<NodeReference[]> {
|
||||
return Promise.all(
|
||||
(
|
||||
await this.page.evaluate((type) => {
|
||||
return window['app'].graph.nodes
|
||||
const app = window.app
|
||||
if (!app?.graph?.nodes) return []
|
||||
return app.graph.nodes
|
||||
.filter((n: LGraphNode) => n.type === type)
|
||||
.map((n: LGraphNode) => n.id)
|
||||
}, type)
|
||||
@@ -1606,7 +1652,9 @@ export class ComfyPage {
|
||||
return Promise.all(
|
||||
(
|
||||
await this.page.evaluate((title) => {
|
||||
return window['app'].graph.nodes
|
||||
const app = window.app
|
||||
if (!app?.graph?.nodes) return []
|
||||
return app.graph.nodes
|
||||
.filter((n: LGraphNode) => n.title === title)
|
||||
.map((n: LGraphNode) => n.id)
|
||||
}, title)
|
||||
@@ -1616,7 +1664,9 @@ export class ComfyPage {
|
||||
|
||||
async getFirstNodeRef(): Promise<NodeReference | null> {
|
||||
const id = await this.page.evaluate(() => {
|
||||
return window['app'].graph.nodes[0]?.id
|
||||
const app = window.app
|
||||
if (!app?.graph?.nodes) return undefined
|
||||
return app.graph.nodes[0]?.id
|
||||
})
|
||||
if (!id) return null
|
||||
return this.getNodeRefById(id)
|
||||
@@ -1626,32 +1676,41 @@ export class ComfyPage {
|
||||
}
|
||||
async getUndoQueueSize() {
|
||||
return this.page.evaluate(() => {
|
||||
const workflow = (window['app'].extensionManager as WorkspaceStore)
|
||||
.workflow.activeWorkflow
|
||||
return workflow?.changeTracker.undoQueue.length
|
||||
const app = window.app
|
||||
if (!app) return 0
|
||||
const extMgr = app.extensionManager as WorkspaceStore
|
||||
return extMgr.workflow.activeWorkflow?.changeTracker.undoQueue.length ?? 0
|
||||
})
|
||||
}
|
||||
async getRedoQueueSize() {
|
||||
return this.page.evaluate(() => {
|
||||
const workflow = (window['app'].extensionManager as WorkspaceStore)
|
||||
.workflow.activeWorkflow
|
||||
return workflow?.changeTracker.redoQueue.length
|
||||
const app = window.app
|
||||
if (!app) return 0
|
||||
const extMgr = app.extensionManager as WorkspaceStore
|
||||
return extMgr.workflow.activeWorkflow?.changeTracker.redoQueue.length ?? 0
|
||||
})
|
||||
}
|
||||
async isCurrentWorkflowModified() {
|
||||
return this.page.evaluate(() => {
|
||||
return (window['app'].extensionManager as WorkspaceStore).workflow
|
||||
.activeWorkflow?.isModified
|
||||
const app = window.app
|
||||
if (!app) return false
|
||||
const extMgr = app.extensionManager as WorkspaceStore
|
||||
return extMgr.workflow.activeWorkflow?.isModified ?? false
|
||||
})
|
||||
}
|
||||
async getExportedWorkflow({ api = false }: { api?: boolean } = {}) {
|
||||
return this.page.evaluate(async (api) => {
|
||||
return (await window['app'].graphToPrompt())[api ? 'output' : 'workflow']
|
||||
const app = window.app
|
||||
if (!app) return undefined
|
||||
return (await app.graphToPrompt())[api ? 'output' : 'workflow']
|
||||
}, api)
|
||||
}
|
||||
async setFocusMode(focusMode: boolean) {
|
||||
await this.page.evaluate((focusMode) => {
|
||||
window['app'].extensionManager.focusMode = focusMode
|
||||
const app = window.app
|
||||
if (!app) return
|
||||
const extMgr = app.extensionManager as { focusMode?: boolean }
|
||||
extMgr.focusMode = focusMode
|
||||
}, focusMode)
|
||||
await this.nextFrame()
|
||||
}
|
||||
@@ -1664,7 +1723,9 @@ export class ComfyPage {
|
||||
*/
|
||||
async getGroupPosition(title: string): Promise<Position> {
|
||||
const pos = await this.page.evaluate((title) => {
|
||||
const groups = window['app'].graph.groups
|
||||
const app = window.app
|
||||
if (!app?.graph?.groups) return null
|
||||
const groups = app.graph.groups
|
||||
const group = groups.find((g: { title: string }) => g.title === title)
|
||||
if (!group) return null
|
||||
return { x: group.pos[0], y: group.pos[1] }
|
||||
@@ -1686,7 +1747,8 @@ export class ComfyPage {
|
||||
}): Promise<void> {
|
||||
const { name, deltaX, deltaY } = options
|
||||
const screenPos = await this.page.evaluate((title) => {
|
||||
const app = window['app']
|
||||
const app = window.app
|
||||
if (!app?.graph?.groups) return null
|
||||
const groups = app.graph.groups
|
||||
const group = groups.find((g: { title: string }) => g.title === title)
|
||||
if (!group) return null
|
||||
@@ -1754,11 +1816,16 @@ export const comfyPageFixture = base.extend<{
|
||||
}
|
||||
})
|
||||
|
||||
interface MatcherContext {
|
||||
isNot: boolean
|
||||
}
|
||||
|
||||
const makeMatcher = function <T>(
|
||||
getValue: (node: NodeReference) => Promise<T> | T,
|
||||
type: string
|
||||
) {
|
||||
return async function (
|
||||
this: MatcherContext,
|
||||
node: NodeReference,
|
||||
options?: { timeout?: number; intervals?: number[] }
|
||||
) {
|
||||
@@ -1784,7 +1851,11 @@ export const comfyExpect = expect.extend({
|
||||
toBePinned: makeMatcher((n) => n.isPinned(), 'pinned'),
|
||||
toBeBypassed: makeMatcher((n) => n.isBypassed(), 'bypassed'),
|
||||
toBeCollapsed: makeMatcher((n) => n.isCollapsed(), 'collapsed'),
|
||||
async toHaveFocus(locator: Locator, options = { timeout: 256 }) {
|
||||
async toHaveFocus(
|
||||
this: MatcherContext,
|
||||
locator: Locator,
|
||||
options = { timeout: 256 }
|
||||
) {
|
||||
const isFocused = await locator.evaluate(
|
||||
(el) => el === document.activeElement
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ class SidebarTab {
|
||||
}
|
||||
|
||||
export class NodeLibrarySidebarTab extends SidebarTab {
|
||||
constructor(public readonly page: Page) {
|
||||
constructor(public override readonly page: Page) {
|
||||
super(page, 'node-library')
|
||||
}
|
||||
|
||||
@@ -55,12 +55,12 @@ export class NodeLibrarySidebarTab extends SidebarTab {
|
||||
return this.tabContainer.locator('.new-folder-button')
|
||||
}
|
||||
|
||||
async open() {
|
||||
override async open() {
|
||||
await super.open()
|
||||
await this.nodeLibraryTree.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async close() {
|
||||
override async close() {
|
||||
if (!this.tabButton.isVisible()) {
|
||||
return
|
||||
}
|
||||
@@ -87,7 +87,7 @@ export class NodeLibrarySidebarTab extends SidebarTab {
|
||||
}
|
||||
|
||||
export class WorkflowsSidebarTab extends SidebarTab {
|
||||
constructor(public readonly page: Page) {
|
||||
constructor(public override readonly page: Page) {
|
||||
super(page, 'workflows')
|
||||
}
|
||||
|
||||
@@ -140,7 +140,14 @@ export class WorkflowsSidebarTab extends SidebarTab {
|
||||
|
||||
// Wait for workflow service to finish renaming
|
||||
await this.page.waitForFunction(
|
||||
() => !window['app']?.extensionManager?.workflow?.isBusy,
|
||||
() => {
|
||||
const app = window.app
|
||||
if (!app) return true
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { isBusy?: boolean }
|
||||
}
|
||||
return !extMgr.workflow?.isBusy
|
||||
},
|
||||
undefined,
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
|
||||
@@ -86,7 +86,14 @@ export class Topbar {
|
||||
|
||||
// Wait for workflow service to finish saving
|
||||
await this.page.waitForFunction(
|
||||
() => !window['app'].extensionManager.workflow.isBusy,
|
||||
() => {
|
||||
const app = window.app
|
||||
if (!app) return true
|
||||
const extMgr = app.extensionManager as {
|
||||
workflow?: { isBusy?: boolean }
|
||||
}
|
||||
return !extMgr.workflow?.isBusy
|
||||
},
|
||||
undefined,
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
|
||||
@@ -22,7 +22,10 @@ export class SubgraphSlotReference {
|
||||
async getPosition(): Promise<Position> {
|
||||
const pos: [number, number] = await this.comfyPage.page.evaluate(
|
||||
([type, slotName]) => {
|
||||
const currentGraph = window['app'].canvas.graph
|
||||
const app = window.app
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const currentGraph = app.canvas.graph
|
||||
if (!currentGraph) throw new Error('No graph available')
|
||||
|
||||
// Check if we're in a subgraph
|
||||
if (currentGraph.constructor.name !== 'Subgraph') {
|
||||
@@ -31,15 +34,18 @@ export class SubgraphSlotReference {
|
||||
)
|
||||
}
|
||||
|
||||
const slots =
|
||||
type === 'input' ? currentGraph.inputs : currentGraph.outputs
|
||||
const subgraph = currentGraph as {
|
||||
inputs?: Array<{ name: string; pos?: number[] }>
|
||||
outputs?: Array<{ name: string; pos?: number[] }>
|
||||
}
|
||||
const slots = type === 'input' ? subgraph.inputs : subgraph.outputs
|
||||
if (!slots || slots.length === 0) {
|
||||
throw new Error(`No ${type} slots found in subgraph`)
|
||||
}
|
||||
|
||||
// Find the specific slot or use the first one if no name specified
|
||||
const slot = slotName
|
||||
? slots.find((s) => s.name === slotName)
|
||||
? slots.find((s: { name: string }) => s.name === slotName)
|
||||
: slots[0]
|
||||
|
||||
if (!slot) {
|
||||
@@ -51,7 +57,7 @@ export class SubgraphSlotReference {
|
||||
}
|
||||
|
||||
// Convert from offset to canvas coordinates
|
||||
const canvasPos = window['app'].canvas.ds.convertOffsetToCanvas([
|
||||
const canvasPos = app.canvas.ds.convertOffsetToCanvas([
|
||||
slot.pos[0],
|
||||
slot.pos[1]
|
||||
])
|
||||
@@ -69,7 +75,10 @@ export class SubgraphSlotReference {
|
||||
async getOpenSlotPosition(): Promise<Position> {
|
||||
const pos: [number, number] = await this.comfyPage.page.evaluate(
|
||||
([type]) => {
|
||||
const currentGraph = window['app'].canvas.graph
|
||||
const app = window.app
|
||||
if (!app) throw new Error('App not initialized')
|
||||
const currentGraph = app.canvas.graph
|
||||
if (!currentGraph) throw new Error('No graph available')
|
||||
|
||||
if (currentGraph.constructor.name !== 'Subgraph') {
|
||||
throw new Error(
|
||||
@@ -77,35 +86,35 @@ export class SubgraphSlotReference {
|
||||
)
|
||||
}
|
||||
|
||||
const node =
|
||||
type === 'input' ? currentGraph.inputNode : currentGraph.outputNode
|
||||
const slots =
|
||||
type === 'input' ? currentGraph.inputs : currentGraph.outputs
|
||||
const subgraph = currentGraph as {
|
||||
inputNode?: { pos: number[]; size: number[] }
|
||||
outputNode?: { pos: number[]; size: number[] }
|
||||
inputs?: Array<{ pos?: number[] }>
|
||||
outputs?: Array<{ pos?: number[] }>
|
||||
slotAnchorX?: number
|
||||
}
|
||||
const node = type === 'input' ? subgraph.inputNode : subgraph.outputNode
|
||||
const slots = type === 'input' ? subgraph.inputs : subgraph.outputs
|
||||
|
||||
if (!node) {
|
||||
throw new Error(`No ${type} node found in subgraph`)
|
||||
}
|
||||
|
||||
// Calculate position for next available slot
|
||||
// const nextSlotIndex = slots?.length || 0
|
||||
// const slotHeight = 20
|
||||
// const slotY = node.pos[1] + 30 + nextSlotIndex * slotHeight
|
||||
|
||||
// Find last slot position
|
||||
const lastSlot = slots.at(-1)
|
||||
const lastSlot = slots?.at(-1)
|
||||
let slotX: number
|
||||
let slotY: number
|
||||
|
||||
if (lastSlot) {
|
||||
if (lastSlot?.pos) {
|
||||
// If there are existing slots, position the new one below the last one
|
||||
const gapHeight = 20
|
||||
slotX = lastSlot.pos[0]
|
||||
slotY = lastSlot.pos[1] + gapHeight
|
||||
} else {
|
||||
// No existing slots - use slotAnchorX if available, otherwise calculate from node position
|
||||
if (currentGraph.slotAnchorX !== undefined) {
|
||||
if (subgraph.slotAnchorX !== undefined) {
|
||||
// The actual slot X position seems to be slotAnchorX - 10
|
||||
slotX = currentGraph.slotAnchorX - 10
|
||||
slotX = subgraph.slotAnchorX - 10
|
||||
} else {
|
||||
// Fallback: calculate from node edge
|
||||
slotX =
|
||||
@@ -118,10 +127,7 @@ export class SubgraphSlotReference {
|
||||
}
|
||||
|
||||
// Convert from offset to canvas coordinates
|
||||
const canvasPos = window['app'].canvas.ds.convertOffsetToCanvas([
|
||||
slotX,
|
||||
slotY
|
||||
])
|
||||
const canvasPos = app.canvas.ds.convertOffsetToCanvas([slotX, slotY])
|
||||
return canvasPos
|
||||
},
|
||||
[this.type] as const
|
||||
@@ -143,16 +149,18 @@ class NodeSlotReference {
|
||||
async getPosition() {
|
||||
const pos: [number, number] = await this.node.comfyPage.page.evaluate(
|
||||
([type, id, index]) => {
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const graph = app.canvas.graph
|
||||
// Use canvas.graph to get the current graph (works in both main graph and subgraphs)
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const node = graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
|
||||
const rawPos = node.getConnectionPos(type === 'input', index)
|
||||
const convertedPos =
|
||||
window['app'].canvas.ds.convertOffsetToCanvas(rawPos)
|
||||
const convertedPos = app.canvas.ds.convertOffsetToCanvas(rawPos)
|
||||
|
||||
// Debug logging - convert Float64Arrays to regular arrays for visibility
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
console.log(
|
||||
`NodeSlotReference debug for ${type} slot ${index} on node ${id}:`,
|
||||
{
|
||||
@@ -160,7 +168,7 @@ class NodeSlotReference {
|
||||
nodeSize: [node.size[0], node.size[1]],
|
||||
rawConnectionPos: [rawPos[0], rawPos[1]],
|
||||
convertedPos: [convertedPos[0], convertedPos[1]],
|
||||
currentGraphType: window['app'].canvas.graph.constructor.name
|
||||
currentGraphType: graph.constructor.name
|
||||
}
|
||||
)
|
||||
|
||||
@@ -176,7 +184,9 @@ class NodeSlotReference {
|
||||
async getLinkCount() {
|
||||
return await this.node.comfyPage.page.evaluate(
|
||||
([type, id, index]) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
if (type === 'input') {
|
||||
return node.inputs[index].link == null ? 0 : 1
|
||||
@@ -189,7 +199,9 @@ class NodeSlotReference {
|
||||
async removeLinks() {
|
||||
await this.node.comfyPage.page.evaluate(
|
||||
([type, id, index]) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
if (type === 'input') {
|
||||
node.disconnectInput(index)
|
||||
@@ -214,15 +226,19 @@ class NodeWidgetReference {
|
||||
async getPosition(): Promise<Position> {
|
||||
const pos: [number, number] = await this.node.comfyPage.page.evaluate(
|
||||
([id, index]) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
if (!node.widgets) throw new Error(`Node ${id} has no widgets.`)
|
||||
const widget = node.widgets[index]
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
|
||||
const [x, y, w, h] = node.getBounding()
|
||||
return window['app'].canvasPosToClientPos([
|
||||
const [x, y, w] = node.getBounding()
|
||||
const titleHeight = window.LiteGraph?.NODE_TITLE_HEIGHT ?? 20
|
||||
return app.canvasPosToClientPos([
|
||||
x + w / 2,
|
||||
y + window['LiteGraph']['NODE_TITLE_HEIGHT'] + widget.last_y + 1
|
||||
y + titleHeight + (widget.last_y ?? 0) + 1
|
||||
])
|
||||
},
|
||||
[this.node.id, this.index] as const
|
||||
@@ -239,8 +255,11 @@ class NodeWidgetReference {
|
||||
async getSocketPosition(): Promise<Position> {
|
||||
const pos: [number, number] = await this.node.comfyPage.page.evaluate(
|
||||
([id, index]) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
if (!node.widgets) throw new Error(`Node ${id} has no widgets.`)
|
||||
const widget = node.widgets[index]
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
|
||||
@@ -248,11 +267,13 @@ class NodeWidgetReference {
|
||||
(slot) => slot.widget?.name === widget.name
|
||||
)
|
||||
if (!slot) throw new Error(`Socket ${widget.name} not found.`)
|
||||
if (!slot.pos) throw new Error(`Socket ${widget.name} has no position.`)
|
||||
|
||||
const [x, y] = node.getBounding()
|
||||
return window['app'].canvasPosToClientPos([
|
||||
const titleHeight = window.LiteGraph?.NODE_TITLE_HEIGHT ?? 20
|
||||
return app.canvasPosToClientPos([
|
||||
x + slot.pos[0],
|
||||
y + slot.pos[1] + window['LiteGraph']['NODE_TITLE_HEIGHT']
|
||||
y + slot.pos[1] + titleHeight
|
||||
])
|
||||
},
|
||||
[this.node.id, this.index] as const
|
||||
@@ -288,8 +309,11 @@ class NodeWidgetReference {
|
||||
async getValue() {
|
||||
return await this.node.comfyPage.page.evaluate(
|
||||
([id, index]) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.graph) throw new Error('App not initialized')
|
||||
const node = app.graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
if (!node.widgets) throw new Error(`Node ${id} has no widgets.`)
|
||||
const widget = node.widgets[index]
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
return widget.value
|
||||
@@ -305,7 +329,9 @@ export class NodeReference {
|
||||
) {}
|
||||
async exists(): Promise<boolean> {
|
||||
return await this.comfyPage.page.evaluate((id) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) return false
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
return !!node
|
||||
}, this.id)
|
||||
}
|
||||
@@ -322,17 +348,19 @@ export class NodeReference {
|
||||
}
|
||||
}
|
||||
async getBounding(): Promise<Position & Size> {
|
||||
const [x, y, width, height]: [number, number, number, number] =
|
||||
await this.comfyPage.page.evaluate((id) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error('Node not found')
|
||||
return node.getBounding()
|
||||
}, this.id)
|
||||
const bounding = await this.comfyPage.page.evaluate((id) => {
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error('Node not found')
|
||||
const b = node.getBounding()
|
||||
return [b[0], b[1], b[2], b[3]] as [number, number, number, number]
|
||||
}, this.id)
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height
|
||||
x: bounding[0],
|
||||
y: bounding[1],
|
||||
width: bounding[2],
|
||||
height: bounding[3]
|
||||
}
|
||||
}
|
||||
async getSize(): Promise<Size> {
|
||||
@@ -355,14 +383,16 @@ export class NodeReference {
|
||||
return (await this.getProperty<number | null | undefined>('mode')) === 4
|
||||
}
|
||||
async getProperty<T>(prop: string): Promise<T> {
|
||||
return await this.comfyPage.page.evaluate(
|
||||
return (await this.comfyPage.page.evaluate(
|
||||
([id, prop]) => {
|
||||
const node = window['app'].canvas.graph.getNodeById(id)
|
||||
const app = window.app
|
||||
if (!app?.canvas?.graph) throw new Error('App not initialized')
|
||||
const node = app.canvas.graph.getNodeById(id)
|
||||
if (!node) throw new Error('Node not found')
|
||||
return node[prop]
|
||||
return (node as unknown as Record<string, unknown>)[prop]
|
||||
},
|
||||
[this.id, prop] as const
|
||||
)
|
||||
)) as T
|
||||
}
|
||||
async getOutput(index: number) {
|
||||
return new NodeSlotReference('output', index, this)
|
||||
@@ -480,7 +510,7 @@ export class NodeReference {
|
||||
}
|
||||
async navigateIntoSubgraph() {
|
||||
const titleHeight = await this.comfyPage.page.evaluate(() => {
|
||||
return window['LiteGraph']['NODE_TITLE_HEIGHT']
|
||||
return window.LiteGraph?.NODE_TITLE_HEIGHT ?? 20
|
||||
})
|
||||
const nodePos = await this.getPosition()
|
||||
const nodeSize = await this.getSize()
|
||||
@@ -513,7 +543,9 @@ export class NodeReference {
|
||||
|
||||
// Check if we successfully entered the subgraph
|
||||
isInSubgraph = await this.comfyPage.page.evaluate(() => {
|
||||
const graph = window['app'].canvas.graph
|
||||
const app = window.app
|
||||
if (!app) return false
|
||||
const graph = app.canvas.graph
|
||||
return graph?.constructor?.name === 'Subgraph'
|
||||
})
|
||||
|
||||
|
||||
@@ -120,15 +120,23 @@ export default class TaskHistory {
|
||||
filenames: string[],
|
||||
filetype: OutputFileType
|
||||
): TaskOutput {
|
||||
return filenames.reduce((outputs, filename, i) => {
|
||||
const nodeId = `${i + 1}`
|
||||
outputs[nodeId] = {
|
||||
[filetype]: [{ filename, subfolder: '', type: 'output' }]
|
||||
}
|
||||
const contentType = getContentType(filename, filetype)
|
||||
this.outputContentTypes.set(filename, contentType)
|
||||
return outputs
|
||||
}, {})
|
||||
return filenames.reduce(
|
||||
(outputs, filename, i) => {
|
||||
const nodeId = `${i + 1}`
|
||||
outputs[nodeId] = {
|
||||
[filetype]: [{ filename, subfolder: '', type: 'output' }]
|
||||
}
|
||||
const contentType = getContentType(filename, filetype)
|
||||
this.outputContentTypes.set(filename, contentType)
|
||||
return outputs
|
||||
},
|
||||
{} as Record<
|
||||
string,
|
||||
{
|
||||
[key: string]: { filename: string; subfolder: string; type: string }[]
|
||||
}
|
||||
>
|
||||
)
|
||||
}
|
||||
|
||||
private addTask(task: HistoryTaskItem) {
|
||||
|
||||
Reference in New Issue
Block a user