Compare commits

...

140 Commits

Author SHA1 Message Date
trsommer
e2b8308e49 fix: no input in text fields 2024-12-10 00:58:05 +01:00
Tristan Sommer
44482e017a initial implementation, copy from pr #1820 + migration + comments 2024-12-09 15:20:30 +01:00
Chenlei Hu
0b91d53c9f 1.5.10 (#1850) 2024-12-08 20:42:27 -05:00
Chenlei Hu
b4ccaf4fec Rename EditAttention to EditTokenWeight (#1848)
* Rename EditAttention to EditTokenWeight

* Update locales [skip ci]

* Remove EditAttention

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-08 20:22:09 -05:00
Chenlei Hu
2d1b2cb1cc Fix group node playwright tests (#1849)
* Fix group node playwright tests

* nit

* nit
2024-12-08 20:21:59 -05:00
Chenlei Hu
aa04ab78c1 Replace window.prompt with custom prompt impl (#1847)
* Replace window.prompt with custom prompt impl

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-08 16:55:32 -05:00
Chenlei Hu
1671437fb3 Fix korea translation activation (#1845)
* Fix korea translation activation

* nit
2024-12-08 11:48:05 -05:00
Chenlei Hu
9d49cb0e4b Add support for korea translation (#1830)
* Add support for korea translation

* Update locales [skip ci]

* Update ko.json

* Update locales [skip ci]

* Update ko.json

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2024-12-08 11:44:36 -05:00
Chenlei Hu
43548785b5 Fix Comfy-Desktop.Reinstall command label (#1844) 2024-12-07 22:13:03 -05:00
Chenlei Hu
b6038128cb [i18n] g global namespace (#1843)
* Batch move global scope i18n to g. namespace

* Minor fix

* Update locale

* Update locales [skip ci]

* More moves

* Regroup icon/color

* nit

* Fix component test

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 22:07:03 -05:00
Chenlei Hu
a76159e9a0 [i18n] Translate node categories (#1842)
* [i18n] Translate node categories

* nit

* Update locales [skip ci]

* Fix some translations

* Do translate

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 21:25:40 -05:00
Chenlei Hu
a0ff78dbdb 1.5.9 (#1840) 2024-12-07 18:54:26 -05:00
Yuki Shindo
059dfcbebb Use i18n keys for workflow tree type labels (#1839)
* fix: use i18n keys for workflow tree type labels

* Inline translations

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-07 18:52:52 -05:00
Chenlei Hu
c17d44638e [i18n] Safe fallback to original string (#1838)
* [i18n] Safe fallback to original string

* nit
2024-12-07 18:48:14 -05:00
filtered
0e385c4262 Require confirmation for desktop reinstall (#1835)
* Add generic default button for unknown dialogs

* Add reinstall confirmation dialog

* Prevent removal of newlines in i18n dialogs

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 10:24:00 -05:00
Chenlei Hu
da98b1c4cc Update CODEOWNER matching rule (#1836) 2024-12-07 10:23:43 -05:00
Chenlei Hu
26d2e5de31 Add code owners (#1832)
* Add code owners

* nit
2024-12-06 22:29:21 -05:00
filtered
b0e37036d5 Fix missing titles during desktop installation (#1831)
* Add missing desktop translation keys

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-06 22:18:19 -05:00
Chenlei Hu
b7ddb47d2a 1.5.8 (#1829) 2024-12-06 21:29:11 -05:00
Yuki Shindo
6eb5ee99d8 feat: use friendly language names for Comfy.Locale setting options (#1828) 2024-12-06 20:19:01 -05:00
Chenlei Hu
6409e17d4a Hint for empty canvas when invoking Comfy.Canvas.FitView (#1826) 2024-12-06 18:07:01 -05:00
Yuki Shindo
a6549fb41e docs: Localize menu paths in firstTimeUIMessage (#1825) 2024-12-06 18:06:43 -05:00
Yuki Shindo
06d5064b7c fix: updated About label translation to a more appropriate Japanese term (#1827) 2024-12-06 18:06:20 -05:00
Chenlei Hu
fd621f485e Reload current workflow when locale changes (#1824) 2024-12-06 17:37:25 -05:00
Chenlei Hu
517ae56763 Translate node title and description (#1822)
* Collect node def i18n

* Add collected en locale

* Sort by node id

* Add translations

* Show translated node def

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-06 16:56:54 -05:00
Chenlei Hu
ae26390776 Properly implement ComfyNodeDef interface (#1821)
* nit

* Properly implement ComfyNodeDef interface

* nit

* Mark readonly
2024-12-06 12:00:15 -05:00
Chenlei Hu
aa68422e0f 1.5.7 (#1818) 2024-12-05 20:00:18 -05:00
Chenlei Hu
dac2a2ec86 [Electron] Translate server config panel (#1817)
* Update i18 collection to include server config items

* Use translation

* Update locales [skip ci]

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 19:56:51 -05:00
Chenlei Hu
5cee4d828f [Electron] Use shortname for server config disable-smart-memory (#1816) 2024-12-05 17:17:24 -05:00
Chenlei Hu
d6247d69ce [Electron] Hide auto-launch in server config (#1815) 2024-12-05 17:02:17 -05:00
Chenlei Hu
e2fa1e65d1 Add Workspace.SearchBox.Toggle command (#1814)
* Add Workspace.SearchBox.Toggle command

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 15:30:02 -05:00
Chenlei Hu
55c04b5533 [Electron] Allow force install on not supported device page (#1812)
* [Electron] Allow force install on not supported device page

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 12:06:51 -05:00
Chenlei Hu
540e785424 Reland "Replace magic numbers with LGraphEventMode (#1809)" (#1811)
* Revert "Revert "Replace magic numbers with LGraphEventMode (#1809)" (#1810)"

This reverts commit 30a92d40eb.

* Update litegraph
2024-12-05 11:48:07 -05:00
Chenlei Hu
30a92d40eb Revert "Replace magic numbers with LGraphEventMode (#1809)" (#1810)
This reverts commit 679405e0a2.
2024-12-05 11:29:41 -05:00
Chenlei Hu
679405e0a2 Replace magic numbers with LGraphEventMode (#1809) 2024-12-05 11:27:43 -05:00
Chenlei Hu
6cdc524174 Align desktop version badge format with other badges (#1808) 2024-12-05 11:11:28 -05:00
Chenlei Hu
80a42ebf10 Fix fallback of unrecognized extension categories (#1807) 2024-12-05 11:06:06 -05:00
Chenlei Hu
c45f03f5e7 1.5.6 (#1805) 2024-12-04 22:35:06 -05:00
Chenlei Hu
36c2604639 [Electron] Use electronAPI to get ComfyUI core version (#1804) 2024-12-04 22:34:55 -05:00
Chenlei Hu
73f50e7e0b Replace locale presubmit hook with Github action (#1802)
* Replace locale presubmit hook with Github action

* nit

* Make sure electronAPI is defined on window object

* Set correct env var
2024-12-04 21:43:02 -05:00
Chenlei Hu
418fddd669 Revert "Fix setting list nav separator (#1799)" (#1803)
This reverts commit 30465f17e0.
2024-12-04 17:26:44 -08:00
Chenlei Hu
e3cda0e749 Translate queue buttom modes (#1800)
* Translate queue button

* Update label

* nit
2024-12-04 17:43:06 -05:00
Chenlei Hu
30465f17e0 Fix setting list nav separator (#1799) 2024-12-04 16:37:38 -05:00
Chenlei Hu
5206939c78 Translate setting categories (#1797)
* Add i18n for setting categories

* Update locales

* Translate about

* nit

* nit
2024-12-04 16:32:28 -05:00
Chenlei Hu
73396784a8 Keep shim of several legacy components (#1796) 2024-12-04 15:45:38 -05:00
Chenlei Hu
3745d8d791 Remove logging module (#1795)
* Remove logging module

* Remove translation
2024-12-04 14:16:43 -05:00
filtered
735153886f Confirm delete workflow (#1772)
* Add confirm delete workflow prompt

* Add confirm delete workflow setting

* Add delete workflow tests

* Change dialog to modal, set default cancel

* Fix setting version key

* Rename for clarity

* Fix tests

- Move into correct section
- Add confirm control

* Export type: ShowDialogOptions

* Replace workflow overwrite with new dialog

* Add delete workflow confirmation dialog

* Update i18n

* Add item list support to confirmation dialog

* Prevent multiple dialogs for same action

* Add confirm close file when dirty

* Add i18n for overwrite dialog

* Fix regression: confirm dialog setting ignored

* Fix delete last workflow does not open replacement

* Update tests
2024-12-04 14:11:49 -05:00
Chenlei Hu
d04cc4e272 Translate dynamically added settings (#1794)
* Collect settings i18n at runtime

* Translation

* Remove unused file
2024-12-04 13:51:37 -05:00
Chenlei Hu
473aa120eb Fix extra_model_paths_yaml i18n key (#1793) 2024-12-04 13:25:04 -05:00
Chenlei Hu
7986aebf27 Translate command label on top command dropdown menu (#1792)
* collect i18n with playwright

* Add command label translation

* Normalize i18n object key
2024-12-04 13:19:53 -05:00
filtered
2caa87d35d Prevent interface crash from dialog abuse (#1789)
- Adds hard-coded limit of 10 open dialogs
- Discards the oldest item on the dialog stack to maintain size
2024-12-04 10:09:06 -05:00
Chenlei Hu
dabcd4741b [Refactor] Extract command definitions to coreCommandHooks (#1785)
* [Refactor] Extract command definitions to coreCommandHooks

* Register core commands

* Fix rebase issue
2024-12-03 23:06:19 -05:00
Hayden
e893f3ed03 Add a unique key to each showDialog (#1786) 2024-12-03 23:04:27 -05:00
Chenlei Hu
1856479de9 Translate topbar command menu (#1784)
* Rename script

* Translate menu labels

* Update translations

* Display translated menu items
2024-12-03 22:51:15 -05:00
Chenlei Hu
fc69129a2f 1.5.5 (#1783) 2024-12-03 22:05:35 -05:00
Chenlei Hu
d953b8fa46 Merge core settings with existing translations (#1782) 2024-12-03 22:04:19 -05:00
Yuki Shindo
1847db7a47 chore: add locale keys for settings dialog (i18n) (#1779) 2024-12-03 17:27:13 -08:00
Chenlei Hu
9cbcda20a7 [Refactor] Extract top menu bar command definitions (#1778) 2024-12-03 17:17:15 -08:00
filtered
0fe0aea242 Fix keybinds invalidated by capslock state (#1776)
* [Refactor] Simplify keybinds code

* [Refactor] Type safety

* Fix capslock inverts undo/redo shortcuts

* [Refactor] Type safety

* Fix capslock state changes keybinds

* Deprecate keybind deserialize

* Remove keybind deserialize
2024-12-03 19:12:09 -05:00
Chenlei Hu
08ae36818a Fix litegraph types in NodeSearchBoxPopover (#1777) 2024-12-03 19:07:15 -05:00
Chenlei Hu
da6b4d2872 Disable install button when there is error (#1775) 2024-12-03 14:34:03 -05:00
Chenlei Hu
8e293b41f5 [Refactor] Remove abstract inheritance of node search filter (#1774) 2024-12-03 11:39:29 -05:00
Chenlei Hu
d2771a7a1d Always include node title metadata for API export (#1771) 2024-12-02 22:49:42 -05:00
Chenlei Hu
13869da300 1.5.4 (#1770) 2024-12-02 22:09:49 -05:00
filtered
6ba53f3af1 Keep log on screen after installation error (#1769) 2024-12-02 21:57:30 -05:00
Tristan Sommer
b23cebcba4 maskeditor: massive improvements to brush opacity in brush strokes and lines (#1768)
* massive improvements to brush opacity in brush strokes and lines, improved save button visibility

* prettier formatting fixed
2024-12-02 18:16:18 -08:00
pythongosssss
30dfe76577 Allow copy/paste in terminal (#1760) 2024-12-02 19:55:14 -05:00
Chenlei Hu
c1f0cfe366 Move Comfy.EnableWorkflowViewRestore to coreSettings (#1767) 2024-12-02 17:50:46 -05:00
Chenlei Hu
d1279fa474 Move Comfy.TreeExplorer.ItemPadding to Appearance category (#1766) 2024-12-02 17:38:46 -05:00
Chenlei Hu
a56462fc7c Translate core setting name & tooltip (#1765)
* lazy eval default value

* Add setting translation

* Adjust hooks

* Add all translations

* nit

* Normalized setting id

* Update locales

* Fallback

* Locale => Language

* Locale => Language

* Update translations
2024-12-02 17:34:21 -05:00
Chenlei Hu
646bcf595b Revert "Bump @intlify/core-base and vue-i18n (#1762)" (#1764)
This reverts commit 01ac9d9336.
2024-12-02 15:22:49 -05:00
Chenlei Hu
4796677a0a Create presubmit hooks for i18n (#1763)
* Add locale scripts to package.json

* Add i18n hooks
2024-12-02 15:10:53 -05:00
dependabot[bot]
01ac9d9336 Bump @intlify/core-base and vue-i18n (#1762)
Bumps [@intlify/core-base](https://github.com/intlify/vue-i18n/tree/HEAD/packages/core) to 9.14.2 and updates ancestor dependency [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n). These dependencies need to be updated together.


Updates `@intlify/core-base` from 9.13.1 to 9.14.2
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v9.14.2/packages/core)

Updates `vue-i18n` from 9.13.1 to 9.14.2
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v9.14.2/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: "@intlify/core-base"
  dependency-type: indirect
- dependency-name: vue-i18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 15:10:03 -05:00
pythongosssss
89a9288154 Support closing tab with middle click (#1761) 2024-12-02 15:09:46 -05:00
pythongosssss
17b7ef18d6 Add context menu to tabs (#1759)
* Add context menu to tabs

* Flatten menu
Translate

* Add translations

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-02 13:19:39 -05:00
Yuki Shindo
1afc584393 fix: remove unnecessary $t calls for column headers (#1758) 2024-12-02 09:47:08 -05:00
Chenlei Hu
9d71cdf8ef 1.5.3 (#1756) 2024-12-01 22:48:49 -05:00
pythongosssss
1c7f3e865a Add test for cloning pinned node (#1753)
* Add test for cloning pinned node

* Remove only

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-01 22:38:09 -05:00
Yuki Shindo
9ef40189f9 Add lobe-i18n setup and translation scripts and update translation files for consistency (#1751)
* refactor: convert translation files from TS to JSON format

* feat: add lobe-i18n setup and translation scripts

* chore: update translation files for consistency

* chore: refine translations in ja_JP.json for natural phrasing

* refactor: revert locale file names to original simpler format (e.g., en_US → en)
2024-12-01 20:03:17 -05:00
pythongosssss
5c6eecd660 Fix group node manage opening to wrong node type (#1754)
Remove dialog from DOM when closed
Add test
2024-12-01 19:52:08 -05:00
filtered
9b07993e1a Clear console error - missed API type (#1749) 2024-11-30 09:47:15 -05:00
Chenlei Hu
c35d29f31c 1.5.2 (#1745) 2024-11-29 17:31:10 -05:00
Chenlei Hu
e50d7c5eef Update litegraph 0.8.41 (#1744)
* chore: update litegraph to 0.8.41

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-29 17:30:04 -05:00
filtered
7c2cce40de Add default setting for node error colour (#1738) 2024-11-29 13:20:54 -05:00
filtered
2017b9016b Add TS types - API (#1736)
* nit

* Add TS types - API events

* Replace all API event emits with type-safe variants

* Add missing API type

* nit

* Remove test code, nit
2024-11-29 13:15:25 -05:00
filtered
0bf30e7621 Add git download page (#1742) 2024-11-29 13:12:09 -05:00
Chenlei Hu
7f5b685c9f Add current user message in settings dialog (#1740) 2024-11-29 11:24:21 -05:00
Yuki Shindo
ec824579d6 docs: add i18n section to README with instructions for adding new languages (#1735) 2024-11-29 10:57:37 -05:00
Chenlei Hu
9e565154a9 Rework user selection (#1728)
* Move to new route

* Convert to tailwind

* Basic style

* Add userStore

* nit

* nit

* nit

* Remove app.#setUser

* Route to user-select view

* Mock login

* Use primevue UI components

* handle create new user

* Remove legacy user selection

* Add logout button on side toolbar

* Add username to logout button tooltip

* Add playwright tests

* hide logout button in single user server

* nit
2024-11-28 23:36:41 -05:00
Chenlei Hu
541335bb31 1.5.1 (#1734) 2024-11-28 17:25:52 -05:00
Chenlei Hu
814c4b8ef0 Remove node bookmark migration (#1733)
* Remove node bookmarks setting migration

* nit

* nit
2024-11-28 14:28:53 -05:00
Chenlei Hu
df3fff5dbb Remove migration logic (settings & templates storage location) (#1732)
* Remove isNewUserSession handling

* Remove writing of setting and templates to localStorage
2024-11-28 14:24:06 -05:00
Chenlei Hu
b0085114d7 Remove app.storageLocation handling (#1731) 2024-11-28 13:35:08 -05:00
Chenlei Hu
7f9c70386f Update package.json (Fix repo name) (#1729) 2024-11-28 12:41:28 -05:00
filtered
578870d345 Add canvas maximum FPS setting (#1727)
* Add canvas maximum FPS setting

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-28 11:46:54 -05:00
Chenlei Hu
05fab91bda Build comfyui-frontend-types library (#1725)
* Install vite-plugin-dts

* Explicitly type workflowStore

* Working rollup

* Hide diff type

* Inline primevue toast mesage types

* Add prepare-types script to generate package.json for type package

* Add global declaration

* Add publish types Github action

* Update litegraph

* Add @comfyorg to package name
2024-11-28 11:21:19 -05:00
Yuki Shindo
5191e11650 Add Japanese (ja) locale files and update type definition for supported locales (#1726)
* feat: add locale files for Japanese (ja)

* fix: update locale type definition to include all supported languages
2024-11-28 09:45:00 -05:00
filtered
92079a653e Update unsupproted card help link (#1724) 2024-11-27 22:43:03 -05:00
Chenlei Hu
a7d14eb815 1.5.0 (#1723) 2024-11-27 19:25:18 -05:00
Chenlei Hu
9aea6eae70 chore: update litegraph to 0.8.38 (#1722) 2024-11-27 19:23:40 -05:00
Chenlei Hu
88a42172c5 Typecheck vue components (#1721)
* Fix various type issues in vue components

* Add vue tsc

* Add to hooks

* nit
2024-11-27 19:18:16 -05:00
Chenlei Hu
e79013dcfe Remove deprecated type def on ComfyNodeDef (#1720) 2024-11-27 16:16:50 -05:00
Chenlei Hu
08f3370828 Use auto inferred type on electronAPI.Terminal (#1719) 2024-11-27 16:14:30 -05:00
Chenlei Hu
c4d3c672ad Enforce ComfyExtension types (#1718)
* Enforce extension types

* nit
2024-11-27 15:35:18 -05:00
Chenlei Hu
39eaa2e850 [Electron] Add not supported hardware page (#1717) 2024-11-27 10:46:24 -05:00
oto-ciulis-tt
2d022e4e49 feat: Remove successful model downloads (#1710)
* feat: Remove successful model downloads

* PR comments

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
2024-11-27 10:40:43 -05:00
Chenlei Hu
1ac6d6529f Add empty workflows placeholder (#1712) 2024-11-26 17:22:05 -05:00
Chenlei Hu
86fec820ac Remove app.multiUserServer flag (#1711) 2024-11-26 16:06:58 -05:00
Chenlei Hu
030d5845db 1.4.13 (#1709) 2024-11-26 14:27:55 -05:00
Chenlei Hu
dd1c878fdf [Electron] Fix path validation on typing in input box (#1708) 2024-11-26 14:26:46 -05:00
Chenlei Hu
3942603a38 [Electron] Add version number to error state (#1707) 2024-11-26 14:21:37 -05:00
Chenlei Hu
244578db96 1.4.12 (#1706) 2024-11-26 14:02:43 -05:00
Chenlei Hu
6b6edfde9f chore: update litegraph to 0.8.37 (#1704) 2024-11-26 13:20:21 -05:00
Chenlei Hu
c54b675a48 Revert Filter cached/canceled results (#1586) (#1703)
* Revert "Filter cached/canceled results (#1586)"

This reverts commit 6fbf1248f4.

* nit
2024-11-26 13:17:26 -05:00
Chenlei Hu
b7008dfc5c Revert "nit: Fix import of OutputFilters in queue sidebar (#1680)" (#1702)
This reverts commit f97b673481.
2024-11-26 13:11:28 -05:00
Chenlei Hu
d0ad4af51c Revert "Move queueStore update to GraphView (#1679)" (#1701)
This reverts commit c8d5a6f154.
2024-11-26 13:08:42 -05:00
Chenlei Hu
4a182014e1 Revert "Fix queue sidebar tab task filter (#1682)" (#1700)
This reverts commit c1c5573e7f.
2024-11-26 13:08:19 -05:00
Chenlei Hu
46cd522384 Fix save temporary workflow loop on overwrite (#1699) 2024-11-26 10:44:25 -05:00
Hayden
c977667a15 Change dialog to multi-window mode (#1695)
Fixed Dropdown's z-index being below the dialog
2024-11-26 10:11:15 -05:00
Chenlei Hu
d531bc34c4 Make ChangeTracker detect changes in workflow.extra (Except ds) (#1692)
* Compare workflow.extra content

* Add tests
2024-11-25 21:59:27 -05:00
Chenlei Hu
adfbec2744 Add setting to adjust queue MaxHistoryItems (#1689)
* Add MaxHistoryItems

* nit
2024-11-25 18:49:40 -05:00
Chenlei Hu
23521559bf 1.4.11 (#1688) 2024-11-25 18:43:17 -05:00
Chenlei Hu
51f57aba17 Revert "Change dialog to multi-window mode (#1672)" (#1686)
This reverts commit 43c23e526c.
2024-11-25 13:29:47 -05:00
Chenlei Hu
97bab053df Split i18n locales to multiple files (#1683) 2024-11-25 13:18:14 -05:00
Chenlei Hu
c1c5573e7f Fix queue sidebar tab task filter (#1682)
* Fix queue sidebar tab task filter

* nit
2024-11-25 12:59:10 -05:00
Chenlei Hu
16d2a95760 chore: update litegraph to 0.8.36 (#1681) 2024-11-25 11:55:42 -05:00
Chenlei Hu
f97b673481 nit: Fix import of OutputFilters in queue sidebar (#1680) 2024-11-25 11:49:49 -05:00
Chenlei Hu
c8d5a6f154 Move queueStore update to GraphView (#1679) 2024-11-25 11:47:35 -05:00
Chenlei Hu
3708afaf21 [Electron] Add fp32 and fp64 to unet inference precision options (#1678) 2024-11-25 11:20:49 -05:00
Hayden
43c23e526c Change dialog to multi-window mode (#1672) 2024-11-24 21:36:30 -05:00
Chenlei Hu
a80eb84df1 1.4.10 (#1676) 2024-11-24 21:36:00 -05:00
Chenlei Hu
f89898b3d0 Add searchbox for extensions panel (#1675) 2024-11-24 21:33:43 -05:00
Chenlei Hu
af21142602 Use setting panel template (#1674)
* PanelTemplate

* Use panel template
2024-11-24 21:24:13 -05:00
Chenlei Hu
4b91860227 [Refactor] Extract SettingsPanel (#1673)
* [Refactor] Extract SettingsPanel

* nit
2024-11-24 20:48:35 -05:00
Chenlei Hu
e53bafbca6 [Electron] Add custom node migration placeholder (#1670) 2024-11-24 20:16:39 -05:00
Chenlei Hu
e01c8f06c7 [Electron] Show server launch args in server config panel (#1669)
* Move revertChanges

* Show launch args

* Explicit ServerConfigValue type

* nit

* nit

* Add tests
2024-11-24 18:14:05 -05:00
Chenlei Hu
c61ed4da37 Add server config modified message to prompt restart (#1668)
* Server config changed message

* Write to settings on unmount

* nit

* Highlight modified config

* Move modified logic to store

* Add jest test

* nit
2024-11-24 16:13:37 -05:00
Terry Jia
4a4d6d070a restore camera state (#1666) 2024-11-24 16:09:58 -05:00
Tristan Sommer
4bedd873a1 improved mouse brush adjustment, added zoom level indicator with reset, added invert button, bug fixes (#1664) 2024-11-24 11:32:13 -05:00
filtered
f8bd910e63 Fix terminal resizes incorrectly in flex parent (#1663)
Pushes siblings or self off-screen
2024-11-24 11:31:29 -05:00
225 changed files with 18097 additions and 3674 deletions

38
.github/workflows/i18n.yaml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Update Locales
on:
pull_request:
branches: [ main, master, dev* ]
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Commit updated locales
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git fetch origin ${{ github.head_ref }}
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
git add src/locales/
git diff --staged --quiet || git commit -m "Update locales [skip ci]"
git push origin HEAD:${{ github.head_ref }}
working-directory: ComfyUI_frontend

View File

@@ -41,3 +41,21 @@ jobs:
draft: true
prerelease: false
make_latest: "true"
publish_types:
runs-on: ubuntu-latest
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'Release')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: lts/*
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build:types
- name: Publish package
run: npm publish --access public
working-directory: ./dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

15
.i18nrc.cjs Normal file
View File

@@ -0,0 +1,15 @@
// This file is intentionally kept in CommonJS format (.cjs)
// to resolve compatibility issues with dependencies that require CommonJS.
// Do not convert this file to ESModule format unless all dependencies support it.
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
entry: 'src/locales/en.json',
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'ru', 'ja', 'ko'],
reference: `Keep following model names untranslated:
- flux
- photomaker
`
});

18
CODEOWNERS Normal file
View File

@@ -0,0 +1,18 @@
# Admins
* @Comfy-Org/comfy_frontend_devs
# Maintainers
*.md @Comfy-Org/comfy_maintainer
/tests-ui/ @Comfy-Org/comfy_maintainer
/browser_tests/ @Comfy-Org/comfy_maintainer
/.env_example @Comfy-Org/comfy_maintainer
/src/locales/ @Comfy-Org/comfy_maintainer
# Japanese translation
/src/locales/ja.json @shinshin86 @Comfy-Org/comfy_maintainer
# Load 3D extension
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
# Mask Editor extension
/src/extensions/core/maskeditor.ts @trsommer @Comfy-Org/comfy_frontend_devs

View File

@@ -414,6 +414,7 @@ We will support custom icons later.
- [PrimeVue](https://primevue.org/) with [TailwindCSS](https://tailwindcss.com/) for UI
- [Litegraph](https://github.com/Comfy-Org/litegraph.js) for node editor
- [zod](https://zod.dev/) for schema validation
- [vue-i18n](https://github.com/intlify/vue-i18n) for internationalization
### Git pre-commit hooks
@@ -480,6 +481,84 @@ This repo is using litegraph package hosted on <https://github.com/Comfy-Org/lit
This will replace the litegraph package in this repo with the local litegraph repo.
## Internationalization (i18n)
Our project supports multiple languages using `vue-i18n`. This allows users around the world to use the application in their preferred language.
### Supported Languages
- en (English)
- zh (中文)
- ru (Русский)
- ja (日本語)
- ko (한국어)
### How to Add a New Language
We welcome the addition of new languages. You can add a new language by following these steps:
#### 1. Generate language files
We use [lobe-i18n](https://github.com/lobehub/lobe-cli-toolbox/blob/master/packages/lobe-i18n/README.md) as our translation tool, which integrates with LLM for efficient localization.
Update the configuration file to include the new language(s) you wish to add:
```javascript
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
entry: 'src/locales/en.json', // Base language file
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'ru', 'ja'], // Add the new language(s) here
});
```
Set your OpenAI API Key by running the following command:
```sh
npx lobe-i18n --option
```
Once configured, generate the translation files with:
```sh
npx lobe-i18n locale
```
This will create the language files for the specified languages in the configuration.
#### 2. Update i18n Configuration
Import the newly generated locale file(s) in the `src/i18n.ts` file to include them in the application's i18n setup.
#### 3. Enable Selection of the New Language
Add the newly added language to the following item in `src/constants/coreSettings.ts`:
```typescript
{
id: 'Comfy.Locale',
name: 'Locale',
type: 'combo',
// Add the new language(s) here
options: [
{ value: 'en', text: 'English' },
{ value: 'zh', text: '中文' },
{ value: 'ru', text: 'Русский' },
{ value: 'ja', text: '日本語' }
],
defaultValue: navigator.language.split('-')[0] || 'en'
},
```
This will make the new language selectable in the application's settings.
#### 4. Test the Translations
Start the development server, switch to the new language, and verify the translations.
You can switch languages by opening the ComfyUI Settings and selecting from the `ComfyUI > Locale` dropdown box.
## Deploy
- Option 1: Set `DEPLOY_COMFYUI_DIR` in `.env` and run `npm run deploy`.

View File

@@ -3,9 +3,6 @@ import {
comfyPageFixture as test,
comfyExpect as expect
} from './fixtures/ComfyPage'
import type { useWorkspaceStore } from '../src/stores/workspaceStore'
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
async function beforeChange(comfyPage: ComfyPage) {
await comfyPage.page.evaluate(() => {
@@ -26,64 +23,41 @@ test.describe('Change Tracker', () => {
})
test('Can undo multiple operations', async ({ comfyPage }) => {
function isModified() {
return comfyPage.page.evaluate(async () => {
return !!(window['app'].extensionManager as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
function getUndoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.undoQueue.length
})
}
function getRedoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.redoQueue.length
})
}
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
// Save, confirm no errors & workflow modified flag removed
await comfyPage.menu.topbar.saveWorkflow('undo-redo-test')
expect(await comfyPage.getToastErrorCount()).toBe(0)
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
const node = (await comfyPage.getFirstNodeRef())!
await node.click('title')
await node.click('collapse')
await expect(node).toBeCollapsed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(1)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
await comfyPage.ctrlB()
await expect(node).toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(2)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(2)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
await comfyPage.ctrlZ()
await expect(node).not.toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(1)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(1)
expect(await comfyPage.getRedoQueueSize()).toBe(1)
await comfyPage.ctrlZ()
await expect(node).not.toBeCollapsed()
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(2)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(2)
})
})
@@ -174,4 +148,20 @@ test.describe('Change Tracker', () => {
await expect(node).toBePinned()
await expect(node).toBeCollapsed()
})
test('Can detect changes in workflow.extra', async ({ comfyPage }) => {
expect(await comfyPage.getUndoQueueSize()).toBe(0)
await comfyPage.page.evaluate(() => {
window['app'].graph.extra.foo = 'bar'
})
// Click empty space to trigger a change detection.
await comfyPage.clickEmptySpace()
expect(await comfyPage.getUndoQueueSize()).toBe(1)
})
test('Ignores changes in workflow.ds', async ({ comfyPage }) => {
expect(await comfyPage.getUndoQueueSize()).toBe(0)
await comfyPage.pan({ x: 10, y: 10 })
expect(await comfyPage.getUndoQueueSize()).toBe(0)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -17,8 +17,11 @@ import {
import { Topbar } from './components/Topbar'
import { NodeReference } from './utils/litegraphUtils'
import type { Position, Size } from './types'
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
import { SettingDialog } from './components/SettingDialog'
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
class ComfyMenu {
public readonly sideToolbar: Locator
public readonly themeToggleButton: Locator
@@ -72,6 +75,28 @@ type FolderStructure = {
[key: string]: FolderStructure | string
}
type KeysOfType<T, Match> = {
[K in keyof T]: T[K] extends Match ? K : never
}[keyof T]
class ConfirmDialog {
public readonly delete: Locator
public readonly overwrite: Locator
public readonly reject: Locator
constructor(public readonly page: Page) {
this.delete = page.locator('button.p-button[aria-label="Delete"]')
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
}
async click(locator: KeysOfType<ConfirmDialog, Locator>) {
const loc = this[locator]
await expect(loc).toBeVisible()
await loc.click()
}
}
export class ComfyPage {
public readonly url: string
// All canvas position operations are based on default view of canvas.
@@ -91,6 +116,7 @@ export class ComfyPage {
public readonly actionbar: ComfyActionbar
public readonly templates: ComfyTemplates
public readonly settingDialog: SettingDialog
public readonly confirmDialog: ConfirmDialog
/** Worker index to test user ID */
public readonly userIds: string[] = []
@@ -115,6 +141,7 @@ export class ComfyPage {
this.actionbar = new ComfyActionbar(page)
this.templates = new ComfyTemplates(page)
this.settingDialog = new SettingDialog(page)
this.confirmDialog = new ConfirmDialog(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {
@@ -487,6 +514,10 @@ export class ComfyPage {
return { x: 427, y: 98 }
}
get promptDialogInput() {
return this.page.locator('.p-dialog-content input[type="text"]')
}
async disconnectEdge() {
await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace)
}
@@ -737,28 +768,29 @@ export class ComfyPage {
)
}
async confirmDialog(prompt: string, text: string = 'Yes') {
async clickDialogButton(prompt: string, buttonText: string = 'Yes') {
const modal = this.page.locator(
`.comfy-modal-content:has-text("${prompt}")`
)
await expect(modal).toBeVisible()
await modal
.locator('.comfyui-button', {
hasText: text
hasText: buttonText
})
.click()
await expect(modal).toBeHidden()
}
async convertAllNodesToGroupNode(groupNodeName: string) {
this.page.on('dialog', async (dialog) => {
await dialog.accept(groupNodeName)
})
await this.canvas.press('Control+a')
const node = await this.getFirstNodeRef()
await node!.clickContextMenuOption('Convert to Group Node')
await this.promptDialogInput.fill(groupNodeName)
await this.page.keyboard.press('Enter')
await this.promptDialogInput.waitFor({ state: 'hidden' })
await this.nextFrame()
}
async convertOffsetToCanvas(pos: [number, number]) {
return this.page.evaluate((pos) => {
return window['app'].canvas.ds.convertOffsetToCanvas(pos)
@@ -788,6 +820,26 @@ export class ComfyPage {
async moveMouseToEmptyArea() {
await this.page.mouse.move(10, 10)
}
async getUndoQueueSize() {
return this.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.undoQueue.length
})
}
async getRedoQueueSize() {
return this.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.redoQueue.length
})
}
async isCurrentWorkflowModified() {
return this.page.evaluate(() => {
return (window['app'].extensionManager as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
}
export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
@@ -799,18 +851,23 @@ export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
const userId = await comfyPage.setupUser(username)
comfyPage.userIds[parallelIndex] = userId
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId
})
try {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId
})
} catch (e) {
console.error(e)
}
await comfyPage.setup()
await use(comfyPage)
}

View File

@@ -0,0 +1,41 @@
import { Page } from 'playwright'
import { test as base } from '@playwright/test'
export class UserSelectPage {
constructor(
public readonly url: string,
public readonly page: Page
) {}
get selectionUrl() {
return this.url + '/user-select'
}
get container() {
return this.page.locator('#comfy-user-selection')
}
get newUserInput() {
return this.container.locator('#new-user-input')
}
get existingUserSelect() {
return this.container.locator('#existing-user-select')
}
get nextButton() {
return this.container.getByText('Next')
}
}
export const userSelectPageFixture = base.extend<{
userSelectPage: UserSelectPage
}>({
userSelectPage: async ({ page }, use) => {
const userSelectPage = new UserSelectPage(
process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188',
page
)
await use(userSelectPage)
}
})

View File

@@ -233,10 +233,10 @@ export class NodeReference {
await ctx.getByText(optionText).click()
}
async convertToGroupNode(groupNodeName: string = 'GroupNode') {
this.comfyPage.page.once('dialog', async (dialog) => {
await dialog.accept(groupNodeName)
})
await this.clickContextMenuOption('Convert to Group Node')
await this.comfyPage.promptDialogInput.fill(groupNodeName)
await this.comfyPage.page.keyboard.press('Enter')
await this.comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
await this.comfyPage.nextFrame()
const nodes = await this.comfyPage.getNodeRefsByType(
`workflow>${groupNodeName}`

View File

@@ -103,6 +103,36 @@ test.describe('Group Node', () => {
await expect(comfyPage.page.locator('.node-tooltip')).toBeVisible()
})
test('Manage group opens with the correct group selected', async ({
comfyPage
}) => {
const makeGroup = async (name, type1, type2) => {
const node1 = (await comfyPage.getNodeRefsByType(type1))[0]
const node2 = (await comfyPage.getNodeRefsByType(type2))[0]
await node1.click('title')
await node2.click('title', {
modifiers: ['Shift']
})
return await node2.convertToGroupNode(name)
}
const group1 = await makeGroup(
'g1',
'CLIPTextEncode',
'CheckpointLoaderSimple'
)
const group2 = await makeGroup('g2', 'EmptyLatentImage', 'KSampler')
const manage1 = await group1.manageGroupNode()
await comfyPage.nextFrame()
expect(await manage1.getSelectedNodeType()).toBe('g1')
await manage1.close()
await expect(manage1.root).not.toBeVisible()
const manage2 = await group2.manageGroupNode()
expect(await manage2.getSelectedNodeType()).toBe('g2')
})
test('Reconnects inputs after configuration changed via manage dialog save', async ({
comfyPage
}) => {

View File

@@ -1,12 +1,14 @@
import { Locator, Page } from '@playwright/test'
export class ManageGroupNode {
footer: Locator
header: Locator
constructor(
readonly page: Page,
readonly root: Locator
) {
this.footer = root.locator('footer')
this.header = root.locator('header')
}
async setLabel(name: string, label: string) {
@@ -23,6 +25,11 @@ export class ManageGroupNode {
await this.footer.getByText('Close').click()
}
async getSelectedNodeType() {
const select = this.header.locator('select').first()
return await select.inputValue()
}
async selectNode(name: string) {
const list = this.root.locator('.comfy-group-manage-list-items')
const item = list.getByText(name)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -350,23 +350,6 @@ test.describe('Menu', () => {
await comfyPage.page.waitForTimeout(1000)
expect(await tab.getNode('KSampler (Advanced)').count()).toBe(2)
})
test('Can migrate legacy bookmarks', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [
'foo/',
'foo/KSampler (Advanced)',
'UNKNOWN',
'KSampler'
])
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [])
await comfyPage.reload()
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
[]
)
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks.V2')
).toEqual(['foo/', 'foo/KSamplerAdvanced', 'KSampler'])
})
})
test.describe('Workflows sidebar', () => {
@@ -459,12 +442,26 @@ test.describe('Menu', () => {
).toEqual(['workflow5.json'])
await comfyPage.menu.topbar.saveWorkflowAs('workflow5.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['workflow5.json'])
})
test('Can save temporary workflow with unmodified name', async ({
comfyPage
}) => {
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
await comfyPage.menu.topbar.saveWorkflow('Unsaved Workflow')
// Should not trigger the overwrite dialog
expect(
await comfyPage.page.locator('.comfy-modal-content:visible').count()
).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
})
test('Can overwrite other workflows with save as', async ({
comfyPage
}) => {
@@ -480,7 +477,7 @@ test.describe('Menu', () => {
)
await topbar.saveWorkflowAs('workflow1.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
// The old workflow1.json should be deleted and the new one should be saved.
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
@@ -522,6 +519,43 @@ test.describe('Menu', () => {
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['*Unsaved Workflow.json'])
})
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Workflow.ConfirmDelete', false)
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.nextFrame()
await comfyPage.clickContextMenuItem('Delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
test('Can delete workflows', async ({ comfyPage }) => {
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.clickContextMenuItem('Delete')
await comfyPage.confirmDialog.click('delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
})
test.describe('Workflows topbar tabs', () => {

View File

@@ -32,11 +32,11 @@ test.describe('Canvas Right Click Menu', () => {
test('Can convert to group node', async ({ comfyPage }) => {
await comfyPage.select2Nodes()
await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png')
comfyPage.page.on('dialog', async (dialog) => {
await dialog.accept('GroupNode2CLIP')
})
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Convert to Group Node')
await comfyPage.promptDialogInput.fill('GroupNode2CLIP')
await comfyPage.page.keyboard.press('Enter')
await comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-node-group-node.png'
@@ -186,4 +186,22 @@ test.describe('Node Right Click Menu', () => {
'selected-nodes-unpinned.png'
)
})
test('Can clone pinned nodes', async ({ comfyPage }) => {
const nodeCount = await comfyPage.getGraphNodesCount()
const node = (await comfyPage.getFirstNodeRef())!
await node.clickContextMenuOption('Pin')
await comfyPage.nextFrame()
await node.click('title', { button: 'right' })
await expect(
comfyPage.page.locator('.litemenu-entry:has-text("Unpin")')
).toBeAttached()
const cloneItem = comfyPage.page.locator(
'.litemenu-entry:has-text("Clone")'
)
await cloneItem.click()
await expect(cloneItem).toHaveCount(0)
await comfyPage.nextFrame()
expect(await comfyPage.getGraphNodesCount()).toBe(nodeCount + 1)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View File

@@ -0,0 +1,42 @@
import { expect } from '@playwright/test'
import { userSelectPageFixture as test } from './fixtures/UserSelectPage'
/**
* Expects ComfyUI backend to be launched with `--multi-user` flag.
*/
test.describe('User Select View', () => {
test.beforeEach(async ({ userSelectPage, page }) => {
await page.goto(userSelectPage.url)
await page.evaluate(() => {
localStorage.clear()
sessionStorage.clear()
})
})
test('Redirects to user select view if no user is logged in', async ({
userSelectPage,
page
}) => {
await page.goto(userSelectPage.url)
await expect(userSelectPage.container).toBeVisible()
expect(page.url()).toBe(userSelectPage.selectionUrl)
})
test('Can create new user', async ({ userSelectPage, page }) => {
const randomUser = `test-user-${Math.random().toString(36).substring(2, 7)}`
await page.goto(userSelectPage.url)
await expect(page).toHaveURL(userSelectPage.selectionUrl)
await userSelectPage.newUserInput.fill(randomUser)
await userSelectPage.nextButton.click()
await expect(page).toHaveURL(userSelectPage.url)
})
test('Can choose existing user', async ({ userSelectPage, page }) => {
await page.goto(userSelectPage.url)
await expect(page).toHaveURL(userSelectPage.selectionUrl)
await userSelectPage.existingUserSelect.click()
await page.locator('.p-select-list .p-select-option').first().click()
await userSelectPage.nextButton.click()
await expect(page).toHaveURL(userSelectPage.url)
})
})

View File

@@ -9,33 +9,6 @@
</head>
<body class="litegraph grid">
<div id="vue-app"></div>
<div id="comfy-user-selection" class="comfy-user-selection" style="display: none;">
<main class="comfy-user-selection-inner">
<h1>ComfyUI</h1>
<form>
<section>
<label>New user:
<input placeholder="Enter a username" />
</label>
</section>
<div class="comfy-user-existing">
<span class="or-separator">OR</span>
<section>
<label>
Existing user:
<select>
<option hidden disabled selected value> Select a user </option>
</select>
</label>
</section>
</div>
<footer>
<span class="comfy-user-error">&nbsp;</span>
<button class="comfy-btn comfy-user-button-next">Next</button>
</footer>
</form>
</main>
</div>
<script type="module" src="src/main.ts"></script>
</body>
</html>

View File

@@ -3,6 +3,7 @@ export default {
'./**/*.{ts,tsx,vue}': (stagedFiles) => [
...formatFiles(stagedFiles),
'vue-tsc --noEmit',
'tsc --noEmit',
'tsc-strict'
]

5012
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,22 @@
{
"name": "comfyui-frontend",
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.4.9",
"version": "1.5.10",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
"description": "Official front-end implementation of ComfyUI",
"license": "GPL-3.0-only",
"scripts": {
"dev": "vite",
"dev:electron": "vite --config vite.electron.config.mts",
"build": "npm run typecheck && vite build",
"build:types": "vite build --config vite.types.config.mts && node scripts/prepare-types.js",
"deploy": "npm run build && node scripts/deploy.js",
"release": "node scripts/release.js",
"update-litegraph": "node scripts/update-litegraph.js",
"zipdist": "node scripts/zipdist.js",
"typecheck": "tsc --noEmit && tsc-strict",
"typecheck": "vue-tsc --noEmit && tsc --noEmit && tsc-strict",
"format": "prettier --write './**/*.{js,ts,tsx,vue}'",
"test": "jest --config jest.config.base.ts",
"test:jest:fast": "jest --config jest.config.fast.ts",
@@ -23,13 +28,16 @@
"prepare": "husky || true",
"preview": "vite preview",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
"lint:fix": "eslint src --fix",
"locale": "lobe-i18n locale",
"collect-i18n": "playwright test --config=playwright.i18n.config.ts"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/preset-env": "^7.22.20",
"@eslint/js": "^9.8.0",
"@iconify/json": "^2.2.245",
"@lobehub/i18n-cli": "^1.20.1",
"@pinia/testing": "^0.1.5",
"@playwright/test": "^1.44.1",
"@types/jest": "^29.5.12",
@@ -66,14 +74,16 @@
"unplugin-icons": "^0.19.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.6",
"vite-plugin-dts": "^4.3.0",
"vite-plugin-static-copy": "^1.0.5",
"vitest": "^2.0.5",
"vue-tsc": "^2.1.10",
"zip-dir": "^2.0.0"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.3.6",
"@comfyorg/litegraph": "^0.8.35",
"@comfyorg/comfyui-electron-types": "^0.3.25",
"@comfyorg/litegraph": "^0.8.44",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
"@xterm/addon-fit": "^0.10.0",

14
playwright.i18n.config.ts Normal file
View File

@@ -0,0 +1,14 @@
import { PlaywrightTestConfig } from '@playwright/test'
const config: PlaywrightTestConfig = {
testDir: './scripts',
use: {
baseURL: 'http://localhost:5173',
headless: true
},
reporter: 'list',
timeout: 10000,
testMatch: /collect-i18n\.ts/
}
export default config

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="292" height="92pt" viewBox="0 0 219 92"><defs><clipPath id="a"><path d="M159 .79h25V69h-25Zm0 0"/></clipPath><clipPath id="b"><path d="M183 9h35.371v60H183Zm0 0"/></clipPath><clipPath id="c"><path d="M0 .79h92V92H0Zm0 0"/></clipPath></defs><path style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M130.871 31.836c-4.785 0-8.351 2.352-8.351 8.008 0 4.261 2.347 7.222 8.093 7.222 4.871 0 8.18-2.867 8.18-7.398 0-5.133-2.961-7.832-7.922-7.832Zm-9.57 39.95c-1.133 1.39-2.262 2.87-2.262 4.612 0 3.48 4.434 4.524 10.527 4.524 5.051 0 11.926-.352 11.926-5.043 0-2.793-3.308-2.965-7.488-3.227Zm25.761-39.688c1.563 2.004 3.22 4.789 3.22 8.793 0 9.656-7.571 15.316-18.536 15.316-2.789 0-5.312-.348-6.879-.785l-2.87 4.613 8.526.52c15.059.96 23.934 1.398 23.934 12.968 0 10.008-8.789 15.665-23.934 15.665-15.75 0-21.757-4.004-21.757-10.88 0-3.917 1.742-6 4.789-8.878-2.875-1.211-3.828-3.387-3.828-5.739 0-1.914.953-3.656 2.523-5.312 1.566-1.652 3.305-3.305 5.395-5.219-4.262-2.09-7.485-6.617-7.485-13.058 0-10.008 6.613-16.88 19.93-16.88 3.742 0 6.004.344 8.008.872h16.972v7.394l-8.007.61"/><g clip-path="url(#a)"><path style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M170.379 16.281c-4.961 0-7.832-2.87-7.832-7.836 0-4.957 2.871-7.656 7.832-7.656 5.05 0 7.922 2.7 7.922 7.656 0 4.965-2.871 7.836-7.922 7.836Zm-11.227 52.305V61.71l4.438-.606c1.219-.175 1.394-.437 1.394-1.746V33.773c0-.953-.261-1.566-1.132-1.824l-4.7-1.656.957-7.047h18.016V59.36c0 1.399.086 1.57 1.395 1.746l4.437.606v6.875h-24.805"/></g><g clip-path="url(#b)"><path style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M218.371 65.21c-3.742 1.825-9.223 3.481-14.187 3.481-10.356 0-14.27-4.175-14.27-14.015V31.879c0-.524 0-.871-.7-.871h-6.093v-7.746c7.664-.871 10.707-4.703 11.664-14.188h8.27v12.36c0 .609 0 .87.695.87h12.27v8.704h-12.965v20.797c0 5.136 1.218 7.136 5.918 7.136 2.437 0 4.96-.609 7.047-1.39l2.351 7.66"/></g><g clip-path="url(#c)"><path style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M89.422 42.371 49.629 2.582a5.868 5.868 0 0 0-8.3 0l-8.263 8.262 10.48 10.484a6.965 6.965 0 0 1 7.173 1.668 6.98 6.98 0 0 1 1.656 7.215l10.102 10.105a6.963 6.963 0 0 1 7.214 1.657 6.976 6.976 0 0 1 0 9.875 6.98 6.98 0 0 1-9.879 0 6.987 6.987 0 0 1-1.519-7.594l-9.422-9.422v24.793a6.979 6.979 0 0 1 1.848 1.32 6.988 6.988 0 0 1 0 9.88c-2.73 2.726-7.153 2.726-9.875 0a6.98 6.98 0 0 1 0-9.88 6.893 6.893 0 0 1 2.285-1.523V34.398a6.893 6.893 0 0 1-2.285-1.523 6.988 6.988 0 0 1-1.508-7.637L29.004 14.902 1.719 42.187a5.868 5.868 0 0 0 0 8.301l39.793 39.793a5.868 5.868 0 0 0 8.3 0l39.61-39.605a5.873 5.873 0 0 0 0-8.305"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

148
scripts/collect-i18n.ts Normal file
View File

@@ -0,0 +1,148 @@
import * as fs from 'fs'
import { comfyPageFixture as test } from '../browser_tests/fixtures/ComfyPage'
import { CORE_MENU_COMMANDS } from '../src/constants/coreMenuCommands'
import { SERVER_CONFIG_ITEMS } from '../src/constants/serverConfig'
import { formatCamelCase, normalizeI18nKey } from '../src/utils/formatUtil'
import type { ComfyCommandImpl } from '../src/stores/commandStore'
import type { FormItem, SettingParams } from '../src/types/settingTypes'
import type { ComfyApi } from '../src/scripts/api'
import type { ComfyNodeDef } from '../src/types/apiTypes'
const localePath = './src/locales/en.json'
const extractMenuCommandLocaleStrings = (): Set<string> => {
const labels = new Set<string>()
for (const [category, _] of CORE_MENU_COMMANDS) {
category.forEach((category) => labels.add(category))
}
return labels
}
test('collect-i18n', async ({ comfyPage }) => {
const commands = await comfyPage.page.evaluate(() => {
const workspace = window['app'].extensionManager
const commands = workspace.command.commands as ComfyCommandImpl[]
return commands.map((command) => ({
id: command.id,
label: command.label,
menubarLabel: command.menubarLabel
}))
})
const locale = JSON.parse(fs.readFileSync(localePath, 'utf-8'))
// Commands
const menuLabels = extractMenuCommandLocaleStrings()
const commandMenuLabels = new Set(
commands.map((command) => command.menubarLabel ?? command.label ?? '')
)
const allLabels = new Set([...menuLabels, ...commandMenuLabels])
allLabels.delete('')
const allLabelsLocale = Object.fromEntries(
Array.from(allLabels).map((label) => [normalizeI18nKey(label), label])
)
// Settings
const settings = await comfyPage.page.evaluate(() => {
const workspace = window['app'].extensionManager
const settings = workspace.setting.settings as Record<string, SettingParams>
return Object.values(settings)
.sort((a, b) => a.id.localeCompare(b.id))
.map((setting) => ({
id: setting.id,
name: setting.name,
tooltip: setting.tooltip,
category: setting.category
}))
})
const allSettingsLocale = Object.fromEntries(
settings.map((setting) => [
normalizeI18nKey(setting.id),
{
name: setting.name,
tooltip: setting.tooltip
}
])
)
const allSettingCategoriesLocale = Object.fromEntries(
settings
.flatMap((setting) => {
return (setting.category ?? setting.id.split('.')).slice(0, 2)
})
.map((category: string) => [
normalizeI18nKey(category),
formatCamelCase(category)
])
)
// Server Configs
const allServerConfigsLocale = Object.fromEntries(
SERVER_CONFIG_ITEMS.map((config) => [
normalizeI18nKey(config.id),
{
name: (config as unknown as FormItem).name,
tooltip: (config as unknown as FormItem).tooltip
}
])
)
const allServerConfigCategoriesLocale = Object.fromEntries(
SERVER_CONFIG_ITEMS.flatMap((config) => {
return config.category ?? ['General']
}).map((category) => [
normalizeI18nKey(category),
formatCamelCase(category)
])
)
// Node Definitions
const nodeDefs = (await comfyPage.page.evaluate(async () => {
const api = window['app'].api as ComfyApi
return await api.getNodeDefs()
})) as Record<string, ComfyNodeDef>
const allNodeDefsLocale = Object.fromEntries(
Object.values(nodeDefs)
.sort((a, b) => a.name.localeCompare(b.name))
.map((nodeDef) => [
normalizeI18nKey(nodeDef.name),
{
display_name: nodeDef.display_name ?? nodeDef.name,
description: nodeDef.description || undefined
}
])
)
const allNodeCategoriesLocale = Object.fromEntries(
Object.values(nodeDefs).flatMap((nodeDef) =>
nodeDef.category
.split('/')
.map((category) => [normalizeI18nKey(category), category])
)
)
fs.writeFileSync(
localePath,
JSON.stringify(
{
...locale,
menuLabels: allLabelsLocale,
settingsDialog: allSettingsLocale,
// Do merge for settingsCategories as there are some manual translations
// for special panels like "About" and "Keybinding".
settingsCategories: {
...(locale.settingsCategories ?? {}),
...allSettingCategoriesLocale
},
serverConfigItems: allServerConfigsLocale,
serverConfigCategories: allServerConfigCategoriesLocale,
nodeDefs: allNodeDefsLocale,
nodeCategories: allNodeCategoriesLocale
},
null,
2
)
)
})

40
scripts/prepare-types.js Normal file
View File

@@ -0,0 +1,40 @@
import fs from 'fs'
import path from 'path'
const mainPackage = JSON.parse(fs.readFileSync('./package.json', 'utf8'))
// Create the types-only package.json
const typesPackage = {
name: `${mainPackage.name}-types`,
version: mainPackage.version,
types: './index.d.ts',
files: ['index.d.ts'],
publishConfig: {
access: 'public'
},
repository: mainPackage.repository,
homepage: mainPackage.homepage,
description: `TypeScript definitions for ${mainPackage.name}`,
license: mainPackage.license,
dependencies: {
'@comfyorg/litegraph': mainPackage.dependencies['@comfyorg/litegraph']
},
peerDependencies: {
vue: mainPackage.dependencies.vue,
zod: mainPackage.dependencies.zod
}
}
// Ensure dist directory exists
const distDir = './dist'
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true })
}
// Write the new package.json to the dist directory
fs.writeFileSync(
path.join(distDir, 'package.json'),
JSON.stringify(typesPackage, null, 2)
)
console.log('Types package.json have been prepared in the dist directory')

View File

@@ -61,11 +61,9 @@ import BatchCountEdit from './BatchCountEdit.vue'
import ButtonGroup from 'primevue/buttongroup'
import { useI18n } from 'vue-i18n'
import {
AutoQueueMode,
useQueuePendingTaskCountStore,
useQueueSettingsStore
} from '@/stores/queueStore'
import type { MenuItem } from 'primevue/menuitem'
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import { useCommandStore } from '@/stores/commandStore'
@@ -76,10 +74,10 @@ const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode } = storeToRefs(useQueueSettingsStore())
const { t } = useI18n()
const queueModeMenuItemLookup: Record<AutoQueueMode, MenuItem> = {
const queueModeMenuItemLookup = computed(() => ({
disabled: {
key: 'disabled',
label: 'Queue',
label: t('menu.queue'),
tooltip: t('menu.disabledTooltip'),
command: () => {
queueMode.value = 'disabled'
@@ -87,7 +85,7 @@ const queueModeMenuItemLookup: Record<AutoQueueMode, MenuItem> = {
},
instant: {
key: 'instant',
label: 'Queue (Instant)',
label: `${t('menu.queue')} (${t('menu.instant')})`,
tooltip: t('menu.instantTooltip'),
command: () => {
queueMode.value = 'instant'
@@ -95,19 +93,19 @@ const queueModeMenuItemLookup: Record<AutoQueueMode, MenuItem> = {
},
change: {
key: 'change',
label: 'Queue (Change)',
tooltip: t('menu.changeTooltip'),
label: `${t('menu.queue')} (${t('menu.onChange')})`,
tooltip: t('menu.onChangeTooltip'),
command: () => {
queueMode.value = 'change'
}
}
}
}))
const activeQueueModeMenuItem = computed(
() => queueModeMenuItemLookup[queueMode.value]
() => queueModeMenuItemLookup.value[queueMode.value]
)
const queueModeMenuItems = computed(() =>
Object.values(queueModeMenuItemLookup)
Object.values(queueModeMenuItemLookup.value)
)
const executingPrompt = computed(() => !!queueCountStore.count.value)

View File

@@ -1,5 +1,5 @@
<template>
<div class="relative h-full w-full bg-black" ref="rootEl">
<div class="relative overflow-hidden h-full w-full bg-black" ref="rootEl">
<div class="p-terminal rounded-none h-full w-full p-2">
<div class="h-full terminal-host" ref="terminalEl"></div>
</div>

View File

@@ -13,18 +13,7 @@ const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement>
) => {
// TODO: use types from electron package
const terminalApi = electronAPI()['Terminal'] as {
onOutput(cb: (message: string) => void): () => void
resize(cols: number, rows: number): void
restore(): Promise<{
buffer: string[]
pos: { x: number; y: number }
size: { cols: number; rows: number }
}>
storePos(x: number, y: number): void
write(data: string): void
}
const terminalApi = electronAPI().Terminal
let offData: IDisposable
let offOutput: () => void

View File

@@ -22,7 +22,7 @@
</span>
<div v-if="imageBroken" class="broken-image-placeholder">
<i class="pi pi-image"></i>
<span>{{ $t('imageFailedToLoad') }}</span>
<span>{{ $t('g.imageFailedToLoad') }}</span>
</div>
</template>

View File

@@ -1,8 +1,8 @@
<template>
<Dialog v-model:visible="visible" :header="$t('customizeFolder')">
<Dialog v-model:visible="visible" :header="$t('g.customizeFolder')">
<div class="p-fluid">
<div class="field icon-field">
<label for="icon">{{ $t('icon') }}</label>
<label for="icon">{{ $t('g.icon') }}</label>
<SelectButton
v-model="selectedIcon"
:options="iconOptions"
@@ -19,7 +19,7 @@
</div>
<Divider />
<div class="field color-field">
<label for="color">{{ $t('color') }}</label>
<label for="color">{{ $t('g.color') }}</label>
<div class="color-picker-container">
<SelectButton
v-model="selectedColor"
@@ -41,7 +41,7 @@
v-else
class="pi pi-palette"
:style="{ fontSize: '1.2rem' }"
v-tooltip="$t('customColor')"
v-tooltip="$t('g.customColor')"
></i>
</template>
</SelectButton>
@@ -54,13 +54,13 @@
</div>
<template #footer>
<Button
:label="$t('reset')"
:label="$t('g.reset')"
icon="pi pi-refresh"
@click="resetCustomization"
class="p-button-text"
/>
<Button
:label="$t('confirm')"
:label="$t('g.confirm')"
icon="pi pi-check"
@click="confirmCustomization"
autofocus
@@ -100,24 +100,24 @@ const visible = computed({
const nodeBookmarkStore = useNodeBookmarkStore()
const iconOptions = [
{ name: t('bookmark'), value: nodeBookmarkStore.defaultBookmarkIcon },
{ name: t('folder'), value: 'pi-folder' },
{ name: t('star'), value: 'pi-star' },
{ name: t('heart'), value: 'pi-heart' },
{ name: t('file'), value: 'pi-file' },
{ name: t('inbox'), value: 'pi-inbox' },
{ name: t('box'), value: 'pi-box' },
{ name: t('briefcase'), value: 'pi-briefcase' }
{ name: t('icon.bookmark'), value: nodeBookmarkStore.defaultBookmarkIcon },
{ name: t('icon.folder'), value: 'pi-folder' },
{ name: t('icon.star'), value: 'pi-star' },
{ name: t('icon.heart'), value: 'pi-heart' },
{ name: t('icon.file'), value: 'pi-file' },
{ name: t('icon.inbox'), value: 'pi-inbox' },
{ name: t('icon.box'), value: 'pi-box' },
{ name: t('icon.briefcase'), value: 'pi-briefcase' }
]
const colorOptions = [
{ name: t('default'), value: nodeBookmarkStore.defaultBookmarkColor },
{ name: t('blue'), value: '#007bff' },
{ name: t('green'), value: '#28a745' },
{ name: t('red'), value: '#dc3545' },
{ name: t('pink'), value: '#e83e8c' },
{ name: t('yellow'), value: '#ffc107' },
{ name: t('custom'), value: 'custom' }
{ name: t('color.default'), value: nodeBookmarkStore.defaultBookmarkColor },
{ name: t('color.blue'), value: '#007bff' },
{ name: t('color.green'), value: '#28a745' },
{ name: t('color.red'), value: '#dc3545' },
{ name: t('color.pink'), value: '#e83e8c' },
{ name: t('color.yellow'), value: '#ffc107' },
{ name: t('color.custom'), value: 'custom' }
]
const defaultIcon = iconOptions.find(
@@ -157,7 +157,7 @@ const resetCustomization = () => {
selectedColor.value = defaultColor
} else if (!colorOption) {
customColor.value = props.initialColor.replace('#', '')
selectedColor.value = { name: 'Custom', value: 'custom' }
selectedColor.value = { name: t('color.custom'), value: 'custom' }
} else {
selectedColor.value = colorOption
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="grid grid-cols-2 gap-2">
<template v-for="col in deviceColumns" :key="col.field">
<div class="font-medium">{{ $t(col.header) }}</div>
<div class="font-medium">{{ col.header }}</div>
<div>{{ formatValue(props.device[col.field], col.field) }}</div>
</template>
</div>

View File

@@ -14,7 +14,7 @@
<div class="file-action">
<Button
class="file-action-button"
:label="$t('download') + ' (' + fileSize + ')'"
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
:disabled="props.error"

View File

@@ -12,7 +12,7 @@
<div class="file-action">
<Button
class="file-action-button"
:label="$t('download') + ' (' + fileSize + ')'"
:label="$t('g.download') + ' (' + fileSize + ')'"
size="small"
outlined
:disabled="props.error"

View File

@@ -1,7 +1,7 @@
<!-- A generalized form item for rendering in a form. -->
<template>
<div class="form-label flex flex-grow items-center">
<span class="text-[var(--p-text-muted-color)]">
<span class="text-muted" :class="props.labelClass">
<slot name="name-prefix"></slot>
{{ props.item.name }}
<i
@@ -33,15 +33,11 @@ import CustomFormValue from '@/components/common/CustomFormValue.vue'
import InputSlider from '@/components/common/InputSlider.vue'
const formValue = defineModel<any>('formValue')
const props = withDefaults(
defineProps<{
item: FormItem
id: string | undefined
}>(),
{
id: undefined
}
)
const props = defineProps<{
item: FormItem
id?: string
labelClass?: string | Record<string, boolean>
}>()
function getFormAttrs(item: FormItem) {
const attrs = { ...(item.attrs || {}) }

View File

@@ -44,21 +44,22 @@ import Button from 'primevue/button'
import SearchFilterChip from './SearchFilterChip.vue'
import { toRefs } from 'vue'
interface Props {
class?: string
modelValue: string
placeholder?: string
icon?: string
debounceTime?: number
filterIcon?: string
filters?: TFilter[]
}
const props = withDefaults(defineProps<Props>(), {
placeholder: 'Search...',
icon: 'pi pi-search',
debounceTime: 300
})
const props = withDefaults(
defineProps<{
class?: string
modelValue: string
placeholder?: string
icon?: string
debounceTime?: number
filterIcon?: string
filters?: TFilter[]
}>(),
{
placeholder: 'Search...',
icon: 'pi pi-search',
debounceTime: 300
}
)
const { filters } = toRefs(props)

View File

@@ -1,10 +1,10 @@
<template>
<div class="system-stats">
<div class="mb-6">
<h2 class="text-2xl font-semibold mb-4">{{ $t('systemInfo') }}</h2>
<h2 class="text-2xl font-semibold mb-4">{{ $t('g.systemInfo') }}</h2>
<div class="grid grid-cols-2 gap-2">
<template v-for="col in systemColumns" :key="col.field">
<div class="font-medium">{{ $t(col.header) }}</div>
<div class="font-medium">{{ col.header }}</div>
<div>{{ formatValue(systemInfo[col.field], col.field) }}</div>
</template>
</div>
@@ -13,7 +13,7 @@
<Divider />
<div>
<h2 class="text-2xl font-semibold mb-4">{{ $t('devices') }}</h2>
<h2 class="text-2xl font-semibold mb-4">{{ $t('g.devices') }}</h2>
<TabView v-if="props.stats.devices.length > 1">
<TabPanel
v-for="device in props.stats.devices"

View File

@@ -130,13 +130,13 @@ const deleteCommand = async (node: RenderedTreeExplorerNode) => {
const menuItems = computed<MenuItem[]>(() =>
[
{
label: t('rename'),
label: t('g.rename'),
icon: 'pi pi-file-edit',
command: () => renameCommand(menuTargetNode.value),
visible: menuTargetNode.value?.handleRename !== undefined
},
{
label: t('delete'),
label: t('g.delete'),
icon: 'pi pi-trash',
command: () => deleteCommand(menuTargetNode.value),
visible: menuTargetNode.value?.handleDelete !== undefined,

View File

@@ -128,7 +128,7 @@ describe('TreeExplorerTreeNode', () => {
expect(handleRenameMock).toHaveBeenCalledOnce()
expect(addToastSpy).toHaveBeenCalledWith({
severity: 'error',
summary: 'error',
summary: 'g.error',
detail: 'Rename failed',
life: 3000
})

View File

@@ -1,61 +1,51 @@
<!-- The main global dialog to show various things -->
<template>
<Dialog
v-model:visible="dialogStore.isVisible"
v-for="(item, index) in dialogStore.dialogStack"
:key="item.key"
v-model:visible="item.visible"
class="global-dialog"
modal
closable
closeOnEscape
dismissableMask
:maximizable="maximizable"
:maximized="maximized"
@hide="dialogStore.closeDialog"
@maximize="onMaximize"
@unmaximize="onUnmaximize"
:aria-labelledby="headerId"
v-bind="item.dialogComponentProps"
:auto-z-index="false"
:pt:mask:style="{ zIndex: baseZIndex + index + 1 }"
:aria-labelledby="item.key"
>
<template #header>
<component
v-if="dialogStore.headerComponent"
:is="dialogStore.headerComponent"
:id="headerId"
v-if="item.headerComponent"
:is="item.headerComponent"
:id="item.key"
/>
<h3 v-else :id="headerId">{{ dialogStore.title || ' ' }}</h3>
<h3 v-else :id="item.key">{{ item.title || ' ' }}</h3>
</template>
<component :is="dialogStore.component" v-bind="contentProps" />
<component
:is="item.component"
v-bind="item.contentProps"
:maximized="item.dialogComponentProps.maximized"
/>
</Dialog>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onMounted } from 'vue'
import { ZIndex } from '@primeuix/utils/zindex'
import { usePrimeVue } from '@primevue/core'
import { useDialogStore } from '@/stores/dialogStore'
import Dialog from 'primevue/dialog'
const dialogStore = useDialogStore()
const maximizable = computed(
() => dialogStore.dialogComponentProps.maximizable ?? false
)
const maximized = ref(false)
const onMaximize = () => {
maximized.value = true
}
const primevue = usePrimeVue()
const onUnmaximize = () => {
maximized.value = false
}
const baseZIndex = computed(() => {
return primevue?.config?.zIndex?.modal ?? 1100
})
const contentProps = computed(() =>
maximizable.value
? {
...dialogStore.props,
maximized: maximized.value
}
: dialogStore.props
)
const headerId = `dialog-${Math.random().toString(36).substr(2, 9)}`
onMounted(() => {
const mask = document.createElement('div')
ZIndex.set('model', mask, baseZIndex.value)
})
</script>
<style>

View File

@@ -0,0 +1,86 @@
<template>
<section class="prompt-dialog-content flex flex-col gap-6 m-2 mt-4">
<span>{{ message }}</span>
<ul v-if="itemList?.length" class="pl-4 m-0 flex flex-col gap-2">
<li v-for="item of itemList" :key="item">{{ item }}</li>
</ul>
<div class="flex gap-4 justify-end">
<Button
:label="$t('g.cancel')"
icon="pi pi-undo"
severity="secondary"
@click="onCancel"
autofocus
/>
<Button
v-if="type === 'delete'"
:label="$t('g.delete')"
severity="danger"
@click="onConfirm"
icon="pi pi-trash"
/>
<Button
v-else-if="type === 'overwrite'"
:label="$t('g.overwrite')"
severity="warn"
@click="onConfirm"
icon="pi pi-save"
/>
<template v-else-if="type === 'dirtyClose'">
<Button
:label="$t('g.no')"
severity="secondary"
@click="onDeny"
icon="pi pi-times"
/>
<Button :label="$t('g.save')" @click="onConfirm" icon="pi pi-save" />
</template>
<Button
v-else-if="type === 'reinstall'"
:label="$t('desktopMenu.reinstall')"
severity="warn"
@click="onConfirm"
icon="pi pi-eraser"
/>
<!-- Invalid - just show a close button. -->
<Button
v-else
:label="$t('g.close')"
severity="primary"
@click="onCancel"
icon="pi pi-times"
/>
</div>
</section>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { useDialogStore } from '@/stores/dialogStore'
import type { ConfirmationDialogType } from '@/services/dialogService'
const props = defineProps<{
message: string
type: ConfirmationDialogType
onConfirm: (value?: boolean) => void
itemList?: string[]
}>()
const onCancel = () => useDialogStore().closeDialog()
const onDeny = () => {
props.onConfirm(false)
useDialogStore().closeDialog()
}
const onConfirm = () => {
props.onConfirm(true)
useDialogStore().closeDialog()
}
</script>
<style lang="css" scoped>
.prompt-dialog-content {
white-space: pre-wrap;
}
</style>

View File

@@ -7,7 +7,7 @@
<div class="comfy-error-report">
<Button
v-show="!reportOpen"
:label="$t('showReport')"
:label="$t('g.showReport')"
@click="showReport"
text
/>
@@ -28,7 +28,7 @@
/>
<Button
v-if="reportOpen"
:label="$t('copyToClipboard')"
:label="$t('g.copyToClipboard')"
icon="pi pi-copy"
@click="copyReportToClipboard"
/>
@@ -38,7 +38,6 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useClipboard } from '@vueuse/core'
import { useToast } from 'primevue/usetoast'
import Button from 'primevue/button'
import Divider from 'primevue/divider'
@@ -50,6 +49,7 @@ import type { ExecutionErrorWsMessage, SystemStats } from '@/types/apiTypes'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
import { isElectron } from '@/utils/envUtil'
import { useCopyToClipboard } from '@/hooks/clipboardHooks'
const props = defineProps<{
error: ExecutionErrorWsMessage
@@ -65,7 +65,6 @@ const showReport = () => {
const showSendError = isElectron()
const toast = useToast()
const { copy, isSupported } = useClipboard()
onMounted(async () => {
try {
@@ -140,30 +139,9 @@ ${workflowText}
`
}
const { copyToClipboard } = useCopyToClipboard()
const copyReportToClipboard = async () => {
if (isSupported) {
try {
await copy(reportContent.value)
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Report copied to clipboard',
life: 3000
})
} catch (err) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Failed to copy report'
})
}
} else {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Clipboard API not supported in your browser'
})
}
await copyToClipboard(reportContent.value)
}
const openNewGithubIssue = async () => {

View File

@@ -10,7 +10,7 @@
/>
<label>{{ message }}</label>
</FloatLabel>
<Button @click="onConfirm">{{ $t('confirm') }}</Button>
<Button @click="onConfirm">{{ $t('g.confirm') }}</Button>
</div>
</template>

View File

@@ -5,83 +5,59 @@
class="settings-search-box w-full mb-2"
v-model:modelValue="searchQuery"
@search="handleSearch"
:placeholder="$t('searchSettings') + '...'"
:placeholder="$t('g.searchSettings') + '...'"
/>
<Listbox
v-model="activeCategory"
:options="categories"
optionLabel="label"
optionLabel="translatedLabel"
scrollHeight="100%"
:disabled="inSearch"
class="border-none w-full"
/>
</ScrollPanel>
<Divider layout="vertical" class="mx-1 2xl:mx-4" />
<ScrollPanel class="settings-content flex-grow">
<Tabs :value="tabValue" :lazy="true">
<FirstTimeUIMessage v-if="tabValue === 'Comfy'" />
<TabPanels class="settings-tab-panels">
<TabPanel key="search-results" value="Search Results">
<div v-if="searchResults.length > 0">
<SettingGroup
v-for="(group, i) in searchResults"
:key="group.label"
:divider="i !== 0"
:group="group"
/>
</div>
<NoResultsPlaceholder
v-else
icon="pi pi-search"
:title="$t('noResultsFound')"
:message="$t('searchFailedMessage')"
/>
</TabPanel>
<TabPanel
v-for="category in categories"
:key="category.key"
:value="category.label"
>
<SettingGroup
v-for="(group, i) in sortedGroups(category)"
:key="group.label"
:divider="i !== 0"
:group="{
label: group.label,
settings: flattenTree<SettingParams>(group)
}"
/>
</TabPanel>
<TabPanel key="about" value="About">
<AboutPanel />
</TabPanel>
<TabPanel key="keybinding" value="Keybinding">
<Suspense>
<KeybindingPanel />
<template #fallback>
<div>Loading keybinding panel...</div>
</template>
</Suspense>
</TabPanel>
<TabPanel key="extension" value="Extension">
<Suspense>
<ExtensionPanel />
<template #fallback>
<div>Loading extension panel...</div>
</template>
</Suspense>
</TabPanel>
<TabPanel key="server-config" value="Server-Config">
<Suspense>
<ServerConfigPanel />
<template #fallback>
<div>Loading server config panel...</div>
</template>
</Suspense>
</TabPanel>
</TabPanels>
</Tabs>
</ScrollPanel>
<Tabs :value="tabValue" :lazy="true" class="settings-content h-full w-full">
<TabPanels class="settings-tab-panels h-full w-full pr-0">
<PanelTemplate value="Search Results">
<SettingsPanel :settingGroups="searchResults" />
</PanelTemplate>
<PanelTemplate
v-for="category in settingCategories"
:key="category.key"
:value="category.label"
>
<template #header>
<CurrentUserMessage v-if="tabValue === 'Comfy'" />
<FirstTimeUIMessage v-if="tabValue === 'Comfy'" />
</template>
<SettingsPanel :settingGroups="sortedGroups(category)" />
</PanelTemplate>
<AboutPanel />
<Suspense>
<KeybindingPanel />
<template #fallback>
<div>Loading keybinding panel...</div>
</template>
</Suspense>
<Suspense>
<ExtensionPanel />
<template #fallback>
<div>Loading extension panel...</div>
</template>
</Suspense>
<Suspense>
<ServerConfigPanel />
<template #fallback>
<div>Loading server config panel...</div>
</template>
</Suspense>
</TabPanels>
</Tabs>
</div>
</template>
@@ -90,18 +66,20 @@ import { ref, computed, onMounted, watch, defineAsyncComponent } from 'vue'
import Listbox from 'primevue/listbox'
import Tabs from 'primevue/tabs'
import TabPanels from 'primevue/tabpanels'
import TabPanel from 'primevue/tabpanel'
import Divider from 'primevue/divider'
import ScrollPanel from 'primevue/scrollpanel'
import { SettingTreeNode, useSettingStore } from '@/stores/settingStore'
import { SettingParams } from '@/types/settingTypes'
import SettingGroup from './setting/SettingGroup.vue'
import { ISettingGroup, SettingParams } from '@/types/settingTypes'
import SearchBox from '@/components/common/SearchBox.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import { flattenTree } from '@/utils/treeUtil'
import SettingsPanel from './setting/SettingsPanel.vue'
import PanelTemplate from './setting/PanelTemplate.vue'
import AboutPanel from './setting/AboutPanel.vue'
import FirstTimeUIMessage from './setting/FirstTimeUIMessage.vue'
import CurrentUserMessage from './setting/CurrentUserMessage.vue'
import { flattenTree } from '@/utils/treeUtil'
import { isElectron } from '@/utils/envUtil'
import { normalizeI18nKey } from '@/utils/formatUtil'
import { useI18n } from 'vue-i18n'
const KeybindingPanel = defineAsyncComponent(
() => import('./setting/KeybindingPanel.vue')
@@ -113,11 +91,6 @@ const ServerConfigPanel = defineAsyncComponent(
() => import('./setting/ServerConfigPanel.vue')
)
interface ISettingGroup {
label: string
settings: SettingParams[]
}
const aboutPanelNode: SettingTreeNode = {
key: 'about',
label: 'About',
@@ -158,13 +131,26 @@ const serverConfigPanelNodeList = computed<SettingTreeNode[]>(() => {
const settingStore = useSettingStore()
const settingRoot = computed<SettingTreeNode>(() => settingStore.settingTree)
const categories = computed<SettingTreeNode[]>(() => [
...(settingRoot.value.children || []),
keybindingPanelNode,
...extensionPanelNodeList.value,
...serverConfigPanelNodeList.value,
aboutPanelNode
])
const settingCategories = computed<SettingTreeNode[]>(
() => settingRoot.value.children ?? []
)
const { t } = useI18n()
const categories = computed<SettingTreeNode[]>(() =>
[
...settingCategories.value,
keybindingPanelNode,
...extensionPanelNodeList.value,
...serverConfigPanelNodeList.value,
aboutPanelNode
].map((node) => ({
...node,
translatedLabel: t(
`settingsCategories.${normalizeI18nKey(node.label)}`,
node.label
)
}))
)
const activeCategory = ref<SettingTreeNode | null>(null)
const searchResults = ref<ISettingGroup[]>([])
@@ -178,10 +164,13 @@ onMounted(() => {
activeCategory.value = categories.value[0]
})
const sortedGroups = (category: SettingTreeNode) => {
return [...(category.children || [])].sort((a, b) =>
a.label.localeCompare(b.label)
)
const sortedGroups = (category: SettingTreeNode): ISettingGroup[] => {
return [...(category.children ?? [])]
.sort((a, b) => a.label.localeCompare(b.label))
.map((group) => ({
label: group.label,
settings: flattenTree<SettingParams>(group)
}))
}
const searchQuery = ref<string>('')

View File

@@ -1,7 +1,7 @@
<template>
<Button
@click="openGitHubIssues"
:label="$t('findIssues')"
:label="$t('g.findIssues')"
severity="secondary"
icon="pi pi-github"
>

Some files were not shown because too many files have changed in this diff Show More