mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-04 21:22:07 +00:00
## Summary Implement 11 Figma design discrepancies for the Node Library sidebar and V2 Node Search dialog, aligning the UI with the [Toolbox Figma design](https://www.figma.com/design/xMFxCziXJe6Denz4dpDGTq/Toolbox?node-id=2074-21394&m=dev). ## Changes - **What**: Sidebar: reorder tabs (All/Essentials/Blueprints), rename Custom→Blueprints, uppercase section headers, chevron-left of folder icon, bookmark-on-hover for node rows, filter dropdown with checkbox items, sort labels (Categorized/A-Z) with label-left/check-right layout, hide section headers in A-Z mode. Search dialog: expand filter chips from 3→6, add Recents and source categories to sidebar, remove "Filter by" label. Pull foundation V2 components from merged PR #8548. - **Dependencies**: Depends on #8987 (V2 Node Search) and #8548 (NodeLibrarySidebarTabV2) ## Review Focus - Filter dropdown (`filterOptions`) is UI-scaffolded but not yet wired to filtering logic (pending V2 integration) - "Recents" category currently returns frequency-based results as placeholder until a usage-tracking store is implemented - Pre-existing type errors from V2 PR dependencies not in the base commit (SearchBoxV2, usePerTabState, TextTicker, getProviderIcon, getLinkTypeColor, SidebarContainerKey) are expected and will resolve when rebased onto main after parent PRs land ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9085-feat-Node-Library-sidebar-and-V2-Search-dialog-Figma-design-improvements-30f6d73d36508175bf72d716f5904476) by [Unito](https://www.unito.io) --------- Co-authored-by: Yourz <crazilou@vip.qq.com> Co-authored-by: github-actions <github-actions@github.com>
177 lines
4.2 KiB
TypeScript
177 lines
4.2 KiB
TypeScript
import type { TreeNode } from '@/types/treeExplorerTypes'
|
|
|
|
export function buildTree<T>(items: T[], key: (item: T) => string[]): TreeNode {
|
|
const root: TreeNode = {
|
|
key: 'root',
|
|
label: 'root',
|
|
children: []
|
|
}
|
|
|
|
const map: Record<string, TreeNode> = {
|
|
root: root
|
|
}
|
|
|
|
for (const item of items) {
|
|
const keys = key(item)
|
|
let parent = root
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const k = keys[i]
|
|
// 'a/b/c/' represents an empty folder 'c' in folder 'b' in folder 'a'
|
|
// 'a/b/c/' is split into ['a', 'b', 'c', '']
|
|
if (k === '' && i === keys.length - 1) break
|
|
|
|
const id = parent.key + '/' + k
|
|
if (!map[id]) {
|
|
const node: TreeNode = {
|
|
key: id,
|
|
label: k,
|
|
leaf: false,
|
|
children: []
|
|
}
|
|
map[id] = node
|
|
parent.children?.push(node)
|
|
}
|
|
parent = map[id]
|
|
}
|
|
parent.leaf = keys[keys.length - 1] !== ''
|
|
parent.data = item
|
|
}
|
|
return root
|
|
}
|
|
|
|
/**
|
|
* If a tree root has exactly one non-leaf child folder, promote that
|
|
* folder's children up to the root level, removing the redundant layer.
|
|
*/
|
|
export function unwrapTreeRoot(tree: TreeNode): TreeNode {
|
|
if (
|
|
tree.children?.length === 1 &&
|
|
!tree.children[0].leaf &&
|
|
(tree.children[0].children?.length ?? 0) > 0
|
|
) {
|
|
return { ...tree, children: tree.children[0].children }
|
|
}
|
|
return tree
|
|
}
|
|
|
|
export function flattenTree<T>(tree: TreeNode): T[] {
|
|
const result: T[] = []
|
|
const stack: TreeNode[] = [tree]
|
|
while (stack.length) {
|
|
const node = stack.pop()!
|
|
if (node.leaf && node.data) result.push(node.data)
|
|
stack.push(...(node.children ?? []))
|
|
}
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
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 })
|
|
)
|
|
]
|
|
}
|
|
}
|
|
|
|
return newNode
|
|
}
|
|
|
|
export const findNodeByKey = <T extends TreeNode>(
|
|
root: T,
|
|
key: string
|
|
): T | null => {
|
|
if (root.key === key) {
|
|
return root
|
|
}
|
|
if (!root.children) {
|
|
return null
|
|
}
|
|
for (const child of root.children) {
|
|
const result = findNodeByKey(child, key)
|
|
if (result) {
|
|
return result
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Deep clone a tree node and its children.
|
|
* @param node - The node to clone.
|
|
* @returns A deep clone of the node.
|
|
*/
|
|
function cloneTree<T extends TreeNode>(node: T): T {
|
|
const clone = { ...node }
|
|
|
|
// Clone children recursively
|
|
if (node.children && node.children.length > 0) {
|
|
clone.children = node.children.map((child) => cloneTree(child))
|
|
}
|
|
|
|
return clone
|
|
}
|
|
|
|
/**
|
|
* Merge a subtree into the tree.
|
|
* @param root - The root of the tree.
|
|
* @param subtree - The subtree to merge.
|
|
* @returns A new tree with the subtree merged.
|
|
*/
|
|
export const combineTrees = <T extends TreeNode>(root: T, subtree: T): T => {
|
|
const newRoot = cloneTree(root)
|
|
|
|
const parentKey = subtree.key.slice(0, subtree.key.lastIndexOf('/'))
|
|
const parent = findNodeByKey(newRoot, parentKey)
|
|
|
|
if (parent) {
|
|
parent.children ??= []
|
|
parent.children.push(cloneTree(subtree))
|
|
}
|
|
|
|
return newRoot
|
|
}
|