Merge branch 'main' into refactor/set-nodes
This commit is contained in:
@@ -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} />
|
||||
Reference in New Issue
Block a user