mirror of
https://github.com/hexastack/hexabot
synced 2025-04-29 02:33:00 +00:00
148 lines
3.8 KiB
TypeScript
148 lines
3.8 KiB
TypeScript
/*
|
|
* Copyright © 2025 Hexastack. All rights reserved.
|
|
*
|
|
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
|
|
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
|
|
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
|
|
*/
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useId,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
|
|
import {
|
|
CloseDialog,
|
|
DialogComponent,
|
|
DialogProviderProps,
|
|
DialogStackEntry,
|
|
OpenDialog,
|
|
OpenDialogOptions,
|
|
} from "@/types/common/dialogs.types";
|
|
|
|
export const DialogsContext = createContext<
|
|
| {
|
|
open: OpenDialog;
|
|
close: CloseDialog;
|
|
}
|
|
| undefined
|
|
>(undefined);
|
|
|
|
/**
|
|
* Provider for Dialog stacks. The subtree of this component can use the `useDialogs` hook to
|
|
* access the dialogs API. The dialogs are rendered in the order they are requested.
|
|
*
|
|
* Demos:
|
|
*
|
|
* - [useDialogs](https://mui.com/toolpad/core/react-use-dialogs/)
|
|
*
|
|
* API:
|
|
*
|
|
* - [DialogsProvider API](https://mui.com/toolpad/core/api/dialogs-provider)
|
|
*/
|
|
function DialogsProvider(props: DialogProviderProps) {
|
|
const { children, unmountAfter = 1000 } = props;
|
|
const [stack, setStack] = useState<DialogStackEntry<any, any>[]>([]);
|
|
const keyPrefix = useId();
|
|
const nextId = useRef(0);
|
|
const requestDialog = useCallback<OpenDialog>(
|
|
function open<P, R>(
|
|
Component: DialogComponent<P, R>,
|
|
payload: P,
|
|
options: OpenDialogOptions<R> = {},
|
|
) {
|
|
const { onClose = async () => {} } = options;
|
|
let resolve: ((result: R) => void) | undefined;
|
|
const promise = new Promise<R>((resolveImpl) => {
|
|
resolve = resolveImpl;
|
|
});
|
|
|
|
if (!resolve) {
|
|
throw new Error("resolve not set");
|
|
}
|
|
|
|
const key = `${keyPrefix}-${nextId.current}`;
|
|
|
|
nextId.current += 1;
|
|
|
|
const newEntry: DialogStackEntry<P, R> = {
|
|
key,
|
|
open: true,
|
|
promise,
|
|
Component,
|
|
payload,
|
|
onClose,
|
|
resolve,
|
|
msgProps: { count: options.count, mode: options.mode },
|
|
};
|
|
|
|
setStack((prevStack) => [...prevStack, newEntry]);
|
|
|
|
return promise;
|
|
},
|
|
[keyPrefix],
|
|
);
|
|
const closeDialogUi = useCallback(
|
|
function closeDialogUi<R>(dialog: Promise<R>) {
|
|
setStack((prevStack) =>
|
|
prevStack.map((entry) =>
|
|
entry.promise === dialog ? { ...entry, open: false } : entry,
|
|
),
|
|
);
|
|
setTimeout(() => {
|
|
// wait for closing animation
|
|
setStack((prevStack) =>
|
|
prevStack.filter((entry) => entry.promise !== dialog),
|
|
);
|
|
}, unmountAfter);
|
|
},
|
|
[unmountAfter],
|
|
);
|
|
const closeDialog = useCallback(
|
|
async function closeDialog<R>(dialog: Promise<R>, result: R) {
|
|
const entryToClose = stack.find((entry) => entry.promise === dialog);
|
|
|
|
if (!entryToClose) {
|
|
throw new Error("dialog not found");
|
|
}
|
|
|
|
await entryToClose.onClose(result);
|
|
entryToClose.resolve(result);
|
|
closeDialogUi(dialog);
|
|
|
|
return dialog;
|
|
},
|
|
[stack, closeDialogUi],
|
|
);
|
|
const contextValue = useMemo(
|
|
() => ({
|
|
open: requestDialog,
|
|
close: closeDialog,
|
|
}),
|
|
[requestDialog, closeDialog],
|
|
);
|
|
|
|
return (
|
|
<DialogsContext.Provider value={contextValue}>
|
|
{children}
|
|
{stack.map(({ key, open, Component, payload, promise, msgProps }) => (
|
|
<Component
|
|
key={key}
|
|
payload={payload}
|
|
open={open}
|
|
onClose={async (result) => {
|
|
await closeDialog(promise, result);
|
|
}}
|
|
{...msgProps}
|
|
/>
|
|
))}
|
|
</DialogsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export { DialogsProvider };
|