mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 08:30:58 -05:00
feat: re-work modal behaviour to be more natural
This commit is contained in:
parent
63d5f6bb7d
commit
0ee7b73d61
11 changed files with 72 additions and 12 deletions
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
10
yarn.lock
10
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
|
||||
|
|
Loading…
Reference in a new issue