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(null); const molViewerRef = useRef(null); const [isResizing, setIsResizing] = useState(false); const [viewingData, setViewingData] = useState(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: , style: { paddingLeft: "5px" }, }); }); } miew.setOptions({ settings: { bg: { color: theme.colors.secondaryDark[7], }, fogAlpha: 0.7, axes: true, }, }); }; //мемоизируем объект чтобы он не перегружаля при изменении [miew] состояния const MemoViewer = useMemo( () => , [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 (
{MemoViewer} {isResizing && viewingData && (
)}
{props.selectedAtom && ( )}
); } export default MoleculeViewer;