Files
vue-flow/src/store/configure-store.ts
2021-07-09 21:54:05 +02:00

313 lines
9.9 KiB
TypeScript

import { createPinia, defineStore, StoreDefinition } from 'pinia';
import isEqual from 'fast-deep-equal';
import { Edge, Node, NodeDiffUpdate, ReactFlowState, RevueFlowActionsTree, XYPosition } from '../types';
import {
getConnectedEdges,
getNodesInside,
getRectOfNodes,
isEdge,
isNode,
parseEdge,
parseNode
} from '../utils/graph';
import { clampPosition, getDimensions } from '../utils';
import { getHandleBounds } from '../components/Nodes/utils';
import { provide } from 'vue-demi';
type NextElements = {
nextNodes: Node[];
nextEdges: Edge[];
};
export default function configureStore(
preloadedState: ReactFlowState
): StoreDefinition<'revue-flow', ReactFlowState, any, RevueFlowActionsTree> {
provide(Symbol('pinia'), createPinia());
return defineStore({
id: 'revue-flow',
state: () => preloadedState,
getters: {},
actions: {
setOnConnect(onConnect) {
this.onConnect = onConnect;
},
setOnConnectStart(onConnectStart) {
this.onConnectStart = onConnectStart;
},
setOnConnectEnd(onConnectEnd) {
this.onConnectEnd = onConnectEnd;
},
setOnConnectStop(onConnectStop) {
this.onConnectStop = onConnectStop;
},
setElements(elements) {
const propElements = elements;
const nextElements: NextElements = {
nextNodes: [],
nextEdges: []
};
const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => {
if (isNode(propElement)) {
const storeNode = this.nodes.find((node) => node.id === propElement.id);
if (storeNode) {
const updatedNode: Node = {
...storeNode,
...propElement
};
if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) {
updatedNode.__rf.position = propElement.position;
}
if (typeof propElement.type !== 'undefined' && propElement.type !== storeNode.type) {
// 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.
updatedNode.__rf.width = null;
}
res.nextNodes.push(updatedNode);
} else {
res.nextNodes.push(parseNode(propElement, this.nodeExtent));
}
} else if (isEdge(propElement)) {
const storeEdge = this.edges.find((se) => se.id === propElement.id);
if (storeEdge) {
res.nextEdges.push({
...storeEdge,
...propElement
});
} else {
res.nextEdges.push(parseEdge(propElement));
}
}
return res;
}, nextElements);
this.nodes = nextNodes;
this.edges = nextEdges;
},
updateNodeDimensions(updates) {
this.nodes = this.nodes.map((node) => {
const update = updates.find((u) => u.id === node.id);
if (update) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate =
dimensions.width &&
dimensions.height &&
(node.__rf.width !== dimensions.width || node.__rf.height !== dimensions.height || update.forceUpdate);
if (doUpdate) {
const handleBounds = getHandleBounds(update.nodeElement, this.transform[2]);
return {
...node,
__rf: {
...node.__rf,
...dimensions,
handleBounds
}
};
}
}
return node;
});
},
updateNodePos(payload) {
const { id, pos } = payload;
let position: XYPosition = pos;
if (this.snapToGrid) {
const [gridSizeX, gridSizeY] = this.snapGrid;
position = {
x: gridSizeX * Math.round(pos.x / gridSizeX),
y: gridSizeY * Math.round(pos.y / gridSizeY)
};
}
this.nodes = this.nodes.map((node) => {
if (node.id === id) {
return {
...node,
__rf: {
...node.__rf,
position
}
};
}
return node;
});
},
updateNodePosDiff(payload: NodeDiffUpdate) {
const { id, diff, isDragging } = payload;
this.nodes = this.nodes.map((node) => {
if (id === node.id || this.selectedElements?.find((sNode) => sNode.id === node.id)) {
const updatedNode = {
...node,
__rf: {
...node.__rf,
isDragging
}
};
if (diff) {
updatedNode.__rf.position = {
x: node.__rf.position.x + diff.x,
y: node.__rf.position.y + diff.y
};
}
return updatedNode;
}
return node;
});
},
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: ReactFlowState['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];
const selectedElementsChanged = !isEqual(nextSelectedElements, this.selectedElements);
const selectedElementsUpdate = selectedElementsChanged
? {
selectedElements: nextSelectedElements.length > 0 ? nextSelectedElements : null
}
: {};
this.userSelectionRect = nextUserSelectRect;
this.selectedElements = selectedElementsUpdate.selectedElements as any;
},
unsetUserSelection() {
const selectedNodes = this.selectedElements?.filter((node) => isNode(node) && node.__rf) as Node[];
this.selectionActive = false;
this.userSelectionRect.draw = false;
if (!selectedNodes || selectedNodes.length === 0) {
this.selectedElements = null;
this.nodesSelectionActive = false;
} else {
this.selectedNodesBbox = getRectOfNodes(selectedNodes);
this.nodesSelectionActive = true;
}
},
addSelectedElements(elements) {
const selectedElementsArr = Array.isArray(elements) ? elements : [elements];
const selectedElementsUpdated = !isEqual(selectedElementsArr, this.selectedElements);
this.selectedElements = selectedElementsUpdated ? selectedElementsArr : this.selectedElements;
},
initD3Zoom(payload) {
const { d3Zoom, d3Selection, d3ZoomHandler, transform } = payload;
this.d3Zoom = d3Zoom;
this.d3Selection = d3Selection;
this.d3ZoomHandler = d3ZoomHandler;
this.transform = transform;
},
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: clampPosition(node.__rf.position, nodeExtent)
}
};
});
},
resetSelectedElements() {
this.selectedElements = null;
},
unsetNodesSelection() {
this.nodesSelectionActive = false;
},
updateTransform(transform) {
this.transform = transform;
},
updateSize(size) {
this.height = size.height;
this.width = size.width;
},
setConnectionPosition(connectionPosition) {
this.connectionPosition = connectionPosition;
},
setConnectionNodeId(payload) {
this.connectionNodeId = payload.connectionNodeId;
this.connectionHandleId = payload.connectionHandleId;
this.connectionHandleType = payload.connectionHandleType;
},
setSnapToGrid(snapToGrid) {
this.snapToGrid = snapToGrid;
},
setSnapGrid(snapGrid) {
this.snapGrid = snapGrid;
},
setInteractive(isInteractive) {
this.nodesDraggable = isInteractive;
this.nodesConnectable = isInteractive;
this.elementsSelectable = isInteractive;
},
setNodesDraggable(nodesDraggable) {
this.nodesDraggable = nodesDraggable;
},
setNodesConnectable(nodesConnectable) {
this.nodesConnectable = nodesConnectable;
},
setElementsSelectable(elementsSelectable) {
this.elementsSelectable = elementsSelectable;
},
setMultiSelectionActive(multiSelectionActive) {
this.multiSelectionActive = multiSelectionActive;
},
setConnectionMode(connectionMode) {
this.connectionMode = connectionMode;
}
}
});
}