github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/packages/pyroscope-flamegraph/src/FlameGraph/FlameGraphComponent/ContextMenu.tsx (about)

     1  /* eslint-disable react/jsx-props-no-spreading, import/no-extraneous-dependencies */
     2  import React from 'react';
     3  import { ControlledMenu, useMenuState } from '@webapp/ui/Menu';
     4  import styles from './ContextMenu.module.scss';
     5  
     6  type xyToMenuItems = (x: number, y: number) => JSX.Element[];
     7  
     8  export interface ContextMenuProps {
     9    canvasRef: React.RefObject<HTMLCanvasElement>;
    10  
    11    /**
    12     * The menu is built dynamically
    13     * Based on the cell's contents
    14     * only MenuItem and SubMenu should be supported
    15     */
    16    xyToMenuItems: xyToMenuItems;
    17  
    18    onClose: () => void;
    19    onOpen: (x: number, y: number) => void;
    20  }
    21  
    22  export default function ContextMenu(props: ContextMenuProps) {
    23    const [menuProps, toggleMenu] = useMenuState({ transition: true });
    24    const [anchorPoint, setAnchorPoint] = React.useState({ x: 0, y: 0 });
    25    const { canvasRef } = props;
    26    const [menuItems, setMenuItems] = React.useState<JSX.Element[]>([]);
    27    const {
    28      xyToMenuItems,
    29      onClose: onCloseCallback,
    30      onOpen: onOpenCallback,
    31    } = props;
    32  
    33    const onClose = () => {
    34      toggleMenu(false);
    35  
    36      onCloseCallback();
    37    };
    38  
    39    React.useEffect(() => {
    40      toggleMenu(false);
    41  
    42      // use closure to "cache" the current canvas reference
    43      // so that when cleaning up, it points to a valid canvas
    44      // (otherwise it would be null)
    45      const canvasEl = canvasRef.current;
    46      if (!canvasEl) {
    47        return () => {};
    48      }
    49  
    50      const onContextMenu = (e: MouseEvent) => {
    51        e.preventDefault();
    52  
    53        const items = xyToMenuItems(e.offsetX, e.offsetY);
    54        setMenuItems(items);
    55  
    56        // TODO
    57        // if the menu becomes too large, it may overflow to outside the screen
    58        const x = e.clientX;
    59        const y = e.clientY + 20;
    60  
    61        setAnchorPoint({ x, y });
    62        toggleMenu(true);
    63  
    64        onOpenCallback(e.offsetX, e.offsetY);
    65      };
    66  
    67      // watch for mouse events on the bar
    68      canvasEl.addEventListener('contextmenu', onContextMenu);
    69  
    70      return () => {
    71        canvasEl.removeEventListener('contextmenu', onContextMenu);
    72      };
    73    }, [xyToMenuItems]);
    74  
    75    return (
    76      <ControlledMenu
    77        {...menuProps}
    78        className={styles.dummy}
    79        anchorPoint={anchorPoint}
    80        onClose={onClose}
    81      >
    82        {menuItems}
    83      </ControlledMenu>
    84    );
    85  }