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, someWorkBackend } = useUiTools() const trashQuestions = useQuestionsStore().questions const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null && !question.deleted) 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() }, []) //Отлов mouseup для отрисовки ноды 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 (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 } } ]) cy?.layout(layoutOptions).run() cy?.center(es) } else { enqueueSnackbar("Добавляемый вопрос не найден") } } 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 = [] //Это листик. Сбросим ему на всякий случай не листиковые поля question.content.rule.children = [] question.content.rule.default = "" }) const noChild = parentQuestion.content.rule.children.length === 0 //предупреждаем родителя о новом потомке (если он ещё не знает о нём) if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id)) updateQuestion(parentNodeContentId, question => { question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id] //единственному ребёнку даём дефолт по-умолчанию question.content.rule.default = noChild ? targetQuestion.content.id : question.content.rule.default }) if (!noChild) {//детей больше 1 console.log("детей ", noChild, " открываем модалку ветвления") //- предупреждаем стор вопросов об открытии модалки ветвления updateOpenedModalSettingsId(targetQuestion.content.id) } } useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); cleardragQuestionContentId(); setStartCreate(""); } }, [startCreate]); useEffect(() => { if (startRemove) { updateDeleteId(startRemove); setStartRemove(""); } }, [startRemove]); //Отработка первичного рендера странички графика const firstRender = useRef(true) useEffect(() => { console.log("____________ПЕРВЧИНЫЙ РЕНДЕР____________") console.log("______someWorkBackend______", someWorkBackend) if (!someWorkBackend && firstRender.current) { console.log("цс первично отрабатывает") document .querySelector("#root") ?.addEventListener("mouseup", cleardragQuestionContentId); const cy = cyRef.current; console.log("СПИСОК ЭЛЕМЕНТОВ ЦИТОСКЕЙПА В ПЕРВЧИНЫЙ РЕНДЕР") console.log(cy?.elements()) 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() firstRender.current = false } return () => { console.log("разрендер") document .querySelector("#root") ?.removeEventListener("mouseup", cleardragQuestionContentId); layoutsContainer.current?.remove(); plusesContainer.current?.remove(); crossesContainer.current?.remove(); gearsContainer.current?.remove(); }; }, [someWorkBackend]); return ( <> updateModalInfoWhyCantCreate(true)} /> { 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) }, });