Browse Source

Fix saving poll results to state

Release poll if it's deleted
Added thumbs for audio
Fix sending voice message to wrong chat
Fix missed avatars in messages from group
Fix clickable links in input
master
morethanwords 3 years ago
parent
commit
40a2e087d6
  1. 135
      src/components/appMediaPlaybackController.ts
  2. 276
      src/components/appMediaViewer.ts
  3. 16
      src/components/appSearchSuper..ts
  4. 212
      src/components/audio.ts
  5. 2
      src/components/avatar.ts
  6. 19
      src/components/chat/bubbles.ts
  7. 12
      src/components/chat/input.ts
  8. 7
      src/components/chat/selection.ts
  9. 7
      src/components/poll.ts
  10. 35
      src/components/preloader.ts
  11. 16
      src/components/wrappers.ts
  12. 8
      src/helpers/cancellablePromise.ts
  13. 26
      src/lib/appManagers/appMessagesManager.ts
  14. 60
      src/lib/appManagers/appPollsManager.ts
  15. 4
      src/lib/richtextprocessor.ts
  16. 398
      src/scss/partials/_audio.scss
  17. 8
      src/scss/partials/_chatBubble.scss
  18. 17
      src/scss/partials/_document.scss
  19. 1
      src/scss/partials/_poll.scss
  20. 4
      src/scss/style.scss

135
src/components/appMediaPlaybackController.ts

