-- full integration with mol-edit backend
-- added readme
-- added vqe local_computational
This commit is contained in:
2026-05-25 19:17:04 +03:00
parent 76fe1c4294
commit c5ece3ff37
16 changed files with 1134 additions and 163 deletions

View File

@@ -1,6 +1,6 @@
<!-- index.html -->
<!doctype html>
<html>
<html style="height: 100%">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -9,8 +9,6 @@
margin: 0;
padding: 0;
box-sizing: border-box;
background: none;
background-color: var(--mantine-color-secondary);
}
body {
font-family:
@@ -19,7 +17,7 @@
}
</style>
</head>
<body>
<body style="background-color: transparent !important; height: 100%">
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>

View File

@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
interface TextAreaProps {
text: string;
onTextChange: (text: string) => void;
enabled: boolean;
}
export function CodeTextArea(props: TextAreaProps) {
@@ -52,6 +53,7 @@ export function CodeTextArea(props: TextAreaProps) {
className="MoleculeEdit"
value={props.text}
ref={inputRef}
disabled={!props.enabled}
onChange={(e) => {
props.onTextChange(e.currentTarget.value);
}}

View File

@@ -0,0 +1,62 @@
export interface ConvertSchema {
inputText: string;
inputFormat: string;
add_h: boolean;
make_3d: boolean;
optimize: boolean;
}
export async function ConvertMoleculeToStandart(
data: ConvertSchema,
): Promise<string> {
try {
const response = await fetch("https://mol-convert.deowl.ru" + "/convert", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
text: data.inputText,
format: data.inputFormat,
convert_3d: data.make_3d,
add_hydrogen: data.add_h,
optimize_geometry: data.optimize,
}),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const responseData = await response.json();
return responseData.molfile;
} catch (error) {
// Error handling
if (error instanceof Error) {
return error;
}
throw error;
}
}
export async function GetInFormats(): Promise<{ [key: string]: string }> {
try {
const response = await fetch(
"https://mol-convert.deowl.ru" + "/informats",
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
throw error;
}
}

View File

@@ -1,90 +1,291 @@
import {
Button,
Card,
Center,
Divider,
Tabs,
Title,
Text,
Textarea,
useMantineTheme,
Paper,
Badge,
SimpleGrid,
} from "@mantine/core";
import { Group, Panel, Separator } from "react-resizable-panels";
import "./MoleculePage.css";
import {
IconCheck,
IconAlertCircle,
IconChartBar,
IconCode,
IconGripHorizontal,
IconGripVertical,
IconList,
IconX,
} from "@tabler/icons-react";
import { CodeTextArea } from "./CodeTextArea/CodeTextArea";
import MoleculeViewer from "./MoleculeViewer/MoleculeViewer";
import { MoleculeData, SelectedAtom, TaskEditorProps } from "../Types/plugin";
import { useState } from "react";
import { SelectedAtom, TaskEditorProps } from "../Types/plugin";
import { useEffect, useState } from "react";
import { NewMoleculeModal } from "./NewMolecule/NewMolecule";
function MoleculeEditorPage(props: TaskEditorProps<MoleculeData>) {
const [text, setText] = useState(props.data?.data.text);
function MoleculeEditorPage(props: TaskEditorProps) {
const [text, setText] = useState(props.data?.text);
const [selectedAtom, setSelectedAtom] = useState<SelectedAtom | null>(null);
const theme = useMantineTheme();
const [isOpen, setIsOpen] = useState(false);
const [counter_new, set_counter_new] = useState(0);
useEffect(() => {
console.log("Resetting text");
setText(props.data?.text);
setSelectedAtom(null);
}, [props.counter]);
const get_qubits = (text: string) => {
try {
const lines = text.trim().replace("\\\\", "\\").split("\n") || [
"",
"",
"",
];
console.log(lines);
const secondLine = lines[1]; // "Charge=0 Multiplicity=1 Electrons=10 Orbitals=9"
const variables = secondLine.split(" ").reduce(
(acc, pair) => {
const [key, value] = pair.split("=");
acc[key] = parseInt(value); // or Number(value) if you want to keep as number
return acc;
},
{} as Record<string, number>,
);
return variables.Orbitals * 2;
} catch {
return 0;
}
};
return (
<>
<div style={{ display: "flex", flexDirection: "column", height: "90vh" }}>
<div
style={{
display: "flex",
flexDirection: "column",
height: props.simProgress ? "100%" : "90vh",
}}
>
<NewMoleculeModal
isOpened={isOpen}
setIsOpened={setIsOpen}
setMolecule={(data) => {
props.setData(data, get_qubits(data.text));
setText(data.text);
setTimeout(() => set_counter_new((old) => old + 2), 100);
}}
/>
{!props.simProgress && (
<Button onClick={() => setIsOpen(true)}>Импротировать</Button>
)}
<Divider style={{ margin: "10px" }} />
<Group style={{ flex: 1, gap: "5px" }}>
<Panel minSize={200} defaultSize={500}>
<Tabs
variant="outline"
style={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
radius="lg"
defaultValue="code"
classNames={{ panel: "tabPanel" }}
>
<Tabs.List>
<Tabs.Tab value="code" leftSection={<IconCode size={12} />}>
Код
</Tabs.Tab>
<Tabs.Tab value="list" leftSection={<IconList size={12} />}>
Список
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="code">
<CodeTextArea
text={text || ""}
onTextChange={(text) => {
setText(text);
props.setData({ text: text });
<Group orientation="vertical" style={{ flex: 1, gap: "5px" }}>
<Panel>
<Group style={{ flex: 1, gap: "5px" }}>
<Panel minSize={200} defaultSize={500}>
<Tabs
variant="outline"
style={{
height: "100%",
display: "flex",
flexDirection: "column",
}}
/>
</Tabs.Panel>
radius="lg"
defaultValue="code"
classNames={{ panel: "tabPanel" }}
>
<Tabs.List>
<Tabs.Tab value="code" leftSection={<IconCode size={12} />}>
Код
</Tabs.Tab>
<Tabs.Tab value="list" leftSection={<IconList size={12} />}>
Список
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="list">
<></>
</Tabs.Panel>
</Tabs>
</Panel>
<Separator>
<Center
style={{
height: "100%",
background: theme.colors.dark[4],
borderRadius: "5px",
}}
>
<IconGripVertical size={12} />
</Center>
</Separator>
<Panel style={{ flexShrink: 0 }} minSize={200}>
<MoleculeViewer
moleculeData={props.data ? props.data.data.text : ""}
selectedAtom={selectedAtom}
setSelectedAtom={setSelectedAtom}
/>
<Tabs.Panel value="code">
<CodeTextArea
enabled={props.simProgress == undefined}
text={text || ""}
onTextChange={(text) => {
setText(text);
props.setData({ text: text }, get_qubits(text));
}}
/>
</Tabs.Panel>
<Tabs.Panel value="list">
<></>
</Tabs.Panel>
</Tabs>
</Panel>
<Separator>
<Center
style={{
height: "100%",
background: theme.colors.dark[4],
borderRadius: "5px",
}}
>
<IconGripVertical size={12} />
</Center>
</Separator>
<Panel style={{ flexShrink: 0 }} minSize={200}>
<MoleculeViewer
moleculeData={props.data ? props.data.text : ""}
selectedAtom={selectedAtom}
setSelectedAtom={setSelectedAtom}
reload_counter={props.counter + counter_new}
/>
</Panel>
</Group>
</Panel>
{props.simProgress && (
<>
<Separator>
<Center
style={{
height: "100%",
background: theme.colors.dark[4],
borderRadius: "5px",
}}
>
<IconGripHorizontal size={12} />
</Center>
</Separator>
<Panel>
{props.simProgress.error ? (
<Card withBorder style={{ height: "100%" }}>
<Group gap="xs" mb="md" orientation="vertical">
<IconAlertCircle size={20} color="#fa5252" />
<Title order={4} c="red" style={{ margin: 0 }}>
Ошибка выполнения
</Title>
<Text size="sm" c="red" fw={500} mb="xs">
{props.simProgress.error}
</Text>
</Group>
{props.simProgress.traceback && (
<>
<Text size="xs" c="dimmed" mb="xs" fw={500}>
Подробности:
</Text>
<Paper
p="xs"
withBorder
bg="dark.8"
style={{
maxHeight: "200px",
overflow: "auto",
fontFamily: "monospace",
fontSize: "11px",
}}
>
<Text
size="xs"
c="gray.5"
style={{
whiteSpace: "pre-wrap",
wordBreak: "break-all",
}}
>
{props.simProgress.traceback}
</Text>
</Paper>
</>
)}
</Card>
) : (
<Card withBorder style={{ height: "100%" }}>
<Group gap="xs">
<IconChartBar size={20} color="#4299e1" />
<Title order={4} style={{ margin: 0 }}>
Прогресс VQE
</Title>
</Group>
<SimpleGrid cols={3} mb="lg">
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
Итерация
</Text>
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
Энергия (Hartree)
</Text>
<Text size="xs" c="dimmed" tt="uppercase" fw={700} mb={4}>
Сходимость
</Text>
<Text size="xl" fw={700} c="blue">
{props.simProgress["iter_num"]}
</Text>
<Text size="xl" fw={700} c="teal">
{props.simProgress["energy"]?.toFixed(8)}
</Text>
<Text
size="xl"
fw={700}
c={
props.simProgress["conv"] < 1e-6 ? "green" : "orange"
}
>
{props.simProgress["conv"]}
</Text>
</SimpleGrid>
<details style={{ marginTop: "16px" }}>
<summary
style={{
cursor: "pointer",
fontSize: "13px",
color: "#868e96",
marginBottom: "8px",
}}
>
Показать итоговые веса (
{props.simProgress["params"]?.length})
</summary>
<Paper
p="sm"
withBorder
bg="dark.8"
style={{ maxHeight: "150px", overflow: "auto" }}
>
<Text size="xs" fw={500} mb="xs" c="dimmed">
Параметры оптимизации:
</Text>
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: "8px",
}}
>
{props.simProgress["params"]?.map((param, idx) => (
<Badge
key={idx}
size="sm"
variant="outline"
color="blue"
>
p{idx}: {param.toFixed(6)}
</Badge>
))}
</div>
</Paper>
</details>
</Card>
)}
</Panel>
</>
)}
</Group>
</div>
</>

View File

@@ -1,6 +1,7 @@
import Viewer from "miew-react";
import Miew from "miew";
import {
LoadingOverlay,
Paper,
UnstyledButton,
useMantineColorScheme,
@@ -45,6 +46,7 @@ interface MoleculeViewerProps {
moleculeData: string;
selectedAtom: SelectedAtom | null;
setSelectedAtom: (selectedAtom: SelectedAtom | null) => void;
reload_counter: number;
}
function MoleculeViewer(props: MoleculeViewerProps) {
@@ -57,10 +59,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
const [isResizing, setIsResizing] = useState<boolean>(false);
const [viewingData, setViewingData] = useState<string>(props.moleculeData);
const [isError, setIsError] = useState(false);
useEffect(() => {
setViewingData(props.moleculeData);
}, [props.reload_counter]);
//при загрузке сохраняем объект miew
const onInitMiew = (miew: Miew) => {
setMiew(miew);
setIsError(false);
if (miew && viewingData) {
//прогружаем молекулу
miew
@@ -77,15 +85,7 @@ function MoleculeViewer(props: MoleculeViewerProps) {
if (error.message == "Operation cancelled") {
return;
}
notifications.show({
color: "orange",
radius: "md",
title: "Ошибка при отображении молекулы",
message:
"Проверте правильность написания кода молекулы, в нем содержатся ошибки",
icon: <IconAlertHexagon />,
style: { paddingLeft: "5px" },
});
setIsError(true);
});
}
miew.setOptions({
@@ -212,10 +212,16 @@ function MoleculeViewer(props: MoleculeViewerProps) {
backgroundColor: theme.colors.secondaryDark[7],
borderRadius: "5px",
overflow: "hidden",
position: "relative",
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<LoadingOverlay
visible={isError}
loaderProps={{ children: "Ошибка отображения молекулы" }}
zIndex={8}
/>
{MemoViewer}
{isResizing && viewingData && (
<div
@@ -254,7 +260,7 @@ function MoleculeViewer(props: MoleculeViewerProps) {
left: "10px",
top: "10px",
display: "flex",
zIndex: 5,
zIndex: 9,
width: "auto",
}}
>

View File

@@ -0,0 +1,3 @@
.StepperRoot {
flex-direction: row-reverse !important;
}

View File

@@ -0,0 +1,119 @@
import { Button, Modal, Space, Title, Select, Textarea } from "@mantine/core";
import { useEffect, useState } from "react";
import "./NewMolecule.css";
import {
ConvertMoleculeToStandart,
GetInFormats,
} from "../ConvertBackendCalls";
import { MoleculeData } from "../../Types/plugin";
interface NewMoleculeModalProps {
isOpened: boolean;
setIsOpened: (opened: boolean) => void;
setMolecule: (mol: MoleculeData) => void;
}
export function NewMoleculeModal(props: NewMoleculeModalProps) {
const [inMolecule, setInMolecule] = useState("");
const [inFormat, setInFormat] = useState<string | null>();
const [inOptions, setInOptions] = useState<string[] | undefined>();
const getOptions = () => {
GetInFormats()
.then((value: { [key: string]: string }) => {
const values = Object.keys(value);
setInOptions(values);
})
.catch(() => {});
};
useEffect(() => {
getOptions();
}, []);
//reset on open dialog
useEffect(() => {
if (props.isOpened) {
setInMolecule("");
setInFormat(undefined);
if (inOptions == undefined) {
getOptions();
}
}
}, [props.isOpened]);
const handleClose = () => {
props.setIsOpened(false);
};
const handleConvert = () => {
if (inFormat) {
ConvertMoleculeToStandart({
inputText: inMolecule,
inputFormat: inFormat,
make_3d: true,
add_h: false,
optimize: false,
}).then((value: string) => {
props.setMolecule({ text: value });
props.setIsOpened(false);
});
} else {
//TODO: add no format selected handling
}
};
const handleEmptyMolecule = () => {
props.setIsOpened(false);
};
return (
<Modal
opened={props.isOpened}
onClose={handleClose}
title=<Title size="xl">Новая молекула</Title>
centered
size="75%"
styles={{
content: { paddingLeft: "10px" },
title: { width: "100%" },
}}
>
<Textarea
className="moleculeInput"
value={inMolecule}
onChange={(e) => setInMolecule(e.currentTarget.value)}
placeholder="Введите текст молекулы"
classNames={{
wrapper: "moleculeInputWrapper",
}}
onKeyDown={(e) => {
// Stop arrow keys from reaching react-resizable-panels
if (
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)
) {
e.stopPropagation();
}
}}
/>
<Space h="md" />
<Select
value={inFormat}
onChange={(value: string | null) => setInFormat(value)}
searchable
data={inOptions}
placeholder="Выберите формат"
classNames={{
input: "selectInFormat",
dropdown: "selectDropDown",
option: "selectDropDownOption",
}}
/>
<Space h="md" />
<Button color="accent" onClick={handleConvert}>
Преобразовать
</Button>
</Modal>
);
}

View File

@@ -0,0 +1,7 @@
.truncatedString {
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}

View File

@@ -1,22 +1,158 @@
// ListItem/ListItem.tsx
import React from "react";
import { MoleculeData, TaskData } from "../Types/plugin";
import React, { useEffect, useState } from "react";
import { MoleculeData } from "../Types/plugin";
import { Card, Text, Title } from "@mantine/core";
import "./ListItem.css";
const ListItem: React.FunctionComponent<{
data: TaskData<MoleculeData> | undefined;
data: MoleculeData | undefined;
simProgress: any;
}> = (props) => {
var variables = {
Multiplicity: "-",
Charge: "-",
Electrons: "-",
Orbitals: "-",
};
try {
const lines = props.data?.text.trim().split("\n") || ["", "", ""];
const secondLine = lines[1]; // "Charge=0 Multiplicity=1 Electrons=10 Orbitals=9"
variables = secondLine.split(" ").reduce(
(acc, pair) => {
const [key, value] = pair.split("=");
acc[key] = parseInt(value); // or Number(value) if you want to keep as number
return acc;
},
{} as Record<string, number>,
);
} catch {
variables = {
Multiplicity: "-",
Charge: "-",
Electrons: "-",
Orbitals: "-",
};
}
const [smiles, setSmiles] = useState<string>("");
useEffect(() => {
const response = fetch("https://mol-convert.deowl.ru/smiles", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ text: props.data?.text }),
}).then((data) => {
if (!data.ok) {
throw new Error(`HTTP error! status: ${data.status}`);
}
data.json().then((data) => {
setSmiles(data.molfile);
});
});
}, []);
return (
<div
style={{ padding: "1rem", border: "1px solid #ccc", borderRadius: "4px" }}
>
<h3 style={{ margin: "0 0 0.5rem 0" }}>{props.data?.name}</h3>
<p style={{ margin: "0 0 0.5rem 0", color: "#666" }}>
{props.data?.description}
</p>
<div style={{ fontSize: "0.9rem", color: "#999" }}>
Value: {props.data?.data.text || "Not set"}
</div>
</div>
<Card orientation="horizontal" style={{ height: "100%" }}>
<Card.Section
style={{
minWidth: "100px",
maxWidth: "200px",
justifyContent: "space-evenly",
display: "flex",
flexDirection: "column",
flexGrow: 1,
}}
px="sm"
>
<Text size="md">
Заряд: {variables.Charge != undefined ? variables.Charge : "-"}
</Text>
<Text size="md">
Мультиплексивность:{" "}
{variables.Multiplicity != undefined ? variables.Multiplicity : "-"}
</Text>
</Card.Section>
<Card.Section
style={{
minWidth: "100px",
maxWidth: "200px",
justifyContent: "space-evenly",
display: "flex",
flexDirection: "column",
flexGrow: 1,
}}
px="sm"
withBorder
>
<Text size="md">
Акт. эклектроны:{" "}
{variables.Electrons != undefined ? variables.Electrons : "-"}
</Text>
<Text size="md">
Акт. орбитали:{" "}
{variables.Orbitals != undefined ? variables.Orbitals : "-"}
</Text>
</Card.Section>
<Card.Section
style={{
minWidth: "150px",
maxWidth: "250px",
justifyContent: "space-evenly",
display: "flex",
flexDirection: "column",
flexGrow: 1,
}}
px="sm"
withBorder
>
<Text size="md" className="truncatedString">
Smiles: {smiles}
</Text>
<Text size="md">
Необходимо кубит: {Number(variables.Orbitals) * 2 || "-"}
</Text>
</Card.Section>
{props.simProgress && !props.simProgress["error"] && (
<Card.Section
style={{
width: "250px",
justifyContent: "space-evenly",
display: "flex",
flexDirection: "column",
}}
px="sm"
withBorder
>
<Title size={"md"}>Прогресс выполнения:</Title>
<Text size="md" className="truncatedString">
Итерация: {props.simProgress["iter_num"]}
</Text>
<Text size="md" className="truncatedString">
Энергия: {Math.fround(props.simProgress["energy"])}
</Text>
</Card.Section>
)}
{props.simProgress && props.simProgress["error"] && (
<Card.Section
style={{
width: "250px",
justifyContent: "space-evenly",
display: "flex",
flexDirection: "column",
}}
px="sm"
withBorder
>
<Title size={"md"}>Ошибка:</Title>
<Text size="md" className="truncatedString">
{props.simProgress["error"]}
</Text>
</Card.Section>
)}
</Card>
);
};

View File

@@ -1,26 +1,14 @@
// Types
export interface TaskData<TData = any> {
id: number;
name: string;
description: string;
data: TData;
}
export interface TaskEditorProps<TData = any> {
data: TaskData<TData> | undefined;
setData: (data: TData) => void;
}
export interface TaskTypePlugin<TData = any> {
type: string;
ListItem: React.ComponentType<TaskData<TData> | undefined>;
Editor: React.ComponentType<TaskEditorProps<TData>>;
}
export interface MoleculeData {
text: string;
}
export interface TaskEditorProps {
data: MoleculeData | undefined;
setData: (data: MoleculeData, qubits_needed: number) => void;
counter: number;
simProgress: any;
}
export interface SelectedAtom {
serial: number;
name: string;

View File

@@ -4,19 +4,13 @@ import "./index.css";
import "@mantine/core/styles.css";
import Wrapper from "./wrapper";
// Parse URL parameters
const urlParams = new URLSearchParams(window.location.search);
const mode = urlParams.get("mode") == "List" ? "List" : "Editor";
// Render directly
const rootElement = document.getElementById("root");
if (rootElement) {
rootElement.style = "height: 100%";
const root = ReactDOM.createRoot(rootElement);
root.render(<Wrapper mode={mode} />);
}
// Notify parent that plugin is ready
if (window.parent !== window) {
window.parent.postMessage({ type: "plugin-ready" }, "*");
root.render(<Wrapper />);
}

View File

@@ -11,18 +11,15 @@ import {
} from "@mantine/core";
import "./index.css";
import "@mantine/core/styles.css";
import { MoleculeData, TaskData } from "./Types/plugin";
interface WrapperProps {
mode: "List" | "Editor";
}
import { MoleculeData } from "./Types/plugin";
// Create setData function that communicates with parent
const setData = (newData: MoleculeData) => {
const setData = (newData: string, qubits_needed: number) => {
window.parent.postMessage(
{
type: "plugin-update",
data: newData,
qubits_needed: qubits_needed,
},
"*",
);
@@ -69,41 +66,80 @@ const theme = createTheme({
},
});
const Wrapper: React.FunctionComponent<WrapperProps> = (props) => {
const [currentTaskData, setTaskData] = useState<TaskData<MoleculeData>>();
const Wrapper: React.FunctionComponent = () => {
const [currentTaskData, setTaskData] = useState<string>();
const [currentSimProgress, setCurrentSimProgress] = useState<string>();
const [currentTheme, setTheme] = useState<"light" | "dark">("dark");
const [mode, setMode] = useState("List");
const [counter, setCounter] = useState(0);
// Handle messages from parent
useEffect(() => {
window.addEventListener("message", (event) => {
const { type, data } = event.data;
console.log(event.data);
setTaskData(data.taskData);
setTheme(data.theme);
if (type == "plugin-data") {
setTaskData(data.taskData);
setTheme(data.theme);
setMode(data.mode);
try {
JSON.parse(data.simProgress);
setCurrentSimProgress(data.simProgress);
} catch {
setCurrentSimProgress(undefined);
}
}
if (type == "update") {
console.log("Updating");
setCounter((prevData) => {
return prevData + 1;
});
}
});
}, [props.mode]);
if (props.mode === "List") {
return (
<MantineProvider
colorSchemeManager={colorSchemeManager}
theme={theme}
forceColorScheme={currentTheme}
>
<ListItem data={currentTaskData} />
</MantineProvider>
);
} else {
return (
<MantineProvider
colorSchemeManager={colorSchemeManager}
theme={theme}
forceColorScheme={currentTheme}
>
<MoleculeEditorPage data={currentTaskData} setData={setData} />
</MantineProvider>
);
}
const sendReadyWithRetry = () => {
window.parent.postMessage({ type: "plugin-ready" }, "*");
};
// Also send ready after a short delay to catch any late listeners
const timeoutId = setTimeout(sendReadyWithRetry, 100);
}, []);
return mode === "List" ? (
<MantineProvider
colorSchemeManager={colorSchemeManager}
theme={theme}
forceColorScheme={currentTheme}
>
{currentTaskData && (
<ListItem
data={JSON.parse(currentTaskData) as MoleculeData}
simProgress={
currentSimProgress ? JSON.parse(currentSimProgress) : undefined
}
/>
)}
</MantineProvider>
) : (
<MantineProvider
colorSchemeManager={colorSchemeManager}
theme={theme}
forceColorScheme={currentTheme}
>
{currentTaskData && (
<MoleculeEditorPage
data={JSON.parse(currentTaskData) as MoleculeData}
setData={(data, qubits_needed) =>
setData(JSON.stringify(data), qubits_needed)
}
counter={counter}
simProgress={
currentSimProgress ? JSON.parse(currentSimProgress) : undefined
}
/>
)}
</MantineProvider>
);
};
export default Wrapper;