2023-12-04 13:33:43 +00:00
|
|
|
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
2023-11-29 15:45:15 +00:00
|
|
|
|
import Cytoscape from "cytoscape";
|
|
|
|
|
|
import CytoscapeComponent from "react-cytoscapejs";
|
|
|
|
|
|
import popper from "cytoscape-popper";
|
2023-12-20 18:38:28 +00:00
|
|
|
|
import { Button, Box } from "@mui/material";
|
2023-12-20 12:34:07 +00:00
|
|
|
|
import { withErrorBoundary } from "react-error-boundary";
|
|
|
|
|
|
import { enqueueSnackbar } from "notistack";
|
|
|
|
|
|
|
2023-11-29 15:45:15 +00:00
|
|
|
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
2023-12-20 10:46:38 +00:00
|
|
|
|
import { updateRootContentId } from "@root/quizes/actions";
|
|
|
|
|
|
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
2023-11-29 15:45:15 +00:00
|
|
|
|
import { useQuestionsStore } from "@root/questions/store";
|
2023-12-20 12:34:07 +00:00
|
|
|
|
import { useUiTools } from "@root/uiTools/store";
|
2023-12-20 10:46:38 +00:00
|
|
|
|
import {
|
|
|
|
|
|
deleteQuestion,
|
|
|
|
|
|
updateQuestion,
|
|
|
|
|
|
getQuestionByContentId,
|
|
|
|
|
|
clearRuleForAll,
|
2023-12-21 08:29:51 +00:00
|
|
|
|
createResult,
|
2023-12-20 10:46:38 +00:00
|
|
|
|
} from "@root/questions/actions";
|
2023-12-20 18:38:28 +00:00
|
|
|
|
import {
|
|
|
|
|
|
updateModalInfoWhyCantCreate,
|
|
|
|
|
|
updateOpenedModalSettingsId
|
|
|
|
|
|
} from "@root/uiTools/actions";
|
2023-12-14 09:40:53 +00:00
|
|
|
|
import { cleardragQuestionContentId } from "@root/uiTools/actions";
|
2023-12-20 14:18:01 +00:00
|
|
|
|
import { updateDeleteId } from "@root/uiTools/actions";
|
|
|
|
|
|
|
|
|
|
|
|
import { DeleteNodeModal } from "../DeleteNodeModal";
|
2023-12-19 15:00:51 +00:00
|
|
|
|
import { ProblemIcon } from "@ui_kit/ProblemIcon";
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
2023-12-20 17:31:51 +00:00
|
|
|
|
import { useRemoveNode } from "./hooks/useRemoveNode";
|
|
|
|
|
|
import { usePopper } from "./hooks/usePopper";
|
|
|
|
|
|
|
2023-11-29 15:45:15 +00:00
|
|
|
|
import { storeToNodes } from "./helper";
|
2023-12-20 18:38:28 +00:00
|
|
|
|
import { stylesheet } from "./style/stylesheet";
|
|
|
|
|
|
import "./style/styles.css";
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
2023-12-20 12:34:07 +00:00
|
|
|
|
import type { Core } from "cytoscape";
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
|
|
|
|
|
Cytoscape.use(popper);
|
|
|
|
|
|
|
2023-12-20 17:31:51 +00:00
|
|
|
|
interface CsComponentProps {
|
2023-12-01 19:56:13 +00:00
|
|
|
|
modalQuestionParentContentId: string;
|
|
|
|
|
|
modalQuestionTargetContentId: string;
|
2023-12-02 13:11:57 +00:00
|
|
|
|
setOpenedModalQuestions: (open: boolean) => void;
|
|
|
|
|
|
setModalQuestionParentContentId: (id: string) => void;
|
|
|
|
|
|
setModalQuestionTargetContentId: (id: string) => void;
|
2023-12-01 19:56:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-11 10:08:54 +00:00
|
|
|
|
function CsComponent({
|
2023-12-01 19:56:13 +00:00
|
|
|
|
modalQuestionParentContentId,
|
|
|
|
|
|
modalQuestionTargetContentId,
|
|
|
|
|
|
setOpenedModalQuestions,
|
|
|
|
|
|
setModalQuestionParentContentId,
|
|
|
|
|
|
setModalQuestionTargetContentId
|
2023-12-20 17:31:51 +00:00
|
|
|
|
}: CsComponentProps) {
|
2023-11-29 15:45:15 +00:00
|
|
|
|
const quiz = useCurrentQuiz();
|
|
|
|
|
|
|
2023-12-19 15:00:51 +00:00
|
|
|
|
const { dragQuestionContentId, desireToOpenABranchingModal, canCreatePublic } = useUiTools()
|
2023-12-07 22:56:31 +00:00
|
|
|
|
const trashQuestions = useQuestionsStore().questions
|
2023-12-09 14:52:03 +00:00
|
|
|
|
const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null)
|
2023-11-29 15:45:15 +00:00
|
|
|
|
const [startCreate, setStartCreate] = useState("");
|
|
|
|
|
|
const [startRemove, setStartRemove] = useState("");
|
|
|
|
|
|
|
|
|
|
|
|
const cyRef = useRef<Core | null>(null);
|
2023-12-01 08:12:59 +00:00
|
|
|
|
const layoutsContainer = useRef<HTMLDivElement | null>(null);
|
2023-11-29 15:45:15 +00:00
|
|
|
|
const plusesContainer = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
|
const crossesContainer = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
|
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
|
|
|
|
|
|
2023-12-20 17:31:51 +00:00
|
|
|
|
const { layoutOptions } = usePopper({
|
|
|
|
|
|
layoutsContainer,
|
|
|
|
|
|
plusesContainer,
|
|
|
|
|
|
crossesContainer,
|
|
|
|
|
|
gearsContainer,
|
|
|
|
|
|
setModalQuestionParentContentId,
|
|
|
|
|
|
setOpenedModalQuestions,
|
|
|
|
|
|
setStartCreate,
|
|
|
|
|
|
setStartRemove,
|
|
|
|
|
|
});
|
|
|
|
|
|
const { removeNode } = useRemoveNode({
|
|
|
|
|
|
cyRef,
|
|
|
|
|
|
layoutOptions,
|
|
|
|
|
|
layoutsContainer,
|
|
|
|
|
|
plusesContainer,
|
|
|
|
|
|
crossesContainer,
|
|
|
|
|
|
gearsContainer,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2023-12-19 19:55:59 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
// if (!canCreatePublic) updateModalInfoWhyCantCreate(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2023-12-05 23:34:40 +00:00
|
|
|
|
useLayoutEffect(() => {
|
2023-12-07 00:03:43 +00:00
|
|
|
|
const cy = cyRef?.current
|
2023-12-05 23:34:40 +00:00
|
|
|
|
if (desireToOpenABranchingModal) {
|
2023-12-07 00:03:43 +00:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true)
|
|
|
|
|
|
}, 250)
|
2023-12-05 23:34:40 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
cy?.elements().data("eroticeyeblink", false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [desireToOpenABranchingModal])
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
|
updateOpenedModalSettingsId()
|
2023-12-19 15:00:51 +00:00
|
|
|
|
// updateRootContentId(quiz.id, "")
|
|
|
|
|
|
// clearRuleForAll()
|
2023-12-05 23:34:40 +00:00
|
|
|
|
}, [])
|
2023-12-02 13:11:57 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
2023-12-04 09:30:14 +00:00
|
|
|
|
addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId })
|
2023-12-02 13:11:57 +00:00
|
|
|
|
}
|
2023-12-04 15:40:15 +00:00
|
|
|
|
setModalQuestionParentContentId("")
|
|
|
|
|
|
setModalQuestionTargetContentId("")
|
2023-12-02 13:11:57 +00:00
|
|
|
|
}, [modalQuestionTargetContentId])
|
2023-12-01 08:12:59 +00:00
|
|
|
|
|
2023-12-02 21:20:41 +00:00
|
|
|
|
const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => {
|
2023-12-22 21:14:48 +00:00
|
|
|
|
if (quiz) {
|
|
|
|
|
|
|
|
|
|
|
|
//запрещаем работу родителя-ребенка если это один и тот же вопрос
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2023-12-01 08:12:59 +00:00
|
|
|
|
}
|
2023-12-22 21:14:48 +00:00
|
|
|
|
])
|
|
|
|
|
|
cy?.layout(layoutOptions).run()
|
|
|
|
|
|
cy?.center(es)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
enqueueSnackbar("Добавляемый вопрос не найден")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-02 09:59:31 +00:00
|
|
|
|
} else {
|
2023-12-22 21:14:48 +00:00
|
|
|
|
enqueueSnackbar("Квиз не найден")
|
2023-12-02 13:11:57 +00:00
|
|
|
|
}
|
2023-12-01 19:56:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-03 13:09:57 +00:00
|
|
|
|
const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => {
|
2023-12-11 15:40:42 +00:00
|
|
|
|
|
|
|
|
|
|
const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-12-21 08:29:51 +00:00
|
|
|
|
//смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
|
2023-12-11 15:40:42 +00:00
|
|
|
|
trashQuestions.forEach((targetQuestion) => {
|
|
|
|
|
|
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
|
2023-12-21 08:29:51 +00:00
|
|
|
|
updateQuestion(targetQuestion.id, (q) => q.content.usage = false);
|
2023-12-11 15:40:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2023-12-02 09:59:31 +00:00
|
|
|
|
//предупреждаем добавленный вопрос о том, кто его родитель
|
2023-12-02 13:11:57 +00:00
|
|
|
|
updateQuestion(targetQuestion.content.id, question => {
|
|
|
|
|
|
question.content.rule.parentId = parentNodeContentId
|
|
|
|
|
|
question.content.rule.main = []
|
2023-12-23 11:35:09 +00:00
|
|
|
|
//Это листик. Сбросим ему на всякий случай не листиковые поля
|
|
|
|
|
|
question.content.rule.children = []
|
|
|
|
|
|
question.content.rule.default = ""
|
2023-12-02 13:11:57 +00:00
|
|
|
|
})
|
2023-12-23 11:35:09 +00:00
|
|
|
|
|
|
|
|
|
|
const noChild = parentQuestion.content.rule.children.length === 0
|
|
|
|
|
|
|
2023-12-11 15:40:42 +00:00
|
|
|
|
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
|
|
|
|
|
|
if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id)) updateQuestion(parentNodeContentId, question => {
|
2023-12-11 10:08:54 +00:00
|
|
|
|
question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id]
|
2023-12-23 11:35:09 +00:00
|
|
|
|
//единственному ребёнку даём дефолт по-умолчанию
|
|
|
|
|
|
question.content.rule.default = noChild ? targetQuestion.content.id : question.content.rule.default
|
2023-12-11 10:08:54 +00:00
|
|
|
|
})
|
|
|
|
|
|
|
2023-12-23 11:35:09 +00:00
|
|
|
|
if (!noChild) {//детей больше 1
|
|
|
|
|
|
//- предупреждаем стор вопросов об открытии модалки ветвления
|
2023-12-04 13:33:43 +00:00
|
|
|
|
updateOpenedModalSettingsId(targetQuestion.content.id)
|
2023-12-02 13:11:57 +00:00
|
|
|
|
}
|
2023-12-23 11:35:09 +00:00
|
|
|
|
|
|
|
|
|
|
|
2023-12-01 19:56:13 +00:00
|
|
|
|
}
|
2023-12-02 09:59:31 +00:00
|
|
|
|
|
2023-12-12 15:44:18 +00:00
|
|
|
|
|
2023-11-29 15:45:15 +00:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (startCreate) {
|
2023-12-01 19:56:13 +00:00
|
|
|
|
addNode({ parentNodeContentId: startCreate });
|
2023-12-20 10:46:38 +00:00
|
|
|
|
cleardragQuestionContentId();
|
2023-11-29 15:45:15 +00:00
|
|
|
|
setStartCreate("");
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [startCreate]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (startRemove) {
|
2023-12-20 17:31:51 +00:00
|
|
|
|
updateDeleteId(startRemove);
|
2023-11-29 15:45:15 +00:00
|
|
|
|
setStartRemove("");
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [startRemove]);
|
|
|
|
|
|
|
2023-12-02 09:59:31 +00:00
|
|
|
|
useEffect(() => {
|
2023-12-20 10:46:38 +00:00
|
|
|
|
document
|
|
|
|
|
|
.querySelector("#root")
|
|
|
|
|
|
?.addEventListener("mouseup", cleardragQuestionContentId);
|
2023-12-02 09:59:31 +00:00
|
|
|
|
const cy = cyRef.current;
|
2023-12-20 10:46:38 +00:00
|
|
|
|
const eles = cy?.add(
|
|
|
|
|
|
storeToNodes(
|
|
|
|
|
|
questions.filter(
|
|
|
|
|
|
(question) => question.type && question.type !== "result"
|
|
|
|
|
|
) as AnyTypedQuizQuestion[]
|
|
|
|
|
|
)
|
|
|
|
|
|
);
|
|
|
|
|
|
cy?.data("changed", true);
|
2023-12-05 23:34:40 +00:00
|
|
|
|
// cy.data('changed', true)
|
2023-12-20 10:46:38 +00:00
|
|
|
|
const elecs = eles?.layout(layoutOptions).run();
|
|
|
|
|
|
cy?.on("add", () => cy.data("changed", true));
|
|
|
|
|
|
cy?.fit();
|
2023-12-02 09:59:31 +00:00
|
|
|
|
//cy?.layout().run()
|
2023-11-29 15:45:15 +00:00
|
|
|
|
|
|
|
|
|
|
return () => {
|
2023-12-20 10:46:38 +00:00
|
|
|
|
document
|
|
|
|
|
|
.querySelector("#root")
|
|
|
|
|
|
?.removeEventListener("mouseup", cleardragQuestionContentId);
|
2023-12-01 08:12:59 +00:00
|
|
|
|
layoutsContainer.current?.remove();
|
2023-11-29 15:45:15 +00:00
|
|
|
|
plusesContainer.current?.remove();
|
|
|
|
|
|
crossesContainer.current?.remove();
|
|
|
|
|
|
gearsContainer.current?.remove();
|
|
|
|
|
|
};
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2023-12-02 20:47:28 +00:00
|
|
|
|
<>
|
2023-12-19 15:00:51 +00:00
|
|
|
|
<Box
|
2023-12-22 21:14:48 +00:00
|
|
|
|
mb="20px">
|
2023-12-19 15:00:51 +00:00
|
|
|
|
<Button
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
height: "27px",
|
|
|
|
|
|
color: "#7E2AEA",
|
|
|
|
|
|
textDecoration: "underline",
|
|
|
|
|
|
fontSize: "16px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
variant="text"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
|
|
|
|
|
|
cyRef.current?.fit()
|
|
|
|
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
Выровнять
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<ProblemIcon blink={!canCreatePublic} onClick={() => updateModalInfoWhyCantCreate(true)} />
|
|
|
|
|
|
</Box>
|
2023-12-22 21:14:48 +00:00
|
|
|
|
|
2023-12-04 09:30:14 +00:00
|
|
|
|
<CytoscapeComponent
|
|
|
|
|
|
wheelSensitivity={0.1}
|
|
|
|
|
|
elements={[]}
|
|
|
|
|
|
// elements={createGraphElements(tree, quiz)}
|
|
|
|
|
|
style={{ height: "480px", background: "#F2F3F7" }}
|
|
|
|
|
|
stylesheet={stylesheet}
|
2023-12-20 18:38:28 +00:00
|
|
|
|
layout={(layoutOptions)}
|
2023-12-04 09:30:14 +00:00
|
|
|
|
cy={(cy) => {
|
|
|
|
|
|
cyRef.current = cy;
|
|
|
|
|
|
}}
|
2023-12-11 10:08:54 +00:00
|
|
|
|
autoungrabify={true}
|
2023-12-04 09:30:14 +00:00
|
|
|
|
/>
|
2023-12-21 14:56:29 +00:00
|
|
|
|
<DeleteNodeModal removeNode={removeNode} />
|
2023-12-02 20:47:28 +00:00
|
|
|
|
</>
|
2023-11-29 15:45:15 +00:00
|
|
|
|
);
|
|
|
|
|
|
};
|
2023-12-07 00:03:43 +00:00
|
|
|
|
|
2023-12-11 10:08:54 +00:00
|
|
|
|
function Clear() {
|
|
|
|
|
|
const quiz = useCurrentQuiz();
|
2023-12-21 14:56:29 +00:00
|
|
|
|
if (quiz) {
|
|
|
|
|
|
updateRootContentId(quiz?.id, "");
|
|
|
|
|
|
}
|
2023-12-11 10:08:54 +00:00
|
|
|
|
clearRuleForAll()
|
|
|
|
|
|
return <></>
|
2023-12-07 00:03:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default withErrorBoundary(CsComponent, {
|
2023-12-11 10:08:54 +00:00
|
|
|
|
fallback: <Clear />,
|
2023-12-07 00:03:43 +00:00
|
|
|
|
onError: (error, info) => {
|
|
|
|
|
|
enqueueSnackbar("Дерево порвалось")
|
|
|
|
|
|
console.log(info)
|
|
|
|
|
|
console.log(error)
|
|
|
|
|
|
},
|
2023-12-09 19:31:06 +00:00
|
|
|
|
});
|