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,269 @@
import Viewer from "miew-react";
import Miew from "miew";
import {
Paper,
UnstyledButton,
useMantineColorScheme,
useMantineTheme,
} from "@mantine/core";
import { useEffect, useMemo, useRef, useState } from "react";
import "./MoleculeViewer.css";
import { notifications } from "@mantine/notifications";
import {
IconAlertHexagon,
IconArrowBarBoth,
IconRefresh,
} from "@tabler/icons-react";
import MoleculeViewerMenu from "./MoleculeViewerMenu";
import type { SelectedAtom } from "Types/Experiment/MoleculeEdit/MoleculeEdit";
const callbackOnResizeFinish = (
dom_elem: HTMLElement,
beginning_callback: () => void,
end_callback: () => void,
delay = 100, // ms after resize "finishes"
) => {
let timeoutId: number | undefined;
const resizeObserver = new ResizeObserver(() => {
beginning_callback();
if (timeoutId) {
window.clearTimeout(timeoutId);
}
timeoutId = window.setTimeout(() => {
end_callback();
}, delay);
});
resizeObserver.observe(dom_elem);
return () => resizeObserver.disconnect(); // cleanup helper
};
interface MoleculeViewerProps {
moleculeData: string;
selectedAtom: SelectedAtom | null;
setSelectedAtom: (selectedAtom: SelectedAtom | null) => void;
}
function MoleculeViewer(props: MoleculeViewerProps) {
const theme = useMantineTheme();
const { colorScheme } = useMantineColorScheme();
// объект загруженного редактора молекул для редактирования
const [miew, setMiew] = useState<Miew | null>(null);
const molViewerRef = useRef<HTMLDivElement>(null);
const [isResizing, setIsResizing] = useState<boolean>(false);
const [viewingData, setViewingData] = useState<string>(props.moleculeData);
//при загрузке сохраняем объект miew
const onInitMiew = (miew: Miew) => {
setMiew(miew);
if (miew && viewingData) {
//прогружаем молекулу
miew
.load(viewingData, {
sourceType: "immediate",
fileType: "xyz",
})
.then(() => {
if (props.selectedAtom) {
props.setSelectedAtom(null);
}
})
.catch((error) => {
if (error.message == "Operation cancelled") {
return;
}
notifications.show({
color: "orange",
radius: "md",
title: "Ошибка при отображении молекулы",
message:
"Проверте правильность написания кода молекулы, в нем содержатся ошибки",
icon: <IconAlertHexagon />,
style: { paddingLeft: "5px" },
});
});
}
miew.setOptions({
settings: {
bg: {
color: theme.colors.secondaryDark[7],
},
fogAlpha: 0.7,
axes: true,
},
});
};
//мемоизируем объект чтобы он не перегружаля при изменении [miew] состояния
const MemoViewer = useMemo(
() => <Viewer onInit={onInitMiew} />,
[viewingData],
);
//Замена станартного обработчика нажатия на молекулы
useEffect(() => {
if (molViewerRef.current && miew) {
//INFO: при изменении размера окна обносить webgl
const cleanup = callbackOnResizeFinish(
molViewerRef.current,
handleResizeStart,
handleResizeEnd,
);
//@ts-expect-error Miew class not implementing typescript correctly
miew.removeEventListener("newpick");
//@ts-expect-error Miew class not implementing typescript correctly
miew.addEventListener("newpick", handleClick);
return () => {
cleanup();
//@ts-expect-error Miew class not implementing typescript correctly
miew.removeEventListener("newpick");
};
}
}, [molViewerRef, miew]);
//On selected Atom change, highlight it
useEffect(() => {
if (miew) {
if (!props.selectedAtom) {
//@ts-expect-error Miew class not implementing typescript correctly
miew.select("");
return;
}
//@ts-expect-error Miew class not implementing typescript correctly
miew.select("serial " + props.selectedAtom.serial, false);
//спрятать информацию встроенную
//@ts-expect-error Miew class not implementing typescript correctly
miew._msgAtomInfo.style.opacity = 0.0;
//@ts-expect-error Miew class not implementing typescript correctly
miew._msgAtomInfo.style.height = "0px";
//@ts-expect-error Miew class not implementing typescript correctly
miew._msgAtomInfo.style.overflow = "hidden";
}
}, [props.selectedAtom, miew]);
//при смене темы обновляем фон MoleculeViewer
useEffect(() => {
if (miew) {
miew.setOptions({
settings: {
bg: {
color: theme.colors.secondaryDark[7],
},
},
});
}
}, [colorScheme]);
//при нажятии на атом выбираем его
const handleClick = (pick: { type: string; obj: { atom: SelectedAtom } }) => {
if (miew) {
if (pick.obj.atom) {
props.setSelectedAtom(pick.obj.atom);
} else {
props.setSelectedAtom(null);
}
}
};
const handleResizeStart = () => {
setIsResizing(true);
};
const handleResizeEnd = () => {
//@ts-expect-error Miew class not implementing typescript correctly
miew._onResize();
//@ts-expect-error Miew class not implementing typescript correctly
miew._picker.handleResize();
setTimeout(() => {
setIsResizing(false);
}, 50);
};
const handleMouseEnter = () => {
document.body.style.overflow = "hidden"; // disable page scroll
};
const handleMouseLeave = () => {
document.body.style.overflow = "auto"; // re-enable scroll
};
const refreshDisplay = () => {
if (props.moleculeData != viewingData) {
setViewingData(props.moleculeData);
} else {
if (miew) {
//@ts-expect-error Miew class not implementing typescript correctly
miew.resetView();
}
}
};
return (
<div ref={molViewerRef} className="moleculeViewer">
<div
style={{
width: "100%",
height: "100%",
backgroundColor: theme.colors.secondaryDark[7],
borderRadius: "5px",
overflow: "hidden",
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{MemoViewer}
{isResizing && viewingData && (
<div
style={{
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: theme.colors.secondaryDark[7], // optional dim
opacity: 0.5,
zIndex: 10,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<IconArrowBarBoth size={80} />
</div>
</div>
)}
</div>
{props.selectedAtom && (
<Paper className="atomEditor" radius="md" p="md">
<MoleculeViewerMenu selectedAtom={props.selectedAtom} miew={miew} />
</Paper>
)}
<div
style={{
position: "absolute",
left: "10px",
top: "10px",
display: "flex",
zIndex: 5,
width: "auto",
}}
>
<UnstyledButton onClick={refreshDisplay}>
<IconRefresh size={25} />
</UnstyledButton>
</div>
</div>
);
}
export default MoleculeViewer;