Browse Source

Call incompatibility notification

Music replay & loop
Fix incrementing notification by reaction
master
Eduard Kuzmenko 2 years ago
parent
commit
bab69c170a
  1. 79
      src/components/appMediaPlaybackController.ts
  2. 10
      src/components/audio.ts
  3. 40
      src/components/chat/audio.ts
  4. 2
      src/components/chat/input.ts
  5. 2
      src/components/peerProfileAvatars.ts
  6. 2
      src/components/wrappers.ts
  7. 2
      src/helpers/avatarListLoader.ts
  8. 10
      src/helpers/compareValue.ts
  9. 65
      src/helpers/listLoader.ts
  10. 25
      src/helpers/long/compareLong.ts
  11. 152
      src/helpers/searchListLoader.ts
  12. 54
      src/helpers/slicedArray.ts
  13. 7
      src/lib/appManagers/appImManager.ts
  14. 22
      src/lib/appManagers/appMessagesManager.ts
  15. 6
      src/lib/appManagers/appNotificationsManager.ts
  16. 7
      src/lib/calls/callInstance.ts
  17. 6
      src/lib/rootScope.ts
  18. 4
      src/scss/partials/_chat.scss
  19. 51
      src/scss/tgico/_style.scss
  20. 181
      src/scss/tgico/_variables.scss
  21. 22
      src/tests/slicedArray.test.ts

79
src/components/appMediaPlaybackController.ts

