diff --git a/.prettierrc.json b/.prettierrc.json index 0967ef4..55c1943 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1 +1,3 @@ -{} +{ + "plugins": ["prettier-plugin-organize-imports"] +} diff --git a/__tests__/converter/dimacs/DimacsLogicalExpression.spec.ts b/__tests__/converter/dimacs/DimacsLogicalExpression.spec.ts index d4d2506..0382853 100644 --- a/__tests__/converter/dimacs/DimacsLogicalExpression.spec.ts +++ b/__tests__/converter/dimacs/DimacsLogicalExpression.spec.ts @@ -1,8 +1,7 @@ import each from "jest-each"; -import { regexBlank } from "../../../src/converter/dimacs/Syntax/CommonSyntax"; import { DimacsParser } from "../../../src/converter/dimacs/DimacsParser"; +import { regexBlank } from "../../../src/converter/dimacs/Syntax/CommonSyntax"; import { regexComment } from "../../../src/converter/dimacs/Syntax/DimacsSyntax"; -import { LogicalExpressionParser } from "../../../src/converter/dimacs/LogicalExpressionParser"; import { regexNOT } from "../../../src/converter/dimacs/Syntax/LogicalExpressionSyntax"; function isEquivalentLogicalExpression(f1: string, f2: string) { @@ -19,36 +18,6 @@ function isEquivalentDimacs(f1: string, f2: string) { describe("Parsing", () => { let dimacsParser = new DimacsParser(); - let logicalExpressionParser = new LogicalExpressionParser(); - - each([ - [ - "((1 or 2 or not 3) and (!4 and (not 5 and 6)) and 3 and (7 or 2))", - "c First comment\nc Some Comment\nc 1 => 1\np sat 7\n*(+( 1 2 -3 )*( -4 *( -5 6 )) 3 +( 7 2 ))", - ], - [ - "(1 or (2 and 3) or (((1 and 4) or 5) and 6))", - "p sat 6\n+(1 *(2 3)*(+(*(1 4) 5) 6))", - ], - [ - "(((1 and not 2 and 3 and 4) or 3) and 5)", - "c Sample DIMACS .sat file\np sat 5\n*(+(*(1 -2 3 4) 3) 5)", - ], - ["((1 and not 2) or 3)", "p sat 3\n+(*(1 -2) 3)"], - ["((1 and 2) or not 3)", "p sat 3\n+(*(1 2) -3)"], - ]).test( - "parsing bi-directional", - (logicalExpression: string, dimacs: string) => { - isEquivalentDimacs( - logicalExpressionParser.parseDimacs(logicalExpression), - dimacs - ); - isEquivalentLogicalExpression( - dimacsParser.parseLogicalExpression(dimacs), - logicalExpression - ); - } - ); each([ [ diff --git a/package.json b/package.json index ee530d9..a2403e2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "provideq", - "version": "0.3.0", + "version": "0.4.0", "private": true, "scripts": { "dev": "next dev", @@ -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", @@ -40,6 +37,7 @@ "jest": "^29.1.1", "jest-environment-jsdom": "^29.1.1", "prettier": "2.8.8", + "prettier-plugin-organize-imports": "^3.2.4", "typescript": "4.8.2" } } diff --git a/src/api/ToolboxAPI.ts b/src/api/ToolboxAPI.ts index d0703f0..d9a392c 100644 --- a/src/api/ToolboxAPI.ts +++ b/src/api/ToolboxAPI.ts @@ -1,68 +1,115 @@ -import { MetaSolverSetting } from "./data-model/MetaSolverSettings"; -import { SubRoutineDefinition } from "./data-model/SubRoutineDefinition"; -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 { getInvalidProblemDto, ProblemDto } from "./data-model/ProblemDto"; +import { ProblemSolverInfo } from "./data-model/ProblemSolverInfo"; +import { ProblemState } from "./data-model/ProblemState"; +import { SolverSetting } from "./data-model/SolverSettings"; +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( + problemTypeId: string, + problemId: string +): Promise> { + return fetch(`${baseUrl()}/problems/${problemTypeId}/${problemId}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .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(), + error: `${reason}`, + }; + }); +} + export async function postProblem( - problemType: string, - solveRequest: SolveRequest -): Promise { - return fetch(`${baseUrl()}/solve/${problemType}`, { + problemTypeId: string, + problemRequest: ProblemDto +): Promise> { + return fetch(`${baseUrl()}/problems/${problemTypeId}`, { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(solveRequest), + body: JSON.stringify(problemRequest), }) .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: "", + ...problemRequest, + error: `${reason}`, + }; + }); +} + +export async function patchProblem( + problemTypeId: string, + problemId: string, + updateParameters: { + input?: any; + solverId?: string; + state?: ProblemState; + solverSettings?: SolverSetting[]; + } +): Promise> { + return fetch(`${baseUrl()}/problems/${problemTypeId}/${problemId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(updateParameters), + }) + .then((response) => response.json()) + .then((json) => json as ProblemDto) + .catch((reason) => { + return { + ...getInvalidProblemDto(), error: `${reason}`, }; }); } export async function fetchSolvers( - problemType: string -): Promise { - return fetch(`${baseUrl()}/solvers/${problemType}`, { + problemTypeId: string +): Promise { + return fetch(`${baseUrl()}/solvers/${problemTypeId}`, { 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}.`); + alert(`Could not retrieve solvers of type ${problemTypeId}.`); return []; }); } export async function fetchSubRoutines( - problemType: string, + problemTypeId: string, solverId: string -): Promise { +): Promise { return fetch( - `${baseUrl()}/sub-routines/${problemType}?${new URLSearchParams({ - id: solverId, - })}`, + `${baseUrl()}/solvers/${problemTypeId}/${solverId}/sub-routines`, { method: "GET", headers: { @@ -78,10 +125,11 @@ export async function fetchSubRoutines( }); } -export async function fetchMetaSolverSettings( - problemType: string -): Promise { - return fetch(`${baseUrl()}/meta-solver/settings/${problemType}`, { +export async function fetchSolverSettings( + problemTypeId: string, + solverId: string +): Promise<[]> { + return fetch(`${baseUrl()}/solvers/${problemTypeId}/${solverId}/settings`, { method: "GET", headers: { "Content-Type": "application/json", @@ -89,7 +137,8 @@ export async function fetchMetaSolverSettings( }) .then((response) => response.json()) .catch((reason) => { - console.log(reason); + console.error(reason); + alert(`Could not retrieve subroutines of solver ${solverId}.`); return []; }); } diff --git a/src/api/data-model/MetaSolverSettings.ts b/src/api/data-model/MetaSolverSettings.ts deleted file mode 100644 index f2471a9..0000000 --- a/src/api/data-model/MetaSolverSettings.ts +++ /dev/null @@ -1,30 +0,0 @@ -export enum MetaSolverSettingType { - RANGE = "RANGE", - CHECKBOX = "CHECKBOX", - TEXT = "TEXT", - SELECT = "SELECT", -} - -export interface MetaSolverSetting { - name: string; - type: MetaSolverSettingType; -} - -export interface RangeSetting extends MetaSolverSetting { - min: number; - max: number; - value: number; -} - -export interface CheckboxSetting extends MetaSolverSetting { - state: boolean; -} - -export interface TextSetting extends MetaSolverSetting { - text: string; -} - -export interface SelectSetting extends MetaSolverSetting { - options: string[]; - selectedOption: string; -} diff --git a/src/api/data-model/ProblemDto.ts b/src/api/data-model/ProblemDto.ts new file mode 100644 index 0000000..5af02f8 --- /dev/null +++ b/src/api/data-model/ProblemDto.ts @@ -0,0 +1,37 @@ +import { ProblemState } from "./ProblemState"; +import { getInvalidSolutionObject, SolutionObject } from "./SolutionObject"; +import { SolverSetting } from "./SolverSettings"; +import { SubRoutineReferenceDto } from "./SubRoutineReferenceDto"; + +export interface ProblemDto { + id: string; + typeId: string; + input: T; + solution: SolutionObject; + state: ProblemState; + solverId?: string; + solverSettings: SolverSetting[]; + subProblems: SubRoutineReferenceDto[]; + error: string; +} + +export function getInvalidProblemDto(): ProblemDto { + return { + error: "", + id: "", + input: {} as T, + solution: getInvalidSolutionObject(), + solverId: "", + solverSettings: [], + state: ProblemState.READY_TO_SOLVE, + subProblems: [], + typeId: "", + }; +} + +export function canProblemSolverBeUpdated(problem: ProblemDto): boolean { + return ( + problem.state === ProblemState.NEEDS_CONFIGURATION || + problem.state === ProblemState.READY_TO_SOLVE + ); +} 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 8f65e4f..70bdb66 100644 --- a/src/api/data-model/SolutionStatus.ts +++ b/src/api/data-model/SolutionStatus.ts @@ -1,5 +1,6 @@ export enum SolutionStatus { - INVALID, - COMPUTING, - SOLVED, + 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/SolverSettings.ts b/src/api/data-model/SolverSettings.ts new file mode 100644 index 0000000..f8bd877 --- /dev/null +++ b/src/api/data-model/SolverSettings.ts @@ -0,0 +1,33 @@ +export enum SolverSettingType { + INTEGER = "INTEGER", + DOUBLE = "DOUBLE", + CHECKBOX = "CHECKBOX", + TEXT = "TEXT", + SELECT = "SELECT", +} + +export interface SolverSetting { + name: string; + description: string; + type: SolverSettingType; + required: boolean; +} + +export interface RangeSetting extends SolverSetting { + min: number; + max: number; + value: number; +} + +export interface CheckboxSetting extends SolverSetting { + state: boolean; +} + +export interface TextSetting extends SolverSetting { + text: string; +} + +export interface SelectSetting extends SolverSetting { + options: string[]; + selectedOption: string; +} 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/demonstrators/Demonstrator.tsx b/src/components/demonstrators/Demonstrator.tsx new file mode 100644 index 0000000..4e704fc --- /dev/null +++ b/src/components/demonstrators/Demonstrator.tsx @@ -0,0 +1,126 @@ +import { + Button, + FormControl, + FormLabel, + HStack, + Spinner, + Text, + Tooltip, + VStack, +} from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import { getInvalidProblemDto } from "../../api/data-model/ProblemDto"; +import { ProblemState } from "../../api/data-model/ProblemState"; +import { SolverSetting } from "../../api/data-model/SolverSettings"; +import { fetchSolverSettings, postProblem } from "../../api/ToolboxAPI"; +import { settingComponentMap } from "../solvers/settings/SettingsView"; + +export interface DemonstratorProps { + demonstratorId: string; + onSolved: (solution: string) => void; +} + +enum DemonstratorState { + IDLE, + SOLVING, + SOLVED, +} + +function getHumanReadableState(state: DemonstratorState) { + switch (state) { + case DemonstratorState.IDLE: + return "Go"; + case DemonstratorState.SOLVING: + return ( + + Running + + + ); + case DemonstratorState.SOLVED: + return "Run again"; + } +} + +const demonstratorTypeId = "demonstrator"; + +export const Demonstrator = (props: DemonstratorProps) => { + const [settings, setSettings] = useState([]); + const [state, setState] = useState(DemonstratorState.IDLE); + const [error, setError] = useState(""); + + useEffect(() => { + fetchSolverSettings(demonstratorTypeId, props.demonstratorId).then( + setSettings + ); + }, [props.demonstratorId]); + + function updateSetting(newSetting: SolverSetting) { + const index = settings.findIndex( + (setting) => setting.name === newSetting.name + ); + if (index !== -1) { + settings.splice(index, 1, newSetting); + } + + setSettings([...settings]); + } + + return ( + + {settings.map((setting) => { + const SettingComponent = settingComponentMap[setting.type]; + return ( + + + {setting.name} + + + + ); + })} + + + + {error && {error}} + + ); +}; diff --git a/src/components/landing-page/Card.tsx b/src/components/landing-page/Card.tsx new file mode 100644 index 0000000..14d3229 --- /dev/null +++ b/src/components/landing-page/Card.tsx @@ -0,0 +1,65 @@ +import { Badge, Box, LinkBox, LinkOverlay } from "@chakra-ui/react"; +import NextLink from "next/link"; + +export interface CardProps { + href: string; + new?: boolean; + tags?: string[]; + title: string; + description: string; +} + +export const Card = (props: CardProps & { refer: string }) => { + return ( + + + + {props.new && ( + + New! + + )} + + {props.tags && + props.tags.map((tag) => ( + + {tag} + + ))} + + + + + + {props.title} + + + + {props.description} + + + {props.refer} → + + + + ); +}; diff --git a/src/components/landing-page/DemonstratorCard.tsx b/src/components/landing-page/DemonstratorCard.tsx new file mode 100644 index 0000000..ecbbac7 --- /dev/null +++ b/src/components/landing-page/DemonstratorCard.tsx @@ -0,0 +1,5 @@ +import { Card, CardProps } from "./Card"; + +export const DemonstratorCard = (props: CardProps) => { + return ; +}; diff --git a/src/components/landing-page/DemonstratorChooser.tsx b/src/components/landing-page/DemonstratorChooser.tsx new file mode 100644 index 0000000..ede48b3 --- /dev/null +++ b/src/components/landing-page/DemonstratorChooser.tsx @@ -0,0 +1,15 @@ +import { Grid, GridItem, GridProps } from "@chakra-ui/react"; +import { DemonstratorCard } from "./DemonstratorCard"; + +export const DemonstratorChooser = (props: GridProps) => ( + + + + + +); diff --git a/src/components/landing-page/ProblemCard.tsx b/src/components/landing-page/ProblemCard.tsx index e280c07..d262a75 100644 --- a/src/components/landing-page/ProblemCard.tsx +++ b/src/components/landing-page/ProblemCard.tsx @@ -1,64 +1,5 @@ -import { LinkBox, LinkOverlay, Box, Image, Badge } from "@chakra-ui/react"; -import NextLink from "next/link"; +import { Card, CardProps } from "./Card"; -interface ProblemCardProps { - href: string; - new: boolean; - tags: string[]; - problemName: string; - description: string; -} - -export const ProblemCard = (props: ProblemCardProps) => { - return ( - - - - {props.new && ( - - New - - )} - - {props.tags.map((tag) => ( - - {tag} - - ))} - - - - - - {props.problemName} - - - - {props.description} - - - Solve this problem → - - - - ); +export const ProblemCard = (props: CardProps) => { + return ; }; diff --git a/src/components/landing-page/ProblemChooser.tsx b/src/components/landing-page/ProblemChooser.tsx index bd1bde4..5c58733 100644 --- a/src/components/landing-page/ProblemChooser.tsx +++ b/src/components/landing-page/ProblemChooser.tsx @@ -6,28 +6,61 @@ export const ProblemChooser = (props: GridProps) => ( + + + + + + + + + + + + diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index 2b2e31c..60113c1 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -1,9 +1,9 @@ -import { ReactElement, ReactNode } from "react"; +import { Spacer } from "@chakra-ui/react"; +import { ReactNode } from "react"; +import { Container } from "../Container"; import { Hero } from "../landing-page/Hero"; import { Main } from "../Main"; -import { Container } from "../Container"; import { Footer } from "./Footer"; -import { Spacer } from "@chakra-ui/react"; export const Layout = (props: { children: ReactNode }) => ( diff --git a/src/components/solvers/EditorControls.tsx b/src/components/solvers/EditorControls.tsx index f643079..c7d8994 100644 --- a/src/components/solvers/EditorControls.tsx +++ b/src/components/solvers/EditorControls.tsx @@ -5,9 +5,15 @@ import { Text, Tooltip, } from "@chakra-ui/react"; -import { TbDownload, TbHelp, TbUpload } from "react-icons/tb"; -import { chooseFile } from "./FileInput"; +import { + TbDownload, + TbHelp, + TbRepeat, + TbTrash, + TbUpload, +} from "react-icons/tb"; import { baseUrl } from "../../api/ToolboxAPI"; +import { chooseFile } from "./FileInput"; export interface EditorControlsProps { /** @@ -25,9 +31,9 @@ export interface EditorControlsProps { */ editorContent: string; /** - * Contents of uploaded problem files will be sent to this handler. + * Function to set the contents of the editor that these controls relate to. */ - onUpload: (uploadContent: string) => void; + setEditorContent: (newContent: string) => void; /** * Link to the documentation for the problem type that is being edited. @@ -74,7 +80,7 @@ export const EditorControls = (props: EditorControlsProps) => { return ( {props.errorText ? ( - {props.errorText} + {props.errorText} ) : ( {props.idleText} )} @@ -90,7 +96,26 @@ export const EditorControls = (props: EditorControlsProps) => { } - onClick={() => upload(props.onUpload)} + onClick={() => upload(props.setEditorContent)} + /> + + + } + onClick={() => props.setEditorContent("")} + /> + + + } + onClick={() => { + props.setEditorContent(""); + setTimeout(() => { + props.setEditorContent(props.editorContent); + }); + }} /> diff --git a/src/components/solvers/FileInput.tsx b/src/components/solvers/FileInput.tsx index 15b8b87..92b7ce4 100644 --- a/src/components/solvers/FileInput.tsx +++ b/src/components/solvers/FileInput.tsx @@ -1,5 +1,3 @@ -import React from "react"; - export async function chooseFile( multiple?: boolean, accept?: string diff --git a/src/components/solvers/Graph/GMLGraphView.tsx b/src/components/solvers/Graph/GMLGraphView.tsx new file mode 100644 index 0000000..55a10eb --- /dev/null +++ b/src/components/solvers/Graph/GMLGraphView.tsx @@ -0,0 +1,100 @@ +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 ( +
+ + + +
+ ); +}; diff --git a/src/components/solvers/Graph/GraphArea.tsx b/src/components/solvers/Graph/GraphArea.tsx deleted file mode 100644 index 63adbae..0000000 --- a/src/components/solvers/Graph/GraphArea.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Box, Center, Container, Select, Spacer } from "@chakra-ui/react"; -import React, { useEffect, useRef, useState } from "react"; -import G6, { Graph, GraphData } from "@antv/g6"; - -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/components/solvers/Graph/ProblemDetails.tsx b/src/components/solvers/Graph/ProblemDetails.tsx new file mode 100644 index 0000000..0a206c7 --- /dev/null +++ b/src/components/solvers/Graph/ProblemDetails.tsx @@ -0,0 +1,103 @@ +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 { SettingsView } from "../settings/SettingsView"; +import { SolutionView } from "../SolutionView"; +import { useGraphUpdates } from "./ProblemGraphView"; +import { useSolvers } from "./SolverProvider"; + +function getHumanReadableState(state: ProblemState) { + switch (state) { + 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(); + const { updateProblem } = useGraphUpdates(); + + // Update solvers in case they are not loaded yet + if (!solvers[props.problemDto.typeId]) getSolvers(props.problemDto.typeId); + + const solver = solvers[props.problemDto.typeId]?.find( + (s) => s.id === props.problemDto.solverId + ); + + return ( + +