update!: graphnode and node typing

* graphnode is a node containing internal Vue Flow data
* move util files to util directory
* move flow actions type into store file
* rename panel type file to zoom
* rename types file to flow
* Rename Node to NodeWrapper
* Rename Edge to EdgeWrapper

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
Braks
2021-11-21 16:25:27 +01:00
parent ba24f3ca62
commit 062aee45b2
36 changed files with 280 additions and 275 deletions
+2 -2
View File
@@ -35,7 +35,7 @@ const onLoad = (flowInstance: FlowInstance) => {
} }
const updatePos = () => { const updatePos = () => {
elements.value = elements.value.map((el: FlowElement) => { elements.value = elements.value.map((el) => {
if (isNode(el)) { if (isNode(el)) {
el.position = { el.position = {
x: Math.random() * 400, x: Math.random() * 400,
@@ -50,7 +50,7 @@ const logToObject = () => console.log(rfInstance.value?.toObject())
const resetTransform = () => rfInstance.value?.setTransform({ x: 0, y: 0, zoom: 1 }) const resetTransform = () => rfInstance.value?.setTransform({ x: 0, y: 0, zoom: 1 })
const toggleclasss = () => { const toggleclasss = () => {
elements.value = elements.value.map((el: FlowElement) => { elements.value = elements.value.map((el) => {
if (isNode(el)) el.class = el.class === 'light' ? 'dark' : 'light' if (isNode(el)) el.class = el.class === 'light' ? 'dark' : 'light'
return el return el
}) })
+2 -2
View File
@@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { getEdgeCenter, getBezierPath, getMarkerEnd, ArrowHeadType, Edge, ElementId, Position, useVueFlow } from '~/index' import { getEdgeCenter, getBezierPath, getMarkerEnd, ArrowHeadType, ElementId, Position, useVueFlow, EdgeProps } from '~/index'
interface CustomEdgeProps<T = any> extends Edge<T> { interface CustomEdgeProps<T = any> extends EdgeProps<T> {
id: ElementId id: ElementId
sourceX: number sourceX: number
sourceY: number sourceY: number
@@ -44,8 +44,7 @@ const nodeClassNameFunc = props.nodeClassName instanceof Function ? props.nodeCl
const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision' const shapeRendering: ShapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'
const nodes = computed(() => store.nodes.filter((node) => !node.isHidden)) const bb = computed(() => getRectOfNodes(store.getNodes))
const bb = computed(() => getRectOfNodes(nodes.value))
const viewBB = computed(() => ({ const viewBB = computed(() => ({
x: -store.transform[0] / store.transform[2], x: -store.transform[0] / store.transform[2],
y: -store.transform[1] / store.transform[2], y: -store.transform[1] / store.transform[2],
@@ -90,7 +89,7 @@ const d = computed(() => {
:viewBox="`${viewBox.x || 0} ${viewBox.y || 0} ${viewBox.width || 0} ${viewBox.height || 0}`" :viewBox="`${viewBox.x || 0} ${viewBox.y || 0} ${viewBox.width || 0} ${viewBox.height || 0}`"
class="vue-flow__minimap" class="vue-flow__minimap"
> >
<template v-for="node of nodes" :key="`mini-map-node-${node.id}`"> <template v-for="node of store.getNodes" :key="`mini-map-node-${node.id}`">
<slot <slot
:x="node.__vf.position.x" :x="node.__vf.position.x"
:y="node.__vf.position.y" :y="node.__vf.position.y"
@@ -2,10 +2,10 @@
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { getBezierPath, getSmoothStepPath } from '../Edges/utils' import { getBezierPath, getSmoothStepPath } from '../Edges/utils'
import { useStore } from '../../composables' import { useStore } from '../../composables'
import { ConnectionLineType, HandleElement, Node, Position } from '../../types' import { ConnectionLineType, HandleElement, GraphNode, Position } from '../../types'
interface ConnectionLineProps { interface ConnectionLineProps {
sourceNode: Node sourceNode: GraphNode
connectionLineType?: ConnectionLineType connectionLineType?: ConnectionLineType
connectionLineStyle?: CSSProperties connectionLineStyle?: CSSProperties
} }
@@ -19,29 +19,29 @@ const store = useStore()
const sourceHandle = const sourceHandle =
store.connectionHandleId && store.connectionHandleType store.connectionHandleId && store.connectionHandleType
? props.sourceNode.__vf?.handleBounds[store.connectionHandleType].find( ? props.sourceNode.__vf.handleBounds[store.connectionHandleType]?.find(
(d: HandleElement) => d.id === store.connectionHandleId, (d: HandleElement) => d.id === store.connectionHandleId,
) )
: store.connectionHandleType && props.sourceNode.__vf?.handleBounds[store.connectionHandleType ?? 'source'][0] : store.connectionHandleType && props.sourceNode.__vf.handleBounds[store.connectionHandleType ?? 'source']?.[0]
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.__vf?.width as number) / 2 const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.__vf?.width as number) / 2
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.__vf?.height const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.__vf?.height
const sourceX = props.sourceNode.__vf?.position?.x + sourceHandleX const sourceX = props.sourceNode.__vf?.position?.x + sourceHandleX
const sourceY = props.sourceNode.__vf?.position?.y + sourceHandleY const sourceY = props.sourceNode.__vf?.position?.y + sourceHandleY
const isRightOrLeft = sourceHandle.position === Position.Left || sourceHandle.position === Position.Right const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
const targetPosition = isRightOrLeft ? Position.Left : Position.Top const targetPosition = isRightOrLeft ? Position.Left : Position.Top
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2]) const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2]) const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
const dAttr = computed(() => { const dAttr = computed(() => {
let path = `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}` let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
switch (props.connectionLineType) { switch (props.connectionLineType) {
case ConnectionLineType.Bezier: case ConnectionLineType.Bezier:
path = getBezierPath({ path = getBezierPath({
sourceX, sourceX,
sourceY, sourceY,
sourcePosition: sourceHandle.position, sourcePosition: sourceHandle?.position,
targetX: targetX.value, targetX: targetX.value,
targetY: targetY.value, targetY: targetY.value,
targetPosition, targetPosition,
@@ -51,7 +51,7 @@ const dAttr = computed(() => {
path = getSmoothStepPath({ path = getSmoothStepPath({
sourceX, sourceX,
sourceY, sourceY,
sourcePosition: sourceHandle.position, sourcePosition: sourceHandle?.position,
targetX: targetX.value, targetX: targetX.value,
targetY: targetY.value, targetY: targetY.value,
targetPosition, targetPosition,
@@ -62,7 +62,7 @@ const dAttr = computed(() => {
path = getSmoothStepPath({ path = getSmoothStepPath({
sourceX, sourceX,
sourceY, sourceY,
sourcePosition: sourceHandle.position, sourcePosition: sourceHandle?.position,
targetX: targetX.value, targetX: targetX.value,
targetY: targetY.value, targetY: targetY.value,
targetPosition, targetPosition,
@@ -78,7 +78,7 @@ const dAttr = computed(() => {
v-bind="{ v-bind="{
sourceX, sourceX,
sourceY, sourceY,
sourcePosition: sourceHandle.position, sourcePosition: sourceHandle?.position,
targetX, targetX,
targetY, targetY,
targetPosition, targetPosition,
@@ -1,17 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useHandle, useStore } from '../../composables' import { useHandle, useStore } from '../../composables'
import { ConnectionMode, Edge, EdgePositions, Position } from '../../types' import { ConnectionMode, Edge, EdgePositions, Position } from '../../types'
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils' import { isEdge, getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../utils'
import { isEdge } from '../../utils'
import EdgeAnchor from './EdgeAnchor.vue' import EdgeAnchor from './EdgeAnchor.vue'
interface EdgeProps { interface EdgeWrapper {
edge: Edge edge: Edge
markerEndId?: string markerEndId?: string
edgeUpdaterRadius?: number edgeUpdaterRadius?: number
} }
const props = withDefaults(defineProps<EdgeProps>(), {}) const props = withDefaults(defineProps<EdgeWrapper>(), {})
const store = useStore() const store = useStore()
+1 -1
View File
@@ -4,4 +4,4 @@ export { default as SmoothStepEdge } from './SmoothStepEdge.vue'
export { default as StraightEdge } from './StraightEdge.vue' export { default as StraightEdge } from './StraightEdge.vue'
export { default as EdgeAnchor } from './EdgeAnchor.vue' export { default as EdgeAnchor } from './EdgeAnchor.vue'
export { default as EdgeText } from './EdgeText.vue' export { default as EdgeText } from './EdgeText.vue'
export { default as EdgeWrapper } from './Edge.vue' export { default as EdgeWrapper } from './EdgeWrapper.vue'
@@ -1,16 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import { DraggableEventListener, DraggableCore } from '@braks/revue-draggable' import { DraggableEventListener, DraggableCore } from '@braks/revue-draggable'
import { useStore } from '../../composables' import { useStore } from '../../composables'
import { Node, SnapGrid } from '../../types' import { GraphNode, SnapGrid } from '../../types'
import { NodeId } from '../../context' import { NodeId } from '../../context'
interface NodeProps { interface NodeWrapperProps {
node: Node node: GraphNode
selectNodesOnDrag?: boolean selectNodesOnDrag?: boolean
snapGrid?: SnapGrid snapGrid?: SnapGrid
} }
const props = withDefaults(defineProps<NodeProps>(), { const props = withDefaults(defineProps<NodeWrapperProps>(), {
selected: false, selected: false,
selectNodesOnDrag: true, selectNodesOnDrag: true,
}) })
+1 -1
View File
@@ -1,4 +1,4 @@
export { default as DefaultNode } from './DefaultNode.vue' export { default as DefaultNode } from './DefaultNode.vue'
export { default as InputNode } from './InputNode.vue' export { default as InputNode } from './InputNode.vue'
export { default as OutputNode } from './OutputNode.vue' export { default as OutputNode } from './OutputNode.vue'
export { default as NodeWrapper } from './Node.vue' export { default as NodeWrapper } from './NodeWrapper.vue'
@@ -1,19 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Draggable, DraggableEventListener } from '@braks/revue-draggable' import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
import { useStore } from '../../composables' import { useStore } from '../../composables'
import { Node } from '../../types' import { GraphNode } from '../../types'
import { getRectOfNodes, isNode } from '../../utils' import { getRectOfNodes, isGraphNode } from '../../utils'
const store = useStore() const store = useStore()
const selectedNodes = store.selectedElements const selectedNodes: GraphNode[] = store.selectedElements
? store.selectedElements.filter(isNode).map((selectedNode) => { ? store.selectedElements.filter(isGraphNode).map((selectedNode) => {
const matchingNode = store.nodes.find((node) => node.id === selectedNode.id) const matchingNode = store.nodes.find((node) => node.id === selectedNode.id)
return { return {
...matchingNode, ...matchingNode,
position: matchingNode?.__vf?.position, position: matchingNode?.__vf?.position,
} as Node } as GraphNode
}) })
: [] : []
+7
View File
@@ -0,0 +1,7 @@
export * from './Nodes'
export * from './Edges'
export * from './ConnectionLine/ConnectionLine.vue'
export * from './Handle/Handle.vue'
export * from './Loading/LoadingIndicator.vue'
export * from './NodesSelection/NodesSelection.vue'
export * from './UserSelection/UserSelection.vue'
+39 -56
View File
@@ -1,61 +1,44 @@
import { import { EmitFunc, FlowHooks, FlowEvents, FlowStore } from '~/types'
Connection,
Edge,
Elements,
EmitFunc,
FlowHooks,
FlowTransform,
Node,
OnConnectStartParams,
FlowInstance,
FlowEvents,
FlowStore,
} from '~/types'
// flow event hooks // flow event hooks
export const createHooks = (): FlowHooks => { export const createHooks = (): FlowHooks => ({
return { elementClick: createEventHook(),
elementClick: createEventHook<{ event: MouseEvent; element: Node | Edge }>(), elementsRemove: createEventHook(),
elementsRemove: createEventHook<Elements>(), nodeDoubleClick: createEventHook(),
nodeDoubleClick: createEventHook<{ event: MouseEvent; node: Node }>(), nodeClick: createEventHook(),
nodeClick: createEventHook<{ event: MouseEvent; node: Node }>(), nodeMouseEnter: createEventHook(),
nodeMouseEnter: createEventHook<{ event: MouseEvent; node: Node }>(), nodeMouseMove: createEventHook(),
nodeMouseMove: createEventHook<{ event: MouseEvent; node: Node }>(), nodeMouseLeave: createEventHook(),
nodeMouseLeave: createEventHook<{ event: MouseEvent; node: Node }>(), nodeContextMenu: createEventHook(),
nodeContextMenu: createEventHook<{ event: MouseEvent; node: Node }>(), nodeDragStart: createEventHook(),
nodeDragStart: createEventHook<{ event: MouseEvent; node: Node }>(), nodeDrag: createEventHook(),
nodeDrag: createEventHook<{ event: MouseEvent; node: Node }>(), nodeDragStop: createEventHook(),
nodeDragStop: createEventHook<{ event: MouseEvent; node: Node }>(), connect: createEventHook(),
connect: createEventHook<Connection>(), connectStart: createEventHook(),
connectStart: createEventHook<{ connectStop: createEventHook(),
event: MouseEvent connectEnd: createEventHook(),
params: OnConnectStartParams load: createEventHook(),
}>(), move: createEventHook(),
connectStop: createEventHook<MouseEvent>(), moveStart: createEventHook(),
connectEnd: createEventHook<MouseEvent>(), moveEnd: createEventHook(),
load: createEventHook<FlowInstance>(), selectionChange: createEventHook(),
move: createEventHook<FlowTransform | undefined>(), selectionDragStart: createEventHook(),
moveStart: createEventHook<FlowTransform | undefined>(), selectionDrag: createEventHook(),
moveEnd: createEventHook<FlowTransform | undefined>(), selectionDragStop: createEventHook(),
selectionChange: createEventHook<Elements | null>(), selectionContextMenu: createEventHook(),
selectionDragStart: createEventHook<{ event: MouseEvent; nodes: Node[] }>(), paneScroll: createEventHook(),
selectionDrag: createEventHook<{ event: MouseEvent; nodes: Node[] }>(), paneClick: createEventHook(),
selectionDragStop: createEventHook<{ event: MouseEvent; nodes: Node[] }>(), paneContextMenu: createEventHook(),
selectionContextMenu: createEventHook<{ event: MouseEvent; nodes: Node[] }>(), edgeUpdate: createEventHook(),
paneScroll: createEventHook<WheelEvent | undefined>(), edgeContextMenu: createEventHook(),
paneClick: createEventHook<MouseEvent>(), edgeMouseEnter: createEventHook(),
paneContextMenu: createEventHook<MouseEvent>(), edgeMouseMove: createEventHook(),
edgeUpdate: createEventHook<{ edge: Edge; connection: Connection }>(), edgeMouseLeave: createEventHook(),
edgeContextMenu: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeDoubleClick: createEventHook(),
edgeMouseEnter: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeClick: createEventHook(),
edgeMouseMove: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeUpdateStart: createEventHook(),
edgeMouseLeave: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeUpdateEnd: createEventHook(),
edgeDoubleClick: createEventHook<{ event: MouseEvent; edge: Edge }>(), })
edgeClick: createEventHook<{ event: MouseEvent; edge: Edge }>(),
edgeUpdateStart: createEventHook<{ event: MouseEvent; edge: Edge }>(),
edgeUpdateEnd: createEventHook<MouseEvent>(),
}
}
const bind = (emit: EmitFunc, hooks: FlowHooks) => { const bind = (emit: EmitFunc, hooks: FlowHooks) => {
for (const [key, value] of Object.entries(hooks)) { for (const [key, value] of Object.entries(hooks)) {
+5 -5
View File
@@ -1,7 +1,7 @@
import { FlowExportObject, FlowOptions } from '~/types' import { FlowExportObject, FlowOptions, FlowState } from '~/types'
import { useStateStore, initialState } from '~/store' import { useStateStore } from '~/store'
import { Store } from '~/context' import { Store } from '~/context'
import { onLoadToObject } from '~/utils' import { onLoadToObject, initialState } from '~/utils'
let id = 0 let id = 0
export default (options?: Partial<FlowOptions>, key?: string) => { export default (options?: Partial<FlowOptions>, key?: string) => {
@@ -15,10 +15,10 @@ export default (options?: Partial<FlowOptions>, key?: string) => {
const storageKey = key ?? `vue-flow-${id++}` const storageKey = key ?? `vue-flow-${id++}`
const preloadedState = { const preloadedState = {
...initial, ...initial,
...options, ...(options as FlowState),
} }
if (withStorage) { if (withStorage) {
storedState = useStorage(storageKey, {} as FlowExportObject) storedState = useStorage<FlowExportObject>(storageKey, { elements: [], position: [0, 0], zoom: 0 })
if (storedState.value) { if (storedState.value) {
preloadedState.elements = storedState.value.elements ?? options?.elements ?? [] preloadedState.elements = storedState.value.elements ?? options?.elements ?? []
if (storedState.value.position && storedState.value.zoom) if (storedState.value.position && storedState.value.zoom)
-1
View File
@@ -1,4 +1,3 @@
// @ts-nocheck
import { zoomIdentity } from 'd3-zoom' import { zoomIdentity } from 'd3-zoom'
import useStore from './useStore' import useStore from './useStore'
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils' import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils'
+1 -1
View File
@@ -2,7 +2,7 @@
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { ConnectionLineType } from '../../types' import { ConnectionLineType } from '../../types'
import { useStore } from '../../composables' import { useStore } from '../../composables'
import Edge from '../../components/Edges/Edge.vue' import Edge from '../../components/Edges/EdgeWrapper.vue'
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue' import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
import MarkerDefinitions from './MarkerDefinitions.vue' import MarkerDefinitions from './MarkerDefinitions.vue'
+5 -2
View File
@@ -1,9 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useStore } from '../../composables' import { useStore } from '../../composables'
import Node from '../../components/Nodes/Node.vue' import Node from '../../components/Nodes/NodeWrapper.vue'
import { SnapGrid } from '../../types'
interface NodeRendererProps { interface NodeRendererProps {
selectNodesOnDrag?: boolean selectNodesOnDrag?: boolean
snapToGrid?: boolean
snapGrid?: SnapGrid
} }
const props = withDefaults(defineProps<NodeRendererProps>(), { const props = withDefaults(defineProps<NodeRendererProps>(), {
@@ -13,7 +16,7 @@ const props = withDefaults(defineProps<NodeRendererProps>(), {
const store = useStore() const store = useStore()
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`) const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
const snapGrid = computed(() => (store.snapToGrid ? store.snapGrid : undefined)) const snapGrid = computed(() => (props.snapToGrid || store.snapToGrid ? props.snapGrid ?? store.snapGrid : undefined))
</script> </script>
<template> <template>
<div class="vue-flow__nodes" :style="{ transform }"> <div class="vue-flow__nodes" :style="{ transform }">
@@ -3,7 +3,7 @@ import { ElementId, FlowElement, KeyCode } from '../../types'
import { useStore, useKeyPress } from '../../composables' import { useStore, useKeyPress } from '../../composables'
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue' import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
import UserSelection from '../../components/UserSelection/UserSelection.vue' import UserSelection from '../../components/UserSelection/UserSelection.vue'
import { getConnectedEdges, isNode } from '../../utils' import { getConnectedEdges, isGraphNode } from '../../utils'
interface SelectionPaneProps { interface SelectionPaneProps {
selectionKeyCode?: KeyCode selectionKeyCode?: KeyCode
@@ -33,7 +33,7 @@ const selectionKeyPresed = ref(false)
onMounted(() => { onMounted(() => {
useKeyPress(props.deleteKeyCode, (keyPressed) => { useKeyPress(props.deleteKeyCode, (keyPressed) => {
if (keyPressed && store.selectedElements) { if (keyPressed && store.selectedElements) {
const selectedNodes = store.selectedElements.filter(isNode) const selectedNodes = store.selectedElements.filter(isGraphNode)
const connectedEdges = getConnectedEdges(selectedNodes, store.edges) const connectedEdges = getConnectedEdges(selectedNodes, store.edges)
const elementsToRemove = [...store.selectedElements, ...connectedEdges].reduce( const elementsToRemove = [...store.selectedElements, ...connectedEdges].reduce(
(res, item) => res.set(item.id, item), (res, item) => res.set(item.id, item),
-1
View File
@@ -181,7 +181,6 @@ const transitionName = computed(() => {
<Suspense> <Suspense>
<template #default> <template #default>
<ZoomPane <ZoomPane
v-show="store.isReady"
key="zoom-pane" key="zoom-pane"
:selection-key-code="store.selectionKeyCode" :selection-key-code="store.selectionKeyCode"
:zoom-activation-key-code="store.zoomActivationKeyCode" :zoom-activation-key-code="store.zoomActivationKeyCode"
+1 -2
View File
@@ -20,11 +20,10 @@ export {
isInputDOMNode, isInputDOMNode,
graphPosToZoomedPos, graphPosToZoomedPos,
} from './utils/graph' } from './utils/graph'
export { defaultEdgeTypes, defaultNodeTypes } from './utils/store'
export { default as useZoomPanHelper } from './composables/useZoomPanHelper' export { default as useZoomPanHelper } from './composables/useZoomPanHelper'
export { default as useVueFlow } from './composables/useVueFlow' export { default as useVueFlow } from './composables/useVueFlow'
export { default as useHandle } from './composables/useHandle' export { default as useHandle } from './composables/useHandle'
export { default as useKeyPress } from './composables/useKeyPress'
export { defaultEdgeTypes, defaultNodeTypes } from './store'
export * from './additional-components' export * from './additional-components'
export * from './types' export * from './types'
-1
View File
@@ -1,2 +1 @@
export { default as useStateStore } from './stateStore' export { default as useStateStore } from './stateStore'
export { defaultEdgeTypes, defaultNodeTypes, initialState } from './utils'
+22 -19
View File
@@ -1,9 +1,20 @@
import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia' import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia'
import microDiff from 'microdiff' import microDiff from 'microdiff'
import { parseElements, defaultNodeTypes, defaultEdgeTypes, deepUnref, NextElements } from './utils' import { FlowState, Node, FlowActions, Elements, FlowGetters, Edge, GraphNode, NextElements } from '~/types'
import { FlowState, Node, FlowActions, Elements, FlowGetters, Edge } from '~/types' import {
import { clampPosition, getDimensions, getConnectedEdges, getNodesInside, getRectOfNodes, isNode } from '~/utils' clampPosition,
import { getHandleBounds } from '~/components/Nodes/utils' getDimensions,
getConnectedEdges,
getNodesInside,
getRectOfNodes,
isNode,
parseElements,
defaultNodeTypes,
defaultEdgeTypes,
deepUnref,
getHandleBounds,
isGraphNode,
} from '~/utils'
import parseElementsWorker from '~/workers/parseElements' import parseElementsWorker from '~/workers/parseElements'
const pinia = createPinia() const pinia = createPinia()
@@ -81,7 +92,7 @@ export default function flowStore(
} else next = await parseElements(elements, this.nodes, this.edges, this.nodeExtent) } else next = await parseElements(elements, this.nodes, this.edges, this.nodeExtent)
} }
if (next) { if (next) {
this.elements = elements ?? [] this.elements = [...next.nextNodes, ...next.nextEdges]
this.nodes = next?.nextNodes ?? [] this.nodes = next?.nextNodes ?? []
this.edges = next?.nextEdges ?? [] this.edges = next?.nextEdges ?? []
} }
@@ -102,7 +113,6 @@ export default function flowStore(
this.nodes.splice(i, 1, { this.nodes.splice(i, 1, {
...node, ...node,
__vf: { __vf: {
position: { x: 0, y: 0 },
...node.__vf, ...node.__vf,
...dimensions, ...dimensions,
handleBounds, handleBounds,
@@ -125,21 +135,16 @@ export default function flowStore(
this.nodes.splice(i, 1, { this.nodes.splice(i, 1, {
...node, ...node,
__vf: { __vf: {
width: 0,
height: 0,
...node.__vf, ...node.__vf,
position: pos, position: pos,
}, },
}) })
}, },
updateNodePosDiff({ id, diff, isDragging }) { updateNodePosDiff({ id, diff, isDragging }) {
const update = (node: Node, i: number) => { const update = (node: GraphNode, i: number) => {
const updatedNode: Node = { const updatedNode: GraphNode = {
...node, ...node,
__vf: { __vf: {
width: 0,
height: 0,
position: { x: 0, y: 0 },
...node.__vf, ...node.__vf,
isDragging, isDragging,
}, },
@@ -161,8 +166,8 @@ export default function flowStore(
if (!id) { if (!id) {
const selectedNodes = this.nodes.filter((x) => this.selectedElements?.find((sNode) => sNode?.id === x.id)) const selectedNodes = this.nodes.filter((x) => this.selectedElements?.find((sNode) => sNode?.id === x.id))
selectedNodes.forEach((node) => { selectedNodes.forEach((node) => {
const i = this.nodes.map((x) => x.id).indexOf((node as Node).id) const i = this.nodes.map((x) => x.id).indexOf(node.id)
update(node as Node, i) update(node, i)
}) })
} else { } else {
const i = this.nodes.map((x) => x.id).indexOf(id) const i = this.nodes.map((x) => x.id).indexOf(id)
@@ -202,7 +207,7 @@ export default function flowStore(
this.selectedElements = nextSelectedElements this.selectedElements = nextSelectedElements
}, },
unsetUserSelection() { unsetUserSelection() {
const selectedNodes = this.selectedElements?.filter((node) => node && isNode(node) && node.__vf) as Node[] const selectedNodes = this.selectedElements?.filter((node) => node && isGraphNode(node) && node.__vf) as GraphNode[]
this.selectionActive = false this.selectionActive = false
this.userSelectionRect.draw = false this.userSelectionRect.draw = false
@@ -243,8 +248,6 @@ export default function flowStore(
return { return {
...node, ...node,
__vf: { __vf: {
height: 0,
width: 0,
...node.__vf, ...node.__vf,
position: node.__vf?.position ? clampPosition(node.__vf.position, nodeExtent) : { x: 0, y: 0 }, position: node.__vf?.position ? clampPosition(node.__vf.position, nodeExtent) : { x: 0, y: 0 },
}, },
@@ -272,7 +275,7 @@ export default function flowStore(
}, },
async addElements(elements: Elements) { async addElements(elements: Elements) {
const { nextNodes, nextEdges } = await parseElements(elements, this.nodes, this.edges, this.nodeExtent) const { nextNodes, nextEdges } = await parseElements(elements, this.nodes, this.edges, this.nodeExtent)
this.elements = [...this.elements, ...elements] this.elements = [...this.elements, ...nextNodes, ...nextEdges]
this.nodes = [...this.nodes, ...nextNodes] this.nodes = [...this.nodes, ...nextNodes]
this.edges = [...this.edges, ...nextEdges] this.edges = [...this.edges, ...nextEdges]
}, },
-26
View File
@@ -1,26 +0,0 @@
import { Dimensions, Elements, XYPosition } from './types'
import { NodeDiffUpdate, NodeDimensionUpdate, NodeExtent, NodePosUpdate, TranslateExtent } from './node'
import { InitD3ZoomPayload } from './panel'
import { SetConnectionId } from './connection'
export interface FlowActions {
setElements: (elements: Elements) => void
updateNodeDimensions: (update: NodeDimensionUpdate) => void
updateNodePos: (payload: NodePosUpdate) => void
updateNodePosDiff: (payload: NodeDiffUpdate) => void
setUserSelection: (mousePos: XYPosition) => void
updateUserSelection: (mousePos: XYPosition) => void
unsetUserSelection: () => void
addSelectedElements: (elements: Elements) => void
initD3Zoom: (payload: InitD3ZoomPayload) => void
setMinZoom: (zoom: number) => void
setMaxZoom: (zoom: number) => void
setTranslateExtent: (translateExtent: TranslateExtent) => void
setNodeExtent: (nodeExtent: NodeExtent) => void
resetSelectedElements: () => void
unsetNodesSelection: () => void
updateSize: (size: Dimensions) => void
setConnectionNodeId: (payload: SetConnectionId) => void
setInteractive: (isInteractive: boolean) => void
addElements: (elements: Elements) => void
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { ElementId, Position } from './types' import { ElementId, Position } from './flow'
import { HandleType } from './handle' import { HandleType } from './handle'
export enum ConnectionLineType { export enum ConnectionLineType {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Component, CSSProperties, DefineComponent } from 'vue' import { Component, CSSProperties, DefineComponent } from 'vue'
import { ArrowHeadType, ElementId, Position } from './types' import { ArrowHeadType, ElementId, Position } from './flow'
export interface Edge<T = any> { export interface Edge<T = any> {
id: ElementId id: ElementId
+12 -9
View File
@@ -1,15 +1,18 @@
import { CSSProperties } from 'vue' import { CSSProperties } from 'vue'
import { Edge, EdgeTypes } from './edge' import { Edge, EdgeTypes } from './edge'
import { Node, NodeExtent, NodeTypes, TranslateExtent } from './node' import { NodeExtent, GraphNode, NodeTypes, TranslateExtent, Node } from './node'
import { ConnectionLineType, ConnectionMode } from './connection' import { ConnectionLineType, ConnectionMode } from './connection'
import { KeyCode, PanOnScrollMode } from './panel' import { KeyCode, PanOnScrollMode } from './zoom'
import { FlowStore } from './store' import { FlowStore } from './store'
export type ElementId = string export type ElementId = string
export type FlowElement<T = any> = GraphNode<T> | Edge<T>
export type FlowElement<T = any> = Node<T> | Edge<T> export type FlowElements<T = any> = FlowElement<T>[]
export type Elements<T = any> = (Node<T> | Edge<T>)[]
export type Elements<T = any> = FlowElement<T>[] export type NextElements = {
nextNodes: GraphNode[]
nextEdges: Edge[]
}
export type Transform = [number, number, number] export type Transform = [number, number, number]
@@ -63,7 +66,7 @@ export type FitViewParams = {
} }
export type FlowExportObject<T = any> = { export type FlowExportObject<T = any> = {
elements: Elements<T> elements: FlowElements<T>
position: [number, number] position: [number, number]
zoom: number zoom: number
} }
@@ -98,14 +101,13 @@ export type FlowInstance<T = any> = {
zoomTo: (zoomLevel: number) => void zoomTo: (zoomLevel: number) => void
fitView: FitViewFunc fitView: FitViewFunc
project: ProjectFunc project: ProjectFunc
getElements: () => Elements<T> getElements: () => FlowElements<T>
setTransform: (transform: FlowTransform) => void setTransform: (transform: FlowTransform) => void
toObject: ToObjectFunc<T> toObject: ToObjectFunc<T>
} }
export interface FlowOptions { export interface FlowOptions {
id?: string id?: string
store?: FlowStore
elements?: Elements elements?: Elements
nodeTypes?: NodeTypes nodeTypes?: NodeTypes
edgeTypes?: EdgeTypes edgeTypes?: EdgeTypes
@@ -142,4 +144,5 @@ export interface FlowOptions {
storageKey?: string storageKey?: string
loading?: Loading loading?: Loading
worker?: boolean worker?: boolean
store?: FlowStore
} }
+2 -2
View File
@@ -1,5 +1,5 @@
import { Dimensions, ElementId, Position, XYPosition } from './types' import { Dimensions, ElementId, Position, XYPosition } from './flow'
import { Connection } from '~/types/connection' import { Connection } from './connection'
export type HandleType = 'source' | 'target' export type HandleType = 'source' | 'target'
+3 -3
View File
@@ -1,5 +1,5 @@
import { EventHook } from '@vueuse/core' import { EventHook } from '@vueuse/core'
import { Elements, FlowTransform, FlowInstance } from './types' import { FlowTransform, FlowInstance, FlowElements } from './flow'
import { Edge } from './edge' import { Edge } from './edge'
import { Node } from './node' import { Node } from './node'
import { Connection, OnConnectStartParams } from './connection' import { Connection, OnConnectStartParams } from './connection'
@@ -8,7 +8,7 @@ export type FlowHook<T = any> = EventHook<T>
export interface FlowEvents { export interface FlowEvents {
elementClick: { event: MouseEvent; element: Node | Edge } elementClick: { event: MouseEvent; element: Node | Edge }
elementsRemove: Elements elementsRemove: FlowElements
nodeDoubleClick: { event: MouseEvent; node: Node } nodeDoubleClick: { event: MouseEvent; node: Node }
nodeClick: { event: MouseEvent; node: Node } nodeClick: { event: MouseEvent; node: Node }
nodeMouseEnter: { event: MouseEvent; node: Node } nodeMouseEnter: { event: MouseEvent; node: Node }
@@ -29,7 +29,7 @@ export interface FlowEvents {
move: FlowTransform | undefined move: FlowTransform | undefined
moveStart: FlowTransform | undefined moveStart: FlowTransform | undefined
moveEnd: FlowTransform | undefined moveEnd: FlowTransform | undefined
selectionChange: Elements | null selectionChange: FlowElements | null
selectionDragStart: { event: MouseEvent; nodes: Node[] } selectionDragStart: { event: MouseEvent; nodes: Node[] }
selectionDrag: { event: MouseEvent; nodes: Node[] } selectionDrag: { event: MouseEvent; nodes: Node[] }
selectionDragStop: { event: MouseEvent; nodes: Node[] } selectionDragStop: { event: MouseEvent; nodes: Node[] }
+2 -3
View File
@@ -1,9 +1,8 @@
export * from './types' export * from './flow'
export * from './actions'
export * from './connection' export * from './connection'
export * from './edge' export * from './edge'
export * from './handle' export * from './handle'
export * from './node' export * from './node'
export * from './panel' export * from './zoom'
export * from './store' export * from './store'
export * from './hooks' export * from './hooks'
+17 -12
View File
@@ -1,21 +1,22 @@
import { Component, CSSProperties, DefineComponent } from 'vue' import { Component, CSSProperties, DefineComponent } from 'vue'
import { XYPosition, ElementId, Position } from './types' import { XYPosition, ElementId, Position } from './flow'
import { HandleElement } from '~/types/handle' import { HandleElement } from './handle'
interface VFInternals {
position: XYPosition
isDragging?: boolean
width: number
height: number
handleBounds: {
source: HandleElement[] | null
target: HandleElement[] | null
}
}
export interface Node<T = any> { export interface Node<T = any> {
id: ElementId id: ElementId
position: XYPosition position: XYPosition
type?: string type?: string
__vf?: {
position: XYPosition
isDragging?: boolean
width: number
height: number
handleBounds?: {
source?: HandleElement[] | null
target?: HandleElement[] | null
}
}
class?: string class?: string
style?: CSSProperties style?: CSSProperties
data?: T data?: T
@@ -27,6 +28,10 @@ export interface Node<T = any> {
connectable?: boolean connectable?: boolean
} }
export interface GraphNode<T = any> extends Node<T> {
__vf: VFInternals
}
export type NodePosUpdate = { export type NodePosUpdate = {
id: ElementId id: ElementId
pos: XYPosition pos: XYPosition
+38 -9
View File
@@ -3,6 +3,7 @@ import {
Dimensions, Dimensions,
ElementId, ElementId,
Elements, Elements,
FlowElements,
FlowInstance, FlowInstance,
FlowOptions, FlowOptions,
Rect, Rect,
@@ -10,20 +11,26 @@ import {
SnapGrid, SnapGrid,
Transform, Transform,
XYPosition, XYPosition,
} from './types' } from './flow'
import { HandleType } from './handle' import { HandleType } from './handle'
import { ConnectionMode, OnConnectEndFunc, OnConnectFunc, OnConnectStartFunc, OnConnectStopFunc } from './connection' import {
ConnectionMode,
OnConnectEndFunc,
OnConnectFunc,
OnConnectStartFunc,
OnConnectStopFunc,
SetConnectionId,
} from './connection'
import { Edge, EdgeComponent } from './edge' import { Edge, EdgeComponent } from './edge'
import { Node, NodeComponent, NodeExtent, TranslateExtent } from './node' import { NodeComponent, NodeDiffUpdate, NodeDimensionUpdate, NodeExtent, GraphNode, NodePosUpdate, TranslateExtent } from './node'
import { FlowActions } from './actions' import { D3Selection, D3Zoom, D3ZoomHandler, InitD3ZoomPayload } from './zoom'
import { D3Selection, D3Zoom, D3ZoomHandler } from './panel'
import { FlowHooks } from './hooks' import { FlowHooks } from './hooks'
export interface FlowState extends FlowOptions { export interface FlowState extends FlowOptions {
elements: Elements elements: FlowElements
nodes: Node[] nodes: GraphNode[]
edges: Edge[] edges: Edge[]
selectedElements?: Elements selectedElements?: FlowElements
selectedNodesBbox: Rect selectedNodesBbox: Rect
d3Zoom?: D3Zoom d3Zoom?: D3Zoom
@@ -66,10 +73,32 @@ export interface FlowState extends FlowOptions {
vueFlowVersion: string vueFlowVersion: string
} }
export interface FlowActions {
setElements: (elements: Elements) => Promise<void>
updateNodeDimensions: (update: NodeDimensionUpdate) => void
updateNodePos: (payload: NodePosUpdate) => void
updateNodePosDiff: (payload: NodeDiffUpdate) => void
setUserSelection: (mousePos: XYPosition) => void
updateUserSelection: (mousePos: XYPosition) => void
unsetUserSelection: () => void
addSelectedElements: (elements: FlowElements) => void
initD3Zoom: (payload: InitD3ZoomPayload) => void
setMinZoom: (zoom: number) => void
setMaxZoom: (zoom: number) => void
setTranslateExtent: (translateExtent: TranslateExtent) => void
setNodeExtent: (nodeExtent: NodeExtent) => void
resetSelectedElements: () => void
unsetNodesSelection: () => void
updateSize: (size: Dimensions) => void
setConnectionNodeId: (payload: SetConnectionId) => void
setInteractive: (isInteractive: boolean) => void
addElements: (elements: Elements) => Promise<void>
}
export interface FlowGetters { export interface FlowGetters {
getEdgeTypes: () => Record<string, EdgeComponent> getEdgeTypes: () => Record<string, EdgeComponent>
getNodeTypes: () => Record<string, NodeComponent> getNodeTypes: () => Record<string, NodeComponent>
getNodes: () => Node[] getNodes: () => GraphNode[]
getEdges: () => Edge[] getEdges: () => Edge[]
} }
+1 -1
View File
@@ -1,5 +1,5 @@
import { Selection, ZoomBehavior } from 'd3' import { Selection, ZoomBehavior } from 'd3'
import { FitViewFunc, FlowTransform, Rect, XYPosition } from './types' import { FitViewFunc, FlowTransform, Rect, XYPosition } from './flow'
export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown> export type D3Zoom = ZoomBehavior<HTMLDivElement, unknown>
export type D3Selection = Selection<HTMLDivElement, unknown, any, any> export type D3Selection = Selection<HTMLDivElement, unknown, any, any>
@@ -1,11 +1,11 @@
import { Edge, EdgePositions, ElementId, HandleElement, Node, Position, Transform, XYPosition } from '../../types' import { rectToBox } from './graph'
import { rectToBox } from '~/utils' import { Edge, EdgePositions, ElementId, GraphNode, HandleElement, Position, Transform, XYPosition } from '~/types'
export function getHandlePosition(position: Position, node: Node, handle: any | null = null): XYPosition { export function getHandlePosition(position: Position, node: GraphNode, handle: any | null = null): XYPosition {
const x = (handle?.x || 0) + node.__vf?.position?.x const x = (handle?.x || 0) + node.__vf.position.x
const y = (handle?.y || 0) + node.__vf?.position?.y const y = (handle?.y || 0) + node.__vf.position.y
const width = handle?.width || node.__vf?.width const width = handle?.width || node.__vf.width
const height = handle?.height || node.__vf?.height const height = handle?.height || node.__vf.height
switch (position) { switch (position) {
case Position.Top: case Position.Top:
@@ -47,10 +47,10 @@ export function getHandle(bounds: HandleElement[], handleId: ElementId | null):
} }
export const getEdgePositions = ( export const getEdgePositions = (
sourceNode: Node, sourceNode: GraphNode,
sourceHandle: HandleElement | unknown, sourceHandle: HandleElement | unknown,
sourcePosition: Position, sourcePosition: Position,
targetNode: Node, targetNode: GraphNode,
targetHandle: HandleElement | unknown, targetHandle: HandleElement | unknown,
targetPosition: Position, targetPosition: Position,
): EdgePositions => { ): EdgePositions => {
@@ -104,11 +104,11 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform }
} }
type SourceTargetNode = { type SourceTargetNode = {
sourceNode: Node sourceNode: GraphNode
targetNode: Node targetNode: GraphNode
} }
export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => { export const getSourceTargetNodes = (edge: Edge, nodes: GraphNode[]): SourceTargetNode => {
return nodes.reduce( return nodes.reduce(
(res, node) => { (res, node) => {
if (node.id === edge.source) { if (node.id === edge.source) {
+42 -45
View File
@@ -12,12 +12,20 @@ import {
NodeExtent, NodeExtent,
Dimensions, Dimensions,
FlowStore, FlowStore,
GraphNode,
FlowElements,
FlowElement,
} from '~/types' } from '~/types'
import { useWindow } from '~/composables' import { useWindow } from '~/composables'
const isHTMLElement = (el: EventTarget): el is HTMLElement => ('nodeName' || 'hasAttribute') in el
export const isInputDOMNode = (e: KeyboardEvent | MouseEvent): boolean => { export const isInputDOMNode = (e: KeyboardEvent | MouseEvent): boolean => {
const target = e.target as HTMLElement const target = e.target
return ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName) || target.hasAttribute('contentEditable') if (target && isHTMLElement(target)) {
return ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName) || target.hasAttribute('contentEditable')
}
return false
} }
export const getDimensions = (node: HTMLElement): Dimensions => ({ export const getDimensions = (node: HTMLElement): Dimensions => ({
@@ -40,40 +48,30 @@ export const getHostForElement = (element: HTMLElement): Document => {
else return window.document else return window.document
} }
export const isEdge = (element: Node | Connection | Edge): element is Edge => export const isEdge = (element: Node | FlowElement | Connection): element is Edge =>
'id' in element && 'source' in element && 'target' in element 'id' in element && 'source' in element && 'target' in element
export const isNode = (element: Node | Connection | Edge): element is Node => export const isNode = (element: Node | FlowElement | Connection): element is Node =>
'id' in element && !('source' in element) && !('target' in element) 'id' in element && !('source' in element) && !('target' in element)
export const getOutgoers = (node: Node, elements: Elements): Node[] => { export const isGraphNode = (element: Node | FlowElement | Connection): element is GraphNode =>
if (!isNode(node)) { isNode(element) && '__vf' in element
return []
}
const outgoerIds = elements.filter((e) => isEdge(e) && e.source === node.id).map((e) => (e as Edge).target) const getConnectedElements = (node: GraphNode, elements: Elements, dir: 'source' | 'target') => {
return elements.filter((e) => outgoerIds.includes(e.id)) as Node[] if (!isNode(node)) return []
const ids = elements.filter((e) => isEdge(e) && e.source === node.id).map((e) => isEdge(e) && e[dir])
return elements.filter((e) => ids.includes(e.id))
} }
export const getOutgoers = (node: GraphNode, elements: Elements) => getConnectedElements(node, elements, 'target')
export const getIncomers = (node: Node, elements: Elements): Node[] => { export const getIncomers = (node: GraphNode, elements: Elements) => getConnectedElements(node, elements, 'source')
if (!isNode(node)) {
return []
}
const incomersIds = elements.filter((e) => isEdge(e) && e.target === node.id).map((e) => (e as Edge).source) export const removeElements = (elementsToRemove: Elements, elements: Elements) => {
return elements.filter((e) => incomersIds.includes(e.id)) as Node[]
}
export const removeElements = (elementsToRemove: Elements, elements: Elements): Elements => {
const nodeIdsToRemove = elementsToRemove.map((n) => n.id) const nodeIdsToRemove = elementsToRemove.map((n) => n.id)
return elements.filter((element) => { return elements.filter((element) => {
const edgeElement = element as Edge const { target, source } = isEdge(element) ? element : { target: '', source: '' }
return !( return !(nodeIdsToRemove.includes(element.id) || nodeIdsToRemove.includes(target) || nodeIdsToRemove.includes(source))
nodeIdsToRemove.includes(element.id) ||
nodeIdsToRemove.includes(edgeElement.target) ||
nodeIdsToRemove.includes(edgeElement.source)
)
}) })
} }
@@ -91,13 +89,13 @@ const connectionExists = (edge: Edge, elements: Elements) => {
) )
} }
export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elements => { export const addEdge = (edgeParams: Edge | Connection, elements: Elements) => {
if (!edgeParams.source || !edgeParams.target) { if (!edgeParams.source || !edgeParams.target) {
console.log("Can't create edge. An edge needs a source and a target.") console.log("Can't create edge. An edge needs a source and a target.")
return elements return elements
} }
let edge: Edge let edge
if (isEdge(edgeParams)) { if (isEdge(edgeParams)) {
edge = { ...edgeParams } edge = { ...edgeParams }
} else { } else {
@@ -114,13 +112,13 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem
return elements.concat(edge) return elements.concat(edge)
} }
export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements): Elements => { export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: Elements) => {
if (!newConnection.source || !newConnection.target) { if (!newConnection.source || !newConnection.target) {
console.warn("Can't create new edge. An edge needs a source and a target.") console.warn("Can't create new edge. An edge needs a source and a target.")
return elements return elements
} }
const foundEdge = elements.find((e) => isEdge(e) && e.id === oldEdge.id) as Edge const foundEdge = elements.find((e) => isEdge(e) && e.id === oldEdge.id)
if (!foundEdge) { if (!foundEdge) {
console.warn(`The old edge with id=${oldEdge.id} does not exist.`) console.warn(`The old edge with id=${oldEdge.id} does not exist.`)
@@ -128,14 +126,14 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: E
} }
// Remove old edge and create the new edge with parameters of old edge. // Remove old edge and create the new edge with parameters of old edge.
const edge = { const edge: Edge = {
...oldEdge, ...oldEdge,
id: getEdgeId(newConnection), id: getEdgeId(newConnection),
source: newConnection.source, source: newConnection.source,
target: newConnection.target, target: newConnection.target,
sourceHandle: newConnection.sourceHandle, sourceHandle: newConnection.sourceHandle,
targetHandle: newConnection.targetHandle, targetHandle: newConnection.targetHandle,
} as Edge }
return elements.filter((e) => e.id !== oldEdge.id).concat(edge) return elements.filter((e) => e.id !== oldEdge.id).concat(edge)
} }
@@ -145,7 +143,7 @@ export const pointToRendererPoint = (
[tx, ty, tScale]: Transform, [tx, ty, tScale]: Transform,
snapToGrid: boolean, snapToGrid: boolean,
[snapX, snapY]: [number, number], [snapX, snapY]: [number, number],
): XYPosition => { ) => {
const position: XYPosition = { const position: XYPosition = {
x: (x - tx) / tScale, x: (x - tx) / tScale,
y: (y - ty) / tScale, y: (y - ty) / tScale,
@@ -161,12 +159,10 @@ export const pointToRendererPoint = (
return position return position
} }
export const onLoadProject = export const onLoadProject = (currentStore: FlowStore) => (position: XYPosition) =>
(currentStore: FlowStore) => pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid)
(position: XYPosition): XYPosition =>
pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid)
export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => ({ export const parseNode = (node: Node, nodeExtent: NodeExtent): GraphNode => ({
...node, ...node,
id: node.id.toString(), id: node.id.toString(),
type: node.type || 'default', type: node.type || 'default',
@@ -174,7 +170,10 @@ export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => ({
position: clampPosition(node.position, nodeExtent), position: clampPosition(node.position, nodeExtent),
width: 0, width: 0,
height: 0, height: 0,
handleBounds: {}, handleBounds: {
source: null,
target: null,
},
isDragging: false, isDragging: false,
}, },
}) })
@@ -210,10 +209,9 @@ export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({
height: y2 - y, height: y2 - y,
}) })
export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect => export const getBoundsofRects = (rect1: Rect, rect2: Rect) => boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)))
boxToRect(getBoundsOfBoxes(rectToBox(rect1), rectToBox(rect2)))
export const getRectOfNodes = (nodes: Node[]): Rect => { export const getRectOfNodes = (nodes: GraphNode[]) => {
const box = nodes.reduce( const box = nodes.reduce(
(currBox, { __vf: { position = { x: 0, y: 0 }, width = 0, height = 0 } = {} }) => (currBox, { __vf: { position = { x: 0, y: 0 }, width = 0, height = 0 } = {} }) =>
getBoundsOfBoxes( getBoundsOfBoxes(
@@ -235,7 +233,7 @@ export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Tran
y: y * tScale + ty, y: y * tScale + ty,
}) })
export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false): Node[] => { export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false) => {
const rBox = rectToBox({ const rBox = rectToBox({
x: (rect.x - tx) / tScale, x: (rect.x - tx) / tScale,
y: (rect.y - ty) / tScale, y: (rect.y - ty) / tScale,
@@ -266,13 +264,12 @@ export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Tran
}) })
} }
export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { export const getConnectedEdges = (nodes: GraphNode[], edges: Edge[]) => {
const nodeIds = nodes.map((node) => node.id) const nodeIds = nodes.map((node) => node.id)
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target)) return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target))
} }
export const onLoadGetElements = (currentStore: FlowStore) => (): Elements => currentStore.elements export const onLoadGetElements = (currentStore: FlowStore) => (): FlowElements => currentStore.elements
export const onLoadToObject = (currentStore: FlowStore) => (): FlowExportObject => { export const onLoadToObject = (currentStore: FlowStore) => (): FlowExportObject => {
// we have to stringify/parse so objects containing refs (like nodes and edges) can potentially be saved in a storage // we have to stringify/parse so objects containing refs (like nodes and edges) can potentially be saved in a storage
+3
View File
@@ -1 +1,4 @@
export * from './edge'
export * from './graph' export * from './graph'
export * from './node'
export * from './store'
@@ -1,5 +1,5 @@
import { HandleElement, Position } from '~/types' import { HandleElement, Position } from '~/types'
import { getDimensions } from '~/utils' import { getDimensions } from '~/utils/graph'
export const getHandleBoundsByHandleType = ( export const getHandleBoundsByHandleType = (
selector: string, selector: string,
@@ -19,7 +19,7 @@ export const getHandleBoundsByHandleType = (
const bounds = handle.getBoundingClientRect() const bounds = handle.getBoundingClientRect()
const dimensions = getDimensions(handle) const dimensions = getDimensions(handle)
const handleId = handle.getAttribute('data-handleid') const handleId = handle.getAttribute('data-handleid')
const handlePosition = handle.getAttribute('data-handlepos') as unknown as Position const handlePosition = handle.getAttribute('data-handlepos') as Position
return { return {
id: handleId, id: handleId,
+19 -14
View File
@@ -1,15 +1,20 @@
import { Component } from 'vue' import { Component } from 'vue'
import { ConnectionMode, Edge, EdgeProps, Elements, FlowState, Node, NodeExtent, NodeProps, PanOnScrollMode } from '~/types' import { isEdge, isNode, parseEdge, parseNode } from './graph'
import { isEdge, isNode, parseEdge, parseNode } from '~/utils' import {
import { DefaultNode, InputNode, OutputNode } from '~/components/Nodes' ConnectionMode,
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components/Edges' Edge,
EdgeProps,
Elements,
FlowState,
NextElements,
NodeExtent,
GraphNode,
NodeProps,
PanOnScrollMode,
} from '~/types'
import { DefaultNode, InputNode, OutputNode, BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components'
import { createHooks } from '~/composables' import { createHooks } from '~/composables'
export type NextElements = {
nextNodes: Node[]
nextEdges: Edge[]
}
export const defaultNodeTypes: Record<string, Component<NodeProps>> = { export const defaultNodeTypes: Record<string, Component<NodeProps>> = {
input: InputNode, input: InputNode,
default: DefaultNode, default: DefaultNode,
@@ -94,7 +99,7 @@ export const initialState = (): FlowState => ({
vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : '-', vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : '-',
}) })
export const parseElements = async (elements: Elements, nodes: Node[], edges: Edge[], nodeExtent: NodeExtent) => export const parseElements = async (elements: Elements, nodes: GraphNode[], edges: Edge[], nodeExtent: NodeExtent) =>
new Promise<NextElements>((resolve) => { new Promise<NextElements>((resolve) => {
const nextElements: NextElements = { const nextElements: NextElements = {
nextNodes: [], nextNodes: [],
@@ -105,20 +110,20 @@ export const parseElements = async (elements: Elements, nodes: Node[], edges: Ed
const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)] const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)]
if (storeNode) { if (storeNode) {
const updatedNode: Node = { const updatedNode: GraphNode = {
...storeNode, ...storeNode,
...element, ...element,
} }
if (!updatedNode.__vf) updatedNode.__vf = {} if (!updatedNode.__vf) updatedNode.__vf = {} as any
if (storeNode.position.x !== element.position.x || storeNode.position.y !== element.position.y) { if (storeNode.position.x !== element.position.x || storeNode.position.y !== element.position.y) {
updatedNode.__vf.position = element.position updatedNode.__vf!.position = element.position
} }
if (typeof element.type !== 'undefined' && element.type !== storeNode.type) { if (typeof element.type !== 'undefined' && element.type !== storeNode.type) {
// we reset the elements dimensions here in order to force a re-calculation of the bounds. // we reset the elements dimensions here in order to force a re-calculation of the bounds.
// When the type of a node changes it is possible that the number or positions of handles changes too. // When the type of a node changes it is possible that the number or positions of handles changes too.
updatedNode.__vf.width = undefined updatedNode.__vf!.width = 0
} }
nextElements.nextNodes.push(updatedNode) nextElements.nextNodes.push(updatedNode)
+13 -13
View File
@@ -1,7 +1,7 @@
import { Connection, Edge, Elements, Node, NodeExtent, XYPosition } from '~/types' import { Connection, Edge, Elements, Node, NodeExtent, GraphNode, XYPosition, NextElements, FlowElement } from '~/types'
export default () => export default () =>
useWebWorkerFn((elements: Elements, nodes: Node[], edges: Edge[], nodeExtent: NodeExtent) => { useWebWorkerFn((elements: Elements, nodes: GraphNode[], edges: Edge[], nodeExtent: NodeExtent): NextElements => {
const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max) const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max)
const clampPosition = (position: XYPosition, extent: NodeExtent): XYPosition => ({ const clampPosition = (position: XYPosition, extent: NodeExtent): XYPosition => ({
@@ -9,14 +9,14 @@ export default () =>
y: clamp(position.y, extent[0][1], extent[1][1]), y: clamp(position.y, extent[0][1], extent[1][1]),
}) })
const parseNode = (node: Node, nodeExtent: NodeExtent): Node => const parseNode = (node: Node, nodeExtent: NodeExtent): GraphNode =>
Object.assign(node, { Object.assign(node as GraphNode, {
id: node.id.toString(), id: node.id.toString(),
type: node.type || 'default', type: node.type || 'default',
__vf: { __vf: {
position: clampPosition(node.position, nodeExtent), position: clampPosition(node.position, nodeExtent),
width: undefined, width: 0,
height: undefined, height: 0,
handleBounds: {}, handleBounds: {},
isDragging: false, isDragging: false,
}, },
@@ -32,13 +32,13 @@ export default () =>
type: edge.type || 'default', type: edge.type || 'default',
}) })
const isEdge = (element: Node | Connection | Edge): element is Edge => const isEdge = (element: Node | FlowElement | Connection): element is Edge =>
'id' in element && 'source' in element && 'target' in element 'id' in element && 'source' in element && 'target' in element
const isNode = (element: Node | Connection | Edge): element is Node => const isNode = (element: Node | FlowElement | Connection): element is Node =>
'id' in element && !('source' in element) && !('target' in element) 'id' in element && !('source' in element) && !('target' in element)
const nextElements: any = { const nextElements: NextElements = {
nextNodes: [], nextNodes: [],
nextEdges: [], nextEdges: [],
} }
@@ -48,17 +48,17 @@ export default () =>
const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)] const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)]
if (storeNode) { if (storeNode) {
const updatedNode: Node = Object.assign(storeNode, element) const updatedNode = Object.assign(storeNode, element)
if (!updatedNode.__vf) updatedNode.__vf = {} if (!updatedNode.__vf) updatedNode.__vf = {} as any
if (storeNode.position.x !== element.position.x || storeNode.position.y !== element.position.y) { if (storeNode.position.x !== element.position.x || storeNode.position.y !== element.position.y) {
updatedNode.__vf.position = element.position updatedNode.__vf!.position = element.position
} }
if (typeof element.type !== 'undefined' && element.type !== storeNode.type) { if (typeof element.type !== 'undefined' && element.type !== storeNode.type) {
// we reset the elements dimensions here in order to force a re-calculation of the bounds. // we reset the elements dimensions here in order to force a re-calculation of the bounds.
// When the type of a node changes it is possible that the number or positions of handles changes too. // When the type of a node changes it is possible that the number or positions of handles changes too.
updatedNode.__vf.width = undefined updatedNode.__vf!.width = 0
} }
nextElements.nextNodes.push(updatedNode) nextElements.nextNodes.push(updatedNode)