frontPanel/src/pages/Questions/BranchingMap/helper.ts

264 lines
9.9 KiB
TypeScript
Raw Normal View History

2024-01-05 16:48:35 +00:00
import { devlog } from "@frontend/kitui";
import { QuizQuestionResult } from "@model/questionTypes/result";
import { AnyTypedQuizQuestion, QuestionBranchingRule, QuestionBranchingRuleMain, UntypedQuizQuestion } from "@model/questionTypes/shared";
import { Quiz } from "@model/quiz/quiz";
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 { Core } from "cytoscape";
import { enqueueSnackbar } from "notistack";
2023-11-29 15:45:15 +00:00
interface Nodes {
data: {
id: string;
label: string;
parent?: string;
2024-01-05 16:48:35 +00:00
};
2023-11-29 15:45:15 +00:00
}
interface Edges {
data: {
source: string;
target: string;
2024-01-05 16:48:35 +00:00
};
2023-11-29 15:45:15 +00:00
}
2023-12-03 13:09:57 +00:00
export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
2024-01-05 16:48:35 +00:00
const nodes: Nodes[] = [];
const edges: Edges[] = [];
2023-11-29 15:45:15 +00:00
questions.forEach((question) => {
if (question.content.rule.parentId) {
2024-01-05 16:48:35 +00:00
nodes.push({
data: {
id: question.content.id,
label: question.title === "" || question.title === " " ? "noname" : question.title
}
});
// nodes.push({
// data: {
// id: "delete" + question.content.id,
// label: "X",
// parent: question.content.id,
// }
// },)
2024-01-05 16:48:35 +00:00
if (question.content.rule.parentId !== "root") edges.push({
data: {
source: question.content.rule.parentId,
target: question.content.id
}
});
2023-11-29 15:45:15 +00:00
}
2024-01-05 16:48:35 +00:00
});
2023-11-29 15:45:15 +00:00
return [...nodes, ...edges];
2024-01-05 16:48:35 +00:00
};
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);
2024-01-05 16:48:35 +00:00
}
//чистим 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;
}
}
}
2024-01-05 16:48:35 +00:00
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) {
2024-01-05 16:48:35 +00:00
clearDataAfterAddNode({ parentNodeContentId, targetQuestion });
createResult(targetQuestion.content.id);
2024-01-05 16:48:35 +00:00
} else {
enqueueSnackbar("Добавляемый вопрос не найден");
}
};