chore(docs): update drag and drop example

This commit is contained in:
braks
2024-02-03 19:07:11 +01:00
committed by Braks
parent be57e51685
commit 88beaeca2b
7 changed files with 162 additions and 69 deletions

View File

@@ -1,73 +1,31 @@
<script setup>
import { ref } from 'vue'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { nextTick, watch } from 'vue'
import DropzoneBackground from './DropzoneBackground.vue'
import Sidebar from './Sidebar.vue'
import useDragAndDrop from './useDnD'
let id = 0
function getId() {
return `dndnode_${id++}`
}
const { onConnect, addEdges } = useVueFlow()
const { findNode, onConnect, addEdges, addNodes, project, vueFlowRef } = useVueFlow({
nodes: [
{
id: '1',
type: 'input',
label: 'input node',
position: { x: 250, y: 25 },
},
],
})
const { onDragOver, onDrop, onDragLeave, isDragging } = useDragAndDrop()
function onDragOver(event) {
event.preventDefault()
const nodes = ref([])
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
onConnect((params) => addEdges(params))
function onDrop(event) {
const type = event.dataTransfer?.getData('application/vueflow')
const { left, top } = vueFlowRef.value.getBoundingClientRect()
const position = project({
x: event.clientX - left,
y: event.clientY - top,
})
const newNode = {
id: getId(),
type,
position,
label: `${type} node`,
}
addNodes([newNode])
// align node position after drop, so it's centered to the mouse
nextTick(() => {
const node = findNode(newNode.id)
const stop = watch(
() => node.dimensions,
(dimensions) => {
if (dimensions.width > 0 && dimensions.height > 0) {
node.position = { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 }
stop()
}
},
{ deep: true, flush: 'post' },
)
})
}
onConnect(addEdges)
</script>
<template>
<div class="dndflow" @drop="onDrop">
<VueFlow @dragover="onDragOver" />
<VueFlow :nodes="nodes" @dragover="onDragOver" @dragleave="onDragLeave">
<DropzoneBackground
:style="{
backgroundColor: isDragging ? 'rgba(0, 0, 0, 0.1)' : 'transparent',
borderColor: isDragging ? 'rgba(0, 0, 0, 0.2)' : 'transparent',
transition: 'background-color 0.2s, border-color 0.2s',
}"
>
</DropzoneBackground>
</VueFlow>
<Sidebar />
</div>

View File

@@ -0,0 +1,11 @@
<script lang="ts" setup>
import { Background } from '@vue-flow/background'
</script>
<template>
<div style="height: 100%; width: 100%">
<Background :size="2" :gap="20" pattern-color="#BDBDBD">
<slot />
</Background>
</div>
</template>

View File

@@ -1,10 +1,7 @@
<script setup>
function onDragStart(event, nodeType) {
if (event.dataTransfer) {
event.dataTransfer.setData('application/vueflow', nodeType)
event.dataTransfer.effectAllowed = 'move'
}
}
import useDragAndDrop from './useDnD'
const { onDragStart } = useDragAndDrop()
</script>
<template>

View File

@@ -1,3 +1,5 @@
export { default as DndApp } from './App.vue?raw'
export { default as DndSidebar } from './Sidebar.vue?raw'
export { default as DndBackground } from './DropzoneBackground.vue?raw'
export { default as DndCSS } from './style.css?inline'
export { default as DndScript } from './useDnD.js?raw'

120
docs/examples/dnd/useDnD.js Normal file
View File

