1313 @click =" nodeBookmarkTreeExplorerRef?.addNewBookmarkFolder()"
1414 />
1515 <Button
16- v-tooltip.bottom =" $t('sideToolbar.nodeLibraryTab.sortOrder')"
17- class =" sort-button"
18- :icon =" alphabeticalSort ? 'pi pi-sort-alpha-down' : 'pi pi-sort-alt'"
16+ v-tooltip.bottom =" $t('sideToolbar.nodeLibraryTab.groupBy')"
17+ :icon =" selectedGroupingIcon"
1918 text
2019 severity =" secondary"
21- @click =" alphabeticalSort = !alphabeticalSort "
20+ @click =" groupingPopover?.toggle($event) "
2221 />
22+ <Button
23+ v-tooltip.bottom =" $t('sideToolbar.nodeLibraryTab.sortMode')"
24+ :icon =" selectedSortingIcon"
25+ text
26+ severity =" secondary"
27+ @click =" sortingPopover?.toggle($event)"
28+ />
29+ <Button
30+ v-tooltip.bottom =" $t('sideToolbar.nodeLibraryTab.resetView')"
31+ icon =" pi pi-refresh"
32+ text
33+ severity =" secondary"
34+ @click =" resetOrganization"
35+ />
36+ <Popover ref =" groupingPopover" >
37+ <div class =" flex flex-col gap-1 p-2" >
38+ <Button
39+ v-for =" option in groupingOptions"
40+ :key =" option.id"
41+ :icon =" option.icon"
42+ :label =" $t(option.label)"
43+ text
44+ :severity ="
45+ selectedGroupingId === option.id ? 'primary' : 'secondary'
46+ "
47+ class =" justify-start"
48+ @click =" selectGrouping(option.id)"
49+ />
50+ </div >
51+ </Popover >
52+ <Popover ref =" sortingPopover" >
53+ <div class =" flex flex-col gap-1 p-2" >
54+ <Button
55+ v-for =" option in sortingOptions"
56+ :key =" option.id"
57+ :icon =" option.icon"
58+ :label =" $t(option.label)"
59+ text
60+ :severity ="
61+ selectedSortingId === option.id ? 'primary' : 'secondary'
62+ "
63+ class =" justify-start"
64+ @click =" selectSorting(option.id)"
65+ />
66+ </div >
67+ </Popover >
2368 </template >
2469 <template #header >
2570 <SearchBox
62107</template >
63108
64109<script setup lang="ts">
110+ import { useLocalStorage } from ' @vueuse/core'
65111import Button from ' primevue/button'
66112import Divider from ' primevue/divider'
67113import Popover from ' primevue/popover'
@@ -76,16 +122,20 @@ import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue
76122import NodeTreeLeaf from ' @/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue'
77123import { useTreeExpansion } from ' @/composables/useTreeExpansion'
78124import { useLitegraphService } from ' @/services/litegraphService'
79- import { useNodeBookmarkStore } from ' @/stores/nodeBookmarkStore'
80125import {
81- ComfyNodeDefImpl ,
82- buildNodeDefTree ,
83- useNodeDefStore
84- } from ' @/stores/nodeDefStore'
126+ DEFAULT_GROUPING_ID ,
127+ DEFAULT_SORTING_ID ,
128+ nodeOrganizationService
129+ } from ' @/services/nodeOrganizationService'
130+ import { useNodeBookmarkStore } from ' @/stores/nodeBookmarkStore'
131+ import { ComfyNodeDefImpl , useNodeDefStore } from ' @/stores/nodeDefStore'
132+ import type {
133+ GroupingStrategyId ,
134+ SortingStrategyId
135+ } from ' @/types/nodeOrganizationTypes'
85136import type { TreeNode } from ' @/types/treeExplorerTypes'
86137import type { TreeExplorerNode } from ' @/types/treeExplorerTypes'
87138import { FuseFilterWithValue } from ' @/utils/fuseUtil'
88- import { sortedTree } from ' @/utils/treeUtil'
89139
90140import NodeBookmarkTreeExplorer from ' ./nodeLibrary/NodeBookmarkTreeExplorer.vue'
91141
@@ -98,13 +148,67 @@ const nodeBookmarkTreeExplorerRef = ref<InstanceType<
98148 typeof NodeBookmarkTreeExplorer
99149> | null > (null )
100150const searchFilter = ref <InstanceType <typeof Popover > | null >(null )
101- const alphabeticalSort = ref (false )
151+ const groupingPopover = ref <InstanceType <typeof Popover > | null >(null )
152+ const sortingPopover = ref <InstanceType <typeof Popover > | null >(null )
153+ const selectedGroupingId = useLocalStorage <GroupingStrategyId >(
154+ ' Comfy.NodeLibrary.GroupBy' ,
155+ DEFAULT_GROUPING_ID
156+ )
157+ const selectedSortingId = useLocalStorage <SortingStrategyId >(
158+ ' Comfy.NodeLibrary.SortBy' ,
159+ DEFAULT_SORTING_ID
160+ )
102161
103162const searchQuery = ref <string >(' ' )
104163
164+ const groupingOptions = computed (() =>
165+ nodeOrganizationService .getGroupingStrategies ().map ((strategy ) => ({
166+ id: strategy .id ,
167+ label: strategy .label ,
168+ icon: strategy .icon
169+ }))
170+ )
171+ const sortingOptions = computed (() =>
172+ nodeOrganizationService .getSortingStrategies ().map ((strategy ) => ({
173+ id: strategy .id ,
174+ label: strategy .label ,
175+ icon: strategy .icon
176+ }))
177+ )
178+
179+ const selectedGroupingIcon = computed (() =>
180+ nodeOrganizationService .getGroupingIcon (selectedGroupingId .value )
181+ )
182+ const selectedSortingIcon = computed (() =>
183+ nodeOrganizationService .getSortingIcon (selectedSortingId .value )
184+ )
185+
186+ const selectGrouping = (groupingId : string ) => {
187+ selectedGroupingId .value = groupingId as GroupingStrategyId
188+ groupingPopover .value ?.hide ()
189+ }
190+ const selectSorting = (sortingId : string ) => {
191+ selectedSortingId .value = sortingId as SortingStrategyId
192+ sortingPopover .value ?.hide ()
193+ }
194+
195+ const resetOrganization = () => {
196+ selectedGroupingId .value = DEFAULT_GROUPING_ID
197+ selectedSortingId .value = DEFAULT_SORTING_ID
198+ }
199+
105200const root = computed (() => {
106- const root = filteredRoot .value || nodeDefStore .nodeTree
107- return alphabeticalSort .value ? sortedTree (root , { groupLeaf: true }) : root
201+ // Determine which nodes to use
202+ const nodes =
203+ filteredNodeDefs .value .length > 0
204+ ? filteredNodeDefs .value
205+ : nodeDefStore .visibleNodeDefs
206+
207+ // Use the service to organize nodes
208+ return nodeOrganizationService .organizeNodes (nodes , {
209+ groupBy: selectedGroupingId .value ,
210+ sortBy: selectedSortingId .value
211+ })
108212})
109213
110214const renderedRoot = computed <TreeExplorerNode <ComfyNodeDefImpl >>(() => {
@@ -144,12 +248,6 @@ const renderedRoot = computed<TreeExplorerNode<ComfyNodeDefImpl>>(() => {
144248})
145249
146250const filteredNodeDefs = ref <ComfyNodeDefImpl []>([])
147- const filteredRoot = computed <TreeNode | null >(() => {
148- if (! filteredNodeDefs .value .length ) {
149- return null
150- }
151- return buildNodeDefTree (filteredNodeDefs .value )
152- })
153251const filters: Ref <
154252 (SearchFilter & { filter: FuseFilterWithValue <ComfyNodeDefImpl , string > })[]
155253> = ref ([])
@@ -175,8 +273,10 @@ const handleSearch = async (query: string) => {
175273 )
176274
177275 await nextTick ()
178- // @ts-expect-error fixme ts strict error
179- expandNode (filteredRoot .value )
276+ // Expand the search results tree
277+ if (filteredNodeDefs .value .length > 0 ) {
278+ expandNode (root .value )
279+ }
180280}
181281
182282const onAddFilter = async (
0 commit comments