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 }