import { DOMAINS, DomainModule, ISearchFilter, ModuleProperty } from "bf-types"; import { IObservableArray, action, computed, observable } from "mobx"; import { ISearchManager, ISearchManagerOptions } from "./types"; import { BfLib } from "bf-lib"; import { SearchFilterCheck } from "./SearchFilterMatch"; import debounce from "lodash.debounce"; import { toLowerCamel } from "./utils/strings"; const LIMIT_DEFAULT: number = 25; export class SearchManager< T extends Record = {}, D extends string = DOMAINS, M extends string = DomainModule, MP extends string = ModuleProperty > implements ISearchManager { @computed public get loaded() { return this._loaded; } @observable public refreshing: boolean = false; @computed public get search_results() { return this._search_results.concat().filter(this._filter_checker.test); } @computed get options() { const options: ISearchManagerOptions = { limit: this._limit, filters: JSON.parse(JSON.stringify(this.search_filters)), apiPath: this.apiPath }; if (this._sort) { options.sort = this._sort; } return options; } public refreshData = debounce(() => { this._refreshData(); }, 250); @computed public get apiPath() { return ( this._apiPath || `/${toLowerCamel(this.associated_domain)}/${toLowerCamel( this.associated_module )}/search` ); } set options({ limit, filters, sort, apiPath }: ISearchManagerOptions) { this._limit = limit || LIMIT_DEFAULT; this._sort = sort || null; this.search_filters.replace(filters || []); this._apiPath = apiPath || null; } public associated_module: M; public associated_domain: D; public search_filters: IObservableArray = observable([]); @observable private _loaded: boolean = false; private _search_results: IObservableArray = observable([]); @observable private _limit: number = LIMIT_DEFAULT; @observable private _sort: { field: MP; direction: "asc" | "desc" } | null = null; @observable private _apiPath: string | null = null; @computed private get _filter_checker() { return SearchFilterCheck(this.search_filters); } constructor( private readonly bflib: BfLib, { module, domain }: { module: M; domain: D } ) { this.associated_domain = domain; this.associated_module = module; } public async initialLoad( options?: ISearchManagerOptions, extra?: { mapResult?: (item: T) => T } ) { if (options) { this.options = options; // Threads, and allows settings to complete await Promise.resolve(); } if (extra && extra.mapResult && typeof extra.mapResult === "function") { this._mapResult = extra.mapResult; } this.livesyncSetup(); this.refreshData(); } public updateSearchFilters(searchFilters: ISearchFilter) {} @action private async _refreshData() { this.refreshing = true; const { filters, limit, sort } = this.options; const data = await this.bflib.api.put(this.apiPath, { filters, limit, sort }); 2; this._search_results.replace(data.map(this.mapResult)); } private _mapResult = (data: T): T => { return data; }; private mapResult = (item: T): T => { return this._mapResult({ ...item, module_id: item.id }); }; private checkEvent({ module_name }: { module_name: string }) { return toLowerCamel(module_name) === toLowerCamel(this.associated_module); } private livesyncEvent(eventtype: string, cb: (item: T) => void) { this.bflib.livesync.subscribe<{ module_name: string; module_id: string }>( eventtype, ({ body }) => { const { module_name } = body; if (!this.checkEvent({ module_name })) { return; } cb(body as any); } ); } private livesyncSetup() { this.livesyncEvent("CORE::ENTITIES::ENTITY_CREATED", item => { if (this._filter_checker.test(item)) { this._search_results.push(this.mapResult(item)); } }); this.livesyncEvent("CORE::ENTITIES::ENTITY_DELETED", item => { try { const ck = this._search_results.find((ck: T) => { ck.module_id === item.module_id; }); if (ck) { this._search_results.remove(ck); } } finally { } }); this.livesyncEvent("CORE::MODULE_PROPERTIES::PROPERTY_UPDATED", item => { try { const ck = this._search_results.find((ck: T) => { return ck.id === item.module_id; }); if (ck) { this._search_results.replace( this._search_results.concat().map(i => { if (i.id === item.id) { return this.mapResult(item); } return i; }) ); } } finally { } }); } }