Initial commit
This commit is contained in:
22
src/Components/MoleculeViewer/MoleculeViewer.css
Executable file
22
src/Components/MoleculeViewer/MoleculeViewer.css
Executable file
@@ -0,0 +1,22 @@
|
||||
.moleculeViewer {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
overscroll-behavior: contain;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.atomEditor {
|
||||
position: absolute;
|
||||
border-radius: md;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.atomEditor * {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
269
src/Components/MoleculeViewer/MoleculeViewer.tsx
Executable file
269
src/Components/MoleculeViewer/MoleculeViewer.tsx
Executable 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;
|
||||
29
src/Components/MoleculeViewer/MoleculeViewerMenu.tsx
Executable file
29
src/Components/MoleculeViewer/MoleculeViewerMenu.tsx
Executable file
@@ -0,0 +1,29 @@
|
||||
import { Text, Divider } from "@mantine/core";
|
||||
import Miew from "miew";
|
||||
import "./MoleculeViewer.css";
|
||||
import type { SelectedAtom } from "Types/Experiment/MoleculeEdit/MoleculeEdit";
|
||||
|
||||
interface MoleculeViewerMenuProps {
|
||||
selectedAtom: SelectedAtom;
|
||||
miew: Miew | null;
|
||||
}
|
||||
|
||||
function MoleculeViewerMenu(props: MoleculeViewerMenuProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="moleculeViewerMenu">
|
||||
<>
|
||||
<Text ta="center">
|
||||
Атом: {props.selectedAtom.name} ({props.selectedAtom.serial})
|
||||
</Text>
|
||||
<Divider my="sm" />
|
||||
<Text ta="center">Положение X: {props.selectedAtom.position.x}</Text>
|
||||
<Text ta="center">Положение Y: {props.selectedAtom.position.y}</Text>
|
||||
<Text ta="center">Положение Z: {props.selectedAtom.position.z}</Text>
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MoleculeViewerMenu;
|
||||
Reference in New Issue
Block a user