v1.0
-- full integration with mol-edit backend -- added readme -- added vqe local_computational
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
|
||||
62
frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
Normal file
62
frontend-plugin/src/EditorPage/ConvertBackendCalls.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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",
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.StepperRoot {
|
||||
flex-direction: row-reverse !important;
|
||||
}
|
||||
119
frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
Normal file
119
frontend-plugin/src/EditorPage/NewMolecule/NewMolecule.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
7
frontend-plugin/src/ListItem/ListItem.css
Normal file
7
frontend-plugin/src/ListItem/ListItem.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.truncatedString {
|
||||
-webkit-line-clamp: 2;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 />);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user