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>; type NodeSingularWithPopper = NodeSingular & { popper: (config: PopperConfig) => PopperInstance; }; export const usePopper = ({ cyRef, quizId, popperContainerRef, popperInstancesRef, runCyLayout, }: { cyRef: MutableRefObject; quizId: number | undefined, popperContainerRef: MutableRefObject; popperInstancesRef: MutableRefObject; 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 }; };