mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-06 17:11:55 +00:00
feat: re-work modal behaviour to be more natural
This commit is contained in:
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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} />
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,4 +39,5 @@ export type Modal = {
|
||||
|
||||
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {
|
||||
onClose: () => void;
|
||||
signal?: "close" | "confirm";
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user