import { useEffect, useLayoutEffect, useRef, useState } from "react"; import Cytoscape from "cytoscape"; import CytoscapeComponent from "react-cytoscapejs"; import popper from "cytoscape-popper"; import { Button } 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, createFrontResult, } from "@root/questions/actions"; import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { cleardragQuestionContentId } from "@root/uiTools/actions"; import { useRemoveNode } from "./hooks/useRemoveNode"; import { usePopper } from "./hooks/usePopper"; import { storeToNodes } from "./helper"; import { stylesheet } from "./stylesheet"; import "./styles.css"; import type { Core } from "cytoscape"; Cytoscape.use(popper); type 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 } = 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(null); const layoutsContainer = useRef(null); const plusesContainer = useRef(null); const crossesContainer = useRef(null); const gearsContainer = useRef(null); const { layoutOptions } = usePopper({ layoutsContainer, plusesContainer, crossesContainer, gearsContainer, setModalQuestionParentContentId, setOpenedModalQuestions, setStartCreate, setStartRemove, }); const { removeNode } = useRemoveNode({ cyRef, layoutOptions, layoutsContainer, plusesContainer, crossesContainer, gearsContainer, }); 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); if (quiz) { createFrontResult(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(); console.log(es); 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 ) { console.log("deleteQ", targetQuestion.id); deleteQuestion(targetQuestion.id); } }); //предупреждаем добавленный вопрос о том, кто его родитель 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) { removeNode(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 ( <> { cyRef.current = cy; }} autoungrabify={true} /> {/* */} ); } function Clear() { const quiz = useCurrentQuiz(); if (quiz) { updateRootContentId(quiz.id, ""); } clearRuleForAll(); return <>; } export default withErrorBoundary(CsComponent, { fallback: , onError: (error, info) => { enqueueSnackbar("Дерево порвалось"); console.log(info); console.log(error); }, });