UNPKG

stream-chat-react

Version:

React components to create chat conversations or livestream style chat

145 lines (144 loc) 4.67 kB
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), }, }, })); } }