mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-10 09:07:54 +00:00
Compare commits
49 Commits
v1.16.3
...
export-vue
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a675de88 | ||
|
|
e45f5bdebb | ||
|
|
c270e7734a | ||
|
|
8d7a21e008 | ||
|
|
29e63baca6 | ||
|
|
b22713daf0 | ||
|
|
c8b8953e0a | ||
|
|
731ce8599d | ||
|
|
ec8e55c1c1 | ||
|
|
04d38f2538 | ||
|
|
1c41db75f8 | ||
|
|
c7a7397000 | ||
|
|
e660e1d678 | ||
|
|
fb19752389 | ||
|
|
d098d6ae4e | ||
|
|
e4a5355f58 | ||
|
|
42c004d41d | ||
|
|
009c389607 | ||
|
|
b449dbd26b | ||
|
|
67835edfca | ||
|
|
60c0ce228a | ||
|
|
1990f25638 | ||
|
|
30c473db77 | ||
|
|
2371288fed | ||
|
|
2337fe6f8e | ||
|
|
25e6386b2a | ||
|
|
a03841cb1a | ||
|
|
dc5d7ea1be | ||
|
|
59e20964a0 | ||
|
|
8f00d8ca6a | ||
|
|
05e0036898 | ||
|
|
9e7690405a | ||
|
|
d687ea2cde | ||
|
|
c801a0c854 | ||
|
|
615c183059 | ||
|
|
27c8389b9f | ||
|
|
261f671ef0 | ||
|
|
22ae30132c | ||
|
|
7d3bf372b0 | ||
|
|
cd35373c25 | ||
|
|
a500a96c4a | ||
|
|
dc9ea44f3a | ||
|
|
2dc33b1eb9 | ||
|
|
ed8f9a5a4f | ||
|
|
6e72e1924e | ||
|
|
f7854a4e0b | ||
|
|
05023b7889 | ||
|
|
609496957b | ||
|
|
a879f413bb |
2
.github/workflows/test-ui.yaml
vendored
2
.github/workflows/test-ui.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
repository: 'Comfy-Org/ComfyUI_devtools'
|
||||
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
||||
ref: '080e6d4af809a46852d1c4b7ed85f06e8a3a72be'
|
||||
ref: '49c8220be49120dbaff85f32813d854d6dff2d05'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
53
browser_tests/assets/default_input.json
Normal file
53
browser_tests/assets/default_input.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"id": "9bcb9451-8319-492a-88d4-fb711d8c3d25",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "DevToolsNodeWithDefaultInput",
|
||||
"pos": [
|
||||
8.39722728729248,
|
||||
29.727279663085938
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "float_input",
|
||||
"shape": 7,
|
||||
"type": "FLOAT",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsNodeWithDefaultInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 2.1600300525920346,
|
||||
"offset": [
|
||||
63.071794466403446,
|
||||
75.18055335968394
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,11 @@ test.describe('Optional input', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('force_input.png')
|
||||
})
|
||||
|
||||
test('Default input', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('default_input')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default_input.png')
|
||||
})
|
||||
|
||||
test('Only optional inputs', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('only_optional_inputs')
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(1)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 103 KiB |
@@ -43,4 +43,15 @@ test.describe('Primitive Node', () => {
|
||||
'static_primitive_connected.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Report missing nodes when connect to missing node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow(
|
||||
'primitive/primitive_node_connect_missing_node'
|
||||
)
|
||||
// Wait for the element with the .comfy-missing-nodes selector to be visible
|
||||
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
|
||||
await expect(missingNodesWarning).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -192,3 +192,19 @@ test.describe('Load audio widget', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('load_audio_widget.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Unserialized widgets', () => {
|
||||
test('Unserialized widgets values do not mark graph as modified', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Add workflow w/ LoadImage node, which contains file upload and image preview widgets (not serialized)
|
||||
await comfyPage.loadWorkflow('widgets/load_image_widget')
|
||||
|
||||
// Move mouse and click to trigger the `graphEqual` check in `changeTracker.ts`
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.page.mouse.click(10, 10)
|
||||
|
||||
// Expect the graph to not be modified
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import pluginJs from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
@@ -20,21 +21,26 @@ export default [
|
||||
globals: {
|
||||
...globals.browser,
|
||||
__COMFYUI_FRONTEND_VERSION__: 'readonly'
|
||||
},
|
||||
parser: tseslint.parser,
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module',
|
||||
extraFileExtensions: ['.vue']
|
||||
}
|
||||
}
|
||||
},
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
...pluginVue.configs['flat/recommended'],
|
||||
eslintPluginPrettierRecommended,
|
||||
{
|
||||
files: ['src/**/*.vue'],
|
||||
languageOptions: { parserOptions: { parser: tseslint.parser } }
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/prefer-as-const': 'off'
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tseslint.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -42,10 +48,12 @@ export default [
|
||||
'unused-imports': unusedImports
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/prefer-as-const': 'off',
|
||||
'unused-imports/no-unused-imports': 'error'
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'vue/no-v-html': 'off'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
4
global.d.ts
vendored
4
global.d.ts
vendored
@@ -15,3 +15,7 @@ interface Navigator {
|
||||
visible: boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface Window {
|
||||
Vue: typeof import('vue')
|
||||
}
|
||||
|
||||
125
package-lock.json
generated
125
package-lock.json
generated
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.16.3",
|
||||
"version": "1.16.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.16.3",
|
||||
"version": "1.16.7",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.31",
|
||||
"@comfyorg/litegraph": "^0.13.0",
|
||||
"@comfyorg/litegraph": "^0.13.1",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
@@ -61,6 +61,8 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chalk": "^5.3.0",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -77,7 +79,7 @@
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.17",
|
||||
"vite": "^5.4.18",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.0.0",
|
||||
"vue-tsc": "^2.1.10",
|
||||
@@ -478,9 +480,9 @@
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.13.0.tgz",
|
||||
"integrity": "sha512-n8YLtX3j86AG8VkcmBGi0R/LB20fs+LkxUc+CQX+M3dt7ZwBo8LCJ4y5CugXgs5Wlm7tUvS+3pPuTcdJoI55BA==",
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.13.1.tgz",
|
||||
"integrity": "sha512-tAlAdeBpvBch5Wj7MghoJvJkGwdeHG3825LqDMkKynYVXfiMcrTtM+LWRkH58JlbEEocD+1fdWKLMZ+lJc9m0A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -1791,6 +1793,19 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.2.tgz",
|
||||
"integrity": "sha512-25L86MyPvnlQoX2MTIV2OiUcb6vJ6aRbFa9pbwByn95INKD5mFH2smgjDhq+fwJoqAgvgbdJLj6Tz7V9X5CFAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.45.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.1.tgz",
|
||||
@@ -5620,6 +5635,50 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.2.tgz",
|
||||
"integrity": "sha512-Epgp/EofAUeEpIdZkW60MHKvPyru1ruQJxPL+WIycnaPApuseK0Zpkrh/FwL9oIpQvIhJwV7ptOy0DWUjTlCiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.2.6",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
|
||||
"integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint-plugin-prettier"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/eslint": ">=8.0.0",
|
||||
"eslint": ">=8.0.0",
|
||||
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
|
||||
"prettier": ">=3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/eslint": {
|
||||
"optional": true
|
||||
},
|
||||
"eslint-config-prettier": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unused-imports": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
|
||||
@@ -6078,6 +6137,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
|
||||
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
@@ -10275,6 +10341,19 @@
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier-linter-helpers": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
|
||||
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-diff": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-format": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
|
||||
@@ -11663,6 +11742,23 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.3",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
|
||||
"integrity": "sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
|
||||
@@ -11954,10 +12050,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"dev": true
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.16.2",
|
||||
@@ -12559,9 +12656,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.17",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.17.tgz",
|
||||
"integrity": "sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==",
|
||||
"version": "5.4.18",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
|
||||
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.16.3",
|
||||
"version": "1.16.7",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -44,6 +44,8 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chalk": "^5.3.0",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -60,7 +62,7 @@
|
||||
"typescript-eslint": "^8.0.0",
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.17",
|
||||
"vite": "^5.4.18",
|
||||
"vite-plugin-dts": "^4.3.0",
|
||||
"vitest": "^2.0.0",
|
||||
"vue-tsc": "^2.1.10",
|
||||
@@ -71,7 +73,7 @@
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.4.31",
|
||||
"@comfyorg/litegraph": "^0.13.0",
|
||||
"@comfyorg/litegraph": "^0.13.1",
|
||||
"@primevue/forms": "^4.2.5",
|
||||
"@primevue/themes": "^4.2.5",
|
||||
"@sentry/vue": "^8.48.0",
|
||||
|
||||
@@ -398,6 +398,7 @@ button.comfy-queue-btn {
|
||||
.graphdialog {
|
||||
min-height: 1em;
|
||||
background-color: var(--comfy-menu-bg);
|
||||
z-index: 41; /* z-index is set to 41 here in order to appear over selection-overlay-container which should have a z-index of 40 */
|
||||
}
|
||||
|
||||
.graphdialog .name {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<Splitter
|
||||
:key="activeSidebarTabId ?? undefined"
|
||||
class="splitter-overlay-root splitter-overlay"
|
||||
:pt:gutter="sidebarPanelVisible ? '' : 'hidden'"
|
||||
:key="activeSidebarTabId ?? undefined"
|
||||
:stateKey="activeSidebarTabId ?? undefined"
|
||||
stateStorage="local"
|
||||
:state-key="activeSidebarTabId ?? undefined"
|
||||
state-storage="local"
|
||||
>
|
||||
<SplitterPanel
|
||||
class="side-bar-panel"
|
||||
:minSize="10"
|
||||
:size="20"
|
||||
v-show="sidebarPanelVisible"
|
||||
v-if="sidebarLocation === 'left'"
|
||||
class="side-bar-panel"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
>
|
||||
<slot name="side-bar-panel"></slot>
|
||||
<slot name="side-bar-panel" />
|
||||
</SplitterPanel>
|
||||
|
||||
<SplitterPanel :size="100">
|
||||
@@ -21,26 +21,26 @@
|
||||
class="splitter-overlay max-w-full"
|
||||
layout="vertical"
|
||||
:pt:gutter="bottomPanelVisible ? '' : 'hidden'"
|
||||
stateKey="bottom-panel-splitter"
|
||||
stateStorage="local"
|
||||
state-key="bottom-panel-splitter"
|
||||
state-storage="local"
|
||||
>
|
||||
<SplitterPanel class="graph-canvas-panel relative">
|
||||
<slot name="graph-canvas-panel"></slot>
|
||||
<slot name="graph-canvas-panel" />
|
||||
</SplitterPanel>
|
||||
<SplitterPanel class="bottom-panel" v-show="bottomPanelVisible">
|
||||
<slot name="bottom-panel"></slot>
|
||||
<SplitterPanel v-show="bottomPanelVisible" class="bottom-panel">
|
||||
<slot name="bottom-panel" />
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
</SplitterPanel>
|
||||
|
||||
<SplitterPanel
|
||||
class="side-bar-panel"
|
||||
:minSize="10"
|
||||
:size="20"
|
||||
v-show="sidebarPanelVisible"
|
||||
v-if="sidebarLocation === 'right'"
|
||||
class="side-bar-panel"
|
||||
:min-size="10"
|
||||
:size="20"
|
||||
>
|
||||
<slot name="side-bar-panel"></slot>
|
||||
<slot name="side-bar-panel" />
|
||||
</SplitterPanel>
|
||||
</Splitter>
|
||||
</template>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
:style="positionCSS"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
icon="pi pi-bars"
|
||||
severity="secondary"
|
||||
text
|
||||
size="large"
|
||||
v-tooltip="{ value: $t('menu.showMenu'), showDelay: 300 }"
|
||||
:aria-label="$t('menu.showMenu')"
|
||||
aria-live="assertive"
|
||||
@click="exitFocusMode"
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div
|
||||
class="batch-count"
|
||||
v-tooltip.bottom="{
|
||||
value: $t('menu.batchCount'),
|
||||
showDelay: 600
|
||||
}"
|
||||
class="batch-count"
|
||||
:aria-label="$t('menu.batchCount')"
|
||||
>
|
||||
<InputNumber
|
||||
class="w-14"
|
||||
v-model="batchCount"
|
||||
class="w-14"
|
||||
:min="minQueueCount"
|
||||
:max="maxQueueCount"
|
||||
fluid
|
||||
showButtons
|
||||
show-buttons
|
||||
:pt="{
|
||||
incrementButton: {
|
||||
class: 'w-6',
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
:style="style"
|
||||
:class="{ 'is-dragging': isDragging, 'is-docked': isDocked }"
|
||||
>
|
||||
<div class="actionbar-content flex items-center select-none" ref="panelRef">
|
||||
<span class="drag-handle cursor-move mr-2 p-0!" ref="dragHandleRef">
|
||||
</span>
|
||||
<div ref="panelRef" class="actionbar-content flex items-center select-none">
|
||||
<span ref="dragHandleRef" class="drag-handle cursor-move mr-2 p-0!" />
|
||||
<ComfyQueueButton />
|
||||
</div>
|
||||
</Panel>
|
||||
@@ -89,9 +88,9 @@ const setInitialPosition = () => {
|
||||
}
|
||||
}
|
||||
onMounted(setInitialPosition)
|
||||
watch(visible, (newVisible) => {
|
||||
watch(visible, async (newVisible) => {
|
||||
if (newVisible) {
|
||||
nextTick(setInitialPosition)
|
||||
await nextTick(setInitialPosition)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<template>
|
||||
<div class="queue-button-group flex">
|
||||
<SplitButton
|
||||
class="comfyui-queue-button"
|
||||
:label="activeQueueModeMenuItem.label"
|
||||
severity="primary"
|
||||
size="small"
|
||||
@click="queuePrompt"
|
||||
:model="queueModeMenuItems"
|
||||
data-testid="queue-button"
|
||||
v-tooltip.bottom="{
|
||||
value: workspaceStore.shiftDown
|
||||
? $t('menu.runWorkflowFront')
|
||||
: $t('menu.runWorkflow'),
|
||||
showDelay: 600
|
||||
}"
|
||||
class="comfyui-queue-button"
|
||||
:label="activeQueueModeMenuItem.label"
|
||||
severity="primary"
|
||||
size="small"
|
||||
:model="queueModeMenuItems"
|
||||
data-testid="queue-button"
|
||||
@click="queuePrompt"
|
||||
>
|
||||
<template #icon>
|
||||
<i-lucide:list-start v-if="workspaceStore.shiftDown" />
|
||||
@@ -23,15 +23,15 @@
|
||||
</template>
|
||||
<template #item="{ item }">
|
||||
<Button
|
||||
v-tooltip="{
|
||||
value: item.tooltip,
|
||||
showDelay: 600
|
||||
}"
|
||||
:label="String(item.label)"
|
||||
:icon="item.icon"
|
||||
:severity="item.key === queueMode ? 'primary' : 'secondary'"
|
||||
size="small"
|
||||
text
|
||||
v-tooltip="{
|
||||
value: item.tooltip,
|
||||
showDelay: 600
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
</SplitButton>
|
||||
@@ -48,8 +48,7 @@
|
||||
text
|
||||
:aria-label="$t('menu.interrupt')"
|
||||
@click="() => commandStore.execute('Comfy.Interrupt')"
|
||||
>
|
||||
</Button>
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.bottom="{
|
||||
value: $t('sideToolbar.queueTab.clearPendingTasks'),
|
||||
@@ -135,12 +134,12 @@ const hasPendingTasks = computed(
|
||||
)
|
||||
|
||||
const commandStore = useCommandStore()
|
||||
const queuePrompt = (e: Event) => {
|
||||
const queuePrompt = async (e: Event) => {
|
||||
const commandId =
|
||||
'shiftKey' in e && e.shiftKey
|
||||
? 'Comfy.QueuePromptFront'
|
||||
: 'Comfy.QueuePrompt'
|
||||
commandStore.execute(commandId)
|
||||
await commandStore.execute(commandId)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<Tabs v-model:value="bottomPanelStore.activeBottomPanelTabId">
|
||||
<TabList pt:tabList="border-none">
|
||||
<TabList pt:tab-list="border-none">
|
||||
<div class="w-full flex justify-between">
|
||||
<div class="tabs-container">
|
||||
<Tab
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="relative overflow-hidden h-full w-full bg-black" ref="rootEl">
|
||||
<div ref="rootEl" class="relative overflow-hidden h-full w-full bg-black">
|
||||
<div class="p-terminal rounded-none h-full w-full p-2">
|
||||
<div class="h-full terminal-host" ref="terminalEl"></div>
|
||||
<div ref="terminalEl" class="h-full terminal-host" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -24,17 +24,17 @@ const terminalCreated = (
|
||||
root,
|
||||
autoRows: true,
|
||||
autoCols: true,
|
||||
onResize: () => {
|
||||
onResize: async () => {
|
||||
// If we aren't visible, don't resize
|
||||
if (!terminal.element?.offsetParent) return
|
||||
|
||||
terminalApi.resize(terminal.cols, terminal.rows)
|
||||
await terminalApi.resize(terminal.cols, terminal.rows)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
offData = terminal.onData(async (message: string) => {
|
||||
terminalApi.write(message)
|
||||
await terminalApi.write(message)
|
||||
})
|
||||
|
||||
offOutput = terminalApi.onOutput((message) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<div class="bg-black h-full w-full">
|
||||
<p v-if="errorMessage" class="p-4 text-center">{{ errorMessage }}</p>
|
||||
<p v-if="errorMessage" class="p-4 text-center">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<ProgressSpinner
|
||||
v-else-if="loading"
|
||||
class="relative inset-0 flex justify-center items-center h-full z-10"
|
||||
@@ -57,7 +59,7 @@ const terminalCreated = (
|
||||
if (!clientId.value) {
|
||||
await until(clientId).not.toBeNull()
|
||||
}
|
||||
api.subscribeLogs(true)
|
||||
await api.subscribeLogs(true)
|
||||
api.addEventListener('logs', logReceived)
|
||||
}
|
||||
|
||||
@@ -76,9 +78,9 @@ const terminalCreated = (
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
onUnmounted(async () => {
|
||||
if (api.clientId) {
|
||||
api.subscribeLogs(false)
|
||||
await api.subscribeLogs(false)
|
||||
}
|
||||
api.removeEventListener('logs', logReceived)
|
||||
})
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<SelectButton
|
||||
v-model="selectedColorOption"
|
||||
:options="colorOptionsWithCustom"
|
||||
optionLabel="name"
|
||||
dataKey="value"
|
||||
option-label="name"
|
||||
data-key="value"
|
||||
:allow-empty="false"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
@@ -18,8 +18,8 @@
|
||||
backgroundColor: slotProps.option.value,
|
||||
borderRadius: '50%'
|
||||
}"
|
||||
></div>
|
||||
<i v-else class="pi pi-palette text-lg"></i>
|
||||
/>
|
||||
<i v-else class="pi pi-palette text-lg" />
|
||||
</template>
|
||||
</SelectButton>
|
||||
<ColorPicker
|
||||
|
||||
@@ -8,22 +8,22 @@
|
||||
<img
|
||||
v-if="contain"
|
||||
:src="src"
|
||||
@error="handleImageError"
|
||||
:data-test="src"
|
||||
class="comfy-image-blur"
|
||||
:style="{ 'background-image': `url(${src})` }"
|
||||
:alt="alt"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
<img
|
||||
:src="src"
|
||||
@error="handleImageError"
|
||||
class="comfy-image-main"
|
||||
:class="classProp"
|
||||
:alt="alt"
|
||||
@error="handleImageError"
|
||||
/>
|
||||
</span>
|
||||
<div v-if="imageBroken" class="broken-image-placeholder">
|
||||
<i class="pi pi-image"></i>
|
||||
<i class="pi pi-image" />
|
||||
<span>{{ $t('g.imageFailedToLoad') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="container"></div>
|
||||
<div ref="container" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
<SelectButton
|
||||
v-model="selectedIcon"
|
||||
:options="iconOptions"
|
||||
optionLabel="name"
|
||||
dataKey="value"
|
||||
option-label="name"
|
||||
data-key="value"
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<i
|
||||
:class="['pi', slotProps.option.value, 'mr-2']"
|
||||
:style="{ color: finalColor }"
|
||||
></i>
|
||||
/>
|
||||
</template>
|
||||
</SelectButton>
|
||||
</div>
|
||||
@@ -30,14 +30,14 @@
|
||||
<Button
|
||||
:label="$t('g.reset')"
|
||||
icon="pi pi-refresh"
|
||||
@click="resetCustomization"
|
||||
class="p-button-text"
|
||||
@click="resetCustomization"
|
||||
/>
|
||||
<Button
|
||||
:label="$t('g.confirm')"
|
||||
icon="pi pi-check"
|
||||
@click="confirmCustomization"
|
||||
autofocus
|
||||
@click="confirmCustomization"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<template v-for="col in deviceColumns" :key="col.field">
|
||||
<div class="font-medium">{{ col.header }}</div>
|
||||
<div class="font-medium">
|
||||
{{ col.header }}
|
||||
</div>
|
||||
<div>
|
||||
{{ formatValue(props.device[col.field], col.field) }}
|
||||
</div>
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
<!-- Avoid double triggering finishEditing event when keyup.enter is triggered -->
|
||||
<InputText
|
||||
v-else
|
||||
ref="inputRef"
|
||||
v-model:modelValue="inputValue"
|
||||
v-focus
|
||||
type="text"
|
||||
size="small"
|
||||
fluid
|
||||
v-model:modelValue="inputValue"
|
||||
ref="inputRef"
|
||||
@keyup.enter="blurInputElement"
|
||||
@click.stop
|
||||
:pt="{
|
||||
root: {
|
||||
onBlur: finishEditing
|
||||
}
|
||||
}"
|
||||
v-focus
|
||||
@keyup.enter="blurInputElement"
|
||||
@click.stop
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -45,10 +45,10 @@ const finishEditing = () => {
|
||||
}
|
||||
watch(
|
||||
() => isEditing,
|
||||
(newVal) => {
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
inputValue.value = modelValue
|
||||
nextTick(() => {
|
||||
await nextTick(() => {
|
||||
if (!inputRef.value) return
|
||||
const fileName = inputValue.value.includes('.')
|
||||
? inputValue.value.split('.').slice(0, -1).join('.')
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<i class="pi pi-check text-green-500" v-if="status === 'completed'" />
|
||||
<i v-if="status === 'completed'" class="pi pi-check text-green-500" />
|
||||
<div class="file-info">
|
||||
<div class="file-details">
|
||||
<span class="file-type" :title="hint">{{ label }}</span>
|
||||
@@ -14,20 +14,20 @@
|
||||
|
||||
<div class="file-action">
|
||||
<Button
|
||||
v-if="status === null || status === 'error'"
|
||||
class="file-action-button"
|
||||
:label="$t('g.download') + ' (' + fileSize + ')'"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!!props.error"
|
||||
@click="triggerDownload"
|
||||
v-if="status === null || status === 'error'"
|
||||
icon="pi pi-download"
|
||||
@click="triggerDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-row items-center gap-2"
|
||||
v-if="status === 'in_progress' || status === 'paused'"
|
||||
class="flex flex-row items-center gap-2"
|
||||
>
|
||||
<!-- Temporary fix for issue when % only comes into view only if the progress bar is large enough
|
||||
https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1731551013385499
|
||||
@@ -39,36 +39,36 @@
|
||||
/>
|
||||
|
||||
<Button
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!!props.error"
|
||||
@click="triggerPauseDownload"
|
||||
v-if="status === 'in_progress'"
|
||||
icon="pi pi-pause-circle"
|
||||
v-tooltip.top="t('electronFileDownload.pause')"
|
||||
/>
|
||||
|
||||
<Button
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!!props.error"
|
||||
@click="triggerResumeDownload"
|
||||
icon="pi pi-pause-circle"
|
||||
@click="triggerPauseDownload"
|
||||
/>
|
||||
|
||||
<Button
|
||||
v-if="status === 'paused'"
|
||||
icon="pi pi-play-circle"
|
||||
v-tooltip.top="t('electronFileDownload.resume')"
|
||||
/>
|
||||
|
||||
<Button
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!!props.error"
|
||||
@click="triggerCancelDownload"
|
||||
icon="pi pi-play-circle"
|
||||
@click="triggerResumeDownload"
|
||||
/>
|
||||
|
||||
<Button
|
||||
v-tooltip.top="t('electronFileDownload.cancel')"
|
||||
class="file-action-button"
|
||||
size="small"
|
||||
outlined
|
||||
:disabled="!!props.error"
|
||||
icon="pi pi-times-circle"
|
||||
severity="danger"
|
||||
v-tooltip.top="t('electronFileDownload.cancel')"
|
||||
@click="triggerCancelDownload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<component v-if="extension.type === 'vue'" :is="extension.component" />
|
||||
<component :is="extension.component" v-if="extension.type === 'vue'" />
|
||||
<div
|
||||
v-else
|
||||
:ref="
|
||||
@@ -11,7 +11,7 @@
|
||||
)
|
||||
}
|
||||
"
|
||||
></div>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:src="modelValue"
|
||||
class="max-w-full max-h-full object-contain"
|
||||
/>
|
||||
<i v-else class="pi pi-image text-gray-400 text-xl"></i>
|
||||
<i v-else class="pi pi-image text-gray-400 text-xl" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
|
||||
@@ -3,26 +3,26 @@
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div class="form-label flex flex-grow items-center">
|
||||
<span
|
||||
:id="`${props.id}-label`"
|
||||
class="text-muted"
|
||||
:class="props.labelClass"
|
||||
:id="`${props.id}-label`"
|
||||
>
|
||||
<slot name="name-prefix"></slot>
|
||||
<slot name="name-prefix" />
|
||||
{{ props.item.name }}
|
||||
<i
|
||||
v-if="props.item.tooltip"
|
||||
class="pi pi-info-circle bg-transparent"
|
||||
v-tooltip="props.item.tooltip"
|
||||
class="pi pi-info-circle bg-transparent"
|
||||
/>
|
||||
<slot name="name-suffix"></slot>
|
||||
<slot name="name-suffix" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-input flex justify-end">
|
||||
<component
|
||||
:is="markRaw(getFormComponent(props.item))"
|
||||
:id="props.id"
|
||||
:aria-labelledby="`${props.id}-label`"
|
||||
v-model:modelValue="formValue"
|
||||
:aria-labelledby="`${props.id}-label`"
|
||||
v-bind="getFormAttrs(props.item)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<div class="input-knob flex flex-row items-center gap-2">
|
||||
<Knob
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="updateValue"
|
||||
:valueTemplate="displayValue"
|
||||
:model-value="modelValue"
|
||||
:value-template="displayValue"
|
||||
class="knob-part"
|
||||
:class="knobClass"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
v-bind="$attrs"
|
||||
@update:model-value="updateValue"
|
||||
/>
|
||||
<InputNumber
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="updateValue"
|
||||
:model-value="modelValue"
|
||||
class="input-part"
|
||||
:max-fraction-digits="3"
|
||||
:class="inputClass"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:allowEmpty="false"
|
||||
:allow-empty="false"
|
||||
@update:model-value="updateValue"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<div class="input-slider flex flex-row items-center gap-2">
|
||||
<Slider
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="(value) => updateValue(value as number)"
|
||||
:model-value="modelValue"
|
||||
class="slider-part"
|
||||
:class="sliderClass"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
v-bind="$attrs"
|
||||
@update:model-value="(value) => updateValue(value as number)"
|
||||
/>
|
||||
<InputNumber
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="updateValue"
|
||||
:model-value="modelValue"
|
||||
class="input-part"
|
||||
:max-fraction-digits="3"
|
||||
:class="inputClass"
|
||||
:min="min"
|
||||
:max="max"
|
||||
:step="step"
|
||||
:allowEmpty="false"
|
||||
:allow-empty="false"
|
||||
@update:model-value="updateValue"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
<Card>
|
||||
<template #content>
|
||||
<div class="flex flex-col items-center">
|
||||
<i :class="icon" style="font-size: 3rem; margin-bottom: 1rem"></i>
|
||||
<i :class="icon" style="font-size: 3rem; margin-bottom: 1rem" />
|
||||
<h3>{{ title }}</h3>
|
||||
<p class="whitespace-pre-line text-center">{{ message }}</p>
|
||||
<p class="whitespace-pre-line text-center">
|
||||
{{ message }}
|
||||
</p>
|
||||
<Button
|
||||
v-if="buttonLabel"
|
||||
:label="buttonLabel"
|
||||
@click="$emit('action')"
|
||||
class="p-button-text"
|
||||
@click="$emit('action')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
class="p-button-icon pi pi-refresh transition-all"
|
||||
:class="{ 'opacity-0': active }"
|
||||
data-pc-section="icon"
|
||||
></span>
|
||||
/>
|
||||
<span class="p-button-label" data-pc-section="label"> </span>
|
||||
<ProgressSpinner v-show="active" class="absolute w-1/2 h-1/2" />
|
||||
</Button>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
/>
|
||||
<InputText
|
||||
class="search-box-input w-full"
|
||||
@input="handleInput"
|
||||
:modelValue="modelValue"
|
||||
:model-value="modelValue"
|
||||
:placeholder="placeholder"
|
||||
@input="handleInput"
|
||||
/>
|
||||
<InputIcon v-if="!modelValue" :class="icon" />
|
||||
<Button
|
||||
@@ -26,8 +26,8 @@
|
||||
/>
|
||||
</IconField>
|
||||
<div
|
||||
class="search-filters pt-2 flex flex-wrap gap-2"
|
||||
v-if="filters?.length"
|
||||
class="search-filters pt-2 flex flex-wrap gap-2"
|
||||
>
|
||||
<SearchFilterChip
|
||||
v-for="filter in filters"
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<div class="system-stats">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-semibold mb-4">{{ $t('g.systemInfo') }}</h2>
|
||||
<h2 class="text-2xl font-semibold mb-4">
|
||||
{{ $t('g.systemInfo') }}
|
||||
</h2>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<template v-for="col in systemColumns" :key="col.field">
|
||||
<div class="font-medium">{{ col.header }}</div>
|
||||
<div class="font-medium">
|
||||
{{ col.header }}
|
||||
</div>
|
||||
<div>{{ formatValue(systemInfo[col.field], col.field) }}</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -13,7 +17,9 @@
|
||||
<Divider />
|
||||
|
||||
<div>
|
||||
<h2 class="text-2xl font-semibold mb-4">{{ $t('g.devices') }}</h2>
|
||||
<h2 class="text-2xl font-semibold mb-4">
|
||||
{{ $t('g.devices') }}
|
||||
</h2>
|
||||
<TabView v-if="props.stats.devices.length > 1">
|
||||
<TabPanel
|
||||
v-for="device in props.stats.devices"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<Tree
|
||||
class="tree-explorer py-0 px-2 2xl:px-4"
|
||||
:class="props.class"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectionKeys="selectionKeys"
|
||||
class="tree-explorer py-0 px-2 2xl:px-4"
|
||||
:class="props.class"
|
||||
:value="renderedRoot.children"
|
||||
selectionMode="single"
|
||||
selection-mode="single"
|
||||
:pt="{
|
||||
nodeLabel: 'tree-explorer-node-label',
|
||||
nodeContent: ({ context }) => ({
|
||||
@@ -186,9 +186,9 @@ const menuItems = computed<MenuItem[]>(() =>
|
||||
{
|
||||
label: t('g.delete'),
|
||||
icon: 'pi pi-trash',
|
||||
command: () => {
|
||||
command: async () => {
|
||||
if (menuTargetNode.value) {
|
||||
deleteCommand(menuTargetNode.value)
|
||||
await deleteCommand(menuTargetNode.value)
|
||||
}
|
||||
},
|
||||
visible: menuTargetNode.value?.handleDelete !== undefined,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
:class="[
|
||||
'tree-node',
|
||||
{
|
||||
@@ -8,17 +9,16 @@
|
||||
'tree-leaf': props.node.leaf
|
||||
}
|
||||
]"
|
||||
ref="container"
|
||||
>
|
||||
<div class="node-content">
|
||||
<span class="node-label">
|
||||
<slot name="before-label" :node="props.node"></slot>
|
||||
<slot name="before-label" :node="props.node" />
|
||||
<EditableText
|
||||
:modelValue="node.label"
|
||||
:isEditing="isEditing"
|
||||
:model-value="node.label"
|
||||
:is-editing="isEditing"
|
||||
@edit="handleRename"
|
||||
/>
|
||||
<slot name="after-label" :node="props.node"></slot>
|
||||
<slot name="after-label" :node="props.node" />
|
||||
</span>
|
||||
<Badge
|
||||
v-if="showNodeBadgeText"
|
||||
@@ -30,7 +30,7 @@
|
||||
<div
|
||||
class="node-actions motion-safe:opacity-0 motion-safe:group-hover/tree-node:opacity-100"
|
||||
>
|
||||
<slot name="actions" :node="props.node"></slot>
|
||||
<slot name="actions" :node="props.node" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div :style="{ height: `${(state.start / cols) * itemHeight}px` }" />
|
||||
<div :style="gridStyle">
|
||||
<div v-for="item in renderedItems" :key="item.key" data-virtual-grid-item>
|
||||
<slot name="item" :item="item"> </slot>
|
||||
<slot name="item" :item="item" />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('UrlInput', () => {
|
||||
})
|
||||
})
|
||||
|
||||
wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
@@ -74,7 +74,7 @@ describe('UrlInput', () => {
|
||||
validateUrlFn: () => Promise.resolve(true)
|
||||
})
|
||||
|
||||
wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('UrlInput', () => {
|
||||
validateUrlFn: () => Promise.resolve(false)
|
||||
})
|
||||
|
||||
wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
@@ -141,14 +141,14 @@ describe('UrlInput', () => {
|
||||
}
|
||||
})
|
||||
|
||||
wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await wrapper.setProps({ modelValue: 'https://test.com' })
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
// Trigger multiple validations in quick succession
|
||||
wrapper.find('.pi-spinner').trigger('click')
|
||||
wrapper.find('.pi-spinner').trigger('click')
|
||||
wrapper.find('.pi-spinner').trigger('click')
|
||||
await wrapper.find('.pi-spinner').trigger('click')
|
||||
await wrapper.find('.pi-spinner').trigger('click')
|
||||
await wrapper.find('.pi-spinner').trigger('click')
|
||||
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
>
|
||||
<template #header>
|
||||
<component
|
||||
v-if="item.headerComponent"
|
||||
:is="item.headerComponent"
|
||||
v-if="item.headerComponent"
|
||||
:id="item.key"
|
||||
/>
|
||||
<h3 v-else :id="item.key">{{ item.title || ' ' }}</h3>
|
||||
<h3 v-else :id="item.key">
|
||||
{{ item.title || ' ' }}
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<component
|
||||
@@ -26,7 +28,7 @@
|
||||
:maximized="item.dialogComponentProps.maximized"
|
||||
/>
|
||||
|
||||
<template #footer v-if="item.footerComponent">
|
||||
<template v-if="item.footerComponent" #footer>
|
||||
<component :is="item.footerComponent" />
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<section class="prompt-dialog-content flex flex-col gap-6 m-2 mt-4">
|
||||
<span>{{ message }}</span>
|
||||
<ul v-if="itemList?.length" class="pl-4 m-0 flex flex-col gap-2">
|
||||
<li v-for="item of itemList" :key="item">{{ item }}</li>
|
||||
<li v-for="item of itemList" :key="item">
|
||||
{{ item }}
|
||||
</li>
|
||||
</ul>
|
||||
<Message
|
||||
v-if="hint"
|
||||
@@ -18,53 +20,53 @@
|
||||
:label="$t('g.cancel')"
|
||||
icon="pi pi-undo"
|
||||
severity="secondary"
|
||||
@click="onCancel"
|
||||
autofocus
|
||||
@click="onCancel"
|
||||
/>
|
||||
<Button
|
||||
v-if="type === 'default'"
|
||||
:label="$t('g.confirm')"
|
||||
severity="primary"
|
||||
@click="onConfirm"
|
||||
icon="pi pi-check"
|
||||
@click="onConfirm"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="type === 'delete'"
|
||||
:label="$t('g.delete')"
|
||||
severity="danger"
|
||||
@click="onConfirm"
|
||||
icon="pi pi-trash"
|
||||
@click="onConfirm"
|
||||
/>
|
||||
<Button
|
||||
v-else-if="type === 'overwrite'"
|
||||
:label="$t('g.overwrite')"
|
||||
severity="warn"
|
||||
@click="onConfirm"
|
||||
icon="pi pi-save"
|
||||
@click="onConfirm"
|
||||
/>
|
||||
<template v-else-if="type === 'dirtyClose'">
|
||||
<Button
|
||||
:label="$t('g.no')"
|
||||
severity="secondary"
|
||||
@click="onDeny"
|
||||
icon="pi pi-times"
|
||||
@click="onDeny"
|
||||
/>
|
||||
<Button :label="$t('g.save')" @click="onConfirm" icon="pi pi-save" />
|
||||
<Button :label="$t('g.save')" icon="pi pi-save" @click="onConfirm" />
|
||||
</template>
|
||||
<Button
|
||||
v-else-if="type === 'reinstall'"
|
||||
:label="$t('desktopMenu.reinstall')"
|
||||
severity="warn"
|
||||
@click="onConfirm"
|
||||
icon="pi pi-eraser"
|
||||
@click="onConfirm"
|
||||
/>
|
||||
<!-- Invalid - just show a close button. -->
|
||||
<Button
|
||||
v-else
|
||||
:label="$t('g.close')"
|
||||
severity="primary"
|
||||
@click="onCancel"
|
||||
icon="pi pi-times"
|
||||
@click="onCancel"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
/>
|
||||
<div class="flex gap-4 justify-end">
|
||||
<FindIssueButton
|
||||
:errorMessage="error.exceptionMessage"
|
||||
:repoOwner="repoOwner"
|
||||
:repoName="repoName"
|
||||
:error-message="error.exceptionMessage"
|
||||
:repo-owner="repoOwner"
|
||||
:repo-name="repoName"
|
||||
/>
|
||||
<Button
|
||||
v-if="reportOpen"
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
>
|
||||
<template #header>
|
||||
<header class="flex flex-col items-center w-full">
|
||||
<h2 id="issue-report-title" class="text-4xl">{{ title }}</h2>
|
||||
<h2 id="issue-report-title" class="text-4xl">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<span v-if="subtitle" class="text-muted mt-0">{{ subtitle }}</span>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
/>
|
||||
<ListBox
|
||||
:options="uniqueNodes"
|
||||
optionLabel="label"
|
||||
scrollHeight="100%"
|
||||
option-label="label"
|
||||
scroll-height="100%"
|
||||
class="comfy-missing-nodes"
|
||||
:pt="{
|
||||
list: { class: 'border-none' }
|
||||
@@ -22,14 +22,17 @@
|
||||
}}</span>
|
||||
<Button
|
||||
v-if="slotProps.option.action"
|
||||
@click="slotProps.option.action.callback"
|
||||
:label="slotProps.option.action.text"
|
||||
size="small"
|
||||
outlined
|
||||
@click="slotProps.option.action.callback"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</ListBox>
|
||||
<div class="flex justify-end py-3">
|
||||
<Button label="Open Manager" size="small" outlined @click="openManager" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -38,7 +41,9 @@ import ListBox from 'primevue/listbox'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import type { MissingNodeType } from '@/types/comfy'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
missingNodeTypes: MissingNodeType[]
|
||||
@@ -64,6 +69,12 @@ const uniqueNodes = computed(() => {
|
||||
return { label: node }
|
||||
})
|
||||
})
|
||||
|
||||
const openManager = () => {
|
||||
useDialogService().showManagerDialog({
|
||||
initialTab: ManagerTab.Missing
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -129,9 +129,12 @@ const missingModels = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
onBeforeUnmount(async () => {
|
||||
if (doNotAskAgain.value) {
|
||||
useSettingStore().set('Comfy.Workflow.ShowMissingModelsWarning', false)
|
||||
await useSettingStore().set(
|
||||
'Comfy.Workflow.ShowMissingModelsWarning',
|
||||
false
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
<InputText
|
||||
ref="inputRef"
|
||||
v-model="inputValue"
|
||||
autofocus
|
||||
@keyup.enter="onConfirm"
|
||||
@focus="selectAllText"
|
||||
autofocus
|
||||
/>
|
||||
<label>{{ message }}</label>
|
||||
</FloatLabel>
|
||||
<Button @click="onConfirm">{{ $t('g.confirm') }}</Button>
|
||||
<Button @click="onConfirm">
|
||||
{{ $t('g.confirm') }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
<div class="settings-container">
|
||||
<ScrollPanel class="settings-sidebar flex-shrink-0 p-2 w-48 2xl:w-64">
|
||||
<SearchBox
|
||||
class="settings-search-box w-full mb-2"
|
||||
v-model:modelValue="searchQuery"
|
||||
@search="handleSearch"
|
||||
class="settings-search-box w-full mb-2"
|
||||
:placeholder="$t('g.searchSettings') + '...'"
|
||||
:debounce-time="128"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<Listbox
|
||||
v-model="activeCategory"
|
||||
:options="categories"
|
||||
optionLabel="translatedLabel"
|
||||
scrollHeight="100%"
|
||||
:optionDisabled="
|
||||
option-label="translatedLabel"
|
||||
scroll-height="100%"
|
||||
:option-disabled="
|
||||
(option: SettingTreeNode) =>
|
||||
!queryIsEmpty && !searchResultsCategories.has(option.label ?? '')
|
||||
"
|
||||
@@ -25,7 +25,7 @@
|
||||
<Tabs :value="tabValue" :lazy="true" class="settings-content h-full w-full">
|
||||
<TabPanels class="settings-tab-panels h-full w-full pr-0">
|
||||
<PanelTemplate value="Search Results">
|
||||
<SettingsPanel :settingGroups="searchResults" />
|
||||
<SettingsPanel :setting-groups="searchResults" />
|
||||
</PanelTemplate>
|
||||
|
||||
<PanelTemplate
|
||||
@@ -38,7 +38,7 @@
|
||||
<FirstTimeUIMessage v-if="tabValue === 'Comfy'" />
|
||||
<ColorPaletteMessage v-if="tabValue === 'Appearance'" />
|
||||
</template>
|
||||
<SettingsPanel :settingGroups="sortedGroups(category)" />
|
||||
<SettingsPanel :setting-groups="sortedGroups(category)" />
|
||||
</PanelTemplate>
|
||||
|
||||
<AboutPanel />
|
||||
@@ -293,6 +293,10 @@ watch(activeCategory, (_, oldValue) => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.settings-container {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<Button
|
||||
@click="openGitHubIssues"
|
||||
:label="$t('g.findIssues')"
|
||||
severity="secondary"
|
||||
icon="pi pi-github"
|
||||
>
|
||||
</Button>
|
||||
@click="openGitHubIssues"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<Form
|
||||
v-slot="$form"
|
||||
@submit="submit"
|
||||
:resolver="zodResolver(issueReportSchema)"
|
||||
@submit="submit"
|
||||
>
|
||||
<Panel :pt="$attrs.pt as any">
|
||||
<template #header>
|
||||
@@ -33,15 +33,15 @@
|
||||
>
|
||||
<Checkbox
|
||||
v-bind="$field"
|
||||
:inputId="field.value"
|
||||
:value="field.value"
|
||||
v-model="selection"
|
||||
:input-id="field.value"
|
||||
:value="field.value"
|
||||
/>
|
||||
<label :for="field.value">{{ field.label }}</label>
|
||||
</FormField>
|
||||
</div>
|
||||
</div>
|
||||
<FormField class="mb-4" v-slot="$field" name="details">
|
||||
<FormField v-slot="$field" class="mb-4" name="details">
|
||||
<Textarea
|
||||
v-bind="$field"
|
||||
class="w-full"
|
||||
@@ -83,9 +83,9 @@
|
||||
>
|
||||
<Checkbox
|
||||
v-bind="$field"
|
||||
:inputId="checkbox.value"
|
||||
:value="checkbox.value"
|
||||
v-model="contactPrefs"
|
||||
:input-id="checkbox.value"
|
||||
:value="checkbox.value"
|
||||
:disabled="
|
||||
$form.contactInfo?.error || !$form.contactInfo?.value
|
||||
"
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<div class="flex flex-1 relative overflow-hidden">
|
||||
<ManagerNavSidebar
|
||||
v-if="isSideNavOpen"
|
||||
:tabs="tabs"
|
||||
v-model:selectedTab="selectedTab"
|
||||
:tabs="tabs"
|
||||
/>
|
||||
<div
|
||||
class="flex-1 overflow-auto pr-80"
|
||||
@@ -29,7 +29,8 @@
|
||||
<RegistrySearchBar
|
||||
v-model:searchQuery="searchQuery"
|
||||
v-model:searchMode="searchMode"
|
||||
:searchResults="searchResults"
|
||||
v-model:sortField="sortField"
|
||||
:search-results="searchResults"
|
||||
:suggestions="suggestions"
|
||||
/>
|
||||
<div class="flex-1 overflow-auto">
|
||||
@@ -56,16 +57,16 @@
|
||||
<VirtualGrid
|
||||
:items="resultsWithKeys"
|
||||
:buffer-rows="3"
|
||||
:gridStyle="GRID_STYLE"
|
||||
:grid-style="GRID_STYLE"
|
||||
@approach-end="onApproachEnd"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<PackCard
|
||||
@click.stop="(event) => selectNodePack(item, event)"
|
||||
:node-pack="item"
|
||||
:is-selected="
|
||||
selectedNodePacks.some((pack) => pack.id === item.id)
|
||||
"
|
||||
@click.stop="(event) => selectNodePack(item, event)"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
@@ -113,15 +114,12 @@ import { useRegistrySearch } from '@/composables/useRegistrySearch'
|
||||
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
|
||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||
import type { TabItem } from '@/types/comfyManagerTypes'
|
||||
import { ManagerTab } from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
enum ManagerTab {
|
||||
All = 'all',
|
||||
Installed = 'installed',
|
||||
Workflow = 'workflow',
|
||||
Missing = 'missing',
|
||||
UpdateAvailable = 'updateAvailable'
|
||||
}
|
||||
const { initialTab = ManagerTab.All } = defineProps<{
|
||||
initialTab: ManagerTab
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const comfyManagerStore = useComfyManagerStore()
|
||||
@@ -159,7 +157,9 @@ const tabs = ref<TabItem[]>([
|
||||
icon: 'pi-sync'
|
||||
}
|
||||
])
|
||||
const selectedTab = ref<TabItem>(tabs.value[0])
|
||||
const selectedTab = ref<TabItem>(
|
||||
tabs.value.find((tab) => tab.id === initialTab) || tabs.value[0]
|
||||
)
|
||||
|
||||
const {
|
||||
searchQuery,
|
||||
@@ -167,6 +167,7 @@ const {
|
||||
isLoading: isSearchLoading,
|
||||
searchResults,
|
||||
searchMode,
|
||||
sortField,
|
||||
suggestions
|
||||
} = useRegistrySearch()
|
||||
pageNumber.value = 0
|
||||
@@ -185,14 +186,16 @@ const {
|
||||
startFetchInstalled,
|
||||
filterInstalledPack,
|
||||
installedPacks,
|
||||
isLoading: isLoadingInstalled
|
||||
isLoading: isLoadingInstalled,
|
||||
isReady: installedPacksReady
|
||||
} = useInstalledPacks()
|
||||
|
||||
const {
|
||||
startFetchWorkflowPacks,
|
||||
filterWorkflowPack,
|
||||
workflowPacks,
|
||||
isLoading: isLoadingWorkflow
|
||||
isLoading: isLoadingWorkflow,
|
||||
isReady: workflowPacksReady
|
||||
} = useWorkflowPacks()
|
||||
|
||||
const filterMissingPacks = (packs: components['schemas']['Node'][]) =>
|
||||
@@ -219,45 +222,68 @@ const isOutdatedPack = (pack: components['schemas']['Node']) => {
|
||||
const filterOutdatedPacks = (packs: components['schemas']['Node'][]) =>
|
||||
packs.filter(isOutdatedPack)
|
||||
|
||||
watch([isUpdateAvailableTab, installedPacks], () => {
|
||||
if (!isUpdateAvailableTab.value) return
|
||||
watch(
|
||||
[isUpdateAvailableTab, installedPacks],
|
||||
async () => {
|
||||
if (!isUpdateAvailableTab.value) return
|
||||
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
} else if (!installedPacks.value.length) {
|
||||
startFetchInstalled()
|
||||
} else {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
}
|
||||
})
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
} else if (!installedPacks.value.length) {
|
||||
await startFetchInstalled()
|
||||
} else {
|
||||
displayPacks.value = filterOutdatedPacks(installedPacks.value)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch([isInstalledTab, installedPacks], () => {
|
||||
if (!isInstalledTab.value) return
|
||||
watch(
|
||||
[isInstalledTab, installedPacks],
|
||||
async () => {
|
||||
if (!isInstalledTab.value) return
|
||||
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterInstalledPack(searchResults.value)
|
||||
} else if (!installedPacks.value.length) {
|
||||
startFetchInstalled()
|
||||
} else {
|
||||
displayPacks.value = installedPacks.value
|
||||
}
|
||||
})
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterInstalledPack(searchResults.value)
|
||||
} else if (
|
||||
!installedPacks.value.length &&
|
||||
!installedPacksReady.value &&
|
||||
!isLoadingInstalled.value
|
||||
) {
|
||||
await startFetchInstalled()
|
||||
} else {
|
||||
displayPacks.value = installedPacks.value
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch([isMissingTab, isWorkflowTab, workflowPacks], () => {
|
||||
if (!isWorkflowTab.value && !isMissingTab.value) return
|
||||
watch(
|
||||
[isMissingTab, isWorkflowTab, workflowPacks, installedPacks],
|
||||
async () => {
|
||||
if (!isWorkflowTab.value && !isMissingTab.value) return
|
||||
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = isMissingTab.value
|
||||
? filterMissingPacks(filterWorkflowPack(searchResults.value))
|
||||
: filterWorkflowPack(searchResults.value)
|
||||
} else if (!workflowPacks.value.length) {
|
||||
startFetchWorkflowPacks()
|
||||
} else {
|
||||
displayPacks.value = isMissingTab.value
|
||||
? filterMissingPacks(workflowPacks.value)
|
||||
: workflowPacks.value
|
||||
}
|
||||
})
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = isMissingTab.value
|
||||
? filterMissingPacks(filterWorkflowPack(searchResults.value))
|
||||
: filterWorkflowPack(searchResults.value)
|
||||
} else if (
|
||||
!workflowPacks.value.length &&
|
||||
!isLoadingWorkflow.value &&
|
||||
!workflowPacksReady.value
|
||||
) {
|
||||
await startFetchWorkflowPacks()
|
||||
if (isMissingTab.value) {
|
||||
await startFetchInstalled()
|
||||
}
|
||||
} else {
|
||||
displayPacks.value = isMissingTab.value
|
||||
? filterMissingPacks(workflowPacks.value)
|
||||
: workflowPacks.value
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch([isAllTab, searchResults], () => {
|
||||
if (!isAllTab.value) return
|
||||
@@ -267,25 +293,33 @@ watch([isAllTab, searchResults], () => {
|
||||
const onResultsChange = () => {
|
||||
switch (selectedTab.value?.id) {
|
||||
case ManagerTab.Installed:
|
||||
displayPacks.value = filterInstalledPack(searchResults.value)
|
||||
displayPacks.value = isEmptySearch.value
|
||||
? installedPacks.value
|
||||
: filterInstalledPack(searchResults.value)
|
||||
break
|
||||
case ManagerTab.Workflow:
|
||||
displayPacks.value = filterWorkflowPack(searchResults.value)
|
||||
displayPacks.value = isEmptySearch.value
|
||||
? workflowPacks.value
|
||||
: filterWorkflowPack(searchResults.value)
|
||||
break
|
||||
case ManagerTab.Missing:
|
||||
displayPacks.value = filterMissingPacks(
|
||||
filterWorkflowPack(searchResults.value)
|
||||
)
|
||||
if (!isEmptySearch.value) {
|
||||
displayPacks.value = filterMissingPacks(
|
||||
filterWorkflowPack(searchResults.value)
|
||||
)
|
||||
}
|
||||
break
|
||||
case ManagerTab.UpdateAvailable:
|
||||
displayPacks.value = filterOutdatedPacks(searchResults.value)
|
||||
displayPacks.value = isEmptySearch.value
|
||||
? filterOutdatedPacks(installedPacks.value)
|
||||
: filterOutdatedPacks(searchResults.value)
|
||||
break
|
||||
default:
|
||||
displayPacks.value = searchResults.value
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchResults, onResultsChange, { flush: 'pre' })
|
||||
watch(searchResults, onResultsChange, { flush: 'post' })
|
||||
watch(() => comfyManagerStore.installedPacksIds, onResultsChange)
|
||||
|
||||
const isLoading = computed(() => {
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<Listbox
|
||||
v-model="selectedTab"
|
||||
:options="tabs"
|
||||
optionLabel="label"
|
||||
listStyle="max-height:unset"
|
||||
option-label="label"
|
||||
list-style="max-height:unset"
|
||||
class="w-full border-0 bg-transparent shadow-none"
|
||||
:pt="{
|
||||
list: { class: 'p-5' },
|
||||
@@ -17,7 +17,7 @@
|
||||
>
|
||||
<template #option="slotProps">
|
||||
<div class="text-left flex items-center">
|
||||
<i :class="['pi', slotProps.option.icon, 'mr-3']"></i>
|
||||
<i :class="['pi', slotProps.option.icon, 'mr-3']" />
|
||||
<span class="text-lg">{{ slotProps.option.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<i
|
||||
class="pi pi-circle-fill mr-1.5 text-[0.6rem] p-0"
|
||||
:style="{ opacity: 0.8 }"
|
||||
></i>
|
||||
/>
|
||||
{{ $t(`manager.status.${statusLabel}`) }}
|
||||
</Message>
|
||||
</template>
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
'w-full': fullWidth,
|
||||
'w-min-content': !fullWidth
|
||||
}"
|
||||
:disabled="isInstalling"
|
||||
:disabled="loading"
|
||||
v-bind="$attrs"
|
||||
@click="onClick"
|
||||
>
|
||||
<span class="py-2.5 px-3">
|
||||
<template v-if="isInstalling">
|
||||
<template v-if="loading">
|
||||
{{ loadingMessage ?? $t('g.loading') }}
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -23,9 +23,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import Button from 'primevue/button'
|
||||
import { inject, ref } from 'vue'
|
||||
|
||||
import { IsInstallingKey } from '@/types/comfyManagerTypes'
|
||||
|
||||
const {
|
||||
label,
|
||||
@@ -33,6 +30,7 @@ const {
|
||||
fullWidth = false
|
||||
} = defineProps<{
|
||||
label: string
|
||||
loading?: boolean
|
||||
loadingMessage?: string
|
||||
fullWidth?: boolean
|
||||
}>()
|
||||
@@ -45,10 +43,7 @@ defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
|
||||
const onClick = (): void => {
|
||||
isInstalling.value = true
|
||||
emit('action')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
nodePacks.length > 1 ? $t('manager.installSelected') : $t('g.install')
|
||||
"
|
||||
severity="secondary"
|
||||
:loading="isInstalling"
|
||||
:loading-message="$t('g.installing')"
|
||||
@action="installAllPacks"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -31,6 +33,10 @@ const { nodePacks } = defineProps<{
|
||||
|
||||
const isInstalling = inject(IsInstallingKey, ref(false))
|
||||
|
||||
const onClick = (): void => {
|
||||
isInstalling.value = true
|
||||
}
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const createPayload = (installItem: NodePack) => {
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
<div class="top-0 z-10 px-6 pt-6 w-full">
|
||||
<InfoPanelHeader :node-packs="[nodePack]" />
|
||||
</div>
|
||||
<div class="p-6 pt-2 overflow-y-auto flex-1 text-sm hidden-scrollbar">
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="p-6 pt-2 overflow-y-auto flex-1 text-sm hidden-scrollbar"
|
||||
>
|
||||
<div class="mb-6">
|
||||
<MetadataRow
|
||||
v-if="isPackInstalled(nodePack.id)"
|
||||
@@ -46,7 +49,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { whenever } from '@vueuse/core'
|
||||
import { useScroll, whenever } from '@vueuse/core'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
@@ -70,6 +73,8 @@ const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
}>()
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
const isInstalled = computed(() => managerStore.isPackInstalled(nodePack.id))
|
||||
const isInstalling = ref(false)
|
||||
@@ -103,6 +108,17 @@ const infoItems = computed<InfoItem[]>(() => [
|
||||
: undefined
|
||||
}
|
||||
])
|
||||
|
||||
const { y } = useScroll(scrollContainer, {
|
||||
eventListenerOptions: {
|
||||
passive: true
|
||||
}
|
||||
})
|
||||
const onNodePackChange = () => {
|
||||
y.value = 0
|
||||
}
|
||||
|
||||
whenever(() => nodePack, onNodePackChange, { immediate: true, deep: true })
|
||||
</script>
|
||||
<style scoped>
|
||||
.hidden-scrollbar {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
@@ -46,7 +46,14 @@ const { nodePacks } = defineProps<{
|
||||
|
||||
const managerStore = useComfyManagerStore()
|
||||
|
||||
const isAllInstalled = computed(() =>
|
||||
nodePacks.every((nodePack) => managerStore.isPackInstalled(nodePack.id))
|
||||
const isAllInstalled = ref(false)
|
||||
watch(
|
||||
[() => nodePacks, () => managerStore.installedPacks],
|
||||
() => {
|
||||
isAllInstalled.value = nodePacks.every((nodePack) =>
|
||||
managerStore.isPackInstalled(nodePack.id)
|
||||
)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -31,28 +31,29 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onUnmounted } from 'vue'
|
||||
|
||||
import PackStatusMessage from '@/components/dialog/content/manager/PackStatusMessage.vue'
|
||||
import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue'
|
||||
import InfoPanelHeader from '@/components/dialog/content/manager/infoPanel/InfoPanelHeader.vue'
|
||||
import MetadataRow from '@/components/dialog/content/manager/infoPanel/MetadataRow.vue'
|
||||
import PackIconStacked from '@/components/dialog/content/manager/packIcon/PackIconStacked.vue'
|
||||
import { useComfyRegistryService } from '@/services/comfyRegistryService'
|
||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { nodePacks } = defineProps<{
|
||||
nodePacks: components['schemas']['Node'][]
|
||||
}>()
|
||||
|
||||
const comfyRegistryService = useComfyRegistryService()
|
||||
const { getNodeDefs } = useComfyRegistryStore()
|
||||
|
||||
const getPackNodes = async (pack: components['schemas']['Node']) => {
|
||||
if (!comfyRegistryService.packNodesAvailable(pack)) return []
|
||||
return comfyRegistryService.getNodeDefs({
|
||||
if (!pack.latest_version?.version) return []
|
||||
const nodeDefs = await getNodeDefs.call({
|
||||
packId: pack.id,
|
||||
versionId: pack.latest_version?.id
|
||||
version: pack.latest_version?.version
|
||||
})
|
||||
return nodeDefs?.comfy_nodes ?? []
|
||||
}
|
||||
|
||||
const { state: allNodeDefs } = useAsyncState(
|
||||
@@ -69,4 +70,8 @@ const totalNodesCount = computed(() =>
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
getNodeDefs.cancel()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,15 +2,19 @@
|
||||
<div class="overflow-hidden">
|
||||
<Tabs :value="activeTab">
|
||||
<TabList>
|
||||
<Tab value="description">{{ $t('g.description') }}</Tab>
|
||||
<Tab value="nodes">{{ $t('g.nodes') }}</Tab>
|
||||
<Tab value="description">
|
||||
{{ $t('g.description') }}
|
||||
</Tab>
|
||||
<Tab value="nodes">
|
||||
{{ $t('g.nodes') }}
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels class="overflow-auto">
|
||||
<TabPanel value="description">
|
||||
<DescriptionTabPanel :node-pack="nodePack" />
|
||||
</TabPanel>
|
||||
<TabPanel value="nodes">
|
||||
<NodesTabPanel :node-pack="nodePack" />
|
||||
<NodesTabPanel :node-pack="nodePack" :node-names="nodeNames" />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
@@ -23,15 +27,21 @@ import TabList from 'primevue/tablist'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
import TabPanels from 'primevue/tabpanels'
|
||||
import Tabs from 'primevue/tabs'
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import DescriptionTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue'
|
||||
import NodesTabPanel from '@/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
defineProps<{
|
||||
const { nodePack } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
}>()
|
||||
|
||||
const nodeNames = computed(() => {
|
||||
// @ts-expect-error comfy_nodes is an Algolia-specific field
|
||||
const { comfy_nodes } = nodePack
|
||||
return comfy_nodes ?? []
|
||||
})
|
||||
|
||||
const activeTab = ref('description')
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 text-sm">
|
||||
<div v-for="(section, index) in sections" :key="index" class="mb-4">
|
||||
<div class="mb-1">{{ section.title }}</div>
|
||||
<div class="mb-1">
|
||||
{{ section.title }}
|
||||
</div>
|
||||
<div class="text-muted break-words">
|
||||
<a
|
||||
v-if="section.isUrl"
|
||||
@@ -10,10 +12,7 @@
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<i
|
||||
v-if="isGitHubLink(section.text)"
|
||||
class="pi pi-github text-base"
|
||||
></i>
|
||||
<i v-if="isGitHubLink(section.text)" class="pi pi-github text-base" />
|
||||
<span class="break-all">{{ section.text }}</span>
|
||||
</a>
|
||||
<MarkdownText v-else :text="section.text" class="text-muted" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!hasMarkdown" v-text="text" class="break-words"></div>
|
||||
<div v-if="!hasMarkdown" class="break-words" v-text="text" />
|
||||
<div v-else class="break-words">
|
||||
<template v-for="(segment, index) in parsedSegments" :key="index">
|
||||
<a
|
||||
|
||||
@@ -1,47 +1,92 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 mt-4 overflow-auto text-sm">
|
||||
<div v-if="nodeDefs?.length">
|
||||
<!-- TODO: when registry returns node defs, use them here -->
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="border border-surface-border rounded-lg p-4"
|
||||
>
|
||||
<NodePreview
|
||||
:node-def="placeholderNodeDef"
|
||||
class="!text-[.625rem] !min-w-full"
|
||||
<div class="flex flex-col gap-4 mt-4 text-sm">
|
||||
<template v-if="mappedNodeDefs?.length">
|
||||
<div
|
||||
v-for="nodeDef in mappedNodeDefs"
|
||||
:key="createNodeDefKey(nodeDef)"
|
||||
class="border border-surface-border rounded-lg p-4"
|
||||
>
|
||||
<NodePreview :node-def="nodeDef" class="!text-[.625rem] !min-w-full" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="isLoading">
|
||||
<ProgressSpinner />
|
||||
</template>
|
||||
<template v-else-if="nodeNames.length">
|
||||
<div v-for="node in nodeNames" :key="node" class="text-muted truncate">
|
||||
{{ node }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NoResultsPlaceholder
|
||||
:title="$t('manager.noNodesFound')"
|
||||
:message="$t('manager.noNodesFoundDescription')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import NodePreview from '@/components/node/NodePreview.vue'
|
||||
import { ComfyNodeDef } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
import { whenever } from '@vueuse/core'
|
||||
import ProgressSpinner from 'primevue/progressspinner'
|
||||
import { computed, ref, shallowRef, useId } from 'vue'
|
||||
|
||||
defineProps<{
|
||||
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
|
||||
import NodePreview from '@/components/node/NodePreview.vue'
|
||||
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
|
||||
import { components, operations } from '@/types/comfyRegistryTypes'
|
||||
import { registryToFrontendV2NodeDef } from '@/utils/mapperUtil'
|
||||
|
||||
type ListComfyNodesResponse =
|
||||
operations['ListComfyNodes']['responses'][200]['content']['application/json']['comfy_nodes']
|
||||
|
||||
const { nodePack, nodeNames } = defineProps<{
|
||||
nodePack: components['schemas']['Node']
|
||||
nodeDefs?: components['schemas']['ComfyNode'][]
|
||||
nodeNames: string[]
|
||||
}>()
|
||||
|
||||
// TODO: when registry returns node defs, use them here
|
||||
const placeholderNodeDef: ComfyNodeDef = {
|
||||
name: 'Sample Node',
|
||||
display_name: 'Sample Node',
|
||||
description: 'This is a sample node for preview purposes',
|
||||
inputs: {
|
||||
input1: { name: 'Input 1', type: 'IMAGE' },
|
||||
input2: { name: 'Input 2', type: 'CONDITIONING' }
|
||||
},
|
||||
outputs: [
|
||||
{ name: 'Output 1', type: 'IMAGE', index: 0, is_list: false },
|
||||
{ name: 'Output 2', type: 'MASK', index: 1, is_list: false }
|
||||
],
|
||||
category: 'Utility',
|
||||
output_node: false,
|
||||
python_module: 'nodes'
|
||||
const { getNodeDefs } = useComfyRegistryStore()
|
||||
|
||||
const isLoading = ref(false)
|
||||
const registryNodeDefs = shallowRef<ListComfyNodesResponse | null>(null)
|
||||
|
||||
const fetchNodeDefs = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
const { id: packId } = nodePack
|
||||
const version = nodePack.latest_version?.version
|
||||
|
||||
if (!packId || !version) {
|
||||
registryNodeDefs.value = null
|
||||
} else {
|
||||
const response = await getNodeDefs.call({
|
||||
packId,
|
||||
version,
|
||||
page: 1,
|
||||
limit: 256
|
||||
})
|
||||
registryNodeDefs.value = response?.comfy_nodes ?? null
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
whenever(() => nodePack, fetchNodeDefs, { immediate: true, deep: true })
|
||||
|
||||
const toFrontendNodeDef = (nodeDef: components['schemas']['ComfyNode']) => {
|
||||
try {
|
||||
return registryToFrontendV2NodeDef(nodeDef, nodePack)
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
const mappedNodeDefs = computed(() => {
|
||||
if (!registryNodeDefs.value) return null
|
||||
return registryNodeDefs.value
|
||||
.map(toFrontendNodeDef)
|
||||
.filter((nodeDef) => nodeDef !== null)
|
||||
})
|
||||
|
||||
const createNodeDefKey = (nodeDef: components['schemas']['ComfyNode']) =>
|
||||
`${nodeDef.category}${nodeDef.comfy_node_name ?? useId()}`
|
||||
</script>
|
||||
|
||||
@@ -7,10 +7,8 @@
|
||||
:placeholder="$t('manager.searchPlaceholder')"
|
||||
:complete-on-focus="false"
|
||||
:delay="8"
|
||||
optionLabel="query"
|
||||
option-label="query"
|
||||
class="w-full"
|
||||
@complete="stubTrue"
|
||||
@option-select="onOptionSelect"
|
||||
:pt="{
|
||||
pcInputText: {
|
||||
root: {
|
||||
@@ -22,8 +20,9 @@
|
||||
style: 'display: none'
|
||||
}
|
||||
}"
|
||||
>
|
||||
</AutoComplete>
|
||||
@complete="stubTrue"
|
||||
@option-select="onOptionSelect"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex mt-3 text-sm">
|
||||
<div class="flex gap-6 ml-1">
|
||||
@@ -57,7 +56,10 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import SearchFilterDropdown from '@/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue'
|
||||
import type { NodesIndexSuggestion } from '@/services/algoliaSearchService'
|
||||
import type { PackField, SearchOption } from '@/types/comfyManagerTypes'
|
||||
import {
|
||||
type SearchOption,
|
||||
SortableAlgoliaField
|
||||
} from '@/types/comfyManagerTypes'
|
||||
import { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const { searchResults } = defineProps<{
|
||||
@@ -67,7 +69,9 @@ const { searchResults } = defineProps<{
|
||||
|
||||
const searchQuery = defineModel<string>('searchQuery')
|
||||
const searchMode = defineModel<string>('searchMode', { default: 'packs' })
|
||||
const sortField = defineModel<PackField>('sortField', { default: 'downloads' })
|
||||
const sortField = defineModel<SortableAlgoliaField>('sortField', {
|
||||
default: SortableAlgoliaField.Downloads
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -75,11 +79,12 @@ const hasResults = computed(
|
||||
() => searchQuery.value?.trim() && searchResults?.length
|
||||
)
|
||||
|
||||
const sortOptions: SearchOption<PackField>[] = [
|
||||
{ id: 'downloads', label: t('manager.sort.downloads') },
|
||||
{ id: 'name', label: t('g.name') },
|
||||
{ id: 'rating', label: t('manager.sort.rating') },
|
||||
{ id: 'category', label: t('g.category') }
|
||||
const sortOptions: SearchOption<SortableAlgoliaField>[] = [
|
||||
{ id: SortableAlgoliaField.Downloads, label: t('manager.sort.downloads') },
|
||||
{ id: SortableAlgoliaField.Created, label: t('manager.sort.created') },
|
||||
{ id: SortableAlgoliaField.Updated, label: t('manager.sort.updated') },
|
||||
{ id: SortableAlgoliaField.Publisher, label: t('manager.sort.publisher') },
|
||||
{ id: SortableAlgoliaField.Name, label: t('g.name') }
|
||||
]
|
||||
const filterOptions: SearchOption<string>[] = [
|
||||
{ id: 'packs', label: t('manager.filter.nodePack') },
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="text-muted">{{ label }}:</span>
|
||||
<Dropdown
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="$emit('update:modelValue', $event)"
|
||||
:model-value="modelValue"
|
||||
:options="options"
|
||||
optionLabel="label"
|
||||
optionValue="id"
|
||||
option-label="label"
|
||||
option-value="id"
|
||||
class="min-w-[6rem] border-none bg-transparent shadow-none"
|
||||
:pt="{
|
||||
input: { class: 'py-0 px-1 border-none' },
|
||||
@@ -14,6 +13,7 @@
|
||||
panel: { class: 'shadow-md' },
|
||||
item: { class: 'py-2 px-3 text-sm' }
|
||||
}"
|
||||
@update:model-value="$emit('update:modelValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,52 +7,44 @@
|
||||
<div class="w-full px-4 py-3 flex justify-between items-center border-b">
|
||||
<div class="flex items-center">
|
||||
<div class="w-6 h-6 flex items-center justify-center">
|
||||
<Skeleton shape="circle" width="1.5rem" height="1.5rem"></Skeleton>
|
||||
<Skeleton shape="circle" width="1.5rem" height="1.5rem" />
|
||||
</div>
|
||||
<Skeleton width="5rem" height="1rem" class="ml-2"></Skeleton>
|
||||
<Skeleton width="5rem" height="1rem" class="ml-2" />
|
||||
</div>
|
||||
<Skeleton width="4rem" height="1.75rem" borderRadius="0.75rem"></Skeleton>
|
||||
<Skeleton width="4rem" height="1.75rem" border-radius="0.75rem" />
|
||||
</div>
|
||||
|
||||
<!-- Card content with icon on left and text on right -->
|
||||
<div class="flex-1 p-4 flex">
|
||||
<!-- Left icon - 64x64 -->
|
||||
<div class="flex-shrink-0 mr-4">
|
||||
<Skeleton width="4rem" height="4rem" borderRadius="0.5rem"></Skeleton>
|
||||
<Skeleton width="4rem" height="4rem" border-radius="0.5rem" />
|
||||
</div>
|
||||
|
||||
<!-- Right content -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- Title -->
|
||||
<Skeleton width="80%" height="1rem" class="mb-2"></Skeleton>
|
||||
<Skeleton width="80%" height="1rem" class="mb-2" />
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mb-3">
|
||||
<Skeleton width="100%" height="0.75rem" class="mb-1"></Skeleton>
|
||||
<Skeleton width="95%" height="0.75rem" class="mb-1"></Skeleton>
|
||||
<Skeleton width="90%" height="0.75rem"></Skeleton>
|
||||
<Skeleton width="100%" height="0.75rem" class="mb-1" />
|
||||
<Skeleton width="95%" height="0.75rem" class="mb-1" />
|
||||
<Skeleton width="90%" height="0.75rem" />
|
||||
</div>
|
||||
|
||||
<!-- Tags/Badges -->
|
||||
<div class="flex gap-2">
|
||||
<Skeleton
|
||||
width="4rem"
|
||||
height="1.5rem"
|
||||
borderRadius="0.75rem"
|
||||
></Skeleton>
|
||||
<Skeleton
|
||||
width="5rem"
|
||||
height="1.5rem"
|
||||
borderRadius="0.75rem"
|
||||
></Skeleton>
|
||||
<Skeleton width="4rem" height="1.5rem" border-radius="0.75rem" />
|
||||
<Skeleton width="5rem" height="1.5rem" border-radius="0.75rem" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card footer - similar to header -->
|
||||
<div class="w-full px-5 py-4 flex justify-between items-center border-t">
|
||||
<Skeleton width="4rem" height="0.8rem"></Skeleton>
|
||||
<Skeleton width="6rem" height="0.8rem"></Skeleton>
|
||||
<Skeleton width="4rem" height="0.8rem" />
|
||||
<Skeleton width="6rem" height="0.8rem" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<PanelTemplate value="About" class="about-container">
|
||||
<h2 class="text-2xl font-bold mb-2">{{ $t('g.about') }}</h2>
|
||||
<h2 class="text-2xl font-bold mb-2">
|
||||
{{ $t('g.about') }}
|
||||
</h2>
|
||||
<div class="space-y-2">
|
||||
<a
|
||||
v-for="badge in aboutPanelStore.badges"
|
||||
@@ -13,7 +15,7 @@
|
||||
>
|
||||
<Tag class="mr-2">
|
||||
<template #icon>
|
||||
<i :class="[badge.icon, 'mr-2 text-xl']"></i>
|
||||
<i :class="[badge.icon, 'mr-2 text-xl']" />
|
||||
</template>
|
||||
{{ badge.label }}
|
||||
</Tag>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
</div>
|
||||
<div class="actions">
|
||||
<Select
|
||||
class="w-44"
|
||||
v-model="activePaletteId"
|
||||
class="w-44"
|
||||
:options="palettes"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-file-export"
|
||||
@@ -29,8 +29,8 @@
|
||||
severity="danger"
|
||||
text
|
||||
:title="$t('g.delete')"
|
||||
@click="colorPaletteService.deleteCustomColorPalette(activePaletteId)"
|
||||
:disabled="!colorPaletteStore.isCustomPalette(activePaletteId)"
|
||||
@click="colorPaletteService.deleteCustomColorPalette(activePaletteId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,7 +55,7 @@ const { palettes, activePaletteId } = storeToRefs(colorPaletteStore)
|
||||
const importCustomPalette = async () => {
|
||||
const palette = await colorPaletteService.importColorPalette()
|
||||
if (palette) {
|
||||
settingStore.set('Comfy.ColorPalette', palette.id)
|
||||
await settingStore.set('Comfy.ColorPalette', palette.id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div>
|
||||
{{ $t('g.currentUser') }}: {{ userStore.currentUser?.username }}
|
||||
</div>
|
||||
<Button icon="pi pi-sign-out" @click="logout" text />
|
||||
<Button icon="pi pi-sign-out" text @click="logout" />
|
||||
</div>
|
||||
</Message>
|
||||
</template>
|
||||
@@ -22,8 +22,8 @@ import Message from 'primevue/message'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const logout = () => {
|
||||
userStore.logout()
|
||||
const logout = async () => {
|
||||
await userStore.logout()
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -22,16 +22,16 @@
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
:label="$t('g.reloadToApplyChanges')"
|
||||
@click="applyChanges"
|
||||
outlined
|
||||
severity="danger"
|
||||
@click="applyChanges"
|
||||
/>
|
||||
</div>
|
||||
</Message>
|
||||
</template>
|
||||
<DataTable
|
||||
:value="extensionStore.extensions"
|
||||
stripedRows
|
||||
striped-rows
|
||||
size="small"
|
||||
:filters="filters"
|
||||
>
|
||||
@@ -61,8 +61,8 @@
|
||||
</template>
|
||||
<template #body="slotProps">
|
||||
<ToggleSwitch
|
||||
:disabled="extensionStore.isExtensionReadOnly(slotProps.data.name)"
|
||||
v-model="editingEnabledExtensions[slotProps.data.name]"
|
||||
:disabled="extensionStore.isExtensionReadOnly(slotProps.data.name)"
|
||||
@change="updateExtensionStatus"
|
||||
/>
|
||||
</template>
|
||||
@@ -116,44 +116,44 @@ const hasChanges = computed(() => {
|
||||
return changedExtensions.value.length > 0
|
||||
})
|
||||
|
||||
const updateExtensionStatus = () => {
|
||||
const updateExtensionStatus = async () => {
|
||||
const editingDisabledExtensionNames = Object.entries(
|
||||
editingEnabledExtensions.value
|
||||
)
|
||||
.filter(([_, enabled]) => !enabled)
|
||||
.map(([name]) => name)
|
||||
|
||||
settingStore.set('Comfy.Extension.Disabled', [
|
||||
await settingStore.set('Comfy.Extension.Disabled', [
|
||||
...extensionStore.inactiveDisabledExtensionNames,
|
||||
...editingDisabledExtensionNames
|
||||
])
|
||||
}
|
||||
|
||||
const enableAllExtensions = () => {
|
||||
const enableAllExtensions = async () => {
|
||||
extensionStore.extensions.forEach((ext) => {
|
||||
if (extensionStore.isExtensionReadOnly(ext.name)) return
|
||||
|
||||
editingEnabledExtensions.value[ext.name] = true
|
||||
})
|
||||
updateExtensionStatus()
|
||||
await updateExtensionStatus()
|
||||
}
|
||||
|
||||
const disableAllExtensions = () => {
|
||||
const disableAllExtensions = async () => {
|
||||
extensionStore.extensions.forEach((ext) => {
|
||||
if (extensionStore.isExtensionReadOnly(ext.name)) return
|
||||
|
||||
editingEnabledExtensions.value[ext.name] = false
|
||||
})
|
||||
updateExtensionStatus()
|
||||
await updateExtensionStatus()
|
||||
}
|
||||
|
||||
const disableThirdPartyExtensions = () => {
|
||||
const disableThirdPartyExtensions = async () => {
|
||||
extensionStore.extensions.forEach((ext) => {
|
||||
if (extensionStore.isCoreExtension(ext.name)) return
|
||||
|
||||
editingEnabledExtensions.value[ext.name] = false
|
||||
})
|
||||
updateExtensionStatus()
|
||||
await updateExtensionStatus()
|
||||
}
|
||||
|
||||
const applyChanges = () => {
|
||||
|
||||
@@ -18,9 +18,9 @@ import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const show = computed(() => !settingStore.exists('Comfy.UseNewMenu'))
|
||||
const handleClose = () => {
|
||||
const handleClose = async () => {
|
||||
// Explicitly write the current value to the store.
|
||||
const currentValue = settingStore.get('Comfy.UseNewMenu')
|
||||
settingStore.set('Comfy.UseNewMenu', currentValue)
|
||||
await settingStore.set('Comfy.UseNewMenu', currentValue)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -8,16 +8,16 @@
|
||||
</template>
|
||||
|
||||
<DataTable
|
||||
:value="commandsData"
|
||||
v-model:selection="selectedCommandData"
|
||||
:value="commandsData"
|
||||
:global-filter-fields="['id', 'label']"
|
||||
:filters="filters"
|
||||
selectionMode="single"
|
||||
stripedRows
|
||||
selection-mode="single"
|
||||
striped-rows
|
||||
:pt="{
|
||||
header: 'px-0'
|
||||
}"
|
||||
@rowDblclick="editKeybinding($event.data)"
|
||||
@row-dblclick="editKeybinding($event.data)"
|
||||
>
|
||||
<Column field="actions" header="">
|
||||
<template #body="slotProps">
|
||||
@@ -27,11 +27,19 @@
|
||||
class="p-button-text"
|
||||
@click="editKeybinding(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-replay"
|
||||
class="p-button-text p-button-warn"
|
||||
:disabled="
|
||||
!keybindingStore.isCommandKeybindingModified(slotProps.data.id)
|
||||
"
|
||||
@click="resetKeybinding(slotProps.data)"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
class="p-button-text p-button-danger"
|
||||
@click="removeKeybinding(slotProps.data)"
|
||||
:disabled="!slotProps.data.keybinding"
|
||||
@click="removeKeybinding(slotProps.data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -55,35 +63,41 @@
|
||||
<template #body="slotProps">
|
||||
<KeyComboDisplay
|
||||
v-if="slotProps.data.keybinding"
|
||||
:keyCombo="slotProps.data.keybinding.combo"
|
||||
:isModified="
|
||||
:key-combo="slotProps.data.keybinding.combo"
|
||||
:is-modified="
|
||||
keybindingStore.isCommandKeybindingModified(slotProps.data.id)
|
||||
"
|
||||
/>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="source" :header="$t('g.source')">
|
||||
<template #body="slotProps">
|
||||
<span class="overflow-hidden text-ellipsis">{{
|
||||
slotProps.data.source || '-'
|
||||
}}</span>
|
||||
</template>
|
||||
</Column>
|
||||
</DataTable>
|
||||
|
||||
<Dialog
|
||||
class="min-w-96"
|
||||
v-model:visible="editDialogVisible"
|
||||
class="min-w-96"
|
||||
modal
|
||||
:header="currentEditingCommand?.label"
|
||||
@hide="cancelEdit"
|
||||
>
|
||||
<div>
|
||||
<InputText
|
||||
class="mb-2 text-center"
|
||||
ref="keybindingInput"
|
||||
:modelValue="newBindingKeyCombo?.toString() ?? ''"
|
||||
class="mb-2 text-center"
|
||||
:model-value="newBindingKeyCombo?.toString() ?? ''"
|
||||
placeholder="Press keys for new binding"
|
||||
@keydown.stop.prevent="captureKeybinding"
|
||||
autocomplete="off"
|
||||
fluid
|
||||
:invalid="!!existingKeybindingOnCombo"
|
||||
@keydown.stop.prevent="captureKeybinding"
|
||||
/>
|
||||
<Message v-if="existingKeybindingOnCombo" severity="error">
|
||||
<Message v-if="existingKeybindingOnCombo" severity="warn">
|
||||
Keybinding already exists on
|
||||
<Tag
|
||||
severity="secondary"
|
||||
@@ -93,23 +107,23 @@
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button
|
||||
label="Save"
|
||||
icon="pi pi-check"
|
||||
@click="saveKeybinding"
|
||||
:disabled="!!existingKeybindingOnCombo"
|
||||
:label="existingKeybindingOnCombo ? 'Overwrite' : 'Save'"
|
||||
:icon="existingKeybindingOnCombo ? 'pi pi-pencil' : 'pi pi-check'"
|
||||
:severity="existingKeybindingOnCombo ? 'warn' : undefined"
|
||||
autofocus
|
||||
@click="saveKeybinding"
|
||||
/>
|
||||
</template>
|
||||
</Dialog>
|
||||
<Button
|
||||
v-tooltip="$t('g.resetAllKeybindingsTooltip')"
|
||||
class="mt-4"
|
||||
:label="$t('g.reset')"
|
||||
v-tooltip="$t('g.resetKeybindingsTooltip')"
|
||||
icon="pi pi-trash"
|
||||
:label="$t('g.resetAll')"
|
||||
icon="pi pi-replay"
|
||||
severity="danger"
|
||||
fluid
|
||||
text
|
||||
@click="resetKeybindings"
|
||||
@click="resetAllKeybindings"
|
||||
/>
|
||||
</PanelTemplate>
|
||||
</template>
|
||||
@@ -153,6 +167,7 @@ interface ICommandData {
|
||||
id: string
|
||||
keybinding: KeybindingImpl | null
|
||||
label: string
|
||||
source?: string
|
||||
}
|
||||
|
||||
const commandsData = computed<ICommandData[]>(() => {
|
||||
@@ -162,7 +177,8 @@ const commandsData = computed<ICommandData[]>(() => {
|
||||
`commands.${normalizeI18nKey(command.id)}.label`,
|
||||
command.label ?? ''
|
||||
),
|
||||
keybinding: keybindingStore.getKeybindingByCommandId(command.id)
|
||||
keybinding: keybindingStore.getKeybindingByCommandId(command.id),
|
||||
source: command.source
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -211,14 +227,14 @@ watchEffect(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function removeKeybinding(commandData: ICommandData) {
|
||||
async function removeKeybinding(commandData: ICommandData) {
|
||||
if (commandData.keybinding) {
|
||||
keybindingStore.unsetKeybinding(commandData.keybinding)
|
||||
keybindingService.persistUserKeybindings()
|
||||
await keybindingService.persistUserKeybindings()
|
||||
}
|
||||
}
|
||||
|
||||
function captureKeybinding(event: KeyboardEvent) {
|
||||
async function captureKeybinding(event: KeyboardEvent) {
|
||||
// Allow the use of keyboard shortcuts when adding keyboard shortcuts
|
||||
if (!event.shiftKey && !event.altKey && !event.ctrlKey && !event.metaKey) {
|
||||
switch (event.key) {
|
||||
@@ -226,7 +242,7 @@ function captureKeybinding(event: KeyboardEvent) {
|
||||
cancelEdit()
|
||||
return
|
||||
case 'Enter':
|
||||
saveKeybinding()
|
||||
await saveKeybinding()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -240,7 +256,7 @@ function cancelEdit() {
|
||||
newBindingKeyCombo.value = null
|
||||
}
|
||||
|
||||
function saveKeybinding() {
|
||||
async function saveKeybinding() {
|
||||
if (currentEditingCommand.value && newBindingKeyCombo.value) {
|
||||
const updated = keybindingStore.updateKeybindingOnCommand(
|
||||
new KeybindingImpl({
|
||||
@@ -249,20 +265,30 @@ function saveKeybinding() {
|
||||
})
|
||||
)
|
||||
if (updated) {
|
||||
keybindingService.persistUserKeybindings()
|
||||
await keybindingService.persistUserKeybindings()
|
||||
}
|
||||
}
|
||||
cancelEdit()
|
||||
}
|
||||
|
||||
async function resetKeybinding(commandData: ICommandData) {
|
||||
if (keybindingStore.resetKeybindingForCommand(commandData.id)) {
|
||||
await keybindingService.persistUserKeybindings()
|
||||
} else {
|
||||
console.warn(
|
||||
`No changes made when resetting keybinding for command: ${commandData.id}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
async function resetKeybindings() {
|
||||
keybindingStore.resetKeybindings()
|
||||
async function resetAllKeybindings() {
|
||||
keybindingStore.resetAllKeybindings()
|
||||
await keybindingService.persistUserKeybindings()
|
||||
toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Info',
|
||||
detail: 'Keybindings reset',
|
||||
detail: 'All keybindings reset',
|
||||
life: 3000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
:label="$t('serverConfig.revertChanges')"
|
||||
@click="revertChanges"
|
||||
outlined
|
||||
@click="revertChanges"
|
||||
/>
|
||||
<Button
|
||||
:label="$t('serverConfig.restart')"
|
||||
@click="restartApp"
|
||||
outlined
|
||||
severity="danger"
|
||||
@click="restartApp"
|
||||
/>
|
||||
</div>
|
||||
</Message>
|
||||
@@ -37,9 +37,9 @@
|
||||
<p>{{ commandLineArgs }}</p>
|
||||
<Button
|
||||
icon="pi pi-clipboard"
|
||||
@click="copyCommandLineArgs"
|
||||
severity="secondary"
|
||||
text
|
||||
@click="copyCommandLineArgs"
|
||||
/>
|
||||
</div>
|
||||
</Message>
|
||||
@@ -53,10 +53,10 @@
|
||||
<h3>{{ $t(`serverConfigCategories.${label}`, label) }}</h3>
|
||||
<div v-for="item in items" :key="item.name" class="mb-4">
|
||||
<FormItem
|
||||
:item="translateItem(item)"
|
||||
v-model:formValue="item.value"
|
||||
:id="item.id"
|
||||
:labelClass="{
|
||||
v-model:formValue="item.value"
|
||||
:item="translateItem(item)"
|
||||
:label-class="{
|
||||
'text-highlight': item.initialValue !== item.value
|
||||
}"
|
||||
/>
|
||||
@@ -97,16 +97,16 @@ const revertChanges = () => {
|
||||
serverConfigStore.revertChanges()
|
||||
}
|
||||
|
||||
const restartApp = () => {
|
||||
electronAPI().restartApp()
|
||||
const restartApp = async () => {
|
||||
await electronAPI().restartApp()
|
||||
}
|
||||
|
||||
watch(launchArgs, (newVal) => {
|
||||
settingStore.set('Comfy.Server.LaunchArgs', newVal)
|
||||
watch(launchArgs, async (newVal) => {
|
||||
await settingStore.set('Comfy.Server.LaunchArgs', newVal)
|
||||
})
|
||||
|
||||
watch(serverConfigValues, (newVal) => {
|
||||
settingStore.set('Comfy.Server.ServerConfigValues', newVal)
|
||||
watch(serverConfigValues, async (newVal) => {
|
||||
await settingStore.set('Comfy.Server.ServerConfigValues', newVal)
|
||||
})
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<FormItem
|
||||
:item="formItem"
|
||||
:id="setting.id"
|
||||
:formValue="settingValue"
|
||||
@update:formValue="updateSettingValue"
|
||||
:item="formItem"
|
||||
:form-value="settingValue"
|
||||
@update:form-value="updateSettingValue"
|
||||
>
|
||||
<template #name-prefix>
|
||||
<Tag v-if="setting.id === 'Comfy.Locale'" class="pi pi-language" />
|
||||
<Tag v-if="setting.experimental" :value="$t('g.experimental')" />
|
||||
<Tag
|
||||
v-if="setting.deprecated"
|
||||
@@ -68,7 +69,7 @@ const formItem = computed(() => {
|
||||
|
||||
const settingStore = useSettingStore()
|
||||
const settingValue = computed(() => settingStore.get(props.setting.id))
|
||||
const updateSettingValue = (value: any) => {
|
||||
settingStore.set(props.setting.id, value)
|
||||
const updateSettingValue = async (value: any) => {
|
||||
await settingStore.set(props.setting.id, value)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -70,6 +70,7 @@ import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { api } from '@/scripts/api'
|
||||
import { useComfyManagerService } from '@/services/comfyManagerService'
|
||||
import { useWorkflowService } from '@/services/workflowService'
|
||||
import {
|
||||
useComfyManagerStore,
|
||||
useManagerProgressDialogStore
|
||||
@@ -96,14 +97,21 @@ const currentTaskName = computed(() => {
|
||||
})
|
||||
|
||||
const handleRestart = async () => {
|
||||
await useComfyManagerService().rebootComfyUI()
|
||||
closeDialog()
|
||||
const onReconnect = async () => {
|
||||
// Refresh manager state
|
||||
|
||||
const onReconnect = () => {
|
||||
useCommandStore().execute('Comfy.RefreshNodeDefinitions')
|
||||
comfyManagerStore.clearLogs()
|
||||
comfyManagerStore.setStale()
|
||||
|
||||
// Refresh node definitions
|
||||
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
|
||||
|
||||
// Reload workflow
|
||||
await useWorkflowService().reloadCurrentWorkflow()
|
||||
}
|
||||
useEventListener(api, 'reconnected', onReconnect, { once: true })
|
||||
|
||||
await useComfyManagerService().rebootComfyUI()
|
||||
closeDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2 class="px-4">
|
||||
<i class="pi pi-cog"></i>
|
||||
<i class="pi pi-cog" />
|
||||
<span>{{ $t('g.settings') }}</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<TitleEditor />
|
||||
<GraphCanvasMenu v-if="!betaMenuEnabled && canvasMenuEnabled" />
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
id="graph-canvas"
|
||||
ref="canvasRef"
|
||||
tabindex="1"
|
||||
class="w-full h-full touch-none"
|
||||
/>
|
||||
@@ -150,16 +150,16 @@ const colorPaletteService = useColorPaletteService()
|
||||
const colorPaletteStore = useColorPaletteStore()
|
||||
watch(
|
||||
[() => canvasStore.canvas, () => settingStore.get('Comfy.ColorPalette')],
|
||||
([canvas, currentPaletteId]) => {
|
||||
async ([canvas, currentPaletteId]) => {
|
||||
if (!canvas) return
|
||||
|
||||
colorPaletteService.loadColorPalette(currentPaletteId)
|
||||
await colorPaletteService.loadColorPalette(currentPaletteId)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => colorPaletteStore.activePaletteId,
|
||||
(newValue) => {
|
||||
settingStore.set('Comfy.ColorPalette', newValue)
|
||||
async (newValue) => {
|
||||
await settingStore.set('Comfy.ColorPalette', newValue)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -292,7 +292,7 @@ onMounted(async () => {
|
||||
() => settingStore.get('Comfy.Locale'),
|
||||
async () => {
|
||||
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
|
||||
useWorkflowService().reloadCurrentWorkflow()
|
||||
await useWorkflowService().reloadCurrentWorkflow()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,36 +3,36 @@
|
||||
class="p-buttongroup-vertical absolute bottom-[10px] right-[10px] z-[1000]"
|
||||
>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomIn')"
|
||||
severity="secondary"
|
||||
icon="pi pi-plus"
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomIn')"
|
||||
:aria-label="$t('graphCanvasMenu.zoomIn')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomIn')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomOut')"
|
||||
severity="secondary"
|
||||
icon="pi pi-minus"
|
||||
v-tooltip.left="t('graphCanvasMenu.zoomOut')"
|
||||
:aria-label="$t('graphCanvasMenu.zoomOut')"
|
||||
@mousedown="repeat('Comfy.Canvas.ZoomOut')"
|
||||
@mouseup="stopRepeat"
|
||||
/>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.fitView')"
|
||||
severity="secondary"
|
||||
icon="pi pi-expand"
|
||||
v-tooltip.left="t('graphCanvasMenu.fitView')"
|
||||
:aria-label="$t('graphCanvasMenu.fitView')"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.FitView')"
|
||||
/>
|
||||
<Button
|
||||
severity="secondary"
|
||||
v-tooltip.left="
|
||||
t(
|
||||
'graphCanvasMenu.' +
|
||||
(canvasStore.canvas?.read_only ? 'panMode' : 'selectMode')
|
||||
) + ' (Space)'
|
||||
"
|
||||
severity="secondary"
|
||||
:aria-label="
|
||||
t(
|
||||
'graphCanvasMenu.' +
|
||||
@@ -49,12 +49,12 @@
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
v-tooltip.left="t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
severity="secondary"
|
||||
:icon="linkHidden ? 'pi pi-eye-slash' : 'pi pi-eye'"
|
||||
v-tooltip.left="t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
:aria-label="$t('graphCanvasMenu.toggleLinkVisibility')"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
data-testid="toggle-link-visibility-button"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleLinkVisibility')"
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</template>
|
||||
@@ -80,10 +80,10 @@ const linkHidden = computed(
|
||||
)
|
||||
|
||||
let interval: number | null = null
|
||||
const repeat = (command: string) => {
|
||||
const repeat = async (command: string) => {
|
||||
if (interval) return
|
||||
const cmd = () => commandStore.execute(command)
|
||||
cmd()
|
||||
await cmd()
|
||||
interval = window.setInterval(cmd, 100)
|
||||
}
|
||||
const stopRepeat = () => {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<!-- This component is used to bound the selected items on the canvas. -->
|
||||
<template>
|
||||
<div
|
||||
v-show="visible"
|
||||
class="selection-overlay-container pointer-events-none z-40"
|
||||
:class="{
|
||||
'show-border': showBorder
|
||||
}"
|
||||
:style="style"
|
||||
v-show="visible"
|
||||
>
|
||||
<slot></slot>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@
|
||||
<ColorPickerButton v-show="nodeSelected || groupSelected" />
|
||||
<Button
|
||||
v-show="nodeSelected"
|
||||
severity="secondary"
|
||||
text
|
||||
@click="
|
||||
() => commandStore.execute('Comfy.Canvas.ToggleSelectedNodes.Bypass')
|
||||
"
|
||||
data-testid="bypass-button"
|
||||
v-tooltip.top="{
|
||||
value: t('commands.Comfy_Canvas_ToggleSelectedNodes_Bypass.label'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
severity="secondary"
|
||||
text
|
||||
data-testid="bypass-button"
|
||||
@click="
|
||||
() => commandStore.execute('Comfy.Canvas.ToggleSelectedNodes.Bypass')
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<i-game-icons:detour />
|
||||
@@ -26,24 +26,24 @@
|
||||
</Button>
|
||||
<Button
|
||||
v-show="nodeSelected || groupSelected"
|
||||
severity="secondary"
|
||||
text
|
||||
icon="pi pi-thumbtack"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleSelected.Pin')"
|
||||
v-tooltip.top="{
|
||||
value: t('commands.Comfy_Canvas_ToggleSelectedNodes_Pin.label'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
severity="secondary"
|
||||
text
|
||||
icon="pi pi-thumbtack"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.ToggleSelected.Pin')"
|
||||
/>
|
||||
<Button
|
||||
severity="danger"
|
||||
text
|
||||
icon="pi pi-trash"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')"
|
||||
v-tooltip.top="{
|
||||
value: t('commands.Comfy_Canvas_DeleteSelectedItems.label'),
|
||||
showDelay: 1000
|
||||
}"
|
||||
severity="danger"
|
||||
text
|
||||
icon="pi pi-trash"
|
||||
@click="() => commandStore.execute('Comfy.Canvas.DeleteSelectedItems')"
|
||||
/>
|
||||
<Button
|
||||
v-show="isRefreshable"
|
||||
@@ -55,15 +55,15 @@
|
||||
<Button
|
||||
v-for="command in extensionToolboxCommands"
|
||||
:key="command.id"
|
||||
severity="secondary"
|
||||
text
|
||||
:icon="typeof command.icon === 'function' ? command.icon() : command.icon"
|
||||
@click="() => commandStore.execute(command.id)"
|
||||
v-tooltip.top="{
|
||||
value:
|
||||
st(`commands.${normalizeI18nKey(command.id)}.label`, '') || undefined,
|
||||
showDelay: 1000
|
||||
}"
|
||||
severity="secondary"
|
||||
text
|
||||
:icon="typeof command.icon === 'function' ? command.icon() : command.icon"
|
||||
@click="() => commandStore.execute(command.id)"
|
||||
/>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
:style="inputStyle"
|
||||
>
|
||||
<EditableText
|
||||
:isEditing="showInput"
|
||||
:modelValue="editedTitle"
|
||||
:is-editing="showInput"
|
||||
:model-value="editedTitle"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
class="color-picker-container absolute -top-10 left-1/2"
|
||||
>
|
||||
<SelectButton
|
||||
:modelValue="selectedColorOption"
|
||||
@update:modelValue="applyColor"
|
||||
:model-value="selectedColorOption"
|
||||
:options="colorOptions"
|
||||
optionLabel="name"
|
||||
dataKey="value"
|
||||
option-label="name"
|
||||
data-key="value"
|
||||
@update:model-value="applyColor"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<i
|
||||
v-tooltip.top="option.localizedName"
|
||||
class="pi pi-circle-fill"
|
||||
:style="{
|
||||
color: isLightTheme ? option.value.light : option.value.dark
|
||||
}"
|
||||
v-tooltip.top="option.localizedName"
|
||||
:data-testid="option.name"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div
|
||||
v-show="widgetState.visible"
|
||||
ref="widgetElement"
|
||||
class="dom-widget"
|
||||
:title="tooltip"
|
||||
ref="widgetElement"
|
||||
:style="style"
|
||||
v-show="widgetState.visible"
|
||||
>
|
||||
<component
|
||||
v-if="isComponentWidget(widget)"
|
||||
:is="widget.component"
|
||||
:modelValue="widget.value"
|
||||
@update:modelValue="emit('update:widgetValue', $event)"
|
||||
v-if="isComponentWidget(widget)"
|
||||
:model-value="widget.value"
|
||||
:widget="widget"
|
||||
@update:model-value="emit('update:widgetValue', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:options="options"
|
||||
filter
|
||||
:placeholder="placeholder"
|
||||
:maxSelectedLabels="3"
|
||||
:max-selected-labels="3"
|
||||
:display="display"
|
||||
class="w-full"
|
||||
/>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
icon="pi pi-exclamation-triangle"
|
||||
severity="warn"
|
||||
:value="t('icon.exclamation-triangle')"
|
||||
></Tag>
|
||||
/>
|
||||
{{ $t('install.gpuSelection.cpuModeDescription') }}
|
||||
</p>
|
||||
<p class="m-1">
|
||||
@@ -119,7 +119,7 @@
|
||||
>
|
||||
<ToggleSwitch
|
||||
v-model="cpuMode"
|
||||
inputId="cpu-mode"
|
||||
input-id="cpu-mode"
|
||||
class="-translate-y-40"
|
||||
/>
|
||||
<label for="cpu-mode" class="select-none">
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
v-model="installPath"
|
||||
class="w-full"
|
||||
:class="{ 'p-invalid': pathError }"
|
||||
@update:modelValue="validatePath"
|
||||
@update:model-value="validatePath"
|
||||
@focus="onFocus"
|
||||
/>
|
||||
<InputIcon
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip.top="$t('install.installLocationTooltip')"
|
||||
class="pi pi-info-circle"
|
||||
/>
|
||||
</IconField>
|
||||
<Button icon="pi pi-folder" @click="browsePath" class="w-12" />
|
||||
<Button icon="pi pi-folder" class="w-12" @click="browsePath" />
|
||||
</div>
|
||||
|
||||
<Message v-if="pathError" severity="error" class="whitespace-pre-line">
|
||||
@@ -49,18 +49,18 @@
|
||||
<span class="text-neutral-400">App Data:</span>
|
||||
<span class="text-neutral-200">{{ appData }}</span>
|
||||
<span
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip="$t('install.appDataLocationTooltip')"
|
||||
></span>
|
||||
class="pi pi-info-circle"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="pi pi-desktop text-neutral-400" />
|
||||
<span class="text-neutral-400">App Path:</span>
|
||||
<span class="text-neutral-200">{{ appPath }}</span>
|
||||
<span
|
||||
class="pi pi-info-circle"
|
||||
v-tooltip="$t('install.appPathLocationTooltip')"
|
||||
></span>
|
||||
class="pi pi-info-circle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,12 +142,12 @@ const browsePath = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onFocus = () => {
|
||||
const onFocus = async () => {
|
||||
if (!inputTouched.value) {
|
||||
inputTouched.value = true
|
||||
return
|
||||
}
|
||||
// Refresh validation on re-focus
|
||||
validatePath(installPath.value)
|
||||
await validatePath(installPath.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
placeholder="Select existing ComfyUI installation (optional)"
|
||||
class="flex-1"
|
||||
:class="{ 'p-invalid': pathError }"
|
||||
@update:modelValue="validateSource"
|
||||
@update:model-value="validateSource"
|
||||
/>
|
||||
<Button icon="pi pi-folder" @click="browsePath" class="w-12" />
|
||||
<Button icon="pi pi-folder" class="w-12" @click="browsePath" />
|
||||
</div>
|
||||
|
||||
<Message v-if="pathError" severity="error">
|
||||
@@ -44,7 +44,7 @@
|
||||
>
|
||||
<Checkbox
|
||||
v-model="item.selected"
|
||||
:inputId="item.id"
|
||||
:input-id="item.id"
|
||||
:binary="true"
|
||||
@click.stop
|
||||
/>
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
<Divider v-if="index > 0" />
|
||||
|
||||
<MirrorItem
|
||||
:item="item"
|
||||
v-model="modelValue.value"
|
||||
:item="item"
|
||||
@state-change="validationStates[index] = $event"
|
||||
/>
|
||||
</template>
|
||||
<template #icons>
|
||||
<i
|
||||
v-tooltip="validationStateTooltip"
|
||||
:class="{
|
||||
'pi pi-spin pi-spinner text-neutral-400':
|
||||
validationState === ValidationState.LOADING,
|
||||
@@ -27,7 +28,6 @@
|
||||
'pi pi-times text-red-500':
|
||||
validationState === ValidationState.INVALID
|
||||
}"
|
||||
v-tooltip="validationStateTooltip"
|
||||
/>
|
||||
</template>
|
||||
</Panel>
|
||||
|
||||
@@ -7,55 +7,55 @@
|
||||
<Load3DScene
|
||||
ref="load3DSceneRef"
|
||||
:node="node"
|
||||
:inputSpec="inputSpec"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:lightIntensity="lightIntensity"
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:light-intensity="lightIntensity"
|
||||
:fov="fov"
|
||||
:cameraType="cameraType"
|
||||
:showPreview="showPreview"
|
||||
:backgroundImage="backgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
:edgeThreshold="edgeThreshold"
|
||||
@materialModeChange="listenMaterialModeChange"
|
||||
@backgroundColorChange="listenBackgroundColorChange"
|
||||
@lightIntensityChange="listenLightIntensityChange"
|
||||
@fovChange="listenFOVChange"
|
||||
@cameraTypeChange="listenCameraTypeChange"
|
||||
@showGridChange="listenShowGridChange"
|
||||
@showPreviewChange="listenShowPreviewChange"
|
||||
@backgroundImageChange="listenBackgroundImageChange"
|
||||
@upDirectionChange="listenUpDirectionChange"
|
||||
@edgeThresholdChange="listenEdgeThresholdChange"
|
||||
:camera-type="cameraType"
|
||||
:show-preview="showPreview"
|
||||
:background-image="backgroundImage"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
:edge-threshold="edgeThreshold"
|
||||
@material-mode-change="listenMaterialModeChange"
|
||||
@background-color-change="listenBackgroundColorChange"
|
||||
@light-intensity-change="listenLightIntensityChange"
|
||||
@fov-change="listenFOVChange"
|
||||
@camera-type-change="listenCameraTypeChange"
|
||||
@show-grid-change="listenShowGridChange"
|
||||
@show-preview-change="listenShowPreviewChange"
|
||||
@background-image-change="listenBackgroundImageChange"
|
||||
@up-direction-change="listenUpDirectionChange"
|
||||
@edge-threshold-change="listenEdgeThresholdChange"
|
||||
/>
|
||||
<Load3DControls
|
||||
:inputSpec="inputSpec"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:showPreview="showPreview"
|
||||
:lightIntensity="lightIntensity"
|
||||
:showLightIntensityButton="showLightIntensityButton"
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:show-preview="showPreview"
|
||||
:light-intensity="lightIntensity"
|
||||
:show-light-intensity-button="showLightIntensityButton"
|
||||
:fov="fov"
|
||||
:showFOVButton="showFOVButton"
|
||||
:showPreviewButton="showPreviewButton"
|
||||
:cameraType="cameraType"
|
||||
:hasBackgroundImage="hasBackgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
:edgeThreshold="edgeThreshold"
|
||||
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||
@switchCamera="switchCamera"
|
||||
@toggleGrid="toggleGrid"
|
||||
@updateBackgroundColor="handleBackgroundColorChange"
|
||||
@updateLightIntensity="handleUpdateLightIntensity"
|
||||
@togglePreview="togglePreview"
|
||||
@updateFOV="handleUpdateFOV"
|
||||
@updateUpDirection="handleUpdateUpDirection"
|
||||
@updateMaterialMode="handleUpdateMaterialMode"
|
||||
@updateEdgeThreshold="handleUpdateEdgeThreshold"
|
||||
@uploadTexture="handleUploadTexture"
|
||||
@exportModel="handleExportModel"
|
||||
:show-f-o-v-button="showFOVButton"
|
||||
:show-preview-button="showPreviewButton"
|
||||
:camera-type="cameraType"
|
||||
:has-background-image="hasBackgroundImage"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
:edge-threshold="edgeThreshold"
|
||||
@update-background-image="handleBackgroundImageUpdate"
|
||||
@switch-camera="switchCamera"
|
||||
@toggle-grid="toggleGrid"
|
||||
@update-background-color="handleBackgroundColorChange"
|
||||
@update-light-intensity="handleUpdateLightIntensity"
|
||||
@toggle-preview="togglePreview"
|
||||
@update-f-o-v="handleUpdateFOV"
|
||||
@update-up-direction="handleUpdateUpDirection"
|
||||
@update-material-mode="handleUpdateMaterialMode"
|
||||
@update-edge-threshold="handleUpdateEdgeThreshold"
|
||||
@upload-texture="handleUploadTexture"
|
||||
@export-model="handleExportModel"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,63 +7,63 @@
|
||||
<Load3DAnimationScene
|
||||
ref="load3DAnimationSceneRef"
|
||||
:node="node"
|
||||
:inputSpec="inputSpec"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:lightIntensity="lightIntensity"
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:light-intensity="lightIntensity"
|
||||
:fov="fov"
|
||||
:cameraType="cameraType"
|
||||
:showPreview="showPreview"
|
||||
:showFOVButton="showFOVButton"
|
||||
:showLightIntensityButton="showLightIntensityButton"
|
||||
:camera-type="cameraType"
|
||||
:show-preview="showPreview"
|
||||
:show-f-o-v-button="showFOVButton"
|
||||
:show-light-intensity-button="showLightIntensityButton"
|
||||
:playing="playing"
|
||||
:selectedSpeed="selectedSpeed"
|
||||
:selectedAnimation="selectedAnimation"
|
||||
:backgroundImage="backgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
@materialModeChange="listenMaterialModeChange"
|
||||
@backgroundColorChange="listenBackgroundColorChange"
|
||||
@lightIntensityChange="listenLightIntensityChange"
|
||||
@fovChange="listenFOVChange"
|
||||
@cameraTypeChange="listenCameraTypeChange"
|
||||
@showGridChange="listenShowGridChange"
|
||||
@showPreviewChange="listenShowPreviewChange"
|
||||
@backgroundImageChange="listenBackgroundImageChange"
|
||||
@animationListChange="animationListChange"
|
||||
@upDirectionChange="listenUpDirectionChange"
|
||||
:selected-speed="selectedSpeed"
|
||||
:selected-animation="selectedAnimation"
|
||||
:background-image="backgroundImage"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
@material-mode-change="listenMaterialModeChange"
|
||||
@background-color-change="listenBackgroundColorChange"
|
||||
@light-intensity-change="listenLightIntensityChange"
|
||||
@fov-change="listenFOVChange"
|
||||
@camera-type-change="listenCameraTypeChange"
|
||||
@show-grid-change="listenShowGridChange"
|
||||
@show-preview-change="listenShowPreviewChange"
|
||||
@background-image-change="listenBackgroundImageChange"
|
||||
@animation-list-change="animationListChange"
|
||||
@up-direction-change="listenUpDirectionChange"
|
||||
/>
|
||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||
<Load3DControls
|
||||
:inputSpec="inputSpec"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:showPreview="showPreview"
|
||||
:lightIntensity="lightIntensity"
|
||||
:showLightIntensityButton="showLightIntensityButton"
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:show-preview="showPreview"
|
||||
:light-intensity="lightIntensity"
|
||||
:show-light-intensity-button="showLightIntensityButton"
|
||||
:fov="fov"
|
||||
:showFOVButton="showFOVButton"
|
||||
:showPreviewButton="showPreviewButton"
|
||||
:cameraType="cameraType"
|
||||
:hasBackgroundImage="hasBackgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||
@switchCamera="switchCamera"
|
||||
@toggleGrid="toggleGrid"
|
||||
@updateBackgroundColor="handleBackgroundColorChange"
|
||||
@updateLightIntensity="handleUpdateLightIntensity"
|
||||
@togglePreview="togglePreview"
|
||||
@updateFOV="handleUpdateFOV"
|
||||
@updateUpDirection="handleUpdateUpDirection"
|
||||
@updateMaterialMode="handleUpdateMaterialMode"
|
||||
:show-f-o-v-button="showFOVButton"
|
||||
:show-preview-button="showPreviewButton"
|
||||
:camera-type="cameraType"
|
||||
:has-background-image="hasBackgroundImage"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
@update-background-image="handleBackgroundImageUpdate"
|
||||
@switch-camera="switchCamera"
|
||||
@toggle-grid="toggleGrid"
|
||||
@update-background-color="handleBackgroundColorChange"
|
||||
@update-light-intensity="handleUpdateLightIntensity"
|
||||
@toggle-preview="togglePreview"
|
||||
@update-f-o-v="handleUpdateFOV"
|
||||
@update-up-direction="handleUpdateUpDirection"
|
||||
@update-material-mode="handleUpdateMaterialMode"
|
||||
/>
|
||||
<Load3DAnimationControls
|
||||
:animations="animations"
|
||||
:playing="playing"
|
||||
@togglePlay="togglePlay"
|
||||
@speedChange="speedChange"
|
||||
@animationChange="animationChange"
|
||||
@toggle-play="togglePlay"
|
||||
@speed-change="speedChange"
|
||||
@animation-change="animationChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,25 +6,25 @@
|
||||
<Button class="p-button-rounded p-button-text" @click="togglePlay">
|
||||
<i
|
||||
:class="['pi', playing ? 'pi-pause' : 'pi-play', 'text-white text-lg']"
|
||||
></i>
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Select
|
||||
v-model="selectedSpeed"
|
||||
:options="speedOptions"
|
||||
optionLabel="name"
|
||||
optionValue="value"
|
||||
@change="speedChange"
|
||||
option-label="name"
|
||||
option-value="value"
|
||||
class="w-24"
|
||||
@change="speedChange"
|
||||
/>
|
||||
|
||||
<Select
|
||||
v-model="selectedAnimation"
|
||||
:options="animations"
|
||||
optionLabel="name"
|
||||
optionValue="index"
|
||||
@change="animationChange"
|
||||
option-label="name"
|
||||
option-value="index"
|
||||
class="w-32"
|
||||
@change="animationChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
<template>
|
||||
<Load3DScene
|
||||
:node="node"
|
||||
:inputSpec="inputSpec"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:lightIntensity="lightIntensity"
|
||||
:fov="fov"
|
||||
:cameraType="cameraType"
|
||||
:showPreview="showPreview"
|
||||
:extraListeners="animationListeners"
|
||||
:backgroundImage="backgroundImage"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
@materialModeChange="listenMaterialModeChange"
|
||||
@backgroundColorChange="listenBackgroundColorChange"
|
||||
@lightIntensityChange="listenLightIntensityChange"
|
||||
@fovChange="listenFOVChange"
|
||||
@cameraTypeChange="listenCameraTypeChange"
|
||||
@showGridChange="listenShowGridChange"
|
||||
@showPreviewChange="listenShowPreviewChange"
|
||||
ref="load3DSceneRef"
|
||||
:node="node"
|
||||
:input-spec="inputSpec"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:light-intensity="lightIntensity"
|
||||
:fov="fov"
|
||||
:camera-type="cameraType"
|
||||
:show-preview="showPreview"
|
||||
:extra-listeners="animationListeners"
|
||||
:background-image="backgroundImage"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
@material-mode-change="listenMaterialModeChange"
|
||||
@background-color-change="listenBackgroundColorChange"
|
||||
@light-intensity-change="listenLightIntensityChange"
|
||||
@fov-change="listenFOVChange"
|
||||
@camera-type-change="listenCameraTypeChange"
|
||||
@show-grid-change="listenShowGridChange"
|
||||
@show-preview-change="listenShowPreviewChange"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
class="p-button-rounded p-button-text bg-opacity-30"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<i class="pi pi-bars text-white text-lg"></i>
|
||||
<i class="pi pi-bars text-white text-lg" />
|
||||
</Button>
|
||||
|
||||
<div
|
||||
@@ -22,7 +22,7 @@
|
||||
:class="{ 'bg-gray-600': activeCategory === category }"
|
||||
@click="selectCategory(category)"
|
||||
>
|
||||
<i :class="getCategoryIcon(category)"></i>
|
||||
<i :class="getCategoryIcon(category)" />
|
||||
<span class="text-white">{{ t(categoryLabels[category]) }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -32,62 +32,62 @@
|
||||
<div v-show="activeCategory" class="bg-gray-700 bg-opacity-30 rounded-lg">
|
||||
<SceneControls
|
||||
v-if="activeCategory === 'scene'"
|
||||
:backgroundColor="backgroundColor"
|
||||
:showGrid="showGrid"
|
||||
:hasBackgroundImage="hasBackgroundImage"
|
||||
@toggleGrid="handleToggleGrid"
|
||||
@updateBackgroundColor="handleBackgroundColorChange"
|
||||
@updateBackgroundImage="handleBackgroundImageUpdate"
|
||||
ref="sceneControlsRef"
|
||||
:background-color="backgroundColor"
|
||||
:show-grid="showGrid"
|
||||
:has-background-image="hasBackgroundImage"
|
||||
@toggle-grid="handleToggleGrid"
|
||||
@update-background-color="handleBackgroundColorChange"
|
||||
@update-background-image="handleBackgroundImageUpdate"
|
||||
/>
|
||||
|
||||
<ModelControls
|
||||
v-if="activeCategory === 'model'"
|
||||
:inputSpec="inputSpec"
|
||||
:upDirection="upDirection"
|
||||
:materialMode="materialMode"
|
||||
:edgeThreshold="edgeThreshold"
|
||||
@updateUpDirection="handleUpdateUpDirection"
|
||||
@updateMaterialMode="handleUpdateMaterialMode"
|
||||
@updateEdgeThreshold="handleUpdateEdgeThreshold"
|
||||
@uploadTexture="handleUploadTexture"
|
||||
ref="modelControlsRef"
|
||||
:input-spec="inputSpec"
|
||||
:up-direction="upDirection"
|
||||
:material-mode="materialMode"
|
||||
:edge-threshold="edgeThreshold"
|
||||
@update-up-direction="handleUpdateUpDirection"
|
||||
@update-material-mode="handleUpdateMaterialMode"
|
||||
@update-edge-threshold="handleUpdateEdgeThreshold"
|
||||
@upload-texture="handleUploadTexture"
|
||||
/>
|
||||
|
||||
<CameraControls
|
||||
v-if="activeCategory === 'camera'"
|
||||
:cameraType="cameraType"
|
||||
:fov="fov"
|
||||
:showFOVButton="showFOVButton"
|
||||
@switchCamera="switchCamera"
|
||||
@updateFOV="handleUpdateFOV"
|
||||
ref="cameraControlsRef"
|
||||
:camera-type="cameraType"
|
||||
:fov="fov"
|
||||
:show-f-o-v-button="showFOVButton"
|
||||
@switch-camera="switchCamera"
|
||||
@update-f-o-v="handleUpdateFOV"
|
||||
/>
|
||||
|
||||
<LightControls
|
||||
v-if="activeCategory === 'light'"
|
||||
:lightIntensity="lightIntensity"
|
||||
:showLightIntensityButton="showLightIntensityButton"
|
||||
@updateLightIntensity="handleUpdateLightIntensity"
|
||||
ref="lightControlsRef"
|
||||
:light-intensity="lightIntensity"
|
||||
:show-light-intensity-button="showLightIntensityButton"
|
||||
@update-light-intensity="handleUpdateLightIntensity"
|
||||
/>
|
||||
|
||||
<ExportControls
|
||||
v-if="activeCategory === 'export'"
|
||||
@exportModel="handleExportModel"
|
||||
ref="exportControlsRef"
|
||||
@export-model="handleExportModel"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="showPreviewButton">
|
||||
<Button class="p-button-rounded p-button-text" @click="togglePreview">
|
||||
<i
|
||||
v-tooltip.right="{ value: t('load3d.previewOutput'), showDelay: 300 }"
|
||||
:class="[
|
||||
'pi',
|
||||
showPreview ? 'pi-eye' : 'pi-eye-slash',
|
||||
'text-white text-lg'
|
||||
]"
|
||||
v-tooltip.right="{ value: t('load3d.previewOutput'), showDelay: 300 }"
|
||||
></i>
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -71,7 +71,7 @@ const eventConfig = {
|
||||
textureLoadingEnd: () => loadingOverlayRef.value?.endLoading()
|
||||
} as const
|
||||
|
||||
watchEffect(() => {
|
||||
watchEffect(async () => {
|
||||
if (load3d.value) {
|
||||
const rawLoad3d = toRaw(load3d.value)
|
||||
|
||||
@@ -81,7 +81,7 @@ watchEffect(() => {
|
||||
rawLoad3d.setFOV(props.fov)
|
||||
rawLoad3d.toggleCamera(props.cameraType)
|
||||
rawLoad3d.togglePreview(props.showPreview)
|
||||
rawLoad3d.setBackgroundImage(props.backgroundImage)
|
||||
await rawLoad3d.setBackgroundImage(props.backgroundImage)
|
||||
rawLoad3d.setUpDirection(props.upDirection)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="spinner"></div>
|
||||
<div class="spinner" />
|
||||
<div class="text-white mt-4 text-lg">
|
||||
{{ loadingMessage }}
|
||||
</div>
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
<div class="flex flex-col">
|
||||
<Button class="p-button-rounded p-button-text" @click="switchCamera">
|
||||
<i
|
||||
:class="['pi', getCameraIcon, 'text-white text-lg']"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.switchCamera'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
:class="['pi', getCameraIcon, 'text-white text-lg']"
|
||||
/>
|
||||
</Button>
|
||||
<div class="relative show-fov" v-if="showFOVButton">
|
||||
<div v-if="showFOVButton" class="relative show-fov">
|
||||
<Button class="p-button-rounded p-button-text" @click="toggleFOV">
|
||||
<i
|
||||
class="pi pi-expand text-white text-lg"
|
||||
v-tooltip.right="{ value: t('load3d.fov'), showDelay: 300 }"
|
||||
></i>
|
||||
class="pi pi-expand text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showFOV"
|
||||
@@ -24,10 +24,10 @@
|
||||
<Slider
|
||||
v-model="fov"
|
||||
class="w-full"
|
||||
@change="updateFOV"
|
||||
:min="10"
|
||||
:max="150"
|
||||
:step="1"
|
||||
@change="updateFOV"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
@click="toggleExportFormats"
|
||||
>
|
||||
<i
|
||||
class="pi pi-download text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.exportModel'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-download text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showExportFormats"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="relative show-light-intensity" v-if="showLightIntensityButton">
|
||||
<div v-if="showLightIntensityButton" class="relative show-light-intensity">
|
||||
<Button
|
||||
class="p-button-rounded p-button-text"
|
||||
@click="toggleLightIntensity"
|
||||
>
|
||||
<i
|
||||
class="pi pi-sun text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.lightIntensity'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-sun text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showLightIntensity"
|
||||
@@ -21,10 +21,10 @@
|
||||
<Slider
|
||||
v-model="lightIntensity"
|
||||
class="w-full"
|
||||
@change="updateLightIntensity"
|
||||
:min="1"
|
||||
:max="20"
|
||||
:step="1"
|
||||
@change="updateLightIntensity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<div class="relative show-up-direction">
|
||||
<Button class="p-button-rounded p-button-text" @click="toggleUpDirection">
|
||||
<i
|
||||
class="pi pi-arrow-up text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.upDirection'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-arrow-up text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showUpDirection"
|
||||
@@ -34,12 +34,12 @@
|
||||
@click="toggleMaterialMode"
|
||||
>
|
||||
<i
|
||||
class="pi pi-box text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.materialMode'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-box text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showMaterialMode"
|
||||
@@ -69,18 +69,18 @@
|
||||
>
|
||||
<Button class="p-button-rounded p-button-text" @click="openTextureUpload">
|
||||
<i
|
||||
class="pi pi-image text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.uploadTexture'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-image text-white text-lg"
|
||||
/>
|
||||
<input
|
||||
type="file"
|
||||
ref="texturePickerRef"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="uploadTexture"
|
||||
class="absolute opacity-0 w-0 h-0 p-0 m-0 pointer-events-none"
|
||||
@change="uploadTexture"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -91,12 +91,12 @@
|
||||
@click="toggleEdgeThreshold"
|
||||
>
|
||||
<i
|
||||
class="pi pi-sliders-h text-white text-lg"
|
||||
v-tooltip.right="{
|
||||
value: t('load3d.edgeThreshold'),
|
||||
showDelay: 300
|
||||
}"
|
||||
></i>
|
||||
class="pi pi-sliders-h text-white text-lg"
|
||||
/>
|
||||
</Button>
|
||||
<div
|
||||
v-show="showEdgeThreshold"
|
||||
@@ -109,10 +109,10 @@
|
||||
<Slider
|
||||
v-model="edgeThreshold"
|
||||
class="w-full"
|
||||
@change="updateEdgeThreshold"
|
||||
:min="0"
|
||||
:max="120"
|
||||
:step="1"
|
||||
@change="updateEdgeThreshold"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user