/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import insertInDescendSortedArray from "./array/insertInDescendSortedArray"; import { getMiddleware } from "./middleware"; import safeAssign from "./object/safeAssign"; export type SortedElementId = PeerId; export type SortedElementBase = { id: SortedElementId, index: number }; export default class SortedList { protected elements: Map; protected sorted: Array; protected getIndex: (element: SortedElement) => PromiseLike | number; protected onDelete: (element: SortedElement) => void; protected onUpdate: (element: SortedElement) => void; protected onSort: (element: SortedElement, idx: number) => void; protected onElementCreate: (base: SortedElementBase, batch: boolean) => SortedElement; protected updateElementWith = (callback: () => void) => callback(); protected updateListWith = (callback: (canUpdate: boolean | undefined) => void) => callback(true); protected middleware = getMiddleware(); constructor(options: { getIndex: SortedList['getIndex'], onDelete?: SortedList['onDelete'], onUpdate?: SortedList['onUpdate'], onSort?: SortedList['onSort'], onElementCreate: SortedList['onElementCreate'], updateElementWith?: SortedList['updateElementWith'], updateListWith?: SortedList['updateListWith'] }) { safeAssign(this, options); this.elements = new Map(); this.sorted = []; } public clear() { this.middleware.clean(); this.elements.clear(); this.sorted.length = 0; } protected _updateList() { this.elements.forEach((element) => { this.update(element.id, true); }); if(this.onSort) { this.sorted.forEach((element, idx) => { this.onSort(element, idx); }); } } public updateList(callback: (updated: boolean) => void) { const middleware = this.middleware.get(); this.updateListWith((canUpdate) => { if(!middleware() || (canUpdate !== undefined && !canUpdate)) { return callback(false); } this._updateList(); callback(true); }); } public has(id: SortedElementId) { return this.elements.has(id); } public get(id: SortedElementId) { return this.elements.get(id); } public getAll() { return this.elements; } public add( id: SortedElementId, batch = false, updateElementWith?: SortedList['updateElementWith'], updateBatch = batch ) { let element = this.get(id); if(element) { return element; } const base: SortedElementBase = { id, index: 0 }; element = this.onElementCreate(base, batch); this.elements.set(id, element); this.update(id, updateBatch, element, updateElementWith); return element; } public delete(id: SortedElementId, noScheduler?: boolean) { const element = this.elements.get(id); if(!element) { return false; } this.elements.delete(id); const idx = this.sorted.indexOf(element); if(idx !== -1) { this.sorted.splice(idx, 1); } if(this.onDelete) { if(noScheduler) { this.onDelete(element); } else { const middleware = this.middleware.get(); this.updateElementWith(() => { if(!middleware()) { return; } this.onDelete(element); }); } } return true; } public async update( id: SortedElementId, batch = false, element = this.get(id), updateElementWith?: SortedList['updateElementWith'] ) { if(!element) { return; } element.index = await this.getIndex(element); this.onUpdate && this.onUpdate(element); const idx = insertInDescendSortedArray(this.sorted, element, 'index'); if(!batch && this.onSort) { const middleware = this.middleware.get(); (updateElementWith || this.updateElementWith)(() => { if(!middleware()) { return; } // * в случае пересортировки этого же элемента во время ожидания вызовется вторая такая же. нужно соблюдать последовательность событий this.onSort(element, idx); /* if(this.get(id) === element) { this.onSort(element, this.sorted.indexOf(element)); } */ }); } } }