diff --git a/src/appState.ts b/src/appState.ts index e25c5929..25382159 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -39,6 +39,7 @@ export function getDefaultAppState(): AppState { selectedElementIds: {}, collaborators: new Map(), shouldCacheIgnoreZoom: false, + showShortcutsDialog: false, }; } @@ -55,6 +56,7 @@ export function clearAppStateForLocalStorage(appState: AppState) { isCollaborating, isLoading, errorMessage, + showShortcutsDialog, ...exportedState } = appState; return exportedState; diff --git a/src/components/App.tsx b/src/components/App.tsx index 764fd10c..913a6992 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1006,6 +1006,12 @@ export class App extends React.Component { return; } + if (event.key === KEYS.QUESTION_MARK) { + this.setState({ + showShortcutsDialog: true, + }); + } + if (event.code === "KeyC" && event.altKey && event.shiftKey) { this.copyToClipboardAsPng(); event.preventDefault(); diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx index 2786b198..fdca0215 100644 --- a/src/components/LayerUI.tsx +++ b/src/components/LayerUI.tsx @@ -23,6 +23,7 @@ import { ZoomActions, SelectedShapeActions, ShapesSwitcher } from "./Actions"; import { Section } from "./Section"; import { RoomDialog } from "./RoomDialog"; import { ErrorDialog } from "./ErrorDialog"; +import { ShortcutsDialog } from "./ShortcutsDialog"; import { LoadingMessage } from "./LoadingMessage"; interface LayerUIProps { @@ -112,6 +113,11 @@ export const LayerUI = React.memo( onClose={() => setAppState({ errorMessage: null })} /> )} + {appState.showShortcutsDialog && ( + setAppState({ showShortcutsDialog: null })} + /> + )}
diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index 0b23670d..6614924a 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -28,7 +28,14 @@ export function Modal(props: { aria-labelledby={props.labelledBy} >
-
+
{props.children}
, diff --git a/src/components/ShortcutsDialog.tsx b/src/components/ShortcutsDialog.tsx new file mode 100644 index 00000000..7854625c --- /dev/null +++ b/src/components/ShortcutsDialog.tsx @@ -0,0 +1,228 @@ +import React from "react"; +import { t } from "../i18n"; +import { isDarwin } from "../keys"; +import { Dialog } from "./Dialog"; +import { getShortcutKey } from "../utils"; + +const ShortcutIsland = (props: { + title: string; + children: React.ReactNode; +}) => ( +
+

+ {props.title} +

+ {props.children} +
+); + +const Shortcut = (props: { title: string; shortcuts: string[] }) => ( +
+
+
+ {props.title} +
+
+ {props.shortcuts.map((shortcut) => ( + {shortcut} + ))} +
+
+
+); + +const ShortcutKey = (props: { children: React.ReactNode }) => ( + +); + +const Footer = () => ( + +); + +export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => { + const handleClose = React.useCallback(() => { + if (onClose) { + onClose(); + } + }, [onClose]); + + return ( + <> + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + ); +}; diff --git a/src/keys.ts b/src/keys.ts index 09b73f38..e91aba12 100644 --- a/src/keys.ts +++ b/src/keys.ts @@ -12,6 +12,7 @@ export const KEYS = { CTRL_OR_CMD: isDarwin ? "metaKey" : "ctrlKey", TAB: "Tab", SPACE: " ", + QUESTION_MARK: "?", } as const; export type Key = keyof typeof KEYS; diff --git a/src/locales/en.json b/src/locales/en.json index 25ffb32a..4214b521 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -127,5 +127,14 @@ }, "errorDialog": { "title": "Error" + }, + "shortcutsDialog": { + "title": "Keyboard shortcuts", + "shapes": "Shapes", + "editor": "Editor", + "view": "View", + "blog": "Read our blog", + "howto": "Follow our guides", + "github": "Found an issue? Submit" } } diff --git a/src/styles.scss b/src/styles.scss index fd0886e9..f16b9cb8 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -22,6 +22,16 @@ body { cursor: text; } +a { + font-weight: 500; + text-decoration: none; + color: #1c7ed6; /* OC Blue 7 */ + + &:hover { + text-decoration: underline; + } +} + canvas { touch-action: none; user-select: none; diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 1238676f..1aba0993 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -37,6 +37,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -224,6 +225,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -335,6 +337,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -587,6 +590,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -735,6 +739,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -917,6 +922,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -1106,6 +1112,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -1386,6 +1393,7 @@ Object { "selectedElementIds": Object {}, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -1985,6 +1993,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2096,6 +2105,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2207,6 +2217,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2318,6 +2329,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2451,6 +2463,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2584,6 +2597,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2717,6 +2731,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2828,6 +2843,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -2939,6 +2955,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -3072,6 +3089,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -3183,6 +3201,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": true, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -3252,6 +3271,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -3930,6 +3950,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -4291,6 +4312,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -4580,6 +4602,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -4797,6 +4820,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -4958,6 +4982,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -5607,6 +5632,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -6184,6 +6210,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -6689,6 +6716,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -7123,6 +7151,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -7520,6 +7549,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -7845,6 +7875,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -8098,6 +8129,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -8295,6 +8327,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -8980,6 +9013,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -9593,6 +9627,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -10134,6 +10169,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -10599,6 +10635,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -10838,6 +10875,7 @@ Object { "selectedElementIds": Object {}, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -10891,6 +10929,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": true, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -10944,6 +10983,7 @@ Object { }, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } @@ -11225,6 +11265,7 @@ Object { "selectedElementIds": Object {}, "selectionElement": null, "shouldCacheIgnoreZoom": false, + "showShortcutsDialog": false, "viewBackgroundColor": "#ffffff", "zoom": 1, } diff --git a/src/types.ts b/src/types.ts index 44c1fca8..87865064 100644 --- a/src/types.ts +++ b/src/types.ts @@ -56,6 +56,7 @@ export type AppState = { } >; shouldCacheIgnoreZoom: boolean; + showShortcutsDialog: boolean; }; export type PointerCoords = Readonly<{ diff --git a/src/utils.ts b/src/utils.ts index f5b09ff4..2c05a8b2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -144,16 +144,17 @@ export function resetCursor() { document.documentElement.style.cursor = ""; } -export const getShortcutKey = (shortcut: string): string => { +export const getShortcutKey = (shortcut: string, prefix = " — "): string => { const isMac = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform); if (isMac) { - return ` — ${shortcut + return `${prefix}${shortcut .replace("CtrlOrCmd+", "⌘") .replace("Alt+", "⌥") .replace("Ctrl+", "⌃") - .replace("Shift+", "⇧")}`; + .replace("Shift+", "⇧") + .replace("Del", "⌫")}`; } - return ` — ${shortcut.replace("CtrlOrCmd", "Ctrl")}`; + return `${prefix}${shortcut.replace("CtrlOrCmd", "Ctrl")}`; }; export function viewportCoordsToSceneCoords( { clientX, clientY }: { clientX: number; clientY: number },