Browse Source

fixes & fixes

master
morethanwords 3 years ago
parent
commit
5f807a6552
  1. 71
      src/components/chat/bubbles.ts
  2. 6
      src/components/sidebarLeft/tabs/archivedTab.ts
  3. 137
      src/components/sortedUserList.ts
  4. 42
      src/components/transition.ts
  5. 53
      src/components/wrappers.ts
  6. 10
      src/helpers/array.ts
  7. 4
      src/helpers/dom/positionElementByIndex.ts
  8. 21
      src/helpers/schedulers.ts
  9. 163
      src/helpers/sortedList.ts
  10. 371
      src/lib/appManagers/appDialogsManager.ts
  11. 90
      src/lib/appManagers/appMessagesManager.ts
  12. 2
      src/lib/appManagers/appUsersManager.ts
  13. 1
      src/lib/mtproto/mtproto_config.ts
  14. 1
      src/lib/rootScope.ts
  15. 98
      src/lib/storages/dialogs.ts
  16. 4
      src/scss/partials/_chatBubble.scss
  17. 4
      src/scss/partials/_document.scss
  18. 29
      src/scss/style.scss

71
src/components/chat/bubbles.ts

@ -149,7 +149,7 @@ export default class ChatBubbles {
private replyFollowHistory: number[] = []; private replyFollowHistory: number[] = [];
private isHeavyAnimationInProgress = false; private isHeavyAnimationInProgress = false;
private scrollingToNewBubble: HTMLElement; private scrollingToBubble: HTMLElement;
private isFirstLoad = true; private isFirstLoad = true;
private needReflowScroll: boolean; private needReflowScroll: boolean;
@ -225,7 +225,7 @@ export default class ChatBubbles {
this.setBubblePosition(bubble, message, false); this.setBubblePosition(bubble, message, false);
//this.log('history_update', this.bubbles[mid], mid, message); //this.log('history_update', this.bubbles[mid], mid, message);
if(this.scrollingToNewBubble) { if(this.scrollingToBubble) {
this.scrollToBubbleEnd(); this.scrollToBubbleEnd();
} }
@ -1420,6 +1420,12 @@ export default class ChatBubbles {
return; return;
} */ } */
if(!scrolledDown) {
if(this.scrollingToBubble && this.scrollingToBubble === this.getLastBubble()) {
scrolledDown = true;
}
}
const promise = this.performHistoryResult(mids, false, true); const promise = this.performHistoryResult(mids, false, true);
if(scrolledDown) { if(scrolledDown) {
promise.then(() => { promise.then(() => {
@ -1437,6 +1443,13 @@ export default class ChatBubbles {
} }
} }
public getLastBubble() {
const lastDateGroup = this.getLastDateGroup();
if(lastDateGroup) {
return lastDateGroup.lastElementChild as HTMLElement;
}
}
public scrollToBubble( public scrollToBubble(
element: HTMLElement, element: HTMLElement,
position: ScrollLogicalPosition, position: ScrollLogicalPosition,
@ -1458,21 +1471,17 @@ export default class ChatBubbles {
return this.scrollable.scrollIntoViewNew(element, position, 4, undefined, forceDirection, forceDuration); return this.scrollable.scrollIntoViewNew(element, position, 4, undefined, forceDirection, forceDuration);
} }
public scrollToBubbleEnd(bubble?: HTMLElement) { public scrollToBubbleEnd(bubble = this.getLastBubble()) {
if(!bubble) {
const lastDateGroup = this.getLastDateGroup();
if(lastDateGroup) {
bubble = lastDateGroup.lastElementChild as HTMLElement;
}
}
/* if(DEBUG) { /* if(DEBUG) {
this.log('scrollToNewLastBubble: will scroll into view:', bubble); this.log('scrollToNewLastBubble: will scroll into view:', bubble);
} */ } */
if(bubble) { if(bubble) {
this.scrollingToNewBubble = bubble; this.scrollingToBubble = bubble;
const middleware = this.getMiddleware();
this.scrollToBubble(bubble, 'end').then(() => { this.scrollToBubble(bubble, 'end').then(() => {
this.scrollingToNewBubble = null; if(!middleware()) return;
this.scrollingToBubble = undefined;
}); });
} }
} }
@ -1517,7 +1526,7 @@ export default class ChatBubbles {
const date = new Date(message.date * 1000); const date = new Date(message.date * 1000);
date.setHours(0, 0, 0); date.setHours(0, 0, 0);
const dateTimestamp = date.getTime(); const dateTimestamp = date.getTime();
if(!(dateTimestamp in this.dateMessages)) { if(!this.dateMessages[dateTimestamp]) {
let dateElement: HTMLElement; let dateElement: HTMLElement;
const today = new Date(); const today = new Date();
@ -1549,8 +1558,8 @@ export default class ChatBubbles {
} }
} }
const div = document.createElement('div'); const bubble = document.createElement('div');
div.className = 'bubble service is-date'; bubble.className = 'bubble service is-date';
const bubbleContent = document.createElement('div'); const bubbleContent = document.createElement('div');
bubbleContent.classList.add('bubble-content'); bubbleContent.classList.add('bubble-content');
const serviceMsg = document.createElement('div'); const serviceMsg = document.createElement('div');
@ -1559,36 +1568,38 @@ export default class ChatBubbles {
serviceMsg.append(dateElement); serviceMsg.append(dateElement);
bubbleContent.append(serviceMsg); bubbleContent.append(serviceMsg);
div.append(bubbleContent); bubble.append(bubbleContent);
////////this.log('need to render date message', dateTimestamp, str); ////////this.log('need to render date message', dateTimestamp, str);
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'bubbles-date-group'; container.className = 'bubbles-date-group';
container.append(bubble);
this.dateMessages[dateTimestamp] = {
div: bubble,
container,
firstTimestamp: date.getTime()
};
const haveTimestamps = getObjectKeysAndSort(this.dateMessages, 'asc'); const haveTimestamps = getObjectKeysAndSort(this.dateMessages, 'asc');
let i = 0; let i = 0, length = haveTimestamps.length, insertBefore: HTMLElement; // there can be 'first bubble' (e.g. bot description) so can't insert by index
for(; i < haveTimestamps.length; ++i) { for(; i < haveTimestamps.length; ++i) {
const t = haveTimestamps[i]; const t = haveTimestamps[i];
insertBefore = this.dateMessages[t].container;
if(dateTimestamp < t) { if(dateTimestamp < t) {
break; break;
} }
} }
this.dateMessages[dateTimestamp] = {
div,
container,
firstTimestamp: date.getTime()
};
container.append(div); if(i === length && insertBefore) {
insertBefore = insertBefore.nextElementSibling as HTMLElement;
positionElementByIndex(container, this.chatInner, i); }
/* if(reverse) { if(!insertBefore) {
this.chatInner.prepend(container);
} else {
this.chatInner.append(container); this.chatInner.append(container);
} */ } else {
this.chatInner.insertBefore(container, insertBefore);
}
if(this.stickyIntersector) { if(this.stickyIntersector) {
this.stickyIntersector.observeStickyHeaderChanges(container); this.stickyIntersector.observeStickyHeaderChanges(container);
@ -1676,6 +1687,8 @@ export default class ChatBubbles {
this.onAnimateLadder = undefined; this.onAnimateLadder = undefined;
this.resolveLadderAnimation = undefined; this.resolveLadderAnimation = undefined;
this.emptyPlaceholderMid = undefined; this.emptyPlaceholderMid = undefined;
this.scrollingToBubble = undefined;
////console.timeEnd('appImManager cleanup'); ////console.timeEnd('appImManager cleanup');
} }

6
src/components/sidebarLeft/tabs/archivedTab.ts

@ -15,7 +15,7 @@ export default class AppArchivedTab extends SliderSuperTab {
this.container.id = 'chats-archived-container'; this.container.id = 'chats-archived-container';
this.setTitle('ArchivedChats'); this.setTitle('ArchivedChats');
if(!appDialogsManager.chatLists[AppArchivedTab.filterId]) { if(!appDialogsManager.sortedLists[AppArchivedTab.filterId]) {
const chatList = appDialogsManager.createChatList(); const chatList = appDialogsManager.createChatList();
appDialogsManager.generateScrollable(chatList, AppArchivedTab.filterId).container.append(chatList); appDialogsManager.generateScrollable(chatList, AppArchivedTab.filterId).container.append(chatList);
appDialogsManager.setListClickListener(chatList, null, true); appDialogsManager.setListClickListener(chatList, null, true);
@ -40,7 +40,7 @@ export default class AppArchivedTab extends SliderSuperTab {
// вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд... // вообще, так делать нельзя, но нет времени чтобы переделать главный чатлист на слайд...
onOpenAfterTimeout() { onOpenAfterTimeout() {
appDialogsManager.chatLists[this.wasFilterId].innerHTML = ''; appDialogsManager.sortedLists[this.wasFilterId].clear();
} }
onClose() { onClose() {
@ -49,7 +49,7 @@ export default class AppArchivedTab extends SliderSuperTab {
} }
onCloseAfterTimeout() { onCloseAfterTimeout() {
appDialogsManager.chatLists[AppArchivedTab.filterId].innerHTML = ''; appDialogsManager.sortedLists[AppArchivedTab.filterId].clear();
return super.onCloseAfterTimeout(); return super.onCloseAfterTimeout();
} }
} }

137
src/components/sortedUserList.ts

@ -8,21 +8,19 @@ import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager"; import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager";
import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck"; import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
import appUsersManager from "../lib/appManagers/appUsersManager"; import appUsersManager from "../lib/appManagers/appUsersManager";
import { insertInDescendSortedArray } from "../helpers/array";
import isInDOM from "../helpers/dom/isInDOM"; import isInDOM from "../helpers/dom/isInDOM";
import positionElementByIndex from "../helpers/dom/positionElementByIndex"; import positionElementByIndex from "../helpers/dom/positionElementByIndex";
import replaceContent from "../helpers/dom/replaceContent"; import replaceContent from "../helpers/dom/replaceContent";
import { safeAssign } from "../helpers/object"; import { safeAssign } from "../helpers/object";
import { fastRaf } from "../helpers/schedulers";
import SortedList, { SortedElementBase } from "../helpers/sortedList";
type SortedUser = { interface SortedUser extends SortedElementBase {
peerId: number,
status: number,
dom: DialogDom dom: DialogDom
}; }
export default class SortedUserList {
export default class SortedUserList extends SortedList<SortedUser> {
protected static SORT_INTERVAL = 30e3; protected static SORT_INTERVAL = 30e3;
protected users: Map<number, SortedUser>;
protected sorted: Array<SortedUser>;
public list: HTMLUListElement; public list: HTMLUListElement;
protected lazyLoadQueue: LazyLoadQueueIntersector; protected lazyLoadQueue: LazyLoadQueueIntersector;
@ -35,17 +33,53 @@ export default class SortedUserList {
rippleEnabled: SortedUserList['rippleEnabled'], rippleEnabled: SortedUserList['rippleEnabled'],
new: boolean new: boolean
}> = {}) { }> = {}) {
super({
getIndex: (id) => appUsersManager.getUserStatusForSort(id),
onDelete: (element) => element.dom.listEl.remove(),
onUpdate: (element) => {
const status = appUsersManager.getUserStatusString(element.id);
replaceContent(element.dom.lastMessageSpan, status);
},
onSort: (element, idx) => positionElementByIndex(element.dom.listEl, this.list, idx),
onElementCreate: (base) => {
const {dom} = appDialogsManager.addDialogNew({
dialog: base.id,
container: false,
drawStatus: false,
avatarSize: this.avatarSize,
autonomous: true,
meAsSaved: false,
rippleEnabled: this.rippleEnabled,
lazyLoadQueue: this.lazyLoadQueue
});
(base as SortedUser).dom = dom;
return base as SortedUser;
},
updateElementWith: fastRaf,
updateListWith: async(callback) => {
if(!isInDOM(this.list)) {
return callback(false);
}
await getHeavyAnimationPromise();
if(!isInDOM(this.list)) {
return callback(false);
}
callback(true);
}
});
safeAssign(this, options); safeAssign(this, options);
this.list = appDialogsManager.createChatList({new: options.new}); this.list = appDialogsManager.createChatList({new: options.new});
this.users = new Map();
this.sorted = [];
let timeout: number; let timeout: number;
const doTimeout = () => { const doTimeout = () => {
timeout = window.setTimeout(() => { timeout = window.setTimeout(() => {
this.updateList().then((good) => { this.updateList((good) => {
if(good) { if(good) {
doTimeout(); doTimeout();
} }
@ -55,83 +89,4 @@ export default class SortedUserList {
doTimeout(); 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 has(peerId: number) {
return this.users.has(peerId);
}
public add(peerId: number) {
if(this.has(peerId)) {
return;
}
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: false,
drawStatus: false,
avatarSize: this.avatarSize,
autonomous: true,
meAsSaved: false,
rippleEnabled: this.rippleEnabled,
lazyLoadQueue: this.lazyLoadQueue
});
const sortedUser: SortedUser = {
peerId,
status: appUsersManager.getUserStatusForSort(peerId),
dom
};
this.users.set(peerId, sortedUser);
this.update(peerId);
}
public delete(peerId: number) {
const user = this.users.get(peerId);
if(!user) {
return;
}
user.dom.listEl.remove();
this.users.delete(peerId);
const idx = this.sorted.indexOf(user);
if(idx !== -1) {
this.sorted.splice(idx, 1);
}
}
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);
}
}
} }

42
src/components/transition.ts

@ -8,8 +8,6 @@ import rootScope from "../lib/rootScope";
import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise"; import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromise";
import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck"; import { dispatchHeavyAnimationEvent } from "../hooks/useHeavyAnimationCheck";
import whichChild from "../helpers/dom/whichChild"; import whichChild from "../helpers/dom/whichChild";
import findUpClassName from "../helpers/dom/findUpClassName";
import { isSafari } from "../helpers/userAgent";
import { cancelEvent } from "../helpers/dom/cancelEvent"; import { cancelEvent } from "../helpers/dom/cancelEvent";
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) { function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
@ -33,13 +31,13 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) { function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
// Jolly Cobra's // Workaround for scrollable content flickering during animation. // Jolly Cobra's // Workaround for scrollable content flickering during animation.
const scrollableContainer = findUpClassName(tabContent, 'scrollable-y'); // const scrollableContainer = findUpClassName(tabContent, 'scrollable-y');
if(scrollableContainer && scrollableContainer.style.overflowY !== 'hidden') { // if(scrollableContainer && scrollableContainer.style.overflowY !== 'hidden') {
// const scrollBarWidth = scrollableContainer.offsetWidth - scrollableContainer.clientWidth; // // const scrollBarWidth = scrollableContainer.offsetWidth - scrollableContainer.clientWidth;
scrollableContainer.style.overflowY = 'hidden'; // scrollableContainer.style.overflowY = 'hidden';
// scrollableContainer.style.paddingRight = `${scrollBarWidth}px`; // // scrollableContainer.style.paddingRight = `${scrollBarWidth}px`;
// this.container.classList.add('sliding'); // // this.container.classList.add('sliding');
} // }
//window.requestAnimationFrame(() => { //window.requestAnimationFrame(() => {
const width = prevTabContent.getBoundingClientRect().width; const width = prevTabContent.getBoundingClientRect().width;
@ -62,22 +60,22 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
return () => { return () => {
prevTabContent.style.transform = ''; prevTabContent.style.transform = '';
if(scrollableContainer) { // if(scrollableContainer) {
// Jolly Cobra's // Workaround for scrollable content flickering during animation. // // Jolly Cobra's // Workaround for scrollable content flickering during animation.
if(isSafari) { // ! safari doesn't respect sticky header, so it flicks when overflow is changing // if(isSafari) { // ! safari doesn't respect sticky header, so it flicks when overflow is changing
scrollableContainer.style.display = 'none'; // scrollableContainer.style.display = 'none';
} // }
scrollableContainer.style.overflowY = ''; // scrollableContainer.style.overflowY = '';
if(isSafari) { // if(isSafari) {
void scrollableContainer.offsetLeft; // reflow // void scrollableContainer.offsetLeft; // reflow
scrollableContainer.style.display = ''; // scrollableContainer.style.display = '';
} // }
// scrollableContainer.style.paddingRight = '0'; // // scrollableContainer.style.paddingRight = '0';
// this.container.classList.remove('sliding'); // // this.container.classList.remove('sliding');
} // }
}; };
} }

53
src/components/wrappers.ts

@ -1005,6 +1005,59 @@ export function renderImageWithFadeIn(container: HTMLElement,
}); });
} }
// export function renderImageWithFadeIn(container: HTMLElement,
// image: HTMLImageElement,
// url: string,
// needFadeIn: boolean,
// aspecter = container,
// thumbImage?: HTMLImageElement
// ) {
// if(needFadeIn) {
// // image.classList.add('fade-in-new', 'not-yet');
// image.classList.add('fade-in');
// }
// return new Promise<void>((resolve) => {
// /* if(photo._ === 'document') {
// console.error('wrapPhoto: will render document', photo, size, cacheContext);
// return resolve();
// } */
// renderImageFromUrl(image, url, () => {
// sequentialDom.mutateElement(container, () => {
// aspecter.append(image);
// // (needFadeIn ? getHeavyAnimationPromise() : Promise.resolve()).then(() => {
// // fastRaf(() => {
// resolve();
// // });
// if(needFadeIn) {
// fastRaf(() => {
// /* if(!image.isConnected) {
// alert('aaaa');
// } */
// // fastRaf(() => {
// image.classList.remove('not-yet');
// // });
// });
// image.addEventListener('transitionend', () => {
// sequentialDom.mutate(() => {
// image.classList.remove('fade-in-new');
// if(thumbImage) {
// thumbImage.remove();
// }
// });
// }, {once: true});
// }
// // });
// });
// });
// });
// }
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn}: { export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop, loadPromises, needFadeIn}: {
doc: MyDocument, doc: MyDocument,
div: HTMLElement, div: HTMLElement,

10
src/helpers/array.ts

@ -38,14 +38,22 @@ export function forEachReverse<T>(array: Array<T>, callback: (value: T, index?:
}; };
export function insertInDescendSortedArray<T extends {[smth in K]?: number}, K extends keyof T>(array: Array<T>, element: T, property: K, pos?: number) { export function insertInDescendSortedArray<T extends {[smth in K]?: number}, K extends keyof T>(array: Array<T>, element: T, property: K, pos?: number) {
const sortProperty: number = element[property];
if(pos === undefined) { if(pos === undefined) {
pos = array.indexOf(element); pos = array.indexOf(element);
if(pos !== -1) { if(pos !== -1) {
const prev = array[pos - 1];
const next = array[pos + 1];
if((!prev || prev[property] >= sortProperty) && (!next || next[property] <= sortProperty)) {
// console.warn('same pos', pos, sortProperty, prev, next);
return pos;
}
array.splice(pos, 1); array.splice(pos, 1);
} }
} }
const sortProperty: number = element[property];
const len = array.length; const len = array.length;
if(!len || sortProperty <= array[len - 1][property]) { if(!len || sortProperty <= array[len - 1][property]) {
return array.push(element) - 1; return array.push(element) - 1;

4
src/helpers/dom/positionElementByIndex.ts

@ -17,7 +17,9 @@ export default function positionElementByIndex(element: HTMLElement, container:
pos += 1; pos += 1;
} }
if(container.childElementCount > pos) { if(!pos) {
container.prepend(element);
} else if(container.childElementCount > pos) {
container.insertBefore(element, container.children[pos]); container.insertBefore(element, container.children[pos]);
} else { } else {
container.append(element); container.append(element);

21
src/helpers/schedulers.ts

@ -63,6 +63,27 @@ export function fastRaf(callback: NoneToVoidFunction) {
} }
} }
let fastRafConventionalCallbacks: NoneToVoidFunction[] | undefined, processing = false;
export function fastRafConventional(callback: NoneToVoidFunction) {
if(!fastRafConventionalCallbacks) {
fastRafConventionalCallbacks = [callback];
requestAnimationFrame(() => {
processing = true;
for(let i = 0; i < fastRafConventionalCallbacks.length; ++i) {
fastRafConventionalCallbacks[i]();
}
fastRafConventionalCallbacks = undefined;
processing = false;
});
} else if(processing) {
callback();
} else {
fastRafConventionalCallbacks.push(callback);
}
}
let rafPromise: Promise<DOMHighResTimeStamp>; let rafPromise: Promise<DOMHighResTimeStamp>;
export function fastRafPromise() { export function fastRafPromise() {
if(rafPromise) return rafPromise; if(rafPromise) return rafPromise;

163
src/helpers/sortedList.ts

@ -0,0 +1,163 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { insertInDescendSortedArray } from "./array";
import { getMiddleware } from "./middleware";
import { safeAssign } from "./object";
export type SortedElementBase = {
id: number,
index: number
};
export default class SortedList<SortedElement extends SortedElementBase> {
protected elements: Map<number, SortedElement>;
protected sorted: Array<SortedElement>;
protected getIndex: (id: number) => 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<SortedElement>['getIndex'],
onDelete?: SortedList<SortedElement>['onDelete'],
onUpdate?: SortedList<SortedElement>['onUpdate'],
onSort?: SortedList<SortedElement>['onSort'],
onElementCreate: SortedList<SortedElement>['onElementCreate'],
updateElementWith?: SortedList<SortedElement>['updateElementWith'],
updateListWith?: SortedList<SortedElement>['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: number) {
return this.elements.has(id);
}
public get(id: number) {
return this.elements.get(id);
}
public getAll() {
return this.elements;
}
public add(id: number, batch = false, updateElementWith?: SortedList<SortedElement>['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: number, 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 update(id: number, batch = false, element = this.get(id), updateElementWith?: SortedList<SortedElement>['updateElementWith']) {
if(!element) {
return;
}
element.index = this.getIndex(id);
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));
} */
});
}
}
}

371
src/lib/appManagers/appDialogsManager.ts

@ -43,7 +43,7 @@ import replaceContent from "../../helpers/dom/replaceContent";
import ConnectionStatusComponent from "../../components/connectionStatus"; import ConnectionStatusComponent from "../../components/connectionStatus";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
import { fastRafPromise } from "../../helpers/schedulers"; import { fastRaf, fastRafConventional, fastRafPromise } from "../../helpers/schedulers";
import SortedUserList from "../../components/sortedUserList"; import SortedUserList from "../../components/sortedUserList";
import { isTouchSupported } from "../../helpers/touchSupport"; import { isTouchSupported } from "../../helpers/touchSupport";
import handleTabSwipe from "../../helpers/dom/handleTabSwipe"; import handleTabSwipe from "../../helpers/dom/handleTabSwipe";
@ -52,6 +52,8 @@ import isInDOM from "../../helpers/dom/isInDOM";
import appPhotosManager, { MyPhoto } from "./appPhotosManager"; import appPhotosManager, { MyPhoto } from "./appPhotosManager";
import { MyDocument } from "./appDocsManager"; import { MyDocument } from "./appDocsManager";
import { setSendingStatus } from "../../components/sendingStatus"; import { setSendingStatus } from "../../components/sendingStatus";
import SortedList, { SortedElementBase } from "../../helpers/sortedList";
import debounce from "../../helpers/schedulers/debounce";
export type DialogDom = { export type DialogDom = {
avatarEl: AvatarElement, avatarEl: AvatarElement,
@ -68,14 +70,56 @@ export type DialogDom = {
subtitleEl: HTMLElement subtitleEl: HTMLElement
}; };
interface SortedDialog extends SortedElementBase {
dom: DialogDom,
loadPromises?: Promise<any>[]
}
class SortedDialogList extends SortedList<SortedDialog> {
constructor(public list: HTMLUListElement, public indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>) {
super({
getIndex: (id) => appMessagesManager.getDialogOnly(id)[this.indexKey],
onDelete: (element) => {
element.dom.listEl.remove();
appDialogsManager.onListLengthChange();
},
onSort: (element, idx) => {
const willChangeLength = element.dom.listEl.parentElement !== this.list;
positionElementByIndex(element.dom.listEl, this.list, idx);
if(willChangeLength) {
appDialogsManager.onListLengthChange();
}
},
onElementCreate: (base, batch) => {
const loadPromises: Promise<any>[] = batch ? [] : undefined;
const {dom} = appDialogsManager.addListDialog({dialog: base.id, loadPromises, isBatch: batch});
(base as SortedDialog).dom = dom;
if(loadPromises?.length) {
(base as SortedDialog).loadPromises = loadPromises;
Promise.all(loadPromises).finally(() => {
delete (base as SortedDialog).loadPromises;
});
}
return base as SortedDialog;
},
updateElementWith: fastRafConventional
});
}
public clear() {
this.list.innerHTML = '';
super.clear();
}
}
//const testScroll = false; //const testScroll = false;
//let testTopSlice = 1; //let testTopSlice = 1;
export class AppDialogsManager { export class AppDialogsManager {
private chatList: HTMLUListElement;
private doms: {[peerId: number]: DialogDom} = {};
private chatsContainer = document.getElementById('chatlist-container') as HTMLDivElement; private chatsContainer = document.getElementById('chatlist-container') as HTMLDivElement;
private chatsPreloader: HTMLElement; private chatsPreloader: HTMLElement;
@ -87,7 +131,8 @@ export class AppDialogsManager {
private contextMenu = new DialogsContextMenu(); private contextMenu = new DialogsContextMenu();
public chatLists: {[filterId: number]: HTMLUListElement} = {}; public sortedList: SortedDialogList;
public sortedLists: {[filterId: number]: SortedDialogList} = {};
public scrollables: {[filterId: number]: Scrollable} = {}; public scrollables: {[filterId: number]: Scrollable} = {};
public filterId: number; public filterId: number;
private folders: {[k in 'menu' | 'container' | 'menuScrollContainer']: HTMLElement} = { private folders: {[k in 'menu' | 'container' | 'menuScrollContainer']: HTMLElement} = {
@ -111,7 +156,6 @@ export class AppDialogsManager {
//private topOffsetIndex = 0; //private topOffsetIndex = 0;
private sliceTimeout: number; private sliceTimeout: number;
private reorderDialogsTimeout: number;
private lastActiveElements: Set<HTMLElement> = new Set(); private lastActiveElements: Set<HTMLElement> = new Set();
@ -122,6 +166,8 @@ export class AppDialogsManager {
private indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>; private indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>;
public onListLengthChange: () => Promise<void>;
constructor() { constructor() {
this.chatsPreloader = putPreloader(null, true); this.chatsPreloader = putPreloader(null, true);
@ -170,7 +216,7 @@ export class AppDialogsManager {
orderIndex: 0 orderIndex: 0
}); });
this.chatList = this.chatLists[this.filterId]; this.sortedList = this.sortedLists[this.filterId];
this.scroll = this.scrollables[this.filterId]; this.scroll = this.scrollables[this.filterId];
/* if(testScroll) { /* if(testScroll) {
@ -212,7 +258,7 @@ export class AppDialogsManager {
rootScope.addEventListener('dialog_flush', ({peerId}) => { rootScope.addEventListener('dialog_flush', ({peerId}) => {
const dialog = appMessagesManager.getDialogOnly(peerId); const dialog = appMessagesManager.getDialogOnly(peerId);
if(dialog) { if(dialog) {
this.setLastMessage(dialog); this.setLastMessage(dialog, undefined, undefined, undefined, undefined, undefined, true);
this.validateDialogForFilter(dialog); this.validateDialogForFilter(dialog);
this.setFiltersUnreadCount(); this.setFiltersUnreadCount();
} }
@ -313,7 +359,7 @@ export class AppDialogsManager {
elements.container.remove(); elements.container.remove();
elements.menu.remove(); elements.menu.remove();
delete this.chatLists[filter.id]; delete this.sortedLists[filter.id];
delete this.scrollables[filter.id]; delete this.scrollables[filter.id];
delete this.filtersRendered[filter.id]; delete this.filtersRendered[filter.id];
@ -328,6 +374,9 @@ export class AppDialogsManager {
const filter = appMessagesManager.filtersStorage.getFilter(filterId); const filter = appMessagesManager.filtersStorage.getFilter(filterId);
const renderedFilter = this.filtersRendered[filterId]; const renderedFilter = this.filtersRendered[filterId];
const sortedList = this.sortedLists[filterId];
sortedList.indexKey = appMessagesManager.dialogsStorage.getDialogIndexKey(filterId);
positionElementByIndex(renderedFilter.menu, containerToAppend, filter.orderIndex); positionElementByIndex(renderedFilter.menu, containerToAppend, filter.orderIndex);
positionElementByIndex(renderedFilter.container, this.folders.container, filter.orderIndex); positionElementByIndex(renderedFilter.container, this.folders.container, filter.orderIndex);
}); });
@ -393,13 +442,13 @@ export class AppDialogsManager {
if(this.filterId === id) return; if(this.filterId === id) return;
this.chatLists[id].innerHTML = ''; this.sortedLists[id].clear();
this.setFilterId(id); this.setFilterId(id);
this.onTabChange(); this.onTabChange();
}, () => { }, () => {
for(const folderId in this.chatLists) { for(const folderId in this.sortedLists) {
if(+folderId !== this.filterId) { if(+folderId !== this.filterId) {
this.chatLists[folderId].innerHTML = ''; this.sortedLists[folderId].clear();
} }
} }
}, undefined, foldersScrollable); }, undefined, foldersScrollable);
@ -436,6 +485,12 @@ export class AppDialogsManager {
setTimeout(() => { setTimeout(() => {
lottieLoader.loadLottieWorkers(); lottieLoader.loadLottieWorkers();
}, 200); }, 200);
this.onListLengthChange = debounce(this._onListLengthChange, 100, false, true);
}
public get chatList() {
return this.sortedList.list;
} }
public setFilterId(filterId: number) { public setFilterId(filterId: number) {
@ -484,7 +539,7 @@ export class AppDialogsManager {
} }
private isDialogMustBeInViewport(dialog: Dialog) { private isDialogMustBeInViewport(dialog: Dialog) {
if(dialog.migratedTo !== undefined) return false; if(dialog.migratedTo !== undefined || !this.testDialogForFilter(dialog)) return false;
//return true; //return true;
const topOffset = this.getOffsetIndex('top'); const topOffset = this.getOffsetIndex('top');
const bottomOffset = this.getOffsetIndex('bottom'); const bottomOffset = this.getOffsetIndex('bottom');
@ -498,69 +553,31 @@ export class AppDialogsManager {
} }
private deleteDialog(peerId: number) { private deleteDialog(peerId: number) {
const dom = this.getDialogDom(peerId); this.sortedList.delete(peerId);
if(dom) {
dom.listEl.remove();
delete this.doms[peerId];
this.onListLengthChange();
return true;
}
return false;
} }
private updateDialog(dialog: Dialog) { private updateDialog(dialog: Dialog) {
if(!dialog) {
return;
}
if(this.isDialogMustBeInViewport(dialog)) { if(this.isDialogMustBeInViewport(dialog)) {
if(!this.doms.hasOwnProperty(dialog.peerId)) { this.sortedList.add(dialog.peerId);
const ret = this.addListDialog({dialog});
if(ret) {
const idx = appMessagesManager.getDialogByPeerId(dialog.peerId)[1];
positionElementByIndex(ret.dom.listEl, this.chatList, idx);
this.onListLengthChange();
} else {
return;
}
}
} else { } else {
this.deleteDialog(dialog.peerId); this.deleteDialog(dialog.peerId);
return; return;
} }
/* const topOffset = this.getOffset('top'); const dom = this.getDialogDom(dialog.peerId);
if(topOffset.index && dialog.index > topOffset.index) { if(dom) {
const dom = this.getDialogDom(dialog.peerId); this.setLastMessage(dialog, undefined, dom, undefined, undefined, undefined, true);
if(dom) { this.sortedList.update(dialog.peerId);
dom.listEl.remove();
delete this.doms[dialog.peerId];
}
return;
}
if(!this.doms.hasOwnProperty(dialog.peerId)) {
this.addDialogNew({dialog});
} */
if(this.getDialogDom(dialog.peerId)) {
this.setLastMessage(dialog);
this.reorderDialogs();
} }
} }
public onTabChange = () => { public onTabChange = () => {
this.doms = {};
this.scroll = this.scrollables[this.filterId]; this.scroll = this.scrollables[this.filterId];
this.scroll.loadedAll.top = true; this.scroll.loadedAll.top = true;
this.scroll.loadedAll.bottom = false; this.scroll.loadedAll.bottom = false;
this.offsets.top = this.offsets.bottom = 0; this.offsets.top = this.offsets.bottom = 0;
this.loadDialogsPromise = undefined; this.loadDialogsPromise = undefined;
this.chatList = this.chatLists[this.filterId]; this.sortedList = this.sortedLists[this.filterId];
this.onChatsScroll(); this.onChatsScroll();
}; };
@ -603,35 +620,35 @@ export class AppDialogsManager {
* Удалит неподходящие чаты из списка, но не добавит их(!) * Удалит неподходящие чаты из списка, но не добавит их(!)
*/ */
private validateListForFilter() { private validateListForFilter() {
// !WARNING, возможно это было зачем-то, но комментарий исправил архивирование const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
//if(this.filterId === 0) return; this.sortedList.getAll().forEach((element) => {
const dialog = appMessagesManager.getDialogOnly(element.id);
const folder = appMessagesManager.dialogsStorage.getFolder(this.filterId); if(!this.testDialogForFilter(dialog, filter || null)) {
for(const _peerId in this.doms) { this.deleteDialog(element.id);
const peerId = +_peerId;
// если больше не подходит по фильтру, удаляем
if(folder.findIndex((dialog) => dialog.peerId === peerId) === -1) {
this.deleteDialog(peerId);
} }
} });
} }
/** /**
* Удалит неподходящие чат из списка, но не добавит его(!) * Удалит неподходящий чат из списка, но не добавит его(!)
*/ */
private validateDialogForFilter(dialog: Dialog, filter?: MyDialogFilter) { private validateDialogForFilter(dialog: Dialog, filter?: MyDialogFilter) {
if(this.filterId <= 1 || !this.doms[dialog.peerId]) { if(!this.getDialogDom(dialog.peerId)) {
return; return;
} }
if(!filter) { if(!this.testDialogForFilter(dialog, filter)) {
filter = appMessagesManager.filtersStorage.getFilter(this.filterId); this.deleteDialog(dialog.peerId);
} }
}
if(!appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) { public testDialogForFilter(dialog: Dialog, filter = appMessagesManager.filtersStorage.getFilter(this.filterId)) {
this.deleteDialog(dialog.peerId); if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) ||
(!filter && this.filterId !== dialog.folder_id)) {
return false;
} }
return true;
} }
public generateScrollable(list: HTMLUListElement, filterId: number) { public generateScrollable(list: HTMLUListElement, filterId: number) {
@ -642,8 +659,13 @@ export class AppDialogsManager {
scrollable.onScrolledBottom = this.onChatsScroll; scrollable.onScrolledBottom = this.onChatsScroll;
scrollable.setVirtualContainer(list); scrollable.setVirtualContainer(list);
this.chatLists[filterId] = list; const sortedDialogList = new SortedDialogList(list, appMessagesManager.dialogsStorage ? appMessagesManager.dialogsStorage.getDialogIndexKey(filterId) : 'index');
this.scrollables[filterId] = scrollable; this.scrollables[filterId] = scrollable;
this.sortedLists[filterId] = sortedDialogList;
// list.classList.add('hide');
// scrollable.container.style.backgroundColor = '#' + (Math.random() * (16 ** 6 - 1) | 0).toString(16);
return scrollable; return scrollable;
} }
@ -723,15 +745,12 @@ export class AppDialogsManager {
if(this.loadDialogsPromise/* || 1 === 1 */) return this.loadDialogsPromise; if(this.loadDialogsPromise/* || 1 === 1 */) return this.loadDialogsPromise;
const promise = new Promise<void>(async(resolve) => { const promise = new Promise<void>(async(resolve) => {
if(!this.chatList.childElementCount) { const {chatList, filterId} = this;
const container = this.chatList.parentElement;
container.append(this.chatsPreloader);
}
//return; //return;
const filterId = this.filterId; // let loadCount = 30/*this.chatsLoadCount */;
let loadCount = 30/*this.chatsLoadCount */; let loadCount = windowSize.windowH / 72 * 1.25 | 0;
let offsetIndex = 0; let offsetIndex = 0;
const {index: currentOffsetIndex} = this.getOffsetIndex(side); const {index: currentOffsetIndex} = this.getOffsetIndex(side);
@ -753,8 +772,9 @@ export class AppDialogsManager {
//console.time('getDialogs time'); //console.time('getDialogs time');
const getConversationsResult = appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true); const getConversationsResult = appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true);
if(getConversationsResult.cached) { if(!getConversationsResult.cached && !chatList.childElementCount) {
this.chatsPreloader.remove(); const container = chatList.parentElement;
container.append(this.chatsPreloader);
} }
const result = await getConversationsResult.promise; const result = await getConversationsResult.promise;
@ -766,43 +786,37 @@ export class AppDialogsManager {
//console.timeEnd('getDialogs time'); //console.timeEnd('getDialogs time');
// * loaded all // * loaded all
//if(!result.dialogs.length || this.chatList.childElementCount === result.count) { //if(!result.dialogs.length || chatList.childElementCount === result.count) {
// !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст. // !result.dialogs.length не подходит, так как при супердревном диалоге getConversations его не выдаст.
//if(this.chatList.childElementCount === result.count) { //if(chatList.childElementCount === result.count) {
if(side === 'bottom') { if(side === 'bottom') {
if(result.isEnd) { if(result.isEnd) {
this.scroll.loadedAll[side] = true; this.scroll.loadedAll[side] = true;
} }
} else { } else if(result.isTopEnd) {
const storage = appMessagesManager.dialogsStorage.getFolder(filterId); this.scroll.loadedAll[side] = true;
if(!result.dialogs.length || (storage.length && storage[0][this.indexKey] < offsetIndex)) {
this.scroll.loadedAll[side] = true;
}
} }
if(result.dialogs.length) { if(result.dialogs.length) {
const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs; const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs;
const container = document.createDocumentFragment();
const loadPromises: Promise<any>[] = []; const loadPromises: Promise<any>[] = [];
const append = side === 'bottom';
const callbacks: (() => void)[] = [];
const cccc = (callback: () => void) => {
callbacks.push(callback);
};
dialogs.forEach((dialog) => { dialogs.forEach((dialog) => {
this.addListDialog({ const element = this.sortedList.add(dialog.peerId, true, cccc, false);
dialog, if(element.loadPromises) {
container, loadPromises.push(...element.loadPromises);
append, }
loadPromises,
isBatch: true
});
}); });
if(container.childElementCount) { await Promise.all(loadPromises).finally();
if(loadPromises.length) {
await Promise.all(loadPromises).finally();
}
this.scroll[append ? 'append' : 'prepend'](container); callbacks.forEach(callback => callback());
}
} }
const offsetDialog = result.dialogs[side === 'top' ? 0 : result.dialogs.length - 1]; const offsetDialog = result.dialogs[side === 'top' ? 0 : result.dialogs.length - 1];
@ -810,9 +824,7 @@ export class AppDialogsManager {
this.offsets[side] = offsetDialog[this.indexKey]; this.offsets[side] = offsetDialog[this.indexKey];
} }
this.onListLengthChange(); this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, chatList.childElementCount);
this.log.debug('getDialogs ' + loadCount + ' dialogs by offset:', offsetIndex, result, this.chatList.childElementCount);
setTimeout(() => { setTimeout(() => {
this.scroll.onScroll(); this.scroll.onScroll();
@ -821,7 +833,10 @@ export class AppDialogsManager {
this.log.error(err); this.log.error(err);
} }
this.chatsPreloader.remove(); if(this.chatsPreloader.parentElement) {
this.chatsPreloader.remove();
}
resolve(); resolve();
}).finally(() => { }).finally(() => {
this.loadDialogsPromise = undefined; this.loadDialogsPromise = undefined;
@ -860,10 +875,11 @@ export class AppDialogsManager {
return; return;
} }
const part = this.chatList.parentElement as HTMLElement; const chatList = this.chatList;
const part = chatList.parentElement as HTMLElement;
let placeholderContainer = (Array.from(part.children) as HTMLElement[]).find(el => el.matches('.empty-placeholder')); let placeholderContainer = (Array.from(part.children) as HTMLElement[]).find(el => el.matches('.empty-placeholder'));
const needPlaceholder = this.scroll.loadedAll.bottom && !this.chatList.childElementCount/* || true */; const needPlaceholder = this.scroll.loadedAll.bottom && !chatList.childElementCount/* || true */;
// this.chatList.style.display = 'none'; // chatList.style.display = 'none';
if(needPlaceholder && placeholderContainer) { if(needPlaceholder && placeholderContainer) {
return; return;
@ -944,15 +960,16 @@ export class AppDialogsManager {
part.classList.add('with-placeholder'); part.classList.add('with-placeholder');
} }
private onListLengthChange = () => { public _onListLengthChange = () => {
this.checkIfPlaceholderNeeded(); this.checkIfPlaceholderNeeded();
if(this.filterId > 0) return; if(this.filterId > 0) return;
const count = this.chatList.childElementCount; const chatList = this.chatList;
const count = chatList.childElementCount;
const parts = this.chatList.parentElement.parentElement; const parts = chatList.parentElement.parentElement;
const bottom = this.chatList.parentElement.nextElementSibling as HTMLElement; const bottom = chatList.parentElement.nextElementSibling as HTMLElement;
const hasContacts = !!bottom.childElementCount; const hasContacts = !!bottom.childElementCount;
if(count >= 10) { if(count >= 10) {
if(hasContacts) { if(hasContacts) {
@ -1019,14 +1036,11 @@ export class AppDialogsManager {
}; };
public onChatsRegularScroll = () => { public onChatsRegularScroll = () => {
// return;
if(this.sliceTimeout) clearTimeout(this.sliceTimeout); if(this.sliceTimeout) clearTimeout(this.sliceTimeout);
this.sliceTimeout = window.setTimeout(() => { this.sliceTimeout = window.setTimeout(() => {
this.sliceTimeout = undefined; this.sliceTimeout = undefined;
if(this.reorderDialogsTimeout) {
this.onChatsRegularScroll();
return;
}
if(!this.chatList.childElementCount || this.processContact) { if(!this.chatList.childElementCount || this.processContact) {
return; return;
@ -1040,6 +1054,10 @@ export class AppDialogsManager {
observer.observe(el); observer.observe(el);
}); */ }); */
fastRafConventional(() => {
const perf = performance.now();
const scrollTopWas = this.scroll.scrollTop; const scrollTopWas = this.scroll.scrollTop;
const firstElementChild = this.chatList.firstElementChild; const firstElementChild = this.chatList.firstElementChild;
@ -1115,16 +1133,20 @@ export class AppDialogsManager {
//alert('left length:' + children.length); //alert('left length:' + children.length);
this.scroll.scrollTop = firstElement.offsetTop - elementOverflow; this.scroll.scrollTop = firstElement.offsetTop - elementOverflow;
this.log('slice time', performance.now() - perf);
/* const firstElementRect = firstElement.getBoundingClientRect(); /* const firstElementRect = firstElement.getBoundingClientRect();
const scrollTop = */ const scrollTop = */
//this.scroll.scrollIntoView(firstElement, false); //this.scroll.scrollIntoView(firstElement, false);
});
}, 200); }, 200);
}; };
private setOffsets() { private setOffsets() {
const firstDialog = this.getDialogFromElement(this.chatList.firstElementChild as HTMLElement); const chatList = this.chatList;
const lastDialog = this.getDialogFromElement(this.chatList.lastElementChild as HTMLElement); const firstDialog = this.getDialogFromElement(chatList.firstElementChild as HTMLElement);
const lastDialog = this.getDialogFromElement(chatList.lastElementChild as HTMLElement);
this.offsets.top = firstDialog[this.indexKey]; this.offsets.top = firstDialog[this.indexKey];
this.offsets.bottom = lastDialog[this.indexKey]; this.offsets.bottom = lastDialog[this.indexKey];
@ -1230,71 +1252,14 @@ export class AppDialogsManager {
return list; return list;
} }
private reorderDialogs() {
//const perf = performance.now();
if(this.reorderDialogsTimeout) {
return;
}
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
this.reorderDialogsTimeout = window.requestAnimationFrame(() => {
this.reorderDialogsTimeout = 0;
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterId);
const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.dataset.peerId);
const {index} = this.getOffsetIndex('top');
const pos = dialogs.findIndex(dialog => dialog[this.indexKey] <= index);
const offset = Math.max(0, pos);
dialogs.forEach((dialog, index) => {
const dom = this.getDialogDom(dialog.peerId);
if(!dom) {
return;
}
const needIndex = index - offset;
if(needIndex > currentOrder.length) {
this.deleteDialog(dialog.peerId);
return;
}
const peerIdByIndex = currentOrder[needIndex];
if(peerIdByIndex !== dialog.peerId) {
if(positionElementByIndex(dom.listEl, this.chatList, needIndex)) {
this.log.debug('setDialogPosition:', dialog, dom, peerIdByIndex, needIndex);
}
}
});
//this.log('Reorder time:', performance.now() - perf);
});
}
public setLastMessage( public setLastMessage(
dialog: Dialog, dialog: Dialog,
lastMessage?: any, lastMessage?: any,
dom?: DialogDom, dom?: DialogDom,
highlightWord?: string, highlightWord?: string,
loadPromises?: Promise<any>[], loadPromises?: Promise<any>[],
isBatch = false isBatch = false,
setUnread = false
) { ) {
///////console.log('setlastMessage:', lastMessage); ///////console.log('setlastMessage:', lastMessage);
if(!dom) { if(!dom) {
@ -1408,16 +1373,16 @@ export class AppDialogsManager {
replaceContent(dom.lastTimeSpan, formatDateAccordingToTodayNew(new Date(date * 1000))); replaceContent(dom.lastTimeSpan, formatDateAccordingToTodayNew(new Date(date * 1000)));
} else dom.lastTimeSpan.textContent = ''; } else dom.lastTimeSpan.textContent = '';
if(this.doms[peerId] === dom) { if(setUnread !== null) {
this.setUnreadMessages(dialog, isBatch); if(setUnread) {
} else { // means search this.setUnreadMessages(dialog, dom, isBatch);
dom.listEl.dataset.mid = lastMessage.mid; } else { // means search
dom.listEl.dataset.mid = lastMessage.mid;
}
} }
} }
private setUnreadMessages(dialog: Dialog, isBatch = false) { private setUnreadMessages(dialog: Dialog, dom = this.getDialogDom(dialog.peerId), isBatch = false) {
const dom = this.getDialogDom(dialog.peerId);
if(dialog.folder_id === 1) { if(dialog.folder_id === 1) {
this.accumulateArchivedUnread(); this.accumulateArchivedUnread();
} }
@ -1523,7 +1488,9 @@ export class AppDialogsManager {
} }
private getDialogDom(peerId: number) { private getDialogDom(peerId: number) {
return this.doms[peerId]; // return this.doms[peerId];
const element = this.sortedList.get(peerId);
return element?.dom;
} }
private getDialog(dialog: Dialog | number): Dialog { private getDialog(dialog: Dialog | number): Dialog {
@ -1543,32 +1510,20 @@ export class AppDialogsManager {
return dialog; return dialog;
} }
private addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) { public addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) {
const dialog = this.getDialog(options.dialog); const dialog = this.getDialog(options.dialog);
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) ||
(!filter && this.filterId !== dialog.folder_id)) {
return;
}
if(!options.container) {
options.container = this.scroll;
}
options.autonomous = false; options.autonomous = false;
const ret = this.addDialogNew(options); const ret = this.addDialogNew(options);
if(ret) { if(ret) {
this.doms[dialog.peerId] = ret.dom;
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true); const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
if(isMuted) { if(isMuted) {
ret.dom.listEl.classList.add('is-muted'); ret.dom.listEl.classList.add('is-muted');
} }
this.setLastMessage(dialog, undefined, undefined, undefined, options.loadPromises, options.isBatch); this.setLastMessage(dialog, undefined, ret.dom, undefined, options.loadPromises, options.isBatch, true);
} }
return ret; return ret;
@ -1745,7 +1700,7 @@ export class AppDialogsManager {
} }
dom.lastMessageSpan.classList.remove('user-typing'); dom.lastMessageSpan.classList.remove('user-typing');
this.setLastMessage(dialog, null, dom); this.setLastMessage(dialog, null, dom, undefined, undefined, undefined, null);
} }
} }

90
src/lib/appManagers/appMessagesManager.ts

@ -51,7 +51,7 @@ import PeerTitle from "../../components/peerTitle";
import { forEachReverse } from "../../helpers/array"; import { forEachReverse } from "../../helpers/array";
import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment"; import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment";
import htmlToSpan from "../../helpers/dom/htmlToSpan"; import htmlToSpan from "../../helpers/dom/htmlToSpan";
import { REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config"; import { MUTE_UNTIL, REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config";
import formatCallDuration from "../../helpers/formatCallDuration"; import formatCallDuration from "../../helpers/formatCallDuration";
import appAvatarsManager from "./appAvatarsManager"; import appAvatarsManager from "./appAvatarsManager";
import telegramMeWebManager from "../mtproto/telegramMeWebManager"; import telegramMeWebManager from "../mtproto/telegramMeWebManager";
@ -1379,8 +1379,8 @@ export class AppMessagesManager {
//if(!options.isGroupedItem) { //if(!options.isGroupedItem) {
this.saveMessages([message], {storage, isOutgoing: true}); this.saveMessages([message], {storage, isOutgoing: true});
this.setDialogTopMessage(message);
setTimeout(() => { setTimeout(() => {
this.setDialogTopMessage(message);
rootScope.dispatchEvent('history_append', {storage, peerId, mid: messageId}); rootScope.dispatchEvent('history_append', {storage, peerId, mid: messageId});
}, 0); }, 0);
} }
@ -3096,7 +3096,7 @@ export class AppMessagesManager {
return true; return true;
} }
public canDeleteMessage(message: any) { public canDeleteMessage(message: MyMessage) {
return message && ( return message && (
message.peerId > 0 message.peerId > 0
|| message.fromId === rootScope.myId || message.fromId === rootScope.myId
@ -3553,7 +3553,7 @@ export class AppMessagesManager {
} }
} }
handleNewMessages = () => { private handleNewMessages = () => {
clearTimeout(this.newMessagesHandleTimeout); clearTimeout(this.newMessagesHandleTimeout);
this.newMessagesHandleTimeout = 0; this.newMessagesHandleTimeout = 0;
@ -3561,7 +3561,7 @@ export class AppMessagesManager {
this.newMessagesToHandle = {}; this.newMessagesToHandle = {};
}; };
handleNewDialogs = () => { private handleNewDialogs = () => {
let newMaxSeenId = 0; let newMaxSeenId = 0;
const obj = this.newDialogsToHandle; const obj = this.newDialogsToHandle;
for(const peerId in obj) { for(const peerId in obj) {
@ -3593,8 +3593,9 @@ export class AppMessagesManager {
} }
if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise; if(this.newDialogsHandlePromise) return this.newDialogsHandlePromise;
return this.newDialogsHandlePromise = new Promise((resolve) => { return this.newDialogsHandlePromise = new Promise<void>((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve();
this.newDialogsHandlePromise = undefined; this.newDialogsHandlePromise = undefined;
this.handleNewDialogs(); this.handleNewDialogs();
}, 0); }, 0);
@ -4113,7 +4114,7 @@ export class AppMessagesManager {
} }
if((message as Message.message).fwd_from) { if((message as Message.message).fwd_from) {
notifyPeerToHandle.fwdCount++; ++notifyPeerToHandle.fwdCount;
} }
notifyPeerToHandle.topMessage = message; notifyPeerToHandle.topMessage = message;
@ -4322,17 +4323,10 @@ export class AppMessagesManager {
}; };
private onUpdateChannelAvailableMessages = (update: Update.updateChannelAvailableMessages) => { private onUpdateChannelAvailableMessages = (update: Update.updateChannelAvailableMessages) => {
const channelId: number = update.channel_id; const peerId: number = -update.channel_id;
const messages: number[] = [];
const peerId: number = -channelId;
const history = this.getHistoryStorage(peerId).history.slice; const history = this.getHistoryStorage(peerId).history.slice;
if(history.length) { const availableMinId = appMessagesIdsManager.generateMessageId(update.available_min_id);
history.forEach((msgId: number) => { const messages = history.filter(mid => mid <= availableMinId);
if(!update.available_min_id || msgId <= update.available_min_id) {
messages.push(msgId);
}
});
}
(update as any as Update.updateDeleteChannelMessages).messages = messages; (update as any as Update.updateDeleteChannelMessages).messages = messages;
this.onUpdateDeleteMessages(update as any as Update.updateDeleteChannelMessages); this.onUpdateDeleteMessages(update as any as Update.updateDeleteChannelMessages);
@ -4368,17 +4362,14 @@ export class AppMessagesManager {
return this.getHistoryStorage(+splitted[0], +splitted[1]); return this.getHistoryStorage(+splitted[0], +splitted[1]);
}); });
[this.getHistoryStorage(peerId)].concat(threadsStorages).forEach(historyStorage => { const historyStorage = this.getHistoryStorage(peerId);
for(const mid in historyUpdated.msgs) { [historyStorage].concat(threadsStorages).forEach(historyStorage => {
historyStorage.history.delete(+mid); for(const mid of historyUpdated.msgs) {
historyStorage.history.delete(mid);
} }
if(historyUpdated.count &&
historyStorage.count !== null && if(historyUpdated.count && historyStorage.count) {
historyStorage.count > 0) { historyStorage.count = Math.max(0, historyStorage.count - historyUpdated.count);
historyStorage.count -= historyUpdated.count;
if(historyStorage.count < 0) {
historyStorage.count = 0;
}
} }
}); });
@ -4392,12 +4383,21 @@ export class AppMessagesManager {
if(historyUpdated.unread) { if(historyUpdated.unread) {
foundDialog.unread_count -= historyUpdated.unread; foundDialog.unread_count -= historyUpdated.unread;
}
if(historyUpdated.unreadMentions || historyUpdated.unread) {
rootScope.dispatchEvent('dialog_unread', {peerId}); rootScope.dispatchEvent('dialog_unread', {peerId});
} }
if(historyUpdated.msgs.has(foundDialog.top_message)) { if(historyUpdated.msgs.has(foundDialog.top_message)) {
this.reloadConversation(peerId); const slice = historyStorage.history.first;
if(slice.isEnd(SliceEnd.Bottom) && slice.length) {
const mid = slice[0];
const message = this.getMessageByPeer(peerId, mid);
this.setDialogTopMessage(message, foundDialog);
} else {
this.reloadConversation(peerId);
}
} }
} }
}; };
@ -4420,7 +4420,7 @@ export class AppMessagesManager {
const dialog = this.getDialogOnly(peerId); const dialog = this.getDialogOnly(peerId);
if(!!dialog !== needDialog) { if(!!dialog !== needDialog) {
if(needDialog) { if(needDialog) {
this.reloadConversation(-channelId); this.reloadConversation(peerId);
} else { } else {
if(dialog) { if(dialog) {
this.dialogsStorage.dropDialog(peerId); this.dialogsStorage.dropDialog(peerId);
@ -4431,13 +4431,12 @@ export class AppMessagesManager {
}; };
private onUpdateChannelReload = (update: Update.updateChannelReload) => { private onUpdateChannelReload = (update: Update.updateChannelReload) => {
const channelId = update.channel_id; const peerId = update.channel_id;
const peerId = -channelId;
this.dialogsStorage.dropDialog(peerId); this.dialogsStorage.dropDialog(peerId);
delete this.historiesStorage[peerId]; delete this.historiesStorage[peerId];
this.reloadConversation(-channelId).then(() => { this.reloadConversation(peerId).then(() => {
rootScope.dispatchEvent('history_reload', peerId); rootScope.dispatchEvent('history_reload', peerId);
}); });
}; };
@ -4447,7 +4446,7 @@ export class AppMessagesManager {
const peerId = -update.channel_id; const peerId = -update.channel_id;
const mid = appMessagesIdsManager.generateMessageId(update.id); const mid = appMessagesIdsManager.generateMessageId(update.id);
const message: Message.message = this.getMessageByPeer(peerId, mid); const message: Message.message = this.getMessageByPeer(peerId, mid);
if(!message.deleted && message.views && message.views < views) { if(!message.deleted && message.views !== undefined && message.views < views) {
message.views = views; message.views = views;
rootScope.dispatchEvent('message_views', {peerId, mid, views}); rootScope.dispatchEvent('message_views', {peerId, mid, views});
} }
@ -4458,7 +4457,7 @@ export class AppMessagesManager {
const fromId = SERVICE_PEER_ID; const fromId = SERVICE_PEER_ID;
const peerId = fromId; const peerId = fromId;
const messageId = this.generateTempMessageId(peerId); const messageId = this.generateTempMessageId(peerId);
const message: any = { const message: Message.message = {
_: 'message', _: 'message',
id: messageId, id: messageId,
from_id: appPeersManager.getOutputPeer(fromId), from_id: appPeersManager.getOutputPeer(fromId),
@ -4474,7 +4473,7 @@ export class AppMessagesManager {
_: 'user', _: 'user',
id: fromId, id: fromId,
pFlags: {verified: true}, pFlags: {verified: true},
access_hash: 0, access_hash: '0',
first_name: 'Telegram', first_name: 'Telegram',
phone: '42777' phone: '42777'
}]); }]);
@ -4595,7 +4594,7 @@ export class AppMessagesManager {
} }
}; };
public setDialogToStateIfMessageIsTop(message: any) { public setDialogToStateIfMessageIsTop(message: MyMessage) {
const dialog = this.getDialogOnly(message.peerId); const dialog = this.getDialogOnly(message.peerId);
if(dialog && dialog.top_message === message.mid) { if(dialog && dialog.top_message === message.mid) {
this.dialogsStorage.setDialogToState(dialog); this.dialogsStorage.setDialogToState(dialog);
@ -4642,9 +4641,9 @@ export class AppMessagesManager {
return promise; return promise;
} }
private checkPendingMessage(message: any) { private checkPendingMessage(message: MyMessage) {
const randomId = this.pendingByMessageId[message.mid]; const randomId = this.pendingByMessageId[message.mid];
let pendingMessage: any; let pendingMessage: ReturnType<AppMessagesManager['finalizePendingMessage']>;
if(randomId) { if(randomId) {
const pendingData = this.pendingByRandomId[randomId]; const pendingData = this.pendingByRandomId[randomId];
if(pendingMessage = this.finalizePendingMessage(randomId, message)) { if(pendingMessage = this.finalizePendingMessage(randomId, message)) {
@ -4666,7 +4665,7 @@ export class AppMessagesManager {
mute = !appNotificationsManager.isPeerLocalMuted(peerId, false); mute = !appNotificationsManager.isPeerLocalMuted(peerId, false);
} }
settings.mute_until = mute ? 0x7FFFFFFF : 0; settings.mute_until = mute ? MUTE_UNTIL : 0;
return appNotificationsManager.updateNotifySettings({ return appNotificationsManager.updateNotifySettings({
_: 'inputNotifyPeer', _: 'inputNotifyPeer',
@ -4684,7 +4683,7 @@ export class AppMessagesManager {
} }
} }
public finalizePendingMessage(randomId: string, finalMessage: any) { public finalizePendingMessage(randomId: string, finalMessage: MyMessage) {
const pendingData = this.pendingByRandomId[randomId]; const pendingData = this.pendingByRandomId[randomId];
// this.log('pdata', randomID, pendingData) // this.log('pdata', randomID, pendingData)
@ -4699,7 +4698,7 @@ export class AppMessagesManager {
// this.log('pending', randomID, historyStorage.pending) // this.log('pending', randomID, historyStorage.pending)
const message = this.getMessageFromStorage(storage, tempId); const message: Message.message = this.getMessageFromStorage(storage, tempId);
if(!message.deleted) { if(!message.deleted) {
delete message.pFlags.is_outgoing; delete message.pFlags.is_outgoing;
delete message.pending; delete message.pending;
@ -5382,17 +5381,18 @@ export class AppMessagesManager {
message.deleted = true; message.deleted = true;
if(message._ !== 'messageService' && message.grouped_id) { const groupedId = (message as Message.message).grouped_id;
const groupedStorage = this.groupedMessagesStorage[message.grouped_id]; if(groupedId) {
const groupedStorage = this.groupedMessagesStorage[groupedId];
if(groupedStorage) { if(groupedStorage) {
delete groupedStorage[mid]; delete groupedStorage[mid];
if(!history.albums) history.albums = {}; if(!history.albums) history.albums = {};
(history.albums[message.grouped_id] || (history.albums[message.grouped_id] = new Set())).add(mid); (history.albums[groupedId] || (history.albums[groupedId] = new Set())).add(mid);
if(!Object.keys(groupedStorage).length) { if(!Object.keys(groupedStorage).length) {
delete history.albums; delete history.albums;
delete this.groupedMessagesStorage[message.grouped_id]; delete this.groupedMessagesStorage[groupedId];
} }
} }
} }

2
src/lib/appManagers/appUsersManager.ts

@ -344,7 +344,7 @@ export class AppUsersManager {
}); });
} }
public saveApiUsers(apiUsers: any[], override?: boolean) { public saveApiUsers(apiUsers: MTUser[], override?: boolean) {
apiUsers.forEach((user) => this.saveApiUser(user, override)); apiUsers.forEach((user) => this.saveApiUser(user, override));
} }

1
src/lib/mtproto/mtproto_config.ts

@ -11,3 +11,4 @@ export type UserAuth = {dcID: number | string, date: number, id: number};
export const REPLIES_PEER_ID = 1271266957; export const REPLIES_PEER_ID = 1271266957;
export const SERVICE_PEER_ID = 777000; export const SERVICE_PEER_ID = 777000;
export const MUTE_UNTIL = 0x7FFFFFFF;

1
src/lib/rootScope.ts

@ -43,6 +43,7 @@ export type BroadcastEvents = {
'dialog_migrate': {migrateFrom: number, migrateTo: number}, 'dialog_migrate': {migrateFrom: number, migrateTo: number},
//'dialog_top': Dialog, //'dialog_top': Dialog,
'dialog_notify_settings': Dialog, 'dialog_notify_settings': Dialog,
// 'dialog_order': {dialog: Dialog, pos: number},
'dialogs_multiupdate': {[peerId: string]: Dialog}, 'dialogs_multiupdate': {[peerId: string]: Dialog},
'dialogs_archived_unread': {count: number}, 'dialogs_archived_unread': {count: number},

98
src/lib/storages/dialogs.ts

@ -27,6 +27,7 @@ import rootScope from "../rootScope";
import { safeReplaceObject } from "../../helpers/object"; import { safeReplaceObject } from "../../helpers/object";
import { AppStateManager } from "../appManagers/appStateManager"; import { AppStateManager } from "../appManagers/appStateManager";
import { SliceEnd } from "../../helpers/slicedArray"; import { SliceEnd } from "../../helpers/slicedArray";
import { MyDialogFilter } from "./filters";
export type FolderDialog = { export type FolderDialog = {
dialog: Dialog, dialog: Dialog,
@ -84,6 +85,14 @@ export default class DialogsStorage {
} }
}); });
// to set new indexes
rootScope.addEventListener('filter_order', () => {
// ! MUST BE REFACTORED !
for(let id in this.appMessagesManager.filtersStorage.filters) {
this.getFolder(+id, false);
}
});
rootScope.addMultipleEventsListeners({ rootScope.addMultipleEventsListeners({
updateFolderPeers: this.onUpdateFolderPeers, updateFolderPeers: this.onUpdateFolderPeers,
@ -186,19 +195,7 @@ export default class DialogsStorage {
const indexStr = this.getDialogIndexKey(id); const indexStr = this.getDialogIndexKey(id);
for(const peerId in this.dialogs) { for(const peerId in this.dialogs) {
const dialog = this.dialogs[peerId]; const dialog = this.dialogs[peerId];
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter) && (!skipMigrated || dialog.migratedTo === undefined)) { if(this.setDialogIndexInFilter(dialog, indexStr, filter) && (!skipMigrated || dialog.migratedTo === undefined)) {
let index: number;
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerId);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex), true);
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
dialog[indexStr] = index;
insertInDescendSortedArray(dialogs, dialog, indexStr, -1); insertInDescendSortedArray(dialogs, dialog, indexStr, -1);
} }
} }
@ -209,6 +206,23 @@ export default class DialogsStorage {
// return dialogs.map(d => d.dialog); // return dialogs.map(d => d.dialog);
} }
private setDialogIndexInFilter(dialog: Dialog, indexKey: ReturnType<DialogsStorage['getDialogIndexKey']>, filter: MyDialogFilter) {
let index: number;
if(this.appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) {
const pinnedIndex = filter.pinned_peers.indexOf(dialog.peerId);
if(pinnedIndex !== -1) {
index = this.generateDialogIndex(this.generateDialogPinnedDateByIndex(filter.pinned_peers.length - 1 - pinnedIndex), true);
} else if(dialog.pFlags?.pinned) {
index = this.generateIndexForDialog(dialog, true);
} else {
index = dialog.index;
}
}
return dialog[indexKey] = index;
}
public getDialog(peerId: number, folderId?: number, skipMigrated = true): [Dialog, number] | [] { public getDialog(peerId: number, folderId?: number, skipMigrated = true): [Dialog, number] | [] {
const folders: Dialog[][] = []; const folders: Dialog[][] = [];
@ -286,8 +300,17 @@ export default class DialogsStorage {
} }
const index = this.generateDialogIndex(topDate, isPinned); const index = this.generateDialogIndex(topDate, isPinned);
if(justReturn) return index; if(justReturn) {
return index;
}
dialog.index = index; dialog.index = index;
// ! MUST BE REFACTORED !
for(let id in this.appMessagesManager.filtersStorage.filters) {
const filter = this.appMessagesManager.filtersStorage.filters[id];
this.setDialogIndexInFilter(dialog, this.getDialogIndexKey(+id), filter);
}
} }
public generateDialogPinnedDateByIndex(pinnedIndex: number) { public generateDialogPinnedDateByIndex(pinnedIndex: number) {
@ -327,11 +350,12 @@ export default class DialogsStorage {
public setDialogToState(dialog: Dialog) { public setDialogToState(dialog: Dialog) {
const historyStorage = this.appMessagesManager.getHistoryStorage(dialog.peerId); const historyStorage = this.appMessagesManager.getHistoryStorage(dialog.peerId);
const history = [].concat(historyStorage.history.slice); const messagesStorage = this.appMessagesManager.getMessagesStorage(dialog.peerId);
const history = historyStorage.history.slice;
let incomingMessage: any; let incomingMessage: any;
for(let i = 0, length = history.length; i < length; ++i) { for(let i = 0, length = history.length; i < length; ++i) {
const mid = history[i]; const mid = history[i];
const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, mid); const message: MyMessage = this.appMessagesManager.getMessageFromStorage(messagesStorage, mid);
if(!message.pFlags.is_outgoing) { if(!message.pFlags.is_outgoing) {
incomingMessage = message; incomingMessage = message;
@ -339,7 +363,7 @@ export default class DialogsStorage {
if(fromId !== dialog.peerId) { if(fromId !== dialog.peerId) {
this.appStateManager.requestPeer(fromId, 'topMessage_' + dialog.peerId, 1); this.appStateManager.requestPeer(fromId, 'topMessage_' + dialog.peerId, 1);
} }
break; break;
} }
} }
@ -372,24 +396,29 @@ export default class DialogsStorage {
if(pos !== -1) { if(pos !== -1) {
dialogs.splice(pos, 1); dialogs.splice(pos, 1);
} }
//if(!this.dialogs[dialog.peerId]) { //if(!this.dialogs[dialog.peerId]) {
this.dialogs[dialog.peerId] = dialog; this.dialogs[dialog.peerId] = dialog;
this.setDialogToState(dialog); this.setDialogToState(dialog);
//} //}
// let pos: number;
if(offsetDate && if(offsetDate &&
!dialog.pFlags.pinned && !dialog.pFlags.pinned &&
(!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) { (!this.dialogsOffsetDate[dialog.folder_id] || offsetDate < this.dialogsOffsetDate[dialog.folder_id])) {
if(pos !== -1) { if(pos !== -1) { // So the dialog jumped to the last position
// So the dialog jumped to the last position // dialogs.splice(pos, 1);
return false; return false;
} }
this.dialogsOffsetDate[dialog.folder_id] = offsetDate; this.dialogsOffsetDate[dialog.folder_id] = offsetDate;
} }
insertInDescendSortedArray(dialogs, dialog, 'index', pos); /* const newPos = */insertInDescendSortedArray(dialogs, dialog, 'index', pos);
/* if(pos !== -1 && pos !== newPos) {
rootScope.dispatchEvent('dialog_order', {dialog, pos: newPos});
} */
} }
public dropDialog(peerId: number): [Dialog, number] | [] { public dropDialog(peerId: number): [Dialog, number] | [] {
@ -600,12 +629,13 @@ export default class DialogsStorage {
} }
public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): { public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): {
cached: boolean; cached: boolean,
promise: Promise<{ promise: Promise<{
dialogs: Dialog[]; dialogs: Dialog[],
count: number; count: number,
isEnd: boolean; isTopEnd: boolean,
}>; isEnd: boolean
}>
} { } {
if(folderId > 1) { if(folderId > 1) {
const fillContactsResult = this.appUsersManager.fillContacts(); const fillContactsResult = this.appUsersManager.fillContacts();
@ -659,12 +689,14 @@ export default class DialogsStorage {
} }
const loadedAll = this.isDialogsLoaded(realFolderId); const loadedAll = this.isDialogsLoaded(realFolderId);
if(query || loadedAll || curDialogStorage.length >= offset + limit) { if(query || loadedAll || curDialogStorage.length >= (offset + limit)) {
const dialogs = curDialogStorage.slice(offset, offset + limit);
return { return {
cached: true, cached: true,
promise: Promise.resolve({ promise: Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit), dialogs,
count: loadedAll ? curDialogStorage.length : null, count: loadedAll ? curDialogStorage.length : null,
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex),
isEnd: loadedAll && (offset + limit) >= curDialogStorage.length isEnd: loadedAll && (offset + limit) >= curDialogStorage.length
}) })
}; };
@ -689,9 +721,11 @@ export default class DialogsStorage {
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length); //this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length);
const dialogs = curDialogStorage.slice(offset, offset + limit);
return { return {
dialogs: curDialogStorage.slice(offset, offset + limit), dialogs,
count: result.count === undefined ? curDialogStorage.length : result.count, count: result.count === undefined ? curDialogStorage.length : result.count,
isTopEnd: curDialogStorage.length && ((dialogs[0] && dialogs[0] === curDialogStorage[0]) || curDialogStorage[0][indexStr] < offsetIndex),
// isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length // isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length
isEnd: result.isEnd isEnd: result.isEnd
}; };

4
src/scss/partials/_chatBubble.scss

@ -632,6 +632,10 @@ $bubble-margin: .25rem;
> .thumbnail { > .thumbnail {
opacity: .8; opacity: .8;
&.fade-in {
animation: thumbnail-fade-in-opacity .2s ease-in-out forwards;
}
} }
} }
} }

4
src/scss/partials/_document.scss

@ -12,6 +12,10 @@
.media-photo { .media-photo {
border-radius: inherit; border-radius: inherit;
&.thumbnail {
left: 0;
}
} }
&-ico { &-ico {

29
src/scss/style.scss

@ -931,6 +931,15 @@ img.emoji {
} }
} }
/* .fade-in-new {
opacity: 1;
transition: opacity .2s ease-in-out;
&.not-yet {
opacity: 0;
}
} */
.show-more { .show-more {
padding-top: 13px; padding-top: 13px;
padding-bottom: 13px; padding-bottom: 13px;
@ -1169,12 +1178,6 @@ middle-ellipsis-element {
.media-sticker, .media-sticker,
.media-round, .media-round,
.media-poster { .media-poster {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
@include animation-level(2) { @include animation-level(2) {
&.fade-in { &.fade-in {
animation: fade-in-opacity .2s ease-in-out forwards; animation: fade-in-opacity .2s ease-in-out forwards;
@ -1184,14 +1187,14 @@ middle-ellipsis-element {
animation: fade-out-opacity .2s ease-in-out forwards; animation: fade-out-opacity .2s ease-in-out forwards;
} }
} }
}
.media-photo.thumbnail { // & ~ & {
@include animation-level(2) { position: absolute;
&.fade-in { top: 0;
animation: thumbnail-fade-in-opacity .2s ease-in-out forwards; right: 0;
} bottom: 0;
} left: 0;
// }
} }
.media-video { .media-video {

Loading…
Cancel
Save