Files
vue-flow/src/store/flowStore.ts
T
Braks 70b077a1f5 fix!: props and composable not merging together properly
* Add getter for node/edge types
* change nodeTypes prop type to only Record<string, NodeType> - same for edges
* Add rgb example
* remove v-model for elements

Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
2021-11-19 01:43:07 +01:00

265 lines
8.8 KiB
TypeScript

import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia'
import diff from 'microdiff'
import { parseElements } from './utils'
import { FlowState, Node, FlowActions, Elements, NodeType, EdgeType, FlowGetters, Edge } from '~/types'
import { clampPosition, getDimensions, getConnectedEdges, getNodesInside, getRectOfNodes, isNode } from '~/utils'
import { getHandleBounds } from '~/components/Nodes/utils'
import { DefaultNode, InputNode, OutputNode } from '~/components/Nodes'
import { BezierEdge, SmoothStepEdge, StepEdge, StraightEdge } from '~/components/Edges'
const defaultNodeTypes: Record<string, NodeType> = {
input: InputNode as NodeType,
default: DefaultNode as NodeType,
output: OutputNode as NodeType,
}
const defaultEdgeTypes: Record<string, EdgeType> = {
default: BezierEdge as EdgeType,
straight: StraightEdge as EdgeType,
step: StepEdge as EdgeType,
smoothstep: SmoothStepEdge as EdgeType,
}
const pinia = createPinia()
export default function flowStore(
id: string,
preloadedState: FlowState,
): StoreDefinition<string, FlowState, FlowGetters, FlowActions> {
setActivePinia(pinia)
const { nextEdges, nextNodes } = parseElements(preloadedState.elements, [], [], preloadedState.nodeExtent)
preloadedState.nodes = nextNodes
preloadedState.edges = nextEdges
return defineStore({
id: id ?? 'vue-flow',
state: () => ({
...preloadedState,
}),
getters: {
getEdgeTypes() {
let edgeTypes = defaultEdgeTypes
if (Array.isArray(this.edgeTypes)) this.edgeTypes.forEach((type) => (edgeTypes[type] = true))
else edgeTypes = { ...edgeTypes, ...this.edgeTypes }
return edgeTypes
},
getNodeTypes() {
let nodeTypes = defaultNodeTypes
if (Array.isArray(this.nodeTypes)) this.nodeTypes.forEach((type) => (nodeTypes[type] = true))
else nodeTypes = { ...nodeTypes, ...this.nodeTypes }
return nodeTypes
},
getNodes() {
const n: Node[] = this.onlyRenderVisibleElements
? this.nodes &&
getNodesInside(
this.nodes,
{
x: 0,
y: 0,
width: this.dimensions.width,
height: this.dimensions.height,
},
this.transform,
true,
)
: this.nodes
return n.filter((node) => !node.isHidden)
},
getEdges(): Edge[] {
return this.edges.filter((edge) => !edge.isHidden)
},
},
actions: {
setElements(elements) {
const { nextNodes, nextEdges } = parseElements(elements, this.nodes, this.edges, this.nodeExtent)
this.nodes = nextNodes
this.edges = nextEdges
},
updateNodeDimensions({ id, nodeElement, forceUpdate }) {
const i = this.nodes.map((x) => x.id).indexOf(id)
const node = this.nodes[i]
const dimensions = getDimensions(nodeElement)
if (!node.__rf) node.__rf = {}
const doUpdate =
dimensions.width &&
dimensions.height &&
(node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || forceUpdate)
if (doUpdate) {
const handleBounds = getHandleBounds(nodeElement, this.transform[2])
this.nodes.splice(i, 1, {
...node,
__rf: {
...node.__rf,
...dimensions,
handleBounds,
},
})
}
},
updateNodePos({ id, pos }) {
const i = this.nodes.map((x) => x.id).indexOf(id)
const node = this.nodes[i]
if (this.snapToGrid) {
const [gridSizeX, gridSizeY] = this.snapGrid
pos = {
x: gridSizeX * Math.round(pos.x / gridSizeX),
y: gridSizeY * Math.round(pos.y / gridSizeY),
}
}
this.nodes.splice(i, 1, {
...node,
__rf: {
...node.__rf,
position: pos,
},
})
},
updateNodePosDiff({ id, diff, isDragging }) {
const update = (node: Node, i: number) => {
const updatedNode = {
...node,
__rf: {
...node.__rf,
isDragging,
},
}
if (diff) {
updatedNode.__rf.position = {
x: (node.__rf?.position?.x as number) + diff.x,
y: (node.__rf?.position?.y as number) + diff.y,
}
}
this.nodes.splice(i, 1, {
...node,
...updatedNode,
})
}
if (!id) {
const selectedNodes = this.nodes.filter((x) => this.selectedElements?.find((sNode) => sNode?.id === x.id))
selectedNodes.forEach((node) => {
const i = this.nodes.map((x) => x.id).indexOf((node as Node).id)
update(node as Node, i)
})
} else {
const i = this.nodes.map((x) => x.id).indexOf(id)
const node = this.nodes[i]
update(node, i)
}
},
setUserSelection(mousePos) {
this.selectionActive = true
this.userSelectionRect = {
width: 0,
height: 0,
startX: mousePos.x,
startY: mousePos.y,
x: mousePos.x,
y: mousePos.y,
draw: true,
}
},
updateUserSelection(mousePos) {
const startX = this.userSelectionRect.startX || 0
const startY = this.userSelectionRect.startY || 0
const nextUserSelectRect: FlowState['userSelectionRect'] = {
...this.userSelectionRect,
x: mousePos.x < startX ? mousePos.x : this.userSelectionRect.x,
y: mousePos.y < startY ? mousePos.y : this.userSelectionRect.y,
width: Math.abs(mousePos.x - startX),
height: Math.abs(mousePos.y - startY),
}
const selectedNodes = getNodesInside(this.nodes, nextUserSelectRect, this.transform)
const selectedEdges = getConnectedEdges(selectedNodes, this.edges)
const nextSelectedElements = [...selectedNodes, ...selectedEdges]
this.userSelectionRect = nextUserSelectRect
this.selectedElements = nextSelectedElements
},
unsetUserSelection() {
const selectedNodes = this.selectedElements?.filter((node) => node && isNode(node) && node.__rf) as Node[]
this.selectionActive = false
this.userSelectionRect.draw = false
if (!selectedNodes || selectedNodes.length === 0) {
this.selectedElements = undefined
this.nodesSelectionActive = false
} else {
this.selectedNodesBbox = getRectOfNodes(selectedNodes)
this.nodesSelectionActive = true
}
},
addSelectedElements(elements) {
const selectedElementsArr = Array.isArray(elements) ? elements : [elements]
const selectedElementsUpdated = diff(selectedElementsArr, this.selectedElements ?? []).length
this.selectedElements = selectedElementsUpdated ? selectedElementsArr : this.selectedElements
},
initD3Zoom({ d3ZoomHandler, d3Zoom, d3Selection }) {
this.d3Zoom = d3Zoom
this.d3Selection = d3Selection
this.d3ZoomHandler = d3ZoomHandler
},
setMinZoom(minZoom) {
this.d3Zoom?.scaleExtent([minZoom, this.maxZoom])
this.minZoom = minZoom
},
setMaxZoom(maxZoom) {
this.d3Zoom?.scaleExtent([this.minZoom, maxZoom])
this.maxZoom = maxZoom
},
setTranslateExtent(translateExtent) {
this.d3Zoom?.translateExtent(translateExtent)
this.translateExtent = translateExtent
},
setNodeExtent(nodeExtent) {
this.nodeExtent = nodeExtent
this.nodes = this.nodes.map((node) => {
return {
...node,
__rf: {
...node.__rf,
position: node.__rf?.position && clampPosition(node.__rf.position, nodeExtent),
},
}
})
},
resetSelectedElements() {
this.selectedElements = undefined
},
unsetNodesSelection() {
this.nodesSelectionActive = false
},
updateSize(size) {
this.dimensions = size
},
setConnectionNodeId({ connectionHandleId, connectionHandleType, connectionNodeId }) {
this.connectionNodeId = connectionNodeId
this.connectionHandleId = connectionHandleId
this.connectionHandleType = connectionHandleType
},
setInteractive(isInteractive) {
this.nodesDraggable = isInteractive
this.nodesConnectable = isInteractive
this.elementsSelectable = isInteractive
},
addElements(elements: Elements) {
const { nextNodes, nextEdges } = parseElements(elements, this.nodes, this.edges, this.nodeExtent)
this.nodes = [...this.nodes, ...nextNodes]
this.edges = [...this.edges, ...nextEdges]
},
},
})
}