Skip to content

Commit dee00ed

Browse files
christian-byrnegithub-actions
andauthored
[feat] Add node library sorting and grouping controls (#4024)
Co-authored-by: github-actions <[email protected]>
1 parent afac449 commit dee00ed

File tree

12 files changed

+788
-31
lines changed

12 files changed

+788
-31
lines changed

src/components/sidebar/tabs/NodeLibrarySidebarTab.vue

Lines changed: 121 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,58 @@
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
@@ -62,6 +107,7 @@
62107
</template>
63108

64109
<script setup lang="ts">
110+
import { useLocalStorage } from '@vueuse/core'
65111
import Button from 'primevue/button'
66112
import Divider from 'primevue/divider'
67113
import Popover from 'primevue/popover'
@@ -76,16 +122,20 @@ import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue
76122
import NodeTreeLeaf from '@/components/sidebar/tabs/nodeLibrary/NodeTreeLeaf.vue'
77123
import { useTreeExpansion } from '@/composables/useTreeExpansion'
78124
import { useLitegraphService } from '@/services/litegraphService'
79-
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
80125
import {
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'
85136
import type { TreeNode } from '@/types/treeExplorerTypes'
86137
import type { TreeExplorerNode } from '@/types/treeExplorerTypes'
87138
import { FuseFilterWithValue } from '@/utils/fuseUtil'
88-
import { sortedTree } from '@/utils/treeUtil'
89139
90140
import NodeBookmarkTreeExplorer from './nodeLibrary/NodeBookmarkTreeExplorer.vue'
91141
@@ -98,13 +148,67 @@ const nodeBookmarkTreeExplorerRef = ref<InstanceType<
98148
typeof NodeBookmarkTreeExplorer
99149
> | null>(null)
100150
const 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
103162
const 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+
105200
const 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
110214
const renderedRoot = computed<TreeExplorerNode<ComfyNodeDefImpl>>(() => {
@@ -144,12 +248,6 @@ const renderedRoot = computed<TreeExplorerNode<ComfyNodeDefImpl>>(() => {
144248
})
145249
146250
const filteredNodeDefs = ref<ComfyNodeDefImpl[]>([])
147-
const filteredRoot = computed<TreeNode | null>(() => {
148-
if (!filteredNodeDefs.value.length) {
149-
return null
150-
}
151-
return buildNodeDefTree(filteredNodeDefs.value)
152-
})
153251
const 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
182282
const onAddFilter = async (

src/locales/en/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,23 @@
416416
"openWorkflow": "Open workflow in local file system",
417417
"newBlankWorkflow": "Create a new blank workflow",
418418
"nodeLibraryTab": {
419-
"sortOrder": "Sort Order"
419+
"groupBy": "Group By",
420+
"sortMode": "Sort Mode",
421+
"resetView": "Reset View to Default",
422+
"groupStrategies": {
423+
"category": "Category",
424+
"categoryDesc": "Group by node category",
425+
"module": "Module",
426+
"moduleDesc": "Group by module source",
427+
"source": "Source",
428+
"sourceDesc": "Group by source type (Core, Custom, API)"
429+
},
430+
"sortBy": {
431+
"original": "Original",
432+
"originalDesc": "Keep original order",
433+
"alphabetical": "Alphabetical",
434+
"alphabeticalDesc": "Sort alphabetically within groups"
435+
}
420436
},
421437
"modelLibrary": "Model Library",
422438
"downloads": "Downloads",

src/locales/es/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "Crear un nuevo flujo de trabajo en blanco",
10661066
"nodeLibrary": "Biblioteca de nodos",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "Orden de clasificación"
1068+
"groupBy": "Agrupar por",
1069+
"groupStrategies": {
1070+
"category": "Categoría",
1071+
"categoryDesc": "Agrupar por categoría de nodo",
1072+
"module": "Módulo",
1073+
"moduleDesc": "Agrupar por fuente del módulo",
1074+
"source": "Fuente",
1075+
"sourceDesc": "Agrupar por tipo de fuente (Core, Custom, API)"
1076+
},
1077+
"resetView": "Restablecer vista a la predeterminada",
1078+
"sortBy": {
1079+
"alphabetical": "Alfabético",
1080+
"alphabeticalDesc": "Ordenar alfabéticamente dentro de los grupos",
1081+
"original": "Original",
1082+
"originalDesc": "Mantener el orden original"
1083+
},
1084+
"sortMode": "Modo de ordenación"
10691085
},
10701086
"openWorkflow": "Abrir flujo de trabajo en el sistema de archivos local",
10711087
"queue": "Cola",

src/locales/fr/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "Créer un nouveau flux de travail vierge",
10661066
"nodeLibrary": "Bibliothèque de nœuds",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "Ordre de tri"
1068+
"groupBy": "Grouper par",
1069+
"groupStrategies": {
1070+
"category": "Catégorie",
1071+
"categoryDesc": "Grouper par catégorie de nœud",
1072+
"module": "Module",
1073+
"moduleDesc": "Grouper par source du module",
1074+
"source": "Source",
1075+
"sourceDesc": "Grouper par type de source (Core, Custom, API)"
1076+
},
1077+
"resetView": "Réinitialiser la vue par défaut",
1078+
"sortBy": {
1079+
"alphabetical": "Alphabétique",
1080+
"alphabeticalDesc": "Trier alphabétiquement dans les groupes",
1081+
"original": "Original",
1082+
"originalDesc": "Conserver l'ordre d'origine"
1083+
},
1084+
"sortMode": "Mode de tri"
10691085
},
10701086
"openWorkflow": "Ouvrir le flux de travail dans le système de fichiers local",
10711087
"queue": "File d'attente",

src/locales/ja/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "新しい空のワークフローを作成",
10661066
"nodeLibrary": "ノードライブラリ",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "並び順"
1068+
"groupBy": "グループ化",
1069+
"groupStrategies": {
1070+
"category": "カテゴリ",
1071+
"categoryDesc": "ノードカテゴリでグループ化",
1072+
"module": "モジュール",
1073+
"moduleDesc": "モジュールソースでグループ化",
1074+
"source": "ソース",
1075+
"sourceDesc": "ソースタイプ(Core、Custom、API)でグループ化"
1076+
},
1077+
"resetView": "ビューをデフォルトにリセット",
1078+
"sortBy": {
1079+
"alphabetical": "アルファベット順",
1080+
"alphabeticalDesc": "グループ内でアルファベット順に並び替え",
1081+
"original": "元の順序",
1082+
"originalDesc": "元の順序を維持"
1083+
},
1084+
"sortMode": "並び替えモード"
10691085
},
10701086
"openWorkflow": "ローカルでワークフローを開く",
10711087
"queue": "キュー",

src/locales/ko/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "새 빈 워크플로 만들기",
10661066
"nodeLibrary": "노드 라이브러리",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "정렬 순서"
1068+
"groupBy": "그룹 기준",
1069+
"groupStrategies": {
1070+
"category": "카테고리",
1071+
"categoryDesc": "노드 카테고리별로 그룹화",
1072+
"module": "모듈",
1073+
"moduleDesc": "모듈 소스별로 그룹화",
1074+
"source": "소스",
1075+
"sourceDesc": "소스 유형(Core, Custom, API)별로 그룹화"
1076+
},
1077+
"resetView": "기본 보기로 재설정",
1078+
"sortBy": {
1079+
"alphabetical": "알파벳순",
1080+
"alphabeticalDesc": "그룹 내에서 알파벳순으로 정렬",
1081+
"original": "원본 순서",
1082+
"originalDesc": "원래 순서를 유지"
1083+
},
1084+
"sortMode": "정렬 방식"
10691085
},
10701086
"openWorkflow": "로컬 파일 시스템에서 워크플로 열기",
10711087
"queue": "실행 대기열",

src/locales/ru/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "Создайте новый пустой рабочий процесс",
10661066
"nodeLibrary": "Библиотека нод",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "Порядок сортировки"
1068+
"groupBy": "Группировать по",
1069+
"groupStrategies": {
1070+
"category": "Категория",
1071+
"categoryDesc": "Группировать по категории узла",
1072+
"module": "Модуль",
1073+
"moduleDesc": "Группировать по источнику модуля",
1074+
"source": "Источник",
1075+
"sourceDesc": "Группировать по типу источника (Core, Custom, API)"
1076+
},
1077+
"resetView": "Сбросить вид по умолчанию",
1078+
"sortBy": {
1079+
"alphabetical": "По алфавиту",
1080+
"alphabeticalDesc": "Сортировать по алфавиту внутри групп",
1081+
"original": "Оригинальный порядок",
1082+
"originalDesc": "Сохранять исходный порядок"
1083+
},
1084+
"sortMode": "Режим сортировки"
10691085
},
10701086
"openWorkflow": "Открыть рабочий процесс в локальной файловой системе",
10711087
"queue": "Очередь",

src/locales/zh/main.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1065,7 +1065,23 @@
10651065
"newBlankWorkflow": "创建空白工作流",
10661066
"nodeLibrary": "节点库",
10671067
"nodeLibraryTab": {
1068-
"sortOrder": "排序顺序"
1068+
"groupBy": "分组方式",
1069+
"groupStrategies": {
1070+
"category": "类别",
1071+
"categoryDesc": "按节点类别分组",
1072+
"module": "模块",
1073+
"moduleDesc": "按模块来源分组",
1074+
"source": "来源",
1075+
"sourceDesc": "按来源类型分组(核心,自定义,API)"
1076+
},
1077+
"resetView": "重置视图为默认",
1078+
"sortBy": {
1079+
"alphabetical": "字母顺序",
1080+
"alphabeticalDesc": "在分组内按字母顺序排序",
1081+
"original": "原始顺序",
1082+
"originalDesc": "保持原始顺序"
1083+
},
1084+
"sortMode": "排序模式"
10691085
},
10701086
"openWorkflow": "在本地文件系统中打开工作流",
10711087
"queue": "队列",

0 commit comments

Comments
 (0)