diff --git a/src/pages/Questions/BranchingMap/CsComponent.tsx b/src/pages/Questions/BranchingMap/CsComponent.tsx index 30db6972..a66cbd02 100644 --- a/src/pages/Questions/BranchingMap/CsComponent.tsx +++ b/src/pages/Questions/BranchingMap/CsComponent.tsx @@ -4,15 +4,25 @@ import { Button } from "@mui/material"; import CytoscapeComponent from "react-cytoscapejs"; import popper from "cytoscape-popper"; import { useCurrentQuiz } from "@root/quizes/hooks"; -import { updateRootContentId } from "@root/quizes/actions" -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared" +import { updateRootContentId } from "@root/quizes/actions"; +import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { useQuestionsStore } from "@root/questions/store"; -import { deleteQuestion, updateQuestion, getQuestionByContentId, clearRuleForAll, createFrontResult } from "@root/questions/actions"; -import { updateOpenedModalSettingsId, } from "@root/uiTools/actions"; +import { + deleteQuestion, + updateQuestion, + getQuestionByContentId, + clearRuleForAll, + createFrontResult, +} from "@root/questions/actions"; +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; import { cleardragQuestionContentId } from "@root/uiTools/actions"; import { withErrorBoundary } from "react-error-boundary"; +import { useRemoveNode } from "./hooks/useRemoveNode"; +import { usePopper } from "./hooks/usePopper"; + import { storeToNodes } from "./helper"; +import { stylesheet } from "./stylesheet"; import "./styles.css"; @@ -22,111 +32,61 @@ import type { NodeSingular, AbstractEventObject, ElementDefinition, + LayoutEventObject, } from "cytoscape"; import { enqueueSnackbar } from "notistack"; import { useUiTools } from "@root/uiTools/store"; -type PopperItem = { - id: () => string; -}; +// type PopperItem = { +// id: () => string; +// }; -type Modifier = { - name: string; - options: unknown; -}; +// type Modifier = { +// name: string; +// options: unknown; +// }; -type PopperConfig = { - popper: { - placement: string; - modifiers?: Modifier[]; - }; - content: (items: PopperItem[]) => void; -}; +// type PopperConfig = { +// popper: { +// placement: string; +// modifiers?: Modifier[]; +// }; +// content: (items: PopperItem[]) => void; +// }; -type Popper = { - update: () => Promise; - setOptions: (modifiers: { modifiers?: Modifier[] }) => void; -}; +// type Popper = { +// update: () => Promise; +// setOptions: (modifiers: { modifiers?: Modifier[] }) => void; +// }; -type NodeSingularWithPopper = NodeSingular & { - popper: (config: PopperConfig) => Popper; -}; - -const stylesheet: Stylesheet[] = [ - { - selector: "node", - style: { - shape: "round-rectangle", - width: 130, - height: 130, - backgroundColor: "#FFFFFF", - label: "data(label)", - "font-size": "16", - color: "#4D4D4D", - "text-halign": "center", - "text-valign": "center", - "text-wrap": "wrap", - "text-max-width": "80", - }, - }, - { - selector: "[?eroticeyeblink]", - style: { - "border-width": "4px", - "border-style": "solid", - "border-color": "#7e2aea", - }, - }, - { - selector: ".multiline-auto", - style: { - "text-wrap": "wrap", - "text-max-width": "80", - }, - }, - { - selector: "edge", - style: { - width: 30, - "line-color": "#DEDFE7", - "curve-style": "taxi", - "taxi-direction": "horizontal", - "taxi-turn": 60, - }, - }, - { - selector: ":selected", - style: { - "border-style": "solid", - "border-width": 1.5, - "border-color": "#9A9AAF", - }, - }, -]; +// type NodeSingularWithPopper = NodeSingular & { +// popper: (config: PopperConfig) => Popper; +// }; Cytoscape.use(popper); -interface Props { +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 -}: Props) { + 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 { 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(""); @@ -136,354 +96,421 @@ function CsComponent({ const crossesContainer = useRef(null); const gearsContainer = useRef(null); + // const { layoutOptions } = usePopper({ + // layoutsContainer, + // plusesContainer, + // crossesContainer, + // gearsContainer, + // setModalQuestionParentContentId, + // setOpenedModalQuestions, + // setStartCreate, + // setStartRemove, + // }); + const layoutOptions = {}; + const removeNode = () => {}; + // const { removeNode } = useRemoveNode({ + // cyRef, + // layoutOptions, + // layoutsContainer, + // plusesContainer, + // crossesContainer, + // gearsContainer, + // }); + useLayoutEffect(() => { - const cy = cyRef?.current + const cy = cyRef?.current; if (desireToOpenABranchingModal) { setTimeout(() => { - cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true) - }, 250) + cy?.getElementById(desireToOpenABranchingModal)?.data( + "eroticeyeblink", + true + ); + }, 250); } else { - cy?.elements().data("eroticeyeblink", false) + cy?.elements().data("eroticeyeblink", false); } - }, [desireToOpenABranchingModal]) + }, [desireToOpenABranchingModal]); useLayoutEffect(() => { - updateOpenedModalSettingsId() - // updateRootContentId(quiz.id, "") - // clearRuleForAll() - }, []) + updateOpenedModalSettingsId(); + // updateRootContentId(quiz.id, "") + // clearRuleForAll() + }, []); useEffect(() => { - if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) { - addNode({ parentNodeContentId: modalQuestionParentContentId, targetNodeContentId: modalQuestionTargetContentId }) + if ( + modalQuestionTargetContentId.length !== 0 && + modalQuestionParentContentId.length !== 0 + ) { + addNode({ + parentNodeContentId: modalQuestionParentContentId, + targetNodeContentId: modalQuestionTargetContentId, + }); } - setModalQuestionParentContentId("") - setModalQuestionTargetContentId("") - }, [modalQuestionTargetContentId]) - const addNode = ({ parentNodeContentId, targetNodeContentId }: { parentNodeContentId: string, targetNodeContentId?: string }) => { - + setModalQuestionParentContentId(""); + setModalQuestionTargetContentId(""); + }, [modalQuestionTargetContentId]); + + const addNode = ({ + parentNodeContentId, + targetNodeContentId, + }: { + parentNodeContentId: string; + targetNodeContentId?: string; + }) => { //запрещаем работу родителя-ребенка если это один и тот же вопрос - if (parentNodeContentId === targetNodeContentId) return + if (parentNodeContentId === targetNodeContentId) return; - - const cy = cyRef?.current - const parentNodeChildren = cy?.$('edge[source = "' + parentNodeContentId + '"]')?.length + 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) - createFrontResult(quiz.backendId, targetQuestion.content.id) + 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 - } + label: + targetQuestion.title === "" || targetQuestion.title === " " + ? "noname" + : targetQuestion.title, + }, }, { data: { source: parentNodeContentId, - target: targetQuestion.content.id - } - } - ]) - cy?.layout(lyopts).run() - console.log(es) - cy?.center(es) + target: targetQuestion.content.id, + }, + }, + ]); + cy?.layout(layoutOptions).run(); + console.log(es); + cy?.center(es); } else { - enqueueSnackbar("Добавляемый вопрос не найден") + enqueueSnackbar("Добавляемый вопрос не найден"); } - } - - const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => { - - const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion + }; + 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) + 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 = [] - }) + 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] - }) - + 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) + updateOpenedModalSettingsId(targetQuestion.content.id); } - } + }; + // const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { + // console.log("старт удаление") + // const deleteNodes: string[] = [] + // const deleteEdges: any = [] + // const cy = cyRef?.current - const removeNode = ({ targetNodeContentId }: { targetNodeContentId: string }) => { - console.log("старт удаление") - const deleteNodes = [] as string[] - const deleteEdges: any = [] - const cy = cyRef?.current + // const findChildrenToDelete = (node) => { - const findChildrenToDelete = (node) => { + // //Узнаём грани, идущие от этой ноды + // cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { + // const edgeData = edge.data() - //Узнаём грани, идущие от этой ноды - cy?.$('edge[source = "' + node.id() + '"]')?.toArray().forEach((edge) => { - const edgeData = edge.data() + // //записываем id грани для дальнейшего удаления + // deleteEdges.push(edge) + // //ищем ноду на конце грани, записываем её ID для дальнейшего удаления + // const targetNode = cy?.$("#" + edgeData.target) + // deleteNodes.push(targetNode.data().id) + // //вызываем функцию для анализа потомков уже у этой ноды + // findChildrenToDelete(targetNode) + // }) - //записываем id грани для дальнейшего удаления - deleteEdges.push(edge) - //ищем ноду на конце грани, записываем её ID для дальнейшего удаления - const targetNode = cy?.$("#" + edgeData.target) - deleteNodes.push(targetNode.data().id) - //вызываем функцию для анализа потомков уже у этой ноды - findChildrenToDelete(targetNode) - }) + // } + // findChildrenToDelete(cy?.getElementById(targetNodeContentId)) - } - findChildrenToDelete(cy?.getElementById(targetNodeContentId)) + // const targetQuestion = getQuestionByContentId(targetNodeContentId) - const targetQuestion = getQuestionByContentId(targetNodeContentId) + // if (targetQuestion.content.rule.parentId === "root" && quiz) { + // updateRootContentId(quiz?.id, "") + // updateQuestion(targetNodeContentId, question => { + // question.content.rule.parentId = "" + // question.content.rule.main = [] + // question.content.rule.children = [] + // question.content.rule.default = "" + // }) + // trashQuestions.forEach(q => { + // if (q.type === "result") { + // deleteQuestion(q.id); + // } + // }); + // clearRuleForAll() - if (targetQuestion.content.rule.parentId === "root" && quiz) { - updateRootContentId(quiz?.id, "") - updateQuestion(targetNodeContentId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.children = [] - question.content.rule.default = "" - }) - trashQuestions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); - clearRuleForAll() + // } else { - } else { + // const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source + // if (targetNodeContentId && parentQuestionContentId) { + // if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) + // createFrontResult(quiz.backendId, parentQuestionContentId) + // clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) + // cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() + // } - const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source - if (targetNodeContentId && parentQuestionContentId) { -if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) - createFrontResult(quiz.backendId, parentQuestionContentId) - clearDataAfterRemoveNode({ targetQuestionContentId: targetNodeContentId, parentQuestionContentId }) - cy?.remove(cy?.$('#' + targetNodeContentId)).layout(lyopts).run() - } + // } - } + // //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке - //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке + // deleteNodes.forEach((nodeId) => {//Ноды + // cy?.remove(cy?.$("#" + nodeId)) + // removeButtons(nodeId) + // updateQuestion(nodeId, question => { + // question.content.rule.parentId = "" + // question.content.rule.main = [] + // question.content.rule.default = "" + // question.content.rule.children = [] + // }) - deleteNodes.forEach((nodeId) => {//Ноды - cy?.remove(cy?.$("#" + nodeId)) - removeButtons(nodeId) - updateQuestion(nodeId, question => { - question.content.rule.parentId = "" - question.content.rule.main = [] - question.content.rule.default = "" - question.content.rule.children = [] - }) + // }) + // deleteEdges.forEach((edge: any) => {//Грани + // cy?.remove(edge) + // }) - }) + // removeButtons(targetNodeContentId) + // cy?.data('changed', true) + // cy?.layout(lyopts).run() - deleteEdges.forEach((edge: any) => {//Грани - cy?.remove(edge) - }) + // //удаляем result всех потомков + // trashQuestions.forEach((qr) => { + // if (qr.type === "result") { + // if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { + // deleteQuestion(qr.id); + // } + // } + // }) + // } - removeButtons(targetNodeContentId) - cy?.data('changed', true) - cy?.layout(lyopts).run() + // const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - //удаляем result всех потомков - trashQuestions.forEach((qr) => { - if (qr.type === "result") { - if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) { - deleteQuestion(qr.id); - } - } - }) - } - - - const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { - - - - updateQuestion(targetQuestionContentId, question => { - question.content.rule.parentId = "" - question.content.rule.children = [] - question.content.rule.main = [] - question.content.rule.default = "" - }) - - - //чистим rule родителя - const parentQuestion = getQuestionByContentId(parentQuestionContentId) - const newRule = {} - const newChildren = [...parentQuestion.content.rule.children] - newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); - newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу - newRule.parentId = parentQuestion.content.rule.parentId - newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default - newRule.children = newChildren - - updateQuestion(parentQuestionContentId, (PQ) => { - PQ.content.rule = newRule - }) - } + // updateQuestion(targetQuestionContentId, question => { + // question.content.rule.parentId = "" + // question.content.rule.children = [] + // question.content.rule.main = [] + // question.content.rule.default = "" + // }) + // //чистим rule родителя + // const parentQuestion = getQuestionByContentId(parentQuestionContentId) + // const newRule = {} + // const newChildren = [...parentQuestion.content.rule.children] + // newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1); + // newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу + // newRule.parentId = parentQuestion.content.rule.parentId + // newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default + // newRule.children = newChildren + // updateQuestion(parentQuestionContentId, (PQ) => { + // PQ.content.rule = newRule + // }) + // } useEffect(() => { if (startCreate) { addNode({ parentNodeContentId: startCreate }); - cleardragQuestionContentId() + cleardragQuestionContentId(); setStartCreate(""); } }, [startCreate]); useEffect(() => { if (startRemove) { - removeNode({ targetNodeContentId: startRemove }); + removeNode(startRemove); setStartRemove(""); } }, [startRemove]); + // const readyLO = (e) => { + // if (e.cy.data('firstNode') === 'nonroot') { + // e.cy.data('firstNode', 'root') + // e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() - const readyLO = (e) => { - if (e.cy.data('firstNode') === 'nonroot') { - e.cy.data('firstNode', 'root') - e.cy.nodes().sort((a, b) => (a.data('root') ? 1 : -1)).layout(lyopts).run() + // } else { - } else { + // e.cy.data('changed', false) + // e.cy.removeData('firstNode') + // } + // //удаляем иконки + // e.cy.nodes().forEach((ele: any) => { + // const data = ele.data() + // data.id && removeButtons(data.id); + // }) + // initialPopperIcons(e) + // } - e.cy.data('changed', false) - e.cy.removeData('firstNode') - } + // const lyopts = { + // name: 'preset', - //удаляем иконки - e.cy.nodes().forEach((ele: any) => { - const data = ele.data() - data.id && removeButtons(data.id); - }) - initialPopperIcons(e) - } + // positions: (e) => { + // if (!e.cy().data('changed')) { + // return e.data('oldPos') + // } + // const id = e.id() + // const incomming = e.cy().edges(`[target="${id}"]`) + // const layer = 0 + // e.removeData('lastChild') - const lyopts = { - name: 'preset', + // if (incomming.length === 0) { + // if (e.cy().data('firstNode') === undefined) + // e.cy().data('firstNode', 'root') + // e.data('root', true) + // const children = e.cy().edges(`[source="${id}"]`).targets() + // e.data('layer', layer) + // e.data('children', children.length) + // const queue = [] + // children.forEach(n => { + // queue.push({ task: n, layer: layer + 1 }) + // }) + // while (queue.length) { + // const task = queue.pop() + // task.task.data('layer', task.layer) + // task.task.removeData('subtreeWidth') + // const children = e.cy().edges(`[source="${task.task.id()}"]`).targets() + // task.task.data('children', children.length) + // if (children.length !== 0) { + // children.forEach(n => queue.push({ task: n, layer: task.layer + 1 })) + // } + // } + // queue.push({ parent: e, children: children }) + // while (queue.length) { + // const task = queue.pop() + // if (task.children.length === 0) { + // task.parent.data('subtreeWidth', task.parent.height() + 50) + // continue + // } + // const unprocessed = task?.children.filter(e => { + // return (e.data('subtreeWidth') === undefined) + // }) + // if (unprocessed.length !== 0) { + // queue.push(task) + // unprocessed.forEach(t => { + // queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() }) + // }) + // continue + // } - positions: (e) => { - if (!e.cy().data('changed')) { - return e.data('oldPos') - } - const id = e.id() - const incomming = e.cy().edges(`[target="${id}"]`) - const layer = 0 - e.removeData('lastChild') + // task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) + // } - if (incomming.length === 0) { - if (e.cy().data('firstNode') === undefined) - e.cy().data('firstNode', 'root') - e.data('root', true) - const children = e.cy().edges(`[source="${id}"]`).targets() - e.data('layer', layer) - e.data('children', children.length) - const queue = [] - children.forEach(n => { - queue.push({ task: n, layer: layer + 1 }) - }) - while (queue.length) { - const task = queue.pop() - task.task.data('layer', task.layer) - task.task.removeData('subtreeWidth') - const children = e.cy().edges(`[source="${task.task.id()}"]`).targets() - task.task.data('children', children.length) - if (children.length !== 0) { - children.forEach(n => queue.push({ task: n, layer: task.layer + 1 })) - } - } - queue.push({ parent: e, children: children }) - while (queue.length) { - const task = queue.pop() - if (task.children.length === 0) { - task.parent.data('subtreeWidth', task.parent.height() + 50) - continue - } - const unprocessed = task?.children.filter(e => { - return (e.data('subtreeWidth') === undefined) - }) - if (unprocessed.length !== 0) { - queue.push(task) - unprocessed.forEach(t => { - queue.push({ parent: t, children: t.cy().edges(`[source="${t.id()}"]`).targets() }) - }) - continue - } + // const pos = { x: 0, y: 0 } + // e.data('oldPos', pos) - task?.parent.data('subtreeWidth', task.children.reduce((p, n) => p + n.data('subtreeWidth'), 0)) - } + // queue.push({ task: children, parent: e }) + // while (queue.length) { + // const task = queue.pop() + // const oldPos = task.parent.data('oldPos') + // let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 + // task.task.forEach(n => { + // const width = n.data('subtreeWidth') - const pos = { x: 0, y: 0 } - e.data('oldPos', pos) + // n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) + // yoffset += width + // queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) + // }) + // } + // e.cy().data('changed', false) + // return pos + // } else { - queue.push({ task: children, parent: e }) - while (queue.length) { - const task = queue.pop() - const oldPos = task.parent.data('oldPos') - let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 - task.task.forEach(n => { - const width = n.data('subtreeWidth') - - n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 }) - yoffset += width - queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n }) - }) - } - e.cy().data('changed', false) - return pos - } else { - - const opos = e.data('oldPos') - if (opos) { - return opos - } - } - }, // map of (node id) => (position obj); or function(node){ return somPos; } - zoom: undefined, // the zoom level to set (prob want fit = false if set) - pan: true, // the pan level to set (prob want fit = false if set) - fit: false, // whether to fit to viewport - padding: 30, // padding on fit - animate: false, // whether to transition the node positions - animationDuration: 500, // duration of animation in ms if enabled - animationEasing: undefined, // easing of animation if enabled - animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts - ready: readyLO, // callback on layoutready - transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts - } + // const opos = e.data('oldPos') + // if (opos) { + // return opos + // } + // } + // }, // map of (node id) => (position obj); or function(node){ return somPos; } + // zoom: undefined, // the zoom level to set (prob want fit = false if set) + // pan: true, // the pan level to set (prob want fit = false if set) + // fit: false, // whether to fit to viewport + // padding: 30, // padding on fit + // animate: false, // whether to transition the node positions + // animationDuration: 500, // duration of animation in ms if enabled + // animationEasing: undefined, // easing of animation if enabled + // animateFilter: function (node, i) { return false; }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts + // ready: readyLO, // callback on layoutready + // transform: function (node, position) { return position; } // transform a given node position. Useful for changing flow direction in discrete layouts + // } useEffect(() => { - document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId); + document + .querySelector("#root") + ?.addEventListener("mouseup", cleardragQuestionContentId); const cy = cyRef.current; - const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null)))) - cy.data('changed', true) + 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(lyopts).run() - cy?.on('add', () => cy.data('changed', true)) - cy?.fit() + 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); + document + .querySelector("#root") + ?.removeEventListener("mouseup", cleardragQuestionContentId); layoutsContainer.current?.remove(); plusesContainer.current?.remove(); crossesContainer.current?.remove(); @@ -491,298 +518,292 @@ if (cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) }; }, []); + // const removeButtons = (id: string) => { + // layoutsContainer.current + // ?.querySelector(`.popper-layout[data-id='${id}']`) + // ?.remove(); + // plusesContainer.current + // ?.querySelector(`.popper-plus[data-id='${id}']`) + // ?.remove(); + // crossesContainer.current + // ?.querySelector(`.popper-cross[data-id='${id}']`) + // ?.remove(); + // gearsContainer.current + // ?.querySelector(`.popper-gear[data-id='${id}']`) + // ?.remove(); + // }; - const removeButtons = (id: string) => { - layoutsContainer.current - ?.querySelector(`.popper-layout[data-id='${id}']`) - ?.remove(); - plusesContainer.current - ?.querySelector(`.popper-plus[data-id='${id}']`) - ?.remove(); - crossesContainer.current - ?.querySelector(`.popper-cross[data-id='${id}']`) - ?.remove(); - gearsContainer.current - ?.querySelector(`.popper-gear[data-id='${id}']`) - ?.remove(); - }; + // const initialPopperIcons = ({ cy }: LayoutEventObject) => { + // const container = + // (document.body.querySelector( + // ".__________cytoscape_container" + // ) as HTMLDivElement) || null; + // if (!container) { + // return; + // } - const initialPopperIcons = (e) => { - const cy = e.cy + // container.style.overflow = "hidden"; - const container = - (document.body.querySelector( - ".__________cytoscape_container" - ) as HTMLDivElement) || null; + // if (!plusesContainer.current) { + // plusesContainer.current = document.createElement("div"); + // plusesContainer.current.setAttribute("id", "popper-pluses"); + // container.append(plusesContainer.current); + // } + // if (!crossesContainer.current) { + // crossesContainer.current = document.createElement("div"); + // crossesContainer.current.setAttribute("id", "popper-crosses"); + // container.append(crossesContainer.current); + // } + // if (!gearsContainer.current) { + // gearsContainer.current = document.createElement("div"); + // gearsContainer.current.setAttribute("id", "popper-gears"); + // container.append(gearsContainer.current); + // } + // if (!layoutsContainer.current) { + // layoutsContainer.current = document.createElement("div"); + // layoutsContainer.current.setAttribute("id", "popper-layouts"); + // container.append(layoutsContainer.current); + // } - if (!container) { - return; - } + // const ext = cy.extent(); + // const nodesInView = cy.nodes().filter((n) => { + // const bb = n.boundingBox(); + // return ( + // bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 + // ); + // }); - container.style.overflow = "hidden"; + // nodesInView.toArray()?.forEach((item) => { + // const node = item as NodeSingularWithPopper; - if (!plusesContainer.current) { - plusesContainer.current = document.createElement("div"); - plusesContainer.current.setAttribute("id", "popper-pluses"); - container.append(plusesContainer.current); - } - if (!crossesContainer.current) { - crossesContainer.current = document.createElement("div"); - crossesContainer.current.setAttribute("id", "popper-crosses"); - container.append(crossesContainer.current); - } - if (!gearsContainer.current) { - gearsContainer.current = document.createElement("div"); - gearsContainer.current.setAttribute("id", "popper-gears"); - container.append(gearsContainer.current); - } - if (!layoutsContainer.current) { - layoutsContainer.current = document.createElement("div"); - layoutsContainer.current.setAttribute("id", "popper-layouts"); - container.append(layoutsContainer.current); - } + // const layoutsPopper = node.popper({ + // popper: { + // placement: "left", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = layoutsContainer.current?.querySelector( + // `.popper-layout[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const ext = cy.extent() - const nodesInView = cy.nodes().filter(n => { - const bb = n.boundingBox() - return bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 - }) + // const layoutElement = document.createElement("div"); + // layoutElement.style.zIndex = "0"; + // layoutElement.classList.add("popper-layout"); + // layoutElement.setAttribute("data-id", item.id()); + // layoutElement.addEventListener("mouseup", () => { + // //Узнаём грани, идущие от этой ноды + // setModalQuestionParentContentId(item.id()); + // setOpenedModalQuestions(true); + // }); + // layoutsContainer.current?.appendChild(layoutElement); - nodesInView - .toArray() - ?.forEach((item) => { - const node = item as NodeSingularWithPopper; + // return layoutElement; + // }, + // }); - const layoutsPopper = node.popper({ - popper: { - placement: "left", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = layoutsContainer.current?.querySelector( - `.popper-layout[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // const plusesPopper = node.popper({ + // popper: { + // placement: "right", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = plusesContainer.current?.querySelector( + // `.popper-plus[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const layoutElement = document.createElement("div"); - layoutElement.style.zIndex = "0" - layoutElement.classList.add("popper-layout"); - layoutElement.setAttribute("data-id", item.id()); - layoutElement.addEventListener("mouseup", () => { - //Узнаём грани, идущие от этой ноды - setModalQuestionParentContentId(item.id()) - setOpenedModalQuestions(true) - }); - layoutsContainer.current?.appendChild(layoutElement); + // const plusElement = document.createElement("div"); + // plusElement.classList.add("popper-plus"); + // plusElement.setAttribute("data-id", item.id()); + // plusElement.style.zIndex = "1"; + // plusElement.addEventListener("mouseup", () => { + // setStartCreate(node.id()); + // }); - return layoutElement; - }, - }); + // plusesContainer.current?.appendChild(plusElement); - const plusesPopper = node.popper({ - popper: { - placement: "right", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = plusesContainer.current?.querySelector( - `.popper-plus[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // return plusElement; + // }, + // }); - const plusElement = document.createElement("div"); - plusElement.classList.add("popper-plus"); - plusElement.setAttribute("data-id", item.id()); - plusElement.style.zIndex = "1" - plusElement.addEventListener("mouseup", () => { - setStartCreate(node.id()); - }); + // const crossesPopper = node.popper({ + // popper: { + // placement: "top-end", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); + // const itemElement = crossesContainer.current?.querySelector( + // `.popper-cross[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - plusesContainer.current?.appendChild(plusElement); + // const crossElement = document.createElement("div"); + // crossElement.classList.add("popper-cross"); + // crossElement.setAttribute("data-id", item.id()); + // crossElement.style.zIndex = "2"; + // crossesContainer.current?.appendChild(crossElement); + // crossElement.addEventListener("mouseup", () => { + // setStartRemove(node.id()); + // }); - return plusElement; - }, - }); + // return crossElement; + // }, + // }); + // let gearsPopper: Popper | null = null; + // if (node.data().root !== true) { + // gearsPopper = node.popper({ + // popper: { + // placement: "left", + // modifiers: [{ name: "flip", options: { boundary: node } }], + // }, + // content: ([item]) => { + // const itemId = item.id(); - const crossesPopper = node.popper({ - popper: { - placement: "top-end", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); - const itemElement = crossesContainer.current?.querySelector( - `.popper-cross[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // const itemElement = gearsContainer.current?.querySelector( + // `.popper-gear[data-id='${itemId}']` + // ); + // if (itemElement) { + // return itemElement; + // } - const crossElement = document.createElement("div"); - crossElement.classList.add("popper-cross"); - crossElement.setAttribute("data-id", item.id()); - crossElement.style.zIndex = "2" - crossesContainer.current?.appendChild(crossElement); - crossElement.addEventListener("mouseup", () => { - setStartRemove(node.id()) + // const gearElement = document.createElement("div"); + // gearElement.classList.add("popper-gear"); + // gearElement.setAttribute("data-id", item.id()); + // gearElement.style.zIndex = "1"; + // gearsContainer.current?.appendChild(gearElement); + // gearElement.addEventListener("mouseup", (e) => { + // console.log("up"); + // updateOpenedModalSettingsId(item.id()); + // }); + // return gearElement; + // }, + // }); + // } + // const update = async () => { + // await plusesPopper.update(); + // await crossesPopper.update(); + // await gearsPopper?.update(); + // await layoutsPopper.update(); + // }; - } - ); + // const onZoom = (event: AbstractEventObject) => { + // const zoom = event.cy.zoom(); - return crossElement; - }, - }); -let gearsPopper = null - if (node.data().root !== true) { - gearsPopper = node.popper({ - popper: { - placement: "left", - modifiers: [{ name: "flip", options: { boundary: node } }], - }, - content: ([item]) => { - const itemId = item.id(); + // //update(); - const itemElement = gearsContainer.current?.querySelector( - `.popper-gear[data-id='${itemId}']` - ); - if (itemElement) { - return itemElement; - } + // crossesPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, + // ], + // }); - const gearElement = document.createElement("div"); - gearElement.classList.add("popper-gear"); - gearElement.setAttribute("data-id", item.id()); - gearElement.style.zIndex = "1" - gearsContainer.current?.appendChild(gearElement); - gearElement.addEventListener("mouseup", (e) => { - console.log("up") - updateOpenedModalSettingsId(item.id()) - }); + // layoutsPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, -130 * zoom] } }, + // ], + // }); + // plusesPopper.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, 0 * zoom] } }, + // ], + // }); + // gearsPopper?.setOptions({ + // modifiers: [ + // { name: "flip", options: { boundary: node } }, + // { name: "offset", options: { offset: [0, 0] } }, + // ], + // }); - return gearElement; - }, - }); - } - const update = async () => { - await plusesPopper.update(); - await crossesPopper.update(); - await gearsPopper?.update(); - await layoutsPopper.update(); - }; + // layoutsContainer.current + // ?.querySelectorAll("#popper-layouts > .popper-layout") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${130 * zoom}px`; + // element.style.height = `${130 * zoom}px`; + // }); - const onZoom = (event: AbstractEventObject) => { - const zoom = event.cy.zoom(); + // plusesContainer.current + // ?.querySelectorAll("#popper-pluses > .popper-plus") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${40 * zoom}px`; + // element.style.height = `${40 * zoom}px`; + // element.style.fontSize = `${40 * zoom}px`; + // element.style.borderRadius = `${6 * zoom}px`; + // }); - //update(); + // crossesContainer.current + // ?.querySelectorAll("#popper-crosses > .popper-cross") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${24 * zoom}px`; + // element.style.height = `${24 * zoom}px`; + // element.style.fontSize = `${24 * zoom}px`; + // element.style.borderRadius = `${6 * zoom}px`; + // }); - crossesPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, - ], - }); + // gearsContainer?.current + // ?.querySelectorAll("#popper-gears > .popper-gear") + // .forEach((item) => { + // const element = item as HTMLDivElement; + // element.style.width = `${60 * zoom}px`; + // element.style.height = `${40 * zoom}px`; + // }); + // }; - layoutsPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, -130 * zoom] } }, - ], - }); - plusesPopper.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, 0 * zoom] } }, - ], - }); - gearsPopper?.setOptions({ - modifiers: [ - { name: "flip", options: { boundary: node } }, - { name: "offset", options: { offset: [0, 0] } }, - ], - }); + // //node?.on("position", update); + // let pressed = false; + // let hide = false; + // cy?.on("mousedown", () => { + // pressed = true; + // }); + // cy?.on("mouseup", () => { + // pressed = false; + // hide = false; - layoutsContainer.current - ?.querySelectorAll("#popper-layouts > .popper-layout") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${130 * zoom}px`; - element.style.height = `${130 * zoom}px`; - }); + // const gc = gearsContainer.current; + // if (gc) gc.style.display = "block"; + // const pc = plusesContainer.current; + // const xc = crossesContainer.current; + // const lc = layoutsContainer.current; + // if (pc) pc.style.display = "block"; + // if (xc) xc.style.display = "block"; + // if (lc) lc.style.display = "block"; + // update(); + // }); - plusesContainer.current - ?.querySelectorAll("#popper-pluses > .popper-plus") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${40 * zoom}px`; - element.style.height = `${40 * zoom}px`; - element.style.fontSize = `${40 * zoom}px`; - element.style.borderRadius = `${6 * zoom}px`; - }); + // cy?.on("mousemove", () => { + // if (pressed && !hide) { + // hide = true; + // const gc = gearsContainer.current; + // if (gc) gc.style.display = "none"; + // const pc = plusesContainer.current; + // const xc = crossesContainer.current; + // const lc = layoutsContainer.current; + // if (pc) pc.style.display = "none"; + // if (xc) xc.style.display = "none"; + // if (lc) lc.style.display = "block"; + // } + // }); - crossesContainer.current - ?.querySelectorAll("#popper-crosses > .popper-cross") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${24 * zoom}px`; - element.style.height = `${24 * zoom}px`; - element.style.fontSize = `${24 * zoom}px`; - element.style.borderRadius = `${6 * zoom}px`; - }); - - gearsContainer?.current - ?.querySelectorAll("#popper-gears > .popper-gear") - .forEach((item) => { - const element = item as HTMLDivElement; - element.style.width = `${60 * zoom}px`; - element.style.height = `${40 * zoom}px`; - }); - }; - - //node?.on("position", update); - let pressed = false - let hide = false - cy?.on('mousedown', () => { pressed = true }) - cy?.on('mouseup', () => { - pressed = false - hide = false - - - const gc = gearsContainer.current - if (gc) gc.style.display = 'block' - const pc = plusesContainer.current - const xc = crossesContainer.current - const lc = layoutsContainer.current - if (pc) pc.style.display = 'block' - if (xc) xc.style.display = 'block' - if (lc) lc.style.display = 'block' - update() - }) - - cy?.on('mousemove', () => { - if (pressed && !hide) { - hide = true - const gc = gearsContainer.current - if (gc) gc.style.display = 'none' - const pc = plusesContainer.current - const xc = crossesContainer.current - const lc = layoutsContainer.current - if (pc) pc.style.display = 'none' - if (xc) xc.style.display = 'none' - if (lc) lc.style.display = 'block' - } - }); - - cy?.on("zoom render", onZoom); - }); - }; + // cy?.on("zoom render", onZoom); + // }); + // }; return ( <> @@ -796,9 +817,7 @@ let gearsPopper = null }} variant="text" onClick={() => { - - cyRef.current?.fit() - + cyRef.current?.fit(); }} > Выровнять @@ -809,7 +828,7 @@ let gearsPopper = null // elements={createGraphElements(tree, quiz)} style={{ height: "480px", background: "#F2F3F7" }} stylesheet={stylesheet} - layout={(lyopts)} + layout={layoutOptions} cy={(cy) => { cyRef.current = cy; }} @@ -827,20 +846,22 @@ let gearsPopper = null }}>elements */} ); -}; +} function Clear() { const quiz = useCurrentQuiz(); - updateRootContentId(quiz.id, "") - clearRuleForAll() - return <> + if (quiz) { + updateRootContentId(quiz.id, ""); + } + clearRuleForAll(); + return <>; } export default withErrorBoundary(CsComponent, { fallback: , onError: (error, info) => { - enqueueSnackbar("Дерево порвалось") - console.log(info) - console.log(error) + enqueueSnackbar("Дерево порвалось"); + console.log(info); + console.log(error); }, }); diff --git a/src/pages/Questions/BranchingMap/FirstNodeField.tsx b/src/pages/Questions/BranchingMap/FirstNodeField.tsx index 5bae72c1..f2e68a3a 100644 --- a/src/pages/Questions/BranchingMap/FirstNodeField.tsx +++ b/src/pages/Questions/BranchingMap/FirstNodeField.tsx @@ -1,94 +1,98 @@ -import { Box } from "@mui/material" +import { Box } from "@mui/material"; import { useEffect, useRef, useLayoutEffect } from "react"; -import { deleteQuestion, clearRuleForAll, updateQuestion } from "@root/questions/actions" -import { updateOpenedModalSettingsId } from "@root/uiTools/actions" -import { updateRootContentId } from "@root/quizes/actions" -import { useCurrentQuiz } from "@root/quizes/hooks" -import { useQuestionsStore } from "@root/questions/store" +import { + deleteQuestion, + clearRuleForAll, + updateQuestion, +} from "@root/questions/actions"; +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; +import { updateRootContentId } from "@root/quizes/actions"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { useQuestionsStore } from "@root/questions/store"; import { enqueueSnackbar } from "notistack"; import { useUiTools } from "@root/uiTools/store"; interface Props { - setOpenedModalQuestions: (open: boolean) => void; - modalQuestionTargetContentId: string; + setOpenedModalQuestions: (open: boolean) => void; + modalQuestionTargetContentId: string; } -export const FirstNodeField = ({ setOpenedModalQuestions, modalQuestionTargetContentId }: Props) => { - const quiz = useCurrentQuiz(); - - - useLayoutEffect(() => { - updateOpenedModalSettingsId() - console.log("first render firstComponent") - updateRootContentId(quiz.id, "") - clearRuleForAll() - }, []) - - - const { questions } = useQuestionsStore() - const { dragQuestionContentId } = useUiTools() - const Container = useRef(null); - - const modalOpen = () => setOpenedModalQuestions(true) - - const newRootNode = () => { - if (quiz) { - if (dragQuestionContentId) { - updateRootContentId(quiz?.id, dragQuestionContentId) - updateQuestion(dragQuestionContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) - - } - } else { - enqueueSnackbar("Нет информации о взятом опроснике") - } +export const FirstNodeField = ({ + setOpenedModalQuestions, + modalQuestionTargetContentId, +}: Props) => { + const quiz = useCurrentQuiz(); + useLayoutEffect(() => { + updateOpenedModalSettingsId(); + console.log("first render firstComponent"); + if (quiz) { + updateRootContentId(quiz.id, ""); } + clearRuleForAll(); + }, []); - useEffect(() => { - Container.current?.addEventListener("mouseup", newRootNode) - Container.current?.addEventListener("click", modalOpen) - return () => { - Container.current?.removeEventListener("mouseup", newRootNode) - Container.current?.removeEventListener("click", modalOpen) - } - }, [dragQuestionContentId]) + const { questions } = useQuestionsStore(); + const { dragQuestionContentId } = useUiTools(); + const Container = useRef(null); - useEffect(() => { - if (quiz) { + const modalOpen = () => setOpenedModalQuestions(true); - if (modalQuestionTargetContentId) { - updateRootContentId(quiz?.id, modalQuestionTargetContentId) - updateQuestion(modalQuestionTargetContentId, (question) => question.content.rule.parentId = "root") - //если были результаты - удалить - questions.forEach((q) => { - if (q.type === 'result') deleteQuestion(q.id) - }) - } - } else { - enqueueSnackbar("Нет информации о взятом опроснике") - } + const newRootNode = () => { + if (quiz && dragQuestionContentId) { + updateRootContentId(quiz?.id, dragQuestionContentId); + updateQuestion( + dragQuestionContentId, + (question) => (question.content.rule.parentId = "root") + ); + //если были результаты - удалить + questions.forEach((q) => { + if (q.type === "result") deleteQuestion(q.id); + }); + } else { + enqueueSnackbar("Нет информации о взятом опроснике"); + } + }; - }, [modalQuestionTargetContentId]) + useEffect(() => { + Container.current?.addEventListener("mouseup", newRootNode); + Container.current?.addEventListener("click", modalOpen); + return () => { + Container.current?.removeEventListener("mouseup", newRootNode); + Container.current?.removeEventListener("click", modalOpen); + }; + }, [dragQuestionContentId]); + useEffect(() => { + if (quiz && modalQuestionTargetContentId) { + updateRootContentId(quiz?.id, modalQuestionTargetContentId); + updateQuestion( + modalQuestionTargetContentId, + (question) => (question.content.rule.parentId = "root") + ); + //если были результаты - удалить + questions.forEach((q) => { + if (q.type === "result") deleteQuestion(q.id); + }); + } else { + enqueueSnackbar("Нет информации о взятом опроснике"); + } + }, [modalQuestionTargetContentId]); - return ( - - + - - ) -} \ No newline at end of file + return ( + + + + + ); +}; diff --git a/src/pages/Questions/BranchingMap/hooks/usePopper.ts b/src/pages/Questions/BranchingMap/hooks/usePopper.ts new file mode 100644 index 00000000..957beece --- /dev/null +++ b/src/pages/Questions/BranchingMap/hooks/usePopper.ts @@ -0,0 +1,482 @@ +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; + +import type { MutableRefObject } from "react"; +import type { + PresetLayoutOptions, + LayoutEventObject, + NodeSingular, + NodePositionMap, + NodePositionFunction, + AbstractEventObject, +} from "cytoscape"; + +type usePopperArgs = { + layoutsContainer: MutableRefObject; + plusesContainer: MutableRefObject; + crossesContainer: MutableRefObject; + gearsContainer: MutableRefObject; + setModalQuestionParentContentId: (id: string) => void; + setOpenedModalQuestions: (open: boolean) => void; + setStartCreate: (id: string) => void; + setStartRemove: (id: string) => void; +}; + +type PopperItem = { + id: () => string; +}; + +type Modifier = { + name: string; + options: unknown; +}; + +type PopperConfig = { + popper: { + placement: string; + modifiers?: Modifier[]; + }; + content: (items: PopperItem[]) => void; +}; + +type Popper = { + update: () => Promise; + setOptions: (modifiers: { modifiers?: Modifier[] }) => void; +}; + +type NodeSingularWithPopper = NodeSingular & { + popper: (config: PopperConfig) => Popper; +}; + +export const usePopper = ({ + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, + setModalQuestionParentContentId, + setOpenedModalQuestions, + setStartCreate, + setStartRemove, +}: usePopperArgs) => { + const removeButtons = (id: string) => { + layoutsContainer.current + ?.querySelector(`.popper-layout[data-id='${id}']`) + ?.remove(); + plusesContainer.current + ?.querySelector(`.popper-plus[data-id='${id}']`) + ?.remove(); + crossesContainer.current + ?.querySelector(`.popper-cross[data-id='${id}']`) + ?.remove(); + gearsContainer.current + ?.querySelector(`.popper-gear[data-id='${id}']`) + ?.remove(); + }; + + const initialPopperIcons = ({ cy }: LayoutEventObject) => { + const container = + (document.body.querySelector( + ".__________cytoscape_container" + ) as HTMLDivElement) || null; + + if (!container) { + return; + } + + container.style.overflow = "hidden"; + + if (!plusesContainer.current) { + plusesContainer.current = document.createElement("div"); + plusesContainer.current.setAttribute("id", "popper-pluses"); + container.append(plusesContainer.current); + } + if (!crossesContainer.current) { + crossesContainer.current = document.createElement("div"); + crossesContainer.current.setAttribute("id", "popper-crosses"); + container.append(crossesContainer.current); + } + if (!gearsContainer.current) { + gearsContainer.current = document.createElement("div"); + gearsContainer.current.setAttribute("id", "popper-gears"); + container.append(gearsContainer.current); + } + if (!layoutsContainer.current) { + layoutsContainer.current = document.createElement("div"); + layoutsContainer.current.setAttribute("id", "popper-layouts"); + container.append(layoutsContainer.current); + } + + const ext = cy.extent(); + const nodesInView = cy.nodes().filter((n) => { + const bb = n.boundingBox(); + return ( + bb.x2 > ext.x1 && bb.x1 < ext.x2 && bb.y2 > ext.y1 && bb.y1 < ext.y2 + ); + }); + + nodesInView.toArray()?.forEach((item) => { + const node = item as NodeSingularWithPopper; + + const layoutsPopper = node.popper({ + popper: { + placement: "left", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = layoutsContainer.current?.querySelector( + `.popper-layout[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const layoutElement = document.createElement("div"); + layoutElement.style.zIndex = "0"; + layoutElement.classList.add("popper-layout"); + layoutElement.setAttribute("data-id", item.id()); + layoutElement.addEventListener("mouseup", () => { + //Узнаём грани, идущие от этой ноды + setModalQuestionParentContentId(item.id()); + setOpenedModalQuestions(true); + }); + layoutsContainer.current?.appendChild(layoutElement); + + return layoutElement; + }, + }); + + const plusesPopper = node.popper({ + popper: { + placement: "right", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = plusesContainer.current?.querySelector( + `.popper-plus[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const plusElement = document.createElement("div"); + plusElement.classList.add("popper-plus"); + plusElement.setAttribute("data-id", item.id()); + plusElement.style.zIndex = "1"; + plusElement.addEventListener("mouseup", () => { + setStartCreate(node.id()); + }); + + plusesContainer.current?.appendChild(plusElement); + + return plusElement; + }, + }); + + const crossesPopper = node.popper({ + popper: { + placement: "top-end", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + const itemElement = crossesContainer.current?.querySelector( + `.popper-cross[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const crossElement = document.createElement("div"); + crossElement.classList.add("popper-cross"); + crossElement.setAttribute("data-id", item.id()); + crossElement.style.zIndex = "2"; + crossesContainer.current?.appendChild(crossElement); + crossElement.addEventListener("mouseup", () => { + setStartRemove(node.id()); + }); + + return crossElement; + }, + }); + let gearsPopper: Popper | null = null; + if (node.data().root !== true) { + gearsPopper = node.popper({ + popper: { + placement: "left", + modifiers: [{ name: "flip", options: { boundary: node } }], + }, + content: ([item]) => { + const itemId = item.id(); + + const itemElement = gearsContainer.current?.querySelector( + `.popper-gear[data-id='${itemId}']` + ); + if (itemElement) { + return itemElement; + } + + const gearElement = document.createElement("div"); + gearElement.classList.add("popper-gear"); + gearElement.setAttribute("data-id", item.id()); + gearElement.style.zIndex = "1"; + gearsContainer.current?.appendChild(gearElement); + gearElement.addEventListener("mouseup", (e) => { + console.log("up"); + updateOpenedModalSettingsId(item.id()); + }); + + return gearElement; + }, + }); + } + const update = async () => { + await plusesPopper.update(); + await crossesPopper.update(); + await gearsPopper?.update(); + await layoutsPopper.update(); + }; + + const onZoom = (event: AbstractEventObject) => { + const zoom = event.cy.zoom(); + + //update(); + + crossesPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [-5 * zoom, -30 * zoom] } }, + ], + }); + + layoutsPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, -130 * zoom] } }, + ], + }); + plusesPopper.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, 0 * zoom] } }, + ], + }); + gearsPopper?.setOptions({ + modifiers: [ + { name: "flip", options: { boundary: node } }, + { name: "offset", options: { offset: [0, 0] } }, + ], + }); + + layoutsContainer.current + ?.querySelectorAll("#popper-layouts > .popper-layout") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${130 * zoom}px`; + element.style.height = `${130 * zoom}px`; + }); + + plusesContainer.current + ?.querySelectorAll("#popper-pluses > .popper-plus") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${40 * zoom}px`; + element.style.height = `${40 * zoom}px`; + element.style.fontSize = `${40 * zoom}px`; + element.style.borderRadius = `${6 * zoom}px`; + }); + + crossesContainer.current + ?.querySelectorAll("#popper-crosses > .popper-cross") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${24 * zoom}px`; + element.style.height = `${24 * zoom}px`; + element.style.fontSize = `${24 * zoom}px`; + element.style.borderRadius = `${6 * zoom}px`; + }); + + gearsContainer?.current + ?.querySelectorAll("#popper-gears > .popper-gear") + .forEach((item) => { + const element = item as HTMLDivElement; + element.style.width = `${60 * zoom}px`; + element.style.height = `${40 * zoom}px`; + }); + }; + + //node?.on("position", update); + let pressed = false; + let hide = false; + cy?.on("mousedown", () => { + pressed = true; + }); + cy?.on("mouseup", () => { + pressed = false; + hide = false; + + const gc = gearsContainer.current; + if (gc) gc.style.display = "block"; + const pc = plusesContainer.current; + const xc = crossesContainer.current; + const lc = layoutsContainer.current; + if (pc) pc.style.display = "block"; + if (xc) xc.style.display = "block"; + if (lc) lc.style.display = "block"; + update(); + }); + + cy?.on("mousemove", () => { + if (pressed && !hide) { + hide = true; + const gc = gearsContainer.current; + if (gc) gc.style.display = "none"; + const pc = plusesContainer.current; + const xc = crossesContainer.current; + const lc = layoutsContainer.current; + if (pc) pc.style.display = "none"; + if (xc) xc.style.display = "none"; + if (lc) lc.style.display = "block"; + } + }); + + cy?.on("zoom render", onZoom); + }); + }; + + const readyLO = (event: LayoutEventObject) => { + if (event.cy.data("firstNode") === "nonroot") { + event.cy.data("firstNode", "root"); + event.cy + .nodes() + .sort((a, b) => (a.data("root") ? 1 : -1)) + .layout(layoutOptions) + .run(); + } else { + event.cy.data("changed", false); + event.cy.removeData("firstNode"); + } + + //удаляем иконки + event.cy.nodes().forEach((ele: any) => { + const data = ele.data(); + data.id && removeButtons(data.id); + }); + + initialPopperIcons(event); + }; + + const layoutOptions: PresetLayoutOptions = { + name: "preset", + + positions: (node) => { + if (!node.cy().data("changed")) { + return node.data("oldPos"); + } + const id = node.id(); + const incomming = node.cy().edges(`[target="${id}"]`); + const layer = 0; + node.removeData("lastChild"); + + if (incomming.length === 0) { + if (node.cy().data("firstNode") === undefined) + node.cy().data("firstNode", "root"); + node.data("root", true); + const children = node.cy().edges(`[source="${id}"]`).targets(); + node.data("layer", layer); + node.data("children", children.length); + const queue = []; + children.forEach((n) => { + queue.push({ task: n, layer: layer + 1 }); + }); + while (queue.length) { + const task = queue.pop(); + task.task.data("layer", task.layer); + task.task.removeData("subtreeWidth"); + const children = e + .cy() + .edges(`[source="${task.task.id()}"]`) + .targets(); + task.task.data("children", children.length); + if (children.length !== 0) { + children.forEach((n) => + queue.push({ task: n, layer: task.layer + 1 }) + ); + } + } + queue.push({ parent: e, children: children }); + while (queue.length) { + const task = queue.pop(); + if (task.children.length === 0) { + task.parent.data("subtreeWidth", task.parent.height() + 50); + continue; + } + const unprocessed = task?.children.filter((e) => { + return node.data("subtreeWidth") === undefined; + }); + if (unprocessed.length !== 0) { + queue.push(task); + unprocessed.forEach((t) => { + queue.push({ + parent: t, + children: t.cy().edges(`[source="${t.id()}"]`).targets(), + }); + }); + continue; + } + + task?.parent.data( + "subtreeWidth", + task.children.reduce((p, n) => p + n.data("subtreeWidth"), 0) + ); + } + + const pos = { x: 0, y: 0 }; + node.data("oldPos", pos); + + queue.push({ task: children, parent: e }); + while (queue.length) { + const task = queue.pop(); + const oldPos = task.parent.data("oldPos"); + let yoffset = oldPos.y - task.parent.data("subtreeWidth") / 2; + task.task.forEach((n) => { + const width = n.data("subtreeWidth"); + + n.data("oldPos", { + x: 250 * n.data("layer"), + y: yoffset + width / 2, + }); + yoffset += width; + queue.push({ + task: n.cy().edges(`[source="${n.id()}"]`).targets(), + parent: n, + }); + }); + } + node.cy().data("changed", false); + return pos; + } else { + const opos = node.data("oldPos"); + if (opos) { + return opos; + } + } + }, // map of (node id) => (position obj); or function(node){ return somPos; } + zoom: undefined, // the zoom level to set (prob want fit = false if set) + pan: 1, // the pan level to set (prob want fit = false if set) + fit: false, // whether to fit to viewport + padding: 30, // padding on fit + animate: false, // whether to transition the node positions + animationDuration: 500, // duration of animation in ms if enabled + animationEasing: undefined, // easing of animation if enabled + animateFilter: function (node, i) { + return false; + }, // a function that determines whether the node should be animated. All nodes animated by default on animate enabled. Non-animated nodes are positioned immediately when the layout starts + ready: readyLO, // callback on layoutready + transform: function (node, position) { + return position; + }, // transform a given node position. Useful for changing flow direction in discrete layouts + }; + + return { layoutOptions }; +}; diff --git a/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts new file mode 100644 index 00000000..05586b95 --- /dev/null +++ b/src/pages/Questions/BranchingMap/hooks/useRemoveNode.ts @@ -0,0 +1,211 @@ +import { + deleteQuestion, + updateQuestion, + getQuestionByContentId, + clearRuleForAll, + createFrontResult, +} from "@root/questions/actions"; +import { useQuestionsStore } from "@root/questions/store"; +import { useCurrentQuiz } from "@root/quizes/hooks"; +import { updateRootContentId } from "@root/quizes/actions"; + +import type { MutableRefObject } from "react"; +import type { + Core, + CollectionReturnValue, + PresetLayoutOptions, +} from "cytoscape"; +import type { + QuestionBranchingRule, + QuestionBranchingRuleMain, +} from "../../../../model/questionTypes/shared"; + +type UseRemoveNodeArgs = { + cyRef: MutableRefObject; + layoutOptions: PresetLayoutOptions; + layoutsContainer: MutableRefObject; + plusesContainer: MutableRefObject; + crossesContainer: MutableRefObject; + gearsContainer: MutableRefObject; +}; + +export const useRemoveNode = ({ + cyRef, + layoutOptions, + layoutsContainer, + plusesContainer, + crossesContainer, + gearsContainer, +}: UseRemoveNodeArgs) => { + const { questions: trashQuestions } = useQuestionsStore(); + const quiz = useCurrentQuiz(); + + const removeButtons = (id: string) => { + layoutsContainer.current + ?.querySelector(`.popper-layout[data-id='${id}']`) + ?.remove(); + plusesContainer.current + ?.querySelector(`.popper-plus[data-id='${id}']`) + ?.remove(); + crossesContainer.current + ?.querySelector(`.popper-cross[data-id='${id}']`) + ?.remove(); + gearsContainer.current + ?.querySelector(`.popper-gear[data-id='${id}']`) + ?.remove(); + }; + + const clearDataAfterRemoveNode = ({ + targetQuestionContentId, + parentQuestionContentId, + }: { + targetQuestionContentId: string; + parentQuestionContentId: string; + }) => { + updateQuestion(targetQuestionContentId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.children = []; + question.content.rule.main = []; + question.content.rule.default = ""; + }); + + //чистим rule родителя + const parentQuestion = getQuestionByContentId(parentQuestionContentId); + + if (!parentQuestion?.type) { + return; + } + + const newChildren = [...parentQuestion.content.rule.children]; + newChildren.splice( + parentQuestion.content.rule.children.indexOf(targetQuestionContentId), + 1 + ); + + const newRule: QuestionBranchingRule = { + children: newChildren, + default: + parentQuestion.content.rule.default === targetQuestionContentId + ? "" + : parentQuestion.content.rule.default, + //удаляем условия перехода от родителя к этому вопросу, + main: parentQuestion.content.rule.main.filter( + (data: QuestionBranchingRuleMain) => + data.next !== targetQuestionContentId + ), + parentId: parentQuestion.content.rule.parentId, + }; + + updateQuestion(parentQuestionContentId, (PQ) => { + PQ.content.rule = newRule; + }); + }; + + const removeNode = (targetNodeContentId: string) => { + const deleteNodes: string[] = []; + const deleteEdges: any = []; + const cy = cyRef?.current; + + const findChildrenToDelete = (node: CollectionReturnValue) => { + //Узнаём грани, идущие от этой ноды + cy?.$('edge[source = "' + node.id() + '"]') + ?.toArray() + .forEach((edge) => { + const edgeData = edge.data(); + + //записываем id грани для дальнейшего удаления + deleteEdges.push(edge); + //ищем ноду на конце грани, записываем её ID для дальнейшего удаления + const targetNode = cy?.$("#" + edgeData.target); + deleteNodes.push(targetNode.data().id); + //вызываем функцию для анализа потомков уже у этой ноды + findChildrenToDelete(targetNode); + }); + }; + + const elementToDelete = cy?.getElementById(targetNodeContentId); + + if (elementToDelete) { + findChildrenToDelete(elementToDelete); + } + + const targetQuestion = getQuestionByContentId(targetNodeContentId); + + if ( + targetQuestion?.type && + targetQuestion.content.rule.parentId === "root" && + quiz + ) { + updateRootContentId(quiz?.id, ""); + updateQuestion(targetNodeContentId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.children = []; + question.content.rule.default = ""; + }); + trashQuestions.forEach((q) => { + if (q.type === "result") { + deleteQuestion(q.id); + } + }); + clearRuleForAll(); + } else { + const parentQuestionContentId = cy + ?.$('edge[target = "' + targetNodeContentId + '"]') + ?.toArray()?.[0] + ?.data()?.source; + if (targetNodeContentId && parentQuestionContentId) { + if ( + quiz && + cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0 + ) { + createFrontResult(quiz.backendId, parentQuestionContentId); + } + clearDataAfterRemoveNode({ + targetQuestionContentId: targetNodeContentId, + parentQuestionContentId, + }); + cy?.remove(cy?.$("#" + targetNodeContentId)) + .layout(layoutOptions) + .run(); + } + } + + //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке + + deleteNodes.forEach((nodeId) => { + //Ноды + cy?.remove(cy?.$("#" + nodeId)); + removeButtons(nodeId); + updateQuestion(nodeId, (question) => { + question.content.rule.parentId = ""; + question.content.rule.main = []; + question.content.rule.default = ""; + question.content.rule.children = []; + }); + }); + + deleteEdges.forEach((edge: any) => { + //Грани + cy?.remove(edge); + }); + + removeButtons(targetNodeContentId); + cy?.data("changed", true); + cy?.layout(layoutOptions).run(); + + //удаляем result всех потомков + trashQuestions.forEach((qr) => { + if ( + qr.type === "result" && + (deleteNodes.includes(qr.content.rule.parentId || "") || + (targetQuestion?.type && + qr.content.rule.parentId === targetQuestion.content.id)) + ) { + deleteQuestion(qr.id); + } + }); + }; + + return { removeNode }; +}; diff --git a/src/pages/Questions/BranchingMap/index.tsx b/src/pages/Questions/BranchingMap/index.tsx index 6517a19e..39b22789 100644 --- a/src/pages/Questions/BranchingMap/index.tsx +++ b/src/pages/Questions/BranchingMap/index.tsx @@ -14,8 +14,6 @@ export const BranchingMap = () => { const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState("") const [openedModalQuestions, setOpenedModalQuestions] = useState(false) - - return ( void; setOpenedModalQuestions: (open: boolean) => void; - setModalQuestionParentContentId: (open: string) => void; } export const BranchingQuestionsModal = ({ openedModalQuestions, setOpenedModalQuestions, setModalQuestionTargetContentId, - setModalQuestionParentContentId, }: Props) => { const trashQuestions = useQuestionsStore().questions; const questions = trashQuestions.filter( diff --git a/src/stores/uiTools/store.ts b/src/stores/uiTools/store.ts index 2a102999..98ce7918 100644 --- a/src/stores/uiTools/store.ts +++ b/src/stores/uiTools/store.ts @@ -8,14 +8,16 @@ export type UiTools = { openBranchingPanel: boolean; desireToOpenABranchingModal: string | null; editSomeQuestion: string | null; + lastDeletionNodeTime: number | null; }; const initialState: UiTools = { - openedModalSettingsId: null as null, + openedModalSettingsId: null, dragQuestionContentId: null, openBranchingPanel: false, - desireToOpenABranchingModal: null as null, - editSomeQuestion: null as null, + desireToOpenABranchingModal: null, + editSomeQuestion: null, + lastDeletionNodeTime: null }; export const useUiTools = create()(