refactor: Cytoscape components

This commit is contained in:
IlyaDoronin 2023-12-20 13:46:38 +03:00
parent fa2dedeed6
commit 2e7c4d4de6
8 changed files with 1474 additions and 705 deletions

File diff suppressed because it is too large Load Diff

@ -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<HTMLDivElement | null>(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<HTMLDivElement | null>(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 (
<Box
ref={Container}
sx={{
height: "100%",
width: "100%",
backgroundColor: "#f2f3f7",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#4d4d4d",
fontSize: "50px"
}}
>
+
</Box>
)
}
return (
<Box
ref={Container}
sx={{
height: "100%",
width: "100%",
backgroundColor: "#f2f3f7",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#4d4d4d",
fontSize: "50px",
}}
>
+
</Box>
);
};

@ -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<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;
};
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<void>;
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 };
};

@ -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<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) => {
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 };
};

@ -14,8 +14,6 @@ export const BranchingMap = () => {
const [modalQuestionTargetContentId, setModalQuestionTargetContentId] = useState<string>("")
const [openedModalQuestions, setOpenedModalQuestions] = useState<boolean>(false)
return (
<Box
id="cytoscape-container"

@ -0,0 +1,53 @@
import type { Stylesheet } from "cytoscape";
export 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",
},
},
];

@ -6,14 +6,12 @@ interface Props {
openedModalQuestions: boolean;
setModalQuestionTargetContentId: (contentId: string) => 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(

@ -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<UiTools>()(