mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-25 16:40: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",
|
"@hcaptcha/react-hcaptcha": "^0.3.6",
|
||||||
"@insertish/vite-plugin-babel-macros": "^1.0.5",
|
"@insertish/vite-plugin-babel-macros": "^1.0.5",
|
||||||
"@preact/preset-vite": "^2.0.0",
|
"@preact/preset-vite": "^2.0.0",
|
||||||
"@revoltchat/ui": "1.0.40",
|
"@revoltchat/ui": "1.0.43",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
"@styled-icons/boxicons-logos": "^10.38.0",
|
"@styled-icons/boxicons-logos": "^10.38.0",
|
||||||
"@styled-icons/boxicons-regular": "^10.38.0",
|
"@styled-icons/boxicons-regular": "^10.38.0",
|
||||||
|
|
|
@ -1,5 +1,22 @@
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { modalController } from ".";
|
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({
|
export default function Changelog({
|
||||||
initial,
|
initial,
|
||||||
onClose,
|
onClose,
|
||||||
|
signal,
|
||||||
}: ModalProps<"changelog">) {
|
}: ModalProps<"changelog">) {
|
||||||
const [log, setLog] = useState(initial);
|
const [log, setLog] = useState(initial);
|
||||||
|
|
||||||
|
@ -86,7 +87,8 @@ export default function Changelog({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
onClose={onClose}>
|
onClose={onClose}
|
||||||
|
signal={signal}>
|
||||||
{entry ? (
|
{entry ? (
|
||||||
<RenderLog post={entry} />
|
<RenderLog post={entry} />
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -31,6 +31,7 @@ export default function MFAEnableTOTP({
|
||||||
secret,
|
secret,
|
||||||
callback,
|
callback,
|
||||||
onClose,
|
onClose,
|
||||||
|
signal,
|
||||||
}: ModalProps<"mfa_enable_totp">) {
|
}: ModalProps<"mfa_enable_totp">) {
|
||||||
const uri = `otpauth://totp/Revolt:${identifier}?secret=${secret}&issuer=Revolt`;
|
const uri = `otpauth://totp/Revolt:${identifier}?secret=${secret}&issuer=Revolt`;
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
@ -61,7 +62,9 @@ export default function MFAEnableTOTP({
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
callback();
|
callback();
|
||||||
onClose();
|
onClose();
|
||||||
}}>
|
}}
|
||||||
|
signal={signal}
|
||||||
|
nonDismissable>
|
||||||
<Column>
|
<Column>
|
||||||
<Centred>
|
<Centred>
|
||||||
<Qr>
|
<Qr>
|
||||||
|
|
|
@ -81,7 +81,11 @@ function ResponseEntry({
|
||||||
/**
|
/**
|
||||||
* MFA ticket creation flow
|
* 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>(
|
const [methods, setMethods] = useState<API.MFAMethod[] | undefined>(
|
||||||
props.state === "unknown" ? props.available_methods : 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={() => {
|
onClose={() => {
|
||||||
props.callback();
|
props.callback();
|
||||||
onClose();
|
onClose();
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default function MFARecovery({
|
||||||
codes,
|
codes,
|
||||||
client,
|
client,
|
||||||
onClose,
|
onClose,
|
||||||
|
signal,
|
||||||
}: ModalProps<"mfa_recovery">) {
|
}: ModalProps<"mfa_recovery">) {
|
||||||
// Keep track of changes to recovery codes
|
// Keep track of changes to recovery codes
|
||||||
const [known, setCodes] = useState(codes);
|
const [known, setCodes] = useState(codes);
|
||||||
|
@ -69,7 +70,8 @@ export default function MFARecovery({
|
||||||
onClick: reset,
|
onClick: reset,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onClose={onClose}>
|
onClose={onClose}
|
||||||
|
signal={signal}>
|
||||||
<List>
|
<List>
|
||||||
{known.map((code) => (
|
{known.map((code) => (
|
||||||
<span key={code}>{code}</span>
|
<span key={code}>{code}</span>
|
||||||
|
|
|
@ -31,8 +31,10 @@ class ModalController<T extends Modal> {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
stack: observable,
|
stack: observable,
|
||||||
push: action,
|
push: action,
|
||||||
|
pop: action,
|
||||||
remove: action,
|
remove: action,
|
||||||
rendered: computed,
|
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
|
* Remove the keyed modal from the stack
|
||||||
*/
|
*/
|
||||||
|
@ -66,6 +78,8 @@ class ModalController<T extends Modal> {
|
||||||
{this.stack.map((modal) => {
|
{this.stack.map((modal) => {
|
||||||
const Component = this.components[modal.type];
|
const Component = this.components[modal.type];
|
||||||
return (
|
return (
|
||||||
|
// ESLint does not understand spread operator
|
||||||
|
// eslint-disable-next-line
|
||||||
<Component
|
<Component
|
||||||
{...modal}
|
{...modal}
|
||||||
onClose={() => this.remove(modal.key!)}
|
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 } & {
|
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
signal?: "close" | "confirm";
|
||||||
};
|
};
|
||||||
|
|
|
@ -150,7 +150,6 @@ export default class State {
|
||||||
() => stringify(store.toJSON()),
|
() => stringify(store.toJSON()),
|
||||||
async (value) => {
|
async (value) => {
|
||||||
try {
|
try {
|
||||||
console.log(id, "updated!");
|
|
||||||
// Save updated store to local storage.
|
// Save updated store to local storage.
|
||||||
await localforage.setItem(id, JSON.parse(value));
|
await localforage.setItem(id, JSON.parse(value));
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||||
|
|
||||||
import { useApplicationState } from "../../mobx/State";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
|
||||||
|
import { modalController } from "../../context/modals";
|
||||||
|
|
||||||
import ButtonItem from "../../components/navigation/items/ButtonItem";
|
import ButtonItem from "../../components/navigation/items/ButtonItem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -61,6 +63,8 @@ export function GenericSettings({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function keyDown(e: KeyboardEvent) {
|
function keyDown(e: KeyboardEvent) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
|
if (modalController.isVisible) return;
|
||||||
|
|
||||||
exitSettings();
|
exitSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2231,9 +2231,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@revoltchat/ui@npm:1.0.40":
|
"@revoltchat/ui@npm:1.0.43":
|
||||||
version: 1.0.40
|
version: 1.0.43
|
||||||
resolution: "@revoltchat/ui@npm:1.0.40"
|
resolution: "@revoltchat/ui@npm:1.0.43"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@styled-icons/boxicons-logos": ^10.38.0
|
"@styled-icons/boxicons-logos": ^10.38.0
|
||||||
"@styled-icons/boxicons-regular": ^10.38.0
|
"@styled-icons/boxicons-regular": ^10.38.0
|
||||||
|
@ -2246,7 +2246,7 @@ __metadata:
|
||||||
react-device-detect: "*"
|
react-device-detect: "*"
|
||||||
react-virtuoso: "*"
|
react-virtuoso: "*"
|
||||||
revolt.js: "*"
|
revolt.js: "*"
|
||||||
checksum: bc0bc906cdb22e8a31c862d1e87f8bd5c46cb463aa23ad773e9c683514fbe0e52ac44e9eab41dd6aa6e8e207050f9ab0590d6e51b2a4d8af6c0fb2ea899d789f
|
checksum: d6a6d0cb4a2f08fea45a4d61e5599894012fbb591472ef95d34ee8ddc9e66cfdc7626e94360b7c104e59d3c64a7d0bd674d6a42f5c3cefc723574db8c1aee64e
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -3539,7 +3539,7 @@ __metadata:
|
||||||
"@hcaptcha/react-hcaptcha": ^0.3.6
|
"@hcaptcha/react-hcaptcha": ^0.3.6
|
||||||
"@insertish/vite-plugin-babel-macros": ^1.0.5
|
"@insertish/vite-plugin-babel-macros": ^1.0.5
|
||||||
"@preact/preset-vite": ^2.0.0
|
"@preact/preset-vite": ^2.0.0
|
||||||
"@revoltchat/ui": 1.0.40
|
"@revoltchat/ui": 1.0.43
|
||||||
"@rollup/plugin-replace": ^2.4.2
|
"@rollup/plugin-replace": ^2.4.2
|
||||||
"@styled-icons/boxicons-logos": ^10.38.0
|
"@styled-icons/boxicons-logos": ^10.38.0
|
||||||
"@styled-icons/boxicons-regular": ^10.38.0
|
"@styled-icons/boxicons-regular": ^10.38.0
|
||||||
|
|
Loading…
Reference in a new issue