import { Add, Close } from "@mui/icons-material"; import { Box, IconButton } from "@mui/material"; import { cleardragQuestionContentId, setModalQuestionParentContentId, setOpenedModalQuestions, updateDeleteId, updateOpenedModalSettingsId, } from "@root/uiTools/actions"; import { Core, EventObject, NodeSingular } from "cytoscape"; import { MutableRefObject, 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; type CsButtonType = (typeof csButtonTypes)[number]; type CsNodeButtonsByType = Partial< Record >; type CsButtonsById = Record; interface Props { csElements: ReturnType; cyRef: MutableRefObject; } export default function CsNodeButtons({ csElements, cyRef }: Props) { const buttonRefsById = useRef({}); const buttons = useMemo(() => { const nodeElements = csElements.filter(isElementANode); buttonRefsById.current = nodeElements.reduce( (acc, node) => ((acc[node.data.id] = {}), acc), {}, ); return ( {nodeElements.flatMap((csElement) => [ { const buttonData = buttonRefsById.current[csElement.data.id]; if (buttonData) buttonData.delete = r; }} onClick={() => { updateDeleteId(csElement.data.id); }} />, { const buttonData = buttonRefsById.current[csElement.data.id]; if (buttonData) buttonData.add = r; }} onPointerUp={() => { addNode({ parentNodeContentId: csElement.data.id }); cleardragQuestionContentId(); }} />, !csElement.data.isRoot && !isQuestionProhibited(csElement.data.qtype) && ( { const buttonData = buttonRefsById.current[csElement.data.id]; if (buttonData) buttonData.settings = r; }} onClick={() => { updateOpenedModalSettingsId(csElement.data.id); }} /> ), //оболочка узла { const buttonData = buttonRefsById.current[csElement.data.id]; if (buttonData) buttonData.select = r; }} onClick={() => { setModalQuestionParentContentId(csElement.data.id); console.log("csElement ", csElement); setOpenedModalQuestions( !( isQuestionProhibited(csElement.data.type) && csElement.data.children > 0 ), ); }} />, ])} ); }, [csElements]); useEffect(function attachViewportHandler() { const cy = cyRef.current; if (!cy) return; let rafId: number = 0; function handleViewportChange(event: EventObject) { cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { for (const nodeId in buttonRefsById.current) { const buttonsByType = buttonRefsById.current[nodeId]; if (!buttonsByType) continue; const node = event.cy.$id(nodeId); if (!node) { console.warn("Could not find node for id:" + nodeId); continue; } for (const buttonType of csButtonTypes) { const button = buttonsByType[buttonType]; if (!button) continue; applyButtonStyleByType[buttonType](button, node, event.cy.zoom()); } } }); } cy.on("viewport", handleViewportChange); return () => { cy.off("viewport", handleViewportChange); }; }, []); const container = cyRef.current?.container(); const buttonsPortal = container ? createPortal(buttons, container) : null; return buttonsPortal; } const applyButtonStyleByType: Record< CsButtonType, (button: HTMLButtonElement, node: NodeSingular, zoom: number) => void > = { delete(button, node, zoom) { const nodePosition = node.renderedPosition(); const shiftX = node.renderedWidth() / 2 - (CLOSE_BUTTON_WIDTH / 2 + 6) * zoom; const shiftY = node.renderedHeight() / 2 - (CLOSE_BUTTON_HEIGHT / 2 + 6) * zoom; if (!isNodeInViewport(node, 100)) { return button.style.setProperty("display", "none"); } button.style.setProperty("display", "flex"); button.style.setProperty("left", `${nodePosition.x}px`); button.style.setProperty("top", `${nodePosition.y}px`); button.style.setProperty( "transform", `translate3d(calc(-50% + ${shiftX}px), calc(-50% - ${shiftY}px), 0) scale(${zoom})`, ); }, add(button, node, zoom) { const nodePosition = node.renderedPosition(); const shiftX = node.renderedWidth() / 2 + (ADD_BUTTON_WIDTH / 2) * zoom; if (!isNodeInViewport(node, 100)) { return button.style.setProperty("display", "none"); } button.style.setProperty("display", "flex"); button.style.setProperty("left", `${nodePosition.x}px`); button.style.setProperty("top", `${nodePosition.y}px`); button.style.setProperty( "transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`, ); }, settings(button, node, zoom) { const nodePosition = node.renderedPosition(); const shiftX = -node.renderedWidth() / 2 - (SETTINGS_BUTTON_WIDTH / 2) * zoom; if (!isNodeInViewport(node, 100)) { return button.style.setProperty("display", "none"); } button.style.setProperty("display", "flex"); button.style.setProperty("left", `${nodePosition.x}px`); button.style.setProperty("top", `${nodePosition.y}px`); button.style.setProperty( "transform", `translate3d(calc(-50% + ${shiftX}px), -50%, 0) scale(${zoom})`, ); }, select(button, node, zoom) { const nodePosition = node.renderedPosition(); if (!isNodeInViewport(node, 100)) { return button.style.setProperty("display", "none"); } button.style.setProperty("display", "flex"); button.style.setProperty("left", `${nodePosition.x}px`); button.style.setProperty("top", `${nodePosition.y}px`); button.style.setProperty( "transform", `translate3d(-50%, -50%, 0) scale(${zoom})`, ); }, }; const CLOSE_BUTTON_WIDTH = 24; const CLOSE_BUTTON_HEIGHT = CLOSE_BUTTON_WIDTH; const CsDeleteButton = forwardRef< HTMLButtonElement, { onClick: () => void; } >(({ onClick }, ref) => ( event.stopPropagation()} onTouchStartCapture={(event) => event.stopPropagation()} > )); const ADD_BUTTON_WIDTH = 40; const ADD_BUTTON_HEIGHT = ADD_BUTTON_WIDTH; const CsAddButton = forwardRef< HTMLButtonElement, { onPointerUp: () => void; } >(({ onPointerUp }, ref) => ( event.stopPropagation()} onTouchStartCapture={(event) => event.stopPropagation()} > )); const SETTINGS_BUTTON_WIDTH = 70; const SETTINGS_BUTTON_HEIGHT = 60; const CsSettingsButton = forwardRef< HTMLButtonElement, { onClick: () => void; } >(({ onClick }, ref) => ( event.stopPropagation()} onTouchStartCapture={(event) => event.stopPropagation()} > )); const SELECT_BUTTON_WIDTH = 130; const SELECT_BUTTON_HEIGHT = SELECT_BUTTON_WIDTH; const CsSelectButton = forwardRef< HTMLButtonElement, { onClick: () => void; } >(({ onClick }, ref) => ( event.stopPropagation()} onTouchStartCapture={(event) => event.stopPropagation()} /> ));