frontPanel/src/pages/Questions/BranchingMap/hooks/usePopper.ts
2024-01-05 19:48:35 +03:00

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