Migrate to a monorepo structure (#2909)

This commit is contained in:
Charles Bochet
2023-12-10 18:10:54 +01:00
committed by GitHub
parent a70a9281eb
commit 5bdca9de6c
2304 changed files with 37152 additions and 25869 deletions

View File

@ -0,0 +1,22 @@
import { Keys } from 'react-hotkeys-hook';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
type HotkeyEffectProps = {
hotkey: {
key: Keys;
scope: string;
};
onHotkeyTriggered: () => void;
};
export const HotkeyEffect = ({
hotkey,
onHotkeyTriggered,
}: HotkeyEffectProps) => {
useScopedHotkeys(hotkey.key, () => onHotkeyTriggered(), hotkey.scope, [
onHotkeyTriggered,
]);
return <></>;
};

View File

@ -0,0 +1,18 @@
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope';
export const DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES: CustomHotkeyScopes = {
commandMenu: true,
goto: false,
keyboardShortcutMenu: false,
};
export const INITIAL_HOTKEYS_SCOPE: HotkeyScope = {
scope: AppHotkeyScope.App,
customScopes: {
commandMenu: true,
goto: true,
keyboardShortcutMenu: true,
},
};

View File

@ -0,0 +1,25 @@
import { Keys } from 'react-hotkeys-hook/dist/types';
import { useNavigate } from 'react-router-dom';
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { useSequenceHotkeys } from './useSequenceScopedHotkeys';
export const useGoToHotkeys = (key: Keys, location: string) => {
const navigate = useNavigate();
useSequenceHotkeys(
'g',
key,
() => {
navigate(location);
},
AppHotkeyScope.Goto,
{
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
[navigate],
);
};

View File

@ -0,0 +1,50 @@
import { useRecoilCallback } from 'recoil';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { previousHotkeyScopeState } from '../states/internal/previousHotkeyScopeState';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { useSetHotkeyScope } from './useSetHotkeyScope';
export const usePreviousHotkeyScope = () => {
const setHotkeyScope = useSetHotkeyScope();
const goBackToPreviousHotkeyScope = useRecoilCallback(
({ snapshot, set }) =>
() => {
const previousHotkeyScope = snapshot
.getLoadable(previousHotkeyScopeState)
.valueOrThrow();
if (!previousHotkeyScope) {
return;
}
setHotkeyScope(
previousHotkeyScope.scope,
previousHotkeyScope.customScopes,
);
set(previousHotkeyScopeState, null);
},
[setHotkeyScope],
);
const setHotkeyScopeAndMemorizePreviousScope = useRecoilCallback(
({ snapshot, set }) =>
(scope: string, customScopes?: CustomHotkeyScopes) => {
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.valueOrThrow();
setHotkeyScope(scope, customScopes);
set(previousHotkeyScopeState, currentHotkeyScope);
},
[setHotkeyScope],
);
return {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
};
};

View File

@ -0,0 +1,65 @@
import { Hotkey } from 'react-hotkeys-hook/dist/types';
import { useRecoilCallback } from 'recoil';
import { logDebug } from '~/utils/logDebug';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
const DEBUG_HOTKEY_SCOPE = true;
export const useScopedHotkeyCallback = () =>
useRecoilCallback(
({ snapshot }) =>
({
callback,
hotkeysEvent,
keyboardEvent,
scope,
preventDefault = true,
}: {
keyboardEvent: KeyboardEvent;
hotkeysEvent: Hotkey;
callback: (keyboardEvent: KeyboardEvent, hotkeysEvent: Hotkey) => void;
scope: string;
preventDefault?: boolean;
}) => {
const currentHotkeyScopes = snapshot
.getLoadable(internalHotkeysEnabledScopesState)
.valueOrThrow();
if (!currentHotkeyScopes.includes(scope)) {
if (DEBUG_HOTKEY_SCOPE) {
logDebug(
`%cI can't call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}]`,
'color: gray; ',
);
}
return;
}
if (DEBUG_HOTKEY_SCOPE) {
logDebug(
`%cI can call hotkey (${
hotkeysEvent.keys
}) because I'm in scope [${scope}] and the active scopes are : [${currentHotkeyScopes.join(
', ',
)}]`,
'color: green;',
);
}
if (preventDefault) {
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
keyboardEvent.stopImmediatePropagation();
}
return callback(keyboardEvent, hotkeysEvent);
},
[],
);

View File

@ -0,0 +1,52 @@
import { useHotkeys } from 'react-hotkeys-hook';
import {
HotkeyCallback,
Keys,
Options,
OptionsOrDependencyArray,
} from 'react-hotkeys-hook/dist/types';
import { useRecoilState } from 'recoil';
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
export const useScopedHotkeys = (
keys: Keys,
callback: HotkeyCallback,
scope: string,
dependencies?: OptionsOrDependencyArray,
options: Options = {
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
) => {
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
const callScopedHotkeyCallback = useScopedHotkeyCallback();
return useHotkeys(
keys,
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
if (!pendingHotkey) {
callback(keyboardEvent, hotkeysEvent);
return;
}
setPendingHotkey(null);
},
scope,
preventDefault: !!options.preventDefault,
});
},
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
dependencies,
);
};

