Initial commit

This commit is contained in:
2026-03-16 22:05:19 +03:00
commit 8e78b8cb47
71 changed files with 7846 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
.DocumentationPage {
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
padding-top: 25px;
}
.contents {
flex-grow: 1;
}
.tableOfContents {
position: sticky;
top: 60px;
width: 150px;
height: min-content;
}
.docTitle {
padding-bottom: 50px;
}
.hoverCard {
position: fixed;
bottom: 10px;
}

View File

@@ -0,0 +1,103 @@
import {
ActionIcon,
Box,
Divider,
Menu,
TableOfContents,
Title,
} from "@mantine/core";
import "./DocumentationPage.css";
import { IconMenu2 } from "@tabler/icons-react";
import { Helmet } from "react-helmet";
function DocumentationPage() {
return (
<>
<Helmet>
<title>Documentation | QMolSim</title>
<meta
name="description"
content="See the documentation on how to setup and use the qunatum computational system"
/>
</Helmet>
<Title order={1} className="docTitle">
Документация
</Title>
<Divider />
<div className="DocumentationPage">
<Box className="tableOfContents" visibleFrom="md">
<TableOfContents
variant="light"
color="accent"
size="sm"
radius="sm"
scrollSpyOptions={{
selector: "section h2",
}}
getControlProps={({ data }) => ({
onClick: () =>
data
.getNode()
.scrollIntoView({ behavior: "smooth", block: "center" }),
children: data.value,
})}
/>
</Box>
<Box className="hoverCard" hiddenFrom="md">
<Menu
width={280}
shadow="md"
openDelay={100}
closeDelay={100}
closeOnClickOutside
closeOnItemClick
floatingStrategy="fixed"
>
<Menu.Target>
<ActionIcon
aria-label="navigation"
variant="gradient"
gradient={{ from: "blue", to: "cyan", deg: 90 }}
radius={"50%"}
size={40}
>
<IconMenu2 size={25} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<TableOfContents
variant="light"
color="accent"
size="sm"
radius="sm"
scrollSpyOptions={{
selector: "section h2",
}}
getControlProps={({ data }) => ({
onClick: () =>
data
.getNode()
.scrollIntoView({ behavior: "smooth", block: "center" }),
children: data.value,
})}
/>
</Menu.Dropdown>
</Menu>
</Box>
<div className="contents">
<section id="introduction" style={{ height: 1000 }}>
<Title order={2}>Introduction</Title>
</section>
<section id="features" style={{ height: 1000 }}>
<Title order={2}>features</Title>
</section>
<section id="conclusion" style={{ height: 1000 }}>
<Title order={2}>Conslusion</Title>
</section>
</div>
</div>
</>
);
}
export default DocumentationPage;

View File

@@ -0,0 +1,16 @@
.ExperimentPage {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.experimentButtons {
display: flex;
gap: 20px;
flex-direction: row;
justify-content: right;
width: fit-content;
margin-left: auto;
flex-wrap: nowrap;
text-wrap: nowrap;
}

View File

@@ -0,0 +1,108 @@
import { Alert, Center, Text } from "@mantine/core";
import "./ExperimentPage.css";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
import { NewMoleculeModal } from "Modals/NewMolecule/NewMolecule";
import { useState } from "react";
import { Helmet } from "react-helmet";
import { IconPlus, IconSettings } from "@tabler/icons-react";
import { useParams } from "react-router";
import {
useExperimentStore,
useSelectedExperiment,
} from "Stores/ExperimentStore";
import CustomButton from "Components/CustomButton/CustomButton";
function ExperimentPage() {
const [isOpen, setIsOpen] = useState(false);
const { experiment_id } = useParams();
const { selectedExperimentId, selectExperiment } = useExperimentStore();
if (
!selectedExperimentId ||
(Number(experiment_id) != selectedExperimentId && experiment_id)
) {
console.log(Number(experiment_id));
selectExperiment(Number(experiment_id));
}
const experiment = useSelectedExperiment();
return (
<>
<Helmet>
<title>
{experiment
? "Experiment " + experiment.id + " | QMolSim"
: "Error |QmolSim"}
</title>
<meta
name="description"
content="See the documentation on how to setup and use the qunatum computational system"
/>
</Helmet>
{experiment && (
<div className="ExperimentPage">
<NewMoleculeModal
isOpened={isOpen}
setIsOpened={(toOpen: boolean) => setIsOpen(toOpen)}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
marginBottom: "15px",
}}
>
<div className="experimentButtons">
<CustomButton
color="contrast"
onClick={() => {
setIsOpen(true);
}}
icon={<IconPlus />}
text="Добавить задачу"
/>
<CustomButton
color="contrast"
style="outline"
onClick={() => {
setIsOpen(true);
}}
icon={<IconSettings />}
text="Параметры эксперимента"
/>
</div>
</div>
{experiment.tasks_ids.length > 0 && (
<PaginationContainer>
{experiment.tasks_ids.map((task: ExperimentTask) => {
return <div>{task.data.name}</div>;
})}
</PaginationContainer>
)}
{experiment.tasks_ids.length == 0 && (
<Alert>
<Center>
<Text size={"xl"} c="contrast">
Нет задач
</Text>
</Center>
</Alert>
)}
</div>
)}
{!experiment && (
<Alert color="red">
<Center>
{" "}
<Text c="contrast" size={"xl"} size="md">
Ошибка. Эксперимент не найден
</Text>{" "}
</Center>
</Alert>
)}
</>
);
}
export default ExperimentPage;

