Merge branch 'cscomponent-refactor' into dev
This commit is contained in:
commit
0c7ed71d3d
@ -1,117 +1,54 @@
|
||||
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 { 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 { useUiTools } from "@root/uiTools/store";
|
||||
import {
|
||||
deleteQuestion,
|
||||
updateQuestion,
|
||||
getQuestionByContentId,
|
||||
clearRuleForAll,
|
||||
createResult,
|
||||
} from "@root/questions/actions";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import {
|
||||
cleardragQuestionContentId,
|
||||
setModalQuestionParentContentId,
|
||||
setModalQuestionTargetContentId,
|
||||
updateModalInfoWhyCantCreate,
|
||||
updateOpenedModalSettingsId,
|
||||
} from "@root/uiTools/actions";
|
||||
import { cleardragQuestionContentId } from "@root/uiTools/actions";
|
||||
import { updateDeleteId } from "@root/uiTools/actions";
|
||||
|
||||
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
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";
|
||||
import { nameCutter } from "./nameCutter";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||||
import CytoscapeComponent from "react-cytoscapejs";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||||
import CsNodeButtons from "./CsNodeButtons";
|
||||
import { addNode, layoutOptions, storeToNodes } from "./helper";
|
||||
import { useRemoveNode } from "./hooks/useRemoveNode";
|
||||
import "./style/styles.css";
|
||||
import { stylesheet } from "./style/stylesheet";
|
||||
|
||||
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,
|
||||
function CsComponent() {
|
||||
const desireToOpenABranchingModal = useUiTools(
|
||||
(state) => state.desireToOpenABranchingModal,
|
||||
);
|
||||
const [startCreate, setStartCreate] = useState("");
|
||||
const [startRemove, setStartRemove] = useState("");
|
||||
|
||||
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 cyRef = useRef<Core | null>(null);
|
||||
const layoutsContainer = useRef<HTMLDivElement | null>(null);
|
||||
const plusesContainer = useRef<HTMLDivElement | null>(null);
|
||||
const crossesContainer = useRef<HTMLDivElement | null>(null);
|
||||
const gearsContainer = useRef<HTMLDivElement | null>(null);
|
||||
const { removeNode } = useRemoveNode({ cyRef });
|
||||
|
||||
const { layoutOptions } = usePopper({
|
||||
layoutsContainer,
|
||||
plusesContainer,
|
||||
crossesContainer,
|
||||
gearsContainer,
|
||||
setModalQuestionParentContentId,
|
||||
setOpenedModalQuestions,
|
||||
setStartCreate,
|
||||
setStartRemove,
|
||||
});
|
||||
const { removeNode } = useRemoveNode({
|
||||
cyRef,
|
||||
layoutOptions,
|
||||
layoutsContainer,
|
||||
plusesContainer,
|
||||
crossesContainer,
|
||||
gearsContainer,
|
||||
});
|
||||
const csElements = useMemo(() => {
|
||||
const questions = trashQuestions.filter(
|
||||
(question): question is AnyTypedQuizQuestion =>
|
||||
question.type !== null && question.type !== "result",
|
||||
);
|
||||
|
||||
function fitGraphToRootNode() {
|
||||
const cy = cyRef.current;
|
||||
if (!cy) return;
|
||||
|
||||
const rootNode = cy.nodes().filter((n) => n.data("root"))[0];
|
||||
if (!rootNode) throw new Error("Root node not found");
|
||||
|
||||
const height = cy.height();
|
||||
const position = rootNode.position();
|
||||
const shift = rootNode.width() / 2;
|
||||
|
||||
cy.pan({
|
||||
x: position.x + shift,
|
||||
y: position.y + height / 2,
|
||||
});
|
||||
}
|
||||
return storeToNodes(questions);
|
||||
}, [trashQuestions]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const cy = cyRef?.current;
|
||||
@ -125,19 +62,14 @@ function CsComponent({
|
||||
cy?.elements().data("eroticeyeblink", false);
|
||||
}
|
||||
}, [desireToOpenABranchingModal]);
|
||||
//Техническая штучка. Гарантирует не отрисовку модалки по первому входу на страничку. И очистка данных по расскоменчиванию
|
||||
//Быстро просто дешево и сердито :)
|
||||
useLayoutEffect(() => {
|
||||
updateOpenedModalSettingsId();
|
||||
// updateRootContentId(quiz.id, "")
|
||||
// clearRuleForAll()
|
||||
}, []);
|
||||
//Отлов mouseup для отрисовки ноды
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
modalQuestionTargetContentId.length !== 0 &&
|
||||
modalQuestionParentContentId.length !== 0
|
||||
) {
|
||||
if (!cyRef.current) return;
|
||||
|
||||
addNode({
|
||||
parentNodeContentId: modalQuestionParentContentId,
|
||||
targetNodeContentId: modalQuestionTargetContentId,
|
||||
@ -147,195 +79,27 @@ function CsComponent({
|
||||
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 parentQuestion = getQuestionByContentId(parentNodeContentId);
|
||||
//Нельзя добавлять больше 1 ребёнка вопросам типа страница, ползунок, своё поле для ввода и дата
|
||||
if (
|
||||
(parentQuestion?.type === "date" ||
|
||||
parentQuestion?.type === "text" ||
|
||||
parentQuestion?.type === "number" ||
|
||||
parentQuestion?.type === "page") &&
|
||||
parentQuestion.content.rule.children.length === 1
|
||||
) {
|
||||
enqueueSnackbar("у вопроса этого типа может быть только 1 потомок");
|
||||
return;
|
||||
}
|
||||
|
||||
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
|
||||
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.page
|
||||
: nameCutter(targetQuestion.title),
|
||||
parentType: parentNodeContentId,
|
||||
},
|
||||
},
|
||||
{
|
||||
data: {
|
||||
source: parentNodeContentId,
|
||||
target: targetQuestion.content.id,
|
||||
},
|
||||
},
|
||||
]);
|
||||
cy?.layout(layoutOptions).run();
|
||||
cy?.center(es);
|
||||
} else {
|
||||
enqueueSnackbar("Перетащите на плюсик вопрос");
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Quiz не найден");
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
//- предупреждаем стор вопросов об открытии модалки ветвления
|
||||
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(() => {
|
||||
if (!someWorkBackend && firstRender.current) {
|
||||
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));
|
||||
fitGraphToRootNode();
|
||||
//cy?.layout().run()
|
||||
firstRender.current = false;
|
||||
}
|
||||
useEffect(function onMount() {
|
||||
updateOpenedModalSettingsId();
|
||||
document.addEventListener("pointerup", cleardragQuestionContentId);
|
||||
|
||||
return () => {
|
||||
document
|
||||
.querySelector("#root")
|
||||
?.removeEventListener("mouseup", cleardragQuestionContentId);
|
||||
layoutsContainer.current?.remove();
|
||||
plusesContainer.current?.remove();
|
||||
crossesContainer.current?.remove();
|
||||
gearsContainer.current?.remove();
|
||||
document.removeEventListener("pointerup", cleardragQuestionContentId);
|
||||
};
|
||||
}, [someWorkBackend]);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
function rerunLayout() {
|
||||
cyRef.current?.layout(layoutOptions).run();
|
||||
cyRef.current?.fit(undefined, 70);
|
||||
},
|
||||
[csElements],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
mb: "20px",
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
|
||||
<Box mb="20px">
|
||||
<Button
|
||||
sx={{
|
||||
height: "27px",
|
||||
@ -344,7 +108,9 @@ function CsComponent({
|
||||
fontSize: "16px",
|
||||
}}
|
||||
variant="text"
|
||||
onClick={fitGraphToRootNode}
|
||||
onClick={() => {
|
||||
cyRef.current?.fit(undefined, 70);
|
||||
}}
|
||||
>
|
||||
Выровнять
|
||||
</Button>
|
||||
@ -353,20 +119,22 @@ function CsComponent({
|
||||
onClick={() => updateModalInfoWhyCantCreate(true)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<CytoscapeComponent
|
||||
wheelSensitivity={0.1}
|
||||
elements={[]}
|
||||
// elements={createGraphElements(tree, quiz)}
|
||||
style={{ height: "480px", background: "#F2F3F7" }}
|
||||
elements={csElements}
|
||||
style={{
|
||||
height: "480px",
|
||||
background: "#F2F3F7",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
stylesheet={stylesheet}
|
||||
layout={layoutOptions}
|
||||
cy={(cy) => {
|
||||
cyRef.current = cy;
|
||||
}}
|
||||
autoungrabify={true}
|
||||
zoom={0.6}
|
||||
zoomingEnabled={false}
|
||||
autounselectify={true}
|
||||
boxSelectionEnabled={false}
|
||||
/>
|
||||
<DeleteNodeModal removeNode={removeNode} />
|
||||
</>
|
||||
@ -386,7 +154,7 @@ export default withErrorBoundary(CsComponent, {
|
||||
fallback: <Clear />,
|
||||
onError: (error, info) => {
|
||||
enqueueSnackbar("Дерево порвалось");
|
||||
console.log(info);
|
||||
console.log(error);
|
||||
devlog(info);
|
||||
devlog(error);
|
||||
},
|
||||
});
|
||||
|
366
src/pages/Questions/BranchingMap/CsNodeButtons.tsx
Normal file
366
src/pages/Questions/BranchingMap/CsNodeButtons.tsx
Normal file
File diff suppressed because one or more lines are too long
@ -1,35 +1,33 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { useEffect, useRef, useLayoutEffect } from "react";
|
||||
import {
|
||||
deleteQuestion,
|
||||
clearRuleForAll,
|
||||
updateQuestion,
|
||||
createResult,
|
||||
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;
|
||||
}
|
||||
export const FirstNodeField = ({
|
||||
import {
|
||||
setOpenedModalQuestions,
|
||||
modalQuestionTargetContentId,
|
||||
}: Props) => {
|
||||
updateOpenedModalSettingsId,
|
||||
} from "@root/uiTools/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
|
||||
export const FirstNodeField = () => {
|
||||
const quiz = useCurrentQuiz();
|
||||
const modalQuestionTargetContentId = useUiTools(
|
||||
(state) => state.modalQuestionTargetContentId,
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!quiz) return;
|
||||
|
||||
updateOpenedModalSettingsId();
|
||||
updateRootContentId(quiz.id, "");
|
||||
clearRuleForAll();
|
||||
}, []);
|
||||
|
||||
const { questions } = useQuestionsStore();
|
||||
const { dragQuestionContentId } = useUiTools();
|
||||
const Container = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
@ -43,18 +41,18 @@ export const FirstNodeField = ({
|
||||
dragQuestionContentId,
|
||||
(question) => (question.content.rule.parentId = "root"),
|
||||
);
|
||||
createResult(quiz?.backendId, dragQuestionContentId);
|
||||
createResult(dragQuestionContentId);
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Нет информации о взятом опросе");
|
||||
enqueueSnackbar("Нет информации о взятом опроснике");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Container.current?.addEventListener("mouseup", newRootNode);
|
||||
Container.current?.addEventListener("pointerup", newRootNode);
|
||||
Container.current?.addEventListener("click", modalOpen);
|
||||
return () => {
|
||||
Container.current?.removeEventListener("mouseup", newRootNode);
|
||||
Container.current?.removeEventListener("pointerup", newRootNode);
|
||||
Container.current?.removeEventListener("click", modalOpen);
|
||||
};
|
||||
}, [dragQuestionContentId]);
|
||||
@ -67,10 +65,10 @@ export const FirstNodeField = ({
|
||||
modalQuestionTargetContentId,
|
||||
(question) => (question.content.rule.parentId = "root"),
|
||||
);
|
||||
createResult(quiz?.backendId, modalQuestionTargetContentId);
|
||||
createResult(modalQuestionTargetContentId);
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Нет информации о взятом опросе");
|
||||
enqueueSnackbar("Нет информации о взятом опроснике");
|
||||
}
|
||||
}, [modalQuestionTargetContentId]);
|
||||
|
||||
|
@ -1,34 +1,72 @@
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { nameCutter } from "./nameCutter";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
QuestionBranchingRule,
|
||||
QuestionBranchingRuleMain,
|
||||
UntypedQuizQuestion,
|
||||
} from "@model/questionTypes/shared";
|
||||
import {
|
||||
createResult,
|
||||
getQuestionByContentId,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import { NodeSingular, PresetLayoutOptions } from "cytoscape";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
interface Nodes {
|
||||
export interface Node {
|
||||
data: {
|
||||
isRoot: boolean;
|
||||
id: string;
|
||||
label: string;
|
||||
parent?: string;
|
||||
};
|
||||
classes: string;
|
||||
}
|
||||
interface Edges {
|
||||
|
||||
export interface Edge {
|
||||
data: {
|
||||
source: string;
|
||||
target: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function isElementANode(element: Node | Edge): element is Node {
|
||||
return !("source" in element.data && "target" in element.data);
|
||||
}
|
||||
|
||||
export function isNodeInViewport(node: NodeSingular, padding: number = 0) {
|
||||
const extent = node.cy().extent();
|
||||
const bb = node.boundingBox();
|
||||
|
||||
return (
|
||||
bb.x2 > extent.x1 - padding &&
|
||||
bb.x1 < extent.x2 + padding &&
|
||||
bb.y2 > extent.y1 - padding &&
|
||||
bb.y1 < extent.y2 + padding
|
||||
);
|
||||
}
|
||||
|
||||
export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
|
||||
const nodes: Nodes[] = [];
|
||||
const edges: Edges[] = [];
|
||||
const nodes: Node[] = [];
|
||||
const edges: Edge[] = [];
|
||||
questions.forEach((question) => {
|
||||
if (question.content.rule.parentId) {
|
||||
let label =
|
||||
question.title === "" || question.title === " "
|
||||
? "noname"
|
||||
: question.title;
|
||||
if (label.length > 25) label = label.slice(0, 25) + "…";
|
||||
|
||||
nodes.push({
|
||||
data: {
|
||||
isRoot: question.content.rule.parentId === "root",
|
||||
id: question.content.id,
|
||||
label:
|
||||
question.title === "" || question.title === " "
|
||||
? "noname №" + question.page
|
||||
: nameCutter(question.title),
|
||||
parentType: question.content.rule.parentId,
|
||||
label,
|
||||
},
|
||||
classes: "multiline-auto",
|
||||
});
|
||||
// nodes.push({
|
||||
// data: {
|
||||
@ -48,3 +86,260 @@ export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
|
||||
});
|
||||
return [...nodes, ...edges];
|
||||
};
|
||||
|
||||
export 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,
|
||||
};
|
||||
|
||||
export function clearDataAfterAddNode({
|
||||
parentNodeContentId,
|
||||
targetQuestion,
|
||||
}: {
|
||||
parentNodeContentId: string;
|
||||
targetQuestion: AnyTypedQuizQuestion;
|
||||
}) {
|
||||
const parentQuestion = {
|
||||
...getQuestionByContentId(parentNodeContentId),
|
||||
} as AnyTypedQuizQuestion;
|
||||
|
||||
//смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
|
||||
useQuestionsStore
|
||||
.getState()
|
||||
.questions.filter(
|
||||
(question): question is QuizQuestionResult => question.type === "result",
|
||||
)
|
||||
.forEach((targetQuestion) => {
|
||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {
|
||||
updateQuestion<QuizQuestionResult>(
|
||||
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
|
||||
//- предупреждаем стор вопросов об открытии модалки ветвления
|
||||
updateOpenedModalSettingsId(targetQuestion.content.id);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearDataAfterRemoveNode({
|
||||
trashQuestions,
|
||||
targetQuestionContentId,
|
||||
parentQuestionContentId,
|
||||
}: {
|
||||
trashQuestions: (AnyTypedQuizQuestion | UntypedQuizQuestion)[];
|
||||
targetQuestionContentId: string;
|
||||
parentQuestionContentId: string;
|
||||
}) {
|
||||
updateQuestion(targetQuestionContentId, (question) => {
|
||||
question.content.rule.parentId = "";
|
||||
question.content.rule.children = [];
|
||||
question.content.rule.main = [];
|
||||
question.content.rule.default = "";
|
||||
});
|
||||
|
||||
//Ищём родителя
|
||||
const parentQuestion = getQuestionByContentId(parentQuestionContentId);
|
||||
|
||||
//Делаем результат родителя активным
|
||||
const parentResult = trashQuestions.find(
|
||||
(q): q is QuizQuestionResult =>
|
||||
q.type === "result" &&
|
||||
q.content.rule.parentId === parentQuestionContentId,
|
||||
);
|
||||
if (parentResult) {
|
||||
updateQuestion<QuizQuestionResult>(parentResult.content.id, (q) => {
|
||||
q.content.usage = true;
|
||||
});
|
||||
} else {
|
||||
createResult(parentQuestionContentId);
|
||||
}
|
||||
|
||||
//чистим rule родителя
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
export function calcNodePosition(node: any) {
|
||||
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: any[] = [];
|
||||
children.forEach((n: any) => {
|
||||
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 = node
|
||||
.cy()
|
||||
.edges(`[source="${task.task.id()}"]`)
|
||||
.targets();
|
||||
task.task.data("children", children.length);
|
||||
if (children.length !== 0) {
|
||||
children.forEach((n: any) =>
|
||||
queue.push({ task: n, layer: task.layer + 1 }),
|
||||
);
|
||||
}
|
||||
}
|
||||
queue.push({ parent: node, 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((node: any) => {
|
||||
return node.data("subtreeWidth") === undefined;
|
||||
});
|
||||
if (unprocessed.length !== 0) {
|
||||
queue.push(task);
|
||||
unprocessed.forEach((t: any) => {
|
||||
queue.push({
|
||||
parent: t,
|
||||
children: t.cy().edges(`[source="${t.id()}"]`).targets(),
|
||||
});
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
task?.parent.data(
|
||||
"subtreeWidth",
|
||||
task.children.reduce((p: any, n: any) => p + n.data("subtreeWidth"), 0),
|
||||
);
|
||||
}
|
||||
|
||||
const pos = { x: 0, y: 0 };
|
||||
node.data("oldPos", pos);
|
||||
|
||||
queue.push({ task: children, parent: node });
|
||||
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: any) => {
|
||||
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,
|
||||
});
|
||||
});
|
||||
}
|
||||
return pos;
|
||||
} else {
|
||||
const opos = node.data("oldPos");
|
||||
if (opos) {
|
||||
return opos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const addNode = ({
|
||||
parentNodeContentId,
|
||||
targetNodeContentId,
|
||||
}: {
|
||||
parentNodeContentId: string;
|
||||
targetNodeContentId?: string;
|
||||
}) => {
|
||||
//запрещаем работу родителя-ребенка если это один и тот же вопрос
|
||||
if (parentNodeContentId === targetNodeContentId) return;
|
||||
|
||||
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
|
||||
const targetQuestion = {
|
||||
...getQuestionByContentId(
|
||||
targetNodeContentId || useUiTools.getState().dragQuestionContentId,
|
||||
),
|
||||
} as AnyTypedQuizQuestion;
|
||||
|
||||
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId) {
|
||||
clearDataAfterAddNode({ parentNodeContentId, targetQuestion });
|
||||
createResult(targetQuestion.content.id);
|
||||
} else {
|
||||
enqueueSnackbar("Добавляемый вопрос не найден");
|
||||
}
|
||||
};
|
||||
|
@ -1,24 +1,19 @@
|
||||
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||
|
||||
import type { MutableRefObject } from "react";
|
||||
import {
|
||||
cleardragQuestionContentId,
|
||||
setModalQuestionParentContentId,
|
||||
setOpenedModalQuestions,
|
||||
updateDeleteId,
|
||||
updateOpenedModalSettingsId,
|
||||
} from "@root/uiTools/actions";
|
||||
import type {
|
||||
PresetLayoutOptions,
|
||||
LayoutEventObject,
|
||||
NodeSingular,
|
||||
AbstractEventObject,
|
||||
Core,
|
||||
NodeSingular,
|
||||
SingularData,
|
||||
} from "cytoscape";
|
||||
import { getQuestionByContentId } from "@root/questions/actions";
|
||||
|
||||
type usePopperArgs = {
|
||||
layoutsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
plusesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
crossesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
gearsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
setModalQuestionParentContentId: (id: string) => void;
|
||||
setOpenedModalQuestions: (open: boolean) => void;
|
||||
setStartCreate: (id: string) => void;
|
||||
setStartRemove: (id: string) => void;
|
||||
};
|
||||
import { getPopperInstance } from "cytoscape-popper";
|
||||
import { useCallback, type MutableRefObject, useRef } from "react";
|
||||
import { addNode } from "../helper";
|
||||
|
||||
type PopperItem = {
|
||||
id: () => string;
|
||||
@ -37,231 +32,175 @@ type PopperConfig = {
|
||||
content: (items: PopperItem[]) => void;
|
||||
};
|
||||
|
||||
type Popper = {
|
||||
update: () => Promise<void>;
|
||||
setOptions: (modifiers: { modifiers?: Modifier[] }) => void;
|
||||
};
|
||||
type PopperInstance = ReturnType<getPopperInstance<SingularData>>;
|
||||
|
||||
type NodeSingularWithPopper = NodeSingular & {
|
||||
popper: (config: PopperConfig) => Popper;
|
||||
popper: (config: PopperConfig) => PopperInstance;
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
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();
|
||||
};
|
||||
cyRef,
|
||||
}: {
|
||||
cyRef: MutableRefObject<Core | null>;
|
||||
}) => {
|
||||
const popperContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const popperInstancesRef = useRef<PopperInstance[]>([]);
|
||||
|
||||
const initialPopperIcons = ({ cy }: LayoutEventObject) => {
|
||||
const container =
|
||||
(document.body.querySelector(
|
||||
".__________cytoscape_container",
|
||||
) as HTMLDivElement) || null;
|
||||
const removeAllPoppers = useCallback(() => {
|
||||
cyRef.current?.removeListener("zoom render");
|
||||
|
||||
popperInstancesRef.current.forEach((p) => p.destroy());
|
||||
popperInstancesRef.current = [];
|
||||
popperContainerRef.current?.remove();
|
||||
popperContainerRef.current = null;
|
||||
}, []);
|
||||
|
||||
const recreatePoppers = useCallback(() => {
|
||||
removeAllPoppers();
|
||||
|
||||
const cy = cyRef.current;
|
||||
if (!cy) return;
|
||||
|
||||
const container = cy.container();
|
||||
|
||||
if (!container) {
|
||||
console.warn("Cannot create popper 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);
|
||||
if (!popperContainerRef.current) {
|
||||
popperContainerRef.current = document.createElement("div");
|
||||
popperContainerRef.current.setAttribute("id", "poppers-container");
|
||||
container.append(popperContainerRef.current);
|
||||
}
|
||||
|
||||
cy?.removeAllListeners();
|
||||
cy.nodes().forEach((item) => {
|
||||
const node = item as NodeSingularWithPopper;
|
||||
|
||||
cy
|
||||
.nodes()
|
||||
.toArray()
|
||||
?.forEach((item) => {
|
||||
const node = item as NodeSingularWithPopper;
|
||||
const layoutsPopper = node.popper({
|
||||
popper: {
|
||||
placement: "left",
|
||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||
},
|
||||
content: (items) => {
|
||||
const item = items[0];
|
||||
const itemId = item.id();
|
||||
const itemElement = popperContainerRef.current?.querySelector(
|
||||
`.popper-layout[data-id='${itemId}']`,
|
||||
);
|
||||
if (itemElement) {
|
||||
return itemElement;
|
||||
}
|
||||
|
||||
const layoutsPopper = node.popper({
|
||||
const layoutElement = document.createElement("div");
|
||||
layoutElement.style.zIndex = "0";
|
||||
layoutElement.classList.add("popper-layout");
|
||||
layoutElement.setAttribute("data-id", item.id());
|
||||
layoutElement.addEventListener("pointerup", () => {
|
||||
//Узнаём грани, идущие от этой ноды
|
||||
setModalQuestionParentContentId(item.id());
|
||||
setOpenedModalQuestions(true);
|
||||
});
|
||||
popperContainerRef.current?.appendChild(layoutElement);
|
||||
|
||||
return layoutElement;
|
||||
},
|
||||
});
|
||||
popperInstancesRef.current.push(layoutsPopper);
|
||||
|
||||
const plusesPopper = node.popper({
|
||||
popper: {
|
||||
placement: "right",
|
||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||
},
|
||||
content: ([item]) => {
|
||||
const itemId = item.id();
|
||||
const itemElement = popperContainerRef.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("pointerup", () => {
|
||||
addNode({ parentNodeContentId: node.id() });
|
||||
cleardragQuestionContentId();
|
||||
});
|
||||
|
||||
popperContainerRef.current?.appendChild(plusElement);
|
||||
|
||||
return plusElement;
|
||||
},
|
||||
});
|
||||
popperInstancesRef.current.push(plusesPopper);
|
||||
|
||||
const crossesPopper = node.popper({
|
||||
popper: {
|
||||
placement: "top-end",
|
||||
modifiers: [{ name: "flip", options: { boundary: node } }],
|
||||
},
|
||||
content: ([item]) => {
|
||||
const itemId = item.id();
|
||||
const itemElement = popperContainerRef.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";
|
||||
popperContainerRef.current?.appendChild(crossElement);
|
||||
crossElement.addEventListener("pointerup", () => {
|
||||
updateDeleteId(node.id());
|
||||
});
|
||||
|
||||
return crossElement;
|
||||
},
|
||||
});
|
||||
popperInstancesRef.current.push(crossesPopper);
|
||||
|
||||
let gearsPopper: PopperInstance | 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 = layoutsContainer.current?.querySelector(
|
||||
`.popper-layout[data-id='${itemId}']`,
|
||||
|
||||
const itemElement = popperContainerRef.current?.querySelector(
|
||||
`.popper-gear[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);
|
||||
const gearElement = document.createElement("div");
|
||||
gearElement.classList.add("popper-gear");
|
||||
gearElement.setAttribute("data-id", item.id());
|
||||
gearElement.style.zIndex = "1";
|
||||
popperContainerRef.current?.appendChild(gearElement);
|
||||
gearElement.addEventListener("pointerup", () => {
|
||||
updateOpenedModalSettingsId(item.id());
|
||||
});
|
||||
layoutElement.addEventListener("touchstart", () => {
|
||||
//Узнаём грани, идущие от этой ноды
|
||||
setModalQuestionParentContentId(item.id());
|
||||
setOpenedModalQuestions(true);
|
||||
});
|
||||
layoutsContainer.current?.appendChild(layoutElement);
|
||||
|
||||
return layoutElement;
|
||||
return gearElement;
|
||||
},
|
||||
});
|
||||
popperInstancesRef.current.push(gearsPopper);
|
||||
}
|
||||
|
||||
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());
|
||||
});
|
||||
plusElement.addEventListener("touchstart", () => {
|
||||
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());
|
||||
});
|
||||
crossElement.addEventListener("touchstart", () => {
|
||||
setStartRemove(node.id());
|
||||
});
|
||||
|
||||
return crossElement;
|
||||
},
|
||||
});
|
||||
let gearsPopper: Popper | null = null;
|
||||
if (node.data().root !== true) {
|
||||
const parentQuestion = getQuestionByContentId(
|
||||
node.data("parentType"),
|
||||
);
|
||||
|
||||
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", () => {
|
||||
updateOpenedModalSettingsId(item.id());
|
||||
});
|
||||
gearElement.addEventListener("touchstart", () => {
|
||||
updateOpenedModalSettingsId(item.id());
|
||||
});
|
||||
|
||||
if (
|
||||
parentQuestion?.type === "date" ||
|
||||
parentQuestion?.type === "text" ||
|
||||
parentQuestion?.type === "number" ||
|
||||
parentQuestion?.type === "page"
|
||||
) {
|
||||
gearElement.classList.add("popper-gear-none");
|
||||
}
|
||||
|
||||
return gearElement;
|
||||
},
|
||||
});
|
||||
}
|
||||
const update = async () => {
|
||||
await plusesPopper.update();
|
||||
await crossesPopper.update();
|
||||
await gearsPopper?.update();
|
||||
await layoutsPopper.update();
|
||||
};
|
||||
|
||||
const zoom = cy.zoom();
|
||||
|
||||
//update();
|
||||
const onZoom = (event: AbstractEventObject) => {
|
||||
const zoom = event.cy.zoom();
|
||||
|
||||
crossesPopper.setOptions({
|
||||
modifiers: [
|
||||
@ -279,7 +218,7 @@ export const usePopper = ({
|
||||
plusesPopper.setOptions({
|
||||
modifiers: [
|
||||
{ name: "flip", options: { boundary: node } },
|
||||
{ name: "offset", options: { offset: [0, 0 * zoom] } },
|
||||
{ name: "offset", options: { offset: [0, 0] } },
|
||||
],
|
||||
});
|
||||
gearsPopper?.setOptions({
|
||||
@ -289,16 +228,16 @@ export const usePopper = ({
|
||||
],
|
||||
});
|
||||
|
||||
layoutsContainer.current
|
||||
?.querySelectorAll("#popper-layouts > .popper-layout")
|
||||
popperContainerRef.current
|
||||
?.querySelectorAll(".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")
|
||||
popperContainerRef.current
|
||||
?.querySelectorAll(".popper-plus")
|
||||
.forEach((item) => {
|
||||
const element = item as HTMLDivElement;
|
||||
element.style.width = `${40 * zoom}px`;
|
||||
@ -307,8 +246,8 @@ export const usePopper = ({
|
||||
element.style.borderRadius = `${6 * zoom}px`;
|
||||
});
|
||||
|
||||
crossesContainer.current
|
||||
?.querySelectorAll("#popper-crosses > .popper-cross")
|
||||
popperContainerRef.current
|
||||
?.querySelectorAll(".popper-cross")
|
||||
.forEach((item) => {
|
||||
const element = item as HTMLDivElement;
|
||||
element.style.width = `${24 * zoom}px`;
|
||||
@ -317,188 +256,18 @@ export const usePopper = ({
|
||||
element.style.borderRadius = `${6 * zoom}px`;
|
||||
});
|
||||
|
||||
gearsContainer?.current
|
||||
?.querySelectorAll("#popper-gears > .popper-gear")
|
||||
popperContainerRef?.current
|
||||
?.querySelectorAll(".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("render", () => {
|
||||
update();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
cy.on("zoom render", onZoom);
|
||||
});
|
||||
}, []);
|
||||
|
||||
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 = node
|
||||
.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: node, 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((node) => {
|
||||
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: node });
|
||||
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 };
|
||||
return { removeAllPoppers, recreatePoppers };
|
||||
};
|
||||
|
@ -1,130 +1,34 @@
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import {
|
||||
deleteQuestion,
|
||||
updateQuestion,
|
||||
getQuestionByContentId,
|
||||
clearRuleForAll,
|
||||
createResult,
|
||||
getQuestionByContentId,
|
||||
updateQuestion,
|
||||
} 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 { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import type {
|
||||
Core,
|
||||
CollectionReturnValue,
|
||||
PresetLayoutOptions,
|
||||
Core,
|
||||
SingularElementArgument,
|
||||
} from "cytoscape";
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
QuestionBranchingRule,
|
||||
QuestionBranchingRuleMain,
|
||||
} from "../../../../model/questionTypes/shared";
|
||||
import type { MutableRefObject } from "react";
|
||||
import { clearDataAfterRemoveNode } from "../helper";
|
||||
|
||||
type UseRemoveNodeArgs = {
|
||||
cyRef: MutableRefObject<Core | null>;
|
||||
layoutOptions: PresetLayoutOptions;
|
||||
layoutsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
plusesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
crossesContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
gearsContainer: MutableRefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
export const useRemoveNode = ({
|
||||
cyRef,
|
||||
layoutOptions,
|
||||
layoutsContainer,
|
||||
plusesContainer,
|
||||
crossesContainer,
|
||||
gearsContainer,
|
||||
}: UseRemoveNodeArgs) => {
|
||||
export const useRemoveNode = ({ cyRef }: 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 = "";
|
||||
});
|
||||
|
||||
//Ищём родителя
|
||||
const parentQuestion = getQuestionByContentId(parentQuestionContentId);
|
||||
if (parentQuestion.content.rule.children.length === 1) {
|
||||
//если у родителя больше нет потомков
|
||||
//Делаем результат родителя активным
|
||||
const parentResult = trashQuestions.find(
|
||||
(q) =>
|
||||
q.type === "result" &&
|
||||
q.content.rule.parentId === parentQuestionContentId,
|
||||
);
|
||||
if (parentResult) {
|
||||
updateQuestion(parentResult.content.id, (q) => {
|
||||
q.content.usage = true;
|
||||
});
|
||||
} else {
|
||||
createResult(quiz?.backendId, parentQuestionContentId);
|
||||
}
|
||||
}
|
||||
|
||||
//чистим rule родителя
|
||||
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) => {
|
||||
const deleteNodesRecursively = (node: CollectionReturnValue) => {
|
||||
//Узнаём грани, идущие от этой ноды
|
||||
cy
|
||||
?.$('edge[source = "' + node.id() + '"]')
|
||||
@ -132,20 +36,18 @@ export const useRemoveNode = ({
|
||||
.forEach((edge) => {
|
||||
const edgeData = edge.data();
|
||||
|
||||
//записываем id грани для дальнейшего удаления
|
||||
deleteEdges.push(edge);
|
||||
//ищем ноду на конце грани, записываем её ID для дальнейшего удаления
|
||||
const targetNode = cy?.$("#" + edgeData.target);
|
||||
deleteNodes.push(targetNode.data().id);
|
||||
//вызываем функцию для анализа потомков уже у этой ноды
|
||||
findChildrenToDelete(targetNode);
|
||||
deleteNodesRecursively(targetNode);
|
||||
});
|
||||
};
|
||||
|
||||
const elementToDelete = cy?.getElementById(targetNodeContentId);
|
||||
|
||||
if (elementToDelete) {
|
||||
findChildrenToDelete(elementToDelete);
|
||||
deleteNodesRecursively(elementToDelete);
|
||||
}
|
||||
|
||||
const targetQuestion = getQuestionByContentId(targetNodeContentId);
|
||||
@ -155,7 +57,7 @@ export const useRemoveNode = ({
|
||||
targetQuestion.content.rule.parentId === "root" &&
|
||||
quiz
|
||||
) {
|
||||
updateRootContentId(quiz?.id, "");
|
||||
updateRootContentId(quiz.id, "");
|
||||
updateQuestion(targetNodeContentId, (question) => {
|
||||
question.content.rule.parentId = "";
|
||||
question.content.rule.main = [];
|
||||
@ -173,16 +75,14 @@ export const useRemoveNode = ({
|
||||
quiz &&
|
||||
cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0
|
||||
) {
|
||||
devlog(parentQuestionContentId);
|
||||
//createFrontResult(quiz.backendId, parentQuestionContentId);
|
||||
}
|
||||
clearDataAfterRemoveNode({
|
||||
trashQuestions,
|
||||
targetQuestionContentId: targetNodeContentId,
|
||||
parentQuestionContentId,
|
||||
});
|
||||
cy
|
||||
?.remove(cy?.$("#" + targetNodeContentId))
|
||||
.layout(layoutOptions)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,8 +90,6 @@ export const useRemoveNode = ({
|
||||
|
||||
deleteNodes.forEach((nodeId) => {
|
||||
//Ноды
|
||||
cy?.remove(cy?.$("#" + nodeId));
|
||||
removeButtons(nodeId);
|
||||
updateQuestion(nodeId, (question) => {
|
||||
question.content.rule.parentId = "";
|
||||
question.content.rule.main = [];
|
||||
@ -200,15 +98,6 @@ export const useRemoveNode = ({
|
||||
});
|
||||
});
|
||||
|
||||
deleteEdges.forEach((edge: any) => {
|
||||
//Грани
|
||||
cy?.remove(edge);
|
||||
});
|
||||
|
||||
removeButtons(targetNodeContentId);
|
||||
cy?.data("changed", true);
|
||||
cy?.layout(layoutOptions).run();
|
||||
|
||||
//делаем result всех потомков неактивными
|
||||
trashQuestions.forEach((qr) => {
|
||||
if (
|
||||
@ -217,7 +106,7 @@ export const useRemoveNode = ({
|
||||
(targetQuestion?.type &&
|
||||
qr.content.rule.parentId === targetQuestion.content.id))
|
||||
) {
|
||||
updateQuestion(qr.content.id, (q) => {
|
||||
updateQuestion<QuizQuestionResult>(qr.content.id, (q) => {
|
||||
q.content.usage = false;
|
||||
});
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
import { Box } from "@mui/material";
|
||||
import { FirstNodeField } from "./FirstNodeField";
|
||||
import CsComponent from "./CsComponent";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BranchingQuestionsModal } from "../BranchingQuestionsModal";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import { BranchingQuestionsModal } from "../BranchingQuestionsModal";
|
||||
import CsComponent from "./CsComponent";
|
||||
import { FirstNodeField } from "./FirstNodeField";
|
||||
|
||||
export const BranchingMap = () => {
|
||||
const quiz = useCurrentQuiz();
|
||||
const { dragQuestionContentId } = useUiTools();
|
||||
const [modalQuestionParentContentId, setModalQuestionParentContentId] =
|
||||
useState<string>("");
|
||||
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] =
|
||||
useState<string>("");
|
||||
const [openedModalQuestions, setOpenedModalQuestions] =
|
||||
useState<boolean>(false);
|
||||
const dragQuestionContentId = useUiTools(
|
||||
(state) => state.dragQuestionContentId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -30,25 +25,8 @@ export const BranchingMap = () => {
|
||||
border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed",
|
||||
}}
|
||||
>
|
||||
{quiz?.config.haveRoot ? (
|
||||
<CsComponent
|
||||
modalQuestionParentContentId={modalQuestionParentContentId}
|
||||
modalQuestionTargetContentId={modalQuestionTargetContentId}
|
||||
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||
setModalQuestionParentContentId={setModalQuestionParentContentId}
|
||||
setModalQuestionTargetContentId={setModalQuestionTargetContentId}
|
||||
/>
|
||||
) : (
|
||||
<FirstNodeField
|
||||
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||
modalQuestionTargetContentId={modalQuestionTargetContentId}
|
||||
/>
|
||||
)}
|
||||
<BranchingQuestionsModal
|
||||
openedModalQuestions={openedModalQuestions}
|
||||
setOpenedModalQuestions={setOpenedModalQuestions}
|
||||
setModalQuestionTargetContentId={setModalQuestionTargetContentId}
|
||||
/>
|
||||
{quiz?.config.haveRoot ? <CsComponent /> : <FirstNodeField />}
|
||||
<BranchingQuestionsModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
#popper-pluses > .popper-plus {
|
||||
.popper-plus {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -9,13 +9,13 @@
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
#popper-pluses > .popper-plus::before {
|
||||
.popper-plus::before {
|
||||
content: "+";
|
||||
color: rgba(154, 154, 175, 0.5);
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#popper-crosses > .popper-cross {
|
||||
.popper-cross {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -25,14 +25,14 @@
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
#popper-crosses > .popper-cross::before {
|
||||
.popper-cross::before {
|
||||
content: "+";
|
||||
transform: rotate(45deg);
|
||||
color: #fff;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
#popper-gears > .popper-gear {
|
||||
.popper-gear {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -1,22 +1,20 @@
|
||||
import { Box, Modal, Button, Typography } from "@mui/material";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
|
||||
interface Props {
|
||||
openedModalQuestions: boolean;
|
||||
setModalQuestionTargetContentId: (contentId: string) => void;
|
||||
setOpenedModalQuestions: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const BranchingQuestionsModal = ({
|
||||
openedModalQuestions,
|
||||
setOpenedModalQuestions,
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import {
|
||||
setModalQuestionTargetContentId,
|
||||
}: Props) => {
|
||||
setOpenedModalQuestions,
|
||||
} from "@root/uiTools/actions";
|
||||
|
||||
export const BranchingQuestionsModal = () => {
|
||||
const trashQuestions = useQuestionsStore().questions;
|
||||
const questions = trashQuestions.filter(
|
||||
(question) => question.type !== "result",
|
||||
);
|
||||
const openedModalQuestions = useUiTools(
|
||||
(state) => state.openedModalQuestions,
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setOpenedModalQuestions(false);
|
||||
|
@ -26,6 +26,8 @@ import { useUiTools } from "../uiTools/store";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
|
||||
import { useQuizPreviewStore } from "@root/quizPreview";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
|
||||
export const setQuestions = (questions: RawQuestion[] | null) =>
|
||||
setProducedState(
|
||||
|
@ -43,3 +43,12 @@ export const updateSomeWorkBackend = (someWorkBackend: boolean) =>
|
||||
|
||||
export const updateNextStep = (nextStep: number) =>
|
||||
useUiTools.setState({ nextStep });
|
||||
|
||||
export const setModalQuestionParentContentId = (
|
||||
modalQuestionParentContentId: string,
|
||||
) => useUiTools.setState({ modalQuestionParentContentId });
|
||||
export const setModalQuestionTargetContentId = (
|
||||
modalQuestionTargetContentId: string,
|
||||
) => useUiTools.setState({ modalQuestionTargetContentId });
|
||||
export const setOpenedModalQuestions = (open: boolean) =>
|
||||
useUiTools.setState({ openedModalQuestions: open });
|
||||
|
@ -13,6 +13,9 @@ export type UiTools = {
|
||||
showConfirmLeaveModal: boolean;
|
||||
someWorkBackend: boolean;
|
||||
nextStep: number;
|
||||
modalQuestionParentContentId: string;
|
||||
modalQuestionTargetContentId: string;
|
||||
openedModalQuestions: boolean;
|
||||
};
|
||||
|
||||
export type WhyCantCreatePublic = {
|
||||
@ -32,6 +35,9 @@ const initialState: UiTools = {
|
||||
showConfirmLeaveModal: false,
|
||||
someWorkBackend: false,
|
||||
nextStep: -1,
|
||||
modalQuestionParentContentId: "",
|
||||
modalQuestionTargetContentId: "",
|
||||
openedModalQuestions: false,
|
||||
};
|
||||
|
||||
export const useUiTools = create<UiTools>()(
|
||||
|
Loading…
Reference in New Issue
Block a user