@@ -0,0 +1,120 @@
import { useVueFlow } from '@vue-flow/core'
import { ref, watch } from 'vue'
let id = 0
/**
* @returns {string} - A unique id.
*/
function getId() {
return `dndnode_${id++}`
}
/**
* In a real world scenario you'd want to avoid creating refs in a global scope like this as they might not be cleaned up properly.
* @type {{draggedType: Ref<string|null>, isDragOver: Ref<boolean>, isDragging: Ref<boolean>}}
*/
const state = {
/**
* The type of the node being dragged.
*/
draggedType: ref(null),
isDragOver: ref(false),
isDragging: ref(false),
}
export default function useDragAndDrop() {
const { draggedType, isDragOver, isDragging } = state
const { addNodes, screenToFlowCoordinate, onNodesInitialized, updateNode } = useVueFlow()
watch(isDragging, (dragging) => {
document.body.style.userSelect = dragging ? 'none' : ''
})
function onDragStart(event, type) {
if (event.dataTransfer) {
event.dataTransfer.setData('application/vueflow', type)
event.dataTransfer.effectAllowed = 'move'
}
draggedType.value = type
isDragging.value = true
document.addEventListener('drop', onDragEnd)
}
/**
* Handles the drag over event.
*
* @param {DragEvent} event
*/
function onDragOver(event) {
event.preventDefault()
if (draggedType.value) {
isDragOver.value = true
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'move'
}
}
}
function onDragLeave() {
isDragOver.value = false
}
function onDragEnd() {
isDragging.value = false
isDragOver.value = false
draggedType.value = null
document.removeEventListener('drop', onDragEnd)
}
/**
* Handles the drop event.
*
* @param {DragEvent} event
*/
function onDrop(event) {
const position = screenToFlowCoordinate({
x: event.clientX,
y: event.clientY,
})
const nodeId = getId()
const newNode = {
id: nodeId,
type: draggedType.value,
position,
label: `[${nodeId}]`,
}
/**
* Align node position after drop, so it's centered to the mouse
*
* We can hook into events even in a callback, and we can remove the event listener after it's been called.
*/
const { off } = onNodesInitialized(() => {
updateNode(nodeId, (node) => ({
position: { x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2 },
}))
off()
})
addNodes(newNode)
}
return {
draggedType,
isDragOver,
isDragging,
onDragStart,
onDragLeave,
onDragOver,
onDrop,
}
}

View File

@@ -1,12 +1,12 @@
<script setup>
import { h, ref } from 'vue'
import { Background } from '@vue-flow/background'
import { MarkerType, VueFlow } from '@vue-flow/core'
import { h, ref } from 'vue'
import CustomEdge from './CustomEdge.vue'
import CustomEdge2 from './CustomEdge2.vue'
import CustomEdgeLabel from './CustomEdgeLabel.vue'
const elements = ref([
const nodes = ref([
{ id: '1', type: 'input', label: 'Start', position: { x: 50, y: 0 }, style: { borderColor: '#10b981' } },
{ id: '2', label: 'Node 2', position: { x: 150, y: 100 } },
{ id: '2a', label: 'Node 2a', position: { x: 0, y: 180 } },
@@ -18,6 +18,9 @@ const elements = ref([
{ id: '7', type: 'output', label: 'Output 7', position: { x: 50, y: 600 } },
{ id: '8', type: 'output', label: 'Output 8', position: { x: 350, y: 600 } },
{ id: '9', type: 'output', label: 'Output 9', position: { x: 550, y: 400 } },
])
const edges = ref([
{ id: 'e1-2', source: '1', target: '2', label: 'bezier edge (default)', class: 'normal-edge' },
{ id: 'e2-2a', source: '2', target: '2a', type: 'smoothstep', label: 'smoothstep edge' },
{ id: 'e2-3', source: '2', target: '3', type: 'step', label: 'step edge' },
@@ -61,7 +64,7 @@ const elements = ref([
</script>
<template>
<VueFlow v-model="elements" :fit-view-on-init="true" :snap-to-grid="true">
<VueFlow :nodes="nodes" :edges="edges" fit-view-on-init>
<template #edge-custom="props">
<CustomEdge v-bind="props" />
</template>

View File

@@ -8,7 +8,7 @@ import { UpdateEdgeApp } from './update-edge'
import { UpdateNodeApp, UpdateNodeCSS } from './update-node'
import { ValidationApp, ValidationCSS, ValidationCustomInput, ValidationCustomNode } from './validation'
import { SaveRestoreApp, SaveRestoreCSS, SaveRestoreControls } from './save-restore'
import { DndApp, DndCSS, DndSidebar } from './dnd'
import { DndApp, DndBackground, DndCSS, DndScript, DndSidebar } from './dnd'
import { EmptyApp } from './empty'
import { HiddenApp } from './hidden'
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
@@ -75,7 +75,9 @@ export const exampleImports = {
dnd: {
'App.vue': DndApp,
'Sidebar.vue': DndSidebar,
'DropzoneBackground.vue': DndBackground,
'style.css': DndCSS,
'useDnD.js': DndScript,
},
empty: {
'App.vue': EmptyApp,