From 892fac05c7f4b69bd225b124447fff49872ff31d Mon Sep 17 00:00:00 2001 From: nickp Date: Mon, 29 Jan 2024 16:15:56 +0100 Subject: [PATCH 01/92] Replace g6 with reactflow --- package.json | 7 +- src/components/solvers/Graph/GMLGraphView.tsx | 94 ++ src/components/solvers/Graph/GraphArea.tsx | 109 --- src/pages/solve/MaxCut.tsx | 18 +- yarn.lock | 802 ++++++++---------- 5 files changed, 453 insertions(+), 577 deletions(-) create mode 100644 src/components/solvers/Graph/GMLGraphView.tsx delete mode 100644 src/components/solvers/Graph/GraphArea.tsx diff --git a/package.json b/package.json index 0fd83d5..9a5b889 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,7 @@ "format": "prettier --write .", "test": "jest" }, - "resolutions": { - "d3-interpolate": "2.0.1" - }, "dependencies": { - "@antv/g6": "^4.7.16", "@chakra-ui/react": "^2.3.1", "@emotion/react": "11", "@emotion/styled": "11", @@ -26,7 +22,8 @@ "react-dom": "18.2.0", "react-icons": "^4.4.0", "react-multi-select-component": "^4.3.4", - "react-simple-code-editor": "^0.13.0" + "react-simple-code-editor": "^0.13.0", + "reactflow": "^11.10.3" }, "devDependencies": { "@testing-library/jest-dom": "^5.16.5", diff --git a/src/components/solvers/Graph/GMLGraphView.tsx b/src/components/solvers/Graph/GMLGraphView.tsx new file mode 100644 index 0000000..eb37821 --- /dev/null +++ b/src/components/solvers/Graph/GMLGraphView.tsx @@ -0,0 +1,94 @@ +import { useEffect } from "react"; +import { Controls, Edge, Node, ReactFlow, useEdgesState, useNodesState } from "reactflow"; +import "reactflow/dist/style.css"; +import { parseGML } from "../../../converter/graph/gml/GmlParser"; + +export const GMLGraphView = (props: { gml: string }) => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + useEffect(() => { + let { nodes, edges } = gmlToReactFlow(props.gml); + setEdges(edges); + setNodes(nodes); + + function gmlToReactFlow(gml: string): { nodes: Node[]; edges: Edge[] } { + if (gml == null || gml == "") return { nodes: [], edges: [] }; + + let graph: any; + try { + graph = parseGML(gml); + } catch (e) { + return { nodes: [], edges: [] }; + } + + function getNodeType(node: any): string { + let input = false; + let output = false; + for (let edge of graph.edges) { + if (edge.source == node.id) { + input = true; + } + if (edge.target == node.id) { + output = true; + } + } + + if (input && output) { + return "default"; + } else if (input) { + return "input"; + } else if (output) { + return "output"; + } else { + return "default"; + } + } + + let nodes: Node[] = graph.nodes.map((node: any, i: number) => { + let n: Node = { + id: node.id, + data: { label: node.label }, + type: getNodeType(node), + position: { + x: 0, + y: i * 100 + } + }; + return n; + }); + + let edges: Edge[] = graph.edges.map((edge: any, i: number) => { + let e: Edge = { + id: i.toString(), + source: edge.source, + target: edge.target, + label: edge.label + }; + return e; + }); + + return { nodes, edges }; + } + }, [props.gml, setEdges, setNodes]); + + return ( +
+ + + +
+ ); +}; \ No newline at end of file diff --git a/src/components/solvers/Graph/GraphArea.tsx b/src/components/solvers/Graph/GraphArea.tsx deleted file mode 100644 index a4176c9..0000000 --- a/src/components/solvers/Graph/GraphArea.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import G6, { Graph, GraphData } from "@antv/g6"; -import { Box, Center, Container, Select, Spacer } from "@chakra-ui/react"; -import { useEffect, useRef, useState } from "react"; - -interface GraphAreaProps { - graphData: GraphData; - - graphHeight: number; - graphWidth: number; -} - -export const GraphArea = (props: GraphAreaProps) => { - const graphTypes = [ - "random", - "force", - "circular", - "radial", - "mds", - "dagre", - "concentric", - "grid", - ]; - - const graphRef = useRef(null); - const [graph, setGraph] = useState(null); - const [graphDirected, setGraphDirected] = useState(false); - - useEffect(() => { - if (!graph && graphRef.current && graphRef.current.children.length == 0) { - setGraph( - new G6.Graph({ - container: graphRef.current, - width: 500, - height: 500, - modes: { - default: ["drag-canvas", "zoom-canvas", "drag-node"], - }, - defaultEdge: { - style: { - startArrow: true, - endArrow: true, - }, - }, - }) - ); - } - - if (graph && props.graphData) { - graph.data(props.graphData); - graph.render(); - updateGraphDirected(props.graphData.directed == "1" ?? false); - } - - function updateGraphDirected(directed: boolean) { - if (!graph || graphDirected == directed) return; - setGraphDirected(directed); - - if (directed) { - for (let edge of graph.getEdges()) { - graph.update(edge, { - style: { - startArrow: false, - endArrow: true, - }, - }); - } - } else { - for (let edge of graph.getEdges()) { - graph.update(edge, { - style: { - startArrow: true, - endArrow: true, - }, - }); - } - } - } - }, [graph, graphDirected, props.graphData]); - - return ( - - - - - -
- -
-
- ); -}; diff --git a/src/pages/solve/MaxCut.tsx b/src/pages/solve/MaxCut.tsx index dbaae80..cb428c3 100644 --- a/src/pages/solve/MaxCut.tsx +++ b/src/pages/solve/MaxCut.tsx @@ -11,21 +11,13 @@ import { NextPage } from "next"; import { useState } from "react"; import { Container } from "../../components/Container"; import { Layout } from "../../components/layout/Layout"; -import { GraphArea } from "../../components/solvers/Graph/GraphArea"; +import { GMLGraphView } from "../../components/solvers/Graph/GMLGraphView"; import { ProgressHandler } from "../../components/solvers/ProgressHandler"; import { TextInputMask } from "../../components/solvers/TextInputMask"; -import { parseGML } from "../../converter/graph/gml/GmlParser"; const MaxCut: NextPage = () => { - const [graphData, setGraphData] = useState(null); const [graphString, setGraphString] = useState(""); - function change(x: string): void { - setGraphString(x); - let data = parseGML(x); - setGraphData(data); - } - return ( MaxCut Solver @@ -47,14 +39,12 @@ const MaxCut: NextPage = () => {
-
diff --git a/yarn.lock b/yarn.lock index b86008f..13cb09c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,267 +20,6 @@ "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" -"@ant-design/colors@^4.0.5": - version "4.0.5" - resolved "https://registry.yarnpkg.com/@ant-design/colors/-/colors-4.0.5.tgz#d7d100d7545cca8f624954604a6892fc48ba5aae" - integrity sha512-3mnuX2prnWOWvpFTS2WH2LoouWlOgtnIpc6IarWN6GOzzLF8dW/U8UctuvIPhoboETehZfJ61XP+CGakBEPJ3Q== - dependencies: - tinycolor2 "^1.4.1" - -"@antv/algorithm@^0.1.8": - version "0.1.25" - resolved "https://registry.yarnpkg.com/@antv/algorithm/-/algorithm-0.1.25.tgz#1aa88d4909138a2900935f0a9a3cb97bc8db65d2" - integrity sha512-TGwPyFoAu4+iEJd0y1l0gHdBXCbUj8a4gR7P3GzZRfEfRnWfk+gswApzOSTd7c6HP402JOEF64PAJQKxQgSPSQ== - dependencies: - "@antv/util" "^2.0.13" - tslib "^2.0.0" - -"@antv/dom-util@^2.0.1", "@antv/dom-util@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@antv/dom-util/-/dom-util-2.0.4.tgz#b09b56c56fec42896fc856edad56b595b47ab514" - integrity sha512-2shXUl504fKwt82T3GkuT4Uoc6p9qjCKnJ8gXGLSW4T1W37dqf9AV28aCfoVPHp2BUXpSsB+PAJX2rG/jLHsLQ== - dependencies: - tslib "^2.0.3" - -"@antv/event-emitter@^0.1.1", "@antv/event-emitter@~0.1.0": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@antv/event-emitter/-/event-emitter-0.1.3.tgz#3e06323b9dcd55a3241ddc7c5458cfabd2095164" - integrity sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg== - -"@antv/g-base@^0.5.1", "@antv/g-base@^0.5.12": - version "0.5.15" - resolved "https://registry.yarnpkg.com/@antv/g-base/-/g-base-0.5.15.tgz#4d4174c956ba652a4b55c4f6a2c28aaa92e7738d" - integrity sha512-QOtq50QpnKez9J75/Z8j2yZ7QDQdk8R8mVQJiHtaEO5eI7DM4ZbrsWff/Ew26JYmPWdq7nbRuARMAD4PX9uuLA== - dependencies: - "@antv/event-emitter" "^0.1.1" - "@antv/g-math" "^0.1.9" - "@antv/matrix-util" "^3.1.0-beta.1" - "@antv/path-util" "~2.0.5" - "@antv/util" "~2.0.13" - "@types/d3-timer" "^2.0.0" - d3-ease "^1.0.5" - d3-interpolate "^3.0.1" - d3-timer "^1.0.9" - detect-browser "^5.1.0" - tslib "^2.0.3" - -"@antv/g-canvas@^0.5.2": - version "0.5.14" - resolved "https://registry.yarnpkg.com/@antv/g-canvas/-/g-canvas-0.5.14.tgz#099668cb65d9c89dc2fc1000313c18298dcf8a13" - integrity sha512-IUGLEMIMAUYgaBMT8h3FTmYQYz7sjQkKWwh6Psqx+UPK86fySa+G8fMRrh1EqAL07jVB+GRnn6Ym+3FoFUgeFg== - dependencies: - "@antv/g-base" "^0.5.12" - "@antv/g-math" "^0.1.9" - "@antv/matrix-util" "^3.1.0-beta.1" - "@antv/path-util" "~2.0.5" - "@antv/util" "~2.0.0" - gl-matrix "^3.0.0" - tslib "^2.0.3" - -"@antv/g-math@^0.1.1", "@antv/g-math@^0.1.9": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@antv/g-math/-/g-math-0.1.9.tgz#1f981b9aebf5c024f284389aa3e5cba8cefa1f28" - integrity sha512-KHMSfPfZ5XHM1PZnG42Q2gxXfOitYveNTA7L61lR6mhZ8Y/aExsYmHqaKBsSarU0z+6WLrl9C07PQJZaw0uljQ== - dependencies: - "@antv/util" "~2.0.0" - gl-matrix "^3.0.0" - -"@antv/g-svg@^0.5.1", "@antv/g-svg@^0.5.2": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@antv/g-svg/-/g-svg-0.5.7.tgz#d63db5f8590a5f3ceab097c183ec80ed143f0a50" - integrity sha512-jUbWoPgr4YNsOat2Y/rGAouNQYGpw4R0cvlN0YafwOyacFFYy2zC8RslNd6KkPhhR3XHNSqJOuCYZj/YmLUwYw== - dependencies: - "@antv/g-base" "^0.5.12" - "@antv/g-math" "^0.1.9" - "@antv/util" "~2.0.0" - detect-browser "^5.0.0" - tslib "^2.0.3" - -"@antv/g-webgpu-core@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@antv/g-webgpu-core/-/g-webgpu-core-0.7.2.tgz#65ef2a1253e319ffb2ed568d222b8313635d6677" - integrity sha512-xUMmop7f3Rs34zFYKXLqHhDR1CQTeDl/7vI7Sn3X/73BqJc3X3HIIRvm83Fg2CjVACaOzw4WeLRXNaOCp9fz9w== - dependencies: - eventemitter3 "^4.0.0" - gl-matrix "^3.1.0" - lodash "^4.17.15" - probe.gl "^3.1.1" - -"@antv/g-webgpu-engine@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@antv/g-webgpu-engine/-/g-webgpu-engine-0.7.2.tgz#64631f24930f449ef41ffff6429d4fb9519eca23" - integrity sha512-lx8Y93IW2cnJvdoDRKyMmTdYqSC1pOmF0nyG3PGGyA0NI9vBYVgO0KTF6hkyWjdTWVq7XDZyf/h8CJridLh3lg== - dependencies: - "@antv/g-webgpu-core" "^0.7.2" - gl-matrix "^3.1.0" - lodash "^4.17.15" - regl "^1.3.11" - -"@antv/g-webgpu@0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@antv/g-webgpu/-/g-webgpu-0.7.2.tgz#39ba2123816322fec9563236211ad8ab1e40924d" - integrity sha512-kw+oYGsdvj5qeUfy5DPb/jztZBV+2fmqBd3Vv8NlKatfBmv8AirYX/CCW74AUSdWm99rEiLyxFB1VdRZ6b/wnQ== - dependencies: - "@antv/g-webgpu-core" "^0.7.2" - "@antv/g-webgpu-engine" "^0.7.2" - gl-matrix "^3.1.0" - gl-vec2 "^1.3.0" - lodash "^4.17.15" - -"@antv/g6-core@0.8.19": - version "0.8.19" - resolved "https://registry.yarnpkg.com/@antv/g6-core/-/g6-core-0.8.19.tgz#ad4f33ba59b4b2aa364af8a951e563cd02a8c310" - integrity sha512-JBpmdMuHOLCOvoF3EReCH3w1jTsZ/GL6Kl0aYNGEAlAeKVTT3iqHgXUAim3G49RKELXRfhit+IsYTRN1jboF9A== - dependencies: - "@antv/algorithm" "^0.1.8" - "@antv/dom-util" "^2.0.1" - "@antv/event-emitter" "~0.1.0" - "@antv/g-base" "^0.5.1" - "@antv/g-math" "^0.1.1" - "@antv/matrix-util" "^3.1.0-beta.3" - "@antv/path-util" "^2.0.3" - "@antv/util" "~2.0.5" - ml-matrix "^6.5.0" - tslib "^2.1.0" - -"@antv/g6-element@0.8.19": - version "0.8.19" - resolved "https://registry.yarnpkg.com/@antv/g6-element/-/g6-element-0.8.19.tgz#1b17bfb3856d6703ea7ec8ebda42fad222838e12" - integrity sha512-q22KlMHlFjR+hUMeHJyDt1NDsuMB6YBGSV/AgJRcaoatNQRHqdUBDQuwoNhw40tjVs4OJQsWwJ/6PI0zlA3LwQ== - dependencies: - "@antv/g-base" "^0.5.1" - "@antv/g6-core" "0.8.19" - "@antv/util" "~2.0.5" - -"@antv/g6-pc@0.8.19": - version "0.8.19" - resolved "https://registry.yarnpkg.com/@antv/g6-pc/-/g6-pc-0.8.19.tgz#120f666c33fade469e612fa55e0005f1c3d03905" - integrity sha512-dxaFGQlT7NGQ6kWweBYaBd0hwW7OQtQfT6DB/r431gP02vbXkl3C2Nnjw1rtOeSVBI7PcWOX9T9yHZglM0GsHg== - dependencies: - "@ant-design/colors" "^4.0.5" - "@antv/algorithm" "^0.1.8" - "@antv/dom-util" "^2.0.1" - "@antv/event-emitter" "~0.1.0" - "@antv/g-base" "^0.5.1" - "@antv/g-canvas" "^0.5.2" - "@antv/g-math" "^0.1.1" - "@antv/g-svg" "^0.5.1" - "@antv/g6-core" "0.8.19" - "@antv/g6-element" "0.8.19" - "@antv/g6-plugin" "0.8.19" - "@antv/hierarchy" "^0.6.10" - "@antv/layout" "^0.3.0" - "@antv/matrix-util" "^3.1.0-beta.3" - "@antv/path-util" "^2.0.3" - "@antv/util" "~2.0.5" - color "^3.1.3" - d3-force "^2.0.1" - dagre "^0.8.5" - insert-css "^2.0.0" - ml-matrix "^6.5.0" - -"@antv/g6-plugin@0.8.19": - version "0.8.19" - resolved "https://registry.yarnpkg.com/@antv/g6-plugin/-/g6-plugin-0.8.19.tgz#f9f01fe83451f7694a61b2be52406a639a1ac91c" - integrity sha512-vPDko7gdrszRp/2OlYwxyWOAFiW/mn/I798s689KsC7/e/YSOuq94TdmdZHulNLOlT6zqqYwIP3JQG6YxdyPTQ== - dependencies: - "@antv/dom-util" "^2.0.2" - "@antv/g-base" "^0.5.1" - "@antv/g-canvas" "^0.5.2" - "@antv/g-svg" "^0.5.2" - "@antv/g6-core" "0.8.19" - "@antv/g6-element" "0.8.19" - "@antv/matrix-util" "^3.1.0-beta.3" - "@antv/path-util" "^2.0.3" - "@antv/scale" "^0.3.4" - "@antv/util" "^2.0.9" - insert-css "^2.0.0" - -"@antv/g6@^4.7.16": - version "4.8.19" - resolved "https://registry.yarnpkg.com/@antv/g6/-/g6-4.8.19.tgz#1ba580f88b38ff7440405f1f0b670b3baa0f5a9d" - integrity sha512-VBs45lAnxjjfDu6CTDdb/nMJRYcwAG4K5MWZiVf+vq4789rtElm7q2GKV5zVChadtrqcUt/dCjKp56zR5/foaA== - dependencies: - "@antv/g6-pc" "0.8.19" - -"@antv/graphlib@^1.0.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@antv/graphlib/-/graphlib-1.2.0.tgz#c88f97d4b3456d261480a1207ffc4fbc5d7dcf0d" - integrity sha512-hhJOMThec51nU4Fe5p/viLlNIL71uDEgYFzKPajWjr2715SFG1HAgiP6AVylIeqBcAZ04u3Lw7usjl/TuI5RuQ== - -"@antv/hierarchy@^0.6.10": - version "0.6.11" - resolved "https://registry.yarnpkg.com/@antv/hierarchy/-/hierarchy-0.6.11.tgz#244d6820347170e0107f3611802d1e5bb089ca7a" - integrity sha512-RJVhEMCuu4vj+Dt25lXIiNdd7jaqm/fqWGYikiELha4S5tnzdJoTUaUvvpfWlxLx4B0RsS9XRwBs1bOKN71TKg== - dependencies: - "@antv/util" "^2.0.7" - -"@antv/layout@^0.3.0": - version "0.3.23" - resolved "https://registry.yarnpkg.com/@antv/layout/-/layout-0.3.23.tgz#00e5f47cf29841808afb233f0585565d793ef2b7" - integrity sha512-F/CyfQuc1WSgCVemX0jA3pE3XuDRbDJmMueY1cL8WgL6nhdzm3/jg5UPamwbBVnhLk+rzNUDYdEIyX+RJbpcMA== - dependencies: - "@antv/g-webgpu" "0.7.2" - "@antv/graphlib" "^1.0.0" - "@antv/util" "^3.3.2" - d3-force "^2.1.1" - d3-quadtree "^2.0.0" - dagre-compound "^0.0.11" - ml-matrix "^6.5.0" - -"@antv/matrix-util@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@antv/matrix-util/-/matrix-util-3.0.4.tgz#ea13f158aa2fb4ba2fb8d6b6b561ec467ea3ac20" - integrity sha512-BAPyu6dUliHcQ7fm9hZSGKqkwcjEDVLVAstlHULLvcMZvANHeLXgHEgV7JqcAV/GIhIz8aZChIlzM1ZboiXpYQ== - dependencies: - "@antv/util" "^2.0.9" - gl-matrix "^3.3.0" - tslib "^2.0.3" - -"@antv/matrix-util@^3.1.0-beta.1", "@antv/matrix-util@^3.1.0-beta.3": - version "3.1.0-beta.3" - resolved "https://registry.yarnpkg.com/@antv/matrix-util/-/matrix-util-3.1.0-beta.3.tgz#e061de8fa7be04605a155c69cc5ce9082eedddee" - integrity sha512-W2R6Za3A6CmG51Y/4jZUM/tFgYSq7vTqJL1VD9dKrvwxS4sE0ZcXINtkp55CdyBwJ6Cwm8pfoRpnD4FnHahN0A== - dependencies: - "@antv/util" "^2.0.9" - gl-matrix "^3.4.3" - tslib "^2.0.3" - -"@antv/path-util@^2.0.3", "@antv/path-util@~2.0.5": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@antv/path-util/-/path-util-2.0.15.tgz#a6f691dfc8b7bce5be7f0aabb5bd614964325631" - integrity sha512-R2VLZ5C8PLPtr3VciNyxtjKqJ0XlANzpFb5sE9GE61UQqSRuSVSzIakMxjEPrpqbgc+s+y8i+fmc89Snu7qbNw== - dependencies: - "@antv/matrix-util" "^3.0.4" - "@antv/util" "^2.0.9" - tslib "^2.0.3" - -"@antv/scale@^0.3.4": - version "0.3.18" - resolved "https://registry.yarnpkg.com/@antv/scale/-/scale-0.3.18.tgz#b911f431b3e0b9547b6a65f66d0d3fa295b5ef32" - integrity sha512-GHwE6Lo7S/Q5fgaLPaCsW+CH+3zl4aXpnN1skOiEY0Ue9/u+s2EySv6aDXYkAqs//i0uilMDD/0/4n8caX9U9w== - dependencies: - "@antv/util" "~2.0.3" - fecha "~4.2.0" - tslib "^2.0.0" - -"@antv/util@^2.0.13", "@antv/util@^2.0.7", "@antv/util@^2.0.9", "@antv/util@~2.0.0", "@antv/util@~2.0.13", "@antv/util@~2.0.3", "@antv/util@~2.0.5": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@antv/util/-/util-2.0.17.tgz#e8ef42aca7892815b229269f3dd10c6b3c7597a9" - integrity sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q== - dependencies: - csstype "^3.0.8" - tslib "^2.0.3" - -"@antv/util@^3.3.2": - version "3.3.2" - resolved "https://registry.yarnpkg.com/@antv/util/-/util-3.3.2.tgz#6238226c9e2e4a7ba2f0c0821e19ee10844931f1" - integrity sha512-uvyQxEOugdJs/FVlpz/+8pKxn70z8jEVydPqv+LI62cpIF7YDjVnMfNIsoMqwEoTzPUJ9TJalyLqZhT5rYez0w== - dependencies: - fast-deep-equal "^3.1.3" - gl-matrix "^3.3.0" - tslib "^2.3.1" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" @@ -1952,27 +1691,71 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@probe.gl/env@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@probe.gl/env/-/env-3.6.0.tgz#33343fd9041a14d21374c1911826d4a2f9d9a35d" - integrity sha512-4tTZYUg/8BICC3Yyb9rOeoKeijKbZHRXBEKObrfPmX4sQmYB15ZOUpoVBhAyJkOYVAM8EkPci6Uw5dLCwx2BEQ== - dependencies: - "@babel/runtime" "^7.0.0" - -"@probe.gl/log@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@probe.gl/log/-/log-3.6.0.tgz#c645bfd22b4769dc65161caa17f13bd2b231e413" - integrity sha512-hjpyenpEvOdowgZ1qMeCJxfRD4JkKdlXz0RC14m42Un62NtOT+GpWyKA4LssT0+xyLULCByRAtG2fzZorpIAcA== - dependencies: - "@babel/runtime" "^7.0.0" - "@probe.gl/env" "3.6.0" - -"@probe.gl/stats@3.6.0": - version "3.6.0" - resolved "https://registry.yarnpkg.com/@probe.gl/stats/-/stats-3.6.0.tgz#a1bb12860fa6f40b9c028f9eb575d7ada0b4dbdd" - integrity sha512-JdALQXB44OP4kUBN/UrQgzbJe4qokbVF4Y8lkIA8iVCFnjVowWIgkD/z/0QO65yELT54tTrtepw1jScjKB+rhQ== - dependencies: - "@babel/runtime" "^7.0.0" +"@reactflow/background@11.3.8": + version "11.3.8" + resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.8.tgz#367a21c3fd9204ae84cca2ea6d98cd0d142e57d6" + integrity sha512-U4aI54F7PwqgYI0Knv72QFRI/wXeipPmIYAlDsA0j51+tlPxs3Nk2z7G1/4pC11GxEZkgQVfcIXro4l1Fk+bIQ== + dependencies: + "@reactflow/core" "11.10.3" + classcat "^5.0.3" + zustand "^4.4.1" + +"@reactflow/controls@11.2.8": + version "11.2.8" + resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.2.8.tgz#70d17eecc1c5a3be4aea371704426bd76b7892a7" + integrity sha512-Y9YVx38sRjYtbPsI/xa+B1FGN73FV1GqqajlmGfrc1TmqhJaX+gaMXMvVazT/N5haK1hMJvOApUTLQ2V/5Rdbg== + dependencies: + "@reactflow/core" "11.10.3" + classcat "^5.0.3" + zustand "^4.4.1" + +"@reactflow/core@11.10.3": + version "11.10.3" + resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.10.3.tgz#653280dd232e31a73b26cffb892fd02404df3c46" + integrity sha512-nV3nep4fjBy3h8mYSnJcclGcQtj8fkUBmNkEwCZCK4ps+n3HNkXFB0BRisSnQz6GRQlYboSsi0cThEl3KdNITw== + dependencies: + "@types/d3" "^7.4.0" + "@types/d3-drag" "^3.0.1" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.4.1" + +"@reactflow/minimap@11.7.8": + version "11.7.8" + resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.7.8.tgz#9d66c79f08b20c2b066b76a45ce9b7fb8f693617" + integrity sha512-MwyP5q3VomC91Dhd4P6YcxEfnjDbREGYV6rRxbSJSTHiG0x7ETQCcPelYDGy7JvQej77Pa2yJ4g0FDwP7CsQEA== + dependencies: + "@reactflow/core" "11.10.3" + "@types/d3-selection" "^3.0.3" + "@types/d3-zoom" "^3.0.1" + classcat "^5.0.3" + d3-selection "^3.0.0" + d3-zoom "^3.0.0" + zustand "^4.4.1" + +"@reactflow/node-resizer@2.2.8": + version "2.2.8" + resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.2.8.tgz#233713f8ca3371b3dfbe5a4c373309dd9735c1f6" + integrity sha512-u/EXLpvOfAmq1sGoPYwoX4gi0PnCi0mH3eHVClHNc8JQD0WCqcV1UeVV7H3PF+1SGhhg/aOv/vPG1PcQ5fu4jQ== + dependencies: + "@reactflow/core" "11.10.3" + classcat "^5.0.4" + d3-drag "^3.0.0" + d3-selection "^3.0.0" + zustand "^4.4.1" + +"@reactflow/node-toolbar@1.3.8": + version "1.3.8" + resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.3.8.tgz#b8b1367bce61c5be8d147b2d41408558629194b9" + integrity sha512-cfvlTPeJa/ciQTosx2bGrjHT8K/UL9kznpvpOzeZFnJm5UQXmbwAYt4Vo6GfkD51mORcIL7ujQJvB9ym3ZI9KA== + dependencies: + "@reactflow/core" "11.10.3" + classcat "^5.0.3" + zustand "^4.4.1" "@rushstack/eslint-patch@^1.1.3": version "1.3.2" @@ -2086,10 +1869,220 @@ dependencies: "@babel/types" "^7.20.7" -"@types/d3-timer@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-2.0.1.tgz#ffb6620d290624f3726aa362c0c8a4b44c8d7200" - integrity sha512-TF8aoF5cHcLO7W7403blM7L1T+6NF3XMyN3fxyUolq2uOcFeicG/khQg/dGxiCJWoAcmYulYN7LYSRKO54IXaA== +"@types/d3-array@*": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-axis@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795" + integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-brush@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c" + integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-chord@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d" + integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-contour@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231" + integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== + dependencies: + "@types/d3-array" "*" + "@types/geojson" "*" + +"@types/d3-delaunay@*": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1" + integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== + +"@types/d3-dispatch@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7" + integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== + +"@types/d3-drag@*", "@types/d3-drag@^3.0.1": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02" + integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-dsv@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17" + integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== + +"@types/d3-ease@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-fetch@*": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980" + integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== + dependencies: + "@types/d3-dsv" "*" + +"@types/d3-force@*": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29" + integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA== + +"@types/d3-format@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90" + integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== + +"@types/d3-geo@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440" + integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== + dependencies: + "@types/geojson" "*" + +"@types/d3-hierarchy@*": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz#8d3638df273ec90da34b3ac89d8784c59708cb0d" + integrity sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw== + +"@types/d3-interpolate@*": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.0.2.tgz#4327f4a05d475cf9be46a93fc2e0f8d23380805a" + integrity sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA== + +"@types/d3-polygon@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c" + integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== + +"@types/d3-quadtree@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f" + integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== + +"@types/d3-random@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb" + integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== + +"@types/d3-scale-chromatic@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644" + integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw== + +"@types/d3-scale@*": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-selection@*", "@types/d3-selection@^3.0.3": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" + integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== + +"@types/d3-shape@*": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time-format@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2" + integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== + +"@types/d3-time@*": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@*": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/d3-transition@*": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f" + integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ== + dependencies: + "@types/d3-selection" "*" + +"@types/d3-zoom@*", "@types/d3-zoom@^3.0.1": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b" + integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== + dependencies: + "@types/d3-interpolate" "*" + "@types/d3-selection" "*" + +"@types/d3@^7.4.0": + version "7.4.3" + resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2" + integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== + dependencies: + "@types/d3-array" "*" + "@types/d3-axis" "*" + "@types/d3-brush" "*" + "@types/d3-chord" "*" + "@types/d3-color" "*" + "@types/d3-contour" "*" + "@types/d3-delaunay" "*" + "@types/d3-dispatch" "*" + "@types/d3-drag" "*" + "@types/d3-dsv" "*" + "@types/d3-ease" "*" + "@types/d3-fetch" "*" + "@types/d3-force" "*" + "@types/d3-format" "*" + "@types/d3-geo" "*" + "@types/d3-hierarchy" "*" + "@types/d3-interpolate" "*" + "@types/d3-path" "*" + "@types/d3-polygon" "*" + "@types/d3-quadtree" "*" + "@types/d3-random" "*" + "@types/d3-scale" "*" + "@types/d3-scale-chromatic" "*" + "@types/d3-selection" "*" + "@types/d3-shape" "*" + "@types/d3-time" "*" + "@types/d3-time-format" "*" + "@types/d3-timer" "*" + "@types/d3-transition" "*" + "@types/d3-zoom" "*" + +"@types/geojson@*": + version "7946.0.13" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.13.tgz#e6e77ea9ecf36564980a861e24e62a095988775e" + integrity sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ== "@types/graceful-fs@^4.1.3": version "4.1.6" @@ -2679,6 +2672,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +classcat@^5.0.3, classcat@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.4.tgz#e12d1dfe6df6427f260f03b80dc63571a5107ba6" + integrity sha512-sbpkOw6z413p+HDGcBENe498WM9woqWHiJxCq7nvmxe9WmrUmqfAcxpIwAiMtM5Q3AhYkzXcNQHqsWq0mND51g== + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -2698,7 +2696,7 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== -color-convert@^1.9.0, color-convert@^1.9.3: +color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -2717,32 +2715,16 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@^1.0.0, color-name@~1.1.4: +color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - color2k@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/color2k/-/color2k-2.0.2.tgz#ac2b4aea11c822a6bcb70c768b5a289f4fffcebb" integrity sha512-kJhwH5nAwb34tmyuqq/lgjEKzlFXn1U99NlnB6Ws4qVaERcRUYeYP1cBw6BJ4vxaWStAUEef4WMr7WjOCnBt8w== -color@^3.1.3: - version "3.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -2826,7 +2808,7 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.11, csstype@^3.0.2, csstype@^3.0.8: +csstype@^3.0.11, csstype@^3.0.2: version "3.1.2" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== @@ -2836,59 +2818,67 @@ csstype@^3.0.11, csstype@^3.0.2, csstype@^3.0.8: resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-2.0.0.tgz#8d625cab42ed9b8f601a1760a389f7ea9189d62e" integrity sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ== -"d3-dispatch@1 - 2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-2.0.0.tgz#8a18e16f76dd3fcaef42163c97b926aa9b55e7cf" - integrity sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA== +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== -d3-ease@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" - integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ== +"d3-dispatch@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" + integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== -d3-force@^2.0.1, d3-force@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/d3-force/-/d3-force-2.1.1.tgz#f20ccbf1e6c9e80add1926f09b51f686a8bc0937" - integrity sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew== +"d3-drag@2 - 3", d3-drag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" + integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== dependencies: - d3-dispatch "1 - 2" - d3-quadtree "1 - 2" - d3-timer "1 - 2" + d3-dispatch "1 - 3" + d3-selection "3" + +"d3-ease@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== -d3-interpolate@2.0.1, d3-interpolate@^3.0.1: +"d3-interpolate@1 - 3": version "2.0.1" resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-2.0.1.tgz#98be499cfb8a3b94d4ff616900501a64abc91163" integrity sha512-c5UhwwTs/yybcmTpAVqwSFl6vrQ8JZJoT5F7xNFK9pymv5C0Ymcc9/LIJHtYIggg/yS9YHw8i8O8tgb9pupjeQ== dependencies: d3-color "1 - 2" -"d3-quadtree@1 - 2", d3-quadtree@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-quadtree/-/d3-quadtree-2.0.0.tgz#edbad045cef88701f6fee3aee8e93fb332d30f9d" - integrity sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw== - -"d3-timer@1 - 2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-2.0.0.tgz#055edb1d170cfe31ab2da8968deee940b56623e6" - integrity sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA== +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" + integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== -d3-timer@^1.0.9: - version "1.0.10" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" - integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== +"d3-timer@1 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== -dagre-compound@^0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/dagre-compound/-/dagre-compound-0.0.11.tgz#8d3d1004d756f420582d29f28c92045375018987" - integrity sha512-UrSgRP9LtOZCYb9e5doolZXpc7xayyszgyOs7uakTK4n4KsLegLVTRRtq01GpQd/iZjYw5fWMapx9ed+c80MAQ== +"d3-transition@2 - 3": + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" + integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== + dependencies: + d3-color "1 - 3" + d3-dispatch "1 - 3" + d3-ease "1 - 3" + d3-interpolate "1 - 3" + d3-timer "1 - 3" -dagre@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" - integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw== +d3-zoom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" + integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== dependencies: - graphlib "^2.1.8" - lodash "^4.17.15" + d3-dispatch "1 - 3" + d3-drag "2 - 3" + d3-interpolate "1 - 3" + d3-selection "2 - 3" + d3-transition "2 - 3" damerau-levenshtein@^1.0.8: version "1.0.8" @@ -2980,11 +2970,6 @@ dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -detect-browser@^5.0.0, detect-browser@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" - integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3395,11 +3380,6 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -3472,11 +3452,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fecha@~4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3654,16 +3629,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -gl-matrix@^3.0.0, gl-matrix@^3.1.0, gl-matrix@^3.3.0, gl-matrix@^3.4.3: - version "3.4.3" - resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" - integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== - -gl-vec2@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/gl-vec2/-/gl-vec2-1.3.0.tgz#83d472ed46034de8e09cbc857123fb6c81c51199" - integrity sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A== - glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3750,13 +3715,6 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== -graphlib@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" - integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A== - dependencies: - lodash "^4.17.15" - has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -3900,11 +3858,6 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -insert-css@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/insert-css/-/insert-css-2.0.0.tgz#eb5d1097b7542f4c79ea3060d3aee07d053880f4" - integrity sha512-xGq5ISgcUP5cvGkS2MMFLtPDBtrtQPSFfC6gA6U8wHKqfjTIMZLZNxOItQnoSjdOzlXOLU/yD32RKC4SvjNbtA== - internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" @@ -3921,11 +3874,6 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -is-any-array@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-any-array/-/is-any-array-2.0.1.tgz#9233242a9c098220290aa2ec28f82ca7fa79899e" - integrity sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ== - is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -3948,11 +3896,6 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -4781,37 +4724,6 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -ml-array-max@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/ml-array-max/-/ml-array-max-1.2.4.tgz#2373e2b7e51c8807e456cc0ef364c5863713623b" - integrity sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ== - dependencies: - is-any-array "^2.0.0" - -ml-array-min@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/ml-array-min/-/ml-array-min-1.2.3.tgz#662f027c400105816b849cc3cd786915d0801495" - integrity sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q== - dependencies: - is-any-array "^2.0.0" - -ml-array-rescale@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ml-array-rescale/-/ml-array-rescale-1.3.7.tgz#c4d129320d113a732e62dd963dc1695bba9a5340" - integrity sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ== - dependencies: - is-any-array "^2.0.0" - ml-array-max "^1.2.4" - ml-array-min "^1.2.3" - -ml-matrix@^6.5.0: - version "6.10.4" - resolved "https://registry.yarnpkg.com/ml-matrix/-/ml-matrix-6.10.4.tgz#babee344b20062d9c123aa801c2e5d0d0c7477f6" - integrity sha512-rUyEhfNPzqFsltYwvjNeYQXlYEaVea3KgzcJKJteQUj2WVAGFx9fLNRjtMR9mg2B6bd5buxlmkZmxM4hmO+SKg== - dependencies: - is-any-array "^2.0.0" - ml-array-rescale "^1.3.7" - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -5140,16 +5052,6 @@ prismjs@^1.29.0: resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== -probe.gl@^3.1.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/probe.gl/-/probe.gl-3.6.0.tgz#e816234412b27a70b9be029cb82c8cf96cd72659" - integrity sha512-19JydJWI7+DtR4feV+pu4Mn1I5TAc0xojuxVgZdXIyfmTLfUaFnk4OloWK1bKbPtkgGKLr2lnbnCXmpZEcEp9g== - dependencies: - "@babel/runtime" "^7.0.0" - "@probe.gl/env" "3.6.0" - "@probe.gl/log" "3.6.0" - "@probe.gl/stats" "3.6.0" - prompts@^2.0.1: version "2.4.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" @@ -5289,6 +5191,18 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" +reactflow@^11.10.3: + version "11.10.3" + resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.10.3.tgz#9cf812e16e0a9c57131050860d3ec80afe4097ca" + integrity sha512-DGNrTdkWjZtPOhj5MV8fiWWGkJo+otMVdoJ9l67bQL+Xf+8NkJ4AHmRXoYIxtgcENzwTr5WTAIJlswV9i91cyw== + dependencies: + "@reactflow/background" "11.3.8" + "@reactflow/controls" "11.2.8" + "@reactflow/core" "11.10.3" + "@reactflow/minimap" "11.7.8" + "@reactflow/node-resizer" "2.2.8" + "@reactflow/node-toolbar" "1.3.8" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -5316,11 +5230,6 @@ regexpp@^3.2.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== -regl@^1.3.11: - version "1.7.0" - resolved "https://registry.yarnpkg.com/regl/-/regl-1.7.0.tgz#0d185431044a356bf80e9b775b11b935ef2746d3" - integrity sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -5456,13 +5365,6 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -5675,11 +5577,6 @@ tiny-invariant@^1.0.6: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== -tinycolor2@^1.4.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" - integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== - tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6012,3 +5909,10 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zustand@^4.4.1: + version "4.5.0" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.0.tgz#141354af56f91de378aa6c4b930032ab338f3ef0" + integrity sha512-zlVFqS5TQ21nwijjhJlx4f9iGrXSL0o/+Dpy4txAP22miJ8Ti6c1Ol1RLNN98BMib83lmDH/2KmLwaNXpjrO1A== + dependencies: + use-sync-external-store "1.2.0" From 037e35dfcde8ce382b96cb714244f96527270948 Mon Sep 17 00:00:00 2001 From: nickp Date: Mon, 29 Jan 2024 16:16:34 +0100 Subject: [PATCH 02/92] Add test problem graph using reactflow --- src/api/ToolboxAPI.ts | 46 +++++ src/api/data-model/ProblemGraph.ts | 14 ++ src/api/data-model/SolutionStatus.ts | 1 + .../solvers/Graph/ProblemGraphView.tsx | 159 ++++++++++++++++++ .../solvers/Graph/TestGraphDisplay.tsx | 18 ++ src/pages/index.tsx | 2 + 6 files changed, 240 insertions(+) create mode 100644 src/api/data-model/ProblemGraph.ts create mode 100644 src/components/solvers/Graph/ProblemGraphView.tsx create mode 100644 src/components/solvers/Graph/TestGraphDisplay.tsx diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index 53b3561..698d525 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -1,4 +1,5 @@ import { MetaSolverSetting } from "./data-model/MetaSolverSettings"; +import { ProblemGraph } from "./data-model/ProblemGraph"; import { ProblemSolver } from "./data-model/ProblemSolver"; import { Solution } from "./data-model/Solution"; import { SolutionStatus } from "./data-model/SolutionStatus"; @@ -40,6 +41,17 @@ export async function postProblem( export async function fetchSolvers( problemType: string ): Promise { + // Return mock promise + // todo remove + return new Promise((resolve) => { + setTimeout(() => { + resolve([ + { id: "tsp1", name: "TSP 1" }, + { id: "tsp2", name: "TSP 2" }, + ]); + }, 1000); + }); + return fetch(`${baseUrl()}/solvers/${problemType}`, { method: "GET", headers: { @@ -93,3 +105,37 @@ export async function fetchMetaSolverSettings( return []; }); } + +export async function fetchProblemGraph(): Promise { + return new Promise((resolve) => { + return resolve({ + start: { + problemType: "vehicle-routing", + status: SolutionStatus.SOLVED, + solver: { + id: "vr-solver-1", + name: "VR Solver 1" + }, + solutionId: 1, + subRoutines: [ + { + problemType: "clustering", + status: SolutionStatus.COMPUTING, + solutionId: 2, + solver: { + id: "clustering-solver-1", + name: "Clustering Solver 1" + }, + subRoutines: [] + }, + { + problemType: "travelling-salesman", + status: SolutionStatus.PENDING_USER_ACTION, + solutionId: 3, + subRoutines: [] + } + ] + } + }); + }); +} diff --git a/src/api/data-model/ProblemGraph.ts b/src/api/data-model/ProblemGraph.ts new file mode 100644 index 0000000..02bc846 --- /dev/null +++ b/src/api/data-model/ProblemGraph.ts @@ -0,0 +1,14 @@ +import { ProblemSolver } from "./ProblemSolver"; +import { SolutionStatus } from "./SolutionStatus"; + +export interface ProblemGraph { + start: ProblemNode; +} + +export interface ProblemNode { + problemType: string; + status: SolutionStatus; + solutionId: number; + solver?: ProblemSolver; + subRoutines: ProblemNode[]; +} diff --git a/src/api/data-model/SolutionStatus.ts b/src/api/data-model/SolutionStatus.ts index 8f65e4f..5c0bf3c 100644 --- a/src/api/data-model/SolutionStatus.ts +++ b/src/api/data-model/SolutionStatus.ts @@ -2,4 +2,5 @@ export enum SolutionStatus { INVALID, COMPUTING, SOLVED, + PENDING_USER_ACTION, } diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx new file mode 100644 index 0000000..41f0db0 --- /dev/null +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -0,0 +1,159 @@ +import { useEffect } from "react"; +import { Controls, Edge, Node, ReactFlow, useEdgesState, useNodesState } from "reactflow"; +import { ProblemGraph, ProblemNode } from "../../../api/data-model/ProblemGraph"; +import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; +import { fetchSolvers } from "../../../api/ToolboxAPI"; +import 'reactflow/dist/style.css'; + +export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + + useEffect(() => { + if (!props.graph) return; + + let { nodes, edges } = problemGraphToReactFlow(props.graph); + setEdges(edges); + setNodes(nodes); + + function problemGraphToReactFlow(graph: ProblemGraph): { nodes: Node[]; edges: Edge[] } { + function getNodeId(node: ProblemNode) { + return node.problemType + node.solutionId.toString(); + } + + function getEdgeId(from: ProblemNode, to: ProblemNode) { + return getNodeId(from) + "-" + getNodeId(to); + } + + function getPositionX(levelInfo: LevelInfo | null) { + return levelInfo == null + ? 0 + : levelInfo.index * 200 - levelInfo.count * 50; + } + + function getPositionY(level: number) { + return level * 150; + } + + interface LevelInfo { + index: number; + count: number; + } + + let nodes: Node[] = []; + let edges: Edge[] = []; + + function addNode( + problemNode: ProblemNode, + level: number, + levelInfo: LevelInfo | null + ) { + let id = getNodeId(problemNode); + let label = + problemNode.problemType + + (problemNode.solver == null ? "" : " - " + problemNode.solver?.name); + let position = { + x: getPositionX(levelInfo), + y: getPositionY(level) + }; + let data = { label: label }; + let type: string; + if (level == 0) { + type = "input"; + } else if (problemNode.subRoutines.length == 0) { + type = "output"; + } else { + type = "default"; + } + + let node: Node = { + id: id, + data: data, + position: position, + type: type + }; + + if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) { + node.style = { + background: "teal" + }; + + fetchSolvers(problemNode.problemType).then((solvers) => { + node.type = "default"; + for (let i = 0; i < solvers.length; i++) { + // Add new node manually + let solverId = solvers[i].id.toString(); + nodes.push({ + id: solverId, + data: { label: solvers[i].name }, + position: { + x: + node.position.x + + getPositionX({ index: i, count: solvers.length }), + y: getPositionY(level + 1) + }, + type: "output", + style: { + background: "teal" + } + }); + + edges.push({ + id: id + "-" + solverId, + type: "step", + source: id, + target: solverId + }); + } + node.style = { + background: "grey" + }; + + setNodes(nodes); + setEdges(edges); + }); + } + + nodes.push(node); + + for (let i = 0; i < problemNode.subRoutines.length; i++) { + const subRoutine = problemNode.subRoutines[i]; + edges.push({ + id: getEdgeId(problemNode, subRoutine), + source: id, + target: getNodeId(subRoutine), + animated: subRoutine.status == SolutionStatus.COMPUTING + }); + addNode(subRoutine, level + 1, { + index: i, + count: problemNode.subRoutines.length + }); + } + } + + addNode(graph.start, 0, null); + + return { nodes, edges }; + } + }, [props.graph, setEdges, setNodes]); + + return ( +
+ + + +
+ ); +}; \ No newline at end of file diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx new file mode 100644 index 0000000..98c8452 --- /dev/null +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -0,0 +1,18 @@ +import { useEffect, useState } from "react"; +import { ProblemGraph } from "../../../api/data-model/ProblemGraph"; +import { fetchProblemGraph } from "../../../api/ToolboxAPI"; +import { ProblemGraphView } from "./ProblemGraphView"; + +export const TestGraphDisplay = () => { + let [graph, setGraph] = useState(null); + + useEffect(() => { + fetchProblemGraph() + .then(graph => { + setGraph(graph); + }); + }, []); + + return ( + ); +}; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 54368ba..b895301 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -4,6 +4,7 @@ import Head from "next/head"; import { baseUrl } from "../api/ToolboxAPI"; import { ProblemChooser } from "../components/landing-page/ProblemChooser"; import { Layout } from "../components/layout/Layout"; +import { TestGraphDisplay } from "../components/solvers/Graph/TestGraphDisplay"; const Home: NextPage = () => { return ( @@ -14,6 +15,7 @@ const Home: NextPage = () => { {/* TODO: replace favicon */} + Welcome to the ProvideQ Toolbox! From 382fdede4fa9b6b7a63d866c0bfab734f9971753 Mon Sep 17 00:00:00 2001 From: nickp Date: Mon, 29 Jan 2024 18:18:50 +0100 Subject: [PATCH 03/92] Run prettier --- src/api/ToolboxAPI.ts | 14 +++--- src/components/solvers/Graph/GMLGraphView.tsx | 22 ++++++--- .../solvers/Graph/ProblemGraphView.tsx | 48 ++++++++++++------- .../solvers/Graph/TestGraphDisplay.tsx | 10 ++-- src/pages/index.tsx | 2 +- src/pages/solve/MaxCut.tsx | 4 +- 6 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index 698d525..d1bb18d 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -114,7 +114,7 @@ export async function fetchProblemGraph(): Promise { status: SolutionStatus.SOLVED, solver: { id: "vr-solver-1", - name: "VR Solver 1" + name: "VR Solver 1", }, solutionId: 1, subRoutines: [ @@ -124,18 +124,18 @@ export async function fetchProblemGraph(): Promise { solutionId: 2, solver: { id: "clustering-solver-1", - name: "Clustering Solver 1" + name: "Clustering Solver 1", }, - subRoutines: [] + subRoutines: [], }, { problemType: "travelling-salesman", status: SolutionStatus.PENDING_USER_ACTION, solutionId: 3, - subRoutines: [] - } - ] - } + subRoutines: [], + }, + ], + }, }); }); } diff --git a/src/components/solvers/Graph/GMLGraphView.tsx b/src/components/solvers/Graph/GMLGraphView.tsx index eb37821..fcffd18 100644 --- a/src/components/solvers/Graph/GMLGraphView.tsx +++ b/src/components/solvers/Graph/GMLGraphView.tsx @@ -1,5 +1,12 @@ import { useEffect } from "react"; -import { Controls, Edge, Node, ReactFlow, useEdgesState, useNodesState } from "reactflow"; +import { + Controls, + Edge, + Node, + ReactFlow, + useEdgesState, + useNodesState, +} from "reactflow"; import "reactflow/dist/style.css"; import { parseGML } from "../../../converter/graph/gml/GmlParser"; @@ -52,8 +59,8 @@ export const GMLGraphView = (props: { gml: string }) => { type: getNodeType(node), position: { x: 0, - y: i * 100 - } + y: i * 100, + }, }; return n; }); @@ -63,7 +70,7 @@ export const GMLGraphView = (props: { gml: string }) => { id: i.toString(), source: edge.source, target: edge.target, - label: edge.label + label: edge.label, }; return e; }); @@ -78,7 +85,7 @@ export const GMLGraphView = (props: { gml: string }) => { width: "50vw", height: "50vh", border: "2px solid black", - borderRadius: "15px" + borderRadius: "15px", }} > { edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} - fitView> + fitView + > ); -}; \ No newline at end of file +}; diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 41f0db0..9212301 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -1,9 +1,19 @@ import { useEffect } from "react"; -import { Controls, Edge, Node, ReactFlow, useEdgesState, useNodesState } from "reactflow"; -import { ProblemGraph, ProblemNode } from "../../../api/data-model/ProblemGraph"; +import { + Controls, + Edge, + Node, + ReactFlow, + useEdgesState, + useNodesState, +} from "reactflow"; +import "reactflow/dist/style.css"; +import { + ProblemGraph, + ProblemNode, +} from "../../../api/data-model/ProblemGraph"; import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; import { fetchSolvers } from "../../../api/ToolboxAPI"; -import 'reactflow/dist/style.css'; export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -16,7 +26,10 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { setEdges(edges); setNodes(nodes); - function problemGraphToReactFlow(graph: ProblemGraph): { nodes: Node[]; edges: Edge[] } { + function problemGraphToReactFlow(graph: ProblemGraph): { + nodes: Node[]; + edges: Edge[]; + } { function getNodeId(node: ProblemNode) { return node.problemType + node.solutionId.toString(); } @@ -54,7 +67,7 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { (problemNode.solver == null ? "" : " - " + problemNode.solver?.name); let position = { x: getPositionX(levelInfo), - y: getPositionY(level) + y: getPositionY(level), }; let data = { label: label }; let type: string; @@ -70,12 +83,12 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { id: id, data: data, position: position, - type: type + type: type, }; if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) { node.style = { - background: "teal" + background: "teal", }; fetchSolvers(problemNode.problemType).then((solvers) => { @@ -90,23 +103,23 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { x: node.position.x + getPositionX({ index: i, count: solvers.length }), - y: getPositionY(level + 1) + y: getPositionY(level + 1), }, type: "output", style: { - background: "teal" - } + background: "teal", + }, }); edges.push({ id: id + "-" + solverId, type: "step", source: id, - target: solverId + target: solverId, }); } node.style = { - background: "grey" + background: "grey", }; setNodes(nodes); @@ -122,11 +135,11 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { id: getEdgeId(problemNode, subRoutine), source: id, target: getNodeId(subRoutine), - animated: subRoutine.status == SolutionStatus.COMPUTING + animated: subRoutine.status == SolutionStatus.COMPUTING, }); addNode(subRoutine, level + 1, { index: i, - count: problemNode.subRoutines.length + count: problemNode.subRoutines.length, }); } } @@ -143,7 +156,7 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { width: "50vw", height: "50vh", border: "2px solid black", - borderRadius: "15px" + borderRadius: "15px", }} > { edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} - fitView> + fitView + > ); -}; \ No newline at end of file +}; diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx index 98c8452..4edf438 100644 --- a/src/components/solvers/Graph/TestGraphDisplay.tsx +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -7,12 +7,10 @@ export const TestGraphDisplay = () => { let [graph, setGraph] = useState(null); useEffect(() => { - fetchProblemGraph() - .then(graph => { - setGraph(graph); - }); + fetchProblemGraph().then((graph) => { + setGraph(graph); + }); }, []); - return ( - ); + return ; }; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b895301..ee700db 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -15,7 +15,7 @@ const Home: NextPage = () => { {/* TODO: replace favicon */} - + Welcome to the ProvideQ Toolbox! diff --git a/src/pages/solve/MaxCut.tsx b/src/pages/solve/MaxCut.tsx index cb428c3..ba05edf 100644 --- a/src/pages/solve/MaxCut.tsx +++ b/src/pages/solve/MaxCut.tsx @@ -43,9 +43,7 @@ const MaxCut: NextPage = () => { body={
- +
From 766cec16dcbbeb1f54e141e426234be9b96c5020 Mon Sep 17 00:00:00 2001 From: nickp Date: Tue, 6 Feb 2024 14:20:15 +0100 Subject: [PATCH 04/92] Add some comments --- src/components/solvers/Graph/ProblemGraphView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 9212301..89f1a1c 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -70,6 +70,8 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { y: getPositionY(level), }; let data = { label: label }; + + // Add input/output connect points for edges let type: string; if (level == 0) { type = "input"; @@ -86,6 +88,7 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { type: type, }; + // Load decision nodes when user action is required if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) { node.style = { background: "teal", @@ -129,6 +132,7 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { nodes.push(node); + // Add edge per sub-routine and recursively add sub-routines for (let i = 0; i < problemNode.subRoutines.length; i++) { const subRoutine = problemNode.subRoutines[i]; edges.push({ From 621689ac6fefe0658ebf919878eb3b72c441528e Mon Sep 17 00:00:00 2001 From: nickp Date: Tue, 6 Feb 2024 14:20:25 +0100 Subject: [PATCH 05/92] Remove border around the graph --- src/components/solvers/Graph/GMLGraphView.tsx | 2 -- src/components/solvers/Graph/ProblemGraphView.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/components/solvers/Graph/GMLGraphView.tsx b/src/components/solvers/Graph/GMLGraphView.tsx index fcffd18..55a10eb 100644 --- a/src/components/solvers/Graph/GMLGraphView.tsx +++ b/src/components/solvers/Graph/GMLGraphView.tsx @@ -84,8 +84,6 @@ export const GMLGraphView = (props: { gml: string }) => { style={{ width: "50vw", height: "50vh", - border: "2px solid black", - borderRadius: "15px", }} > { style={{ width: "50vw", height: "50vh", - border: "2px solid black", - borderRadius: "15px", }} > Date: Tue, 6 Feb 2024 14:21:04 +0100 Subject: [PATCH 06/92] Add color function --- .../solvers/Graph/ProblemGraphView.tsx | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 88932d2..b19a308 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -1,3 +1,4 @@ +import { Property } from "csstype"; import { useEffect } from "react"; import { Controls, @@ -14,6 +15,20 @@ import { } from "../../../api/data-model/ProblemGraph"; import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; import { fetchSolvers } from "../../../api/ToolboxAPI"; +import Color = Property.Color; + +function getStatusColor(status: SolutionStatus): Color { + switch (status) { + case SolutionStatus.INVALID: + return "red"; + case SolutionStatus.COMPUTING: + return "cornflowerblue"; + case SolutionStatus.SOLVED: + return "teal"; + case SolutionStatus.PENDING_USER_ACTION: + return "ghostwhite"; + } +} export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -86,14 +101,13 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { data: data, position: position, type: type, + style: { + background: getStatusColor(problemNode.status), + }, }; // Load decision nodes when user action is required if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) { - node.style = { - background: "teal", - }; - fetchSolvers(problemNode.problemType).then((solvers) => { node.type = "default"; for (let i = 0; i < solvers.length; i++) { @@ -110,7 +124,9 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { }, type: "output", style: { - background: "teal", + background: getStatusColor( + SolutionStatus.PENDING_USER_ACTION + ), }, }); @@ -121,9 +137,6 @@ export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { target: solverId, }); } - node.style = { - background: "grey", - }; setNodes(nodes); setEdges(edges); From 61256dbde9a4c2d678c02b7f285355ea56f74d81 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 23 Jul 2024 10:06:22 +0200 Subject: [PATCH 07/92] FIrst pass --- src/api/ToolboxAPI.ts | 157 +++--- src/api/data-model/ProblemDto.ts | 27 + src/api/data-model/ProblemGraph.ts | 14 - src/api/data-model/ProblemSolver.ts | 4 - src/api/data-model/ProblemSolverInfo.ts | 4 + src/api/data-model/ProblemState.ts | 6 + src/api/data-model/Solution.ts | 12 - src/api/data-model/SolutionObject.ts | 26 + src/api/data-model/SolutionStatus.ts | 8 +- src/api/data-model/SolveRequest.ts | 28 - ...finition.ts => SubRoutineDefinitionDto.ts} | 4 +- src/api/data-model/SubRoutineReferenceDto.ts | 6 + src/components/solvers/Graph/DecisionNode.tsx | 98 ++++ .../solvers/Graph/ProblemGraphView.tsx | 516 +++++++++++++----- src/components/solvers/Graph/ProblemNode.tsx | 473 ++++++++++++++++ .../solvers/Graph/SolverProvider.tsx | 42 ++ .../solvers/Graph/TestGraphDisplay.tsx | 21 +- src/components/solvers/ProgressHandler.tsx | 91 --- src/components/solvers/SettingsView.tsx | 14 +- src/components/solvers/SolutionView.tsx | 67 +-- src/components/solvers/SolverPicker.tsx | 142 ----- src/pages/solve/FeatureModelAnomaly.tsx | 6 - src/pages/solve/MaxCut.tsx | 5 - src/pages/solve/QUBO.tsx | 2 - src/pages/solve/SAT.tsx | 5 - 25 files changed, 1180 insertions(+), 598 deletions(-) create mode 100644 src/api/data-model/ProblemDto.ts delete mode 100644 src/api/data-model/ProblemGraph.ts delete mode 100644 src/api/data-model/ProblemSolver.ts create mode 100644 src/api/data-model/ProblemSolverInfo.ts create mode 100644 src/api/data-model/ProblemState.ts delete mode 100644 src/api/data-model/Solution.ts create mode 100644 src/api/data-model/SolutionObject.ts delete mode 100644 src/api/data-model/SolveRequest.ts rename src/api/data-model/{SubRoutineDefinition.ts => SubRoutineDefinitionDto.ts} (85%) create mode 100644 src/api/data-model/SubRoutineReferenceDto.ts create mode 100644 src/components/solvers/Graph/DecisionNode.tsx create mode 100644 src/components/solvers/Graph/ProblemNode.tsx create mode 100644 src/components/solvers/Graph/SolverProvider.tsx delete mode 100644 src/components/solvers/ProgressHandler.tsx delete mode 100644 src/components/solvers/SolverPicker.tsx diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index d1bb18d..096e219 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -1,38 +1,74 @@ -import { MetaSolverSetting } from "./data-model/MetaSolverSettings"; -import { ProblemGraph } from "./data-model/ProblemGraph"; -import { ProblemSolver } from "./data-model/ProblemSolver"; -import { Solution } from "./data-model/Solution"; -import { SolutionStatus } from "./data-model/SolutionStatus"; -import { SolveRequest } from "./data-model/SolveRequest"; -import { SubRoutineDefinition } from "./data-model/SubRoutineDefinition"; +import { getInvalidProblemDto, ProblemDto } from "./data-model/ProblemDto"; +import { ProblemSolverInfo } from "./data-model/ProblemSolverInfo"; +import { ProblemState } from "./data-model/ProblemState"; +import { SubRoutineDefinitionDto } from "./data-model/SubRoutineDefinitionDto"; /** * Getter for the base url of the toolbox API. */ export const baseUrl = () => process.env.NEXT_PUBLIC_API_BASE_URL; +export async function fetchProblem( + problemType: string, + problemId: string +): Promise> { + return fetch(`${baseUrl()}/problems/${problemType}/${problemId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((json) => json as ProblemDto) + .catch((reason) => { + return { + ...getInvalidProblemDto(), + error: `${reason}`, + }; + }); +} + export async function postProblem( problemType: string, - solveRequest: SolveRequest -): Promise { - return fetch(`${baseUrl()}/solve/${problemType}`, { + problemRequest: ProblemDto +): Promise> { + return fetch(`${baseUrl()}/problems/${problemType}`, { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(solveRequest), + body: JSON.stringify(problemRequest), + }) + .then((response) => response.json()) + .then((json) => json as ProblemDto) + .catch((reason) => { + return { + ...problemRequest, + error: `${reason}`, + }; + }); +} + +export async function patchProblem( + problemType: string, + problemId: string, + updateParameters: { input?: any; solverId?: string; state?: ProblemState } +): Promise> { + let x = JSON.stringify(updateParameters); + let url = `${baseUrl()}/problems/${problemType}/${problemId}`; + console.log(url); + return fetch(url, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(updateParameters), }) .then((response) => response.json()) - .then((json) => json as Solution) + .then((json) => json as ProblemDto) .catch((reason) => { return { - id: -1, - status: SolutionStatus.INVALID, - solverName: "", - executionMilliseconds: 0, - solutionData: "", - debugData: "", - metaData: "", + ...getInvalidProblemDto(), error: `${reason}`, }; }); @@ -40,26 +76,15 @@ export async function postProblem( export async function fetchSolvers( problemType: string -): Promise { - // Return mock promise - // todo remove - return new Promise((resolve) => { - setTimeout(() => { - resolve([ - { id: "tsp1", name: "TSP 1" }, - { id: "tsp2", name: "TSP 2" }, - ]); - }, 1000); - }); - +): Promise { return fetch(`${baseUrl()}/solvers/${problemType}`, { method: "GET", headers: { "Content-Type": "application/json", }, }) - .then((response) => response.json()) - .then((json) => json as ProblemSolver[]) + .then(async (response) => response.json()) + .then((json) => json as ProblemSolverInfo[]) .catch((reason) => { console.error(reason); alert(`Could not retrieve solvers of type ${problemType}.`); @@ -70,7 +95,7 @@ export async function fetchSolvers( export async function fetchSubRoutines( problemType: string, solverId: string -): Promise { +): Promise { return fetch( `${baseUrl()}/sub-routines/${problemType}?${new URLSearchParams({ id: solverId, @@ -90,52 +115,18 @@ export async function fetchSubRoutines( }); } -export async function fetchMetaSolverSettings( - problemType: string -): Promise { - return fetch(`${baseUrl()}/meta-solver/settings/${problemType}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - .then((response) => response.json()) - .catch((reason) => { - console.log(reason); - return []; - }); -} - -export async function fetchProblemGraph(): Promise { - return new Promise((resolve) => { - return resolve({ - start: { - problemType: "vehicle-routing", - status: SolutionStatus.SOLVED, - solver: { - id: "vr-solver-1", - name: "VR Solver 1", - }, - solutionId: 1, - subRoutines: [ - { - problemType: "clustering", - status: SolutionStatus.COMPUTING, - solutionId: 2, - solver: { - id: "clustering-solver-1", - name: "Clustering Solver 1", - }, - subRoutines: [], - }, - { - problemType: "travelling-salesman", - status: SolutionStatus.PENDING_USER_ACTION, - solutionId: 3, - subRoutines: [], - }, - ], - }, - }); - }); -} +// export async function fetchMetaSolverSettings( +// problemType: string +// ): Promise { +// return fetch(`${baseUrl()}/meta-solver/settings/${problemType}`, { +// method: "GET", +// headers: { +// "Content-Type": "application/json", +// }, +// }) +// .then((response) => response.json()) +// .catch((reason) => { +// console.log(reason); +// return []; +// }); +// } diff --git a/src/api/data-model/ProblemDto.ts b/src/api/data-model/ProblemDto.ts new file mode 100644 index 0000000..a5f4ec5 --- /dev/null +++ b/src/api/data-model/ProblemDto.ts @@ -0,0 +1,27 @@ +import { ProblemState } from "./ProblemState"; +import { getInvalidSolutionObject, SolutionObject } from "./SolutionObject"; +import { SubRoutineReferenceDto } from "./SubRoutineReferenceDto"; + +export interface ProblemDto { + id: string; + typeId: string; + input: T; + solution: SolutionObject; + state: ProblemState; + solverId?: string; + subProblems: SubRoutineReferenceDto[]; + error: string; +} + +export function getInvalidProblemDto(): ProblemDto { + return { + error: "", + id: "", + input: {} as T, + solution: getInvalidSolutionObject(), + solverId: "", + state: ProblemState.READY_TO_SOLVE, + subProblems: [], + typeId: "", + }; +} diff --git a/src/api/data-model/ProblemGraph.ts b/src/api/data-model/ProblemGraph.ts deleted file mode 100644 index 02bc846..0000000 --- a/src/api/data-model/ProblemGraph.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ProblemSolver } from "./ProblemSolver"; -import { SolutionStatus } from "./SolutionStatus"; - -export interface ProblemGraph { - start: ProblemNode; -} - -export interface ProblemNode { - problemType: string; - status: SolutionStatus; - solutionId: number; - solver?: ProblemSolver; - subRoutines: ProblemNode[]; -} diff --git a/src/api/data-model/ProblemSolver.ts b/src/api/data-model/ProblemSolver.ts deleted file mode 100644 index c682924..0000000 --- a/src/api/data-model/ProblemSolver.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ProblemSolver { - id: string; - name: string; -} diff --git a/src/api/data-model/ProblemSolverInfo.ts b/src/api/data-model/ProblemSolverInfo.ts new file mode 100644 index 0000000..dc83213 --- /dev/null +++ b/src/api/data-model/ProblemSolverInfo.ts @@ -0,0 +1,4 @@ +export interface ProblemSolverInfo { + id: string; + name: string; +} diff --git a/src/api/data-model/ProblemState.ts b/src/api/data-model/ProblemState.ts new file mode 100644 index 0000000..2f98e7f --- /dev/null +++ b/src/api/data-model/ProblemState.ts @@ -0,0 +1,6 @@ +export enum ProblemState { + NEEDS_CONFIGURATION = "NEEDS_CONFIGURATION", + READY_TO_SOLVE = "READY_TO_SOLVE", + SOLVING = "SOLVING", + SOLVED = "SOLVED", +} diff --git a/src/api/data-model/Solution.ts b/src/api/data-model/Solution.ts deleted file mode 100644 index ad9c508..0000000 --- a/src/api/data-model/Solution.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { SolutionStatus } from "./SolutionStatus"; - -export interface Solution { - id: number; - status: SolutionStatus; - solverName: string; - executionMilliseconds: number; - solutionData: any; - metaData: string; - debugData: string; - error: string; -} diff --git a/src/api/data-model/SolutionObject.ts b/src/api/data-model/SolutionObject.ts new file mode 100644 index 0000000..197ce88 --- /dev/null +++ b/src/api/data-model/SolutionObject.ts @@ -0,0 +1,26 @@ +import { SolutionStatus } from "./SolutionStatus"; + +export interface SolutionObject { + /** + * UUID of the solution. + */ + id: string; + status: SolutionStatus; + metaData: string; + solutionData: any; + debugData: string; + solverName: string; + executionMilliseconds: number; +} + +export function getInvalidSolutionObject(): SolutionObject { + return { + id: "", + status: SolutionStatus.INVALID, + metaData: "", + solutionData: undefined, + debugData: "", + solverName: "", + executionMilliseconds: -1, + }; +} diff --git a/src/api/data-model/SolutionStatus.ts b/src/api/data-model/SolutionStatus.ts index 5c0bf3c..70bdb66 100644 --- a/src/api/data-model/SolutionStatus.ts +++ b/src/api/data-model/SolutionStatus.ts @@ -1,6 +1,6 @@ export enum SolutionStatus { - INVALID, - COMPUTING, - SOLVED, - PENDING_USER_ACTION, + INVALID = "INVALID", + COMPUTING = "COMPUTING", + SOLVED = "SOLVED", + ERROR = "ERROR", } diff --git a/src/api/data-model/SolveRequest.ts b/src/api/data-model/SolveRequest.ts deleted file mode 100644 index be08e54..0000000 --- a/src/api/data-model/SolveRequest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { MetaSolverSetting } from "./MetaSolverSettings"; - -export interface SolverChoice { - /** - * If no solver id is provided, the toolbox choose the solver itself via meta solver strategy - */ - requestedSolverId?: string; - /** - * If no solver id is provided, the toolbox choose the solver itself via meta solver strategy - */ - requestedMetaSolverSettings?: MetaSolverSetting[]; - /** - * Map from problem type to SubSolveRequest to allow explicitly requested solvers for a subroutine - */ - requestedSubSolveRequests: SolveMap; -} - -export interface SolveRequest extends SolverChoice { - requestContent: T; -} - -/** - * A SolveMap contains `SolverChoice` data for each problem type used in - * sub-routines of a request. - */ -export type SolveMap = { - [problemTypeId: string]: SolverChoice; -}; diff --git a/src/api/data-model/SubRoutineDefinition.ts b/src/api/data-model/SubRoutineDefinitionDto.ts similarity index 85% rename from src/api/data-model/SubRoutineDefinition.ts rename to src/api/data-model/SubRoutineDefinitionDto.ts index 8afafbc..109fbde 100644 --- a/src/api/data-model/SubRoutineDefinition.ts +++ b/src/api/data-model/SubRoutineDefinitionDto.ts @@ -2,12 +2,12 @@ * A sub-routine definition describes which problem type needs to be solved by a * sub-routine and why it needs to be solved. */ -export interface SubRoutineDefinition { +export interface SubRoutineDefinitionDto { /** * Identifies the type of problem that needs to be solved with the described * sub-routine. */ - type: string; + typeId: string; /** * Describes why this sub-routine is required to solve a bigger problem. */ diff --git a/src/api/data-model/SubRoutineReferenceDto.ts b/src/api/data-model/SubRoutineReferenceDto.ts new file mode 100644 index 0000000..c6f2b16 --- /dev/null +++ b/src/api/data-model/SubRoutineReferenceDto.ts @@ -0,0 +1,6 @@ +import { SubRoutineDefinitionDto } from "./SubRoutineDefinitionDto"; + +export interface SubRoutineReferenceDto { + subRoutine: SubRoutineDefinitionDto; + subProblemIds: string[]; +} diff --git a/src/components/solvers/Graph/DecisionNode.tsx b/src/components/solvers/Graph/DecisionNode.tsx new file mode 100644 index 0000000..b43247c --- /dev/null +++ b/src/components/solvers/Graph/DecisionNode.tsx @@ -0,0 +1,98 @@ +import { + Box, + Button, + HStack, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverFooter, + PopoverHeader, + PopoverTrigger, + Portal, + Text, + Tooltip, + VStack, +} from "@chakra-ui/react"; +import { useState } from "react"; +import { FaQuestionCircle } from "react-icons/fa"; +import { FaGears } from "react-icons/fa6"; +import { Handle, NodeProps, Position } from "reactflow"; +import { ProblemDto } from "../../../api/data-model/ProblemDto"; +import { ProblemSolverInfo } from "../../../api/data-model/ProblemSolverInfo"; + +export interface DecisionNodeData { + problemSolver: ProblemSolverInfo; + problemDto: ProblemDto; + selectCallback: (problemSolver: ProblemSolverInfo) => void; +} + +export function DecisionNode(props: NodeProps) { + const [selected, setSelected] = useState(false); + + return ( + + + + + +
+ +
+
+ + {props.data.problemSolver.name} + + + + +
+ +
+
+ + + + + + + {props.data.problemSolver.name} + + + + Solves {props.data.problemDto.typeId} + Additional Info: + + + + {props.data.problemSolver.id} + + + +
+
+ { + props.data.selectCallback(props.data.problemSolver); + setSelected(true); + }} + > + Select + +
+
+ ); +} diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index b19a308..004b499 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -1,171 +1,409 @@ -import { Property } from "csstype"; -import { useEffect } from "react"; +import type { XYPosition } from "@reactflow/core/dist/esm/types"; +import { useCallback, useEffect, useState } from "react"; import { Controls, Edge, Node, + NodeTypes, ReactFlow, + ReactFlowInstance, useEdgesState, useNodesState, } from "reactflow"; import "reactflow/dist/style.css"; -import { - ProblemGraph, - ProblemNode, -} from "../../../api/data-model/ProblemGraph"; -import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; -import { fetchSolvers } from "../../../api/ToolboxAPI"; -import Color = Property.Color; - -function getStatusColor(status: SolutionStatus): Color { - switch (status) { - case SolutionStatus.INVALID: - return "red"; - case SolutionStatus.COMPUTING: - return "cornflowerblue"; - case SolutionStatus.SOLVED: - return "teal"; - case SolutionStatus.PENDING_USER_ACTION: - return "ghostwhite"; - } +import { ProblemDto } from "../../../api/data-model/ProblemDto"; +import { ProblemState } from "../../../api/data-model/ProblemState"; +import { SubRoutineDefinitionDto } from "../../../api/data-model/SubRoutineDefinitionDto"; +import { fetchProblem, patchProblem } from "../../../api/ToolboxAPI"; +import { DecisionNode, DecisionNodeData } from "./DecisionNode"; +import { LevelInfo, ProblemNode, ProblemNodeData } from "./ProblemNode"; +import { useSolvers } from "./SolverProvider"; + +interface ProblemEdgeData { + sourceProblemDto: ProblemDto; } -export const ProblemGraphView = (props: { graph: ProblemGraph | null }) => { - const [nodes, setNodes, onNodesChange] = useNodesState([]); - const [edges, setEdges, onEdgesChange] = useEdgesState([]); +export interface ProblemGraphViewProps { + problemType: string; + problemId: string; +} - useEffect(() => { - if (!props.graph) return; - - let { nodes, edges } = problemGraphToReactFlow(props.graph); - setEdges(edges); - setNodes(nodes); - - function problemGraphToReactFlow(graph: ProblemGraph): { - nodes: Node[]; - edges: Edge[]; - } { - function getNodeId(node: ProblemNode) { - return node.problemType + node.solutionId.toString(); - } +/** + * Nodes are identified recursively through their parent node and the sub-routine definition. + * Each node can represent either one problem or a collection of problems, + * but they share the same sub-routine definition and recursive context. + * @param subRoutineDefinitionDto + * @param parentNode + */ +function getNodeId( + subRoutineDefinitionDto: SubRoutineDefinitionDto, + parentNode: Node +): string { + return ( + parentNode.id + + "|" + + subRoutineDefinitionDto.typeId + + "-" + + subRoutineDefinitionDto.description + ); +} - function getEdgeId(from: ProblemNode, to: ProblemNode) { - return getNodeId(from) + "-" + getNodeId(to); - } +/** + * Edge identifiers are determined by the source and target node identifiers. + * @param to The target sub-routine definition + * @param fromId The source node + */ +function getEdgeId(to: SubRoutineDefinitionDto, fromId: Node): string { + return fromId + "->" + getNodeId(to, fromId); +} - function getPositionX(levelInfo: LevelInfo | null) { - return levelInfo == null - ? 0 - : levelInfo.index * 200 - levelInfo.count * 50; - } +function getNodePosition(data: ProblemNodeData): XYPosition { + return { + x: getNodePositionX(data.levelInfo), + y: getNodePositionY(data.level), + }; +} - function getPositionY(level: number) { - return level * 150; - } +function getNodePositionX(levelInfo: LevelInfo | null): number { + return levelInfo == null ? 0 : levelInfo.index * 200 - levelInfo.count * 50; +} - interface LevelInfo { - index: number; - count: number; - } +function getNodePositionY(level: number): number { + return level * 150; +} - let nodes: Node[] = []; - let edges: Edge[] = []; +const nodeTypes: NodeTypes = { + decisionNode: DecisionNode, + problemNode: ProblemNode, +}; +const decisionNodeIdentifier: string = "-decision-node-"; +const decisionEdgeIdentifier: string = "-decision-edge-"; - function addNode( - problemNode: ProblemNode, - level: number, - levelInfo: LevelInfo | null - ) { - let id = getNodeId(problemNode); - let label = - problemNode.problemType + - (problemNode.solver == null ? "" : " - " + problemNode.solver?.name); - let position = { - x: getPositionX(levelInfo), - y: getPositionY(level), - }; - let data = { label: label }; - - // Add input/output connect points for edges - let type: string; - if (level == 0) { - type = "input"; - } else if (problemNode.subRoutines.length == 0) { - type = "output"; - } else { - type = "default"; +export const ProblemGraphView = (props: ProblemGraphViewProps) => { + const [nodes, setNodes, onNodesChange] = useNodesState([]); + const [edges, setEdges, onEdgesChange] = useEdgesState([]); + const [graphInstance, setGraphInstance] = useState< + ReactFlowInstance | undefined + >(undefined); + + const { solvers, getSolvers } = useSolvers(); + + /** + * Node updates are scheduled in order to provide an asynchronous update mechanism. + */ + const [scheduledNodeUpdates, setScheduledNodeUpdates] = useState([]); + + const scheduleNodeUpdate = useCallback((updateNode: Node) => { + setScheduledNodeUpdates((nodes) => nodes.concat(updateNode)); + }, []); + + const addNode = useCallback( + (newNode: Node) => { + setNodes((previousNodes) => { + let existingNode = previousNodes.find((node) => node.id === newNode.id); + if (existingNode) { + return previousNodes; } - let node: Node = { - id: id, - data: data, - position: position, - type: type, - style: { - background: getStatusColor(problemNode.status), - }, - }; + return previousNodes.concat(newNode); + }); + }, + [setNodes] + ); - // Load decision nodes when user action is required - if (problemNode.status == SolutionStatus.PENDING_USER_ACTION) { - fetchSolvers(problemNode.problemType).then((solvers) => { - node.type = "default"; - for (let i = 0; i < solvers.length; i++) { - // Add new node manually - let solverId = solvers[i].id.toString(); - nodes.push({ - id: solverId, - data: { label: solvers[i].name }, - position: { - x: - node.position.x + - getPositionX({ index: i, count: solvers.length }), - y: getPositionY(level + 1), - }, - type: "output", - style: { - background: getStatusColor( - SolutionStatus.PENDING_USER_ACTION - ), - }, - }); - - edges.push({ - id: id + "-" + solverId, - type: "step", - source: id, - target: solverId, - }); - } - - setNodes(nodes); - setEdges(edges); - }); + const addEdge = useCallback( + (newEdge: Edge) => { + setEdges((edges) => { + let existingEdge = edges.find((edge) => edge.id === newEdge.id); + if (existingEdge) { + return edges; } - nodes.push(node); + return edges.concat(newEdge); + }); + }, + [setEdges] + ); - // Add edge per sub-routine and recursively add sub-routines - for (let i = 0; i < problemNode.subRoutines.length; i++) { - const subRoutine = problemNode.subRoutines[i]; - edges.push({ - id: getEdgeId(problemNode, subRoutine), - source: id, - target: getNodeId(subRoutine), - animated: subRoutine.status == SolutionStatus.COMPUTING, - }); - addNode(subRoutine, level + 1, { - index: i, - count: problemNode.subRoutines.length, + const createNode = useCallback( + ( + nodeId: string, + problemDto: ProblemDto, + level: number, + levelInfo: LevelInfo + ) => { + let data: ProblemNodeData = { + problemDto: problemDto, + level: level, + levelInfo: levelInfo, + }; + + let node: Node = { + id: nodeId, + data: data, + position: getNodePosition(data), + type: "problemNode", + }; + + addNode(node); + + return node; + }, + [addNode, scheduleNodeUpdate] + ); + + const updateNode = useCallback( + (node: Node) => { + updateDecisionNodes(node); + + // Update node data + updateNodeData(node.id, (n) => ({ + ...n, + position: getNodePosition(n.data), + type: "problemNode", + })); + + // Update sub-routine nodes + for (let i = 0; i < node.data.problemDto.subProblems.length; i++) { + let subProblem = node.data.problemDto.subProblems[i]; + let subNodeId = getNodeId(subProblem.subRoutine, node); + + let subNode = nodes.find((node) => node.id === subNodeId); + let edgeId = getEdgeId(subProblem.subRoutine, node); + if (subNode === undefined) { + // Create new node + fetchProblem( + subProblem.subRoutine.typeId, + subProblem.subProblemIds[0] + ).then((subProblemDto) => { + let subNodeId = getNodeId(subProblem.subRoutine, node); + + addEdge({ + id: edgeId, + source: node.id, + target: subNodeId, + data: { + sourceProblemDto: node.data.problemDto, + }, + animated: subProblemDto.state === ProblemState.SOLVING, + }); + + let subNode = createNode( + subNodeId, + subProblemDto, + node.data.level + 1, + { + index: i, + count: node.data.problemDto.subProblems.length, + } + ); + scheduleNodeUpdate(subNode); }); + } else { + // Update existing node + scheduleNodeUpdate(subNode); + + const edge = edges.find((edge) => edge.id === edgeId); + if (edge) { + updateEdge(edge); + } } } - addNode(graph.start, 0, null); + function createDecisionNodes(node: Node) { + getSolvers(node.data.problemDto.typeId).then((solvers) => { + for (let i = 0; i < solvers.length; i++) { + let solverId = solvers[i].id.toString(); + let solverNodeId = node.id + decisionNodeIdentifier + solverId; + let decisionNode: Node = { + id: solverNodeId, + data: { + problemSolver: solvers[i], + problemDto: node.data.problemDto, + selectCallback: (problemSolver) => { + // todo update visuals here too + + let edge = edges.find((e) => + e.target.startsWith(node.id + decisionEdgeIdentifier) + ); + if (edge) { + updateEdge(edge); + } + + patchProblem( + node.data.problemDto.typeId, + node.data.problemDto.id, + { + solverId: problemSolver.id, + state: ProblemState.SOLVING, + } + ).then((dto) => + setNodes((previousNodes) => + previousNodes.map((n) => { + if (n.id !== node.id) return n; + + let updatedNode: Node = { + ...n, + data: { + ...n.data, + problemDto: dto, + }, + }; + scheduleNodeUpdate(updatedNode); + + return updatedNode; + }) + ) + ); + }, + }, + position: { + x: + node.position.x + + getNodePositionX({ index: i, count: solvers.length }), + y: getNodePositionY(node.data.level + 1), + }, + type: "decisionNode", + }; - return { nodes, edges }; + addNode(decisionNode); + + addEdge({ + id: node.id + decisionEdgeIdentifier + decisionNode.id, + type: "step", + source: node.id, + target: decisionNode.id, + }); + } + }); + } + + function updateDecisionNodes(node: Node) { + // Load decision nodes when user action is required + if (node.data.problemDto.state == ProblemState.NEEDS_CONFIGURATION) { + const existingDecisionNode = nodes.find((n) => + n.id.startsWith(node.id + decisionNodeIdentifier) + ); + if (existingDecisionNode === undefined) { + createDecisionNodes(node); + } + } else { + removeDecisionNodes(node); + } + } + + function removeDecisionNodes(node: Node) { + setNodes((previousNodes) => + previousNodes.filter((n) => { + let x = n.id.startsWith(node.id + decisionNodeIdentifier); + return !x; + }) + ); + setEdges((edges) => + edges.filter( + (e) => !e.id.startsWith(node.id + decisionEdgeIdentifier) + ) + ); + } + + function updateNodeData( + nodeId: string, + update: (node: Node) => Node + ) { + setNodes((previousNodes) => + previousNodes.map((node) => { + if (node.id !== nodeId) return node; + + return update(node); + }) + ); + } + + function updateEdge(edge: Edge) { + setEdges((edges) => + edges.map((e) => { + if (e.id !== edge.id) return e; + + return { + ...e, + animated: + edge.data?.sourceProblemDto.state === ProblemState.SOLVING, + }; + }) + ); + } + }, + [ + addEdge, + addNode, + createNode, + edges, + nodes, + scheduleNodeUpdate, + setEdges, + setNodes, + ] + ); + + useEffect(() => { + if (scheduledNodeUpdates.length === 0) return; + + for (let scheduledNodeUpdate of scheduledNodeUpdates) { + updateNode(scheduledNodeUpdate); } - }, [props.graph, setEdges, setNodes]); + setScheduledNodeUpdates((nodes) => + nodes.filter((node) => !scheduledNodeUpdates.includes(node)) + ); + }, [scheduledNodeUpdates, updateNode]); + + // Repopulate graph when problem changes + useEffect(() => { + setNodes([]); + setEdges([]); + fetchProblem(props.problemType, props.problemId).then((problemDto) => { + if (problemDto.error) { + console.error(problemDto.error); + return; + } + + // Create root node + let rootNode = createNode(props.problemId, problemDto, 0, { + index: 0, + count: 1, + }); + scheduleNodeUpdate(rootNode); + }); + }, [ + createNode, + props.problemId, + props.problemType, + scheduleNodeUpdate, + setEdges, + setNodes, + ]); + + // Update graph every second + // useEffect(() => { + // const interval = setInterval(() => { + // let rootNode = nodes.find((node) => node.id == props.problemId); + // if (rootNode === undefined) { + // return; + // } + // console.log("update node after one second"); + // + // updateNode(rootNode); + // }, 1000); + // + // return () => clearInterval(interval); + // }, [nodes, props.problemId, updateNode]); + + // Fit view when nodes change + useEffect(() => { + graphInstance?.fitView({ + duration: 500, + nodes: nodes, + }); + }, [graphInstance, nodes]); return (
{ }} > setGraphInstance(reactFlowInstance)} nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} + nodeTypes={nodeTypes} fitView > diff --git a/src/components/solvers/Graph/ProblemNode.tsx b/src/components/solvers/Graph/ProblemNode.tsx new file mode 100644 index 0000000..fda5359 --- /dev/null +++ b/src/components/solvers/Graph/ProblemNode.tsx @@ -0,0 +1,473 @@ +import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, + Box, + Flex, + HStack, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Spinner, + Text, + Tooltip, + useDisclosure, + VStack, +} from "@chakra-ui/react"; +import { Property } from "csstype"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { BsDatabaseFillGear } from "react-icons/bs"; +import { + FaChevronCircleDown, + FaChevronCircleUp, + FaQuestion, + FaQuestionCircle, +} from "react-icons/fa"; +import { GrInProgress } from "react-icons/gr"; +import { ImCheckmark } from "react-icons/im"; +import { MdError } from "react-icons/md"; +import { Handle, NodeProps, Position } from "reactflow"; +import { ProblemDto } from "../../../api/data-model/ProblemDto"; +import { ProblemState } from "../../../api/data-model/ProblemState"; +import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; +import { SubRoutineReferenceDto } from "../../../api/data-model/SubRoutineReferenceDto"; +import { fetchProblem } from "../../../api/ToolboxAPI"; +import { SolutionView } from "../SolutionView"; +import { useSolvers } from "./SolverProvider"; +import Color = Property.Color; + +// Used to determine the horizontal position of the node in the canvas +export interface LevelInfo { + /** + * Index of the node at this level + */ + index: number; + /** + * Total amount of nodes at this level + */ + count: number; +} + +export interface ProblemNodeData { + // Data + problemDto: ProblemDto; + + // Styling info + level: number; + levelInfo: LevelInfo; + + // solve: (problem: ProblemDto) => void; +} + +function getNodeType(data: ProblemNodeData) { + // Add input/output connect points for edges + if (data.problemDto.state === ProblemState.NEEDS_CONFIGURATION) { + if (data.level == 0) { + return "input"; + } else { + return "default"; + } + } else { + if (data.level === 0) { + return "input"; + } else if (data.problemDto.subProblems.length == 0) { + return "output"; + } else { + return "default"; + } + } +} + +function getStatusColor(problemDto: ProblemDto): Color { + switch (problemDto.state) { + case ProblemState.NEEDS_CONFIGURATION: + return "ghostwhite"; + case ProblemState.READY_TO_SOLVE: + return "cornflowerblue"; + case ProblemState.SOLVING: + return "cornflowerblue"; + case ProblemState.SOLVED: + switch (problemDto.solution.status) { + case SolutionStatus.INVALID: + return "orange"; + case SolutionStatus.COMPUTING: + return "cornflowerblue"; + case SolutionStatus.SOLVED: + return "teal"; + case SolutionStatus.ERROR: + return "red"; + } + } +} +function getStatusIcon(problemDto: ProblemDto): JSX.Element { + switch (problemDto.state) { + case ProblemState.NEEDS_CONFIGURATION: + return ; + case ProblemState.READY_TO_SOLVE: + return ; + case ProblemState.SOLVING: + return ; + case ProblemState.SOLVED: + switch (problemDto.solution.status) { + case SolutionStatus.INVALID: + return ; + case SolutionStatus.COMPUTING: + return ; + case SolutionStatus.SOLVED: + return ; + case SolutionStatus.ERROR: + return ; + } + } +} + +export function ProblemNode(props: NodeProps) { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [dropdownOpen, setDropdownOpen] = useState(false); + const [selectedSubProblems, setSelectedSubProblems] = useState([]); + + const nodeType = getNodeType(props.data); + const nodeColor = getStatusColor(props.data.problemDto); + + useEffect(() => {}, [props.data.problemDto.subProblems]); + + const { solvers, getSolvers } = useSolvers(); + + const solverName = solvers[props.data.problemDto.typeId]?.find( + (s) => s.id === props.data.problemDto.solverId + )?.name; + + console.log( + "ProblemNode", + props.data.problemDto.typeId, + props.data.problemDto.id, + props.data.problemDto.state, + props.data.problemDto.subProblems + ); + + return ( + + {(nodeType === "output" || nodeType === "default") && ( + + )} + {(nodeType === "input" || nodeType === "default") && ( + + )} + + +
+ +
+
+ + {props.data.problemDto.typeId} + + {solverName} + + +
+ +
+ + + + + + + {props.data.problemDto.typeId} + + + + + + +
+ + {props.data.problemDto.subProblems.length > 0 && ( + + setDropdownOpen(!dropdownOpen)} + top={0} + left="50%" + transform="translate(-50%, -50%)" + > + {dropdownOpen ? : } + + +
+ {/*// todo: subproblem list should be in the child node instead. so subproblemms for SAT should be listed in the SAT node - EACH SAT NODE should also keep the state of the current subproblem meaning you can see the solution of the subproblem too + SO WE NEED TO PASS THE LIST OF SUBPROBLEMS TO THE CHILD NODE AND HAVE A WAY TO REPRESENT A LIST OF PROBLEM NODES*/} + {dropdownOpen + ? props.data.problemDto.subProblems.map((subRoutine) => ( + + )) + : props.data.problemDto.subProblems.map((subProblem) => ( + + + {subProblem.subProblemIds.length} + {"x "} + {subProblem.subRoutine.typeId} + +
+ {subProblem.subProblemIds.map((subProblemId) => { + // todo extract to component and save subproblemdto state + // let subProblemDto = await fetchProblem( + // subProblem.subRoutine.typeId, + // subProblemId + // ); + return ( + + ); + })} +
+
+ ))} +
+
+ )} +
+ ); +} + +function getHumanReadableState(status: ProblemState) { + switch (status) { + case ProblemState.NEEDS_CONFIGURATION: + return "Needs Configuration"; + case ProblemState.READY_TO_SOLVE: + return "Ready to Solve"; + case ProblemState.SOLVING: + return "Solving"; + case ProblemState.SOLVED: + return "Solved"; + } +} + +export const SubProblemList = (props: { + subRoutineDefinition: SubRoutineReferenceDto; + selectedProblems: string[]; + setSelectedProblems: Dispatch>; +}) => { + const [subProblems, setSubProblems] = useState[]>([]); + const [lastSelectedProblem, setLastSelectedProblem] = useState< + ProblemDto | undefined + >(undefined); + + const { solvers, getSolvers } = useSolvers(); + + // Fetch latest sub problem dtos + useEffect(() => { + Promise.all( + props.subRoutineDefinition.subProblemIds.map((subProblemId) => + fetchProblem(props.subRoutineDefinition.subRoutine.typeId, subProblemId) + ) + ).then((subProblems) => setSubProblems(subProblems)); + }, [props.subRoutineDefinition]); + + function handleClick( + e: React.MouseEvent, + clickedSubProblem: ProblemDto + ) { + setLastSelectedProblem(clickedSubProblem); + if (!lastSelectedProblem) { + props.setSelectedProblems([clickedSubProblem.id]); + return; + } + + let newProblemIds: string[]; + if (e.shiftKey) { + let lastIndex = subProblems.indexOf(lastSelectedProblem); + let currentIndex = subProblems.indexOf(clickedSubProblem); + let start = Math.min(lastIndex, currentIndex); + let end = Math.max(lastIndex, currentIndex); + newProblemIds = subProblems.slice(start, end + 1).map((p) => p.id); + } else { + newProblemIds = [clickedSubProblem.id]; + } + + if (e.ctrlKey) { + props.setSelectedProblems((prev) => [ + ...prev.filter((id) => !newProblemIds.includes(id)), + ...newProblemIds.filter( + (id) => lastSelectedProblem.id == id || !prev.includes(id) + ), + ]); + } else { + props.setSelectedProblems((prev) => + newProblemIds.filter( + (id) => lastSelectedProblem.id == id || !prev.includes(id) + ) + ); + } + } + + return ( + // + // + // + // + // + // + // + // + // + // + // {subProblem.subProblemIds.map((subProblemId, index) => { + // return ( + // + // + // + // + // + // ); + // })} + // + //
SubroutineSolverStatus
+ // {subProblem.subRoutine.typeId} {index} + // No solverNot solved
+ //
+ <> + + + Selected {props.selectedProblems.length} of {subProblems.length} + + + {subProblems.map((subProblem, index) => { + getSolvers(subProblem.typeId); + return ( + id == subProblem.id) + ? "lightgrey" + : "white" + } + onClick={(e) => handleClick(e, subProblem)} + > + {getStatusIcon(subProblem)} + + {props.subRoutineDefinition.subRoutine.typeId} {index + 1} + + + + {subProblem.solverId + ? solvers[subProblem.typeId]?.find( + (s) => s.id == subProblem.solverId + )?.name ?? subProblem.solverId + : "-"} + + + // + // + // + // {props.subRoutineDefinition.subRoutine.typeId} {index} + // + // + // + // - + // + // + // {} + // + // + ); + })} + + + ); +}; + +export const ProblemDetails = (props: { problemDto: ProblemDto }) => { + const { solvers, getSolvers } = useSolvers(); + + // Update solvers in case they are not loaded yet + if (!solvers[props.problemDto.typeId]) getSolvers(props.problemDto.typeId); + + return ( + + Status: {getHumanReadableState(props.problemDto.state)} + + Solver:{" "} + {solvers[props.problemDto.typeId]?.find( + (s) => s.id === props.problemDto.solverId + )?.name ?? "-"} + + {props.problemDto.subProblems.length === 0 ? ( + No subroutines + ) : ( + + Sub Routines: + + {props.problemDto.subProblems.map((subProblem) => ( + +

+ + + {subProblem.subRoutine.typeId} + + + +

+ + {subProblem.subRoutine.description} + +
+ ))} +
+
+ )} + {props.problemDto.solution !== null && ( + + )} +
+ ); +}; diff --git a/src/components/solvers/Graph/SolverProvider.tsx b/src/components/solvers/Graph/SolverProvider.tsx new file mode 100644 index 0000000..552ef7f --- /dev/null +++ b/src/components/solvers/Graph/SolverProvider.tsx @@ -0,0 +1,42 @@ +import { createContext, ReactNode, useContext, useState } from "react"; +import { ProblemSolverInfo } from "../../../api/data-model/ProblemSolverInfo"; +import { fetchSolvers } from "../../../api/ToolboxAPI"; + +interface SolversMap { + [key: string]: ProblemSolverInfo[]; +} + +const SolverContext = createContext<{ + solvers: SolversMap; + getSolvers: (problemTypeId: string) => Promise; +}>({ + solvers: {}, + getSolvers: (x) => Promise.resolve([]), +}); + +export const useSolvers = () => useContext(SolverContext); + +export const SolverProvider = (props: { children: ReactNode }) => { + const [solvers, setSolvers] = useState({}); + + // Function to get solvers, either from cache or by fetching + const getSolvers = async (problemType: string) => { + const cachedSolvers = solvers[problemType]; + if (cachedSolvers) { + return cachedSolvers; + } else { + const fetchedSolvers = await fetchSolvers(problemType); + setSolvers((prevSolvers) => ({ + ...prevSolvers, + [problemType]: fetchedSolvers, + })); + return fetchedSolvers; + } + }; + + return ( + + {props.children} + + ); +}; diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx index 4edf438..6180ec3 100644 --- a/src/components/solvers/Graph/TestGraphDisplay.tsx +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -1,16 +1,13 @@ -import { useEffect, useState } from "react"; -import { ProblemGraph } from "../../../api/data-model/ProblemGraph"; -import { fetchProblemGraph } from "../../../api/ToolboxAPI"; import { ProblemGraphView } from "./ProblemGraphView"; +import { SolverProvider } from "./SolverProvider"; export const TestGraphDisplay = () => { - let [graph, setGraph] = useState(null); - - useEffect(() => { - fetchProblemGraph().then((graph) => { - setGraph(graph); - }); - }, []); - - return ; + return ( + + + + ); }; diff --git a/src/components/solvers/ProgressHandler.tsx b/src/components/solvers/ProgressHandler.tsx deleted file mode 100644 index da5566c..0000000 --- a/src/components/solvers/ProgressHandler.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Box, Center, HStack } from "@chakra-ui/react"; -import { useState } from "react"; -import { Solution } from "../../api/data-model/Solution"; -import { SolveRequest } from "../../api/data-model/SolveRequest"; -import { postProblem } from "../../api/ToolboxAPI"; -import { Container } from "../Container"; -import { GoButton } from "./buttons/GoButton"; -import { SolutionView } from "./SolutionView"; -import { SolverPicker } from "./SolverPicker"; - -export interface ProgressHandlerProps { - /** - * List of problem types that should be solved with the given input. - */ - problemTypes: string[]; - problemInput: T; -} - -export const ProgressHandler = ( - props: ProgressHandlerProps -) => { - const [wasClicked, setClicked] = useState(false); - const [finished, setFinished] = useState(false); - const [solutions, setSolutions] = useState(); - const [solveRequest, setSolveRequest] = useState>({ - requestContent: props.problemInput, - requestedSubSolveRequests: {}, - }); - - async function startSolving() { - setClicked(true); - setFinished(false); - - let newSolveRequest: SolveRequest = { - ...solveRequest, - requestContent: props.problemInput, - }; - - setSolveRequest(newSolveRequest); - Promise.all( - props.problemTypes.map((problemType) => - postProblem(problemType, newSolveRequest) - ) - ).then((solutions) => { - setSolutions(solutions); - setFinished(true); - }); - } - - return ( - - {!wasClicked || finished ? ( - - {props.problemTypes.map((problemType) => ( - { - setSolveRequest({ - ...solveRequest, - requestedSolverId: solverChoice.requestedSolverId, - requestedSubSolveRequests: - solveRequest.requestedSubSolveRequests, - }); - }} - /> - ))} -
- -
-
- ) : null} - - {wasClicked - ? solutions?.map((s) => ( - - - - )) - : null} -
- ); -}; diff --git a/src/components/solvers/SettingsView.tsx b/src/components/solvers/SettingsView.tsx index 159ad3c..44f6d42 100644 --- a/src/components/solvers/SettingsView.tsx +++ b/src/components/solvers/SettingsView.tsx @@ -12,7 +12,7 @@ import { Textarea, VStack, } from "@chakra-ui/react"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { CheckboxSetting, MetaSolverSetting, @@ -21,7 +21,7 @@ import { SelectSetting, TextSetting, } from "../../api/data-model/MetaSolverSettings"; -import { fetchMetaSolverSettings } from "../../api/ToolboxAPI"; +// import { fetchMetaSolverSettings } from "../../api/ToolboxAPI"; interface SettingsViewProps { problemType: string; @@ -31,11 +31,11 @@ interface SettingsViewProps { export const SettingsView = (props: SettingsViewProps) => { const [settings, setSettings] = useState([]); - useEffect(() => { - fetchMetaSolverSettings(props.problemType).then( - (settings: MetaSolverSetting[]) => setSettings(settings) - ); - }, [props.problemType]); + // useEffect(() => { + // fetchMetaSolverSettings(props.problemType).then( + // (settings: MetaSolverSetting[]) => setSettings(settings) + // ); + // }, [props.problemType]); if (settings.length == 0) { return null; diff --git a/src/components/solvers/SolutionView.tsx b/src/components/solvers/SolutionView.tsx index e66eb06..e7515b9 100644 --- a/src/components/solvers/SolutionView.tsx +++ b/src/components/solvers/SolutionView.tsx @@ -6,14 +6,11 @@ import { AccordionPanel, Box, Code, - Spinner, } from "@chakra-ui/react"; -import { Solution } from "../../api/data-model/Solution"; -import { Container } from "../Container"; +import { SolutionObject } from "../../api/data-model/SolutionObject"; export interface SolutionViewProps { - finished: boolean; - solution: Solution | undefined; + solution: SolutionObject; } const OutputSection = (props: { title: string; content: any[] }) => ( @@ -47,41 +44,25 @@ const OutputSection = (props: { title: string; content: any[] }) => ( ); -export const SolutionView = (props: SolutionViewProps) => { - if (props.finished && props.solution) { - return ( - - - - - - - ); - } else { - return ( - - - - ); - } -}; +export const SolutionView = (props: SolutionViewProps) => ( + + + + + {/**/} + +); diff --git a/src/components/solvers/SolverPicker.tsx b/src/components/solvers/SolverPicker.tsx deleted file mode 100644 index 2d237fe..0000000 --- a/src/components/solvers/SolverPicker.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Box, Container, Select, Text, Tooltip } from "@chakra-ui/react"; -import { ChangeEvent, useEffect, useState } from "react"; -import { ProblemSolver } from "../../api/data-model/ProblemSolver"; -import { SolverChoice } from "../../api/data-model/SolveRequest"; -import { SubRoutineDefinition } from "../../api/data-model/SubRoutineDefinition"; -import { fetchSolvers, fetchSubRoutines } from "../../api/ToolboxAPI"; -import { SettingsView } from "./SettingsView"; - -export interface SolverPickerProps { - problemType: string; - - /** - * Only needed internally if this should show a sub routine - */ - subRoutineDefinition?: SubRoutineDefinition; - setSolveRequest: (subRoutines: SolverChoice) => void; -} - -export const SolverPicker = (props: SolverPickerProps) => { - const [loadingSolvers, setLoadingSolvers] = useState(true); - const [solvers, setSolvers] = useState([]); - const [subRoutines, setSubRoutines] = useState< - SubRoutineDefinition[] | undefined - >(undefined); - const [solveRequest, setSolveRequest] = useState({ - requestedSubSolveRequests: {}, - }); - - useEffect(() => { - setSubRoutines(undefined); - setLoadingSolvers(true); - fetchSolvers(props.problemType).then((solvers: ProblemSolver[]) => { - setSolvers(solvers); - setLoadingSolvers(false); - }); - }, [props.problemType]); - - function onSolverChanged(e: ChangeEvent) { - if ( - e.target.selectedIndex == 0 || - e.target.selectedIndex > solvers.length - ) { - let newSolveRequest: SolverChoice = { - ...solveRequest, - requestedSolverId: undefined, - }; - - setSolveRequest(newSolveRequest); - props.setSolveRequest?.(newSolveRequest); - - setSubRoutines(undefined); - } else { - let solver = solvers[e.target.selectedIndex - 1]; - let newSolveRequest: SolverChoice = { - ...solveRequest, - requestedSolverId: solver.id, - }; - - setSolveRequest(newSolveRequest); - props.setSolveRequest?.(newSolveRequest); - - fetchSubRoutines(props.problemType, solver.id).then((subRoutines) => - setSubRoutines(subRoutines) - ); - } - } - - const SolverSelection = () => { - return ( - - {props.subRoutineDefinition == undefined ? null : ( - {props.subRoutineDefinition?.type} Subroutine: - )} - {props.subRoutineDefinition?.description} - - - - - ); - }; - - return ( - - {loadingSolvers ? Loading solvers... : } - - {solveRequest.requestedSolverId == undefined ? ( - { - let newSolveRequest: SolverChoice = { - ...solveRequest, - requestedMetaSolverSettings: settings, - }; - - setSolveRequest(newSolveRequest); - props.setSolveRequest(newSolveRequest); - }} - /> - ) : null} - - {subRoutines == undefined || subRoutines.length == 0 ? null : ( - - {subRoutines.map((def) => ( - { - let newSolveRequest: SolverChoice = { - ...solveRequest, - requestedSubSolveRequests: { - ...solveRequest.requestedSubSolveRequests, - [def.type]: subSolveRequest, - }, - }; - setSolveRequest(newSolveRequest); - props.setSolveRequest?.(newSolveRequest); - }} - /> - ))} - - )} - - ); -}; diff --git a/src/pages/solve/FeatureModelAnomaly.tsx b/src/pages/solve/FeatureModelAnomaly.tsx index 6188c96..4ee7354 100644 --- a/src/pages/solve/FeatureModelAnomaly.tsx +++ b/src/pages/solve/FeatureModelAnomaly.tsx @@ -11,7 +11,6 @@ import { NextPage } from "next"; import { useState } from "react"; import { MultiSelect, Option } from "react-multi-select-component"; import { Layout } from "../../components/layout/Layout"; -import { ProgressHandler } from "../../components/solvers/ProgressHandler"; import { TextInputMask } from "../../components/solvers/TextInputMask"; const anomalies: Option[] = [ @@ -65,11 +64,6 @@ const FeatureModelAnomaly: NextPage = () => { - - option.value)} - problemInput={uvl} - /> } /> diff --git a/src/pages/solve/MaxCut.tsx b/src/pages/solve/MaxCut.tsx index ba05edf..b5a7909 100644 --- a/src/pages/solve/MaxCut.tsx +++ b/src/pages/solve/MaxCut.tsx @@ -12,7 +12,6 @@ import { useState } from "react"; import { Container } from "../../components/Container"; import { Layout } from "../../components/layout/Layout"; import { GMLGraphView } from "../../components/solvers/Graph/GMLGraphView"; -import { ProgressHandler } from "../../components/solvers/ProgressHandler"; import { TextInputMask } from "../../components/solvers/TextInputMask"; const MaxCut: NextPage = () => { @@ -47,10 +46,6 @@ const MaxCut: NextPage = () => { - } /> diff --git a/src/pages/solve/QUBO.tsx b/src/pages/solve/QUBO.tsx index 1624b73..6f5167c 100644 --- a/src/pages/solve/QUBO.tsx +++ b/src/pages/solve/QUBO.tsx @@ -3,7 +3,6 @@ import type { NextPage } from "next"; import Head from "next/head"; import { useState } from "react"; import { Layout } from "../../components/layout/Layout"; -import { ProgressHandler } from "../../components/solvers/ProgressHandler"; import { TextInputMask } from "../../components/solvers/TextInputMask"; const QUBO: NextPage = () => { @@ -34,7 +33,6 @@ const QUBO: NextPage = () => { textPlaceholder={"Enter your QUBO problem in LP format"} /> - ); }; diff --git a/src/pages/solve/SAT.tsx b/src/pages/solve/SAT.tsx index 7ffac6f..0424603 100644 --- a/src/pages/solve/SAT.tsx +++ b/src/pages/solve/SAT.tsx @@ -5,7 +5,6 @@ import { useState } from "react"; import { baseUrl } from "../../api/ToolboxAPI"; import { Layout } from "../../components/layout/Layout"; import { EditorControls } from "../../components/solvers/EditorControls"; -import { ProgressHandler } from "../../components/solvers/ProgressHandler"; import { TextArea } from "../../components/solvers/SAT/TextArea"; import { LogicalExpressionValidator } from "../../converter/dimacs/LogicalExpressionValidator"; @@ -58,10 +57,6 @@ const SAT: NextPage = () => { }} /> - ); }; From 7b5aec2557a8054a9344d74433dbbe6aa5a38e12 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 23 Jul 2024 10:49:36 +0200 Subject: [PATCH 08/92] Rename DecisionNode to SolverNode --- .../solvers/Graph/ProblemGraphView.tsx | 48 +++++++++---------- .../{DecisionNode.tsx => SolverNode.tsx} | 4 +- 2 files changed, 25 insertions(+), 27 deletions(-) rename src/components/solvers/Graph/{DecisionNode.tsx => SolverNode.tsx} (96%) diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 004b499..ddd8483 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -15,8 +15,8 @@ import { ProblemDto } from "../../../api/data-model/ProblemDto"; import { ProblemState } from "../../../api/data-model/ProblemState"; import { SubRoutineDefinitionDto } from "../../../api/data-model/SubRoutineDefinitionDto"; import { fetchProblem, patchProblem } from "../../../api/ToolboxAPI"; -import { DecisionNode, DecisionNodeData } from "./DecisionNode"; import { LevelInfo, ProblemNode, ProblemNodeData } from "./ProblemNode"; +import { SolverNode, SolverNodeData } from "./SolverNode"; import { useSolvers } from "./SolverProvider"; interface ProblemEdgeData { @@ -73,11 +73,11 @@ function getNodePositionY(level: number): number { } const nodeTypes: NodeTypes = { - decisionNode: DecisionNode, + solverNode: SolverNode, problemNode: ProblemNode, }; -const decisionNodeIdentifier: string = "-decision-node-"; -const decisionEdgeIdentifier: string = "-decision-edge-"; +const solverNodeIdentifier: string = "-solver-node-"; +const solverEdgeIdentifier: string = "-solver-edge-"; export const ProblemGraphView = (props: ProblemGraphViewProps) => { const [nodes, setNodes, onNodesChange] = useNodesState([]); @@ -154,7 +154,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { const updateNode = useCallback( (node: Node) => { - updateDecisionNodes(node); + updateSolverNodes(node); // Update node data updateNodeData(node.id, (n) => ({ @@ -210,12 +210,12 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { } } - function createDecisionNodes(node: Node) { + function createSolverNodes(node: Node) { getSolvers(node.data.problemDto.typeId).then((solvers) => { for (let i = 0; i < solvers.length; i++) { let solverId = solvers[i].id.toString(); - let solverNodeId = node.id + decisionNodeIdentifier + solverId; - let decisionNode: Node = { + let solverNodeId = node.id + solverNodeIdentifier + solverId; + let solverNode: Node = { id: solverNodeId, data: { problemSolver: solvers[i], @@ -224,7 +224,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { // todo update visuals here too let edge = edges.find((e) => - e.target.startsWith(node.id + decisionEdgeIdentifier) + e.target.startsWith(node.id + solverEdgeIdentifier) ); if (edge) { updateEdge(edge); @@ -263,46 +263,44 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { getNodePositionX({ index: i, count: solvers.length }), y: getNodePositionY(node.data.level + 1), }, - type: "decisionNode", + type: "solverNode", }; - addNode(decisionNode); + addNode(solverNode); addEdge({ - id: node.id + decisionEdgeIdentifier + decisionNode.id, + id: node.id + solverEdgeIdentifier + solverNode.id, type: "step", source: node.id, - target: decisionNode.id, + target: solverNode.id, }); } }); } - function updateDecisionNodes(node: Node) { - // Load decision nodes when user action is required + function updateSolverNodes(node: Node) { + // Load solver nodes when user action is required if (node.data.problemDto.state == ProblemState.NEEDS_CONFIGURATION) { - const existingDecisionNode = nodes.find((n) => - n.id.startsWith(node.id + decisionNodeIdentifier) + const existingSolverNode = nodes.find((n) => + n.id.startsWith(node.id + solverNodeIdentifier) ); - if (existingDecisionNode === undefined) { - createDecisionNodes(node); + if (existingSolverNode === undefined) { + createSolverNodes(node); } } else { - removeDecisionNodes(node); + removeSolverNodes(node); } } - function removeDecisionNodes(node: Node) { + function removeSolverNodes(node: Node) { setNodes((previousNodes) => previousNodes.filter((n) => { - let x = n.id.startsWith(node.id + decisionNodeIdentifier); + let x = n.id.startsWith(node.id + solverNodeIdentifier); return !x; }) ); setEdges((edges) => - edges.filter( - (e) => !e.id.startsWith(node.id + decisionEdgeIdentifier) - ) + edges.filter((e) => !e.id.startsWith(node.id + solverEdgeIdentifier)) ); } diff --git a/src/components/solvers/Graph/DecisionNode.tsx b/src/components/solvers/Graph/SolverNode.tsx similarity index 96% rename from src/components/solvers/Graph/DecisionNode.tsx rename to src/components/solvers/Graph/SolverNode.tsx index b43247c..a711ff0 100644 --- a/src/components/solvers/Graph/DecisionNode.tsx +++ b/src/components/solvers/Graph/SolverNode.tsx @@ -22,13 +22,13 @@ import { Handle, NodeProps, Position } from "reactflow"; import { ProblemDto } from "../../../api/data-model/ProblemDto"; import { ProblemSolverInfo } from "../../../api/data-model/ProblemSolverInfo"; -export interface DecisionNodeData { +export interface SolverNodeData { problemSolver: ProblemSolverInfo; problemDto: ProblemDto; selectCallback: (problemSolver: ProblemSolverInfo) => void; } -export function DecisionNode(props: NodeProps) { +export function SolverNode(props: NodeProps) { const [selected, setSelected] = useState(false); return ( From 6e5e81443cd6c0453f7d9019147da9cfb9b2ba14 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 23 Jul 2024 10:55:43 +0200 Subject: [PATCH 09/92] Define KIT green as theme color --- src/components/solvers/Graph/SolverNode.tsx | 2 +- src/theme.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/solvers/Graph/SolverNode.tsx b/src/components/solvers/Graph/SolverNode.tsx index a711ff0..3782340 100644 --- a/src/components/solvers/Graph/SolverNode.tsx +++ b/src/components/solvers/Graph/SolverNode.tsx @@ -36,7 +36,7 @@ export function SolverNode(props: NodeProps) { border="1px" borderRadius="10px" padding=".5rem" - background={selected ? "green" : "#00876CBE"} + background={selected ? "green" : "kitGreen"} fontSize="xs" > diff --git a/src/theme.ts b/src/theme.ts index 728f100..5a40348 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -3,8 +3,9 @@ import { extendTheme } from "@chakra-ui/react"; const theme = extendTheme({ semanticTokens: { colors: { + kitGreen: "#00876C", heroGradientStart: { - default: "#00876C", + default: "kitGreen", _dark: "#e3a7f9", }, heroGradientEnd: { From 7a41af0c6df20e3b0c8e942266f43e6653549df3 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 23 Jul 2024 18:43:12 +0200 Subject: [PATCH 10/92] Initial second pass on problem graph --- src/api/ToolboxAPI.ts | 11 +- .../solvers/Graph/ProblemDetails.tsx | 87 +++ .../solvers/Graph/ProblemGraphView.tsx | 236 +++++--- src/components/solvers/Graph/ProblemList.tsx | 189 ++++++ src/components/solvers/Graph/ProblemNode.tsx | 573 +++++++----------- src/components/solvers/Graph/SolverNode.tsx | 28 +- .../solvers/Graph/TestGraphDisplay.tsx | 2 +- 7 files changed, 693 insertions(+), 433 deletions(-) create mode 100644 src/components/solvers/Graph/ProblemDetails.tsx create mode 100644 src/components/solvers/Graph/ProblemList.tsx diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index 096e219..c97e2ec 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -19,7 +19,16 @@ export async function fetchProblem( }, }) .then((response) => response.json()) - .then((json) => json as ProblemDto) + .then((json) => { + const data = json as ProblemDto; + + // Explicitly set solverId to undefined if it is null + if (data.solverId === null) { + data.solverId = undefined; + } + + return data; + }) .catch((reason) => { return { ...getInvalidProblemDto(), diff --git a/src/components/solvers/Graph/ProblemDetails.tsx b/src/components/solvers/Graph/ProblemDetails.tsx new file mode 100644 index 0000000..3e0b127 --- /dev/null +++ b/src/components/solvers/Graph/ProblemDetails.tsx @@ -0,0 +1,87 @@ +import { + Accordion, + AccordionButton, + AccordionIcon, + AccordionItem, + AccordionPanel, + Box, + Text, + Textarea, + VStack, +} from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { ProblemDto } from "../../../api/data-model/ProblemDto"; +import { ProblemState } from "../../../api/data-model/ProblemState"; +import { SolutionView } from "../SolutionView"; +import { useSolvers } from "./SolverProvider"; + +function getHumanReadableState(status: ProblemState) { + switch (status) { + case ProblemState.NEEDS_CONFIGURATION: + return "Needs Configuration"; + case ProblemState.READY_TO_SOLVE: + return "Ready to Solve"; + case ProblemState.SOLVING: + return "Solving"; + case ProblemState.SOLVED: + return "Solved"; + } +} + +function getAccordionItem(label: string, content: ReactNode) { + return ( + +

+ + + {label} + + + +

+ {content} +
+ ); +} + +export const ProblemDetails = (props: { problemDto: ProblemDto }) => { + const { solvers, getSolvers } = useSolvers(); + + // Update solvers in case they are not loaded yet + if (!solvers[props.problemDto.typeId]) getSolvers(props.problemDto.typeId); + + return ( + + + + Status: {getHumanReadableState(props.problemDto.state)} + + + Solver:{" "} + {solvers[props.problemDto.typeId]?.find( + (s) => s.id === props.problemDto.solverId + )?.name ?? "-"} + + {props.problemDto.subProblems.length === 0 ? ( + No subroutines + ) : ( + + Sub Routines: + + {props.problemDto.subProblems.map((subProblem) => + getAccordionItem( + subProblem.subRoutine.typeId, + subProblem.subRoutine.description + ) + )} + + + )} + {props.problemDto.solution !== null && ( + + )} + + ); +}; diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index ddd8483..469b5f2 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -28,23 +28,30 @@ export interface ProblemGraphViewProps { problemId: string; } +export interface ProblemNodeIdentifier { + subRoutineDefinitionDto: SubRoutineDefinitionDto; + solverId?: string; +} + /** * Nodes are identified recursively through their parent node and the sub-routine definition. * Each node can represent either one problem or a collection of problems, * but they share the same sub-routine definition and recursive context. - * @param subRoutineDefinitionDto + * @param identifier Identifier for the problem node * @param parentNode */ function getNodeId( - subRoutineDefinitionDto: SubRoutineDefinitionDto, + identifier: ProblemNodeIdentifier, parentNode: Node ): string { return ( parentNode.id + "|" + - subRoutineDefinitionDto.typeId + + identifier.subRoutineDefinitionDto.typeId + + "-" + + identifier.subRoutineDefinitionDto.description + "-" + - subRoutineDefinitionDto.description + identifier.solverId ); } @@ -53,7 +60,7 @@ function getNodeId( * @param to The target sub-routine definition * @param fromId The source node */ -function getEdgeId(to: SubRoutineDefinitionDto, fromId: Node): string { +function getEdgeId(to: ProblemNodeIdentifier, fromId: Node): string { return fromId + "->" + getNodeId(to, fromId); } @@ -69,7 +76,20 @@ function getNodePositionX(levelInfo: LevelInfo | null): number { } function getNodePositionY(level: number): number { - return level * 150; + return level * 200; +} + +function groupBySolver(problemDtos: ProblemDto[]) { + let solvers = new Map[]>(); + for (let problemDto of problemDtos) { + const problems = solvers.get(problemDto.solverId); + if (problems) { + problems.push(problemDto); + } else { + solvers.set(problemDto.solverId, [problemDto]); + } + } + return solvers; } const nodeTypes: NodeTypes = { @@ -125,23 +145,12 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { [setEdges] ); - const createNode = useCallback( - ( - nodeId: string, - problemDto: ProblemDto, - level: number, - levelInfo: LevelInfo - ) => { - let data: ProblemNodeData = { - problemDto: problemDto, - level: level, - levelInfo: levelInfo, - }; - + const createProblemNode = useCallback( + (nodeId: string, nodeData: ProblemNodeData) => { let node: Node = { id: nodeId, - data: data, - position: getNodePosition(data), + data: nodeData, + position: getNodePosition(nodeData), type: "problemNode", }; @@ -149,7 +158,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { return node; }, - [addNode, scheduleNodeUpdate] + [addNode] ); const updateNode = useCallback( @@ -163,63 +172,99 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { type: "problemNode", })); + // Solver id and thus sub problems are the same for all problems + const subProblems = node.data.problemDtos[0].subProblems; + // Update sub-routine nodes - for (let i = 0; i < node.data.problemDto.subProblems.length; i++) { - let subProblem = node.data.problemDto.subProblems[i]; - let subNodeId = getNodeId(subProblem.subRoutine, node); - - let subNode = nodes.find((node) => node.id === subNodeId); - let edgeId = getEdgeId(subProblem.subRoutine, node); - if (subNode === undefined) { - // Create new node - fetchProblem( - subProblem.subRoutine.typeId, - subProblem.subProblemIds[0] - ).then((subProblemDto) => { - let subNodeId = getNodeId(subProblem.subRoutine, node); + for (let i = 0; i < subProblems.length; i++) { + const subProblem = subProblems[i]; + + Promise.all( + subProblem.subProblemIds.map((subProblemId) => + fetchProblem(subProblem.subRoutine.typeId, subProblemId) + ) + ).then((subProblemDtos) => { + // Create sub problem nodes per used solver + const problemsPerSolver = groupBySolver(subProblemDtos); + problemsPerSolver.forEach((problemDtos, solverId) => { + const problemNodeIdentifier: ProblemNodeIdentifier = { + subRoutineDefinitionDto: subProblem.subRoutine, + solverId: solverId, + }; - addEdge({ - id: edgeId, - source: node.id, - target: subNodeId, - data: { - sourceProblemDto: node.data.problemDto, - }, - animated: subProblemDto.state === ProblemState.SOLVING, - }); + const subNodeId = getNodeId(problemNodeIdentifier, node); + const edgeId = getEdgeId(problemNodeIdentifier, node); - let subNode = createNode( - subNodeId, - subProblemDto, - node.data.level + 1, - { - index: i, - count: node.data.problemDto.subProblems.length, + const subNode = nodes.find((n) => n.id === subNodeId); + if (subNode) { + // Update existing node if it exists + scheduleNodeUpdate(subNode); + + const edge = edges.find((edge) => edge.id === edgeId); + if (edge) { + updateEdge(edge); } - ); - scheduleNodeUpdate(subNode); - }); - } else { - // Update existing node - scheduleNodeUpdate(subNode); + } else { + // Otherwise create a new node + addEdge({ + id: edgeId, + source: node.id, + target: subNodeId, + data: { + sourceProblemDto: problemDtos, + }, + animated: problemDtos.some( + (dto) => dto.state === ProblemState.SOLVING + ), + }); + + let subNode = createProblemNode(subNodeId, { + problemDtos: problemDtos, + level: node.data.level + 1, + levelInfo: { + index: i, + count: subProblems.length, + }, - const edge = edges.find((edge) => edge.id === edgeId); - if (edge) { - updateEdge(edge); - } - } + solveCallback: (problemDto) => { + patchProblem(problemDto.typeId, problemDto.id, { + state: ProblemState.SOLVING, + }).then((dto) => { + setNodes((previousNodes) => + previousNodes.map((n) => { + if (n.id !== subNodeId) return n; + + let updatedNode: Node = { + ...n, + data: { + ...n.data, + problemDtos: [dto], // todo this needs to include all updated dtos + }, + }; + scheduleNodeUpdate(updatedNode); + + return updatedNode; + }) + ); + }); + }, + }); + scheduleNodeUpdate(subNode); + } + }); + }); } function createSolverNodes(node: Node) { - getSolvers(node.data.problemDto.typeId).then((solvers) => { + getSolvers(node.data.problemDtos[0].typeId).then((solvers) => { for (let i = 0; i < solvers.length; i++) { let solverId = solvers[i].id.toString(); let solverNodeId = node.id + solverNodeIdentifier + solverId; let solverNode: Node = { id: solverNodeId, data: { + problemTypeId: node.data.problemDtos[0].typeId, problemSolver: solvers[i], - problemDto: node.data.problemDto, selectCallback: (problemSolver) => { // todo update visuals here too @@ -230,14 +275,13 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { updateEdge(edge); } - patchProblem( - node.data.problemDto.typeId, - node.data.problemDto.id, - { - solverId: problemSolver.id, - state: ProblemState.SOLVING, - } - ).then((dto) => + Promise.all( + node.data.problemDtos.map((problemDto) => + patchProblem(problemDto.typeId, problemDto.id, { + solverId: problemSolver.id, + }) + ) + ).then((dtos) => { setNodes((previousNodes) => previousNodes.map((n) => { if (n.id !== node.id) return n; @@ -246,15 +290,15 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { ...n, data: { ...n.data, - problemDto: dto, + problemDtos: dtos, }, }; scheduleNodeUpdate(updatedNode); return updatedNode; }) - ) - ); + ); + }); }, }, position: { @@ -278,9 +322,10 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { }); } - function updateSolverNodes(node: Node) { + function updateSolverNodes(node: Node) { // Load solver nodes when user action is required - if (node.data.problemDto.state == ProblemState.NEEDS_CONFIGURATION) { + console.log("data", node.data); + if (node.data.problemDtos[0].solverId === undefined) { const existingSolverNode = nodes.find((n) => n.id.startsWith(node.id + solverNodeIdentifier) ); @@ -334,8 +379,9 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { [ addEdge, addNode, - createNode, + createProblemNode, edges, + getSolvers, nodes, scheduleNodeUpdate, setEdges, @@ -365,14 +411,40 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { } // Create root node - let rootNode = createNode(props.problemId, problemDto, 0, { - index: 0, - count: 1, + let rootNode = createProblemNode(props.problemId, { + problemDtos: [problemDto], + level: 0, + levelInfo: { + index: 0, + count: 1, + }, + solveCallback: (problemDto) => { + patchProblem(problemDto.typeId, problemDto.id, { + state: ProblemState.SOLVING, + }).then((dto) => { + setNodes((previousNodes) => + previousNodes.map((n) => { + if (n.id !== rootNode.id) return n; + + let updatedNode: Node = { + ...n, + data: { + ...n.data, + problemDtos: [dto], + }, + }; + scheduleNodeUpdate(updatedNode); + + return updatedNode; + }) + ); + }); + }, }); scheduleNodeUpdate(rootNode); }); }, [ - createNode, + createProblemNode, props.problemId, props.problemType, scheduleNodeUpdate, diff --git a/src/components/solvers/Graph/ProblemList.tsx b/src/components/solvers/Graph/ProblemList.tsx new file mode 100644 index 0000000..a5ea1b9 --- /dev/null +++ b/src/components/solvers/Graph/ProblemList.tsx @@ -0,0 +1,189 @@ +import { + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalHeader, + ModalOverlay, + Spinner, + Text, + useDisclosure, + VStack, +} from "@chakra-ui/react"; +import { Dispatch, SetStateAction, useState } from "react"; +import { FaQuestion, FaQuestionCircle } from "react-icons/fa"; +import { GrInProgress } from "react-icons/gr"; +import { ImCheckmark } from "react-icons/im"; +import { MdError } from "react-icons/md"; +import { ProblemDto } from "../../../api/data-model/ProblemDto"; +import { ProblemState } from "../../../api/data-model/ProblemState"; +import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; +import { ProblemDetails } from "./ProblemDetails"; +import { useSolvers } from "./SolverProvider"; + +function getStatusIcon(problemDto: ProblemDto): JSX.Element { + switch (problemDto.state) { + case ProblemState.NEEDS_CONFIGURATION: + return ; + case ProblemState.READY_TO_SOLVE: + return ; + case ProblemState.SOLVING: + return ; + case ProblemState.SOLVED: + switch (problemDto.solution.status) { + case SolutionStatus.INVALID: + return ; + case SolutionStatus.COMPUTING: + return ; + case SolutionStatus.SOLVED: + return ; + case SolutionStatus.ERROR: + return ; + } + } +} + +export const ProblemList = (props: { + problemDtos: ProblemDto[]; + selectedProblems: string[]; + setSelectedProblems: Dispatch>; +}) => { + const { isOpen, onOpen, onClose } = useDisclosure(); + const [modalProblemDto, setModalProblemDto] = useState>(); + const [lastSelectedProblem, setLastSelectedProblem] = useState< + ProblemDto | undefined + >(undefined); + + const { solvers, getSolvers } = useSolvers(); + + const typeId = props.problemDtos[0].typeId; + const solverId = props.problemDtos[0].solverId; + + function handleClick( + e: React.MouseEvent, + clickedSubProblem: ProblemDto + ) { + setLastSelectedProblem(clickedSubProblem); + if (!lastSelectedProblem) { + props.setSelectedProblems([clickedSubProblem.id]); + return; + } + + let newProblemIds: string[]; + if (e.shiftKey) { + let lastIndex = props.problemDtos.indexOf(lastSelectedProblem); + let currentIndex = props.problemDtos.indexOf(clickedSubProblem); + let start = Math.min(lastIndex, currentIndex); + let end = Math.max(lastIndex, currentIndex); + newProblemIds = props.problemDtos.slice(start, end + 1).map((p) => p.id); + } else { + newProblemIds = [clickedSubProblem.id]; + } + + if (e.ctrlKey) { + props.setSelectedProblems((prev) => [ + ...prev.filter((id) => !newProblemIds.includes(id)), + ...newProblemIds.filter( + (id) => lastSelectedProblem.id == id || !prev.includes(id) + ), + ]); + } else { + props.setSelectedProblems((prev) => + newProblemIds.filter( + (id) => lastSelectedProblem.id == id || !prev.includes(id) + ) + ); + } + } + + return ( + // + // + // + // + // + // + // + // + // + // + // {subProblem.subProblemIds.map((subProblemId, index) => { + // return ( + // + // + // + // + // + // ); + // })} + // + //
SubroutineSolverStatus
+ // {subProblem.subRoutine.typeId} {index} + // No solverNot solved
+ //
+ <> + + + Selected {props.selectedProblems.length} of {props.problemDtos.length} + + + + + + + + {typeId} + + + {modalProblemDto && ( + + )} + + + + + {props.problemDtos.map((problemDto, index) => ( + id == problemDto.id) + ? "lightgrey" + : "white" + } + onClick={(e) => handleClick(e, problemDto)} + > + {getStatusIcon(problemDto)} + + {typeId} {index + 1} + + + {problemDto.solverId + ? solvers[problemDto.typeId]?.find( + (s) => s.id == problemDto.solverId + )?.name ?? problemDto.solverId + : "-"} + + +
{ + setModalProblemDto(problemDto); + onOpen(); + }} + > + +
+
+ ))} +
+ + ); +}; diff --git a/src/components/solvers/Graph/ProblemNode.tsx b/src/components/solvers/Graph/ProblemNode.tsx index fda5359..5a66afc 100644 --- a/src/components/solvers/Graph/ProblemNode.tsx +++ b/src/components/solvers/Graph/ProblemNode.tsx @@ -1,11 +1,7 @@ import { - Accordion, - AccordionButton, - AccordionIcon, - AccordionItem, - AccordionPanel, Box, - Flex, + Button, + Divider, HStack, Modal, ModalBody, @@ -13,6 +9,15 @@ import { ModalContent, ModalHeader, ModalOverlay, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverFooter, + PopoverHeader, + PopoverTrigger, + Portal, Spinner, Text, Tooltip, @@ -20,7 +25,7 @@ import { VStack, } from "@chakra-ui/react"; import { Property } from "csstype"; -import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { useState } from "react"; import { BsDatabaseFillGear } from "react-icons/bs"; import { FaChevronCircleDown, @@ -28,6 +33,7 @@ import { FaQuestion, FaQuestionCircle, } from "react-icons/fa"; +import { FaGears } from "react-icons/fa6"; import { GrInProgress } from "react-icons/gr"; import { ImCheckmark } from "react-icons/im"; import { MdError } from "react-icons/md"; @@ -35,9 +41,8 @@ import { Handle, NodeProps, Position } from "reactflow"; import { ProblemDto } from "../../../api/data-model/ProblemDto"; import { ProblemState } from "../../../api/data-model/ProblemState"; import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; -import { SubRoutineReferenceDto } from "../../../api/data-model/SubRoutineReferenceDto"; -import { fetchProblem } from "../../../api/ToolboxAPI"; -import { SolutionView } from "../SolutionView"; +import { ProblemDetails } from "./ProblemDetails"; +import { ProblemList } from "./ProblemList"; import { useSolvers } from "./SolverProvider"; import Color = Property.Color; @@ -55,18 +60,19 @@ export interface LevelInfo { export interface ProblemNodeData { // Data - problemDto: ProblemDto; + problemDtos: ProblemDto[]; // Styling info level: number; levelInfo: LevelInfo; - // solve: (problem: ProblemDto) => void; + // Callbacks + solveCallback: (problem: ProblemDto) => void; } function getNodeType(data: ProblemNodeData) { // Add input/output connect points for edges - if (data.problemDto.state === ProblemState.NEEDS_CONFIGURATION) { + if (data.problemDtos.some((dto) => dto.solverId === undefined)) { if (data.level == 0) { return "input"; } else { @@ -75,7 +81,7 @@ function getNodeType(data: ProblemNodeData) { } else { if (data.level === 0) { return "input"; - } else if (data.problemDto.subProblems.length == 0) { + } else if (data.problemDtos.length == 0) { return "output"; } else { return "default"; @@ -83,27 +89,30 @@ function getNodeType(data: ProblemNodeData) { } } -function getStatusColor(problemDto: ProblemDto): Color { - switch (problemDto.state) { - case ProblemState.NEEDS_CONFIGURATION: - return "ghostwhite"; - case ProblemState.READY_TO_SOLVE: - return "cornflowerblue"; - case ProblemState.SOLVING: - return "cornflowerblue"; - case ProblemState.SOLVED: - switch (problemDto.solution.status) { - case SolutionStatus.INVALID: - return "orange"; - case SolutionStatus.COMPUTING: - return "cornflowerblue"; - case SolutionStatus.SOLVED: - return "teal"; - case SolutionStatus.ERROR: - return "red"; - } +function getStatusColor(problemDtos: ProblemDto[]): Color { + for (let problemDto of problemDtos) { + switch (problemDto.state) { + case ProblemState.NEEDS_CONFIGURATION: + return "ghostwhite"; + case ProblemState.SOLVED: + switch (problemDto.solution.status) { + case SolutionStatus.INVALID: + return "orange"; + case SolutionStatus.ERROR: + return "red"; + } + } } + + // If all dtos are solved, the whole node should have the solved color + if (problemDtos.every((dto) => dto.state === ProblemState.SOLVED)) { + return "teal"; + } + + // Otherwise if any dto is ready to solve or solving, the whole node should have the ready to solve color + return "cornflowerblue"; } + function getStatusIcon(problemDto: ProblemDto): JSX.Element { switch (problemDto.state) { case ProblemState.NEEDS_CONFIGURATION: @@ -132,31 +141,32 @@ export function ProblemNode(props: NodeProps) { const [selectedSubProblems, setSelectedSubProblems] = useState([]); const nodeType = getNodeType(props.data); - const nodeColor = getStatusColor(props.data.problemDto); - - useEffect(() => {}, [props.data.problemDto.subProblems]); + const nodeColor = getStatusColor(props.data.problemDtos); const { solvers, getSolvers } = useSolvers(); - const solverName = solvers[props.data.problemDto.typeId]?.find( - (s) => s.id === props.data.problemDto.solverId - )?.name; + // Type id is the same for all problems + const typeId = props.data.problemDtos[0].typeId; + const solverId = props.data.problemDtos[0].solverId; + const solverName = solvers[typeId]?.find((s) => s.id === solverId)?.name; - console.log( - "ProblemNode", - props.data.problemDto.typeId, - props.data.problemDto.id, - props.data.problemDto.state, - props.data.problemDto.subProblems - ); + // Fetch solvers for type if necessary + getSolvers(typeId); + + const extended = solverId !== undefined; + + console.log("ProblemNode", typeId, props.data.problemDtos); return ( - *": { + pointerEvents: "auto", + }, + }} > {(nodeType === "output" || nodeType === "default") && ( @@ -164,310 +174,181 @@ export function ProblemNode(props: NodeProps) { {(nodeType === "input" || nodeType === "default") && ( )} - - -
- + + + +
+ +
+
+ + + {props.data.problemDtos[0].typeId} + + +
+
- - - {props.data.problemDto.typeId} - - {solverName} - - -
- -
- - - - - - - {props.data.problemDto.typeId} - - - - - - -
- {props.data.problemDto.subProblems.length > 0 && ( - - setDropdownOpen(!dropdownOpen)} - top={0} - left="50%" - transform="translate(-50%, -50%)" + - {dropdownOpen ? : } - - -
- {/*// todo: subproblem list should be in the child node instead. so subproblemms for SAT should be listed in the SAT node - EACH SAT NODE should also keep the state of the current subproblem meaning you can see the solution of the subproblem too - SO WE NEED TO PASS THE LIST OF SUBPROBLEMS TO THE CHILD NODE AND HAVE A WAY TO REPRESENT A LIST OF PROBLEM NODES*/} - {dropdownOpen - ? props.data.problemDto.subProblems.map((subRoutine) => ( - - )) - : props.data.problemDto.subProblems.map((subProblem) => ( - - - {subProblem.subProblemIds.length} - {"x "} - {subProblem.subRoutine.typeId} - -
- {subProblem.subProblemIds.map((subProblemId) => { - // todo extract to component and save subproblemdto state - // let subProblemDto = await fetchProblem( - // subProblem.subRoutine.typeId, - // subProblemId - // ); - return ( - - ); - })} -
-
+ + + + + {typeId} + + + {props.data.problemDtos.map((problemDto) => ( + <> + + + ))} -
-
- )} -
- ); -} - -function getHumanReadableState(status: ProblemState) { - switch (status) { - case ProblemState.NEEDS_CONFIGURATION: - return "Needs Configuration"; - case ProblemState.READY_TO_SOLVE: - return "Ready to Solve"; - case ProblemState.SOLVING: - return "Solving"; - case ProblemState.SOLVED: - return "Solved"; - } -} - -export const SubProblemList = (props: { - subRoutineDefinition: SubRoutineReferenceDto; - selectedProblems: string[]; - setSelectedProblems: Dispatch>; -}) => { - const [subProblems, setSubProblems] = useState[]>([]); - const [lastSelectedProblem, setLastSelectedProblem] = useState< - ProblemDto | undefined - >(undefined); - - const { solvers, getSolvers } = useSolvers(); - - // Fetch latest sub problem dtos - useEffect(() => { - Promise.all( - props.subRoutineDefinition.subProblemIds.map((subProblemId) => - fetchProblem(props.subRoutineDefinition.subRoutine.typeId, subProblemId) - ) - ).then((subProblems) => setSubProblems(subProblems)); - }, [props.subRoutineDefinition]); - - function handleClick( - e: React.MouseEvent, - clickedSubProblem: ProblemDto - ) { - setLastSelectedProblem(clickedSubProblem); - if (!lastSelectedProblem) { - props.setSelectedProblems([clickedSubProblem.id]); - return; - } - - let newProblemIds: string[]; - if (e.shiftKey) { - let lastIndex = subProblems.indexOf(lastSelectedProblem); - let currentIndex = subProblems.indexOf(clickedSubProblem); - let start = Math.min(lastIndex, currentIndex); - let end = Math.max(lastIndex, currentIndex); - newProblemIds = subProblems.slice(start, end + 1).map((p) => p.id); - } else { - newProblemIds = [clickedSubProblem.id]; - } + + + + - if (e.ctrlKey) { - props.setSelectedProblems((prev) => [ - ...prev.filter((id) => !newProblemIds.includes(id)), - ...newProblemIds.filter( - (id) => lastSelectedProblem.id == id || !prev.includes(id) - ), - ]); - } else { - props.setSelectedProblems((prev) => - newProblemIds.filter( - (id) => lastSelectedProblem.id == id || !prev.includes(id) - ) - ); - } - } - - return ( - // - // - // - // - // - // - // - // - // - // - // {subProblem.subProblemIds.map((subProblemId, index) => { - // return ( - // - // - // - // - // - // ); - // })} - // - //
SubroutineSolverStatus
- // {subProblem.subRoutine.typeId} {index} - // No solverNot solved
- //
- <> - - - Selected {props.selectedProblems.length} of {subProblems.length} - - - {subProblems.map((subProblem, index) => { - getSolvers(subProblem.typeId); - return ( - id == subProblem.id) - ? "lightgrey" - : "white" - } - onClick={(e) => handleClick(e, subProblem)} + {props.data.problemDtos.length > 1 && ( + + setDropdownOpen(!dropdownOpen)} + top={0} + left="50%" + transform="translate(-50%, -50%)" > - {getStatusIcon(subProblem)} - - {props.subRoutineDefinition.subRoutine.typeId} {index + 1} - + {dropdownOpen ? : } + - - {subProblem.solverId - ? solvers[subProblem.typeId]?.find( - (s) => s.id == subProblem.solverId - )?.name ?? subProblem.solverId - : "-"} - - - // - // - // - // {props.subRoutineDefinition.subRoutine.typeId} {index} - // - // - // - // - - // - // - // {} - // - // - ); - })} - - - ); -}; +
+ {dropdownOpen && ( + + )} +
+ + )} + -export const ProblemDetails = (props: { problemDto: ProblemDto }) => { - const { solvers, getSolvers } = useSolvers(); + {extended && ( + + + +
+ +
+
+ + {solverName} + - // Update solvers in case they are not loaded yet - if (!solvers[props.problemDto.typeId]) getSolvers(props.problemDto.typeId); + + +
+ +
+
+ + + + + + {solverName} + + + Solves {typeId} + Additional Info: + + + + {solverId} + + + +
+
- return ( - - Status: {getHumanReadableState(props.problemDto.state)} - - Solver:{" "} - {solvers[props.problemDto.typeId]?.find( - (s) => s.id === props.problemDto.solverId - )?.name ?? "-"} - - {props.problemDto.subProblems.length === 0 ? ( - No subroutines - ) : ( - - Sub Routines: - - {props.problemDto.subProblems.map((subProblem) => ( - -

- - - {subProblem.subRoutine.typeId} - - - -

- - {subProblem.subRoutine.description} - -
- ))} -
-
- )} - {props.problemDto.solution !== null && ( - + {props.data.problemDtos.some( + (dto) => dto.state === ProblemState.NEEDS_CONFIGURATION + ) && ( + { + for (let problemDto of props.data.problemDtos) { + props.data.solveCallback(problemDto); + } + }} + > + Solve + + )} +
)} ); -}; +} diff --git a/src/components/solvers/Graph/SolverNode.tsx b/src/components/solvers/Graph/SolverNode.tsx index 3782340..405fea4 100644 --- a/src/components/solvers/Graph/SolverNode.tsx +++ b/src/components/solvers/Graph/SolverNode.tsx @@ -19,12 +19,11 @@ import { useState } from "react"; import { FaQuestionCircle } from "react-icons/fa"; import { FaGears } from "react-icons/fa6"; import { Handle, NodeProps, Position } from "reactflow"; -import { ProblemDto } from "../../../api/data-model/ProblemDto"; import { ProblemSolverInfo } from "../../../api/data-model/ProblemSolverInfo"; export interface SolverNodeData { + problemTypeId: string; problemSolver: ProblemSolverInfo; - problemDto: ProblemDto; selectCallback: (problemSolver: ProblemSolverInfo) => void; } @@ -38,6 +37,29 @@ export function SolverNode(props: NodeProps) { padding=".5rem" background={selected ? "green" : "kitGreen"} fontSize="xs" + css={{ + "&::before, &::after": { + content: '""', + position: "absolute", + borderBottomRightRadius: "5px", + borderBottomLeftRadius: "5px", + left: "50%", + top: "-0.2px", + width: "15px", + height: "8px", + background: "white", + borderLeft: "1px solid black", + borderRight: "1px solid black", + borderBottom: "1px solid black", + zIndex: 10, + }, + "&::before": { + transform: "translate(calc(-50% - 50px), -4%)", + }, + "&::after": { + transform: "translate(calc(-50% + 50px), -4%)", + }, + }} > @@ -67,7 +89,7 @@ export function SolverNode(props: NodeProps) { - Solves {props.data.problemDto.typeId} + Solves {props.data.problemTypeId} Additional Info: diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx index 6180ec3..568e05b 100644 --- a/src/components/solvers/Graph/TestGraphDisplay.tsx +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -6,7 +6,7 @@ export const TestGraphDisplay = () => { ); From a5d887022c693de99cde4e393d0797c742414db1 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 30 Jul 2024 11:48:09 +0200 Subject: [PATCH 11/92] refactor: Replace problemType with problemTypeId --- src/api/ToolboxAPI.ts | 26 +++++++++---------- .../solvers/Graph/ProblemGraphView.tsx | 6 ++--- .../solvers/Graph/SolverProvider.tsx | 8 +++--- .../solvers/Graph/TestGraphDisplay.tsx | 1 + src/components/solvers/SettingsView.tsx | 2 +- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index c97e2ec..ebd554f 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -9,10 +9,10 @@ import { SubRoutineDefinitionDto } from "./data-model/SubRoutineDefinitionDto"; export const baseUrl = () => process.env.NEXT_PUBLIC_API_BASE_URL; export async function fetchProblem( - problemType: string, + problemTypeId: string, problemId: string ): Promise> { - return fetch(`${baseUrl()}/problems/${problemType}/${problemId}`, { + return fetch(`${baseUrl()}/problems/${problemTypeId}/${problemId}`, { method: "GET", headers: { "Content-Type": "application/json", @@ -38,10 +38,10 @@ export async function fetchProblem( } export async function postProblem( - problemType: string, + problemTypeId: string, problemRequest: ProblemDto ): Promise> { - return fetch(`${baseUrl()}/problems/${problemType}`, { + return fetch(`${baseUrl()}/problems/${problemTypeId}`, { method: "POST", headers: { "Content-Type": "application/json", @@ -59,12 +59,12 @@ export async function postProblem( } export async function patchProblem( - problemType: string, + problemTypeId: string, problemId: string, updateParameters: { input?: any; solverId?: string; state?: ProblemState } ): Promise> { let x = JSON.stringify(updateParameters); - let url = `${baseUrl()}/problems/${problemType}/${problemId}`; + let url = `${baseUrl()}/problems/${problemTypeId}/${problemId}`; console.log(url); return fetch(url, { method: "PATCH", @@ -84,9 +84,9 @@ export async function patchProblem( } export async function fetchSolvers( - problemType: string + problemTypeId: string ): Promise { - return fetch(`${baseUrl()}/solvers/${problemType}`, { + return fetch(`${baseUrl()}/solvers/${problemTypeId}`, { method: "GET", headers: { "Content-Type": "application/json", @@ -96,17 +96,17 @@ export async function fetchSolvers( .then((json) => json as ProblemSolverInfo[]) .catch((reason) => { console.error(reason); - alert(`Could not retrieve solvers of type ${problemType}.`); + alert(`Could not retrieve solvers of type ${problemTypeId}.`); return []; }); } export async function fetchSubRoutines( - problemType: string, + problemTypeId: string, solverId: string ): Promise { return fetch( - `${baseUrl()}/sub-routines/${problemType}?${new URLSearchParams({ + `${baseUrl()}/sub-routines/${problemTypeId}?${new URLSearchParams({ id: solverId, })}`, { @@ -125,9 +125,9 @@ export async function fetchSubRoutines( } // export async function fetchMetaSolverSettings( -// problemType: string +// problemTypeId: string // ): Promise { -// return fetch(`${baseUrl()}/meta-solver/settings/${problemType}`, { +// return fetch(`${baseUrl()}/meta-solver/settings/${problemTypeId}`, { // method: "GET", // headers: { // "Content-Type": "application/json", diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 469b5f2..9919aa7 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -24,7 +24,7 @@ interface ProblemEdgeData { } export interface ProblemGraphViewProps { - problemType: string; + problemTypeId: string; problemId: string; } @@ -404,7 +404,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { useEffect(() => { setNodes([]); setEdges([]); - fetchProblem(props.problemType, props.problemId).then((problemDto) => { + fetchProblem(props.problemTypeId, props.problemId).then((problemDto) => { if (problemDto.error) { console.error(problemDto.error); return; @@ -446,7 +446,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { }, [ createProblemNode, props.problemId, - props.problemType, + props.problemTypeId, scheduleNodeUpdate, setEdges, setNodes, diff --git a/src/components/solvers/Graph/SolverProvider.tsx b/src/components/solvers/Graph/SolverProvider.tsx index 552ef7f..5782488 100644 --- a/src/components/solvers/Graph/SolverProvider.tsx +++ b/src/components/solvers/Graph/SolverProvider.tsx @@ -20,15 +20,15 @@ export const SolverProvider = (props: { children: ReactNode }) => { const [solvers, setSolvers] = useState({}); // Function to get solvers, either from cache or by fetching - const getSolvers = async (problemType: string) => { - const cachedSolvers = solvers[problemType]; + const getSolvers = async (problemTypeId: string) => { + const cachedSolvers = solvers[problemTypeId]; if (cachedSolvers) { return cachedSolvers; } else { - const fetchedSolvers = await fetchSolvers(problemType); + const fetchedSolvers = await fetchSolvers(problemTypeId); setSolvers((prevSolvers) => ({ ...prevSolvers, - [problemType]: fetchedSolvers, + [problemTypeId]: fetchedSolvers, })); return fetchedSolvers; } diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx index 568e05b..cc46b51 100644 --- a/src/components/solvers/Graph/TestGraphDisplay.tsx +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -7,6 +7,7 @@ export const TestGraphDisplay = () => { ); diff --git a/src/components/solvers/SettingsView.tsx b/src/components/solvers/SettingsView.tsx index 44f6d42..c5ea799 100644 --- a/src/components/solvers/SettingsView.tsx +++ b/src/components/solvers/SettingsView.tsx @@ -24,7 +24,7 @@ import { // import { fetchMetaSolverSettings } from "../../api/ToolboxAPI"; interface SettingsViewProps { - problemType: string; + problemTypeId: string; settingChanged: (newSettings: MetaSolverSetting[]) => void; } From 2b26dab5395659256b1c23b228ae26cf614ae93d Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 30 Jul 2024 16:59:48 +0200 Subject: [PATCH 12/92] fix: Remove unused variable --- src/api/ToolboxAPI.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index ebd554f..b856c01 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -63,7 +63,6 @@ export async function patchProblem( problemId: string, updateParameters: { input?: any; solverId?: string; state?: ProblemState } ): Promise> { - let x = JSON.stringify(updateParameters); let url = `${baseUrl()}/problems/${problemTypeId}/${problemId}`; console.log(url); return fetch(url, { From a66aceb544020d1312ab876b982f3870649120b0 Mon Sep 17 00:00:00 2001 From: Elscrux Date: Tue, 30 Jul 2024 17:00:48 +0200 Subject: [PATCH 13/92] feat: Further updates --- .../solvers/Graph/ProblemGraphView.tsx | 173 +++++++++++------- src/components/solvers/Graph/ProblemList.tsx | 170 +++++++++-------- src/components/solvers/Graph/ProblemNode.tsx | 149 ++++++++++----- src/components/solvers/Graph/SolverNode.tsx | 44 +++-- .../solvers/Graph/TestGraphDisplay.tsx | 3 +- src/theme.ts | 1 + 6 files changed, 327 insertions(+), 213 deletions(-) diff --git a/src/components/solvers/Graph/ProblemGraphView.tsx b/src/components/solvers/Graph/ProblemGraphView.tsx index 9919aa7..4131d86 100644 --- a/src/components/solvers/Graph/ProblemGraphView.tsx +++ b/src/components/solvers/Graph/ProblemGraphView.tsx @@ -1,5 +1,11 @@ import type { XYPosition } from "@reactflow/core/dist/esm/types"; -import { useCallback, useEffect, useState } from "react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from "react"; import { Controls, Edge, @@ -55,6 +61,10 @@ function getNodeId( ); } +function getParentNodeId(nodeId: string): string { + return nodeId.substring(0, nodeId.lastIndexOf("|")); +} + /** * Edge identifiers are determined by the source and target node identifiers. * @param to The target sub-routine definition @@ -99,6 +109,16 @@ const nodeTypes: NodeTypes = { const solverNodeIdentifier: string = "-solver-node-"; const solverEdgeIdentifier: string = "-solver-edge-"; +export interface GraphUpdateProps { + updateProblem: (problemId: string) => void; +} + +const GraphUpdateContext = createContext({ + updateProblem: (_) => {}, +}); + +export const useGraphUpdates = () => useContext(GraphUpdateContext); + export const ProblemGraphView = (props: ProblemGraphViewProps) => { const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); @@ -161,16 +181,36 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { [addNode] ); + const updateProblem = useCallback( + (problemId: string) => { + const node = nodes.find((n) => { + if (n.data.problemDtos === undefined) { + return false; + } + + return n.data.problemDtos.find( + (p: ProblemDto) => p.id === problemId + ); + }); + + if (!node) return; + + // Get parent node + const parentNodeId = getParentNodeId(node.id); + const parentNode = nodes.find((n) => n.id === parentNodeId); + if (!parentNode) return; + + setScheduledNodeUpdates((nodes) => nodes.concat(parentNode)); + }, + [nodes] + ); + const updateNode = useCallback( (node: Node) => { updateSolverNodes(node); // Update node data - updateNodeData(node.id, (n) => ({ - ...n, - position: getNodePosition(n.data), - type: "problemNode", - })); + updateNodeData(node.id, (n) => node); // Solver id and thus sub problems are the same for all problems const subProblems = node.data.problemDtos[0].subProblems; @@ -196,9 +236,44 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { const edgeId = getEdgeId(problemNodeIdentifier, node); const subNode = nodes.find((n) => n.id === subNodeId); + + const nodeData: ProblemNodeData = { + problemDtos: problemDtos, + level: node.data.level + 1, + levelInfo: { + index: i, + count: subProblems.length, + }, + solveCallback: (problemDto) => { + patchProblem(problemDto.typeId, problemDto.id, { + state: ProblemState.SOLVING, + }).then((dto) => { + setNodes((previousNodes) => + previousNodes.map((n) => { + if (n.id !== subNodeId) return n; + + let updatedNode: Node = { + ...n, + data: { + ...n.data, + problemDtos: [dto], // todo this needs to include all updated dtos + }, + }; + scheduleNodeUpdate(updatedNode); + + return updatedNode; + }) + ); + }); + }, + }; + if (subNode) { - // Update existing node if it exists - scheduleNodeUpdate(subNode); + // Update existing node with new data if it exists + scheduleNodeUpdate({ + ...subNode, + data: nodeData, + }); const edge = edges.find((edge) => edge.id === edgeId); if (edge) { @@ -218,37 +293,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { ), }); - let subNode = createProblemNode(subNodeId, { - problemDtos: problemDtos, - level: node.data.level + 1, - levelInfo: { - index: i, - count: subProblems.length, - }, - - solveCallback: (problemDto) => { - patchProblem(problemDto.typeId, problemDto.id, { - state: ProblemState.SOLVING, - }).then((dto) => { - setNodes((previousNodes) => - previousNodes.map((n) => { - if (n.id !== subNodeId) return n; - - let updatedNode: Node = { - ...n, - data: { - ...n.data, - problemDtos: [dto], // todo this needs to include all updated dtos - }, - }; - scheduleNodeUpdate(updatedNode); - - return updatedNode; - }) - ); - }); - }, - }); + let subNode = createProblemNode(subNodeId, nodeData); scheduleNodeUpdate(subNode); } }); @@ -278,6 +323,7 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { Promise.all( node.data.problemDtos.map((problemDto) => patchProblem(problemDto.typeId, problemDto.id, { + state: ProblemState.READY_TO_SOLVE, solverId: problemSolver.id, }) ) @@ -324,7 +370,6 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { function updateSolverNodes(node: Node) { // Load solver nodes when user action is required - console.log("data", node.data); if (node.data.problemDtos[0].solverId === undefined) { const existingSolverNode = nodes.find((n) => n.id.startsWith(node.id + solverNodeIdentifier) @@ -468,31 +513,33 @@ export const ProblemGraphView = (props: ProblemGraphViewProps) => { // }, [nodes, props.problemId, updateNode]); // Fit view when nodes change - useEffect(() => { - graphInstance?.fitView({ - duration: 500, - nodes: nodes, - }); - }, [graphInstance, nodes]); + // useEffect(() => { + // graphInstance?.fitView({ + // duration: 500, + // nodes: nodes, + // }); + // }, [graphInstance, nodes]); return ( -
- setGraphInstance(reactFlowInstance)} - nodes={nodes} - edges={edges} - onNodesChange={onNodesChange} - onEdgesChange={onEdgesChange} - nodeTypes={nodeTypes} - fitView + +
- - -
+ setGraphInstance(reactFlowInstance)} + nodes={nodes} + edges={edges} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChange} + nodeTypes={nodeTypes} + fitView + > + + +
+ ); }; diff --git a/src/components/solvers/Graph/ProblemList.tsx b/src/components/solvers/Graph/ProblemList.tsx index a5ea1b9..f45343f 100644 --- a/src/components/solvers/Graph/ProblemList.tsx +++ b/src/components/solvers/Graph/ProblemList.tsx @@ -6,6 +6,7 @@ import { ModalContent, ModalHeader, ModalOverlay, + Select, Spinner, Text, useDisclosure, @@ -19,7 +20,9 @@ import { MdError } from "react-icons/md"; import { ProblemDto } from "../../../api/data-model/ProblemDto"; import { ProblemState } from "../../../api/data-model/ProblemState"; import { SolutionStatus } from "../../../api/data-model/SolutionStatus"; +import { patchProblem } from "../../../api/ToolboxAPI"; import { ProblemDetails } from "./ProblemDetails"; +import { useGraphUpdates } from "./ProblemGraphView"; import { useSolvers } from "./SolverProvider"; function getStatusIcon(problemDto: ProblemDto): JSX.Element { @@ -46,8 +49,8 @@ function getStatusIcon(problemDto: ProblemDto): JSX.Element { export const ProblemList = (props: { problemDtos: ProblemDto[]; - selectedProblems: string[]; - setSelectedProblems: Dispatch>; + selectedProblemIds: string[]; + setSelectedProblemIds: Dispatch>; }) => { const { isOpen, onOpen, onClose } = useDisclosure(); const [modalProblemDto, setModalProblemDto] = useState>(); @@ -56,6 +59,7 @@ export const ProblemList = (props: { >(undefined); const { solvers, getSolvers } = useSolvers(); + const { updateProblem } = useGraphUpdates(); const typeId = props.problemDtos[0].typeId; const solverId = props.problemDtos[0].solverId; @@ -66,7 +70,7 @@ export const ProblemList = (props: { ) { setLastSelectedProblem(clickedSubProblem); if (!lastSelectedProblem) { - props.setSelectedProblems([clickedSubProblem.id]); + props.setSelectedProblemIds([clickedSubProblem.id]); return; } @@ -82,14 +86,14 @@ export const ProblemList = (props: { } if (e.ctrlKey) { - props.setSelectedProblems((prev) => [ + props.setSelectedProblemIds((prev) => [ ...prev.filter((id) => !newProblemIds.includes(id)), ...newProblemIds.filter( (id) => lastSelectedProblem.id == id || !prev.includes(id) ), ]); } else { - props.setSelectedProblems((prev) => + props.setSelectedProblemIds((prev) => newProblemIds.filter( (id) => lastSelectedProblem.id == id || !prev.includes(id) ) @@ -98,92 +102,84 @@ export const ProblemList = (props: { } return ( - // - // - // - // - // - // - // - // - // - // - // {subProblem.subProblemIds.map((subProblemId, index) => { - // return ( - // - // - // - // - // - // ); - // })} - // - //
SubroutineSolverStatus
- // {subProblem.subRoutine.typeId} {index} - // No solverNot solved
- //
- <> - - - Selected {props.selectedProblems.length} of {props.problemDtos.length} - + + + Selected {props.selectedProblemIds.length} of {props.problemDtos.length} + - - - - - - {typeId} - - - {modalProblemDto && ( - - )} - - - + + + + + + {typeId} + + + {modalProblemDto && } + + + - {props.problemDtos.map((problemDto, index) => ( - ( + id == problemDto.id) + ? "lightgrey" + : "ghostwhite" + } + onClick={(e) => handleClick(e, problemDto)} + > + {getStatusIcon(problemDto)} + + {typeId} {index + 1} + + -
{ - setModalProblemDto(problemDto); - onOpen(); - }} - > - -
-
- ))} -
- +
{ + setModalProblemDto(problemDto); + onOpen(); + }} + > + +
+ + ))} +
); }; diff --git a/src/components/solvers/Graph/ProblemNode.tsx b/src/components/solvers/Graph/ProblemNode.tsx index 5a66afc..565b66c 100644 --- a/src/components/solvers/Graph/ProblemNode.tsx +++ b/src/components/solvers/Graph/ProblemNode.tsx @@ -70,23 +70,23 @@ export interface ProblemNodeData { solveCallback: (problem: ProblemDto) => void; } -function getNodeType(data: ProblemNodeData) { - // Add input/output connect points for edges - if (data.problemDtos.some((dto) => dto.solverId === undefined)) { - if (data.level == 0) { - return "input"; - } else { - return "default"; - } - } else { - if (data.level === 0) { - return "input"; - } else if (data.problemDtos.length == 0) { - return "output"; - } else { - return "default"; - } - } +function getNodeType(data: ProblemNodeData): { + topHandle: boolean; + bottomHandle: boolean; +} { + let topHandle = data.level > 0; + + let bottomHandle = + data.problemDtos[0].solverId === undefined || + data.problemDtos.some( + (dto) => + dto.state === ProblemState.SOLVED || dto.state === ProblemState.SOLVING + ); + + return { + topHandle: topHandle, + bottomHandle: bottomHandle, + }; } function getStatusColor(problemDtos: ProblemDto[]): Color { @@ -138,9 +138,9 @@ function getStatusIcon(problemDto: ProblemDto): JSX.Element { export function ProblemNode(props: NodeProps) { const { isOpen, onOpen, onClose } = useDisclosure(); const [dropdownOpen, setDropdownOpen] = useState(false); - const [selectedSubProblems, setSelectedSubProblems] = useState([]); + const [selectedProblemIds, setSelectedProblemIds] = useState([]); - const nodeType = getNodeType(props.data); + const { topHandle, bottomHandle } = getNodeType(props.data); const nodeColor = getStatusColor(props.data.problemDtos); const { solvers, getSolvers } = useSolvers(); @@ -155,7 +155,7 @@ export function ProblemNode(props: NodeProps) { const extended = solverId !== undefined; - console.log("ProblemNode", typeId, props.data.problemDtos); + console.log("ProblemNode", props.data.problemDtos); return ( ) { }, }} > - {(nodeType === "output" || nodeType === "default") && ( - - )} - {(nodeType === "input" || nodeType === "default") && ( - - )} + {topHandle && } + {bottomHandle && } ) { borderRadius: "5px", zIndex: 1, }, + // TODO: add x button to connection point in case there is a solver connect to deconnect it and set solver id to "" "&::before": { transform: "translate(calc(-50% - 50px))", }, @@ -212,6 +210,9 @@ export function ProblemNode(props: NodeProps) { + {props.data.problemDtos.length > 1 + ? props.data.problemDtos.length + "x " + : ""} {props.data.problemDtos[0].typeId} @@ -233,13 +234,10 @@ export function ProblemNode(props: NodeProps) { {props.data.problemDtos.map((problemDto) => ( - <> - +
+ - +
))}
@@ -247,7 +245,7 @@ export function ProblemNode(props: NodeProps) { {props.data.problemDtos.length > 1 && ( - + ) { width: "100%", maxHeight: "10rem", overflowY: "auto", + marginBottom: "10px", }} > {dropdownOpen && ( )}
@@ -285,7 +284,7 @@ export function ProblemNode(props: NodeProps) { border="1px" borderBottomRadius="10px" padding=".5rem" - background={"kitGreen"} + background="ghostwhite" fontSize="xs" zIndex="0" position="relative" @@ -327,24 +326,84 @@ export function ProblemNode(props: NodeProps) {
+ {/*Solve Button*/} {props.data.problemDtos.some( - (dto) => dto.state === ProblemState.NEEDS_CONFIGURATION + (dto) => + dto.state === ProblemState.NEEDS_CONFIGURATION || + dto.state === ProblemState.READY_TO_SOLVE + ) && + props.data.problemDtos.every( + (dto) => dto.state !== ProblemState.SOLVING + ) && ( +
+ +
+ )} + + {/*Solving*/} + {props.data.problemDtos.some( + (dto) => dto.state === ProblemState.SOLVING ) && ( + Solving + + )} + + {/*Solved*/} + {props.data.problemDtos.every( + (dto) => dto.state === ProblemState.SOLVED + ) && ( + { - for (let problemDto of props.data.problemDtos) { - props.data.solveCallback(problemDto); - } - }} > - Solve + Solved )}
diff --git a/src/components/solvers/Graph/SolverNode.tsx b/src/components/solvers/Graph/SolverNode.tsx index 405fea4..633b76d 100644 --- a/src/components/solvers/Graph/SolverNode.tsx +++ b/src/components/solvers/Graph/SolverNode.tsx @@ -15,7 +15,6 @@ import { Tooltip, VStack, } from "@chakra-ui/react"; -import { useState } from "react"; import { FaQuestionCircle } from "react-icons/fa"; import { FaGears } from "react-icons/fa6"; import { Handle, NodeProps, Position } from "reactflow"; @@ -28,14 +27,12 @@ export interface SolverNodeData { } export function SolverNode(props: NodeProps) { - const [selected, setSelected] = useState(false); - return ( ) { - { - props.data.selectCallback(props.data.problemSolver); - setSelected(true); + +
- Select - + +
); diff --git a/src/components/solvers/Graph/TestGraphDisplay.tsx b/src/components/solvers/Graph/TestGraphDisplay.tsx index cc46b51..f7a3a09 100644 --- a/src/components/solvers/Graph/TestGraphDisplay.tsx +++ b/src/components/solvers/Graph/TestGraphDisplay.tsx @@ -5,9 +5,8 @@ export const TestGraphDisplay = () => { return ( ); diff --git a/src/theme.ts b/src/theme.ts index 5a40348..f0acb2a 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -4,6 +4,7 @@ const theme = extendTheme({ semanticTokens: { colors: { kitGreen: "#00876C", + kitGreenAlpha: "#00876CA0", heroGradientStart: { default: "kitGreen", _dark: "#e3a7f9", From 5fcd32798d90ef8a7b96048539c509f2ef2b6aaa Mon Sep 17 00:00:00 2001 From: Elscrux Date: Sat, 10 Aug 2024 13:43:53 +0200 Subject: [PATCH 14/92] fix: Warning with value nested in textarea --- src/components/solvers/Graph/ProblemDetails.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/solvers/Graph/ProblemDetails.tsx b/src/components/solvers/Graph/ProblemDetails.tsx index 3e0b127..7f1dc4f 100644 --- a/src/components/solvers/Graph/ProblemDetails.tsx +++ b/src/components/solvers/Graph/ProblemDetails.tsx @@ -52,9 +52,7 @@ export const ProblemDetails = (props: { problemDto: ProblemDto }) => { return ( - +