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

327 lines
9.7 KiB
TypeScript
Raw Normal View History

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 12:34:07 +00:00
import { Button } from "@mui/material";
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,
createFrontResult,
} from "@root/questions/actions";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { cleardragQuestionContentId } from "@root/uiTools/actions";
2023-11-29 15:45:15 +00:00
2023-12-20 10:46:38 +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 10:46:38 +00:00
import { stylesheet } from "./stylesheet";
2023-11-29 15:45:15 +00:00
import "./styles.css";
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 10:46:38 +00:00
type CsComponentProps = {
modalQuestionParentContentId: string;
modalQuestionTargetContentId: string;
setOpenedModalQuestions: (open: boolean) => void;
setModalQuestionParentContentId: (id: string) => void;
setModalQuestionTargetContentId: (id: string) => void;
2023-12-20 10:46:38 +00:00
};
function CsComponent({
modalQuestionParentContentId,
modalQuestionTargetContentId,
setOpenedModalQuestions,
setModalQuestionParentContentId,
2023-12-20 10:46:38 +00:00
setModalQuestionTargetContentId,
}: CsComponentProps) {
2023-11-29 15:45:15 +00:00
const quiz = useCurrentQuiz();
2023-12-20 10:46:38 +00:00
const { dragQuestionContentId, desireToOpenABranchingModal } = useUiTools();
const trashQuestions = useQuestionsStore().questions;
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 10:55:38 +00:00
const { layoutOptions } = usePopper({
layoutsContainer,
plusesContainer,
crossesContainer,
gearsContainer,
setModalQuestionParentContentId,
setOpenedModalQuestions,
setStartCreate,
setStartRemove,
});
const { removeNode } = useRemoveNode({
cyRef,
layoutOptions,
layoutsContainer,
plusesContainer,
crossesContainer,
gearsContainer,
});
2023-12-20 10:46:38 +00:00
useLayoutEffect(() => {
2023-12-20 10:46:38 +00:00
const cy = cyRef?.current;
if (desireToOpenABranchingModal) {
setTimeout(() => {
2023-12-20 10:46:38 +00:00
cy?.getElementById(desireToOpenABranchingModal)?.data(
"eroticeyeblink",
true
);
}, 250);
} else {
2023-12-20 10:46:38 +00:00
cy?.elements().data("eroticeyeblink", false);
}
2023-12-20 10:46:38 +00:00
}, [desireToOpenABranchingModal]);
useLayoutEffect(() => {
2023-12-20 10:46:38 +00:00
updateOpenedModalSettingsId();
// updateRootContentId(quiz.id, "")
// clearRuleForAll()
}, []);
useEffect(() => {
2023-12-20 10:46:38 +00:00
if (
modalQuestionTargetContentId.length !== 0 &&
modalQuestionParentContentId.length !== 0
) {
addNode({
parentNodeContentId: modalQuestionParentContentId,
targetNodeContentId: modalQuestionTargetContentId,
});
}
2023-12-01 08:12:59 +00:00
2023-12-20 10:46:38 +00:00
setModalQuestionParentContentId("");
setModalQuestionTargetContentId("");
}, [modalQuestionTargetContentId]);
const addNode = ({
parentNodeContentId,
targetNodeContentId,
}: {
parentNodeContentId: string;
targetNodeContentId?: string;
}) => {
//запрещаем работу родителя-ребенка если это один и тот же вопрос
2023-12-20 10:46:38 +00:00
if (parentNodeContentId === targetNodeContentId) return;
2023-12-20 10:46:38 +00:00
const cy = cyRef?.current;
const parentNodeChildren = cy?.$(
'edge[source = "' + parentNodeContentId + '"]'
)?.length;
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
2023-12-20 10:46:38 +00:00
const targetQuestion = {
...getQuestionByContentId(targetNodeContentId || dragQuestionContentId),
} as AnyTypedQuizQuestion;
if (
Object.keys(targetQuestion).length !== 0 &&
parentNodeContentId &&
parentNodeChildren !== undefined
) {
clearDataAfterAddNode({
parentNodeContentId,
targetQuestion,
parentNodeChildren,
});
cy?.data("changed", true);
if (quiz) {
createFrontResult(quiz.backendId, targetQuestion.content.id);
}
const es = cy?.add([
2023-12-01 08:12:59 +00:00
{
data: {
id: targetQuestion.content.id,
2023-12-20 10:46:38 +00:00
label:
targetQuestion.title === "" || targetQuestion.title === " "
? "noname"
: targetQuestion.title,
},
2023-12-01 08:12:59 +00:00
},
{
data: {
source: parentNodeContentId,
2023-12-20 10:46:38 +00:00
target: targetQuestion.content.id,
},
},
]);
cy?.layout(layoutOptions).run();
console.log(es);
cy?.center(es);
} else {
2023-12-20 10:46:38 +00:00
enqueueSnackbar("Добавляемый вопрос не найден");
}
2023-12-20 10:46:38 +00:00
};
2023-12-20 10:46:38 +00:00
const clearDataAfterAddNode = ({
parentNodeContentId,
targetQuestion,
parentNodeChildren,
}: {
parentNodeContentId: string;
targetQuestion: AnyTypedQuizQuestion;
parentNodeChildren: number;
}) => {
const parentQuestion = {
...getQuestionByContentId(parentNodeContentId),
} as AnyTypedQuizQuestion;
//смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен
trashQuestions.forEach((targetQuestion) => {
2023-12-20 10:46:38 +00:00
if (
targetQuestion.type === "result" &&
targetQuestion.content.rule.parentId === parentQuestion.content.id
) {
console.log("deleteQ", targetQuestion.id);
deleteQuestion(targetQuestion.id);
}
2023-12-20 10:46:38 +00:00
});
//предупреждаем добавленный вопрос о том, кто его родитель
2023-12-20 10:46:38 +00:00
updateQuestion(targetQuestion.content.id, (question) => {
question.content.rule.parentId = parentNodeContentId;
question.content.rule.main = [];
});
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
2023-12-20 10:46:38 +00:00
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) {
2023-12-20 10:46:38 +00:00
updateOpenedModalSettingsId(targetQuestion.content.id);
}
2023-12-20 10:46:38 +00:00
};
2023-11-29 15:45:15 +00:00
useEffect(() => {
if (startCreate) {
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 10:46:38 +00:00
removeNode(startRemove);
2023-11-29 15:45:15 +00:00
setStartRemove("");
}
}, [startRemove]);
useEffect(() => {
2023-12-20 10:46:38 +00:00
document
.querySelector("#root")
?.addEventListener("mouseup", cleardragQuestionContentId);
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);
// 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();
//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 (
<>
<Button
sx={{
mb: "20px",
height: "27px",
color: "#7E2AEA",
textDecoration: "underline",
fontSize: "16px",
}}
variant="text"
onClick={() => {
2023-12-20 10:46:38 +00:00
cyRef.current?.fit();
}}
>
Выровнять
</Button>
<CytoscapeComponent
wheelSensitivity={0.1}
elements={[]}
// elements={createGraphElements(tree, quiz)}
style={{ height: "480px", background: "#F2F3F7" }}
stylesheet={stylesheet}
2023-12-20 10:46:38 +00:00
layout={layoutOptions}
cy={(cy) => {
cyRef.current = cy;
}}
autoungrabify={true}
/>
{/* <button onClick={() => {
console.log("NODES____________________________")
cyRef.current?.elements().forEach((ele: any) => {
console.log(ele.data())
})
}}>nodes</button>
<button onClick={() => {
console.log("ELEMENTS____________________________")
2023-12-04 07:50:55 +00:00
console.log(questions)
}}>elements</button> */}
</>
2023-11-29 15:45:15 +00:00
);
2023-12-20 10:46:38 +00:00
}
function Clear() {
const quiz = useCurrentQuiz();
2023-12-20 10:46:38 +00:00
if (quiz) {
updateRootContentId(quiz.id, "");
}
clearRuleForAll();
return <></>;
}
export default withErrorBoundary(CsComponent, {
fallback: <Clear />,
onError: (error, info) => {
2023-12-20 10:46:38 +00:00
enqueueSnackbar("Дерево порвалось");
console.log(info);
console.log(error);
},
});