mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 19:21:54 +00:00
Refactor node library drag and drop (#617)
This commit is contained in:
@@ -20,7 +20,7 @@ import { ref, computed, onUnmounted, watch, onMounted, watchEffect } from 'vue'
|
|||||||
import { app as comfyApp } from '@/scripts/app'
|
import { app as comfyApp } from '@/scripts/app'
|
||||||
import { useSettingStore } from '@/stores/settingStore'
|
import { useSettingStore } from '@/stores/settingStore'
|
||||||
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
||||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
|
||||||
import { useWorkspaceStore } from '@/stores/workspaceStateStore'
|
import { useWorkspaceStore } from '@/stores/workspaceStateStore'
|
||||||
import {
|
import {
|
||||||
LiteGraph,
|
LiteGraph,
|
||||||
@@ -113,14 +113,18 @@ onMounted(async () => {
|
|||||||
element: canvasRef.value,
|
element: canvasRef.value,
|
||||||
onDrop: (event) => {
|
onDrop: (event) => {
|
||||||
const loc = event.location.current.input
|
const loc = event.location.current.input
|
||||||
// Add an offset on x to make sure after adding the node, the cursor
|
const dndData = event.source.data
|
||||||
// is on the node (top left corner)
|
|
||||||
const pos = comfyApp.clientPosToCanvasPos([loc.clientX - 20, loc.clientY])
|
if (dndData.type === 'add-node') {
|
||||||
const comfyNodeName = event.source.element.getAttribute(
|
const nodeDef = dndData.data as ComfyNodeDefImpl
|
||||||
'data-comfy-node-name'
|
// Add an offset on x to make sure after adding the node, the cursor
|
||||||
)
|
// is on the node (top left corner)
|
||||||
const nodeDef = nodeDefStore.nodeDefsByName[comfyNodeName]
|
const pos = comfyApp.clientPosToCanvasPos([
|
||||||
comfyApp.addNodeOnGraph(nodeDef, { pos })
|
loc.clientX - 20,
|
||||||
|
loc.clientY
|
||||||
|
])
|
||||||
|
comfyApp.addNodeOnGraph(nodeDef, { pos })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
emit('ready')
|
emit('ready')
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
<!-- Tree with all leaf nodes draggable -->
|
|
||||||
<script>
|
|
||||||
import Tree from 'primevue/tree'
|
|
||||||
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
|
||||||
import { h, onMounted, onBeforeUnmount, computed } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'TreePlus',
|
|
||||||
extends: Tree,
|
|
||||||
props: {
|
|
||||||
dragSelector: {
|
|
||||||
type: String,
|
|
||||||
default: '.p-tree-node'
|
|
||||||
},
|
|
||||||
// Explicitly declare all v-model props
|
|
||||||
expandedKeys: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
selectionKeys: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: ['update:expandedKeys', 'update:selectionKeys'],
|
|
||||||
setup(props, context) {
|
|
||||||
// Create computed properties for each v-model prop
|
|
||||||
const computedExpandedKeys = computed({
|
|
||||||
get: () => props.expandedKeys,
|
|
||||||
set: (value) => context.emit('update:expandedKeys', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const computedSelectionKeys = computed({
|
|
||||||
get: () => props.selectionKeys,
|
|
||||||
set: (value) => context.emit('update:selectionKeys', value)
|
|
||||||
})
|
|
||||||
|
|
||||||
let observer = null
|
|
||||||
|
|
||||||
const makeDraggable = (element) => {
|
|
||||||
if (!element._draggableCleanup) {
|
|
||||||
element._draggableCleanup = draggable({
|
|
||||||
element
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const observeTreeChanges = (treeElement) => {
|
|
||||||
observer = new MutationObserver((mutations) => {
|
|
||||||
mutations.forEach((mutation) => {
|
|
||||||
if (mutation.type === 'childList') {
|
|
||||||
mutation.addedNodes.forEach((node) => {
|
|
||||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
node.querySelectorAll(props.dragSelector).forEach(makeDraggable)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
observer.observe(treeElement, { childList: true, subtree: true })
|
|
||||||
|
|
||||||
// Make existing nodes draggable
|
|
||||||
treeElement.querySelectorAll(props.dragSelector).forEach(makeDraggable)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const treeElement = document.querySelector('.p-tree')
|
|
||||||
if (treeElement) {
|
|
||||||
observeTreeChanges(treeElement)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
if (observer) {
|
|
||||||
observer.disconnect()
|
|
||||||
}
|
|
||||||
// Clean up draggable instances if necessary
|
|
||||||
const treeElement = document.querySelector('.p-tree')
|
|
||||||
if (treeElement) {
|
|
||||||
treeElement.querySelectorAll(props.dragSelector).forEach((node) => {
|
|
||||||
if (node._draggableCleanup) {
|
|
||||||
node._draggableCleanup()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return () =>
|
|
||||||
h(
|
|
||||||
Tree,
|
|
||||||
{
|
|
||||||
...context.attrs,
|
|
||||||
...props,
|
|
||||||
expandedKeys: computedExpandedKeys.value,
|
|
||||||
selectionKeys: computedSelectionKeys.value,
|
|
||||||
'onUpdate:expandedKeys': (value) =>
|
|
||||||
(computedExpandedKeys.value = value),
|
|
||||||
'onUpdate:selectionKeys': (value) =>
|
|
||||||
(computedSelectionKeys.value = value)
|
|
||||||
},
|
|
||||||
context.slots
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
:placeholder="$t('searchNodes') + '...'"
|
:placeholder="$t('searchNodes') + '...'"
|
||||||
/>
|
/>
|
||||||
<TreePlus
|
<Tree
|
||||||
class="node-lib-tree"
|
class="node-lib-tree"
|
||||||
v-model:expandedKeys="expandedKeys"
|
v-model:expandedKeys="expandedKeys"
|
||||||
selectionMode="single"
|
selectionMode="single"
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
@toggleBookmark="toggleBookmark(node.data.display_name)"
|
@toggleBookmark="toggleBookmark(node.data.display_name)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</TreePlus>
|
</Tree>
|
||||||
<div
|
<div
|
||||||
v-if="hoveredComfyNode"
|
v-if="hoveredComfyNode"
|
||||||
class="node-lib-node-preview"
|
class="node-lib-node-preview"
|
||||||
@@ -89,7 +89,7 @@ import {
|
|||||||
import { computed, ref, nextTick } from 'vue'
|
import { computed, ref, nextTick } from 'vue'
|
||||||
import type { TreeNode } from 'primevue/treenode'
|
import type { TreeNode } from 'primevue/treenode'
|
||||||
import NodeTreeLeaf from './nodeLibrary/NodeTreeLeaf.vue'
|
import NodeTreeLeaf from './nodeLibrary/NodeTreeLeaf.vue'
|
||||||
import TreePlus from '@/components/primevueOverride/TreePlus.vue'
|
import Tree from 'primevue/tree'
|
||||||
import NodePreview from '@/components/node/NodePreview.vue'
|
import NodePreview from '@/components/node/NodePreview.vue'
|
||||||
import SearchBox from '@/components/common/SearchBox.vue'
|
import SearchBox from '@/components/common/SearchBox.vue'
|
||||||
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
|
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="node-tree-leaf">
|
<div class="node-tree-leaf" ref="container">
|
||||||
<div class="node-content">
|
<div class="node-content">
|
||||||
<Tag
|
<Tag
|
||||||
v-if="node.experimental"
|
v-if="node.experimental"
|
||||||
@@ -24,6 +24,9 @@
|
|||||||
import Button from 'primevue/button'
|
import Button from 'primevue/button'
|
||||||
import Tag from 'primevue/tag'
|
import Tag from 'primevue/tag'
|
||||||
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
import { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
|
||||||
|
import { CanvasDragAndDropData } from '@/types/litegraphTypes'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
node: ComfyNodeDefImpl
|
node: ComfyNodeDefImpl
|
||||||
@@ -37,6 +40,26 @@ const emit = defineEmits<{
|
|||||||
const toggleBookmark = () => {
|
const toggleBookmark = () => {
|
||||||
emit('toggle-bookmark', props.node)
|
emit('toggle-bookmark', props.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const container = ref<HTMLElement | null>(null)
|
||||||
|
let draggableCleanup: () => void
|
||||||
|
onMounted(() => {
|
||||||
|
const treeNodeElement = container.value?.closest(
|
||||||
|
'.p-tree-node'
|
||||||
|
) as HTMLElement
|
||||||
|
draggableCleanup = draggable({
|
||||||
|
element: treeNodeElement,
|
||||||
|
getInitialData() {
|
||||||
|
return {
|
||||||
|
type: 'add-node',
|
||||||
|
data: props.node
|
||||||
|
} as CanvasDragAndDropData<ComfyNodeDefImpl>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
draggableCleanup()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -73,3 +73,8 @@ export class ConnectingLinkImpl implements ConnectingLink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CanvasDragAndDropData<T = any> = {
|
||||||
|
type: 'add-node'
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user