Prompt users to set up recovery (#30075)
* Show indicator in settings dialog when user doesn't have recovery set up * Update settings headers to use red dot for recommended settings * update recovery setup toast and remember if the user dismisses it * update playwright snapshots * use typed event emitters * reverse logic for the account data flag * fix comment and type
This commit is contained in:
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { useRef, useEffect, useState, useCallback } from "react";
|
||||
import { useRef, useEffect, useState, useCallback, type DependencyList } from "react";
|
||||
import { type ListenerMap, type TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { EventEmitter } from "events";
|
||||
@@ -93,3 +93,100 @@ export function useEventEmitterState<T>(
|
||||
useEventEmitter(emitter, eventName, handler);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The return value of the callback function for `useEventEmitterAsyncState`.
|
||||
*/
|
||||
export type AsyncStateCallbackResult<T> = Promise<T | NoChange>;
|
||||
|
||||
/**
|
||||
* Creates a state, which is computed asynchronously, and can be updated by events.
|
||||
*
|
||||
* Similar to `useEventEmitterState`, but the callback is `async`.
|
||||
*
|
||||
* If the event is emitted while the callback is running, it will wait until
|
||||
* after the callback completes before calling the callback again. If the event
|
||||
* is emitted multiple times while the callback is running, the callback will be
|
||||
* called once for each time the event was emitted, in the order that the events
|
||||
* were emitted.
|
||||
*
|
||||
* @param emitter The emitter sending the event
|
||||
* @param eventName Event name to listen for
|
||||
* @param fn The callback function, that should return the state value.
|
||||
* It should have the signature of the event callback, except that all
|
||||
* parameters are optional. If the params are not set, a default value
|
||||
* for the state should be returned. If the state value should not
|
||||
* change from its previous value, the function can return a `NoChange`
|
||||
* object.
|
||||
* @param deps The dependencies of the callback function.
|
||||
* @param initialValue The initial value of the state, before the callback finishes its initial run.
|
||||
* @returns State
|
||||
*/
|
||||
export function useEventEmitterAsyncState<T, Events extends string, Arguments extends ListenerMap<Events>>(
|
||||
emitter: TypedEventEmitter<Events, Arguments> | undefined,
|
||||
eventName: string | symbol,
|
||||
fn: Mapper<AsyncStateCallbackResult<T>>,
|
||||
deps: DependencyList,
|
||||
initialValue: T,
|
||||
): T;
|
||||
export function useEventEmitterAsyncState<T, Events extends string, Arguments extends ListenerMap<Events>>(
|
||||
emitter: TypedEventEmitter<Events, Arguments> | undefined,
|
||||
eventName: string | symbol,
|
||||
fn: Mapper<AsyncStateCallbackResult<T>>,
|
||||
deps: DependencyList,
|
||||
initialValue?: T,
|
||||
): T | undefined;
|
||||
export function useEventEmitterAsyncState<T, Events extends string, Arguments extends ListenerMap<Events>>(
|
||||
emitter: TypedEventEmitter<Events, Arguments> | undefined,
|
||||
eventName: string | symbol,
|
||||
fn: Mapper<AsyncStateCallbackResult<T>>,
|
||||
deps: DependencyList,
|
||||
initialValue?: T,
|
||||
): T | undefined {
|
||||
const [value, setValue] = useState<T | undefined>(initialValue);
|
||||
|
||||
let running = false;
|
||||
// If the handler is called while it's already running, we remember the
|
||||
// arguments that it was called with, and call the handler again when the
|
||||
// first call is done.
|
||||
const rerunArgs: any[] = [];
|
||||
|
||||
const handler = useCallback(
|
||||
(...args: any[]) => {
|
||||
if (running) {
|
||||
// We're already running, so remember the arguments we were
|
||||
// called with, so that we can call the handler again when we're
|
||||
// done.
|
||||
rerunArgs.push(args);
|
||||
return;
|
||||
}
|
||||
running = true; // eslint-disable-line react-hooks/exhaustive-deps
|
||||
// Note: We need to use .then notation instead of async/await,
|
||||
// because async/await would cause this function to return a
|
||||
// promise, which `useEffect` doesn't like.
|
||||
fn(...args)
|
||||
.then((v) => {
|
||||
if (!(v instanceof NoChange)) {
|
||||
setValue(v);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
running = false;
|
||||
if (rerunArgs.length != 0) {
|
||||
handler(...rerunArgs.shift());
|
||||
}
|
||||
});
|
||||
},
|
||||
[fn, ...deps], // eslint-disable-line react-compiler/react-compiler
|
||||
);
|
||||
|
||||
// re-run when the emitter changes
|
||||
useEffect(handler, [emitter, handler, ...deps]);
|
||||
useEventEmitter(emitter, eventName, handler);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the callback for `useEventEmitterAsyncState` is not changing the value of the state.
|
||||
*/
|
||||
export class NoChange {}
|
||||
|
||||
Reference in New Issue
Block a user