import * as React from 'react'; import type { EmitterSubscription } from 'react-native'; import { addEventListener, emitEvent, removeEventListener, removeEventListenerBySub, useEventEmitter, } from '../dispatch'; import { gEventDataProfileProvider } from './const'; import type { DataModel, DataModelType } from './types'; /** * Data Profile Provider. */ export class DataProfileProvider { _dataList: Map; private _userProfile?: | ((data: Map) => Map) | ((data: Map) => Promise>); private _groupProfile?: | ((data: Map) => Map) | ((data: Map) => Promise>); constructor() { this._dataList = new Map(); } /** * Convert data to map. * @param data * @returns */ toMap(data: DataModel[]): Map { const map = new Map(); data.forEach((d) => { map.set(d.id, d); }); return map; } /** * Convert map to array. */ toArray(data: Map): DataModel[] { const arr = [] as DataModel[]; data.forEach((v) => { arr.push(v); }); return arr; } /** * Register user profile. */ registerUserProfile( h?: | ((data: Map) => Map) | ((data: Map) => Promise>) ): void { this._userProfile = h; } /** * Register group profile. */ registerGroupProfile( h?: | ((data: Map) => Map) | ((data: Map) => Promise>) ): void { this._groupProfile = h; } /** * Clear data list. */ clearDataList(): void { this._dataList.clear(); } /** * Get a list of data. * @param list The data list. Typically comes from a component data source. * @returns List of cached data. */ getDataList(list: DataModel[]): DataModel[] { const ret = [] as DataModel[]; list.forEach((d) => { const data = this._dataList.get(d.id); if (data) { ret.push(data); } else { ret.push(d); } }); return ret; } /** * Get data by id. */ getDataById(id: string): DataModel | undefined { return this._dataList.get(id); } /** * Get a list of users. */ getUserList(ids: string[]): DataModel[] { const ret = [] as DataModel[]; ids.forEach((id) => { const data = this._dataList.get(id); if (data && data.type === 'user') { ret.push(data); } else { ret.push({ id, type: 'user' }); } }); return ret; } /** * Get a list of groups. */ getGroupList(ids: string[]): DataModel[] { const ret = [] as DataModel[]; ids.forEach((id) => { const data = this._dataList.get(id); if (data && data.type === 'group') { ret.push(data); } else { ret.push({ id, type: 'group' }); } }); return ret; } /** * Update the cache and notify interested components. * * Data that has been requested and received will no longer trigger notifications. * * @param dataList The default value can be obtained through the `getDataList` method. * @param isUpdateNotExisted Whether the non-existing data also needs to be updated. Default is false. * @param disableDispatch Whether to disable notification. Default is false. * @param dispatchHandler Callback for dispatching notifications. If false is returned, it is not distributed internally. */ updateDataList(params: { dataList: Map; isUpdateNotExisted?: boolean; disableDispatch?: boolean; dispatchHandler?: (data: Map) => boolean; }): void { const { dataList, isUpdateNotExisted = false, disableDispatch = false, dispatchHandler, } = params; let needUpdate = false; const list = new Map(); dataList.forEach((v, k) => { const isExisted = this._dataList.get(k); if (isExisted) { if (JSON.stringify(isExisted) !== JSON.stringify(v)) { const d = { ...isExisted, ...v } as DataModel; this._dataList.set(k, d); list.set(k, d); needUpdate = true; } } else { if (isUpdateNotExisted) { this._dataList.set(k, v); list.set(k, v); needUpdate = true; } } }); if (needUpdate && disableDispatch !== true) { if (dispatchHandler) { if (dispatchHandler(list) === false) { return; } } this.dispatch(list); } } /** * Request updated data and notify interested components. The registered callback `registerUserProfile` and `registerGroupProfile` will be used to request data. * * Data that has been requested and received will no longer trigger notifications. * * @params dataList The data list to be updated. * - dataList: The data list to be updated. * - isUpdateNotExisted: Whether the non-existing data also needs to be updated. Default is false. * - disableDispatch: Whether to disable notification. Default is false. * - requestHasData: Whether to request data that already exists. Default is true. * - dispatchHandler: Callback for dispatching notifications. If false is returned, it is not distributed internally. */ async requestDataList(params: { dataList: Map; isUpdateNotExisted?: boolean; disableDispatch?: boolean; requestHasData?: boolean; dispatchHandler?: (data: Map) => boolean; }): Promise { const { dataList, isUpdateNotExisted = false, disableDispatch = false, requestHasData = true, dispatchHandler, } = params; const newList = new Map(); // user const users = Array.from(dataList.entries()) .filter((v) => v[1] === 'user') .map((v) => v[0]!); const requestUsers = new Map(); users.forEach((v) => { if (this._dataList.has(v)) { if (requestHasData === true) { requestUsers.set(v, this._dataList.get(v)!); } } else { requestUsers.set(v, { id: v, type: 'user' } as DataModel); } }); if (this._userProfile) { const newUsers = await this._userProfile(requestUsers); users.forEach((v) => { if (newUsers.has(v)) { const data = newUsers.get(v); const newData = { id: v, type: data?.type } as DataModel; if (data?.avatar && data.avatar.length > 0) { newData.avatar = data.avatar; } if (data?.name && data.name.length > 0) { newData.name = data.name; } if (data?.remark && data.remark.length > 0) { newData.remark = data.remark; } newList.set(v, newData); } }); } // group const groups = Array.from(dataList.entries()) .filter((v) => v[1] === 'group') .map((v) => v[0]!); const requestGroups = new Map(); groups.forEach((v) => { if (this._dataList.has(v)) { if (requestHasData === true) { requestGroups.set(v, this._dataList.get(v)!); } } else { requestGroups.set(v, { id: v, type: 'group' } as DataModel); } }); if (this._groupProfile) { const newGroups = await this._groupProfile(requestGroups); groups.forEach((v) => { if (newGroups.has(v)) { const data = newGroups.get(v); const newData = { id: v, type: data?.type } as DataModel; if (data?.avatar && data.avatar.length > 0) { newData.avatar = data.avatar; } if (data?.name && data.name.length > 0) { newData.name = data.name; } if (data?.remark && data.remark.length > 0) { newData.remark = data.remark; } newList.set(v, newData); } }); } this.updateDataList({ dataList: newList, isUpdateNotExisted: isUpdateNotExisted, disableDispatch: disableDispatch, dispatchHandler: dispatchHandler, }); } /** * Notify the component of interest. * * Follow notifications via `setObserver`. * * @param updateList Changed list. */ dispatch(updateList: Map): void { emitEvent(gEventDataProfileProvider, updateList); } /** * Add an observer. */ addObserver( observer: (updateList: Map) => void ): EmitterSubscription { return addEventListener(gEventDataProfileProvider, observer); } /** * Remove the observer by subscription. */ removeObserver(sub: EmitterSubscription): void { removeEventListenerBySub(gEventDataProfileProvider, sub); } /** * Clear the observer. */ clearObserver(): void { removeEventListener(gEventDataProfileProvider); } } export function useDataProfileProvider() { const { addEventListener, removeEventListenerBySub, emitEvent } = useEventEmitter(); const _addListener = React.useCallback( (listener: (updateList: Map) => void) => { return addEventListener(gEventDataProfileProvider, listener); }, [addEventListener] ); const _removeListener = React.useCallback( (sub: EmitterSubscription) => { removeEventListenerBySub(gEventDataProfileProvider, sub); }, [removeEventListenerBySub] ); return { emitEvent, addDataProfileListener: _addListener, removeDataProfileListener: _removeListener, }; }