diff --git a/browser_tests/menu.spec.ts b/browser_tests/menu.spec.ts index fa83f4684..bbba49a8f 100644 --- a/browser_tests/menu.spec.ts +++ b/browser_tests/menu.spec.ts @@ -62,6 +62,7 @@ test.describe('Menu', () => { test.describe('Node library sidebar', () => { test.beforeEach(async ({ comfyPage }) => { await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', []) + await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', {}) // Open the sidebar const tab = comfyPage.menu.nodeLibraryTab await tab.open() @@ -237,6 +238,93 @@ test.describe('Menu', () => { [] ) }) + test('Can customize icon', async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/']) + const tab = comfyPage.menu.nodeLibraryTab + await tab.getFolder('foo').click({ button: 'right' }) + await comfyPage.page.getByLabel('Customize').click() + await comfyPage.page + .locator('.icon-field .p-selectbutton > *:nth-child(2)') + .click() + await comfyPage.page + .locator('.color-field .p-selectbutton > *:nth-child(2)') + .click() + await comfyPage.page.getByLabel('Confirm').click() + await comfyPage.nextFrame() + expect( + await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization') + ).toEqual({ + 'foo/': { + icon: 'pi-folder', + color: '#007bff' + } + }) + }) + // If color is left as default, it should not be saved + test('Can customize icon (default field)', async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/']) + const tab = comfyPage.menu.nodeLibraryTab + await tab.getFolder('foo').click({ button: 'right' }) + await comfyPage.page.getByLabel('Customize').click() + await comfyPage.page + .locator('.icon-field .p-selectbutton > *:nth-child(2)') + .click() + await comfyPage.page.getByLabel('Confirm').click() + await comfyPage.nextFrame() + expect( + await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization') + ).toEqual({ + 'foo/': { + icon: 'pi-folder' + } + }) + }) + test('Can rename customized bookmark folder', async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/']) + await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', { + 'foo/': { + icon: 'pi-folder', + color: '#007bff' + } + }) + const tab = comfyPage.menu.nodeLibraryTab + await tab.getFolder('foo').click({ button: 'right' }) + await comfyPage.page.getByLabel('Rename').click() + await comfyPage.page.keyboard.insertText('bar') + await comfyPage.page.keyboard.press('Enter') + await comfyPage.nextFrame() + expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual( + ['bar/'] + ) + expect( + await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization') + ).toEqual({ + 'bar/': { + icon: 'pi-folder', + color: '#007bff' + } + }) + }) + + test('Can delete customized bookmark folder', async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo/']) + await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', { + 'foo/': { + icon: 'pi-folder', + color: '#007bff' + } + }) + const tab = comfyPage.menu.nodeLibraryTab + await tab.getFolder('foo').click({ button: 'right' }) + await comfyPage.page.getByLabel('Delete').click() + await comfyPage.nextFrame() + expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual( + [] + ) + expect( + await comfyPage.getSetting('Comfy.NodeLibrary.BookmarksCustomization') + ).toEqual({}) + }) }) test('Can change canvas zoom speed setting', async ({ comfyPage }) => { diff --git a/src/components/common/CustomizationDialog.vue b/src/components/common/CustomizationDialog.vue new file mode 100644 index 000000000..e7eab57c4 --- /dev/null +++ b/src/components/common/CustomizationDialog.vue @@ -0,0 +1,197 @@ + + + + + {{ $t('icon') }} + + + + + + + + + {{ $t('color') }} + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue index ab05d6e21..74e316038 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue @@ -86,6 +86,12 @@ + diff --git a/src/i18n.ts b/src/i18n.ts index 2ff2ff618..d2b12194e 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -2,6 +2,17 @@ import { createI18n } from 'vue-i18n' const messages = { en: { + customizeFolder: 'Customize Folder', + icon: 'Icon', + color: 'Color', + bookmark: 'Bookmark', + folder: 'Folder', + star: 'Star', + heart: 'Heart', + file: 'File', + inbox: 'Inbox', + box: 'Box', + briefcase: 'Briefcase', error: 'Error', findIssues: 'Find Issues', copyToClipboard: 'Copy to Clipboard', @@ -39,6 +50,17 @@ const messages = { } }, zh: { + customizeFolder: '定制文件夹', + icon: '图标', + color: '颜色', + bookmark: '书签', + folder: '文件夹', + star: '星星', + heart: '心', + file: '文件', + inbox: '收件箱', + box: '盒子', + briefcase: '公文包', error: '错误', showReport: '显示报告', imageFailedToLoad: '图像加载失败', diff --git a/src/stores/nodeBookmarkStore.ts b/src/stores/nodeBookmarkStore.ts index a550d2209..07c677e63 100644 --- a/src/stores/nodeBookmarkStore.ts +++ b/src/stores/nodeBookmarkStore.ts @@ -6,6 +6,7 @@ import { ComfyNodeDefImpl, createDummyFolderNodeDef } from './nodeDefStore' import { buildNodeDefTree } from './nodeDefStore' import type { TreeNode } from 'primevue/treenode' import _ from 'lodash' +import type { BookmarkCustomization } from '@/types/apiTypes' export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { const settingStore = useSettingStore() @@ -113,6 +114,7 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { : b ) ) + renameBookmarkCustomization(folderNode.nodePath, newNodePath) } const deleteBookmarkFolder = (folderNode: ComfyNodeDefImpl) => { @@ -126,8 +128,64 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { b !== folderNode.nodePath && !b.startsWith(folderNode.nodePath) ) ) + deleteBookmarkCustomization(folderNode.nodePath) } + const bookmarksCustomization = computed< + Record + >(() => settingStore.get('Comfy.NodeLibrary.BookmarksCustomization')) + + const updateBookmarkCustomization = ( + nodePath: string, + customization: BookmarkCustomization + ) => { + const currentCustomization = bookmarksCustomization.value[nodePath] || {} + const newCustomization = { ...currentCustomization, ...customization } + + // Remove attributes that are set to default values + if (newCustomization.icon === defaultBookmarkIcon) { + delete newCustomization.icon + } + if (newCustomization.color === defaultBookmarkColor) { + delete newCustomization.color + } + + // If the customization is empty, remove it entirely + if (Object.keys(newCustomization).length === 0) { + deleteBookmarkCustomization(nodePath) + } else { + settingStore.set('Comfy.NodeLibrary.BookmarksCustomization', { + ...bookmarksCustomization.value, + [nodePath]: newCustomization + }) + } + } + + const deleteBookmarkCustomization = (nodePath: string) => { + settingStore.set('Comfy.NodeLibrary.BookmarksCustomization', { + ...bookmarksCustomization.value, + [nodePath]: undefined + }) + } + + const renameBookmarkCustomization = ( + oldNodePath: string, + newNodePath: string + ) => { + const updatedCustomization = { ...bookmarksCustomization.value } + if (updatedCustomization[oldNodePath]) { + updatedCustomization[newNodePath] = updatedCustomization[oldNodePath] + delete updatedCustomization[oldNodePath] + } + settingStore.set( + 'Comfy.NodeLibrary.BookmarksCustomization', + updatedCustomization + ) + } + + const defaultBookmarkIcon = 'pi-bookmark-fill' + const defaultBookmarkColor = '#a1a1aa' + return { bookmarks, bookmarkedRoot, @@ -136,6 +194,13 @@ export const useNodeBookmarkStore = defineStore('nodeBookmark', () => { addBookmark, addNewBookmarkFolder, renameBookmarkFolder, - deleteBookmarkFolder + deleteBookmarkFolder, + + bookmarksCustomization, + updateBookmarkCustomization, + deleteBookmarkCustomization, + renameBookmarkCustomization, + defaultBookmarkIcon, + defaultBookmarkColor } }) diff --git a/src/stores/settingStore.ts b/src/stores/settingStore.ts index e8200e990..65ed2b5d7 100644 --- a/src/stores/settingStore.ts +++ b/src/stores/settingStore.ts @@ -211,6 +211,14 @@ export const useSettingStore = defineStore('setting', { type: 'hidden', defaultValue: [] }) + + // Stores mapping from bookmark folder name to its customization. + app.ui.settings.addSetting({ + id: 'Comfy.NodeLibrary.BookmarksCustomization', + name: 'Node library bookmarks customization', + type: 'hidden', + defaultValue: {} + }) }, set(key: K, value: Settings[K]) { diff --git a/src/types/apiTypes.ts b/src/types/apiTypes.ts index 5ddcaada6..bd1371e3a 100644 --- a/src/types/apiTypes.ts +++ b/src/types/apiTypes.ts @@ -408,6 +408,13 @@ const zUser = z.object({ users: z.record(z.string(), z.unknown()) }) const zUserData = z.array(z.array(z.string(), z.string())) + +const zBookmarkCustomization = z.object({ + icon: z.string().optional(), + color: z.string().optional() +}) +export type BookmarkCustomization = z.infer + const zSettings = z.record(z.any()).and( z .object({ @@ -428,6 +435,10 @@ const zSettings = z.record(z.any()).and( 'Comfy.InvertMenuScrolling': z.boolean(), 'Comfy.Logging.Enabled': z.boolean(), 'Comfy.NodeLibrary.Bookmarks': z.array(z.string()), + 'Comfy.NodeLibrary.BookmarksCustomization': z.record( + z.string(), + zBookmarkCustomization + ), 'Comfy.NodeInputConversionSubmenus': z.boolean(), 'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger': z.enum([ 'always',