diff --git a/browser/data-browser/package.json b/browser/data-browser/package.json index ee5990426..0e842e395 100644 --- a/browser/data-browser/package.json +++ b/browser/data-browser/package.json @@ -8,6 +8,7 @@ "@bugsnag/core": "^7.16.1", "@bugsnag/js": "^7.16.5", "@bugsnag/plugin-react": "^7.16.5", + "@dagrejs/dagre": "^1.0.2", "@dnd-kit/core": "^6.0.5", "@dnd-kit/sortable": "^7.0.1", "@dnd-kit/utilities": "^3.2.0", @@ -35,6 +36,7 @@ "react-router-dom": "^6.9.0", "react-virtualized-auto-sizer": "^1.0.7", "react-window": "^1.8.7", + "reactflow": "^11.8.3", "remark-gfm": "^3.0.1", "styled-components": "^6.0.7", "stylis": "4.3.0", diff --git a/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx b/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx new file mode 100644 index 000000000..16e1d2bd9 --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx @@ -0,0 +1,123 @@ +import React, { useCallback } from 'react'; +import { + useStore as useFlowStore, + getBezierPath, + EdgeText, + EdgeProps, + Node, +} from 'reactflow'; +import styled, { useTheme } from 'styled-components'; +import { getEdgeParams, getSelfReferencePath } from './getEdgeParams'; +import { EdgeData } from './buildGraph'; + +const getPathData = ( + sourceNode: Node, + targetNode: Node, + overlapping: boolean, +) => { + // Self referencing edges use a custom path. + if (sourceNode.id === targetNode.id) { + return getSelfReferencePath(sourceNode); + } + + const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams( + sourceNode, + targetNode, + overlapping, + ); + + return getBezierPath({ + sourceX: sx, + sourceY: sy, + sourcePosition: sourcePos, + targetPosition: targetPos, + targetX: tx, + targetY: ty, + }); +}; + +function Label({ text }: { text: string }): JSX.Element | string { + const parts = text.split('\n'); + + if (parts.length === 1) { + return text; + } + + // SVG does not have any auto word wrap so we split the lines manually and offset them. + return ( + <> + {parts.map((part, i) => ( + + {part} + + ))} + + ); +} + +/** + * A custom edge that doesn't clutter the graph as mutch as the default edge. + * It casts a ray from the center of the source node to the center of the target node then draws a bezier curve between the two intersecting border of the nodes. + */ +export function FloatingEdge({ + id, + source, + target, + markerEnd, + style, + label, + data, +}: EdgeProps) { + const theme = useTheme(); + const sourceNode = useFlowStore( + useCallback(store => store.nodeInternals.get(source), [source]), + ); + const targetNode = useFlowStore( + useCallback(store => store.nodeInternals.get(target), [target]), + ); + + if (!sourceNode || !targetNode) { + return null; + } + + const [path, labelX, labelY] = getPathData( + sourceNode, + targetNode, + !!data?.overlapping, + ); + + return ( + <> + + } + labelStyle={{ + fill: theme.colors.text, + }} + labelShowBg + labelBgStyle={{ fill: theme.colors.bg1 }} + labelBgPadding={[2, 4]} + labelBgBorderRadius={2} + /> + + ); +} + +const Path = styled.path` + flex-direction: column; + display: flex; + flex-grow: 1; + height: 100%; + + & .react-flow__handle { + opacity: 0; + } +`; diff --git a/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx b/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx new file mode 100644 index 000000000..f1bb34d4c --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx @@ -0,0 +1,75 @@ +import { Resource, useStore } from '@tomic/react'; +import React, { useCallback } from 'react'; +import ReactFlow, { + Controls, + useReactFlow, + Node, + ReactFlowProvider, +} from 'reactflow'; +import 'reactflow/dist/style.css'; +import './reactFlowOverrides.css'; +import { buildGraph } from './buildGraph'; +import { FloatingEdge } from './FloatingEdge'; +import { useGraph } from './useGraph'; +import { useEffectOnce } from '../../hooks/useEffectOnce'; +import { toAnchorId } from '../../views/OntologyPage/toAnchorId'; + +const edgeTypes = { + floating: FloatingEdge, +}; + +interface OntologyGraphProps { + ontology: Resource; +} + +/** + * !ASYNC COMPONENT, DO NOT IMPORT DIRECTLY! + * Displays an ontology as a graph. + */ +export default function OntologyGraph({ + ...props +}: OntologyGraphProps): JSX.Element { + return ( + + + + ); +} + +function OntologyGraphInner({ ontology }: OntologyGraphProps): JSX.Element { + const store = useStore(); + const { fitView } = useReactFlow(); + + const { nodes, edges, setGraph, handleNodeChange, handleNodeDoubleClick } = + useGraph(ontology); + + useEffectOnce(() => { + buildGraph(ontology, store).then(([n, e]) => { + setGraph(n, e); + + requestAnimationFrame(() => { + fitView(); + }); + }); + }); + + const handleClick = useCallback((_: React.MouseEvent, node: Node) => { + const domId = toAnchorId(node.id); + + document.getElementById(domId)?.scrollIntoView({ behavior: 'smooth' }); + }, []); + + return ( + + + + ); +} diff --git a/browser/data-browser/src/chunks/GraphViewer/buildGraph.ts b/browser/data-browser/src/chunks/GraphViewer/buildGraph.ts new file mode 100644 index 000000000..1bc22e994 --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/buildGraph.ts @@ -0,0 +1,188 @@ +import { Datatype, Resource, Store, urls } from '@tomic/react'; +import { Node, Edge, MarkerType } from 'reactflow'; +import { randomString } from '../../helpers/randomString'; +import { DefaultTheme } from 'styled-components'; + +const RELEVANT_DATATYPES = [Datatype.ATOMIC_URL, Datatype.RESOURCEARRAY]; + +export interface NodeData { + label: string; + external: boolean; +} + +export enum OverlapIndex { + First, + Second, +} + +export interface EdgeData { + required: boolean; + overlapping: boolean; +} + +interface Routing { + source: string; + target: string; +} + +const label = (text: string, required: boolean): string => + `${required ? '*' : ''}${text}`; + +const newEdge = ( + routing: Routing, + name: string, + required: boolean, + overlapping: boolean, +): Edge => ({ + ...routing, + id: randomString(), + label: label(name, required), + markerEnd: { + type: MarkerType.ArrowClosed, + width: 15, + height: 15, + }, + type: 'floating', + data: { required, overlapping }, +}); + +const findEdgeWithSameRouting = (edges: Edge[], routing: Routing): number => + edges.findIndex( + edge => edge.source === routing.source && edge.target === routing.target, + ); + +const findAndTagOverlappingEdges = ( + edges: Edge[], + routing: Routing, +): boolean => { + const index = edges.findIndex( + edge => edge.target === routing.source && edge.source === routing.target, + ); + + if (index !== -1) { + edges[index] = { + ...edges[index], + data: { + ...edges[index].data, + overlapping: true, + }, + }; + } + + return index !== -1; +}; + +const mergeEdges = ( + existingEdge: Edge, + name: string, + isRequired: boolean, +): Edge => ({ + ...existingEdge, + data: { + required: isRequired || (existingEdge.data?.required ?? false), + overlapping: existingEdge.data?.overlapping ?? false, + }, + label: `${existingEdge.label},\n${label(name, isRequired)}`, +}); + +export async function buildGraph( + ontology: Resource, + store: Store, +): Promise<[Node[], Edge[]]> { + const classes = ontology.get(urls.properties.classes) as string[]; + // Any classes that are not in the ontology but are referenced by classes that are in the ontology. + const externalClasses: Set = new Set(); + + const nodes: Node[] = []; + const edges: Edge[] = []; + + const classToNode = async ( + classSubject: string, + isExtra = false, + ): Promise> => { + const res = await store.getResourceAsync(classSubject); + + if (!isExtra) { + await createEdges(res); + } + + return { + id: classSubject, + position: { x: 0, y: 100 }, + width: 100, + height: 100, + data: { label: res.title, external: isExtra }, + }; + }; + + const createEdges = async (classResource: Resource) => { + const recommends = (classResource.get(urls.properties.recommends) ?? + []) as string[]; + const requires = (classResource.get(urls.properties.requires) ?? + []) as string[]; + + for (const subject of [...recommends, ...requires]) { + const property = await store.getProperty(subject); + + const isRequired = requires.includes(subject); + + if ( + RELEVANT_DATATYPES.includes(property.datatype) && + property.classType + ) { + const routing = { + source: classResource.getSubject(), + target: property.classType, + }; + + const existingEdgeIndex = findEdgeWithSameRouting(edges, routing); + + if (existingEdgeIndex === -1) { + const isOverlapping = findAndTagOverlappingEdges(edges, routing); + + edges.push( + newEdge(routing, property.shortname, isRequired, isOverlapping), + ); + + if (!classes.includes(property.classType)) { + externalClasses.add(property.classType); + } + + continue; + } + + edges[existingEdgeIndex] = mergeEdges( + edges[existingEdgeIndex], + property.shortname, + isRequired, + ); + } + } + }; + + for (const item of classes) { + nodes.push(await classToNode(item)); + } + + for (const extra of externalClasses) { + nodes.push(await classToNode(extra, true)); + } + + return [nodes, edges]; +} + +export function applyNodeStyling( + nodes: Node[], + theme: DefaultTheme, +): Node[] { + return nodes.map(node => ({ + ...node, + style: { + ...node.style, + backgroundColor: theme.colors.bg, + borderColor: theme.colors.bg2, + color: theme.colors.text, + borderStyle: node.data.external ? 'dashed' : 'solid', + }, + })); +} diff --git a/browser/data-browser/src/chunks/GraphViewer/getEdgeParams.ts b/browser/data-browser/src/chunks/GraphViewer/getEdgeParams.ts new file mode 100644 index 000000000..86f81eb79 --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/getEdgeParams.ts @@ -0,0 +1,132 @@ +import { Position, Node } from 'reactflow'; + +// this helper function returns the intersection point +// of the line between the center of the intersectionNode and the target node +function getNodeIntersection(intersectionNode: Node, targetNode: Node) { + // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a + const { + width: intersectionNodeWidth, + height: intersectionNodeHeight, + positionAbsolute: intersectionNodePosition, + } = intersectionNode; + const targetPosition = targetNode.positionAbsolute; + + const w = intersectionNodeWidth! / 2; + const h = intersectionNodeHeight! / 2; + + const x2 = intersectionNodePosition!.x + w; + const y2 = intersectionNodePosition!.y + h; + const x1 = targetPosition!.x + w; + const y1 = targetPosition!.y + h; + + const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h); + const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h); + const a = 1 / (Math.abs(xx1) + Math.abs(yy1)); + const xx3 = a * xx1; + const yy3 = a * yy1; + const x = w * (xx3 + yy3) + x2; + const y = h * (-xx3 + yy3) + y2; + + return { x, y }; +} + +// returns the position (top,right,bottom or right) passed node compared to the intersection point +function getEdgePosition( + node: Node, + intersectionPoint: { x: number; y: number }, +) { + const n = { ...node.positionAbsolute, ...node }; + const nx = Math.round(n.x!); + const ny = Math.round(n.y!); + const px = Math.round(intersectionPoint.x); + const py = Math.round(intersectionPoint.y); + + if (px <= nx + 1) { + return Position.Left; + } + + if (px >= nx + n.width! - 1) { + return Position.Right; + } + + if (py <= ny + 1) { + return Position.Top; + } + + if (py >= n.y! + n.height! - 1) { + return Position.Bottom; + } + + return Position.Top; +} + +export function getEdgeParams( + source: Node, + target: Node, + overlapping: boolean, +) { + const sourceIntersectionPoint = getNodeIntersection(source, target); + const targetIntersectionPoint = getNodeIntersection(target, source); + + const sourcePos = getEdgePosition(source, sourceIntersectionPoint); + const targetPos = getEdgePosition(target, targetIntersectionPoint); + + let sx = sourceIntersectionPoint.x; + + if (overlapping) { + const center = source.positionAbsolute!.x! + source.width! / 2; + const diff = Math.abs(sx - center); + + if (sx < center) { + sx = center + diff; + } else { + sx = center - diff; + } + } + + return { + sx: sx, + sy: sourceIntersectionPoint.y, + tx: targetIntersectionPoint.x, + ty: targetIntersectionPoint.y, + sourcePos, + targetPos, + }; +} + +export function getSelfReferencePath( + node: Node, +): [path: string, labelX: number, labelY: number] { + const { positionAbsolute, width, height } = node; + + const { x, y } = positionAbsolute!; + const HORIZONTAL_START_OFFSET = 20; + const HORIZONTAL_OFFSET = 50; + const VERTICAL_OFFSET = 15; + const BORDER_RADIUS = 10; + + const start = { x: x! + width! - HORIZONTAL_START_OFFSET, y: y! + height! }; + + const path = [ + `M ${start.x}, ${start.y}`, + line(0, VERTICAL_OFFSET - BORDER_RADIUS), + arc(BORDER_RADIUS, BORDER_RADIUS), + line(HORIZONTAL_OFFSET - BORDER_RADIUS, 0), + arc(BORDER_RADIUS, -BORDER_RADIUS), + line(0, (height! + (VERTICAL_OFFSET - BORDER_RADIUS) * 2) * -1), + arc(-BORDER_RADIUS, -BORDER_RADIUS), + line((HORIZONTAL_OFFSET - BORDER_RADIUS) * -1, 0), + arc(-BORDER_RADIUS, BORDER_RADIUS), + line(0, VERTICAL_OFFSET - BORDER_RADIUS), + ].join(', '); + + const labelX = x + width! + HORIZONTAL_OFFSET - HORIZONTAL_START_OFFSET / 2; + const labelY = y + height! / 2; + + return [path, labelX, labelY]; +} + +const line = (x: number, y: number) => `l ${x} ${y}`; + +const arc = (x: number, y: number, sweep = false) => + `a ${x} ${x} 0 0 ${sweep ? 1 : 0} ${x} ${y}`; diff --git a/browser/data-browser/src/chunks/GraphViewer/reactFlowOverrides.css b/browser/data-browser/src/chunks/GraphViewer/reactFlowOverrides.css new file mode 100644 index 000000000..e0f327673 --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/reactFlowOverrides.css @@ -0,0 +1,5 @@ +.react-flow__handle { + background-color: transparent; + border: none; + cursor: grab; +} diff --git a/browser/data-browser/src/chunks/GraphViewer/useGraph.ts b/browser/data-browser/src/chunks/GraphViewer/useGraph.ts new file mode 100644 index 000000000..27d3535b2 --- /dev/null +++ b/browser/data-browser/src/chunks/GraphViewer/useGraph.ts @@ -0,0 +1,156 @@ +import { + Edge, + Node, + NodeChange, + NodePositionChange, + applyNodeChanges, +} from 'reactflow'; +import { EdgeData, NodeData, applyNodeStyling } from './buildGraph'; +import { useCallback, useMemo, useState } from 'react'; +import Dagre from '@dagrejs/dagre'; +import { useTheme } from 'styled-components'; +import { Resource, urls, useString } from '@tomic/react'; + +interface CustomNodePositioning { + [key: string]: [x: number, y: number]; +} + +type UseNodeReturn = { + nodes: Node[]; + edges: Edge[]; + setGraph: (nodes: Node[], edges: Edge[]) => void; + handleNodeChange: (changes: NodeChange[]) => void; + handleNodeDoubleClick: (event: React.MouseEvent, node: Node) => void; +}; + +const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); + +const getLayoutedElements = (nodes: Node[], edges: Edge[]) => { + g.setGraph({ rankdir: 'vertical', ranksep: 70 }); + + edges.forEach(edge => g.setEdge(edge.source, edge.target)); + nodes.forEach(node => + g.setNode(node.id, { label: node, width: 120, height: 100 }), + ); + + Dagre.layout(g); + + return { + positionedNodes: nodes.map(node => { + const { x, y } = g.node(node.id); + + return { ...node, position: { x, y } }; + }), + positionedEdges: edges, + }; +}; + +const placeNodesInSpace = ( + nodes: Node[], + edges: Edge[], + customPositioning: CustomNodePositioning, +): [nodes: Node[], edges: Edge[]] => { + const { positionedNodes, positionedEdges } = getLayoutedElements( + nodes, + edges, + ); + + const ajustedNodes = positionedNodes.map(node => { + if (customPositioning[node.id]) { + const [x, y] = customPositioning[node.id]; + + return { ...node, position: { x, y }, positionAbsolute: { x, y } }; + } + + return node; + }); + + return [ajustedNodes, positionedEdges]; +}; + +export function useGraph(ontology: Resource): UseNodeReturn { + const theme = useTheme(); + + const [customPositioningSTR, setCustomPositioningSTR] = useString( + ontology, + urls.properties.ontology.customNodePositioning, + { commit: true }, + ); + + const customPositioning = useMemo( + () => JSON.parse(customPositioningSTR || '{}'), + [customPositioningSTR], + ); + + const [nodes, setNodes] = useState[]>([]); + const [edges, setEdges] = useState[]>([]); + const [lastPositionChange, setLastPositionChange] = + useState(); + + const setGraph = useCallback( + (_nodes: Node[], _edges: Edge[]) => { + const [positionedNodes, positionedEdges] = placeNodesInSpace( + _nodes, + _edges, + customPositioning, + ); + setNodes(applyNodeStyling(positionedNodes, theme)); + setEdges(positionedEdges); + }, + [theme, customPositioning], + ); + + const handleNodeDoubleClick = useCallback( + async (_e: React.MouseEvent, node: Node) => { + const newCustomPositioning = { + ...customPositioning, + }; + + delete newCustomPositioning[node.id]; + + await setCustomPositioningSTR(JSON.stringify(newCustomPositioning)); + + const [positionedNodes] = placeNodesInSpace( + nodes, + edges, + newCustomPositioning, + ); + + setNodes(positionedNodes); + }, + [customPositioning, nodes, edges], + ); + + const handleNodeChange = useCallback( + (changes: NodeChange[]) => { + const change = changes[0]; + + if (change.type === 'position') { + if (change.dragging) { + setLastPositionChange(change); + } else { + setCustomPositioningSTR( + JSON.stringify({ + ...customPositioning, + [change.id]: [ + lastPositionChange!.positionAbsolute?.x, + lastPositionChange!.positionAbsolute?.y, + ], + }), + ); + } + } + + setNodes(prev => applyNodeChanges(changes, prev)); + }, + [customPositioning, lastPositionChange], + ); + + return { + nodes, + edges, + setGraph, + handleNodeChange, + handleNodeDoubleClick, + }; +} diff --git a/browser/data-browser/src/views/OntologyPage/Graph.tsx b/browser/data-browser/src/views/OntologyPage/Graph.tsx new file mode 100644 index 000000000..edfda9893 --- /dev/null +++ b/browser/data-browser/src/views/OntologyPage/Graph.tsx @@ -0,0 +1,33 @@ +import { Resource } from '@tomic/react'; +import React, { Suspense } from 'react'; +import styled from 'styled-components'; + +const OntologyGraph = React.lazy( + () => import('../../chunks/GraphViewer/OntologyGraph'), +); + +interface GraphProps { + ontology: Resource; +} + +export function Graph({ ontology }: GraphProps): JSX.Element { + return ( + + + + + + ); +} + +const GraphWrapper = styled.div` + position: var(--ontology-graph-position); + display: grid; + place-items: center; + background-color: ${p => p.theme.colors.bg1}; + border: 1px solid ${p => p.theme.colors.bg2}; + aspect-ratio: var(--ontology-graph-ratio); + border-radius: ${p => p.theme.radius}; + top: 1rem; + overflow: hidden; +`; diff --git a/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx b/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx index b5d172527..51be7e0f9 100644 --- a/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx +++ b/browser/data-browser/src/views/OntologyPage/OntologyPage.tsx @@ -15,6 +15,7 @@ import { NewClassButton } from './NewClassButton'; import { toAnchorId } from './toAnchorId'; import { OntologyContextProvider } from './OntologyContext'; import { PropertyCardWrite } from './Property/PropertyCardWrite'; +import { Graph } from './Graph'; export function OntologyPage({ resource }: ResourcePageProps) { const [classes] = useArray(resource, urls.properties.classes); @@ -91,7 +92,7 @@ export function OntologyPage({ resource }: ResourcePageProps) { {!editMode && ( - Placeholder + )} @@ -99,19 +100,10 @@ export function OntologyPage({ resource }: ResourcePageProps) { ); } -const TempGraph = styled.div` - position: sticky; - display: grid; - place-items: center; - background-color: ${p => p.theme.colors.bg1}; - border: 1px solid ${p => p.theme.colors.bg2}; - aspect-ratio: 9 / 16; - border-radius: ${p => p.theme.radius}; - top: 1rem; - overflow: hidden; -`; - const FullPageWrapper = styled.div<{ edit: boolean }>` + --ontology-graph-position: sticky; + --ontology-graph-ratio: 9 / 16; + display: grid; grid-template-areas: ${p => p.edit @@ -130,11 +122,8 @@ const FullPageWrapper = styled.div<{ edit: boolean }>` grid-template-columns: 1fr 5fr; grid-template-rows: 4rem auto auto; - - ${TempGraph} { - position: static; - aspect-ratio: 16/9; - } + --ontology-graph-position: sticky; + --ontology-graph-ratio: 16/9; } `; diff --git a/browser/lib/src/urls.ts b/browser/lib/src/urls.ts index 4b3d58bf4..98f25ac8d 100644 --- a/browser/lib/src/urls.ts +++ b/browser/lib/src/urls.ts @@ -142,6 +142,10 @@ export const properties = { table: { tableColumnWidths: 'https://atomicdata.dev/properties/tableColumnWidths', }, + ontology: { + customNodePositioning: + 'https://atomicdata.dev/properties/custom-node-positioning', + }, color: 'https://atomicdata.dev/properties/color', emoji: 'https://atomicdata.dev/properties/emoji', classes: 'https://atomicdata.dev/properties/classes', diff --git a/browser/pnpm-lock.yaml b/browser/pnpm-lock.yaml index 50cccecb3..6cabf0cce 100644 --- a/browser/pnpm-lock.yaml +++ b/browser/pnpm-lock.yaml @@ -108,6 +108,9 @@ importers: '@bugsnag/plugin-react': specifier: ^7.16.5 version: 7.19.0(@bugsnag/core@7.19.0) + '@dagrejs/dagre': + specifier: ^1.0.2 + version: 1.0.2 '@dnd-kit/core': specifier: ^6.0.5 version: 6.0.8(react-dom@18.2.0)(react@18.2.0) @@ -189,6 +192,9 @@ importers: react-window: specifier: ^1.8.7 version: 1.8.9(react-dom@18.2.0)(react@18.2.0) + reactflow: + specifier: ^11.8.3 + version: 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) remark-gfm: specifier: ^3.0.1 version: 3.0.1 @@ -234,7 +240,7 @@ importers: version: 1.1.0 vite: specifier: ^4.0.4 - version: 4.4.8(@types/node@16.18.39) + version: 4.4.8 vite-plugin-pwa: specifier: ^0.14.1 version: 0.14.7(vite@4.4.8)(workbox-build@6.6.0)(workbox-window@6.6.0) @@ -1656,6 +1662,17 @@ packages: engines: {node: '>=10'} dev: false + /@dagrejs/dagre@1.0.2: + resolution: {integrity: sha512-7N7vEZDlcU4uRHWuL/9RyI8IgM/d4ULR7z2exJALshh7BHF3tFjYL2pW6bQ4mmlDzd2Tr49KJMIY87Be1L6J0w==} + dependencies: + '@dagrejs/graphlib': 2.1.13 + dev: false + + /@dagrejs/graphlib@2.1.13: + resolution: {integrity: sha512-calbMa7Gcyo+/t23XBaqQqon8LlgE9regey4UVoikoenKBXvUnCUL3s9RP6USCxttfr0XWVICtYUuKMdehKqMw==} + engines: {node: '>17.0.0'} + dev: false + /@dnd-kit/accessibility@3.0.1(react@18.2.0): resolution: {integrity: sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==} peerDependencies: @@ -2758,6 +2775,114 @@ packages: '@babel/runtime': 7.22.6 dev: false + /@reactflow/background@11.2.8(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5o41N2LygiNC2/Pk8Ak2rIJjXbKHfQ23/Y9LFsnAlufqwdzFqKA8txExpsMoPVHHlbAdA/xpQaMuoChGPqmyDw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/controls@11.1.19(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Vo0LFfAYjiSRMLEII/aeBo+1MT2a0Yc7iLVnkuRTLzChC0EX+A2Fa+JlzeOEYKxXlN4qcDxckRNGR7092v1HOQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/core@11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-y6DN8Wy4V4KQBGHFqlj9zWRjLJU6CgdnVwWaEA/PdDg/YUkFBMpZnXqTs60czinoA2rAcvsz50syLTPsj5e+Wg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@types/d3': 7.4.0 + '@types/d3-drag': 3.0.3 + '@types/d3-selection': 3.0.6 + '@types/d3-zoom': 3.0.4 + classcat: 5.0.4 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/minimap@11.6.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-PSA28dk09RnBHOA1zb45fjQXz3UozSJZmsIpgq49O3trfVFlSgRapxNdGsughWLs7/emg2M5jmi6Vc+ejcfjvQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@types/d3-selection': 3.0.6 + '@types/d3-zoom': 3.0.4 + classcat: 5.0.4 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/node-resizer@2.1.5(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-z/hJlsptd2vTx13wKouqvN/Kln08qbkA+YTJLohc2aJ6rx3oGn9yX4E4IqNxhA7zNqYEdrnc1JTEA//ifh9z3w==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + + /@reactflow/node-toolbar@1.2.7(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-vs+Wg1tjy3SuD7eoeTqEtscBfE9RY+APqC28urVvftkrtsN7KlnoQjqDG6aE45jWP4z+8bvFizRWjAhxysNLkg==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + classcat: 5.0.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + zustand: 4.4.1(@types/react@18.2.18)(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /@remix-run/router@1.7.2: resolution: {integrity: sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==} engines: {node: '>=14'} @@ -2924,6 +3049,185 @@ packages: resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} dev: true + /@types/d3-array@3.0.7: + resolution: {integrity: sha512-4/Q0FckQ8TBjsB0VdGFemJOG8BLXUB2KKlL0VmZ+eOYeOnTb/wDRQqYWpBmQ6IlvWkXwkYiot+n9Px2aTJ7zGQ==} + dev: false + + /@types/d3-axis@3.0.3: + resolution: {integrity: sha512-SE3x/pLO/+GIHH17mvs1uUVPkZ3bHquGzvZpPAh4yadRy71J93MJBpgK/xY8l9gT28yTN1g9v3HfGSFeBMmwZw==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-brush@3.0.3: + resolution: {integrity: sha512-MQ1/M/B5ifTScHSe5koNkhxn2mhUPqXjGuKjjVYckplAPjP9t2I2sZafb/YVHDwhoXWZoSav+Q726eIbN3qprA==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-chord@3.0.3: + resolution: {integrity: sha512-keuSRwO02c7PBV3JMWuctIfdeJrVFI7RpzouehvBWL4/GGUB3PBNg/9ZKPZAgJphzmS2v2+7vr7BGDQw1CAulw==} + dev: false + + /@types/d3-color@3.1.0: + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + dev: false + + /@types/d3-contour@3.0.3: + resolution: {integrity: sha512-x7G/tdDZt4m09XZnG2SutbIuQqmkNYqR9uhDMdPlpJbcwepkEjEWG29euFcgVA1k6cn92CHdDL9Z+fOnxnbVQw==} + dependencies: + '@types/d3-array': 3.0.7 + '@types/geojson': 7946.0.10 + dev: false + + /@types/d3-delaunay@6.0.1: + resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==} + dev: false + + /@types/d3-dispatch@3.0.3: + resolution: {integrity: sha512-Df7KW3Re7G6cIpIhQtqHin8yUxUHYAqiE41ffopbmU5+FifYUNV7RVyTg8rQdkEagg83m14QtS8InvNb95Zqug==} + dev: false + + /@types/d3-drag@3.0.3: + resolution: {integrity: sha512-82AuQMpBQjuXeIX4tjCYfWjpm3g7aGCfx6dFlxX2JlRaiME/QWcHzBsINl7gbHCODA2anPYlL31/Trj/UnjK9A==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-dsv@3.0.2: + resolution: {integrity: sha512-DooW5AOkj4AGmseVvbwHvwM/Ltu0Ks0WrhG6r5FG9riHT5oUUTHz6xHsHqJSVU8ZmPkOqlUEY2obS5C9oCIi2g==} + dev: false + + /@types/d3-ease@3.0.0: + resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==} + dev: false + + /@types/d3-fetch@3.0.3: + resolution: {integrity: sha512-/EsDKRiQkby3Z/8/AiZq8bsuLDo/tYHnNIZkUpSeEHWV7fHUl6QFBjvMPbhkKGk9jZutzfOkGygCV7eR/MkcXA==} + dependencies: + '@types/d3-dsv': 3.0.2 + dev: false + + /@types/d3-force@3.0.5: + resolution: {integrity: sha512-EGG+IWx93ESSXBwfh/5uPuR9Hp8M6o6qEGU7bBQslxCvrdUBQZha/EFpu/VMdLU4B0y4Oe4h175nSm7p9uqFug==} + dev: false + + /@types/d3-format@3.0.1: + resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==} + dev: false + + /@types/d3-geo@3.0.4: + resolution: {integrity: sha512-kmUK8rVVIBPKJ1/v36bk2aSgwRj2N/ZkjDT+FkMT5pgedZoPlyhaG62J+9EgNIgUXE6IIL0b7bkLxCzhE6U4VQ==} + dependencies: + '@types/geojson': 7946.0.10 + dev: false + + /@types/d3-hierarchy@3.1.3: + resolution: {integrity: sha512-GpSK308Xj+HeLvogfEc7QsCOcIxkDwLhFYnOoohosEzOqv7/agxwvJER1v/kTC+CY1nfazR0F7gnHo7GE41/fw==} + dev: false + + /@types/d3-interpolate@3.0.1: + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + dependencies: + '@types/d3-color': 3.1.0 + dev: false + + /@types/d3-path@3.0.0: + resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==} + dev: false + + /@types/d3-polygon@3.0.0: + resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==} + dev: false + + /@types/d3-quadtree@3.0.2: + resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==} + dev: false + + /@types/d3-random@3.0.1: + resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==} + dev: false + + /@types/d3-scale-chromatic@3.0.0: + resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==} + dev: false + + /@types/d3-scale@4.0.4: + resolution: {integrity: sha512-eq1ZeTj0yr72L8MQk6N6heP603ubnywSDRfNpi5enouR112HzGLS6RIvExCzZTraFF4HdzNpJMwA/zGiMoHUUw==} + dependencies: + '@types/d3-time': 3.0.0 + dev: false + + /@types/d3-selection@3.0.6: + resolution: {integrity: sha512-2ACr96USZVjXR9KMD9IWi1Epo4rSDKnUtYn6q2SPhYxykvXTw9vR77lkFNruXVg4i1tzQtBxeDMx0oNvJWbF1w==} + dev: false + + /@types/d3-shape@3.1.2: + resolution: {integrity: sha512-NN4CXr3qeOUNyK5WasVUV8NCSAx/CRVcwcb0BuuS1PiTqwIm6ABi1SyasLZ/vsVCFDArF+W4QiGzSry1eKYQ7w==} + dependencies: + '@types/d3-path': 3.0.0 + dev: false + + /@types/d3-time-format@4.0.0: + resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==} + dev: false + + /@types/d3-time@3.0.0: + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + dev: false + + /@types/d3-timer@3.0.0: + resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==} + dev: false + + /@types/d3-transition@3.0.4: + resolution: {integrity: sha512-512a4uCOjUzsebydItSXsHrPeQblCVk8IKjqCUmrlvBWkkVh3donTTxmURDo1YPwIVDh5YVwCAO6gR4sgimCPQ==} + dependencies: + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3-zoom@3.0.4: + resolution: {integrity: sha512-cqkuY1ah9ZQre2POqjSLcM8g40UVya/qwEUrNYP2/rCVljbmqKCVcv+ebvwhlI5azIbSEL7m+os6n+WlYA43aA==} + dependencies: + '@types/d3-interpolate': 3.0.1 + '@types/d3-selection': 3.0.6 + dev: false + + /@types/d3@7.4.0: + resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==} + dependencies: + '@types/d3-array': 3.0.7 + '@types/d3-axis': 3.0.3 + '@types/d3-brush': 3.0.3 + '@types/d3-chord': 3.0.3 + '@types/d3-color': 3.1.0 + '@types/d3-contour': 3.0.3 + '@types/d3-delaunay': 6.0.1 + '@types/d3-dispatch': 3.0.3 + '@types/d3-drag': 3.0.3 + '@types/d3-dsv': 3.0.2 + '@types/d3-ease': 3.0.0 + '@types/d3-fetch': 3.0.3 + '@types/d3-force': 3.0.5 + '@types/d3-format': 3.0.1 + '@types/d3-geo': 3.0.4 + '@types/d3-hierarchy': 3.1.3 + '@types/d3-interpolate': 3.0.1 + '@types/d3-path': 3.0.0 + '@types/d3-polygon': 3.0.0 + '@types/d3-quadtree': 3.0.2 + '@types/d3-random': 3.0.1 + '@types/d3-scale': 4.0.4 + '@types/d3-scale-chromatic': 3.0.0 + '@types/d3-selection': 3.0.6 + '@types/d3-shape': 3.1.2 + '@types/d3-time': 3.0.0 + '@types/d3-time-format': 4.0.0 + '@types/d3-timer': 3.0.0 + '@types/d3-transition': 3.0.4 + '@types/d3-zoom': 3.0.4 + dev: false + /@types/debug@4.1.8: resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} dependencies: @@ -2944,6 +3248,10 @@ packages: fast-json-stable-stringify: 2.1.0 dev: true + /@types/geojson@7946.0.10: + resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==} + dev: false + /@types/graceful-fs@4.1.6: resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} dependencies: @@ -3860,6 +4168,10 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true + /classcat@5.0.4: + resolution: {integrity: sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g==} + dev: false + /classnames@2.3.2: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} dev: false @@ -4081,6 +4393,71 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + dev: false + + /d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + + /d3-transition@3.0.1(d3-selection@3.0.0): + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + dev: false + + /d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + dev: false + /damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true @@ -8017,6 +8394,25 @@ packages: dependencies: loose-envify: 1.4.0 + /reactflow@11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-wuVxJOFqi1vhA4WAEJLK0JWx2TsTiWpxTXTRp/wvpqKInQgQcB49I2QNyNYsKJCQ6jjXektS7H+LXoaVK/pG4A==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + dependencies: + '@reactflow/background': 11.2.8(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/controls': 11.1.19(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/core': 11.8.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/minimap': 11.6.3(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-resizer': 2.1.5(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + '@reactflow/node-toolbar': 1.2.7(@types/react@18.2.18)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + dev: false + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -9198,6 +9594,14 @@ packages: tslib: 2.6.1 dev: false + /use-sync-external-store@1.2.0(react@18.2.0): + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true @@ -9256,7 +9660,7 @@ packages: fast-glob: 3.3.1 pretty-bytes: 6.1.1 rollup: 3.27.2 - vite: 4.4.8(@types/node@16.18.39) + vite: 4.4.8 workbox-build: 6.6.0 workbox-window: 6.6.0 transitivePeerDependencies: @@ -9297,7 +9701,7 @@ packages: fsevents: 2.3.2 dev: true - /vite@4.4.8(@types/node@16.18.39): + /vite@4.4.8: resolution: {integrity: sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -9325,7 +9729,6 @@ packages: terser: optional: true dependencies: - '@types/node': 16.18.39 esbuild: 0.18.17 postcss: 8.4.27 rollup: 3.27.2 @@ -9727,6 +10130,26 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + /zustand@4.4.1(@types/react@18.2.18)(react@18.2.0): + resolution: {integrity: sha512-QCPfstAS4EBiTQzlaGP1gmorkh/UL1Leaj2tdj+zZCZ/9bm0WS7sI2wnfD5lpOszFqWJ1DcPnGoY8RDL61uokw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + dependencies: + '@types/react': 18.2.18 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false