refactor(svelte): cleanup store usage

This commit is contained in:
moklick
2023-02-28 12:43:29 +01:00
parent 7a879f3c54
commit 62a6278682
25 changed files with 420 additions and 397 deletions
@@ -25,13 +25,13 @@ type UseDragParams = {
handleSelector?: string; handleSelector?: string;
nodeId?: string; nodeId?: string;
updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void; updateNodePositions: (dragItems: NodeDragItem[], d: boolean, p: boolean) => void;
nodesStore: Writable<Node[]>; nodes: Writable<Node[]>;
transformStore: Writable<Transform>; transform: Writable<Transform>;
}; };
export default function drag( export default function drag(
nodeRef: Element, nodeRef: Element,
{ handleSelector, nodeId, updateNodePositions, nodesStore, transformStore }: UseDragParams { handleSelector, nodeId, updateNodePositions, nodes, transform: transformStore }: UseDragParams
) { ) {
let dragging = false; let dragging = false;
let dragItems: NodeDragItem[] = []; let dragItems: NodeDragItem[] = [];
@@ -62,7 +62,7 @@ export default function drag(
dragItems = dragItems.map((n) => { dragItems = dragItems.map((n) => {
const nextPosition = { x: x - n.distance.x, y: y - n.distance.y }; const nextPosition = { x: x - n.distance.x, y: y - n.distance.y };
const updatedPos = calcNextPosition(n, nextPosition, get(nodesStore) as RFNode[]); const updatedPos = calcNextPosition(n, nextPosition, get(nodes) as RFNode[]);
// we want to make sure that we only fire a change event when there is a changes // we want to make sure that we only fire a change event when there is a changes
hasChange = hasChange =
@@ -89,7 +89,7 @@ export default function drag(
const pointerPos = getPointerPosition(event); const pointerPos = getPointerPosition(event);
console.log(pointerPos); console.log(pointerPos);
lastPos = pointerPos; lastPos = pointerPos;
dragItems = getDragItems(get(nodesStore) as RFNode[], pointerPos, nodeId); dragItems = getDragItems(get(nodes) as RFNode[], pointerPos, nodeId);
}) })
.on('drag', (event: UseDragEvent) => { .on('drag', (event: UseDragEvent) => {
const pointerPos = getPointerPosition(event); const pointerPos = getPointerPosition(event);
@@ -78,13 +78,13 @@ function filter(event: any, params: ZoomParams): boolean {
} }
type ZoomParams = { type ZoomParams = {
transformStore: Writable<Transform>; transform: Writable<Transform>;
selecting: boolean; selecting: boolean;
d3Store: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>; d3: Writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>;
}; };
export default function zoom(domNode: Element, params: ZoomParams) { export default function zoom(domNode: Element, params: ZoomParams) {
const { transformStore, d3Store } = params; const { transform, d3 } = params;
const d3ZoomInstance = d3Zoom(); const d3ZoomInstance = d3Zoom();
const selection = select(domNode).call(d3ZoomInstance); const selection = select(domNode).call(d3ZoomInstance);
const d3ZoomHandler = selection.on('wheel.zoom'); const d3ZoomHandler = selection.on('wheel.zoom');
@@ -100,13 +100,13 @@ export default function zoom(domNode: Element, params: ZoomParams) {
d3ZoomHandler!.call(this, event, d); d3ZoomHandler!.call(this, event, d);
}); });
d3Store.set({ d3.set({
zoom: d3ZoomInstance, zoom: d3ZoomInstance,
selection selection
}); });
d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => { d3ZoomInstance.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
transformStore.set([event.transform.x, event.transform.y, event.transform.k]); transform.set([event.transform.x, event.transform.y, event.transform.k]);
}); });
d3ZoomInstance.filter((event: any) => filter(event, params)); d3ZoomInstance.filter((event: any) => filter(event, params));
@@ -6,17 +6,17 @@
type $$Props = ConnectionLineProps; type $$Props = ConnectionLineProps;
const { connectionPathStore, widthStore, heightStore, connectionStore } = useStore(); const { connectionPath, width, height, connection } = useStore();
</script> </script>
{#if $connectionPathStore} {#if $connectionPath}
<svg <svg
width={$widthStore} width={$width}
height={$heightStore} height={$height}
class="react-flow__connectionline" class="react-flow__connectionline"
> >
<g class={cc(['react-flow__connection', $connectionStore.status])}> <g class={cc(['react-flow__connection', $connection.status])}>
<path d={$connectionPathStore} fill="none" class="react-flow__connection-path" /> <path d={$connectionPath} fill="none" class="react-flow__connection-path" />
</g> </g>
</svg> </svg>
{/if} {/if}
@@ -38,7 +38,7 @@ export function handlePointerDown({
connectionMode, connectionMode,
connectionRadius, connectionRadius,
isTarget, isTarget,
transformStore, transform: transformStore,
panBy, panBy,
updateConnection, updateConnection,
cancelConnection, cancelConnection,
@@ -56,7 +56,7 @@ export function handlePointerDown({
nodes: Node[]; nodes: Node[];
connectionRadius: number; connectionRadius: number;
isValidConnection: ValidConnectionFunc; isValidConnection: ValidConnectionFunc;
transformStore: Writable<Transform>; transform: Writable<Transform>;
updateConnection: (connection: Partial<ConnectionData>) => void; updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void; cancelConnection: () => void;
panBy: (delta: XYPosition) => void; panBy: (delta: XYPosition) => void;
@@ -22,11 +22,11 @@
const handleId = id || null; const handleId = id || null;
const { const {
connectionModeStore, connectionMode,
domNodeStore, domNode,
nodesStore, nodes,
connectionRadiusStore, connectionRadius,
transformStore, transform,
addEdge, addEdge,
panBy, panBy,
cancelConnection, cancelConnection,
@@ -49,11 +49,11 @@
handleId, handleId,
nodeId, nodeId,
isTarget, isTarget,
connectionRadius: $connectionRadiusStore, connectionRadius: $connectionRadius,
domNode: $domNodeStore, domNode: $domNode,
nodes: $nodesStore, nodes: $nodes,
connectionMode: $connectionModeStore, connectionMode: $connectionMode,
transformStore, transform,
isValidConnection: isValidConnection!, isValidConnection: isValidConnection!,
onConnect: onConnectExtended, onConnect: onConnectExtended,
updateConnection, updateConnection,
@@ -5,7 +5,7 @@
import type { KeyHandlerProps } from './KeyHandler' import type { KeyHandlerProps } from './KeyHandler'
import type { KeyDefinition, KeyDefinitionObject } from '$lib/types'; import type { KeyDefinition, KeyDefinitionObject } from '$lib/types';
const { selectionKeyPressedStore, deleteKeyPressedStore } = useStore(); const { selectionKeyPressed, deleteKeyPressed } = useStore();
type $$Props = KeyHandlerProps; type $$Props = KeyHandlerProps;
@@ -25,19 +25,19 @@
<svelte:window <svelte:window
use:shortcut={{ use:shortcut={{
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressedStore.set(true) }], trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(true) }],
type: 'keydown' type: 'keydown'
}} }}
use:shortcut={{ use:shortcut={{
trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressedStore.set(false) }], trigger: [{ key: selectionKeyString, modifier: selectionKeyModifier, callback: () => selectionKeyPressed.set(false) }],
type: 'keyup' type: 'keyup'
}} }}
use:shortcut={{ use:shortcut={{
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressedStore.set(true) }], trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(true) }],
type: 'keydown' type: 'keydown'
}} }}
use:shortcut={{ use:shortcut={{
trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressedStore.set(false) }], trigger: [{ key: deleteKeyString, modifier: deleteKeyModifier, callback: () => deleteKeyPressed.set(false) }],
type: 'keyup' type: 'keyup'
}} }}
/> />
@@ -5,20 +5,20 @@
import Selection from '$lib/components/Selection/index.svelte'; import Selection from '$lib/components/Selection/index.svelte';
import drag from '$lib/actions/drag' import drag from '$lib/actions/drag'
const { selectionRectModeStore, nodesStore, nodeOriginStore, transformStore, updateNodePositions } = useStore(); const { selectionRectMode, nodes, nodeOrigin, transform, updateNodePositions } = useStore();
$: selectedNodes = $nodesStore.filter(n => n.selected); $: selectedNodes = $nodes.filter(n => n.selected);
$: rect = getRectOfNodes(selectedNodes, $nodeOriginStore); $: rect = getRectOfNodes(selectedNodes, $nodeOrigin);
</script> </script>
{#if selectedNodes} {#if selectedNodes}
<div <div
class="selection-wrapper nopan" class="selection-wrapper nopan"
style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`} style={`width: ${rect.width}px; height: ${rect.height}px; transform: translate(${rect.x}px, ${rect.y}px)`}
use:drag={{ nodesStore, transformStore, updateNodePositions }} use:drag={{ nodes, transform, updateNodePositions }}
/> />
<Selection <Selection
isVisible={$selectionRectModeStore === 'nodes'} isVisible={$selectionRectMode === 'nodes'}
width={rect.width} width={rect.width}
height={rect.height} height={rect.height}
x={rect.x} x={rect.x}
@@ -2,13 +2,13 @@
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import Selection from '$lib/components/Selection/index.svelte'; import Selection from '$lib/components/Selection/index.svelte';
const { selectionRectStore, selectionRectModeStore } = useStore(); const { selectionRect, selectionRectMode } = useStore();
</script> </script>
<Selection <Selection
isVisible={!!($selectionRectStore && $selectionRectModeStore === 'user')} isVisible={!!($selectionRect && $selectionRectMode === 'user')}
width={$selectionRectStore?.width} width={$selectionRect?.width}
height={$selectionRectStore?.height} height={$selectionRect?.height}
x={$selectionRectStore?.x} x={$selectionRect?.x}
y={$selectionRectStore?.y} y={$selectionRect?.y}
/> />
@@ -22,8 +22,8 @@
export let selected: $$Props['selected'] = false; export let selected: $$Props['selected'] = false;
export let label: $$Props['label'] = undefined; export let label: $$Props['label'] = undefined;
const { edgeTypesStore } = useStore(); const { edgeTypes } = useStore();
const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypesStore[type!] || BezierEdge; const edgeComponent: typeof SvelteComponentTyped<EdgeProps> = $edgeTypes[type!] || BezierEdge;
</script> </script>
<g <g
@@ -28,8 +28,8 @@
let nodeRef: HTMLDivElement; let nodeRef: HTMLDivElement;
const { nodesStore, transformStore, nodeTypesStore, updateNodePositions, addSelectedNodes } = useStore(); const { nodes, transform, nodeTypes, updateNodePositions, addSelectedNodes } = useStore();
const nodeComponent: typeof SvelteComponentTyped<Partial<NodeProps>> = $nodeTypesStore[type] || DefaultNode; const nodeComponent: typeof SvelteComponentTyped<Partial<NodeProps>> = $nodeTypes[type] || DefaultNode;
const isSelectable = true; const isSelectable = true;
const selectNodesOnDrag = false; const selectNodesOnDrag = false;
const isDraggable = true; const isDraggable = true;
@@ -58,7 +58,7 @@
</script> </script>
<div <div
use:drag={{ nodeId: id, nodesStore, transformStore, updateNodePositions }} use:drag={{ nodeId: id, nodes, transform, updateNodePositions }}
class={cc(['react-flow__node', `react-flow__node-${type}`, className])} class={cc(['react-flow__node', `react-flow__node-${type}`, className])}
class:initializing={!width && !height} class:initializing={!width && !height}
class:dragging={dragging} class:dragging={dragging}
@@ -2,15 +2,15 @@
import EdgeWrapper from '$lib/components/edges/EdgeWrapper.svelte'; import EdgeWrapper from '$lib/components/edges/EdgeWrapper.svelte';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
const { edgesWithDataStore, widthStore, heightStore } = useStore(); const { edgesLayouted, width, height } = useStore();
</script> </script>
<svg <svg
width={$widthStore} width={$width}
height={$height} height={$height}
class="react-flow__edges" class="react-flow__edges"
> >
{#each $edgesLayouted as edge (edge.id)} {#each $edgesLayouted as edge (edge.id)}
<EdgeWrapper {...edge} /> <EdgeWrapper {...edge} />
{/each} {/each}
@@ -4,7 +4,7 @@
import NodeWrapper from '$lib/components/nodes/NodeWrapper.svelte'; import NodeWrapper from '$lib/components/nodes/NodeWrapper.svelte';
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
const { nodesStore, updateNodeDimensions } = useStore(); const { nodes, updateNodeDimensions } = useStore();
const resizeObserver: ResizeObserver | null = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver((entries: ResizeObserverEntry[]) => { const resizeObserver: ResizeObserver | null = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver((entries: ResizeObserverEntry[]) => {
const updates = entries.map((entry: ResizeObserverEntry) => ({ const updates = entries.map((entry: ResizeObserverEntry) => ({
id: entry.target.getAttribute('data-id') as string, id: entry.target.getAttribute('data-id') as string,
@@ -21,7 +21,7 @@
</script> </script>
<div class="react-flow__nodes"> <div class="react-flow__nodes">
{#each $nodesStore as node (node.id)} {#each $nodes as node (node.id)}
<NodeWrapper {...node} {resizeObserver} /> <NodeWrapper {...node} {resizeObserver} />
{/each} {/each}
</div> </div>
@@ -33,7 +33,7 @@
import { SelectionMode, type Node, type Edge } from '@reactflow/system'; import { SelectionMode, type Node, type Edge } from '@reactflow/system';
import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils'; import { getConnectedEdges, getEventPosition, getNodesInside } from '@reactflow/utils';
const { nodesStore, edgesStore, transformStore, nodeOriginStore, draggingStore, selectionRectStore, selectionRectModeStore, selectionKeyPressedStore, resetSelectedElements } = useStore(); const { nodes, edges, transform, nodeOrigin, dragging, selectionRect, selectionRectMode, selectionKeyPressed, resetSelectedElements } = useStore();
// @todo take from props // @todo take from props
const elementsSelectable = true; const elementsSelectable = true;
@@ -43,15 +43,15 @@
let containerBounds: DOMRect | null = null let containerBounds: DOMRect | null = null
let selectedNodes: Node[] = []; let selectedNodes: Node[] = [];
$: isSelecting = $selectionKeyPressedStore; $: isSelecting = $selectionKeyPressed;
$: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectModeStore === 'user'); $: hasActiveSelection = elementsSelectable && (isSelecting || $selectionRectMode === 'user');
function onClick (event: MouseEvent) { function onClick (event: MouseEvent) {
// onPaneClick?.(event); // onPaneClick?.(event);
resetSelectedElements(); resetSelectedElements();
selectionRectModeStore.set(null) selectionRectMode.set(null)
} }
function onMouseDown(event: MouseEvent) { function onMouseDown(event: MouseEvent) {
@@ -71,7 +71,7 @@
resetSelectedElements(); resetSelectedElements();
selectionRectStore.set({ selectionRect.set({
width: 0, width: 0,
height: 0, height: 0,
startX: x, startX: x,
@@ -87,14 +87,14 @@
function onMouseMove(event: MouseEvent) { function onMouseMove(event: MouseEvent) {
if (!isSelecting || !containerBounds || !$selectionRectStore) { if (!isSelecting || !containerBounds || !$selectionRect) {
return; return;
} }
const mousePos = getEventPosition(event, containerBounds); const mousePos = getEventPosition(event, containerBounds);
const startX = $selectionRectStore.startX ?? 0; const startX = $selectionRect.startX ?? 0;
const startY = $selectionRectStore.startY ?? 0; const startY = $selectionRect.startY ?? 0;
const nextUserSelectRect = { const nextUserSelectRect = {
...$selectionRectStore, ...$selectionRect,
x: mousePos.x < startX ? mousePos.x : startX, x: mousePos.x < startX ? mousePos.x : startX,
y: mousePos.y < startY ? mousePos.y : startY, y: mousePos.y < startY ? mousePos.y : startY,
width: Math.abs(mousePos.x - startX), width: Math.abs(mousePos.x - startX),
@@ -102,21 +102,21 @@
}; };
selectedNodes = getNodesInside( selectedNodes = getNodesInside(
new Map($nodesStore.map(node => [node.id, node])), new Map($nodes.map(node => [node.id, node])),
nextUserSelectRect, nextUserSelectRect,
$transformStore, $transform,
selectionMode === SelectionMode.Partial, selectionMode === SelectionMode.Partial,
true, true,
$nodeOriginStore $nodeOrigin
); );
const selectedEdgeIds = getConnectedEdges(selectedNodes, $edgesStore).map((e) => e.id); const selectedEdgeIds = getConnectedEdges(selectedNodes, $edges).map((e) => e.id);
const selectedNodeIds = selectedNodes.map((n) => n.id); const selectedNodeIds = selectedNodes.map((n) => n.id);
nodesStore.update(nodes => nodes.map(toggleSelected(selectedNodeIds))); nodes.update(nodes => nodes.map(toggleSelected(selectedNodeIds)));
edgesStore.update(edges => edges.map(toggleSelected(selectedEdgeIds))); edges.update(edges => edges.map(toggleSelected(selectedEdgeIds)));
selectionRectModeStore.set('user'); selectionRectMode.set('user');
selectionRectStore.set(nextUserSelectRect); selectionRect.set(nextUserSelectRect);
} }
function onMouseUp(event: MouseEvent) { function onMouseUp(event: MouseEvent) {
@@ -129,10 +129,10 @@
// if (!userSelectionActive && userSelectionRect && event.target === container.current) { // if (!userSelectionActive && userSelectionRect && event.target === container.current) {
// onClick?.(event); // onClick?.(event);
// } // }
selectionRectStore.set(null); selectionRect.set(null);
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {
selectionRectModeStore.set('nodes'); selectionRectMode.set('nodes');
} }
@@ -140,19 +140,19 @@
} }
const onMouseLeave = (event: MouseEvent) => { const onMouseLeave = (event: MouseEvent) => {
if ($selectionRectModeStore === 'user') { if ($selectionRectMode === 'user') {
selectionRectModeStore.set(selectedNodes.length > 0 ? 'nodes' : null); selectionRectMode.set(selectedNodes.length > 0 ? 'nodes' : null);
// onSelectionEnd?.(event); // onSelectionEnd?.(event);
} }
selectionRectStore.set(null); selectionRect.set(null);
}; };
</script> </script>
<div <div
bind:this={container} bind:this={container}
class="react-flow__pane" class="react-flow__pane"
class:dragging={$draggingStore} class:dragging={$dragging}
class:selection={isSelecting} class:selection={isSelecting}
on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)} on:click={hasActiveSelection ? undefined : wrapHandler(onClick, container)}
on:mousedown={hasActiveSelection ? onMouseDown : undefined} on:mousedown={hasActiveSelection ? onMouseDown : undefined}
@@ -23,6 +23,7 @@
export let nodeTypes: $$Props['nodeTypes'] = undefined; export let nodeTypes: $$Props['nodeTypes'] = undefined;
export let selectionKey: $$Props['selectionKey'] = undefined; export let selectionKey: $$Props['selectionKey'] = undefined;
export let deleteKey: $$Props['deleteKey'] = undefined; export let deleteKey: $$Props['deleteKey'] = undefined;
export let connectionLineType: $$Props['connectionLineType'] = undefined;
let className: $$Props['class'] = undefined; let className: $$Props['class'] = undefined;
export { className as class }; export { className as class };
@@ -40,16 +41,38 @@
onMount(() => { onMount(() => {
const { width, height } = domNode.getBoundingClientRect(); const { width, height } = domNode.getBoundingClientRect();
store.widthStore.set(width); store.width.set(width);
store.heightStore.set(height); store.height.set(height);
store.domNodeStore.set(domNode); store.domNode.set(domNode);
// @todo: is this a svelte way for two way binding? // @todo: is this a svelte way for two way binding?
store.nodesStore.subscribe((ns) => { store.nodes.subscribe((ns) => {
nodes = ns; nodes = ns;
}); });
}); });
// $: {
// const updatableProps = {
// defaultEdgeOptions,
// connectionMode,
// snapToGrid,
// snapGrid,
// nodesDraggable,
// connectOnClick,
// fitViewOnInit: fitView,
// fitViewOnInitOptions: fitViewOptions,
// };
// Object.keys(updatableProps).forEach((key) => {
// store.update((state) => ({
// ...state,
// [key]: valuesToUpdate[key],
// }));
// });
// }
$: { $: {
store.setNodes(nodes); store.setNodes(nodes);
} }
@@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
const { transformStore} = useStore(); const { transform } = useStore();
</script> </script>
<div <div
class="react-flow__viewport" class="react-flow__viewport"
style="transform: translate({$transformStore[0]}px, {$transformStore[1]}px) scale({$transformStore[2]})" style="transform: translate({$transform[0]}px, {$transform[1]}px) scale({$transform[2]})"
> >
<slot /> <slot />
</div> </div>
@@ -2,12 +2,12 @@
import { useStore } from '$lib/store'; import { useStore } from '$lib/store';
import zoom from '$lib/actions/zoom'; import zoom from '$lib/actions/zoom';
const { transformStore, d3Store, selectionKeyPressedStore, selectionRectModeStore } = useStore(); const { transform, d3, selectionKeyPressed, selectionRectMode } = useStore();
$: selecting = $selectionKeyPressedStore || $selectionRectModeStore === 'user'; $: selecting = $selectionKeyPressed || $selectionRectMode === 'user';
</script> </script>
<div class="react-flow__zoom" use:zoom={{ transformStore, d3Store, selecting }}> <div class="react-flow__zoom" use:zoom={{ transform, d3, selecting }}>
<slot /> <slot />
</div> </div>
@@ -27,16 +27,16 @@
[BackgroundVariant.Cross]: 6, [BackgroundVariant.Cross]: 6,
}; };
const { transformStore, idStore } = useStore(); const { transform, id } = useStore();
const patternColor = color || defaultColor[variant]; const patternColor = color || defaultColor[variant];
const patternSize = size || defaultSize[variant]; const patternSize = size || defaultSize[variant];
const isDots = variant === BackgroundVariant.Dots; const isDots = variant === BackgroundVariant.Dots;
const isCross = variant === BackgroundVariant.Cross; const isCross = variant === BackgroundVariant.Cross;
const gapXY: number[] = Array.isArray(gap) ? gap : [gap, gap]; const gapXY: number[] = Array.isArray(gap) ? gap : [gap, gap];
$: patternId = `background-pattern-${$idStore}`; $: patternId = `background-pattern-${$id}`;
$: scaledGap = [gapXY[0] * $transformStore[2] || 1, gapXY[1] * $transformStore[2] || 1]; $: scaledGap = [gapXY[0] * $transform[2] || 1, gapXY[1] * $transform[2] || 1];
$: scaledSize = patternSize * $transformStore[2]; $: scaledSize = patternSize * $transform[2];
$: patternDimensions = (isCross ? [scaledSize, scaledSize] : scaledGap) as [number, number]; $: patternDimensions = (isCross ? [scaledSize, scaledSize] : scaledGap) as [number, number];
$: patternOffset = isDots $: patternOffset = isDots
? [scaledSize / 2, scaledSize / 2] ? [scaledSize / 2, scaledSize / 2]
@@ -46,8 +46,8 @@
<svg class={cc(['react-flow__background', className])} > <svg class={cc(['react-flow__background', className])} >
<pattern <pattern
id={patternId} id={patternId}
x={$transformStore[0] % scaledGap[0]} x={$transform[0] % scaledGap[0]}
y={$transformStore[1] % scaledGap[1]} y={$transform[1] % scaledGap[1]}
width={scaledGap[0]} width={scaledGap[0]}
height={scaledGap[1]} height={scaledGap[1]}
patternUnits="userSpaceOnUse" patternUnits="userSpaceOnUse"
@@ -24,7 +24,7 @@
const defaultWidth = 200; const defaultWidth = 200;
const defaultHeight = 150; const defaultHeight = 150;
const { nodesStore, transformStore, widthStore, heightStore, nodeOriginStore, idStore } = useStore(); const { nodes, transform, width, height, nodeOrigin, id } = useStore();
type GetMiniMapNodeAttribute = (node: Node) => string; type GetMiniMapNodeAttribute = (node: Node) => string;
const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func); const getAttrFunction = (func: any): GetMiniMapNodeAttribute => (func instanceof Function ? func : () => func);
@@ -32,15 +32,15 @@
const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor); const nodeStrokeColorFunc = getAttrFunction(nodeStrokeColor);
const nodeClassNameFunc = getAttrFunction(nodeClassName); const nodeClassNameFunc = getAttrFunction(nodeClassName);
const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision'; const shapeRendering = typeof window === 'undefined' || !!window.chrome ? 'crispEdges' : 'geometricPrecision';
const labelledBy = `react-flow__minimap-desc-${$idStore}`; const labelledBy = `react-flow__minimap-desc-${$id}`;
$: viewBB = { $: viewBB = {
x: -$transformStore[0] / $transformStore[2], x: -$transform[0] / $transform[2],
y: -$transformStore[1] / $transformStore[2], y: -$transform[1] / $transform[2],
width: $widthStore / $transformStore[2], width: $width / $transform[2],
height: $heightStore / $transformStore[2], height: $height / $transform[2],
} as Rect; } as Rect;
$: boundingRect = $nodesStore.length > 0 ? getBoundsOfRects(getRectOfNodes($nodesStore, $nodeOriginStore), viewBB) : viewBB $: boundingRect = $nodes.length > 0 ? getBoundsOfRects(getRectOfNodes($nodes, $nodeOrigin), viewBB) : viewBB
$: elementWidth = (style?.width as number) ?? defaultWidth; $: elementWidth = (style?.width as number) ?? defaultWidth;
$: elementHeight = (style?.height as number) ?? defaultHeight; $: elementHeight = (style?.height as number) ?? defaultHeight;
$: scaledWidth = boundingRect.width / elementWidth; $: scaledWidth = boundingRect.width / elementWidth;
@@ -51,23 +51,23 @@
$: offset = 5 * viewScale; $: offset = 5 * viewScale;
$: x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset; $: x = boundingRect.x - (viewWidth - boundingRect.width) / 2 - offset;
$: y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset; $: y = boundingRect.y - (viewHeight - boundingRect.height) / 2 - offset;
$: width = viewWidth + offset * 2; $: viewboxWidth = viewWidth + offset * 2;
$: height = viewHeight + offset * 2; $: viewboxHeight = viewHeight + offset * 2;
</script> </script>
<Panel position={position} class={cc(['react-flow__minimap', className])} style={`background-color: ${bgColor};`}> <Panel position={position} class={cc(['react-flow__minimap', className])} style={`background-color: ${bgColor};`}>
<svg <svg
width={elementWidth} width={elementWidth}
height={elementHeight} height={elementHeight}
viewBox={`${x} ${y} ${width} ${height}`} viewBox={`${x} ${y} ${viewboxWidth} ${viewboxHeight}`}
role="img" role="img"
aria-labelledby={labelledBy} aria-labelledby={labelledBy}
> >
{#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if} {#if ariaLabel}<title id={labelledBy}>{ariaLabel}</title>{/if}
{#each $nodesStore as node} {#each $nodes as node(node.id)}
{#if node.width && node.height} {#if node.width && node.height}
{@const pos = getNodePositionWithOrigin(node, $nodeOriginStore).positionAbsolute} {@const pos = getNodePositionWithOrigin(node, $nodeOrigin).positionAbsolute}
<MinimapNode <MinimapNode
x={pos.x} x={pos.x}
y={pos.y} y={pos.y}
@@ -85,7 +85,7 @@
{/each} {/each}
<path <path
class="react-flow__minimap-mask" class="react-flow__minimap-mask"
d={`M${x - offset},${y - offset}h${width + offset * 2}v${height + offset * 2}h${-width - offset * 2}z d={`M${x - offset},${y - offset}h${viewboxWidth + offset * 2}v${viewboxHeight + offset * 2}h${-viewboxWidth - offset * 2}z
M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`} M${viewBB.x},${viewBB.y}h${viewBB.width}v${viewBB.height}h${-viewBB.width}z`}
fill={maskColor} fill={maskColor}
fill-rule="evenodd" fill-rule="evenodd"
@@ -10,9 +10,8 @@
export let shapeRendering: string; export let shapeRendering: string;
export let strokeColor: string; export let strokeColor: string;
export let strokeWidth: number; export let strokeWidth: number;
export let className: string = '';
export let style: Record<string, string>; export let style: Record<string, string>;
let className: string = '';
export { className as class }; export { className as class };
const { background, backgroundColor } = style || {}; const { background, backgroundColor } = style || {};
@@ -0,0 +1,76 @@
import { getBezierPath, getSmoothStepPath, getStraightPath } from '@reactflow/edge-utils';
import { ConnectionLineType, ConnectionMode, Position } from '@reactflow/system';
import type { SvelteFlowStoreState } from './types';
import { derived } from 'svelte/store';
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
export function getConnectionPath(store: SvelteFlowStoreState) {
return derived(
[
store.connection,
store.connectionLineType,
store.connectionMode,
store.nodes,
store.transform
],
([$connection, $connectionLineType, $connectionMode, $nodes, $transform]) => {
if (!$connection.nodeId) {
return null;
}
const fromNode = $nodes.find((n) => n.id === $connection.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = fromHandleBounds?.[$connection.handleType || 'source'] || [];
const handleBoundsLoose = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[$connection.handleType === 'source' ? 'target' : 'source']!;
const handleBounds =
$connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = $connection.handleId
? handleBounds.find((d) => d.id === $connection.handleId)
: handleBounds[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: (($connection.position?.x ?? 0) - $transform[0]) / $transform[2],
targetY: (($connection.position?.y ?? 0) - $transform[1]) / $transform[2],
targetPosition: toPosition
};
let path = '';
if ($connectionLineType === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if ($connectionLineType === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if ($connectionLineType === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return path;
}
);
}
@@ -0,0 +1,61 @@
import { derived } from 'svelte/store';
import { Position } from '@reactflow/system';
import { getEdgePositions, getHandle, getNodeData } from '$lib/container/EdgeRenderer/utils';
import type { WrapEdgeProps } from '$lib/types';
import type { SvelteFlowStoreState } from './types';
export function getEdgesLayouted(store: SvelteFlowStoreState) {
return derived([store.edges, store.nodes], ([$edges, $nodes]) => {
return $edges
.map((edge) => {
const sourceNode = $nodes.find((node) => node.id === edge.source);
const targetNode = $nodes.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
if (!sourceIsValid || !targetIsValid) {
return null;
}
const edgeType = edge.type || 'default';
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
return null;
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
// we nee to do this to match the types
const sourceHandleId = edge.sourceHandle;
const targetHandleId = edge.targetHandle;
return {
...edge,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
sourceHandleId,
targetHandleId
};
})
.filter((e) => e !== null) as WrapEdgeProps[];
});
}
+51 -287
View File
@@ -1,23 +1,16 @@
import { getContext } from 'svelte'; import { getContext } from 'svelte';
import { derived, get, writable, type Readable, type Writable } from 'svelte/store'; import { get } from 'svelte/store';
import { import {
type Transform, type Transform,
type NodeDragItem, type NodeDragItem,
type NodeDimensionUpdate, type NodeDimensionUpdate,
Position,
internalsSymbol, internalsSymbol,
SelectionMode,
type NodeOrigin, type NodeOrigin,
type D3ZoomInstance,
type D3SelectionInstance,
type ViewportHelperFunctionOptions, type ViewportHelperFunctionOptions,
type SelectionRect,
type Node as RFNode, type Node as RFNode,
type Connection, type Connection,
ConnectionMode,
type XYPosition, type XYPosition,
type CoordinateExtent, type CoordinateExtent
ConnectionLineType
} from '@reactflow/system'; } from '@reactflow/system';
import { import {
fitView as fitViewUtil, fitView as fitViewUtil,
@@ -29,15 +22,12 @@ import {
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom';
import { getHandleBounds } from '../../utils'; import { getHandleBounds } from '../../utils';
import { getEdgePositions, getHandle, getNodeData } from '$lib/container/EdgeRenderer/utils'; import type { EdgeTypes, NodeTypes, Node, Edge, ConnectionData } from '$lib/types';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import InputNode from '$lib/components/nodes/InputNode.svelte'; import { getEdgesLayouted } from './edges-layouted';
import OutputNode from '$lib/components/nodes/OutputNode.svelte'; import { getConnectionPath } from './connection-path';
import type { EdgeTypes, NodeTypes, Node, Edge, WrapEdgeProps, ConnectionData } from '$lib/types'; import { initConnectionData, initialStoreState } from './initial-store';
import BezierEdge from '$lib/components/edges/BezierEdge.svelte'; import type { SvelteFlowStore } from './types';
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
import { getBezierPath, getSmoothStepPath, getStraightPath } from '@reactflow/edge-utils';
export const key = Symbol(); export const key = Symbol();
@@ -50,59 +40,6 @@ type CreateStoreProps = {
id?: string; id?: string;
}; };
type SvelteFlowStore = {
nodesStore: Writable<Node[]>;
edgesStore: Writable<Edge[]>;
heightStore: Writable<number>;
widthStore: Writable<number>;
d3Store: Writable<{
zoom: D3ZoomInstance | null;
selection: D3SelectionInstance | null;
}>;
transformStore: Writable<Transform>;
edgesWithDataStore: Readable<WrapEdgeProps[]>;
idStore: Writable<string>;
nodeOriginStore: Writable<NodeOrigin>;
draggingStore: Writable<boolean>;
selectionRectStore: Writable<SelectionRect | null>;
selectionRectModeStore: Writable<string | null>;
selectionMode: Writable<SelectionMode>;
selectionKeyPressedStore: Writable<boolean>;
deleteKeyPressedStore: Writable<boolean>;
nodeTypesStore: Writable<NodeTypes>;
edgeTypesStore: Writable<EdgeTypes>;
domNodeStore: Writable<HTMLDivElement | null>;
connectionRadiusStore: Writable<number>;
connectionModeStore: Writable<ConnectionMode>;
connectionStore: Writable<ConnectionData>;
connectionPathStore: Readable<string | null>;
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
dragging?: boolean
) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
addSelectedNodes: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
};
const initConnectionData = {
nodeId: null,
handleId: null,
handleType: null,
position: null,
status: null
};
export function createStore({ export function createStore({
transform = [0, 0, 1], transform = [0, 0, 1],
nodeOrigin = [0, 0], nodeOrigin = [0, 0],
@@ -111,182 +48,23 @@ export function createStore({
edgeTypes = {}, edgeTypes = {},
id = '1' id = '1'
}: CreateStoreProps): SvelteFlowStore { }: CreateStoreProps): SvelteFlowStore {
const nodesStore = writable([] as Node[]); const store = {
const edgesStore = writable([] as Edge[]); ...initialStoreState
const heightStore = writable(500); };
const widthStore = writable(500);
const nodeOriginStore = writable(nodeOrigin);
const d3Store = writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null,
selection: null
});
const idStore = writable(id);
const draggingStore = writable(false);
const selectionRectStore = writable(null);
const selectionKeyPressedStore = writable(false);
const multiselectionKeyPressedStore = writable(false);
const deleteKeyPressedStore = writable(false);
const selectionRectModeStore = writable(null);
const selectionMode = writable(SelectionMode.Partial);
const nodeTypesStore = writable({
...nodeTypes,
input: nodeTypes.input || InputNode,
output: nodeTypes.output || OutputNode,
default: nodeTypes.default || DefaultNode
});
const edgeTypesStore = writable({
...edgeTypes,
straight: edgeTypes.straight || StraightEdge,
smoothstep: edgeTypes.smoothstep || SmoothStepEdge,
default: edgeTypes.default || BezierEdge
});
const transformStore = writable(transform);
const connectionModeStore = writable(ConnectionMode.Strict);
const domNodeStore = writable(null);
const connectionStore = writable<ConnectionData>(initConnectionData);
const connectionRadiusStore = writable(25);
const connectionLineTypeStore = writable(ConnectionLineType.Bezier);
let fitViewOnInitDone = false; let fitViewOnInitDone = false;
const edgesWithDataStore = derived([edgesStore, nodesStore], ([$edges, $nodes]) => {
return $edges
.map((edge) => {
const sourceNode = $nodes.find((node) => node.id === edge.source);
const targetNode = $nodes.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(
sourceNode as RFNode
);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(
targetNode as RFNode
);
if (!sourceIsValid || !targetIsValid) {
return null;
}
const edgeType = edge.type || 'default';
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
return null;
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
// we nee to do this to match the types
const sourceHandleId = edge.sourceHandle;
const targetHandleId = edge.targetHandle;
return {
...edge,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY,
sourcePosition,
targetPosition,
sourceHandleId,
targetHandleId
};
})
.filter((e) => e !== null) as WrapEdgeProps[];
});
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
const connectionPathStore = derived(
[connectionStore, connectionLineTypeStore, connectionModeStore, nodesStore, transformStore],
([
$connectionStore,
$connectionLineTypeStore,
$connectionModeStore,
$nodesStore,
$transformStore
]) => {
if (!$connectionStore.nodeId) {
return null;
}
const fromNode = $nodesStore.find((n) => n.id === $connectionStore.nodeId);
const fromHandleBounds = fromNode?.[internalsSymbol]?.handleBounds;
const handleBoundsStrict = fromHandleBounds?.[$connectionStore.handleType || 'source'] || [];
const handleBoundsLoose = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[$connectionStore.handleType === 'source' ? 'target' : 'source']!;
const handleBounds =
$connectionModeStore === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = $connectionStore.handleId
? handleBounds.find((d) => d.id === $connectionStore.handleId)
: handleBounds[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.width ?? 0) / 2;
const fromHandleY = fromHandle ? fromHandle.y + fromHandle.height / 2 : fromNode?.height ?? 0;
const fromX = (fromNode?.positionAbsolute?.x ?? 0) + fromHandleX;
const fromY = (fromNode?.positionAbsolute?.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition = fromPosition ? oppositePosition[fromPosition] : undefined;
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: (($connectionStore.position?.x ?? 0) - $transformStore[0]) / $transformStore[2],
targetY: (($connectionStore.position?.y ?? 0) - $transformStore[1]) / $transformStore[2],
targetPosition: toPosition
};
let path = '';
if ($connectionLineTypeStore === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if ($connectionLineTypeStore === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if ($connectionLineTypeStore === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return path;
}
);
function setEdges(edges: Edge[]) { function setEdges(edges: Edge[]) {
edgesStore.set(edges); store.edges.set(edges);
} }
function addEdge(edgeParams: Edge | Connection) { function addEdge(edgeParams: Edge | Connection) {
const edges = get(edgesStore); const edges = get(store.edges);
edgesStore.set(addEdgeUtil(edgeParams, edges)); store.edges.set(addEdgeUtil(edgeParams, edges));
} }
function setNodes(nodes: Node[]) { function setNodes(nodes: Node[]) {
nodesStore.update((currentNodes) => { store.nodes.update((currentNodes) => {
const nextNodes = nodes.map((n) => { const nextNodes = nodes.map((n) => {
const currentNode = currentNodes.find((cn) => cn.id === n.id) || {}; const currentNode = currentNodes.find((cn) => cn.id === n.id) || {};
@@ -302,7 +80,7 @@ export function createStore({
} }
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) { function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
nodesStore.update((nds) => { store.nodes.update((nds) => {
return nds.map((n) => { return nds.map((n) => {
const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id); const nodeDragItem = nodeDragItems.find((ndi) => ndi.id === n.id);
@@ -330,7 +108,7 @@ export function createStore({
const style = window.getComputedStyle(viewportNode); const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform); const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nextNodes = get(nodesStore).map((node) => { const nextNodes = get(store.nodes).map((node) => {
const update = updates.find((u) => u.id === node.id); const update = updates.find((u) => u.id === node.id);
if (update) { if (update) {
@@ -360,16 +138,16 @@ export function createStore({
return node; return node;
}); });
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
fitViewOnInitDone = fitViewOnInitDone =
fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView()); fitViewOnInitDone || (fitViewOnInit && !!d3Zoom && !!d3Selection && fitView());
nodesStore.set(nextNodes); store.nodes.set(nextNodes);
} }
function zoomIn(options?: ViewportHelperFunctionOptions) { function zoomIn(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (d3Zoom && d3Selection) { if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2); d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1.2);
@@ -377,14 +155,14 @@ export function createStore({
} }
function zoomOut(options?: ViewportHelperFunctionOptions) { function zoomOut(options?: ViewportHelperFunctionOptions) {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (d3Zoom && d3Selection) { if (d3Zoom && d3Selection) {
d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2); d3Zoom.scaleBy(getD3Transition(d3Selection, options?.duration), 1 / 1.2);
} }
} }
function fitView() { function fitView() {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
if (!d3Zoom || !d3Selection) { if (!d3Zoom || !d3Selection) {
return false; return false;
@@ -392,14 +170,14 @@ export function createStore({
return fitViewUtil( return fitViewUtil(
{ {
nodes: get(nodesStore) as RFNode[], nodes: get(store.nodes) as RFNode[],
width: get(widthStore), width: get(store.width),
height: get(heightStore), height: get(store.height),
minZoom: 0.2, minZoom: 0.2,
maxZoom: 2, maxZoom: 2,
d3Selection, d3Selection,
d3Zoom, d3Zoom,
nodeOrigin: get(nodeOriginStore) nodeOrigin: get(store.nodeOrigin)
}, },
{} {}
); );
@@ -417,14 +195,14 @@ export function createStore({
} }
function resetSelectedElements() { function resetSelectedElements() {
nodesStore.update((ns) => ns.map(resetSelectedItem)); store.nodes.update((ns) => ns.map(resetSelectedItem));
edgesStore.update((es) => es.map(resetSelectedItem)); store.edges.update((es) => es.map(resetSelectedItem));
} }
deleteKeyPressedStore.subscribe((deleteKeyPressed) => { store.deleteKeyPressed.subscribe((deleteKeyPressed) => {
if (deleteKeyPressed) { if (deleteKeyPressed) {
const nodes = get(nodesStore); const nodes = get(store.nodes);
const edges = get(edgesStore); const edges = get(store.edges);
const selectedNodes = nodes.filter((node) => node.selected); const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected); const selectedEdges = edges.filter((edge) => edge.selected);
@@ -458,21 +236,21 @@ export function createStore({
return res; return res;
}, []); }, []);
nodesStore.update((nds) => nds.filter((node) => !nodeIds.includes(node.id))); store.nodes.update((nds) => nds.filter((node) => !nodeIds.includes(node.id)));
edgesStore.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id))); store.edges.update((eds) => eds.filter((edge) => !edgeIdsToRemove.includes(edge.id)));
} }
} }
}); });
function addSelectedNodes(ids: string[]) { function addSelectedNodes(ids: string[]) {
selectionRectStore.set(null); store.selectionRect.set(null);
selectionRectModeStore.set(null); store.selectionRectMode.set(null);
if (get(multiselectionKeyPressedStore)) { if (get(store.multiselectionKeyPressed)) {
// @todo handle multiselection key // @todo handle multiselection key
} }
nodesStore.update((ns) => store.nodes.update((ns) =>
ns.map((node) => { ns.map((node) => {
return { return {
...node, ...node,
@@ -483,10 +261,10 @@ export function createStore({
} }
function panBy(delta: XYPosition) { function panBy(delta: XYPosition) {
const { zoom: d3Zoom, selection: d3Selection } = get(d3Store); const { zoom: d3Zoom, selection: d3Selection } = get(store.d3);
const transform = get(transformStore); const transform = get(store.transform);
const width = get(widthStore); const width = get(store.width);
const height = get(heightStore); const height = get(store.height);
if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) { if (!d3Zoom || !d3Selection || (!delta.x && !delta.y)) {
return; return;
@@ -509,7 +287,7 @@ export function createStore({
} }
function updateConnection(connectionUpdate: Partial<ConnectionData> | null) { function updateConnection(connectionUpdate: Partial<ConnectionData> | null) {
const currentConnectionData = get(connectionStore); const currentConnectionData = get(store.connection);
const nextConnectionData = currentConnectionData const nextConnectionData = currentConnectionData
? { ? {
...initConnectionData, ...initConnectionData,
@@ -521,7 +299,7 @@ export function createStore({
...connectionUpdate ...connectionUpdate
}; };
connectionStore.set(nextConnectionData); store.connection.set(nextConnectionData);
} }
function cancelConnection() { function cancelConnection() {
@@ -529,28 +307,14 @@ export function createStore({
} }
return { return {
nodesStore, // state
edgesStore, ...store,
transformStore,
d3Store, // derived state
heightStore, edgesLayouted: getEdgesLayouted(store),
widthStore, connectionPath: getConnectionPath(store),
edgesWithDataStore,
idStore, // actions
nodeOriginStore,
draggingStore,
selectionRectStore,
selectionKeyPressedStore,
deleteKeyPressedStore,
selectionRectModeStore,
selectionMode,
nodeTypesStore,
edgeTypesStore,
connectionModeStore,
domNodeStore,
connectionStore,
connectionRadiusStore,
connectionPathStore,
setNodes, setNodes,
setEdges, setEdges,
addEdge, addEdge,
@@ -0,0 +1,65 @@
import { readable, writable } from 'svelte/store';
import {
SelectionMode,
type D3ZoomInstance,
type D3SelectionInstance,
ConnectionMode,
ConnectionLineType,
type Transform,
type NodeOrigin,
type Rect
} from '@reactflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
import InputNode from '$lib/components/nodes/InputNode.svelte';
import OutputNode from '$lib/components/nodes/OutputNode.svelte';
import type { Node, Edge, ConnectionData, NodeTypes, EdgeTypes } from '$lib/types';
import BezierEdge from '$lib/components/edges/BezierEdge.svelte';
import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
export const initConnectionData = {
nodeId: null,
handleId: null,
handleType: null,
position: null,
status: null
};
export const initialStoreState = {
nodes: writable<Node[]>([]),
edges: writable<Edge[]>([]),
edgesLayouted: readable<Edge[]>([]),
height: writable<number>(500),
width: writable<number>(500),
nodeOrigin: writable<NodeOrigin>([0.5, 0.5]),
d3: writable<{ zoom: D3ZoomInstance | null; selection: D3SelectionInstance | null }>({
zoom: null,
selection: null
}),
id: writable<string | null>(null),
dragging: writable<boolean>(false),
selectionRect: writable<(Rect & { startX: number; startY: number }) | null>(null),
selectionKeyPressed: writable<boolean>(false),
multiselectionKeyPressed: writable<boolean>(false),
deleteKeyPressed: writable<boolean>(false),
selectionRectMode: writable<string | null>(null),
selectionMode: writable<SelectionMode>(SelectionMode.Partial),
nodeTypes: writable<NodeTypes>({
input: InputNode,
output: OutputNode,
default: DefaultNode
}),
edgeTypes: writable<EdgeTypes>({
straight: StraightEdge,
smoothstep: SmoothStepEdge,
default: BezierEdge
}),
transform: writable<Transform>([0, 0, 1]),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connectionPath: readable<string | null>(null),
connection: writable<ConnectionData>(initConnectionData),
connectionRadius: writable<number>(25),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier)
};
+34
View File
@@ -0,0 +1,34 @@
import type {
NodeDimensionUpdate,
XYPosition,
ViewportHelperFunctionOptions,
Connection,
NodeDragItem
} from '@reactflow/system';
import { initialStoreState } from './initial-store';
import type { Node, Edge, ConnectionData } from '$lib/types';
export type SvelteFlowStoreActions = {
setNodes: (nodes: Node[]) => void;
setEdges: (edges: Edge[]) => void;
addEdge: (edge: Edge | Connection) => void;
zoomIn: (options?: ViewportHelperFunctionOptions) => void;
zoomOut: (options?: ViewportHelperFunctionOptions) => void;
fitView: (options?: ViewportHelperFunctionOptions) => boolean;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
dragging?: boolean
) => void;
updateNodeDimensions: (updates: NodeDimensionUpdate[]) => void;
resetSelectedElements: () => void;
addSelectedNodes: (ids: string[]) => void;
panBy: (delta: XYPosition) => void;
updateConnection: (connection: Partial<ConnectionData>) => void;
cancelConnection: () => void;
};
export type SvelteFlowStoreState = typeof initialStoreState;
export type SvelteFlowStore = SvelteFlowStoreState & SvelteFlowStoreActions;
+2 -1
View File
@@ -1,6 +1,6 @@
import type { Node, NodeTypes } from './nodes'; import type { Node, NodeTypes } from './nodes';
import type { ShortcutModifierDefinition } from '@svelte-put/shortcut'; import type { ShortcutModifierDefinition } from '@svelte-put/shortcut';
import type { Edge, HandleType, XYPosition } from '@reactflow/system'; import type { ConnectionLineType, Edge, HandleType, XYPosition } from '@reactflow/system';
export type KeyModifier = ShortcutModifierDefinition; export type KeyModifier = ShortcutModifierDefinition;
export type KeyDefinitionObject = { key: string; modifier?: KeyModifier }; export type KeyDefinitionObject = { key: string; modifier?: KeyModifier };
@@ -20,6 +20,7 @@ export type SvelteFlowProps = {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
connectionLineType?: ConnectionLineType;
selectionKey?: KeyDefinition; selectionKey?: KeyDefinition;
deleteKey?: KeyDefinition; deleteKey?: KeyDefinition;
nodeTypes?: NodeTypes; nodeTypes?: NodeTypes;