Line data Source code
1 : /*
2 : * Copyright (C) 2019 Red Hat, Inc.
3 : * SPDX-License-Identifier: LGPL-2.1-or-later
4 : */
5 :
6 12 : 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 12 : export const ContextMenu = ({ parentId, children } : {
20 : parentId: string,
21 : children?: React.ReactNode,
22 12 : }) => {
23 12 : const [visible, setVisible] = React.useState(false);
24 12 : const [event, setEvent] = React.useState<MouseEvent | null>(null);
25 12 : const root = React.useRef<HTMLDivElement>(null);
26 :
27 12 : 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 12 : const parent = document.getElementById(parentId)!;
48 12 : parent.addEventListener('contextmenu', _handleContextMenu);
49 12 : document.addEventListener('click', _handleClick);
50 :
51 1 : return () => {
52 1 : parent.removeEventListener('contextmenu', _handleContextMenu);
53 1 : document.removeEventListener('click', _handleClick);
54 1 : };
55 12 : }, [parentId]);
56 :
57 12 : React.useEffect(() => {
58 2 : if (!event || !root.current)
59 12 : return;
60 :
61 2 : const clickX = event.clientX;
62 2 : const clickY = event.clientY;
63 2 : const screenW = window.innerWidth;
64 2 : const screenH = window.innerHeight;
65 2 : const rootW = root.current.offsetWidth;
66 2 : const rootH = root.current.offsetHeight;
67 :
68 2 : const right = (screenW - clickX) > rootW;
69 2 : const left = !right;
70 2 : const top = (screenH - clickY) > rootH;
71 2 : const bottom = !top;
72 :
73 2 : if (right) {
74 2 : root.current.style.left = `${clickX + 5}px`;
75 2 : }
76 :
77 2 : if (left) {
78 2 : root.current.style.left = `${clickX - rootW - 5}px`;
79 2 : }
80 :
81 2 : if (top) {
82 2 : root.current.style.top = `${clickY + 5}px`;
83 2 : }
84 :
85 2 : if (bottom) {
86 2 : root.current.style.top = `${clickY - rootH - 5}px`;
87 2 : }
88 12 : }, [event]);
89 :
90 12 : return visible &&
91 2 : <Menu ref={root} className="contextMenu">
92 2 : <MenuContent ref={root}>
93 2 : {children}
94 2 : </MenuContent>
95 2 : </Menu>;
96 12 : };
|