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

287 lines
9.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Cytoscape from "cytoscape";
import CytoscapeComponent from "react-cytoscapejs";
import popper from "cytoscape-popper";
import { Button, Box } from "@mui/material";
import { withErrorBoundary } from "react-error-boundary";
import { enqueueSnackbar } from "notistack";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { useQuestionsStore } from "@root/questions/store";
import { useUiTools } from "@root/uiTools/store";
import {
deleteQuestion,
updateQuestion,
getQuestionByContentId,
clearRuleForAll,
createResult,
} from "@root/questions/actions";
import {
updateModalInfoWhyCantCreate,
updateOpenedModalSettingsId
} from "@root/uiTools/actions";
import { cleardragQuestionContentId } from "@root/uiTools/actions";
import { updateDeleteId } from "@root/uiTools/actions";
import { DeleteNodeModal } from "../DeleteNodeModal";
import { ProblemIcon } from "@ui_kit/ProblemIcon";
import { useRemoveNode } from "./hooks/useRemoveNode";
import { usePopper } from "./hooks/usePopper";
import { storeToNodes } from "./helper";
import { stylesheet } from "./style/stylesheet";
import "./style/styles.css";
import type { Core } from "cytoscape";
Cytoscape.use(popper);
interface CsComponentProps {
modalQuestionParentContentId: string;
modalQuestionTargetContentId: string;
setOpenedModalQuestions: (open: boolean) => void;
setModalQuestionParentContentId: (id: string) => void;
setModalQuestionTargetContentId: (id: string) => void;
}
function CsComponent({
modalQuestionParentContentId,
modalQuestionTargetContentId,
setOpenedModalQuestions,
setModalQuestionParentContentId,
setModalQuestionTargetContentId
}: CsComponentProps) {
const quiz = useCurrentQuiz();
const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools()
const trashQuestions = useQuestionsStore().questions
const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null)
const [startCreate, setStartCreate] = useState("");
const [startRemove, setStartRemove] = useState("");
const cyRef = useRef<Core | null>(null);
const layoutsContainer = useRef<HTMLDivElement | null>(null);
const plusesContainer = useRef<HTMLDivElement | null>(null);
const crossesContainer = useRef<HTMLDivElement | null>(null);
const gearsContainer = useRef<HTMLDivElement | null>(null);
const { layoutOptions } = usePopper({
layoutsContainer,
plusesContainer,
crossesContainer,
gearsContainer,
setModalQuestionParentContentId,
setOpenedModalQuestions,
setStartCreate,
setStartRemove,
});
const { removeNode } = useRemoveNode({
cyRef,
layoutOptions,
layoutsContainer,
plusesContainer,
crossesContainer,
gearsContainer,
});
useEffect(() => {
return () => {
// if (!canCreatePublic) updateModalInfoWhyCantCreate(true)
}
}, []);
useLayoutEffect(() => {
const cy = cyRef?.current
if (desireToOpenABranchingModal) {
setTimeout(() => {
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
}, 250)
} else {
cy?.elements().data("eroticeyeblink", false)
}
}, [desireToOpenABranchingModal])
useLayoutEffect(() => {
updateOpenedModalSettingsId()
// updateRootContentId(quiz.id, "")
// clearRuleForAll()
}, [])
useEffect(() => {
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
}
setModalQuestionParentContentId("")
setModalQuestionTargetContentId("")
}, [modalQuestionTargetContentId])
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
//запрещаем работу родителя-ребенка если это один и тот же вопрос
if (parentNodeContentId === targetNodeContentId) return
const cy = cyRef?.current
const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
const targetQuestion = { ...getQuestionByContentId(targetNodeContentId || dragQuestionContentId) } as AnyTypedQuizQuestion
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId && parentNodeChildren !== undefined) {
clearDataAfterAddNode({ parentNodeContentId, targetQuestion, parentNodeChildren })
cy?.data('changed', true)
createResult(quiz?.backendId, targetQuestion.content.id)
const es = cy?.add([
{
data: {
id: targetQuestion.content.id,
label: targetQuestion.title === "" || targetQuestion.title === " " ? "noname" : targetQuestion.title
}
},
{
data: {
source: parentNodeContentId,
target: targetQuestion.content.id
}
}
])
cy?.layout(layoutOptions).run()
cy?.center(es)
} else {
enqueueSnackbar("Добавляемый вопрос не найден")
}
}
const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => {
const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion
//смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
trashQuestions.forEach((targetQuestion) => {
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
updateQuestion(targetQuestion.id, (q) => q.content.usage = false);
}
})
//предупреждаем добавленный вопрос о том, кто его родитель
updateQuestion(targetQuestion.content.id, question => {
question.content.rule.parentId = parentNodeContentId
question.content.rule.main = []
})
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id)) updateQuestion(parentNodeContentId, question => {
question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id]
})
//Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления
if (parentQuestion.content.rule.children >= 1) {
updateOpenedModalSettingsId(targetQuestion.content.id)
}
}
useEffect(() => {
if (startCreate) {
addNode({ parentNodeContentId: startCreate });
cleardragQuestionContentId();
setStartCreate("");
}
}, [startCreate]);
useEffect(() => {
if (startRemove) {
updateDeleteId(startRemove);
setStartRemove("");
}
}, [startRemove]);
useEffect(() => {
document
.querySelector("#root")
?.addEventListener("mouseup", cleardragQuestionContentId);
const cy = cyRef.current;
const eles = cy?.add(
storeToNodes(
questions.filter(
(question) => question.type && question.type !== "result"
) as AnyTypedQuizQuestion[]
)
);
cy?.data("changed", true);
// cy.data('changed', true)
const elecs = eles?.layout(layoutOptions).run();
cy?.on("add", () => cy.data("changed", true));
cy?.fit();
//cy?.layout().run()
return () => {
document
.querySelector("#root")
?.removeEventListener("mouseup", cleardragQuestionContentId);
layoutsContainer.current?.remove();
plusesContainer.current?.remove();
crossesContainer.current?.remove();
gearsContainer.current?.remove();
};
}, []);
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={[]}
// elements={createGraphElements(tree, quiz)}
style={{ height: "480px", background: "#F2F3F7" }}
stylesheet={stylesheet}
layout={(layoutOptions)}
cy={(cy) => {
cyRef.current = cy;
}}
autoungrabify={true}
/>
<DeleteNodeModal />
</>
);
};
function Clear() {
const quiz = useCurrentQuiz();
updateRootContentId(quiz?.id, "")
clearRuleForAll()
return <></>
}
export default withErrorBoundary(CsComponent, {
fallback: <Clear />,
onError: (error, info) => {
enqueueSnackbar("Дерево порвалось")
console.log(info)
console.log(error)
},
});