diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue index 25fcba74d..ba3508128 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue @@ -63,17 +63,11 @@ >
import Badge from 'primevue/badge' -import Tag from 'primevue/tag' import ToggleButton from 'primevue/togglebutton' -import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore' +import { + buildNodeDefTree, + ComfyNodeDefImpl, + useNodeDefStore +} from '@/stores/nodeDefStore' import { computed, ref, nextTick } from 'vue' import type { TreeNode } from 'primevue/treenode' +import NodeTreeLeaf from './nodeLibrary/NodeTreeLeaf.vue' import TreePlus from '@/components/primevueOverride/TreePlus.vue' import NodePreview from '@/components/node/NodePreview.vue' import SearchBox from '@/components/common/SearchBox.vue' import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue' import { useSettingStore } from '@/stores/settingStore' import { app } from '@/scripts/app' -import { buildTree, sortedTree } from '@/utils/treeUtil' +import { sortedTree } from '@/utils/treeUtil' +import _ from 'lodash' const nodeDefStore = useNodeDefStore() const alphabeticalSort = ref(false) @@ -130,13 +129,63 @@ const nodePreviewStyle = ref>({ left: '0px' }) +// Bookmarks are in format of category/display_name. e.g. "comfy/conditioning/CLIPTextEncode" +const bookmarks = computed(() => + settingStore.get('Comfy.NodeLibrary.Bookmarks') +) +const bookmarkedNodes = computed( + () => + new Set( + bookmarks.value.map((bookmark: string) => bookmark.split('/').pop()) + ) +) +const isBookmarked = (node: ComfyNodeDefImpl) => + bookmarkedNodes.value.has(node.display_name) +const toggleBookmark = (bookmark: string) => { + if (bookmarks.value.includes(bookmark)) { + settingStore.set( + 'Comfy.NodeLibrary.Bookmarks', + bookmarks.value.filter((b: string) => b !== bookmark) + ) + } else { + settingStore.set('Comfy.NodeLibrary.Bookmarks', [ + ...bookmarks.value, + bookmark + ]) + } +} +const bookmarkedRoot = computed(() => { + const bookmarkNodes = bookmarks.value.map((bookmark: string) => { + const parts = bookmark.split('/') + const nodeName = parts.pop() + const category = parts.join('/') + const nodeDef = _.clone(nodeDefStore.nodeDefsByDisplayName[nodeName]) + nodeDef.category = category + return nodeDef + }) + return buildNodeDefTree(bookmarkNodes) +}) + +const allNodesRoot = computed(() => { + return { + key: 'all-nodes', + label: 'All Nodes', + children: [ + ...(bookmarkedRoot.value?.children ?? []), + ...nodeDefStore.nodeTree.children + ] + } +}) + const root = computed(() => { - const root = filteredRoot.value || nodeDefStore.nodeTree + const root = filteredRoot.value || allNodesRoot.value return alphabeticalSort.value ? sortedTree(root) : root }) + const renderedRoot = computed(() => { return fillNodeInfo(root.value) }) + const fillNodeInfo = (node: TreeNode): TreeNode => { const isExpanded = expandedKeys.value[node.key] const icon = node.leaf @@ -208,10 +257,7 @@ const handleSearch = (query: string) => { limit: 64 }) - filteredRoot.value = buildTree(matchedNodes, (nodeDef: ComfyNodeDefImpl) => [ - ...nodeDef.category.split('/'), - nodeDef.display_name - ]) + filteredRoot.value = buildNodeDefTree(matchedNodes) expandNode(filteredRoot.value) } @@ -231,6 +277,7 @@ const expandNode = (node: TreeNode) => { display: flex; align-items: center; margin-left: var(--p-tree-node-gap); + flex-grow: 1; } diff --git a/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue b/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue new file mode 100644 index 000000000..ec37df172 --- /dev/null +++ b/src/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/src/stores/nodeDefStore.ts b/src/stores/nodeDefStore.ts index c63048161..602d7ebac 100644 --- a/src/stores/nodeDefStore.ts +++ b/src/stores/nodeDefStore.ts @@ -243,8 +243,16 @@ export const SYSTEM_NODE_DEFS: Record = { } } +export function buildNodeDefTree(nodeDefs: ComfyNodeDefImpl[]): TreeNode { + return buildTree(nodeDefs, (nodeDef: ComfyNodeDefImpl) => [ + ...nodeDef.category.split('/').filter((s) => s !== ''), + nodeDef.display_name + ]) +} + interface State { nodeDefsByName: Record + nodeDefsByDisplayName: Record widgets: Record showDeprecated: boolean showExperimental: boolean @@ -253,6 +261,7 @@ interface State { export const useNodeDefStore = defineStore('nodeDef', { state: (): State => ({ nodeDefsByName: {}, + nodeDefsByDisplayName: {}, widgets: {}, showDeprecated: false, showExperimental: false @@ -262,7 +271,7 @@ export const useNodeDefStore = defineStore('nodeDef', { return Object.values(state.nodeDefsByName) }, // Node defs that are not deprecated - visibleNodeDefs(state) { + visibleNodeDefs(state): ComfyNodeDefImpl[] { return this.nodeDefs.filter( (nodeDef: ComfyNodeDefImpl) => (state.showDeprecated || !nodeDef.deprecated) && @@ -273,22 +282,20 @@ export const useNodeDefStore = defineStore('nodeDef', { return new NodeSearchService(this.visibleNodeDefs) }, nodeTree(): TreeNode { - return buildTree(this.visibleNodeDefs, (nodeDef: ComfyNodeDefImpl) => [ - ...nodeDef.category.split('/'), - nodeDef.display_name - ]) + return buildNodeDefTree(this.visibleNodeDefs) } }, actions: { updateNodeDefs(nodeDefs: ComfyNodeDef[]) { const newNodeDefsByName: { [key: string]: ComfyNodeDefImpl } = {} + const nodeDefsByDisplayName: { [key: string]: ComfyNodeDefImpl } = {} for (const nodeDef of nodeDefs) { - newNodeDefsByName[nodeDef.name] = plainToClass( - ComfyNodeDefImpl, - nodeDef - ) + const nodeDefImpl = plainToClass(ComfyNodeDefImpl, nodeDef) + newNodeDefsByName[nodeDef.name] = nodeDefImpl + nodeDefsByDisplayName[nodeDef.display_name] = nodeDefImpl } this.nodeDefsByName = newNodeDefsByName + this.nodeDefsByDisplayName = nodeDefsByDisplayName }, updateWidgets(widgets: Record) { this.widgets = widgets diff --git a/src/stores/settingStore.ts b/src/stores/settingStore.ts index 4b4b3c76b..b2dc17b97 100644 --- a/src/stores/settingStore.ts +++ b/src/stores/settingStore.ts @@ -191,6 +191,15 @@ export const useSettingStore = defineStore('setting', { step: 0.01 } }) + + // Bookmarks are stored in the settings store. + // Bookmarks are in format of category/display_name. e.g. "conditioning/CLIPTextEncode" + app.ui.settings.addSetting({ + id: 'Comfy.NodeLibrary.Bookmarks', + name: 'Node library bookmarks', + type: 'hidden', + defaultValue: [] + }) }, set(key: K, value: Settings[K]) { diff --git a/src/types/apiTypes.ts b/src/types/apiTypes.ts index e3fdcfb6d..bdd8b3bba 100644 --- a/src/types/apiTypes.ts +++ b/src/types/apiTypes.ts @@ -77,7 +77,7 @@ const zDownloadModelStatus = z.object({ status: z.string(), progress_percentage: z.number(), message: z.string(), - already_existed: z.boolean(), + already_existed: z.boolean() }) export type StatusWsMessageStatus = z.infer @@ -423,6 +423,7 @@ const zSettings = z.record(z.any()).and( 'Comfy.Graph.ZoomSpeed': z.number(), 'Comfy.InvertMenuScrolling': z.boolean(), 'Comfy.Logging.Enabled': z.boolean(), + 'Comfy.NodeLibrary.Bookmarks': z.array(z.string()), 'Comfy.NodeInputConversionSubmenus': z.boolean(), 'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger': z.enum([ 'always',