View File

@@ -0,0 +1,14 @@
.ExperimentsPage {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.experimentsButtons {
display: flex;
gap: 20px;
flex-direction: row;
justify-content: right;
width: fit-content;
margin-left: auto;
}

View File

@@ -0,0 +1,67 @@
import { Helmet } from "react-helmet";
import "./ExperimentsPage.css";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
import { useState } from "react";
import { IconMicroscope } from "@tabler/icons-react";
import { NewExperimentModal } from "Modals/NewExperiment/NewExperiment";
import ExperimentsListCard from "Components/ListCard/ExperimentsListCard";
import { useExperimentStore } from "Stores/ExperimentStore";
import type { Experiment } from "Types/Experiment/Experiment";
import CustomButton from "Components/CustomButton/CustomButton";
function ExperimentsPage() {
const [isOpen, setIsOpen] = useState(false);
const { experiments, teams, tasks } = useExperimentStore();
return (
<>
<Helmet>
<title>Experiments Page | QMolSim</title>
<meta
name="description"
content="See the list of all of users experiments"
/>
</Helmet>
<NewExperimentModal isOpened={isOpen} setIsOpened={setIsOpen} />
<div className="ExperimentsPage">
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
marginBottom: "20px",
}}
>
<div className="experimentsButtons">
<CustomButton
color="contrast"
onClick={() => {
setIsOpen(true);
}}
icon={<IconMicroscope />}
text="Создать эксперимент"
/>
</div>
</div>
<PaginationContainer>
{experiments.map((exp: Experiment) => {
return (
<ExperimentsListCard
id={exp.id}
name={exp.name}
description={exp.description}
team_id={exp.team_id}
tasks_ids={exp.tasks_ids}
date_created={exp.date_created}
experiment_status={exp.experiment_status}
experiment_type="A"
/>
);
})}
</PaginationContainer>
</div>
</>
);
}
export default ExperimentsPage;

View File

@@ -0,0 +1,65 @@
.MoleculeEdit {
height: 100%;
display: flex;
}
.MoleculeEditWrapper {
display: flex !important;
flex: 1 !important;
min-width: 0;
min-height: 0;
background-color: var(--mantine-color-secondary-filled);
}
.MoleculeEditLineNumber {
height: 100%;
padding-top: calc(1px + var(--input-padding-y, 0rem));
padding-bottom: calc(1px + var(--input-padding-y, 0rem));
font-family:
"Fira Code", "Courier New", Courier, monospace; /* Monospace fonts */
font-size: 14px; /* Comfortable size */
line-height: 1.5; /* Spacing like an editor */
letter-spacing: 0; /* Keeps punctuation aligned */
white-space: pre-line;
overflow: scroll;
scrollbar-width: none;
overscroll-behavior: none;
cursor: default;
}
.MoleculeEditInput {
font-family:
"Fira Code", "Courier New", Courier, monospace; /* Monospace fonts */
font-size: 14px; /* Comfortable size */
line-height: 1.5; /* Spacing like an editor */
letter-spacing: 0; /* Keeps punctuation aligned */
white-space: pre; /* preserves spaces & tabs */
overflow-x: auto; /* horizontal scroll when needed */
overflow-y: auto; /* vertical scroll when needed */
word-wrap: normal; /* prevent wrapping */
overscroll-behavior: none;
background-color: var(--mantine-color-secondary-filled);
}
.Separator {
width: 10px;
}
.moleculeInput {
height: 250px;
}
.moleculeInputWrapper {
height: 100%;
}
.moleculeInputWrapper > * {
height: 100%;
}
.tabPanel {
flex: 1;
padding: 10px;
border-left: 1px solid var(--tab-border-color);
border-right: 1px solid var(--tab-border-color);
border-bottom: 1px solid var(--tab-border-color);
}

