feat: re-work modal behaviour to be more natural

This commit is contained in:
Paul Makles
2022-06-18 11:22:37 +01:00
parent 12217ae2a8
commit bfd9439f27
11 changed files with 72 additions and 12 deletions

View File

@@ -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;
});

View File

@@ -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 ? (
<RenderLog post={entry} />
) : (

View File

@@ -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>
<Column>
<Centred>
<Qr>

View File

@@ -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<API.MFAMethod[] | undefined>(
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();

View File

@@ -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}>
<List>
{known.map((code) => (
<span key={code}>{code}</span>

View File

@@ -31,8 +31,10 @@ class ModalController<T extends Modal> {
makeObservable(this, {
stack: observable,
push: action,
pop: action,
remove: action,
rendered: computed,
isVisible: computed,
});
}
@@ -50,6 +52,16 @@ class ModalController<T extends Modal> {
];
}
/**
* 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<T extends Modal> {
{this.stack.map((modal) => {
const Component = this.components[modal.type];
return (
// ESLint does not understand spread operator
// eslint-disable-next-line
<Component
{...modal}
onClose={() => this.remove(modal.key!)}
@@ -75,6 +89,10 @@ class ModalController<T extends Modal> {
</>
);
}
get isVisible() {
return this.stack.length > 0;
}
}
/**

View File

@@ -39,4 +39,5 @@ export type Modal = {
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {
onClose: () => void;
signal?: "close" | "confirm";
};

View File

@@ -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));

View File

@@ -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();
}
}