diff --git a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue index df887e115..bcb4b0c02 100644 --- a/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue +++ b/src/components/sidebar/tabs/NodeLibrarySidebarTab.vue @@ -104,7 +104,7 @@ const searchQuery = ref('') const root = computed(() => { const root = filteredRoot.value || nodeDefStore.nodeTree - return alphabeticalSort.value ? sortedTree(root) : root + return alphabeticalSort.value ? sortedTree(root, { groupLeaf: true }) : root }) const renderedRoot = computed>(() => { diff --git a/src/stores/workflowStore.ts b/src/stores/workflowStore.ts index f36bd09e7..e96984ec5 100644 --- a/src/stores/workflowStore.ts +++ b/src/stores/workflowStore.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia' import { computed, markRaw, ref } from 'vue' -import { buildTree } from '@/utils/treeUtil' +import { buildTree, sortedTree } from '@/utils/treeUtil' import { api } from '@/scripts/api' import { UserFile } from './userFileStore' import { ChangeTracker } from '@/scripts/changeTracker' @@ -295,7 +295,7 @@ export const useWorkflowStore = defineStore('workflow', () => { ) } const workflowsTree = computed(() => - buildWorkflowTree(persistedWorkflows.value) + sortedTree(buildWorkflowTree(persistedWorkflows.value), { groupLeaf: true }) ) // Bookmarked workflows tree is flat. const bookmarkedWorkflowsTree = computed(() => diff --git a/src/utils/treeUtil.ts b/src/utils/treeUtil.ts index f2dd2f034..a8763be53 100644 --- a/src/utils/treeUtil.ts +++ b/src/utils/treeUtil.ts @@ -50,21 +50,55 @@ export function flattenTree(tree: TreeNode): T[] { return result } -export function sortedTree(node: TreeNode): TreeNode { - // Create a new node with the same label and data +/** + * Sort the children of the node recursively. + * @param node - The node to sort. + * @param options - The options for sorting. + * @param options.groupLeaf - Whether to group leaf nodes together. + * @returns The sorted node. + */ +export function sortedTree( + node: TreeNode, + { + groupLeaf = false + }: { + groupLeaf?: boolean + } = {} +): TreeNode { const newNode: TreeNode = { ...node } if (node.children) { - // Sort the children of the current node - const sortedChildren = [...node.children].sort((a, b) => - (a.label ?? '').localeCompare(b.label ?? '') - ) - // Recursively sort the children and add them to the new node - newNode.children = [] - for (const child of sortedChildren) { - newNode.children.push(sortedTree(child)) + if (groupLeaf) { + // Split children into folders and files + const folders = node.children.filter((child) => !child.leaf) + const files = node.children.filter((child) => child.leaf) + + // Sort folders and files separately by label + const sortedFolders = folders.sort((a, b) => + (a.label ?? '').localeCompare(b.label ?? '') + ) + const sortedFiles = files.sort((a, b) => + (a.label ?? '').localeCompare(b.label ?? '') + ) + + // Recursively sort folder children + newNode.children = [ + ...sortedFolders.map((folder) => + sortedTree(folder, { groupLeaf: true }) + ), + ...sortedFiles + ] + } else { + const sortedChildren = [...node.children].sort((a, b) => + (a.label ?? '').localeCompare(b.label ?? '') + ) + newNode.children = [ + ...sortedChildren.map((child) => + sortedTree(child, { groupLeaf: false }) + ) + ] } } diff --git a/tests-ui/tests/fast/utils/treeUtilTest.test.ts b/tests-ui/tests/fast/utils/treeUtilTest.test.ts index c3adf14e8..28c322b5d 100644 --- a/tests-ui/tests/fast/utils/treeUtilTest.test.ts +++ b/tests-ui/tests/fast/utils/treeUtilTest.test.ts @@ -1,4 +1,5 @@ -import { buildTree } from '@/utils/treeUtil' +import { buildTree, sortedTree } from '@/utils/treeUtil' +import { TreeNode } from 'primevue/treenode' describe('buildTree', () => { it('should handle empty folder items correctly', () => { @@ -61,3 +62,98 @@ describe('buildTree', () => { }) }) }) + +describe('sortedTree', () => { + const createNode = (label: string, leaf = false): TreeNode => ({ + key: label, + label, + leaf, + children: [] + }) + + it('should return a new node instance', () => { + const node = createNode('root') + const result = sortedTree(node) + expect(result).not.toBe(node) + expect(result).toEqual(node) + }) + + it('should sort children by label', () => { + const node: TreeNode = { + key: 'root', + label: 'root', + children: [createNode('c'), createNode('a'), createNode('b')] + } + + const result = sortedTree(node) + expect(result.children?.map((c) => c.label)).toEqual(['a', 'b', 'c']) + }) + + it('should handle undefined labels', () => { + const node: TreeNode = { + key: 'root', + label: 'root', + children: [ + { key: '1', label: 'b' }, + { key: '2', label: 'a' }, + { key: '3', label: undefined } + ] + } + + const result = sortedTree(node) + expect(result.children?.map((c) => c.label)).toEqual([undefined, 'a', 'b']) + }) + + describe('with groupLeaf=true', () => { + it('should group folders before files', () => { + const node: TreeNode = { + key: 'root', + label: 'root', + children: [ + createNode('file.txt', true), + createNode('folder1'), + createNode('another.txt', true), + createNode('folder2') + ] + } + + const result = sortedTree(node, { groupLeaf: true }) + const labels = result.children?.map((c) => c.label) + expect(labels).toEqual(['folder1', 'folder2', 'another.txt', 'file.txt']) + }) + + it('should sort recursively', () => { + const node: TreeNode = { + key: 'root', + label: 'root', + children: [ + { + ...createNode('folder1'), + children: [ + createNode('z.txt', true), + createNode('subfolder2'), + createNode('a.txt', true), + createNode('subfolder1') + ] + } + ] + } + + const result = sortedTree(node, { groupLeaf: true }) + const folder = result.children?.[0] + const subLabels = folder?.children?.map((c) => c.label) + expect(subLabels).toEqual(['subfolder1', 'subfolder2', 'a.txt', 'z.txt']) + }) + }) + + it('should handle nodes without children', () => { + const node: TreeNode = { + key: 'leaf', + label: 'leaf', + leaf: true + } + + const result = sortedTree(node) + expect(result).toEqual(node) + }) +})