Sorted user list (by online time)
Sequential dom instead of simple rafs
This commit is contained in:
parent
b9ca65650f
commit
41ebab81c7
@ -5,11 +5,11 @@
|
||||
*/
|
||||
|
||||
import { formatDateAccordingToToday, months } from "../helpers/date";
|
||||
import { positionElementByIndex } from "../helpers/dom";
|
||||
import { positionElementByIndex, isInDOM, replaceContent } from "../helpers/dom";
|
||||
import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object";
|
||||
import { escapeRegExp, limitSymbols } from "../helpers/string";
|
||||
import appChatsManager from "../lib/appManagers/appChatsManager";
|
||||
import appDialogsManager from "../lib/appManagers/appDialogsManager";
|
||||
import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager";
|
||||
import appMessagesManager, { MyInputMessagesFilter, MyMessage } from "../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||
import appPhotosManager from "../lib/appManagers/appPhotosManager";
|
||||
@ -34,6 +34,7 @@ import findUpClassName from "../helpers/dom/findUpClassName";
|
||||
import { getMiddleware } from "../helpers/middleware";
|
||||
import appProfileManager from "../lib/appManagers/appProfileManager";
|
||||
import { ChannelParticipant, ChatFull, ChatParticipant, ChatParticipants } from "../layer";
|
||||
import SortedUserList from "./sortedUserList";
|
||||
|
||||
//const testScroll = false;
|
||||
|
||||
@ -104,6 +105,8 @@ export default class AppSearchSuper {
|
||||
|
||||
public mediaTabsMap: Map<SearchSuperMediaType, SearchSuperMediaTab> = new Map();
|
||||
|
||||
private membersList: SortedUserList;
|
||||
|
||||
// * arguments
|
||||
public mediaTabs: SearchSuperMediaTab[];
|
||||
public scrollable: Scrollable;
|
||||
@ -850,32 +853,24 @@ export default class AppSearchSuper {
|
||||
}
|
||||
}
|
||||
|
||||
let list = mediaTab.contentTab.firstElementChild as HTMLUListElement;
|
||||
if(!list) {
|
||||
list = appDialogsManager.createChatList();
|
||||
appDialogsManager.setListClickListener(list, undefined, undefined, true, true);
|
||||
mediaTab.contentTab.append(list);
|
||||
if(!this.membersList) {
|
||||
this.membersList = new SortedUserList();
|
||||
mediaTab.contentTab.append(this.membersList.list);
|
||||
this.afterPerforming(1, mediaTab.contentTab);
|
||||
}
|
||||
|
||||
participants.forEach(participant => {
|
||||
let {dialog, dom} = appDialogsManager.addDialogNew({
|
||||
dialog: participant.user_id,
|
||||
container: list,
|
||||
drawStatus: false,
|
||||
avatarSize: 48,
|
||||
autonomous: true,
|
||||
meAsSaved: false,
|
||||
rippleEnabled: false
|
||||
});
|
||||
const user = appUsersManager.getUser(participant.user_id);
|
||||
if(user.pFlags.deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let status = appUsersManager.getUserStatusString(participant.user_id);
|
||||
dom.lastMessageSpan.append(status);
|
||||
this.membersList.add(participant.user_id);
|
||||
});
|
||||
};
|
||||
|
||||
if(appChatsManager.isChannel(id)) {
|
||||
const LOAD_COUNT = 50;
|
||||
const LOAD_COUNT = !this.membersList ? 50 : 200;
|
||||
promise = appProfileManager.getChannelParticipants(id, undefined, LOAD_COUNT, this.nextRates[mediaTab.inputFilter]).then(participants => {
|
||||
if(!middleware()) {
|
||||
return;
|
||||
@ -1139,6 +1134,7 @@ export default class AppSearchSuper {
|
||||
|
||||
this.middleware.clean();
|
||||
this.cleanScrollPositions();
|
||||
this.membersList = undefined;
|
||||
}
|
||||
|
||||
public cleanScrollPositions() {
|
||||
|
@ -660,7 +660,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
|
||||
}
|
||||
|
||||
public init() {
|
||||
const perf = performance.now();
|
||||
//const perf = performance.now();
|
||||
|
||||
this.container.classList.add('shared-media-container', 'profile-container');
|
||||
|
||||
@ -859,7 +859,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
|
||||
}
|
||||
});
|
||||
|
||||
console.log('construct shared media time:', performance.now() - perf);
|
||||
//console.log('construct shared media time:', performance.now() - perf);
|
||||
}
|
||||
|
||||
public renderNewMessages(peerId: number, mids: number[]) {
|
||||
@ -923,7 +923,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
|
||||
}
|
||||
|
||||
public cleanupHTML() {
|
||||
const perf = performance.now();
|
||||
// const perf = performance.now();
|
||||
this.profile.cleanupHTML();
|
||||
|
||||
this.editBtn.style.display = 'none';
|
||||
@ -932,7 +932,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
|
||||
|
||||
this.container.classList.toggle('can-add-members', this.searchSuper.canViewMembers() && appChatsManager.hasRights(-this.peerId, 'invite_users'));
|
||||
|
||||
console.log('cleanupHTML shared media time:', performance.now() - perf);
|
||||
// console.log('cleanupHTML shared media time:', performance.now() - perf);
|
||||
}
|
||||
|
||||
public setLoadMutex(promise: Promise<any>) {
|
||||
|
97
src/components/sortedUserList.ts
Normal file
97
src/components/sortedUserList.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager";
|
||||
import { isInDOM, positionElementByIndex, replaceContent } from "../helpers/dom";
|
||||
import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
|
||||
import appUsersManager from "../lib/appManagers/appUsersManager";
|
||||
import { insertInDescendSortedArray, forEachReverse } from "../helpers/array";
|
||||
|
||||
type SortedUser = {
|
||||
peerId: number,
|
||||
status: number,
|
||||
dom: DialogDom
|
||||
};
|
||||
export default class SortedUserList {
|
||||
public static SORT_INTERVAL = 30e3;
|
||||
public list: HTMLUListElement;
|
||||
public users: Map<number, SortedUser>;
|
||||
public sorted: Array<SortedUser>;
|
||||
|
||||
constructor() {
|
||||
this.list = appDialogsManager.createChatList();
|
||||
appDialogsManager.setListClickListener(this.list, undefined, undefined, true, true);
|
||||
|
||||
this.users = new Map();
|
||||
this.sorted = [];
|
||||
|
||||
let timeout: number;
|
||||
const doTimeout = () => {
|
||||
timeout = window.setTimeout(() => {
|
||||
this.updateList().then((good) => {
|
||||
if(good) {
|
||||
doTimeout();
|
||||
}
|
||||
});
|
||||
}, SortedUserList.SORT_INTERVAL);
|
||||
};
|
||||
|
||||
doTimeout();
|
||||
}
|
||||
|
||||
public async updateList() {
|
||||
if(!isInDOM(this.list)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await getHeavyAnimationPromise();
|
||||
|
||||
if(!isInDOM(this.list)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.users.forEach(user => {
|
||||
this.update(user.peerId, true);
|
||||
});
|
||||
|
||||
this.sorted.forEach((sortedUser, idx) => {
|
||||
positionElementByIndex(sortedUser.dom.listEl, this.list, idx);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public add(peerId: number) {
|
||||
if(this.users.has(peerId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {dom} = appDialogsManager.addDialogNew({
|
||||
dialog: peerId,
|
||||
container: false,
|
||||
drawStatus: false,
|
||||
avatarSize: 48,
|
||||
autonomous: true,
|
||||
meAsSaved: false,
|
||||
rippleEnabled: false
|
||||
});
|
||||
|
||||
const sortedUser: SortedUser = {
|
||||
peerId,
|
||||
status: appUsersManager.getUserStatusForSort(peerId),
|
||||
dom
|
||||
};
|
||||
|
||||
this.users.set(peerId, sortedUser);
|
||||
this.update(peerId);
|
||||
}
|
||||
|
||||
public update(peerId: number, batch = false) {
|
||||
const sortedUser = this.users.get(peerId);
|
||||
sortedUser.status = appUsersManager.getUserStatusForSort(peerId);
|
||||
const status = appUsersManager.getUserStatusString(peerId);
|
||||
replaceContent(sortedUser.dom.lastMessageSpan, status);
|
||||
|
||||
const idx = insertInDescendSortedArray(this.sorted, sortedUser, 'status');
|
||||
if(!batch) {
|
||||
positionElementByIndex(sortedUser.dom.listEl, this.list, idx);
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ import rootScope from '../lib/rootScope';
|
||||
import { onVideoLoad } from '../helpers/files';
|
||||
import { animateSingle } from '../helpers/animation';
|
||||
import renderImageFromUrl from '../helpers/dom/renderImageFromUrl';
|
||||
import { fastRaf } from '../helpers/schedulers';
|
||||
import sequentialDom from '../helpers/sequentialDom';
|
||||
|
||||
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
@ -726,22 +726,23 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
|
||||
|
||||
return new Promise((resolve) => {
|
||||
renderImageFromUrl(image, cacheContext.url || photo.url, () => {
|
||||
container.append(image);
|
||||
sequentialDom.mutateElement(container, () => {
|
||||
container.append(image);
|
||||
|
||||
fastRaf(() => {
|
||||
resolve();
|
||||
|
||||
if(needFadeIn) {
|
||||
image.addEventListener('animationend', () => {
|
||||
sequentialDom.mutate(() => {
|
||||
image.classList.remove('fade-in');
|
||||
|
||||
if(thumbImage) {
|
||||
thumbImage.remove();
|
||||
}
|
||||
});
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
//resolve();
|
||||
|
||||
if(needFadeIn) {
|
||||
image.addEventListener('animationend', () => {
|
||||
image.classList.remove('fade-in');
|
||||
|
||||
if(thumbImage) {
|
||||
thumbImage.remove();
|
||||
}
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -849,8 +850,11 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
const afterRender = () => {
|
||||
if(!div.childElementCount) {
|
||||
thumbImage.classList.add('media-sticker', 'thumbnail');
|
||||
div.append(thumbImage);
|
||||
loadThumbPromise.resolve();
|
||||
|
||||
sequentialDom.mutateElement(div, () => {
|
||||
div.append(thumbImage);
|
||||
loadThumbPromise.resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -974,17 +978,23 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
};
|
||||
|
||||
if(!needFadeIn) {
|
||||
cb();
|
||||
} else {
|
||||
animation.canvas.classList.add('fade-in');
|
||||
if(element) {
|
||||
element.classList.add('fade-out');
|
||||
sequentialDom.mutate(cb);
|
||||
}
|
||||
} else {
|
||||
sequentialDom.mutate(() => {
|
||||
animation.canvas.classList.add('fade-in');
|
||||
if(element) {
|
||||
element.classList.add('fade-out');
|
||||
}
|
||||
|
||||
animation.canvas.addEventListener('animationend', () => {
|
||||
animation.canvas.classList.remove('fade-in');
|
||||
cb();
|
||||
}, {once: true});
|
||||
animation.canvas.addEventListener('animationend', () => {
|
||||
sequentialDom.mutate(() => {
|
||||
animation.canvas.classList.remove('fade-in');
|
||||
cb();
|
||||
});
|
||||
}, {once: true});
|
||||
});
|
||||
}
|
||||
|
||||
appDocsManager.saveLottiePreview(doc, animation.canvas, toneIndex);
|
||||
@ -1025,23 +1035,23 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
||||
if(middleware && !middleware()) return resolve();
|
||||
|
||||
renderImageFromUrl(image, doc.url, () => {
|
||||
div.append(image);
|
||||
if(thumbImage) {
|
||||
thumbImage.classList.add('fade-out');
|
||||
}
|
||||
sequentialDom.mutateElement(div, () => {
|
||||
div.append(image);
|
||||
if(thumbImage) {
|
||||
thumbImage.classList.add('fade-out');
|
||||
}
|
||||
|
||||
fastRaf(() => {
|
||||
resolve();
|
||||
});
|
||||
|
||||
if(needFadeIn) {
|
||||
image.addEventListener('animationend', () => {
|
||||
image.classList.remove('fade-in');
|
||||
if(thumbImage) {
|
||||
thumbImage.remove();
|
||||
}
|
||||
}, {once: true});
|
||||
}
|
||||
if(needFadeIn) {
|
||||
image.addEventListener('animationend', () => {
|
||||
image.classList.remove('fade-in');
|
||||
if(thumbImage) {
|
||||
thumbImage.remove();
|
||||
}
|
||||
}, {once: true});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -36,3 +36,31 @@ export function forEachReverse<T>(array: Array<T>, callback: (value: T, index?:
|
||||
callback(array[i], i, array);
|
||||
}
|
||||
};
|
||||
|
||||
export function insertInDescendSortedArray<T extends {[smth in K]?: number}, K extends keyof T>(array: Array<T>, element: T, property: K, pos?: number) {
|
||||
if(pos === undefined) {
|
||||
pos = array.indexOf(element);
|
||||
if(pos !== -1) {
|
||||
array.splice(pos, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const sortProperty: number = element[property];
|
||||
const len = array.length;
|
||||
if(!len || sortProperty <= array[len - 1][property]) {
|
||||
return array.push(element) - 1;
|
||||
} else if(sortProperty >= array[0][property]) {
|
||||
array.unshift(element);
|
||||
return 0;
|
||||
} else {
|
||||
for(let i = 0; i < len; i++) {
|
||||
if(sortProperty > array[i][property]) {
|
||||
array.splice(i, 0, element);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.error('wtf', array, element);
|
||||
return array.indexOf(element);
|
||||
}
|
||||
|
@ -405,8 +405,10 @@ export function calcImageInBox(imageW: number, imageH: number, boxW: number, box
|
||||
|
||||
MOUNT_CLASS_TO.calcImageInBox = calcImageInBox;
|
||||
|
||||
export function positionElementByIndex(element: HTMLElement, container: HTMLElement, pos: number) {
|
||||
const prevPos = element.parentElement === container ? whichChild(element) : -1;
|
||||
export function positionElementByIndex(element: HTMLElement, container: HTMLElement, pos: number, prevPos?: number) {
|
||||
if(prevPos === undefined) {
|
||||
prevPos = element.parentElement === container ? whichChild(element) : -1;
|
||||
}
|
||||
|
||||
if(prevPos === pos) {
|
||||
return false;
|
||||
|
@ -132,8 +132,8 @@ export function fastRaf(callback: NoneToVoidFunction) {
|
||||
|
||||
export function doubleRaf() {
|
||||
return new Promise((resolve) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(resolve);
|
||||
fastRaf(() => {
|
||||
fastRaf(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
68
src/helpers/sequentialDom.ts
Normal file
68
src/helpers/sequentialDom.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { fastRaf } from "./schedulers";
|
||||
import { CancellablePromise, deferredPromise } from "./cancellablePromise";
|
||||
import { isInDOM } from "./dom";
|
||||
import { MOUNT_CLASS_TO } from "../config/debug";
|
||||
|
||||
class SequentialDom {
|
||||
private promises: Partial<{
|
||||
read: CancellablePromise<void>,
|
||||
write: CancellablePromise<void>
|
||||
}> = {};
|
||||
private raf = fastRaf.bind(null);
|
||||
private scheduled = false;
|
||||
|
||||
private do(kind: keyof SequentialDom['promises'], callback?: VoidFunction) {
|
||||
let promise = this.promises[kind];
|
||||
if(!promise) {
|
||||
this.scheduleFlush();
|
||||
promise = this.promises[kind] = deferredPromise<void>();
|
||||
}
|
||||
|
||||
if(callback !== undefined) {
|
||||
promise.then(() => callback());
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
public measure(callback?: VoidFunction) {
|
||||
return this.do('read', callback);
|
||||
}
|
||||
|
||||
public mutate(callback?: VoidFunction) {
|
||||
return this.do('write', callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will fire instantly if element is not connected
|
||||
* @param element
|
||||
* @param callback
|
||||
*/
|
||||
public mutateElement(element: HTMLElement, callback?: VoidFunction) {
|
||||
const promise = isInDOM(element) ? this.mutate() : Promise.resolve();
|
||||
|
||||
if(callback !== undefined) {
|
||||
promise.then(() => callback());
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
private scheduleFlush() {
|
||||
if(!this.scheduled) {
|
||||
this.scheduled = true;
|
||||
|
||||
this.raf(() => {
|
||||
this.promises.read && this.promises.read.resolve();
|
||||
this.promises.write && this.promises.write.resolve();
|
||||
|
||||
this.scheduled = false;
|
||||
this.promises = {};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sequentialDom = new SequentialDom();
|
||||
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.sequentialDom = sequentialDom);
|
||||
export default sequentialDom;
|
@ -37,7 +37,7 @@ import PeerTitle from "../../components/peerTitle";
|
||||
import { i18n } from "../langPack";
|
||||
import findUpTag from "../../helpers/dom/findUpTag";
|
||||
|
||||
type DialogDom = {
|
||||
export type DialogDom = {
|
||||
avatarEl: AvatarElement,
|
||||
captionDiv: HTMLDivElement,
|
||||
titleSpan: HTMLSpanElement,
|
||||
@ -1198,7 +1198,7 @@ export class AppDialogsManager {
|
||||
|
||||
public addDialogNew(options: {
|
||||
dialog: Dialog | number,
|
||||
container?: HTMLUListElement | Scrollable,
|
||||
container?: HTMLUListElement | Scrollable | false,
|
||||
drawStatus?: boolean,
|
||||
rippleEnabled?: boolean,
|
||||
onlyFirstName?: boolean,
|
||||
@ -1210,7 +1210,7 @@ export class AppDialogsManager {
|
||||
return this.addDialog(options.dialog, options.container, options.drawStatus, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous);
|
||||
}
|
||||
|
||||
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true, append = true, avatarSize = 54, autonomous = !!container) {
|
||||
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable | false, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true, append = true, avatarSize = 54, autonomous = !!container) {
|
||||
let dialog: Dialog;
|
||||
|
||||
if(typeof(_dialog) === 'number') {
|
||||
@ -1230,7 +1230,7 @@ export class AppDialogsManager {
|
||||
|
||||
const peerId: number = dialog.peerId;
|
||||
|
||||
if(!container) {
|
||||
if(container === undefined) {
|
||||
if(this.doms[peerId]) return;
|
||||
|
||||
const filter = appMessagesManager.filtersStorage.filters[this.filterId];
|
||||
@ -1350,7 +1350,7 @@ export class AppDialogsManager {
|
||||
}
|
||||
} */
|
||||
const method: 'append' | 'prepend' = append ? 'append' : 'prepend';
|
||||
if(!container/* || good */) {
|
||||
if(container === undefined/* || good */) {
|
||||
this.scroll[method](li);
|
||||
|
||||
this.doms[dialog.peerId] = dom;
|
||||
@ -1365,7 +1365,7 @@ export class AppDialogsManager {
|
||||
}
|
||||
|
||||
this.setLastMessage(dialog);
|
||||
} else {
|
||||
} else if(container) {
|
||||
container[method](li);
|
||||
}
|
||||
|
||||
|
@ -363,14 +363,18 @@ export class AppUsersManager {
|
||||
this.userAccess[id] = accessHash;
|
||||
} */
|
||||
|
||||
public getUserStatusForSort(status: User['status']) {
|
||||
public getUserStatusForSort(status: User['status'] | number) {
|
||||
if(typeof(status) === 'number') {
|
||||
status = this.getUser(status).status;
|
||||
}
|
||||
|
||||
if(status) {
|
||||
const expires = status._ === 'userStatusOnline' ? status.expires : (status._ === 'userStatusOffline' ? status.was_online : 0);
|
||||
if(expires) {
|
||||
return expires;
|
||||
}
|
||||
|
||||
const timeNow = tsNow(true);
|
||||
/* const timeNow = tsNow(true);
|
||||
switch(status._) {
|
||||
case 'userStatusRecently':
|
||||
return timeNow - 86400 * 3;
|
||||
@ -378,6 +382,14 @@ export class AppUsersManager {
|
||||
return timeNow - 86400 * 7;
|
||||
case 'userStatusLastMonth':
|
||||
return timeNow - 86400 * 30;
|
||||
} */
|
||||
switch(status._) {
|
||||
case 'userStatusRecently':
|
||||
return 3;
|
||||
case 'userStatusLastWeek':
|
||||
return 2;
|
||||
case 'userStatusLastMonth':
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import type { AppMessagesManager, Dialog, MyMessage } from "../appManagers/appMe
|
||||
import type { AppPeersManager } from "../appManagers/appPeersManager";
|
||||
import type { ServerTimeManager } from "../mtproto/serverTimeManager";
|
||||
import searchIndexManager from "../searchIndexManager";
|
||||
import { insertInDescendSortedArray } from "../../helpers/array";
|
||||
|
||||
export default class DialogsStorage {
|
||||
public dialogs: {[peerId: string]: Dialog} = {};
|
||||
@ -181,20 +182,7 @@ export default class DialogsStorage {
|
||||
this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
|
||||
}
|
||||
|
||||
const index = dialog.index;
|
||||
const len = dialogs.length;
|
||||
if(!len || index < dialogs[len - 1].index) {
|
||||
dialogs.push(dialog);
|
||||
} else if(index >= dialogs[0].index) {
|
||||
dialogs.unshift(dialog);
|
||||
} else {
|
||||
for(let i = 0; i < len; i++) {
|
||||
if(index > dialogs[i].index) {
|
||||
dialogs.splice(i, 0, dialog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
insertInDescendSortedArray(dialogs, dialog, 'index', pos);
|
||||
}
|
||||
|
||||
public dropDialog(peerId: number): [Dialog, number] | [] {
|
||||
|
Loading…
Reference in New Issue
Block a user