github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/webapp/javascript/components/TimelineChart/ContextMenu.plugin.tsx (about)

     1  import React from 'react';
     2  import * as ReactDOM from 'react-dom';
     3  import { randomId } from '@webapp/util/randomId';
     4  
     5  // Pre calculated once
     6  // TODO(eh-am): does this work with multiple contextMenus?
     7  const WRAPPER_ID = randomId('context_menu');
     8  
     9  export interface ContextMenuProps {
    10    click: {
    11      /** The X position in the window where the click originated */
    12      pageX: number;
    13      /** The Y position in the window where the click originated */
    14      pageY: number;
    15    };
    16    timestamp: number;
    17    containerEl: HTMLElement;
    18  }
    19  
    20  (function ($: JQueryStatic) {
    21    function init(plot: jquery.flot.plot & jquery.flot.plotOptions) {
    22      const placeholder = plot.getPlaceholder();
    23  
    24      function onClick(
    25        event: unknown,
    26        pos: { x: number; pageX: number; pageY: number }
    27      ) {
    28        const options: jquery.flot.plotOptions & {
    29          ContextMenu?: React.FC<ContextMenuProps>;
    30        } = plot.getOptions();
    31        const container = inject($);
    32        const containerEl = container?.[0];
    33  
    34        // unmount any previous menus
    35        ReactDOM.unmountComponentAtNode(containerEl);
    36  
    37        const ContextMenu = options?.ContextMenu;
    38  
    39        if (ContextMenu && containerEl) {
    40          ReactDOM.render(
    41            <ContextMenu
    42              click={{ ...pos }}
    43              containerEl={containerEl}
    44              timestamp={Math.round(pos.x / 1000)}
    45            />,
    46            containerEl
    47          );
    48        }
    49      }
    50  
    51      // Register events and shutdown
    52      // It's important to bind/unbind to the SAME element
    53      // Since a plugin may be register/unregistered multiple times due to react re-rendering
    54      plot.hooks!.bindEvents!.push(function () {
    55        placeholder.bind('plotclick', onClick);
    56      });
    57  
    58      plot.hooks!.shutdown!.push(function () {
    59        placeholder.unbind('plotclick', onClick);
    60  
    61        const container = inject($);
    62  
    63        ReactDOM.unmountComponentAtNode(container?.[0]);
    64      });
    65    }
    66  
    67    $.plot.plugins.push({
    68      init,
    69      options: {},
    70      name: 'context_menu',
    71      version: '1.0',
    72    });
    73  })(jQuery);
    74  
    75  function inject($: JQueryStatic) {
    76    const alreadyInitialized = $(`#${WRAPPER_ID}`).length > 0;
    77  
    78    if (alreadyInitialized) {
    79      return $(`#${WRAPPER_ID}`);
    80    }
    81  
    82    const body = $('body');
    83    return $(`<div id="${WRAPPER_ID}" />`).appendTo(body);
    84  }