From 0ee7b73d61ed289c96c5966980e2e3f465be768b Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sat, 18 Jun 2022 11:22:37 +0100 Subject: [PATCH] feat: re-work modal behaviour to be more natural --- package.json | 2 +- src/context/modals/ModalRenderer.tsx | 19 ++++++++++++++++++- src/context/modals/components/Changelog.tsx | 4 +++- .../modals/components/MFAEnableTOTP.tsx | 5 ++++- src/context/modals/components/MFAFlow.tsx | 16 +++++++++++++++- src/context/modals/components/MFARecovery.tsx | 4 +++- src/context/modals/index.tsx | 18 ++++++++++++++++++ src/context/modals/types.ts | 1 + src/mobx/State.ts | 1 - src/pages/settings/GenericSettings.tsx | 4 ++++ yarn.lock | 10 +++++----- 11 files changed, 72 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 4db1eacf..62141717 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "@hcaptcha/react-hcaptcha": "^0.3.6", "@insertish/vite-plugin-babel-macros": "^1.0.5", "@preact/preset-vite": "^2.0.0", - "@revoltchat/ui": "1.0.40", + "@revoltchat/ui": "1.0.43", "@rollup/plugin-replace": "^2.4.2", "@styled-icons/boxicons-logos": "^10.38.0", "@styled-icons/boxicons-regular": "^10.38.0", diff --git a/src/context/modals/ModalRenderer.tsx b/src/context/modals/ModalRenderer.tsx index ab5c0582..0092d93b 100644 --- a/src/context/modals/ModalRenderer.tsx +++ b/src/context/modals/ModalRenderer.tsx @@ -1,5 +1,22 @@ import { observer } from "mobx-react-lite"; +import { useEffect } from "preact/hooks"; + import { modalController } from "."; -export default observer(() => modalController.rendered); +export default observer(() => { + useEffect(() => { + function keyUp(event: KeyboardEvent) { + if (event.key === "Escape") { + modalController.pop("close"); + } else if (event.key === "Enter") { + modalController.pop("confirm"); + } + } + + document.addEventListener("keyup", keyUp); + return () => document.removeEventListener("keyup", keyUp); + }, []); + + return modalController.rendered; +}); diff --git a/src/context/modals/components/Changelog.tsx b/src/context/modals/components/Changelog.tsx index 059ed776..fd6c0498 100644 --- a/src/context/modals/components/Changelog.tsx +++ b/src/context/modals/components/Changelog.tsx @@ -40,6 +40,7 @@ function RenderLog({ post }: { post: ChangelogPost }) { export default function Changelog({ initial, onClose, + signal, }: ModalProps<"changelog">) { const [log, setLog] = useState(initial); @@ -86,7 +87,8 @@ export default function Changelog({ ) } actions={actions} - onClose={onClose}> + onClose={onClose} + signal={signal}> {entry ? ( ) : ( diff --git a/src/context/modals/components/MFAEnableTOTP.tsx b/src/context/modals/components/MFAEnableTOTP.tsx index 0e18ef74..32caa8fd 100644 --- a/src/context/modals/components/MFAEnableTOTP.tsx +++ b/src/context/modals/components/MFAEnableTOTP.tsx @@ -31,6 +31,7 @@ export default function MFAEnableTOTP({ secret, callback, onClose, + signal, }: ModalProps<"mfa_enable_totp">) { const uri = `otpauth://totp/Revolt:${identifier}?secret=${secret}&issuer=Revolt`; const [value, setValue] = useState(""); @@ -61,7 +62,9 @@ export default function MFAEnableTOTP({ onClose={() => { callback(); onClose(); - }}> + }} + signal={signal} + nonDismissable> diff --git a/src/context/modals/components/MFAFlow.tsx b/src/context/modals/components/MFAFlow.tsx index ab0a0180..21bd2e0f 100644 --- a/src/context/modals/components/MFAFlow.tsx +++ b/src/context/modals/components/MFAFlow.tsx @@ -81,7 +81,11 @@ function ResponseEntry({ /** * MFA ticket creation flow */ -export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { +export default function MFAFlow({ + onClose, + signal, + ...props +}: ModalProps<"mfa_flow">) { const [methods, setMethods] = useState( props.state === "unknown" ? props.available_methods : undefined, ); @@ -178,6 +182,16 @@ export default function MFAFlow({ onClose, ...props }: ModalProps<"mfa_flow">) { }, ] } + // If we are logging in or have selected a method, + // don't allow the user to dismiss the modal by clicking off. + // This is to just generally prevent annoying situations + // where you accidentally close the modal while logging in + // or when switching to your password manager. + nonDismissable={ + props.state === "unknown" || + typeof selectedMethod !== "undefined" + } + signal={signal} onClose={() => { props.callback(); onClose(); diff --git a/src/context/modals/components/MFARecovery.tsx b/src/context/modals/components/MFARecovery.tsx index 27cc37ca..bed9afd1 100644 --- a/src/context/modals/components/MFARecovery.tsx +++ b/src/context/modals/components/MFARecovery.tsx @@ -32,6 +32,7 @@ export default function MFARecovery({ codes, client, onClose, + signal, }: ModalProps<"mfa_recovery">) { // Keep track of changes to recovery codes const [known, setCodes] = useState(codes); @@ -69,7 +70,8 @@ export default function MFARecovery({ onClick: reset, }, ]} - onClose={onClose}> + onClose={onClose} + signal={signal}> {known.map((code) => ( {code} diff --git a/src/context/modals/index.tsx b/src/context/modals/index.tsx index dd10f78d..61bc3e01 100644 --- a/src/context/modals/index.tsx +++ b/src/context/modals/index.tsx @@ -31,8 +31,10 @@ class ModalController { makeObservable(this, { stack: observable, push: action, + pop: action, remove: action, rendered: computed, + isVisible: computed, }); } @@ -50,6 +52,16 @@ class ModalController { ]; } + /** + * Remove the top modal from the screen + * @param signal What action to trigger + */ + pop(signal: "close" | "confirm" | "force") { + this.stack = this.stack.map((entry, index) => + index === this.stack.length - 1 ? { ...entry, signal } : entry, + ); + } + /** * Remove the keyed modal from the stack */ @@ -66,6 +78,8 @@ class ModalController { {this.stack.map((modal) => { const Component = this.components[modal.type]; return ( + // ESLint does not understand spread operator + // eslint-disable-next-line this.remove(modal.key!)} @@ -75,6 +89,10 @@ class ModalController { ); } + + get isVisible() { + return this.stack.length > 0; + } } /** diff --git a/src/context/modals/types.ts b/src/context/modals/types.ts index 5b1e061d..be3127af 100644 --- a/src/context/modals/types.ts +++ b/src/context/modals/types.ts @@ -39,4 +39,5 @@ export type Modal = { export type ModalProps = Modal & { type: T } & { onClose: () => void; + signal?: "close" | "confirm"; }; diff --git a/src/mobx/State.ts b/src/mobx/State.ts index 6a9c7cfb..ffc2ebbb 100644 --- a/src/mobx/State.ts +++ b/src/mobx/State.ts @@ -150,7 +150,6 @@ export default class State { () => stringify(store.toJSON()), async (value) => { try { - console.log(id, "updated!"); // Save updated store to local storage. await localforage.setItem(id, JSON.parse(value)); diff --git a/src/pages/settings/GenericSettings.tsx b/src/pages/settings/GenericSettings.tsx index f4f4fc34..0c6e5d94 100644 --- a/src/pages/settings/GenericSettings.tsx +++ b/src/pages/settings/GenericSettings.tsx @@ -13,6 +13,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { useApplicationState } from "../../mobx/State"; +import { modalController } from "../../context/modals"; + import ButtonItem from "../../components/navigation/items/ButtonItem"; interface Props { @@ -61,6 +63,8 @@ export function GenericSettings({ useEffect(() => { function keyDown(e: KeyboardEvent) { if (e.key === "Escape") { + if (modalController.isVisible) return; + exitSettings(); } } diff --git a/yarn.lock b/yarn.lock index 4ebbce01..88c78fb3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2231,9 +2231,9 @@ __metadata: languageName: node linkType: hard -"@revoltchat/ui@npm:1.0.40": - version: 1.0.40 - resolution: "@revoltchat/ui@npm:1.0.40" +"@revoltchat/ui@npm:1.0.43": + version: 1.0.43 + resolution: "@revoltchat/ui@npm:1.0.43" dependencies: "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0 @@ -2246,7 +2246,7 @@ __metadata: react-device-detect: "*" react-virtuoso: "*" revolt.js: "*" - checksum: bc0bc906cdb22e8a31c862d1e87f8bd5c46cb463aa23ad773e9c683514fbe0e52ac44e9eab41dd6aa6e8e207050f9ab0590d6e51b2a4d8af6c0fb2ea899d789f + checksum: d6a6d0cb4a2f08fea45a4d61e5599894012fbb591472ef95d34ee8ddc9e66cfdc7626e94360b7c104e59d3c64a7d0bd674d6a42f5c3cefc723574db8c1aee64e languageName: node linkType: hard @@ -3539,7 +3539,7 @@ __metadata: "@hcaptcha/react-hcaptcha": ^0.3.6 "@insertish/vite-plugin-babel-macros": ^1.0.5 "@preact/preset-vite": ^2.0.0 - "@revoltchat/ui": 1.0.40 + "@revoltchat/ui": 1.0.43 "@rollup/plugin-replace": ^2.4.2 "@styled-icons/boxicons-logos": ^10.38.0 "@styled-icons/boxicons-regular": ^10.38.0