mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-02 19:49:58 +00:00
[Major Refactor] Use TreeExplorer on nodeLibrarySidebarTab (#699)
* Basic move * Add back node bookmark * Move node preview * Fix drag node to canvas * Restore click node to add to canvas * Split bookmark tree and library tree * Migrate rename and delete context menu * Fix expanded keys * Split components * Support extra menu items * Context menu only for folder * Migrate add folder * Handle drop * Store color customization * remove extra padding * Do not show context menu if no item * Hide divider if no bookmark * Sort bookmarks alphabetically default * nit * proper edit * Update test selectors * Auto expand on item drop * nit * Fix tests * Search also searches bookmarks tree * Add serach playwright test
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
:class="props.class"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
:value="renderedRoots"
|
||||
selectionMode="single"
|
||||
:pt="{
|
||||
nodeLabel: 'tree-explorer-node-label',
|
||||
nodeContent: ({ props }) => ({
|
||||
@@ -40,25 +41,31 @@ import type {
|
||||
TreeExplorerNode
|
||||
} from '@/types/treeExplorerTypes'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { useTreeExpansion } from '@/hooks/treeHooks'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const expandedKeys = defineModel<Record<string, boolean>>('expandedKeys')
|
||||
provide('expandedKeys', expandedKeys)
|
||||
const props = defineProps<{
|
||||
roots: TreeExplorerNode[]
|
||||
class?: string
|
||||
extraMenuItems?: MenuItem[]
|
||||
extraMenuItems?:
|
||||
| MenuItem[]
|
||||
| ((targetNode: RenderedTreeExplorerNode) => MenuItem[])
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode): void
|
||||
(e: 'nodeClick', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
(e: 'nodeDelete', node: RenderedTreeExplorerNode): void
|
||||
(e: 'contextMenu', node: RenderedTreeExplorerNode, event: MouseEvent): void
|
||||
}>()
|
||||
const { expandedKeys, toggleNodeOnEvent } = useTreeExpansion()
|
||||
const renderedRoots = computed<RenderedTreeExplorerNode[]>(() => {
|
||||
return props.roots.map(fillNodeInfo)
|
||||
})
|
||||
const getTreeNodeIcon = (node: TreeExplorerNode) => {
|
||||
if (node.getIcon) {
|
||||
return node.getIcon(node)
|
||||
const icon = node.getIcon(node)
|
||||
if (icon) {
|
||||
return icon
|
||||
}
|
||||
} else if (node.icon) {
|
||||
return node.icon
|
||||
}
|
||||
@@ -82,46 +89,57 @@ const fillNodeInfo = (node: TreeExplorerNode): RenderedTreeExplorerNode => {
|
||||
}
|
||||
}
|
||||
const onNodeContentClick = (e: MouseEvent, node: RenderedTreeExplorerNode) => {
|
||||
if (!node.key) return
|
||||
if (node.type === 'folder') {
|
||||
toggleNodeOnEvent(e, node)
|
||||
}
|
||||
emit('nodeClick', node)
|
||||
emit('nodeClick', node, e)
|
||||
}
|
||||
const menu = ref(null)
|
||||
const menuTargetNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
provide('menuTargetNode', menuTargetNode)
|
||||
const renameEditingNode = ref<RenderedTreeExplorerNode | null>(null)
|
||||
provide('renameEditingNode', renameEditingNode)
|
||||
|
||||
const { t } = useI18n()
|
||||
const renameCommand = (node: RenderedTreeExplorerNode) => {
|
||||
renameEditingNode.value = node
|
||||
}
|
||||
const deleteCommand = (node: RenderedTreeExplorerNode) => {
|
||||
node.handleDelete?.(node)
|
||||
emit('nodeDelete', node)
|
||||
}
|
||||
const menuItems = computed<MenuItem[]>(() => [
|
||||
{
|
||||
label: 'Rename',
|
||||
label: t('rename'),
|
||||
icon: 'pi pi-file-edit',
|
||||
command: () => {
|
||||
renameEditingNode.value = menuTargetNode.value
|
||||
},
|
||||
command: () => renameCommand(menuTargetNode.value),
|
||||
visible: menuTargetNode.value?.handleRename !== undefined
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
label: t('delete'),
|
||||
icon: 'pi pi-trash',
|
||||
command: () => {
|
||||
menuTargetNode.value?.handleDelete?.(menuTargetNode.value)
|
||||
emit('nodeDelete', menuTargetNode.value)
|
||||
},
|
||||
command: () => deleteCommand(menuTargetNode.value),
|
||||
visible: menuTargetNode.value?.handleDelete !== undefined
|
||||
},
|
||||
...(props.extraMenuItems || [])
|
||||
...(props.extraMenuItems
|
||||
? typeof props.extraMenuItems === 'function'
|
||||
? props.extraMenuItems(menuTargetNode.value)
|
||||
: props.extraMenuItems
|
||||
: [])
|
||||
])
|
||||
const handleContextMenu = (node: RenderedTreeExplorerNode, e: MouseEvent) => {
|
||||
menuTargetNode.value = node
|
||||
emit('contextMenu', node, e)
|
||||
menu.value?.show(e)
|
||||
if (menuItems.value.filter((item) => item.visible).length > 0) {
|
||||
menu.value?.show(e)
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
renameCommand,
|
||||
deleteCommand
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tree-explorer-node-label {
|
||||
:deep(.tree-explorer-node-label) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: var(--p-tree-node-gap);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<EditableText
|
||||
:modelValue="node.label"
|
||||
:isEditing="isEditing"
|
||||
@edit="(newName: string) => props.node.handleRename(node, newName)"
|
||||
@edit="handleRename"
|
||||
/>
|
||||
<slot name="after-label" :node="props.node"></slot>
|
||||
</span>
|
||||
@@ -26,9 +26,9 @@
|
||||
severity="secondary"
|
||||
class="leaf-count-badge"
|
||||
/>
|
||||
<slot name="actions" :node="node">
|
||||
<!-- Default slot content for actions -->
|
||||
</slot>
|
||||
</div>
|
||||
<div class="node-actions">
|
||||
<slot name="actions" :node="props.node"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -62,13 +62,15 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const labelEditable = computed<boolean>(() => !!props.node.handleRename)
|
||||
const renameEditingNode = inject(
|
||||
'renameEditingNode'
|
||||
) as Ref<TreeExplorerNode | null>
|
||||
const renameEditingNode =
|
||||
inject<Ref<TreeExplorerNode | null>>('renameEditingNode')
|
||||
const isEditing = computed(
|
||||
() => labelEditable.value && renameEditingNode.value?.key === props.node.key
|
||||
)
|
||||
|
||||
const handleRename = (newName: string) => {
|
||||
props.node.handleRename(props.node, newName)
|
||||
renameEditingNode.value = null
|
||||
}
|
||||
const container = ref<HTMLElement | null>(null)
|
||||
const canDrop = ref(false)
|
||||
const treeNodeElement = ref<HTMLElement | null>(null)
|
||||
|
||||
Reference in New Issue
Block a user