181 lines
6.1 KiB
TypeScript
181 lines
6.1 KiB
TypeScript
import { devlog } from "@frontend/kitui";
|
||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||
import { Box, Button } from "@mui/material";
|
||
import { clearRuleForAll } from "@root/questions/actions";
|
||
import { useQuestionsStore } from "@root/questions/store";
|
||
import { updateRootContentId } from "@root/quizes/actions";
|
||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||
import { cleardragQuestionContentId, setModalQuestionParentContentId, setModalQuestionTargetContentId, updateModalInfoWhyCantCreate, updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||
import { useUiTools } from "@root/uiTools/store";
|
||
import { ProblemIcon } from "@ui_kit/ProblemIcon";
|
||
import type { Core } from "cytoscape";
|
||
import Cytoscape from "cytoscape";
|
||
import popper from "cytoscape-popper";
|
||
import { enqueueSnackbar } from "notistack";
|
||
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||
import CytoscapeComponent from "react-cytoscapejs";
|
||
import { withErrorBoundary } from "react-error-boundary";
|
||
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||
import { addNode, layoutOptions, storeToNodes } from "./helper";
|
||
import { usePopper } from "./hooks/usePopper";
|
||
import { useRemoveNode } from "./hooks/useRemoveNode";
|
||
import "./style/styles.css";
|
||
import { stylesheet } from "./style/stylesheet";
|
||
|
||
|
||
Cytoscape.use(popper);
|
||
|
||
function CsComponent() {
|
||
const desireToOpenABranchingModal = useUiTools(state => state.desireToOpenABranchingModal);
|
||
const canCreatePublic = useUiTools(state => state.canCreatePublic);
|
||
const modalQuestionParentContentId = useUiTools(state => state.modalQuestionParentContentId);
|
||
const modalQuestionTargetContentId = useUiTools(state => state.modalQuestionTargetContentId);
|
||
const trashQuestions = useQuestionsStore(state => state.questions);
|
||
const cyRef = useRef<Core | null>(null);
|
||
const { recreatePoppers, removeAllPoppers } = usePopper({ cyRef });
|
||
const { removeNode } = useRemoveNode({ cyRef });
|
||
|
||
const cyElements = useMemo(() => {
|
||
const questions = trashQuestions.filter(
|
||
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||
);
|
||
|
||
return storeToNodes(questions);
|
||
}, [trashQuestions]);
|
||
|
||
useLayoutEffect(() => {
|
||
const cy = cyRef?.current;
|
||
if (desireToOpenABranchingModal) {
|
||
setTimeout(() => {
|
||
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true);
|
||
}, 250);
|
||
} else {
|
||
cy?.elements().data("eroticeyeblink", false);
|
||
}
|
||
}, [desireToOpenABranchingModal]);
|
||
|
||
useEffect(() => {
|
||
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||
if (!cyRef.current) return;
|
||
|
||
addNode({
|
||
parentNodeContentId: modalQuestionParentContentId,
|
||
targetNodeContentId: modalQuestionTargetContentId,
|
||
});
|
||
}
|
||
setModalQuestionParentContentId("");
|
||
setModalQuestionTargetContentId("");
|
||
}, [modalQuestionTargetContentId]);
|
||
|
||
useEffect(function onMount() {
|
||
updateOpenedModalSettingsId();
|
||
document.addEventListener("pointerup", cleardragQuestionContentId);
|
||
|
||
return () => {
|
||
document.removeEventListener("pointerup", cleardragQuestionContentId);
|
||
removeAllPoppers();
|
||
};
|
||
}, []);
|
||
|
||
useEffect(function removePoppersOnDrag() {
|
||
const cy = cyRef.current;
|
||
if (!cy) return;
|
||
|
||
let isPointerDown = false;
|
||
let isDragging = false;
|
||
|
||
const onPointerDown = () => {
|
||
isPointerDown = true;
|
||
};
|
||
const onPointerUp = () => {
|
||
if (isDragging) {
|
||
isDragging = false;
|
||
recreatePoppers();
|
||
}
|
||
isPointerDown = false;
|
||
};
|
||
const handleMove = () => {
|
||
if (isPointerDown) {
|
||
isDragging = true;
|
||
removeAllPoppers();
|
||
}
|
||
};
|
||
|
||
cy.on("vmousedown", onPointerDown);
|
||
cy.on("vmousemove", handleMove);
|
||
document.addEventListener("pointerup", onPointerUp);
|
||
|
||
return () => {
|
||
cy.off("vmousedown", onPointerDown);
|
||
cy.off("vmousemove", handleMove);
|
||
document.removeEventListener("pointerup", onPointerUp);
|
||
};
|
||
}, [recreatePoppers, removeAllPoppers]);
|
||
|
||
useEffect(() => {
|
||
cyRef.current?.layout(layoutOptions).run();
|
||
cyRef.current?.fit(undefined, 70);
|
||
recreatePoppers();
|
||
}, [cyElements, recreatePoppers]);
|
||
|
||
return (
|
||
<>
|
||
<Box mb="20px">
|
||
<Button
|
||
sx={{
|
||
height: "27px",
|
||
color: "#7E2AEA",
|
||
textDecoration: "underline",
|
||
fontSize: "16px",
|
||
}}
|
||
variant="text"
|
||
onClick={() => {
|
||
cyRef.current?.fit();
|
||
}}
|
||
>
|
||
Выровнять
|
||
</Button>
|
||
<ProblemIcon
|
||
blink={!canCreatePublic}
|
||
onClick={() => updateModalInfoWhyCantCreate(true)}
|
||
/>
|
||
</Box>
|
||
|
||
<CytoscapeComponent
|
||
wheelSensitivity={0.1}
|
||
elements={cyElements}
|
||
style={{
|
||
height: "480px",
|
||
background: "#F2F3F7",
|
||
overflow: "hidden",
|
||
}}
|
||
stylesheet={stylesheet}
|
||
layout={layoutOptions}
|
||
cy={(cy) => {
|
||
cyRef.current = cy;
|
||
}}
|
||
autoungrabify={true}
|
||
/>
|
||
<DeleteNodeModal removeNode={removeNode} />
|
||
</>
|
||
);
|
||
}
|
||
|
||
function Clear() {
|
||
const quiz = useCurrentQuiz();
|
||
if (quiz) {
|
||
updateRootContentId(quiz?.id, "");
|
||
}
|
||
clearRuleForAll();
|
||
return <></>;
|
||
}
|
||
|
||
export default withErrorBoundary(CsComponent, {
|
||
fallback: <Clear />,
|
||
onError: (error, info) => {
|
||
enqueueSnackbar("Дерево порвалось");
|
||
devlog(info);
|
||
devlog(error);
|
||
},
|
||
});
|