View File

@@ -0,0 +1,113 @@
import MoleculeViewer from "Components/MoleculeViewer/MoleculeViewer";
import {
Button,
Center,
Divider,
Tabs,
Text,
useMantineTheme,
} from "@mantine/core";
import { Group, Panel, Separator } from "react-resizable-panels";
import "./MoleculePage.css";
import {
IconCheck,
IconCode,
IconGripVertical,
IconList,
IconX,
} from "@tabler/icons-react";
import { useMoleculeEditStore } from "Stores/MoleculeEditStore";
import { CodeTextArea } from "Components/CodeTextArea/CodeTextArea";
import { Helmet } from "react-helmet";
function MoleculeEditorPage() {
const {
currentMoleculeString,
setCurrentMoleculeString,
selectedAtom,
setSelectedAtom,
} = useMoleculeEditStore();
const theme = useMantineTheme();
return (
<>
<Helmet>
<title>Molecule Page | QMolSim</title>
<meta
name="description"
content="Edit the atomic structure of a molecule"
/>
</Helmet>
<div className="experimentButtons">
<Button color="secondary" onClick={() => {}}>
<IconCheck style={{ marginRight: "10px" }} />
<Text c="contrast">Сохранить</Text>
</Button>
<Button color="contrast" onClick={() => {}}>
<IconX style={{ marginRight: "10px" }} color="black" />
<Text c="secondary">Отменить</Text>
</Button>
</div>
<div style={{ display: "flex", flexDirection: "column", height: "85vh" }}>
<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={currentMoleculeString}
onTextChange={(text: string) => {
setCurrentMoleculeString(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={currentMoleculeString}
selectedAtom={selectedAtom}
setSelectedAtom={setSelectedAtom}
/>
</Panel>
</Group>
</div>
</>
);
}
export default MoleculeEditorPage;

41
src/Pages/MainPage/MainPage.css Executable file
View File

@@ -0,0 +1,41 @@
.background_holder {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden;
z-index: 0;
}
.slogan {
width: 100%;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 5vh;
padding-bottom: 10vh;
z-index: 1;
}
.slogan > * {
font-family: "Quicking";
text-align: center;
color: var(--mantine-color-contrast-filled);
z-index: 1;
}
.tiles {
width: 100%;
align-items: stretch;
gap: 40px 0px;
}
#documentationButton {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-top: 10vh;
text-decoration: underline;
}

63
src/Pages/MainPage/MainPage.tsx Executable file
View File

@@ -0,0 +1,63 @@
import { Title, Button, Group } from "@mantine/core";
import Background from "Components/StripedBg/Background";
import "./MainPage.css";
import { Helmet } from "react-helmet";
import ButtonWithText from "Components/CardWithButton/CardWithButton";
import { Link } from "react-router";
import { routes } from "Routes/Routes";
function MainPage() {
return (
<div>
<Helmet>
<title>Landing Page | QMolSim</title>
<meta
name="description"
content="Description of the main functionaity of the QMolSim service"
/>
</Helmet>
<div className="background_holder">
<Background />
</div>
<div className="slogan">
<Title size={50} textWrap="balance">
Quantum Chemistry Made easy
</Title>
</div>
<Group className="tiles" justify="space-evenly">
<ButtonWithText
title="Работа с молекулами"
text="Вводите информацию о молекулах любым удобным способом. Визуализируйте и
редактируйте молекулы в простом интерфейсе."
buttonText="Начать Эксперимент"
link={routes.ExperimentsPage.path}
/>
<ButtonWithText
title="Параллельные вычисления"
text="Запускайте квантовые вычисления сразу на нескольких машинах.
Упарвляйте и следите за машинами в одном месте."
buttonText="Подключить Машину"
link={routes.MachinesPage.path}
/>
<ButtonWithText
title="Работа в команде"
text="Создавайте команы с другими людьми и настраивайте доступ ко всем
аспектам работы"
buttonText="Создать Команду"
link={routes.TeamsPage.path}
/>
</Group>
<Link className="invisible_link" to={routes.DocumentationPage.path}>
<div id="documentationButton">
<Button variant="subtle" color="secondaryContrast">
&nbsp;Документация&nbsp;
</Button>
</div>
</Link>
</div>
);
}
export default MainPage;

View File

@@ -0,0 +1,4 @@
.TeamsPage {
flex-grow: 1;
display: flex;
}

View File

@@ -0,0 +1,22 @@
import { Helmet } from "react-helmet";
import "./TeamsPage.css";
import { PaginationContainer } from "Components/PaginationContainer/PaginationContainer";
function TeamsPage() {
return (
<>
<Helmet>
<title>Teams Page | QMolSim</title>
<meta
name="description"
content="See the documentation on how to setup and use the qunatum computational system"
/>
</Helmet>
<div className="TeamsPage">
<PaginationContainer />
</div>
</>
);
}
export default TeamsPage;

View File

@@ -0,0 +1,12 @@
.tabPanel {
flex-grow: 1;
padding: 10px;
border-left: 1px solid var(--tab-border-color);
border-right: 1px solid var(--tab-border-color);
border-bottom: 1px solid var(--tab-border-color);
}
.userPage {
flex-grow: 1;
display: flex;
}

View File

@@ -0,0 +1,166 @@
import {
Button,
Title,
Tabs,
TextInput,
SimpleGrid,
Chip,
Text,
Divider,
Switch,
} from "@mantine/core";
import {
IconChartCandle,
IconCheck,
IconUserFilled,
IconCancel,
IconSun,
IconMoonStars,
} from "@tabler/icons-react";
import keycloak from "Api/Keycloak/Keycloak";
import { useEffect } from "react";
import { Helmet } from "react-helmet";
import { AuthenticationStore } from "Stores/AuthenticationStore";
import "./UserPage.css";
import { useUserPreferencesStore } from "Stores/PreferencesStore";
import CustomButton from "Components/CustomButton/CustomButton";
function UserPage() {
const { profile, token } = AuthenticationStore();
const { theme, set_theme } = useUserPreferencesStore();
useEffect(() => {
console.log(token);
}, []);
return (
<>
<Helmet>
<title>User Page | QMolSim</title>
<meta
name="description"
content="See the documentation on how to setup and use the qunatum computational system"
/>
</Helmet>
<div className="userPage">
<Tabs
variant="outline"
style={{
display: "flex",
flexGrow: 1,
flexDirection: "column",
}}
radius="lg"
defaultValue="profile"
classNames={{ panel: "tabPanel" }}
>
<Tabs.List>
<Tabs.Tab
value="profile"
leftSection={<IconUserFilled size={20} />}
>
Профиль
</Tabs.Tab>
<Tabs.Tab
value="preference"
leftSection={<IconChartCandle size={20} />}
>
Предпочтения
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="profile">
<SimpleGrid verticalSpacing="lg" cols={2}>
<Title size="lg">Имя пользователя:</Title>
<TextInput size="md" value={profile?.username} />
<Title size="lg">Почта:</Title>
<TextInput size="md" value={profile?.email} />
<CustomButton color="accent" text="Сохранить" />
<CustomButton color="error" text="Отменить" />
</SimpleGrid>
<Divider my="lg" />
<SimpleGrid verticalSpacing={"lg"} cols={2}>
<Title
size="lg"
style={{ display: "flex", alignItems: "center" }}
>
Почта верифицирована:{" "}
<Chip mx="lg" size="auto" checked={profile?.emailVerified}>
{profile?.emailVerified ? (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<IconCheck
size={25}
color={"green"}
style={{ verticalAlign: "center", margin: "5px" }}
/>
</div>
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<IconCancel
size={25}
color="red"
style={{
verticalAlign: "center",
margin: "5px",
}}
/>
</div>
)}
</Chip>
</Title>
{!profile?.emailVerified && (
<CustomButton
style="outline"
color="contrast"
text="Подтвердить почту"
/>
)}
{profile?.emailVerified && <div></div>}
<Title size="lg">Пароль:</Title>
<CustomButton
style="outline"
color="contrast"
text="Изменить пароль"
/>
</SimpleGrid>
</Tabs.Panel>
<Tabs.Panel value="preference">
<Switch
size="xl"
onChange={(event) =>
set_theme(!event.currentTarget.checked ? "dark" : "light")
}
onLabel={
<IconSun
size={16}
stroke={2.5}
color="var(--mantine-color-yellow-4)"
/>
}
offLabel={
<IconMoonStars
size={16}
stroke={2.5}
color="var(--mantine-color-blue-6)"
/>
}
/>
</Tabs.Panel>
</Tabs>
</div>
</>
);
}
export default UserPage;