frontPanel/src/pages/Questions/BranchingMap/CsComponent.tsx

254 lines
7.4 KiB
TypeScript
Raw Normal View History

2024-01-05 16:48:35 +00:00
import { devlog } from "@frontend/kitui";
2023-12-20 10:46:38 +00:00
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
2024-01-10 18:23:38 +00:00
import { clearRuleForAll } from "@root/questions/actions";
2023-11-29 15:45:15 +00:00
import { useQuestionsStore } from "@root/questions/store";
2024-01-05 16:48:35 +00:00
import { updateRootContentId } from "@root/quizes/actions";
import { useCurrentQuiz } from "@root/quizes/hooks";
import AddIcon from "@mui/icons-material/Add";
import RemoveIcon from "@mui/icons-material/Remove";
import {
cleardragQuestionContentId,
setModalQuestionParentContentId,
setModalQuestionTargetContentId,
2023-12-31 02:53:25 +00:00
updateOpenedModalSettingsId,
} from "@root/uiTools/actions";
2024-01-05 16:48:35 +00:00
import { useUiTools } from "@root/uiTools/store";
2024-01-10 18:23:38 +00:00
import type { Core } from "cytoscape";
2024-01-05 16:48:35 +00:00
import { enqueueSnackbar } from "notistack";
2024-01-10 18:23:38 +00:00
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
2024-01-05 16:48:35 +00:00
import CytoscapeComponent from "react-cytoscapejs";
import { withErrorBoundary } from "react-error-boundary";
import { DeleteNodeModal } from "../DeleteNodeModal";
2024-01-17 15:42:25 +00:00
import CsNodeButtons from "./CsNodeButtons";
2024-01-10 18:23:38 +00:00
import { addNode, layoutOptions, storeToNodes } from "./helper";
import { useRemoveNode } from "./hooks/useRemoveNode";
import "./style/styles.css";
2024-01-05 16:48:35 +00:00
import { stylesheet } from "./style/stylesheet";
import { Box, Button, useMediaQuery, useTheme } from "@mui/material";
import { AlignIcon } from "@icons/questionsPage/AlignIcon";
import { ExpandIcon } from "@icons/questionsPage/ExpandIcon";
2023-11-29 15:45:15 +00:00
2024-01-05 16:48:35 +00:00
function CsComponent() {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(660));
const desireToOpenABranchingModal = useUiTools(
(state) => state.desireToOpenABranchingModal,
2023-12-31 02:53:25 +00:00
);
const canCreatePublic = useUiTools((state) => state.canCreatePublic);
const modalQuestionParentContentId = useUiTools(
(state) => state.modalQuestionParentContentId,
);
const modalQuestionTargetContentId = useUiTools(
(state) => state.modalQuestionTargetContentId,
);
const trashQuestions = useQuestionsStore((state) => state.questions);
2023-11-29 15:45:15 +00:00
const cyRef = useRef<Core | null>(null);
const { removeNode } = useRemoveNode({ cyRef });
const csElements = useMemo(() => {
const questions = trashQuestions.filter(
(question): question is AnyTypedQuizQuestion =>
question.type !== null && question.type !== "result",
);
return storeToNodes(questions);
}, [trashQuestions]);
2023-12-31 10:36:30 +00:00
useLayoutEffect(() => {
2023-12-31 02:53:25 +00:00
const cy = cyRef?.current;
if (desireToOpenABranchingModal) {
setTimeout(() => {
2023-12-31 02:53:25 +00:00
cy
?.getElementById(desireToOpenABranchingModal)
?.data("eroticeyeblink", true);
}, 250);
} else {
2023-12-31 02:53:25 +00:00
cy?.elements().data("eroticeyeblink", false);
}
2023-12-31 02:53:25 +00:00
}, [desireToOpenABranchingModal]);
2024-01-05 16:48:35 +00:00
useEffect(() => {
2023-12-31 02:53:25 +00:00
if (
modalQuestionTargetContentId.length !== 0 &&
modalQuestionParentContentId.length !== 0
) {
if (!cyRef.current) return;
2024-01-05 16:48:35 +00:00
2023-12-31 02:53:25 +00:00
addNode({
parentNodeContentId: modalQuestionParentContentId,
targetNodeContentId: modalQuestionTargetContentId,
});
}
2023-12-31 02:53:25 +00:00
setModalQuestionParentContentId("");
setModalQuestionTargetContentId("");
}, [modalQuestionTargetContentId]);
useEffect(function onMount() {
updateOpenedModalSettingsId();
document.addEventListener("pointerup", cleardragQuestionContentId);
2023-11-29 15:45:15 +00:00
return () => {
document.removeEventListener("pointerup", cleardragQuestionContentId);
2023-11-29 15:45:15 +00:00
};
}, []);
2024-01-05 16:48:35 +00:00
useEffect(
function rerunLayout() {
cyRef.current?.layout(layoutOptions).run();
cyRef.current?.fit(undefined, 70);
},
[csElements],
);
2023-11-29 15:45:15 +00:00
return (
<Box
sx={{
width: "100%",
}}
>
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
<Box sx={{ position: "relative" }}>
<Box
sx={{
position: "absolute",
bottom: isMobile ? "15px" : "20px",
left: isMobile ? "15px" : "20px",
display: "flex",
gap: "10px",
}}
>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#9A9AAF",
opacity: "0.7",
zIndex: 2,
}}
onClick={() => {
cyRef.current?.zoom(1);
cyRef.current?.center();
}}
>
<ExpandIcon />
</Button>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#9A9AAF",
opacity: "0.7",
zIndex: 2,
}}
onClick={() => {
cyRef.current?.fit(undefined, 70);
}}
>
<AlignIcon />
</Button>
</Box>
<Box
sx={{
position: "absolute",
bottom: isMobile ? "15px" : "20px",
right: isMobile ? "15px" : "20px",
display: "flex",
flexDirection: "column",
gap: "10px",
background: "#EEE4FC",
padding: "16px",
borderRadius: "8px",
zIndex: 2,
}}
>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#7E2AEA",
fontSize: "16px",
color: "#fff",
zIndex: 2,
}}
onClick={() => {
const currentZoom = cyRef.current?.zoom() || 1;
cyRef.current?.zoom(currentZoom + 0.1);
}}
>
<AddIcon />
</Button>
<Button
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
minWidth: "36px",
width: "36px",
height: "36px",
background: "#7E2AEA",
fontSize: "16px",
color: "#fff",
zIndex: 2,
}}
onClick={() => {
const currentZoom = cyRef.current?.zoom() || 1;
cyRef.current?.zoom(currentZoom - 0.1);
}}
>
<RemoveIcon />
</Button>
</Box>
<CytoscapeComponent
wheelSensitivity={0.1}
elements={csElements}
style={{
height: isMobile ? "327px" : "481px",
background: "#F2F3F7",
overflow: "hidden",
borderRadius: "12px",
width: "100%",
}}
stylesheet={stylesheet}
layout={layoutOptions}
cy={(cy) => {
cyRef.current = cy;
}}
autoungrabify={true}
autounselectify={true}
boxSelectionEnabled={false}
/>
</Box>
2023-12-21 14:56:29 +00:00
<DeleteNodeModal removeNode={removeNode} />
</Box>
2023-11-29 15:45:15 +00:00
);
2023-12-31 02:53:25 +00:00
}
function Clear() {
const quiz = useCurrentQuiz();
2023-12-21 14:56:29 +00:00
if (quiz) {
updateRootContentId(quiz?.id, "");
}
2023-12-31 02:53:25 +00:00
clearRuleForAll();
return <></>;
}
export default withErrorBoundary(CsComponent, {
fallback: <Clear />,
onError: (error, info) => {
2023-12-31 02:53:25 +00:00
enqueueSnackbar("Дерево порвалось");
devlog(info);
devlog(error);
},
});