This repository has been archived by the owner on Mar 24, 2024. It is now read-only.
/
GlobalKeyListener.ts
128 lines (117 loc) · 4.58 KB
/
GlobalKeyListener.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright 2018-2021 Cruise LLC
//
// This source code is licensed under the Apache License, Version 2.0,
// found at http://www.apache.org/licenses/LICENSE-2.0
// You may not use this file except in compliance with the License.
import { useCallback, useMemo, useContext } from "react";
import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { redoLayoutChange, undoLayoutChange } from "@foxglove-studio/app/actions/layoutHistory";
import { ScreenshotsContext } from "@foxglove-studio/app/components/Screenshots/ScreenshotsProvider";
import useEventListener from "@foxglove-studio/app/hooks/useEventListener";
import { downloadFiles } from "@foxglove-studio/app/util";
const inNativeUndoRedoElement = (eventTarget: EventTarget) => {
if (eventTarget instanceof HTMLTextAreaElement) {
let element: Element | null | undefined = eventTarget;
// It's not always convenient to set the data property on the textarea itself, but we can set
// it on a nearby ancestor.
while (element) {
if (element instanceof HTMLElement && element.dataset.nativeundoredo) {
return true;
}
element = element.parentElement;
}
}
return false;
};
type Props = {
openSaveLayoutModal?: () => void;
openLayoutModal?: () => void;
openShortcutsModal?: () => void;
history: any;
};
export default function GlobalKeyListener({
openSaveLayoutModal,
openLayoutModal,
history,
}: Props) {
const dispatch = useDispatch();
const actions = useMemo(
() => bindActionCreators({ redoLayoutChange, undoLayoutChange }, dispatch),
[dispatch],
);
const { takeScreenshot } = useContext(ScreenshotsContext);
const keyDownHandler: (arg0: KeyboardEvent) => void = useCallback(
(e) => {
const target = e.target;
if (
target instanceof HTMLInputElement ||
target instanceof HTMLTextAreaElement ||
(target instanceof HTMLElement && target.isContentEditable)
) {
// The user is typing in an editable field; ignore the event.
return;
}
const lowercaseEventKey = e.key.toLowerCase();
if (e.key === "?") {
history.push(`/help${window.location.search}`);
}
if (!(e.ctrlKey || e.metaKey)) {
return;
}
if (lowercaseEventKey === "z") {
// Don't use ctrl-Z for layout history actions inside the Monaco Editor. It isn't
// controlled, and changes inside it don't result in updates to the Redux state. We could
// consider making the editor controlled, with a separate "unsaved state".
if (inNativeUndoRedoElement(e.target as any)) {
return;
}
// Use e.shiftKey instead of e.key to decide between undo and redo because of capslock.
e.stopPropagation();
e.preventDefault();
if (e.shiftKey) {
actions.redoLayoutChange();
} else {
actions.undoLayoutChange();
}
} else if (lowercaseEventKey === "s" && openSaveLayoutModal) {
e.preventDefault();
openSaveLayoutModal();
} else if (lowercaseEventKey === "e" && openLayoutModal) {
e.preventDefault();
openLayoutModal();
} else if (lowercaseEventKey === "/") {
e.preventDefault();
history.push(`/shortcuts${window.location.search}`);
} else if (lowercaseEventKey === "j" && process.env.NODE_ENV !== "production") {
// TODO (DWinegar): Remove this key listener once we get the screenshots for comments working.
e.preventDefault();
const element = document.querySelector(".PanelLayout-root");
if (!element) {
throw new Error(
`takeScreenshot could not find element with selector ".PanelLayout-root"`,
);
}
takeScreenshot(element as any)
.then((blob) => {
if (blob) {
downloadFiles([{ blob, fileName: "screenshot.png" }]);
}
})
.catch((error) => console.warn(error));
}
},
[openSaveLayoutModal, openLayoutModal, history, actions, takeScreenshot],
);
// Not using KeyListener because we want to preventDefault on [ctrl+z] but not on [z], and we want
// to handle events when text areas have focus.
useEventListener(document, "keydown", true, keyDownHandler, [keyDownHandler]);
return null;
}