Wrap pragmatic dnd API with hooks (#1207)

This commit is contained in:
Chenlei Hu
2024-10-10 10:53:49 -04:00
committed by GitHub
parent 4413fd248c
commit 009dbcf8c7
5 changed files with 159 additions and 120 deletions

8
package-lock.json generated
View File

@@ -8,7 +8,7 @@
"name": "comfyui-frontend",
"version": "1.3.16",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/litegraph": "^0.8.1",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
@@ -139,9 +139,9 @@
}
},
"node_modules/@atlaskit/pragmatic-drag-and-drop": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.2.1.tgz",
"integrity": "sha512-gW2wJblFAeg94YXITHg0YdhFM2nmFAdDmX0LKYBIm79yEbIrOiuHHukgSjII07M4U5JpJ0Ff/4BaADjN23ix+A==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.3.1.tgz",
"integrity": "sha512-MptcLppK78B2eplL5fHk93kfCbZ6uCpt33YauBPrOwI5zcHYJhZGeaGEaAXoVAHnSJOdQUhy6kGVVC9qggz2Fg==",
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.0.0",

View File

@@ -64,7 +64,7 @@
"zip-dir": "^2.0.0"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/litegraph": "^0.8.1",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",

View File

@@ -34,12 +34,8 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, inject, Ref, computed } from 'vue'
import { ref, inject, Ref, computed } from 'vue'
import Badge from 'primevue/badge'
import {
dropTargetForElements,
draggable
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
import type {
TreeExplorerDragAndDropData,
RenderedTreeExplorerNode,
@@ -47,6 +43,7 @@ import type {
} from '@/types/treeExplorerTypes'
import EditableText from '@/components/common/EditableText.vue'
import { useErrorHandling } from '@/hooks/errorHooks'
import { usePragmaticDraggable, usePragmaticDroppable } from '@/hooks/dndHooks'
const props = defineProps<{
node: RenderedTreeExplorerNode
@@ -89,54 +86,44 @@ const handleRename = errorHandling.wrapWithErrorHandlingAsync(
)
const container = ref<HTMLElement | null>(null)
const canDrop = ref(false)
const treeNodeElement = ref<HTMLElement | null>(null)
let dropTargetCleanup = () => {}
let draggableCleanup = () => {}
onMounted(() => {
treeNodeElement.value = container.value?.closest(
'.p-tree-node-content'
) as HTMLElement
if (props.node.droppable) {
dropTargetCleanup = dropTargetForElements({
element: treeNodeElement.value,
onDrop: async (event) => {
const dndData = event.source.data as TreeExplorerDragAndDropData
if (dndData.type === 'tree-explorer-node') {
await props.node.handleDrop?.(props.node, dndData)
canDrop.value = false
emit('itemDropped', props.node, dndData.data)
}
},
onDragEnter: (event) => {
const dndData = event.source.data as TreeExplorerDragAndDropData
if (dndData.type === 'tree-explorer-node') {
canDrop.value = true
}
},
onDragLeave: () => {
canDrop.value = false
}
})
}
if (props.node.draggable) {
draggableCleanup = draggable({
element: treeNodeElement.value,
getInitialData() {
return {
type: 'tree-explorer-node',
data: props.node
}
},
onDragStart: () => emit('dragStart', props.node),
onDrop: () => emit('dragEnd', props.node)
})
}
})
onUnmounted(() => {
dropTargetCleanup()
draggableCleanup()
})
const treeNodeElementGetter = () =>
container.value?.closest('.p-tree-node-content') as HTMLElement
if (props.node.draggable) {
usePragmaticDraggable(treeNodeElementGetter, {
getInitialData: () => {
return {
type: 'tree-explorer-node',
data: props.node
}
},
onDragStart: () => emit('dragStart', props.node),
onDrop: () => emit('dragEnd', props.node)
})
}
if (props.node.droppable) {
usePragmaticDroppable(treeNodeElementGetter, {
onDrop: async (event) => {
const dndData = event.source.data as TreeExplorerDragAndDropData
if (dndData.type === 'tree-explorer-node') {
await props.node.handleDrop?.(props.node, dndData)
canDrop.value = false
emit('itemDropped', props.node, dndData.data)
}
},
onDragEnter: (event) => {
const dndData = event.source.data as TreeExplorerDragAndDropData
if (dndData.type === 'tree-explorer-node') {
canDrop.value = true
}
},
onDragLeave: () => {
canDrop.value = false
}
})
}
</script>
<style scoped>

View File

@@ -22,10 +22,9 @@ import SideToolbar from '@/components/sidebar/SideToolbar.vue'
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue'
import NodeTooltip from '@/components/graph/NodeTooltip.vue'
import { ref, computed, onUnmounted, onMounted, watchEffect } from 'vue'
import { ref, computed, onMounted, watchEffect } from 'vue'
import { app as comfyApp } from '@/scripts/app'
import { useSettingStore } from '@/stores/settingStore'
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
import { useWorkspaceStore } from '@/stores/workspaceStateStore'
import {
@@ -47,6 +46,7 @@ import {
useModelToNodeStore
} from '@/stores/modelToNodeStore'
import GraphCanvasMenu from '@/components/graph/GraphCanvasMenu.vue'
import { usePragmaticDroppable } from '@/hooks/dndHooks'
const emit = defineEmits(['ready'])
const canvasRef = ref<HTMLCanvasElement | null>(null)
@@ -115,7 +115,60 @@ watchEffect(() => {
canvasStore.canvas.canvas.style.cursor = 'default'
})
let dropTargetCleanup = () => {}
usePragmaticDroppable(() => canvasRef.value, {
onDrop: (event) => {
const loc = event.location.current.input
const dndData = event.source.data
if (dndData.type === 'tree-explorer-node') {
const node = dndData.data as RenderedTreeExplorerNode
if (node.data instanceof ComfyNodeDefImpl) {
const nodeDef = node.data
// Add an offset on x to make sure after adding the node, the cursor
// is on the node (top left corner)
const pos = comfyApp.clientPosToCanvasPos([
loc.clientX - 20,
loc.clientY
])
comfyApp.addNodeOnGraph(nodeDef, { pos })
} else if (node.data instanceof ComfyModelDef) {
const model = node.data
const pos = comfyApp.clientPosToCanvasPos([loc.clientX, loc.clientY])
const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1])
let targetProvider: ModelNodeProvider | null = null
let targetGraphNode: LGraphNode | null = null
if (nodeAtPos) {
const providers = modelToNodeStore.getAllNodeProviders(
model.directory
)
for (const provider of providers) {
if (provider.nodeDef.name === nodeAtPos.comfyClass) {
targetGraphNode = nodeAtPos
targetProvider = provider
}
}
}
if (!targetGraphNode) {
const provider = modelToNodeStore.getNodeProvider(model.directory)
if (provider) {
targetGraphNode = comfyApp.addNodeOnGraph(provider.nodeDef, {
pos
})
targetProvider = provider
}
}
if (targetGraphNode) {
const widget = targetGraphNode.widgets.find(
(widget) => widget.name === targetProvider.key
)
if (widget) {
widget.value = model.file_name
}
}
}
}
}
})
onMounted(async () => {
// Backward compatible
@@ -140,66 +193,6 @@ onMounted(async () => {
window['app'] = comfyApp
window['graph'] = comfyApp.graph
dropTargetCleanup = dropTargetForElements({
element: canvasRef.value,
onDrop: (event) => {
const loc = event.location.current.input
const dndData = event.source.data
if (dndData.type === 'tree-explorer-node') {
const node = dndData.data as RenderedTreeExplorerNode
if (node.data instanceof ComfyNodeDefImpl) {
const nodeDef = node.data
// Add an offset on x to make sure after adding the node, the cursor
// is on the node (top left corner)
const pos = comfyApp.clientPosToCanvasPos([
loc.clientX - 20,
loc.clientY
])
comfyApp.addNodeOnGraph(nodeDef, { pos })
} else if (node.data instanceof ComfyModelDef) {
const model = node.data
const pos = comfyApp.clientPosToCanvasPos([loc.clientX, loc.clientY])
const nodeAtPos = comfyApp.graph.getNodeOnPos(pos[0], pos[1])
let targetProvider: ModelNodeProvider | null = null
let targetGraphNode: LGraphNode | null = null
if (nodeAtPos) {
const providers = modelToNodeStore.getAllNodeProviders(
model.directory
)
for (const provider of providers) {
if (provider.nodeDef.name === nodeAtPos.comfyClass) {
targetGraphNode = nodeAtPos
targetProvider = provider
}
}
}
if (!targetGraphNode) {
const provider = modelToNodeStore.getNodeProvider(model.directory)
if (provider) {
targetGraphNode = comfyApp.addNodeOnGraph(provider.nodeDef, {
pos
})
targetProvider = provider
}
}
if (targetGraphNode) {
const widget = targetGraphNode.widgets.find(
(widget) => widget.name === targetProvider.key
)
if (widget) {
widget.value = model.file_name
}
}
}
}
}
})
emit('ready')
})
onUnmounted(() => {
dropTargetCleanup()
})
</script>

59
src/hooks/dndHooks.ts Normal file
View File

@@ -0,0 +1,59 @@
import { onBeforeUnmount, onMounted } from 'vue'
import {
dropTargetForElements,
draggable
} from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
export function usePragmaticDroppable(
dropTargetElement: HTMLElement | (() => HTMLElement),
options: Omit<Parameters<typeof dropTargetForElements>[0], 'element'>
) {
let cleanup = () => {}
onMounted(() => {
const element =
typeof dropTargetElement === 'function'
? dropTargetElement()
: dropTargetElement
if (!element) {
return
}
cleanup = dropTargetForElements({
element,
...options
})
})
onBeforeUnmount(() => {
cleanup()
})
}
export function usePragmaticDraggable(
draggableElement: HTMLElement | (() => HTMLElement),
options: Omit<Parameters<typeof draggable>[0], 'element'>
) {
let cleanup = () => {}
onMounted(() => {
const element =
typeof draggableElement === 'function'
? draggableElement()
: draggableElement
if (!element) {
return
}
cleanup = draggable({
element,
...options
})
})
onBeforeUnmount(() => {
cleanup()
})
}