import { devlog } from "@frontend/kitui"; import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { Box, Button } from "@mui/material"; import { clearRuleForAll } from "@root/questions/actions"; import { useQuestionsStore } from "@root/questions/store"; import { updateRootContentId } from "@root/quizes/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { cleardragQuestionContentId, setModalQuestionParentContentId, setModalQuestionTargetContentId, updateModalInfoWhyCantCreate, updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { useUiTools } from "@root/uiTools/store"; import { ProblemIcon } from "@ui_kit/ProblemIcon"; import type { Core, PresetLayoutOptions, SingularData } from "cytoscape"; import Cytoscape from "cytoscape"; import popper, { getPopperInstance } from "cytoscape-popper"; import { enqueueSnackbar } from "notistack"; import { useEffect, useLayoutEffect, useRef, useState } from "react"; import CytoscapeComponent from "react-cytoscapejs"; import { withErrorBoundary } from "react-error-boundary"; import { DeleteNodeModal } from "../DeleteNodeModal"; import { addNode, calcNodePosition, storeToNodes } from "./helper"; import { usePopper } from "./hooks/usePopper"; import { useRemoveNode } from "./hooks/useRemoveNode"; import "./style/styles.css"; import { stylesheet } from "./style/stylesheet"; Cytoscape.use(popper); type PopperInstance = ReturnType>; function CsComponent() { const quiz = useCurrentQuiz(); const desireToOpenABranchingModal = useUiTools(state => state.desireToOpenABranchingModal); const canCreatePublic = useUiTools(state => state.canCreatePublic); const modalQuestionParentContentId = useUiTools(state => state.modalQuestionParentContentId); const modalQuestionTargetContentId = useUiTools(state => state.modalQuestionTargetContentId); const trashQuestions = useQuestionsStore(state => state.questions); const questions = trashQuestions.filter((question) => question.type !== "result" && question.type !== null); const [isPanningCy, setIsPanningCy] = useState(false); const cyRef = useRef(null); const popperContainerRef = useRef(null); const popperInstancesRef = useRef([]); const { createPoppers, removeAllPoppers, removePoppersById } = usePopper({ cyRef, quizId: quiz?.backendId, runCyLayout, popperContainerRef, popperInstancesRef, }); function runCyLayout() { cyRef.current?.layout(layoutOptions).run(); createPoppers(); }; const { removeNode } = useRemoveNode({ cyRef, runCyLayout, removeButtons: removePoppersById, }); useLayoutEffect(() => { const cy = cyRef?.current; if (desireToOpenABranchingModal) { setTimeout(() => { cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true); }, 250); } else { cy?.elements().data("eroticeyeblink", false); } }, [desireToOpenABranchingModal]); useEffect(() => { if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { if (!cyRef.current || !quiz) return; const es = addNode({ cy: cyRef.current, quizId: quiz.backendId, parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId, }); runCyLayout(); if (es) cyRef.current.fit(es, 100); } setModalQuestionParentContentId(""); setModalQuestionTargetContentId(""); }, [modalQuestionTargetContentId, quiz?.backendId]); useEffect(function onMount() { updateOpenedModalSettingsId(); document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId); const cy = cyRef.current; if (!cy) return; cy.add( storeToNodes( questions.filter((question) => question.type && question.type !== "result") as AnyTypedQuizQuestion[], ), ); runCyLayout(); cy.fit(); return () => { document.querySelector("#root")?.removeEventListener("mouseup", cleardragQuestionContentId); removeAllPoppers(); }; }, []); useEffect(function attachDragHandlers() { const cy = cyRef.current; if (!cy) return; let isPointerDown = false; const onPointerDown = () => { isPointerDown = true; cy.data("dragging", true); }; const onPointerUp = () => { isPointerDown = false; cy.data("dragging", false); setIsPanningCy(false); }; const handleMove = () => { setIsPanningCy(isPointerDown); }; cy.on("vmousedown", onPointerDown); cy.on("vmousemove", handleMove); document.addEventListener("pointerup", onPointerUp); return () => { cy.off("vmousedown", onPointerDown); cy.off("vmousemove", handleMove); document.removeEventListener("pointerup", onPointerUp); }; }, []); useEffect(function poppersLifecycle() { if (isPanningCy) { removeAllPoppers(); } else { createPoppers(); } }, [isPanningCy]); 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("Дерево порвалось"); devlog(info); devlog(error); }, }); const layoutOptions: PresetLayoutOptions = { name: "preset", positions: calcNodePosition, zoom: undefined, pan: 1, fit: false, padding: 30, animate: false, animationDuration: 500, animationEasing: undefined, animateFilter: () => false, ready: event => { if (event.cy.data("firstNode") === "nonroot") { event.cy.data("firstNode", "root"); event.cy.nodes().sort((a, b) => (a.data("root") ? 1 : -1)); } else { event.cy.removeData("firstNode"); } }, transform: (_, p) => p, };