@ -58,7 +58,7 @@ type MediaDetails = { @@ -58,7 +58,7 @@ type MediaDetails = {
export type PlaybackMediaType = 'voice' | 'video' | 'audio';
class AppMediaPlaybackController {
export class AppMediaPlaybackController {
private container: HTMLElement;
private media: Map<PeerId, Map<number, HTMLMediaElement>> = new Map();
private scheduled: AppMediaPlaybackController['media'] = new Map();
@ -78,9 +78,13 @@ class AppMediaPlaybackController { @@ -78,9 +78,13 @@ class AppMediaPlaybackController {
public volume: number;
public muted: boolean;
public playbackRate: number;
public loop: boolean;
public round: boolean;
private _volume = 1;
private _muted = false;
private _playbackRate = 1;
private _loop = false;
private _round = false;
private lockedSwitchers: boolean;
private playbackRates: Record<PlaybackMediaType, number> = {
voice: 1,
@ -128,7 +132,9 @@ class AppMediaPlaybackController { @@ -128,7 +132,9 @@ class AppMediaPlaybackController {
const keys = [
'volume' as const,
'muted' as const,
'playbackRate' as const
'playbackRate' as const,
'loop' as const,
'round' as const
];
keys.forEach(key => {
const _key = ('_' + key) as `_${typeof key}`;
@ -141,7 +147,7 @@ class AppMediaPlaybackController { @@ -141,7 +147,7 @@ class AppMediaPlaybackController {
// @ts-ignore
this[_key] = value;
if(this.playingMedia) {
if(this.playingMedia && (key !== 'loop' || this.playingMediaType === 'audio') && key !== 'round') {
// @ts-ignore
this.playingMedia[key] = value;
}
@ -158,12 +164,20 @@ class AppMediaPlaybackController { @@ -158,12 +164,20 @@ class AppMediaPlaybackController {
}
private dispatchPlaybackParams() {
const {volume, muted, playbackRate} = this;
rootScope.dispatchEvent('media_playback_params', {
volume, muted, playbackRate
});
rootScope.dispatchEvent('media_playback_params', this.getPlaybackParams());
}
public getPlaybackParams() {
const {volume, muted, playbackRate, loop, round} = this;
return {
volume,
muted,
playbackRate,
loop,
round
};
}
public seekBackward = (details: MediaSessionActionDetails) => {
const media = this.playingMedia;
if(media) {
@ -302,6 +316,10 @@ class AppMediaPlaybackController { @@ -302,6 +316,10 @@ class AppMediaPlaybackController {
if(this.playingMedia === media) {
media.playbackRate = this.playbackRate;
if(doc.type === 'audio') {
media.loop = this.loop;
}
}
// }, doc.supportsStreaming ? 500e3 : 0);
@ -494,15 +512,19 @@ class AppMediaPlaybackController { @@ -494,15 +512,19 @@ class AppMediaPlaybackController {
const previousMedia = this.playingMedia;
if(previousMedia !== media) {
this.stop();
this.setMedia(media, message);
const verify = (element: MediaItem) => element.mid === mid && element.peerId === peerId;
if(!this.listLoader.current || !verify(this.listLoader.current)) {
let idx = this.listLoader.previous.findIndex(verify);
const current = this.listLoader.getCurrent();
if(!current || !verify(current)) {
const previous = this.listLoader.getPrevious();
let idx = previous.findIndex(verify);
let jumpLength: number;
if(idx !== -1) {
jumpLength = -(this.listLoader.previous.length - idx);
jumpLength = -(previous.length - idx);
} else {
idx = this.listLoader.next.findIndex(verify);
idx = this.listLoader.getNext().findIndex(verify);
if(idx !== -1) {
jumpLength = idx + 1;
}
@ -510,14 +532,12 @@ class AppMediaPlaybackController { @@ -510,14 +532,12 @@ class AppMediaPlaybackController {
if(idx !== -1) {
if(jumpLength) {
this.listLoader.go(jumpLength, false);
this.go(jumpLength, false);
}
} else {
this.setTargets({peerId, mid});
}
}
this.setMedia(media, message);
}
// audio_pause не успеет сработать без таймаута
@ -540,7 +560,8 @@ class AppMediaPlaybackController { @@ -540,7 +560,8 @@ class AppMediaPlaybackController {
return {
doc: appMessagesManager.getMediaFromMessage(message),
message,
media: playingMedia
media: playingMedia,
playbackParams: this.getPlaybackParams()
};
}
@ -564,7 +585,10 @@ class AppMediaPlaybackController { @@ -564,7 +585,10 @@ class AppMediaPlaybackController {
//console.log('on media end');
if(!this.next()) {
if(this.lockedSwitchers ||
(!this.round && this.listLoader.current && !this.listLoader.next.length) ||
!this.listLoader.getNext().length ||
!this.next()) {
this.stop();
rootScope.dispatchEvent('media_stop');
}
@ -654,19 +678,32 @@ class AppMediaPlaybackController { @@ -654,19 +678,32 @@ class AppMediaPlaybackController {
}, 0);
};
public go = (length: number, dispatchJump?: boolean) => {
if(this.lockedSwitchers) {
return;
}
if(this.playingMediaType === 'audio') {
return this.listLoader.goRound(length, dispatchJump);
} else {
return this.listLoader.go(length, dispatchJump);
}
};
public next = () => {
return !this.lockedSwitchers && this.listLoader.go(1);
return this.go(1);
};
public previous = () => {
const media = this.playingMedia;
if(media && (media.currentTime > 5 || !this.listLoader.previous.length)) {
// if(media && (media.currentTime > 5 || !this.listLoader.getPrevious().length)) {
if(media && media.currentTime > 5) {
media.currentTime = 0;
this.toggle(true);
return;
}
return !this.lockedSwitchers && this.listLoader.go(-1);
return this.go(-1);
};
public willBePlayed(media: HTMLMediaElement) {
@ -746,6 +783,10 @@ class AppMediaPlaybackController { @@ -746,6 +783,10 @@ class AppMediaPlaybackController {
this.playingMedia.muted = this.muted;
this.playingMedia.playbackRate = this.playbackRate;
if(mediaType === 'audio') {
this.playingMedia.loop = this.loop;
}
if('mediaSession' in navigator) {
this.setNewMediadata(message);
}

10
src/components/audio.ts

@ -362,7 +362,7 @@ function constructDownloadPreloader(tryAgainOnFail = true) { @@ -362,7 +362,7 @@ function constructDownloadPreloader(tryAgainOnFail = true) {
return preloader;
}
export const findMediaTargets = (anchor: HTMLElement/* , useSearch: boolean */) => {
export const findMediaTargets = (anchor: HTMLElement, anchorMid: number/* , useSearch: boolean */) => {
let prev: MediaItem[], next: MediaItem[];
// if(anchor.classList.contains('search-super-item') || !useSearch) {
const isBubbles = !anchor.classList.contains('search-super-item');
@ -394,6 +394,12 @@ export const findMediaTargets = (anchor: HTMLElement/* , useSearch: boolean */) @@ -394,6 +394,12 @@ export const findMediaTargets = (anchor: HTMLElement/* , useSearch: boolean */)
}
// }
if((next.length && next[0].mid < anchorMid) || (prev.length && prev[prev.length - 1].mid > anchorMid)) {
[prev, next] = [next.reverse(), prev.reverse()];
}
// prev = next = undefined;
return [prev, next];
};
@ -490,7 +496,7 @@ export default class AudioElement extends HTMLElement { @@ -490,7 +496,7 @@ export default class AudioElement extends HTMLElement {
inputFilter: {_: 'inputMessagesFilterEmpty'},
useSearch: false
})) {
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this/* , this.searchContext.useSearch */);
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(this, this.message.mid/* , this.searchContext.useSearch */);
appMediaPlaybackController.setTargets({peerId: this.message.peerId, mid: this.message.mid}, prev, next);
}

40
src/components/chat/audio.ts

@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import type ChatTopbar from "./topbar";
import rootScope from "../../lib/rootScope";
import appMediaPlaybackController from "../appMediaPlaybackController";
import appMediaPlaybackController, { AppMediaPlaybackController } from "../appMediaPlaybackController";
import DivAndCaption from "../divAndCaption";
import PinnedContainer from "./pinnedContainer";
import Chat from "./chat";
@ -27,6 +27,7 @@ export default class ChatAudio extends PinnedContainer { @@ -27,6 +27,7 @@ export default class ChatAudio extends PinnedContainer {
private progressLine: MediaProgressLine;
private volumeSelector: VolumeSelector;
private fasterEl: HTMLElement;
private repeatEl: HTMLButtonElement;
constructor(protected topbar: ChatTopbar, protected chat: Chat, protected appMessagesManager: AppMessagesManager) {
super({
@ -84,12 +85,25 @@ export default class ChatAudio extends PinnedContainer { @@ -84,12 +85,25 @@ export default class ChatAudio extends PinnedContainer {
this.volumeSelector.btn.prepend(tunnel);
this.volumeSelector.btn.append(volumeProgressLineContainer);
this.repeatEl = ButtonIcon('audio_repeat', {noRipple: true});
attachClick(this.repeatEl, () => {
const params = appMediaPlaybackController.getPlaybackParams();
if(!params.round) {
appMediaPlaybackController.round = true;
} else if(params.loop) {
appMediaPlaybackController.round = false;
appMediaPlaybackController.loop = false;
} else {
appMediaPlaybackController.loop = !appMediaPlaybackController.loop;
}
});
const fasterEl = this.fasterEl = ButtonIcon('playback_2x', {noRipple: true});
attachClick(fasterEl, () => {
appMediaPlaybackController.playbackRate = fasterEl.classList.contains('active') ? 1 : 1.75;
});
this.wrapperUtils.prepend(this.volumeSelector.btn, fasterEl);
this.wrapperUtils.prepend(this.volumeSelector.btn, fasterEl, this.repeatEl);
const progressWrapper = document.createElement('div');
progressWrapper.classList.add('pinned-audio-progress-wrapper');
@ -102,14 +116,12 @@ export default class ChatAudio extends PinnedContainer { @@ -102,14 +116,12 @@ export default class ChatAudio extends PinnedContainer {
this.topbar.listenerSetter.add(rootScope)('media_play', this.onMediaPlay);
this.topbar.listenerSetter.add(rootScope)('media_pause', this.onPause);
this.topbar.listenerSetter.add(rootScope)('media_stop', this.onStop);
this.topbar.listenerSetter.add(rootScope)('media_playback_params', ({playbackRate}) => {
this.onPlaybackRateChange(playbackRate);
});
this.topbar.listenerSetter.add(rootScope)('media_playback_params', this.onPlaybackParams);
const playingDetails = appMediaPlaybackController.getPlayingDetails();
if(playingDetails) {
this.onMediaPlay(playingDetails);
this.onPlaybackRateChange(appMediaPlaybackController.playbackRate);
this.onPlaybackParams(playingDetails.playbackParams);
}
}
@ -119,8 +131,12 @@ export default class ChatAudio extends PinnedContainer { @@ -119,8 +131,12 @@ export default class ChatAudio extends PinnedContainer {
}
}
private onPlaybackRateChange = (playbackRate: number) => {
this.fasterEl.classList.toggle('active', playbackRate > 1);
private onPlaybackParams = (playbackParams: ReturnType<AppMediaPlaybackController['getPlaybackParams']>) => {
this.fasterEl.classList.toggle('active', playbackParams.playbackRate > 1);
this.repeatEl.classList.remove('tgico-audio_repeat', 'tgico-audio_repeat_single');
this.repeatEl.classList.add(playbackParams.loop ? 'tgico-audio_repeat_single' : 'tgico-audio_repeat');
this.repeatEl.classList.toggle('active', playbackParams.loop || playbackParams.round);
};
private onPause = () => {
@ -137,18 +153,20 @@ export default class ChatAudio extends PinnedContainer { @@ -137,18 +153,20 @@ export default class ChatAudio extends PinnedContainer {
media: HTMLMediaElement
}) => {
let title: string | HTMLElement, subtitle: string | HTMLElement | DocumentFragment;
if(doc.type === 'voice' || doc.type === 'round') {
const isMusic = doc.type !== 'voice' && doc.type !== 'round';
if(!isMusic) {
title = new PeerTitle({peerId: message.fromId, fromName: message.fwd_from?.from_name}).element;
//subtitle = 'Voice message';
subtitle = formatFullSentTime(message.date);
this.fasterEl.classList.remove('hide');
} else {
title = doc.audioTitle || doc.fileName;
subtitle = doc.audioPerformer || i18n('AudioUnknownArtist');
this.fasterEl.classList.add('hide');
}
this.fasterEl.classList.toggle('hide', isMusic);
this.repeatEl.classList.toggle('hide', !isMusic);
this.progressLine.setMedia(media);
this.fill(title, subtitle, message);

2
src/components/chat/input.ts

@ -682,7 +682,7 @@ export default class ChatInput { @@ -682,7 +682,7 @@ export default class ChatInput {
<span class="tgico tgico-send"></span>
<span class="tgico tgico-schedule"></span>
<span class="tgico tgico-check"></span>
<span class="tgico tgico-microphone"></span>
<span class="tgico tgico-microphone_filled"></span>
`);
this.btnSendContainer.append(this.recordRippleEl, this.btnSend);

2
src/components/peerProfileAvatars.ts

@ -257,7 +257,7 @@ export default class PeerProfileAvatars { @@ -257,7 +257,7 @@ export default class PeerProfileAvatars {
if(!older) return Promise.resolve({count: undefined, items: []});
if(peerId.isUser()) {
const maxId: Photo.photo['id'] = (anchor || listLoader.current) as any;
const maxId: Photo.photo['id'] = anchor as any;
return appPhotosManager.getUserPhotos(peerId, maxId, loadCount).then(value => {
return {
count: value.count,

2
src/components/wrappers.ts

@ -328,7 +328,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -328,7 +328,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
inputFilter: {_: 'inputMessagesFilterEmpty'},
useSearch: false
})) {
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(divRound/* , searchContext.useSearch */);
const [prev, next] = !hadSearchContext ? [] : findMediaTargets(divRound, message.mid/* , searchContext.useSearch */);
appMediaPlaybackController.setTargets({peerId: message.peerId, mid: message.mid}, prev, next);
}

2
src/helpers/avatarListLoader.ts

@ -17,7 +17,7 @@ export default class AvatarListLoader<Item extends {photoId: Photo.photo['id']}> @@ -17,7 +17,7 @@ export default class AvatarListLoader<Item extends {photoId: Photo.photo['id']}>
loadMore: (anchor, older, loadCount) => {
if(this.peerId.isAnyChat() || !older) return Promise.resolve({count: 0, items: []}); // ! это значит, что открыло аватар чата, но следующих фотографий нет.
const maxId = anchor?.photoId || this.current?.photoId;
const maxId = anchor?.photoId;
return appPhotosManager.getUserPhotos(this.peerId, maxId, loadCount).then(value => {
const items = value.photos.map(photoId => {
return {element: null as HTMLElement, photoId} as any;

10
src/helpers/compareValue.ts

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
import compareLong from "./long/compareLong";
export default function compareValue(val1: string | number, val2: typeof val1) {
if((val1 as number).toExponential) {
const diff = (val1 as number) - (val2 as number);
return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
}
return compareLong(val1 as string, val2 as string);
}

65
src/helpers/listLoader.ts

@ -66,8 +66,8 @@ export default class ListLoader<T extends {}, P extends {}> { @@ -66,8 +66,8 @@ export default class ListLoader<T extends {}, P extends {}> {
this.current = undefined;
this.previous = [];
this.next = [];
this.loadedAllUp = this.loadedAllDown = loadedAll;
this.loadPromiseUp = this.loadPromiseDown = null;
this.setLoaded(true, loadedAll);
this.setLoaded(false, loadedAll);
}
public go(length: number, dispatchJump = true) {
@ -79,15 +79,17 @@ export default class ListLoader<T extends {}, P extends {}> { @@ -79,15 +79,17 @@ export default class ListLoader<T extends {}, P extends {}> {
return;
}
this.previous.push(this.current, ...items);
if(this.current !== undefined) items.unshift(this.current);
this.previous.push(...items);
} else {
items = this.previous.splice(this.previous.length + length, -length);
items = this.previous.splice(Math.max(0, this.previous.length + length), -length);
item = items.shift();
if(!item) {
return;
}
this.next.unshift(...items, this.current);
if(this.current !== undefined) items.push(this.current);
this.next.unshift(...items);
}
if(this.next.length < this.loadWhenLeft) {
@ -103,13 +105,50 @@ export default class ListLoader<T extends {}, P extends {}> { @@ -103,13 +105,50 @@ export default class ListLoader<T extends {}, P extends {}> {
return this.current;
}
protected unsetCurrent(toPrevious: boolean) {
if(toPrevious) this.previous.push(this.current);
else this.next.unshift(this.current);
this.current = undefined;
}
public goUnsafe(length: number, dispatchJump?: boolean) {
const leftLength = length > 0 ? Math.max(0, length - this.next.length) : Math.min(0, length + this.previous.length);
const item = this.go(length, leftLength ? false : dispatchJump);
/* if(length > 0 ? this.loadedAllUp : this.loadedAllDown) {
this.unsetCurrent(length > 0);
} */
return {
item: !leftLength ? item : undefined,
leftLength
};
}
protected setLoaded(down: boolean, value: boolean) {
const isChanged = (down ? this.loadedAllDown : this.loadedAllUp) !== value;
if(!isChanged) {
return false;
}
if(down) this.loadedAllDown = value;
else this.loadedAllUp = value;
if(!value) {
if(down) this.loadPromiseDown = null;
else this.loadPromiseUp = null;
}
return true;
}
// нет смысла делать проверку для reverse и loadMediaPromise
public load(older: boolean) {
if(older && this.loadedAllDown) return Promise.resolve();
else if(!older && this.loadedAllUp) return Promise.resolve();
if(older ? this.loadedAllDown : this.loadedAllUp) return Promise.resolve();
if(older && this.loadPromiseDown) return this.loadPromiseDown;
else if(!older && this.loadPromiseUp) return this.loadPromiseUp;
let promise = older ? this.loadPromiseDown : this.loadPromiseUp;
if(promise) return promise;
let anchor: T;
if(older) {
@ -118,14 +157,14 @@ export default class ListLoader<T extends {}, P extends {}> { @@ -118,14 +157,14 @@ export default class ListLoader<T extends {}, P extends {}> {
anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0];
}
const promise = this.loadMore(anchor, older, this.loadCount).then(result => {
if((older && this.loadPromiseDown !== promise) || (!older && this.loadPromiseUp !== promise)) {
anchor ??= this.current;
promise = this.loadMore(anchor, older, this.loadCount).then(result => {
if((older ? this.loadPromiseDown : this.loadPromiseUp) !== promise) {
return;
}
if(result.items.length < this.loadCount) {
if(older) this.loadedAllDown = true;
else this.loadedAllUp = true;
this.setLoaded(older, true);
}
if(this.count === undefined) {

25
src/helpers/long/compareLong.ts

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
export default function compareLong(str1: string, str2: string) {
const str1Length = str1.length;
if(str1Length !== str2.length) {
const diff = str1Length - str2.length;
return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
}
const maxPartLength = 15;
for(let i = 0; i < str1Length; i += maxPartLength) {
const v1 = +str1.slice(i, i + maxPartLength);
const v2 = +str2.slice(i, i + maxPartLength);
const diff = v1 - v2;
if(diff) {
return diff;
}
}
return 0;
}

152
src/helpers/searchListLoader.ts

@ -18,14 +18,16 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -18,14 +18,16 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
public searchContext: MediaSearchContext;
public onEmptied: () => void;
constructor(options: Omit<ListLoaderOptions<Item, Message.message>, 'loadMore'> & {onEmptied?: () => void} = {}) {
private otherSideLoader: SearchListLoader<Item>;
constructor(options: Omit<ListLoaderOptions<Item, Message.message>, 'loadMore'> & {onEmptied?: () => void, isInner?: boolean} = {}) {
super({
...options,
loadMore: (anchor, older, loadCount) => {
const backLimit = older ? 0 : loadCount;
let maxId = this.current?.mid;
let maxId = anchor?.mid;
if(anchor) maxId = anchor.mid;
if(maxId === undefined) maxId = this.searchContext.maxId;
if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1);
return appMessagesManager.getSearch({
@ -63,6 +65,17 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -63,6 +65,17 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
rootScope.addEventListener('history_delete', this.onHistoryDelete);
rootScope.addEventListener('history_multiappend', this.onHistoryMultiappend);
rootScope.addEventListener('message_sent', this.onMessageSent);
if(!options.isInner) {
this.otherSideLoader = new SearchListLoader({
...options,
isInner: true
});
// this.otherSideLoader.onLoadedMore = () => {
// };
}
}
protected filterMids(mids: number[]) {
@ -116,7 +129,26 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -116,7 +129,26 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
const filtered = this.filterMids(sorted);
const targets = filtered.map(message => this.processItem(message)).filter(Boolean);
if(targets.length) {
this.next.push(...targets);
/* const {previous, current, next} = this;
const targets = previous.concat(current, next);
const currentIdx = targets.length;
const mid = targets[0].mid;
let i = 0, length = targets.length;
for(; i < length; ++i) {
const target = targets[i];
if(!target || mid < target.mid) {
break;
}
}
if(i < currentIdx) previous.push(...targets);
else next. */
if(!this.current) {
this.previous.push(...targets);
} else {
this.next.push(...targets);
}
}
};
@ -141,14 +173,117 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -141,14 +173,117 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
this.loadedAllUp = true;
}
if(!this.searchContext.useSearch) {
this.loadedAllDown = this.loadedAllUp = true;
// if(!this.searchContext.useSearch) {
// this.loadedAllDown = this.loadedAllUp = true;
// }
if(this.otherSideLoader) {
this.otherSideLoader.setSearchContext(context);
}
}
public reset() {
super.reset();
this.searchContext = undefined;
if(this.otherSideLoader) {
this.otherSideLoader.reset();
}
}
public getPrevious() {
let previous = this.previous;
if(this.otherSideLoader) {
previous = previous.concat(this.otherSideLoader.previous);
}
return previous;
}
public getNext() {
let next = this.next;
if(this.otherSideLoader) {
next = next.concat(this.otherSideLoader.next);
}
return next;
}
public getCurrent() {
return this.current || this.otherSideLoader?.current;
}
private goToOtherEnd(length: number) {
if(length > 0) return this.go(-this.previous.length);
else return this.go(this.next.length);
}
public goRound(length: number, dispatchJump?: boolean) {
let ret: ReturnType<SearchListLoader<Item>['goUnsafe']>;
if(this.otherSideLoader?.current) {
ret = this.otherSideLoader.goUnsafe(length, dispatchJump);
if(ret.item) {
return ret.item;
}
length = ret.leftLength;
if(!(length > 0 ? this.otherSideLoader.next : this.otherSideLoader.previous).length) {
const loaded = length > 0 ? this.otherSideLoader.loadedAllUp : this.otherSideLoader.loadedAllDown;
if(!loaded) { // do not reset anything until it's loaded
return;
}
// if other side is loaded too will start from its begin
if((length > 0 && (this.otherSideLoader.searchContext.maxId === 1 || this.otherSideLoader.loadedAllDown)) ||
(length < 0 && (this.otherSideLoader.searchContext.maxId === 0 || this.otherSideLoader.loadedAllUp))) {
return this.otherSideLoader.goToOtherEnd(length);
}
this.otherSideLoader.unsetCurrent(length > 0);
}
}
ret = this.goUnsafe(length, dispatchJump);
if(!ret.item) {
if(this.loadedAllUp && this.loadedAllDown) { // just use the same loader if the list is too short
return this.goToOtherEnd(length);
} else if(this.otherSideLoader) {
length = ret.leftLength;
ret = this.otherSideLoader.goUnsafe(length, dispatchJump);
if(ret.item) {
this.unsetCurrent(length > 0);
}
}
}
return ret?.item;
}
// public setTargets(previous: Item[], next: Item[], reverse: boolean) {
// super.setTargets(previous, next, reverse);
// }
protected setLoaded(down: boolean, value: boolean) {
const changed = super.setLoaded(down, value);
if(changed && this.otherSideLoader && value/* && (this.reverse ? this.loadedAllUp : this.loadedAllDown) */) {
const reverse = this.loadedAllUp;
this.otherSideLoader.setSearchContext({
...this.searchContext,
maxId: reverse ? 1 : 0
});
// these 'reverse' are different, not a mistake here.
this.otherSideLoader.reverse = this.reverse;
this.otherSideLoader.setLoaded(reverse, true);
this.otherSideLoader.load(!reverse);
}
return changed;
}
public cleanup() {
@ -157,5 +292,10 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId} @@ -157,5 +292,10 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
rootScope.removeEventListener('history_multiappend', this.onHistoryMultiappend);
rootScope.removeEventListener('message_sent', this.onMessageSent);
this.onEmptied = undefined;
if(this.otherSideLoader) {
this.otherSideLoader.cleanup();
this.otherSideLoader = undefined;
}
}
}

54
src/helpers/slicedArray.ts

@ -5,12 +5,13 @@ @@ -5,12 +5,13 @@
*/
import { MOUNT_CLASS_TO } from "../config/debug";
import compareValue from "./compareValue";
/**
* Descend sorted storage
*/
type ItemType = number;
type ItemType = number | string;
export enum SliceEnd {
None = 0,
@ -19,7 +20,7 @@ export enum SliceEnd { @@ -19,7 +20,7 @@ export enum SliceEnd {
Both = SliceEnd.Top | SliceEnd.Bottom
};
export interface Slice extends Array<ItemType> {
export interface Slice<T extends ItemType> extends Array<T> {
//slicedArray: SlicedArray;
end: SliceEnd;
@ -27,17 +28,18 @@ export interface Slice extends Array<ItemType> { @@ -27,17 +28,18 @@ export interface Slice extends Array<ItemType> {
setEnd: (side: SliceEnd) => void;
unsetEnd: (side: SliceEnd) => void;
slice: (from?: number, to?: number) => Slice;
splice: (start: number, deleteCount: number, ...items: ItemType[]) => Slice;
slice: (from?: number, to?: number) => Slice<T>;
splice: (start: number, deleteCount: number, ...items: ItemType[]) => Slice<T>;
}
export interface SliceConstructor {
new(...items: ItemType[]): Slice;
export interface SliceConstructor<T extends ItemType> {
// new(...items: T[]): Slice<T>;
new(length: number): Slice<T>;
}
export default class SlicedArray {
private slices: Slice[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */;
private sliceConstructor: SliceConstructor;
export default class SlicedArray<T extends ItemType> {
private slices: Slice<T>[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */;
private sliceConstructor: SliceConstructor<T>;
constructor() {
// @ts-ignore
@ -48,8 +50,8 @@ export default class SlicedArray { @@ -48,8 +50,8 @@ export default class SlicedArray {
this.slices = [first];
}
private static getSliceConstructor(slicedArray: SlicedArray) {
return class Slice extends Array<ItemType> implements Slice {
private static getSliceConstructor(slicedArray: SlicedArray<ItemType>) {
return class Slice<T> extends Array<ItemType> implements Slice<T> {
//slicedArray: SlicedArray;
end: SliceEnd = SliceEnd.None;
@ -95,7 +97,7 @@ export default class SlicedArray { @@ -95,7 +97,7 @@ export default class SlicedArray {
const ret = super.splice(start, deleteCount, ...items);
if(!this.length) {
const slices = slicedArray.slices as number[][];
const slices = slicedArray.slices as ItemType[][];
const idx = slices.indexOf(this);
if(idx !== -1) {
if(slices.length === 1) { // left empty slice without ends
@ -111,7 +113,7 @@ export default class SlicedArray { @@ -111,7 +113,7 @@ export default class SlicedArray {
}
}
public constructSlice(...items: ItemType[]) {
public constructSlice(...items: T[]) {
//const slice = new Slice(this, ...items);
// can't pass items directly to constructor because first argument is length
const slice = new this.sliceConstructor(items.length);
@ -166,7 +168,7 @@ export default class SlicedArray { @@ -166,7 +168,7 @@ export default class SlicedArray {
*/
}
public insertSlice(slice: ItemType[], flatten = true) {
public insertSlice(slice: T[], flatten = true) {
if(!slice.length) {
return;
}
@ -180,7 +182,7 @@ export default class SlicedArray { @@ -180,7 +182,7 @@ export default class SlicedArray {
const lowerBound = slice[slice.length - 1];
const upperBound = slice[0];
let foundSlice: Slice, lowerIndex = -1, upperIndex = -1, foundSliceIndex = 0;
let foundSlice: Slice<T>, lowerIndex = -1, upperIndex = -1, foundSliceIndex = 0;
for(; foundSliceIndex < this.slices.length; ++foundSliceIndex) {
foundSlice = this.slices[foundSliceIndex];
lowerIndex = foundSlice.indexOf(lowerBound);
@ -205,7 +207,7 @@ export default class SlicedArray { @@ -205,7 +207,7 @@ export default class SlicedArray {
let insertIndex = 0;
for(const length = this.slices.length; insertIndex < length; ++insertIndex) { // * maybe should iterate from the end, could be faster ?
const s = this.slices[insertIndex];
if(slice[0] > s[0]) {
if(compareValue(slice[0], s[0]) === 1) {
break;
}
}
@ -263,7 +265,7 @@ export default class SlicedArray { @@ -263,7 +265,7 @@ export default class SlicedArray {
return this.slice.length;
}
public findSlice(item: ItemType) {
public findSlice(item: T) {
for(let i = 0, length = this.slices.length; i < length; ++i) {
const slice = this.slices[i];
const index = slice.indexOf(item);
@ -275,8 +277,8 @@ export default class SlicedArray { @@ -275,8 +277,8 @@ export default class SlicedArray {
return undefined;
}
public findSliceOffset(maxId: number) {
let slice: Slice;
public findSliceOffset(maxId: T) {
let slice: Slice<T>;
for(let i = 0; i < this.slices.length; ++i) {
let offset = 0;
slice = this.slices[i];
@ -284,8 +286,8 @@ export default class SlicedArray { @@ -284,8 +286,8 @@ export default class SlicedArray {
continue;
}
for(; offset < slice.length; offset++) {
if(maxId >= slice[offset]) {
for(; offset < slice.length; ++offset) {
if(compareValue(maxId, slice[offset]) >= 0) {
/* if(!offset) { // because can't find 3 in [[5,4], [2,1]]
return undefined;
} */
@ -309,7 +311,7 @@ export default class SlicedArray { @@ -309,7 +311,7 @@ export default class SlicedArray {
}
// * https://core.telegram.org/api/offsets
public sliceMe(offsetId: number, add_offset: number, limit: number) {
public sliceMe(offsetId: T, add_offset: number, limit: number) {
let slice = this.slice;
let offset = 0;
let sliceOffset = 0;
@ -337,7 +339,7 @@ export default class SlicedArray { @@ -337,7 +339,7 @@ export default class SlicedArray {
//const fixHalfBackLimit = add_offset && !(limit / add_offset % 2) && (sliceEnd % 2) ? 1 : 0;
//sliceEnd += fixHalfBackLimit;
const sliced = slice.slice(sliceStart, sliceEnd) as Slice;
const sliced = slice.slice(sliceStart, sliceEnd) as Slice<T>;
const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit;
const bottomWasMeantToLoad = Math.abs(add_offset);
@ -356,7 +358,7 @@ export default class SlicedArray { @@ -356,7 +358,7 @@ export default class SlicedArray {
};
}
public unshift(...items: ItemType[]) {
public unshift(...items: T[]) {
let slice = this.first;
if(!slice.length) {
slice.setEnd(SliceEnd.Bottom);
@ -369,7 +371,7 @@ export default class SlicedArray { @@ -369,7 +371,7 @@ export default class SlicedArray {
slice.unshift(...items);
}
public push(...items: ItemType[]) {
public push(...items: T[]) {
let slice = this.last;
if(!slice.length) {
slice.setEnd(SliceEnd.Top);
@ -382,7 +384,7 @@ export default class SlicedArray { @@ -382,7 +384,7 @@ export default class SlicedArray {
slice.push(...items);
}
public delete(item: ItemType) {
public delete(item: T) {
const found = this.findSlice(item);
if(found) {
found.slice.splice(found.index, 1);

7
src/lib/appManagers/appImManager.ts

@ -345,6 +345,13 @@ export class AppImManager { @@ -345,6 +345,13 @@ export class AppImManager {
this.toggleChatGradientAnimation(to);
});
rootScope.addEventListener('service_notification', (update) => {
confirmationPopup({
button: {langKey: 'OK', isCancel: true},
description: RichTextProcessor.wrapRichText(update.message)
});
});
stateStorage.get('chatPositions').then((c) => {
stateStorage.setToCache('chatPositions', c || {});
});

22
src/lib/appManagers/appMessagesManager.ts

@ -80,7 +80,7 @@ const DO_NOT_READ_HISTORY = false; @@ -80,7 +80,7 @@ const DO_NOT_READ_HISTORY = false;
export type HistoryStorage = {
count: number | null,
history: SlicedArray,
history: SlicedArray<number>,
maxId?: number,
readPromise?: Promise<void>,
@ -94,7 +94,7 @@ export type HistoryStorage = { @@ -94,7 +94,7 @@ export type HistoryStorage = {
export type HistoryResult = {
count: number,
history: Slice,
history: Slice<number>,
offsetIdOffset?: number,
};
@ -220,7 +220,7 @@ export class AppMessagesManager { @@ -220,7 +220,7 @@ export class AppMessagesManager {
private middleware: ReturnType<typeof getMiddleware>;
private unreadMentions: {[peerId: PeerId]: SlicedArray} = {};
private unreadMentions: {[peerId: PeerId]: SlicedArray<number>} = {};
private goToNextMentionPromises: {[peerId: PeerId]: Promise<any>} = {};
private batchUpdates: {
@ -3901,7 +3901,7 @@ export class AppMessagesManager { @@ -3901,7 +3901,7 @@ export class AppMessagesManager {
let storage: {
count?: number;
history: SlicedArray;
history: SlicedArray<number>;
};
// * костыль для limit 1, если нужно и получить сообщение, и узнать количество сообщений
@ -4314,7 +4314,7 @@ export class AppMessagesManager { @@ -4314,7 +4314,7 @@ export class AppMessagesManager {
}
}
private fixUnreadMentionsCountIfNeeded(peerId: PeerId, slicedArray: SlicedArray) {
private fixUnreadMentionsCountIfNeeded(peerId: PeerId, slicedArray: SlicedArray<number>) {
const dialog = this.getDialogOnly(peerId);
if(!slicedArray.length && dialog?.unread_mentions_count) {
this.reloadConversation(peerId);
@ -5117,6 +5117,11 @@ export class AppMessagesManager { @@ -5117,6 +5117,11 @@ export class AppMessagesManager {
private onUpdateServiceNotification = (update: Update.updateServiceNotification) => {
//this.log('updateServiceNotification', update);
if(update.pFlags?.popup) {
rootScope.dispatchEvent('service_notification', update);
return;
}
const fromId = SERVICE_PEER_ID;
const peerId = fromId;
const messageId = this.generateTempMessageId(peerId);
@ -5613,6 +5618,11 @@ export class AppMessagesManager { @@ -5613,6 +5618,11 @@ export class AppMessagesManager {
notificationMessage = I18n.format('Notifications.New', true);
}
if(options.userReaction) {
notification.noIncrement = true;
notification.silent = true;
}
notification.title = appPeersManager.getPeerTitle(peerId, true);
if(isAnyChat && message.fromId !== message.peerId) {
notification.title = appPeersManager.getPeerTitle(message.fromId, true) +
@ -5848,7 +5858,7 @@ export class AppMessagesManager { @@ -5848,7 +5858,7 @@ export class AppMessagesManager {
return {count, offsetIdOffset, isTopEnd, isBottomEnd};
}
public mergeHistoryResult(slicedArray: SlicedArray,
public mergeHistoryResult(slicedArray: SlicedArray<number>,
historyResult: Parameters<AppMessagesManager['isHistoryResultEnd']>[0],
offset_id: number,
limit: number,

6
src/lib/appManagers/appNotificationsManager.ts

@ -45,6 +45,7 @@ export type NotifyOptions = Partial<{ @@ -45,6 +45,7 @@ export type NotifyOptions = Partial<{
message: string;
silent: boolean;
onclick: () => void;
noIncrement: boolean;
}>;
export type NotificationSettings = {
@ -595,7 +596,10 @@ export class AppNotificationsManager { @@ -595,7 +596,10 @@ export class AppNotificationsManager {
}
// console.log('notify image', data.image)
this.notificationsCount++;
if(!data.noIncrement) {
++this.notificationsCount;
}
if(!this.titleInterval) {
this.toggleToggler();
}

7
src/lib/calls/callInstance.ts

@ -357,6 +357,13 @@ export default class CallInstance extends CallInstanceBase<{ @@ -357,6 +357,13 @@ export default class CallInstance extends CallInstanceBase<{
});
}).then(phonePhoneCall => {
this.appCallsManager.savePhonePhoneCall(phonePhoneCall);
}).catch(err => {
this.log.error('accept call error', err);
// if(err.type === 'CALL_PROTOCOL_COMPAT_LAYER_INVALID') {
// }
this.hangUp('phoneCallDiscardReasonHangup');
});
}

6
src/lib/rootScope.ts

@ -96,7 +96,7 @@ export type BroadcastEvents = { @@ -96,7 +96,7 @@ export type BroadcastEvents = {
'media_play': {doc: MyDocument, message: Message.message, media: HTMLMediaElement},
'media_pause': void,
'media_playback_params': {volume: number, muted: boolean, playbackRate: number},
'media_playback_params': {volume: number, muted: boolean, playbackRate: number, loop: boolean, round: boolean},
'media_stop': void,
'state_cleared': void,
@ -164,7 +164,9 @@ export type BroadcastEvents = { @@ -164,7 +164,9 @@ export type BroadcastEvents = {
'quick_reaction': string,
'missed_reactions_element': {message: Message.message, changedResults: ReactionCount[]}
'missed_reactions_element': {message: Message.message, changedResults: ReactionCount[]},
'service_notification': Update.updateServiceNotification
};
export class RootScope extends EventListenerBase<{

4
src/scss/partials/_chat.scss

@ -354,7 +354,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi @@ -354,7 +354,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
// }
&.send .tgico-send,
&.record .tgico-microphone,
&.record .tgico-microphone_filled,
&.edit .tgico-check,
&.schedule .tgico-schedule {
visibility: visible !important;
@ -368,7 +368,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi @@ -368,7 +368,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
@include animation-level(2) {
&.send .tgico-send,
&.record .tgico-microphone,
&.record .tgico-microphone_filled,
&.edit .tgico-check,
&.schedule .tgico-schedule {
animation: grow-icon .4s forwards ease-in-out !important;

51
src/scss/tgico/_style.scss

@ -3,9 +3,9 @@ @@ -3,9 +3,9 @@
@font-face {
font-family: '#{$tgico-font-family}';
src:
url('#{$tgico-font-path}/#{$tgico-font-family}.ttf?cyy67r') format('truetype'),
url('#{$tgico-font-path}/#{$tgico-font-family}.woff?cyy67r') format('woff'),
url('#{$tgico-font-path}/#{$tgico-font-family}.svg?cyy67r##{$tgico-font-family}') format('svg');
url('#{$tgico-font-path}/#{$tgico-font-family}.ttf?1mzumm') format('truetype'),
url('#{$tgico-font-path}/#{$tgico-font-family}.woff?1mzumm') format('woff'),
url('#{$tgico-font-path}/#{$tgico-font-family}.svg?1mzumm##{$tgico-font-family}') format('svg');
font-weight: normal;
font-style: normal;
font-display: block;
@ -332,11 +332,6 @@ @@ -332,11 +332,6 @@
content: $tgico-email;
}
}
.tgico-endcall {
&:before {
content: $tgico-endcall;
}
}
.tgico-endcall_filled {
&:before {
content: $tgico-endcall_filled;
@ -377,6 +372,11 @@ @@ -377,6 +372,11 @@
content: $tgico-flag;
}
}
.tgico-flip {
&:before {
content: $tgico-flip;
}
}
.tgico-folder {
&:before {
content: $tgico-folder;
@ -547,6 +547,21 @@ @@ -547,6 +547,21 @@
content: $tgico-microphone;
}
}
.tgico-microphone_crossed {
&:before {
content: $tgico-microphone_crossed;
}
}
.tgico-microphone_crossed_filled {
&:before {
content: $tgico-microphone_crossed_filled;
}
}
.tgico-microphone_filled {
&:before {
content: $tgico-microphone_filled;
}
}
.tgico-minus {
&:before {
content: $tgico-minus;
@ -737,6 +752,16 @@ @@ -737,6 +752,16 @@
content: $tgico-rightpanel;
}
}
.tgico-rotate_left {
&:before {
content: $tgico-rotate_left;
}
}
.tgico-rotate_right {
&:before {
content: $tgico-rotate_right;
}
}
.tgico-saved {
&:before {
content: $tgico-saved;
@ -802,6 +827,11 @@ @@ -802,6 +827,11 @@
content: $tgico-sharescreen_filled;
}
}
.tgico-shuffle {
&:before {
content: $tgico-shuffle;
}
}
.tgico-smallscreen {
&:before {
content: $tgico-smallscreen;
@ -897,6 +927,11 @@ @@ -897,6 +927,11 @@
content: $tgico-videocamera;
}
}
.tgico-videocamera_crossed_filled {
&:before {
content: $tgico-videocamera_crossed_filled;
}
}
.tgico-videocamera_filled {
&:before {
content: $tgico-videocamera_filled;

181
src/scss/tgico/_variables.scss

@ -58,15 +58,15 @@ $tgico-dragmedia: "\e938"; @@ -58,15 +58,15 @@ $tgico-dragmedia: "\e938";
$tgico-eats: "\e939";
$tgico-edit: "\e93a";
$tgico-email: "\e93b";
$tgico-endcall: "\e93c";
$tgico-endcall_filled: "\e93d";
$tgico-enter: "\e93e";
$tgico-eye1: "\e93f";
$tgico-eye2: "\e940";
$tgico-fast_forward: "\e941";
$tgico-fast_rewind: "\e942";
$tgico-favourites: "\e943";
$tgico-flag: "\e944";
$tgico-endcall_filled: "\e93c";
$tgico-enter: "\e93d";
$tgico-eye1: "\e93e";
$tgico-eye2: "\e93f";
$tgico-fast_forward: "\e940";
$tgico-fast_rewind: "\e941";
$tgico-favourites: "\e942";
$tgico-flag: "\e943";
$tgico-flip: "\e944";
$tgico-folder: "\e945";
$tgico-fontsize: "\e946";
$tgico-forward: "\e947";
@ -101,82 +101,89 @@ $tgico-menu: "\e963"; @@ -101,82 +101,89 @@ $tgico-menu: "\e963";
$tgico-message: "\e964";
$tgico-messageunread: "\e965";
$tgico-microphone: "\e966";
$tgico-minus: "\e967";
$tgico-monospace: "\e968";
$tgico-more: "\e969";
$tgico-mute: "\e96a";
$tgico-muted: "\e96b";
$tgico-newchannel: "\e96c";
$tgico-newchat_filled: "\e96d";
$tgico-newgroup: "\e96e";
$tgico-newprivate: "\e96f";
$tgico-next: "\e970";
$tgico-noncontacts: "\e971";
$tgico-nosound: "\e972";
$tgico-passwordoff: "\e973";
$tgico-pause: "\e974";
$tgico-permissions: "\e975";
$tgico-phone: "\e976";
$tgico-pin: "\e977";
$tgico-pinlist: "\e978";
$tgico-pinned_filled: "\e979";
$tgico-pinnedchat: "\e97a";
$tgico-pip: "\e97b";
$tgico-play: "\e97c";
$tgico-playback_05: "\e97d";
$tgico-playback_15: "\e97e";
$tgico-playback_1x: "\e97f";
$tgico-playback_2x: "\e980";
$tgico-plus: "\e981";
$tgico-poll: "\e982";
$tgico-previous: "\e983";
$tgico-radiooff: "\e984";
$tgico-radioon: "\e985";
$tgico-reactions: "\e986";
$tgico-readchats: "\e987";
$tgico-recent: "\e988";
$tgico-replace: "\e989";
$tgico-reply: "\e98a";
$tgico-reply_filled: "\e98b";
$tgico-rightpanel: "\e98c";
$tgico-saved: "\e98d";
$tgico-savedmessages: "\e98e";
$tgico-schedule: "\e98f";
$tgico-scheduled: "\e990";
$tgico-search: "\e991";
$tgico-select: "\e992";
$tgico-send: "\e993";
$tgico-send2: "\e994";
$tgico-sending: "\e995";
$tgico-sendingerror: "\e996";
$tgico-settings: "\e997";
$tgico-settings_filled: "\e998";
$tgico-sharescreen_filled: "\e999";
$tgico-smallscreen: "\e99a";
$tgico-smile: "\e99b";
$tgico-spoiler: "\e99c";
$tgico-sport: "\e99d";
$tgico-stickers: "\e99e";
$tgico-stop: "\e99f";
$tgico-strikethrough: "\e9a0";
$tgico-textedit: "\e9a1";
$tgico-tip: "\e9a2";
$tgico-tools: "\e9a3";
$tgico-unarchive: "\e9a4";
$tgico-underline: "\e9a5";
$tgico-unmute: "\e9a6";
$tgico-unpin: "\e9a7";
$tgico-unread: "\e9a8";
$tgico-up: "\e9a9";
$tgico-user: "\e9aa";
$tgico-username: "\e9ab";
$tgico-videocamera: "\e9ac";
$tgico-videocamera_filled: "\e9ad";
$tgico-videochat: "\e9ae";
$tgico-volume_down: "\e9af";
$tgico-volume_mute: "\e9b0";
$tgico-volume_off: "\e9b1";
$tgico-volume_up: "\e9b2";
$tgico-zoomin: "\e9b3";
$tgico-zoomout: "\e9b4";
$tgico-microphone_crossed: "\e967";
$tgico-microphone_crossed_filled: "\e968";
$tgico-microphone_filled: "\e969";
$tgico-minus: "\e96a";
$tgico-monospace: "\e96b";
$tgico-more: "\e96c";
$tgico-mute: "\e96d";
$tgico-muted: "\e96e";
$tgico-newchannel: "\e96f";
$tgico-newchat_filled: "\e970";
$tgico-newgroup: "\e971";
$tgico-newprivate: "\e972";
$tgico-next: "\e973";
$tgico-noncontacts: "\e974";
$tgico-nosound: "\e975";
$tgico-passwordoff: "\e976";
$tgico-pause: "\e977";
$tgico-permissions: "\e978";
$tgico-phone: "\e979";
$tgico-pin: "\e97a";
$tgico-pinlist: "\e97b";
$tgico-pinned_filled: "\e97c";
$tgico-pinnedchat: "\e97d";
$tgico-pip: "\e97e";
$tgico-play: "\e97f";
$tgico-playback_05: "\e980";
$tgico-playback_15: "\e981";
$tgico-playback_1x: "\e982";
$tgico-playback_2x: "\e983";
$tgico-plus: "\e984";
$tgico-poll: "\e985";
$tgico-previous: "\e986";
$tgico-radiooff: "\e987";
$tgico-radioon: "\e988";
$tgico-reactions: "\e989";
$tgico-readchats: "\e98a";
$tgico-recent: "\e98b";
$tgico-replace: "\e98c";
$tgico-reply: "\e98d";
$tgico-reply_filled: "\e98e";
$tgico-rightpanel: "\e98f";
$tgico-rotate_left: "\e990";
$tgico-rotate_right: "\e991";
$tgico-saved: "\e992";
$tgico-savedmessages: "\e993";
$tgico-schedule: "\e994";
$tgico-scheduled: "\e995";
$tgico-search: "\e996";
$tgico-select: "\e997";
$tgico-send: "\e998";
$tgico-send2: "\e999";
$tgico-sending: "\e99a";
$tgico-sendingerror: "\e99b";
$tgico-settings: "\e99c";
$tgico-settings_filled: "\e99d";
$tgico-sharescreen_filled: "\e99e";
$tgico-shuffle: "\e99f";
$tgico-smallscreen: "\e9a0";
$tgico-smile: "\e9a1";
$tgico-spoiler: "\e9a2";
$tgico-sport: "\e9a3";
$tgico-stickers: "\e9a4";
$tgico-stop: "\e9a5";
$tgico-strikethrough: "\e9a6";
$tgico-textedit: "\e9a7";
$tgico-tip: "\e9a8";
$tgico-tools: "\e9a9";
$tgico-unarchive: "\e9aa";
$tgico-underline: "\e9ab";
$tgico-unmute: "\e9ac";
$tgico-unpin: "\e9ad";
$tgico-unread: "\e9ae";
$tgico-up: "\e9af";
$tgico-user: "\e9b0";
$tgico-username: "\e9b1";
$tgico-videocamera: "\e9b2";
$tgico-videocamera_crossed_filled: "\e9b3";
$tgico-videocamera_filled: "\e9b4";
$tgico-videochat: "\e9b5";
$tgico-volume_down: "\e9b6";
$tgico-volume_mute: "\e9b7";
$tgico-volume_off: "\e9b8";
$tgico-volume_up: "\e9b9";
$tgico-zoomin: "\e9ba";
$tgico-zoomout: "\e9bb";

22
src/tests/slicedArray.test.ts

@ -7,23 +7,27 @@ test('Slicing returns new Slice', () => { @@ -7,23 +7,27 @@ test('Slicing returns new Slice', () => {
});
describe('Inserting', () => {
const sliced = new SlicedArray();
const sliced = new SlicedArray<typeof arr[0]>();
// @ts-ignore
const slices = sliced.slices;
const toSomething = (v: number) => {
return '' + v;
};
const arr = [100, 99, 98, 97, 96, 95];
const distantArr = arr.slice(-2).map(v => v - 2);
const missingArr = [arr[arr.length - 1], arr[arr.length - 1] - 1, distantArr[0]];
const arr = [100, 99, 98, 97, 96, 95].map(toSomething);
const distantArr = arr.slice(-2).map(v => toSomething(+v - 2));
const missingArr = [arr[arr.length - 1], toSomething(+arr[arr.length - 1] - 1), distantArr[1]];
const startValue = 90;
const values: number[] = [];
const startValue = toSomething(90);
const values: typeof arr = [];
const valuesPerArray = 3;
const totalArrays = 10;
for(let i = 0, length = valuesPerArray * totalArrays; i < length; ++i) {
values.push(startValue - i);
values.push(toSomething(+startValue - i));
}
const arrays: number[][] = [];
const arrays: (typeof values)[] = [];
for(let i = 0; i < totalArrays; ++i) {
arrays.push(values.slice(valuesPerArray * i, valuesPerArray * (i + 1)));
}
@ -57,7 +61,7 @@ describe('Inserting', () => { @@ -57,7 +61,7 @@ describe('Inserting', () => {
expect(slices.length).toEqual(length - 1);
});
let returnedSlice: Slice;
let returnedSlice: Slice<typeof arr[0]>;
test('Insert arrays with gap & join them', () => {
slices[0].length = 0;

Loading…
Cancel
Save