fix branching graph unmount bug
CsNodeButtons renders normally instead of portal
This commit is contained in:
parent
03cbf1d963
commit
a6c1b4b93a
@ -9,11 +9,9 @@ import {
|
|||||||
cleardragQuestionContentId,
|
cleardragQuestionContentId,
|
||||||
setModalQuestionParentContentId,
|
setModalQuestionParentContentId,
|
||||||
setModalQuestionTargetContentId,
|
setModalQuestionTargetContentId,
|
||||||
updateModalInfoWhyCantCreate,
|
|
||||||
updateOpenedModalSettingsId,
|
updateOpenedModalSettingsId,
|
||||||
} from "@root/uiTools/actions";
|
} from "@root/uiTools/actions";
|
||||||
import { useUiTools } from "@root/uiTools/store";
|
import { useUiTools } from "@root/uiTools/store";
|
||||||
import { ProblemIcon } from "@ui_kit/ProblemIcon";
|
|
||||||
import type { Core } from "cytoscape";
|
import type { Core } from "cytoscape";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
import { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||||||
@ -27,24 +25,16 @@ import "./style/styles.css";
|
|||||||
import { stylesheet } from "./style/stylesheet";
|
import { stylesheet } from "./style/stylesheet";
|
||||||
|
|
||||||
function CsComponent() {
|
function CsComponent() {
|
||||||
const desireToOpenABranchingModal = useUiTools(
|
const desireToOpenABranchingModal = useUiTools((state) => state.desireToOpenABranchingModal);
|
||||||
(state) => state.desireToOpenABranchingModal,
|
const modalQuestionParentContentId = useUiTools((state) => state.modalQuestionParentContentId);
|
||||||
);
|
const modalQuestionTargetContentId = useUiTools((state) => state.modalQuestionTargetContentId);
|
||||||
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 trashQuestions = useQuestionsStore((state) => state.questions);
|
||||||
const cyRef = useRef<Core | null>(null);
|
const cyRef = useRef<Core | null>(null);
|
||||||
const { removeNode } = useRemoveNode({ cyRef });
|
const { removeNode } = useRemoveNode({ cyRef });
|
||||||
|
|
||||||
const csElements = useMemo(() => {
|
const csElements = useMemo(() => {
|
||||||
const questions = trashQuestions.filter(
|
const questions = trashQuestions.filter(
|
||||||
(question): question is AnyTypedQuizQuestion =>
|
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
|
||||||
question.type !== null && question.type !== "result",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return storeToNodes(questions);
|
return storeToNodes(questions);
|
||||||
@ -54,9 +44,7 @@ function CsComponent() {
|
|||||||
const cy = cyRef?.current;
|
const cy = cyRef?.current;
|
||||||
if (desireToOpenABranchingModal) {
|
if (desireToOpenABranchingModal) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cy
|
cy?.getElementById(desireToOpenABranchingModal)?.data("eroticeyeblink", true);
|
||||||
?.getElementById(desireToOpenABranchingModal)
|
|
||||||
?.data("eroticeyeblink", true);
|
|
||||||
}, 250);
|
}, 250);
|
||||||
} else {
|
} else {
|
||||||
cy?.elements().data("eroticeyeblink", false);
|
cy?.elements().data("eroticeyeblink", false);
|
||||||
@ -64,10 +52,7 @@ function CsComponent() {
|
|||||||
}, [desireToOpenABranchingModal]);
|
}, [desireToOpenABranchingModal]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (modalQuestionTargetContentId.length !== 0 && modalQuestionParentContentId.length !== 0) {
|
||||||
modalQuestionTargetContentId.length !== 0 &&
|
|
||||||
modalQuestionParentContentId.length !== 0
|
|
||||||
) {
|
|
||||||
if (!cyRef.current) return;
|
if (!cyRef.current) return;
|
||||||
|
|
||||||
addNode({
|
addNode({
|
||||||
@ -93,12 +78,11 @@ function CsComponent() {
|
|||||||
cyRef.current?.layout(layoutOptions).run();
|
cyRef.current?.layout(layoutOptions).run();
|
||||||
cyRef.current?.fit(undefined, 70);
|
cyRef.current?.fit(undefined, 70);
|
||||||
},
|
},
|
||||||
[csElements],
|
[csElements]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
|
|
||||||
<Box mb="20px">
|
<Box mb="20px">
|
||||||
<Button
|
<Button
|
||||||
sx={{
|
sx={{
|
||||||
@ -115,23 +99,32 @@ function CsComponent() {
|
|||||||
Выровнять
|
Выровнять
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<CytoscapeComponent
|
<Box
|
||||||
wheelSensitivity={0.1}
|
sx={{
|
||||||
elements={csElements}
|
position: "relative",
|
||||||
style={{
|
|
||||||
height: "480px",
|
height: "480px",
|
||||||
background: "#F2F3F7",
|
background: "#F2F3F7",
|
||||||
overflow: "hidden",
|
|
||||||
}}
|
}}
|
||||||
stylesheet={stylesheet}
|
>
|
||||||
layout={layoutOptions}
|
<CytoscapeComponent
|
||||||
cy={(cy) => {
|
wheelSensitivity={0.1}
|
||||||
cyRef.current = cy;
|
elements={csElements}
|
||||||
}}
|
style={{
|
||||||
autoungrabify={true}
|
height: "100%",
|
||||||
autounselectify={true}
|
width: "100%",
|
||||||
boxSelectionEnabled={false}
|
overflow: "hidden",
|
||||||
/>
|
}}
|
||||||
|
stylesheet={stylesheet}
|
||||||
|
layout={layoutOptions}
|
||||||
|
cy={(cy) => {
|
||||||
|
cyRef.current = cy;
|
||||||
|
}}
|
||||||
|
autoungrabify={true}
|
||||||
|
autounselectify={true}
|
||||||
|
boxSelectionEnabled={false}
|
||||||
|
/>
|
||||||
|
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
|
||||||
|
</Box>
|
||||||
<DeleteNodeModal removeNode={removeNode} />
|
<DeleteNodeModal removeNode={removeNode} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -8,29 +8,14 @@ import {
|
|||||||
updateOpenedModalSettingsId,
|
updateOpenedModalSettingsId,
|
||||||
} from "@root/uiTools/actions";
|
} from "@root/uiTools/actions";
|
||||||
import { Core, EventObject, NodeSingular } from "cytoscape";
|
import { Core, EventObject, NodeSingular } from "cytoscape";
|
||||||
import {
|
import { MutableRefObject, forwardRef, useEffect, useMemo, useRef } from "react";
|
||||||
MutableRefObject,
|
import { addNode, isElementANode, isNodeInViewport, isQuestionProhibited, storeToNodes } from "./helper";
|
||||||
forwardRef,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import {
|
|
||||||
addNode,
|
|
||||||
isElementANode,
|
|
||||||
isNodeInViewport,
|
|
||||||
isQuestionProhibited,
|
|
||||||
storeToNodes,
|
|
||||||
} from "./helper";
|
|
||||||
|
|
||||||
const csButtonTypes = ["delete", "add", "settings", "select"] as const;
|
const csButtonTypes = ["delete", "add", "settings", "select"] as const;
|
||||||
|
|
||||||
type CsButtonType = (typeof csButtonTypes)[number];
|
type CsButtonType = (typeof csButtonTypes)[number];
|
||||||
|
|
||||||
type CsNodeButtonsByType = Partial<
|
type CsNodeButtonsByType = Partial<Record<CsButtonType, HTMLButtonElement | null>>;
|
||||||
Record<CsButtonType, HTMLButtonElement | null>
|
|
||||||
>;
|
|
||||||
|
|
||||||
type CsButtonsById = Record<string, CsNodeButtonsByType | undefined>;
|
type CsButtonsById = Record<string, CsNodeButtonsByType | undefined>;
|
||||||
|
|
||||||
@ -44,10 +29,7 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
|
|||||||
|
|
||||||
const buttons = useMemo(() => {
|
const buttons = useMemo(() => {
|
||||||
const nodeElements = csElements.filter(isElementANode);
|
const nodeElements = csElements.filter(isElementANode);
|
||||||
buttonRefsById.current = nodeElements.reduce<CsButtonsById>(
|
buttonRefsById.current = nodeElements.reduce<CsButtonsById>((acc, node) => ((acc[node.data.id] = {}), acc), {});
|
||||||
(acc, node) => ((acc[node.data.id] = {}), acc),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -56,6 +38,10 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
|
|||||||
left: 0,
|
left: 0,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
pointerEvents: "none",
|
||||||
|
"> *": {
|
||||||
|
pointerEvents: "auto",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{nodeElements.flatMap((csElement) => [
|
{nodeElements.flatMap((csElement) => [
|
||||||
@ -80,19 +66,18 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
|
|||||||
cleardragQuestionContentId();
|
cleardragQuestionContentId();
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
!csElement.data.isRoot &&
|
!csElement.data.isRoot && !isQuestionProhibited(csElement.data.qtype) && (
|
||||||
!isQuestionProhibited(csElement.data.qtype) && (
|
<CsSettingsButton
|
||||||
<CsSettingsButton
|
key={`settings-${csElement.data.id}`}
|
||||||
key={`settings-${csElement.data.id}`}
|
ref={(r) => {
|
||||||
ref={(r) => {
|
const buttonData = buttonRefsById.current[csElement.data.id];
|
||||||
const buttonData = buttonRefsById.current[csElement.data.id];
|
if (buttonData) buttonData.settings = r;
|
||||||
if (buttonData) buttonData.settings = r;
|
}}
|
||||||
}}
|
onClick={() => {
|
||||||
onClick={() => {
|
updateOpenedModalSettingsId(csElement.data.id);
|
||||||
updateOpenedModalSettingsId(csElement.data.id);
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
),
|
||||||
),
|
|
||||||
//оболочка узла
|
//оболочка узла
|
||||||
<CsSelectButton
|
<CsSelectButton
|
||||||
key={`select-${csElement.data.id}`}
|
key={`select-${csElement.data.id}`}
|
||||||
@ -102,12 +87,7 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
|
|||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setModalQuestionParentContentId(csElement.data.id);
|
setModalQuestionParentContentId(csElement.data.id);
|
||||||
setOpenedModalQuestions(
|
setOpenedModalQuestions(!(isQuestionProhibited(csElement.data.type) && csElement.data.children > 0));
|
||||||
!(
|
|
||||||
isQuestionProhibited(csElement.data.type) &&
|
|
||||||
csElement.data.children > 0
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
/>,
|
/>,
|
||||||
])}
|
])}
|
||||||
@ -150,10 +130,7 @@ export default function CsNodeButtons({ csElements, cyRef }: Props) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const container = cyRef.current?.container();
|
return buttons;
|
||||||
const buttonsPortal = container ? createPortal(buttons, container) : null;
|
|
||||||
|
|
||||||
return buttonsPortal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyButtonStyleByType: Record<
|
const applyButtonStyleByType: Record<
|
||||||
@ -162,10 +139,8 @@ const applyButtonStyleByType: Record<
|
|||||||
> = {
|
> = {
|
||||||
delete(button, node, zoom) {
|
delete(button, node, zoom) {
|
||||||
const nodePosition = node.renderedPosition();
|
const nodePosition = node.renderedPosition();
|
||||||
const shiftX =
|
const shiftX = node.renderedWidth() / 2 - (CLOSE_BUTTON_WIDTH / 2 + 6) * zoom;
|
||||||
node.renderedWidth() / 2 - (CLOSE_BUTTON_WIDTH / 2 + 6) * zoom;
|
const shiftY = node.renderedHeight() / 2 - (CLOSE_BUTTON_HEIGHT / 2 + 6) * zoom;
|
||||||
const shiftY =
|
|
||||||
node.renderedHeight() / 2 - (CLOSE_BUTTON_HEIGHT / 2 + 6) * zoom;
|
|
||||||
|
|
||||||
if (!isNodeInViewport(node, 100)) {
|
if (!isNodeInViewport(node, 100)) {
|
||||||
return button.style.setProperty("display", "none");
|
return button.style.setProperty("display", "none");
|
||||||
@ -176,7 +151,7 @@ const applyButtonStyleByType: Record<
|
|||||||
button.style.setProperty("top", `${nodePosition.y}px`);
|
button.style.setProperty("top", `${nodePosition.y}px`);
|
||||||
button.style.setProperty(
|
button.style.setProperty(
|
||||||
"transform",
|
"transform",
|
||||||
`translate3d(calc(-50% + ${shiftX}px), calc(-50% - ${shiftY}px), 0) scale(${zoom})`,
|
`translate3d(calc(-50% + ${shiftX}px), calc(-50% - ${shiftY}px), 0) scale(${zoom})`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
add(button, node, zoom) {
|
add(button, node, zoom) {
|
||||||
@ -190,15 +165,11 @@ const applyButtonStyleByType: Record<
|
|||||||
button.style.setProperty("display", "flex");
|
button.style.setProperty("display", "flex");
|
||||||
button.style.setProperty("left", `${nodePosition.x}px`);
|
button.style.setProperty("left", `${nodePosition.x}px`);
|
||||||
button.style.setProperty("top", `${nodePosition.y}px`);
|
button.style.setProperty("top", `${nodePosition.y}px`);
|
||||||
button.style.setProperty(
|
button.style.setProperty("transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`);
|
||||||
"transform",
|
|
||||||
`translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
settings(button, node, zoom) {
|
settings(button, node, zoom) {
|
||||||
const nodePosition = node.renderedPosition();
|
const nodePosition = node.renderedPosition();
|
||||||
const shiftX =
|
const shiftX = -node.renderedWidth() / 2 - (SETTINGS_BUTTON_WIDTH / 2) * zoom;
|
||||||
-node.renderedWidth() / 2 - (SETTINGS_BUTTON_WIDTH / 2) * zoom;
|
|
||||||
|
|
||||||
if (!isNodeInViewport(node, 100)) {
|
if (!isNodeInViewport(node, 100)) {
|
||||||
return button.style.setProperty("display", "none");
|
return button.style.setProperty("display", "none");
|
||||||
@ -207,10 +178,7 @@ const applyButtonStyleByType: Record<
|
|||||||
button.style.setProperty("display", "flex");
|
button.style.setProperty("display", "flex");
|
||||||
button.style.setProperty("left", `${nodePosition.x}px`);
|
button.style.setProperty("left", `${nodePosition.x}px`);
|
||||||
button.style.setProperty("top", `${nodePosition.y}px`);
|
button.style.setProperty("top", `${nodePosition.y}px`);
|
||||||
button.style.setProperty(
|
button.style.setProperty("transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`);
|
||||||
"transform",
|
|
||||||
`translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
select(button, node, zoom) {
|
select(button, node, zoom) {
|
||||||
const nodePosition = node.renderedPosition();
|
const nodePosition = node.renderedPosition();
|
||||||
@ -222,10 +190,7 @@ const applyButtonStyleByType: Record<
|
|||||||
button.style.setProperty("display", "flex");
|
button.style.setProperty("display", "flex");
|
||||||
button.style.setProperty("left", `${nodePosition.x}px`);
|
button.style.setProperty("left", `${nodePosition.x}px`);
|
||||||
button.style.setProperty("top", `${nodePosition.y}px`);
|
button.style.setProperty("top", `${nodePosition.y}px`);
|
||||||
button.style.setProperty(
|
button.style.setProperty("transform", `translate3d(-50%, -50%, 0) scale(${zoom})`);
|
||||||
"transform",
|
|
||||||
`translate3d(-50%, -50%, 0) scale(${zoom})`,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,13 +7,10 @@ import { FirstNodeField } from "./FirstNodeField";
|
|||||||
|
|
||||||
export const BranchingMap = () => {
|
export const BranchingMap = () => {
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const dragQuestionContentId = useUiTools(
|
const dragQuestionContentId = useUiTools((state) => state.dragQuestionContentId);
|
||||||
(state) => state.dragQuestionContentId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
id="cytoscape-container"
|
|
||||||
sx={{
|
sx={{
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
padding: "20px",
|
padding: "20px",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user