265 lines
10 KiB
TypeScript
265 lines
10 KiB
TypeScript
import { cleardragQuestionContentId, setModalQuestionParentContentId, setOpenedModalQuestions, updateDeleteId, updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
|
import type { AbstractEventObject, Core, NodeSingular, SingularData } from "cytoscape";
|
|
import { getPopperInstance } from "cytoscape-popper";
|
|
import { type MutableRefObject } from "react";
|
|
import { addNode } from "../helper";
|
|
|
|
type PopperItem = {
|
|
id: () => string;
|
|
};
|
|
|
|
type Modifier = {
|
|
name: string;
|
|
options: unknown;
|
|
};
|
|
|
|
type PopperConfig = {
|
|
popper: {
|
|
placement: string;
|
|
modifiers?: Modifier[];
|
|
};
|
|
content: (items: PopperItem[]) => void;
|
|
};
|
|
|
|
type PopperInstance = ReturnType<getPopperInstance<SingularData>>;
|
|
|
|
type NodeSingularWithPopper = NodeSingular & {
|
|
popper: (config: PopperConfig) => PopperInstance;
|
|
};
|
|
|
|
export const usePopper = ({
|
|
cyRef,
|
|
quizId,
|
|
popperContainerRef,
|
|
popperInstancesRef,
|
|
runCyLayout,
|
|
}: {
|
|
cyRef: MutableRefObject<Core | null>;
|
|
quizId: number | undefined,
|
|
popperContainerRef: MutableRefObject<HTMLDivElement | null>;
|
|
popperInstancesRef: MutableRefObject<PopperInstance[]>;
|
|
runCyLayout: () => void;
|
|
}) => {
|
|
const removePoppersById = (id: string) => {
|
|
popperContainerRef.current?.querySelector(`.popper-layout[data-id='${id}']`)?.remove();
|
|
};
|
|
|
|
const removeAllPoppers = () => {
|
|
cyRef.current?.removeListener("zoom render");
|
|
|
|
popperInstancesRef.current.forEach(p => p.destroy());
|
|
popperInstancesRef.current = [];
|
|
popperContainerRef.current?.remove();
|
|
popperContainerRef.current = null;
|
|
};
|
|
|
|
const createPoppers = () => {
|
|
removeAllPoppers();
|
|
|
|
const cy = cyRef.current;
|
|
if (!cy) return;
|
|
|
|
const container = cy.container();
|
|
|
|
if (!container) {
|
|
console.warn("Cannot create popper container");
|
|
return;
|
|
}
|
|
|
|
if (!popperContainerRef.current) {
|
|
popperContainerRef.current = document.createElement("div");
|
|
popperContainerRef.current.setAttribute("id", "poppers-container");
|
|
container.append(popperContainerRef.current);
|
|
}
|
|
|
|
cy.nodes().forEach((item) => {
|
|
const node = item as NodeSingularWithPopper;
|
|
|
|
const layoutsPopper = node.popper({
|
|
popper: {
|
|
placement: "left",
|
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
},
|
|
content: (items) => {
|
|
const item = items[0];
|
|
const itemId = item.id();
|
|
const itemElement = popperContainerRef.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);
|
|
});
|
|
popperContainerRef.current?.appendChild(layoutElement);
|
|
|
|
return layoutElement;
|
|
},
|
|
});
|
|
popperInstancesRef.current.push(layoutsPopper);
|
|
|
|
const plusesPopper = node.popper({
|
|
popper: {
|
|
placement: "right",
|
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
},
|
|
content: ([item]) => {
|
|
const itemId = item.id();
|
|
const itemElement = popperContainerRef.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", () => {
|
|
if (!cy || !quizId) return;
|
|
|
|
const es = addNode({
|
|
cy,
|
|
quizId,
|
|
parentNodeContentId: node.id(),
|
|
});
|
|
runCyLayout();
|
|
if (es) cy.fit(es, 100);
|
|
cleardragQuestionContentId();
|
|
});
|
|
|
|
popperContainerRef.current?.appendChild(plusElement);
|
|
|
|
return plusElement;
|
|
},
|
|
});
|
|
popperInstancesRef.current.push(plusesPopper);
|
|
|
|
const crossesPopper = node.popper({
|
|
popper: {
|
|
placement: "top-end",
|
|
modifiers: [{ name: "flip", options: { boundary: node } }],
|
|
},
|
|
content: ([item]) => {
|
|
const itemId = item.id();
|
|
const itemElement = popperContainerRef.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";
|
|
popperContainerRef.current?.appendChild(crossElement);
|
|
crossElement.addEventListener("mouseup", () => {
|
|
updateDeleteId(node.id());
|
|
});
|
|
|
|
return crossElement;
|
|
},
|
|
});
|
|
popperInstancesRef.current.push(crossesPopper);
|
|
|
|
let gearsPopper: PopperInstance | 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 = popperContainerRef.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";
|
|
popperContainerRef.current?.appendChild(gearElement);
|
|
gearElement.addEventListener("mouseup", () => {
|
|
updateOpenedModalSettingsId(item.id());
|
|
});
|
|
|
|
return gearElement;
|
|
},
|
|
});
|
|
popperInstancesRef.current.push(gearsPopper);
|
|
}
|
|
|
|
const onZoom = (event: AbstractEventObject) => {
|
|
if (event.cy.data("dragging")) return;
|
|
const zoom = event.cy.zoom();
|
|
|
|
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] } },
|
|
],
|
|
});
|
|
gearsPopper?.setOptions({
|
|
modifiers: [
|
|
{ name: "flip", options: { boundary: node } },
|
|
{ name: "offset", options: { offset: [0, 0] } },
|
|
],
|
|
});
|
|
|
|
popperContainerRef.current?.querySelectorAll(".popper-layout").forEach((item) => {
|
|
const element = item as HTMLDivElement;
|
|
element.style.width = `${130 * zoom}px`;
|
|
element.style.height = `${130 * zoom}px`;
|
|
});
|
|
|
|
popperContainerRef.current?.querySelectorAll(".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`;
|
|
});
|
|
|
|
popperContainerRef.current?.querySelectorAll(".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`;
|
|
});
|
|
|
|
popperContainerRef?.current?.querySelectorAll(".popper-gear").forEach((item) => {
|
|
const element = item as HTMLDivElement;
|
|
element.style.width = `${60 * zoom}px`;
|
|
element.style.height = `${40 * zoom}px`;
|
|
});
|
|
};
|
|
|
|
cy.on("zoom render", onZoom);
|
|
});
|
|
};
|
|
|
|
|
|
return { removeAllPoppers, removePoppersById, createPoppers };
|
|
};
|