refactor(types)!: Remove elements and replace with nodes and edges

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
Braks
2021-12-20 19:29:52 +01:00
parent f28e10b05a
commit b80341467b
17 changed files with 153 additions and 255 deletions
+4 -33
View File
@@ -15,54 +15,31 @@ import {
const onNodeDragStop = (e) => console.log('drag stop', e)
const onElementClick = (e) => console.log('click', e)
const elements = ref<Elements>([
const nodes = ref<Elements>([
{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } },
{ id: '2', data: { label: 'Node 2' }, position: { x: 100, y: 100 } },
{ id: '3', data: { label: 'Node 3' }, position: { x: 400, y: 100 } },
{ id: '4', data: { label: 'Node 4' }, position: { x: 400, y: 200 } },
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
])
const edges = ref<Elements>([])
const vfInstance = ref<FlowInstance>()
const onElementsRemove = (elementsToRemove: Elements) => removeElements(elementsToRemove, elements.value)
const onConnect = (params: Edge | Connection) => addEdge(params, elements.value)
const onLoad = (flowInstance: FlowInstance) => {
flowInstance.fitView({ padding: 0.1 })
vfInstance.value = flowInstance
}
const updatePos = () => {
elements.value = elements.value.map((el) => {
if (isNode(el)) {
el.position = {
x: Math.random() * 400,
y: Math.random() * 400,
}
}
return el
})
}
const logToObject = () => console.log(vfInstance.value?.toObject())
const resetTransform = () => vfInstance.value?.setTransform({ x: 0, y: 0, zoom: 1 })
const toggleclasss = () => {
elements.value = elements.value.map((el) => {
if (isNode(el)) el.class = el.class === 'light' ? 'dark' : 'light'
return el
})
}
</script>
<template>
<VueFlow
v-model="elements"
:nodes="nodes"
:edges="edges"
class="vue-flow-basic-example"
:default-zoom="1.5"
:min-zoom="0.2"
:max-zoom="4"
:zoom-on-scroll="false"
@elements-remove="onElementsRemove"
@connect="onConnect"
@node-drag-stop="onNodeDragStop"
@node-click="onElementClick"
@load="onLoad"
@@ -70,11 +47,5 @@ const toggleclasss = () => {
<Background />
<MiniMap />
<Controls />
<div style="position: absolute; right: 10px; top: 10px; z-index: 4">
<button style="margin-right: 5px" @click="resetTransform">reset transform</button>
<button style="margin-right: 5px" @click="updatePos">change pos</button>
<button style="margin-right: 5px" @click="toggleclasss">toggle classs</button>
<button @click="logToObject">toObject</button>
</div>
</VueFlow>
</template>
+1 -1
View File
@@ -21,7 +21,7 @@ const instance = ref<FlowInstance>()
const onLoad = (flowInstance: FlowInstance) => {
flowInstance.fitView()
instance.value = flowInstance
console.log(flowInstance.getElements())
console.log(flowInstance.getNodes())
}
const initialElements: Elements = getElements(50, 20)
@@ -93,10 +93,8 @@ export default {
>
<template v-for="node of store.getNodes" :key="`mini-map-node-${node.id}`">
<slot
:x="node.computedPosition.x"
:y="node.computedPosition.y"
:width="node.width"
:height="node.height"
:position="node.computedPosition"
:dimensions="node.dimensions"
:style="node.style"
:class="nodeClassNameFunc(node)"
:color="nodeColorFunc(node)"
@@ -106,10 +104,8 @@ export default {
:shape-rendering="shapeRendering"
>
<MiniMapNode
:x="node.computedPosition.x"
:y="node.computedPosition.y"
:width="node.width"
:height="node.height"
:position="node.computedPosition"
:dimensions="node.dimensions"
:style="node.style"
:class="nodeClassNameFunc(node)"
:color="nodeColorFunc(node)"
@@ -1,17 +1,6 @@
<script lang="ts" setup>
import { CSSProperties } from 'vue'
interface MiniMapNodeProps {
x?: number
y?: number
width?: number
height?: number
borderRadius?: number
color?: string
shapeRendering?: CSSProperties['shapeRendering']
strokeColor?: string
strokeWidth?: number
}
import type { MiniMapNodeProps } from '../../types/components'
const props = withDefaults(defineProps<MiniMapNodeProps>(), {
shapeRendering: 'geometricPrecision',
@@ -29,12 +18,12 @@ export default {
<template>
<rect
class="vue-flow__minimap-node"
:x="props.x"
:y="props.y"
:x="props.position.x"
:y="props.position.y"
:rx="props.borderRadius"
:ry="props.borderRadius"
:width="props.width"
:height="props.height"
:width="props.dimensions.width"
:height="props.dimensions.height"
:fill="fill"
:stroke="props.strokeColor"
:stroke-width="props.strokeWidth"
@@ -12,8 +12,8 @@ const sourceHandle =
store.connectionHandleId && store.connectionHandleType
? props.sourceNode.handleBounds[store.connectionHandleType]?.find((d: HandleElement) => d.id === store.connectionHandleId)
: store.connectionHandleType && props.sourceNode.handleBounds[store.connectionHandleType ?? 'source']?.[0]
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.width as number) / 2
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.height
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : props.sourceNode.dimensions.width / 2
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.dimensions.height
const sourceX = props.sourceNode.position.x + sourceHandleX
const sourceY = props.sourceNode.position.y + sourceHandleY
+25 -43
View File
@@ -1,7 +1,7 @@
<script lang="ts" setup>
import { DraggableEventListener, DraggableCore } from '@braks/revue-draggable'
import { useStore } from '../../composables'
import { Draggable, GraphNode, NodeDimensionUpdate, SnapGrid, XYPosition } from '../../types'
import { Draggable, GraphNode, SnapGrid, XYPosition } from '../../types'
import { NodeId } from '../../context'
import { calculateXYZPosition, clampPosition, getDimensions, getHandleBounds, isParentSelected } from '../../utils'
@@ -17,7 +17,7 @@ interface NodeWrapperProps {
}
const props = defineProps<NodeWrapperProps>()
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:node'])
const node = useVModel(props, 'node', emit)
const store = useStore()
provide(NodeId, props.node.id)
@@ -65,7 +65,7 @@ const onDrag: DraggableEventListener = ({ event, data: { deltaX, deltaY } }) =>
node.value.dragging = true
store.hooks.nodeDrag.trigger({ event, node: node.value })
}
const onDragStop: DraggableEventListener = ({ event, data: { deltaX, deltaY } }) => {
const onDragStop: DraggableEventListener = ({ event }) => {
// onDragStop also gets called when user just clicks on a node.
// Because of that we set dragging to true inside the onDrag handler and handle the click here
if (!node.value.dragging) {
@@ -80,7 +80,7 @@ const onDragStop: DraggableEventListener = ({ event, data: { deltaX, deltaY } })
store.hooks.nodeDragStop.trigger({ event, node: node.value })
}
const updateNodePosition = (node: GraphNode, { x, y }: XYPosition) => {
const updateNodePosition = (node: GraphNode, { x, y }: XYPosition = { x: 0, y: 0 }) => {
const position = {
x: node.position.x + x,
y: node.position.y + y,
@@ -88,13 +88,13 @@ const updateNodePosition = (node: GraphNode, { x, y }: XYPosition) => {
}
let extent = store.nodeExtent
if (node.extent === 'parent' && node.parentNode && node.width && node.height) {
if (node.extent === 'parent' && node.parentNode && node.dimensions.width && node.dimensions.height) {
const parent = node.parentNode
extent =
parent.width && parent.height
parent.dimensions.width && parent.dimensions.height
? [
[0, 0],
[parent.width - node.width, parent.height - node.height],
[parent.dimensions.width - node.dimensions.width, parent.dimensions.height - node.dimensions.height],
]
: extent
}
@@ -104,43 +104,25 @@ const updateNodePosition = (node: GraphNode, { x, y }: XYPosition) => {
} else node.computedPosition = { ...node.position, z: position.z }
}
const updateNodeDimensions = ({ nodeElement, forceUpdate }: NodeDimensionUpdate) => {
const dimensions = getDimensions(nodeElement)
const doUpdate =
dimensions.width &&
dimensions.height &&
(node.value.width !== dimensions.width || node.value.height !== dimensions.height || forceUpdate)
if (doUpdate) {
const handleBounds = getHandleBounds(nodeElement, store.transform[2], store.id)
node.value = {
...node.value,
...dimensions,
handleBounds,
}
}
}
onRenderTriggered(() => console.log(node.value.id))
updateNodePosition(node.value)
onMounted(() => {
nextTick(() => {
updateNodeDimensions({
id: props.node.id,
nodeElement: nodeElement.value,
forceUpdate: true,
})
const dimensions = ref(getDimensions(nodeElement.value))
useResizeObserver(nodeElement, () => (dimensions.value = getDimensions(nodeElement.value)))
watch(
dimensions,
({ width: w, height: h }) => {
if (w > 0 && h > 0) {
const handleBounds = getHandleBounds(nodeElement.value, store.transform[2], store.id)
useResizeObserver(nodeElement, (entries) =>
entries.forEach((entry) => {
updateNodeDimensions({
id: entry.target.getAttribute('data-id') as string,
nodeElement: entry.target as HTMLDivElement,
})
}),
)
})
node.value.dimensions = {
width: w,
height: h,
}
node.value.handleBounds = handleBounds
}
},
{ immediate: true },
)
})
</script>
<script lang="ts">
@@ -177,7 +159,7 @@ export default {
zIndex: node.dragging || selected ? 1000 : node.computedPosition.z,
transform: `translate(${node.computedPosition.x}px,${node.computedPosition.y}px)`,
pointerEvents: props.selectable || props.draggable ? 'all' : 'none',
opacity: node.width !== null && node.height !== null ? 1 : 0,
opacity: node.dimensions.width !== 0 && node.dimensions.height !== 0 ? 1 : 0,
...node.style,
}"
:data-id="node.id"
+4 -11
View File
@@ -1,17 +1,9 @@
<script lang="ts" setup>
import { useStore, useWindow, useZoomPanHelper } from '../../composables'
import { Elements, FlowInstance } from '../../types'
import { onLoadGetElements, onLoadProject, onLoadToObject } from '../../utils'
import { FlowInstance } from '../../types'
import { onLoadGetEdges, onLoadGetNodes, onLoadProject, onLoadToObject } from '../../utils'
interface RendererProps {
elements: Elements
}
const props = defineProps<RendererProps>()
const store = useStore()
// if there are preloaded elements we overwrite the current elements with the stored ones and reset the stored elements (so we can properly parse them)
await store.setElements(props.elements, false)
store.isReady = true
nextTick(async () => {
@@ -28,7 +20,8 @@ nextTick(async () => {
zoomTo,
setTransform,
project: onLoadProject(store),
getElements: onLoadGetElements(store),
getNodes: onLoadGetNodes(store),
getEdges: onLoadGetEdges(store),
toObject: onLoadToObject(store),
}
store.hooks.load.trigger(instance)
+3 -27
View File
@@ -9,7 +9,6 @@ import { createHooks, initFlow } from '../../composables'
import type { FlowProps } from '../../types/flow'
const props = withDefaults(defineProps<FlowProps>(), {
modelValue: () => [],
snapToGrid: false,
onlyRenderVisibleElements: false,
edgesUpdatable: false,
@@ -24,38 +23,15 @@ const props = withDefaults(defineProps<FlowProps>(), {
panOnScroll: false,
paneMoveable: true,
})
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue'])
const emit = defineEmits([...Object.keys(createHooks()), 'update:nodes', 'update:edges'])
const store = initFlow(emit, props.id)
const elements = useVModel(props, 'modelValue', emit)
const { nodes, edges } = useVModels(props, emit)
store.setState(props)
watch(
() => props,
() => store.setState(props),
{ flush: 'post', deep: true },
)
const { pause: pauseModel, resume: resumeModel } = pausableWatch(
() => props.modelValue.length,
async () => await store.setElements(elements.value),
)
const { pause, resume } = pausableWatch(elements, async (val) => await store.setElements(val), { flush: 'post' })
until(() => store.isReady)
.toBe(true)
.then(() => {
watch(
() => store.elements,
(val) => {
pause()
pauseModel()
elements.value = val
setTimeout(() => {
resume()
resumeModel()
}, 1)
},
{ flush: 'post', deep: true },
)
})
</script>
<script lang="ts">
export default {
@@ -66,7 +42,7 @@ export default {
<div class="vue-flow">
<Suspense timeout="0">
<template #default>
<Renderer :key="`renderer-${store.id}`" :elements="elements">
<Renderer :key="`renderer-${store.id}`">
<ZoomPane :key="`zoom-pane-${store.id}`">
<template #default="zoomPaneProps">
<SelectionPane :key="`selection-pane-${store.id}`">
+44 -16
View File
@@ -1,15 +1,7 @@
import { Elements, FlowActions, FlowGetters, FlowState } from '~/types'
import { getConnectedEdges, getNodesInside, getRectOfNodes, isGraphNode, parseElements, processElements } from '~/utils'
import { CoordinateExtent, Edge, FlowActions, FlowGetters, FlowState, GraphNode, Node } from '~/types'
import { getConnectedEdges, getNodesInside, getRectOfNodes, isNode, parseEdge, parseNode } from '~/utils'
export default (state: FlowState, getters: FlowGetters): FlowActions => {
const setElements: FlowActions['setElements'] = async (elements, force = true) => {
const { nodes, edges } = parseElements(elements, state.elements, state.nodeExtent)
if (force) state.elements = []
await processElements([...nodes, ...edges], (processed) => {
state.elements = [...state.elements, ...processed]
})
state.hooks.elementsProcessed.trigger(state.elements)
}
const setUserSelection: FlowActions['setUserSelection'] = (mousePos) => {
state.selectionActive = true
state.userSelectionRect = {
@@ -26,7 +18,7 @@ export default (state: FlowState, getters: FlowGetters): FlowActions => {
const startX = state.userSelectionRect.startX
const startY = state.userSelectionRect.startY
state.selectedElements?.forEach((el) => isGraphNode(el) && (el.selected = undefined))
state.selectedElements?.forEach((el) => isNode(el) && (el.selected = undefined))
const nextUserSelectRect: FlowState['userSelectionRect'] = {
...state.userSelectionRect,
x: mousePos.x < startX ? mousePos.x : state.userSelectionRect.x,
@@ -38,7 +30,7 @@ export default (state: FlowState, getters: FlowGetters): FlowActions => {
const selectedEdges = getConnectedEdges(selectedNodes, getters.getEdges.value)
const nextSelectedElements = [...selectedNodes, ...selectedEdges]
nextSelectedElements.forEach((el) => isGraphNode(el) && (el.selected = true))
nextSelectedElements.forEach((el) => isNode(el) && (el.selected = true))
state.userSelectionRect = nextUserSelectRect
state.selectedElements = nextSelectedElements
}
@@ -103,10 +95,48 @@ export default (state: FlowState, getters: FlowGetters): FlowActions => {
state.nodesConnectable = isInteractive
state.elementsSelectable = isInteractive
}
const addElements: FlowActions['addElements'] = (elements: Elements) => {
setElements(elements, false)
const setNodes = (nodes: Node[], extent: CoordinateExtent) => {
const parseChildren = (n: Node, arr: GraphNode[] = []) => {
arr.concat(parseNode(n, extent))
if (n.children && n.children.length) {
n.children.forEach((c) => parseChildren(c))
} else {
return arr
}
}
nodes = nodes.flatMap((node) => {
console.log('flatmap')
const parsed = parseNode(node, extent)
const children: GraphNode[] = []
if (node.children && node.children.length) {
node.children.forEach((c) => parseChildren(c, children))
}
return [parsed, ...children]
})
state.nodes = <GraphNode[]>nodes
}
const setEdges = (edges: Edge[]) => {
state.edges = edges.flatMap((edge) => {
const parsed = parseEdge(edge)
const sourceNode = getters.getNode.value(edge.source)!
const targetNode = getters.getNode.value(edge.target)!
if (!sourceNode || typeof sourceNode === 'undefined')
console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
if (!targetNode || typeof targetNode === 'undefined')
console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
return {
...parsed,
sourceNode,
targetNode,
}
})
}
const setState: FlowActions['setState'] = (opts) => {
if (typeof opts.nodes !== 'undefined') setNodes(opts.nodes, opts.nodeExtent ?? state.nodeExtent)
if (typeof opts.edges !== 'undefined') setEdges(opts.edges)
if (typeof opts.loading !== 'undefined') state.loading = opts.loading
if (typeof opts.panOnScroll !== 'undefined') state.panOnScroll = opts.panOnScroll
if (typeof opts.panOnScrollMode !== 'undefined') state.panOnScrollMode = opts.panOnScrollMode
@@ -151,7 +181,6 @@ export default (state: FlowState, getters: FlowGetters): FlowActions => {
}
}
return {
setElements,
setUserSelection,
updateUserSelection,
unsetUserSelection,
@@ -165,7 +194,6 @@ export default (state: FlowState, getters: FlowGetters): FlowActions => {
updateSize,
setConnectionNodeId,
setInteractive,
addElements,
setState,
}
}
+10 -13
View File
@@ -1,5 +1,5 @@
import { FlowGetters, FlowState, GraphEdge, GraphNode, Node } from '~/types'
import { defaultEdgeTypes, defaultNodeTypes, getNodesInside, getSourceTargetNodes, isEdge, isGraphNode } from '~/utils'
import { FlowGetters, FlowState, GraphEdge, GraphNode } from '~/types'
import { defaultEdgeTypes, defaultNodeTypes, getNodesInside, getSourceTargetNodes, isGraphNode } from '~/utils'
export default (state: FlowState): FlowGetters => {
const getEdgeTypes = computed(() => {
@@ -20,15 +20,8 @@ export default (state: FlowState): FlowGetters => {
const getNodes = computed<GraphNode[]>(() => {
if (state.isReady && state.dimensions.width && state.dimensions.height) {
console.log('getter')
const nodes: GraphNode[] = []
const parseNode = (n: Node) => {
nodes.push(<GraphNode>n)
if (n.children && n.children.length) {
n.children.forEach((c) => parseNode(c))
}
}
state.elements.filter((n) => isGraphNode(n) && !n.hidden).forEach((node) => parseNode(<GraphNode>node))
console.log('getNodes')
const nodes = state.nodes.filter((n) => !n.hidden)
return state.onlyRenderVisibleElements
? nodes &&
getNodesInside(
@@ -48,9 +41,8 @@ export default (state: FlowState): FlowGetters => {
})
const getEdges = computed<GraphEdge[]>(() => {
const edges = state.elements.filter((e) => isEdge(e) && !e.hidden) as GraphEdge[]
const edges = state.edges.filter((e) => !e.hidden)
if (state.isReady && state.dimensions.width && state.dimensions.height) {
console.log('getter2')
return (
edges
.map((edge) => {
@@ -72,7 +64,12 @@ export default (state: FlowState): FlowGetters => {
const getSelectedNodes = computed<GraphNode[]>(() => state.selectedElements?.filter(isGraphNode) ?? [])
const getNode: FlowGetters['getNode'] = computed(() => (id: string) => state.nodes.find((node) => node.id === id))
const getEdge: FlowGetters['getEdge'] = computed(() => (id: string) => state.edges.find((edge) => edge.id === id))
return {
getNode,
getEdge,
getEdgeTypes,
getNodeTypes,
getEdges,
+5 -48
View File
@@ -1,9 +1,8 @@
import { Component, CSSProperties, DefineComponent } from 'vue'
import { BackgroundVariant, Dimensions, Elements, FitViewParams, FlowOptions, Position, XYPosition } from './flow'
import { Connection, ConnectionLineType, ConnectionMode } from './connection'
import { GraphNode, Node, NodeProps, CoordinateExtent } from './node'
import { BackgroundVariant, Dimensions, FitViewParams, Position, XYPosition } from './flow'
import { Connection, ConnectionLineType } from './connection'
import { GraphNode, Node, NodeProps } from './node'
import { EdgeProps } from './edge'
import { KeyCode, PanOnScrollMode } from './zoom'
export type DefaultEdgeTypes = { [key in 'default' | 'straight' | 'smoothstep' | 'step']: Component<EdgeProps> }
export type EdgeTypes = (keyof DefaultEdgeTypes | string)[]
@@ -63,10 +62,8 @@ export interface MiniMapProps {
}
export interface MiniMapNodeProps {
x?: number
y?: number
width?: number
height?: number
position: XYPosition
dimensions: Dimensions
borderRadius?: number
color?: string
shapeRendering?: CSSProperties['shapeRendering']
@@ -103,43 +100,3 @@ export interface CustomConnectionLineProps {
sourceNode: GraphNode
sourceHandle: HandleElement
}
export interface FlowProps extends FlowOptions {
id?: string
modelValue?: Elements
nodeTypes?: NodeTypes
edgeTypes?: EdgeTypes
connectionMode?: ConnectionMode
connectionLineType?: ConnectionLineType
connectionLineStyle?: CSSProperties
deleteKeyCode?: KeyCode
selectionKeyCode?: KeyCode
multiSelectionKeyCode?: KeyCode
zoomActivationKeyCode?: KeyCode
preventScrolling?: boolean
snapToGrid?: boolean
snapGrid?: [number, number]
onlyRenderVisibleElements?: boolean
nodesDraggable?: boolean
nodesConnectable?: boolean
elementsSelectable?: boolean
selectNodesOnDrag?: boolean
paneMoveable?: boolean
minZoom?: number
maxZoom?: number
defaultZoom?: number
defaultPosition?: [number, number]
translateExtent?: CoordinateExtent
nodeExtent?: CoordinateExtent
arrowHeadColor?: string
markerEndId?: string
zoomOnScroll?: boolean
zoomOnPinch?: boolean
panOnScroll?: boolean
panOnScrollSpeed?: number
panOnScrollMode?: PanOnScrollMode
zoomOnDoubleClick?: boolean
edgeUpdaterRadius?: number
storageKey?: string
loading?: string
}
+15 -13
View File
@@ -1,6 +1,6 @@
import { CSSProperties } from 'vue'
import { Edge, GraphEdge } from './edge'
import { GraphNode, Node, CoordinateExtent } from './node'
import { GraphEdge, Edge } from './edge'
import { GraphNode, CoordinateExtent, Node } from './node'
import { ConnectionLineType, ConnectionMode } from './connection'
import { KeyCode, PanOnScrollMode } from './zoom'
import { EdgeTypes, NodeTypes } from './components'
@@ -82,8 +82,9 @@ export type FitViewParams = {
nodes?: string[]
}
export type FlowExportObject<T = any> = {
elements: FlowElements<T>
export type FlowExportObject<DataNode = any, DataEdge = DataNode> = {
nodes: GraphNode<DataNode>[]
edges: GraphEdge<DataEdge>[]
position: [number, number]
zoom: number
}
@@ -96,21 +97,24 @@ export type FlowTransform = {
export type FitViewFunc = (fitViewOptions?: FitViewParams) => void
export type ProjectFunc = (position: XYPosition) => XYPosition
export type ToObjectFunc<T = any> = () => FlowExportObject<T>
export type ToObjectFunc<DataNode = any, DataEdge = DataNode> = () => FlowExportObject<DataNode, DataEdge>
export type FlowInstance<T = any> = {
export type FlowInstance<DataNode = any, DataEdge = DataNode> = {
zoomIn: () => void
zoomOut: () => void
zoomTo: (zoomLevel: number) => void
fitView: FitViewFunc
project: ProjectFunc
getElements: () => FlowElements<T>
getNodes: () => GraphNode[]
getEdges: () => GraphEdge[]
setTransform: (transform: FlowTransform) => void
toObject: ToObjectFunc<T>
toObject: ToObjectFunc<DataNode, DataEdge>
}
export interface FlowProps {
modelValue?: Elements
export interface FlowProps<DataNode = any, DataEdge = DataNode> {
nodes: GraphNode<DataNode>[]
edges?: GraphEdge<DataEdge>[]
elements?: Elements
id?: string
nodeTypes?: NodeTypes
edgeTypes?: EdgeTypes
@@ -150,6 +154,4 @@ export interface FlowProps {
loading?: string
}
export interface FlowOptions extends Omit<FlowProps, 'modelValue'> {
elements?: Elements
}
export type FlowOptions = FlowProps
+4 -6
View File
@@ -1,5 +1,5 @@
import { DraggableOptions } from '@braks/revue-draggable'
import { XYPosition, Position, SnapGrid, Element, XYZPosition } from './flow'
import { XYPosition, Position, SnapGrid, Element, XYZPosition, Dimensions } from './flow'
import { HandleElement, ValidConnectionFunc } from './components'
export type CoordinateExtent = [[number, number], [number, number]]
@@ -19,8 +19,7 @@ export interface Node<T = any> extends Element<T> {
isValidSourcePos?: ValidConnectionFunc
extent?: 'parent' | CoordinateExtent
children?: Node[]
width?: number
height?: number
dimensions?: Dimensions
}
export interface GraphNode<T = any> extends Node<T> {
@@ -33,8 +32,7 @@ export interface GraphNode<T = any> extends Node<T> {
computedPosition: XYZPosition
selected?: boolean
dragging?: boolean
width: number
height: number
dimensions: Dimensions
}
export interface NodeProps<T = any> extends GraphNode {
@@ -60,5 +58,5 @@ export interface NodeProps<T = any> extends GraphNode {
export type NodeDimensionUpdate = {
id: string
nodeElement: HTMLDivElement
forceUpdate?: boolean
dimensions: Dimensions
}
+5 -3
View File
@@ -23,7 +23,9 @@ export interface FlowState extends Omit<FlowOptions, 'elements' | 'id'> {
hooks: FlowHooks
instance?: FlowInstance
elements: FlowElements
elements: Elements
nodes: GraphNode[]
edges: GraphEdge[]
nodeTypes: NodeTypes
edgeTypes: EdgeTypes
@@ -83,7 +85,6 @@ export interface FlowState extends Omit<FlowOptions, 'elements' | 'id'> {
}
export interface FlowActions {
setElements: (elements: Elements, init?: boolean) => Promise<void>
setUserSelection: (mousePos: XYPosition) => void
updateUserSelection: (mousePos: XYPosition) => void
unsetUserSelection: () => void
@@ -97,7 +98,6 @@ export interface FlowActions {
updateSize: (size: Dimensions) => void
setConnectionNodeId: (payload: SetConnectionId) => void
setInteractive: (isInteractive: boolean) => void
addElements: (elements: Elements) => void
setState: (state: Partial<FlowOptions>) => void
}
@@ -106,6 +106,8 @@ export interface FlowGetters {
getNodeTypes: ComputedRef<Record<string, NodeComponent>>
getNodes: ComputedRef<GraphNode[]>
getEdges: ComputedRef<GraphEdge[]>
getNode: ComputedRef<(id: string) => GraphNode | undefined>
getEdge: ComputedRef<(id: string) => GraphEdge | undefined>
getSelectedNodes: ComputedRef<GraphNode[]>
}
+2 -2
View File
@@ -4,8 +4,8 @@ import { Edge, EdgePositions, GraphNode, HandleElement, Position, Transform, XYP
export function getHandlePosition(position: Position, node: GraphNode, handle?: HandleElement): XYPosition {
const x = (handle?.x ?? 0) + node.position.x
const y = (handle?.y ?? 0) + node.position.y
const width = handle?.width ?? node.width
const height = handle?.height ?? node.height
const width = handle?.width ?? node.dimensions.width
const height = handle?.height ?? node.dimensions.height
switch (position) {
case Position.Top:
+13 -10
View File
@@ -169,8 +169,10 @@ export const parseNode = (node: Node, nodeExtent: CoordinateExtent): GraphNode =
...node,
id: node.id.toString(),
type: node.type ?? 'default',
width: 0,
height: 0,
dimensions: {
width: 0,
height: 0,
},
handleBounds: {
source: [],
target: [],
@@ -179,6 +181,7 @@ export const parseNode = (node: Node, nodeExtent: CoordinateExtent): GraphNode =
z: typeof node.style?.zIndex === 'string' ? parseInt(node.style?.zIndex) : node.style?.zIndex ?? 0,
...clampPosition(node.position, nodeExtent),
},
isParent: !!(node.children && node.children.length),
dragging: false,
})
@@ -219,13 +222,12 @@ export const getBoundsofRects = (rect1: Rect, rect2: Rect) => boxToRect(getBound
export const getRectOfNodes = (nodes: GraphNode[]) => {
const box = nodes.reduce(
(currBox, { position = { x: 0, y: 0 }, width = 0, height = 0 } = {} as any) =>
(currBox, { position = { x: 0, y: 0 }, dimensions = { width: 0, height: 0 } } = {} as any) =>
getBoundsOfBoxes(
currBox,
rectToBox({
...position,
width,
height,
...dimensions,
}),
),
{ x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity },
@@ -249,13 +251,13 @@ export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]:
return nodes.filter((node) => {
if (!node || node.selectable === false) return false
const { position = { x: 0, y: 0 }, width = 0, height = 0, dragging = false } = node
const nBox = rectToBox({ ...position, width, height } as any)
const { position = { x: 0, y: 0 }, dimensions = { width: 0, height: 0 }, dragging = false } = node
const nBox = rectToBox({ ...position, ...dimensions })
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x))
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y))
const overlappingArea = Math.ceil(xOverlap * yOverlap)
if (width === null || height === null || dragging) {
if (dimensions.width === null || dimensions.height === null || dragging) {
// nodes are initialized with width and height = null
return true
}
@@ -264,7 +266,7 @@ export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]:
return overlappingArea > 0
}
const area = width * height
const area = dimensions.width * dimensions.height
return overlappingArea >= area
})
@@ -275,7 +277,8 @@ export const getConnectedEdges = (nodes: GraphNode[], edges: GraphEdge[]) => {
return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target))
}
export const onLoadGetElements = (currentStore: FlowStore) => (): FlowElements => currentStore.elements
export const onLoadGetNodes = (currentStore: FlowStore) => (): FlowElements => currentStore.nodes
export const onLoadGetEdges = (currentStore: FlowStore) => (): FlowElements => currentStore.edges
export const onLoadToObject = (currentStore: FlowState) => (): FlowExportObject => {
// we have to stringify/parse so objects containing refs (like nodes and edges) can potentially be saved in a storage
+7 -3
View File
@@ -16,16 +16,20 @@ export const defaultEdgeTypes: DefaultEdgeTypes = {
}
export const initialState = (): FlowState => ({
elements: [],
nodes: [],
edges: [],
nodeTypes: [],
edgeTypes: [],
isReady: false,
instance: undefined,
dimensions: {
width: 0,
height: 0,
},
transform: [0, 0, 1],
elements: [],
nodeTypes: [],
edgeTypes: [],
selectedElements: undefined,
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },