use custom poppers

This commit is contained in:
nflnkr 2024-01-17 18:42:25 +03:00
parent 36ba2dfb61
commit 3889c06be1
4 changed files with 333 additions and 56 deletions

@ -9,22 +9,18 @@ import { cleardragQuestionContentId, setModalQuestionParentContentId, setModalQu
import { useUiTools } from "@root/uiTools/store";
import { ProblemIcon } from "@ui_kit/ProblemIcon";
import type { Core } from "cytoscape";
import Cytoscape from "cytoscape";
import popper from "cytoscape-popper";
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 { usePopper } from "./hooks/usePopper";
import { useRemoveNode } from "./hooks/useRemoveNode";
import "./style/styles.css";
import { stylesheet } from "./style/stylesheet";
Cytoscape.use(popper);
function CsComponent() {
const desireToOpenABranchingModal = useUiTools(state => state.desireToOpenABranchingModal);
const canCreatePublic = useUiTools(state => state.canCreatePublic);
@ -32,10 +28,9 @@ function CsComponent() {
const modalQuestionTargetContentId = useUiTools(state => state.modalQuestionTargetContentId);
const trashQuestions = useQuestionsStore(state => state.questions);
const cyRef = useRef<Core | null>(null);
const { recreatePoppers, removeAllPoppers } = usePopper({ cyRef });
const { removeNode } = useRemoveNode({ cyRef });
const cyElements = useMemo(() => {
const csElements = useMemo(() => {
const questions = trashQuestions.filter(
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
);
@ -73,53 +68,17 @@ function CsComponent() {
return () => {
document.removeEventListener("pointerup", cleardragQuestionContentId);
removeAllPoppers();
};
}, []);
useEffect(function removePoppersOnDrag() {
const cy = cyRef.current;
if (!cy) return;
let isPointerDown = false;
let isDragging = false;
const onPointerDown = () => {
isPointerDown = true;
};
const onPointerUp = () => {
if (isDragging) {
isDragging = false;
recreatePoppers();
}
isPointerDown = false;
};
const handleMove = () => {
if (isPointerDown) {
isDragging = true;
removeAllPoppers();
}
};
cy.on("vmousedown", onPointerDown);
cy.on("vmousemove", handleMove);
document.addEventListener("pointerup", onPointerUp);
return () => {
cy.off("vmousedown", onPointerDown);
cy.off("vmousemove", handleMove);
document.removeEventListener("pointerup", onPointerUp);
};
}, [recreatePoppers, removeAllPoppers]);
useEffect(() => {
useEffect(function rerunLayout() {
cyRef.current?.layout(layoutOptions).run();
cyRef.current?.fit(undefined, 70);
recreatePoppers();
}, [cyElements, recreatePoppers]);
}, [csElements]);
return (
<>
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
<Box mb="20px">
<Button
sx={{
@ -130,7 +89,7 @@ function CsComponent() {
}}
variant="text"
onClick={() => {
cyRef.current?.fit();
cyRef.current?.fit(undefined, 70);
}}
>
Выровнять
@ -140,10 +99,9 @@ function CsComponent() {
onClick={() => updateModalInfoWhyCantCreate(true)}
/>
</Box>
<CytoscapeComponent
wheelSensitivity={0.1}
elements={cyElements}
elements={csElements}
style={{
height: "480px",
background: "#F2F3F7",
@ -155,6 +113,8 @@ function CsComponent() {
cyRef.current = cy;
}}
autoungrabify={true}
autounselectify={true}
boxSelectionEnabled={false}
/>
<DeleteNodeModal removeNode={removeNode} />
</>

File diff suppressed because one or more lines are too long

@ -4,35 +4,60 @@ import { createResult, getQuestionByContentId, updateQuestion } from "@root/ques
import { useQuestionsStore } from "@root/questions/store";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { useUiTools } from "@root/uiTools/store";
import { PresetLayoutOptions } from "cytoscape";
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.title
}
label,
},
classes: "multiline-auto",
});
// nodes.push({
// data: {

@ -27,6 +27,7 @@ type NodeSingularWithPopper = NodeSingular & {
popper: (config: PopperConfig) => PopperInstance;
};
/** @deprecated */
export const usePopper = ({
cyRef,
}: {