Merge branch 'main' into refactor/set-nodes

This commit is contained in:
Hayleigh Thompson
2024-01-10 19:00:56 +00:00
39 changed files with 6902 additions and 9322 deletions
@@ -0,0 +1,176 @@
import {
applyNodeChanges,
applyEdgeChanges,
type Node,
type Edge,
NodeChange,
EdgeChange,
XYPosition,
} from '@xyflow/react';
const nodes: Node[] = [
{
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 } },
];
const edges: Edge[] = [
{ id: 'e1-2', source: '1', target: '2', animated: true },
{ id: 'e1-3', source: '1', target: '3' },
{ id: 'e2-3', source: '2', target: '3' },
];
describe('applyChanges Testing', () => {
it('adds a node/edge', () => {
const currentNodes: Node[] = [];
const nodeChanges: NodeChange[] = [{ type: 'add', item: nodes[0] }];
const nextNodes = applyNodeChanges(nodeChanges, currentNodes);
expect(nextNodes.length).to.be.equal(nodeChanges.length);
const currentEdges: Edge[] = [];
const edgeChanges: EdgeChange[] = [{ type: 'add', item: edges[0] }];
const nextEdges = applyEdgeChanges(edgeChanges, currentEdges);
expect(nextEdges.length).to.be.equal(1);
});
it('adds multiple nodes/edges', () => {
const currentNodes: Node[] = [];
const nodeChanges: NodeChange[] = [
{ type: 'add', item: nodes[0] },
{ type: 'add', item: nodes[1] },
];
const nextNodes = applyNodeChanges(nodeChanges, currentNodes);
expect(nextNodes.length).to.be.equal(nodeChanges.length);
const currentEdges: Edge[] = [];
const edgeChanges: EdgeChange[] = [
{ type: 'add', item: edges[0] },
{ type: 'add', item: edges[1] },
];
const nextEdges = applyEdgeChanges(edgeChanges, currentEdges);
expect(nextEdges.length).to.be.equal(edgeChanges.length);
});
it('removes a node/edge', () => {
const nodesLength = nodes.length;
const nodeChanges: NodeChange[] = [{ type: 'remove', id: '1' }];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes.length).to.be.equal(nodes.length - nodeChanges.length);
expect(nodes.length).to.be.equal(nodesLength);
const edgesLength = edges.length;
const edgeChanges: EdgeChange[] = [{ type: 'remove', id: 'e1-2' }];
const nextEdges = applyEdgeChanges(edgeChanges, edges);
expect(nextEdges.length).to.be.equal(edges.length - 1);
expect(edges.length).to.be.equal(edgesLength);
});
it('removes multiple node/edge', () => {
const nodesLength = nodes.length;
const nodeChanges: NodeChange[] = [
{ type: 'remove', id: '1' },
{ type: 'remove', id: '2' },
];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes.length).to.be.equal(nodes.length - nodeChanges.length);
expect(nodes.length).to.be.equal(nodesLength);
const edgesLength = edges.length;
const edgeChanges: EdgeChange[] = [
{ type: 'remove', id: 'e1-2' },
{ type: 'remove', id: 'e1-3' },
];
const nextEdges = applyEdgeChanges(edgeChanges, edges);
expect(nextEdges.length).to.be.equal(edges.length - edgeChanges.length);
expect(edges.length).to.be.equal(edgesLength);
});
it('selects a node/edge', () => {
const nodeChanges: NodeChange[] = [{ type: 'select', id: '1', selected: true }];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes[0].selected).to.be.true;
const edgeChanges: EdgeChange[] = [{ type: 'select', id: 'e1-2', selected: true }];
const nextEdges = applyEdgeChanges(edgeChanges, edges);
expect(nextEdges[0].selected).to.be.true;
});
it('deselects a node/edge', () => {
const nodeChanges: NodeChange[] = [{ type: 'select', id: '1', selected: false }];
const nextNodes = applyNodeChanges(
nodeChanges,
nodes.map((n) => ({ ...n, selected: true }))
);
expect(nextNodes[0].selected).to.be.false;
expect(nextNodes[1].selected).to.be.true;
const edgeChanges: EdgeChange[] = [{ type: 'select', id: 'e1-2', selected: false }];
const nextEdges = applyEdgeChanges(
edgeChanges,
edges.map((e) => ({ ...e, selected: true }))
);
expect(nextEdges[0].selected).to.be.false;
expect(nextEdges[1].selected).to.be.true;
});
it('updates node position', () => {
const newPosition: XYPosition = { x: 100, y: 100 };
const nodeChanges: NodeChange[] = [{ type: 'position', id: '1', position: newPosition }];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes[0].position).to.be.deep.equal(newPosition);
});
it('updates node dimensions', () => {
const newWidth = 200;
const newHeight = 200;
const nodeChanges: NodeChange[] = [
{ type: 'dimensions', id: '1', dimensions: { width: newWidth, height: newHeight } },
];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nodes[0].computed).to.be.undefined;
expect(nextNodes[0].computed).to.be.deep.equal({ width: newWidth, height: newHeight });
expect(nextNodes[0].width).to.be.undefined;
expect(nextNodes[0].height).to.be.undefined;
});
it('updates node position and dimensions', () => {
const newPosition: XYPosition = { x: 100, y: 100 };
const newWidth = 200;
const newHeight = 200;
const nodeChanges: NodeChange[] = [
{ type: 'position', id: '1', position: newPosition },
{ type: 'dimensions', id: '1', dimensions: { width: newWidth, height: newHeight } },
];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nextNodes[0].position).to.be.deep.equal(newPosition);
expect(nextNodes[0].computed).to.be.deep.equal({ width: newWidth, height: newHeight });
});
it('resets nodes/edges', () => {
const nodesLength = nodes.length;
const nodeChanges: NodeChange[] = [{ type: 'reset', item: nodes[0] }];
const nextNodes = applyNodeChanges(nodeChanges, nodes);
expect(nodes.length).length.to.be.equal(nodesLength);
expect(nextNodes.length).to.be.equal(nodeChanges.length);
const edgesLength = edges.length;
const edgeChange: EdgeChange[] = [{ type: 'reset', item: edges[0] }];
const nextEdges = applyEdgeChanges(edgeChange, edges);
expect(edges.length).length.to.be.equal(edgesLength);
expect(nextEdges.length).to.be.equal(edgeChange.length);
});
});
export {};
@@ -9,7 +9,7 @@ const DefaultResizerNode: FC<NodeProps> = ({ data, selected }) => {
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
isVisible={data.isVisible ?? selected}
isVisible={data.isVisible ?? !!selected}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
@@ -15,6 +15,7 @@
'interaction',
'intersections',
'node-toolbar',
'node-resizer',
'overview',
'stress',
'subflows',
@@ -0,0 +1,128 @@
<script lang="ts">
import { SvelteFlow, Controls, Panel, type Node, type Edge } from '@xyflow/svelte';
import DefaultResizer from './DefaultResizer.svelte';
import CustomResizer from './CustomResizer.svelte';
import VerticalResizer from './VerticalResizer.svelte';
import HorizontalResizer from './HorizontalResizer.svelte';
import { writable } from 'svelte/store';
import '@xyflow/svelte/dist/style.css';
const nodeTypes = {
defaultResizer: DefaultResizer,
customResizer: CustomResizer,
verticalResizer: VerticalResizer,
horizontalResizer: HorizontalResizer
};
const nodeStyle = 'border: 1px solid #222; font-size: 10px; background-color: #ddd;';
const edges = writable<Edge[]>([]);
const nodes = writable<Node[]>([
{
id: '1',
type: 'defaultResizer',
data: { label: 'default resizer' },
position: { x: 0, y: 0 },
style: nodeStyle
},
{
id: '1a',
type: 'defaultResizer',
data: {
label: 'default resizer with min and max dimensions',
minWidth: 100,
minHeight: 80,
maxWidth: 200,
maxHeight: 200
},
position: { x: 0, y: 60 },
style: nodeStyle + ' width: 100px; height: 80px;'
},
{
id: '1b',
type: 'defaultResizer',
data: {
label: 'default resizer with initial size and aspect ratio',
keepAspectRatio: true,
minWidth: 100,
minHeight: 60,
maxWidth: 400,
maxHeight: 400
},
position: { x: 250, y: 0 },
style: nodeStyle + ' width: 174px; height: 123px;'
},
{
id: '2',
type: 'customResizer',
data: { label: 'custom resize icon' },
position: { x: 0, y: 200 },
style: nodeStyle + ' width: 100px; height: 60px;'
},
{
id: '3',
type: 'verticalResizer',
data: { label: 'vertical resizer' },
position: { x: 250, y: 200 },
style: nodeStyle
},
{
id: '3a',
type: 'verticalResizer',
data: {
label: 'vertical resizer with min/maxHeight and aspect ratio',
minHeight: 50,
maxHeight: 200,
keepAspectRatio: true
},
position: { x: 400, y: 200 },
style: nodeStyle + ' height: 50px;'
},
{
id: '4',
type: 'horizontalResizer',
data: {
label: 'horizontal resizer with aspect ratio',
keepAspectRatio: true,
minHeight: 20,
maxHeight: 80,
maxWidth: 300
},
position: { x: 250, y: 300 },
style: nodeStyle
},
{
id: '4a',
type: 'horizontalResizer',
data: { label: 'horizontal resizer with maxWidth', maxWidth: 300 },
position: { x: 250, y: 400 },
style: nodeStyle
}
]);
let snapToGrid = false;
</script>
<SvelteFlow
{nodes}
{edges}
{nodeTypes}
minZoom={0.2}
maxZoom={5}
snapGrid={snapToGrid ? [10, 10] : undefined}
fitView
>
<Controls />
<Panel position="bottom-right">
<button
on:click={() => {
snapToGrid = !snapToGrid;
}}
>
snapToGrid: {snapToGrid ? 'on' : 'off'}
</button>
</Panel>
</SvelteFlow>
@@ -0,0 +1,42 @@
<script lang="ts">
import { Handle, Position, type NodeProps, NodeResizeControl } from '@xyflow/svelte';
type $$Props = NodeProps;
export let data: $$Props['data'] = undefined;
</script>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
style="background: transparent; border: none;"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="8"
height="8"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
style="position: absolute; right: 2px; bottom: 2px;"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="16 20 20 20 20 16" />
<line x1="14" y1="14" x2="20" y2="20" />
<polyline points="8 4 4 4 4 8" />
<line x1="4" y1="4" x2="10" y2="10" />
</svg>
</NodeResizeControl>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
@@ -0,0 +1,24 @@
<script lang="ts">
import { Handle, NodeResizer, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
export let data: $$Props['data'] = undefined;
export let selected: $$Props['selected'] = undefined;
</script>
<NodeResizer
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
isVisible={data.isVisible ?? !!selected}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
/>
<Handle type="target" position={Position.Left} />
<div>{data.label}</div>
<Handle type="source" position={Position.Right} />
@@ -0,0 +1,37 @@
<script lang="ts">
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
export let data: $$Props['data'] = undefined;
</script>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Left}
/>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Right}
/>
<Handle type="target" position={Position.Top} />
<div style="padding: 10px;">{data.label}</div>
<Handle type="source" position={Position.Bottom} />
@@ -0,0 +1,24 @@
function ResizeIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="8"
height="8"
viewBox="0 0 24 24"
strokeWidth="2"
stroke="currentColor"
fill="none"
strokeLinecap="round"
strokeLinejoin="round"
style={{ position: 'absolute', right: 2, bottom: 2 }}
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<polyline points="16 20 20 20 20 16" />
<line x1="14" y1="14" x2="20" y2="20" />
<polyline points="8 4 4 4 4 8" />
<line x1="4" y1="4" x2="10" y2="10" />
</svg>
);
}
export default ResizeIcon;
@@ -0,0 +1,37 @@
<script lang="ts">
import { Handle, NodeResizeControl, Position, type NodeProps } from '@xyflow/svelte';
type $$Props = NodeProps;
export let data: $$Props['data'] = undefined;
</script>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Top}
/>
<NodeResizeControl
minWidth={data.minWidth ?? undefined}
maxWidth={data.maxWidth ?? undefined}
minHeight={data.minHeight ?? undefined}
maxHeight={data.maxHeight ?? undefined}
shouldResize={data.shouldResize ?? undefined}
onResizeStart={data.onResizeStart ?? undefined}
onResize={data.onResize ?? undefined}
onResizeEnd={data.onResizeEnd ?? undefined}
keepAspectRatio={data.keepAspectRatio ?? undefined}
color="red"
position={Position.Bottom}
/>
<Handle type="target" position={Position.Left} />
<div style="padding: 10px;">{data.label}</div>
<Handle type="source" position={Position.Right} />