Compare commits
99 Commits
austin/bat
...
drjkl/I-ha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c48d41a88 | ||
|
|
2fc88abd59 | ||
|
|
b92fa9efe8 | ||
|
|
0a5af960d5 | ||
|
|
cdd8105b1a | ||
|
|
53013d04ef | ||
|
|
18f3877cab | ||
|
|
21445f1faf | ||
|
|
6a1dcf8a1e | ||
|
|
a13d28cc16 | ||
|
|
2ed5618331 | ||
|
|
c4c65070e9 | ||
|
|
7ad917343e | ||
|
|
25cc481e08 | ||
|
|
8ff385fc4d | ||
|
|
6ae2bc0e2a | ||
|
|
ae8940c0c0 | ||
|
|
6465c48423 | ||
|
|
6db94117b8 | ||
|
|
58d82b789b | ||
|
|
da43303ecf | ||
|
|
e00e2848a8 | ||
|
|
f2d5c415ae | ||
|
|
0edd5b17e7 | ||
|
|
8376db4813 | ||
|
|
d9e9d68230 | ||
|
|
06b96b13b9 | ||
|
|
05c5d08acb | ||
|
|
0aec287e83 | ||
|
|
a2fdb2fcb2 | ||
|
|
20b16009c7 | ||
|
|
f13a88a64e | ||
|
|
31a1e14d2c | ||
|
|
924db2b815 | ||
|
|
e0090a5a4b | ||
|
|
620ad24796 | ||
|
|
ea0e6b9d2a | ||
|
|
fd78ec3077 | ||
|
|
d02dfca1de | ||
|
|
81606328b8 | ||
|
|
ee1d61b71b | ||
|
|
f7b50067a6 | ||
|
|
00aa420049 | ||
|
|
eb883c507d | ||
|
|
4aa40a560c | ||
|
|
f998fc0aab | ||
|
|
c03cd17c87 | ||
|
|
62108147c3 | ||
|
|
94523defc1 | ||
|
|
a4df681fb5 | ||
|
|
5f3fd7f7be | ||
|
|
7c18a5a185 | ||
|
|
99bc407a1d | ||
|
|
e60a475d8e | ||
|
|
b56a028635 | ||
|
|
cc0bdf22e7 | ||
|
|
82b4be3988 | ||
|
|
5540d22f64 | ||
|
|
a3cef10edf | ||
|
|
deb9603b30 | ||
|
|
82b83460fc | ||
|
|
cd3b1c5afe | ||
|
|
aa7e59deaa | ||
|
|
4fc40e0039 | ||
|
|
ac5c84b514 | ||
|
|
0b1c2217e5 | ||
|
|
2269275bd9 | ||
|
|
37f8b73cfd | ||
|
|
7741a9bbb2 | ||
|
|
19bcbce4a6 | ||
|
|
53fa5c22c1 | ||
|
|
8d53bbf263 | ||
|
|
0d274344a1 | ||
|
|
e6ec331a71 | ||
|
|
8c38d8a5de | ||
|
|
9f525bb540 | ||
|
|
25a25efbb2 | ||
|
|
933fc35f02 | ||
|
|
27a5701636 | ||
|
|
af48faee96 | ||
|
|
4c5e213cd5 | ||
|
|
a98d01b2a2 | ||
|
|
8fe2ed3efe | ||
|
|
e981c38df7 | ||
|
|
a516c1cb45 | ||
|
|
7821b69ef7 | ||
|
|
6a420f2896 | ||
|
|
2d20de7e1f | ||
|
|
4e9dc97ad5 | ||
|
|
bb6ad22003 | ||
|
|
febc1951d4 | ||
|
|
d012682dec | ||
|
|
826f4b1d80 | ||
|
|
832b34c381 | ||
|
|
74dbad2404 | ||
|
|
aaf33ce6ad | ||
|
|
0bff3fe7ea | ||
|
|
cd70d7a576 | ||
|
|
e974f88e86 |
133
.oxlintrc.json
@@ -21,6 +21,7 @@
|
||||
"eslint",
|
||||
"import",
|
||||
"oxc",
|
||||
"promise",
|
||||
"typescript",
|
||||
"unicorn",
|
||||
"vitest",
|
||||
@@ -28,6 +29,12 @@
|
||||
],
|
||||
"rules": {
|
||||
"no-async-promise-executor": "off",
|
||||
"no-else-return": [
|
||||
"error",
|
||||
{
|
||||
"allowElseIf": false
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
@@ -35,8 +42,29 @@
|
||||
}
|
||||
],
|
||||
"no-control-regex": "off",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"null": "ignore"
|
||||
}
|
||||
],
|
||||
"func-style": [
|
||||
"error",
|
||||
"declaration",
|
||||
{
|
||||
"allowArrowFunctions": true
|
||||
}
|
||||
],
|
||||
"no-eval": "off",
|
||||
"no-new-func": "error",
|
||||
// TODO: Enable and fix 104 violations
|
||||
"no-param-reassign": "off",
|
||||
"no-redeclare": "error",
|
||||
"no-return-assign": ["error", "always"],
|
||||
"no-throw-literal": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-var": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
@@ -64,15 +92,66 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-unneeded-ternary": [
|
||||
"error",
|
||||
{
|
||||
"defaultAssignment": false
|
||||
}
|
||||
],
|
||||
"no-useless-call": "error",
|
||||
"no-useless-concat": "error",
|
||||
"prefer-const": "error",
|
||||
// TODO: Enable and fix 581 violations
|
||||
"prefer-destructuring": "off",
|
||||
"prefer-object-has-own": "error",
|
||||
"prefer-object-spread": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"prefer-template": "error",
|
||||
"promise/no-nesting": "error",
|
||||
"promise/param-names": "error",
|
||||
// TODO: Enable and fix 76 violations
|
||||
"promise/prefer-await-to-callbacks": "off",
|
||||
// TODO: Enable and fix 91 violations
|
||||
"promise/prefer-await-to-then": "off",
|
||||
"promise/prefer-catch": "error",
|
||||
"preserve-caught-error": "error",
|
||||
"yoda": [
|
||||
"error",
|
||||
"never",
|
||||
{
|
||||
"exceptRange": true
|
||||
}
|
||||
],
|
||||
"no-self-assign": "allow",
|
||||
"no-unused-expressions": "off",
|
||||
"no-unused-private-class-members": "off",
|
||||
"no-useless-rename": "off",
|
||||
"operator-assignment": ["error", "always"],
|
||||
"import/default": "error",
|
||||
"import/export": "error",
|
||||
"import/first": ["error", "absolute-first"],
|
||||
"import/namespace": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
"vitest/consistent-each-for": [
|
||||
"error",
|
||||
{
|
||||
"test": "for",
|
||||
"describe": "for"
|
||||
}
|
||||
],
|
||||
"vitest/consistent-test-filename": [
|
||||
"error",
|
||||
{
|
||||
"pattern": ".*\\.test\\.ts$"
|
||||
}
|
||||
],
|
||||
"vitest/consistent-vitest-vi": "error",
|
||||
"vitest/warn-todo": "warn",
|
||||
"vitest/hoisted-apis-on-top": "error",
|
||||
"vitest/no-conditional-tests": "error",
|
||||
"vitest/prefer-describe-function-title": "error",
|
||||
"jest/expect-expect": "off",
|
||||
"jest/no-conditional-expect": "off",
|
||||
"jest/no-disabled-tests": "off",
|
||||
@@ -82,11 +161,55 @@
|
||||
"typescript/no-unnecessary-parameter-property-assignment": "off",
|
||||
"typescript/no-unsafe-declaration-merging": "off",
|
||||
"typescript/no-unused-vars": "off",
|
||||
"unicorn/catch-error-name": [
|
||||
"error",
|
||||
{
|
||||
"ignore": ["^error\\w+$"]
|
||||
}
|
||||
],
|
||||
// TODO: Enable and fix 147 violations
|
||||
"unicorn/consistent-function-scoping": "off",
|
||||
"unicorn/error-message": "error",
|
||||
"unicorn/no-abusive-eslint-disable": "error",
|
||||
// TODO: Enable and fix 165 violations
|
||||
"unicorn/no-array-for-each": "off",
|
||||
"unicorn/no-immediate-mutation": "error",
|
||||
"unicorn/no-instanceof-array": "error",
|
||||
"unicorn/no-length-as-slice-end": "error",
|
||||
"unicorn/no-lonely-if": "error",
|
||||
"unicorn/no-negation-in-equality-check": "error",
|
||||
"unicorn/no-typeof-undefined": "error",
|
||||
"unicorn/prefer-math-min-max": "error",
|
||||
"unicorn/prefer-array-flat-map": "error",
|
||||
"unicorn/no-empty-file": "off",
|
||||
"unicorn/no-new-array": "off",
|
||||
"unicorn/prefer-add-event-listener": "error",
|
||||
"unicorn/prefer-array-find": "error",
|
||||
"unicorn/no-useless-undefined": [
|
||||
"error",
|
||||
{
|
||||
"checkArguments": false,
|
||||
"checkArrowFunctionBody": false
|
||||
}
|
||||
],
|
||||
"unicorn/prefer-classlist-toggle": "error",
|
||||
"unicorn/no-single-promise-in-promise-methods": "off",
|
||||
"unicorn/no-this-assignment": "error",
|
||||
"unicorn/no-useless-collection-argument": "error",
|
||||
"unicorn/no-useless-switch-case": "error",
|
||||
"unicorn/no-useless-fallback-in-spread": "off",
|
||||
"unicorn/no-useless-spread": "off",
|
||||
"unicorn/prefer-optional-catch-binding": "error",
|
||||
"unicorn/prefer-prototype-methods": "error",
|
||||
"unicorn/prefer-query-selector": "error",
|
||||
"unicorn/prefer-spread": "error",
|
||||
"unicorn/prefer-regexp-test": "error",
|
||||
"unicorn/prefer-set-has": "error",
|
||||
"unicorn/prefer-string-replace-all": "error",
|
||||
"unicorn/prefer-string-slice": "error",
|
||||
"unicorn/prefer-string-trim-start-end": "error",
|
||||
"unicorn/prefer-type-error": "error",
|
||||
"unicorn/throw-new-error": "error",
|
||||
"typescript/await-thenable": "off",
|
||||
"typescript/no-base-to-string": "off",
|
||||
"typescript/no-duplicate-type-constituents": "off",
|
||||
@@ -96,7 +219,12 @@
|
||||
"typescript/restrict-template-expressions": "off",
|
||||
"typescript/unbound-method": "off",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-explicit-any": "error",
|
||||
// TODO: Enable and fix 372 violations (use { "ignoreConditionalTests": true })
|
||||
"typescript/prefer-nullish-coalescing": "off",
|
||||
// TODO: Enable and fix violations
|
||||
"typescript/prefer-optional-chain": "off",
|
||||
"typescript/prefer-ts-expect-error": "error",
|
||||
"vue/define-props-destructuring": "error",
|
||||
"vue/no-import-compiler-macros": "error",
|
||||
"vue/no-dupe-keys": "error"
|
||||
},
|
||||
@@ -115,7 +243,8 @@
|
||||
"no-control-regex": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-unused-private-class-members": "error",
|
||||
"unicorn/no-empty-file": "error"
|
||||
"unicorn/no-empty-file": "error",
|
||||
"vitest/consistent-test-filename": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
@@ -12,6 +12,18 @@ This guide covers patterns and examples for testing Vue components in the ComfyU
|
||||
6. [Asynchronous Component Testing](#asynchronous-component-testing)
|
||||
7. [Working with Vue Reactivity](#working-with-vue-reactivity)
|
||||
|
||||
## Describe Block Naming
|
||||
|
||||
Use `Component.__name ?? 'ComponentName'` for the top-level `describe` title. This passes the function reference (satisfying the `prefer-describe-function-title` lint rule) while providing a readable fallback:
|
||||
|
||||
```typescript
|
||||
import MyComponent from './MyComponent.vue'
|
||||
|
||||
describe(MyComponent.__name ?? 'MyComponent', () => {
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## Basic Component Testing
|
||||
|
||||
Basic approach to testing a component's rendering and structure:
|
||||
@@ -21,7 +33,7 @@ Basic approach to testing a component's rendering and structure:
|
||||
import { mount } from '@vue/test-utils'
|
||||
import SidebarIcon from './SidebarIcon.vue'
|
||||
|
||||
describe('SidebarIcon', () => {
|
||||
describe(SidebarIcon.__name ?? 'SidebarIcon', () => {
|
||||
const exampleProps = {
|
||||
icon: 'pi pi-cog',
|
||||
selected: false
|
||||
|
||||
@@ -138,6 +138,10 @@ export default defineConfig([
|
||||
'import-x/no-useless-path-segments': 'error',
|
||||
'import-x/no-relative-packages': 'error',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'vue/return-in-computed-property': [
|
||||
'error',
|
||||
{ treatUndefinedAsUnspecified: false }
|
||||
],
|
||||
'vue/no-v-html': 'off',
|
||||
// Prohibit dark-theme: and dark: prefixes
|
||||
'vue/no-restricted-class': ['error', '/^dark(-theme)?:/'],
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 9">
|
||||
<path d="M1.82148 8.68376C1.61587 8.68376 1.44996 8.60733 1.34177 8.46284C1.23057 8.31438 1.20157 8.10711 1.26219 7.89434L1.50561 7.03961C1.52502 6.97155 1.51151 6.89831 1.46918 6.8417C1.42684 6.7852 1.3606 6.75194 1.29025 6.75194H0.590376C0.384656 6.75194 0.21875 6.67562 0.110614 6.53113C-0.000591531 6.38256 -0.0295831 6.17529 0.0310774 5.96252L0.867308 3.03952L0.959638 2.71838C1.08375 2.28258 1.53638 1.9284 1.96878 1.9284H2.80622C2.90615 1.9284 2.99406 1.86177 3.02157 1.76508L3.29852 0.79284C3.4225 0.357484 3.87514 0.0033043 4.30753 0.0033043L6.09854 0.000112775L7.40967 0C7.61533 0 7.78124 0.0763259 7.88937 0.220813C8.00058 0.369269 8.02957 0.576538 7.96895 0.78931L7.59405 2.10572C7.4701 2.54096 7.01746 2.89503 6.58507 2.89503L4.79008 2.89844H3.95292C3.8531 2.89844 3.7653 2.96496 3.73762 3.06155L3.03961 5.49964C3.02008 5.56781 3.03359 5.64127 3.07604 5.69787C3.11837 5.75437 3.18461 5.78763 3.2549 5.78763C3.25507 5.78763 4.44105 5.78532 4.44105 5.78532H5.7483C5.95396 5.78532 6.11986 5.86164 6.228 6.00613C6.33921 6.1547 6.3682 6.36197 6.30754 6.57474L5.93263 7.89092C5.80869 8.32628 5.35605 8.68034 4.92366 8.68034L3.12872 8.68376H1.82148Z" fill="#8A8A8A"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
477
pnpm-lock.yaml
generated
@@ -205,14 +205,14 @@ catalogs:
|
||||
specifier: 22.2.6
|
||||
version: 22.2.6
|
||||
oxfmt:
|
||||
specifier: ^0.26.0
|
||||
version: 0.26.0
|
||||
specifier: ^0.31.0
|
||||
version: 0.31.0
|
||||
oxlint:
|
||||
specifier: ^1.33.0
|
||||
version: 1.33.0
|
||||
specifier: ^1.46.0
|
||||
version: 1.46.0
|
||||
oxlint-tsgolint:
|
||||
specifier: ^0.9.1
|
||||
version: 0.9.1
|
||||
specifier: ^0.11.5
|
||||
version: 0.11.5
|
||||
picocolors:
|
||||
specifier: ^1.1.1
|
||||
version: 1.1.1
|
||||
@@ -650,13 +650,13 @@ importers:
|
||||
version: 22.2.6
|
||||
oxfmt:
|
||||
specifier: 'catalog:'
|
||||
version: 0.26.0
|
||||
version: 0.31.0
|
||||
oxlint:
|
||||
specifier: 'catalog:'
|
||||
version: 1.33.0(oxlint-tsgolint@0.9.1)
|
||||
version: 1.46.0(oxlint-tsgolint@0.11.5)
|
||||
oxlint-tsgolint:
|
||||
specifier: 'catalog:'
|
||||
version: 0.9.1
|
||||
version: 0.11.5
|
||||
picocolors:
|
||||
specifier: 'catalog:'
|
||||
version: 1.1.1
|
||||
@@ -2626,113 +2626,269 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/darwin-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-AAGc+8CffkiWeVgtWf4dPfQwHEE5c/j/8NWH7VGVxxJRCZFdmWcqCXprvL2H6qZFewvDLrFbuSPRCqYCpYGaTQ==}
|
||||
'@oxfmt/binding-android-arm-eabi@0.31.0':
|
||||
resolution: {integrity: sha512-2A7s+TmsY7xF3yM0VWXq2YJ82Z7Rd7AOKraotyp58Fbk7q9cFZKczW6Zrz/iaMaJYfR/UHDxF3kMR11vayflug==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-android-arm64@0.31.0':
|
||||
resolution: {integrity: sha512-3ppKOIf2lQv/BFhRyENWs/oarueppCEnPNo0Az2fKkz63JnenRuJPoHaGRrMHg1oFMUitdYy+YH29Cv5ISZWRQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxfmt/binding-darwin-arm64@0.31.0':
|
||||
resolution: {integrity: sha512-eFhNnle077DPRW6QPsBtl/wEzPoqgsB1LlzDRYbbztizObHdCo6Yo8T0hew9+HoYtnVMAP19zcRE7VG9OfqkMw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/darwin-x64@0.26.0':
|
||||
resolution: {integrity: sha512-xFx5ijCTjw577wJvFlZEMmKDnp3HSCcbYdCsLRmC5i3TZZiDe9DEYh3P46uqhzj8BkEw1Vm1ZCWdl48aEYAzvQ==}
|
||||
'@oxfmt/binding-darwin-x64@0.31.0':
|
||||
resolution: {integrity: sha512-9UQSunEqokhR1WnlQCgJjkjw13y8PSnBvR98L78beGudTtNSaPMgwE7t/T0IPDibtDTxeEt+IQVKoQJ+8Jo6Lg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxfmt/linux-arm64-gnu@0.26.0':
|
||||
resolution: {integrity: sha512-GubkQeQT5d3B/Jx/IiR7NMkSmXrCZcVI0BPh1i7mpFi8HgD1hQ/LbhiBKAMsMqs5bbugdQOgBEl8bOhe8JhW1g==}
|
||||
'@oxfmt/binding-freebsd-x64@0.31.0':
|
||||
resolution: {integrity: sha512-FHo7ITkDku3kQ8/44nU6IGR1UNH22aqYM3LV2ytV40hWSMVllXFlM+xIVusT+I/SZBAtuFpwEWzyS+Jn4TkkAQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.31.0':
|
||||
resolution: {integrity: sha512-o1NiDlJDO9SOoY5wH8AyPUX60yQcOwu5oVuepi2eetArBp0iFF9qIH1uLlZsUu4QQ6ywqxcJSMjXCqGKC5uQFg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.31.0':
|
||||
resolution: {integrity: sha512-VXiRxlBz7ivAIjhnnVBEYdjCQ66AsjM0YKxYAcliS0vPqhWKiScIT61gee0DPCVaw1XcuW8u19tfRwpfdYoreg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.31.0':
|
||||
resolution: {integrity: sha512-ryGPOtPViNcjs8N8Ap+wn7SM6ViiLzR9f0Pu7yprae+wjl6qwnNytzsUe7wcb+jT43DJYmvemFqE8tLVUavYbQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/linux-arm64-musl@0.26.0':
|
||||
resolution: {integrity: sha512-OEypUwK69bFPj+aa3/LYCnlIUPgoOLu//WNcriwpnWNmt47808Ht7RJSg+MNK8a7pSZHpXJ5/E6CRK/OTwFdaQ==}
|
||||
'@oxfmt/binding-linux-arm64-musl@0.31.0':
|
||||
resolution: {integrity: sha512-BA3Euxp4bfd+AU3cKPgmHL44BbuBtmQTyAQoVDhX/nqPgbS/auoGp71uQBE4SAPTsQM/FcXxfKmCAdBS7ygF9w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/linux-x64-gnu@0.26.0':
|
||||
resolution: {integrity: sha512-xO6iEW2bC6ZHyOTPmPWrg/nM6xgzyRPaS84rATy6F8d79wz69LdRdJ3l/PXlkqhi7XoxhvX4ExysA0Nf10ZZEQ==}
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.31.0':
|
||||
resolution: {integrity: sha512-wIiKHulVWE9s6PSftPItucTviyCvjugwPqEyUl1VD47YsFqa5UtQTknBN49NODHJvBgO+eqqUodgRqmNMp3xyw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.31.0':
|
||||
resolution: {integrity: sha512-6cM8Jt54bg9V/JoeUWhwnzHAS9Kvgc0oFsxql8PVs/njAGs0H4r+GEU4d+LXZPwI3b3ZUuzpbxlRJzer8KW+Cg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.31.0':
|
||||
resolution: {integrity: sha512-d+b05wXVRGaO6gobTaDqUdBvTXwYc0ro7k1UVC37k4VimLRQOzEZqTwVinqIX3LxTaFCmfO1yG00u9Pct3AKwQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.31.0':
|
||||
resolution: {integrity: sha512-Q+i2kj8e+two9jTZ3vxmxdNlg++qShe1ODL6xV4+Qt6SnJYniMxfcqphuXli4ft270kuHqd8HSVZs84CsSh1EA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxfmt/binding-linux-x64-gnu@0.31.0':
|
||||
resolution: {integrity: sha512-F2Z5ffj2okhaQBi92MylwZddKvFPBjrsZnGvvRmVvWRf8WJ0WkKUTtombDgRYNDgoW7GBUUrNNNgWhdB7kVjBA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/linux-x64-musl@0.26.0':
|
||||
resolution: {integrity: sha512-Z3KuZFC+MIuAyFCXBHY71kCsdRq1ulbsbzTe71v+hrEv7zVBn6yzql+/AZcgfIaKzWO9OXNuz5WWLWDmVALwow==}
|
||||
'@oxfmt/binding-linux-x64-musl@0.31.0':
|
||||
resolution: {integrity: sha512-Vz7dZQd1yhE5wTWujGanPmZgDtzLZS1PQoeMmUj89p4eMTmpIkvWaIr3uquJCbh8dQd5cPZrFvMmdDgcY5z+GA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxfmt/win32-arm64@0.26.0':
|
||||
resolution: {integrity: sha512-3zRbqwVWK1mDhRhTknlQFpRFL9GhEB5GfU6U7wawnuEwpvi39q91kJ+SRJvJnhyPCARkjZBd1V8XnweN5IFd1g==}
|
||||
'@oxfmt/binding-openharmony-arm64@0.31.0':
|
||||
resolution: {integrity: sha512-nm0gus6R5V9tM1XaELiiIduUzmdBuCefkwToWKL4UtuFoMCGkigVQnbzHwPTGLVWOEF6wTQucFA8Fn1U8hxxVw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.31.0':
|
||||
resolution: {integrity: sha512-mMpvvPpoLD97Q2TMhjWDJSn+ib3kN+H+F4gq9p88zpeef6sqWc9djorJ3JXM2sOZMJ6KZ+1kSJfe0rkji74Pog==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/win32-x64@0.26.0':
|
||||
resolution: {integrity: sha512-m8TfIljU22i9UEIkD+slGPifTFeaCwIUfxszN3E6ABWP1KQbtwSw9Ak0TdoikibvukF/dtbeyG3WW63jv9DnEg==}
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.31.0':
|
||||
resolution: {integrity: sha512-zTngbPyrTDBYJFVQa4OJldM6w1Rqzi8c0/eFxAEbZRoj6x149GkyMkAY3kM+09ZhmszFitCML2S3p10NE2XmHA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxfmt/binding-win32-x64-msvc@0.31.0':
|
||||
resolution: {integrity: sha512-TB30D+iRLe6eUbc/utOA93+FNz5C6vXSb/TEhwvlODhKYZZSSKn/lFpYzZC7bdhx3a8m4Jq8fEUoCJ6lKnzdpA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.9.1':
|
||||
resolution: {integrity: sha512-vk+8kChWqN+F+QUOvp4/6jDTlDCzXPgYGkxdi6EOUSOmCP1ix0uYOlIi/ytH2imXmC8YfPgLR/1BhqbsuDKuew==}
|
||||
'@oxlint-tsgolint/darwin-arm64@0.11.5':
|
||||
resolution: {integrity: sha512-mzsjJVIUgcGJovBXME63VW2Uau7MS/xCe7xdYj2BplSCuRb5Yoy7WuwCIlbD5ISHjnS6rx26oD2kmzHLRV5Wfw==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.9.1':
|
||||
resolution: {integrity: sha512-yXmqr7El17+Oo56fWkPdUluU8d0jWxwRwAe1QZ0Xprxul9FHJeR/O2oYuBUngvCi02dbt0VZlwgJXcljQEdHlQ==}
|
||||
'@oxlint-tsgolint/darwin-x64@0.11.5':
|
||||
resolution: {integrity: sha512-zItUS0qLzSzVy0ZQHc4MOphA9lVeP5jffsgZFLCdo+JqmkbVZ14aDtiVUHSHi2hia+qatbb109CHQ9YIl0x7+A==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.9.1':
|
||||
resolution: {integrity: sha512-ukLb35BHSsxXaVEe8eIvYXMTxOdv8K4CySmtkWyc0pJT0q8zh85by1bsREWAP2hZc0wN0ClHjZHPdKY3958Jwg==}
|
||||
'@oxlint-tsgolint/linux-arm64@0.11.5':
|
||||
resolution: {integrity: sha512-R0r/3QTdMtIjfUOM1oxIaCV0s+j7xrnUe4CXo10ZbBzlXfMesWYNcf/oCrhsy87w0kCPFsg58nAdKaIR8xylFg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.9.1':
|
||||
resolution: {integrity: sha512-kkxSS/meANLun4dHep2wnfvo8OHJKgdxzuY3RoooSWorVqw3/K5Qttmo0OQFt7UNq/oisn0YTaNhV28S0nAWyQ==}
|
||||
'@oxlint-tsgolint/linux-x64@0.11.5':
|
||||
resolution: {integrity: sha512-g23J3T29EHWUQYC6aTwLnhwcFtjQh+VfxyGuFjYGGTLhESdlQH9E/pwsN8K9HaAiYWjI51m3r3BqQjXxEW8Jjg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.9.1':
|
||||
resolution: {integrity: sha512-F9tiZZRn3x+kjXJC8GAE5C5xkvD8b8unoFeh7mS5W4USAH8+AzYydzLev5rAW2uXdOqtkO30EJl0ygl68Zlb8w==}
|
||||
'@oxlint-tsgolint/win32-arm64@0.11.5':
|
||||
resolution: {integrity: sha512-MJNT/MPUIZKQCRtCX5s6pCnoe7If/i3RjJzFMe4kSLomRsHrNFYOJBwt4+w/Hqfyg9jNOgR8tbgdx6ofjHaPMQ==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.9.1':
|
||||
resolution: {integrity: sha512-DKTBgKUbycKNYgpWpglEHzkgiNVSG1rZmfiqw7w31keAq8q7avNGhz2WNmsRvXh8IGNw1PMb7vgxwUK8eyXIeg==}
|
||||
'@oxlint-tsgolint/win32-x64@0.11.5':
|
||||
resolution: {integrity: sha512-IQmj4EkcZOBlLnj1CdxKFrWT7NAWXZ9ypZ874X/w7S5gRzB2sO4KmE6Z0MWxx05pL9AQF+CWVRjZrKVIYWTzPg==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/darwin-arm64@1.33.0':
|
||||
resolution: {integrity: sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw==}
|
||||
'@oxlint/binding-android-arm-eabi@1.46.0':
|
||||
resolution: {integrity: sha512-vLPcE+HcZ/W/0cVA1KLuAnoUSejGougDH/fDjBFf0Q+rbBIyBNLevOhgx3AnBNAt3hcIGY7U05ISbJCKZeVa3w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-android-arm64@1.46.0':
|
||||
resolution: {integrity: sha512-b8IqCczUsirdtJ3R/be4cRm64I5pMPafMO/9xyTAZvc+R/FxZHMQuhw0iNT9hQwRn+Uo5rNAoA8QS7QurG2QeA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.46.0':
|
||||
resolution: {integrity: sha512-CfC/KGnNMhI01dkfCMjquKnW4zby3kqD5o/9XA7+pgo9I4b+Nipm+JVFyZPWMNwKqLXNmi35GTLWjs9svPxlew==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/darwin-x64@1.33.0':
|
||||
resolution: {integrity: sha512-2R9aH3kR0X2M30z5agGikv3tfNTi8/uLhU5/tYktu33VGUXpbf0OLZSlD25UEuwOKAlf3RVtzV5oDyjoq93JuQ==}
|
||||
'@oxlint/binding-darwin-x64@1.46.0':
|
||||
resolution: {integrity: sha512-m38mKPsV3rBdWOJ4TAGZiUjWU8RGrBxsmdSeMQ0bPr/8O6CUOm/RJkPBf0GAfPms2WRVcbkfEXvIiPshAeFkeA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@oxlint/linux-arm64-gnu@1.33.0':
|
||||
resolution: {integrity: sha512-yb/k8GaMDgnX2LyO6km33kKItZ/n573SlbiHBBFU2HmeU7tzEHL5jHkHQXXcysUkapmqHd7UsDhOZDqPmXaQRg==}
|
||||
'@oxlint/binding-freebsd-x64@1.46.0':
|
||||
resolution: {integrity: sha512-YaFRKslSAfuMwn7ejS1/wo9jENqQigpGBjjThX+mrpmEROLYGky+zIC5xSVGRng28U92VEDVbSNJ/sguz3dUAA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.46.0':
|
||||
resolution: {integrity: sha512-Nlw+5mSZQtkg1Oj0N8ulxzG8ATpmSDz5V2DNaGhaYAVlcdR8NYSm/xTOnweOXc/UOOv3LwkPPYzqcfPhu2lEkA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.46.0':
|
||||
resolution: {integrity: sha512-d3Y5y4ukMqAGnWLMKpwqj8ftNUaac7pA0NrId4AZ77JvHzezmxEcm2gswaBw2HW8y1pnq6KDB0vEPPvpTfDLrA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.46.0':
|
||||
resolution: {integrity: sha512-jkjx+XSOPuFR+C458prQmehO+v0VK19/3Hj2mOYDF4hHUf3CzmtA4fTmQUtkITZiGHnky7Oao6JeJX24mrX7WQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/linux-arm64-musl@1.33.0':
|
||||
resolution: {integrity: sha512-03pt9IO1C4ZfVOW6SQiOK26mzklAhLM3Kc79OXpX1kgZRlxk+rvFoMhlgCOzn7tEdrEgbePkBoxNnwDnJDFqJQ==}
|
||||
'@oxlint/binding-linux-arm64-musl@1.46.0':
|
||||
resolution: {integrity: sha512-X/aPB1rpJUdykjWSeeGIbjk6qbD8VDulgLuTSMWgr/t6m1ljcAjqHb1g49pVG9bZl55zjECgzvlpPLWnfb4FMQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/linux-x64-gnu@1.33.0':
|
||||
resolution: {integrity: sha512-Z7ImLWM50FoVXzYvyxUQ+QwBkBfRyK4YdLEGonyAGMp7iT3DksonDaTK9ODnJ1qHyAyAZCvuqXD7AEDsDvzDbA==}
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.46.0':
|
||||
resolution: {integrity: sha512-AymyOxGWwKY2KJa8b+h8iLrYJZbWKYCjqctSc2q6uIAkYPrCsxcWlge1JP6XZ14Sa80DVMwI/QvktbytSV+xVw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.46.0':
|
||||
resolution: {integrity: sha512-PkeVdPKCDA59rlMuucsel2LjlNEpslQN5AhkMMorIJZItbbqi/0JSuACCzaiIcXYv0oNfbeQ8rbOBikv+aT6cg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.46.0':
|
||||
resolution: {integrity: sha512-snQaRLO/X+Ry/CxX1px1g8GUbmXzymdRs+/RkP2bySHWZFhFDtbLm2hA1ujX/jKlTLMJDZn4hYzFGLDwG/Rh2w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.46.0':
|
||||
resolution: {integrity: sha512-kZhDMwUe/sgDTluGao9c0Dqc1JzV6wPzfGo0l/FLQdh5Zmp39Yg1FbBsCgsJfVKmKl1fNqsHyFLTShWMOlOEhA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.46.0':
|
||||
resolution: {integrity: sha512-n5a7VtQTxHZ13cNAKQc3ziARv5bE1Fx868v/tnhZNVUjaRNYe5uiUrRJ/LZghdAzOxVuQGarjjq/q4QM2+9OPA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/linux-x64-musl@1.33.0':
|
||||
resolution: {integrity: sha512-idb55Uzu5kkqqpMiVUfI9nP7zOqPZinQKsIRQAIU40wILcf/ijvhNZKIu3ucDMmye0n6IWOaSnxIRL5W2fNoUQ==}
|
||||
'@oxlint/binding-linux-x64-musl@1.46.0':
|
||||
resolution: {integrity: sha512-KpsDU/BhdVn3iKCLxMXAOZIpO8fS0jEA5iluRoK1rhHPwKtpzEm/OCwERsu/vboMSZm66qnoTUVXRPJ8M+iKVQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@oxlint/win32-arm64@1.33.0':
|
||||
resolution: {integrity: sha512-wKKFt7cubfrLelNzdmDsNSmtBrlSUe1fWus587+uSxDZdpFbQ7liU0gsUlCbcHvym0H1Tc2O3K3cnLrgQORLPQ==}
|
||||
'@oxlint/binding-openharmony-arm64@1.46.0':
|
||||
resolution: {integrity: sha512-jtbqUyEXlsDlRmMtTZqNbw49+1V/WxqNAR5l0S3OEkdat9diI5I+eqq9IT+jb5cSDdszTGcXpn7S3+gUYSydxQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.46.0':
|
||||
resolution: {integrity: sha512-EE8NjpqEZPwHQVigNvdyJ11dZwWIfsfn4VeBAuiJeAdrnY4HFX27mIjJINJgP5ZdBYEFV1OWH/eb9fURCYel8w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/win32-x64@1.33.0':
|
||||
resolution: {integrity: sha512-ReyR8rNHjKNnO7dxGny9RCPELRAdhm3y780FNBcA07E1wvxSCkB+Mn5db0Pa5bRmxrsU/MTZ/aaBFa+ERXDdXw==}
|
||||
'@oxlint/binding-win32-ia32-msvc@1.46.0':
|
||||
resolution: {integrity: sha512-BHyk3H/HRdXs+uImGZ/2+qCET+B8lwGHOm7m54JiJEEUWf3zYCFX/Df1SPqtozWWmnBvioxoTG1J3mPRAr8KUA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.46.0':
|
||||
resolution: {integrity: sha512-DJbQsSJUr4KSi9uU0QqOgI7PX2C+fKGZX+YDprt3vM2sC0dWZsgVTLoN2vtkNyEWJSY2mnvRFUshWXT3bmo0Ug==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
@@ -6842,21 +6998,21 @@ packages:
|
||||
oxc-resolver@11.15.0:
|
||||
resolution: {integrity: sha512-Hk2J8QMYwmIO9XTCUiOH00+Xk2/+aBxRUnhrSlANDyCnLYc32R1WSIq1sU2yEdlqd53FfMpPEpnBYIKQMzliJw==}
|
||||
|
||||
oxfmt@0.26.0:
|
||||
resolution: {integrity: sha512-UDD1wFNwfeorMm2ZY0xy1KRAAvJ5NjKBfbDmiMwGP7baEHTq65cYpC0aPP+BGHc8weXUbSZaK8MdGyvuRUvS4Q==}
|
||||
oxfmt@0.31.0:
|
||||
resolution: {integrity: sha512-ukl7nojEuJUGbqR4ijC0Z/7a6BYpD4RxLS2UsyJKgbeZfx6TNrsa48veG0z2yQbhTx1nVnes4GIcqMn7n2jFtw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
oxlint-tsgolint@0.9.1:
|
||||
resolution: {integrity: sha512-w1lIvUDkkiAPFyo268SFGrdh1LQ3Lcs1XShES7I4X75TliQA0os5XJ5hNZ4lYsSevqcofgEtq4xq7rBumv69iQ==}
|
||||
oxlint-tsgolint@0.11.5:
|
||||
resolution: {integrity: sha512-4uVv43EhkeMvlxDU1GUsR5P5c0q74rB/pQRhjGsTOnMIrDbg3TABTntRyeAkmXItqVEJTcDRv9+Yk+LFXkHKlg==}
|
||||
hasBin: true
|
||||
|
||||
oxlint@1.33.0:
|
||||
resolution: {integrity: sha512-4WCL0K8jiOshwJ8WrVk35VAuVaZHC0iX6asjKsrENOrynkAAGcTLLx0Urf0eXZ1Tq7r+qAe3Z9EyHMFPzVyUkg==}
|
||||
oxlint@1.46.0:
|
||||
resolution: {integrity: sha512-I9h42QDtAVsRwoueJ4PL/7qN5jFzIUXvbO4Z5ddtII92ZCiD7uiS/JW2V4viBSfGLsbZkQp3YEs6Ls4I8q+8tA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
oxlint-tsgolint: '>=0.9.0'
|
||||
oxlint-tsgolint: '>=0.11.2'
|
||||
peerDependenciesMeta:
|
||||
oxlint-tsgolint:
|
||||
optional: true
|
||||
@@ -7447,6 +7603,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
semver@7.7.4:
|
||||
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -7757,8 +7918,8 @@ packages:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinypool@2.0.0:
|
||||
resolution: {integrity: sha512-/RX9RzeH2xU5ADE7n2Ykvmi9ED3FBGPAjw9u3zucrNNaEBIO0HPSYgL0NT7+3p147ojeSdaVu08F6hjpv31HJg==}
|
||||
tinypool@2.1.0:
|
||||
resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==}
|
||||
engines: {node: ^20.0.0 || >=22.0.0}
|
||||
|
||||
tinyrainbow@2.0.0:
|
||||
@@ -10726,70 +10887,136 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-x64-msvc@11.15.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/darwin-arm64@0.26.0':
|
||||
'@oxfmt/binding-android-arm-eabi@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/darwin-x64@0.26.0':
|
||||
'@oxfmt/binding-android-arm64@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/linux-arm64-gnu@0.26.0':
|
||||
'@oxfmt/binding-darwin-arm64@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/linux-arm64-musl@0.26.0':
|
||||
'@oxfmt/binding-darwin-x64@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/linux-x64-gnu@0.26.0':
|
||||
'@oxfmt/binding-freebsd-x64@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/linux-x64-musl@0.26.0':
|
||||
'@oxfmt/binding-linux-arm-gnueabihf@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/win32-arm64@0.26.0':
|
||||
'@oxfmt/binding-linux-arm-musleabihf@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxfmt/win32-x64@0.26.0':
|
||||
'@oxfmt/binding-linux-arm64-gnu@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-arm64@0.9.1':
|
||||
'@oxfmt/binding-linux-arm64-musl@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/darwin-x64@0.9.1':
|
||||
'@oxfmt/binding-linux-ppc64-gnu@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-arm64@0.9.1':
|
||||
'@oxfmt/binding-linux-riscv64-gnu@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.9.1':
|
||||
'@oxfmt/binding-linux-riscv64-musl@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.9.1':
|
||||
'@oxfmt/binding-linux-s390x-gnu@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.9.1':
|
||||
'@oxfmt/binding-linux-x64-gnu@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/darwin-arm64@1.33.0':
|
||||
'@oxfmt/binding-linux-x64-musl@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/darwin-x64@1.33.0':
|
||||
'@oxfmt/binding-openharmony-arm64@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/linux-arm64-gnu@1.33.0':
|
||||
'@oxfmt/binding-win32-arm64-msvc@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/linux-arm64-musl@1.33.0':
|
||||
'@oxfmt/binding-win32-ia32-msvc@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/linux-x64-gnu@1.33.0':
|
||||
'@oxfmt/binding-win32-x64-msvc@0.31.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/linux-x64-musl@1.33.0':
|
||||
'@oxlint-tsgolint/darwin-arm64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint/win32-arm64@1.33.0':
|
||||
'@oxlint-tsgolint/darwin-x64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint/win32-x64@1.33.0':
|
||||
'@oxlint-tsgolint/linux-arm64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/linux-x64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-arm64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint-tsgolint/win32-x64@0.11.5':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm-eabi@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-android-arm64@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-arm64@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-darwin-x64@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-freebsd-x64@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-gnueabihf@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm-musleabihf@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-gnu@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-arm64-musl@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-ppc64-gnu@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-gnu@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-riscv64-musl@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-s390x-gnu@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-gnu@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-linux-x64-musl@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-openharmony-arm64@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-arm64-msvc@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-ia32-msvc@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@oxlint/binding-win32-x64-msvc@1.46.0':
|
||||
optional: true
|
||||
|
||||
'@phenomnomnominal/tsquery@5.0.1(typescript@5.9.3)':
|
||||
@@ -14489,7 +14716,7 @@ snapshots:
|
||||
acorn: 8.15.0
|
||||
eslint-visitor-keys: 3.4.3
|
||||
espree: 9.6.1
|
||||
semver: 7.7.3
|
||||
semver: 7.7.4
|
||||
|
||||
jsonc-parser@3.2.0: {}
|
||||
|
||||
@@ -15512,39 +15739,61 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-ia32-msvc': 11.15.0
|
||||
'@oxc-resolver/binding-win32-x64-msvc': 11.15.0
|
||||
|
||||
oxfmt@0.26.0:
|
||||
oxfmt@0.31.0:
|
||||
dependencies:
|
||||
tinypool: 2.0.0
|
||||
tinypool: 2.1.0
|
||||
optionalDependencies:
|
||||
'@oxfmt/darwin-arm64': 0.26.0
|
||||
'@oxfmt/darwin-x64': 0.26.0
|
||||
'@oxfmt/linux-arm64-gnu': 0.26.0
|
||||
'@oxfmt/linux-arm64-musl': 0.26.0
|
||||
'@oxfmt/linux-x64-gnu': 0.26.0
|
||||
'@oxfmt/linux-x64-musl': 0.26.0
|
||||
'@oxfmt/win32-arm64': 0.26.0
|
||||
'@oxfmt/win32-x64': 0.26.0
|
||||
'@oxfmt/binding-android-arm-eabi': 0.31.0
|
||||
'@oxfmt/binding-android-arm64': 0.31.0
|
||||
'@oxfmt/binding-darwin-arm64': 0.31.0
|
||||
'@oxfmt/binding-darwin-x64': 0.31.0
|
||||
'@oxfmt/binding-freebsd-x64': 0.31.0
|
||||
'@oxfmt/binding-linux-arm-gnueabihf': 0.31.0
|
||||
'@oxfmt/binding-linux-arm-musleabihf': 0.31.0
|
||||
'@oxfmt/binding-linux-arm64-gnu': 0.31.0
|
||||
'@oxfmt/binding-linux-arm64-musl': 0.31.0
|
||||
'@oxfmt/binding-linux-ppc64-gnu': 0.31.0
|
||||
'@oxfmt/binding-linux-riscv64-gnu': 0.31.0
|
||||
'@oxfmt/binding-linux-riscv64-musl': 0.31.0
|
||||
'@oxfmt/binding-linux-s390x-gnu': 0.31.0
|
||||
'@oxfmt/binding-linux-x64-gnu': 0.31.0
|
||||
'@oxfmt/binding-linux-x64-musl': 0.31.0
|
||||
'@oxfmt/binding-openharmony-arm64': 0.31.0
|
||||
'@oxfmt/binding-win32-arm64-msvc': 0.31.0
|
||||
'@oxfmt/binding-win32-ia32-msvc': 0.31.0
|
||||
'@oxfmt/binding-win32-x64-msvc': 0.31.0
|
||||
|
||||
oxlint-tsgolint@0.9.1:
|
||||
oxlint-tsgolint@0.11.5:
|
||||
optionalDependencies:
|
||||
'@oxlint-tsgolint/darwin-arm64': 0.9.1
|
||||
'@oxlint-tsgolint/darwin-x64': 0.9.1
|
||||
'@oxlint-tsgolint/linux-arm64': 0.9.1
|
||||
'@oxlint-tsgolint/linux-x64': 0.9.1
|
||||
'@oxlint-tsgolint/win32-arm64': 0.9.1
|
||||
'@oxlint-tsgolint/win32-x64': 0.9.1
|
||||
'@oxlint-tsgolint/darwin-arm64': 0.11.5
|
||||
'@oxlint-tsgolint/darwin-x64': 0.11.5
|
||||
'@oxlint-tsgolint/linux-arm64': 0.11.5
|
||||
'@oxlint-tsgolint/linux-x64': 0.11.5
|
||||
'@oxlint-tsgolint/win32-arm64': 0.11.5
|
||||
'@oxlint-tsgolint/win32-x64': 0.11.5
|
||||
|
||||
oxlint@1.33.0(oxlint-tsgolint@0.9.1):
|
||||
oxlint@1.46.0(oxlint-tsgolint@0.11.5):
|
||||
optionalDependencies:
|
||||
'@oxlint/darwin-arm64': 1.33.0
|
||||
'@oxlint/darwin-x64': 1.33.0
|
||||
'@oxlint/linux-arm64-gnu': 1.33.0
|
||||
'@oxlint/linux-arm64-musl': 1.33.0
|
||||
'@oxlint/linux-x64-gnu': 1.33.0
|
||||
'@oxlint/linux-x64-musl': 1.33.0
|
||||
'@oxlint/win32-arm64': 1.33.0
|
||||
'@oxlint/win32-x64': 1.33.0
|
||||
oxlint-tsgolint: 0.9.1
|
||||
'@oxlint/binding-android-arm-eabi': 1.46.0
|
||||
'@oxlint/binding-android-arm64': 1.46.0
|
||||
'@oxlint/binding-darwin-arm64': 1.46.0
|
||||
'@oxlint/binding-darwin-x64': 1.46.0
|
||||
'@oxlint/binding-freebsd-x64': 1.46.0
|
||||
'@oxlint/binding-linux-arm-gnueabihf': 1.46.0
|
||||
'@oxlint/binding-linux-arm-musleabihf': 1.46.0
|
||||
'@oxlint/binding-linux-arm64-gnu': 1.46.0
|
||||
'@oxlint/binding-linux-arm64-musl': 1.46.0
|
||||
'@oxlint/binding-linux-ppc64-gnu': 1.46.0
|
||||
'@oxlint/binding-linux-riscv64-gnu': 1.46.0
|
||||
'@oxlint/binding-linux-riscv64-musl': 1.46.0
|
||||
'@oxlint/binding-linux-s390x-gnu': 1.46.0
|
||||
'@oxlint/binding-linux-x64-gnu': 1.46.0
|
||||
'@oxlint/binding-linux-x64-musl': 1.46.0
|
||||
'@oxlint/binding-openharmony-arm64': 1.46.0
|
||||
'@oxlint/binding-win32-arm64-msvc': 1.46.0
|
||||
'@oxlint/binding-win32-ia32-msvc': 1.46.0
|
||||
'@oxlint/binding-win32-x64-msvc': 1.46.0
|
||||
oxlint-tsgolint: 0.11.5
|
||||
|
||||
p-limit@3.1.0:
|
||||
dependencies:
|
||||
@@ -16282,6 +16531,8 @@ snapshots:
|
||||
|
||||
semver@7.7.3: {}
|
||||
|
||||
semver@7.7.4: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -16682,7 +16933,7 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tinypool@2.0.0: {}
|
||||
tinypool@2.1.0: {}
|
||||
|
||||
tinyrainbow@2.0.0: {}
|
||||
|
||||
|
||||
@@ -69,9 +69,9 @@ catalog:
|
||||
markdown-table: ^3.0.4
|
||||
mixpanel-browser: ^2.71.0
|
||||
nx: 22.2.6
|
||||
oxfmt: ^0.26.0
|
||||
oxlint: ^1.33.0
|
||||
oxlint-tsgolint: ^0.9.1
|
||||
oxfmt: ^0.31.0
|
||||
oxlint: ^1.46.0
|
||||
oxlint-tsgolint: ^0.11.5
|
||||
picocolors: ^1.1.1
|
||||
pinia: ^3.0.4
|
||||
postcss-html: ^1.8.0
|
||||
|
||||
@@ -33,6 +33,7 @@ fi
|
||||
|
||||
EXCLUDE_PATTERNS=(
|
||||
'**/tsconfig*.json'
|
||||
'.oxlintrc.json'
|
||||
)
|
||||
|
||||
if [ -n "${JSON_LINT_EXCLUDES:-}" ]; then
|
||||
|
||||
@@ -16,12 +16,12 @@ import { computed, onMounted } from 'vue'
|
||||
|
||||
import GlobalDialog from '@/components/dialog/GlobalDialog.vue'
|
||||
import config from '@/config'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
|
||||
|
||||
import { electronAPI } from '@/utils/envUtil'
|
||||
import { isDesktop } from '@/platform/distribution/types'
|
||||
import { app } from '@/scripts/app'
|
||||
|
||||
const workspaceStore = useWorkspaceStore()
|
||||
app.extensionManager = useWorkspaceStore()
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('downloadUtil', () => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('downloadFile', () => {
|
||||
describe(downloadFile, () => {
|
||||
it('should create and trigger download with basic URL', () => {
|
||||
const testUrl = 'https://example.com/image.png'
|
||||
|
||||
@@ -285,7 +285,7 @@ describe('downloadUtil', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractFilenameFromContentDisposition', () => {
|
||||
describe(extractFilenameFromContentDisposition, () => {
|
||||
it('returns null for null header', () => {
|
||||
expect(extractFilenameFromContentDisposition(null)).toBeNull()
|
||||
})
|
||||
|
||||
@@ -172,19 +172,17 @@ const splitterRefreshKey = computed(() => {
|
||||
return `main-splitter${rightSidePanelVisible.value ? '-with-right-panel' : ''}-${sidebarLocation.value}`
|
||||
})
|
||||
|
||||
const firstPanelStyle = computed(() => {
|
||||
if (sidebarLocation.value === 'left') {
|
||||
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const firstPanelStyle = computed(() =>
|
||||
sidebarLocation.value === 'left'
|
||||
? { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
||||
: undefined
|
||||
)
|
||||
|
||||
const lastPanelStyle = computed(() => {
|
||||
if (sidebarLocation.value === 'right') {
|
||||
return { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
const lastPanelStyle = computed(() =>
|
||||
sidebarLocation.value === 'right'
|
||||
? { display: sidebarPanelVisible.value ? 'flex' : 'none' }
|
||||
: undefined
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { computed, defineComponent, h, nextTick, onMounted, ref } from 'vue'
|
||||
import { computed, defineComponent, h, nextTick, onMounted } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
@@ -19,11 +19,7 @@ import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'
|
||||
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
|
||||
|
||||
const mockData = vi.hoisted(() => ({
|
||||
isLoggedIn: false,
|
||||
isDesktop: false,
|
||||
setShowConflictRedDot: (_value: boolean) => {}
|
||||
}))
|
||||
const mockData = vi.hoisted(() => ({ isLoggedIn: false, isDesktop: false }))
|
||||
|
||||
vi.mock('@/composables/auth/useCurrentUser', () => ({
|
||||
useCurrentUser: () => {
|
||||
@@ -40,36 +36,6 @@ vi.mock('@/platform/distribution/types', () => ({
|
||||
return mockData.isDesktop
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/platform/updates/common/releaseStore', () => ({
|
||||
useReleaseStore: () => ({
|
||||
shouldShowRedDot: computed(() => true)
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock(
|
||||
'@/workbench/extensions/manager/composables/useConflictAcknowledgment',
|
||||
() => {
|
||||
const shouldShowConflictRedDot = ref(false)
|
||||
mockData.setShowConflictRedDot = (value: boolean) => {
|
||||
shouldShowConflictRedDot.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
useConflictAcknowledgment: () => ({
|
||||
shouldShowRedDot: shouldShowConflictRedDot
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
vi.mock('@/workbench/extensions/manager/composables/useManagerState', () => ({
|
||||
useManagerState: () => ({
|
||||
shouldShowManagerButtons: computed(() => true),
|
||||
openManager: vi.fn()
|
||||
})
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
useFirebaseAuthStore: vi.fn(() => ({
|
||||
currentUser: null,
|
||||
@@ -142,13 +108,12 @@ function createTask(id: string, status: JobStatus): TaskItemImpl {
|
||||
return new TaskItemImpl(createJob(id, status))
|
||||
}
|
||||
|
||||
describe('TopMenuSection', () => {
|
||||
describe(TopMenuSection.__name ?? 'TopMenuSection', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
localStorage.clear()
|
||||
mockData.isDesktop = false
|
||||
mockData.isLoggedIn = false
|
||||
mockData.setShowConflictRedDot(false)
|
||||
})
|
||||
|
||||
describe('authentication state', () => {
|
||||
@@ -277,7 +242,7 @@ describe('TopMenuSection', () => {
|
||||
vi.mocked(settingStore.get).mockImplementation((key) => {
|
||||
if (key === 'Comfy.Queue.QPOV2') return qpoV2Enabled
|
||||
if (key === 'Comfy.UseNewMenu') return 'Top'
|
||||
return undefined
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
@@ -365,16 +330,4 @@ describe('TopMenuSection', () => {
|
||||
const model = menu.props('model') as MenuItem[]
|
||||
expect(model[0]?.disabled).toBe(false)
|
||||
})
|
||||
|
||||
it('shows manager red dot only for manager conflicts', async () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
// Release red dot is mocked as true globally for this test file.
|
||||
expect(wrapper.find('span.bg-red-500').exists()).toBe(false)
|
||||
|
||||
mockData.setShowConflictRedDot(true)
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('span.bg-red-500').exists()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -145,6 +145,7 @@ import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useReleaseStore } from '@/platform/updates/common/releaseStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
@@ -172,6 +173,8 @@ const sidebarTabStore = useSidebarTabStore()
|
||||
const { activeJobsCount } = storeToRefs(queueStore)
|
||||
const { isOverlayExpanded: isQueueOverlayExpanded } = storeToRefs(queueUIStore)
|
||||
const { activeSidebarTabId } = storeToRefs(sidebarTabStore)
|
||||
const releaseStore = useReleaseStore()
|
||||
const { shouldShowRedDot: showReleaseRedDot } = storeToRefs(releaseStore)
|
||||
const { shouldShowRedDot: shouldShowConflictRedDot } =
|
||||
useConflictAcknowledgment()
|
||||
const isTopMenuHovered = ref(false)
|
||||
@@ -233,8 +236,10 @@ const queueContextMenuItems = computed<MenuItem[]>(() => [
|
||||
}
|
||||
])
|
||||
|
||||
// Use either release red dot or conflict red dot
|
||||
const shouldShowRedDot = computed((): boolean => {
|
||||
return shouldShowConflictRedDot.value
|
||||
const releaseRedDot = showReleaseRedDot.value
|
||||
return releaseRedDot || shouldShowConflictRedDot.value
|
||||
})
|
||||
|
||||
// Right side panel toggle
|
||||
@@ -282,9 +287,8 @@ const openCustomNodeManager = async () => {
|
||||
} catch (error) {
|
||||
try {
|
||||
toastErrorHandler(error)
|
||||
} catch (toastError) {
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error(toastError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ vi.mock('primevue/progressspinner', () => ({
|
||||
default: { template: '<div class="progress-spinner" />' }
|
||||
}))
|
||||
|
||||
describe('WorkspaceAuthGate', () => {
|
||||
describe(WorkspaceAuthGate.__name ?? 'WorkspaceAuthGate', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsCloud.value = true
|
||||
|
||||
@@ -51,7 +51,7 @@ vi.mock('@/stores/commandStore', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
describe('EssentialsPanel', () => {
|
||||
describe(EssentialsPanel.__name ?? 'EssentialsPanel', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ vi.mock('vue-i18n', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
describe('ShortcutsList', () => {
|
||||
describe(ShortcutsList.__name ?? 'ShortcutsList', () => {
|
||||
const mockCommands: ComfyCommandImpl[] = [
|
||||
{
|
||||
id: 'Workflow.New',
|
||||
|
||||
@@ -106,7 +106,7 @@ const mountBaseTerminal = () => {
|
||||
})
|
||||
}
|
||||
|
||||
describe('BaseTerminal', () => {
|
||||
describe(BaseTerminal.__name ?? 'BaseTerminal', () => {
|
||||
let wrapper: VueWrapper<InstanceType<typeof BaseTerminal>> | undefined
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -62,8 +62,8 @@ const terminalCreated = (
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadLogEntries()
|
||||
} catch (err) {
|
||||
console.error('Error loading logs', err)
|
||||
} catch (error) {
|
||||
console.error('Error loading logs', error)
|
||||
// On older backends the endpoints won't exist
|
||||
errorMessage.value =
|
||||
'Unable to load logs, please ensure you have updated your ComfyUI backend.'
|
||||
|
||||
@@ -78,9 +78,7 @@ interface Props {
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isActive: false
|
||||
})
|
||||
const { item, isActive = false } = defineProps<Props>()
|
||||
|
||||
const nodeDefStore = useNodeDefStore()
|
||||
const hasMissingNodes = computed(() =>
|
||||
@@ -103,7 +101,7 @@ const rename = async (
|
||||
) => {
|
||||
if (newName && newName !== initialName) {
|
||||
// Synchronize the node titles with the new name
|
||||
props.item.updateTitle?.(newName)
|
||||
item.updateTitle?.(newName)
|
||||
|
||||
if (workflowStore.activeSubgraph) {
|
||||
workflowStore.activeSubgraph.name = newName
|
||||
@@ -127,13 +125,13 @@ const rename = async (
|
||||
}
|
||||
}
|
||||
|
||||
const isRoot = props.item.key === 'root'
|
||||
const isRoot = item.key === 'root'
|
||||
|
||||
const tooltipText = computed(() => {
|
||||
if (hasMissingNodes.value && isRoot) {
|
||||
return t('breadcrumbsMenu.missingNodesWarning')
|
||||
}
|
||||
return props.item.label
|
||||
return item.label
|
||||
})
|
||||
|
||||
const startRename = async () => {
|
||||
@@ -145,7 +143,7 @@ const startRename = async () => {
|
||||
}
|
||||
|
||||
isEditing.value = true
|
||||
itemLabel.value = props.item.label as string
|
||||
itemLabel.value = item.label as string
|
||||
void nextTick(() => {
|
||||
if (itemInputRef.value?.$el) {
|
||||
itemInputRef.value.$el.focus()
|
||||
@@ -165,12 +163,12 @@ const handleClick = (event: MouseEvent) => {
|
||||
}
|
||||
|
||||
if (event.detail === 1) {
|
||||
if (props.isActive) {
|
||||
if (isActive) {
|
||||
menu.value?.toggle(event)
|
||||
} else {
|
||||
props.item.command?.({ item: props.item, originalEvent: event })
|
||||
item.command?.({ item, originalEvent: event })
|
||||
}
|
||||
} else if (props.isActive && event.detail === 2) {
|
||||
} else if (isActive && event.detail === 2) {
|
||||
menu.value?.hide()
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
@@ -180,7 +178,7 @@ const handleClick = (event: MouseEvent) => {
|
||||
|
||||
const inputBlur = async (doRename: boolean) => {
|
||||
if (doRename) {
|
||||
await rename(itemLabel.value, props.item.label as string)
|
||||
await rename(itemLabel.value, item.label as string)
|
||||
}
|
||||
|
||||
isEditing.value = false
|
||||
|
||||
@@ -7,123 +7,128 @@ import { createApp, nextTick } from 'vue'
|
||||
|
||||
import ColorCustomizationSelector from './ColorCustomizationSelector.vue'
|
||||
|
||||
describe('ColorCustomizationSelector', () => {
|
||||
const colorOptions = [
|
||||
{ name: 'Blue', value: '#0d6efd' },
|
||||
{ name: 'Green', value: '#28a745' }
|
||||
]
|
||||
describe(
|
||||
ColorCustomizationSelector.__name ?? 'ColorCustomizationSelector',
|
||||
() => {
|
||||
const colorOptions = [
|
||||
{ name: 'Blue', value: '#0d6efd' },
|
||||
{ name: 'Green', value: '#28a745' }
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
// Setup PrimeVue
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
})
|
||||
beforeEach(() => {
|
||||
// Setup PrimeVue
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
})
|
||||
|
||||
const mountComponent = (props = {}) => {
|
||||
return mount(ColorCustomizationSelector, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { SelectButton, ColorPicker }
|
||||
},
|
||||
props: {
|
||||
modelValue: null,
|
||||
colorOptions,
|
||||
...props
|
||||
}
|
||||
const mountComponent = (props = {}) => {
|
||||
return mount(ColorCustomizationSelector, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { SelectButton, ColorPicker }
|
||||
},
|
||||
props: {
|
||||
modelValue: null,
|
||||
colorOptions,
|
||||
...props
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
it('renders predefined color options and custom option', () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
expect(selectButton.props('options')).toHaveLength(
|
||||
colorOptions.length + 1
|
||||
)
|
||||
expect(selectButton.props('options')?.at(-1)?.name).toBe('_custom')
|
||||
})
|
||||
|
||||
it('initializes with predefined color when provided', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: '#0d6efd'
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
expect(selectButton.props('modelValue')).toEqual({
|
||||
name: 'Blue',
|
||||
value: '#0d6efd'
|
||||
})
|
||||
})
|
||||
|
||||
it('initializes with custom color when non-predefined color provided', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: '#123456'
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
|
||||
expect(selectButton.props('modelValue').name).toBe('_custom')
|
||||
expect(colorPicker.props('modelValue')).toBe('123456')
|
||||
})
|
||||
|
||||
it('shows color picker when custom option is selected', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// Select custom option
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
expect(wrapper.findComponent(ColorPicker).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('emits update when predefined color is selected', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
await selectButton.setValue(colorOptions[0])
|
||||
|
||||
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['#0d6efd'])
|
||||
})
|
||||
|
||||
it('emits update when custom color is changed', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// Select custom option
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
// Change custom color
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
await colorPicker.setValue('ff0000')
|
||||
|
||||
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['#ff0000'])
|
||||
})
|
||||
|
||||
it('inherits color from previous selection when switching to custom', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// First select a predefined color
|
||||
await selectButton.setValue(colorOptions[0])
|
||||
|
||||
// Then switch to custom
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
expect(colorPicker.props('modelValue')).toBe('0d6efd')
|
||||
})
|
||||
|
||||
it('handles null modelValue correctly', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
expect(selectButton.props('modelValue')).toEqual({
|
||||
name: '_custom',
|
||||
value: ''
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
it('renders predefined color options and custom option', () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
expect(selectButton.props('options')).toHaveLength(colorOptions.length + 1)
|
||||
expect(selectButton.props('options')?.at(-1)?.name).toBe('_custom')
|
||||
})
|
||||
|
||||
it('initializes with predefined color when provided', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: '#0d6efd'
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
expect(selectButton.props('modelValue')).toEqual({
|
||||
name: 'Blue',
|
||||
value: '#0d6efd'
|
||||
})
|
||||
})
|
||||
|
||||
it('initializes with custom color when non-predefined color provided', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: '#123456'
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
|
||||
expect(selectButton.props('modelValue').name).toBe('_custom')
|
||||
expect(colorPicker.props('modelValue')).toBe('123456')
|
||||
})
|
||||
|
||||
it('shows color picker when custom option is selected', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// Select custom option
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
expect(wrapper.findComponent(ColorPicker).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('emits update when predefined color is selected', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
await selectButton.setValue(colorOptions[0])
|
||||
|
||||
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['#0d6efd'])
|
||||
})
|
||||
|
||||
it('emits update when custom color is changed', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// Select custom option
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
// Change custom color
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
await colorPicker.setValue('ff0000')
|
||||
|
||||
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['#ff0000'])
|
||||
})
|
||||
|
||||
it('inherits color from previous selection when switching to custom', async () => {
|
||||
const wrapper = mountComponent()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
|
||||
// First select a predefined color
|
||||
await selectButton.setValue(colorOptions[0])
|
||||
|
||||
// Then switch to custom
|
||||
await selectButton.setValue({ name: '_custom', value: '' })
|
||||
|
||||
const colorPicker = wrapper.findComponent(ColorPicker)
|
||||
expect(colorPicker.props('modelValue')).toBe('0d6efd')
|
||||
})
|
||||
|
||||
it('handles null modelValue correctly', async () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
const selectButton = wrapper.findComponent(SelectButton)
|
||||
expect(selectButton.props('modelValue')).toEqual({
|
||||
name: '_custom',
|
||||
value: ''
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
const { renderFunction } = defineProps<{
|
||||
renderFunction: () => HTMLElement
|
||||
}>()
|
||||
|
||||
@@ -14,12 +14,12 @@ const container = ref<HTMLElement | null>(null)
|
||||
function renderContent() {
|
||||
if (container.value) {
|
||||
container.value.innerHTML = ''
|
||||
const element = props.renderFunction()
|
||||
const element = renderFunction()
|
||||
container.value.appendChild(element)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(renderContent)
|
||||
|
||||
watch(() => props.renderFunction, renderContent)
|
||||
watch(() => renderFunction, renderContent)
|
||||
</script>
|
||||
|
||||
@@ -52,7 +52,7 @@ import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
const { modelValue, initialIcon, initialColor } = defineProps<{
|
||||
modelValue: boolean
|
||||
initialIcon?: string
|
||||
initialColor?: string
|
||||
@@ -64,7 +64,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
get: () => modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
@@ -96,17 +96,13 @@ const defaultIcon = iconOptions.find(
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const selectedIcon = ref<{ name: string; value: string }>(defaultIcon)
|
||||
const finalColor = ref(
|
||||
props.initialColor || nodeBookmarkStore.defaultBookmarkColor
|
||||
)
|
||||
const finalColor = ref(initialColor || nodeBookmarkStore.defaultBookmarkColor)
|
||||
|
||||
const resetCustomization = () => {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
selectedIcon.value =
|
||||
iconOptions.find((option) => option.value === props.initialIcon) ||
|
||||
defaultIcon
|
||||
finalColor.value =
|
||||
props.initialColor || nodeBookmarkStore.defaultBookmarkColor
|
||||
iconOptions.find((option) => option.value === initialIcon) || defaultIcon
|
||||
finalColor.value = initialColor || nodeBookmarkStore.defaultBookmarkColor
|
||||
}
|
||||
|
||||
const confirmCustomization = () => {
|
||||
@@ -119,7 +115,7 @@ const closeDialog = () => {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => modelValue,
|
||||
(newValue: boolean) => {
|
||||
if (newValue) {
|
||||
resetCustomization()
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{ col.header }}
|
||||
</div>
|
||||
<div>
|
||||
{{ formatValue(props.device[col.field], col.field) }}
|
||||
{{ formatValue(device[col.field], col.field) }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -15,7 +15,7 @@
|
||||
import type { DeviceStats } from '@/schemas/apiSchema'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
const { device } = defineProps<{
|
||||
device: DeviceStats
|
||||
}>()
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createApp } from 'vue'
|
||||
|
||||
import EditableText from './EditableText.vue'
|
||||
|
||||
describe('EditableText', () => {
|
||||
describe(EditableText.__name ?? 'EditableText', () => {
|
||||
beforeAll(() => {
|
||||
// Create a Vue app instance for PrimeVue
|
||||
const app = createApp({})
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<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>
|
||||
<span class="file-type" :title="displayHint">{{ displayLabel }}</span>
|
||||
</div>
|
||||
<div v-if="props.error" class="file-error">
|
||||
{{ props.error }}
|
||||
<div v-if="error" class="file-error">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
class="file-action-button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:disabled="!!props.error"
|
||||
:disabled="!!error"
|
||||
@click="triggerDownload"
|
||||
>
|
||||
<i class="pi pi-download" />
|
||||
{{ $t('g.downloadWithSize', { size: fileSize }) }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="(status === null || status === 'error') && !!props.url"
|
||||
v-if="(status === null || status === 'error') && !!url"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@click="copyURL"
|
||||
@@ -53,7 +53,7 @@
|
||||
class="file-action-button"
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:disabled="!!props.error"
|
||||
:disabled="!!error"
|
||||
@click="triggerPauseDownload"
|
||||
>
|
||||
<i class="pi pi-pause-circle" />
|
||||
@@ -66,7 +66,7 @@
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
:aria-label="t('electronFileDownload.resume')"
|
||||
:disabled="!!props.error"
|
||||
:disabled="!!error"
|
||||
@click="triggerResumeDownload"
|
||||
>
|
||||
<i class="pi pi-play-circle" />
|
||||
@@ -78,7 +78,7 @@
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
:aria-label="t('electronFileDownload.cancel')"
|
||||
:disabled="!!props.error"
|
||||
:disabled="!!error"
|
||||
@click="triggerCancelDownload"
|
||||
>
|
||||
<i class="pi pi-times-circle" />
|
||||
@@ -98,7 +98,7 @@ import { useDownload } from '@/composables/useDownload'
|
||||
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
const { url, hint, label, error } = defineProps<{
|
||||
url: string
|
||||
hint?: string
|
||||
label?: string
|
||||
@@ -106,9 +106,9 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const label = computed(() => props.label || props.url.split('/').pop())
|
||||
const hint = computed(() => props.hint || props.url)
|
||||
const download = useDownload(props.url)
|
||||
const displayLabel = computed(() => label || url.split('/').pop())
|
||||
const displayHint = computed(() => hint || url)
|
||||
const download = useDownload(url)
|
||||
const downloadProgress = ref<number>(0)
|
||||
const status = ref<string | null>(null)
|
||||
const fileSize = computed(() =>
|
||||
@@ -117,10 +117,10 @@ const fileSize = computed(() =>
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
const electronDownloadStore = useElectronDownloadStore()
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const [savePath, filename] = props.label.split('/')
|
||||
const [savePath, filename] = label.split('/')
|
||||
|
||||
electronDownloadStore.$subscribe((_, { downloads }) => {
|
||||
const download = downloads.find((download) => props.url === download.url)
|
||||
const download = downloads.find((download) => url === download.url)
|
||||
|
||||
if (download) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
@@ -132,17 +132,17 @@ electronDownloadStore.$subscribe((_, { downloads }) => {
|
||||
|
||||
const triggerDownload = async () => {
|
||||
await electronDownloadStore.start({
|
||||
url: props.url,
|
||||
url,
|
||||
savePath: savePath.trim(),
|
||||
filename: filename.trim()
|
||||
})
|
||||
}
|
||||
|
||||
const triggerCancelDownload = () => electronDownloadStore.cancel(props.url)
|
||||
const triggerPauseDownload = () => electronDownloadStore.pause(props.url)
|
||||
const triggerResumeDownload = () => electronDownloadStore.resume(props.url)
|
||||
const triggerCancelDownload = () => electronDownloadStore.cancel(url)
|
||||
const triggerPauseDownload = () => electronDownloadStore.pause(url)
|
||||
const triggerResumeDownload = () => electronDownloadStore.resume(url)
|
||||
|
||||
const copyURL = async () => {
|
||||
await copyToClipboard(props.url)
|
||||
await copyToClipboard(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
:ref="
|
||||
(el) => {
|
||||
if (el)
|
||||
mountCustomExtension(
|
||||
props.extension as CustomExtension,
|
||||
el as HTMLElement
|
||||
)
|
||||
mountCustomExtension(extension as CustomExtension, el as HTMLElement)
|
||||
}
|
||||
"
|
||||
/>
|
||||
@@ -19,17 +16,17 @@ import { onBeforeUnmount } from 'vue'
|
||||
|
||||
import type { CustomExtension, VueExtension } from '@/types/extensionTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
const { extension } = defineProps<{
|
||||
extension: VueExtension | CustomExtension
|
||||
}>()
|
||||
|
||||
const mountCustomExtension = (extension: CustomExtension, el: HTMLElement) => {
|
||||
extension.render(el)
|
||||
const mountCustomExtension = (ext: CustomExtension, el: HTMLElement) => {
|
||||
ext.render(el)
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (props.extension.type === 'custom' && props.extension.destroy) {
|
||||
props.extension.destroy()
|
||||
if (extension.type === 'custom' && extension.destroy) {
|
||||
extension.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,35 +3,35 @@
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div>
|
||||
<div>
|
||||
<span :title="hint">{{ label }}</span>
|
||||
<span :title="displayHint">{{ displayLabel }}</span>
|
||||
</div>
|
||||
<Message
|
||||
v-if="props.error"
|
||||
v-if="error"
|
||||
severity="error"
|
||||
icon="pi pi-exclamation-triangle"
|
||||
size="small"
|
||||
variant="outlined"
|
||||
class="my-2 h-min max-w-xs px-1"
|
||||
:title="props.error"
|
||||
:title="error"
|
||||
:pt="{
|
||||
text: { class: 'overflow-hidden text-ellipsis' }
|
||||
}"
|
||||
>
|
||||
{{ props.error }}
|
||||
{{ error }}
|
||||
</Message>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
:disabled="!!props.error"
|
||||
:title="props.url"
|
||||
:disabled="!!error"
|
||||
:title="url"
|
||||
@click="download.triggerBrowserDownload"
|
||||
>
|
||||
{{ $t('g.downloadWithSize', { size: fileSize }) }}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button variant="secondary" :disabled="!!props.error" @click="copyURL">
|
||||
<Button variant="secondary" :disabled="!!error" @click="copyURL">
|
||||
{{ $t('g.copyURL') }}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -47,22 +47,22 @@ import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
import { useDownload } from '@/composables/useDownload'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
const { url, hint, label, error } = defineProps<{
|
||||
url: string
|
||||
hint?: string
|
||||
label?: string
|
||||
error?: string
|
||||
}>()
|
||||
|
||||
const label = computed(() => props.label || props.url.split('/').pop())
|
||||
const displayLabel = computed(() => label || url.split('/').pop())
|
||||
|
||||
const hint = computed(() => props.hint || props.url)
|
||||
const download = useDownload(props.url)
|
||||
const displayHint = computed(() => hint || url)
|
||||
const download = useDownload(url)
|
||||
const fileSize = computed(() =>
|
||||
download.fileSize.value ? formatSize(download.fileSize.value) : '?'
|
||||
)
|
||||
const copyURL = async () => {
|
||||
await copyToClipboard(props.url)
|
||||
await copyToClipboard(url)
|
||||
}
|
||||
|
||||
const { copyToClipboard } = useCopyToClipboard()
|
||||
|
||||
@@ -10,7 +10,7 @@ import ColorPicker from 'primevue/colorpicker'
|
||||
import InputText from 'primevue/inputtext'
|
||||
|
||||
const modelValue = defineModel<string>('modelValue')
|
||||
defineProps<{
|
||||
const { label } = defineProps<{
|
||||
label?: string
|
||||
}>()
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import { ref } from 'vue'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
|
||||
defineProps<{
|
||||
const { modelValue } = defineProps<{
|
||||
modelValue: string
|
||||
}>()
|
||||
|
||||
@@ -64,9 +64,9 @@ const handleFileUpload = (event: Event) => {
|
||||
if (target.files && target.files[0]) {
|
||||
const file = target.files[0]
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
reader.addEventListener('load', (e) => {
|
||||
emit('update:modelValue', e.target?.result as string)
|
||||
}
|
||||
})
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
<template>
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<div class="form-label flex grow items-center">
|
||||
<span
|
||||
:id="`${props.id}-label`"
|
||||
class="text-muted"
|
||||
:class="props.labelClass"
|
||||
>
|
||||
<span :id="`${id}-label`" class="text-muted" :class="labelClass">
|
||||
<slot name="name-prefix" />
|
||||
{{ props.item.name }}
|
||||
{{ item.name }}
|
||||
<i
|
||||
v-if="props.item.tooltip"
|
||||
v-tooltip="props.item.tooltip"
|
||||
v-if="item.tooltip"
|
||||
v-tooltip="item.tooltip"
|
||||
class="pi pi-info-circle bg-transparent"
|
||||
/>
|
||||
<slot name="name-suffix" />
|
||||
@@ -19,11 +15,11 @@
|
||||
</div>
|
||||
<div class="form-input flex justify-end">
|
||||
<component
|
||||
:is="markRaw(getFormComponent(props.item))"
|
||||
:id="props.id"
|
||||
:is="markRaw(getFormComponent(item))"
|
||||
:id="id"
|
||||
v-model:model-value="formValue"
|
||||
:aria-labelledby="`${props.id}-label`"
|
||||
v-bind="getFormAttrs(props.item)"
|
||||
:aria-labelledby="`${id}-label`"
|
||||
v-bind="getFormAttrs(item)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,35 +44,37 @@ import UrlInput from '@/components/common/UrlInput.vue'
|
||||
import type { FormItem } from '@/platform/settings/types'
|
||||
|
||||
const formValue = defineModel<unknown>('formValue')
|
||||
const props = defineProps<{
|
||||
const { item, id, labelClass } = defineProps<{
|
||||
item: FormItem
|
||||
id?: string
|
||||
labelClass?: string | Record<string, boolean>
|
||||
}>()
|
||||
|
||||
function getFormAttrs(item: FormItem) {
|
||||
const attrs = { ...(item.attrs || {}) }
|
||||
const inputType = item.type
|
||||
function getFormAttrs(formItem: FormItem) {
|
||||
const attrs = { ...(formItem.attrs || {}) }
|
||||
const inputType = formItem.type
|
||||
if (typeof inputType === 'function') {
|
||||
attrs['renderFunction'] = () =>
|
||||
inputType(
|
||||
props.item.name,
|
||||
(v: unknown) => (formValue.value = v),
|
||||
formItem.name,
|
||||
(v: unknown) => {
|
||||
formValue.value = v
|
||||
},
|
||||
formValue.value,
|
||||
item.attrs
|
||||
formItem.attrs
|
||||
)
|
||||
}
|
||||
switch (item.type) {
|
||||
switch (formItem.type) {
|
||||
case 'combo':
|
||||
case 'radio':
|
||||
attrs['options'] =
|
||||
typeof item.options === 'function'
|
||||
typeof formItem.options === 'function'
|
||||
? // @ts-expect-error: Audit and deprecate usage of legacy options type:
|
||||
// (value) => [string | {text: string, value: string}]
|
||||
item.options(formValue.value)
|
||||
: item.options
|
||||
formItem.options(formValue.value)
|
||||
: formItem.options
|
||||
|
||||
if (typeof item.options?.[0] !== 'string') {
|
||||
if (typeof formItem.options?.[0] !== 'string') {
|
||||
attrs['optionLabel'] = 'text'
|
||||
attrs['optionValue'] = 'value'
|
||||
}
|
||||
@@ -85,11 +83,11 @@ function getFormAttrs(item: FormItem) {
|
||||
return attrs
|
||||
}
|
||||
|
||||
function getFormComponent(item: FormItem): Component {
|
||||
if (typeof item.type === 'function') {
|
||||
function getFormComponent(formItem: FormItem): Component {
|
||||
if (typeof formItem.type === 'function') {
|
||||
return CustomFormValue
|
||||
}
|
||||
switch (item.type) {
|
||||
switch (formItem.type) {
|
||||
case 'boolean':
|
||||
return ToggleSwitch
|
||||
case 'number':
|
||||
|
||||
@@ -5,239 +5,242 @@ import { beforeAll, describe, expect, it } from 'vitest'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import type { SettingOption } from '@/platform/settings/types'
|
||||
|
||||
import FormRadioGroup from './FormRadioGroup.vue'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
|
||||
describe('FormRadioGroup', () => {
|
||||
beforeAll(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
})
|
||||
import FormRadioGroup from './FormRadioGroup.vue'
|
||||
|
||||
type FormRadioGroupProps = ComponentProps<typeof FormRadioGroup>
|
||||
const mountComponent = (props: FormRadioGroupProps, options = {}) => {
|
||||
return mount(FormRadioGroup, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { RadioButton }
|
||||
},
|
||||
props,
|
||||
...options
|
||||
describe(
|
||||
(FormRadioGroup as { __name?: string }).__name ?? 'FormRadioGroup',
|
||||
() => {
|
||||
beforeAll(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
})
|
||||
|
||||
type FormRadioGroupProps = ComponentProps<typeof FormRadioGroup>
|
||||
const mountComponent = (props: FormRadioGroupProps, options = {}) => {
|
||||
return mount(FormRadioGroup, {
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { RadioButton }
|
||||
},
|
||||
props,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
describe('normalizedOptions computed property', () => {
|
||||
it('handles string array options', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'option1',
|
||||
options: ['option1', 'option2', 'option3'],
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('option1')
|
||||
expect(radioButtons[1].props('value')).toBe('option2')
|
||||
expect(radioButtons[2].props('value')).toBe('option3')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('option1')
|
||||
expect(labels[1].text()).toBe('option2')
|
||||
expect(labels[2].text()).toBe('option3')
|
||||
})
|
||||
|
||||
it('handles SettingOption array', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Small', value: 'sm' },
|
||||
{ text: 'Medium', value: 'md' },
|
||||
{ text: 'Large', value: 'lg' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'md',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('sm')
|
||||
expect(radioButtons[1].props('value')).toBe('md')
|
||||
expect(radioButtons[2].props('value')).toBe('lg')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Small')
|
||||
expect(labels[1].text()).toBe('Medium')
|
||||
expect(labels[2].text()).toBe('Large')
|
||||
})
|
||||
|
||||
it('handles SettingOption with undefined value (uses text as value)', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Option A', value: undefined },
|
||||
{ text: 'Option B' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'Option A',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('Option A')
|
||||
expect(radioButtons[1].props('value')).toBe('Option B')
|
||||
})
|
||||
|
||||
it('handles custom object with optionLabel and optionValue', () => {
|
||||
const options = [
|
||||
{ name: 'First Option', id: '1' },
|
||||
{ name: 'Second Option', id: '2' },
|
||||
{ name: 'Third Option', id: '3' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 2,
|
||||
options,
|
||||
optionLabel: 'name',
|
||||
optionValue: 'id',
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('1')
|
||||
expect(radioButtons[1].props('value')).toBe('2')
|
||||
expect(radioButtons[2].props('value')).toBe('3')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('First Option')
|
||||
expect(labels[1].text()).toBe('Second Option')
|
||||
expect(labels[2].text()).toBe('Third Option')
|
||||
})
|
||||
|
||||
it('handles mixed array with strings and SettingOptions', () => {
|
||||
const options: (string | SettingOption)[] = [
|
||||
'Simple String',
|
||||
{ text: 'Complex Option', value: 'complex' },
|
||||
'Another String'
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'complex',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('Simple String')
|
||||
expect(radioButtons[1].props('value')).toBe('complex')
|
||||
expect(radioButtons[2].props('value')).toBe('Another String')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Simple String')
|
||||
expect(labels[1].text()).toBe('Complex Option')
|
||||
expect(labels[2].text()).toBe('Another String')
|
||||
})
|
||||
|
||||
it('handles empty options array', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null,
|
||||
options: [],
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles undefined options gracefully', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null,
|
||||
options: undefined,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles object with missing properties gracefully', () => {
|
||||
const options = [{ label: 'Option 1', val: 'opt1' }]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'opt1',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(1)
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Unknown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('component functionality', () => {
|
||||
it('sets correct input-id and name attributes', () => {
|
||||
const options = ['A', 'B']
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'A',
|
||||
options,
|
||||
id: 'my-radio-group'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].props('inputId')).toBe('my-radio-group-A')
|
||||
expect(radioButtons[0].props('name')).toBe('my-radio-group')
|
||||
expect(radioButtons[1].props('inputId')).toBe('my-radio-group-B')
|
||||
expect(radioButtons[1].props('name')).toBe('my-radio-group')
|
||||
})
|
||||
|
||||
it('associates labels with radio buttons correctly', () => {
|
||||
const options = ['Yes', 'No']
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'Yes',
|
||||
options,
|
||||
id: 'confirm-radio'
|
||||
})
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
|
||||
expect(labels[0].attributes('for')).toBe('confirm-radio-Yes')
|
||||
expect(labels[1].attributes('for')).toBe('confirm-radio-No')
|
||||
})
|
||||
|
||||
it('sets aria-describedby attribute correctly', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Option 1', value: 'opt1' },
|
||||
{ text: 'Option 2', value: 'opt2' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'opt1',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].attributes('aria-describedby')).toBe(
|
||||
'Option 1-label'
|
||||
)
|
||||
expect(radioButtons[1].attributes('aria-describedby')).toBe(
|
||||
'Option 2-label'
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('normalizedOptions computed property', () => {
|
||||
it('handles string array options', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'option1',
|
||||
options: ['option1', 'option2', 'option3'],
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('option1')
|
||||
expect(radioButtons[1].props('value')).toBe('option2')
|
||||
expect(radioButtons[2].props('value')).toBe('option3')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('option1')
|
||||
expect(labels[1].text()).toBe('option2')
|
||||
expect(labels[2].text()).toBe('option3')
|
||||
})
|
||||
|
||||
it('handles SettingOption array', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Small', value: 'sm' },
|
||||
{ text: 'Medium', value: 'md' },
|
||||
{ text: 'Large', value: 'lg' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'md',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('sm')
|
||||
expect(radioButtons[1].props('value')).toBe('md')
|
||||
expect(radioButtons[2].props('value')).toBe('lg')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Small')
|
||||
expect(labels[1].text()).toBe('Medium')
|
||||
expect(labels[2].text()).toBe('Large')
|
||||
})
|
||||
|
||||
it('handles SettingOption with undefined value (uses text as value)', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Option A', value: undefined },
|
||||
{ text: 'Option B' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'Option A',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('Option A')
|
||||
expect(radioButtons[1].props('value')).toBe('Option B')
|
||||
})
|
||||
|
||||
it('handles custom object with optionLabel and optionValue', () => {
|
||||
const options = [
|
||||
{ name: 'First Option', id: '1' },
|
||||
{ name: 'Second Option', id: '2' },
|
||||
{ name: 'Third Option', id: '3' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 2,
|
||||
options,
|
||||
optionLabel: 'name',
|
||||
optionValue: 'id',
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('1')
|
||||
expect(radioButtons[1].props('value')).toBe('2')
|
||||
expect(radioButtons[2].props('value')).toBe('3')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('First Option')
|
||||
expect(labels[1].text()).toBe('Second Option')
|
||||
expect(labels[2].text()).toBe('Third Option')
|
||||
})
|
||||
|
||||
it('handles mixed array with strings and SettingOptions', () => {
|
||||
const options: (string | SettingOption)[] = [
|
||||
'Simple String',
|
||||
{ text: 'Complex Option', value: 'complex' },
|
||||
'Another String'
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'complex',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(3)
|
||||
|
||||
expect(radioButtons[0].props('value')).toBe('Simple String')
|
||||
expect(radioButtons[1].props('value')).toBe('complex')
|
||||
expect(radioButtons[2].props('value')).toBe('Another String')
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Simple String')
|
||||
expect(labels[1].text()).toBe('Complex Option')
|
||||
expect(labels[2].text()).toBe('Another String')
|
||||
})
|
||||
|
||||
it('handles empty options array', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null,
|
||||
options: [],
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles undefined options gracefully', () => {
|
||||
const wrapper = mountComponent({
|
||||
modelValue: null,
|
||||
options: undefined,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('handles object with missing properties gracefully', () => {
|
||||
const options = [{ label: 'Option 1', val: 'opt1' }]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'opt1',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
expect(radioButtons).toHaveLength(1)
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
expect(labels[0].text()).toBe('Unknown')
|
||||
})
|
||||
})
|
||||
|
||||
describe('component functionality', () => {
|
||||
it('sets correct input-id and name attributes', () => {
|
||||
const options = ['A', 'B']
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'A',
|
||||
options,
|
||||
id: 'my-radio-group'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].props('inputId')).toBe('my-radio-group-A')
|
||||
expect(radioButtons[0].props('name')).toBe('my-radio-group')
|
||||
expect(radioButtons[1].props('inputId')).toBe('my-radio-group-B')
|
||||
expect(radioButtons[1].props('name')).toBe('my-radio-group')
|
||||
})
|
||||
|
||||
it('associates labels with radio buttons correctly', () => {
|
||||
const options = ['Yes', 'No']
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'Yes',
|
||||
options,
|
||||
id: 'confirm-radio'
|
||||
})
|
||||
|
||||
const labels = wrapper.findAll('label')
|
||||
|
||||
expect(labels[0].attributes('for')).toBe('confirm-radio-Yes')
|
||||
expect(labels[1].attributes('for')).toBe('confirm-radio-No')
|
||||
})
|
||||
|
||||
it('sets aria-describedby attribute correctly', () => {
|
||||
const options: SettingOption[] = [
|
||||
{ text: 'Option 1', value: 'opt1' },
|
||||
{ text: 'Option 2', value: 'opt2' }
|
||||
]
|
||||
|
||||
const wrapper = mountComponent({
|
||||
modelValue: 'opt1',
|
||||
options,
|
||||
id: 'test-radio'
|
||||
})
|
||||
|
||||
const radioButtons = wrapper.findAllComponents(RadioButton)
|
||||
|
||||
expect(radioButtons[0].attributes('aria-describedby')).toBe(
|
||||
'Option 1-label'
|
||||
)
|
||||
expect(radioButtons[1].attributes('aria-describedby')).toBe(
|
||||
'Option 2-label'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ import { computed } from 'vue'
|
||||
|
||||
import type { SettingOption } from '@/platform/settings/types'
|
||||
|
||||
const props = defineProps<{
|
||||
const { modelValue, options, optionLabel, optionValue, id } = defineProps<{
|
||||
modelValue: T
|
||||
options?: (string | SettingOption | Record<string, string>)[]
|
||||
optionLabel?: string
|
||||
@@ -39,9 +39,9 @@ defineEmits<{
|
||||
}>()
|
||||
|
||||
const normalizedOptions = computed<SettingOption[]>(() => {
|
||||
if (!props.options) return []
|
||||
if (!options) return []
|
||||
|
||||
return props.options.map((option) => {
|
||||
return options.map((option) => {
|
||||
if (typeof option === 'string') {
|
||||
return { text: option, value: option }
|
||||
}
|
||||
@@ -54,8 +54,8 @@ const normalizedOptions = computed<SettingOption[]>(() => {
|
||||
}
|
||||
// Handle optionLabel/optionValue
|
||||
return {
|
||||
text: option[props.optionLabel || 'text'] || 'Unknown',
|
||||
value: option[props.optionValue || 'value']
|
||||
text: option[optionLabel || 'text'] || 'Unknown',
|
||||
value: option[optionValue || 'value']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -30,24 +30,25 @@ import InputNumber from 'primevue/inputnumber'
|
||||
import Knob from 'primevue/knob'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: number
|
||||
inputClass?: string
|
||||
knobClass?: string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
resolution?: number
|
||||
}>()
|
||||
const { modelValue, inputClass, knobClass, min, max, step, resolution } =
|
||||
defineProps<{
|
||||
modelValue: number
|
||||
inputClass?: string
|
||||
knobClass?: string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
resolution?: number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: number): void
|
||||
}>()
|
||||
|
||||
const localValue = ref(props.modelValue)
|
||||
const localValue = ref(modelValue)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => modelValue,
|
||||
(newValue) => {
|
||||
localValue.value = newValue
|
||||
}
|
||||
@@ -56,18 +57,18 @@ watch(
|
||||
const updateValue = (newValue: number | null) => {
|
||||
if (newValue === null) {
|
||||
// If the input is cleared, reset to the minimum value or 0
|
||||
newValue = Number(props.min) || 0
|
||||
newValue = Number(min) || 0
|
||||
}
|
||||
|
||||
const min = Number(props.min ?? Number.NEGATIVE_INFINITY)
|
||||
const max = Number(props.max ?? Number.POSITIVE_INFINITY)
|
||||
const step = Number(props.step) || 1
|
||||
const minVal = Number(min ?? Number.NEGATIVE_INFINITY)
|
||||
const maxVal = Number(max ?? Number.POSITIVE_INFINITY)
|
||||
const stepVal = Number(step) || 1
|
||||
|
||||
// Ensure the value is within the allowed range
|
||||
newValue = Math.max(min, Math.min(max, newValue))
|
||||
newValue = Math.max(minVal, Math.min(maxVal, newValue))
|
||||
|
||||
// Round to the nearest step
|
||||
newValue = Math.round(newValue / step) * step
|
||||
newValue = Math.round(newValue / stepVal) * stepVal
|
||||
|
||||
// Update local value and emit change
|
||||
localValue.value = newValue
|
||||
@@ -76,11 +77,11 @@ const updateValue = (newValue: number | null) => {
|
||||
|
||||
const displayValue = (value: number): string => {
|
||||
updateValue(value)
|
||||
const stepString = (props.step ?? 1).toString()
|
||||
const resolution = stepString.includes('.')
|
||||
const stepString = (step ?? 1).toString()
|
||||
const decimalPlaces = stepString.includes('.')
|
||||
? stepString.split('.')[1].length
|
||||
: 0
|
||||
return value.toFixed(props.resolution ?? resolution)
|
||||
return value.toFixed(resolution ?? decimalPlaces)
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
|
||||
@@ -29,7 +29,7 @@ import InputNumber from 'primevue/inputnumber'
|
||||
import Slider from 'primevue/slider'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
const { modelValue, inputClass, sliderClass, min, max, step } = defineProps<{
|
||||
modelValue: number
|
||||
inputClass?: string
|
||||
sliderClass?: string
|
||||
@@ -42,10 +42,10 @@ const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: number): void
|
||||
}>()
|
||||
|
||||
const localValue = ref(props.modelValue)
|
||||
const localValue = ref(modelValue)
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
() => modelValue,
|
||||
(newValue) => {
|
||||
localValue.value = newValue
|
||||
}
|
||||
@@ -54,18 +54,18 @@ watch(
|
||||
const updateValue = (newValue: number | null) => {
|
||||
if (newValue === null) {
|
||||
// If the input is cleared, reset to the minimum value or 0
|
||||
newValue = Number(props.min) || 0
|
||||
newValue = Number(min) || 0
|
||||
}
|
||||
|
||||
const min = Number(props.min ?? Number.NEGATIVE_INFINITY)
|
||||
const max = Number(props.max ?? Number.POSITIVE_INFINITY)
|
||||
const step = Number(props.step) || 1
|
||||
const minVal = Number(min ?? Number.NEGATIVE_INFINITY)
|
||||
const maxVal = Number(max ?? Number.POSITIVE_INFINITY)
|
||||
const stepVal = Number(step) || 1
|
||||
|
||||
// Ensure the value is within the allowed range
|
||||
newValue = Math.max(min, Math.min(max, newValue))
|
||||
newValue = Math.max(minVal, Math.min(maxVal, newValue))
|
||||
|
||||
// Round to the nearest step
|
||||
newValue = Math.round(newValue / step) * step
|
||||
newValue = Math.round(newValue / stepVal) * stepVal
|
||||
|
||||
// Update local value and emit change
|
||||
localValue.value = newValue
|
||||
|
||||
@@ -41,7 +41,6 @@ const spinnerSizeClass = computed(() => {
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
return 'h-6 w-6 border-2'
|
||||
case 'md':
|
||||
default:
|
||||
return 'h-12 w-12 border-4'
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="no-results-placeholder h-full p-8" :class="props.class">
|
||||
<div :class="cn('no-results-placeholder h-full p-8', className)">
|
||||
<Card>
|
||||
<template #content>
|
||||
<div class="flex flex-col items-center">
|
||||
@@ -25,8 +25,16 @@
|
||||
import Card from 'primevue/card'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
const {
|
||||
class: className,
|
||||
icon,
|
||||
title,
|
||||
message,
|
||||
textClass,
|
||||
buttonLabel
|
||||
} = defineProps<{
|
||||
class?: string
|
||||
icon?: string
|
||||
title: string
|
||||
|
||||
@@ -19,7 +19,7 @@ const i18n = createI18n({
|
||||
}
|
||||
})
|
||||
|
||||
describe('SearchBox', () => {
|
||||
describe((SearchBox as { __name?: string }).__name ?? 'SearchBox', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.useFakeTimers()
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface SearchFilter {
|
||||
id: string | number
|
||||
}
|
||||
|
||||
defineProps<Omit<SearchFilter, 'id'>>()
|
||||
const { text, badge, badgeClass } = defineProps<Omit<SearchFilter, 'id'>>()
|
||||
defineEmits(['remove'])
|
||||
</script>
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
<h2 class="mb-4 text-2xl font-semibold">
|
||||
{{ $t('g.devices') }}
|
||||
</h2>
|
||||
<TabView v-if="props.stats.devices.length > 1">
|
||||
<TabView v-if="stats.devices.length > 1">
|
||||
<TabPanel
|
||||
v-for="device in props.stats.devices"
|
||||
v-for="device in stats.devices"
|
||||
:key="device.index"
|
||||
:header="device.name"
|
||||
:value="device.index"
|
||||
@@ -31,7 +31,7 @@
|
||||
<DeviceInfo :device="device" />
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
<DeviceInfo v-else :device="props.stats.devices[0]" />
|
||||
<DeviceInfo v-else :device="stats.devices[0]" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -48,16 +48,16 @@ import { isCloud } from '@/platform/distribution/types'
|
||||
import type { SystemStats } from '@/schemas/apiSchema'
|
||||
import { formatCommitHash, formatSize } from '@/utils/formatUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
const { stats } = defineProps<{
|
||||
stats: SystemStats
|
||||
}>()
|
||||
|
||||
const systemInfo = computed(() => ({
|
||||
...props.stats.system,
|
||||
argv: props.stats.system.argv.join(' ')
|
||||
...stats.system,
|
||||
argv: stats.system.argv.join(' ')
|
||||
}))
|
||||
|
||||
const hasDevices = computed(() => props.stats.devices.length > 0)
|
||||
const hasDevices = computed(() => stats.devices.length > 0)
|
||||
|
||||
type SystemInfoKey = keyof SystemStats['system']
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
v-model:selection-keys="selectionKeys"
|
||||
class="tree-explorer px-2 py-0 2xl:px-4 bg-transparent"
|
||||
:class="props.class"
|
||||
:class="className"
|
||||
:value="renderedRoot.children"
|
||||
selection-mode="single"
|
||||
:pt="{
|
||||
@@ -12,9 +12,9 @@
|
||||
nodeContent: ({ context }) => ({
|
||||
class: 'group/tree-node',
|
||||
onClick: (e: MouseEvent) =>
|
||||
onNodeContentClick(e, context.node as RenderedTreeExplorerNode<T>),
|
||||
onNodeContentClick(e, context.node as RenderedTreeExplorerNode),
|
||||
onContextmenu: (e: MouseEvent) =>
|
||||
handleContextMenu(e, context.node as RenderedTreeExplorerNode<T>)
|
||||
handleContextMenu(e, context.node as RenderedTreeExplorerNode)
|
||||
}),
|
||||
nodeToggleButton: () => ({
|
||||
onClick: (e: MouseEvent) => {
|
||||
@@ -36,11 +36,11 @@
|
||||
</Tree>
|
||||
<ContextMenu ref="menu" :model="menuItems" />
|
||||
</template>
|
||||
<script setup lang="ts" generic="T">
|
||||
<script setup lang="ts">
|
||||
import ContextMenu from 'primevue/contextmenu'
|
||||
import type { MenuItem, MenuItemCommandEvent } from 'primevue/menuitem'
|
||||
import Tree from 'primevue/tree'
|
||||
import { computed, provide, ref, shallowRef } from 'vue'
|
||||
import { computed, provide, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
|
||||
@@ -68,14 +68,14 @@ const selectionKeys = defineModel<Record<string, boolean>>('selectionKeys')
|
||||
// Tracks whether the caller has set the selectionKeys model.
|
||||
const storeSelectionKeys = selectionKeys.value !== undefined
|
||||
|
||||
const props = defineProps<{
|
||||
root: TreeExplorerNode<T>
|
||||
const { root, class: className } = defineProps<{
|
||||
root: TreeExplorerNode
|
||||
class?: string
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode<T>, event: MouseEvent): void
|
||||
(e: 'nodeDelete', node: RenderedTreeExplorerNode<T>): void
|
||||
(e: 'contextMenu', node: RenderedTreeExplorerNode<T>, event: MouseEvent): void
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
(e: 'nodeDelete', node: RenderedTreeExplorerNode): void
|
||||
(e: 'contextMenu', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
}>()
|
||||
|
||||
const {
|
||||
@@ -83,19 +83,19 @@ const {
|
||||
getAddFolderMenuItem,
|
||||
handleFolderCreation,
|
||||
addFolderCommand
|
||||
} = useTreeFolderOperations<T>(
|
||||
/* expandNode */ (node: TreeExplorerNode<T>) => {
|
||||
} = useTreeFolderOperations(
|
||||
/* expandNode */ (node: TreeExplorerNode) => {
|
||||
expandedKeys.value[node.key] = true
|
||||
}
|
||||
)
|
||||
|
||||
const renderedRoot = computed<RenderedTreeExplorerNode<T>>(() => {
|
||||
const renderedRoot = fillNodeInfo(props.root)
|
||||
const renderedRoot = computed<RenderedTreeExplorerNode>(() => {
|
||||
const renderedRoot = fillNodeInfo(root)
|
||||
return newFolderNode.value
|
||||
? combineTrees(renderedRoot, newFolderNode.value)
|
||||
: renderedRoot
|
||||
})
|
||||
const getTreeNodeIcon = (node: TreeExplorerNode<T>) => {
|
||||
const getTreeNodeIcon = (node: TreeExplorerNode) => {
|
||||
if (node.getIcon) {
|
||||
const icon = node.getIcon()
|
||||
if (icon) {
|
||||
@@ -111,9 +111,7 @@ const getTreeNodeIcon = (node: TreeExplorerNode<T>) => {
|
||||
const isExpanded = expandedKeys.value?.[node.key] ?? false
|
||||
return isExpanded ? 'pi pi-folder-open' : 'pi pi-folder'
|
||||
}
|
||||
const fillNodeInfo = (
|
||||
node: TreeExplorerNode<T>
|
||||
): RenderedTreeExplorerNode<T> => {
|
||||
const fillNodeInfo = (node: TreeExplorerNode): RenderedTreeExplorerNode => {
|
||||
const children = node.children?.map(fillNodeInfo) ?? []
|
||||
const totalLeaves = node.leaf
|
||||
? 1
|
||||
@@ -130,7 +128,7 @@ const fillNodeInfo = (
|
||||
}
|
||||
const onNodeContentClick = async (
|
||||
e: MouseEvent,
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
node: RenderedTreeExplorerNode
|
||||
) => {
|
||||
if (!storeSelectionKeys) {
|
||||
selectionKeys.value = {}
|
||||
@@ -141,22 +139,20 @@ const onNodeContentClick = async (
|
||||
emit('nodeClick', node, e)
|
||||
}
|
||||
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
||||
const menuTargetNode = shallowRef<RenderedTreeExplorerNode<T> | null>(null)
|
||||
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const extraMenuItems = computed(() => {
|
||||
const node = menuTargetNode.value
|
||||
return node?.contextMenuItems
|
||||
? typeof node.contextMenuItems === 'function'
|
||||
? node.contextMenuItems(node)
|
||||
: node.contextMenuItems
|
||||
return menuTargetNode.value?.contextMenuItems
|
||||
? typeof menuTargetNode.value.contextMenuItems === 'function'
|
||||
? menuTargetNode.value.contextMenuItems(menuTargetNode.value)
|
||||
: menuTargetNode.value.contextMenuItems
|
||||
: []
|
||||
})
|
||||
const renameEditingNode = shallowRef<RenderedTreeExplorerNode<T> | null>(null)
|
||||
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
const errorHandling = useErrorHandling()
|
||||
const handleNodeLabelEdit = async (
|
||||
n: RenderedTreeExplorerNode,
|
||||
node: RenderedTreeExplorerNode,
|
||||
newName: string
|
||||
) => {
|
||||
const node = n as RenderedTreeExplorerNode<T>
|
||||
await errorHandling.wrapWithErrorHandlingAsync(
|
||||
async () => {
|
||||
if (node.key === newFolderNode.value?.key) {
|
||||
@@ -174,36 +170,35 @@ const handleNodeLabelEdit = async (
|
||||
provide(InjectKeyHandleEditLabelFunction, handleNodeLabelEdit)
|
||||
|
||||
const { t } = useI18n()
|
||||
const renameCommand = (node: RenderedTreeExplorerNode<T>) => {
|
||||
const renameCommand = (node: RenderedTreeExplorerNode) => {
|
||||
renameEditingNode.value = node
|
||||
}
|
||||
const deleteCommand = async (node: RenderedTreeExplorerNode<T>) => {
|
||||
const deleteCommand = async (node: RenderedTreeExplorerNode) => {
|
||||
await node.handleDelete?.()
|
||||
emit('nodeDelete', node)
|
||||
}
|
||||
const menuItems = computed<MenuItem[]>(() => {
|
||||
const node = menuTargetNode.value
|
||||
return [
|
||||
getAddFolderMenuItem(node),
|
||||
const menuItems = computed<MenuItem[]>(() =>
|
||||
[
|
||||
getAddFolderMenuItem(menuTargetNode.value),
|
||||
{
|
||||
label: t('g.rename'),
|
||||
icon: 'pi pi-file-edit',
|
||||
command: () => {
|
||||
if (node) {
|
||||
renameCommand(node)
|
||||
if (menuTargetNode.value) {
|
||||
renameCommand(menuTargetNode.value)
|
||||
}
|
||||
},
|
||||
visible: node?.handleRename !== undefined
|
||||
visible: menuTargetNode.value?.handleRename !== undefined
|
||||
},
|
||||
{
|
||||
label: t('g.delete'),
|
||||
icon: 'pi pi-trash',
|
||||
command: async () => {
|
||||
if (node) {
|
||||
await deleteCommand(node)
|
||||
if (menuTargetNode.value) {
|
||||
await deleteCommand(menuTargetNode.value)
|
||||
}
|
||||
},
|
||||
visible: node?.handleDelete !== undefined,
|
||||
visible: menuTargetNode.value?.handleDelete !== undefined,
|
||||
isAsync: true // The delete command can be async
|
||||
},
|
||||
...extraMenuItems.value
|
||||
@@ -215,12 +210,9 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
})
|
||||
: undefined
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
const handleContextMenu = (
|
||||
e: MouseEvent,
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
) => {
|
||||
const handleContextMenu = (e: MouseEvent, node: RenderedTreeExplorerNode) => {
|
||||
menuTargetNode.value = node
|
||||
emit('contextMenu', node, e)
|
||||
if (menuItems.value.filter((item) => item.visible).length > 0) {
|
||||
@@ -232,13 +224,15 @@ const wrapCommandWithErrorHandler = (
|
||||
command: (event: MenuItemCommandEvent) => void,
|
||||
{ isAsync = false }: { isAsync: boolean }
|
||||
) => {
|
||||
const node = menuTargetNode.value
|
||||
return isAsync
|
||||
? errorHandling.wrapWithErrorHandlingAsync(
|
||||
command as (event: MenuItemCommandEvent) => Promise<void>,
|
||||
node?.handleError
|
||||
menuTargetNode.value?.handleError
|
||||
)
|
||||
: errorHandling.wrapWithErrorHandling(
|
||||
command,
|
||||
menuTargetNode.value?.handleError
|
||||
)
|
||||
: errorHandling.wrapWithErrorHandling(command, node?.handleError)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
|
||||
@@ -19,7 +19,7 @@ const i18n = createI18n({
|
||||
messages: {}
|
||||
})
|
||||
|
||||
describe('TreeExplorerTreeNode', () => {
|
||||
describe(TreeExplorerTreeNode.__name ?? 'TreeExplorerTreeNode', () => {
|
||||
const mockNode = {
|
||||
key: '1',
|
||||
label: 'Test Node',
|
||||
|
||||
@@ -5,21 +5,21 @@
|
||||
'tree-node',
|
||||
{
|
||||
'can-drop': canDrop,
|
||||
'tree-folder': !props.node.leaf,
|
||||
'tree-leaf': props.node.leaf
|
||||
'tree-folder': !node.leaf,
|
||||
'tree-leaf': node.leaf
|
||||
}
|
||||
]"
|
||||
:data-testid="`tree-node-${node.key}`"
|
||||
>
|
||||
<div class="node-content">
|
||||
<span class="node-label">
|
||||
<slot name="before-label" :node="props.node" />
|
||||
<slot name="before-label" :node="node" />
|
||||
<EditableText
|
||||
:model-value="node.label"
|
||||
:is-editing="isEditing"
|
||||
@edit="handleRename"
|
||||
/>
|
||||
<slot name="after-label" :node="props.node" />
|
||||
<slot name="after-label" :node="node" />
|
||||
</span>
|
||||
<Badge
|
||||
v-if="showNodeBadgeText"
|
||||
@@ -31,12 +31,12 @@
|
||||
<div
|
||||
class="node-actions flex gap-1 touch:opacity-100 motion-safe:opacity-0 motion-safe:group-hover/tree-node:opacity-100"
|
||||
>
|
||||
<slot name="actions" :node="props.node" />
|
||||
<slot name="actions" :node="node" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" generic="T">
|
||||
<script setup lang="ts">
|
||||
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview'
|
||||
import Badge from 'primevue/badge'
|
||||
import { computed, inject, ref } from 'vue'
|
||||
@@ -52,35 +52,35 @@ import type {
|
||||
TreeExplorerDragAndDropData
|
||||
} from '@/types/treeExplorerTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
node: RenderedTreeExplorerNode<T>
|
||||
const { node } = defineProps<{
|
||||
node: RenderedTreeExplorerNode
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(
|
||||
e: 'itemDropped',
|
||||
node: RenderedTreeExplorerNode<T>,
|
||||
data: RenderedTreeExplorerNode<T>
|
||||
node: RenderedTreeExplorerNode,
|
||||
data: RenderedTreeExplorerNode
|
||||
): void
|
||||
(e: 'dragStart', node: RenderedTreeExplorerNode<T>): void
|
||||
(e: 'dragEnd', node: RenderedTreeExplorerNode<T>): void
|
||||
(e: 'dragStart', node: RenderedTreeExplorerNode): void
|
||||
(e: 'dragEnd', node: RenderedTreeExplorerNode): void
|
||||
}>()
|
||||
|
||||
const nodeBadgeText = computed<string>(() => {
|
||||
if (props.node.leaf) {
|
||||
if (node.leaf) {
|
||||
return ''
|
||||
}
|
||||
if (props.node.badgeText !== undefined && props.node.badgeText !== null) {
|
||||
return props.node.badgeText
|
||||
if (node.badgeText !== undefined && node.badgeText !== null) {
|
||||
return node.badgeText
|
||||
}
|
||||
return props.node.totalLeaves.toString()
|
||||
return node.totalLeaves.toString()
|
||||
})
|
||||
const showNodeBadgeText = computed<boolean>(() => nodeBadgeText.value !== '')
|
||||
|
||||
const isEditing = computed<boolean>(() => props.node.isEditingLabel ?? false)
|
||||
const isEditing = computed<boolean>(() => node.isEditingLabel ?? false)
|
||||
const handleEditLabel = inject(InjectKeyHandleEditLabelFunction)
|
||||
const handleRename = (newName: string) => {
|
||||
handleEditLabel?.(props.node as RenderedTreeExplorerNode, newName)
|
||||
handleEditLabel?.(node, newName)
|
||||
}
|
||||
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
@@ -89,21 +89,21 @@ const canDrop = ref(false)
|
||||
const treeNodeElementGetter = () =>
|
||||
container.value?.closest('.p-tree-node-content') as HTMLElement
|
||||
|
||||
if (props.node.draggable) {
|
||||
if (node.draggable) {
|
||||
usePragmaticDraggable(treeNodeElementGetter, {
|
||||
getInitialData: () => {
|
||||
return {
|
||||
type: 'tree-explorer-node',
|
||||
data: props.node
|
||||
data: node
|
||||
}
|
||||
},
|
||||
onDragStart: () => emit('dragStart', props.node),
|
||||
onDrop: () => emit('dragEnd', props.node),
|
||||
onGenerateDragPreview: props.node.renderDragPreview
|
||||
onDragStart: () => emit('dragStart', node),
|
||||
onDrop: () => emit('dragEnd', node),
|
||||
onGenerateDragPreview: node.renderDragPreview
|
||||
? ({ nativeSetDragImage }) => {
|
||||
setCustomNativeDragPreview({
|
||||
render: ({ container }) => {
|
||||
return props.node.renderDragPreview?.(container)
|
||||
return node.renderDragPreview?.(container)
|
||||
},
|
||||
nativeSetDragImage
|
||||
})
|
||||
@@ -112,18 +112,14 @@ if (props.node.draggable) {
|
||||
})
|
||||
}
|
||||
|
||||
if (props.node.droppable) {
|
||||
if (node.droppable) {
|
||||
usePragmaticDroppable(treeNodeElementGetter, {
|
||||
onDrop: async (event) => {
|
||||
const dndData = event.source.data as TreeExplorerDragAndDropData
|
||||
if (dndData.type === 'tree-explorer-node') {
|
||||
await props.node.handleDrop?.(dndData as TreeExplorerDragAndDropData<T>)
|
||||
await node.handleDrop?.(dndData)
|
||||
canDrop.value = false
|
||||
emit(
|
||||
'itemDropped',
|
||||
props.node,
|
||||
dndData.data as RenderedTreeExplorerNode<T>
|
||||
)
|
||||
emit('itemDropped', node, dndData.data)
|
||||
}
|
||||
},
|
||||
onDragEnter: (event) => {
|
||||
|
||||
@@ -6,10 +6,11 @@ import InputText from 'primevue/inputtext'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { createApp, nextTick } from 'vue'
|
||||
|
||||
import UrlInput from './UrlInput.vue'
|
||||
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
|
||||
describe('UrlInput', () => {
|
||||
import UrlInput from './UrlInput.vue'
|
||||
|
||||
describe(UrlInput.__name ?? 'UrlInput', () => {
|
||||
beforeEach(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
'pi pi-times cursor-pointer text-red-500':
|
||||
validationState === ValidationState.INVALID
|
||||
}"
|
||||
@click="validateUrl(props.modelValue)"
|
||||
@click="validateUrl(model)"
|
||||
/>
|
||||
</IconField>
|
||||
</template>
|
||||
@@ -32,40 +32,34 @@ import { isValidUrl } from '@/utils/formatUtil'
|
||||
import { checkUrlReachable } from '@/utils/networkUtil'
|
||||
import { ValidationState } from '@/utils/validationUtil'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
const model = defineModel<string>({ required: true })
|
||||
|
||||
const { validateUrlFn } = defineProps<{
|
||||
validateUrlFn?: (url: string) => Promise<boolean>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string]
|
||||
'state-change': [state: ValidationState]
|
||||
}>()
|
||||
|
||||
const validationState = ref<ValidationState>(ValidationState.IDLE)
|
||||
|
||||
const cleanInput = (value: string): string =>
|
||||
value ? value.replace(/\s+/g, '') : ''
|
||||
value ? value.replaceAll(/\s+/g, '') : ''
|
||||
|
||||
// Add internal value state
|
||||
const internalValue = ref(cleanInput(props.modelValue))
|
||||
const internalValue = ref(cleanInput(model.value))
|
||||
|
||||
// Watch for external modelValue changes
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue: string) => {
|
||||
internalValue.value = cleanInput(newValue)
|
||||
await validateUrl(newValue)
|
||||
}
|
||||
)
|
||||
watch(model, async (newValue: string) => {
|
||||
internalValue.value = cleanInput(newValue)
|
||||
await validateUrl(newValue)
|
||||
})
|
||||
|
||||
watch(validationState, (newState) => {
|
||||
emit('state-change', newState)
|
||||
})
|
||||
|
||||
// Validate on mount
|
||||
onMounted(async () => {
|
||||
await validateUrl(props.modelValue)
|
||||
await validateUrl(model.value)
|
||||
})
|
||||
|
||||
const handleInput = (value: string | undefined) => {
|
||||
@@ -87,7 +81,7 @@ const handleBlur = async () => {
|
||||
}
|
||||
|
||||
// Emit the update only on blur
|
||||
emit('update:modelValue', normalizedUrl)
|
||||
model.value = normalizedUrl
|
||||
}
|
||||
|
||||
// Default validation implementation
|
||||
@@ -113,7 +107,7 @@ const validateUrl = async (value: string) => {
|
||||
|
||||
validationState.value = ValidationState.LOADING
|
||||
try {
|
||||
const isValid = await (props.validateUrlFn ?? defaultValidateUrl)(url)
|
||||
const isValid = await (validateUrlFn ?? defaultValidateUrl)(url)
|
||||
validationState.value = isValid
|
||||
? ValidationState.VALID
|
||||
: ValidationState.INVALID
|
||||
|
||||
@@ -23,7 +23,7 @@ const i18n = createI18n({
|
||||
}
|
||||
})
|
||||
|
||||
describe('UserAvatar', () => {
|
||||
describe(UserAvatar.__name ?? 'UserAvatar', () => {
|
||||
beforeEach(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
|
||||
@@ -39,7 +39,7 @@ vi.mock('@/stores/firebaseAuthStore', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('UserCredit', () => {
|
||||
describe(UserCredit.__name ?? 'UserCredit', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockBalance.value = {
|
||||
|
||||
@@ -444,7 +444,6 @@ const distributions = computed(() => {
|
||||
return [TemplateIncludeOnDistributionEnum.Cloud]
|
||||
case 'localhost':
|
||||
return [TemplateIncludeOnDistributionEnum.Local]
|
||||
case 'desktop':
|
||||
default:
|
||||
if (systemStatsStore.systemStats?.system.os === 'darwin') {
|
||||
return [
|
||||
@@ -595,12 +594,10 @@ const coordinateNavAndSort = (source: 'nav' | 'sort') => {
|
||||
// When navigating away from 'Popular' category while sort is 'Popular', reset sort to default.
|
||||
sortBy.value = 'default'
|
||||
}
|
||||
} else if (source === 'sort') {
|
||||
} else if (source === 'sort' && isPopularNav && !isPopularSort) {
|
||||
// When sort is changed away from 'Popular' while in the 'Popular' category,
|
||||
// reset the category to 'All Templates' to avoid a confusing state.
|
||||
if (isPopularNav && !isPopularSort) {
|
||||
selectedNavItem.value = 'all'
|
||||
}
|
||||
selectedNavItem.value = 'all'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,37 +678,37 @@ const runsOnOptions = computed(() =>
|
||||
const modelFilterLabel = computed(() => {
|
||||
if (selectedModelObjects.value.length === 0) {
|
||||
return t('templateWorkflows.modelFilter', 'Model Filter')
|
||||
} else if (selectedModelObjects.value.length === 1) {
|
||||
return selectedModelObjects.value[0].name
|
||||
} else {
|
||||
return t('templateWorkflows.modelsSelected', {
|
||||
count: selectedModelObjects.value.length
|
||||
})
|
||||
}
|
||||
if (selectedModelObjects.value.length === 1) {
|
||||
return selectedModelObjects.value[0].name
|
||||
}
|
||||
return t('templateWorkflows.modelsSelected', {
|
||||
count: selectedModelObjects.value.length
|
||||
})
|
||||
})
|
||||
|
||||
const useCaseFilterLabel = computed(() => {
|
||||
if (selectedUseCaseObjects.value.length === 0) {
|
||||
return t('templateWorkflows.useCaseFilter', 'Use Case')
|
||||
} else if (selectedUseCaseObjects.value.length === 1) {
|
||||
return selectedUseCaseObjects.value[0].name
|
||||
} else {
|
||||
return t('templateWorkflows.useCasesSelected', {
|
||||
count: selectedUseCaseObjects.value.length
|
||||
})
|
||||
}
|
||||
if (selectedUseCaseObjects.value.length === 1) {
|
||||
return selectedUseCaseObjects.value[0].name
|
||||
}
|
||||
return t('templateWorkflows.useCasesSelected', {
|
||||
count: selectedUseCaseObjects.value.length
|
||||
})
|
||||
})
|
||||
|
||||
const runsOnFilterLabel = computed(() => {
|
||||
if (selectedRunsOnObjects.value.length === 0) {
|
||||
return t('templateWorkflows.runsOnFilter', 'Runs On')
|
||||
} else if (selectedRunsOnObjects.value.length === 1) {
|
||||
return selectedRunsOnObjects.value[0].name
|
||||
} else {
|
||||
return t('templateWorkflows.runsOnSelected', {
|
||||
count: selectedRunsOnObjects.value.length
|
||||
})
|
||||
}
|
||||
if (selectedRunsOnObjects.value.length === 1) {
|
||||
return selectedRunsOnObjects.value[0].name
|
||||
}
|
||||
return t('templateWorkflows.runsOnSelected', {
|
||||
count: selectedRunsOnObjects.value.length
|
||||
})
|
||||
})
|
||||
|
||||
// Sort options
|
||||
|
||||
@@ -26,7 +26,7 @@ const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
return undefined
|
||||
return
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
const { title } = defineProps<{
|
||||
title?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -120,7 +120,13 @@ import { useDialogService } from '@/services/dialogService'
|
||||
import type { ConfirmationDialogType } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const props = defineProps<{
|
||||
const {
|
||||
message,
|
||||
type,
|
||||
onConfirm: onConfirmProp,
|
||||
itemList,
|
||||
hint
|
||||
} = defineProps<{
|
||||
message: string
|
||||
type: ConfirmationDialogType
|
||||
onConfirm: (value?: boolean) => void
|
||||
@@ -143,14 +149,14 @@ function openBlueprintOverwriteSetting() {
|
||||
const doNotAskAgain = ref(false)
|
||||
|
||||
const onDeny = () => {
|
||||
props.onConfirm(false)
|
||||
onConfirmProp(false)
|
||||
useDialogStore().closeDialog()
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
if (props.type === 'overwriteBlueprint' && doNotAskAgain.value)
|
||||
if (type === 'overwriteBlueprint' && doNotAskAgain.value)
|
||||
void useSettingStore().set('Comfy.Workflow.WarnBlueprintOverwrite', false)
|
||||
props.onConfirm(true)
|
||||
onConfirmProp(true)
|
||||
useDialogStore().closeDialog()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ const createMockNode = (type: string, version?: string): LGraphNode =>
|
||||
outputs: []
|
||||
})
|
||||
|
||||
describe('MissingCoreNodesMessage', () => {
|
||||
describe(MissingCoreNodesMessage.__name ?? 'MissingCoreNodesMessage', () => {
|
||||
const mockSystemStatsStore = {
|
||||
systemStats: null as { system?: { comfyui_version?: string } } | null,
|
||||
refetchSystemStats: vi.fn()
|
||||
|
||||
@@ -94,7 +94,7 @@ interface ModelInfo {
|
||||
folder_path?: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
const { missingModels: missingModelsProp, paths } = defineProps<{
|
||||
missingModels: ModelInfo[]
|
||||
paths: Record<string, string[]>
|
||||
}>()
|
||||
@@ -113,9 +113,9 @@ function openShowMissingModelsSetting() {
|
||||
|
||||
const modelDownloads = ref<Record<string, ModelInfo>>({})
|
||||
const missingModels = computed(() => {
|
||||
return props.missingModels.map((model) => {
|
||||
const paths = props.paths[model.directory]
|
||||
if (model.directory_invalid || !paths) {
|
||||
return missingModelsProp.map((model) => {
|
||||
const modelPaths = paths[model.directory]
|
||||
if (model.directory_invalid || !modelPaths) {
|
||||
return {
|
||||
label: `${model.directory} / ${model.name}`,
|
||||
url: model.url,
|
||||
@@ -130,7 +130,7 @@ const missingModels = computed(() => {
|
||||
name: model.name,
|
||||
directory: model.directory,
|
||||
url: model.url,
|
||||
folder_path: paths[0]
|
||||
folder_path: modelPaths[0]
|
||||
}
|
||||
modelDownloads.value[model.name] = downloadInfo
|
||||
if (!whiteListedUrls.has(model.url)) {
|
||||
@@ -157,7 +157,7 @@ const missingModels = computed(() => {
|
||||
progress: downloadInfo.progress,
|
||||
error: downloadInfo.error,
|
||||
name: model.name,
|
||||
paths: paths,
|
||||
paths: modelPaths,
|
||||
folderPath: downloadInfo.folder_path
|
||||
}
|
||||
})
|
||||
|
||||
@@ -54,7 +54,7 @@ import { isCloud } from '@/platform/distribution/types'
|
||||
import type { MissingNodeType } from '@/types/comfy'
|
||||
import { useMissingNodes } from '@/workbench/extensions/manager/composables/nodePack/useMissingNodes'
|
||||
|
||||
const props = defineProps<{
|
||||
const { missingNodeTypes } = defineProps<{
|
||||
missingNodeTypes: MissingNodeType[]
|
||||
}>()
|
||||
|
||||
@@ -63,7 +63,7 @@ const { missingCoreNodes } = useMissingNodes()
|
||||
|
||||
const uniqueNodes = computed(() => {
|
||||
const seenTypes = new Set()
|
||||
return props.missingNodeTypes
|
||||
return missingNodeTypes
|
||||
.filter((node) => {
|
||||
const type = typeof node === 'object' ? node.type : node
|
||||
if (seenTypes.has(type)) return false
|
||||
|
||||
@@ -25,17 +25,22 @@ import { ref } from 'vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const props = defineProps<{
|
||||
const {
|
||||
message,
|
||||
defaultValue,
|
||||
onConfirm: onConfirmProp,
|
||||
placeholder
|
||||
} = defineProps<{
|
||||
message: string
|
||||
defaultValue: string
|
||||
onConfirm: (value: string) => void
|
||||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const inputValue = ref<string>(props.defaultValue)
|
||||
const inputValue = ref<string>(defaultValue)
|
||||
|
||||
const onConfirm = () => {
|
||||
props.onConfirm(inputValue.value)
|
||||
onConfirmProp(inputValue.value)
|
||||
useDialogStore().closeDialog()
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ const mountOption = (
|
||||
}
|
||||
})
|
||||
|
||||
describe('CreditTopUpOption', () => {
|
||||
describe(CreditTopUpOption.__name ?? 'CreditTopUpOption', () => {
|
||||
it('renders credit amount and description', () => {
|
||||
const wrapper = mountOption({ credits: 5000, description: '~500 videos*' })
|
||||
expect(wrapper.text()).toContain('5,000')
|
||||
|
||||
@@ -11,23 +11,20 @@ import { computed } from 'vue'
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
|
||||
const props = defineProps<{
|
||||
const { errorMessage, repoOwner, repoName } = defineProps<{
|
||||
errorMessage: string
|
||||
repoOwner: string
|
||||
repoName: string
|
||||
}>()
|
||||
|
||||
const queryString = computed(() => props.errorMessage + ' is:issue')
|
||||
const queryString = computed(() => `${errorMessage} is:issue`)
|
||||
|
||||
/**
|
||||
* Open GitHub issues search and track telemetry.
|
||||
*/
|
||||
const openGitHubIssues = () => {
|
||||
useTelemetry()?.trackUiButtonClicked({
|
||||
button_id: 'error_dialog_find_existing_issues_clicked'
|
||||
})
|
||||
const query = encodeURIComponent(queryString.value)
|
||||
const url = `https://github.com/${props.repoOwner}/${props.repoName}/issues?q=${query}`
|
||||
const url = `https://github.com/${repoOwner}/${repoName}/issues?q=${query}`
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -268,7 +268,7 @@ async function saveKeybinding() {
|
||||
const commandId = currentEditingCommand.value?.id
|
||||
const combo = newBindingKeyCombo.value
|
||||
cancelEdit()
|
||||
if (!combo || commandId == undefined) return
|
||||
if (!combo || commandId == null) return
|
||||
|
||||
const updated = keybindingStore.updateKeybindingOnCommand(
|
||||
new KeybindingImpl({ commandId, combo })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<TabPanel :value="props.value" class="h-full w-full" :class="props.class">
|
||||
<TabPanel :value class="h-full w-full" :class="panelClass">
|
||||
<div class="flex h-full w-full flex-col gap-2">
|
||||
<slot name="header" />
|
||||
<ScrollPanel class="h-0 grow pr-2">
|
||||
@@ -14,7 +14,7 @@
|
||||
import ScrollPanel from 'primevue/scrollpanel'
|
||||
import TabPanel from 'primevue/tabpanel'
|
||||
|
||||
const props = defineProps<{
|
||||
const { value, class: panelClass } = defineProps<{
|
||||
value: string
|
||||
class?: string
|
||||
}>()
|
||||
|
||||
@@ -17,7 +17,7 @@ vi.mock('@/utils/formatUtil', () => ({
|
||||
normalizeI18nKey: vi.fn()
|
||||
}))
|
||||
|
||||
describe('SettingItem', () => {
|
||||
describe(SettingItem.__name ?? 'SettingItem', () => {
|
||||
const mountComponent = (props: Record<string, unknown>, options = {}) => {
|
||||
return mount(SettingItem, {
|
||||
global: {
|
||||
|
||||
@@ -76,7 +76,7 @@ const i18n = createI18n({
|
||||
}
|
||||
})
|
||||
|
||||
describe('UsageLogsTable', () => {
|
||||
describe(UsageLogsTable.__name ?? 'UsageLogsTable', () => {
|
||||
const mockEventsResponse = {
|
||||
events: [
|
||||
{
|
||||
|
||||
@@ -169,9 +169,10 @@ const loadEvents = async () => {
|
||||
} else {
|
||||
error.value = customerEventService.error.value || 'Failed to load events'
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : 'Unknown error'
|
||||
console.error('Error loading events:', err)
|
||||
} catch (errorCaught) {
|
||||
error.value =
|
||||
errorCaught instanceof Error ? errorCaught.message : 'Unknown error'
|
||||
console.error('Error loading events:', errorCaught)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const i18n = createI18n({
|
||||
}
|
||||
})
|
||||
|
||||
describe('ApiKeyForm', () => {
|
||||
describe(ApiKeyForm.__name ?? 'ApiKeyForm', () => {
|
||||
beforeEach(() => {
|
||||
const app = createApp({})
|
||||
app.use(PrimeVue)
|
||||
|
||||
@@ -58,7 +58,7 @@ vi.mock('primevue/usetoast', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('SignInForm', () => {
|
||||
describe(SignInForm.__name ?? 'SignInForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockSendPasswordReset.mockReset()
|
||||
@@ -110,12 +110,17 @@ describe('SignInForm', () => {
|
||||
'span.text-muted.text-base.font-medium.cursor-pointer'
|
||||
)
|
||||
|
||||
// Mock getElementById to track focus
|
||||
// Mock querySelector to track focus on the email input
|
||||
const mockFocus = vi.fn()
|
||||
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||
mockElement as HTMLElement
|
||||
)
|
||||
const originalQuerySelector = document.querySelector.bind(document)
|
||||
const spy = vi
|
||||
.spyOn(document, 'querySelector')
|
||||
.mockImplementation((selector: string) => {
|
||||
if (selector === '#comfy-org-sign-in-email')
|
||||
return mockElement as HTMLElement
|
||||
return originalQuerySelector(selector)
|
||||
})
|
||||
|
||||
// Click forgot password link while email is empty
|
||||
await forgotPasswordSpan.trigger('click')
|
||||
@@ -129,10 +134,9 @@ describe('SignInForm', () => {
|
||||
})
|
||||
|
||||
// Should focus email input
|
||||
expect(document.getElementById).toHaveBeenCalledWith(
|
||||
'comfy-org-sign-in-email'
|
||||
)
|
||||
expect(spy).toHaveBeenCalledWith('#comfy-org-sign-in-email')
|
||||
expect(mockFocus).toHaveBeenCalled()
|
||||
spy.mockRestore()
|
||||
|
||||
// Should NOT call sendPasswordReset
|
||||
expect(mockSendPasswordReset).not.toHaveBeenCalled()
|
||||
@@ -212,7 +216,7 @@ describe('SignInForm', () => {
|
||||
|
||||
expect(wrapper.findComponent(ProgressSpinner).exists()).toBe(true)
|
||||
expect(wrapper.findComponent(Button).exists()).toBe(false)
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Fallback test - check HTML content if component rendering fails
|
||||
mockLoading = true
|
||||
const wrapper = mountComponent()
|
||||
@@ -270,21 +274,25 @@ describe('SignInForm', () => {
|
||||
onSubmit: (data: { valid: boolean; values: unknown }) => void
|
||||
}
|
||||
|
||||
// Mock getElementById to track focus
|
||||
// Mock querySelector to track focus on the email input
|
||||
const mockFocus = vi.fn()
|
||||
const mockElement: Partial<HTMLElement> = { focus: mockFocus }
|
||||
vi.spyOn(document, 'getElementById').mockReturnValue(
|
||||
mockElement as HTMLElement
|
||||
)
|
||||
const originalQuerySelector = document.querySelector.bind(document)
|
||||
const spy = vi
|
||||
.spyOn(document, 'querySelector')
|
||||
.mockImplementation((selector: string) => {
|
||||
if (selector === '#comfy-org-sign-in-email')
|
||||
return mockElement as HTMLElement
|
||||
return originalQuerySelector(selector)
|
||||
})
|
||||
|
||||
// Call handleForgotPassword with no email
|
||||
await component.handleForgotPassword('', false)
|
||||
|
||||
// Should focus email input
|
||||
expect(document.getElementById).toHaveBeenCalledWith(
|
||||
'comfy-org-sign-in-email'
|
||||
)
|
||||
expect(spy).toHaveBeenCalledWith('#comfy-org-sign-in-email')
|
||||
expect(mockFocus).toHaveBeenCalled()
|
||||
spy.mockRestore()
|
||||
})
|
||||
|
||||
it('does not focus email input when valid email is provided', async () => {
|
||||
|
||||
@@ -120,7 +120,7 @@ const handleForgotPassword = async (
|
||||
life: 5_000
|
||||
})
|
||||
// Focus the email input
|
||||
document.getElementById(emailInputId)?.focus?.()
|
||||
document.querySelector<HTMLElement>(`#${emailInputId}`)?.focus?.()
|
||||
return
|
||||
}
|
||||
await firebaseAuthActions.sendPasswordReset(email)
|
||||
|
||||
@@ -52,7 +52,7 @@ import Button from '@/components/ui/button/Button.vue'
|
||||
import { useBillingContext } from '@/composables/billing/useBillingContext'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const props = defineProps<{
|
||||
const { cancelAt } = defineProps<{
|
||||
cancelAt?: string
|
||||
}>()
|
||||
|
||||
@@ -64,7 +64,7 @@ const { cancelSubscription, fetchStatus, subscription } = useBillingContext()
|
||||
const isLoading = ref(false)
|
||||
|
||||
const formattedEndDate = computed(() => {
|
||||
const dateStr = props.cancelAt ?? subscription.value?.endDate
|
||||
const dateStr = cancelAt ?? subscription.value?.endDate
|
||||
if (!dateStr) return t('subscription.cancelDialog.endOfBillingPeriod')
|
||||
const date = new Date(dateStr)
|
||||
return date.toLocaleDateString('en-US', {
|
||||
|
||||
@@ -70,17 +70,31 @@
|
||||
@click="onSelectLink"
|
||||
/>
|
||||
<div
|
||||
class="absolute right-3 top-2.5 cursor-pointer"
|
||||
class="absolute right-4 top-2 cursor-pointer"
|
||||
@click="onCopyLink"
|
||||
>
|
||||
<i
|
||||
:class="
|
||||
cn(
|
||||
'pi size-4',
|
||||
justCopied ? 'pi-check text-green-500' : 'pi-copy'
|
||||
)
|
||||
"
|
||||
/>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0_2127_14348)">
|
||||
<path
|
||||
d="M2.66634 10.6666C1.93301 10.6666 1.33301 10.0666 1.33301 9.33325V2.66659C1.33301 1.93325 1.93301 1.33325 2.66634 1.33325H9.33301C10.0663 1.33325 10.6663 1.93325 10.6663 2.66659M6.66634 5.33325H13.333C14.0694 5.33325 14.6663 5.93021 14.6663 6.66658V13.3333C14.6663 14.0696 14.0694 14.6666 13.333 14.6666H6.66634C5.92996 14.6666 5.33301 14.0696 5.33301 13.3333V6.66658C5.33301 5.93021 5.92996 5.33325 6.66634 5.33325Z"
|
||||
stroke="white"
|
||||
stroke-width="1.3"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2127_14348">
|
||||
<rect width="16" height="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,7 +118,6 @@ import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import Button from '@/components/ui/button/Button.vue'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
@@ -117,7 +130,6 @@ const loading = ref(false)
|
||||
const email = ref('')
|
||||
const step = ref<'email' | 'link'>('email')
|
||||
const generatedLink = ref('')
|
||||
const justCopied = ref(false)
|
||||
|
||||
const isValidEmail = computed(() => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
@@ -149,10 +161,6 @@ async function onCreateLink() {
|
||||
async function onCopyLink() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(generatedLink.value)
|
||||
justCopied.value = true
|
||||
setTimeout(() => {
|
||||
justCopied.value = false
|
||||
}, 759)
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('workspacePanel.inviteMemberDialog.linkCopied'),
|
||||
|
||||
@@ -66,7 +66,7 @@ interface Props {
|
||||
buttonStyles?: Record<string, string>
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
const { buttonStyles } = defineProps<Props>()
|
||||
const buttonRef = ref<ComponentPublicInstance | null>(null)
|
||||
const popover = ref<InstanceType<typeof Popover>>()
|
||||
const commandStore = useCommandStore()
|
||||
|
||||
@@ -65,11 +65,12 @@ const updateWidgets = () => {
|
||||
const canvasStore = useCanvasStore()
|
||||
whenever(
|
||||
() => canvasStore.canvas,
|
||||
(canvas) =>
|
||||
(canvas.onDrawForeground = useChainCallback(
|
||||
(canvas) => {
|
||||
canvas.onDrawForeground = useChainCallback(
|
||||
canvas.onDrawForeground,
|
||||
updateWidgets
|
||||
)),
|
||||
)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -162,6 +162,8 @@ import { useColorPaletteService } from '@/services/colorPaletteService'
|
||||
import { useNewUserService } from '@/services/useNewUserService'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useBootstrapStore } from '@/stores/bootstrapStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { useExecutionStore } from '@/stores/executionStore'
|
||||
@@ -171,11 +173,9 @@ import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore'
|
||||
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
||||
import { isNativeWindow } from '@/utils/envUtil'
|
||||
import { forEachNode } from '@/utils/graphTraversalUtil'
|
||||
import { useInviteUrlLoader } from '@/platform/workspace/composables/useInviteUrlLoader'
|
||||
|
||||
import SelectionRectangle from './SelectionRectangle.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
||||
import { useInviteUrlLoader } from '@/platform/workspace/composables/useInviteUrlLoader'
|
||||
|
||||
const { t } = useI18n()
|
||||
const emit = defineEmits<{
|
||||
@@ -248,9 +248,9 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const allNodes = computed((): VueNodeData[] =>
|
||||
Array.from(vueNodeLifecycle.nodeManager.value?.vueNodeData?.values() ?? [])
|
||||
)
|
||||
const allNodes = computed((): VueNodeData[] => [
|
||||
...(vueNodeLifecycle.nodeManager.value?.vueNodeData?.values() ?? [])
|
||||
])
|
||||
|
||||
function onLinkOverlayReady(el: HTMLCanvasElement) {
|
||||
if (!canvasStore.canvas) return
|
||||
|
||||
@@ -114,8 +114,8 @@ const { isModalVisible, toggleModal, hideModal, hasActivePopup } =
|
||||
useZoomControls()
|
||||
|
||||
const stringifiedMinimapStyles = computed(() => {
|
||||
const buttonGroupKeys = ['borderRadius']
|
||||
const buttonKeys = ['borderRadius']
|
||||
const buttonGroupKeys = new Set(['borderRadius'])
|
||||
const buttonKeys = new Set(['borderRadius'])
|
||||
const additionalButtonStyles = {
|
||||
border: 'none'
|
||||
}
|
||||
@@ -124,14 +124,12 @@ const stringifiedMinimapStyles = computed(() => {
|
||||
|
||||
const buttonStyles = {
|
||||
...Object.fromEntries(
|
||||
Object.entries(containerStyles).filter(([key]) =>
|
||||
buttonKeys.includes(key)
|
||||
)
|
||||
Object.entries(containerStyles).filter(([key]) => buttonKeys.has(key))
|
||||
),
|
||||
...additionalButtonStyles
|
||||
}
|
||||
const buttonGroupStyles = Object.entries(containerStyles)
|
||||
.filter(([key]) => buttonGroupKeys.includes(key))
|
||||
.filter(([key]) => buttonGroupKeys.has(key))
|
||||
.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
|
||||
|
||||
return { buttonStyles, buttonGroupStyles }
|
||||
|
||||
@@ -248,8 +248,8 @@ defineExpose({ toggle, hide, isOpen, show })
|
||||
function showColorPopover(event: MouseEvent) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
const target = Array.from((event.currentTarget as HTMLElement).children).find(
|
||||
(el) => el.classList.contains('icon-[lucide--chevron-right]')
|
||||
const target = [...(event.currentTarget as HTMLElement).children].find((el) =>
|
||||
el.classList.contains('icon-[lucide--chevron-right]')
|
||||
) as HTMLElement
|
||||
colorPickerMenu.value?.toggle(event, target)
|
||||
}
|
||||
|
||||
@@ -34,14 +34,14 @@ const left = ref<string>()
|
||||
const top = ref<string>()
|
||||
|
||||
function hideTooltip() {
|
||||
return (tooltipText.value = '')
|
||||
tooltipText.value = ''
|
||||
}
|
||||
|
||||
async function showTooltip(tooltip: string | null | undefined) {
|
||||
if (!tooltip) return
|
||||
|
||||
left.value = comfyApp.canvas.mouse[0] + 'px'
|
||||
top.value = comfyApp.canvas.mouse[1] + 'px'
|
||||
left.value = `${comfyApp.canvas.mouse[0]}px`
|
||||
top.value = `${comfyApp.canvas.mouse[1]}px`
|
||||
tooltipText.value = tooltip
|
||||
|
||||
await nextTick()
|
||||
@@ -50,11 +50,11 @@ async function showTooltip(tooltip: string | null | undefined) {
|
||||
if (!rect) return
|
||||
|
||||
if (rect.right > window.innerWidth) {
|
||||
left.value = comfyApp.canvas.mouse[0] - rect.width + 'px'
|
||||
left.value = `${comfyApp.canvas.mouse[0] - rect.width}px`
|
||||
}
|
||||
|
||||
if (rect.top < 0) {
|
||||
top.value = comfyApp.canvas.mouse[1] + rect.height + 'px'
|
||||
top.value = `${comfyApp.canvas.mouse[1] + rect.height}px`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ vi.mock('@/stores/nodeDefStore', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
describe('SelectionToolbox', () => {
|
||||
describe(SelectionToolbox.__name ?? 'SelectionToolbox', () => {
|
||||
let canvasStore: ReturnType<typeof useCanvasStore>
|
||||
|
||||
const i18n = createI18n({
|
||||
|
||||
@@ -82,16 +82,14 @@ const { visible } = useSelectionToolboxPosition(toolboxRef)
|
||||
|
||||
const extensionToolboxCommands = computed<ComfyCommandImpl[]>(() => {
|
||||
const commandIds = new Set<string>(
|
||||
canvasStore.selectedItems
|
||||
.map(
|
||||
(item) =>
|
||||
extensionService
|
||||
.invokeExtensions('getSelectionToolboxCommands', item)
|
||||
.flat() as string[]
|
||||
)
|
||||
.flat()
|
||||
canvasStore.selectedItems.flatMap(
|
||||
(item) =>
|
||||
extensionService
|
||||
.invokeExtensions('getSelectionToolboxCommands', item)
|
||||
.flat() as string[]
|
||||
)
|
||||
)
|
||||
return Array.from(commandIds)
|
||||
return [...commandIds]
|
||||
.map((commandId) => commandStore.getCommand(commandId))
|
||||
.filter((command): command is ComfyCommandImpl => command !== undefined)
|
||||
})
|
||||
|
||||
@@ -68,7 +68,7 @@ const createWrapper = (props = {}) => {
|
||||
})
|
||||
}
|
||||
|
||||
describe('ZoomControlsModal', () => {
|
||||
describe(ZoomControlsModal.__name ?? 'ZoomControlsModal', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks()
|
||||
})
|
||||
|
||||
@@ -84,7 +84,7 @@ interface Props {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const { visible } = defineProps<Props>()
|
||||
|
||||
const interval = ref<number | null>(null)
|
||||
|
||||
@@ -132,7 +132,7 @@ const zoomToFitCommandText = computed(() =>
|
||||
const zoomInputContainer = ref<HTMLDivElement | null>(null)
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
() => visible,
|
||||
async (newVal) => {
|
||||
if (newVal) {
|
||||
await nextTick()
|
||||
|
||||
@@ -20,7 +20,7 @@ vi.mock('@/utils/litegraphUtil', () => ({
|
||||
isLGraphNode: vi.fn(() => true)
|
||||
}))
|
||||
|
||||
describe('BypassButton', () => {
|
||||
describe(BypassButton.__name ?? 'BypassButton', () => {
|
||||
let canvasStore: ReturnType<typeof useCanvasStore>
|
||||
let commandStore: ReturnType<typeof useCommandStore>
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ vi.mock('@/lib/litegraph/src/litegraph', async () => {
|
||||
|
||||
// Mock the colorUtil module
|
||||
vi.mock('@/utils/colorUtil', () => ({
|
||||
adjustColor: vi.fn((color: string) => color + '_light')
|
||||
adjustColor: vi.fn((color: string) => `${color}_light`)
|
||||
}))
|
||||
|
||||
// Mock the litegraphUtil module
|
||||
@@ -56,7 +56,7 @@ vi.mock('@/utils/litegraphUtil', () => ({
|
||||
isReroute: vi.fn(() => false)
|
||||
}))
|
||||
|
||||
describe('ColorPickerButton', () => {
|
||||
describe(ColorPickerButton.__name ?? 'ColorPickerButton', () => {
|
||||
let canvasStore: ReturnType<typeof useCanvasStore>
|
||||
let workflowStore: ReturnType<typeof useWorkflowStore>
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ interface Emits {
|
||||
(e: 'submenu-click', subOption: SubMenuOption): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const { option } = defineProps<Props>()
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { getCurrentShape } = useNodeCustomization()
|
||||
@@ -113,9 +113,9 @@ const isShapeSelected = (subOption: SubMenuOption): boolean => {
|
||||
|
||||
const isColorSubmenu = computed(() => {
|
||||
return (
|
||||
props.option.submenu &&
|
||||
props.option.submenu.length > 0 &&
|
||||
props.option.submenu.every((item) => item.color && !item.icon)
|
||||
option.submenu &&
|
||||
option.submenu.length > 0 &&
|
||||
option.submenu.every((item) => item.color && !item.icon)
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ vi.mock('@/composables/graph/useSelectionState', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
describe('ExecuteButton', () => {
|
||||
describe(ExecuteButton.__name ?? 'ExecuteButton', () => {
|
||||
let mockCanvas: LGraphCanvas
|
||||
let mockSelectedNodes: LGraphNode[]
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import type { ComfyCommand } from '@/stores/commandStore'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||
|
||||
defineProps<{
|
||||
const { command } = defineProps<{
|
||||
command: ComfyCommand
|
||||
}>()
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ vi.mock('@/stores/workspace/rightSidePanelStore', () => ({
|
||||
})
|
||||
}))
|
||||
|
||||
describe('InfoButton', () => {
|
||||
describe(InfoButton.__name ?? 'InfoButton', () => {
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useExecutionStore } from '@/stores/executionStore'
|
||||
import { linkifyHtml, nl2br } from '@/utils/formatUtil'
|
||||
|
||||
const modelValue = defineModel<string>({ required: true })
|
||||
const props = defineProps<{
|
||||
const { nodeId } = defineProps<{
|
||||
nodeId: NodeId
|
||||
}>()
|
||||
|
||||
@@ -30,7 +30,7 @@ const formattedText = computed(() => {
|
||||
const src = modelValue.value
|
||||
// Turn [[label|url]] into placeholders to avoid interfering with linkifyHtml
|
||||
const tokens: { label: string; url: string }[] = []
|
||||
const holed = src.replace(
|
||||
const holed = src.replaceAll(
|
||||
/\[\[([^|\]]+)\|([^\]]+)\]\]/g,
|
||||
(_m, label, url) => {
|
||||
tokens.push({ label: String(label), url: String(url) })
|
||||
@@ -42,10 +42,10 @@ const formattedText = computed(() => {
|
||||
let html = nl2br(linkifyHtml(holed))
|
||||
|
||||
// Restore placeholders as <a>...</a> (minimal escaping + http default)
|
||||
html = html.replace(/__LNK(\d+)__/g, (_m, i) => {
|
||||
html = html.replaceAll(/__LNK(\d+)__/g, (_m, i) => {
|
||||
const { label, url } = tokens[+i]
|
||||
const safeHref = url.replace(/"/g, '"')
|
||||
const safeLabel = label.replace(/</g, '<').replace(/>/g, '>')
|
||||
const safeHref = url.replaceAll('"', '"')
|
||||
const safeLabel = label.replaceAll('<', '<').replaceAll('>', '>')
|
||||
return /^https?:\/\//i.test(url)
|
||||
? `<a href="${safeHref}" target="_blank" rel="noopener noreferrer">${safeLabel}</a>`
|
||||
: safeLabel
|
||||
@@ -58,7 +58,7 @@ let parentNodeId: NodeId | null = null
|
||||
onMounted(() => {
|
||||
// Get the parent node ID from props if provided
|
||||
// For backward compatibility, fall back to the first executing node
|
||||
parentNodeId = props.nodeId
|
||||
parentNodeId = nodeId
|
||||
})
|
||||
|
||||
// Watch for either a new node has starting execution or overall execution ending
|
||||
|
||||
@@ -593,11 +593,11 @@ const onUpdateComfyUI = async (): Promise<void> => {
|
||||
})
|
||||
|
||||
await rebootComfyUI()
|
||||
} catch (err) {
|
||||
} catch (error) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail: err instanceof Error ? err.message : t('g.unknownError'),
|
||||
detail: error instanceof Error ? error.message : t('g.unknownError'),
|
||||
life: 5000
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { defineComponent, h, nextTick, ref } from 'vue'
|
||||
|
||||
import HoneyToast from './HoneyToast.vue'
|
||||
|
||||
describe('HoneyToast', () => {
|
||||
describe(HoneyToast.__name ?? 'HoneyToast', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
document.body.innerHTML = ''
|
||||
|
||||
@@ -110,7 +110,7 @@ import { ASPECT_RATIOS, useImageCrop } from '@/composables/useImageCrop'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { Bounds } from '@/renderer/core/layout/types'
|
||||
|
||||
const props = defineProps<{
|
||||
const { nodeId } = defineProps<{
|
||||
nodeId: NodeId
|
||||
}>()
|
||||
|
||||
@@ -142,5 +142,5 @@ const {
|
||||
handleResizeStart,
|
||||
handleResizeMove,
|
||||
handleResizeEnd
|
||||
} = useImageCrop(props.nodeId, { imageEl, containerEl, modelValue })
|
||||
} = useImageCrop(nodeId, { imageEl, containerEl, modelValue })
|
||||
</script>
|
||||
|
||||
@@ -170,7 +170,7 @@ const getLabel = (val: string | null | undefined) => {
|
||||
|
||||
// Extract complex style logic from template
|
||||
const optionStyle = computed(() => {
|
||||
if (!popoverMinWidth && !popoverMaxWidth) return undefined
|
||||
if (!popoverMinWidth && !popoverMaxWidth) return
|
||||
|
||||
const styles: string[] = []
|
||||
if (popoverMinWidth) styles.push(`min-width: ${popoverMinWidth}`)
|
||||
|
||||
@@ -84,24 +84,24 @@ import { app } from '@/scripts/app'
|
||||
import type { ComponentWidget } from '@/scripts/domWidget'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
const props = defineProps<{
|
||||
const { widget, nodeId } = defineProps<{
|
||||
widget: ComponentWidget<string[]> | SimplifiedWidget
|
||||
nodeId?: NodeId
|
||||
}>()
|
||||
|
||||
function isComponentWidget(
|
||||
widget: ComponentWidget<string[]> | SimplifiedWidget
|
||||
): widget is ComponentWidget<string[]> {
|
||||
return 'node' in widget && widget.node !== undefined
|
||||
w: ComponentWidget<string[]> | SimplifiedWidget
|
||||
): w is ComponentWidget<string[]> {
|
||||
return 'node' in w && w.node !== undefined
|
||||
}
|
||||
|
||||
const node = ref<LGraphNode | null>(null)
|
||||
|
||||
if (isComponentWidget(props.widget)) {
|
||||
node.value = props.widget.node
|
||||
} else if (props.nodeId) {
|
||||
if (isComponentWidget(widget)) {
|
||||
node.value = widget.node
|
||||
} else if (nodeId) {
|
||||
onMounted(() => {
|
||||
node.value = app.rootGraph?.getNodeById(props.nodeId!) || null
|
||||
node.value = app.rootGraph?.getNodeById(nodeId!) || null
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,14 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import LoadingOverlay from '@/components/common/LoadingOverlay.vue'
|
||||
import { useLoad3dDrag } from '@/composables/useLoad3dDrag'
|
||||
|
||||
const props = defineProps<{
|
||||
const {
|
||||
initializeLoad3d,
|
||||
cleanup,
|
||||
loading,
|
||||
loadingMessage,
|
||||
onModelDrop,
|
||||
isPreview
|
||||
} = defineProps<{
|
||||
initializeLoad3d: (containerRef: HTMLElement) => Promise<void>
|
||||
cleanup: () => void
|
||||
loading: boolean
|
||||
@@ -53,20 +60,20 @@ function focusContainer() {
|
||||
const { isDragging, dragMessage, handleDragOver, handleDragLeave, handleDrop } =
|
||||
useLoad3dDrag({
|
||||
onModelDrop: async (file) => {
|
||||
if (props.onModelDrop) {
|
||||
await props.onModelDrop(file)
|
||||
if (onModelDrop) {
|
||||
await onModelDrop(file)
|
||||
}
|
||||
},
|
||||
disabled: computed(() => props.isPreview)
|
||||
disabled: computed(() => isPreview)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (container.value) {
|
||||
void props.initializeLoad3d(container.value)
|
||||
void initializeLoad3d(container.value)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
props.cleanup()
|
||||
cleanup()
|
||||
})
|
||||
</script>
|
||||
|
||||