IO-177 Added key modifier hooks.
This commit is contained in:
@@ -45,6 +45,9 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
|
|||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import useKeyboardShortcut, {
|
||||||
|
useKeyboardSaveShortcut,
|
||||||
|
} from "../../utils/useKeyboardShortcut";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -77,6 +80,8 @@ export function JobsDetailPage({
|
|||||||
form.resetFields();
|
form.resetFields();
|
||||||
}, [form, job]);
|
}, [form, job]);
|
||||||
|
|
||||||
|
useKeyboardSaveShortcut(form.submit);
|
||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await mutationUpdateJob({
|
const result = await mutationUpdateJob({
|
||||||
|
|||||||
129
client/src/utils/useKeyboardShortcut.jsx
Normal file
129
client/src/utils/useKeyboardShortcut.jsx
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { useEffect, useCallback, useReducer } from "react";
|
||||||
|
//Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks
|
||||||
|
const blacklistedTargets = []; // ["INPUT", "TEXTAREA"];
|
||||||
|
export const useKeyboardSaveShortcut = (callback) =>
|
||||||
|
useKeyboardShortcut(["Control", "S"], callback, { overrideSystem: true });
|
||||||
|
|
||||||
|
const keysReducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case "set-key-down":
|
||||||
|
const keydownState = { ...state, [action.key]: true };
|
||||||
|
return keydownState;
|
||||||
|
case "set-key-up":
|
||||||
|
const keyUpState = { ...state, [action.key]: false };
|
||||||
|
return keyUpState;
|
||||||
|
case "reset-keys":
|
||||||
|
const resetState = { ...action.data };
|
||||||
|
return resetState;
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
|
||||||
|
if (!Array.isArray(shortcutKeys))
|
||||||
|
throw new Error(
|
||||||
|
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!shortcutKeys.length)
|
||||||
|
throw new Error(
|
||||||
|
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!callback || typeof callback !== "function")
|
||||||
|
throw new Error(
|
||||||
|
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
|
||||||
|
);
|
||||||
|
|
||||||
|
const { overrideSystem } = options || {};
|
||||||
|
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
|
||||||
|
currentKeys[key.toLowerCase()] = false;
|
||||||
|
return currentKeys;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
|
||||||
|
|
||||||
|
const keydownListener = useCallback(
|
||||||
|
(assignedKey) => (keydownEvent) => {
|
||||||
|
const loweredKey = assignedKey.toLowerCase();
|
||||||
|
|
||||||
|
if (keydownEvent.repeat) return;
|
||||||
|
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
|
||||||
|
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
|
||||||
|
if (keys[loweredKey] === undefined) return;
|
||||||
|
|
||||||
|
if (overrideSystem) {
|
||||||
|
keydownEvent.preventDefault();
|
||||||
|
disabledEventPropagation(keydownEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeys({ type: "set-key-down", key: loweredKey });
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[keys, overrideSystem]
|
||||||
|
);
|
||||||
|
|
||||||
|
const keyupListener = useCallback(
|
||||||
|
(assignedKey) => (keyupEvent) => {
|
||||||
|
const raisedKey = assignedKey.toLowerCase();
|
||||||
|
|
||||||
|
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
|
||||||
|
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
|
||||||
|
if (keys[raisedKey] === undefined) return;
|
||||||
|
|
||||||
|
if (overrideSystem) {
|
||||||
|
keyupEvent.preventDefault();
|
||||||
|
disabledEventPropagation(keyupEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeys({ type: "set-key-up", key: raisedKey });
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[keys, overrideSystem]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!Object.values(keys).filter((value) => !value).length) {
|
||||||
|
callback(keys);
|
||||||
|
setKeys({ type: "reset-keys", data: initalKeyMapping });
|
||||||
|
} else {
|
||||||
|
setKeys({ type: null });
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [callback, keys]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
shortcutKeys.forEach((k) =>
|
||||||
|
window.addEventListener("keydown", keydownListener(k))
|
||||||
|
);
|
||||||
|
return () =>
|
||||||
|
shortcutKeys.forEach((k) =>
|
||||||
|
window.removeEventListener("keydown", keydownListener(k))
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
shortcutKeys.forEach((k) =>
|
||||||
|
window.addEventListener("keyup", keyupListener(k))
|
||||||
|
);
|
||||||
|
return () =>
|
||||||
|
shortcutKeys.forEach((k) =>
|
||||||
|
window.removeEventListener("keyup", keyupListener(k))
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useKeyboardShortcut;
|
||||||
|
|
||||||
|
function disabledEventPropagation(e) {
|
||||||
|
if (e) {
|
||||||
|
if (e.stopPropagation) {
|
||||||
|
e.stopPropagation();
|
||||||
|
} else if (window.event) {
|
||||||
|
window.event.cancelBubble = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user