Line data Source code
1 : /*
2 : * Copyright (C) 2019 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 6 : import React from "react";
7 :
8 : import { Menu, MenuContent } from "@patternfly/react-core/dist/esm/components/Menu";
9 :
10 : import "context-menu.scss";
11 :
12 : /*
13 : * A context menu component
14 : *
15 : * It has two properties:
16 : * - parentId (required), area in which it listens to left button click
17 : * - children (optional), a MenuList to be rendered in the context menu
18 : */
19 6 : export const ContextMenu = ({ parentId, children } : {
20 : parentId: string,
21 : children?: React.ReactNode,
22 6 : }) => {
23 6 : const [visible, setVisible] = React.useState(false);
24 6 : const [event, setEvent] = React.useState<MouseEvent | null>(null);
25 6 : const root = React.useRef<HTMLDivElement>(null);
26 :
27 6 : React.useEffect(() => {
28 0 : const _handleContextMenu = (event: MouseEvent) => {
29 : /* In Firefox they explicitly prevent us from interrupting when holding shift while
30 : * right-clicking for context. Lets make it default for all browsers so they can inspect et. al.
31 : */
32 0 : if (event.shiftKey) {
33 0 : setVisible(false);
34 0 : return;
35 0 : }
36 0 : event.preventDefault();
37 :
38 0 : setVisible(true);
39 0 : setEvent(event);
40 0 : };
41 :
42 3 : const _handleClick = (event: MouseEvent) => {
43 3 : if (event.button === 0)
44 3 : setVisible(false);
45 3 : };
46 :
47 6 : const parent = document.getElementById(parentId)!;
48 6 : parent.addEventListener('contextmenu', _handleContextMenu);
49 6 : document.addEventListener('click', _handleClick);
50 :
51 1 : return () => {
52 1 : parent.removeEventListener('contextmenu', _handleContextMenu);
53 1 : document.removeEventListener('click', _handleClick);
54 1 : };
55 6 : }, [parentId]);
56 :
57 6 : React.useEffect(() => {
58 1 : if (!event || !root.current)
59 6 : return;
60 :
61 1 : const clickX = event.clientX;
62 1 : const clickY = event.clientY;
63 1 : const screenW = window.innerWidth;
64 1 : const screenH = window.innerHeight;
65 1 : const rootW = root.current.offsetWidth;
66 1 : const rootH = root.current.offsetHeight;
67 :
68 1 : const right = (screenW - clickX) > rootW;
69 1 : const left = !right;
70 1 : const top = (screenH - clickY) > rootH;
71 1 : const bottom = !top;
72 :
73 1 : if (right) {
74 1 : root.current.style.left = `${clickX + 5}px`;
75 1 : }
76 :
77 1 : if (left) {
78 1 : root.current.style.left = `${clickX - rootW - 5}px`;
79 1 : }
80 :
81 1 : if (top) {
82 1 : root.current.style.top = `${clickY + 5}px`;
83 1 : }
84 :
85 1 : if (bottom) {
86 1 : root.current.style.top = `${clickY - rootH - 5}px`;
87 1 : }
88 6 : }, [event]);
89 :
90 6 : return visible &&
91 1 : <Menu ref={root} className="contextMenu">
92 1 : <MenuContent ref={root}>
93 1 : {children}
94 1 : </MenuContent>
95 1 : </Menu>;
96 6 : };
|