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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 },
|
||||
|
||||
|
||||
Reference in New Issue
Block a user