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;
|
2023-12-02 09:59:31 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-12-02 09:59:31 +00:00
|
|
|
|
// 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 {
|
2024-01-09 16:41:35 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-02 09:59:31 +00:00
|
|
|
|
}
|
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;
|
|
|
|
|
|
2024-01-09 16:41:35 +00:00
|
|
|
|
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId) {
|
2024-01-05 16:48:35 +00:00
|
|
|
|
clearDataAfterAddNode({ parentNodeContentId, targetQuestion });
|
2024-01-09 16:41:35 +00:00
|
|
|
|
createResult(targetQuestion.content.id);
|
2024-01-05 16:48:35 +00:00
|
|
|
|
} else {
|
|
|
|
|
enqueueSnackbar("Добавляемый вопрос не найден");
|
|
|
|
|
}
|
|
|
|
|
};
|