stream-chat-react
Version:
React components to create chat conversations or livestream style chat
145 lines (144 loc) • 4.67 kB
JavaScript
import { nanoid } from 'nanoid';
import { StateStore } from 'stream-chat';
/**
* Keeps a map of Dialog objects.
* Dialog can be controlled via `Dialog` object retrieved using `useDialog()` hook.
* The hook returns an object with the following API:
*
* - `dialog.open()` - opens the dialog
* - `dialog.close()` - closes the dialog
* - `dialog.toggle()` - toggles the dialog open state. Accepts boolean argument closeAll. If enabled closes any other dialog that would be open.
* - `dialog.remove()` - removes the dialog object reference from the state (primarily for cleanup purposes)
*/
export class DialogManager {
constructor({ id } = {}) {
this.state = new StateStore({
dialogsById: {},
});
this.id = id ?? nanoid();
}
get openDialogCount() {
return Object.values(this.state.getLatestValue().dialogsById).reduce((count, dialog) => {
if (dialog.isOpen)
return count + 1;
return count;
}, 0);
}
get(id) {
return this.state.getLatestValue().dialogsById[id];
}
getOrCreate({ id }) {
let dialog = this.state.getLatestValue().dialogsById[id];
if (!dialog) {
dialog = {
close: () => {
this.close(id);
},
id,
isOpen: false,
open: () => {
this.open({ id });
},
removalTimeout: undefined,
remove: () => {
this.remove(id);
},
toggle: (closeAll = false) => {
this.toggle({ id }, closeAll);
},
};
this.state.next((current) => ({
...current,
...{ dialogsById: { ...current.dialogsById, [id]: dialog } },
}));
}
if (dialog.removalTimeout) {
clearTimeout(dialog.removalTimeout);
this.state.next((current) => ({
...current,
...{
dialogsById: {
...current.dialogsById,
[id]: {
...dialog,
removalTimeout: undefined,
},
},
},
}));
}
return dialog;
}
open(params, closeRest) {
const dialog = this.getOrCreate(params);
if (dialog.isOpen)
return;
if (closeRest) {
this.closeAll();
}
this.state.next((current) => ({
...current,
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: true } },
}));
}
close(id) {
const dialog = this.state.getLatestValue().dialogsById[id];
if (!dialog?.isOpen)
return;
this.state.next((current) => ({
...current,
dialogsById: { ...current.dialogsById, [dialog.id]: { ...dialog, isOpen: false } },
}));
}
closeAll() {
Object.values(this.state.getLatestValue().dialogsById).forEach((dialog) => dialog.close());
}
toggle(params, closeAll = false) {
if (this.state.getLatestValue().dialogsById[params.id]?.isOpen) {
this.close(params.id);
}
else {
this.open(params, closeAll);
}
}
remove(id) {
const state = this.state.getLatestValue();
const dialog = state.dialogsById[id];
if (!dialog)
return;
if (dialog.removalTimeout) {
clearTimeout(dialog.removalTimeout);
}
this.state.next((current) => {
const newDialogs = { ...current.dialogsById };
delete newDialogs[id];
return {
...current,
dialogsById: newDialogs,
};
});
}
/**
* Marks the dialog state as unused. If the dialog id is referenced again quickly,
* the state will not be removed. Otherwise, the state will be removed after
* a short timeout.
*/
markForRemoval(id) {
const dialog = this.state.getLatestValue().dialogsById[id];
if (!dialog) {
return;
}
this.state.next((current) => ({
...current,
dialogsById: {
...current.dialogsById,
[id]: {
...dialog,
removalTimeout: setTimeout(() => {
this.remove(id);
}, 16),
},
},
}));
}
}