View File

@ -0,0 +1,76 @@
import { Options, useHotkeys } from 'react-hotkeys-hook';
import { Keys } from 'react-hotkeys-hook/dist/types';
import { useRecoilState } from 'recoil';
import { pendingHotkeyState } from '../states/internal/pendingHotkeysState';
import { useScopedHotkeyCallback } from './useScopedHotkeyCallback';
export const useSequenceHotkeys = (
firstKey: Keys,
secondKey: Keys,
sequenceCallback: () => void,
scope: string,
options: Options = {
enableOnContentEditable: true,
enableOnFormTags: true,
preventDefault: true,
},
deps: any[] = [],
) => {
const [pendingHotkey, setPendingHotkey] = useRecoilState(pendingHotkeyState);
const callScopedHotkeyCallback = useScopedHotkeyCallback();
useHotkeys(
firstKey,
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
setPendingHotkey(firstKey);
},
scope,
preventDefault: !!options.preventDefault,
});
},
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[setPendingHotkey, scope],
);
useHotkeys(
secondKey,
(keyboardEvent, hotkeysEvent) => {
callScopedHotkeyCallback({
keyboardEvent,
hotkeysEvent,
callback: () => {
if (pendingHotkey !== firstKey) {
return;
}
setPendingHotkey(null);
if (options.preventDefault) {
keyboardEvent.stopImmediatePropagation();
keyboardEvent.stopPropagation();
keyboardEvent.preventDefault();
}
sequenceCallback();
},
scope,
preventDefault: false,
});
},
{
enableOnContentEditable: options.enableOnContentEditable,
enableOnFormTags: options.enableOnFormTags,
},
[pendingHotkey, setPendingHotkey, scope, ...deps],
);
};

View File

