mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-09 23:20:04 +00:00
## Summary Implement a redesigned Node Library sidebar using Reka UI components with virtualized tree rendering and improved UX. ## Changes - **What**: - Add three-tab structure (Essential, All, Custom) using Reka UI Tabs - Implement TreeExplorerV2 with virtualized tree using TreeRoot/TreeVirtualizer for performance - Add node hover preview with teleport to show NodePreview component - Implement context menu for toggling favorites on nodes - Add search functionality that auto-expands matching folders - Create panel components: EssentialNodesPanel, AllNodesPanel, CustomNodesPanel - Add 'Open Manager' button in CustomNodesPanel - Use custom icons: comfy--node for nodes, ph--folder-fill for folders - New node preview component: `NodePreviewCard` - Api node folder icon - Node drag preview - **Feature Flag**: Enabled via URL parameter `?nodeRedesign=true` ## Review Focus - TreeExplorerV2.vue uses `[...expandedKeys]` to prevent internal mutation by Reka UI TreeRoot - Context menu injection key is exported from TreeExplorerV2Node.vue and imported by TreeExplorerV2.vue - Hover preview uses teleport to `#node-library-node-preview-container-v2` ## Screenshots (if applicable) | Feature | Screenshot | |---|---| | All nodes tab |<img width="323" height="761" alt="image" src="https://github.com/user-attachments/assets/1976222b-83dc-4a1b-838a-2d49aedea3b8" />| | Custom nodes tab | <img width="308" height="748" alt="image" src="https://github.com/user-attachments/assets/2c23bffb-bdaa-4c6c-8cac-7610fb7f3fb7" />| |Api nodes icon | <img width="299" height="523" alt="image" src="https://github.com/user-attachments/assets/e9ca05b0-1143-44cf-b227-6462173c7cd0" />| | node preview|<img width="499" height="544" alt="image" src="https://github.com/user-attachments/assets/8961a7b4-77ae-4e57-99cf-62d9e4e17088" />| | node drag preview | <img width="434" height="289" alt="image" src="https://github.com/user-attachments/assets/b5838c90-65d4-4bee-b2b3-c41b57870da8" />| Test by adding `?nodeRedesign=true` to the URL ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8548-WIP-feat-implement-NodeLibrarySidebarTabV2-with-Reka-UI-components-2fb6d73d36508134b7e0f75a2c9b976a) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: bymyself <cbyrne@comfy.org>
123 lines
3.6 KiB
Vue
123 lines
3.6 KiB
Vue
<template>
|
|
<ContextMenuRoot>
|
|
<ContextMenuTrigger :disabled="!showContextMenu" as-child>
|
|
<TreeRoot
|
|
:expanded="[...expandedKeys]"
|
|
:items="root.children ?? []"
|
|
:get-key="(item) => item.key"
|
|
:get-children="
|
|
(item) => (item.children?.length ? item.children : undefined)
|
|
"
|
|
class="m-0 p-0 pb-6"
|
|
>
|
|
<TreeVirtualizer
|
|
v-slot="{ item }"
|
|
:estimate-size="36"
|
|
:text-content="(item) => item.value.label ?? ''"
|
|
>
|
|
<TreeExplorerV2Node
|
|
:item="
|
|
item as FlattenedItem<RenderedTreeExplorerNode<ComfyNodeDefImpl>>
|
|
"
|
|
@node-click="
|
|
(
|
|
node: RenderedTreeExplorerNode<ComfyNodeDefImpl>,
|
|
e: MouseEvent
|
|
) => emit('nodeClick', node, e)
|
|
"
|
|
>
|
|
<template #folder="{ node }">
|
|
<slot name="folder" :node="node" />
|
|
</template>
|
|
<template #node="{ node }">
|
|
<slot name="node" :node="node" />
|
|
</template>
|
|
</TreeExplorerV2Node>
|
|
</TreeVirtualizer>
|
|
</TreeRoot>
|
|
</ContextMenuTrigger>
|
|
|
|
<ContextMenuPortal v-if="showContextMenu">
|
|
<ContextMenuContent
|
|
class="z-[9999] min-w-32 overflow-hidden rounded-md border border-border-default bg-comfy-menu-bg p-1 shadow-md"
|
|
>
|
|
<ContextMenuItem
|
|
class="flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-highlight focus:bg-highlight"
|
|
@select="handleAddToFavorites"
|
|
>
|
|
<i
|
|
:class="
|
|
isCurrentNodeBookmarked
|
|
? 'icon-[ph--star-fill]'
|
|
: 'icon-[lucide--star]'
|
|
"
|
|
class="size-4"
|
|
/>
|
|
{{
|
|
isCurrentNodeBookmarked
|
|
? $t('sideToolbar.nodeLibraryTab.sections.unfavoriteNode')
|
|
: $t('sideToolbar.nodeLibraryTab.sections.favoriteNode')
|
|
}}
|
|
</ContextMenuItem>
|
|
</ContextMenuContent>
|
|
</ContextMenuPortal>
|
|
</ContextMenuRoot>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { FlattenedItem } from 'reka-ui'
|
|
import {
|
|
ContextMenuContent,
|
|
ContextMenuItem,
|
|
ContextMenuPortal,
|
|
ContextMenuRoot,
|
|
ContextMenuTrigger,
|
|
TreeRoot,
|
|
TreeVirtualizer
|
|
} from 'reka-ui'
|
|
import { computed, provide, ref } from 'vue'
|
|
|
|
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
|
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
|
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
|
|
import { InjectKeyContextMenuNode } from '@/types/treeExplorerTypes'
|
|
|
|
import TreeExplorerV2Node from './TreeExplorerV2Node.vue'
|
|
|
|
const { showContextMenu = false } = defineProps<{
|
|
root: RenderedTreeExplorerNode<ComfyNodeDefImpl>
|
|
showContextMenu?: boolean
|
|
}>()
|
|
|
|
const expandedKeys = defineModel<string[]>('expandedKeys', {
|
|
default: () => []
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
nodeClick: [
|
|
node: RenderedTreeExplorerNode<ComfyNodeDefImpl>,
|
|
event: MouseEvent
|
|
]
|
|
addToFavorites: [node: RenderedTreeExplorerNode<ComfyNodeDefImpl>]
|
|
}>()
|
|
|
|
const contextMenuNode = ref<RenderedTreeExplorerNode<ComfyNodeDefImpl> | null>(
|
|
null
|
|
)
|
|
provide(InjectKeyContextMenuNode, contextMenuNode)
|
|
|
|
const nodeBookmarkStore = useNodeBookmarkStore()
|
|
|
|
const isCurrentNodeBookmarked = computed(() => {
|
|
const node = contextMenuNode.value
|
|
if (!node?.data) return false
|
|
return nodeBookmarkStore.isBookmarked(node.data)
|
|
})
|
|
|
|
function handleAddToFavorites() {
|
|
if (contextMenuNode.value) {
|
|
emit('addToFavorites', contextMenuNode.value)
|
|
}
|
|
}
|
|
</script>
|