Compare commits
1 Commits
sno-solve-
...
chore/vue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed9e8a2144 |
9
.github/workflows/i18n-custom-nodes.yaml
vendored
@@ -32,10 +32,11 @@ jobs:
|
||||
with:
|
||||
repository: Comfy-Org/ComfyUI_frontend
|
||||
path: ComfyUI_frontend
|
||||
- name: Copy ComfyUI_devtools from frontend repo
|
||||
run: |
|
||||
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||
- name: Checkout ComfyUI_devtools
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Comfy-Org/ComfyUI_devtools
|
||||
path: ComfyUI/custom_nodes/ComfyUI_devtools
|
||||
- name: Checkout custom node repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
|
||||
10
.github/workflows/test-ui.yaml
vendored
@@ -27,10 +27,12 @@ jobs:
|
||||
repository: 'Comfy-Org/ComfyUI_frontend'
|
||||
path: 'ComfyUI_frontend'
|
||||
|
||||
- name: Copy ComfyUI_devtools from frontend repo
|
||||
run: |
|
||||
mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools
|
||||
cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||
- name: Checkout ComfyUI_devtools
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'Comfy-Org/ComfyUI_devtools'
|
||||
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
||||
ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
1
.gitignore
vendored
@@ -44,7 +44,6 @@ components.d.ts
|
||||
tests-ui/data/*
|
||||
tests-ui/ComfyUI_examples
|
||||
tests-ui/workflows/examples
|
||||
coverage/
|
||||
|
||||
# Browser tests
|
||||
/test-results/
|
||||
|
||||
70
CODEOWNERS
@@ -1,61 +1,17 @@
|
||||
# Desktop/Electron
|
||||
/src/types/desktop/ @webfiltered
|
||||
/src/constants/desktopDialogs.ts @webfiltered
|
||||
/src/constants/desktopMaintenanceTasks.ts @webfiltered
|
||||
/src/stores/electronDownloadStore.ts @webfiltered
|
||||
/src/extensions/core/electronAdapter.ts @webfiltered
|
||||
/src/views/DesktopDialogView.vue @webfiltered
|
||||
/src/components/install/ @webfiltered
|
||||
/src/components/maintenance/ @webfiltered
|
||||
/vite.electron.config.mts @webfiltered
|
||||
# Admins
|
||||
* @Comfy-Org/comfy_frontend_devs
|
||||
|
||||
# Common UI Components
|
||||
/src/components/chip/ @viva-jinyi
|
||||
/src/components/card/ @viva-jinyi
|
||||
/src/components/button/ @viva-jinyi
|
||||
/src/components/input/ @viva-jinyi
|
||||
# Maintainers
|
||||
*.md @Comfy-Org/comfy_maintainer
|
||||
/tests-ui/ @Comfy-Org/comfy_maintainer
|
||||
/browser_tests/ @Comfy-Org/comfy_maintainer
|
||||
/.env_example @Comfy-Org/comfy_maintainer
|
||||
|
||||
# Topbar
|
||||
/src/components/topbar/ @pythongosssss
|
||||
# Translations (AIGODLIKE team + shinshin86)
|
||||
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
|
||||
|
||||
# Thumbnail
|
||||
/src/renderer/core/thumbnail/ @pythongosssss
|
||||
# Load 3D extension
|
||||
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
|
||||
|
||||
# Legacy UI
|
||||
/scripts/ui/ @pythongosssss
|
||||
|
||||
# Link rendering
|
||||
/src/renderer/core/canvas/links/ @benceruleanlu
|
||||
|
||||
# Node help system
|
||||
/src/utils/nodeHelpUtil.ts @benceruleanlu
|
||||
/src/stores/workspace/nodeHelpStore.ts @benceruleanlu
|
||||
/src/services/nodeHelpService.ts @benceruleanlu
|
||||
|
||||
# Selection toolbox
|
||||
/src/components/graph/selectionToolbox/ @Myestery
|
||||
|
||||
# Minimap
|
||||
/src/renderer/extensions/minimap/ @jtydhr88
|
||||
|
||||
# Assets
|
||||
/src/platform/assets/ @arjansingh
|
||||
|
||||
# Workflow Templates
|
||||
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||
/src/components/templates/ @Myestery @christian-byrne @comfyui-wiki
|
||||
|
||||
# Mask Editor
|
||||
/src/extensions/core/maskeditor.ts @trsommer @brucew4yn3rp
|
||||
/src/extensions/core/maskEditorLayerFilenames.ts @trsommer @brucew4yn3rp
|
||||
/src/extensions/core/maskEditorOld.ts @trsommer @brucew4yn3rp
|
||||
|
||||
# 3D
|
||||
/src/extensions/core/load3d.ts @jtydhr88
|
||||
/src/components/load3d/ @jtydhr88
|
||||
|
||||
# Manager
|
||||
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
|
||||
|
||||
# Translations
|
||||
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
|
||||
# Mask Editor extension
|
||||
/src/extensions/core/maskeditor.ts @brucew4yn3rp @trsommer @Comfy-Org/comfy_frontend_devs
|
||||
|
||||
@@ -16,14 +16,9 @@ Without this flag, parallel tests will conflict and fail randomly.
|
||||
|
||||
### ComfyUI devtools
|
||||
|
||||
ComfyUI_devtools is now included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory.
|
||||
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
|
||||
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
|
||||
|
||||
For local development, copy the devtools files to your ComfyUI installation:
|
||||
```bash
|
||||
cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/
|
||||
```
|
||||
|
||||
### Node.js & Playwright Prerequisites
|
||||
|
||||
Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver:
|
||||
|
||||
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
@@ -1,44 +0,0 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Nodes - LOD', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
|
||||
expect(initialNodeCount).toBeGreaterThan(0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
|
||||
|
||||
const vueNodesContainer = comfyPage.vueNodes.nodes
|
||||
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
|
||||
const buttonsInNodes = vueNodesContainer.getByRole('button')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(buttonsInNodes.first()).toBeVisible()
|
||||
|
||||
await comfyPage.zoom(120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
|
||||
|
||||
await expect(textboxesInNodes.first()).toBeHidden()
|
||||
await expect(buttonsInNodes.first()).toBeHidden()
|
||||
|
||||
await comfyPage.zoom(-120, 10)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-lod-inactive.png'
|
||||
)
|
||||
await expect(textboxesInNodes.first()).toBeVisible()
|
||||
await expect(buttonsInNodes.first()).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 106 KiB |
@@ -1,47 +0,0 @@
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Node Selection', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
const modifiers = [
|
||||
{ key: 'Control', name: 'ctrl' },
|
||||
{ key: 'Shift', name: 'shift' }
|
||||
] as const
|
||||
|
||||
for (const { key: modifier, name } of modifiers) {
|
||||
test(`should allow selecting multiple nodes with ${name}+click`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1)
|
||||
|
||||
await comfyPage.page.getByText('Empty Latent Image').click({
|
||||
modifiers: [modifier]
|
||||
})
|
||||
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(2)
|
||||
|
||||
await comfyPage.page.getByText('KSampler').click({
|
||||
modifiers: [modifier]
|
||||
})
|
||||
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(3)
|
||||
})
|
||||
|
||||
test(`should allow de-selecting nodes with ${name}+click`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(1)
|
||||
|
||||
await comfyPage.page.getByText('Load Checkpoint').click({
|
||||
modifiers: [modifier]
|
||||
})
|
||||
expect(await comfyPage.vueNodes.getSelectedNodeCount()).toBe(0)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,49 +0,0 @@
|
||||
import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../fixtures/ComfyPage'
|
||||
|
||||
const BYPASS_HOTKEY = 'Control+b'
|
||||
const BYPASS_CLASS = /before:bg-bypass\/60/
|
||||
|
||||
test.describe('Vue Node Bypass', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow toggling bypass on a selected node with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const checkpointNode = comfyPage.page.locator('[data-node-id]').filter({
|
||||
hasText: 'Load Checkpoint'
|
||||
})
|
||||
await checkpointNode.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
})
|
||||
|
||||
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const checkpointNode = comfyPage.page.locator('[data-node-id]').filter({
|
||||
hasText: 'Load Checkpoint'
|
||||
})
|
||||
const ksamplerNode = comfyPage.page.locator('[data-node-id]').filter({
|
||||
hasText: 'KSampler'
|
||||
})
|
||||
|
||||
await checkpointNode.getByText('Load Checkpoint').click()
|
||||
await ksamplerNode.getByText('KSampler').click({ modifiers: ['Control'] })
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
await expect(ksamplerNode).toHaveClass(BYPASS_CLASS)
|
||||
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
await expect(ksamplerNode).not.toHaveClass(BYPASS_CLASS)
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@
|
||||
"compilerOptions": {
|
||||
/* Test files should not be compiled */
|
||||
"noEmit": true,
|
||||
// "strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Build scripts configuration */
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -33,13 +33,7 @@ export default defineConfig([
|
||||
},
|
||||
parserOptions: {
|
||||
parser: tseslint.parser,
|
||||
projectService: {
|
||||
allowDefaultProject: [
|
||||
'vite.config.mts',
|
||||
'vite.electron.config.mts',
|
||||
'vite.types.config.mts'
|
||||
]
|
||||
},
|
||||
projectService: true,
|
||||
tsConfigRootDir: import.meta.dirname,
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
@@ -83,25 +77,12 @@ export default defineConfig([
|
||||
'@typescript-eslint/prefer-as-const': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||||
'@typescript-eslint/no-empty-object-type': [
|
||||
'error',
|
||||
{
|
||||
allowInterfaces: 'always'
|
||||
}
|
||||
],
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'vue/no-v-html': 'off',
|
||||
// Enforce dark-theme: instead of dark: prefix
|
||||
'vue/no-restricted-class': ['error', '/^dark:/'],
|
||||
'vue/multi-word-component-names': 'off', // TODO: fix
|
||||
'vue/no-template-shadow': 'off', // TODO: fix
|
||||
/* Toggle on to do additional until we can clean up existing violations.
|
||||
'vue/no-unused-emit-declarations': 'error',
|
||||
'vue/no-unused-properties': 'error',
|
||||
'vue/no-unused-refs': 'error',
|
||||
'vue/no-use-v-else-with-v-for': 'error',
|
||||
'vue/no-useless-v-bind': 'error',
|
||||
// */
|
||||
'vue/one-component-per-file': 'off', // TODO: fix
|
||||
'vue/require-default-prop': 'off', // TODO: fix -- this one is very worthwhile
|
||||
// Restrict deprecated PrimeVue components
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<link rel="stylesheet" type="text/css" href="user.css" />
|
||||
<link rel="stylesheet" type="text/css" href="api/userdata/user.css" />
|
||||
|
||||
<!-- Fullscreen mode on mobile browsers -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<!-- Fullscreen mode on iOS -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<!-- Status bar style (eg. black or transparent) -->
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
const vueBlockOrderRule =
|
||||
"vue/block-order: ['error', {'order': ['docs', 'script', 'template', 'i18n', 'style']}]"
|
||||
|
||||
export default {
|
||||
'./**/*.js': (stagedFiles) => formatAndEslint(stagedFiles),
|
||||
|
||||
'./**/*.{ts,tsx,vue,mts}': (stagedFiles) => [
|
||||
'./**/*.{ts,tsx,mts}': (stagedFiles) => [
|
||||
...formatAndEslint(stagedFiles),
|
||||
'pnpm typecheck'
|
||||
]
|
||||
],
|
||||
|
||||
'./**/*.vue': (stagedFiles) => [
|
||||
runVueBlockOrder(stagedFiles),
|
||||
...formatAndEslint(stagedFiles)
|
||||
].filter(Boolean)
|
||||
}
|
||||
|
||||
function formatAndEslint(fileNames) {
|
||||
@@ -13,3 +22,9 @@ function formatAndEslint(fileNames) {
|
||||
`pnpm exec prettier --cache --write ${fileNames.join(' ')}`
|
||||
]
|
||||
}
|
||||
|
||||
function runVueBlockOrder(fileNames) {
|
||||
if (fileNames.length === 0) return null
|
||||
const quotedFiles = fileNames.map((file) => `"${file}"`).join(' ')
|
||||
return `pnpm exec eslint --fix --rule "${vueBlockOrderRule}" ${quotedFiles}`
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.28.1",
|
||||
"version": "1.28.0",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -27,8 +27,6 @@
|
||||
"preview": "nx preview",
|
||||
"lint": "eslint src --cache",
|
||||
"lint:fix": "eslint src --cache --fix",
|
||||
"lint:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache",
|
||||
"lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix",
|
||||
"lint:no-cache": "eslint src",
|
||||
"lint:fix:no-cache": "eslint src --fix",
|
||||
"knip": "knip --cache",
|
||||
@@ -96,7 +94,6 @@
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vitest": "^3.2.4",
|
||||
"vue-component-type-helpers": "^3.0.7",
|
||||
"vue-eslint-parser": "^10.2.0",
|
||||
"vue-tsc": "^3.0.7",
|
||||
"zip-dir": "^2.0.0",
|
||||
|
||||
3
pnpm-lock.yaml
generated
@@ -339,9 +339,6 @@ importers:
|
||||
vitest:
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.14.10)(@vitest/ui@3.2.4)(happy-dom@15.11.0)(jsdom@26.1.0)(lightningcss@1.30.1)(terser@5.39.2)
|
||||
vue-component-type-helpers:
|
||||
specifier: ^3.0.7
|
||||
version: 3.0.7
|
||||
vue-eslint-parser:
|
||||
specifier: ^10.2.0
|
||||
version: 10.2.0(eslint@9.35.0(jiti@2.4.2))
|
||||
|
||||
@@ -9,18 +9,9 @@ import { normalizeI18nKey } from '../src/utils/formatUtil'
|
||||
const localePath = './src/locales/en/main.json'
|
||||
const nodeDefsPath = './src/locales/en/nodeDefs.json'
|
||||
|
||||
interface WidgetInfo {
|
||||
name?: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
interface WidgetLabels {
|
||||
[key: string]: Record<string, { name: string }>
|
||||
}
|
||||
|
||||
test('collect-i18n-node-defs', async ({ comfyPage }) => {
|
||||
// Mock view route
|
||||
await comfyPage.page.route('**/view**', async (route) => {
|
||||
comfyPage.page.route('**/view**', async (route) => {
|
||||
await route.fulfill({
|
||||
body: JSON.stringify({})
|
||||
})
|
||||
@@ -29,7 +20,6 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
|
||||
const nodeDefs: ComfyNodeDefImpl[] = (
|
||||
Object.values(
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
// @ts-expect-error - app is dynamically added to window
|
||||
const api = window['app'].api as ComfyApi
|
||||
return await api.getNodeDefs()
|
||||
})
|
||||
@@ -62,7 +52,7 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
|
||||
)
|
||||
|
||||
async function extractWidgetLabels() {
|
||||
const nodeLabels: WidgetLabels = {}
|
||||
const nodeLabels = {}
|
||||
|
||||
for (const nodeDef of nodeDefs) {
|
||||
const inputNames = Object.values(nodeDef.inputs).map(
|
||||
@@ -75,15 +65,12 @@ test('collect-i18n-node-defs', async ({ comfyPage }) => {
|
||||
const widgetsMappings = await comfyPage.page.evaluate(
|
||||
(args) => {
|
||||
const [nodeName, displayName, inputNames] = args
|
||||
// @ts-expect-error - LiteGraph is dynamically added to window
|
||||
const node = window['LiteGraph'].createNode(nodeName, displayName)
|
||||
if (!node.widgets?.length) return {}
|
||||
return Object.fromEntries(
|
||||
node.widgets
|
||||
.filter(
|
||||
(w: WidgetInfo) => w?.name && !inputNames.includes(w.name)
|
||||
)
|
||||
.map((w: WidgetInfo) => [w.name, w.label])
|
||||
.filter((w) => w?.name && !inputNames.includes(w.name))
|
||||
.map((w) => [w.name, w.label])
|
||||
)
|
||||
},
|
||||
[nodeDef.name, nodeDef.display_name, inputNames]
|
||||
|
||||
@@ -72,7 +72,7 @@ function capture(srcLocaleDir: string, tempBaseDir: string) {
|
||||
const relativePath = file.replace(srcLocaleDir, '')
|
||||
const targetPath = join(tempBaseDir, relativePath)
|
||||
ensureDir(dirname(targetPath))
|
||||
writeFileSync(targetPath, readFileSync(file, 'utf8'))
|
||||
writeFileSync(targetPath, readFileSync(file))
|
||||
}
|
||||
console.log('Captured current locale files to temp/base/')
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
/* Script files configuration */
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -929,6 +929,48 @@ audio.comfy-audio.empty-audio-widget {
|
||||
}
|
||||
/* End of [Desktop] Electron window specific styles */
|
||||
|
||||
/* Vue Node LOD (Level of Detail) System */
|
||||
/* These classes control rendering detail based on zoom level */
|
||||
|
||||
/* Minimal LOD (zoom <= 0.4) - Title only for performance */
|
||||
.lg-node--lod-minimal {
|
||||
min-height: 32px;
|
||||
transition: min-height 0.2s ease;
|
||||
/* Performance optimizations */
|
||||
text-shadow: none;
|
||||
backdrop-filter: none;
|
||||
}
|
||||
|
||||
.lg-node--lod-minimal .lg-node-body {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Reduced LOD (0.4 < zoom <= 0.8) - Essential widgets, simplified styling */
|
||||
.lg-node--lod-reduced {
|
||||
transition: opacity 0.1s ease;
|
||||
/* Performance optimizations */
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.lg-node--lod-reduced .lg-widget-label,
|
||||
.lg-node--lod-reduced .lg-slot-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lg-node--lod-reduced .lg-slot {
|
||||
opacity: 0.6;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.lg-node--lod-reduced .lg-widget {
|
||||
margin: 2px 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Full LOD (zoom > 0.8) - Complete detail rendering */
|
||||
.lg-node--lod-full {
|
||||
/* Uses default styling - no overrides needed */
|
||||
}
|
||||
|
||||
.lg-node {
|
||||
/* Disable text selection on all nodes */
|
||||
@@ -954,52 +996,23 @@ audio.comfy-audio.empty-audio-widget {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* START LOD specific styles */
|
||||
/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */
|
||||
|
||||
.isLOD .lg-node {
|
||||
box-shadow: none;
|
||||
filter: none;
|
||||
backdrop-filter: none;
|
||||
text-shadow: none;
|
||||
-webkit-mask-image: none;
|
||||
mask-image: none;
|
||||
clip-path: none;
|
||||
background-image: none;
|
||||
text-rendering: optimizeSpeed;
|
||||
border-radius: 0;
|
||||
contain: layout style;
|
||||
transition: none;
|
||||
|
||||
/* Global performance optimizations for LOD */
|
||||
.lg-node--lod-minimal,
|
||||
.lg-node--lod-reduced {
|
||||
/* Remove ALL expensive paint effects */
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
backdrop-filter: none !important;
|
||||
text-shadow: none !important;
|
||||
-webkit-mask-image: none !important;
|
||||
mask-image: none !important;
|
||||
clip-path: none !important;
|
||||
}
|
||||
|
||||
.isLOD .lg-node > * {
|
||||
pointer-events: none;
|
||||
/* Reduce paint complexity for minimal LOD */
|
||||
.lg-node--lod-minimal {
|
||||
/* Skip complex borders */
|
||||
border-radius: 0 !important;
|
||||
/* Use solid colors only */
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
.lod-toggle {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.isLOD .lod-toggle {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.lod-fallback {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isLOD .lod-fallback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.isLOD .image-preview img {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
|
||||
.isLOD .slot-dot {
|
||||
border-radius: 0;
|
||||
}
|
||||
/* END LOD specific styles */
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/**
|
||||
* Cross-browser async utilities for scheduling tasks during browser idle time
|
||||
* with proper fallbacks for browsers that don't support requestIdleCallback.
|
||||
*
|
||||
* Implementation based on:
|
||||
* https://github.com/microsoft/vscode/blob/main/src/vs/base/common/async.ts
|
||||
*/
|
||||
|
||||
interface IdleDeadline {
|
||||
didTimeout: boolean
|
||||
timeRemaining(): number
|
||||
}
|
||||
|
||||
interface IDisposable {
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation function that handles the actual scheduling logic.
|
||||
* Uses feature detection to determine whether to use native requestIdleCallback
|
||||
* or fall back to setTimeout-based implementation.
|
||||
*/
|
||||
let _runWhenIdle: (
|
||||
targetWindow: any,
|
||||
callback: (idle: IdleDeadline) => void,
|
||||
timeout?: number
|
||||
) => IDisposable
|
||||
|
||||
/**
|
||||
* Execute the callback during the next browser idle period.
|
||||
* Falls back to setTimeout-based scheduling in browsers without native support.
|
||||
*/
|
||||
export let runWhenGlobalIdle: (
|
||||
callback: (idle: IdleDeadline) => void,
|
||||
timeout?: number
|
||||
) => IDisposable
|
||||
|
||||
// Self-invoking function to set up the idle callback implementation
|
||||
;(function () {
|
||||
const safeGlobal: any = globalThis
|
||||
|
||||
if (
|
||||
typeof safeGlobal.requestIdleCallback !== 'function' ||
|
||||
typeof safeGlobal.cancelIdleCallback !== 'function'
|
||||
) {
|
||||
// Fallback implementation for browsers without native support (e.g., Safari)
|
||||
_runWhenIdle = (_targetWindow, runner, _timeout?) => {
|
||||
setTimeout(() => {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
|
||||
// Simulate IdleDeadline - give 15ms window (one frame at ~64fps)
|
||||
const end = Date.now() + 15
|
||||
const deadline: IdleDeadline = {
|
||||
didTimeout: true,
|
||||
timeRemaining() {
|
||||
return Math.max(0, end - Date.now())
|
||||
}
|
||||
}
|
||||
|
||||
runner(Object.freeze(deadline))
|
||||
})
|
||||
|
||||
let disposed = false
|
||||
return {
|
||||
dispose() {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
disposed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Native requestIdleCallback implementation
|
||||
_runWhenIdle = (targetWindow: typeof safeGlobal, runner, timeout?) => {
|
||||
const handle: number = targetWindow.requestIdleCallback(
|
||||
runner,
|
||||
typeof timeout === 'number' ? { timeout } : undefined
|
||||
)
|
||||
|
||||
let disposed = false
|
||||
return {
|
||||
dispose() {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
disposed = true
|
||||
targetWindow.cancelIdleCallback(handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runWhenGlobalIdle = (runner, timeout) =>
|
||||
_runWhenIdle(globalThis, runner, timeout)
|
||||
})()
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<!-- TransformPane for Vue node rendering -->
|
||||
<TransformPane
|
||||
v-if="shouldRenderVueNodes && comfyApp.canvas && comfyAppReady"
|
||||
v-if="isVueNodesEnabled && comfyApp.canvas && comfyAppReady"
|
||||
:canvas="comfyApp.canvas"
|
||||
@transform-update="handleTransformUpdate"
|
||||
@wheel.capture="canvasInteractions.forwardEventToCanvas"
|
||||
@@ -43,6 +43,8 @@
|
||||
v-for="nodeData in allNodes"
|
||||
:key="nodeData.id"
|
||||
:node-data="nodeData"
|
||||
:position="nodePositions.get(nodeData.id)"
|
||||
:size="nodeSizes.get(nodeData.id)"
|
||||
:readonly="false"
|
||||
:error="
|
||||
executionStore.lastExecutionError?.node_id === nodeData.id
|
||||
@@ -51,6 +53,9 @@
|
||||
"
|
||||
:zoom-level="canvasStore.canvas?.ds?.scale || 1"
|
||||
:data-node-id="nodeData.id"
|
||||
@node-click="handleNodeSelect"
|
||||
@update:collapsed="handleNodeCollapse"
|
||||
@update:title="handleNodeTitleUpdate"
|
||||
/>
|
||||
</TransformPane>
|
||||
|
||||
@@ -74,6 +79,7 @@ import {
|
||||
nextTick,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
provide,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
@@ -111,11 +117,14 @@ import { useWorkflowStore } from '@/platform/workflow/management/stores/workflow
|
||||
import { useWorkflowAutoSave } from '@/platform/workflow/persistence/composables/useWorkflowAutoSave'
|
||||
import { useWorkflowPersistence } from '@/platform/workflow/persistence/composables/useWorkflowPersistence'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
|
||||
import { attachSlotLinkPreviewRenderer } from '@/renderer/core/canvas/links/slotLinkPreviewRenderer'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
|
||||
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
|
||||
import VueGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useExecutionStateProvider } from '@/renderer/extensions/vueNodes/execution/useExecutionStateProvider'
|
||||
import { UnauthorizedError, api } from '@/scripts/api'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
import { ChangeTracker } from '@/scripts/changeTracker'
|
||||
@@ -162,13 +171,20 @@ const minimapEnabled = computed(() => settingStore.get('Comfy.Minimap.Visible'))
|
||||
|
||||
// Feature flags
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
const isVueNodesEnabled = computed(() => shouldRenderVueNodes.value)
|
||||
|
||||
// Vue node system
|
||||
const vueNodeLifecycle = useVueNodeLifecycle()
|
||||
const viewportCulling = useViewportCulling()
|
||||
const vueNodeLifecycle = useVueNodeLifecycle(isVueNodesEnabled)
|
||||
const viewportCulling = useViewportCulling(
|
||||
isVueNodesEnabled,
|
||||
vueNodeLifecycle.vueNodeData,
|
||||
vueNodeLifecycle.nodeDataTrigger,
|
||||
vueNodeLifecycle.nodeManager
|
||||
)
|
||||
const nodeEventHandlers = useNodeEventHandlers(vueNodeLifecycle.nodeManager)
|
||||
|
||||
const handleVueNodeLifecycleReset = async () => {
|
||||
if (shouldRenderVueNodes.value) {
|
||||
if (isVueNodesEnabled.value) {
|
||||
vueNodeLifecycle.disposeNodeManagerAndSyncs()
|
||||
await nextTick()
|
||||
vueNodeLifecycle.initializeNodeManager()
|
||||
@@ -187,8 +203,32 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const nodePositions = vueNodeLifecycle.nodePositions
|
||||
const nodeSizes = vueNodeLifecycle.nodeSizes
|
||||
const allNodes = viewportCulling.allNodes
|
||||
const handleTransformUpdate = viewportCulling.handleTransformUpdate
|
||||
|
||||
const handleTransformUpdate = () => {
|
||||
viewportCulling.handleTransformUpdate()
|
||||
// TODO: Fix paste position sync in separate PR
|
||||
vueNodeLifecycle.detectChangesInRAF.value()
|
||||
}
|
||||
const handleNodeSelect = nodeEventHandlers.handleNodeSelect
|
||||
const handleNodeCollapse = nodeEventHandlers.handleNodeCollapse
|
||||
const handleNodeTitleUpdate = nodeEventHandlers.handleNodeTitleUpdate
|
||||
|
||||
// Provide selection state to all Vue nodes
|
||||
const selectedNodeIds = computed(
|
||||
() =>
|
||||
new Set(
|
||||
canvasStore.selectedItems
|
||||
.filter((item) => item.id !== undefined)
|
||||
.map((item) => String(item.id))
|
||||
)
|
||||
)
|
||||
provide(SelectedNodeIdsKey, selectedNodeIds)
|
||||
|
||||
// Provide execution state to all Vue nodes
|
||||
useExecutionStateProvider()
|
||||
|
||||
watchEffect(() => {
|
||||
nodeDefStore.showDeprecated = settingStore.get('Comfy.Node.ShowDeprecated')
|
||||
|
||||
@@ -33,11 +33,9 @@ const tooltipText = ref('')
|
||||
const left = ref<string>()
|
||||
const top = ref<string>()
|
||||
|
||||
function hideTooltip() {
|
||||
return (tooltipText.value = '')
|
||||
}
|
||||
const hideTooltip = () => (tooltipText.value = '')
|
||||
|
||||
async function showTooltip(tooltip: string | null | undefined) {
|
||||
const showTooltip = async (tooltip: string | null | undefined) => {
|
||||
if (!tooltip) return
|
||||
|
||||
left.value = comfyApp.canvas.mouse[0] + 'px'
|
||||
@@ -58,9 +56,9 @@ async function showTooltip(tooltip: string | null | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function onIdle() {
|
||||
const onIdle = () => {
|
||||
const { canvas } = comfyApp
|
||||
const node = canvas?.node_over
|
||||
const node = canvas.node_over
|
||||
if (!node) return
|
||||
|
||||
const ctor = node.constructor as { title_mode?: 0 | 1 | 2 | 3 }
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
:style="`backgroundColor: ${containerStyles.backgroundColor};`"
|
||||
:pt="{
|
||||
header: 'hidden',
|
||||
content: 'p-1 h-10 flex flex-row gap-1'
|
||||
content: 'px-1 py-1 h-10 px-1 flex flex-row gap-1'
|
||||
}"
|
||||
@wheel="canvasInteractions.handleWheel"
|
||||
>
|
||||
|
||||
@@ -112,12 +112,16 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="transition-opacity flex gap-3 h-0 items-center"
|
||||
class="transition-opacity flex gap-3 h-0"
|
||||
:class="{
|
||||
'opacity-40': selected && selected !== 'cpu'
|
||||
}"
|
||||
>
|
||||
<ToggleSwitch v-model="cpuMode" input-id="cpu-mode" />
|
||||
<ToggleSwitch
|
||||
v-model="cpuMode"
|
||||
input-id="cpu-mode"
|
||||
class="-translate-y-40"
|
||||
/>
|
||||
<label for="cpu-mode" class="select-none">
|
||||
{{ $t('install.gpuSelection.enableCpuMode') }}
|
||||
</label>
|
||||
|
||||
@@ -64,29 +64,31 @@ const litegraphService = useLitegraphService()
|
||||
|
||||
const { visible, newSearchBoxEnabled } = storeToRefs(searchBoxStore)
|
||||
const dismissable = ref(true)
|
||||
function getNewNodeLocation(): Point {
|
||||
const getNewNodeLocation = (): Point => {
|
||||
return triggerEvent
|
||||
? [triggerEvent.canvasX, triggerEvent.canvasY]
|
||||
: litegraphService.getCanvasCenter()
|
||||
}
|
||||
const nodeFilters = ref<FuseFilterWithValue<ComfyNodeDefImpl, string>[]>([])
|
||||
function addFilter(filter: FuseFilterWithValue<ComfyNodeDefImpl, string>) {
|
||||
const addFilter = (filter: FuseFilterWithValue<ComfyNodeDefImpl, string>) => {
|
||||
nodeFilters.value.push(filter)
|
||||
}
|
||||
function removeFilter(filter: FuseFilterWithValue<ComfyNodeDefImpl, string>) {
|
||||
const removeFilter = (
|
||||
filter: FuseFilterWithValue<ComfyNodeDefImpl, string>
|
||||
) => {
|
||||
nodeFilters.value = nodeFilters.value.filter(
|
||||
(f) => toRaw(f) !== toRaw(filter)
|
||||
)
|
||||
}
|
||||
function clearFilters() {
|
||||
const clearFilters = () => {
|
||||
nodeFilters.value = []
|
||||
}
|
||||
function closeDialog() {
|
||||
const closeDialog = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
function addNode(nodeDef: ComfyNodeDefImpl) {
|
||||
const addNode = (nodeDef: ComfyNodeDefImpl) => {
|
||||
const node = litegraphService.addNodeOnGraph(nodeDef, {
|
||||
pos: getNewNodeLocation()
|
||||
})
|
||||
@@ -104,7 +106,7 @@ function addNode(nodeDef: ComfyNodeDefImpl) {
|
||||
window.requestAnimationFrame(closeDialog)
|
||||
}
|
||||
|
||||
function showSearchBox(e: CanvasPointerEvent | null) {
|
||||
const showSearchBox = (e: CanvasPointerEvent | null) => {
|
||||
if (newSearchBoxEnabled.value) {
|
||||
if (e?.pointerType === 'touch') {
|
||||
setTimeout(() => {
|
||||
@@ -118,12 +120,11 @@ function showSearchBox(e: CanvasPointerEvent | null) {
|
||||
}
|
||||
}
|
||||
|
||||
function getFirstLink() {
|
||||
return canvasStore.getCanvas().linkConnector.renderLinks.at(0)
|
||||
}
|
||||
const getFirstLink = () =>
|
||||
canvasStore.getCanvas().linkConnector.renderLinks.at(0)
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
function showNewSearchBox(e: CanvasPointerEvent | null) {
|
||||
const showNewSearchBox = (e: CanvasPointerEvent | null) => {
|
||||
const firstLink = getFirstLink()
|
||||
if (firstLink) {
|
||||
const filter =
|
||||
@@ -148,7 +149,7 @@ function showNewSearchBox(e: CanvasPointerEvent | null) {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function showContextMenu(e: CanvasPointerEvent) {
|
||||
const showContextMenu = (e: CanvasPointerEvent) => {
|
||||
const firstLink = getFirstLink()
|
||||
if (!firstLink) return
|
||||
|
||||
@@ -225,7 +226,7 @@ watchEffect(() => {
|
||||
)
|
||||
})
|
||||
|
||||
function canvasEventHandler(e: LiteGraphCanvasEvent) {
|
||||
const canvasEventHandler = (e: LiteGraphCanvasEvent) => {
|
||||
if (e.detail.subType === 'empty-double-click') {
|
||||
showSearchBox(e.detail.originalEvent)
|
||||
} else if (e.detail.subType === 'group-double-click') {
|
||||
@@ -248,10 +249,8 @@ const linkReleaseActionShift = computed(() =>
|
||||
)
|
||||
|
||||
// Prevent normal LinkConnector reset (called by CanvasPointer.finally)
|
||||
function preventDefault(e: Event) {
|
||||
return e.preventDefault()
|
||||
}
|
||||
function cancelNextReset(e: CustomEvent<CanvasPointerEvent>) {
|
||||
const preventDefault = (e: Event) => e.preventDefault()
|
||||
const cancelNextReset = (e: CustomEvent<CanvasPointerEvent>) => {
|
||||
e.preventDefault()
|
||||
|
||||
const canvas = canvasStore.getCanvas()
|
||||
@@ -261,7 +260,7 @@ function cancelNextReset(e: CustomEvent<CanvasPointerEvent>) {
|
||||
})
|
||||
}
|
||||
|
||||
function handleDroppedOnCanvas(e: CustomEvent<CanvasPointerEvent>) {
|
||||
const handleDroppedOnCanvas = (e: CustomEvent<CanvasPointerEvent>) => {
|
||||
disconnectOnReset = true
|
||||
const action = e.detail.shiftKey
|
||||
? linkReleaseActionShift.value
|
||||
@@ -282,7 +281,7 @@ function handleDroppedOnCanvas(e: CustomEvent<CanvasPointerEvent>) {
|
||||
}
|
||||
|
||||
// Resets litegraph state
|
||||
function reset() {
|
||||
const reset = () => {
|
||||
listenerController?.abort()
|
||||
listenerController = null
|
||||
triggerEvent = null
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="workflowTabRef"
|
||||
class="flex p-2 gap-2 workflow-tab group"
|
||||
class="flex p-2 gap-2 workflow-tab"
|
||||
v-bind="$attrs"
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
@@ -11,13 +11,9 @@
|
||||
{{ workflowOption.workflow.filename }}
|
||||
</span>
|
||||
<div class="relative">
|
||||
<span
|
||||
v-if="shouldShowStatusIndicator"
|
||||
class="group-hover:hidden absolute font-bold text-2xl top-1/2 left-1/2 -translate-1/2 z-10 bg-(--comfy-menu-secondary-bg) w-4"
|
||||
>•</span
|
||||
>
|
||||
<span v-if="shouldShowStatusIndicator" class="status-indicator">•</span>
|
||||
<Button
|
||||
class="close-button p-0 w-auto invisible"
|
||||
class="close-button p-0 w-auto"
|
||||
icon="pi pi-times"
|
||||
text
|
||||
severity="secondary"
|
||||
@@ -178,6 +174,18 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@reference '../../assets/css/style.css';
|
||||
|
||||
.status-indicator {
|
||||
@apply absolute font-bold;
|
||||
font-size: 1.5rem;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.p-tooltip.workflow-tab-tooltip {
|
||||
z-index: 1200 !important;
|
||||
|
||||
@@ -358,6 +358,14 @@ onUpdated(() => {
|
||||
@apply visible;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton:hover) .status-indicator {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
:deep(.p-togglebutton) .close-button {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
:deep(.p-scrollpanel-content) {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { t } from '@/i18n'
|
||||
@@ -34,8 +33,19 @@ export const useCurrentUser = () => {
|
||||
return null
|
||||
})
|
||||
|
||||
const onUserResolved = (callback: (user: AuthUserInfo) => void) =>
|
||||
whenever(resolvedUserInfo, callback, { immediate: true })
|
||||
const onUserResolved = (callback: (user: AuthUserInfo) => void) => {
|
||||
if (resolvedUserInfo.value) {
|
||||
callback(resolvedUserInfo.value)
|
||||
}
|
||||
|
||||
const stop = watch(resolvedUserInfo, (value) => {
|
||||
if (value) {
|
||||
callback(value)
|
||||
}
|
||||
})
|
||||
|
||||
return () => stop()
|
||||
}
|
||||
|
||||
const userDisplayName = computed(() => {
|
||||
if (isApiKeyLogin.value) {
|
||||
|
||||
@@ -2,15 +2,42 @@
|
||||
* Vue node lifecycle management for LiteGraph integration
|
||||
* Provides event-driven reactivity with performance optimizations
|
||||
*/
|
||||
import { reactive } from 'vue'
|
||||
import { nextTick, reactive } from 'vue'
|
||||
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
import { type Bounds, QuadTree } from '@/renderer/core/spatial/QuadTree'
|
||||
import type { WidgetValue } from '@/types/simplifiedWidget'
|
||||
import type { SpatialIndexDebugInfo } from '@/types/spatialIndex'
|
||||
|
||||
import type { LGraph, LGraphNode } from '../../lib/litegraph/src/litegraph'
|
||||
|
||||
export interface NodeState {
|
||||
visible: boolean
|
||||
dirty: boolean
|
||||
lastUpdate: number
|
||||
culled: boolean
|
||||
}
|
||||
|
||||
interface NodeMetadata {
|
||||
lastRenderTime: number
|
||||
cachedBounds: DOMRect | null
|
||||
lodLevel: 'high' | 'medium' | 'low'
|
||||
spatialIndex?: QuadTree<string>
|
||||
}
|
||||
|
||||
interface PerformanceMetrics {
|
||||
fps: number
|
||||
frameTime: number
|
||||
updateTime: number
|
||||
nodeCount: number
|
||||
culledCount: number
|
||||
callbackUpdateCount: number
|
||||
rafUpdateCount: number
|
||||
adaptiveQuality: boolean
|
||||
}
|
||||
|
||||
export interface SafeWidgetData {
|
||||
name: string
|
||||
type: string
|
||||
@@ -36,26 +63,109 @@ export interface VueNodeData {
|
||||
}
|
||||
}
|
||||
|
||||
export interface GraphNodeManager {
|
||||
interface SpatialMetrics {
|
||||
queryTime: number
|
||||
nodesInIndex: number
|
||||
}
|
||||
|
||||
interface GraphNodeManager {
|
||||
// Reactive state - safe data extracted from LiteGraph nodes
|
||||
vueNodeData: ReadonlyMap<string, VueNodeData>
|
||||
nodeState: ReadonlyMap<string, NodeState>
|
||||
nodePositions: ReadonlyMap<string, { x: number; y: number }>
|
||||
nodeSizes: ReadonlyMap<string, { width: number; height: number }>
|
||||
|
||||
// Access to original LiteGraph nodes (non-reactive)
|
||||
getNode(id: string): LGraphNode | undefined
|
||||
|
||||
// Lifecycle methods
|
||||
setupEventListeners(): () => void
|
||||
cleanup(): void
|
||||
|
||||
// Update methods
|
||||
scheduleUpdate(
|
||||
nodeId?: string,
|
||||
priority?: 'critical' | 'normal' | 'low'
|
||||
): void
|
||||
forceSync(): void
|
||||
detectChangesInRAF(): void
|
||||
|
||||
// Spatial queries
|
||||
getVisibleNodeIds(viewportBounds: Bounds): Set<string>
|
||||
|
||||
// Performance
|
||||
performanceMetrics: PerformanceMetrics
|
||||
spatialMetrics: SpatialMetrics
|
||||
|
||||
// Debug
|
||||
getSpatialIndexDebugInfo(): SpatialIndexDebugInfo | null
|
||||
}
|
||||
|
||||
export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
export const useGraphNodeManager = (graph: LGraph): GraphNodeManager => {
|
||||
// Get layout mutations composable
|
||||
const { createNode, deleteNode, setSource } = useLayoutMutations()
|
||||
const { moveNode, resizeNode, createNode, deleteNode, setSource } =
|
||||
useLayoutMutations()
|
||||
// Safe reactive data extracted from LiteGraph nodes
|
||||
const vueNodeData = reactive(new Map<string, VueNodeData>())
|
||||
const nodeState = reactive(new Map<string, NodeState>())
|
||||
const nodePositions = reactive(new Map<string, { x: number; y: number }>())
|
||||
const nodeSizes = reactive(
|
||||
new Map<string, { width: number; height: number }>()
|
||||
)
|
||||
|
||||
// Non-reactive storage for original LiteGraph nodes
|
||||
const nodeRefs = new Map<string, LGraphNode>()
|
||||
|
||||
// WeakMap for heavy data that auto-GCs when nodes are removed
|
||||
const nodeMetadata = new WeakMap<LGraphNode, NodeMetadata>()
|
||||
|
||||
// Performance tracking
|
||||
const performanceMetrics = reactive<PerformanceMetrics>({
|
||||
fps: 0,
|
||||
frameTime: 0,
|
||||
updateTime: 0,
|
||||
nodeCount: 0,
|
||||
culledCount: 0,
|
||||
callbackUpdateCount: 0,
|
||||
rafUpdateCount: 0,
|
||||
adaptiveQuality: false
|
||||
})
|
||||
|
||||
// Spatial indexing using QuadTree
|
||||
const spatialIndex = new QuadTree<string>(
|
||||
{ x: -10000, y: -10000, width: 20000, height: 20000 },
|
||||
{ maxDepth: 6, maxItemsPerNode: 4 }
|
||||
)
|
||||
let lastSpatialQueryTime = 0
|
||||
|
||||
// Spatial metrics
|
||||
const spatialMetrics = reactive<SpatialMetrics>({
|
||||
queryTime: 0,
|
||||
nodesInIndex: 0
|
||||
})
|
||||
|
||||
// Update batching
|
||||
const pendingUpdates = new Set<string>()
|
||||
const criticalUpdates = new Set<string>()
|
||||
const lowPriorityUpdates = new Set<string>()
|
||||
let updateScheduled = false
|
||||
let batchTimeoutId: number | null = null
|
||||
|
||||
// Change detection state
|
||||
const lastNodesSnapshot = new Map<
|
||||
string,
|
||||
{ pos: [number, number]; size: [number, number] }
|
||||
>()
|
||||
|
||||
const attachMetadata = (node: LGraphNode) => {
|
||||
nodeMetadata.set(node, {
|
||||
lastRenderTime: performance.now(),
|
||||
cachedBounds: null,
|
||||
lodLevel: 'high',
|
||||
spatialIndex: undefined
|
||||
})
|
||||
}
|
||||
|
||||
// Extract safe data from LiteGraph node for Vue consumption
|
||||
const extractVueNodeData = (node: LGraphNode): VueNodeData => {
|
||||
// Determine subgraph ID - null for root graph, string for subgraphs
|
||||
@@ -176,6 +286,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
...currentData,
|
||||
widgets: updatedWidgets
|
||||
})
|
||||
performanceMetrics.callbackUpdateCount++
|
||||
} catch (error) {
|
||||
// Ignore widget update errors to prevent cascade failures
|
||||
}
|
||||
@@ -245,6 +356,71 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
})
|
||||
}
|
||||
|
||||
// Uncomment when needed for future features
|
||||
// const getNodeMetadata = (node: LGraphNode): NodeMetadata => {
|
||||
// let metadata = nodeMetadata.get(node)
|
||||
// if (!metadata) {
|
||||
// attachMetadata(node)
|
||||
// metadata = nodeMetadata.get(node)!
|
||||
// }
|
||||
// return metadata
|
||||
// }
|
||||
|
||||
const scheduleUpdate = (
|
||||
nodeId?: string,
|
||||
priority: 'critical' | 'normal' | 'low' = 'normal'
|
||||
) => {
|
||||
if (nodeId) {
|
||||
const state = nodeState.get(nodeId)
|
||||
if (state) state.dirty = true
|
||||
|
||||
// Priority queuing
|
||||
if (priority === 'critical') {
|
||||
criticalUpdates.add(nodeId)
|
||||
flush() // Immediate flush for critical updates
|
||||
return
|
||||
} else if (priority === 'low') {
|
||||
lowPriorityUpdates.add(nodeId)
|
||||
} else {
|
||||
pendingUpdates.add(nodeId)
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateScheduled) {
|
||||
updateScheduled = true
|
||||
|
||||
// Adaptive batching strategy
|
||||
if (pendingUpdates.size > 10) {
|
||||
// Many updates - batch in nextTick
|
||||
void nextTick(() => flush())
|
||||
} else {
|
||||
// Few updates - small delay for more batching
|
||||
batchTimeoutId = window.setTimeout(() => flush(), 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const flush = () => {
|
||||
const startTime = performance.now()
|
||||
|
||||
if (batchTimeoutId !== null) {
|
||||
clearTimeout(batchTimeoutId)
|
||||
batchTimeoutId = null
|
||||
}
|
||||
|
||||
// Clear all pending updates
|
||||
criticalUpdates.clear()
|
||||
pendingUpdates.clear()
|
||||
lowPriorityUpdates.clear()
|
||||
updateScheduled = false
|
||||
|
||||
// Sync with graph state
|
||||
syncWithGraph()
|
||||
|
||||
const endTime = performance.now()
|
||||
performanceMetrics.updateTime = endTime - startTime
|
||||
}
|
||||
|
||||
const syncWithGraph = () => {
|
||||
if (!graph?._nodes) return
|
||||
|
||||
@@ -255,6 +431,11 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
if (!currentNodes.has(id)) {
|
||||
nodeRefs.delete(id)
|
||||
vueNodeData.delete(id)
|
||||
nodeState.delete(id)
|
||||
nodePositions.delete(id)
|
||||
nodeSizes.delete(id)
|
||||
lastNodesSnapshot.delete(id)
|
||||
spatialIndex.remove(id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +451,163 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
|
||||
// Extract and store safe data for Vue
|
||||
vueNodeData.set(id, extractVueNodeData(node))
|
||||
|
||||
if (!nodeState.has(id)) {
|
||||
nodeState.set(id, {
|
||||
visible: true,
|
||||
dirty: false,
|
||||
lastUpdate: performance.now(),
|
||||
culled: false
|
||||
})
|
||||
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
||||
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
||||
attachMetadata(node)
|
||||
|
||||
// Add to spatial index
|
||||
const bounds: Bounds = {
|
||||
x: node.pos[0],
|
||||
y: node.pos[1],
|
||||
width: node.size[0],
|
||||
height: node.size[1]
|
||||
}
|
||||
spatialIndex.insert(id, bounds, id)
|
||||
}
|
||||
})
|
||||
|
||||
// Update performance metrics
|
||||
performanceMetrics.nodeCount = vueNodeData.size
|
||||
performanceMetrics.culledCount = Array.from(nodeState.values()).filter(
|
||||
(s) => s.culled
|
||||
).length
|
||||
}
|
||||
|
||||
// Most performant: Direct position sync without re-setting entire node
|
||||
// Query visible nodes using QuadTree spatial index
|
||||
const getVisibleNodeIds = (viewportBounds: Bounds): Set<string> => {
|
||||
const startTime = performance.now()
|
||||
|
||||
// Use QuadTree for fast spatial query
|
||||
const results: string[] = spatialIndex.query(viewportBounds)
|
||||
const visibleIds = new Set(results)
|
||||
|
||||
lastSpatialQueryTime = performance.now() - startTime
|
||||
spatialMetrics.queryTime = lastSpatialQueryTime
|
||||
|
||||
return visibleIds
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects position changes for a single node and updates reactive state
|
||||
*/
|
||||
const detectPositionChanges = (node: LGraphNode, id: string): boolean => {
|
||||
const currentPos = nodePositions.get(id)
|
||||
|
||||
if (
|
||||
!currentPos ||
|
||||
currentPos.x !== node.pos[0] ||
|
||||
currentPos.y !== node.pos[1]
|
||||
) {
|
||||
nodePositions.set(id, { x: node.pos[0], y: node.pos[1] })
|
||||
|
||||
// Push position change to layout store
|
||||
// Source is already set to 'canvas' in detectChangesInRAF
|
||||
void moveNode(id, { x: node.pos[0], y: node.pos[1] })
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects size changes for a single node and updates reactive state
|
||||
*/
|
||||
const detectSizeChanges = (node: LGraphNode, id: string): boolean => {
|
||||
const currentSize = nodeSizes.get(id)
|
||||
|
||||
if (
|
||||
!currentSize ||
|
||||
currentSize.width !== node.size[0] ||
|
||||
currentSize.height !== node.size[1]
|
||||
) {
|
||||
nodeSizes.set(id, { width: node.size[0], height: node.size[1] })
|
||||
|
||||
// Push size change to layout store
|
||||
// Source is already set to 'canvas' in detectChangesInRAF
|
||||
void resizeNode(id, {
|
||||
width: node.size[0],
|
||||
height: node.size[1]
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates spatial index for a node if bounds changed
|
||||
*/
|
||||
const updateSpatialIndex = (node: LGraphNode, id: string): void => {
|
||||
const bounds: Bounds = {
|
||||
x: node.pos[0],
|
||||
y: node.pos[1],
|
||||
width: node.size[0],
|
||||
height: node.size[1]
|
||||
}
|
||||
spatialIndex.update(id, bounds)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates performance metrics after change detection
|
||||
*/
|
||||
const updatePerformanceMetrics = (
|
||||
startTime: number,
|
||||
positionUpdates: number,
|
||||
sizeUpdates: number
|
||||
): void => {
|
||||
const endTime = performance.now()
|
||||
performanceMetrics.updateTime = endTime - startTime
|
||||
performanceMetrics.nodeCount = vueNodeData.size
|
||||
performanceMetrics.culledCount = Array.from(nodeState.values()).filter(
|
||||
(state) => state.culled
|
||||
).length
|
||||
spatialMetrics.nodesInIndex = spatialIndex.size
|
||||
|
||||
if (positionUpdates > 0 || sizeUpdates > 0) {
|
||||
performanceMetrics.rafUpdateCount++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main RAF change detection function
|
||||
*/
|
||||
const detectChangesInRAF = () => {
|
||||
const startTime = performance.now()
|
||||
|
||||
if (!graph?._nodes) return
|
||||
|
||||
let positionUpdates = 0
|
||||
let sizeUpdates = 0
|
||||
|
||||
// Set source for all canvas-driven updates
|
||||
setSource(LayoutSource.Canvas)
|
||||
|
||||
// Process each node for changes
|
||||
for (const node of graph._nodes) {
|
||||
const id = String(node.id)
|
||||
|
||||
const posChanged = detectPositionChanges(node, id)
|
||||
const sizeChanged = detectSizeChanges(node, id)
|
||||
|
||||
if (posChanged) positionUpdates++
|
||||
if (sizeChanged) sizeUpdates++
|
||||
|
||||
// Update spatial index if geometry changed
|
||||
if (posChanged || sizeChanged) {
|
||||
updateSpatialIndex(node, id)
|
||||
}
|
||||
}
|
||||
|
||||
updatePerformanceMetrics(startTime, positionUpdates, sizeUpdates)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,11 +629,32 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
// Extract initial data for Vue (may be incomplete during graph configure)
|
||||
vueNodeData.set(id, extractVueNodeData(node))
|
||||
|
||||
// Set up reactive tracking state
|
||||
nodeState.set(id, {
|
||||
visible: true,
|
||||
dirty: false,
|
||||
lastUpdate: performance.now(),
|
||||
culled: false
|
||||
})
|
||||
|
||||
const initializeVueNodeLayout = () => {
|
||||
// Extract actual positions after configure() has potentially updated them
|
||||
const nodePosition = { x: node.pos[0], y: node.pos[1] }
|
||||
const nodeSize = { width: node.size[0], height: node.size[1] }
|
||||
|
||||
nodePositions.set(id, nodePosition)
|
||||
nodeSizes.set(id, nodeSize)
|
||||
attachMetadata(node)
|
||||
|
||||
// Add to spatial index for viewport culling with final positions
|
||||
const nodeBounds: Bounds = {
|
||||
x: nodePosition.x,
|
||||
y: nodePosition.y,
|
||||
width: nodeSize.width,
|
||||
height: nodeSize.height
|
||||
}
|
||||
spatialIndex.insert(id, nodeBounds, id)
|
||||
|
||||
// Add node to layout store with final positions
|
||||
setSource(LayoutSource.Canvas)
|
||||
void createNode(id, {
|
||||
@@ -340,6 +698,9 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
) => {
|
||||
const id = String(node.id)
|
||||
|
||||
// Remove from spatial index
|
||||
spatialIndex.remove(id)
|
||||
|
||||
// Remove node from layout store
|
||||
setSource(LayoutSource.Canvas)
|
||||
void deleteNode(id)
|
||||
@@ -347,6 +708,10 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
// Clean up all tracking references
|
||||
nodeRefs.delete(id)
|
||||
vueNodeData.delete(id)
|
||||
nodeState.delete(id)
|
||||
nodePositions.delete(id)
|
||||
nodeSizes.delete(id)
|
||||
lastNodesSnapshot.delete(id)
|
||||
|
||||
// Call original callback if provided
|
||||
if (originalCallback) {
|
||||
@@ -368,9 +733,23 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
graph.onNodeRemoved = originalOnNodeRemoved || undefined
|
||||
graph.onTrigger = originalOnTrigger || undefined
|
||||
|
||||
// Clear pending updates
|
||||
if (batchTimeoutId !== null) {
|
||||
clearTimeout(batchTimeoutId)
|
||||
batchTimeoutId = null
|
||||
}
|
||||
|
||||
// Clear all state maps
|
||||
nodeRefs.clear()
|
||||
vueNodeData.clear()
|
||||
nodeState.clear()
|
||||
nodePositions.clear()
|
||||
nodeSizes.clear()
|
||||
lastNodesSnapshot.clear()
|
||||
pendingUpdates.clear()
|
||||
criticalUpdates.clear()
|
||||
lowPriorityUpdates.clear()
|
||||
spatialIndex.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,7 +845,18 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
|
||||
return {
|
||||
vueNodeData,
|
||||
nodeState,
|
||||
nodePositions,
|
||||
nodeSizes,
|
||||
getNode,
|
||||
cleanup
|
||||
setupEventListeners,
|
||||
cleanup,
|
||||
scheduleUpdate,
|
||||
forceSync: syncWithGraph,
|
||||
detectChangesInRAF,
|
||||
getVisibleNodeIds,
|
||||
performanceMetrics,
|
||||
spatialMetrics,
|
||||
getSpatialIndexDebugInfo: () => spatialIndex.getDebugInfo()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,27 @@
|
||||
* 2. Set display none on element to avoid cascade resolution overhead
|
||||
* 3. Only run when transform changes (event driven)
|
||||
*/
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { type Ref, computed } from 'vue'
|
||||
|
||||
import { useVueNodeLifecycle } from '@/composables/graph/useVueNodeLifecycle'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
|
||||
export function useViewportCulling() {
|
||||
interface NodeManager {
|
||||
getNode: (id: string) => any
|
||||
}
|
||||
|
||||
export function useViewportCulling(
|
||||
isVueNodesEnabled: Ref<boolean>,
|
||||
vueNodeData: Ref<ReadonlyMap<string, VueNodeData>>,
|
||||
nodeDataTrigger: Ref<number>,
|
||||
nodeManager: Ref<NodeManager | null>
|
||||
) {
|
||||
const canvasStore = useCanvasStore()
|
||||
const { vueNodeData, nodeManager } = useVueNodeLifecycle()
|
||||
|
||||
const allNodes = computed(() => {
|
||||
if (!isVueNodesEnabled.value) return []
|
||||
void nodeDataTrigger.value // Force re-evaluation when nodeManager initializes
|
||||
return Array.from(vueNodeData.value.values())
|
||||
})
|
||||
|
||||
@@ -25,7 +34,7 @@ export function useViewportCulling() {
|
||||
* Update visibility of all nodes based on viewport
|
||||
* Queries DOM directly - no cache maintenance needed
|
||||
*/
|
||||
function updateVisibility() {
|
||||
const updateVisibility = () => {
|
||||
if (!nodeManager.value || !canvasStore.canvas || !comfyApp.canvas) return
|
||||
|
||||
const canvas = canvasStore.canvas
|
||||
@@ -67,17 +76,31 @@ export function useViewportCulling() {
|
||||
}
|
||||
}
|
||||
|
||||
const updateVisibilityDebounced = useThrottleFn(updateVisibility, 20)
|
||||
|
||||
// RAF throttling for smooth updates during continuous panning
|
||||
function handleTransformUpdate() {
|
||||
requestAnimationFrame(async () => {
|
||||
await updateVisibilityDebounced()
|
||||
let rafId: number | null = null
|
||||
|
||||
/**
|
||||
* Handle transform update - called by TransformPane event
|
||||
* Uses RAF to batch updates for smooth performance
|
||||
*/
|
||||
const handleTransformUpdate = () => {
|
||||
if (!isVueNodesEnabled.value) return
|
||||
|
||||
// Cancel previous RAF if still pending
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId)
|
||||
}
|
||||
|
||||
// Schedule update in next animation frame
|
||||
rafId = requestAnimationFrame(() => {
|
||||
updateVisibility()
|
||||
rafId = null
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
allNodes,
|
||||
handleTransformUpdate
|
||||
handleTransformUpdate,
|
||||
updateVisibility
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import { readonly, ref, shallowRef, watch } from 'vue'
|
||||
/**
|
||||
* Vue Node Lifecycle Management Composable
|
||||
*
|
||||
* Handles the complete lifecycle of Vue node rendering system including:
|
||||
* - Node manager initialization and cleanup
|
||||
* - Layout store synchronization
|
||||
* - Slot and link sync management
|
||||
* - Reactive state management for node data, positions, and sizes
|
||||
* - Memory management and proper cleanup
|
||||
*/
|
||||
import { type Ref, computed, readonly, ref, shallowRef, watch } from 'vue'
|
||||
|
||||
import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager'
|
||||
import type {
|
||||
GraphNodeManager,
|
||||
NodeState,
|
||||
VueNodeData
|
||||
} from '@/composables/graph/useGraphNodeManager'
|
||||
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
|
||||
import type { LGraphCanvas, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
@@ -16,12 +24,13 @@ import { useLinkLayoutSync } from '@/renderer/core/layout/sync/useLinkLayoutSync
|
||||
import { useSlotLayoutSync } from '@/renderer/core/layout/sync/useSlotLayoutSync'
|
||||
import { app as comfyApp } from '@/scripts/app'
|
||||
|
||||
function useVueNodeLifecycleIndividual() {
|
||||
export function useVueNodeLifecycle(isVueNodesEnabled: Ref<boolean>) {
|
||||
const canvasStore = useCanvasStore()
|
||||
const layoutMutations = useLayoutMutations()
|
||||
const { shouldRenderVueNodes } = useVueFeatureFlags()
|
||||
|
||||
const nodeManager = shallowRef<GraphNodeManager | null>(null)
|
||||
const nodeManager = shallowRef<ReturnType<typeof useGraphNodeManager> | null>(
|
||||
null
|
||||
)
|
||||
const cleanupNodeManager = shallowRef<(() => void) | null>(null)
|
||||
|
||||
// Sync management
|
||||
@@ -31,10 +40,22 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
// Vue node data state
|
||||
const vueNodeData = ref<ReadonlyMap<string, VueNodeData>>(new Map())
|
||||
const nodeState = ref<ReadonlyMap<string, NodeState>>(new Map())
|
||||
const nodePositions = ref<ReadonlyMap<string, { x: number; y: number }>>(
|
||||
new Map()
|
||||
)
|
||||
const nodeSizes = ref<ReadonlyMap<string, { width: number; height: number }>>(
|
||||
new Map()
|
||||
)
|
||||
|
||||
// Change detection function
|
||||
const detectChangesInRAF = ref<() => void>(() => {})
|
||||
|
||||
// Trigger for forcing computed re-evaluation
|
||||
const nodeDataTrigger = ref(0)
|
||||
|
||||
const isNodeManagerReady = computed(() => nodeManager.value !== null)
|
||||
|
||||
const initializeNodeManager = () => {
|
||||
// Use canvas graph if available (handles subgraph contexts), fallback to app graph
|
||||
const activeGraph = comfyApp.canvas?.graph || comfyApp.graph
|
||||
@@ -47,6 +68,10 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
// Use the manager's data maps
|
||||
vueNodeData.value = manager.vueNodeData
|
||||
nodeState.value = manager.nodeState
|
||||
nodePositions.value = manager.nodePositions
|
||||
nodeSizes.value = manager.nodeSizes
|
||||
detectChangesInRAF.value = manager.detectChangesInRAF
|
||||
|
||||
// Initialize layout system with existing nodes from active graph
|
||||
const nodes = activeGraph._nodes.map((node: LGraphNode) => ({
|
||||
@@ -109,12 +134,18 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
// Reset reactive maps to clean state
|
||||
vueNodeData.value = new Map()
|
||||
nodeState.value = new Map()
|
||||
nodePositions.value = new Map()
|
||||
nodeSizes.value = new Map()
|
||||
|
||||
// Reset change detection function
|
||||
detectChangesInRAF.value = () => {}
|
||||
}
|
||||
|
||||
// Watch for Vue nodes enabled state changes
|
||||
watch(
|
||||
() =>
|
||||
shouldRenderVueNodes.value &&
|
||||
isVueNodesEnabled.value &&
|
||||
Boolean(comfyApp.canvas?.graph || comfyApp.graph),
|
||||
(enabled) => {
|
||||
if (enabled) {
|
||||
@@ -128,7 +159,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
// Consolidated watch for slot layout sync management
|
||||
watch(
|
||||
[() => canvasStore.canvas, () => shouldRenderVueNodes.value],
|
||||
[() => canvasStore.canvas, () => isVueNodesEnabled.value],
|
||||
([canvas, vueMode], [, oldVueMode]) => {
|
||||
const modeChanged = vueMode !== oldVueMode
|
||||
|
||||
@@ -160,7 +191,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
// Handle case where Vue nodes are enabled but graph starts empty
|
||||
const setupEmptyGraphListener = () => {
|
||||
if (
|
||||
shouldRenderVueNodes.value &&
|
||||
isVueNodesEnabled.value &&
|
||||
comfyApp.graph &&
|
||||
!nodeManager.value &&
|
||||
comfyApp.graph._nodes.length === 0
|
||||
@@ -171,7 +202,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
comfyApp.graph.onNodeAdded = originalOnNodeAdded
|
||||
|
||||
// Initialize node manager if needed
|
||||
if (shouldRenderVueNodes.value && !nodeManager.value) {
|
||||
if (isVueNodesEnabled.value && !nodeManager.value) {
|
||||
initializeNodeManager()
|
||||
}
|
||||
|
||||
@@ -202,7 +233,13 @@ function useVueNodeLifecycleIndividual() {
|
||||
|
||||
return {
|
||||
vueNodeData,
|
||||
nodeState,
|
||||
nodePositions,
|
||||
nodeSizes,
|
||||
nodeDataTrigger: readonly(nodeDataTrigger),
|
||||
nodeManager: readonly(nodeManager),
|
||||
detectChangesInRAF: readonly(detectChangesInRAF),
|
||||
isNodeManagerReady,
|
||||
|
||||
// Lifecycle methods
|
||||
initializeNodeManager,
|
||||
@@ -211,7 +248,3 @@ function useVueNodeLifecycleIndividual() {
|
||||
cleanup
|
||||
}
|
||||
}
|
||||
|
||||
export const useVueNodeLifecycle = createSharedComposable(
|
||||
useVueNodeLifecycleIndividual
|
||||
)
|
||||
|
||||
@@ -1548,71 +1548,6 @@ const apiNodeCosts: Record<string, { displayPrice: string | PricingFunction }> =
|
||||
},
|
||||
ByteDanceImageReferenceNode: {
|
||||
displayPrice: byteDanceVideoPricingCalculator
|
||||
},
|
||||
WanTextToVideoApi: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const durationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
const resolutionWidget = node.widgets?.find(
|
||||
(w) => w.name === 'size'
|
||||
) as IComboWidget
|
||||
|
||||
if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second'
|
||||
|
||||
const seconds = parseFloat(String(durationWidget.value))
|
||||
const resolutionStr = String(resolutionWidget.value).toLowerCase()
|
||||
|
||||
const resKey = resolutionStr.includes('1080')
|
||||
? '1080p'
|
||||
: resolutionStr.includes('720')
|
||||
? '720p'
|
||||
: resolutionStr.includes('480')
|
||||
? '480p'
|
||||
: resolutionStr.match(/^\s*(\d{3,4}p)/)?.[1] ?? ''
|
||||
|
||||
const pricePerSecond: Record<string, number> = {
|
||||
'480p': 0.05,
|
||||
'720p': 0.1,
|
||||
'1080p': 0.15
|
||||
}
|
||||
|
||||
const pps = pricePerSecond[resKey]
|
||||
if (isNaN(seconds) || !pps) return '$0.05-0.15/second'
|
||||
|
||||
const cost = (pps * seconds).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
WanImageToVideoApi: {
|
||||
displayPrice: (node: LGraphNode): string => {
|
||||
const durationWidget = node.widgets?.find(
|
||||
(w) => w.name === 'duration'
|
||||
) as IComboWidget
|
||||
const resolutionWidget = node.widgets?.find(
|
||||
(w) => w.name === 'resolution'
|
||||
) as IComboWidget
|
||||
|
||||
if (!durationWidget || !resolutionWidget) return '$0.05-0.15/second'
|
||||
|
||||
const seconds = parseFloat(String(durationWidget.value))
|
||||
const resolution = String(resolutionWidget.value).trim().toLowerCase()
|
||||
|
||||
const pricePerSecond: Record<string, number> = {
|
||||
'480p': 0.05,
|
||||
'720p': 0.1,
|
||||
'1080p': 0.15
|
||||
}
|
||||
|
||||
const pps = pricePerSecond[resolution]
|
||||
if (isNaN(seconds) || !pps) return '$0.05-0.15/second'
|
||||
|
||||
const cost = (pps * seconds).toFixed(2)
|
||||
return `$${cost}/Run`
|
||||
}
|
||||
},
|
||||
WanTextToImageApi: {
|
||||
displayPrice: '$0.03/Run'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1712,9 +1647,7 @@ export const useNodePricing = () => {
|
||||
ByteDanceTextToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageToVideoNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceFirstLastFrameNode: ['model', 'duration', 'resolution'],
|
||||
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution'],
|
||||
WanTextToVideoApi: ['duration', 'size'],
|
||||
WanImageToVideoApi: ['duration', 'resolution']
|
||||
ByteDanceImageReferenceNode: ['model', 'duration', 'resolution']
|
||||
}
|
||||
return widgetMap[nodeType] || []
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
* Vue-related feature flags composable
|
||||
* Manages local settings-driven flags and LiteGraph integration
|
||||
*/
|
||||
import { createSharedComposable } from '@vueuse/core'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
|
||||
import { LiteGraph } from '../lib/litegraph/src/litegraph'
|
||||
|
||||
function useVueFeatureFlagsIndividual() {
|
||||
export const useVueFeatureFlags = () => {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const shouldRenderVueNodes = computed(() => {
|
||||
const isVueNodesEnabled = computed(() => {
|
||||
try {
|
||||
return settingStore.get('Comfy.VueNodes.Enabled') ?? false
|
||||
} catch {
|
||||
@@ -20,20 +19,20 @@ function useVueFeatureFlagsIndividual() {
|
||||
}
|
||||
})
|
||||
|
||||
// Whether Vue nodes should render
|
||||
const shouldRenderVueNodes = computed(() => isVueNodesEnabled.value)
|
||||
|
||||
// Sync the Vue nodes flag with LiteGraph global settings
|
||||
const syncVueNodesFlag = () => {
|
||||
LiteGraph.vueNodesMode = isVueNodesEnabled.value
|
||||
}
|
||||
|
||||
// Watch for changes and update LiteGraph immediately
|
||||
watch(
|
||||
shouldRenderVueNodes,
|
||||
() => {
|
||||
LiteGraph.vueNodesMode = shouldRenderVueNodes.value
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
watch(isVueNodesEnabled, syncVueNodesFlag, { immediate: true })
|
||||
|
||||
return {
|
||||
shouldRenderVueNodes
|
||||
isVueNodesEnabled,
|
||||
shouldRenderVueNodes,
|
||||
syncVueNodesFlag
|
||||
}
|
||||
}
|
||||
|
||||
export const useVueFeatureFlags = createSharedComposable(
|
||||
useVueFeatureFlagsIndividual
|
||||
)
|
||||
|
||||
@@ -122,14 +122,14 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
key: '.'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.FitView',
|
||||
targetElementId: 'graph-canvas-container'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
key: 'p'
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelected.Pin',
|
||||
targetElementId: 'graph-canvas-container'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -137,7 +137,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
alt: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Collapse',
|
||||
targetElementId: 'graph-canvas-container'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -145,7 +145,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
ctrl: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Bypass',
|
||||
targetElementId: 'graph-canvas-container'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
@@ -153,7 +153,7 @@ export const CORE_KEYBINDINGS: Keybinding[] = [
|
||||
ctrl: true
|
||||
},
|
||||
commandId: 'Comfy.Canvas.ToggleSelectedNodes.Mute',
|
||||
targetElementId: 'graph-canvas-container'
|
||||
targetElementId: 'graph-canvas'
|
||||
},
|
||||
{
|
||||
combo: {
|
||||
|
||||
@@ -3901,19 +3901,6 @@ class UIManager {
|
||||
this.paintBucketSettingsHTML.style.display = 'none'
|
||||
}
|
||||
}
|
||||
if (tool === Tools.MaskColorFill) {
|
||||
this.brushSettingsHTML.style.display = 'none'
|
||||
this.colorSelectSettingsHTML.style.display = 'flex'
|
||||
this.paintBucketSettingsHTML.style.display = 'none'
|
||||
} else if (tool === Tools.MaskBucket) {
|
||||
this.brushSettingsHTML.style.display = 'none'
|
||||
this.colorSelectSettingsHTML.style.display = 'none'
|
||||
this.paintBucketSettingsHTML.style.display = 'flex'
|
||||
} else {
|
||||
this.brushSettingsHTML.style.display = 'flex'
|
||||
this.colorSelectSettingsHTML.style.display = 'none'
|
||||
this.paintBucketSettingsHTML.style.display = 'none'
|
||||
}
|
||||
this.messageBroker.publish('setTool', tool)
|
||||
this.onToolChange()
|
||||
const newActiveLayer = this.toolSettings[tool].newActiveLayerOnSet
|
||||
|
||||
@@ -76,7 +76,7 @@ export const i18n = createI18n({
|
||||
})
|
||||
|
||||
/** Convenience shorthand: i18n.global */
|
||||
export const { t, te, d } = i18n.global
|
||||
export const { t, te } = i18n.global
|
||||
|
||||
/**
|
||||
* Safe translation function that returns the fallback message if the key is not found.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import type {
|
||||
ContextMenuDivElement,
|
||||
IContextMenuOptions,
|
||||
IContextMenuValue
|
||||
} from './interfaces'
|
||||
import { LiteGraph } from './litegraph'
|
||||
|
||||
// TODO: Replace this pattern with something more modern.
|
||||
export interface ContextMenu<TValue = unknown> {
|
||||
@@ -182,7 +182,7 @@ export class ContextMenu<TValue = unknown> {
|
||||
root.style.left = `${left}px`
|
||||
root.style.top = `${top}px`
|
||||
|
||||
if (LiteGraphInternal.context_menu_scaling && options.scale) {
|
||||
if (LiteGraph.context_menu_scaling && options.scale) {
|
||||
root.style.transform = `scale(${Math.round(options.scale * 4) * 0.25})`
|
||||
}
|
||||
}
|
||||
@@ -355,7 +355,7 @@ export class ContextMenu<TValue = unknown> {
|
||||
) {
|
||||
ContextMenu.trigger(
|
||||
this.parentMenu.root,
|
||||
`${LiteGraphInternal.pointerevents_method}leave`,
|
||||
`${LiteGraph.pointerevents_method}leave`,
|
||||
e
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import { LGraphCanvas } from './LGraphCanvas'
|
||||
import { LGraphGroup } from './LGraphGroup'
|
||||
import { LGraphNode, type NodeId } from './LGraphNode'
|
||||
import { LLink, type LinkId } from './LLink'
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import { MapProxyHandler } from './MapProxyHandler'
|
||||
import { Reroute, type RerouteId } from './Reroute'
|
||||
import { CustomEventTarget } from './infrastructure/CustomEventTarget'
|
||||
@@ -35,7 +34,7 @@ import type {
|
||||
Positionable,
|
||||
Size
|
||||
} from './interfaces'
|
||||
import { SubgraphNode } from './litegraph'
|
||||
import { LiteGraph, SubgraphNode } from './litegraph'
|
||||
import {
|
||||
alignOutsideContainer,
|
||||
alignToContainer,
|
||||
@@ -275,7 +274,7 @@ export class LGraph
|
||||
* @param o data from previous serialization [optional]
|
||||
*/
|
||||
constructor(o?: ISerialisedGraph | SerialisableGraph) {
|
||||
if (LiteGraphInternal.debug) console.log('Graph created')
|
||||
if (LiteGraph.debug) console.log('Graph created')
|
||||
|
||||
/** @see MapProxyHandler */
|
||||
const links = this._links
|
||||
@@ -425,7 +424,7 @@ export class LGraph
|
||||
this.sendEventToAllNodes('onStart')
|
||||
|
||||
// launch
|
||||
this.starttime = LiteGraphInternal.getTime()
|
||||
this.starttime = LiteGraph.getTime()
|
||||
this.last_update_time = this.starttime
|
||||
interval ||= 0
|
||||
|
||||
@@ -487,7 +486,7 @@ export class LGraph
|
||||
runStep(num: number, do_not_catch_errors: boolean, limit?: number): void {
|
||||
num = num || 1
|
||||
|
||||
const start = LiteGraphInternal.getTime()
|
||||
const start = LiteGraph.getTime()
|
||||
this.globaltime = 0.001 * (start - this.starttime)
|
||||
|
||||
const nodes = this._nodes_executable || this._nodes
|
||||
@@ -531,15 +530,14 @@ export class LGraph
|
||||
this.errors_in_execution = false
|
||||
} catch (error) {
|
||||
this.errors_in_execution = true
|
||||
if (LiteGraphInternal.throw_errors) throw error
|
||||
if (LiteGraph.throw_errors) throw error
|
||||
|
||||
if (LiteGraphInternal.debug)
|
||||
console.log('Error during execution:', error)
|
||||
if (LiteGraph.debug) console.log('Error during execution:', error)
|
||||
this.stop()
|
||||
}
|
||||
}
|
||||
|
||||
const now = LiteGraphInternal.getTime()
|
||||
const now = LiteGraph.getTime()
|
||||
let elapsed = now - start
|
||||
if (elapsed == 0) elapsed = 1
|
||||
|
||||
@@ -664,7 +662,7 @@ export class LGraph
|
||||
L.push(M[i])
|
||||
}
|
||||
|
||||
if (L.length != this._nodes.length && LiteGraphInternal.debug)
|
||||
if (L.length != this._nodes.length && LiteGraph.debug)
|
||||
console.warn('something went wrong, nodes missing')
|
||||
|
||||
/** Ensure type is set */
|
||||
@@ -720,21 +718,16 @@ export class LGraph
|
||||
if (!column) continue
|
||||
|
||||
let max_size = 100
|
||||
let y = margin + LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
let y = margin + LiteGraph.NODE_TITLE_HEIGHT
|
||||
for (const node of column) {
|
||||
node.pos[0] = layout == LiteGraphInternal.VERTICAL_LAYOUT ? y : x
|
||||
node.pos[1] = layout == LiteGraphInternal.VERTICAL_LAYOUT ? x : y
|
||||
const max_size_index =
|
||||
layout == LiteGraphInternal.VERTICAL_LAYOUT ? 1 : 0
|
||||
node.pos[0] = layout == LiteGraph.VERTICAL_LAYOUT ? y : x
|
||||
node.pos[1] = layout == LiteGraph.VERTICAL_LAYOUT ? x : y
|
||||
const max_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 1 : 0
|
||||
if (node.size[max_size_index] > max_size) {
|
||||
max_size = node.size[max_size_index]
|
||||
}
|
||||
const node_size_index =
|
||||
layout == LiteGraphInternal.VERTICAL_LAYOUT ? 0 : 1
|
||||
y +=
|
||||
node.size[node_size_index] +
|
||||
margin +
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const node_size_index = layout == LiteGraph.VERTICAL_LAYOUT ? 0 : 1
|
||||
y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT
|
||||
}
|
||||
x += max_size + margin
|
||||
}
|
||||
@@ -838,7 +831,7 @@ export class LGraph
|
||||
const { state } = this
|
||||
|
||||
// Ensure created items are snapped
|
||||
if (LiteGraphInternal.alwaysSnapToGrid) {
|
||||
if (LiteGraph.alwaysSnapToGrid) {
|
||||
const snapTo = this.getSnapToGridSize()
|
||||
if (snapTo) node.snapToGrid(snapTo)
|
||||
}
|
||||
@@ -863,18 +856,16 @@ export class LGraph
|
||||
console.warn(
|
||||
'LiteGraph: there is already a node with this ID, changing it'
|
||||
)
|
||||
node.id = LiteGraphInternal.use_uuids
|
||||
? LiteGraphInternal.uuidv4()
|
||||
: ++state.lastNodeId
|
||||
node.id = LiteGraph.use_uuids ? LiteGraph.uuidv4() : ++state.lastNodeId
|
||||
}
|
||||
|
||||
if (this._nodes.length >= LiteGraphInternal.MAX_NUMBER_OF_NODES) {
|
||||
if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) {
|
||||
throw 'LiteGraph: max number of nodes in a graph reached'
|
||||
}
|
||||
|
||||
// give him an id
|
||||
if (LiteGraphInternal.use_uuids) {
|
||||
if (node.id == null || node.id == -1) node.id = LiteGraphInternal.uuidv4()
|
||||
if (LiteGraph.use_uuids) {
|
||||
if (node.id == null || node.id == -1) node.id = LiteGraph.uuidv4()
|
||||
} else {
|
||||
if (node.id == null || node.id == -1) {
|
||||
node.id = ++state.lastNodeId
|
||||
@@ -1137,9 +1128,9 @@ export class LGraph
|
||||
/**
|
||||
* Snaps the provided items to a grid.
|
||||
*
|
||||
* Item positions are reounded to the nearest multiple of {@link LiteGraphInternal.CANVAS_GRID_SIZE}.
|
||||
* Item positions are reounded to the nearest multiple of {@link LiteGraph.CANVAS_GRID_SIZE}.
|
||||
*
|
||||
* When {@link LiteGraphInternal.alwaysSnapToGrid} is enabled
|
||||
* When {@link LiteGraph.alwaysSnapToGrid} is enabled
|
||||
* and the grid size is falsy, a default of 1 is used.
|
||||
* @param items The items to be snapped to the grid
|
||||
* @todo Currently only snaps nodes.
|
||||
@@ -1159,9 +1150,9 @@ export class LGraph
|
||||
*/
|
||||
getSnapToGridSize(): number {
|
||||
// Default to 1 when always snapping
|
||||
return LiteGraphInternal.alwaysSnapToGrid
|
||||
? LiteGraphInternal.CANVAS_GRID_SIZE || 1
|
||||
: LiteGraphInternal.CANVAS_GRID_SIZE
|
||||
return LiteGraph.alwaysSnapToGrid
|
||||
? LiteGraph.CANVAS_GRID_SIZE || 1
|
||||
: LiteGraph.CANVAS_GRID_SIZE
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1173,11 +1164,11 @@ export class LGraph
|
||||
checkNodeTypes() {
|
||||
const { _nodes } = this
|
||||
for (const [i, node] of _nodes.entries()) {
|
||||
const ctor = LiteGraphInternal.registered_node_types[node.type]
|
||||
const ctor = LiteGraph.registered_node_types[node.type]
|
||||
if (node.constructor == ctor) continue
|
||||
|
||||
console.log('node being replaced by newer version:', node.type)
|
||||
const newnode = LiteGraphInternal.createNode(node.type)
|
||||
const newnode = LiteGraph.createNode(node.type)
|
||||
if (!newnode) continue
|
||||
_nodes[i] = newnode
|
||||
newnode.configure(node.serialize())
|
||||
@@ -1238,7 +1229,7 @@ export class LGraph
|
||||
|
||||
/* Called when something visually changed (not the graph!) */
|
||||
change(): void {
|
||||
if (LiteGraphInternal.debug) {
|
||||
if (LiteGraph.debug) {
|
||||
console.log('Graph changed')
|
||||
}
|
||||
this.canvasAction((c) => c.setDirty(true, true))
|
||||
@@ -1588,13 +1579,9 @@ export class LGraph
|
||||
})
|
||||
|
||||
// Create subgraph node object
|
||||
const subgraphNode = LiteGraphInternal.createNode(
|
||||
subgraph.id,
|
||||
subgraph.name,
|
||||
{
|
||||
outputs: structuredClone(outputs)
|
||||
}
|
||||
)
|
||||
const subgraphNode = LiteGraph.createNode(subgraph.id, subgraph.name, {
|
||||
outputs: structuredClone(outputs)
|
||||
})
|
||||
if (!subgraphNode) throw new Error('Failed to create subgraph node')
|
||||
for (let i = 0; i < inputs.length; i++) {
|
||||
Object.assign(subgraphNode.inputs[i], inputs[i])
|
||||
@@ -1611,7 +1598,7 @@ export class LGraph
|
||||
)
|
||||
|
||||
//Correct for title height. It's included in bounding box, but not _posSize
|
||||
subgraphNode.pos[1] += LiteGraphInternal.NODE_TITLE_HEIGHT / 2
|
||||
subgraphNode.pos[1] += LiteGraph.NODE_TITLE_HEIGHT / 2
|
||||
|
||||
// Add the subgraph node to the graph
|
||||
this.add(subgraphNode)
|
||||
@@ -1732,10 +1719,7 @@ export class LGraph
|
||||
const movedNodes = multiClone(subgraphNode.subgraph.nodes)
|
||||
const nodeIdMap = new Map<NodeId, NodeId>()
|
||||
for (const n_info of movedNodes) {
|
||||
const node = LiteGraphInternal.createNode(
|
||||
String(n_info.type),
|
||||
n_info.title
|
||||
)
|
||||
const node = LiteGraph.createNode(String(n_info.type), n_info.title)
|
||||
if (!node) {
|
||||
throw new Error('Node not found')
|
||||
}
|
||||
@@ -2043,7 +2027,7 @@ export class LGraph
|
||||
definitions,
|
||||
config,
|
||||
extra,
|
||||
version: LiteGraphInternal.VERSION
|
||||
version: LiteGraph.VERSION
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2068,7 +2052,7 @@ export class LGraph
|
||||
const { id, revision, config, state } = this
|
||||
|
||||
const nodeList =
|
||||
!LiteGraphInternal.use_uuids && options?.sortNodes
|
||||
!LiteGraph.use_uuids && options?.sortNodes
|
||||
? // @ts-expect-error If LiteGraph.use_uuids is false, ids are numbers.
|
||||
[...this._nodes].sort((a, b) => a.id - b.id)
|
||||
: this._nodes
|
||||
@@ -2088,8 +2072,7 @@ export class LGraph
|
||||
|
||||
// Save scale and offset
|
||||
const extra = { ...this.extra }
|
||||
if (LiteGraphInternal.saveViewportWithGraph)
|
||||
extra.ds = this.#getDragAndScale()
|
||||
if (LiteGraph.saveViewportWithGraph) extra.ds = this.#getDragAndScale()
|
||||
if (!extra.ds) delete extra.ds
|
||||
|
||||
const data: ReturnType<typeof this.asSerialisable> = {
|
||||
@@ -2247,12 +2230,9 @@ export class LGraph
|
||||
if (nodesData) {
|
||||
for (const n_info of nodesData) {
|
||||
// stored info
|
||||
let node = LiteGraphInternal.createNode(
|
||||
String(n_info.type),
|
||||
n_info.title
|
||||
)
|
||||
let node = LiteGraph.createNode(String(n_info.type), n_info.title)
|
||||
if (!node) {
|
||||
if (LiteGraphInternal.debug)
|
||||
if (LiteGraph.debug)
|
||||
console.log('Node not found or has errors:', n_info.type)
|
||||
|
||||
// in case of error we create a replacement node to avoid losing info
|
||||
@@ -2304,7 +2284,7 @@ export class LGraph
|
||||
if (groupData) {
|
||||
for (const data of groupData) {
|
||||
// TODO: Search/remove these global object refs
|
||||
const group = new LiteGraphInternal.LGraphGroup()
|
||||
const group = new LiteGraph.LGraphGroup()
|
||||
group.configure(data)
|
||||
this.add(group)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { LGraph } from './LGraph'
|
||||
import { LGraphGroup } from './LGraphGroup'
|
||||
import { LGraphNode, type NodeId, type NodeProperty } from './LGraphNode'
|
||||
import { LLink, type LinkId } from './LLink'
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import { Reroute, type RerouteId } from './Reroute'
|
||||
import { isOverNodeInput, isOverNodeOutput } from './canvas/measureSlots'
|
||||
import { strokeShape } from './draw'
|
||||
@@ -53,7 +52,7 @@ import type {
|
||||
Rect,
|
||||
Size
|
||||
} from './interfaces'
|
||||
import { Rectangle, SubgraphNode, createUuidv4 } from './litegraph'
|
||||
import { LiteGraph, Rectangle, SubgraphNode, createUuidv4 } from './litegraph'
|
||||
import {
|
||||
containsRect,
|
||||
createBounds,
|
||||
@@ -411,12 +410,12 @@ export class LGraphCanvas
|
||||
* @deprecated Use {@link LGraphNode.titleFontStyle} instead.
|
||||
*/
|
||||
get title_text_font(): string {
|
||||
return `${LiteGraphInternal.NODE_TEXT_SIZE}px ${LiteGraphInternal.NODE_FONT}`
|
||||
return `${LiteGraph.NODE_TEXT_SIZE}px ${LiteGraph.NODE_FONT}`
|
||||
}
|
||||
// #endregion Legacy accessors
|
||||
|
||||
get inner_text_font(): string {
|
||||
return `normal ${LiteGraphInternal.NODE_SUBTEXT_SIZE}px ${LiteGraphInternal.NODE_FONT}`
|
||||
return `normal ${LiteGraph.NODE_SUBTEXT_SIZE}px ${LiteGraph.NODE_FONT}`
|
||||
}
|
||||
|
||||
#maximumFrameGap = 0
|
||||
@@ -435,14 +434,14 @@ export class LGraphCanvas
|
||||
* @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead.
|
||||
*/
|
||||
get round_radius() {
|
||||
return LiteGraphInternal.ROUND_RADIUS
|
||||
return LiteGraph.ROUND_RADIUS
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link LiteGraphGlobal.ROUND_RADIUS} instead.
|
||||
*/
|
||||
set round_radius(value: number) {
|
||||
LiteGraphInternal.ROUND_RADIUS = value
|
||||
LiteGraph.ROUND_RADIUS = value
|
||||
}
|
||||
|
||||
// Cached LOD threshold values for performance
|
||||
@@ -461,7 +460,7 @@ export class LGraphCanvas
|
||||
return
|
||||
}
|
||||
|
||||
const baseFontSize = LiteGraphInternal.NODE_TEXT_SIZE // 14px
|
||||
const baseFontSize = LiteGraph.NODE_TEXT_SIZE // 14px
|
||||
const dprAdjustment = Math.sqrt(window.devicePixelRatio || 1) //Using sqrt here because higher DPR monitors do not linearily scale the readability of the font, instead they increase the font by some heurisitc, and to approximate we use sqrt to say bascially a DPR of 2 increases the readibility by 40%, 3 by 70%
|
||||
|
||||
// Calculate the zoom level where text becomes unreadable
|
||||
@@ -790,7 +789,7 @@ export class LGraphCanvas
|
||||
|
||||
// No longer in use
|
||||
// add menu when releasing link in empty space
|
||||
if (LiteGraphInternal.release_link_on_empty_shows_menu) {
|
||||
if (LiteGraph.release_link_on_empty_shows_menu) {
|
||||
const linkReleaseContext =
|
||||
this.linkConnector.state.connectingTo === 'input'
|
||||
? {
|
||||
@@ -837,8 +836,8 @@ export class LGraphCanvas
|
||||
// in range (1.01, 2.5). Less than 1 will invert the zoom direction
|
||||
this.zoom_speed = 1.1
|
||||
|
||||
this.node_title_color = LiteGraphInternal.NODE_TITLE_COLOR
|
||||
this.default_link_color = LiteGraphInternal.LINK_COLOR
|
||||
this.node_title_color = LiteGraph.NODE_TITLE_COLOR
|
||||
this.default_link_color = LiteGraph.LINK_COLOR
|
||||
this.default_connection_color = {
|
||||
input_off: '#778',
|
||||
input_on: '#7F7',
|
||||
@@ -961,7 +960,7 @@ export class LGraphCanvas
|
||||
): void {
|
||||
const canvas = LGraphCanvas.active_canvas
|
||||
|
||||
const group = new LiteGraphInternal.LGraphGroup()
|
||||
const group = new LiteGraph.LGraphGroup()
|
||||
group.pos = canvas.convertEventToCanvasOffset(mouse_event)
|
||||
if (!canvas.graph) throw new NullGraphError()
|
||||
canvas.graph.add(group)
|
||||
@@ -1011,7 +1010,7 @@ export class LGraphCanvas
|
||||
prev_menu: ContextMenu<string>,
|
||||
node: LGraphNode
|
||||
): void {
|
||||
new LiteGraphInternal.ContextMenu(['Top', 'Bottom', 'Left', 'Right'], {
|
||||
new LiteGraph.ContextMenu(['Top', 'Bottom', 'Left', 'Right'], {
|
||||
event,
|
||||
callback: inner_clicked,
|
||||
parentMenu: prev_menu
|
||||
@@ -1035,7 +1034,7 @@ export class LGraphCanvas
|
||||
event: MouseEvent,
|
||||
prev_menu: ContextMenu<string>
|
||||
): void {
|
||||
new LiteGraphInternal.ContextMenu(['Top', 'Bottom', 'Left', 'Right'], {
|
||||
new LiteGraph.ContextMenu(['Top', 'Bottom', 'Left', 'Right'], {
|
||||
event,
|
||||
callback: inner_clicked,
|
||||
parentMenu: prev_menu
|
||||
@@ -1058,7 +1057,7 @@ export class LGraphCanvas
|
||||
event: MouseEvent,
|
||||
prev_menu: ContextMenu<string>
|
||||
): void {
|
||||
new LiteGraphInternal.ContextMenu(['Vertically', 'Horizontally'], {
|
||||
new LiteGraph.ContextMenu(['Vertically', 'Horizontally'], {
|
||||
event,
|
||||
callback: inner_clicked,
|
||||
parentMenu: prev_menu
|
||||
@@ -1106,7 +1105,7 @@ export class LGraphCanvas
|
||||
): void {
|
||||
if (!graph) return
|
||||
|
||||
const categories = LiteGraphInternal.getNodeTypesCategories(
|
||||
const categories = LiteGraph.getNodeTypesCategories(
|
||||
canvas.filter || graph.filter
|
||||
).filter((category) => category.startsWith(base_category))
|
||||
const entries: AddNodeMenu[] = []
|
||||
@@ -1149,7 +1148,7 @@ export class LGraphCanvas
|
||||
}
|
||||
}
|
||||
|
||||
const nodes = LiteGraphInternal.getNodeTypesInCategory(
|
||||
const nodes = LiteGraph.getNodeTypesInCategory(
|
||||
base_category.slice(0, -1),
|
||||
canvas.filter || graph.filter
|
||||
)
|
||||
@@ -1173,7 +1172,7 @@ export class LGraphCanvas
|
||||
|
||||
const first_event = contextMenu.getFirstEvent()
|
||||
canvas.graph.beforeChange()
|
||||
const node = LiteGraphInternal.createNode(value.value)
|
||||
const node = LiteGraph.createNode(value.value)
|
||||
if (node) {
|
||||
if (!first_event)
|
||||
throw new TypeError(
|
||||
@@ -1193,7 +1192,7 @@ export class LGraphCanvas
|
||||
entries.push(entry)
|
||||
}
|
||||
|
||||
new LiteGraphInternal.ContextMenu(
|
||||
new LiteGraph.ContextMenu(
|
||||
entries,
|
||||
{ event: e, parentMenu: prev_menu },
|
||||
// @ts-expect-error - extra parameter
|
||||
@@ -1222,12 +1221,12 @@ export class LGraphCanvas
|
||||
let entries: (IContextMenuValue<INodeSlotContextItem> | null)[] = []
|
||||
|
||||
if (
|
||||
LiteGraphInternal.do_add_triggers_slots &&
|
||||
LiteGraph.do_add_triggers_slots &&
|
||||
node.findOutputSlot('onExecuted') == -1
|
||||
) {
|
||||
entries.push({
|
||||
content: 'On Executed',
|
||||
value: ['onExecuted', LiteGraphInternal.EVENT, { nameLocked: true }],
|
||||
value: ['onExecuted', LiteGraph.EVENT, { nameLocked: true }],
|
||||
className: 'event'
|
||||
})
|
||||
}
|
||||
@@ -1237,7 +1236,7 @@ export class LGraphCanvas
|
||||
|
||||
if (!entries.length) return
|
||||
|
||||
new LiteGraphInternal.ContextMenu<INodeSlotContextItem>(entries, {
|
||||
new LiteGraph.ContextMenu<INodeSlotContextItem>(entries, {
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: prev_menu,
|
||||
@@ -1265,7 +1264,7 @@ export class LGraphCanvas
|
||||
for (const i in value) {
|
||||
entries.push({ content: i, value: value[i] })
|
||||
}
|
||||
new LiteGraphInternal.ContextMenu(entries, {
|
||||
new LiteGraph.ContextMenu(entries, {
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: prev_menu,
|
||||
@@ -1324,7 +1323,7 @@ export class LGraphCanvas
|
||||
return
|
||||
}
|
||||
|
||||
new LiteGraphInternal.ContextMenu<string>(
|
||||
new LiteGraph.ContextMenu<string>(
|
||||
entries,
|
||||
{
|
||||
event: e,
|
||||
@@ -1467,21 +1466,18 @@ export class LGraphCanvas
|
||||
|
||||
let dialogCloseTimer: number
|
||||
dialog.addEventListener('mouseleave', function () {
|
||||
if (LiteGraphInternal.dialog_close_on_mouse_leave) {
|
||||
if (
|
||||
!dialog.is_modified &&
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave
|
||||
) {
|
||||
if (LiteGraph.dialog_close_on_mouse_leave) {
|
||||
if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
|
||||
// @ts-expect-error - setTimeout type
|
||||
dialogCloseTimer = setTimeout(
|
||||
dialog.close,
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave_delay
|
||||
LiteGraph.dialog_close_on_mouse_leave_delay
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
dialog.addEventListener('mouseenter', function () {
|
||||
if (LiteGraphInternal.dialog_close_on_mouse_leave) {
|
||||
if (LiteGraph.dialog_close_on_mouse_leave) {
|
||||
if (dialogCloseTimer) clearTimeout(dialogCloseTimer)
|
||||
}
|
||||
})
|
||||
@@ -1601,7 +1597,7 @@ export class LGraphCanvas
|
||||
menu: ContextMenu,
|
||||
node: LGraphNode
|
||||
): boolean {
|
||||
new LiteGraphInternal.ContextMenu(LiteGraphInternal.NODE_MODES, {
|
||||
new LiteGraph.ContextMenu(LiteGraph.NODE_MODES, {
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: menu,
|
||||
@@ -1611,9 +1607,9 @@ export class LGraphCanvas
|
||||
function inner_clicked(v: string) {
|
||||
if (!node) return
|
||||
|
||||
const kV = Object.values(LiteGraphInternal.NODE_MODES).indexOf(v)
|
||||
const kV = Object.values(LiteGraph.NODE_MODES).indexOf(v)
|
||||
const fApplyMultiNode = function (node: LGraphNode) {
|
||||
if (kV !== -1 && LiteGraphInternal.NODE_MODES[kV]) {
|
||||
if (kV !== -1 && LiteGraph.NODE_MODES[kV]) {
|
||||
node.changeMode(kV)
|
||||
} else {
|
||||
console.warn(`unexpected mode: ${v}`)
|
||||
@@ -1669,7 +1665,7 @@ export class LGraphCanvas
|
||||
}
|
||||
values.push(value)
|
||||
}
|
||||
new LiteGraphInternal.ContextMenu<string | null>(values, {
|
||||
new LiteGraph.ContextMenu<string | null>(values, {
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: menu,
|
||||
@@ -1703,27 +1699,26 @@ export class LGraphCanvas
|
||||
|
||||
static onMenuNodeShapes(
|
||||
// @ts-expect-error - unused parameter
|
||||
value: IContextMenuValue<(typeof LiteGraphInternal.VALID_SHAPES)[number]>,
|
||||
value: IContextMenuValue<(typeof LiteGraph.VALID_SHAPES)[number]>,
|
||||
// @ts-expect-error - unused parameter
|
||||
options: IContextMenuOptions<
|
||||
(typeof LiteGraphInternal.VALID_SHAPES)[number]
|
||||
>,
|
||||
options: IContextMenuOptions<(typeof LiteGraph.VALID_SHAPES)[number]>,
|
||||
e: MouseEvent,
|
||||
menu?: ContextMenu<(typeof LiteGraphInternal.VALID_SHAPES)[number]>,
|
||||
menu?: ContextMenu<(typeof LiteGraph.VALID_SHAPES)[number]>,
|
||||
node?: LGraphNode
|
||||
): boolean {
|
||||
if (!node) throw 'no node passed'
|
||||
|
||||
new LiteGraphInternal.ContextMenu<
|
||||
(typeof LiteGraphInternal.VALID_SHAPES)[number]
|
||||
>(LiteGraphInternal.VALID_SHAPES, {
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: menu,
|
||||
node
|
||||
})
|
||||
new LiteGraph.ContextMenu<(typeof LiteGraph.VALID_SHAPES)[number]>(
|
||||
LiteGraph.VALID_SHAPES,
|
||||
{
|
||||
event: e,
|
||||
callback: inner_clicked,
|
||||
parentMenu: menu,
|
||||
node
|
||||
}
|
||||
)
|
||||
|
||||
function inner_clicked(v: (typeof LiteGraphInternal.VALID_SHAPES)[number]) {
|
||||
function inner_clicked(v: (typeof LiteGraph.VALID_SHAPES)[number]) {
|
||||
if (!node) return
|
||||
if (!node.graph) throw new NullGraphError()
|
||||
|
||||
@@ -2105,8 +2100,7 @@ export class LGraphCanvas
|
||||
if (this.#maximumFrameGap > 0) {
|
||||
// Manual FPS limit
|
||||
const gap =
|
||||
this.#maximumFrameGap -
|
||||
(LiteGraphInternal.getTime() - this.last_draw_time)
|
||||
this.#maximumFrameGap - (LiteGraph.getTime() - this.last_draw_time)
|
||||
setTimeout(renderFrame.bind(this), Math.max(1, gap))
|
||||
} else {
|
||||
// FPS limited by refresh rate
|
||||
@@ -2177,7 +2171,7 @@ export class LGraphCanvas
|
||||
// Hover transitions
|
||||
// TODO: Implement single lerp ease factor for current progress on hover in/out.
|
||||
// In drawNode, multiply by ease factor and differential value (e.g. bg alpha +0.5).
|
||||
otherNode.lostFocusAt = LiteGraphInternal.getTime()
|
||||
otherNode.lostFocusAt = LiteGraph.getTime()
|
||||
|
||||
this.node_over?.onMouseLeave?.(e)
|
||||
this.node_over = undefined
|
||||
@@ -2230,7 +2224,7 @@ export class LGraphCanvas
|
||||
|
||||
this.canvas.focus()
|
||||
|
||||
LiteGraphInternal.closeAllContextMenus(ref_window)
|
||||
LiteGraph.closeAllContextMenus(ref_window)
|
||||
|
||||
if (this.onMouse?.(e) == true) return
|
||||
|
||||
@@ -2301,7 +2295,7 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
this.last_mouse = [x, y]
|
||||
this.last_mouseclick = LiteGraphInternal.getTime()
|
||||
this.last_mouseclick = LiteGraph.getTime()
|
||||
this.last_mouse_dragging = true
|
||||
|
||||
graph.change()
|
||||
@@ -2355,7 +2349,7 @@ export class LGraphCanvas
|
||||
if (
|
||||
ctrlOrMeta &&
|
||||
!e.altKey &&
|
||||
LiteGraphInternal.leftMouseClickBehavior === 'panning'
|
||||
LiteGraph.leftMouseClickBehavior === 'panning'
|
||||
) {
|
||||
this.#setupNodeSelectionDrag(e, pointer, node)
|
||||
|
||||
@@ -2370,7 +2364,7 @@ export class LGraphCanvas
|
||||
|
||||
// clone node ALT dragging
|
||||
if (
|
||||
LiteGraphInternal.alt_drag_do_clone_nodes &&
|
||||
LiteGraph.alt_drag_do_clone_nodes &&
|
||||
e.altKey &&
|
||||
!e.ctrlKey &&
|
||||
node &&
|
||||
@@ -2390,7 +2384,7 @@ export class LGraphCanvas
|
||||
if (node_data?.type != null) {
|
||||
// Ensure the cloned node is configured against the correct type (especially for SubgraphNodes)
|
||||
node_data.type = newType
|
||||
const cloned = LiteGraphInternal.createNode(newType)
|
||||
const cloned = LiteGraph.createNode(newType)
|
||||
if (cloned) {
|
||||
cloned.configure(node_data)
|
||||
cloned.pos[0] += 5
|
||||
@@ -2573,7 +2567,7 @@ export class LGraphCanvas
|
||||
}
|
||||
pointer.finally = () => (this.resizingGroup = null)
|
||||
} else {
|
||||
const f = group.font_size || LiteGraphInternal.DEFAULT_GROUP_FONT_SIZE
|
||||
const f = group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
||||
const headerHeight = f * 1.4
|
||||
if (
|
||||
isInRectangle(
|
||||
@@ -2624,10 +2618,7 @@ export class LGraphCanvas
|
||||
this.allow_dragcanvas
|
||||
) {
|
||||
// allow dragging canvas based on leftMouseClickBehavior or read-only mode
|
||||
if (
|
||||
LiteGraphInternal.leftMouseClickBehavior === 'panning' ||
|
||||
this.read_only
|
||||
) {
|
||||
if (LiteGraph.leftMouseClickBehavior === 'panning' || this.read_only) {
|
||||
pointer.onClick = () => this.processSelect(null, e)
|
||||
pointer.finally = () => (this.dragging_canvas = false)
|
||||
this.dragging_canvas = true
|
||||
@@ -2724,11 +2715,11 @@ export class LGraphCanvas
|
||||
linkConnector.dragNewFromOutput(graph, node, output)
|
||||
this.#linkConnectorDrop()
|
||||
|
||||
if (LiteGraphInternal.shift_click_do_break_link_from) {
|
||||
if (LiteGraph.shift_click_do_break_link_from) {
|
||||
if (e.shiftKey) {
|
||||
node.disconnectOutput(i)
|
||||
}
|
||||
} else if (LiteGraphInternal.ctrl_alt_click_do_break_link) {
|
||||
} else if (LiteGraph.ctrl_alt_click_do_break_link) {
|
||||
if (ctrlOrMeta && e.altKey && !e.shiftKey) {
|
||||
node.disconnectOutput(i)
|
||||
}
|
||||
@@ -2757,13 +2748,13 @@ export class LGraphCanvas
|
||||
pointer.onClick = () => node.onInputClick?.(i, e)
|
||||
|
||||
const shouldBreakLink =
|
||||
LiteGraphInternal.ctrl_alt_click_do_break_link &&
|
||||
LiteGraph.ctrl_alt_click_do_break_link &&
|
||||
ctrlOrMeta &&
|
||||
e.altKey &&
|
||||
!e.shiftKey
|
||||
if (input.link !== null || input._floatingLinks?.size) {
|
||||
// Existing link
|
||||
if (shouldBreakLink || LiteGraphInternal.click_do_break_link_to) {
|
||||
if (shouldBreakLink || LiteGraph.click_do_break_link_to) {
|
||||
node.disconnectInput(i, true)
|
||||
} else if (e.shiftKey || this.allow_reconnect_links) {
|
||||
linkConnector.moveInputLink(graph, input)
|
||||
@@ -3038,7 +3029,7 @@ export class LGraphCanvas
|
||||
const { pointer } = this
|
||||
|
||||
if (
|
||||
LiteGraphInternal.middle_click_slot_add_default_node &&
|
||||
LiteGraph.middle_click_slot_add_default_node &&
|
||||
node &&
|
||||
this.allow_interaction &&
|
||||
!this.read_only &&
|
||||
@@ -3333,7 +3324,7 @@ export class LGraphCanvas
|
||||
} else if (
|
||||
inputId != -1 &&
|
||||
node.inputs[inputId] &&
|
||||
LiteGraphInternal.isValidConnection(
|
||||
LiteGraph.isValidConnection(
|
||||
firstLink.fromSlot.type,
|
||||
node.inputs[inputId].type
|
||||
)
|
||||
@@ -3360,7 +3351,7 @@ export class LGraphCanvas
|
||||
if (
|
||||
outputId != -1 &&
|
||||
node.outputs[outputId] &&
|
||||
LiteGraphInternal.isValidConnection(
|
||||
LiteGraph.isValidConnection(
|
||||
firstLink.fromSlot.type,
|
||||
node.outputs[outputId].type
|
||||
)
|
||||
@@ -3513,7 +3504,7 @@ export class LGraphCanvas
|
||||
*/
|
||||
#processDraggedItems(e: CanvasPointerEvent): void {
|
||||
const { graph } = this
|
||||
if (e.shiftKey || LiteGraphInternal.alwaysSnapToGrid)
|
||||
if (e.shiftKey || LiteGraph.alwaysSnapToGrid)
|
||||
graph?.snapToGrid(this.selectedItems)
|
||||
|
||||
this.dirty_canvas = true
|
||||
@@ -3537,7 +3528,7 @@ export class LGraphCanvas
|
||||
|
||||
this.adjustMouseEvent(e)
|
||||
|
||||
const now = LiteGraphInternal.getTime()
|
||||
const now = LiteGraph.getTime()
|
||||
e.click_time = now - this.last_mouseclick
|
||||
|
||||
/** The mouseup event occurred near the mousedown event. */
|
||||
@@ -3639,7 +3630,7 @@ export class LGraphCanvas
|
||||
e.ctrlKey || (e.metaKey && navigator.platform.includes('Mac'))
|
||||
const isZoomModifier = isCtrlOrMacMeta && !e.altKey && !e.shiftKey
|
||||
|
||||
if (isZoomModifier || LiteGraphInternal.mouseWheelScroll === 'zoom') {
|
||||
if (isZoomModifier || LiteGraph.mouseWheelScroll === 'zoom') {
|
||||
// Zoom mode or modifier key pressed - use wheel for zoom
|
||||
if (isTrackpad) {
|
||||
// Trackpad gesture - use smooth scaling
|
||||
@@ -3879,7 +3870,7 @@ export class LGraphCanvas
|
||||
|
||||
// if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior
|
||||
if (
|
||||
!LiteGraphInternal.ctrl_shift_v_paste_connect_unselected_outputs &&
|
||||
!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs &&
|
||||
connectInputs
|
||||
)
|
||||
return
|
||||
@@ -3961,8 +3952,7 @@ export class LGraphCanvas
|
||||
|
||||
// Nodes
|
||||
for (const info of parsed.nodes) {
|
||||
const node =
|
||||
info.type == null ? null : LiteGraphInternal.createNode(info.type)
|
||||
const node = info.type == null ? null : LiteGraph.createNode(info.type)
|
||||
if (!node) {
|
||||
// failedNodes.push(info)
|
||||
continue
|
||||
@@ -4005,7 +3995,7 @@ export class LGraphCanvas
|
||||
// If it wasn't copied, use the original graph value
|
||||
if (
|
||||
connectInputs &&
|
||||
LiteGraphInternal.ctrl_shift_v_paste_connect_unselected_outputs
|
||||
LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs
|
||||
) {
|
||||
outNode ??= graph.getNodeById(info.origin_id)
|
||||
afterRerouteId ??= info.parentId
|
||||
@@ -4042,18 +4032,6 @@ export class LGraphCanvas
|
||||
|
||||
// TODO: Report failures, i.e. `failedNodes`
|
||||
|
||||
const newPositions = created.map((node) => ({
|
||||
nodeId: String(node.id),
|
||||
bounds: {
|
||||
x: node.pos[0],
|
||||
y: node.pos[1],
|
||||
width: node.size?.[0] ?? 100,
|
||||
height: node.size?.[1] ?? 200
|
||||
}
|
||||
}))
|
||||
|
||||
layoutStore.batchUpdateNodeBounds(newPositions)
|
||||
|
||||
this.selectItems(created)
|
||||
|
||||
graph.afterChange()
|
||||
@@ -4582,7 +4560,7 @@ export class LGraphCanvas
|
||||
return
|
||||
|
||||
// fps counting
|
||||
const now = LiteGraphInternal.getTime()
|
||||
const now = LiteGraph.getTime()
|
||||
this.render_time = (now - this.last_draw_time) * 0.001
|
||||
this.last_draw_time = now
|
||||
|
||||
@@ -4647,7 +4625,7 @@ export class LGraphCanvas
|
||||
|
||||
// TODO: Set snapping value when changed instead of once per frame
|
||||
this.#snapToGrid =
|
||||
this.#shiftDown || LiteGraphInternal.alwaysSnapToGrid
|
||||
this.#shiftDown || LiteGraph.alwaysSnapToGrid
|
||||
? this.graph?.getSnapToGridSize()
|
||||
: undefined
|
||||
|
||||
@@ -4759,10 +4737,7 @@ export class LGraphCanvas
|
||||
|
||||
ctx.fillStyle = colour
|
||||
ctx.beginPath()
|
||||
if (
|
||||
connType === LiteGraphInternal.EVENT ||
|
||||
connShape === RenderShape.BOX
|
||||
) {
|
||||
if (connType === LiteGraph.EVENT || connShape === RenderShape.BOX) {
|
||||
ctx.rect(pos[0] - 6 + 0.5, pos[1] - 5 + 0.5, 14, 10)
|
||||
ctx.rect(
|
||||
highlightPos[0] - 6 + 0.5,
|
||||
@@ -4852,7 +4827,7 @@ export class LGraphCanvas
|
||||
|
||||
/** Get the target snap / highlight point in graph space */
|
||||
#getHighlightPosition(): ReadOnlyPoint {
|
||||
return LiteGraphInternal.snaps_for_comfy
|
||||
return LiteGraph.snaps_for_comfy
|
||||
? this.linkConnector.state.snapLinksPos ??
|
||||
this._highlight_pos ??
|
||||
this.graph_mouse
|
||||
@@ -4888,7 +4863,7 @@ export class LGraphCanvas
|
||||
const { linkConnector } = this
|
||||
const { overReroute, overWidget } = linkConnector
|
||||
if (
|
||||
!LiteGraphInternal.snap_highlights_node ||
|
||||
!LiteGraph.snap_highlights_node ||
|
||||
!linkConnector.isConnecting ||
|
||||
linkConnectorSnap
|
||||
)
|
||||
@@ -4905,7 +4880,7 @@ export class LGraphCanvas
|
||||
|
||||
const area = node.boundingRect
|
||||
const gap = 3
|
||||
const radius = LiteGraphInternal.ROUND_RADIUS + gap
|
||||
const radius = LiteGraph.ROUND_RADIUS + gap
|
||||
|
||||
const x = area[0] - gap
|
||||
const y = area[1] - gap
|
||||
@@ -4953,7 +4928,7 @@ export class LGraphCanvas
|
||||
const {
|
||||
pos: [nodeX, nodeY]
|
||||
} = node
|
||||
const height = LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
const height = LiteGraph.NODE_WIDGET_HEIGHT
|
||||
if (
|
||||
overWidget.type.startsWith('custom') &&
|
||||
computedHeight != null &&
|
||||
@@ -4997,7 +4972,7 @@ export class LGraphCanvas
|
||||
ctx.save()
|
||||
ctx.translate(x, y)
|
||||
|
||||
ctx.font = `10px ${LiteGraphInternal.DEFAULT_FONT}`
|
||||
ctx.font = `10px ${LiteGraph.DEFAULT_FONT}`
|
||||
ctx.fillStyle = '#888'
|
||||
ctx.textAlign = 'left'
|
||||
if (this.graph) {
|
||||
@@ -5176,7 +5151,7 @@ export class LGraphCanvas
|
||||
// When Vue nodes mode is enabled, LiteGraph should not draw node chrome or widgets.
|
||||
// We still need to keep slot metrics and layout in sync for hit-testing and links.
|
||||
// Interaction system changes coming later, chances are vue nodes mode will be mostly broken on land
|
||||
if (LiteGraphInternal.vueNodesMode) {
|
||||
if (LiteGraph.vueNodesMode) {
|
||||
// Prepare concrete slots and compute layout measures without rendering visuals.
|
||||
node._setConcreteSlots()
|
||||
if (!node.collapsed) {
|
||||
@@ -5193,7 +5168,7 @@ export class LGraphCanvas
|
||||
ctx.globalAlpha = editor_alpha
|
||||
|
||||
if (this.render_shadows && !low_quality) {
|
||||
ctx.shadowColor = LiteGraphInternal.DEFAULT_SHADOW_COLOR
|
||||
ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR
|
||||
ctx.shadowOffsetX = 2 * this.ds.scale
|
||||
ctx.shadowOffsetY = 2 * this.ds.scale
|
||||
ctx.shadowBlur = 3 * this.ds.scale
|
||||
@@ -5233,7 +5208,7 @@ export class LGraphCanvas
|
||||
|
||||
// Render title buttons (if not collapsed)
|
||||
if (node.title_buttons && !node.flags.collapsed) {
|
||||
const title_height = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const title_height = LiteGraph.NODE_TITLE_HEIGHT
|
||||
let current_x = size[0] // Start flush with right edge
|
||||
|
||||
for (let i = 0; i < node.title_buttons.length; i++) {
|
||||
@@ -5260,7 +5235,7 @@ export class LGraphCanvas
|
||||
ctx.shadowColor = 'transparent'
|
||||
|
||||
// TODO: Legacy behaviour: onDrawForeground received ctx in this state
|
||||
ctx.strokeStyle = LiteGraphInternal.NODE_BOX_OUTLINE_COLOR
|
||||
ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR
|
||||
|
||||
// Draw Foreground
|
||||
node.onDrawForeground?.(ctx, this, this.canvas)
|
||||
@@ -5388,7 +5363,7 @@ export class LGraphCanvas
|
||||
ctx.strokeStyle = fgcolor
|
||||
ctx.fillStyle = bgcolor
|
||||
|
||||
const title_height = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const title_height = LiteGraph.NODE_TITLE_HEIGHT
|
||||
const { low_quality } = this
|
||||
|
||||
const { collapsed } = node.flags
|
||||
@@ -5420,13 +5395,8 @@ export class LGraphCanvas
|
||||
area[2],
|
||||
area[3],
|
||||
shape == RenderShape.CARD
|
||||
? [
|
||||
LiteGraphInternal.ROUND_RADIUS,
|
||||
LiteGraphInternal.ROUND_RADIUS,
|
||||
0,
|
||||
0
|
||||
]
|
||||
: [LiteGraphInternal.ROUND_RADIUS]
|
||||
? [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0]
|
||||
: [LiteGraph.ROUND_RADIUS]
|
||||
)
|
||||
} else if (shape == RenderShape.CIRCLE) {
|
||||
ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI * 2)
|
||||
@@ -5554,7 +5524,7 @@ export class LGraphCanvas
|
||||
|
||||
const visibleReroutes: Reroute[] = []
|
||||
|
||||
const now = LiteGraphInternal.getTime()
|
||||
const now = LiteGraph.getTime()
|
||||
const { visible_area } = this
|
||||
LGraphCanvas.#margin_area[0] = visible_area[0] - 20
|
||||
LGraphCanvas.#margin_area[1] = visible_area[1] - 20
|
||||
@@ -5581,7 +5551,7 @@ export class LGraphCanvas
|
||||
const link = graph._links.get(link_id)
|
||||
if (!link) continue
|
||||
|
||||
const endPos: Point = LiteGraphInternal.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
|
||||
const endPos: Point = LiteGraph.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
|
||||
? getSlotPosition(node, i, true)
|
||||
: node.getInputPos(i)
|
||||
|
||||
@@ -5593,7 +5563,7 @@ export class LGraphCanvas
|
||||
const startPos: Point =
|
||||
outputId === -1
|
||||
? [start_node.pos[0] + 10, start_node.pos[1] + 10]
|
||||
: LiteGraphInternal.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
|
||||
: LiteGraph.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
|
||||
? getSlotPosition(start_node, outputId, false)
|
||||
: start_node.getOutputPos(outputId)
|
||||
|
||||
@@ -6013,23 +5983,23 @@ export class LGraphCanvas
|
||||
for (const node of visible_nodes) {
|
||||
ctx.fillStyle = 'black'
|
||||
ctx.fillRect(
|
||||
node.pos[0] - LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
node.pos[1] - LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT,
|
||||
node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT,
|
||||
LiteGraph.NODE_TITLE_HEIGHT,
|
||||
LiteGraph.NODE_TITLE_HEIGHT
|
||||
)
|
||||
if (node.order == 0) {
|
||||
ctx.strokeRect(
|
||||
node.pos[0] - LiteGraphInternal.NODE_TITLE_HEIGHT + 0.5,
|
||||
node.pos[1] - LiteGraphInternal.NODE_TITLE_HEIGHT + 0.5,
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,
|
||||
node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5,
|
||||
LiteGraph.NODE_TITLE_HEIGHT,
|
||||
LiteGraph.NODE_TITLE_HEIGHT
|
||||
)
|
||||
}
|
||||
ctx.fillStyle = '#FFF'
|
||||
ctx.fillText(
|
||||
toString(node.order),
|
||||
node.pos[0] + LiteGraphInternal.NODE_TITLE_HEIGHT * -0.5,
|
||||
node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5,
|
||||
node.pos[1] - 6
|
||||
)
|
||||
}
|
||||
@@ -6128,7 +6098,7 @@ export class LGraphCanvas
|
||||
|
||||
const { origin_id, origin_slot } = segment
|
||||
if (origin_id == null || origin_slot == null) {
|
||||
new LiteGraphInternal.ContextMenu<string>(['Link has no origin'], {
|
||||
new LiteGraph.ContextMenu<string>(['Link has no origin'], {
|
||||
event: e,
|
||||
title
|
||||
})
|
||||
@@ -6140,7 +6110,7 @@ export class LGraphCanvas
|
||||
|
||||
const options = ['Add Node', 'Add Reroute', null, 'Delete', null]
|
||||
|
||||
const menu = new LiteGraphInternal.ContextMenu<string>(options, {
|
||||
const menu = new LiteGraph.ContextMenu<string>(options, {
|
||||
event: e,
|
||||
title,
|
||||
callback: inner_clicked.bind(this)
|
||||
@@ -6303,11 +6273,10 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
// check for defaults nodes for this slottype
|
||||
const fromSlotType =
|
||||
slotX.type == LiteGraphInternal.EVENT ? '_event_' : slotX.type
|
||||
const fromSlotType = slotX.type == LiteGraph.EVENT ? '_event_' : slotX.type
|
||||
const slotTypesDefault = isFrom
|
||||
? LiteGraphInternal.slot_types_default_out
|
||||
: LiteGraphInternal.slot_types_default_in
|
||||
? LiteGraph.slot_types_default_out
|
||||
: LiteGraph.slot_types_default_in
|
||||
if (slotTypesDefault?.[fromSlotType]) {
|
||||
// TODO: Remove "any" kludge
|
||||
let nodeNewType: any = false
|
||||
@@ -6336,18 +6305,14 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
// that.graph.beforeChange();
|
||||
const xSizeFix = opts.posSizeFix[0] * LiteGraphInternal.NODE_WIDTH
|
||||
const ySizeFix = opts.posSizeFix[1] * LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
const xSizeFix = opts.posSizeFix[0] * LiteGraph.NODE_WIDTH
|
||||
const ySizeFix = opts.posSizeFix[1] * LiteGraph.NODE_SLOT_HEIGHT
|
||||
const nodeX = opts.position[0] + opts.posAdd[0] + xSizeFix
|
||||
const nodeY = opts.position[1] + opts.posAdd[1] + ySizeFix
|
||||
const pos = [nodeX, nodeY]
|
||||
const newNode = LiteGraphInternal.createNode(
|
||||
nodeNewType,
|
||||
nodeNewOpts.title,
|
||||
{
|
||||
pos
|
||||
}
|
||||
)
|
||||
const newNode = LiteGraph.createNode(nodeNewType, nodeNewOpts.title, {
|
||||
pos
|
||||
})
|
||||
if (newNode) {
|
||||
// if is object pass options
|
||||
if (nodeNewOpts) {
|
||||
@@ -6515,11 +6480,10 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
// get defaults nodes for this slottype
|
||||
const fromSlotType =
|
||||
slotX.type == LiteGraphInternal.EVENT ? '_event_' : slotX.type
|
||||
const fromSlotType = slotX.type == LiteGraph.EVENT ? '_event_' : slotX.type
|
||||
const slotTypesDefault = isFrom
|
||||
? LiteGraphInternal.slot_types_default_out
|
||||
: LiteGraphInternal.slot_types_default_in
|
||||
? LiteGraph.slot_types_default_out
|
||||
: LiteGraph.slot_types_default_in
|
||||
if (slotTypesDefault?.[fromSlotType]) {
|
||||
if (typeof slotTypesDefault[fromSlotType] == 'object') {
|
||||
for (const typeX in slotTypesDefault[fromSlotType]) {
|
||||
@@ -6531,7 +6495,7 @@ export class LGraphCanvas
|
||||
}
|
||||
|
||||
// build menu
|
||||
const menu = new LiteGraphInternal.ContextMenu<string>(options, {
|
||||
const menu = new LiteGraph.ContextMenu<string>(options, {
|
||||
event: opts.e,
|
||||
extra: slotX,
|
||||
title:
|
||||
@@ -6680,23 +6644,20 @@ export class LGraphCanvas
|
||||
|
||||
let dialogCloseTimer: number
|
||||
let prevent_timeout = 0
|
||||
LiteGraphInternal.pointerListenerAdd(dialog, 'leave', function () {
|
||||
LiteGraph.pointerListenerAdd(dialog, 'leave', function () {
|
||||
if (prevent_timeout) return
|
||||
if (LiteGraphInternal.dialog_close_on_mouse_leave) {
|
||||
if (
|
||||
!dialog.is_modified &&
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave
|
||||
) {
|
||||
if (LiteGraph.dialog_close_on_mouse_leave) {
|
||||
if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
|
||||
// @ts-expect-error - setTimeout type
|
||||
dialogCloseTimer = setTimeout(
|
||||
dialog.close,
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave_delay
|
||||
LiteGraph.dialog_close_on_mouse_leave_delay
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
LiteGraphInternal.pointerListenerAdd(dialog, 'enter', function () {
|
||||
if (LiteGraphInternal.dialog_close_on_mouse_leave && dialogCloseTimer)
|
||||
LiteGraph.pointerListenerAdd(dialog, 'enter', function () {
|
||||
if (LiteGraph.dialog_close_on_mouse_leave && dialogCloseTimer)
|
||||
clearTimeout(dialogCloseTimer)
|
||||
})
|
||||
const selInDia = dialog.querySelectorAll('select')
|
||||
@@ -6805,7 +6766,7 @@ export class LGraphCanvas
|
||||
node_to: null,
|
||||
// TODO check for registered_slot_[in/out]_types not empty
|
||||
// this will be checked for functionality enabled : filter on slot type, in and out
|
||||
do_type_filter: LiteGraphInternal.search_filter_enabled,
|
||||
do_type_filter: LiteGraph.search_filter_enabled,
|
||||
|
||||
// these are default: pass to set initially set values
|
||||
// @ts-expect-error Property missing from interface definition
|
||||
@@ -6814,9 +6775,9 @@ export class LGraphCanvas
|
||||
type_filter_out: false,
|
||||
show_general_if_none_on_typefilter: true,
|
||||
show_general_after_typefiltered: true,
|
||||
hide_on_mouse_leave: LiteGraphInternal.search_hide_on_mouse_leave,
|
||||
hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave,
|
||||
show_all_if_empty: true,
|
||||
show_all_on_open: LiteGraphInternal.search_show_all_on_open
|
||||
show_all_on_open: LiteGraph.search_show_all_on_open
|
||||
}
|
||||
Object.assign(options, searchOptions)
|
||||
|
||||
@@ -6877,7 +6838,7 @@ export class LGraphCanvas
|
||||
// FIXME: Remove "any" kludge
|
||||
let prevent_timeout: any = false
|
||||
let timeout_close: number | null = null
|
||||
LiteGraphInternal.pointerListenerAdd(dialog, 'enter', function () {
|
||||
LiteGraph.pointerListenerAdd(dialog, 'enter', function () {
|
||||
if (timeout_close) {
|
||||
clearTimeout(timeout_close)
|
||||
timeout_close = null
|
||||
@@ -6972,12 +6933,12 @@ export class LGraphCanvas
|
||||
// if should filter on type, load and fill selected and choose elements if passed
|
||||
if (options.do_type_filter) {
|
||||
if (selIn) {
|
||||
const aSlots = LiteGraphInternal.slot_types_in
|
||||
const aSlots = LiteGraph.slot_types_in
|
||||
const nSlots = aSlots.length
|
||||
|
||||
if (
|
||||
options.type_filter_in == LiteGraphInternal.EVENT ||
|
||||
options.type_filter_in == LiteGraphInternal.ACTION
|
||||
options.type_filter_in == LiteGraph.EVENT ||
|
||||
options.type_filter_in == LiteGraph.ACTION
|
||||
) {
|
||||
options.type_filter_in = '_event_'
|
||||
}
|
||||
@@ -7000,11 +6961,11 @@ export class LGraphCanvas
|
||||
})
|
||||
}
|
||||
if (selOut) {
|
||||
const aSlots = LiteGraphInternal.slot_types_out
|
||||
const aSlots = LiteGraph.slot_types_out
|
||||
|
||||
if (
|
||||
options.type_filter_out == LiteGraphInternal.EVENT ||
|
||||
options.type_filter_out == LiteGraphInternal.ACTION
|
||||
options.type_filter_out == LiteGraph.EVENT ||
|
||||
options.type_filter_out == LiteGraph.ACTION
|
||||
) {
|
||||
options.type_filter_out = '_event_'
|
||||
}
|
||||
@@ -7063,7 +7024,7 @@ export class LGraphCanvas
|
||||
if (!graphcanvas.graph) throw new NullGraphError()
|
||||
|
||||
graphcanvas.graph.beforeChange()
|
||||
const node = LiteGraphInternal.createNode(name)
|
||||
const node = LiteGraph.createNode(name)
|
||||
if (node) {
|
||||
node.pos = graphcanvas.convertEventToCanvasOffset(safeEvent)
|
||||
graphcanvas.graph.add(node, false)
|
||||
@@ -7216,7 +7177,7 @@ export class LGraphCanvas
|
||||
sOut = that.search_box.querySelector('.slot_out_type_filter')
|
||||
}
|
||||
|
||||
const keys = Object.keys(LiteGraphInternal.registered_node_types)
|
||||
const keys = Object.keys(LiteGraph.registered_node_types)
|
||||
const filtered = keys.filter((x) => inner_test_filter(x))
|
||||
|
||||
for (const item of filtered) {
|
||||
@@ -7236,7 +7197,7 @@ export class LGraphCanvas
|
||||
// FIXME: Undeclared variable again
|
||||
// @ts-expect-error Variable declared without type annotation
|
||||
filtered_extra = []
|
||||
for (const i in LiteGraphInternal.registered_node_types) {
|
||||
for (const i in LiteGraph.registered_node_types) {
|
||||
if (
|
||||
inner_test_filter(i, {
|
||||
inTypeOverride: sIn && sIn.value ? '*' : false,
|
||||
@@ -7266,7 +7227,7 @@ export class LGraphCanvas
|
||||
) {
|
||||
// @ts-expect-error Variable declared without type annotation
|
||||
filtered_extra = []
|
||||
for (const i in LiteGraphInternal.registered_node_types) {
|
||||
for (const i in LiteGraph.registered_node_types) {
|
||||
if (inner_test_filter(i, { skipFilter: true }))
|
||||
// @ts-expect-error Variable declared without type annotation
|
||||
filtered_extra.push(i)
|
||||
@@ -7297,7 +7258,7 @@ export class LGraphCanvas
|
||||
outTypeOverride: false
|
||||
}
|
||||
const opts = Object.assign(optsDef, optsIn)
|
||||
const ctor = LiteGraphInternal.registered_node_types[type]
|
||||
const ctor = LiteGraph.registered_node_types[type]
|
||||
if (filter && ctor.filter != filter) return false
|
||||
if (
|
||||
(!options.show_all_if_empty || str) &&
|
||||
@@ -7314,30 +7275,18 @@ export class LGraphCanvas
|
||||
let sV =
|
||||
opts.inTypeOverride !== false ? opts.inTypeOverride : sIn.value
|
||||
// type is stored
|
||||
if (
|
||||
sIn &&
|
||||
sV &&
|
||||
LiteGraphInternal.registered_slot_in_types[sV]?.nodes
|
||||
) {
|
||||
if (sIn && sV && LiteGraph.registered_slot_in_types[sV]?.nodes) {
|
||||
const doesInc =
|
||||
LiteGraphInternal.registered_slot_in_types[sV].nodes.includes(
|
||||
sType
|
||||
)
|
||||
LiteGraph.registered_slot_in_types[sV].nodes.includes(sType)
|
||||
if (doesInc === false) return false
|
||||
}
|
||||
|
||||
sV = sOut.value
|
||||
if (opts.outTypeOverride !== false) sV = opts.outTypeOverride
|
||||
// type is stored
|
||||
if (
|
||||
sOut &&
|
||||
sV &&
|
||||
LiteGraphInternal.registered_slot_out_types[sV]?.nodes
|
||||
) {
|
||||
if (sOut && sV && LiteGraph.registered_slot_out_types[sV]?.nodes) {
|
||||
const doesInc =
|
||||
LiteGraphInternal.registered_slot_out_types[sV].nodes.includes(
|
||||
sType
|
||||
)
|
||||
LiteGraph.registered_slot_out_types[sV].nodes.includes(sType)
|
||||
if (doesInc === false) return false
|
||||
}
|
||||
}
|
||||
@@ -7349,7 +7298,7 @@ export class LGraphCanvas
|
||||
const help = document.createElement('div')
|
||||
first ||= type
|
||||
|
||||
const nodeType = LiteGraphInternal.registered_node_types[type]
|
||||
const nodeType = LiteGraph.registered_node_types[type]
|
||||
if (nodeType?.title) {
|
||||
help.textContent = nodeType?.title
|
||||
const typeEl = document.createElement('span')
|
||||
@@ -7583,22 +7532,16 @@ export class LGraphCanvas
|
||||
dialog.addEventListener('mouseleave', function () {
|
||||
if (prevent_timeout) return
|
||||
|
||||
if (
|
||||
!dialog.is_modified &&
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave
|
||||
) {
|
||||
if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) {
|
||||
// @ts-expect-error - setTimeout type
|
||||
dialogCloseTimer = setTimeout(
|
||||
dialog.close,
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave_delay
|
||||
LiteGraph.dialog_close_on_mouse_leave_delay
|
||||
)
|
||||
}
|
||||
})
|
||||
dialog.addEventListener('mouseenter', function () {
|
||||
if (
|
||||
options.closeOnLeave ||
|
||||
LiteGraphInternal.dialog_close_on_mouse_leave
|
||||
) {
|
||||
if (options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) {
|
||||
if (dialogCloseTimer) clearTimeout(dialogCloseTimer)
|
||||
}
|
||||
})
|
||||
@@ -7801,7 +7744,7 @@ export class LGraphCanvas
|
||||
innerChange(propname, v)
|
||||
return false
|
||||
}
|
||||
new LiteGraphInternal.ContextMenu(
|
||||
new LiteGraph.ContextMenu(
|
||||
values,
|
||||
{
|
||||
event,
|
||||
@@ -7884,10 +7827,8 @@ export class LGraphCanvas
|
||||
if (typeof value !== 'string')
|
||||
throw new TypeError('Attempting to set mode to non-string value.')
|
||||
|
||||
const kV = Object.values(LiteGraphInternal.NODE_MODES).indexOf(
|
||||
value
|
||||
)
|
||||
if (kV !== -1 && LiteGraphInternal.NODE_MODES[kV]) {
|
||||
const kV = Object.values(LiteGraph.NODE_MODES).indexOf(value)
|
||||
if (kV !== -1 && LiteGraph.NODE_MODES[kV]) {
|
||||
node.changeMode(kV)
|
||||
} else {
|
||||
console.warn(`unexpected mode: ${value}`)
|
||||
@@ -7918,12 +7859,12 @@ export class LGraphCanvas
|
||||
panel.addWidget('string', 'Title', node.title, {}, fUpdate)
|
||||
|
||||
const mode =
|
||||
node.mode == null ? undefined : LiteGraphInternal.NODE_MODES[node.mode]
|
||||
node.mode == null ? undefined : LiteGraph.NODE_MODES[node.mode]
|
||||
panel.addWidget(
|
||||
'combo',
|
||||
'Mode',
|
||||
mode,
|
||||
{ values: LiteGraphInternal.NODE_MODES },
|
||||
{ values: LiteGraph.NODE_MODES },
|
||||
fUpdate
|
||||
)
|
||||
|
||||
@@ -8071,7 +8012,7 @@ export class LGraphCanvas
|
||||
| IContextMenuValue<string | null>
|
||||
| IContextMenuValue<INodeSlotContextItem>
|
||||
| IContextMenuValue<unknown, LGraphNode>
|
||||
| IContextMenuValue<(typeof LiteGraphInternal.VALID_SHAPES)[number]>
|
||||
| IContextMenuValue<(typeof LiteGraph.VALID_SHAPES)[number]>
|
||||
| null
|
||||
)[]
|
||||
if (node.getMenuOptions) {
|
||||
@@ -8293,10 +8234,10 @@ export class LGraphCanvas
|
||||
}
|
||||
// @ts-expect-error Slot type can be number and has number checks
|
||||
options.title = (slot.input ? slot.input.type : slot.output.type) || '*'
|
||||
if (slot.input && slot.input.type == LiteGraphInternal.ACTION)
|
||||
if (slot.input && slot.input.type == LiteGraph.ACTION)
|
||||
options.title = 'Action'
|
||||
|
||||
if (slot.output && slot.output.type == LiteGraphInternal.EVENT)
|
||||
if (slot.output && slot.output.type == LiteGraph.EVENT)
|
||||
options.title = 'Event'
|
||||
} else {
|
||||
// on node
|
||||
@@ -8358,7 +8299,7 @@ export class LGraphCanvas
|
||||
if (!menu_info) return
|
||||
|
||||
// @ts-expect-error Remove param ref_window - unused
|
||||
new LiteGraphInternal.ContextMenu(menu_info, options, ref_window)
|
||||
new LiteGraph.ContextMenu(menu_info, options, ref_window)
|
||||
|
||||
const createDialog = (options: IDialogOptions) =>
|
||||
this.createDialog(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { NullGraphError } from '@/lib/litegraph/src/infrastructure/NullGraphErro
|
||||
import type { LGraph } from './LGraph'
|
||||
import { LGraphCanvas } from './LGraphCanvas'
|
||||
import { LGraphNode } from './LGraphNode'
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import { strokeShape } from './draw'
|
||||
import type {
|
||||
ColorOption,
|
||||
@@ -14,6 +13,7 @@ import type {
|
||||
Positionable,
|
||||
Size
|
||||
} from './interfaces'
|
||||
import { LiteGraph } from './litegraph'
|
||||
import {
|
||||
containsCentre,
|
||||
containsRect,
|
||||
@@ -39,7 +39,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
color?: string
|
||||
title: string
|
||||
font?: string
|
||||
font_size: number = LiteGraphInternal.DEFAULT_GROUP_FONT || 24
|
||||
font_size: number = LiteGraph.DEFAULT_GROUP_FONT || 24
|
||||
_bounding: Float32Array = new Float32Array([
|
||||
10,
|
||||
10,
|
||||
@@ -170,8 +170,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
*/
|
||||
draw(graphCanvas: LGraphCanvas, ctx: CanvasRenderingContext2D): void {
|
||||
const { padding, resizeLength, defaultColour } = LGraphGroup
|
||||
const font_size =
|
||||
this.font_size || LiteGraphInternal.DEFAULT_GROUP_FONT_SIZE
|
||||
const font_size = this.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE
|
||||
|
||||
const [x, y] = this._pos
|
||||
const [width, height] = this._size
|
||||
@@ -202,7 +201,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
ctx.fill()
|
||||
|
||||
// Title
|
||||
ctx.font = `${font_size}px ${LiteGraphInternal.GROUP_FONT}`
|
||||
ctx.font = `${font_size}px ${LiteGraph.GROUP_FONT}`
|
||||
ctx.textAlign = 'left'
|
||||
ctx.fillText(
|
||||
this.title + (this.pinned ? '📌' : ''),
|
||||
@@ -210,7 +209,7 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
|
||||
y + font_size
|
||||
)
|
||||
|
||||
if (LiteGraphInternal.highlight_selected_group && this.selected) {
|
||||
if (LiteGraph.highlight_selected_group && this.selected) {
|
||||
strokeShape(ctx, this._bounding, {
|
||||
title_height: this.titleHeight,
|
||||
padding
|
||||
|
||||
@@ -14,7 +14,6 @@ import { BadgePosition, LGraphBadge } from './LGraphBadge'
|
||||
import { LGraphButton, type LGraphButtonOptions } from './LGraphButton'
|
||||
import { LGraphCanvas } from './LGraphCanvas'
|
||||
import { LLink } from './LLink'
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import type { Reroute, RerouteId } from './Reroute'
|
||||
import { getNodeInputOnPos, getNodeOutputOnPos } from './canvas/measureSlots'
|
||||
import type { IDrawBoundingOptions } from './draw'
|
||||
@@ -43,7 +42,12 @@ import type {
|
||||
Rect,
|
||||
Size
|
||||
} from './interfaces'
|
||||
import type { LGraphNodeConstructor, Subgraph, SubgraphNode } from './litegraph'
|
||||
import {
|
||||
type LGraphNodeConstructor,
|
||||
LiteGraph,
|
||||
type Subgraph,
|
||||
type SubgraphNode
|
||||
} from './litegraph'
|
||||
import {
|
||||
createBounds,
|
||||
isInRect,
|
||||
@@ -240,11 +244,11 @@ export class LGraphNode
|
||||
* The font style used to render the node's title text.
|
||||
*/
|
||||
get titleFontStyle(): string {
|
||||
return `${LiteGraphInternal.NODE_TEXT_SIZE}px ${LiteGraphInternal.NODE_FONT}`
|
||||
return `${LiteGraph.NODE_TEXT_SIZE}px ${LiteGraph.NODE_FONT}`
|
||||
}
|
||||
|
||||
get innerFontStyle(): string {
|
||||
return `normal ${LiteGraphInternal.NODE_SUBTEXT_SIZE}px ${LiteGraphInternal.NODE_FONT}`
|
||||
return `normal ${LiteGraph.NODE_SUBTEXT_SIZE}px ${LiteGraph.NODE_FONT}`
|
||||
}
|
||||
|
||||
get displayType(): string {
|
||||
@@ -299,19 +303,13 @@ export class LGraphNode
|
||||
|
||||
/** The fg color used to render the node. */
|
||||
get renderingColor(): string {
|
||||
return (
|
||||
this.color ||
|
||||
this.constructor.color ||
|
||||
LiteGraphInternal.NODE_DEFAULT_COLOR
|
||||
)
|
||||
return this.color || this.constructor.color || LiteGraph.NODE_DEFAULT_COLOR
|
||||
}
|
||||
|
||||
/** The bg color used to render the node. */
|
||||
get renderingBgColor(): string {
|
||||
return (
|
||||
this.bgcolor ||
|
||||
this.constructor.bgcolor ||
|
||||
LiteGraphInternal.NODE_DEFAULT_BGCOLOR
|
||||
this.bgcolor || this.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR
|
||||
)
|
||||
}
|
||||
|
||||
@@ -319,17 +317,17 @@ export class LGraphNode
|
||||
get renderingBoxColor(): string {
|
||||
if (this.boxcolor) return this.boxcolor
|
||||
|
||||
if (LiteGraphInternal.node_box_coloured_when_on) {
|
||||
if (LiteGraph.node_box_coloured_when_on) {
|
||||
if (this.action_triggered) return '#FFF'
|
||||
if (this.execute_triggered) return '#AAA'
|
||||
}
|
||||
|
||||
if (LiteGraphInternal.node_box_coloured_by_mode) {
|
||||
if (LiteGraph.node_box_coloured_by_mode) {
|
||||
const modeColour =
|
||||
LiteGraphInternal.NODE_MODES_COLORS[this.mode ?? LGraphEventMode.ALWAYS]
|
||||
LiteGraph.NODE_MODES_COLORS[this.mode ?? LGraphEventMode.ALWAYS]
|
||||
if (modeColour) return modeColour
|
||||
}
|
||||
return LiteGraphInternal.NODE_DEFAULT_BOXCOLOR
|
||||
return LiteGraph.NODE_DEFAULT_BOXCOLOR
|
||||
}
|
||||
|
||||
/** @inheritdoc {@link IColorable.setColorOption} */
|
||||
@@ -509,11 +507,7 @@ export class LGraphNode
|
||||
* The shape of the node used for rendering. @see {@link RenderShape}
|
||||
*/
|
||||
get renderingShape(): RenderShape {
|
||||
return (
|
||||
this._shape ||
|
||||
this.constructor.shape ||
|
||||
LiteGraphInternal.NODE_DEFAULT_SHAPE
|
||||
)
|
||||
return this._shape || this.constructor.shape || LiteGraph.NODE_DEFAULT_SHAPE
|
||||
}
|
||||
|
||||
public get is_selected(): boolean | undefined {
|
||||
@@ -726,7 +720,7 @@ export class LGraphNode
|
||||
return {
|
||||
padding: 12,
|
||||
lineWidth: 10,
|
||||
color: LiteGraphInternal.NODE_ERROR_COLOUR
|
||||
color: LiteGraph.NODE_ERROR_COLOUR
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -740,10 +734,10 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
constructor(title: string, type?: string) {
|
||||
this.id = LiteGraphInternal.use_uuids ? LiteGraphInternal.uuidv4() : -1
|
||||
this.id = LiteGraph.use_uuids ? LiteGraph.uuidv4() : -1
|
||||
this.title = title || 'Unnamed'
|
||||
this.type = type ?? ''
|
||||
this.size = [LiteGraphInternal.NODE_WIDTH, 60]
|
||||
this.size = [LiteGraph.NODE_WIDTH, 60]
|
||||
this.pos = [10, 10]
|
||||
this.strokeStyles = {
|
||||
error: this.#getErrorStrokeStyle,
|
||||
@@ -784,7 +778,7 @@ export class LGraphNode
|
||||
this[j]?.configure(info[j])
|
||||
} else {
|
||||
// @ts-expect-error #594
|
||||
this[j] = LiteGraphInternal.cloneObject(info[j], this[j])
|
||||
this[j] = LiteGraph.cloneObject(info[j], this[j])
|
||||
}
|
||||
} else {
|
||||
// value
|
||||
@@ -869,7 +863,7 @@ export class LGraphNode
|
||||
type: this.type,
|
||||
pos: [this.pos[0], this.pos[1]],
|
||||
size: [this.size[0], this.size[1]],
|
||||
flags: LiteGraphInternal.cloneObject(this.flags),
|
||||
flags: LiteGraph.cloneObject(this.flags),
|
||||
order: this.order,
|
||||
mode: this.mode,
|
||||
showAdvanced: this.showAdvanced
|
||||
@@ -887,8 +881,7 @@ export class LGraphNode
|
||||
|
||||
if (this.title && this.title != this.constructor.title) o.title = this.title
|
||||
|
||||
if (this.properties)
|
||||
o.properties = LiteGraphInternal.cloneObject(this.properties)
|
||||
if (this.properties) o.properties = LiteGraph.cloneObject(this.properties)
|
||||
|
||||
const { widgets } = this
|
||||
if (widgets && this.serialize_widgets) {
|
||||
@@ -918,11 +911,11 @@ export class LGraphNode
|
||||
/* Creates a clone of this node */
|
||||
clone(): LGraphNode | null {
|
||||
if (this.type == null) return null
|
||||
const node = LiteGraphInternal.createNode(this.type)
|
||||
const node = LiteGraph.createNode(this.type)
|
||||
if (!node) return null
|
||||
|
||||
// we clone it because serialize returns shared containers
|
||||
const data = LiteGraphInternal.cloneObject(this.serialize())
|
||||
const data = LiteGraph.cloneObject(this.serialize())
|
||||
const { inputs, outputs } = data
|
||||
|
||||
// remove links
|
||||
@@ -941,7 +934,7 @@ export class LGraphNode
|
||||
// @ts-expect-error Exceptional case: id is removed so that the graph can assign a new one on add.
|
||||
delete data.id
|
||||
|
||||
if (LiteGraphInternal.use_uuids) data.id = LiteGraphInternal.uuidv4()
|
||||
if (LiteGraph.use_uuids) data.id = LiteGraph.uuidv4()
|
||||
|
||||
node.configure(data)
|
||||
|
||||
@@ -1262,7 +1255,7 @@ export class LGraphNode
|
||||
addOnTriggerInput(): number {
|
||||
const trigS = this.findInputSlot('onTrigger')
|
||||
if (trigS == -1) {
|
||||
this.addInput('onTrigger', LiteGraphInternal.EVENT, {
|
||||
this.addInput('onTrigger', LiteGraph.EVENT, {
|
||||
nameLocked: true
|
||||
})
|
||||
return this.findInputSlot('onTrigger')
|
||||
@@ -1273,7 +1266,7 @@ export class LGraphNode
|
||||
addOnExecutedOutput(): number {
|
||||
const trigS = this.findOutputSlot('onExecuted')
|
||||
if (trigS == -1) {
|
||||
this.addOutput('onExecuted', LiteGraphInternal.ACTION, {
|
||||
this.addOutput('onExecuted', LiteGraph.ACTION, {
|
||||
nameLocked: true
|
||||
})
|
||||
return this.findOutputSlot('onExecuted')
|
||||
@@ -1305,7 +1298,7 @@ export class LGraphNode
|
||||
break
|
||||
|
||||
// @ts-expect-error Not impl.
|
||||
case LiteGraphInternal.ON_REQUEST:
|
||||
case LiteGraph.ON_REQUEST:
|
||||
break
|
||||
|
||||
default:
|
||||
@@ -1392,12 +1385,12 @@ export class LGraphNode
|
||||
return
|
||||
}
|
||||
|
||||
if (this.graph) this.graph._last_trigger_time = LiteGraphInternal.getTime()
|
||||
if (this.graph) this.graph._last_trigger_time = LiteGraph.getTime()
|
||||
|
||||
for (const [i, output] of outputs.entries()) {
|
||||
if (
|
||||
!output ||
|
||||
output.type !== LiteGraphInternal.EVENT ||
|
||||
output.type !== LiteGraph.EVENT ||
|
||||
(action && output.name != action)
|
||||
) {
|
||||
continue
|
||||
@@ -1437,7 +1430,7 @@ export class LGraphNode
|
||||
if (!links || !links.length) return
|
||||
|
||||
if (!this.graph) throw new NullGraphError()
|
||||
this.graph._last_trigger_time = LiteGraphInternal.getTime()
|
||||
this.graph._last_trigger_time = LiteGraph.getTime()
|
||||
|
||||
// for every link attached here
|
||||
for (const id of links) {
|
||||
@@ -1448,7 +1441,7 @@ export class LGraphNode
|
||||
// not connected
|
||||
if (!link_info) continue
|
||||
|
||||
link_info._last_time = LiteGraphInternal.getTime()
|
||||
link_info._last_time = LiteGraph.getTime()
|
||||
const node = this.graph.getNodeById(link_info.target_id)
|
||||
// node not found?
|
||||
if (!node) continue
|
||||
@@ -1558,8 +1551,8 @@ export class LGraphNode
|
||||
this.outputs.push(output)
|
||||
this.onOutputAdded?.(output)
|
||||
|
||||
if (LiteGraphInternal.auto_load_slot_types)
|
||||
LiteGraphInternal.registerNodeAndSlotType(this, type, true)
|
||||
if (LiteGraph.auto_load_slot_types)
|
||||
LiteGraph.registerNodeAndSlotType(this, type, true)
|
||||
|
||||
this.expandToFitContent()
|
||||
this.setDirtyCanvas(true, true)
|
||||
@@ -1616,7 +1609,7 @@ export class LGraphNode
|
||||
this.expandToFitContent()
|
||||
|
||||
this.onInputAdded?.(input)
|
||||
LiteGraphInternal.registerNodeAndSlotType(this, type)
|
||||
LiteGraph.registerNodeAndSlotType(this, type)
|
||||
|
||||
this.setDirtyCanvas(true, true)
|
||||
return input
|
||||
@@ -1663,9 +1656,9 @@ export class LGraphNode
|
||||
const size = out || new Float32Array([0, 0])
|
||||
rows = Math.max(rows, 1)
|
||||
// although it should be graphcanvas.inner_text_font size
|
||||
const font_size = LiteGraphInternal.NODE_TEXT_SIZE
|
||||
const font_size = LiteGraph.NODE_TEXT_SIZE
|
||||
|
||||
const padLeft = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const padLeft = LiteGraph.NODE_TITLE_HEIGHT
|
||||
const padRight = padLeft * 0.33
|
||||
const title_width =
|
||||
padLeft + compute_text_size(this.title, this.titleFontStyle) + padRight
|
||||
@@ -1696,13 +1689,13 @@ export class LGraphNode
|
||||
}
|
||||
}
|
||||
|
||||
const minWidth = LiteGraphInternal.NODE_WIDTH * (widgets?.length ? 1.5 : 1)
|
||||
const minWidth = LiteGraph.NODE_WIDTH * (widgets?.length ? 1.5 : 1)
|
||||
// Text + slot width + centre padding
|
||||
const centrePadding = input_width && output_width ? 5 : 0
|
||||
const slotsWidth =
|
||||
input_width +
|
||||
output_width +
|
||||
2 * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
2 * LiteGraph.NODE_SLOT_HEIGHT +
|
||||
centrePadding
|
||||
|
||||
// Total distance from edge of node to the inner edge of the widget 'previous' arrow button
|
||||
@@ -1713,8 +1706,7 @@ export class LGraphNode
|
||||
|
||||
size[0] = Math.max(slotsWidth, widgetWidth, title_width, minWidth)
|
||||
size[1] =
|
||||
(this.constructor.slot_start_y || 0) +
|
||||
rows * LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
(this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT
|
||||
|
||||
// Get widget height & expand size if necessary
|
||||
let widgets_height = 0
|
||||
@@ -1733,7 +1725,7 @@ export class LGraphNode
|
||||
|
||||
widget_height += minHeight
|
||||
} else {
|
||||
widget_height += LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
widget_height += LiteGraph.NODE_WIDGET_HEIGHT
|
||||
}
|
||||
widgets_height += widget_height + 4
|
||||
}
|
||||
@@ -1766,8 +1758,7 @@ export class LGraphNode
|
||||
inResizeCorner(canvasX: number, canvasY: number): boolean {
|
||||
const rows = this.outputs ? this.outputs.length : 1
|
||||
const outputs_offset =
|
||||
(this.constructor.slot_start_y || 0) +
|
||||
rows * LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
(this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT
|
||||
return isInRectangle(
|
||||
canvasX,
|
||||
canvasY,
|
||||
@@ -1968,7 +1959,7 @@ export class LGraphNode
|
||||
|
||||
// If Vue nodes mode is enabled, skip LiteGraph's direct position update
|
||||
// The layout store will handle the movement and sync back to LiteGraph
|
||||
if (LiteGraphInternal.vueNodesMode) {
|
||||
if (LiteGraph.vueNodesMode) {
|
||||
// Vue nodes handle their own dragging through the layout store
|
||||
// This prevents the snap-back issue from conflicting position updates
|
||||
return
|
||||
@@ -1992,7 +1983,7 @@ export class LGraphNode
|
||||
const renderTitle =
|
||||
titleMode != TitleMode.TRANSPARENT_TITLE &&
|
||||
titleMode != TitleMode.NO_TITLE
|
||||
const titleHeight = renderTitle ? LiteGraphInternal.NODE_TITLE_HEIGHT : 0
|
||||
const titleHeight = renderTitle ? LiteGraph.NODE_TITLE_HEIGHT : 0
|
||||
|
||||
out[0] = this.pos[0]
|
||||
out[1] = this.pos[1] + -titleHeight
|
||||
@@ -2004,10 +1995,10 @@ export class LGraphNode
|
||||
this._collapsed_width = Math.min(
|
||||
this.size[0],
|
||||
ctx.measureText(this.getTitle() ?? '').width +
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT * 2
|
||||
LiteGraph.NODE_TITLE_HEIGHT * 2
|
||||
)
|
||||
out[2] = this._collapsed_width || LiteGraphInternal.NODE_COLLAPSED_WIDTH
|
||||
out[3] = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
out[2] = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH
|
||||
out[3] = LiteGraph.NODE_TITLE_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2064,7 +2055,7 @@ export class LGraphNode
|
||||
* @returns true if the x,y point is in the collapse button area, otherwise false
|
||||
*/
|
||||
isPointInCollapse(x: number, y: number): boolean {
|
||||
const squareLength = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const squareLength = LiteGraph.NODE_TITLE_HEIGHT
|
||||
return isInRectangle(
|
||||
x,
|
||||
y,
|
||||
@@ -2167,7 +2158,7 @@ export class LGraphNode
|
||||
const h =
|
||||
widget.computedHeight ??
|
||||
widget.computeSize?.(nodeWidth)[1] ??
|
||||
LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
LiteGraph.NODE_WIDGET_HEIGHT
|
||||
|
||||
const maybeDOMWidget = widget as { margin?: number }
|
||||
const mtop = maybeDOMWidget.margin ?? -2
|
||||
@@ -2448,12 +2439,10 @@ export class LGraphNode
|
||||
|
||||
for (const sourceType of sourceTypes) {
|
||||
// TODO: Remove _event_ entirely.
|
||||
const source =
|
||||
sourceType == '_event_' ? LiteGraphInternal.EVENT : sourceType
|
||||
const source = sourceType == '_event_' ? LiteGraph.EVENT : sourceType
|
||||
|
||||
for (const destType of destTypes) {
|
||||
const dest =
|
||||
destType == '_event_' ? LiteGraphInternal.EVENT : destType
|
||||
const dest = destType == '_event_' ? LiteGraph.EVENT : destType
|
||||
|
||||
if (source == dest || source === '*' || dest === '*') {
|
||||
if (preferFreeSlot && (slot.links?.length || slot.link != null)) {
|
||||
@@ -2513,10 +2502,9 @@ export class LGraphNode
|
||||
if (slot >= 0 && slot !== null) return slot
|
||||
|
||||
// TODO: Remove or reimpl. events. WILL CREATE THE onTrigger IN SLOT
|
||||
if (opts.createEventInCase && slotType == LiteGraphInternal.EVENT) {
|
||||
if (opts.createEventInCase && slotType == LiteGraph.EVENT) {
|
||||
if (findInputs) return -1
|
||||
if (LiteGraphInternal.do_add_triggers_slots)
|
||||
return node.addOnExecutedOutput()
|
||||
if (LiteGraph.do_add_triggers_slots) return node.addOnExecutedOutput()
|
||||
}
|
||||
|
||||
// connect to the first general output slot if not found a specific type and
|
||||
@@ -2529,7 +2517,7 @@ export class LGraphNode
|
||||
opts.wildcardToTyped &&
|
||||
(slotType == 0 || slotType == '*' || slotType == '')
|
||||
) {
|
||||
const opt = { typesNotAccepted: [LiteGraphInternal.EVENT] }
|
||||
const opt = { typesNotAccepted: [LiteGraph.EVENT] }
|
||||
const nonEventSlot = findInputs
|
||||
? node.findInputSlotFree(opt)
|
||||
: node.findOutputSlotFree(opt)
|
||||
@@ -2649,7 +2637,7 @@ export class LGraphNode
|
||||
) {
|
||||
return (
|
||||
this.id !== node.id &&
|
||||
LiteGraphInternal.isValidConnection(fromSlot.type, toSlot.type)
|
||||
LiteGraph.isValidConnection(fromSlot.type, toSlot.type)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2683,13 +2671,12 @@ export class LGraphNode
|
||||
if (typeof slot === 'string') {
|
||||
slot = this.findOutputSlot(slot)
|
||||
if (slot == -1) {
|
||||
if (LiteGraphInternal.debug)
|
||||
if (LiteGraph.debug)
|
||||
console.log(`Connect: Error, no slot of name ${slot}`)
|
||||
return null
|
||||
}
|
||||
} else if (!outputs || slot >= outputs.length) {
|
||||
if (LiteGraphInternal.debug)
|
||||
console.log('Connect: Error, slot number not found')
|
||||
if (LiteGraph.debug) console.log('Connect: Error, slot number not found')
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -2708,13 +2695,13 @@ export class LGraphNode
|
||||
if (typeof target_slot === 'string') {
|
||||
targetIndex = target_node.findInputSlot(target_slot)
|
||||
if (targetIndex == -1) {
|
||||
if (LiteGraphInternal.debug)
|
||||
if (LiteGraph.debug)
|
||||
console.log(`Connect: Error, no slot of name ${targetIndex}`)
|
||||
return null
|
||||
}
|
||||
} else if (target_slot === LiteGraphInternal.EVENT) {
|
||||
} else if (target_slot === LiteGraph.EVENT) {
|
||||
// TODO: Events
|
||||
if (LiteGraphInternal.do_add_triggers_slots) {
|
||||
if (LiteGraph.do_add_triggers_slots) {
|
||||
target_node.changeMode(LGraphEventMode.ON_TRIGGER)
|
||||
targetIndex = target_node.findInputSlot('onTrigger')
|
||||
} else {
|
||||
@@ -2741,8 +2728,7 @@ export class LGraphNode
|
||||
!target_node.inputs ||
|
||||
targetIndex >= target_node.inputs.length
|
||||
) {
|
||||
if (LiteGraphInternal.debug)
|
||||
console.log('Connect: Error, slot number not found')
|
||||
if (LiteGraph.debug) console.log('Connect: Error, slot number not found')
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -2753,8 +2739,8 @@ export class LGraphNode
|
||||
|
||||
if (output.links?.length) {
|
||||
if (
|
||||
output.type === LiteGraphInternal.EVENT &&
|
||||
!LiteGraphInternal.allow_multi_output_for_events
|
||||
output.type === LiteGraph.EVENT &&
|
||||
!LiteGraph.allow_multi_output_for_events
|
||||
) {
|
||||
graph.beforeChange()
|
||||
// @ts-expect-error Unused param
|
||||
@@ -2797,7 +2783,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
// check targetSlot and check connection types
|
||||
if (!LiteGraphInternal.isValidConnection(output.type, input.type)) {
|
||||
if (!LiteGraph.isValidConnection(output.type, input.type)) {
|
||||
this.setDirtyCanvas(false, true)
|
||||
return null
|
||||
}
|
||||
@@ -2968,13 +2954,12 @@ export class LGraphNode
|
||||
if (typeof slot === 'string') {
|
||||
slot = this.findOutputSlot(slot)
|
||||
if (slot == -1) {
|
||||
if (LiteGraphInternal.debug)
|
||||
if (LiteGraph.debug)
|
||||
console.log(`Connect: Error, no slot of name ${slot}`)
|
||||
return false
|
||||
}
|
||||
} else if (!this.outputs || slot >= this.outputs.length) {
|
||||
if (LiteGraphInternal.debug)
|
||||
console.log('Connect: Error, slot number not found')
|
||||
if (LiteGraph.debug) console.log('Connect: Error, slot number not found')
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -3089,12 +3074,12 @@ export class LGraphNode
|
||||
if (typeof slot === 'string') {
|
||||
slot = this.findInputSlot(slot)
|
||||
if (slot == -1) {
|
||||
if (LiteGraphInternal.debug)
|
||||
if (LiteGraph.debug)
|
||||
console.log(`Connect: Error, no slot of name ${slot}`)
|
||||
return false
|
||||
}
|
||||
} else if (!this.inputs || slot >= this.inputs.length) {
|
||||
if (LiteGraphInternal.debug) {
|
||||
if (LiteGraph.debug) {
|
||||
console.log('Connect: Error, slot number not found')
|
||||
}
|
||||
return false
|
||||
@@ -3198,16 +3183,16 @@ export class LGraphNode
|
||||
} = this
|
||||
|
||||
if (this.flags.collapsed) {
|
||||
const w = this._collapsed_width || LiteGraphInternal.NODE_COLLAPSED_WIDTH
|
||||
const w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH
|
||||
out[0] = is_input ? nodeX : nodeX + w
|
||||
out[1] = nodeY - LiteGraphInternal.NODE_TITLE_HEIGHT * 0.5
|
||||
out[1] = nodeY - LiteGraph.NODE_TITLE_HEIGHT * 0.5
|
||||
return out
|
||||
}
|
||||
|
||||
// weird feature that never got finished
|
||||
if (is_input && slot_number == -1) {
|
||||
out[0] = nodeX + LiteGraphInternal.NODE_TITLE_HEIGHT * 0.5
|
||||
out[1] = nodeY + LiteGraphInternal.NODE_TITLE_HEIGHT * 0.5
|
||||
out[0] = nodeX + LiteGraph.NODE_TITLE_HEIGHT * 0.5
|
||||
out[1] = nodeY + LiteGraph.NODE_TITLE_HEIGHT * 0.5
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -3226,7 +3211,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
// default vertical slots
|
||||
const offset = LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
const slotIndex = is_input
|
||||
? this.#defaultVerticalInputs.indexOf(this.inputs[slot_number])
|
||||
: this.#defaultVerticalOutputs.indexOf(this.outputs[slot_number])
|
||||
@@ -3234,7 +3219,7 @@ export class LGraphNode
|
||||
out[0] = is_input ? nodeX + offset : nodeX + this.size[0] + 1 - offset
|
||||
out[1] =
|
||||
nodeY +
|
||||
(slotIndex + 0.7) * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
(slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
|
||||
(this.constructor.slot_start_y || 0)
|
||||
return out
|
||||
}
|
||||
@@ -3312,7 +3297,7 @@ export class LGraphNode
|
||||
|
||||
/** @see {@link snapToGrid} */
|
||||
alignToGrid(): void {
|
||||
this.snapToGrid(LiteGraphInternal.CANVAS_GRID_SIZE)
|
||||
this.snapToGrid(LiteGraph.CANVAS_GRID_SIZE)
|
||||
}
|
||||
|
||||
/* Console output */
|
||||
@@ -3336,7 +3321,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
const img: AsyncImageElement = new Image()
|
||||
img.src = LiteGraphInternal.node_images_path + url
|
||||
img.src = LiteGraph.node_images_path + url
|
||||
img.ready = false
|
||||
|
||||
const dirty = () => this.setDirtyCanvas(true)
|
||||
@@ -3431,7 +3416,7 @@ export class LGraphNode
|
||||
|
||||
get width() {
|
||||
return this.collapsed
|
||||
? this._collapsed_width || LiteGraphInternal.NODE_COLLAPSED_WIDTH
|
||||
? this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH
|
||||
: this.size[0]
|
||||
}
|
||||
|
||||
@@ -3439,7 +3424,7 @@ export class LGraphNode
|
||||
* Returns the height of the node, including the title bar.
|
||||
*/
|
||||
get height() {
|
||||
return LiteGraphInternal.NODE_TITLE_HEIGHT + this.bodyHeight
|
||||
return LiteGraph.NODE_TITLE_HEIGHT + this.bodyHeight
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3462,7 +3447,7 @@ export class LGraphNode
|
||||
(acc, badge) => acc + badge.getWidth(ctx) + gap,
|
||||
0
|
||||
)
|
||||
const y = -(LiteGraphInternal.NODE_TITLE_HEIGHT + gap)
|
||||
const y = -(LiteGraph.NODE_TITLE_HEIGHT + gap)
|
||||
|
||||
for (const badge of badgeInstances) {
|
||||
badge.draw(ctx, currentX, y - badge.height)
|
||||
@@ -3477,7 +3462,7 @@ export class LGraphNode
|
||||
ctx: CanvasRenderingContext2D,
|
||||
{
|
||||
scale,
|
||||
title_height = LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
title_height = LiteGraph.NODE_TITLE_HEIGHT,
|
||||
low_quality = false
|
||||
}: DrawTitleOptions
|
||||
): void {
|
||||
@@ -3495,7 +3480,7 @@ export class LGraphNode
|
||||
}
|
||||
|
||||
if (this.collapsed) {
|
||||
ctx.shadowColor = LiteGraphInternal.DEFAULT_SHADOW_COLOR
|
||||
ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR
|
||||
}
|
||||
|
||||
ctx.fillStyle = this.constructor.title_color || fgcolor
|
||||
@@ -3510,13 +3495,8 @@ export class LGraphNode
|
||||
size[0],
|
||||
title_height,
|
||||
this.collapsed
|
||||
? [LiteGraphInternal.ROUND_RADIUS]
|
||||
: [
|
||||
LiteGraphInternal.ROUND_RADIUS,
|
||||
LiteGraphInternal.ROUND_RADIUS,
|
||||
0,
|
||||
0
|
||||
]
|
||||
? [LiteGraph.ROUND_RADIUS]
|
||||
: [LiteGraph.ROUND_RADIUS, LiteGraph.ROUND_RADIUS, 0, 0]
|
||||
)
|
||||
}
|
||||
ctx.fill()
|
||||
@@ -3533,7 +3513,7 @@ export class LGraphNode
|
||||
{
|
||||
scale,
|
||||
low_quality = false,
|
||||
title_height = LiteGraphInternal.NODE_TITLE_HEIGHT,
|
||||
title_height = LiteGraph.NODE_TITLE_HEIGHT,
|
||||
box_size = 10
|
||||
}: DrawTitleBoxOptions
|
||||
): void {
|
||||
@@ -3609,7 +3589,7 @@ export class LGraphNode
|
||||
scale,
|
||||
default_title_color,
|
||||
low_quality = false,
|
||||
title_height = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
title_height = LiteGraph.NODE_TITLE_HEIGHT
|
||||
}: DrawTitleTextOptions
|
||||
): void {
|
||||
const size = this.renderingSize
|
||||
@@ -3637,7 +3617,7 @@ export class LGraphNode
|
||||
const title = String(rawTitle) + (this.pinned ? '📌' : '')
|
||||
if (title) {
|
||||
if (selected) {
|
||||
ctx.fillStyle = LiteGraphInternal.NODE_SELECTED_TITLE_COLOR
|
||||
ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR
|
||||
} else {
|
||||
ctx.fillStyle = this.constructor.title_text_color || default_title_color
|
||||
}
|
||||
@@ -3676,7 +3656,7 @@ export class LGraphNode
|
||||
ctx.fillText(
|
||||
displayTitle,
|
||||
title_height,
|
||||
LiteGraphInternal.NODE_TITLE_TEXT_Y - title_height
|
||||
LiteGraph.NODE_TITLE_TEXT_Y - title_height
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3709,10 +3689,7 @@ export class LGraphNode
|
||||
if (input.link == null) continue
|
||||
|
||||
const output = outputs[index]
|
||||
if (
|
||||
!output ||
|
||||
!LiteGraphInternal.isValidConnection(input.type, output.type)
|
||||
)
|
||||
if (!output || !LiteGraph.isValidConnection(input.type, output.type))
|
||||
continue
|
||||
|
||||
const inLink = _links.get(input.link)
|
||||
@@ -3736,8 +3713,7 @@ export class LGraphNode
|
||||
if (!inNode) continue
|
||||
|
||||
for (const output of outputs) {
|
||||
if (!LiteGraphInternal.isValidConnection(input.type, output.type))
|
||||
continue
|
||||
if (!LiteGraph.isValidConnection(input.type, output.type)) continue
|
||||
|
||||
bypassAllLinks(output, inNode, inLink, graph)
|
||||
break
|
||||
@@ -3788,7 +3764,7 @@ export class LGraphNode
|
||||
|
||||
const nodeWidth = this.size[0]
|
||||
const { widgets } = this
|
||||
const H = LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
const H = LiteGraph.NODE_WIDGET_HEIGHT
|
||||
const showText = !lowQuality
|
||||
ctx.save()
|
||||
ctx.globalAlpha = editorAlpha
|
||||
@@ -3798,8 +3774,8 @@ export class LGraphNode
|
||||
|
||||
const { y } = widget
|
||||
const outlineColour = widget.advanced
|
||||
? LiteGraphInternal.WIDGET_ADVANCED_OUTLINE_COLOR
|
||||
: LiteGraphInternal.WIDGET_OUTLINE_COLOR
|
||||
? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR
|
||||
: LiteGraph.WIDGET_OUTLINE_COLOR
|
||||
|
||||
widget.last_y = y
|
||||
// Disable widget if it is disabled or if the value is passed from socket connection.
|
||||
@@ -3857,12 +3833,12 @@ export class LGraphNode
|
||||
? this.getInputPos(slotIndex)
|
||||
: this.getOutputPos(slotIndex)
|
||||
|
||||
slot.boundingRect[0] = pos[0] - LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[1] = pos[1] - LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[0] = pos[0] - LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[1] = pos[1] - LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
slot.boundingRect[2] = slot.isWidgetInputSlot
|
||||
? BaseWidget.margin
|
||||
: LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
slot.boundingRect[3] = LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
: LiteGraph.NODE_SLOT_HEIGHT
|
||||
slot.boundingRect[3] = LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
#measureSlots(): ReadOnlyRect | null {
|
||||
@@ -3997,7 +3973,7 @@ export class LGraphNode
|
||||
w
|
||||
})
|
||||
} else {
|
||||
const height = LiteGraphInternal.NODE_WIDGET_HEIGHT + 4
|
||||
const height = LiteGraph.NODE_WIDGET_HEIGHT + 4
|
||||
w.computedHeight = height
|
||||
fixedWidgetHeight += height
|
||||
}
|
||||
@@ -4060,13 +4036,13 @@ export class LGraphNode
|
||||
|
||||
// Only set custom pos if not using Vue positioning
|
||||
// Vue positioning calculates widget slot positions dynamically
|
||||
if (!LiteGraphInternal.vueNodesMode) {
|
||||
if (!LiteGraph.vueNodesMode) {
|
||||
for (const widget of this.widgets) {
|
||||
const slot = slotByWidgetName.get(widget.name)
|
||||
if (!slot) continue
|
||||
|
||||
const actualSlot = this.#concreteInputs[slot.index]
|
||||
const offset = LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
actualSlot.pos = [offset, widget.y + offset]
|
||||
this.#measureSlot(actualSlot, slot.index, true)
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
network: Pick<ReadonlyLinkNetwork, 'reroutes'>,
|
||||
linkSegment: LinkSegment
|
||||
): Reroute[] {
|
||||
if (linkSegment.parentId === undefined) return []
|
||||
if (!linkSegment.parentId) return []
|
||||
return network.reroutes.get(linkSegment.parentId)?.getReroutes() ?? []
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
linkSegment: LinkSegment,
|
||||
rerouteId: RerouteId
|
||||
): Reroute | null | undefined {
|
||||
if (linkSegment.parentId === undefined) return
|
||||
if (!linkSegment.parentId) return
|
||||
return network.reroutes
|
||||
.get(linkSegment.parentId)
|
||||
?.findNextReroute(rerouteId)
|
||||
@@ -498,7 +498,7 @@ export class LLink implements LinkSegment, Serialisable<SerialisableLLink> {
|
||||
target_slot: this.target_slot,
|
||||
type: this.type
|
||||
}
|
||||
if (this.parentId !== undefined) copy.parentId = this.parentId
|
||||
if (this.parentId) copy.parentId = this.parentId
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { LiteGraphGlobal } from './LiteGraphGlobal'
|
||||
|
||||
// To prevent circular dependency in litegraph/itself.ts -> litegraph.ts -> litegraph/itself.ts
|
||||
// For all src/lib/litegraph/**/*.ts files, We should import litegraph instance from here
|
||||
// And for imports outside from litegraph, We can import it directly from 'src/lib/litegraph/src/litegraph' for simplicity
|
||||
|
||||
export const LiteGraphInternal = new LiteGraphGlobal()
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import type { Rectangle } from './infrastructure/Rectangle'
|
||||
import type { CanvasColour, Rect } from './interfaces'
|
||||
import { LiteGraph } from './litegraph'
|
||||
import { RenderShape, TitleMode } from './types/globalEnums'
|
||||
|
||||
const ELLIPSIS = '\u2026'
|
||||
@@ -80,12 +80,12 @@ export function strokeShape(
|
||||
}: IDrawBoundingOptions = {}
|
||||
): void {
|
||||
// These param defaults are not compile-time static, and must be re-evaluated at runtime
|
||||
round_radius ??= LiteGraphInternal.ROUND_RADIUS
|
||||
color ??= LiteGraphInternal.NODE_BOX_OUTLINE_COLOR
|
||||
round_radius ??= LiteGraph.ROUND_RADIUS
|
||||
color ??= LiteGraph.NODE_BOX_OUTLINE_COLOR
|
||||
|
||||
// Adjust area if title is transparent
|
||||
if (title_mode === TitleMode.TRANSPARENT_TITLE) {
|
||||
const height = title_height ?? LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const height = title_height ?? LiteGraph.NODE_TITLE_HEIGHT
|
||||
area[1] -= height
|
||||
area[3] += height
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ export interface Positionable extends Parent<Positionable>, HasBoundingRect {
|
||||
* @default 0,0
|
||||
*/
|
||||
readonly pos: Point
|
||||
readonly size?: Size
|
||||
/** true if this object is part of the selection, otherwise false. */
|
||||
selected?: boolean
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ContextMenu } from './ContextMenu'
|
||||
import type { LGraphNode } from './LGraphNode'
|
||||
import { LiteGraphInternal } from './LiteGraphInternal'
|
||||
import { LiteGraphGlobal } from './LiteGraphGlobal'
|
||||
import type { ConnectingLink, Point } from './interfaces'
|
||||
import type { IContextMenuOptions, Size } from './interfaces'
|
||||
import { loadPolyfills } from './polyfills'
|
||||
@@ -10,10 +10,7 @@ import type { RenderShape, TitleMode } from './types/globalEnums'
|
||||
// Must remain above LiteGraphGlobal (circular dependency due to abstract factory behaviour in `configure`)
|
||||
export { Subgraph } from './subgraph/Subgraph'
|
||||
|
||||
// Note: To prevent circular dependency in litegraph/itself.ts -> litegraph.ts -> litegraph/itself.ts
|
||||
// For all src/lib/litegraph/**/*.ts files, We should import litegraph instance from LiteGraphInternal
|
||||
// And for imports outside from litegraph, We can import it directly from here for simplicity
|
||||
export const LiteGraph = LiteGraphInternal
|
||||
export const LiteGraph = new LiteGraphGlobal()
|
||||
|
||||
// Load legacy polyfills
|
||||
loadPolyfills()
|
||||
@@ -51,6 +48,7 @@ export interface LinkReleaseContextExtended {
|
||||
links: ConnectingLink[]
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface LiteGraphCanvasEvent extends CustomEvent<CanvasEventDetail> {}
|
||||
|
||||
export interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
|
||||
@@ -142,7 +140,7 @@ export { BaseWidget } from './widgets/BaseWidget'
|
||||
|
||||
export { LegacyWidget } from './widgets/LegacyWidget'
|
||||
|
||||
export { isComboWidget, isAssetWidget } from './widgets/widgetMap'
|
||||
export { isComboWidget } from './widgets/widgetMap'
|
||||
// Additional test-specific exports
|
||||
export { LGraphButton } from './LGraphButton'
|
||||
export { MovingOutputLink } from './canvas/MovingOutputLink'
|
||||
|
||||
@@ -7,14 +7,13 @@ import type {
|
||||
OptionalProps,
|
||||
ReadOnlyPoint
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
|
||||
import { isSubgraphInput } from '@/lib/litegraph/src/subgraph/subgraphUtils'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
|
||||
export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
|
||||
link: LinkId | null
|
||||
|
||||
@@ -34,7 +33,7 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
|
||||
}
|
||||
|
||||
get collapsedPos(): ReadOnlyPoint {
|
||||
return [0, LiteGraphInternal.NODE_TITLE_HEIGHT * -0.5]
|
||||
return [0, LiteGraph.NODE_TITLE_HEIGHT * -0.5]
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -53,11 +52,11 @@ export class NodeInputSlot extends NodeSlot implements INodeInputSlot {
|
||||
fromSlot: INodeInputSlot | INodeOutputSlot | SubgraphInput | SubgraphOutput
|
||||
): boolean {
|
||||
if ('links' in fromSlot) {
|
||||
return LiteGraphInternal.isValidConnection(fromSlot.type, this.type)
|
||||
return LiteGraph.isValidConnection(fromSlot.type, this.type)
|
||||
}
|
||||
|
||||
if (isSubgraphInput(fromSlot)) {
|
||||
return LiteGraphInternal.isValidConnection(fromSlot.type, this.type)
|
||||
return LiteGraph.isValidConnection(fromSlot.type, this.type)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -7,13 +7,12 @@ import type {
|
||||
OptionalProps,
|
||||
ReadOnlyPoint
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { type IDrawOptions, NodeSlot } from '@/lib/litegraph/src/node/NodeSlot'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
|
||||
import { isSubgraphOutput } from '@/lib/litegraph/src/subgraph/subgraphUtils'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
|
||||
export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
|
||||
#node: LGraphNode
|
||||
|
||||
@@ -27,8 +26,8 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
|
||||
|
||||
get collapsedPos(): ReadOnlyPoint {
|
||||
return [
|
||||
this.#node._collapsed_width ?? LiteGraphInternal.NODE_COLLAPSED_WIDTH,
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT * -0.5
|
||||
this.#node._collapsed_width ?? LiteGraph.NODE_COLLAPSED_WIDTH,
|
||||
LiteGraph.NODE_TITLE_HEIGHT * -0.5
|
||||
]
|
||||
}
|
||||
|
||||
@@ -47,11 +46,11 @@ export class NodeOutputSlot extends NodeSlot implements INodeOutputSlot {
|
||||
fromSlot: INodeInputSlot | INodeOutputSlot | SubgraphInput | SubgraphOutput
|
||||
): boolean {
|
||||
if ('link' in fromSlot) {
|
||||
return LiteGraphInternal.isValidConnection(this.type, fromSlot.type)
|
||||
return LiteGraph.isValidConnection(this.type, fromSlot.type)
|
||||
}
|
||||
|
||||
if (isSubgraphOutput(fromSlot)) {
|
||||
return LiteGraphInternal.isValidConnection(this.type, fromSlot.type)
|
||||
return LiteGraph.isValidConnection(this.type, fromSlot.type)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
Point,
|
||||
ReadOnlyPoint
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { Rectangle } from '@/lib/litegraph/src/litegraph'
|
||||
import { LiteGraph, Rectangle } from '@/lib/litegraph/src/litegraph'
|
||||
import { getCentre } from '@/lib/litegraph/src/measure'
|
||||
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
|
||||
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
RenderShape
|
||||
} from '@/lib/litegraph/src/types/globalEnums'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import { NodeInputSlot } from './NodeInputSlot'
|
||||
import { SlotBase } from './SlotBase'
|
||||
|
||||
@@ -62,9 +61,9 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
|
||||
|
||||
get highlightColor(): CanvasColour {
|
||||
return (
|
||||
LiteGraphInternal.NODE_TEXT_HIGHLIGHT_COLOR ??
|
||||
LiteGraphInternal.NODE_SELECTED_TITLE_COLOR ??
|
||||
LiteGraphInternal.NODE_TEXT_COLOR
|
||||
LiteGraph.NODE_TEXT_HIGHLIGHT_COLOR ??
|
||||
LiteGraph.NODE_SELECTED_TITLE_COLOR ??
|
||||
LiteGraph.NODE_TEXT_COLOR
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,7 +123,7 @@ export abstract class NodeSlot extends SlotBase implements INodeSlot {
|
||||
|
||||
const labelColor = highlight
|
||||
? this.highlightColor
|
||||
: LiteGraphInternal.NODE_TEXT_COLOR
|
||||
: LiteGraph.NODE_TEXT_COLOR
|
||||
|
||||
const pos = this.#centreOffset
|
||||
const slot_type = this.type
|
||||
|
||||
@@ -9,9 +9,8 @@ import type {
|
||||
CallbackReturn,
|
||||
ISlotType
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphEventMode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { Subgraph } from './Subgraph'
|
||||
import type { SubgraphNode } from './SubgraphNode'
|
||||
|
||||
@@ -347,8 +346,8 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
|
||||
// Prefer input with the same slot ID
|
||||
if (
|
||||
oppositeInput &&
|
||||
LiteGraphInternal.isValidConnection(oppositeInput.type, outputType) &&
|
||||
LiteGraphInternal.isValidConnection(oppositeInput.type, type)
|
||||
LiteGraph.isValidConnection(oppositeInput.type, outputType) &&
|
||||
LiteGraph.isValidConnection(oppositeInput.type, type)
|
||||
) {
|
||||
return slot
|
||||
}
|
||||
@@ -360,8 +359,8 @@ export class ExecutableNodeDTO implements ExecutableLGraphNode {
|
||||
// Find first matching slot - prefer exact type
|
||||
return inputs.findIndex(
|
||||
(input) =>
|
||||
LiteGraphInternal.isValidConnection(input.type, outputType) &&
|
||||
LiteGraphInternal.isValidConnection(input.type, type)
|
||||
LiteGraph.isValidConnection(input.type, outputType) &&
|
||||
LiteGraph.isValidConnection(input.type, type)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ import type {
|
||||
Point,
|
||||
Positionable
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import type {
|
||||
CanvasColour,
|
||||
CanvasPointer,
|
||||
CanvasPointerEvent,
|
||||
IContextMenuValue
|
||||
import {
|
||||
type CanvasColour,
|
||||
type CanvasPointer,
|
||||
type CanvasPointerEvent,
|
||||
type IContextMenuValue,
|
||||
LiteGraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { snapPoint } from '@/lib/litegraph/src/measure'
|
||||
import { CanvasItem } from '@/lib/litegraph/src/types/globalEnums'
|
||||
@@ -22,7 +23,6 @@ import type {
|
||||
Serialisable
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { EmptySubgraphInput } from './EmptySubgraphInput'
|
||||
import type { EmptySubgraphOutput } from './EmptySubgraphOutput'
|
||||
import type { Subgraph } from './Subgraph'
|
||||
@@ -194,7 +194,7 @@ export abstract class SubgraphIONodeBase<
|
||||
const options: (IContextMenuValue | null)[] = this.#getSlotMenuOptions(slot)
|
||||
if (!(options.length > 0)) return
|
||||
|
||||
new LiteGraphInternal.ContextMenu(options, {
|
||||
new LiteGraph.ContextMenu(options, {
|
||||
event: event as any,
|
||||
title: slot.name || 'Subgraph Output',
|
||||
callback: (item: IContextMenuValue) => {
|
||||
|
||||
@@ -9,10 +9,10 @@ import type {
|
||||
Point,
|
||||
ReadOnlyRect
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { SubgraphInputNode } from './SubgraphInputNode'
|
||||
import type { SubgraphOutput } from './SubgraphOutput'
|
||||
import { SubgraphSlot } from './SubgraphSlotBase'
|
||||
@@ -237,12 +237,12 @@ export class SubgraphInput extends SubgraphSlot {
|
||||
if (isNodeSlot(fromSlot)) {
|
||||
return (
|
||||
'link' in fromSlot &&
|
||||
LiteGraphInternal.isValidConnection(this.type, fromSlot.type)
|
||||
LiteGraph.isValidConnection(this.type, fromSlot.type)
|
||||
)
|
||||
}
|
||||
|
||||
if (isSubgraphOutput(fromSlot)) {
|
||||
return LiteGraphInternal.isValidConnection(this.type, fromSlot.type)
|
||||
return LiteGraph.isValidConnection(this.type, fromSlot.type)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -9,9 +9,9 @@ import type {
|
||||
Point,
|
||||
ReadOnlyRect
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { NodeSlotType } from '@/lib/litegraph/src/types/globalEnums'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { SubgraphInput } from './SubgraphInput'
|
||||
import type { SubgraphOutputNode } from './SubgraphOutputNode'
|
||||
import { SubgraphSlot } from './SubgraphSlotBase'
|
||||
@@ -39,7 +39,7 @@ export class SubgraphOutput extends SubgraphSlot {
|
||||
const { subgraph } = this.parent
|
||||
|
||||
// Validate type compatibility
|
||||
if (!LiteGraphInternal.isValidConnection(slot.type, this.type)) return
|
||||
if (!LiteGraph.isValidConnection(slot.type, this.type)) return
|
||||
|
||||
// Allow nodes to block connection
|
||||
const outputIndex = node.outputs.indexOf(slot)
|
||||
@@ -143,12 +143,12 @@ export class SubgraphOutput extends SubgraphSlot {
|
||||
if (isNodeSlot(fromSlot)) {
|
||||
return (
|
||||
'links' in fromSlot &&
|
||||
LiteGraphInternal.isValidConnection(fromSlot.type, this.type)
|
||||
LiteGraph.isValidConnection(fromSlot.type, this.type)
|
||||
)
|
||||
}
|
||||
|
||||
if (isSubgraphInput(fromSlot)) {
|
||||
return LiteGraphInternal.isValidConnection(fromSlot.type, this.type)
|
||||
return LiteGraph.isValidConnection(fromSlot.type, this.type)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
ReadOnlyRect,
|
||||
ReadOnlySize
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { SlotBase } from '@/lib/litegraph/src/node/SlotBase'
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
import type {
|
||||
@@ -22,7 +23,6 @@ import type {
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
import { type UUID, createUuidv4 } from '@/lib/litegraph/src/utils/uuid'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { SubgraphInput } from './SubgraphInput'
|
||||
import type { SubgraphInputNode } from './SubgraphInputNode'
|
||||
import type { SubgraphOutput } from './SubgraphOutput'
|
||||
@@ -42,7 +42,7 @@ export abstract class SubgraphSlot
|
||||
implements SubgraphIO, Hoverable, Serialisable<SubgraphIO>
|
||||
{
|
||||
static get defaultHeight() {
|
||||
return LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
return LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
readonly #pos: Point = new Float32Array(2)
|
||||
@@ -228,9 +228,7 @@ export abstract class SubgraphSlot
|
||||
if (this.displayName) {
|
||||
const [labelX, labelY] = this.labelPos
|
||||
// Also apply highlight logic to text color
|
||||
ctx.fillStyle = highlight
|
||||
? 'white'
|
||||
: LiteGraphInternal.NODE_TEXT_COLOR || '#AAA'
|
||||
ctx.fillStyle = highlight ? 'white' : LiteGraph.NODE_TEXT_COLOR || '#AAA'
|
||||
ctx.fillText(this.displayName, labelX, labelY)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import type {
|
||||
INodeOutputSlot,
|
||||
Positionable
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { createUuidv4 } from '@/lib/litegraph/src/litegraph'
|
||||
import { LiteGraph, createUuidv4 } from '@/lib/litegraph/src/litegraph'
|
||||
import { nextUniqueName } from '@/lib/litegraph/src/strings'
|
||||
import type {
|
||||
ISerialisedNode,
|
||||
@@ -21,7 +21,6 @@ import type {
|
||||
} from '@/lib/litegraph/src/types/serialisation'
|
||||
import type { UUID } from '@/lib/litegraph/src/utils/uuid'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { GraphOrSubgraph } from './Subgraph'
|
||||
import type { SubgraphInput } from './SubgraphInput'
|
||||
import { SubgraphInputNode } from './SubgraphInputNode'
|
||||
@@ -218,14 +217,14 @@ export function multiClone(nodes: Iterable<LGraphNode>): ISerialisedNode[] {
|
||||
|
||||
// Selectively clone - keep IDs & links
|
||||
for (const node of nodes) {
|
||||
const newNode = LiteGraphInternal.createNode(node.type)
|
||||
const newNode = LiteGraph.createNode(node.type)
|
||||
if (!newNode) {
|
||||
console.warn('Failed to create node', node.type)
|
||||
continue
|
||||
}
|
||||
|
||||
// Must be cloned; litegraph "serialize" is mostly shallow clone
|
||||
const data = LiteGraphInternal.cloneObject(node.serialize())
|
||||
const data = LiteGraph.cloneObject(node.serialize())
|
||||
newNode.configure(data)
|
||||
|
||||
clonedNodes.push(newNode.serialize())
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { LGraphConfig, LGraphExtra, LGraphState } from '../LGraph'
|
||||
import type { IGraphGroupFlags } from '../LGraphGroup'
|
||||
import type { NodeId, NodeProperty } from '../LGraphNode'
|
||||
import type { LinkId, SerialisedLLinkArray } from '../LLink'
|
||||
import type { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import type { FloatingRerouteSlot, RerouteId } from '../Reroute'
|
||||
import type {
|
||||
Dictionary,
|
||||
@@ -16,6 +15,7 @@ import type {
|
||||
Point,
|
||||
Size
|
||||
} from '../interfaces'
|
||||
import type { LiteGraph } from '../litegraph'
|
||||
import type { TWidgetValue } from '../types/widgets'
|
||||
import type { RenderShape } from './globalEnums'
|
||||
|
||||
@@ -124,7 +124,7 @@ export interface ISerialisedGraph extends BaseExportedGraph {
|
||||
links: SerialisedLLinkArray[]
|
||||
floatingLinks?: SerialisableLLink[]
|
||||
groups: ISerialisedGroup[]
|
||||
version: typeof LiteGraphInternal.VERSION
|
||||
version: typeof LiteGraph.VERSION
|
||||
extra?: LGraphExtra
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
/** Guard against unbound allocation. */
|
||||
const UNIQUE_MESSAGE_LIMIT = 10_000
|
||||
@@ -11,7 +11,7 @@ const sentWarnings: Set<string> = new Set()
|
||||
* @param source A reference object to include alongside the message, e.g. `this`.
|
||||
*/
|
||||
export function warnDeprecated(message: string, source?: object): void {
|
||||
if (!LiteGraphInternal.alwaysRepeatWarnings) {
|
||||
if (!LiteGraph.alwaysRepeatWarnings) {
|
||||
// Do not repeat
|
||||
if (sentWarnings.has(message)) return
|
||||
|
||||
@@ -21,7 +21,7 @@ export function warnDeprecated(message: string, source?: object): void {
|
||||
sentWarnings.add(message)
|
||||
}
|
||||
|
||||
for (const callback of LiteGraphInternal.onDeprecationWarning) {
|
||||
for (const callback of LiteGraph.onDeprecationWarning) {
|
||||
callback(message, source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,22 +13,6 @@ export class AssetWidget
|
||||
this.value = widget.value?.toString() ?? ''
|
||||
}
|
||||
|
||||
override set value(value: IAssetWidget['value']) {
|
||||
const oldValue = this.value
|
||||
super.value = value
|
||||
|
||||
// Force canvas redraw when value changes to show update immediately
|
||||
if (oldValue !== value && this.node.graph?.list_of_graphcanvas) {
|
||||
for (const canvas of this.node.graph.list_of_graphcanvas) {
|
||||
canvas.setDirty(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override get value(): IAssetWidget['value'] {
|
||||
return super.value
|
||||
}
|
||||
|
||||
override get _displayValue(): string {
|
||||
return String(this.value) //FIXME: Resolve asset name
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@ import type {
|
||||
LGraphNode,
|
||||
Size
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
|
||||
export interface DrawWidgetOptions {
|
||||
/** The width of the node where this widget will be displayed. */
|
||||
width: number
|
||||
@@ -148,28 +147,28 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget>
|
||||
|
||||
get outline_color() {
|
||||
return this.advanced
|
||||
? LiteGraphInternal.WIDGET_ADVANCED_OUTLINE_COLOR
|
||||
: LiteGraphInternal.WIDGET_OUTLINE_COLOR
|
||||
? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR
|
||||
: LiteGraph.WIDGET_OUTLINE_COLOR
|
||||
}
|
||||
|
||||
get background_color() {
|
||||
return LiteGraphInternal.WIDGET_BGCOLOR
|
||||
return LiteGraph.WIDGET_BGCOLOR
|
||||
}
|
||||
|
||||
get height() {
|
||||
return LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
return LiteGraph.NODE_WIDGET_HEIGHT
|
||||
}
|
||||
|
||||
get text_color() {
|
||||
return LiteGraphInternal.WIDGET_TEXT_COLOR
|
||||
return LiteGraph.WIDGET_TEXT_COLOR
|
||||
}
|
||||
|
||||
get secondary_text_color() {
|
||||
return LiteGraphInternal.WIDGET_SECONDARY_TEXT_COLOR
|
||||
return LiteGraph.WIDGET_SECONDARY_TEXT_COLOR
|
||||
}
|
||||
|
||||
get disabledTextColor() {
|
||||
return LiteGraphInternal.WIDGET_DISABLED_TEXT_COLOR
|
||||
return LiteGraph.WIDGET_DISABLED_TEXT_COLOR
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
@@ -256,7 +255,7 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget>
|
||||
if (requiredWidth <= totalWidth) {
|
||||
// Draw label & value normally
|
||||
drawTextInArea({ ctx, text: displayName, area, align: 'left' })
|
||||
} else if (LiteGraphInternal.truncateWidgetTextEvenly) {
|
||||
} else if (LiteGraph.truncateWidgetTextEvenly) {
|
||||
// Label + value will not fit - scale evenly to fit
|
||||
const scale = (totalWidth - gap) / (requiredWidth - gap)
|
||||
area.width = labelWidth * scale
|
||||
@@ -266,7 +265,7 @@ export abstract class BaseWidget<TWidget extends IBaseWidget = IBaseWidget>
|
||||
// Move the area to the right to render the value
|
||||
area.right = x + totalWidth
|
||||
area.setWidthRightAnchored(valueWidth * scale)
|
||||
} else if (LiteGraphInternal.truncateWidgetValuesFirst) {
|
||||
} else if (LiteGraph.truncateWidgetValuesFirst) {
|
||||
// Label + value will not fit - use legacy scaling of value first
|
||||
const cappedLabelWidth = Math.min(labelWidth, totalWidth)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { clamp } from 'es-toolkit/compat'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
IComboWidget,
|
||||
IStringComboWidget
|
||||
} from '@/lib/litegraph/src/types/widgets'
|
||||
import { warnDeprecated } from '@/lib/litegraph/src/utils/feedback'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import { BaseSteppedWidget } from './BaseSteppedWidget'
|
||||
import type { WidgetEventOptions } from './BaseWidget'
|
||||
|
||||
@@ -133,7 +133,7 @@ export class ComboWidget
|
||||
|
||||
// Handle center click - show dropdown menu
|
||||
const text_values = values != values_list ? Object.values(values) : values
|
||||
new LiteGraphInternal.ContextMenu(text_values, {
|
||||
new LiteGraph.ContextMenu(text_values, {
|
||||
scale: Math.max(1, canvas.ds.scale),
|
||||
event: e,
|
||||
className: 'dark',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
||||
|
||||
import { LiteGraphInternal } from '../LiteGraphInternal'
|
||||
import { BaseWidget, type DrawWidgetOptions } from './BaseWidget'
|
||||
|
||||
/**
|
||||
@@ -27,7 +27,7 @@ export class LegacyWidget<TWidget extends IBaseWidget = IBaseWidget>
|
||||
ctx: CanvasRenderingContext2D,
|
||||
options: DrawWidgetOptions
|
||||
) {
|
||||
const H = LiteGraphInternal.NODE_WIDGET_HEIGHT
|
||||
const H = LiteGraph.NODE_WIDGET_HEIGHT
|
||||
this.draw?.(ctx, this.node, options.width, this.y, H, !!options.showText)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import type {
|
||||
IAssetWidget,
|
||||
IBaseWidget,
|
||||
IComboWidget,
|
||||
IWidget,
|
||||
@@ -133,9 +132,4 @@ export function isComboWidget(widget: IBaseWidget): widget is IComboWidget {
|
||||
return widget.type === 'combo'
|
||||
}
|
||||
|
||||
/** Type guard: Narrow **from {@link IBaseWidget}** to {@link IAssetWidget}. */
|
||||
export function isAssetWidget(widget: IBaseWidget): widget is IAssetWidget {
|
||||
return widget.type === 'asset'
|
||||
}
|
||||
|
||||
// #endregion Type Guards
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { describe } from 'vitest'
|
||||
|
||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import { test } from './testExtensions'
|
||||
|
||||
describe('LGraph', () => {
|
||||
@@ -18,8 +17,8 @@ describe('LGraph', () => {
|
||||
const directImport = await import('@/lib/litegraph/src/LGraph')
|
||||
const entryPointImport = await import('@/lib/litegraph/src/litegraph')
|
||||
|
||||
expect(LiteGraphInternal.LGraph).toBe(directImport.LGraph)
|
||||
expect(LiteGraphInternal.LGraph).toBe(entryPointImport.LGraph)
|
||||
expect(LiteGraph.LGraph).toBe(directImport.LGraph)
|
||||
expect(LiteGraph.LGraph).toBe(entryPointImport.LGraph)
|
||||
})
|
||||
|
||||
test('populates optional values', ({ expect, minimalSerialisableGraph }) => {
|
||||
@@ -140,6 +139,6 @@ describe('Legacy LGraph Compatibility Layer', () => {
|
||||
})
|
||||
|
||||
test('is correctly assigned to LiteGraph', ({ expect }) => {
|
||||
expect(LiteGraphInternal.LGraph).toBe(LGraph)
|
||||
expect(LiteGraph.LGraph).toBe(LGraph)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { LGraphCanvas } from '@/lib/litegraph/src/LGraphCanvas'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
describe('LGraphCanvas Title Button Rendering', () => {
|
||||
let canvas: LGraphCanvas
|
||||
@@ -117,7 +115,7 @@ describe('LGraphCanvas Title Button Rendering', () => {
|
||||
// Check draw positions (right-aligned from node width)
|
||||
// First button (rightmost): 200 - 5 = 195, then subtract width
|
||||
// Second button: first button position - 5 - button width
|
||||
const titleHeight = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
|
||||
const buttonY = -titleHeight + (titleHeight - 20) / 2 // Centered
|
||||
expect(draw1).toHaveBeenCalledWith(ctx, 180, buttonY) // 200 - 20
|
||||
expect(draw2).toHaveBeenCalledWith(ctx, 153, buttonY) // 180 - 2 - 25
|
||||
@@ -182,7 +180,7 @@ describe('LGraphCanvas Title Button Rendering', () => {
|
||||
|
||||
canvas.drawNode(node, ctx)
|
||||
|
||||
const titleHeight = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
|
||||
|
||||
// Check positions are correctly spaced (right to left)
|
||||
// Starting position: 200
|
||||
@@ -211,8 +209,7 @@ describe('LGraphCanvas Title Button Rendering', () => {
|
||||
|
||||
// Buttons should still be rendered in low quality mode
|
||||
const buttonY =
|
||||
-LiteGraphInternal.NODE_TITLE_HEIGHT +
|
||||
(LiteGraphInternal.NODE_TITLE_HEIGHT - 20) / 2
|
||||
-LiteGraph.NODE_TITLE_HEIGHT + (LiteGraph.NODE_TITLE_HEIGHT - 20) / 2
|
||||
expect(drawSpy).toHaveBeenCalledWith(ctx, 180, buttonY)
|
||||
})
|
||||
|
||||
@@ -239,7 +236,7 @@ describe('LGraphCanvas Title Button Rendering', () => {
|
||||
|
||||
canvas.drawNode(node, ctx)
|
||||
|
||||
const titleHeight = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
|
||||
|
||||
// Small button (rightmost): 200 - 15 = 185
|
||||
const buttonY = -titleHeight + (titleHeight - 20) / 2
|
||||
@@ -266,7 +263,7 @@ describe('LGraphCanvas Title Button Rendering', () => {
|
||||
|
||||
canvas.drawNode(node, ctx)
|
||||
|
||||
const titleHeight = LiteGraphInternal.NODE_TITLE_HEIGHT
|
||||
const titleHeight = LiteGraph.NODE_TITLE_HEIGHT
|
||||
// Should use new width: 300 - 20 = 280
|
||||
const buttonY = -titleHeight + (titleHeight - 20) / 2
|
||||
expect(drawSpy).toHaveBeenCalledWith(ctx, 280, buttonY)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { beforeEach, describe, expect } from 'vitest'
|
||||
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import { test } from './testExtensions'
|
||||
|
||||
describe('LGraphNode resize functionality', () => {
|
||||
@@ -10,7 +9,7 @@ describe('LGraphNode resize functionality', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
// Set up LiteGraph constants needed for measure
|
||||
LiteGraphInternal.NODE_TITLE_HEIGHT = 20
|
||||
LiteGraph.NODE_TITLE_HEIGHT = 20
|
||||
|
||||
node = new LGraphNode('Test Node')
|
||||
node.pos = [100, 100]
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { afterEach, beforeEach, describe, expect, vi } from 'vitest'
|
||||
|
||||
import type { INodeInputSlot, Point } from '@/lib/litegraph/src/interfaces'
|
||||
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { NodeInputSlot } from '@/lib/litegraph/src/node/NodeInputSlot'
|
||||
import { NodeOutputSlot } from '@/lib/litegraph/src/node/NodeOutputSlot'
|
||||
import type { ISerialisedNode } from '@/lib/litegraph/src/types/serialisation'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import { test } from './testExtensions'
|
||||
|
||||
function getMockISerialisedNode(
|
||||
@@ -29,14 +28,14 @@ function getMockISerialisedNode(
|
||||
|
||||
describe('LGraphNode', () => {
|
||||
let node: LGraphNode
|
||||
let origLiteGraph: typeof LiteGraphInternal
|
||||
let origLiteGraph: typeof LiteGraph
|
||||
|
||||
beforeEach(() => {
|
||||
origLiteGraph = Object.assign({}, LiteGraphInternal)
|
||||
origLiteGraph = Object.assign({}, LiteGraph)
|
||||
// @ts-expect-error TODO: Fix after merge - Classes property not in type
|
||||
delete origLiteGraph.Classes
|
||||
|
||||
Object.assign(LiteGraphInternal, {
|
||||
Object.assign(LiteGraph, {
|
||||
NODE_TITLE_HEIGHT: 20,
|
||||
NODE_SLOT_HEIGHT: 15,
|
||||
NODE_TEXT_SIZE: 14,
|
||||
@@ -53,7 +52,7 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Object.assign(LiteGraphInternal, origLiteGraph)
|
||||
Object.assign(LiteGraph, origLiteGraph)
|
||||
})
|
||||
|
||||
test('should serialize position/size correctly', () => {
|
||||
@@ -461,8 +460,8 @@ describe('LGraphNode', () => {
|
||||
node.addOutput('output2', 'number')
|
||||
|
||||
// Calculate expected positions
|
||||
const slotOffset = LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
const slotSpacing = LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
const slotOffset = LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
const slotSpacing = LiteGraph.NODE_SLOT_HEIGHT
|
||||
const nodeWidth = node.size[0]
|
||||
|
||||
// Test input positions
|
||||
@@ -497,8 +496,8 @@ describe('LGraphNode', () => {
|
||||
node.addInput('default-input1', 'number')
|
||||
node.addInput('default-input2', 'number')
|
||||
|
||||
const slotOffset = LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
const slotSpacing = LiteGraphInternal.NODE_SLOT_HEIGHT
|
||||
const slotOffset = LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
const slotSpacing = LiteGraph.NODE_SLOT_HEIGHT
|
||||
|
||||
// Test: default positioned slots should be consecutive, ignoring absolute positioned ones
|
||||
expect(node.getInputPos(1)).toEqual([
|
||||
@@ -577,10 +576,7 @@ describe('LGraphNode', () => {
|
||||
})
|
||||
test('should return position based on title height when collapsed', () => {
|
||||
node.flags.collapsed = true
|
||||
const expectedPos: Point = [
|
||||
100,
|
||||
200 - LiteGraphInternal.NODE_TITLE_HEIGHT * 0.5
|
||||
]
|
||||
const expectedPos: Point = [100, 200 - LiteGraph.NODE_TITLE_HEIGHT * 0.5]
|
||||
expect(node.getInputSlotPos(inputSlot)).toEqual(expectedPos)
|
||||
})
|
||||
|
||||
@@ -604,16 +600,12 @@ describe('LGraphNode', () => {
|
||||
const slotIndex = 0
|
||||
const nodeOffsetY = (node.constructor as any).slot_start_y || 0
|
||||
const expectedY =
|
||||
200 +
|
||||
(slotIndex + 0.7) * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
nodeOffsetY
|
||||
const expectedX = 100 + LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
expect(node.getInputSlotPos(inputSlot)).toEqual([expectedX, expectedY])
|
||||
const slotIndex2 = 1
|
||||
const expectedY2 =
|
||||
200 +
|
||||
(slotIndex2 + 0.7) * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
nodeOffsetY
|
||||
200 + (slotIndex2 + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||
expect(node.getInputSlotPos(inputSlot2)).toEqual([expectedX, expectedY2])
|
||||
})
|
||||
|
||||
@@ -624,10 +616,8 @@ describe('LGraphNode', () => {
|
||||
const slotIndex = 0
|
||||
const nodeOffsetY = 25
|
||||
const expectedY =
|
||||
200 +
|
||||
(slotIndex + 0.7) * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
nodeOffsetY
|
||||
const expectedX = 100 + LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||
const expectedX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
expect(node.getInputSlotPos(inputSlot)).toEqual([expectedX, expectedY])
|
||||
delete (node.constructor as any).slot_start_y
|
||||
})
|
||||
@@ -660,10 +650,8 @@ describe('LGraphNode', () => {
|
||||
const slotIndex = 0
|
||||
const nodeOffsetY = (node.constructor as any).slot_start_y || 0
|
||||
const expectedDefaultY =
|
||||
200 +
|
||||
(slotIndex + 0.7) * LiteGraphInternal.NODE_SLOT_HEIGHT +
|
||||
nodeOffsetY
|
||||
const expectedDefaultX = 100 + LiteGraphInternal.NODE_SLOT_HEIGHT * 0.5
|
||||
200 + (slotIndex + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + nodeOffsetY
|
||||
const expectedDefaultX = 100 + LiteGraph.NODE_SLOT_HEIGHT * 0.5
|
||||
expect(node.getInputPos(0)).toEqual([expectedDefaultX, expectedDefaultY])
|
||||
spy.mockRestore()
|
||||
})
|
||||
@@ -672,7 +660,7 @@ describe('LGraphNode', () => {
|
||||
describe('removeInput/removeOutput on copied nodes', () => {
|
||||
beforeEach(() => {
|
||||
// Register a test node type so clone() can work
|
||||
LiteGraphInternal.registerNodeType('TestNode', LGraphNode)
|
||||
LiteGraph.registerNodeType('TestNode', LGraphNode)
|
||||
})
|
||||
|
||||
test('should NOT throw error when calling removeInput on a copied node without graph', () => {
|
||||
|
||||
@@ -2,15 +2,14 @@ import { clamp } from 'es-toolkit/compat'
|
||||
import { beforeEach, describe, expect, vi } from 'vitest'
|
||||
|
||||
import { LiteGraphGlobal } from '@/lib/litegraph/src/LiteGraphGlobal'
|
||||
import { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphCanvas, LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import { test } from './testExtensions'
|
||||
|
||||
describe('Litegraph module', () => {
|
||||
test('contains a global export', ({ expect }) => {
|
||||
expect(LiteGraphInternal).toBeInstanceOf(LiteGraphGlobal)
|
||||
expect(LiteGraphInternal.LGraphCanvas).toBe(LGraphCanvas)
|
||||
expect(LiteGraph).toBeInstanceOf(LiteGraphGlobal)
|
||||
expect(LiteGraph.LGraphCanvas).toBe(LGraphCanvas)
|
||||
})
|
||||
|
||||
test('has the same structure', ({ expect }) => {
|
||||
@@ -37,12 +36,8 @@ describe('Import order dependency', () => {
|
||||
const directImport = await import('@/lib/litegraph/src/LGraph')
|
||||
|
||||
// Sanity check that imports were cleared.
|
||||
expect(Object.is(LiteGraphInternal, entryPointImport.LiteGraph)).toBe(
|
||||
false
|
||||
)
|
||||
expect(Object.is(LiteGraphInternal.LGraph, directImport.LGraph)).toBe(
|
||||
false
|
||||
)
|
||||
expect(Object.is(LiteGraph, entryPointImport.LiteGraph)).toBe(false)
|
||||
expect(Object.is(LiteGraph.LGraph, directImport.LGraph)).toBe(false)
|
||||
}
|
||||
|
||||
await expect(importNormally()).resolves.toBeUndefined()
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { assert, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ISlotType, LGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import {
|
||||
LGraphGroup,
|
||||
LGraphNode,
|
||||
LiteGraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../../src/LiteGraphInternal'
|
||||
import {
|
||||
createTestSubgraph,
|
||||
createTestSubgraphNode
|
||||
@@ -16,7 +19,7 @@ function createNode(
|
||||
title?: string
|
||||
) {
|
||||
const type = JSON.stringify({ inputs, outputs })
|
||||
if (!LiteGraphInternal.registered_node_types[type]) {
|
||||
if (!LiteGraph.registered_node_types[type]) {
|
||||
class testnode extends LGraphNode {
|
||||
constructor(title: string) {
|
||||
super(title)
|
||||
@@ -27,9 +30,9 @@ function createNode(
|
||||
this.addOutput('output_' + o_count++, output)
|
||||
}
|
||||
}
|
||||
LiteGraphInternal.registered_node_types[type] = testnode
|
||||
LiteGraph.registered_node_types[type] = testnode
|
||||
}
|
||||
const node = LiteGraphInternal.createNode(type, title)
|
||||
const node = LiteGraph.createNode(type, title)
|
||||
if (!node) {
|
||||
throw new Error('Failed to create node')
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { test as baseTest } from 'vitest'
|
||||
|
||||
import { LGraph } from '@/lib/litegraph/src/LGraph'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
|
||||
import { LiteGraphInternal } from '../src/LiteGraphInternal'
|
||||
import type {
|
||||
ISerialisedGraph,
|
||||
SerialisableGraph
|
||||
@@ -74,10 +74,7 @@ export const dirtyTest = test.extend<DirtyFixtures>({
|
||||
|
||||
// Register node types
|
||||
for (const node of basicSerialisableGraph.nodes) {
|
||||
LiteGraphInternal.registerNodeType(
|
||||
node.type!,
|
||||
LiteGraphInternal.LGraphNode
|
||||
)
|
||||
LiteGraph.registerNodeType(node.type!, LiteGraph.LGraphNode)
|
||||
}
|
||||
|
||||
await use(structuredClone(basicSerialisableGraph))
|
||||
|
||||
@@ -1873,13 +1873,6 @@
|
||||
"noModelsInFolder": "No {type} available in this folder",
|
||||
"searchAssetsPlaceholder": "Search assets...",
|
||||
"allModels": "All Models",
|
||||
"unknown": "Unknown",
|
||||
"fileFormats": "File formats",
|
||||
"baseModels": "Base models",
|
||||
"sortBy": "Sort by",
|
||||
"sortAZ": "A-Z",
|
||||
"sortZA": "Z-A",
|
||||
"sortRecent": "Recent",
|
||||
"sortPopular": "Popular"
|
||||
"unknown": "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
21
src/main.ts
@@ -2,6 +2,11 @@ import { definePreset } from '@primevue/themes'
|
||||
import Aura from '@primevue/themes/aura'
|
||||
import * as Sentry from '@sentry/vue'
|
||||
import { initializeApp } from 'firebase/app'
|
||||
import {
|
||||
browserLocalPersistence,
|
||||
browserSessionPersistence,
|
||||
indexedDBLocalPersistence
|
||||
} from 'firebase/auth'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'primeicons/primeicons.css'
|
||||
import PrimeVue from 'primevue/config'
|
||||
@@ -9,7 +14,7 @@ import ConfirmationService from 'primevue/confirmationservice'
|
||||
import ToastService from 'primevue/toastservice'
|
||||
import Tooltip from 'primevue/tooltip'
|
||||
import { createApp } from 'vue'
|
||||
import { VueFire, VueFireAuth } from 'vuefire'
|
||||
import { VueFire, VueFireAuthWithDependencies } from 'vuefire'
|
||||
|
||||
import { FIREBASE_CONFIG } from '@/config/firebase'
|
||||
import '@/lib/litegraph/public/css/litegraph.css'
|
||||
@@ -66,6 +71,18 @@ app
|
||||
.use(i18n)
|
||||
.use(VueFire, {
|
||||
firebaseApp,
|
||||
modules: [VueFireAuth()]
|
||||
modules: [
|
||||
// Configure Firebase Auth persistence: localStorage first, IndexedDB last.
|
||||
// Localstorage is preferred to IndexedDB for mobile Safari compatibility.
|
||||
VueFireAuthWithDependencies({
|
||||
dependencies: {
|
||||
persistence: [
|
||||
browserLocalPersistence,
|
||||
browserSessionPersistence,
|
||||
indexedDBLocalPersistence
|
||||
]
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
.mount('#vue-app')
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import {
|
||||
createMockAssets,
|
||||
mockAssets
|
||||
@@ -57,7 +56,7 @@ export const Default: Story = {
|
||||
render: (args) => ({
|
||||
components: { AssetBrowserModal },
|
||||
setup() {
|
||||
const onAssetSelect = (asset: AssetDisplayItem) => {
|
||||
const onAssetSelect = (asset: any) => {
|
||||
console.log('Selected asset:', asset)
|
||||
}
|
||||
const onClose = () => {
|
||||
@@ -97,7 +96,7 @@ export const SingleAssetType: Story = {
|
||||
render: (args) => ({
|
||||
components: { AssetBrowserModal },
|
||||
setup() {
|
||||
const onAssetSelect = (asset: AssetDisplayItem) => {
|
||||
const onAssetSelect = (asset: any) => {
|
||||
console.log('Selected asset:', asset)
|
||||
}
|
||||
const onClose = () => {
|
||||
@@ -146,7 +145,7 @@ export const NoLeftPanel: Story = {
|
||||
render: (args) => ({
|
||||
components: { AssetBrowserModal },
|
||||
setup() {
|
||||
const onAssetSelect = (asset: AssetDisplayItem) => {
|
||||
const onAssetSelect = (asset: any) => {
|
||||
console.log('Selected asset:', asset)
|
||||
}
|
||||
const onClose = () => {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:nav-items="availableCategories"
|
||||
>
|
||||
<template #header-icon>
|
||||
<div class="icon-[lucide--folder] size-4" />
|
||||
<i-lucide:folder class="size-4" />
|
||||
</template>
|
||||
<template #header-title>{{ $t('assetBrowser.browseAssets') }}</template>
|
||||
</LeftSidePanel>
|
||||
@@ -37,7 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, provide } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import SearchBox from '@/components/input/SearchBox.vue'
|
||||
import BaseModalLayout from '@/components/widget/layout/BaseModalLayout.vue'
|
||||
@@ -46,7 +46,6 @@ import AssetGrid from '@/platform/assets/components/AssetGrid.vue'
|
||||
import type { AssetDisplayItem } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import { useAssetBrowser } from '@/platform/assets/composables/useAssetBrowser'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { OnCloseKey } from '@/types/widgetTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
nodeType?: string
|
||||
@@ -62,28 +61,35 @@ const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
provide(OnCloseKey, props.onClose ?? (() => {}))
|
||||
|
||||
// Use AssetBrowser composable for all business logic
|
||||
const {
|
||||
searchQuery,
|
||||
selectedCategory,
|
||||
availableCategories,
|
||||
contentTitle,
|
||||
filteredAssets,
|
||||
selectAssetWithCallback
|
||||
selectAsset
|
||||
} = useAssetBrowser(props.assets)
|
||||
|
||||
// Dialog controls panel visibility via prop
|
||||
const shouldShowLeftPanel = computed(() => {
|
||||
return props.showLeftPanel ?? true
|
||||
})
|
||||
|
||||
// Handle close button - call both the prop callback and emit the event
|
||||
const handleClose = () => {
|
||||
props.onClose?.()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleAssetSelectAndEmit = async (asset: AssetDisplayItem) => {
|
||||
emit('asset-select', asset)
|
||||
await selectAssetWithCallback(asset.id, props.onSelect)
|
||||
// Handle asset selection and emit to parent
|
||||
const handleAssetSelectAndEmit = (asset: AssetDisplayItem) => {
|
||||
selectAsset(asset) // This logs the selection for dev mode
|
||||
emit('asset-select', asset) // Emit the full asset object
|
||||
|
||||
// Call prop callback if provided
|
||||
if (props.onSelect) {
|
||||
props.onSelect(asset.name) // Use asset name as the asset path
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'bg-ivory-100 border border-gray-300 dark-theme:bg-charcoal-400 dark-theme:border-charcoal-600',
|
||||
'hover:transform hover:-translate-y-0.5 hover:shadow-lg hover:shadow-black/10 hover:border-gray-400',
|
||||
'dark-theme:hover:shadow-lg dark-theme:hover:shadow-black/30 dark-theme:hover:border-charcoal-700',
|
||||
'focus:outline-none focus:transform focus:-translate-y-0.5 focus:shadow-lg focus:shadow-black/10 dark-theme:focus:shadow-black/30'
|
||||
'focus:outline-none focus:ring-2 focus:ring-blue-500 dark-theme:focus:ring-blue-400'
|
||||
],
|
||||
// Div-specific styles
|
||||
!interactive && [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div :class="leftSideClasses" data-component-id="asset-filter-bar-left">
|
||||
<MultiSelect
|
||||
v-model="fileFormats"
|
||||
:label="$t('assetBrowser.fileFormats')"
|
||||
label="File formats"
|
||||
:options="fileFormatOptions"
|
||||
:class="selectClasses"
|
||||
data-component-id="asset-filter-file-formats"
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<MultiSelect
|
||||
v-model="baseModels"
|
||||
:label="$t('assetBrowser.baseModels')"
|
||||
label="Base models"
|
||||
:options="baseModelOptions"
|
||||
:class="selectClasses"
|
||||
data-component-id="asset-filter-base-models"
|
||||
@@ -23,7 +23,7 @@
|
||||
<div :class="rightSideClasses" data-component-id="asset-filter-bar-right">
|
||||
<SingleSelect
|
||||
v-model="sortBy"
|
||||
:label="$t('assetBrowser.sortBy')"
|
||||
label="Sort by"
|
||||
:options="sortOptions"
|
||||
:class="selectClasses"
|
||||
data-component-id="asset-filter-sort"
|
||||
@@ -43,7 +43,6 @@ import { ref } from 'vue'
|
||||
import MultiSelect from '@/components/input/MultiSelect.vue'
|
||||
import SingleSelect from '@/components/input/SingleSelect.vue'
|
||||
import type { SelectOption } from '@/components/input/types'
|
||||
import { t } from '@/i18n'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
export interface FilterState {
|
||||
@@ -75,10 +74,10 @@ const baseModelOptions = [
|
||||
// TODO: Make sortOptions configurable via props
|
||||
// Different asset types might need different sorting options
|
||||
const sortOptions = [
|
||||
{ name: t('assetBrowser.sortAZ'), value: 'name-asc' },
|
||||
{ name: t('assetBrowser.sortZA'), value: 'name-desc' },
|
||||
{ name: t('assetBrowser.sortRecent'), value: 'recent' },
|
||||
{ name: t('assetBrowser.sortPopular'), value: 'popular' }
|
||||
{ name: 'A-Z', value: 'name-asc' },
|
||||
{ name: 'Z-A', value: 'name-desc' },
|
||||
{ name: 'Recent', value: 'recent' },
|
||||
{ name: 'Popular', value: 'popular' }
|
||||
]
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { d, t } from '@/i18n'
|
||||
import { t } from '@/i18n'
|
||||
import type { UUID } from '@/lib/litegraph/src/utils/uuid'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetFilenameSchema } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import {
|
||||
getAssetBaseModel,
|
||||
getAssetDescription
|
||||
@@ -70,7 +69,7 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
|
||||
// Create display stats from API data
|
||||
const stats = {
|
||||
formattedDate: d(new Date(asset.created_at), { dateStyle: 'short' }),
|
||||
formattedDate: new Date(asset.created_at).toLocaleDateString(),
|
||||
downloadCount: undefined, // Not available in API
|
||||
stars: undefined // Not available in API
|
||||
}
|
||||
@@ -163,41 +162,12 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
return filtered.map(transformAssetForDisplay)
|
||||
})
|
||||
|
||||
/**
|
||||
* Asset selection that fetches full details and executes callback with filename
|
||||
* @param assetId - The asset ID to select and fetch details for
|
||||
* @param onSelect - Optional callback to execute with the asset filename
|
||||
*/
|
||||
async function selectAssetWithCallback(
|
||||
assetId: string,
|
||||
onSelect?: (filename: string) => void
|
||||
): Promise<void> {
|
||||
// Actions
|
||||
function selectAsset(asset: AssetDisplayItem): UUID {
|
||||
if (import.meta.env.DEV) {
|
||||
console.debug('Asset selected:', assetId)
|
||||
}
|
||||
|
||||
if (!onSelect) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const detailAsset = await assetService.getAssetDetails(assetId)
|
||||
const filename = detailAsset.user_metadata?.filename
|
||||
const validatedFilename = assetFilenameSchema.safeParse(filename)
|
||||
if (!validatedFilename.success) {
|
||||
console.error(
|
||||
'Invalid asset filename:',
|
||||
validatedFilename.error.errors,
|
||||
'for asset:',
|
||||
assetId
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
onSelect(validatedFilename.data)
|
||||
} catch (error) {
|
||||
console.error(`Failed to fetch asset details for ${assetId}:`, error)
|
||||
console.log('Asset selected:', asset.id, asset.name)
|
||||
}
|
||||
return asset.id
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -212,6 +182,7 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
filteredAssets,
|
||||
|
||||
// Actions
|
||||
selectAssetWithCallback
|
||||
selectAsset,
|
||||
transformAssetForDisplay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import AssetBrowserModal from '@/platform/assets/components/AssetBrowserModal.vue'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { type DialogComponentProps, useDialogStore } from '@/stores/dialogStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
interface AssetBrowserDialogProps {
|
||||
/** ComfyUI node type for context (e.g., 'CheckpointLoaderSimple') */
|
||||
@@ -10,29 +8,36 @@ interface AssetBrowserDialogProps {
|
||||
inputName: string
|
||||
/** Current selected asset value */
|
||||
currentValue?: string
|
||||
/**
|
||||
* Callback for when an asset is selected
|
||||
* @param {string} filename - The validated filename from user_metadata.filename
|
||||
*/
|
||||
onAssetSelected?: (filename: string) => void
|
||||
/** Callback for when an asset is selected */
|
||||
onAssetSelected?: (assetPath: string) => void
|
||||
}
|
||||
|
||||
export const useAssetBrowserDialog = () => {
|
||||
const dialogStore = useDialogStore()
|
||||
const dialogKey = 'global-asset-browser'
|
||||
|
||||
async function show(props: AssetBrowserDialogProps) {
|
||||
const handleAssetSelected = (filename: string) => {
|
||||
props.onAssetSelected?.(filename)
|
||||
dialogStore.closeDialog({ key: dialogKey })
|
||||
function hide() {
|
||||
dialogStore.closeDialog({ key: dialogKey })
|
||||
}
|
||||
|
||||
function show(props: AssetBrowserDialogProps) {
|
||||
const handleAssetSelected = (assetPath: string) => {
|
||||
props.onAssetSelected?.(assetPath)
|
||||
hide() // Auto-close on selection
|
||||
}
|
||||
const dialogComponentProps: DialogComponentProps = {
|
||||
|
||||
const handleClose = () => {
|
||||
hide()
|
||||
}
|
||||
|
||||
// Default dialog configuration for AssetBrowserModal
|
||||
const dialogComponentProps = {
|
||||
headless: true,
|
||||
modal: true,
|
||||
closable: true,
|
||||
closable: false,
|
||||
pt: {
|
||||
root: {
|
||||
class: 'rounded-2xl overflow-hidden asset-browser-dialog'
|
||||
class: 'rounded-2xl overflow-hidden'
|
||||
},
|
||||
header: {
|
||||
class: 'p-0 hidden'
|
||||
@@ -43,17 +48,6 @@ export const useAssetBrowserDialog = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const assets: AssetItem[] = await assetService
|
||||
.getAssetsForNodeType(props.nodeType)
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
'Failed to fetch assets for node type:',
|
||||
props.nodeType,
|
||||
error
|
||||
)
|
||||
return []
|
||||
})
|
||||
|
||||
dialogStore.showDialog({
|
||||
key: dialogKey,
|
||||
component: AssetBrowserModal,
|
||||
@@ -61,13 +55,12 @@ export const useAssetBrowserDialog = () => {
|
||||
nodeType: props.nodeType,
|
||||
inputName: props.inputName,
|
||||
currentValue: props.currentValue,
|
||||
assets,
|
||||
onSelect: handleAssetSelected,
|
||||
onClose: () => dialogStore.closeDialog({ key: dialogKey })
|
||||
onClose: handleClose
|
||||
},
|
||||
dialogComponentProps
|
||||
})
|
||||
}
|
||||
|
||||
return { show }
|
||||
return { show, hide }
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import { z } from 'zod'
|
||||
const zAsset = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
asset_hash: z.string().nullable(),
|
||||
asset_hash: z.string(),
|
||||
size: z.number(),
|
||||
mime_type: z.string().nullable(),
|
||||
mime_type: z.string(),
|
||||
tags: z.array(z.string()),
|
||||
preview_url: z.string().optional(),
|
||||
created_at: z.string(),
|
||||
updated_at: z.string().optional(),
|
||||
updated_at: z.string(),
|
||||
last_access_time: z.string(),
|
||||
user_metadata: z.record(z.unknown()).optional(), // API allows arbitrary key-value pairs
|
||||
preview_id: z.string().nullable().optional()
|
||||
@@ -33,14 +33,6 @@ const zModelFile = z.object({
|
||||
pathIndex: z.number()
|
||||
})
|
||||
|
||||
// Filename validation schema
|
||||
export const assetFilenameSchema = z
|
||||
.string()
|
||||
.min(1, 'Filename cannot be empty')
|
||||
.regex(/^[^\\:*?"<>|]+$/, 'Invalid filename characters') // Allow forward slashes, block backslashes and other unsafe chars
|
||||
.regex(/^(?!\/|.*\.\.)/, 'Path must not start with / or contain ..') // Prevent absolute paths and directory traversal
|
||||
.trim()
|
||||
|
||||
// Export schemas following repository patterns
|
||||
export const assetResponseSchema = zAssetResponse
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { fromZodError } from 'zod-validation-error'
|
||||
|
||||
import {
|
||||
type AssetItem,
|
||||
type AssetResponse,
|
||||
type ModelFile,
|
||||
type ModelFolder,
|
||||
@@ -128,75 +127,10 @@ function createAssetService() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets assets for a specific node type by finding the matching category
|
||||
* and fetching all assets with that category tag
|
||||
*
|
||||
* @param nodeType - The ComfyUI node type (e.g., 'CheckpointLoaderSimple')
|
||||
* @returns Promise<AssetItem[]> - Full asset objects with preserved metadata
|
||||
*/
|
||||
async function getAssetsForNodeType(nodeType: string): Promise<AssetItem[]> {
|
||||
if (!nodeType || typeof nodeType !== 'string') {
|
||||
return []
|
||||
}
|
||||
|
||||
// Find the category for this node type using efficient O(1) lookup
|
||||
const modelToNodeStore = useModelToNodeStore()
|
||||
const category = modelToNodeStore.getCategoryForNodeType(nodeType)
|
||||
|
||||
if (!category) {
|
||||
return []
|
||||
}
|
||||
|
||||
// Fetch assets for this category using same API pattern as getAssetModels
|
||||
const data = await handleAssetRequest(
|
||||
`${ASSETS_ENDPOINT}?include_tags=${MODELS_TAG},${category}`,
|
||||
`assets for ${nodeType}`
|
||||
)
|
||||
|
||||
// Return full AssetItem[] objects (don't strip like getAssetModels does)
|
||||
return (
|
||||
data?.assets?.filter(
|
||||
(asset) =>
|
||||
!asset.tags.includes(MISSING_TAG) && asset.tags.includes(category)
|
||||
) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets complete details for a specific asset by ID
|
||||
* Calls the detail endpoint which includes user_metadata and all fields
|
||||
*
|
||||
* @param id - The asset ID
|
||||
* @returns Promise<AssetItem> - Complete asset object with user_metadata
|
||||
*/
|
||||
async function getAssetDetails(id: string): Promise<AssetItem> {
|
||||
const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id}`)
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Unable to load asset details for ${id}: Server returned ${res.status}. Please try again.`
|
||||
)
|
||||
}
|
||||
const data = await res.json()
|
||||
|
||||
// Validate the single asset response against our schema
|
||||
const result = assetResponseSchema.safeParse({ assets: [data] })
|
||||
if (result.success && result.data.assets?.[0]) {
|
||||
return result.data.assets[0]
|
||||
}
|
||||
|
||||
const error = result.error
|
||||
? fromZodError(result.error)
|
||||
: 'Unknown validation error'
|
||||
throw new Error(`Invalid asset response against zod schema:\n${error}`)
|
||||
}
|
||||
|
||||
return {
|
||||
getAssetModelFolders,
|
||||
getAssetModels,
|
||||
isAssetBrowserEligible,
|
||||
getAssetsForNodeType,
|
||||
getAssetDetails
|
||||
isAssetBrowserEligible
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -595,7 +595,7 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
migrateDeprecatedValue: (value: any[]) => {
|
||||
return value.map((keybinding) => {
|
||||
if (keybinding['targetSelector'] === '#graph-canvas') {
|
||||
keybinding['targetElementId'] = 'graph-canvas-container'
|
||||
keybinding['targetElementId'] = 'graph-canvas'
|
||||
}
|
||||
return keybinding
|
||||
})
|
||||
|
||||
@@ -99,16 +99,6 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||
const currentGraph = shallowRef<LGraph | null>(null)
|
||||
const isInSubgraph = ref(false)
|
||||
|
||||
// Provide selection state to all Vue nodes
|
||||
const selectedNodeIds = computed(
|
||||
() =>
|
||||
new Set(
|
||||
selectedItems.value
|
||||
.filter((item) => item.id !== undefined)
|
||||
.map((item) => String(item.id))
|
||||
)
|
||||
)
|
||||
|
||||
whenever(
|
||||
() => canvas.value,
|
||||
(newCanvas) => {
|
||||
@@ -132,7 +122,6 @@ export const useCanvasStore = defineStore('canvas', () => {
|
||||
return {
|
||||
canvas,
|
||||
selectedItems,
|
||||
selectedNodeIds,
|
||||
nodeSelected,
|
||||
groupSelected,
|
||||
rerouteSelected,
|
||||
|
||||
25
src/renderer/core/canvas/injectionKeys.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { InjectionKey, Ref } from 'vue'
|
||||
|
||||
import type { NodeProgressState } from '@/schemas/apiSchema'
|
||||
|
||||
/**
|
||||
* Injection key for providing selected node IDs to Vue node components.
|
||||
* Contains a reactive Set of selected node IDs (as strings).
|
||||
*/
|
||||
export const SelectedNodeIdsKey: InjectionKey<Ref<Set<string>>> =
|
||||
Symbol('selectedNodeIds')
|
||||
|
||||
/**
|
||||
* Injection key for providing executing node IDs to Vue node components.
|
||||
* Contains a reactive Set of currently executing node IDs (as strings).
|
||||
*/
|
||||
export const ExecutingNodeIdsKey: InjectionKey<Ref<Set<string>>> =
|
||||
Symbol('executingNodeIds')
|
||||
|
||||
/**
|
||||
* Injection key for providing node progress states to Vue node components.
|
||||
* Contains a reactive Record of node IDs to their current progress state.
|
||||
*/
|
||||
export const NodeProgressStatesKey: InjectionKey<
|
||||
Ref<Record<string, NodeProgressState>>
|
||||
> = Symbol('nodeProgressStates')
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { InjectionKey } from 'vue'
|
||||
|
||||
import type { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
import type { Point } from '@/renderer/core/layout/types'
|
||||
|
||||
/**
|
||||
* Lightweight, injectable transform state used by layout-aware components.
|
||||
@@ -21,11 +21,29 @@ import type { useTransformState } from '@/renderer/core/layout/transform/useTran
|
||||
* const state = inject(TransformStateKey)!
|
||||
* const screen = state.canvasToScreen({ x: 100, y: 50 })
|
||||
*/
|
||||
interface TransformState
|
||||
extends Pick<
|
||||
ReturnType<typeof useTransformState>,
|
||||
'screenToCanvas' | 'canvasToScreen' | 'camera' | 'isNodeInViewport'
|
||||
> {}
|
||||
interface TransformState {
|
||||
/** Convert a screen-space point (CSS pixels) to canvas space. */
|
||||
screenToCanvas: (p: Point) => Point
|
||||
/** Convert a canvas-space point to screen space (CSS pixels). */
|
||||
canvasToScreen: (p: Point) => Point
|
||||
/** Current pan/zoom; `x`/`y` are offsets, `z` is scale. */
|
||||
camera?: { x: number; y: number; z: number }
|
||||
/**
|
||||
* Test whether a node's rectangle intersects the (expanded) viewport.
|
||||
* Handy for viewport culling and lazy work.
|
||||
*
|
||||
* @param nodePos Top-left in canvas space `[x, y]`
|
||||
* @param nodeSize Size in canvas units `[width, height]`
|
||||
* @param viewport Screen-space viewport `{ width, height }`
|
||||
* @param margin Optional fractional margin (e.g. `0.2` = 20%)
|
||||
*/
|
||||
isNodeInViewport?: (
|
||||
nodePos: ArrayLike<number>,
|
||||
nodeSize: ArrayLike<number>,
|
||||
viewport: { width: number; height: number },
|
||||
margin?: number
|
||||
) => boolean
|
||||
}
|
||||
|
||||
export const TransformStateKey: InjectionKey<TransformState> =
|
||||
Symbol('transformState')
|
||||
|
||||
@@ -1379,7 +1379,6 @@ class LayoutStoreImpl implements LayoutStore {
|
||||
|
||||
this.spatialIndex.update(nodeId, bounds)
|
||||
ynode.set('bounds', bounds)
|
||||
ynode.set('position', { x: bounds.x, y: bounds.y })
|
||||
ynode.set('size', { width: bounds.width, height: bounds.height })
|
||||
}
|
||||
}, this.currentActor)
|
||||
|
||||
@@ -134,11 +134,7 @@ export function useSlotLayoutSync() {
|
||||
restoreHandlers = () => {
|
||||
graph.onNodeAdded = origNodeAdded || undefined
|
||||
graph.onNodeRemoved = origNodeRemoved || undefined
|
||||
// Only restore onTrigger if Vue nodes are not active
|
||||
// Vue node manager sets its own onTrigger handler
|
||||
if (!LiteGraph.vueNodesMode) {
|
||||
graph.onTrigger = origTrigger || undefined
|
||||
}
|
||||
graph.onTrigger = origTrigger || undefined
|
||||
graph.onAfterChange = origAfterChange || undefined
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="absolute inset-0 w-full h-full pointer-events-none"
|
||||
:class="
|
||||
cn(
|
||||
isInteracting ? 'transform-pane--interacting' : 'will-change-auto',
|
||||
isLOD ? 'isLOD' : ''
|
||||
)
|
||||
"
|
||||
class="transform-pane"
|
||||
:class="{ 'transform-pane--interacting': isInteracting }"
|
||||
:style="transformStyle"
|
||||
@pointerdown="handlePointerDown"
|
||||
>
|
||||
@@ -23,8 +18,6 @@ import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||
import { useCanvasTransformSync } from '@/renderer/core/layout/transform/useCanvasTransformSync'
|
||||
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
|
||||
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
|
||||
import { useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
interface TransformPaneProps {
|
||||
canvas?: LGraphCanvas
|
||||
@@ -41,8 +34,6 @@ const {
|
||||
isNodeInViewport
|
||||
} = useTransformState()
|
||||
|
||||
const { isLOD } = useLOD(camera)
|
||||
|
||||
const canvasElement = computed(() => props.canvas?.canvas)
|
||||
const { isTransforming: isInteracting } = useTransformSettling(canvasElement, {
|
||||
settleDelay: 200,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
v-else
|
||||
:src="currentImageUrl"
|
||||
:alt="imageAltText"
|
||||
class="w-full h-[352px] object-contain block"
|
||||
class="w-full h-[352px] object-cover block"
|
||||
@load="handleImageLoad"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
@@ -94,20 +94,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<!-- Image Dimensions -->
|
||||
<div class="text-white text-xs text-center mt-2">
|
||||
<span v-if="imageError" class="text-red-400">
|
||||
{{ $t('g.errorLoadingImage') }}
|
||||
</span>
|
||||
<span v-else-if="isLoading" class="text-gray-400">
|
||||
{{ $t('g.loading') }}...
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ actualDimensions || $t('g.calculatingDimensions') }}
|
||||
</span>
|
||||
</div>
|
||||
<LODFallback />
|
||||
<!-- Image Dimensions -->
|
||||
<div class="text-white text-xs text-center mt-2">
|
||||
<span v-if="imageError" class="text-red-400">
|
||||
{{ $t('g.errorLoadingImage') }}
|
||||
</span>
|
||||
<span v-else-if="isLoading" class="text-gray-400">
|
||||
{{ $t('g.loading') }}...
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ actualDimensions || $t('g.calculatingDimensions') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -122,8 +119,6 @@ import { downloadFile } from '@/base/common/downloadUtil'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
|
||||
import LODFallback from './LODFallback.vue'
|
||||
|
||||
interface ImagePreviewProps {
|
||||
/** Array of image URLs to display */
|
||||
readonly imageUrls: readonly string[]
|
||||
|
||||
@@ -10,15 +10,12 @@
|
||||
/>
|
||||
|
||||
<!-- Slot Name -->
|
||||
<div class="relative">
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
class="whitespace-nowrap text-sm font-normal dark-theme:text-slate-200 text-stone-200 lod-toggle"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
|
||||
</span>
|
||||
<LODFallback />
|
||||
</div>
|
||||
<span
|
||||
v-if="!dotOnly"
|
||||
class="whitespace-nowrap text-sm font-normal dark-theme:text-slate-200 text-stone-200"
|
||||
>
|
||||
{{ slotData.localized_name || slotData.name || `Input ${index}` }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -41,7 +38,6 @@ import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composabl
|
||||
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import LODFallback from './LODFallback.vue'
|
||||
import SlotConnectionDot from './SlotConnectionDot.vue'
|
||||
|
||||
interface InputSlotProps {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
bypassed,
|
||||
'will-change-transform': isDragging
|
||||
},
|
||||
|
||||
lodCssClass,
|
||||
shouldHandleNodePointerEvents
|
||||
? 'pointer-events-auto'
|
||||
: 'pointer-events-none'
|
||||
@@ -31,7 +31,7 @@
|
||||
"
|
||||
:style="[
|
||||
{
|
||||
transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`,
|
||||
transform: `translate(${layoutPosition.x ?? position?.x ?? 0}px, ${(layoutPosition.y ?? position?.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`,
|
||||
zIndex: zIndex
|
||||
},
|
||||
dragStyle
|
||||
@@ -48,18 +48,21 @@
|
||||
</template>
|
||||
<!-- Header only updates on title/color changes -->
|
||||
<NodeHeader
|
||||
v-memo="[nodeData.title, isCollapsed]"
|
||||
v-memo="[nodeData.title, lodLevel, isCollapsed]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:lod-level="lodLevel"
|
||||
:collapsed="isCollapsed"
|
||||
@collapse="handleCollapse"
|
||||
@update:title="handleHeaderTitleUpdate"
|
||||
@update:title="handleTitleUpdate"
|
||||
@enter-subgraph="handleEnterSubgraph"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isCollapsed && executing && progress !== undefined"
|
||||
v-if="
|
||||
(isMinimalLOD || isCollapsed) && executing && progress !== undefined
|
||||
"
|
||||
:class="
|
||||
cn(
|
||||
'absolute inset-x-4 -bottom-[1px] translate-y-1/2 rounded-full',
|
||||
@@ -69,7 +72,7 @@
|
||||
:style="{ width: `${Math.min(progress * 100, 100)}%` }"
|
||||
/>
|
||||
|
||||
<template v-if="!isCollapsed">
|
||||
<template v-if="!isMinimalLOD && !isCollapsed">
|
||||
<div class="mb-4 relative">
|
||||
<div :class="separatorClasses" />
|
||||
<!-- Progress bar for executing state -->
|
||||
@@ -93,24 +96,29 @@
|
||||
>
|
||||
<!-- Slots only rendered at full detail -->
|
||||
<NodeSlots
|
||||
v-memo="[nodeData.inputs?.length, nodeData.outputs?.length]"
|
||||
v-if="shouldRenderSlots"
|
||||
v-memo="[nodeData.inputs?.length, nodeData.outputs?.length, lodLevel]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:lod-level="lodLevel"
|
||||
@slot-click="handleSlotClick"
|
||||
/>
|
||||
|
||||
<!-- Widgets rendered at reduced+ detail -->
|
||||
<NodeWidgets
|
||||
v-if="nodeData.widgets?.length"
|
||||
v-memo="[nodeData.widgets?.length]"
|
||||
v-if="shouldShowWidgets"
|
||||
v-memo="[nodeData.widgets?.length, lodLevel]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:lod-level="lodLevel"
|
||||
/>
|
||||
|
||||
<!-- Custom content at reduced+ detail -->
|
||||
<NodeContent
|
||||
v-if="hasCustomContent"
|
||||
v-if="shouldShowContent"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:lod-level="lodLevel"
|
||||
:image-urls="nodeImageUrls"
|
||||
/>
|
||||
<!-- Live preview image -->
|
||||
@@ -131,21 +139,29 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { computed, inject, onErrorCaptured, onMounted, provide, ref } from 'vue'
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
onErrorCaptured,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
toRef,
|
||||
watch
|
||||
} from 'vue'
|
||||
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { SelectedNodeIdsKey } from '@/renderer/core/canvas/injectionKeys'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
|
||||
import { useNodeEventHandlers } from '@/renderer/extensions/vueNodes/composables/useNodeEventHandlers'
|
||||
import { useNodePointerInteractions } from '@/renderer/extensions/vueNodes/composables/useNodePointerInteractions'
|
||||
import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking'
|
||||
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
|
||||
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
|
||||
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
|
||||
import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
import { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'
|
||||
import type { ExecutedWsMessage } from '@/schemas/apiSchema'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
@@ -155,6 +171,7 @@ import {
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { useVueElementTracking } from '../composables/useVueNodeResizeTracking'
|
||||
import NodeContent from './NodeContent.vue'
|
||||
import NodeHeader from './NodeHeader.vue'
|
||||
import NodeSlots from './NodeSlots.vue'
|
||||
@@ -164,6 +181,8 @@ import SlotConnectionDot from './SlotConnectionDot.vue'
|
||||
// Extended props for main node component
|
||||
interface LGraphNodeProps {
|
||||
nodeData: VueNodeData
|
||||
position?: { x: number; y: number }
|
||||
size?: { width: number; height: number }
|
||||
readonly?: boolean
|
||||
error?: string | null
|
||||
zoomLevel?: number
|
||||
@@ -171,16 +190,39 @@ interface LGraphNodeProps {
|
||||
|
||||
const {
|
||||
nodeData,
|
||||
position,
|
||||
size,
|
||||
error = null,
|
||||
readonly = false
|
||||
readonly = false,
|
||||
zoomLevel = 1
|
||||
} = defineProps<LGraphNodeProps>()
|
||||
|
||||
const { handleNodeCollapse, handleNodeTitleUpdate, handleNodeSelect } =
|
||||
useNodeEventHandlers()
|
||||
const emit = defineEmits<{
|
||||
'node-click': [
|
||||
event: PointerEvent,
|
||||
nodeData: VueNodeData,
|
||||
wasDragging: boolean
|
||||
]
|
||||
'slot-click': [
|
||||
event: PointerEvent,
|
||||
nodeData: VueNodeData,
|
||||
slotIndex: number,
|
||||
isInput: boolean
|
||||
]
|
||||
dragStart: [event: DragEvent, nodeData: VueNodeData]
|
||||
'update:collapsed': [nodeId: string, collapsed: boolean]
|
||||
'update:title': [nodeId: string, newTitle: string]
|
||||
}>()
|
||||
|
||||
useVueElementTracking(() => nodeData.id, 'node')
|
||||
useVueElementTracking(nodeData.id, 'node')
|
||||
|
||||
const { selectedNodeIds } = storeToRefs(useCanvasStore())
|
||||
// Inject selection state from parent
|
||||
const selectedNodeIds = inject(SelectedNodeIdsKey)
|
||||
if (!selectedNodeIds) {
|
||||
throw new Error(
|
||||
'SelectedNodeIds not provided - LGraphNode must be used within a component that provides selection state'
|
||||
)
|
||||
}
|
||||
|
||||
// Inject transform state for coordinate conversion
|
||||
const transformState = inject(TransformStateKey)
|
||||
@@ -191,7 +233,7 @@ const isSelected = computed(() => {
|
||||
})
|
||||
|
||||
// Use execution state composable
|
||||
const { executing, progress } = useNodeExecutionState(() => nodeData.id)
|
||||
const { executing, progress } = useNodeExecutionState(nodeData.id)
|
||||
|
||||
// Direct access to execution store for error state
|
||||
const executionStore = useExecutionStore()
|
||||
@@ -207,7 +249,25 @@ const hasAnyError = computed(
|
||||
const bypassed = computed((): boolean => nodeData.mode === 4)
|
||||
|
||||
// Use canvas interactions for proper wheel event handling and pointer event capture control
|
||||
const { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions()
|
||||
const {
|
||||
handleWheel,
|
||||
handlePointer,
|
||||
forwardEventToCanvas,
|
||||
shouldHandleNodePointerEvents
|
||||
} = useCanvasInteractions()
|
||||
|
||||
// LOD (Level of Detail) system based on zoom level
|
||||
const zoomRef = toRef(() => zoomLevel)
|
||||
const {
|
||||
lodLevel,
|
||||
shouldRenderWidgets,
|
||||
shouldRenderSlots,
|
||||
shouldRenderContent,
|
||||
lodCssClass
|
||||
} = useLOD(zoomRef)
|
||||
|
||||
// Computed properties for template usage
|
||||
const isMinimalLOD = computed(() => lodLevel.value === LODLevel.MINIMAL)
|
||||
|
||||
// Error boundary implementation
|
||||
const renderError = ref<string | null>(null)
|
||||
@@ -220,28 +280,48 @@ onErrorCaptured((error) => {
|
||||
})
|
||||
|
||||
// Use layout system for node position and dragging
|
||||
const { position, size, zIndex, resize } = useNodeLayout(() => nodeData.id)
|
||||
const {
|
||||
handlePointerDown,
|
||||
handlePointerUp,
|
||||
handlePointerMove,
|
||||
isDragging,
|
||||
dragStyle
|
||||
} = useNodePointerInteractions(() => nodeData, handleNodeSelect)
|
||||
position: layoutPosition,
|
||||
zIndex,
|
||||
startDrag,
|
||||
handleDrag: handleLayoutDrag,
|
||||
endDrag,
|
||||
resize
|
||||
} = useNodeLayout(nodeData.id)
|
||||
|
||||
onMounted(() => {
|
||||
if (size.value && transformState?.camera) {
|
||||
if (size && transformState?.camera) {
|
||||
const scale = transformState.camera.z
|
||||
const screenSize = {
|
||||
width: size.value.width * scale,
|
||||
height: size.value.height * scale
|
||||
width: size.width * scale,
|
||||
height: size.height * scale
|
||||
}
|
||||
resize(screenSize)
|
||||
}
|
||||
})
|
||||
|
||||
// Drag state for styling
|
||||
const isDragging = ref(false)
|
||||
const dragStyle = computed(() => ({
|
||||
cursor: isDragging.value ? 'grabbing' : 'grab'
|
||||
}))
|
||||
const lastY = ref(0)
|
||||
const lastX = ref(0)
|
||||
// Treat tiny pointer jitter as a click, not a drag
|
||||
const DRAG_THRESHOLD_PX = 4
|
||||
|
||||
// Track collapsed state
|
||||
const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)
|
||||
const isCollapsed = ref(nodeData.flags?.collapsed ?? false)
|
||||
|
||||
// Watch for external changes to the collapsed state
|
||||
watch(
|
||||
() => nodeData.flags?.collapsed,
|
||||
(newCollapsed: boolean | undefined) => {
|
||||
if (newCollapsed !== undefined && newCollapsed !== isCollapsed.value) {
|
||||
isCollapsed.value = newCollapsed
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Check if node has custom content (like image outputs)
|
||||
const hasCustomContent = computed(() => {
|
||||
@@ -251,16 +331,26 @@ const hasCustomContent = computed(() => {
|
||||
|
||||
// Computed classes and conditions for better reusability
|
||||
const separatorClasses =
|
||||
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full lod-toggle'
|
||||
'bg-sand-100 dark-theme:bg-charcoal-600 h-px mx-0 w-full'
|
||||
const progressClasses = 'h-2 bg-primary-500 transition-all duration-300'
|
||||
|
||||
const { latestPreviewUrl, shouldShowPreviewImg } = useNodePreviewState(
|
||||
() => nodeData.id,
|
||||
nodeData.id,
|
||||
{
|
||||
isMinimalLOD,
|
||||
isCollapsed
|
||||
}
|
||||
)
|
||||
|
||||
// Common condition computations to avoid repetition
|
||||
const shouldShowWidgets = computed(
|
||||
() => shouldRenderWidgets.value && nodeData.widgets?.length
|
||||
)
|
||||
|
||||
const shouldShowContent = computed(
|
||||
() => shouldRenderContent.value && hasCustomContent.value
|
||||
)
|
||||
|
||||
const borderClass = computed(() => {
|
||||
if (hasAnyError.value) {
|
||||
return 'border-error'
|
||||
@@ -285,12 +375,86 @@ const outlineClass = computed(() => {
|
||||
})
|
||||
|
||||
// Event handlers
|
||||
const handleCollapse = () => {
|
||||
handleNodeCollapse(nodeData.id, !isCollapsed.value)
|
||||
const handlePointerDown = (event: PointerEvent) => {
|
||||
if (!nodeData) {
|
||||
console.warn('LGraphNode: nodeData is null/undefined in handlePointerDown')
|
||||
return
|
||||
}
|
||||
|
||||
// Don't handle pointer events when canvas is in panning mode - forward to canvas instead
|
||||
if (!shouldHandleNodePointerEvents.value) {
|
||||
forwardEventToCanvas(event)
|
||||
return
|
||||
}
|
||||
|
||||
// Start drag using layout system
|
||||
isDragging.value = true
|
||||
|
||||
// Set Vue node dragging state for selection toolbox
|
||||
layoutStore.isDraggingVueNodes.value = true
|
||||
|
||||
startDrag(event)
|
||||
lastY.value = event.clientY
|
||||
lastX.value = event.clientX
|
||||
}
|
||||
|
||||
const handleHeaderTitleUpdate = (newTitle: string) => {
|
||||
handleNodeTitleUpdate(nodeData.id, newTitle)
|
||||
const handlePointerMove = (event: PointerEvent) => {
|
||||
// Check if this should be forwarded to canvas (e.g., space panning, middle mouse)
|
||||
handlePointer(event)
|
||||
|
||||
if (isDragging.value) {
|
||||
void handleLayoutDrag(event)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePointerUp = (event: PointerEvent) => {
|
||||
if (isDragging.value) {
|
||||
isDragging.value = false
|
||||
void endDrag(event)
|
||||
|
||||
// Clear Vue node dragging state for selection toolbox
|
||||
layoutStore.isDraggingVueNodes.value = false
|
||||
}
|
||||
|
||||
// Don't emit node-click when canvas is in panning mode - forward to canvas instead
|
||||
if (!shouldHandleNodePointerEvents.value) {
|
||||
forwardEventToCanvas(event)
|
||||
return
|
||||
}
|
||||
|
||||
// Emit node-click for selection handling in GraphCanvas
|
||||
const dx = event.clientX - lastX.value
|
||||
const dy = event.clientY - lastY.value
|
||||
const wasDragging = Math.hypot(dx, dy) > DRAG_THRESHOLD_PX
|
||||
emit('node-click', event, nodeData, wasDragging)
|
||||
}
|
||||
|
||||
const handleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
// Emit event so parent can sync with LiteGraph if needed
|
||||
emit('update:collapsed', nodeData.id, isCollapsed.value)
|
||||
}
|
||||
|
||||
const handleSlotClick = (
|
||||
event: PointerEvent,
|
||||
slotIndex: number,
|
||||
isInput: boolean
|
||||
) => {
|
||||
if (!nodeData) {
|
||||
console.warn('LGraphNode: nodeData is null/undefined in handleSlotClick')
|
||||
return
|
||||
}
|
||||
|
||||
// Don't handle slot clicks when canvas is in panning mode
|
||||
if (!shouldHandleNodePointerEvents.value) {
|
||||
return
|
||||
}
|
||||
|
||||
emit('slot-click', event, nodeData, slotIndex, isInput)
|
||||
}
|
||||
|
||||
const handleTitleUpdate = (newTitle: string) => {
|
||||
emit('update:title', nodeData.id, newTitle)
|
||||
}
|
||||
|
||||
const handleEnterSubgraph = () => {
|
||||
@@ -320,17 +484,15 @@ const handleEnterSubgraph = () => {
|
||||
|
||||
const nodeOutputs = useNodeOutputStore()
|
||||
|
||||
const nodeOutputLocatorId = computed(() =>
|
||||
nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id
|
||||
)
|
||||
const nodeImageUrls = computed(() => {
|
||||
const newOutputs = nodeOutputs.nodeOutputs[nodeOutputLocatorId.value]
|
||||
const nodeImageUrls = ref<string[]>([])
|
||||
const onNodeOutputsUpdate = (newOutputs: ExecutedWsMessage['output']) => {
|
||||
const locatorId = getLocatorIdFromNodeData(nodeData)
|
||||
|
||||
// Use root graph for getNodeByLocatorId since it needs to traverse from root
|
||||
const rootGraph = app.graph?.rootGraph || app.graph
|
||||
if (!rootGraph) {
|
||||
return []
|
||||
nodeImageUrls.value = []
|
||||
return
|
||||
}
|
||||
|
||||
const node = getNodeByLocatorId(rootGraph, locatorId)
|
||||
@@ -338,13 +500,30 @@ const nodeImageUrls = computed(() => {
|
||||
if (node && newOutputs?.images?.length) {
|
||||
const urls = nodeOutputs.getNodeImageUrls(node)
|
||||
if (urls) {
|
||||
return urls
|
||||
nodeImageUrls.value = urls
|
||||
}
|
||||
} else {
|
||||
// Clear URLs if no outputs or no images
|
||||
nodeImageUrls.value = []
|
||||
}
|
||||
// Clear URLs if no outputs or no images
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
const nodeContainerRef = ref()
|
||||
const nodeOutputLocatorId = computed(() =>
|
||||
nodeData.subgraphId ? `${nodeData.subgraphId}:${nodeData.id}` : nodeData.id
|
||||
)
|
||||
|
||||
watch(
|
||||
() => nodeOutputs.nodeOutputs[nodeOutputLocatorId.value],
|
||||
(newOutputs) => {
|
||||
onNodeOutputsUpdate(newOutputs)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Template ref for tooltip positioning
|
||||
const nodeContainerRef = ref<HTMLElement>()
|
||||
|
||||
// Provide nodeImageUrls and tooltip container to child components
|
||||
provide('nodeImageUrls', nodeImageUrls)
|
||||
provide('tooltipContainer', nodeContainerRef)
|
||||
</script>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div class="lod-fallback absolute inset-0 w-full h-full bg-zinc-800"></div>
|
||||
</template>
|
||||
@@ -21,6 +21,7 @@ import { computed, onErrorCaptured, ref } from 'vue'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
|
||||
import ImagePreview from './ImagePreview.vue'
|
||||
|
||||
@@ -28,6 +29,7 @@ interface NodeContentProps {
|
||||
node?: LGraphNode // For backwards compatibility
|
||||
nodeData?: VueNodeData // New clean data structure
|
||||
readonly?: boolean
|
||||
lodLevel?: LODLevel
|
||||
imageUrls?: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -4,44 +4,41 @@
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="lg-node-header p-4 rounded-t-2xl cursor-move"
|
||||
class="lg-node-header flex items-center justify-between p-4 rounded-t-2xl cursor-move w-full"
|
||||
:data-testid="`node-header-${nodeData?.id || ''}`"
|
||||
@dblclick="handleDoubleClick"
|
||||
>
|
||||
<div class="flex items-center justify-between relative">
|
||||
<!-- Collapse/Expand Button -->
|
||||
<button
|
||||
v-show="!readonly"
|
||||
class="bg-transparent border-transparent flex items-center lod-toggle"
|
||||
data-testid="node-collapse-button"
|
||||
@click.stop="handleCollapse"
|
||||
@dblclick.stop
|
||||
>
|
||||
<i
|
||||
:class="collapsed ? 'pi pi-chevron-right' : 'pi pi-chevron-down'"
|
||||
class="text-xs leading-none relative top-px text-stone-200 dark-theme:text-slate-300"
|
||||
></i>
|
||||
</button>
|
||||
<!-- Collapse/Expand Button -->
|
||||
<button
|
||||
v-show="!readonly"
|
||||
class="bg-transparent border-transparent flex items-center"
|
||||
data-testid="node-collapse-button"
|
||||
@click.stop="handleCollapse"
|
||||
@dblclick.stop
|
||||
>
|
||||
<i
|
||||
:class="collapsed ? 'pi pi-chevron-right' : 'pi pi-chevron-down'"
|
||||
class="text-xs leading-none relative top-px text-stone-200 dark-theme:text-slate-300"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<!-- Node Title -->
|
||||
<div
|
||||
v-tooltip.top="tooltipConfig"
|
||||
class="text-sm font-bold truncate flex-1 lod-toggle"
|
||||
data-testid="node-title"
|
||||
>
|
||||
<EditableText
|
||||
:model-value="displayTitle"
|
||||
:is-editing="isEditing"
|
||||
:input-attrs="{ 'data-testid': 'node-title-input' }"
|
||||
@edit="handleTitleEdit"
|
||||
@cancel="handleTitleCancel"
|
||||
/>
|
||||
</div>
|
||||
<LODFallback />
|
||||
<!-- Node Title -->
|
||||
<div
|
||||
v-tooltip.top="tooltipConfig"
|
||||
class="text-sm font-bold truncate flex-1"
|
||||
data-testid="node-title"
|
||||
>
|
||||
<EditableText
|
||||
:model-value="displayTitle"
|
||||
:is-editing="isEditing"
|
||||
:input-attrs="{ 'data-testid': 'node-title-input' }"
|
||||
@edit="handleTitleEdit"
|
||||
@cancel="handleTitleCancel"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Title Buttons -->
|
||||
<div v-if="!readonly" class="flex items-center lod-toggle">
|
||||
<div v-if="!readonly" class="flex items-center">
|
||||
<IconButton
|
||||
v-if="isSubgraphNode"
|
||||
size="sm"
|
||||
@@ -66,17 +63,17 @@ import EditableText from '@/components/common/EditableText.vue'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
|
||||
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
import { app } from '@/scripts/app'
|
||||
import {
|
||||
getLocatorIdFromNodeData,
|
||||
getNodeByLocatorId
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
|
||||
import LODFallback from './LODFallback.vue'
|
||||
|
||||
interface NodeHeaderProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
lodLevel?: LODLevel
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import { computed, onErrorCaptured, ref } from 'vue'
|
||||
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
import { isSlotObject } from '@/utils/typeGuardUtil'
|
||||
|
||||
import InputSlot from './InputSlot.vue'
|
||||
@@ -43,6 +44,7 @@ import OutputSlot from './OutputSlot.vue'
|
||||
interface NodeSlotsProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
lodLevel?: LODLevel
|
||||
}
|
||||
|
||||
const { nodeData = null, readonly } = defineProps<NodeSlotsProps>()
|
||||
|
||||
@@ -12,17 +12,16 @@
|
||||
: 'pointer-events-none'
|
||||
)
|
||||
"
|
||||
@pointerdown.stop="handleWidgetPointerEvent"
|
||||
@pointermove.stop="handleWidgetPointerEvent"
|
||||
@pointerup.stop="handleWidgetPointerEvent"
|
||||
@pointerdown="handleWidgetPointerEvent"
|
||||
@pointermove="handleWidgetPointerEvent"
|
||||
@pointerup="handleWidgetPointerEvent"
|
||||
>
|
||||
<div
|
||||
v-for="(widget, index) in processedWidgets"
|
||||
:key="`widget-${index}-${widget.name}`"
|
||||
class="lg-widget-container flex items-center group"
|
||||
class="lg-widget-container relative flex items-center group"
|
||||
>
|
||||
<!-- Widget Input Slot Dot -->
|
||||
|
||||
<div
|
||||
class="opacity-0 group-hover:opacity-100 transition-opacity duration-150"
|
||||
>
|
||||
@@ -62,10 +61,12 @@ import type {
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
|
||||
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
|
||||
import { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
|
||||
// Import widget components directly
|
||||
import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue'
|
||||
import {
|
||||
getComponent,
|
||||
isEssential,
|
||||
shouldRenderAsVue
|
||||
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
|
||||
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
|
||||
@@ -76,9 +77,10 @@ import InputSlot from './InputSlot.vue'
|
||||
interface NodeWidgetsProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
lodLevel?: LODLevel
|
||||
}
|
||||
|
||||
const { nodeData, readonly } = defineProps<NodeWidgetsProps>()
|
||||
const { nodeData, readonly, lodLevel } = defineProps<NodeWidgetsProps>()
|
||||
|
||||
const { shouldHandleNodePointerEvents, forwardEventToCanvas } =
|
||||
useCanvasInteractions()
|
||||
@@ -123,12 +125,18 @@ const processedWidgets = computed((): ProcessedWidget[] => {
|
||||
const widgets = nodeData.widgets as SafeWidgetData[]
|
||||
const result: ProcessedWidget[] = []
|
||||
|
||||
if (lodLevel === LODLevel.MINIMAL) {
|
||||
return []
|
||||
}
|
||||
|
||||
for (const widget of widgets) {
|
||||
if (widget.options?.hidden) continue
|
||||
if (widget.options?.canvasOnly) continue
|
||||
if (!widget.type) continue
|
||||
if (!shouldRenderAsVue(widget)) continue
|
||||
|
||||
if (lodLevel === LODLevel.REDUCED && !isEssential(widget.type)) continue
|
||||
|
||||
const vueComponent = getComponent(widget.type) || WidgetInputText
|
||||
|
||||
const simplified: SimplifiedWidget = {
|
||||
|
||||