@ -0,0 +1,81 @@
import { useRecoilCallback } from 'recoil';
import { isDefined } from '~/utils/isDefined';
import { DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES } from '../constants';
import { currentHotkeyScopeState } from '../states/internal/currentHotkeyScopeState';
import { internalHotkeysEnabledScopesState } from '../states/internal/internalHotkeysEnabledScopesState';
import { AppHotkeyScope } from '../types/AppHotkeyScope';
import { CustomHotkeyScopes } from '../types/CustomHotkeyScope';
import { HotkeyScope } from '../types/HotkeyScope';
const isCustomScopesEqual = (
customScopesA: CustomHotkeyScopes | undefined,
customScopesB: CustomHotkeyScopes | undefined,
) => {
return (
customScopesA?.commandMenu === customScopesB?.commandMenu &&
customScopesA?.goto === customScopesB?.goto &&
customScopesA?.keyboardShortcutMenu === customScopesB?.keyboardShortcutMenu
);
};
export const useSetHotkeyScope = () =>
useRecoilCallback(
({ snapshot, set }) =>
async (hotkeyScopeToSet: string, customScopes?: CustomHotkeyScopes) => {
const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState)
.valueOrThrow();
if (currentHotkeyScope.scope === hotkeyScopeToSet) {
if (!isDefined(customScopes)) {
if (
isCustomScopesEqual(
currentHotkeyScope?.customScopes,
DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES,
)
) {
return;
}
} else {
if (
isCustomScopesEqual(
currentHotkeyScope?.customScopes,
customScopes,
)
) {
return;
}
}
}
const newHotkeyScope: HotkeyScope = {
scope: hotkeyScopeToSet,
customScopes: {
commandMenu: customScopes?.commandMenu ?? true,
goto: customScopes?.goto ?? false,
keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? false,
},
};
const scopesToSet: string[] = [];
if (newHotkeyScope.customScopes?.commandMenu) {
scopesToSet.push(AppHotkeyScope.CommandMenu);
}
if (newHotkeyScope?.customScopes?.goto) {
scopesToSet.push(AppHotkeyScope.Goto);
}
if (newHotkeyScope?.customScopes?.keyboardShortcutMenu) {
scopesToSet.push(AppHotkeyScope.KeyboardShortcutMenu);
}
scopesToSet.push(newHotkeyScope.scope);
set(internalHotkeysEnabledScopesState, scopesToSet);
set(currentHotkeyScopeState, newHotkeyScope);
},
[],
);

View File

@ -0,0 +1,9 @@
import { atom } from 'recoil';
import { INITIAL_HOTKEYS_SCOPE } from '../../constants';
import { HotkeyScope } from '../../types/HotkeyScope';
export const currentHotkeyScopeState = atom<HotkeyScope>({
key: 'currentHotkeyScopeState',
default: INITIAL_HOTKEYS_SCOPE,
});

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const internalHotkeysEnabledScopesState = atom<string[]>({
key: 'internalHotkeysEnabledScopesState',
default: [],
});

View File

@ -0,0 +1,7 @@
import { Keys } from 'react-hotkeys-hook/dist/types';
import { atom } from 'recoil';
export const pendingHotkeyState = atom<Keys | null>({
key: 'pendingHotkeyState',
default: null,
});

View File

@ -0,0 +1,8 @@
import { atom } from 'recoil';
import { HotkeyScope } from '../../types/HotkeyScope';
export const previousHotkeyScopeState = atom<HotkeyScope | null>({
key: 'previousHotkeyScopeState',
default: null,
});

View File

@ -0,0 +1,6 @@
export enum AppHotkeyScope {
App = 'app',
Goto = 'goto',
CommandMenu = 'command-menu',
KeyboardShortcutMenu = 'keyboard-shortcut-menu',
}

View File

@ -0,0 +1,5 @@
export type CustomHotkeyScopes = {
goto?: boolean;
commandMenu?: boolean;
keyboardShortcutMenu?: boolean;
};

View File

@ -0,0 +1,6 @@
import { CustomHotkeyScopes } from './CustomHotkeyScope';
export type HotkeyScope = {
scope: string;
customScopes?: CustomHotkeyScopes;
};

View File

@ -0,0 +1,57 @@
export const isNonTextWritingKey = (key: string) => {
const nonTextWritingKeys = [
'Enter',
'Tab',
'Shift',
'Escape',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'Delete',
'Backspace',
'F1',
'F2',
'F3',
'F4',
'F5',
'F6',
'F7',
'F8',
'F9',
'F10',
'F11',
'F12',
'Meta',
'Alt',
'Control',
'CapsLock',
'NumLock',
'ScrollLock',
'Pause',
'Insert',
'Home',
'PageUp',
'Delete',
'End',
'PageDown',
'ContextMenu',
'PrintScreen',
'BrowserBack',
'BrowserForward',
'BrowserRefresh',
'BrowserStop',
'BrowserSearch',
'BrowserFavorites',
'BrowserHome',
'VolumeMute',
'VolumeDown',
'VolumeUp',
'MediaTrackNext',
'MediaTrackPrevious',
'MediaStop',
'MediaPlayPause',
];
return nonTextWritingKeys.includes(key);
};