Skip to content

Commit

Permalink
Add Undo Keyboard Shortcut (#417)
Browse files Browse the repository at this point in the history
This PR adds the undo keyboard shortcut for Windows (`ctrl+z`) and Mac
(`cmd+z`) depending on the user's OS.

Note that we `preventDefault` behavior for this shortcut. In the context
of Studio in Storm, this means that when the user is focused on the
Studio iframe, the default behavior will be overridden by Studio `undo`.
But, when the user clicks out of the Studio iframe, default behavior
will resume.

J=SLAP-2954
TEST=manual, auto, SIS
  • Loading branch information
alextaing authored Oct 25, 2023
1 parent 3e4f672 commit 1e63832
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 3 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/studio-ui/.size-limit.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module.exports = [
{
path: "lib/**/*.js",
limit: "850 kB",
limit: "870 kB",
gzip: false,
},
];
3 changes: 2 additions & 1 deletion packages/studio-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,17 @@
"immer": "^9.0.21",
"lodash": "^4.17.21",
"path-browserify": "^1.0.1",
"platform": "^1.3.6",
"postcss": "^8.4.27",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-modal": "3.16.1",
"react-select": "^5.7.4",
"react-toastify": "^9.1.1",
"react-tooltip": "^5.18.0",
"true-myth": "^6.2.0",
"tailwind-merge": "^1.8.1",
"tailwindcss": "^3.3.3",
"true-myth": "^6.2.0",
"zundo": "2.0.0-beta.12",
"zustand": "^4.3.2"
},
Expand Down
20 changes: 19 additions & 1 deletion packages/studio-ui/src/components/UndoRedo.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import useTemporalStore from "../store/useTemporalStore";
import { ReactComponent as Undo } from "../icons/undo.svg";
import { useCallback } from "react";
import { useCallback, useEffect } from "react";
import classNames from "classnames";
import platform from "platform";

/**
* Buttons for undo and redo actions.
Expand All @@ -22,6 +23,23 @@ export default function UndoRedo(): JSX.Element {
redo();
}, [redo]);

const handleUndoKeydown = useCallback(
(event: KeyboardEvent) => {
const actionKey =
platform.os.family === "OS X" ? event.metaKey : event.ctrlKey;
if (actionKey && event.key === "z") {
event.preventDefault();
undo();
}
},
[undo]
);

useEffect(() => {
document.addEventListener("keydown", handleUndoKeydown);
return () => document.removeEventListener("keydown", handleUndoKeydown);
}, [handleUndoKeydown]);

const disableUndo = pastStates.length === 0;
const disableRedo = futureStates.length === 0;
const undoClasses = classNames("w-4", {
Expand Down
38 changes: 38 additions & 0 deletions packages/studio-ui/tests/components/UndoRedo.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import useStudioStore from "../../src/store/useStudioStore";
import useTemporalStore from "../../src/store/useTemporalStore";
import { searchBarComponent } from "../__fixtures__/componentStates";
import mockStore from "../__utils__/mockStore";
import platform from "platform";

jest.mock("platform", () => ({
__esModule: true,
default: {
os: {
family: null,
},
},
}));

describe("Undo/redo", () => {
beforeEach(() => {
Expand Down Expand Up @@ -46,6 +56,34 @@ describe("Undo/redo", () => {
expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined();
});

it("undoes last state update using control + z if OS is not OS X", async () => {
platform.os.family = "Windows";
render(<UndoRedo />);
expect(useStudioStore.getState().pages.activeComponentUUID).toBe(
"searchbar-uuid"
);
await userEvent.keyboard("{Meta>}z{/Meta}");
expect(useStudioStore.getState().pages.activeComponentUUID).toBe(
"searchbar-uuid"
);
await userEvent.keyboard("{Control>}z{/Control}");
expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined();
});

it("undoes last state update using command + z if OS is OS X", async () => {
platform.os.family = "OS X";
render(<UndoRedo />);
expect(useStudioStore.getState().pages.activeComponentUUID).toBe(
"searchbar-uuid"
);
await userEvent.keyboard("{Control>}z{/Control}");
expect(useStudioStore.getState().pages.activeComponentUUID).toBe(
"searchbar-uuid"
);
await userEvent.keyboard("{Meta>}z{/Meta}");
expect(useStudioStore.getState().pages.activeComponentUUID).toBeUndefined();
});

it("redoes single state update when redo is clicked", async () => {
const undo = useTemporalStore((store) => store.undo);
undo();
Expand Down

0 comments on commit 1e63832

Please sign in to comment.