@ -11,6 +11,9 @@ import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromi @@ -11,6 +11,9 @@ import { CancellablePromise, deferredPromise } from "../helpers/cancellablePromi
import { isSafari } from "../helpers/userAgent";
import { MOUNT_CLASS_TO } from "../config/debug";
import appDownloadManager from "../lib/appManagers/appDownloadManager";
import simulateEvent from "../helpers/dom/dispatchEvent";
import type { SearchSuperContext } from "./appSearchSuper.";
import { copy, deepEqual } from "../helpers/object";
// TODO: если удалить сообщение, и при этом аудио будет играть - оно не остановится, и можно будет по нему перейти вникуда
@ -18,9 +21,17 @@ import appDownloadManager from "../lib/appManagers/appDownloadManager"; @@ -18,9 +21,17 @@ import appDownloadManager from "../lib/appManagers/appDownloadManager";
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
// TODO: Safari: пофиксить момент, когда заканчивается песня и пытаешься включить её заново - прогресс сразу в конце
type MediaItem = {mid: number, peerId: number};
type HTMLMediaElement = HTMLAudioElement | HTMLVideoElement;
type MediaType = 'voice' | 'audio' | 'round';
const SHOULD_USE_SAFARI_FIX = (() => {
try {
return isSafari && +navigator.userAgent.match(/ Version\/(\d+)/)[1] < 14;
} catch(err) {
return false;
}
})();
class AppMediaPlaybackController {
private container: HTMLElement;
@ -29,7 +40,7 @@ class AppMediaPlaybackController { @@ -29,7 +40,7 @@ class AppMediaPlaybackController {
[mid: string]: HTMLMediaElement
}
} = {};
private playingMedia: HTMLMediaElement;
public playingMedia: HTMLMediaElement;
private waitingMediaForLoad: {
[peerId: string]: {
@ -38,11 +49,15 @@ class AppMediaPlaybackController { @@ -38,11 +49,15 @@ class AppMediaPlaybackController {
} = {};
public willBePlayedMedia: HTMLMediaElement;
public searchContext: SearchSuperContext;
private currentPeerId: number;
private prevMid: number;
private nextMid: number;
private prev: MediaItem[] = [];
private next: MediaItem[] = [];
constructor() {
this.container = document.createElement('div');
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
@ -63,6 +78,7 @@ class AppMediaPlaybackController { @@ -63,6 +78,7 @@ class AppMediaPlaybackController {
//media.muted = true;
}
media.dataset.peerId = '' + peerId;
media.dataset.mid = '' + mid;
media.dataset.type = doc.type;
@ -72,30 +88,11 @@ class AppMediaPlaybackController { @@ -72,30 +88,11 @@ class AppMediaPlaybackController {
this.container.append(media);
media.addEventListener('playing', () => {
this.currentPeerId = peerId;
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
if(this.playingMedia !== media) {
if(this.playingMedia && !this.playingMedia.paused) {
this.playingMedia.pause();
}
this.playingMedia = media;
this.loadSiblingsMedia(peerId, doc.type as MediaType, mid);
}
// audio_pause не успеет сработать без таймаута
setTimeout(() => {
rootScope.dispatchEvent('audio_play', {peerId, doc, mid});
}, 0);
});
media.addEventListener('play', this.onPlay);
media.addEventListener('pause', this.onPause);
media.addEventListener('ended', this.onEnded);
const onError = (e: Event) => {
/* const onError = (e: Event) => {
//console.log('appMediaPlaybackController: video onError', e);
if(this.nextMid === mid) {
@ -107,7 +104,7 @@ class AppMediaPlaybackController { @@ -107,7 +104,7 @@ class AppMediaPlaybackController {
}
};
media.addEventListener('error', onError);
media.addEventListener('error', onError); */
const deferred = deferredPromise<void>();
if(autoload) {
@ -122,14 +119,16 @@ class AppMediaPlaybackController { @@ -122,14 +119,16 @@ class AppMediaPlaybackController {
//console.log('will set media url:', media, doc, doc.type, doc.url);
((!doc.supportsStreaming ? appDocsManager.downloadDoc(doc) : Promise.resolve()) as Promise<any>).then(() => {
if(doc.type === 'audio' && doc.supportsStreaming && isSafari) {
if(doc.type === 'audio' && doc.supportsStreaming && SHOULD_USE_SAFARI_FIX) {
this.handleSafariStreamable(media);
}
// setTimeout(() => {
const cacheContext = appDownloadManager.getCacheContext(doc);
media.src = cacheContext.url;
// }, doc.supportsStreaming ? 500e3 : 0);
});
}, onError);
}/* , onError */);
return storage[mid] = media;
}
@ -163,7 +162,11 @@ class AppMediaPlaybackController { @@ -163,7 +162,11 @@ class AppMediaPlaybackController {
}
public resolveWaitingForLoadMedia(peerId: number, mid: number) {
const storage = this.waitingMediaForLoad[peerId] ?? (this.waitingMediaForLoad[peerId] = {});
const storage = this.waitingMediaForLoad[peerId];
if(!storage) {
return;
}
const promise = storage[mid];
if(promise) {
promise.resolve();
@ -184,6 +187,37 @@ class AppMediaPlaybackController { @@ -184,6 +187,37 @@ class AppMediaPlaybackController {
media.safariBuffering = value;
}
onPlay = (e?: Event) => {
const media = e.target as HTMLMediaElement;
const peerId = +media.dataset.peerId;
const mid = +media.dataset.mid;
this.currentPeerId = peerId;
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
const previousMedia = this.playingMedia;
if(previousMedia !== media) {
if(previousMedia) {
if(!previousMedia.paused) {
previousMedia.pause();
}
// reset media
previousMedia.currentTime = 0;
simulateEvent(previousMedia, 'ended');
}
this.playingMedia = media;
this.loadSiblingsMedia(peerId, mid);
}
// audio_pause не успеет сработать без таймаута
setTimeout(() => {
const message = appMessagesManager.getMessageByPeer(peerId, mid);
rootScope.dispatchEvent('audio_play', {peerId, doc: message.media.document, mid});
}, 0);
};
onPause = (e?: Event) => {
/* const target = e.target as HTMLMediaElement;
if(!isInDOM(target)) {
@ -196,6 +230,10 @@ class AppMediaPlaybackController { @@ -196,6 +230,10 @@ class AppMediaPlaybackController {
};
onEnded = (e?: Event) => {
if(!e.isTrusted) {
return;
}
this.onPause(e);
//console.log('on media end');
@ -215,35 +253,28 @@ class AppMediaPlaybackController { @@ -215,35 +253,28 @@ class AppMediaPlaybackController {
}
};
private loadSiblingsMedia(peerId: number, type: MediaType, mid: number) {
const media = this.playingMedia;
this.prevMid = this.nextMid = 0;
private loadSiblingsMedia(offsetPeerId: number, offsetMid: number) {
const {playingMedia, searchContext} = this;
if(!searchContext) {
return;
}
return appMessagesManager.getSearch({
peerId,
query: '',
inputFilter: {
//_: type === 'audio' ? 'inputMessagesFilterMusic' : (type === 'round' ? 'inputMessagesFilterRoundVideo' : 'inputMessagesFilterVoice')
_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'
},
maxId: mid,
...searchContext,
maxId: offsetMid,
limit: 3,
backLimit: 2
backLimit: 2,
}).then(value => {
if(this.playingMedia !== media) {
if(this.playingMedia !== playingMedia || this.searchContext !== searchContext) {
return;
}
for(const {mid: m} of value.history) {
if(m > mid) {
this.nextMid = m;
} else if(m < mid) {
this.prevMid = m;
break;
}
}
const idx = Math.max(0, value.history.findIndex(message => message.peerId === offsetPeerId && message.mid === offsetMid));
const prev = value.history.slice(Math.max(0, idx));
const next = value.history.slice(0, idx);
[this.prevMid, this.nextMid].filter(Boolean).forEach(mid => {
const peerId = searchContext.peerId;
const message = appMessagesManager.getMessageByPeer(peerId, mid);
this.addMedia(peerId, message.media.document, mid, false);
});
@ -270,6 +301,16 @@ class AppMediaPlaybackController { @@ -270,6 +301,16 @@ class AppMediaPlaybackController {
public willBePlayed(media: HTMLMediaElement) {
this.willBePlayedMedia = media;
}
public setSearchContext(context: SearchSuperContext) {
if(deepEqual(this.searchContext, context)) {
return;
}
this.searchContext = copy(context); // {_: type === 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'}
this.prev.length = 0;
this.next.length = 0;
}
}
const appMediaPlaybackController = new AppMediaPlaybackController();

276
src/components/appMediaViewer.ts

@ -53,6 +53,7 @@ import { attachClickEvent } from "../helpers/dom/clickEvent"; @@ -53,6 +53,7 @@ import { attachClickEvent } from "../helpers/dom/clickEvent";
import PopupDeleteMessages from "./popups/deleteMessages";
import RangeSelector from "./rangeSelector";
import windowSize from "../helpers/windowSize";
import { safeAssign } from "../helpers/object";
const ZOOM_STEP = 0.5;
const ZOOM_INITIAL_VALUE = 1;
@ -65,7 +66,148 @@ const ZOOM_MAX_VALUE = 4; @@ -65,7 +66,148 @@ const ZOOM_MAX_VALUE = 4;
const MEDIA_VIEWER_CLASSNAME = 'media-viewer';
class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType extends string, TargetType extends object> {
type MediaQueueLoaderOptions<Item extends {}> = {
prevTargets?: MediaQueueLoader<Item>['prevTargets'],
nextTargets?: MediaQueueLoader<Item>['nextTargets'],
onLoadedMore?: MediaQueueLoader<Item>['onLoadedMore'],
generateItem?: MediaQueueLoader<Item>['generateItem'],
getLoadPromise?: MediaQueueLoader<Item>['getLoadPromise'],
reverse?: MediaQueueLoader<Item>['reverse']
};
class MediaQueueLoader<Item extends {}> {
public prevTargets: Item[] = [];
public nextTargets: Item[] = [];
public loadMediaPromiseUp: Promise<void> = null;
public loadMediaPromiseDown: Promise<void> = null;
public loadedAllMediaUp = false;
public loadedAllMediaDown = false;
public reverse = false; // reverse means next = higher msgid
protected generateItem: (item: Item) => Item = (item) => item;
protected getLoadPromise: (older: boolean, anchor: Item, loadCount: number) => Promise<Item[]>;
protected onLoadedMore: () => void;
constructor(options: MediaQueueLoaderOptions<Item> = {}) {
safeAssign(this, options);
}
public setTargets(prevTargets: Item[], nextTargets: Item[], reverse: boolean) {
this.prevTargets = prevTargets;
this.nextTargets = nextTargets;
this.reverse = reverse;
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
}
public reset() {
this.prevTargets = [];
this.nextTargets = [];
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
this.loadMediaPromiseUp = this.loadMediaPromiseDown = null;
}
// нет смысла делать проверку для reverse и loadMediaPromise
public loadMoreMedia = (older = true) => {
//if(!older && this.reverse) return;
if(older && this.loadedAllMediaDown) return Promise.resolve();
else if(!older && this.loadedAllMediaUp) return Promise.resolve();
if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown;
else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp;
const loadCount = 50;
let anchor: Item;
if(older) {
anchor = this.reverse ? this.prevTargets[0] : this.nextTargets[this.nextTargets.length - 1];
} else {
anchor = this.reverse ? this.nextTargets[this.nextTargets.length - 1] : this.prevTargets[0];
}
const promise = this.getLoadPromise(older, anchor, loadCount).then(items => {
if(items.length < loadCount) {
/* if(this.reverse) {
if(older) this.loadedAllMediaUp = true;
else this.loadedAllMediaDown = true;
} else { */
if(older) this.loadedAllMediaDown = true;
else this.loadedAllMediaUp = true;
//}
}
const method: any = older ? items.forEach.bind(items) : forEachReverse.bind(null, items);
method((item: Item) => {
const t = this.generateItem(item);
if(!t) {
return;
}
if(older) {
if(this.reverse) this.prevTargets.unshift(t);
else this.nextTargets.push(t);
} else {
if(this.reverse) this.nextTargets.push(t);
else this.prevTargets.unshift(t);
}
});
this.onLoadedMore && this.onLoadedMore();
}, () => {}).then(() => {
if(older) this.loadMediaPromiseDown = null;
else this.loadMediaPromiseUp = null;
});
if(older) this.loadMediaPromiseDown = promise;
else this.loadMediaPromiseUp = promise;
return promise;
};
}
class MediaSearchQueueLoader<Item extends {mid: number, peerId: number}> extends MediaQueueLoader<Item> {
public currentMessageId = 0;
public searchContext: SearchSuperContext;
constructor(options: Omit<MediaQueueLoaderOptions<Item>, 'getLoadPromise'> = {}) {
super({
...options,
getLoadPromise: (older, anchor, loadCount) => {
const backLimit = older ? 0 : loadCount;
let maxId = this.currentMessageId;
if(anchor) maxId = anchor.mid;
if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1);
return appMessagesManager.getSearch({
...this.searchContext,
maxId,
limit: backLimit ? 0 : loadCount,
backLimit
}).then(value => {
/* if(DEBUG) {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
} */
if(value.next_rate) {
this.searchContext.nextRate = value.next_rate;
}
return value.history as any;
});
}
});
}
public setSearchContext(context: SearchSuperContext) {
this.searchContext = context;
}
}
class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType extends string, TargetType extends {element: HTMLElement}> {
protected wholeDiv: HTMLElement;
protected overlaysDiv: HTMLElement;
protected author: {[k in 'container' | 'avatarEl' | 'nameEl' | 'date']: HTMLElement} = {} as any;
@ -78,7 +220,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -78,7 +220,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
protected preloader: ProgressivePreloader = null;
protected preloaderStreamable: ProgressivePreloader = null;
protected lastTarget: HTMLElement = null;
protected target: TargetType = null;
protected prevTargets: TargetType[] = [];
protected nextTargets: TargetType[] = [];
//protected targetContainer: HTMLElement = null;
@ -254,6 +396,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -254,6 +396,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
const target = this.prevTargets.pop();
if(target) {
this.nextTargets.unshift(this.target);
this.onPrevClick(target);
} else {
this.buttons.prev.style.display = 'none';
@ -266,6 +409,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -266,6 +409,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
let target = this.nextTargets.shift();
if(target) {
this.prevTargets.push(this.target);
this.onNextClick(target);
} else {
this.buttons.next.style.display = 'none';
@ -418,9 +562,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -418,9 +562,9 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
this.lazyLoadQueue.clear();
const promise = this.setMoverToTarget(this.lastTarget, true).then(({onAnimationEnd}) => onAnimationEnd);
const promise = this.setMoverToTarget(this.target?.element, true).then(({onAnimationEnd}) => onAnimationEnd);
this.lastTarget = null;
this.target = null;
this.prevTargets = [];
this.nextTargets = [];
this.loadedAllMediaUp = this.loadedAllMediaDown = false;
@ -1209,7 +1353,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -1209,7 +1353,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
const useContainerAsTarget = !target || target === container;
if(useContainerAsTarget) target = container;
this.lastTarget = target;
this.target = {element: target} as any;
const tempId = ++this.tempId;
if(this.needLoadMore) {
@ -1563,6 +1707,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1563,6 +1707,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
protected searchContext: SearchSuperContext;
protected btnMenuDelete: HTMLElement;
protected queueLoader: MediaSearchQueueLoader<AppMediaViewerTargetType>;
constructor() {
super(['delete', 'forward']);
@ -1570,6 +1716,24 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1570,6 +1716,24 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
stub.classList.add(MEDIA_VIEWER_CLASSNAME + '-stub');
this.content.main.prepend(stub); */
this.queueLoader = new MediaSearchQueueLoader({
prevTargets: this.prevTargets,
nextTargets: this.nextTargets,
generateItem: (item) => {
const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument';
const {mid, peerId} = item;
const media: MyPhoto | MyDocument = appMessagesManager.getMediaFromMessage(item);
if(!media) return;
if(isForDocument && !AppMediaViewer.isMediaCompatibleForDocumentViewer(media)) {
return;
}
return {element: null as HTMLElement, mid, peerId};
}
});
this.content.caption = document.createElement('div');
this.content.caption.classList.add(MEDIA_VIEWER_CLASSNAME + '-caption'/* , 'media-viewer-stub' */);
@ -1665,18 +1829,16 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1665,18 +1829,16 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
} */
onPrevClick = (target: AppMediaViewerTargetType) => {
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageId, peerId: this.currentPeerId});
this.openMedia(appMessagesManager.getMessageByPeer(target.peerId, target.mid), target.element, -1);
};
onNextClick = (target: AppMediaViewerTargetType) => {
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageId, peerId: this.currentPeerId});
this.openMedia(appMessagesManager.getMessageByPeer(target.peerId, target.mid), target.element, 1);
};
onDeleteClick = () => {
new PopupDeleteMessages(this.currentPeerId, [this.currentMessageId], 'chat', () => {
this.lastTarget = this.content.media;
this.target = {element: this.content.media} as any;
this.close();
});
};
@ -1732,94 +1894,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1732,94 +1894,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
// нет смысла делать проверку для reverse и loadMediaPromise
protected loadMoreMedia = (older = true) => {
//if(!older && this.reverse) return;
if(older && this.loadedAllMediaDown) return Promise.resolve();
else if(!older && this.loadedAllMediaUp) return Promise.resolve();
if(older && this.loadMediaPromiseDown) return this.loadMediaPromiseDown;
else if(!older && this.loadMediaPromiseUp) return this.loadMediaPromiseUp;
const loadCount = 50;
const backLimit = older ? 0 : loadCount;
let maxId = this.currentMessageId;
let anchor: AppMediaViewerTargetType;
if(older) {
anchor = this.reverse ? this.prevTargets[0] : this.nextTargets[this.nextTargets.length - 1];
} else {
anchor = this.reverse ? this.nextTargets[this.nextTargets.length - 1] : this.prevTargets[0];
}
if(anchor) maxId = anchor.mid;
if(!older) maxId = appMessagesIdsManager.incrementMessageId(maxId, 1);
const promise = appMessagesManager.getSearch({
peerId: this.searchContext.peerId,
query: this.searchContext.query,
inputFilter: {
_: this.searchContext.inputFilter
},
maxId,
limit: backLimit ? 0 : loadCount,
backLimit,
threadId: this.searchContext.threadId,
folderId: this.searchContext.folderId,
nextRate: this.searchContext.nextRate,
minDate: this.searchContext.minDate,
maxDate: this.searchContext.maxDate
}).then(value => {
if(DEBUG) {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
}
if(value.next_rate) {
this.searchContext.nextRate = value.next_rate;
}
if(value.history.length < loadCount) {
/* if(this.reverse) {
if(older) this.loadedAllMediaUp = true;
else this.loadedAllMediaDown = true;
} else { */
if(older) this.loadedAllMediaDown = true;
else this.loadedAllMediaUp = true;
//}
}
const method: any = older ? value.history.forEach.bind(value.history) : forEachReverse.bind(null, value.history);
const isForDocument = this.searchContext.inputFilter === 'inputMessagesFilterDocument';
method((message: Message.message) => {
const {mid, peerId} = message;
const media: MyPhoto | MyDocument = appMessagesManager.getMediaFromMessage(message);
if(!media) return;
if(isForDocument && !AppMediaViewer.isMediaCompatibleForDocumentViewer(media)) {
return;
}
const t = {element: null as HTMLElement, mid, peerId};
if(older) {
if(this.reverse) this.prevTargets.unshift(t);
else this.nextTargets.push(t);
} else {
if(this.reverse) this.nextTargets.push(t);
else this.prevTargets.unshift(t);
}
});
this.buttons.prev.classList.toggle('hide', !this.prevTargets.length);
this.buttons.next.classList.toggle('hide', !this.nextTargets.length);
}, () => {}).then(() => {
if(older) this.loadMediaPromiseDown = null;
else this.loadMediaPromiseUp = null;
});
if(older) this.loadMediaPromiseDown = promise;
else this.loadMediaPromiseUp = promise;
return promise;
return this.queueLoader.loadMoreMedia(older);
};
private setCaption(message: Message.message) {
@ -1870,6 +1945,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1870,6 +1945,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
this.currentPeerId = message.peerId;
this.setCaption(message);
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore);
this.target.mid = mid;
this.target.peerId = message.peerId;
return promise;
}
@ -1905,12 +1982,10 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe @@ -1905,12 +1982,10 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe
}
onPrevClick = (target: AppMediaViewerAvatarTargetType) => {
this.nextTargets.unshift({element: this.lastTarget, photoId: this.currentPhotoId});
this.openMedia(target.photoId, target.element, -1);
};
onNextClick = (target: AppMediaViewerAvatarTargetType) => {
this.prevTargets.push({element: this.lastTarget, photoId: this.currentPhotoId});
this.openMedia(target.photoId, target.element, 1);
};
@ -1963,6 +2038,9 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe @@ -1963,6 +2038,9 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe
this.currentPhotoId = photo.id;
return super._openMedia(photo, photo.date, this.peerId, fromRight, target, false, prevTargets, nextTargets);
const ret = super._openMedia(photo, photo.date, this.peerId, fromRight, target, false, prevTargets, nextTargets);
this.target.photoId = photo.id;
return ret;
}
}

16
src/components/appSearchSuper..ts

@ -56,7 +56,7 @@ import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent" @@ -56,7 +56,7 @@ import { attachClickEvent, simulateClickEvent } from "../helpers/dom/clickEvent"
export type SearchSuperType = MyInputMessagesFilter/* | 'members' */;
export type SearchSuperContext = {
peerId: number,
inputFilter: MyInputMessagesFilter,
inputFilter: {_: MyInputMessagesFilter},
query?: string,
maxId?: number,
folderId?: number,
@ -453,6 +453,7 @@ export default class AppSearchSuper { @@ -453,6 +453,7 @@ export default class AppSearchSuper {
const onMediaClick = (className: string, targetClassName: string, inputFilter: MyInputMessagesFilter, e: MouseEvent) => {
const target = findUpClassName(e.target as HTMLDivElement, className);
if(!target) return;
const mid = +target.dataset.mid;
if(!mid) {
@ -1240,16 +1241,11 @@ export default class AppSearchSuper { @@ -1240,16 +1241,11 @@ export default class AppSearchSuper {
//let loadCount = history.length ? 50 : 15;
return this.loadPromises[type] = appMessagesManager.getSearch({
peerId: this.searchContext.peerId,
query: this.searchContext.query,
...this.searchContext,
inputFilter: {_: type},
maxId,
limit: loadCount,
nextRate: this.nextRates[type] ?? (this.nextRates[type] = 0),
threadId: this.searchContext.threadId,
folderId: this.searchContext.folderId,
minDate: this.searchContext.minDate,
maxDate: this.searchContext.maxDate
nextRate: this.nextRates[type] ?? (this.nextRates[type] = 0)
}).then(value => {
history.push(...value.history.map(m => ({mid: m.mid, peerId: m.peerId})));
@ -1541,7 +1537,7 @@ export default class AppSearchSuper { @@ -1541,7 +1537,7 @@ export default class AppSearchSuper {
private copySearchContext(newInputFilter: MyInputMessagesFilter) {
const context = copy(this.searchContext);
context.inputFilter = newInputFilter;
context.inputFilter = {_: newInputFilter};
context.nextRate = this.nextRates[newInputFilter];
return context;
}
@ -1558,7 +1554,7 @@ export default class AppSearchSuper { @@ -1558,7 +1554,7 @@ export default class AppSearchSuper {
this.searchContext = {
peerId: peerId || 0,
query: query || '',
inputFilter: this.mediaTab.inputFilter,
inputFilter: {_: this.mediaTab.inputFilter},
threadId,
folderId,
minDate,

212
src/components/audio.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import { formatDate } from "./wrappers";
import { formatDate, wrapPhoto } from "./wrappers";
import ProgressivePreloader from "./preloader";
import { MediaProgressLine } from "../lib/mediaPlayer";
import appMediaPlaybackController from "./appMediaPlaybackController";
@ -20,13 +20,14 @@ import { SearchSuperContext } from "./appSearchSuper."; @@ -20,13 +20,14 @@ import { SearchSuperContext } from "./appSearchSuper.";
import { formatDateAccordingToToday } from "../helpers/date";
import { cancelEvent } from "../helpers/dom/cancelEvent";
import { attachClickEvent, detachClickEvent } from "../helpers/dom/clickEvent";
import LazyLoadQueue from "./lazyLoadQueue";
import { deferredPromise } from "../helpers/cancellablePromise";
import ListenerSetter, { Listener } from "../helpers/listenerSetter";
import noop from "../helpers/noop";
rootScope.addEventListener('messages_media_read', e => {
const {mids, peerId} = e;
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
mids.forEach(mid => {
(Array.from(document.querySelectorAll('audio-element[message-id="' + mid + '"][peer-id="' + peerId + '"]')) as AudioElement[]).forEach(elem => {
//console.log('updating avatar:', elem);
(Array.from(document.querySelectorAll('audio-element[message-id="' + mid + '"][peer-id="' + peerId + '"].is-unread')) as AudioElement[]).forEach(elem => {
elem.classList.remove('is-unread');
});
});
@ -182,7 +183,7 @@ function wrapVoiceMessage(audioEl: AudioElement) { @@ -182,7 +183,7 @@ function wrapVoiceMessage(audioEl: AudioElement) {
start();
}
audioEl.addAudioListener('playing', () => {
audioEl.addAudioListener('play', () => {
if(isUnread && !isOut && audioEl.classList.contains('is-unread')) {
audioEl.classList.remove('is-unread');
appMessagesManager.readMessages(audioEl.message.peerId, [audioEl.message.mid]);
@ -319,7 +320,7 @@ function wrapAudio(audioEl: AudioElement) { @@ -319,7 +320,7 @@ function wrapAudio(audioEl: AudioElement) {
launched = false;
});
const onPlaying = () => {
const onPlay = () => {
if(!launched) {
audioEl.classList.add('audio-show-progress');
launched = true;
@ -330,10 +331,10 @@ function wrapAudio(audioEl: AudioElement) { @@ -330,10 +331,10 @@ function wrapAudio(audioEl: AudioElement) {
}
};
audioEl.addAudioListener('playing', onPlaying);
audioEl.addAudioListener('play', onPlay);
if(!audioEl.audio.paused || audioEl.audio.currentTime > 0) {
onPlaying();
onPlay();
}
return () => {
@ -346,6 +347,15 @@ function wrapAudio(audioEl: AudioElement) { @@ -346,6 +347,15 @@ function wrapAudio(audioEl: AudioElement) {
return onLoad;
}
function constructDownloadPreloader(tryAgainOnFail = true) {
const preloader = new ProgressivePreloader({cancelable: true, tryAgainOnFail});
preloader.construct();
preloader.circle.setAttributeNS(null, 'r', '23');
preloader.totalLength = 143.58203125;
return preloader;
}
export default class AudioElement extends HTMLElement {
public audio: HTMLAudioElement;
public preloader: ProgressivePreloader;
@ -355,20 +365,18 @@ export default class AudioElement extends HTMLElement { @@ -355,20 +365,18 @@ export default class AudioElement extends HTMLElement {
public searchContext: SearchSuperContext;
public showSender = false;
public noAutoDownload: boolean;
public lazyLoadQueue: LazyLoadQueue;
public loadPromises: Promise<any>[];
private attachedHandlers: {[name: string]: any[]} = {};
private listenerSetter = new ListenerSetter();
private onTypeDisconnect: () => void;
public onLoad: (autoload?: boolean) => void;
constructor() {
super();
// элемент создан
}
readyPromise: import("/Users/kuzmenko/Documents/projects/tweb/src/helpers/cancellablePromise").CancellablePromise<void>;
public render() {
this.classList.add('audio');
const doc = this.message.media.document || this.message.media.webpage.document;
const doc: MyDocument = this.message.media.document || this.message.media.webpage.document;
const isRealVoice = doc.type === 'voice';
const isVoice = !this.voiceAsMusic && isRealVoice;
const isOutgoing = this.message.pFlags.is_outgoing;
@ -376,15 +384,21 @@ export default class AudioElement extends HTMLElement { @@ -376,15 +384,21 @@ export default class AudioElement extends HTMLElement {
const durationStr = String(doc.duration | 0).toHHMMSS();
this.innerHTML = `<div class="audio-toggle audio-ico">
<div class="part one" x="0" y="0" fill="#fff"></div>
<div class="part two" x="0" y="0" fill="#fff"></div>
</div>`;
this.innerHTML = `
<div class="audio-toggle audio-ico">
<div class="audio-play-icon">
<div class="part one" x="0" y="0" fill="#fff"></div>
<div class="part two" x="0" y="0" fill="#fff"></div>
</div>
</div>`;
const toggle = this.firstElementChild as HTMLElement;
const downloadDiv = document.createElement('div');
downloadDiv.classList.add('audio-download');
if(uploading) {
this.classList.add('is-outgoing');
this.append(downloadDiv);
}
@ -400,31 +414,37 @@ export default class AudioElement extends HTMLElement { @@ -400,31 +414,37 @@ export default class AudioElement extends HTMLElement {
this.onTypeDisconnect = onTypeLoad();
const toggle = this.querySelector('.audio-toggle') as HTMLDivElement;
const getTimeStr = () => String(audio.currentTime | 0).toHHMMSS() + (isVoice ? (' / ' + durationStr) : '');
const onPlaying = () => {
const onPlay = () => {
audioTimeDiv.innerText = getTimeStr();
toggle.classList.toggle('playing', !audio.paused);
};
if(!audio.paused || (audio.currentTime > 0 && audio.currentTime !== audio.duration)) {
onPlaying();
onPlay();
}
attachClickEvent(toggle, (e) => {
cancelEvent(e);
if(audio.paused) audio.play().catch(() => {});
else audio.pause();
});
const togglePlay = (e?: Event, paused = audio.paused) => {
e && cancelEvent(e);
if(paused) {
appMediaPlaybackController.setSearchContext(this.searchContext);
audio.play().catch(() => {});
} else {
audio.pause();
}
};
attachClickEvent(toggle, (e) => togglePlay(e), {listenerSetter: this.listenerSetter});
this.addAudioListener('ended', () => {
toggle.classList.remove('playing');
audioTimeDiv.innerText = durationStr;
});
this.addAudioListener('timeupdate', () => {
if(appMediaPlaybackController.isSafariBuffering(audio)) return;
if(appMediaPlaybackController.playingMedia !== audio || appMediaPlaybackController.isSafariBuffering(audio)) return;
audioTimeDiv.innerText = getTimeStr();
});
@ -432,7 +452,9 @@ export default class AudioElement extends HTMLElement { @@ -432,7 +452,9 @@ export default class AudioElement extends HTMLElement {
toggle.classList.remove('playing');
});
this.addAudioListener('playing', onPlaying);
this.addAudioListener('play', onPlay);
return togglePlay;
};
if(!isOutgoing) {
@ -442,9 +464,7 @@ export default class AudioElement extends HTMLElement { @@ -442,9 +464,7 @@ export default class AudioElement extends HTMLElement {
if(isRealVoice) {
if(!preloader) {
preloader = new ProgressivePreloader({
cancelable: true
});
preloader = constructDownloadPreloader();
}
const load = () => {
@ -468,7 +488,7 @@ export default class AudioElement extends HTMLElement { @@ -468,7 +488,7 @@ export default class AudioElement extends HTMLElement {
return {download};
};
preloader.construct();
// preloader.construct();
preloader.setManual();
preloader.attach(downloadDiv);
preloader.setDownloadFunction(load);
@ -487,35 +507,89 @@ export default class AudioElement extends HTMLElement { @@ -487,35 +507,89 @@ export default class AudioElement extends HTMLElement {
onLoad(false);
}
if(doc.thumbs) {
const imgs: HTMLImageElement[] = [];
const wrapped = wrapPhoto({
photo: doc,
message: null,
container: toggle,
boxWidth: 48,
boxHeight: 48,
loadPromises: this.loadPromises,
withoutPreloader: true,
lazyLoadQueue: this.lazyLoadQueue
});
toggle.style.width = toggle.style.height = '';
if(wrapped.images.thumb) imgs.push(wrapped.images.thumb);
if(wrapped.images.full) imgs.push(wrapped.images.full);
this.classList.add('audio-with-thumb');
imgs.forEach(img => img.classList.add('audio-thumb'));
}
//if(appMediaPlaybackController.mediaExists(mid)) { // чтобы показать прогресс, если аудио уже было скачано
//onLoad();
//} else {
const r = (e: Event) => {
if(!this.audio) {
onLoad(false);
const togglePlay = onLoad(false);
}
if(this.audio.src) {
return;
}
//onLoad();
//cancelEvent(e);
appMediaPlaybackController.resolveWaitingForLoadMedia(this.message.peerId, this.message.mid);
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
if(isSafari) {
this.audio.autoplay = true;
}
// togglePlay(undefined, true);
this.readyPromise = deferredPromise<void>();
if(this.audio.readyState >= 2) this.readyPromise.resolve();
else {
this.addAudioListener('canplay', () => this.readyPromise.resolve(), {once: true});
}
if(!preloader) {
if(doc.supportsStreaming) {
preloader = new ProgressivePreloader({
cancelable: false
});
this.classList.add('corner-download');
let pauseListener: Listener;
const onPlay = () => {
const preloader = constructDownloadPreloader(false);
const deferred = deferredPromise<void>();
deferred.notifyAll({done: 75, total: 100});
deferred.catch(() => {
this.audio.pause();
appMediaPlaybackController.willBePlayed(undefined);
});
deferred.cancel = () => {
deferred.cancel = noop;
const err = new Error();
(err as any).type = 'CANCELED';
deferred.reject(err);
};
preloader.attach(downloadDiv, false, deferred);
pauseListener = this.addAudioListener('pause', () => {
deferred.cancel();
}, {once: true}) as any;
};
preloader.attach(downloadDiv, false);
} else {
preloader = new ProgressivePreloader({
cancelable: true
});
/* if(!this.audio.paused) {
onPlay();
} */
const playListener: any = this.addAudioListener('play', onPlay);
this.readyPromise.then(() => {
this.listenerSetter.remove(playListener);
this.listenerSetter.remove(pauseListener);
});
} else {
const load = () => {
const download = getDownloadPromise();
preloader.attach(downloadDiv, false, download);
@ -527,17 +601,9 @@ export default class AudioElement extends HTMLElement { @@ -527,17 +601,9 @@ export default class AudioElement extends HTMLElement {
}
}
if(isSafari) {
this.audio.autoplay = true;
this.audio.play().catch(() => {});
}
this.append(downloadDiv);
new Promise<void>((resolve) => {
if(this.audio.readyState >= 2) resolve();
else this.addAudioListener('canplay', resolve);
}).then(() => {
this.readyPromise.then(() => {
downloadDiv.classList.add('downloaded');
setTimeout(() => {
downloadDiv.remove();
@ -547,14 +613,14 @@ export default class AudioElement extends HTMLElement { @@ -547,14 +613,14 @@ export default class AudioElement extends HTMLElement {
// release loaded audio
if(appMediaPlaybackController.willBePlayedMedia === this.audio) {
this.audio.play();
appMediaPlaybackController.willBePlayedMedia = null;
appMediaPlaybackController.willBePlayed(undefined);
}
//}, 10e3);
});
};
if(!this.audio?.src) {
attachClickEvent(this, r, {once: true, capture: true, passive: false});
attachClickEvent(toggle, r, {once: true, capture: true, passive: false});
}
//}
}
@ -564,15 +630,8 @@ export default class AudioElement extends HTMLElement { @@ -564,15 +630,8 @@ export default class AudioElement extends HTMLElement {
}
}
/* connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
} */
public addAudioListener(name: string, callback: any) {
if(!this.attachedHandlers[name]) this.attachedHandlers[name] = [];
this.attachedHandlers[name].push(callback);
this.audio.addEventListener(name, callback);
get addAudioListener() {
return this.listenerSetter.add(this.audio);
}
disconnectedCallback() {
@ -580,21 +639,18 @@ export default class AudioElement extends HTMLElement { @@ -580,21 +639,18 @@ export default class AudioElement extends HTMLElement {
return;
}
// браузер вызывает этот метод при удалении элемента из документа
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
if(this.onTypeDisconnect) {
this.onTypeDisconnect();
this.onTypeDisconnect = null;
}
for(let name in this.attachedHandlers) {
for(let callback of this.attachedHandlers[name]) {
this.audio.removeEventListener(name, callback);
}
delete this.attachedHandlers[name];
if(this.readyPromise) {
this.readyPromise.reject();
}
this.listenerSetter.removeAll();
this.listenerSetter = null;
this.preloader = null;
}
}

2
src/components/avatar.ts

@ -82,7 +82,7 @@ export async function openAvatarViewer(target: HTMLElement, peerId: number, midd @@ -82,7 +82,7 @@ export async function openAvatarViewer(target: HTMLElement, peerId: number, midd
new AppMediaViewer()
.setSearchContext({
peerId,
inputFilter,
inputFilter: {_: inputFilter},
})
.openMedia(message, getTarget(), undefined, undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);

19
src/components/chat/bubbles.ts

@ -944,7 +944,7 @@ export default class ChatBubbles { @@ -944,7 +944,7 @@ export default class ChatBubbles {
}
//this.log('chatInner click:', target);
const isVideoComponentElement = target.tagName === 'SPAN';
const isVideoComponentElement = target.tagName === 'SPAN' && !target.classList.contains('emoji');
/* if(isVideoComponentElement) {
const video = target.parentElement.querySelector('video') as HTMLElement;
if(video) {
@ -1054,7 +1054,7 @@ export default class ChatBubbles { @@ -1054,7 +1054,7 @@ export default class ChatBubbles {
.setSearchContext({
threadId: this.chat.threadId,
peerId: this.peerId,
inputFilter: documentDiv ? 'inputMessagesFilterDocument' : 'inputMessagesFilterPhotoVideo'
inputFilter: {_: documentDiv ? 'inputMessagesFilterDocument' : 'inputMessagesFilterPhotoVideo'}
})
.openMedia(message, targets[idx].element, 0, true, targets.slice(0, idx), targets.slice(idx + 1));
@ -2775,6 +2775,11 @@ export default class ChatBubbles { @@ -2775,6 +2775,11 @@ export default class ChatBubbles {
group: CHAT_ANIMATION_GROUP,
loadPromises,
noAutoDownload: this.chat.noAutoDownloadMedia,
searchContext: isRound ? {
peerId: this.peerId,
inputFilter: {_: 'inputMessagesFilterRoundVoice'},
threadId: this.chat.threadId
} : undefined
});
}
} else {
@ -2786,7 +2791,12 @@ export default class ChatBubbles { @@ -2786,7 +2791,12 @@ export default class ChatBubbles {
chat: this.chat,
loadPromises,
noAutoDownload: this.chat.noAutoDownloadMedia,
lazyLoadQueue: this.lazyLoadQueue
lazyLoadQueue: this.lazyLoadQueue,
searchContext: doc.type === 'voice' || doc.type === 'audio' ? {
peerId: this.peerId,
inputFilter: {_: doc.type === 'voice' ? 'inputMessagesFilterRoundVoice' : 'inputMessagesFilterMusic'},
threadId: this.chat.threadId
} : undefined
});
if(newNameContainer) {
@ -2879,7 +2889,8 @@ export default class ChatBubbles { @@ -2879,7 +2889,8 @@ export default class ChatBubbles {
let savedFrom = '';
const needName = ((peerId < 0 && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
// const needName = ((peerId < 0 && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
const needName = (message.fromId !== rootScope.myId && peerId < 0 && !this.appPeersManager.isBroadcast(peerId)) || message.viaBotId;
if(needName || message.fwd_from || message.reply_to_mid) { // chat
let title: HTMLElement | DocumentFragment;

12
src/components/chat/input.ts

@ -388,7 +388,7 @@ export default class ChatInput { @@ -388,7 +388,7 @@ export default class ChatInput {
onClick: () => {
new PopupCreatePoll(this.chat).show();
},
verify: (peerId, threadId) => this.appMessagesManager.canSendToPeer(peerId, threadId, 'send_polls')
verify: (peerId, threadId) => this.appMessagesManager.canSendToPeer(peerId, threadId, 'send_polls') && peerId < 0
}];
this.attachMenu = ButtonMenuToggle({noRipple: true, listenerSetter: this.listenerSetter}, 'top-left', this.attachMenuButtons);
@ -576,6 +576,9 @@ export default class ChatInput { @@ -576,6 +576,9 @@ export default class ChatInput {
this.recorder.ondataavailable = (typedArray: Uint8Array) => {
if(this.recordCanceled) return;
const {peerId, threadId} = this.chat;
const replyToMsgId = this.replyToMsgId;
const duration = (Date.now() - this.recordStartTime) / 1000 | 0;
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'});
@ -588,7 +591,6 @@ export default class ChatInput { @@ -588,7 +591,6 @@ export default class ChatInput {
opusDecodeController.setKeepAlive(false);
let peerId = this.chat.peerId;
// тут objectURL ставится уже с audio/wav
this.appMessagesManager.sendFile(peerId, dataBlob, {
isVoiceMessage: true,
@ -596,8 +598,8 @@ export default class ChatInput { @@ -596,8 +598,8 @@ export default class ChatInput {
duration,
waveform: result.waveform,
objectURL: result.url,
replyToMsgId: this.replyToMsgId,
threadId: this.chat.threadId,
replyToMsgId,
threadId,
clearDraft: true
});
@ -1464,7 +1466,7 @@ export default class ChatInput { @@ -1464,7 +1466,7 @@ export default class ChatInput {
this.recordTimeEl.innerText = formatted;
window.requestAnimationFrame(r);
fastRaf(r);
};
r();

7
src/components/chat/selection.ts

@ -516,7 +516,7 @@ export class SearchSelection extends AppSelection { @@ -516,7 +516,7 @@ export class SearchSelection extends AppSelection {
appMessagesManager,
listenElement: searchSuper.container,
listenerSetter: new ListenerSetter(),
verifyTarget: (e, target) => !!target,
verifyTarget: (e, target) => !!target && this.isSelecting,
getElementFromTarget: (target) => findUpClassName(target, 'search-super-item'),
targetLookupClassName: 'search-super-item',
lookupBetweenParentClassName: 'tabs-tab',
@ -560,6 +560,11 @@ export class SearchSelection extends AppSelection { @@ -560,6 +560,11 @@ export class SearchSelection extends AppSelection {
this.updateElementSelection(element, this.isMidSelected(peerId, mid));
};
public toggleByMid = (peerId: number, mid: number) => {
const element = this.searchSuper.mediaTab.contentTab.querySelector(`.search-super-item[data-peer-id="${peerId}"][data-mid="${mid}"]`) as HTMLElement;
this.toggleByElement(element);
};
protected onUpdateContainer = (cantForward: boolean, cantDelete: boolean, cantSend: boolean) => {
const length = this.length();
replaceContent(this.selectionCountEl, i18n('messages', [length]));

7
src/components/poll.ts

@ -430,9 +430,12 @@ export default class PollElement extends HTMLElement { @@ -430,9 +430,12 @@ export default class PollElement extends HTMLElement {
// const width = mediaSizes.active.poll.width;
// this.maxLength = width + tailLength + this.maxOffset + -13.7; // 13 - position left
if(poll.chosenIndexes.length || this.isClosed) {
const canVote = !(poll.chosenIndexes.length || this.isClosed);
if(!canVote || this.isPublic) {
this.performResults(results, poll.chosenIndexes, false);
} else if(!this.isClosed) {
}
if(canVote) {
this.setVotersCount(results);
attachClickEvent(this, this.clickHandler);
}

35
src/components/preloader.ts

@ -16,7 +16,7 @@ const TRANSITION_TIME = 200; @@ -16,7 +16,7 @@ const TRANSITION_TIME = 200;
export default class ProgressivePreloader {
public preloader: HTMLDivElement;
private circle: SVGCircleElement;
public circle: SVGCircleElement;
private cancelSvg: SVGSVGElement;
private downloadSvg: HTMLElement;
@ -33,7 +33,7 @@ export default class ProgressivePreloader { @@ -33,7 +33,7 @@ export default class ProgressivePreloader {
public loadFunc: (e?: Event) => {download: CancellablePromise<any>};
private totalLength: number;
public totalLength: number;
constructor(options?: Partial<{
isUpload: ProgressivePreloader['isUpload'],
@ -85,6 +85,12 @@ export default class ProgressivePreloader { @@ -85,6 +85,12 @@ export default class ProgressivePreloader {
</svg>
</div>`;
if(this.streamable) {
this.totalLength = 118.61124420166016;
} else {
this.totalLength = 149.82473754882812;
}
if(this.cancelable) {
this.preloader.innerHTML += `
<svg xmlns="http://www.w3.org/2000/svg" class="preloader-close" viewBox="0 0 24 24">
@ -147,7 +153,7 @@ export default class ProgressivePreloader { @@ -147,7 +153,7 @@ export default class ProgressivePreloader {
const startTime = Date.now();
const onEnd = (err: Error) => {
promise.notify = null;
promise.notify = promise.notifyAll = null;
if(tempId !== this.tempId) {
return;
@ -205,10 +211,6 @@ export default class ProgressivePreloader { @@ -205,10 +211,6 @@ export default class ProgressivePreloader {
}
public attach(elem: Element, reset = false, promise?: CancellablePromise<any>) {
if(promise/* && false */) {
this.attachPromise(promise);
}
//return;
this.detached = false;
@ -224,19 +226,24 @@ export default class ProgressivePreloader { @@ -224,19 +226,24 @@ export default class ProgressivePreloader {
this.preloader.classList.remove('manual');
}
const useRafs = isInDOM(this.preloader) ? 1 : 2;
if(this.preloader.parentElement !== elem) {
elem[this.attachMethod](this.preloader);
}
fastRaf(() => {
if(promise/* && false */) {
this.attachPromise(promise);
}
// fastRaf(() => {
//console.log('[PP]: attach after rAF', this.detached, performance.now());
if(this.detached) {
return;
}
// if(this.detached) {
// return;
// }
SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME);
});
SetTransition(this.preloader, 'is-visible', true, TRANSITION_TIME, undefined, useRafs);
// });
if(this.cancelable && reset) {
this.setProgress(0);
@ -272,7 +279,7 @@ export default class ProgressivePreloader { @@ -272,7 +279,7 @@ export default class ProgressivePreloader {
}
public setProgress(percents: number) {
if(!isInDOM(this.circle)) {
if(!this.totalLength && !isInDOM(this.circle)) {
return;
}

16
src/components/wrappers.ts

@ -70,7 +70,7 @@ mediaSizes.addEventListener('changeScreen', (from, to) => { @@ -70,7 +70,7 @@ mediaSizes.addEventListener('changeScreen', (from, to) => {
}
});
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises, noPlayButton, noAutoDownload, size}: {
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group, onlyPreview, withoutPreloader, loadPromises, noPlayButton, noAutoDownload, size, searchContext}: {
doc: MyDocument,
container?: HTMLElement,
message?: any,
@ -87,7 +87,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -87,7 +87,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
withoutPreloader?: boolean,
loadPromises?: Promise<any>[],
noAutoDownload?: boolean,
size?: PhotoSize
size?: PhotoSize,
searchContext?: SearchSuperContext
}) {
const isAlbumItem = !(boxWidth && boxHeight);
const canAutoplay = (doc.type !== 'video' || (doc.size <= MAX_VIDEO_AUTOPLAY_SIZE && !isAlbumItem))
@ -276,6 +277,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai @@ -276,6 +277,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
} */
if(globalVideo.paused) {
appMediaPlaybackController.setSearchContext(searchContext);
globalVideo.play();
} else {
globalVideo.pause();
@ -525,6 +527,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS @@ -525,6 +527,8 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS
audioElement.withTime = withTime;
audioElement.message = message;
audioElement.noAutoDownload = noAutoDownload;
audioElement.lazyLoadQueue = lazyLoadQueue;
audioElement.loadPromises = loadPromises;
if(voiceAsMusic) audioElement.voiceAsMusic = voiceAsMusic;
if(searchContext) audioElement.searchContext = searchContext;
@ -1588,7 +1592,7 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo @@ -1588,7 +1592,7 @@ export function wrapAlbum({groupId, attachmentDiv, middleware, uploading, lazyLo
});
}
export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, messageDiv, chat, loadPromises, noAutoDownload, lazyLoadQueue}: {
export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, messageDiv, chat, loadPromises, noAutoDownload, lazyLoadQueue, searchContext}: {
albumMustBeRenderedFull: boolean,
message: any,
messageDiv: HTMLElement,
@ -1597,7 +1601,8 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, @@ -1597,7 +1601,8 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
chat: Chat,
loadPromises?: Promise<any>[],
noAutoDownload?: boolean,
lazyLoadQueue?: LazyLoadQueue
lazyLoadQueue?: LazyLoadQueue,
searchContext?: SearchSuperContext
}) {
let nameContainer: HTMLElement;
const mids = albumMustBeRenderedFull ? chat.getMidsByMid(message.mid) : [message.mid];
@ -1611,7 +1616,8 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble, @@ -1611,7 +1616,8 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
message,
loadPromises,
noAutoDownload,
lazyLoadQueue
lazyLoadQueue,
searchContext
});
const container = document.createElement('div');

8
src/helpers/cancellablePromise.ts

@ -30,7 +30,6 @@ export function deferredPromise<T>() { @@ -30,7 +30,6 @@ export function deferredPromise<T>() {
deferredHelper.listeners.forEach((callback: any) => callback(...args));
},
lastNotify: undefined,
listeners: [],
addNotifyListener: (callback: (...args: any[]) => void) => {
if(deferredHelper.lastNotify) {
@ -43,14 +42,14 @@ export function deferredPromise<T>() { @@ -43,14 +42,14 @@ export function deferredPromise<T>() {
let deferred: CancellablePromise<T> = new Promise<T>((resolve, reject) => {
deferredHelper.resolve = (value: T) => {
if(deferred.isFulfilled) return;
if(deferred.isFulfilled || deferred.isRejected) return;
deferred.isFulfilled = true;
resolve(value);
};
deferredHelper.reject = (...args: any[]) => {
if(deferred.isRejected) return;
if(deferred.isRejected || deferred.isFulfilled) return;
deferred.isRejected = true;
reject(...args);
@ -64,9 +63,8 @@ export function deferredPromise<T>() { @@ -64,9 +63,8 @@ export function deferredPromise<T>() {
}; */
deferred.finally(() => {
deferred.notify = null;
deferred.notify = deferred.notifyAll = deferred.lastNotify = null;
deferred.listeners.length = 0;
deferred.lastNotify = null;
if(deferred.cancel) {
deferred.cancel = () => {};

26
src/lib/appManagers/appMessagesManager.ts

@ -320,6 +320,18 @@ export class AppMessagesManager { @@ -320,6 +320,18 @@ export class AppMessagesManager {
this.reloadConversation(peerId);
}
});
rootScope.addEventListener('poll_update', ({poll}) => {
const set = appPollsManager.pollToMessages[poll.id];
if(set) {
for(const key of set) {
const [peerId, mid] = key.split('_');
const message = this.getMessageByPeer(+peerId, +mid);
this.setDialogToStateIfMessageIsTop(message);
}
}
});
appStateManager.getState().then(state => {
if(state.maxSeenMsgId) {
@ -2359,7 +2371,9 @@ export class AppMessagesManager { @@ -2359,7 +2371,9 @@ export class AppMessagesManager {
break;
case 'messageMediaPoll':
message.media.poll = appPollsManager.savePoll(message.media.poll, message.media.results);
const result = appPollsManager.savePoll(message.media.poll, message.media.results, message);
message.media.poll = result.poll;
message.media.results = result.results;
break;
case 'messageMediaDocument':
if(message.media.ttl_seconds) {
@ -2616,7 +2630,7 @@ export class AppMessagesManager { @@ -2616,7 +2630,7 @@ export class AppMessagesManager {
break;
}
case 'messageMediaDocument': {
const document = media.document;
const document = media.document as MyDocument;
if(document.type === 'video') {
addPart('AttachVideo', undefined, message.message);
@ -2630,6 +2644,10 @@ export class AppMessagesManager { @@ -2630,6 +2644,10 @@ export class AppMessagesManager {
addPart(undefined, ((plain ? document.stickerEmojiRaw : document.stickerEmoji) || ''));
addPart('AttachSticker');
text = '';
} else if(document.type === 'audio') {
const attribute = document.attributes.find(attribute => attribute._ === 'documentAttributeAudio' && (attribute.title || attribute.performer)) as DocumentAttribute.documentAttributeAudio;
const f = '🎵' + ' ' + (attribute ? [attribute.title, attribute.performer].filter(Boolean).join(' - ') : document.file_name);
addPart(undefined, plain ? f : RichTextProcessor.wrapEmojiText(f), message.message);
} else {
addPart(undefined, plain ? document.file_name : RichTextProcessor.wrapEmojiText(document.file_name), message.message);
}
@ -5353,6 +5371,10 @@ export class AppMessagesManager { @@ -5353,6 +5371,10 @@ export class AppMessagesManager {
const messageKey = appWebPagesManager.getMessageKeyForPendingWebPage(message.peerId, message.mid, isScheduled);
appWebPagesManager.deleteWebPageFromPending(media.webpage, messageKey);
}
if((media as MessageMedia.messageMediaPoll).poll) {
appPollsManager.updatePollToMessage(message as Message.message, false);
}
}
}

60
src/lib/appManagers/appPollsManager.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug";
import { copy } from "../../helpers/object";
import { InputMedia, MessageEntity } from "../../layer";
import { InputMedia, Message, MessageEntity, MessageMedia } from "../../layer";
import { logger, LogTypes } from "../logger";
import apiManager from "../mtproto/mtprotoworker";
import { RichTextProcessor } from "../richtextprocessor";
@ -80,6 +80,7 @@ export type Poll = { @@ -80,6 +80,7 @@ export type Poll = {
export class AppPollsManager {
public polls: {[id: string]: Poll} = {};
public results: {[id: string]: PollResults} = {};
public pollToMessages: {[id: string]: Set<string>} = {};
private log = logger('POLLS', LogTypes.Error);
@ -93,27 +94,35 @@ export class AppPollsManager { @@ -93,27 +94,35 @@ export class AppPollsManager {
return;
}
poll = this.savePoll(poll, update.results as any);
rootScope.dispatchEvent('poll_update', {poll, results: update.results as any});
let results = update.results;
const ret = this.savePoll(poll, results as any);
poll = ret.poll;
results = ret.results;
rootScope.dispatchEvent('poll_update', {poll, results: results as any});
}
});
}
public savePoll(poll: Poll, results: PollResults) {
public savePoll(poll: Poll, results: PollResults, message?: Message.message) {
if(message) {
this.updatePollToMessage(message, true);
}
const id = poll.id;
if(this.polls[id]) {
poll = Object.assign(this.polls[id], poll);
this.saveResults(poll, results);
return poll;
}
results = this.saveResults(poll, results);
} else {
this.polls[id] = poll;
this.polls[id] = poll;
poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question);
poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll');
poll.chosenIndexes = [];
results = this.saveResults(poll, results);
}
poll.rQuestion = RichTextProcessor.wrapEmojiText(poll.question);
poll.rReply = RichTextProcessor.wrapEmojiText('📊') + ' ' + (poll.rQuestion || 'poll');
poll.chosenIndexes = [];
this.saveResults(poll, results);
return poll;
return {poll, results};
}
public saveResults(poll: Poll, results: PollResults) {
@ -133,6 +142,8 @@ export class AppPollsManager { @@ -133,6 +142,8 @@ export class AppPollsManager {
});
}
}
return results;
}
public getPoll(pollId: string): {poll: Poll, results: PollResults} {
@ -162,6 +173,29 @@ export class AppPollsManager { @@ -162,6 +173,29 @@ export class AppPollsManager {
};
}
public updatePollToMessage(message: Message.message, add: boolean) {
const {id} = (message.media as MessageMedia.messageMediaPoll).poll;
let set = this.pollToMessages[id];
if(!add && !set) {
return;
}
if(!set) {
set = this.pollToMessages[id] = new Set();
}
const key = message.peerId + '_' + message.mid;
if(add) set.add(key);
else set.delete(key);
if(!add && !set.size) {
delete this.polls[id];
delete this.results[id];
delete this.pollToMessages[id];
}
}
public sendVote(message: any, optionIds: number[]): Promise<void> {
const poll: Poll = message.media.poll;

4
src/lib/richtextprocessor.ts

@ -640,6 +640,10 @@ namespace RichTextProcessor { @@ -640,6 +640,10 @@ namespace RichTextProcessor {
onclick = 'showMaskedAlert';
}
if(options.wrappingDraft) {
onclick = undefined;
}
const href = (currentContext || typeof electronHelpers === 'undefined')
? encodeEntities(url)
: `javascript:electronHelpers.openExternal('${encodeEntities(url)}');`;

398
src/scss/partials/_audio.scss

@ -8,30 +8,65 @@ @@ -8,30 +8,65 @@
position: relative;
padding-left: 67px;
overflow: visible!important;
height: 54px;
height: 3.375rem;
user-select: none;
/* @include respond-to(handhelds) {
padding-left: 45px;
} */
&-toggle, &-download {
&-toggle,
&-download {
overflow: hidden;
border-radius: 50%;
background-color: var(--primary-color);
align-items: center;
}
&-toggle {
&.corner-download {
.audio-download {
width: 1.375rem;
height: 1.375rem;
margin: 2rem 2rem 0;
background: none;
display: flex !important;
top: 0;
}
.preloader-container {
border-radius: inherit;
background-color: var(--primary-color);
}
.preloader-path-new {
stroke-width: .25rem;
}
}
&-play-icon {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: rotate(-119deg);
overflow: hidden;
max-width: 100%;
max-height: 100%;
border-radius: inherit;
@include animation-level(2) {
transition: transform .25s ease-in-out;
}
}
&-toggle {
.part {
position: absolute;
background-color: white;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
@include animation-level(2) {
transition: clip-path .25s ease-in-out;
@ -185,160 +220,160 @@ @@ -185,160 +220,160 @@
);
}
}
}
&-toggle.playing {
transform: rotate(-90deg);
}
&-toggle:not(.playing) {
.part {
@include respond-to(not-handhelds) {
height: 136px;
width: 136px;
}
@include respond-to(handhelds) {
height: 92px;
width: 92px;
}
&.one {
clip-path: polygon(
43.77666% 55.85251%,
43.77874% 55.46331%,
43.7795% 55.09177%,
43.77934% 54.74844%,
43.77855% 54.44389%,
43.77741% 54.18863%,
43.77625% 53.99325%,
43.77533% 53.86828%,
43.77495% 53.82429%,
43.77518% 53.55329%,
43.7754% 53.2823%,
43.77563% 53.01131%,
43.77585% 52.74031%,
43.77608% 52.46932%,
43.7763% 52.19832%,
43.77653% 51.92733%,
43.77675% 51.65633%,
43.77653% 51.38533%,
43.7763% 51.11434%,
43.77608% 50.84334%,
43.77585% 50.57235%,
43.77563% 50.30136%,
43.7754% 50.03036%,
43.77518% 49.75936%,
43.77495% 49.48837%,
44.48391% 49.4885%,
45.19287% 49.48865%,
45.90183% 49.48878%,
46.61079% 49.48892%,
47.31975% 49.48906%,
48.0287% 49.4892%,
48.73766% 49.48934%,
49.44662% 49.48948%,
50.72252% 49.48934%,
51.99842% 49.4892%,
53.27432% 49.48906%,
54.55022% 49.48892%,
55.82611% 49.48878%,
57.10201% 49.48865%,
58.3779% 49.4885%,
59.6538% 49.48837%,
59.57598% 49.89151%,
59.31883% 50.28598%,
58.84686% 50.70884%,
58.12456% 51.19714%,
57.11643% 51.78793%,
55.78697% 52.51828%,
54.10066% 53.42522%,
52.02202% 54.54581%,
49.96525% 55.66916%,
48.3319% 56.57212%,
47.06745% 57.27347%,
46.11739% 57.79191%,
45.42719% 58.14619%,
44.94235% 58.35507%,
44.60834% 58.43725%,
44.37066% 58.41149%,
44.15383% 58.27711%,
43.99617% 58.0603%,
43.88847% 57.77578%,
43.82151% 57.43825%,
43.78608% 57.06245%,
43.77304% 56.66309%,
43.773% 56.25486%
);
}
&.playing .audio-play-icon {
transform: rotate(-90deg);
}
&.two {
clip-path: polygon(
43.77666% 43.83035%,
43.77874% 44.21955%,
43.7795% 44.59109%,
43.77934% 44.93442%,
43.77855% 45.23898%,
43.77741% 45.49423%,
43.77625% 45.68961%,
43.77533% 45.81458%,
43.77495% 45.85858%,
43.77518% 46.12957%,
43.7754% 46.40056%,
43.77563% 46.67156%,
43.77585% 46.94255%,
43.77608% 47.21355%,
43.7763% 47.48454%,
43.77653% 47.75554%,
43.77675% 48.02654%,
43.77653% 48.29753%,
43.7763% 48.56852%,
43.77608% 48.83952%,
43.77585% 49.11051%,
43.77563% 49.38151%,
43.7754% 49.65251%,
43.77518% 49.9235%,
43.77495% 50.1945%,
44.48391% 50.19436%,
45.19287% 50.19422%,
45.90183% 50.19408%,
46.61079% 50.19394%,
47.31975% 50.1938%,
48.0287% 50.19366%,
48.73766% 50.19353%,
49.44662% 50.19338%,
50.72252% 50.19353%,
51.99842% 50.19366%,
53.27432% 50.1938%,
54.55022% 50.19394%,
55.82611% 50.19408%,
57.10201% 50.19422%,
58.3779% 50.19436%,
59.6538% 50.1945%,
59.57598% 49.79136%,
59.31883% 49.39688%,
58.84686% 48.97402%,
58.12456% 48.48572%,
57.11643% 47.89493%,
55.78697% 47.16458%,
54.10066% 46.25764%,
52.02202% 45.13705%,
49.96525% 44.01371%,
48.3319% 43.11074%,
47.06745% 42.4094%,
46.11739% 41.89096%,
45.42719% 41.53667%,
44.94235% 41.3278%,
44.60834% 41.24561%,
44.37066% 41.27137%,
44.15383% 41.40575%,
43.99617% 41.62256%,
43.88847% 41.90709%,
43.82151% 42.24461%,
43.78608% 42.62041%,
43.77304% 43.01978%,
43.773% 43.428%
);
&:not(.playing) {
.part {
@include respond-to(not-handhelds) {
height: 136px;
width: 136px;
}
@include respond-to(handhelds) {
height: 92px;
width: 92px;
}
&.one {
clip-path: polygon(
43.77666% 55.85251%,
43.77874% 55.46331%,
43.7795% 55.09177%,
43.77934% 54.74844%,
43.77855% 54.44389%,
43.77741% 54.18863%,
43.77625% 53.99325%,
43.77533% 53.86828%,
43.77495% 53.82429%,
43.77518% 53.55329%,
43.7754% 53.2823%,
43.77563% 53.01131%,
43.77585% 52.74031%,
43.77608% 52.46932%,
43.7763% 52.19832%,
43.77653% 51.92733%,
43.77675% 51.65633%,
43.77653% 51.38533%,
43.7763% 51.11434%,
43.77608% 50.84334%,
43.77585% 50.57235%,
43.77563% 50.30136%,
43.7754% 50.03036%,
43.77518% 49.75936%,
43.77495% 49.48837%,
44.48391% 49.4885%,
45.19287% 49.48865%,
45.90183% 49.48878%,
46.61079% 49.48892%,
47.31975% 49.48906%,
48.0287% 49.4892%,
48.73766% 49.48934%,
49.44662% 49.48948%,
50.72252% 49.48934%,
51.99842% 49.4892%,
53.27432% 49.48906%,
54.55022% 49.48892%,
55.82611% 49.48878%,
57.10201% 49.48865%,
58.3779% 49.4885%,
59.6538% 49.48837%,
59.57598% 49.89151%,
59.31883% 50.28598%,
58.84686% 50.70884%,
58.12456% 51.19714%,
57.11643% 51.78793%,
55.78697% 52.51828%,
54.10066% 53.42522%,
52.02202% 54.54581%,
49.96525% 55.66916%,
48.3319% 56.57212%,
47.06745% 57.27347%,
46.11739% 57.79191%,
45.42719% 58.14619%,
44.94235% 58.35507%,
44.60834% 58.43725%,
44.37066% 58.41149%,
44.15383% 58.27711%,
43.99617% 58.0603%,
43.88847% 57.77578%,
43.82151% 57.43825%,
43.78608% 57.06245%,
43.77304% 56.66309%,
43.773% 56.25486%
);
}
&.two {
clip-path: polygon(
43.77666% 43.83035%,
43.77874% 44.21955%,
43.7795% 44.59109%,
43.77934% 44.93442%,
43.77855% 45.23898%,
43.77741% 45.49423%,
43.77625% 45.68961%,
43.77533% 45.81458%,
43.77495% 45.85858%,
43.77518% 46.12957%,
43.7754% 46.40056%,
43.77563% 46.67156%,
43.77585% 46.94255%,
43.77608% 47.21355%,
43.7763% 47.48454%,
43.77653% 47.75554%,
43.77675% 48.02654%,
43.77653% 48.29753%,
43.7763% 48.56852%,
43.77608% 48.83952%,
43.77585% 49.11051%,
43.77563% 49.38151%,
43.7754% 49.65251%,
43.77518% 49.9235%,
43.77495% 50.1945%,
44.48391% 50.19436%,
45.19287% 50.19422%,
45.90183% 50.19408%,
46.61079% 50.19394%,
47.31975% 50.1938%,
48.0287% 50.19366%,
48.73766% 50.19353%,
49.44662% 50.19338%,
50.72252% 50.19353%,
51.99842% 50.19366%,
53.27432% 50.1938%,
54.55022% 50.19394%,
55.82611% 50.19408%,
57.10201% 50.19422%,
58.3779% 50.19436%,
59.6538% 50.1945%,
59.57598% 49.79136%,
59.31883% 49.39688%,
58.84686% 48.97402%,
58.12456% 48.48572%,
57.11643% 47.89493%,
55.78697% 47.16458%,
54.10066% 46.25764%,
52.02202% 45.13705%,
49.96525% 44.01371%,
48.3319% 43.11074%,
47.06745% 42.4094%,
46.11739% 41.89096%,
45.42719% 41.53667%,
44.94235% 41.3278%,
44.60834% 41.24561%,
44.37066% 41.27137%,
44.15383% 41.40575%,
43.99617% 41.62256%,
43.88847% 41.90709%,
43.82151% 42.24461%,
43.78608% 42.62041%,
43.77304% 43.01978%,
43.773% 43.428%
);
}
}
}
}
@ -366,7 +401,8 @@ @@ -366,7 +401,8 @@
opacity: 1;
}
&.active, .audio.is-unread:not(.is-out) & {
&.active,
.audio.is-unread:not(.is-out) & {
opacity: 1;
}
}
@ -392,7 +428,8 @@ @@ -392,7 +428,8 @@
margin-bottom: -2px;
}
&-ico, &-download {
&-ico,
&-download {
width: 3rem;
height: 3rem;
}
@ -400,8 +437,6 @@ @@ -400,8 +437,6 @@
.part {
height: 112px !important;
width: 112px !important;
position: absolute;
background-color: white;
@include respond-to(handhelds) {
width: 100px !important;
@ -415,8 +450,9 @@ @@ -415,8 +450,9 @@
color: var(--primary-text-color);
}
&-time, &-subtitle {
font-size: 14px;
&-time,
&-subtitle {
font-size: .875rem;
color: var(--secondary-text-color);
overflow: hidden;
text-overflow: ellipsis;
@ -424,7 +460,7 @@ @@ -424,7 +460,7 @@
display: flex;
@include respond-to(handhelds) {
font-size: 12px;
font-size: .75rem;
}
}
@ -434,17 +470,19 @@ @@ -434,17 +470,19 @@
.audio-time {
flex: 0 0 auto;
margin-right: 4px;
margin-right: .25rem;
}
}
// * for audio
&-title, &-subtitle {
&-title,
&-subtitle {
margin-left: -1px;
}
// * for audio
&-title, &:not(.audio-show-progress) &-subtitle {
&-title,
&:not(.audio-show-progress) &-subtitle {
white-space: nowrap;
overflow: hidden;
max-width: 100%;
@ -490,6 +528,32 @@ @@ -490,6 +528,32 @@
--border-radius: 4px;
--thumb-size: .75rem;
flex: 1 1 auto;
margin-left: 5px;
margin: 0 6px 0 5px; // margin-right due to overflow
}
&-with-thumb {
.audio-play-icon {
z-index: 1;
background-color: transparent;
@include animation-level(2) {
transition: transform .25 ease-in-out, background-color .2s ease-in-out;
}
.part {
background-color: #fff !important;
}
&:not(:last-child) {
background-color: rgba(0, 0, 0, .3);
}
}
.media-photo {
border-radius: inherit;
object-fit: cover;
width: inherit;
height: inherit;
}
}
}

8
src/scss/partials/_chatBubble.scss

@ -1079,7 +1079,8 @@ $bubble-margin: .25rem; @@ -1079,7 +1079,8 @@ $bubble-margin: .25rem;
@include respond-to(handhelds) {
.document,
.audio {
&-ico, &-download {
&-ico,
&-download {
height: 2.25rem;
width: 2.25rem;
}
@ -1087,8 +1088,6 @@ $bubble-margin: .25rem; @@ -1087,8 +1088,6 @@ $bubble-margin: .25rem;
}
.audio {
padding-right: 2px;
$parent: ".audio";
#{$parent} {
&-title {
@ -2209,7 +2208,8 @@ $bubble-margin: .25rem; @@ -2209,7 +2208,8 @@ $bubble-margin: .25rem;
}
&-toggle,
&-download {
&-download,
&.corner-download .preloader-container {
background-color: var(--message-out-primary-color);
}

17
src/scss/partials/_document.scss

@ -56,12 +56,14 @@ @@ -56,12 +56,14 @@
}
}
&-ico, &-download {
&-ico,
&-download {
font-size: 1.125rem;
background-size: contain;
}
&-ico, &-name {
&-ico,
&-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -129,7 +131,8 @@ @@ -129,7 +131,8 @@
overflow: hidden;
}
&-name, &-size {
&-name,
&-size {
line-height: var(--line-height);
}
@ -152,7 +155,8 @@ @@ -152,7 +155,8 @@
}
}
.document, .audio {
.document,
.audio {
display: flex;
flex-direction: column;
justify-content: center;
@ -160,7 +164,8 @@ @@ -160,7 +164,8 @@
position: relative;
user-select: none;
&-ico, &-download {
&-ico,
&-download {
position: absolute;
left: 0;
width: 3.375rem;
@ -185,7 +190,7 @@ @@ -185,7 +190,7 @@
}
}
.preloader-container:not(.preloader-streamable) {
&:not(.corner-download) .preloader-container:not(.preloader-streamable) {
transform: scale(1) !important;
}
}

1
src/scss/partials/_poll.scss

@ -37,6 +37,7 @@ poll-element { @@ -37,6 +37,7 @@ poll-element {
margin-top: 2px;
margin-bottom: 5px;
display: flex;
align-items: center;
position: relative;
// @include respond-to(handhelds) {

4
src/scss/style.scss

@ -862,11 +862,11 @@ not screen and ( min-resolution: 192dpi), @@ -862,11 +862,11 @@ not screen and ( min-resolution: 192dpi),
not screen and ( min-resolution: 2dppx) {
html:not(.is-safari) {
span.emoji {
margin-right: 5px;
margin-right: 5px !important;
}
avatar-element span.emoji {
margin-right: 0;
margin-right: 0 !important;
}
}
}

Loading…
Cancel
Save