@@ -1,7 +1,7 @@
|
|||||||
import {useSplitClient} from "@splitsoftware/splitio-react";
|
import {useSplitClient} from "@splitsoftware/splitio-react";
|
||||||
import {Button, Result} from "antd";
|
import {Button, Result} from "antd";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
import React, {lazy, Suspense, useEffect} from "react";
|
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {connect} from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import {Route, Routes} from "react-router-dom";
|
import {Route, Routes} from "react-router-dom";
|
||||||
@@ -19,6 +19,7 @@ import {checkUserSession} from "../redux/user/user.actions";
|
|||||||
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
import {selectBodyshop, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
|
import handleBeta from "../utils/betaHandler";
|
||||||
|
|
||||||
const ResetPassword = lazy(() =>
|
const ResetPassword = lazy(() =>
|
||||||
import("../pages/reset-password/reset-password.component")
|
import("../pages/reset-password/reset-password.component")
|
||||||
@@ -40,14 +41,16 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function App({
|
export function App({bodyshop, checkUserSession, currentUser, online, setOnline}) {
|
||||||
bodyshop,
|
|
||||||
checkUserSession,
|
|
||||||
currentUser,
|
|
||||||
online,
|
|
||||||
setOnline,
|
|
||||||
}) {
|
|
||||||
const client = useSplitClient().client;
|
const client = useSplitClient().client;
|
||||||
|
const [listenersAdded, setListenersAdded] = useState(false)
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
// Handle The Beta Switch.
|
||||||
|
useEffect(() => {
|
||||||
|
handleBeta();
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!navigator.onLine) {
|
if (!navigator.onLine) {
|
||||||
@@ -60,15 +63,28 @@ export function App({
|
|||||||
//const b = Grid.useBreakpoint();
|
//const b = Grid.useBreakpoint();
|
||||||
// console.log("Breakpoints:", b);
|
// console.log("Breakpoints:", b);
|
||||||
|
|
||||||
const {t} = useTranslation();
|
const offlineListener = (e) => {
|
||||||
|
|
||||||
window.addEventListener("offline", function (e) {
|
|
||||||
setOnline(false);
|
setOnline(false);
|
||||||
});
|
}
|
||||||
|
|
||||||
window.addEventListener("online", function (e) {
|
const onlineListener = (e) => {
|
||||||
setOnline(true);
|
setOnline(true);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||||
|
useEffect(() => {
|
||||||
|
if (!listenersAdded) {
|
||||||
|
console.log('Added events for offline and online');
|
||||||
|
window.addEventListener("offline", offlineListener);
|
||||||
|
window.addEventListener("online", onlineListener);
|
||||||
|
setListenersAdded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("offline", offlineListener);
|
||||||
|
window.removeEventListener("online", onlineListener);
|
||||||
|
}
|
||||||
|
}, [listenersAdded]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentUser.authorized && bodyshop) {
|
if (currentUser.authorized && bodyshop) {
|
||||||
|
|||||||
@@ -4,20 +4,11 @@ import { Button, notification, Space } from "antd";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||||
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import FcmHandler from "../../utils/fcm-handler";
|
import FcmHandler from "../../utils/fcm-handler";
|
||||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||||
import "./chat-affix.styles.scss";
|
import "./chat-affix.styles.scss";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
chatVisible: selectChatVisible,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -36,35 +27,34 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
console.log("FCM Topic Subscription", r.data);
|
console.log("FCM Topic Subscription", r.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(
|
console.log(
|
||||||
"Error attempting to subscribe to messaging topic: ",
|
"Error attempting to subscribe to messaging topic: ",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
notification.open({
|
notification.open({
|
||||||
type: "warning",
|
type: "warning",
|
||||||
message: t("general.errors.fcm"),
|
message: t("general.errors.fcm"),
|
||||||
btn: (
|
btn: (
|
||||||
<Space>
|
<Space>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await requestForToken();
|
await requestForToken();
|
||||||
|
SubscribeToTopic();
|
||||||
SubscribeToTopic();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{t("general.actions.tryagain")}
|
||||||
{t("general.actions.tryagain")}
|
</Button>
|
||||||
</Button>
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
onClick={() => {
|
const win = window.open(
|
||||||
const win = window.open(
|
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
||||||
"https://help.imex.online/en/article/enabling-notifications-o978xi/",
|
"_blank"
|
||||||
"_blank"
|
);
|
||||||
);
|
win.focus();
|
||||||
win.focus();
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{t("general.labels.help")}
|
||||||
{t("general.labels.help")}
|
</Button>
|
||||||
</Button>
|
</Space>
|
||||||
</Space>
|
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -81,16 +71,16 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
payload: (payload && payload.data && payload.data.data) || payload.data,
|
payload: (payload && payload.data && payload.data.data) || payload.data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let stopMessageListenr, channel;
|
let stopMessageListener, channel;
|
||||||
try {
|
try {
|
||||||
stopMessageListenr = onMessage(messaging, handleMessage);
|
stopMessageListener = onMessage(messaging, handleMessage);
|
||||||
channel = new BroadcastChannel("imex-sw-messages");
|
channel = new BroadcastChannel("imex-sw-messages");
|
||||||
channel.addEventListener("message", handleMessage);
|
channel.addEventListener("message", handleMessage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Unable to set event listeners.");
|
console.log("Unable to set event listeners.");
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
stopMessageListenr && stopMessageListenr();
|
stopMessageListener && stopMessageListener();
|
||||||
channel && channel.removeEventListener("message", handleMessage);
|
channel && channel.removeEventListener("message", handleMessage);
|
||||||
};
|
};
|
||||||
}, [client]);
|
}, [client]);
|
||||||
@@ -98,9 +88,10 @@ export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
|||||||
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
if (!bodyshop || !bodyshop.messagingservicesid) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
<div className={`chat-affix ${chatVisible ? "chat-affix-open" : ""}`}>
|
||||||
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
{bodyshop && bodyshop.messagingservicesid ? <ChatPopupComponent /> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, null)(ChatAffixContainer);
|
|
||||||
|
export default ChatAffixContainer;
|
||||||
@@ -12,7 +12,7 @@ import Icon, {
|
|||||||
FileAddOutlined,
|
FileAddOutlined,
|
||||||
FileFilled,
|
FileFilled,
|
||||||
HomeFilled,
|
HomeFilled,
|
||||||
ImportOutlined,
|
ImportOutlined, InfoCircleOutlined,
|
||||||
LineChartOutlined,
|
LineChartOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
@@ -25,8 +25,8 @@ import Icon, {
|
|||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||||
import {Layout, Menu} from "antd";
|
import {Layout, Menu, Switch, Tooltip} from "antd";
|
||||||
import React from "react";
|
import React, {useEffect, useState} from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {BsKanban} from "react-icons/bs";
|
import {BsKanban} from "react-icons/bs";
|
||||||
import {FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar,} from "react-icons/fa";
|
import {FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar,} from "react-icons/fa";
|
||||||
@@ -41,6 +41,7 @@ import {setModalContext} from "../../redux/modals/modals.actions";
|
|||||||
import {signOutStart} from "../../redux/user/user.actions";
|
import {signOutStart} from "../../redux/user/user.actions";
|
||||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||||
import {FiLogOut} from "react-icons/fi";
|
import {FiLogOut} from "react-icons/fi";
|
||||||
|
import handleBeta, {checkBeta, setBeta} from "../../utils/betaHandler";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -70,9 +71,21 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
|
|||||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||||
splitKey: bodyshop && bodyshop.imexshopid,
|
splitKey: bodyshop && bodyshop.imexshopid,
|
||||||
});
|
});
|
||||||
|
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isBeta = checkBeta();
|
||||||
|
setBetaSwitch(isBeta );
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
const betaSwitchChange = (checked) => {
|
||||||
|
setBeta(checked);
|
||||||
|
setBetaSwitch(checked);
|
||||||
|
handleBeta();
|
||||||
|
}
|
||||||
|
|
||||||
const accountingChildren = [
|
const accountingChildren = [
|
||||||
{
|
{
|
||||||
key: 'bills',
|
key: 'bills',
|
||||||
@@ -451,13 +464,28 @@ function Header({handleMenuClick, currentUser, bodyshop, selectedHeader, signOut
|
|||||||
|
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
]
|
];
|
||||||
|
|
||||||
|
|
||||||
|
menuItems.push({
|
||||||
|
key: 'beta-switch',
|
||||||
|
style: { marginLeft: 'auto' },
|
||||||
|
label: (
|
||||||
|
<Tooltip title="A faster more modern ImEX Online is ready for you to try! You can switch back at any time.">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
|
||||||
|
<Switch
|
||||||
|
checked={betaSwitch}
|
||||||
|
onChange={betaSwitchChange}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Header>
|
<Layout.Header>
|
||||||
<Menu
|
<Menu
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
//theme="light"
|
|
||||||
theme={"dark"}
|
theme={"dark"}
|
||||||
selectedKeys={[selectedHeader]}
|
selectedKeys={[selectedHeader]}
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {FloatButton, Layout} from "antd";
|
import {FloatButton, Layout} from "antd";
|
||||||
import preval from "preval.macro";
|
import preval from "preval.macro";
|
||||||
import React, {lazy, Suspense, useEffect} from "react";
|
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import {connect} from "react-redux";
|
import {connect} from "react-redux";
|
||||||
import {Link, Route, Routes} from "react-router-dom";
|
import {Link, Route, Routes} from "react-router-dom";
|
||||||
@@ -177,6 +177,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
|
|
||||||
export function Manage({conflict, bodyshop}) {
|
export function Manage({conflict, bodyshop}) {
|
||||||
const {t} = useTranslation();
|
const {t} = useTranslation();
|
||||||
|
const [chatVisible, setChatVisible] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const widgetId = "IABVNO4scRKY11XBQkNr";
|
const widgetId = "IABVNO4scRKY11XBQkNr";
|
||||||
@@ -360,7 +362,7 @@ export function Manage({conflict, bodyshop}) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatAffixContainer/>
|
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
||||||
<Layout className="layout-container">
|
<Layout className="layout-container">
|
||||||
<UpdateAlert/>
|
<UpdateAlert/>
|
||||||
<HeaderContainer/>
|
<HeaderContainer/>
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: rootReducer,
|
reducer: rootReducer,
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
|
||||||
serializableCheck: {
|
serializableCheck: false,
|
||||||
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
|
|
||||||
},
|
|
||||||
}).concat(middlewares),
|
}).concat(middlewares),
|
||||||
// middleware: middlewares,
|
// middleware: middlewares,
|
||||||
devTools: process.env.NODE_ENV !== 'production',
|
devTools: process.env.NODE_ENV !== 'production',
|
||||||
|
|||||||
32
client/src/utils/betaHandler.js
Normal file
32
client/src/utils/betaHandler.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
export const BETA_KEY = 'betaSwitchImex';
|
||||||
|
|
||||||
|
export const checkBeta = () => document.cookie.split('; ').find(row => row.startsWith(BETA_KEY)).split('=')[1] === 'true';
|
||||||
|
|
||||||
|
|
||||||
|
export const setBeta = (value) => {
|
||||||
|
const domain = window.location.hostname.split('.').slice(-2).join('.');
|
||||||
|
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handleBeta = () => {
|
||||||
|
// If the current host name does not start with beta or test, then we don't need to do anything.
|
||||||
|
if (window.location.hostname.startsWith('localhost')) {
|
||||||
|
console.log('Not on beta or test, so no need to handle beta.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isBeta = checkBeta();
|
||||||
|
|
||||||
|
const currentHostName = window.location.hostname;
|
||||||
|
|
||||||
|
// Beta is enabled, but the current host name does start with beta.
|
||||||
|
if (isBeta && !currentHostName.startsWith('beta')) {
|
||||||
|
window.location.href = `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beta is not enabled, but the current host name does start with beta.
|
||||||
|
else if (!isBeta && currentHostName.startsWith('beta')) {
|
||||||
|
window.location.href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default handleBeta;
|
||||||
@@ -1,129 +1,128 @@
|
|||||||
import { useEffect, useCallback, useReducer } from "react";
|
import {useCallback, useEffect, useReducer, useState} from "react";
|
||||||
//Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks
|
//Based on https://www.fullstacklabs.co/blog/keyboard-shortcuts-with-react-hooks
|
||||||
const blacklistedTargets = []; // ["INPUT", "TEXTAREA"];
|
const blacklistedTargets = []; // ["INPUT", "TEXTAREA"];
|
||||||
export const useKeyboardSaveShortcut = (callback) =>
|
export const useKeyboardSaveShortcut = (callback) =>
|
||||||
useKeyboardShortcut(["Control", "S"], callback, { overrideSystem: true });
|
useKeyboardShortcut(["Control", "S"], callback, {overrideSystem: true});
|
||||||
|
|
||||||
const keysReducer = (state, action) => {
|
const keysReducer = (state, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "set-key-down":
|
case "set-key-down":
|
||||||
const keydownState = { ...state, [action.key]: true };
|
const keydownState = {...state, [action.key]: true};
|
||||||
return keydownState;
|
return keydownState;
|
||||||
case "set-key-up":
|
case "set-key-up":
|
||||||
const keyUpState = { ...state, [action.key]: false };
|
const keyUpState = {...state, [action.key]: false};
|
||||||
return keyUpState;
|
return keyUpState;
|
||||||
case "reset-keys":
|
case "reset-keys":
|
||||||
const resetState = { ...action.data };
|
const resetState = {...action.data};
|
||||||
return resetState;
|
return resetState;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
|
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
|
||||||
if (!Array.isArray(shortcutKeys))
|
if (!Array.isArray(shortcutKeys))
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings."
|
"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 [listenersAdded, setListenersAdded] = useState(false);
|
||||||
|
|
||||||
|
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]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!shortcutKeys.length)
|
const keyupListener = useCallback(
|
||||||
throw new Error(
|
(assignedKey) => (keyupEvent) => {
|
||||||
"The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string."
|
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]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!callback || typeof callback !== "function")
|
useEffect(() => {
|
||||||
throw new Error(
|
if (!Object.values(keys).filter((value) => !value).length) {
|
||||||
"The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed."
|
callback(keys);
|
||||||
);
|
setKeys({type: "reset-keys", data: initalKeyMapping});
|
||||||
|
} else {
|
||||||
|
setKeys({type: null});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [callback, keys]);
|
||||||
|
|
||||||
const { overrideSystem } = options || {};
|
useEffect(() => {
|
||||||
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
|
if (!listenersAdded) {
|
||||||
currentKeys[key.toLowerCase()] = false;
|
console.log('Added events for keyup and keydown');
|
||||||
return currentKeys;
|
shortcutKeys.forEach((k) => {
|
||||||
}, {});
|
window.addEventListener("keydown", keydownListener(k));
|
||||||
|
window.addEventListener("keyup", keyupListener(k))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
|
setListenersAdded(true);
|
||||||
|
|
||||||
const keydownListener = useCallback(
|
return () => {
|
||||||
(assignedKey) => (keydownEvent) => {
|
shortcutKeys.forEach((k) => {
|
||||||
const loweredKey = assignedKey.toLowerCase();
|
window.removeEventListener("keydown", keydownListener(k));
|
||||||
|
window.removeEventListener("keyup", keyupListener(k));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [listenersAdded]);
|
||||||
|
|
||||||
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;
|
export default useKeyboardShortcut;
|
||||||
|
|
||||||
function disabledEventPropagation(e) {
|
function disabledEventPropagation(e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
if (e.stopPropagation) {
|
if (e.stopPropagation) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
} else if (window.event) {
|
} else if (window.event) {
|
||||||
window.event.cancelBubble = true;
|
window.event.cancelBubble = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user