Browse Source

Prepare audio & gif tab & minor fixes

master
morethanwords 5 years ago
parent
commit
316693a88e
  1. 5
      src/components/appSearch.ts
  2. 94
      src/components/emoticonsDropdown.ts
  3. 2
      src/components/lazyLoadQueue.ts
  4. 3
      src/components/preloader.ts
  5. 131
      src/components/wrappers.ts
  6. 6
      src/lib/appManagers/appDialogsManager.ts
  7. 4
      src/lib/appManagers/appImManager.ts
  8. 8
      src/lib/appManagers/appMediaViewer.ts
  9. 35
      src/lib/appManagers/appSidebarRight.ts
  10. 0
      src/lib/ckin_js.js
  11. 6
      src/lib/config.js
  12. 375
      src/lib/mediaPlayer.ts
  13. 6
      src/lib/mtproto/networker.ts
  14. 27
      src/lib/tl_utils.ts
  15. 29
      src/scss/partials/_chat.scss
  16. 9
      src/scss/partials/_chatlist.scss
  17. 92
      src/scss/partials/_ckin.scss
  18. 25
      src/scss/partials/_emojiDropdown.scss
  19. 82
      src/scss/partials/_rightSIdebar.scss
  20. 8
      src/scss/partials/_sidebar.scss
  21. 7
      src/scss/style.scss

5
src/components/appSearch.ts

@ -175,9 +175,8 @@ export default class AppSearch { @@ -175,9 +175,8 @@ export default class AppSearch {
}
});
if(results.length) {
group.setActive();
}
if(results.length) group.setActive();
else group.clear();
};
setResults(contacts.my_results, this.searchGroups.contacts, true);

94
src/components/emoticonsDropdown.ts

@ -3,7 +3,7 @@ import { AppMessagesManager } from "../lib/appManagers/appMessagesManager"; @@ -3,7 +3,7 @@ import { AppMessagesManager } from "../lib/appManagers/appMessagesManager";
import { horizontalMenu } from "./misc";
import lottieLoader from "../lib/lottieLoader";
import Scrollable from "./scrollable";
import { findUpTag, whichChild } from "../lib/utils";
import { findUpTag, whichChild, calcImageInBox } from "../lib/utils";
import { RichTextProcessor } from "../lib/richtextprocessor";
import appStickersManager, { MTStickerSet } from "../lib/appManagers/appStickersManager";
import apiManager from '../lib/mtproto/apiManager';
@ -11,6 +11,8 @@ import CryptoWorker from '../lib/crypto/cryptoworker'; @@ -11,6 +11,8 @@ import CryptoWorker from '../lib/crypto/cryptoworker';
import LazyLoadQueue from "./lazyLoadQueue";
import { MTDocument, wrapSticker } from "./wrappers";
import appWebpManager from "../lib/appManagers/appWebpManager";
import appDocsManager from "../lib/appManagers/appDocsManager";
import ProgressivePreloader from "./preloader";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -29,10 +31,12 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -29,10 +31,12 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
if(id == 1 && stickersInit) {
stickersInit();
} else if(id == 2 && gifsInit) {
gifsInit();
}
}, () => {
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check(); // for stickers
lazyLoadQueue.check(); // for stickers or gifs
});
(tabs.firstElementChild.children[0] as HTMLLIElement).click(); // set emoji tab
@ -395,6 +399,92 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -395,6 +399,92 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
});
};
let gifsInit = () => {
let contentDiv = document.getElementById('content-gifs') as HTMLDivElement;
let masonry = contentDiv.firstElementChild as HTMLDivElement;
let scroll = new Scrollable(contentDiv, 'y', 500, 'GIFS', null);
scroll.container.addEventListener('scroll', (e) => {
lazyLoadQueue.check();
});
let width = 400;
let maxSingleWidth = width - 100;
let height = 100;
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => {
let res = _res as {
_: 'messages.savedGifs',
gifs: MTDocument[],
hash: number
};
console.log('getSavedGifs res:', res);
let line: MTDocument[] = [];
let wastedWidth = 0;
res.gifs.forEach((gif, idx) => {
res.gifs[idx] = appDocsManager.saveDoc(gif);
});
for(let i = 0, length = res.gifs.length; i < length;) {
let gif = res.gifs[i];
let gifWidth = gif.w;
let gifHeight = gif.h;
if(gifHeight < height) {
gifWidth = height / gifHeight * gifWidth;
gifHeight = height;
}
let willUseWidth = Math.min(maxSingleWidth, width - wastedWidth, gifWidth);
let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
/* wastedWidth += w;
if(wastedWidth == width || h < height) {
wastedWidth = 0;
console.log('completed line', i, line);
line = [];
continue;
}
line.push(gif); */
++i;
console.log('gif:', gif, w, h);
let div = document.createElement('div');
div.style.width = w + 'px';
//div.style.height = h + 'px';
div.dataset.documentID = gif.id;
masonry.append(div);
let preloader = new ProgressivePreloader(div);
lazyLoadQueue.push({
div,
load: () => {
let promise = appDocsManager.downloadDoc(gif);
preloader.attach(div, true, promise);
promise.then(blob => {
preloader.detach();
div.innerHTML = `<video autoplay="true" muted="true" loop="true" src="${gif.url}" type="video/mp4"></video>`;
});
return promise;
}
});
}
});
gifsInit = undefined;
};
return {dropdown, lazyLoadQueue};
};

2
src/components/lazyLoadQueue.ts

@ -2,7 +2,7 @@ import { isElementInViewport } from "../lib/utils"; @@ -2,7 +2,7 @@ import { isElementInViewport } from "../lib/utils";
type LazyLoadElement = {
div: HTMLDivElement,
load: () => Promise<void>,
load: () => Promise<any>,
wasSeen?: boolean
};

3
src/components/preloader.ts

@ -94,7 +94,10 @@ export default class ProgressivePreloader { @@ -94,7 +94,10 @@ export default class ProgressivePreloader {
window.requestAnimationFrame(() => {
if(!this.detached) return;
this.detached = true;
if(this.preloader.parentElement) {
this.preloader.parentElement.removeChild(this.preloader);
}
});
}
}

131
src/components/wrappers.ts

@ -8,7 +8,7 @@ import ProgressivePreloader from './preloader'; @@ -8,7 +8,7 @@ import ProgressivePreloader from './preloader';
import LazyLoadQueue from './lazyLoadQueue';
import apiFileManager from '../lib/mtproto/apiFileManager';
import appWebpManager from '../lib/appManagers/appWebpManager';
import {wrapPlayer} from '../lib/ckin';
import VideoPlayer, { MediaProgressLine } from '../lib/mediaPlayer';
import { RichTextProcessor } from '../lib/richtextprocessor';
import { CancellablePromise } from '../lib/polyfill';
import { renderImageFromUrl } from './misc';
@ -138,11 +138,7 @@ export function wrapVideo({doc, container, message, justLoader, preloader, round @@ -138,11 +138,7 @@ export function wrapVideo({doc, container, message, justLoader, preloader, round
if(!justLoader || round) {
video.dataset.ckin = round ? 'circle' : 'default';
video.dataset.overlay = '1';
let wrapper = wrapPlayer(video);
if(!round) {
(wrapper.querySelector('.toggle') as HTMLButtonElement).click();
}
let player = new VideoPlayer(video, !round);
} else if(doc.type == 'gif') {
video.autoplay = true;
video.loop = true;
@ -176,7 +172,9 @@ export function wrapVideo({doc, container, message, justLoader, preloader, round @@ -176,7 +172,9 @@ export function wrapVideo({doc, container, message, justLoader, preloader, round
export function wrapDocument(doc: MTDocument, withTime = false, uploading = false): HTMLDivElement {
if(doc.type == 'voice') {
return wrapAudio(doc, withTime);
return wrapVoiceMessage(doc, withTime);
} else if(doc.type == 'audio') {
return wrapAudio(doc);
}
let docDiv = document.createElement('div');
@ -250,15 +248,124 @@ export function wrapDocument(doc: MTDocument, withTime = false, uploading = fals @@ -250,15 +248,124 @@ export function wrapDocument(doc: MTDocument, withTime = false, uploading = fals
return docDiv;
}
let lastAudioToggle: HTMLDivElement = null;
export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
let div = document.createElement('div');
div.classList.add('audio');
console.log('wrapAudio doc:', doc);
/* let durationStr = String(doc.duration | 0).toHHMMSS(true);
let title = doc.audioTitle || doc.file_name;
let subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : ''; */
let durationStr = '3:24';
let title = 'Million Telegrams';
let subtitle = 'Best Artist';
div.innerHTML = `
<div class="audio-title">${title}</div>
<div class="audio-subtitle">${subtitle}</div>
<div class="audio-toggle audio-ico tgico-largeplay"></div>
<div class="audio-download"><div class="tgico-download"></div></div>
<div class="audio-time">${durationStr}</div>
`;
//////console.log('wrapping audio', doc, doc.attributes[0].waveform);
let timeDiv = div.lastElementChild as HTMLDivElement;
let downloadDiv = div.querySelector('.audio-download') as HTMLDivElement;
let preloader: ProgressivePreloader;
let promise: CancellablePromise<Blob>;
let progress: MediaProgressLine;
let onClick = () => {
if(!promise) {
if(downloadDiv.classList.contains('downloading')) {
return; // means not ready yet
}
if(!preloader) {
preloader = new ProgressivePreloader(null, true);
}
let promise = appDocsManager.downloadDoc(doc.id);
preloader.attach(downloadDiv, true, promise);
promise.then(blob => {
downloadDiv.classList.remove('downloading');
downloadDiv.remove();
let audio = document.createElement('audio');
let source = document.createElement('source');
source.src = URL.createObjectURL(blob);
source.type = doc.mime_type;
audio.volume = 1;
progress = new MediaProgressLine(audio);
div.removeEventListener('click', onClick);
let toggle = div.querySelector('.audio-toggle') as HTMLDivElement;
let subtitle = div.querySelector('.audio-subtitle') as HTMLDivElement;
toggle.addEventListener('click', () => {
subtitle.innerHTML = '';
subtitle.append(progress.container);
if(audio.paused) {
if(lastAudioToggle && lastAudioToggle.classList.contains('tgico-largepause')) {
lastAudioToggle.click();
}
audio.currentTime = 0;
audio.play();
lastAudioToggle = toggle;
toggle.classList.remove('tgico-largeplay');
toggle.classList.add('tgico-largepause');
} else {
audio.pause();
toggle.classList.add('tgico-largeplay');
toggle.classList.remove('tgico-largepause');
}
});
audio.addEventListener('ended', () => {
toggle.classList.add('tgico-largeplay');
toggle.classList.remove('tgico-largepause');
timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true);
});
audio.style.display = 'none';
audio.append(source);
div.append(audio);
});
downloadDiv.classList.add('downloading');
} else {
downloadDiv.classList.remove('downloading');
promise = null;
}
};
div.addEventListener('click', onClick);
div.click();
return div;
}
let lastAudioToggle: HTMLDivElement = null;
export function wrapVoiceMessage(doc: MTDocument, withTime = false): HTMLDivElement {
let div = document.createElement('div');
div.classList.add('audio', 'is-voice');
let duration = doc.duration;
// @ts-ignore
let durationStr = String(duration | 0).toHHMMSS(true);
div.innerHTML = `
@ -360,7 +467,6 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -360,7 +467,6 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
return;
}
// @ts-ignore
timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true);
lastIndex = Math.round(audio.currentTime / audio.duration * 47);
@ -387,7 +493,6 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -387,7 +493,6 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
clearInterval(interval);
(Array.from(svg.children) as HTMLElement[]).forEach(node => node.classList.remove('active'));
// @ts-ignore
timeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true);
});
@ -430,8 +535,8 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement { @@ -430,8 +535,8 @@ export function wrapAudio(doc: MTDocument, withTime = false): HTMLDivElement {
audio.currentTime = scrubTime;
}
audio.append(source);
audio.style.display = 'none';
audio.append(source);
div.append(audio);
});

6
src/lib/appManagers/appDialogsManager.ts

@ -482,7 +482,7 @@ export class AppDialogsManager { @@ -482,7 +482,7 @@ export class AppDialogsManager {
lastMessageText = '<i>GIF' + (lastMessage.message ? ', ' : '') + '</i>';
found = true;
} else if(document.type == 'round') {
lastMessageText = '<i>Videomessage' + (lastMessage.message ? ', ' : '') + '</i>';
lastMessageText = '<i>Video message' + (lastMessage.message ? ', ' : '') + '</i>';
found = true;
}
@ -524,7 +524,7 @@ export class AppDialogsManager { @@ -524,7 +524,7 @@ export class AppDialogsManager {
let messageText = lastMessage.message;
let messageWrapped = '';
if(messageText) {
let entities = RichTextProcessor.parseEntities(messageText, {noLinebreakers: true});
let entities = RichTextProcessor.parseEntities(messageText.replace(/\n/g, ' '), {noLinebreakers: true});
if(highlightWord) {
let regExp = new RegExp(escapeRegExp(highlightWord), 'gi');
let match: any;
@ -541,7 +541,7 @@ export class AppDialogsManager { @@ -541,7 +541,7 @@ export class AppDialogsManager {
}
}
messageWrapped = RichTextProcessor.wrapRichText(messageText.replace(/\n/g, ' '), {
messageWrapped = RichTextProcessor.wrapRichText(messageText, {
noLinebreakers: true,
entities: entities,
noTextFormat: true

4
src/lib/appManagers/appImManager.ts

@ -355,7 +355,7 @@ export class AppImManager { @@ -355,7 +355,7 @@ export class AppImManager {
let targets = ids.map(id => ({
//element: (this.bubbles[id].querySelector('img, video') || this.bubbles[id].querySelector('image')) as HTMLElement,
element: this.bubbles[id].querySelector('img, video, .bubble__media-container') as HTMLElement,
element: this.bubbles[id].querySelector('.attachment img, .preview img, video, .bubble__media-container') as HTMLElement,
mid: id
}));
@ -1490,7 +1490,7 @@ export class AppImManager { @@ -1490,7 +1490,7 @@ export class AppImManager {
}, this.lazyLoadQueue, 'chat', false, !!message.pending || !multipleRender);
break;
} else if((doc.type == 'video' || doc.type == 'gif') && doc.size <= 20e6) {
} else if((doc.type == 'video' || doc.type == 'gif' || doc.type == 'round') && doc.size <= 20e6) {
this.log('never get free 2', doc);
if(doc.type == 'round') {

8
src/lib/appManagers/appMediaViewer.ts

@ -7,7 +7,7 @@ import { logger } from "../polyfill"; @@ -7,7 +7,7 @@ import { logger } from "../polyfill";
import ProgressivePreloader from "../../components/preloader";
import { findUpClassName, $rootScope, generatePathData } from "../utils";
import appDocsManager from "./appDocsManager";
import { wrapPlayer } from "../ckin";
import VideoPlayer from "../mediaPlayer";
import { renderImageFromUrl } from "../../components/misc";
import appProfileManager from "./appProfileManager";
@ -664,12 +664,10 @@ export class AppMediaViewer { @@ -664,12 +664,10 @@ export class AppMediaViewer {
video.append(source);
}
let wrapper = wrapPlayer(video);
(wrapper.querySelector('.toggle') as HTMLButtonElement).click();
let player = new VideoPlayer(video, true);
});
} else {
let wrapper = wrapPlayer(video);
(wrapper.querySelector('.toggle') as HTMLButtonElement).click();
let player = new VideoPlayer(video, true);
}

35
src/lib/appManagers/appSidebarRight.ts

@ -11,7 +11,7 @@ import { logger } from "../polyfill"; @@ -11,7 +11,7 @@ import { logger } from "../polyfill";
import appImManager from "./appImManager";
import appMediaViewer from "./appMediaViewer";
import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument, wrapAudio } from "../../components/wrappers";
import { wrapDocument } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
const testScroll = false;
@ -51,7 +51,7 @@ class AppSidebarRight { @@ -51,7 +51,7 @@ class AppSidebarRight {
'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument',
'inputMessagesFilterUrl',
'inputMessagesFilterVoice'
'inputMessagesFilterMusic'
];
public sharedMediaType: string = '';
private sharedMediaSelected: HTMLDivElement = null;
@ -227,11 +227,7 @@ class AppSidebarRight { @@ -227,11 +227,7 @@ class AppSidebarRight {
let elemsToAppend: HTMLElement[] = [];
/*'inputMessagesFilterContacts',
'inputMessagesFilterPhotoVideo',
'inputMessagesFilterDocument',
'inputMessagesFilterUrl',
'inputMessagesFilterVoice'*/
// https://core.telegram.org/type/MessagesFilter
switch(type) {
case 'inputMessagesFilterPhotoVideo': {
sharedMediaDiv = this.sharedMedia.contentMedia;
@ -316,7 +312,7 @@ class AppSidebarRight { @@ -316,7 +312,7 @@ class AppSidebarRight {
sharedMediaDiv = this.sharedMedia.contentDocuments;
for(let message of messages) {
if(!message.media.document || message.media.document.type == 'voice') {
if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') {
continue;
}
@ -398,22 +394,19 @@ class AppSidebarRight { @@ -398,22 +394,19 @@ class AppSidebarRight {
break;
}
/* case 'inputMessagesFilterVoice': {
//this.log('wrapping audio', message.media);
if(!message.media || !message.media.document || message.media.document.type != 'voice') {
break;
}
let doc = message.media.document;
this.log('wrapping audio', doc);
let audioDiv = wrapAudio(doc);
case 'inputMessagesFilterMusic': {
sharedMediaDiv = this.sharedMedia.contentAudio;
this.sharedMedia.contentAudio.append(audioDiv);
for(let message of messages) {
if(!message.media.document || message.media.document.type != 'audio') {
continue;
}
let div = wrapDocument(message.media.document, true);
elemsToAppend.push(div);
}
break;
} */
}
default:
//console.warn('death is my friend', message);

0
src/lib/ckin.js → src/lib/ckin_js.js

6
src/lib/config.js

File diff suppressed because one or more lines are too long

375
src/lib/mediaPlayer.ts

@ -0,0 +1,375 @@ @@ -0,0 +1,375 @@
export class MediaProgressLine {
public container: HTMLDivElement;
private filled: HTMLDivElement;
private seek: HTMLInputElement;
private duration = 0;
constructor(private media: HTMLAudioElement | HTMLVideoElement) {
this.container = document.createElement('div');
this.container.classList.add('media-progress');
this.filled = document.createElement('div');
this.filled.classList.add('media-progress__filled');
let seek = this.seek = document.createElement('input');
seek.classList.add('media-progress__seek');
seek.value = '0';
seek.setAttribute('min', '0');
seek.setAttribute('max', '0');
seek.type = 'range';
seek.step = '0.1';
this.setSeekMax();
this.setListeners();
this.container.append(this.filled, seek);
}
private setSeekMax() {
let seek = this.seek;
this.duration = this.media.duration;
if(this.duration > 0) {
seek.setAttribute('max', '' + this.duration * 1000);
} else {
this.media.addEventListener('loadeddata', () => {
this.duration = this.media.duration;
seek.setAttribute('max', '' + this.duration * 1000);
});
}
}
private setProgress() {
let currentTime = this.media.currentTime;
let scaleX = (currentTime / this.duration);
this.filled.style.transform = 'scaleX(' + scaleX + ')';
this.seek.value = '' + currentTime * 1000;
}
private setListeners() {
let mousedown = false;
let stopAndScrubTimeout = 0;
this.media.addEventListener('ended', () => {
this.setProgress();
});
this.media.addEventListener('play', () => {
let r = () => {
this.setProgress();
!this.media.paused && window.requestAnimationFrame(r);
};
window.requestAnimationFrame(r);
});
this.container.addEventListener('mousemove', (e) => {
mousedown && this.scrub(e);
});
this.container.addEventListener('mousedown', (e) => {
this.scrub(e);
//Таймер для того, чтобы стопать видео, если зажал мышку и не отпустил клик
stopAndScrubTimeout = setTimeout(() => {
!this.media.paused && this.media.pause();
stopAndScrubTimeout = 0;
}, 150);
mousedown = true;
});
this.container.addEventListener('mouseup', () => {
if(stopAndScrubTimeout) {
clearTimeout(stopAndScrubTimeout);
}
this.media.paused && this.media.play();
mousedown = false;
});
}
private scrub(e: MouseEvent) {
let scrubTime = e.offsetX / this.container.offsetWidth * this.duration;
this.media.currentTime = scrubTime;
let scaleX = scrubTime / this.duration;
if(scaleX > 1) scaleX = 1;
if(scaleX < 0) scaleX = 0;
this.filled.style.transform = 'scaleX(' + scaleX + ')';
}
}
export default class VideoPlayer {
public wrapper: HTMLDivElement;
private skin: string;
private progress: MediaProgressLine;
constructor(public video: HTMLVideoElement, play = false) {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('ckin__player');
video.parentNode.insertBefore(this.wrapper, video);
this.wrapper.appendChild(video);
this.skin = video.dataset.ckin ?? 'default';
this.stylePlayer();
if(this.skin == 'default') {
let controls = this.wrapper.querySelector('.default__controls.ckin__controls') as HTMLDivElement;
this.progress = new MediaProgressLine(video);
controls.prepend(this.progress.container);
}
if(play) {
(this.wrapper.querySelector('.toggle') as HTMLButtonElement).click();
}
}
private stylePlayer() {
let player = this.wrapper;
let video = this.video;
let skin = this.skin;
player.classList.add(skin);
let html = this.buildControls();
player.insertAdjacentHTML('beforeend', html);
let updateInterval = 0;
let elapsed = 0;
let prevTime = 0;
if(skin === 'default') {
var toggle = player.querySelectorAll('.toggle') as NodeListOf<HTMLElement>;
var fullScreenButton = player.querySelector('.fullscreen') as HTMLElement;
var timeElapsed = player.querySelector('#time-elapsed');
var timeDuration = player.querySelector('#time-duration') as HTMLElement;
timeDuration.innerHTML = String(video.duration | 0).toHHMMSS();
Array.from(toggle).forEach((button) => {
return button.addEventListener('click', () => {
this.togglePlay();
});
});
video.addEventListener('click', () => {
this.togglePlay();
});
video.addEventListener('play', () => {
this.updateButton(toggle);
});
video.addEventListener('pause', () => {
this.updateButton(toggle);
clearInterval(updateInterval);
});
video.addEventListener('dblclick', () => {
return this.toggleFullScreen(fullScreenButton);
})
fullScreenButton.addEventListener('click', (e) => {
return this.toggleFullScreen(fullScreenButton);
});
let b = () => this.onFullScreen();
'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange'.split(' ').forEach(eventName => {
player.addEventListener(eventName, b, false);
});
}
if(skin === 'circle') {
let wrapper = document.createElement('div');
wrapper.classList.add('circle-time-left');
video.parentNode.insertBefore(wrapper, video);
wrapper.innerHTML = '<div class="circle-time"></div><div class="iconVolume tgico-nosound"></div>';
var circle = player.querySelector('.progress-ring__circle') as SVGCircleElement;
var radius = circle.r.baseVal.value;
var circumference = 2 * Math.PI * radius;
var timeDuration = player.querySelector('.circle-time') as HTMLElement;
var iconVolume = player.querySelector('.iconVolume') as HTMLDivElement;
circle.style.strokeDasharray = circumference + ' ' + circumference;
circle.style.strokeDashoffset = '' + circumference;
circle.addEventListener('click', () => {
this.togglePlay();
});
video.addEventListener('play', () => {
iconVolume.style.display = 'none';
updateInterval = setInterval(() => {
//elapsed += 0.02; // Increase with timer interval
if(video.currentTime != prevTime) {
elapsed = video.currentTime; // Update if getCurrentTime was changed
prevTime = video.currentTime;
}
let offset = circumference - elapsed / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset;
if(video.paused) clearInterval(updateInterval);
}, 20);
});
video.addEventListener('pause', () => {
iconVolume.style.display = '';
});
}
if(video.duration > 0) {
timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS();
} else {
video.addEventListener('loadeddata', () => {
timeDuration.innerHTML = String(Math.round(video.duration)).toHHMMSS();
});
}
video.addEventListener('timeupdate', () => {
if(skin == 'default') {
timeElapsed.innerHTML = String(video.currentTime | 0).toHHMMSS();
}
updateInterval = this.handleProgress(timeDuration, circumference, circle, updateInterval);
});
}
public togglePlay(stop?: boolean) {
if(stop) {
this.video.pause();
this.wrapper.classList.remove('is-playing');
return;
} else if(stop === false) {
this.video.play();
this.wrapper.classList.add('is-playing');
return;
}
this.video[this.video.paused ? 'play' : 'pause']();
this.video.paused ? this.wrapper.classList.remove('is-playing') : this.wrapper.classList.add('is-playing');
}
private handleProgress(timeDuration: HTMLElement, circumference: number, circle: SVGCircleElement, updateInterval: number) {
let video = this.video;
let skin = this.skin;
clearInterval(updateInterval);
let elapsed = 0;
let prevTime = 0;
if(skin === 'circle') {
updateInterval = setInterval(() => {
if(video.currentTime != prevTime) {
elapsed = video.currentTime; // Update if getCurrentTime was changed
prevTime = video.currentTime;
}
let offset = circumference - elapsed / video.duration * circumference;
circle.style.strokeDashoffset = '' + offset;
if(video.paused) clearInterval(updateInterval);
}, 20);
let timeLeft = String((video.duration - video.currentTime) | 0).toHHMMSS();
if(timeLeft != '0') timeDuration.innerHTML = timeLeft;
return updateInterval;
}
}
private buildControls() {
let skin = this.skin;
let html = [];
if(skin === 'default') {
html.push('<button class="' + skin + '__button--big toggle tgico-largeplay" title="Toggle Play"></button>');
html.push('<div class="' + skin + '__gradient-bottom ckin__controls"></div>');
html.push('<div class="' + skin + '__controls ckin__controls">');
html.push('<div class="bottom-controls">',
'<div class="left-controls"><button class="' + skin + '__button toggle tgico-play" title="Toggle Video"></button>',
'<div class="time">',
'<time id="time-elapsed">0:00</time>',
'<span> / </span>',
'<time id="time-duration">0:00</time>',
'</div>',
'</div>',
'<div class="right-controls"><button class="' + skin + '__button fullscreen tgico-fullscreen" title="Full Screen"></button></div></div>');
html.push('</div>');
} else if(skin === 'circle') {
html.push('<svg class="progress-ring" width="200px" height="200px">',
'<circle class="progress-ring__circle" stroke="white" stroke-opacity="0.3" stroke-width="3.5" cx="100" cy="100" r="93" fill="transparent" transform="rotate(-90, 100, 100)"/>',
'</svg>');
}
return html.join('');
}
public updateButton(toggle: NodeListOf<HTMLElement>) {
let icon = this.video.paused ? 'tgico-play' : 'tgico-pause';
Array.from(toggle).forEach((button) => {
button.classList.remove('tgico-play', 'tgico-pause');
button.classList.add(icon);
});
}
public toggleFullScreen(fullScreenButton: HTMLElement) {
// alternative standard method
let player = this.wrapper;
// @ts-ignore
if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
player.classList.add('ckin__fullscreen');
if(player.requestFullscreen) {
player.requestFullscreen();
// @ts-ignore
} else if(player.mozRequestFullScreen) {
// @ts-ignore
player.mozRequestFullScreen(); // Firefox
// @ts-ignore
} else if(player.webkitRequestFullscreen) {
// @ts-ignore
player.webkitRequestFullscreen(); // Chrome and Safari
// @ts-ignore
} else if(player.msRequestFullscreen) {
// @ts-ignore
player.msRequestFullscreen();
}
fullScreenButton.classList.remove('tgico-fullscreen');
fullScreenButton.classList.add('tgico-smallscreen');
fullScreenButton.setAttribute('title', 'Exit Full Screen');
} else {
player.classList.remove('ckin__fullscreen');
// @ts-ignore
if(document.cancelFullScreen) {
// @ts-ignore
document.cancelFullScreen();
// @ts-ignore
} else if(document.mozCancelFullScreen) {
// @ts-ignore
document.mozCancelFullScreen();
// @ts-ignore
} else if(document.webkitCancelFullScreen) {
// @ts-ignore
document.webkitCancelFullScreen();
// @ts-ignore
} else if(document.msExitFullscreen) {
// @ts-ignore
document.msExitFullscreen();
}
fullScreenButton.classList.remove('tgico-smallscreen');
fullScreenButton.classList.add('tgico-fullscreen');
fullScreenButton.setAttribute('title', 'Full Screen');
}
}
public onFullScreen() {
// @ts-ignore
let isFullscreenNow = document.webkitFullscreenElement !== null;
if(!isFullscreenNow) {
this.wrapper.classList.remove('ckin__fullscreen');
} else {
}
}
}

6
src/lib/mtproto/networker.ts

@ -2,7 +2,6 @@ import {tsNow, isObject} from '../utils'; @@ -2,7 +2,6 @@ import {tsNow, isObject} from '../utils';
import {convertToUint8Array,
bufferConcat, nextRandomInt, bytesToHex, longToBytes,
bytesCmp, uintToInt, bigStringInt} from '../bin_utils';
import {MTProto} from './mtproto';
import {TLDeserialization, TLSerialization} from '../tl_utils';
import CryptoWorker from '../crypto/cryptoworker';
import AppStorage from '../storage';
@ -285,8 +284,11 @@ class MTPNetworker { @@ -285,8 +284,11 @@ class MTPNetworker {
}
if(options.afterMessageID) {
let invokeAfterMsg = Config.Schema.API.methods.find((m: any) => m.method == 'invokeAfterMsg');
if(!invokeAfterMsg) throw new Error('no invokeAfterMsg!');
this.log('Api call options.afterMessageID!');
serializer.storeInt(0xcb9f372d, 'invokeAfterMsg');
serializer.storeInt(+invokeAfterMsg.id >>> 0, 'invokeAfterMsg');
serializer.storeLong(options.afterMessageID, 'msg_id');
}

27
src/lib/tl_utils.ts

@ -9,6 +9,13 @@ import {bigint, intToUint, bigStringInt, bytesToHex, gzipUncompress, uintToInt} @@ -9,6 +9,13 @@ import {bigint, intToUint, bigStringInt, bytesToHex, gzipUncompress, uintToInt}
import {isObject} from './utils';
import * as Config from './config';
const boolFalse = +Config.Schema.API.constructors.find((c: any) => c.predicate == 'boolFalse').id >>> 0;
const boolTrue = +Config.Schema.API.constructors.find((c: any) => c.predicate == 'boolTrue').id >>> 0;
const vector = +Config.Schema.API.constructors.find((c: any) => c.predicate == 'vector').id >>> 0;
const gzipPacked = +Config.Schema.MTProto.constructors.find((c: any) => c.predicate == 'gzip_packed').id >>> 0;
//console.log('boolFalse', boolFalse == 0xbc799737);
class TLSerialization {
public maxLength = 2048; // 2Kb
public offset = 0; // in bytes
@ -92,9 +99,9 @@ class TLSerialization { @@ -92,9 +99,9 @@ class TLSerialization {
public storeBool(i: boolean, field?: string) {
if(i) {
this.writeInt(0x997275b5, (field || '') + ':bool');
this.writeInt(boolTrue, (field || '') + ':bool');
} else {
this.writeInt(0xbc799737, (field || '') + ':bool');
this.writeInt(boolFalse, (field || '') + ':bool');
}
}
@ -289,7 +296,7 @@ class TLSerialization { @@ -289,7 +296,7 @@ class TLSerialization {
if(Array.isArray(obj)) {
if(type.substr(0, 6) == 'Vector') {
this.writeInt(0x1cb5c415, field + '[id]');
this.writeInt(vector, field + '[id]');
} else if (type.substr(0, 6) != 'vector') {
throw new Error('Invalid vector type ' + type);
}
@ -378,7 +385,6 @@ class TLDeserialization { @@ -378,7 +385,6 @@ class TLDeserialization {
constructor(buffer: ArrayBuffer | Uint8Array, options: any = {}) {
//buffer = addPadding(buffer, 4, true); // fix 21.01.2020 for wss
//console.log("TCL: TLDeserialization -> constructor -> buffer", buffer, buffer instanceof ArrayBuffer);
if(buffer instanceof ArrayBuffer) {
this.buffer = buffer;
this.byteView = new Uint8Array(this.buffer);
@ -387,6 +393,7 @@ class TLDeserialization { @@ -387,6 +393,7 @@ class TLDeserialization {
this.byteView = buffer;
}
//console.log("TCL: TLDeserialization -> constructor -> buffer", buffer, this.byteView, this.byteView.hex);
/* this.buffer = buffer;
//this.intView = new Uint32Array(this.buffer);
this.byteView = new Uint8Array(this.buffer); */
@ -444,9 +451,9 @@ class TLDeserialization { @@ -444,9 +451,9 @@ class TLDeserialization {
public fetchBool(field?: string) {
var i = this.readInt((field || '') + ':bool');
if(i == 0x997275b5) {
if(i == boolTrue) {
return true;
} else if(i == 0xbc799737) {
} else if(i == boolFalse) {
return false;
}
@ -589,7 +596,7 @@ class TLDeserialization { @@ -589,7 +596,7 @@ class TLDeserialization {
var constructor = this.readInt(field + '[id]');
var constructorCmp = uintToInt(constructor);
if(constructorCmp == 0x3072cfa1) { // Gzip packed
if(constructorCmp == gzipPacked) { // Gzip packed
var compressed = this.fetchBytes(field + '[packed_string]');
var uncompressed = gzipUncompress(compressed);
var newDeserializer = new TLDeserialization(uncompressed);
@ -597,7 +604,7 @@ class TLDeserialization { @@ -597,7 +604,7 @@ class TLDeserialization {
return newDeserializer.fetchObject(type, field);
}
if(constructorCmp != 0x1cb5c415) {
if(constructorCmp != vector) {
throw new Error('Invalid vector constructor ' + constructor);
}
}
@ -645,7 +652,7 @@ class TLDeserialization { @@ -645,7 +652,7 @@ class TLDeserialization {
var constructor = this.readInt(field + '[id]');
var constructorCmp = uintToInt(constructor);
if(constructorCmp == 0x3072cfa1) { // Gzip packed
if(constructorCmp == gzipPacked) { // Gzip packed
var compressed = this.fetchBytes(field + '[packed_string]');
var uncompressed = gzipUncompress(compressed);
var newDeserializer = new TLDeserialization(uncompressed);
@ -681,7 +688,7 @@ class TLDeserialization { @@ -681,7 +688,7 @@ class TLDeserialization {
}
if(!constructorData) {
throw new Error('Constructor not found: ' + constructor + ' ' + this.fetchInt() + ' ' + this.fetchInt());
throw new Error('Constructor not found: ' + constructor + ' ' + this.fetchInt() + ' ' + this.fetchInt() + ' ' + field);
}
}

29
src/scss/partials/_chat.scss

@ -59,13 +59,18 @@ $chat-max-width: 696px; @@ -59,13 +59,18 @@ $chat-max-width: 696px;
display: flex;
align-items: center;
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.07);
padding: .5rem 17px;
padding: .5rem 15px;
flex: 0 0 auto; /* Forces side columns to stay same width */
min-height: 60px;
max-height: 60px;
min-height: 61px;
max-height: 61px;
border-bottom: 1px solid #DADCE0;
& > * {
/* & > * {
margin: 0 2px;
} */
.chat-more-button {
margin-left: 8px;
}
.chat-info {
@ -82,6 +87,7 @@ $chat-max-width: 696px; @@ -82,6 +87,7 @@ $chat-max-width: 696px;
display: flex;
align-items: center;
cursor: pointer;
margin-left: 4px;
&:hover {
background-color: transparent;
@ -168,7 +174,7 @@ $chat-max-width: 696px; @@ -168,7 +174,7 @@ $chat-max-width: 696px;
&.is-chat {
.is-in .bubble__container {
margin-left: 45px;
margin-left: 3rem;
}
}
@ -207,10 +213,10 @@ $chat-max-width: 696px; @@ -207,10 +213,10 @@ $chat-max-width: 696px;
.service-msg {
color: #fff;
background-color: rgba(#000, 0.22);
background-color: rgba(0, 0, 0, 0.24);
font-size: 14px;
padding: 0 8px;
line-height: 24px;
font-size: 15px;
border-radius: 12px;
user-select: none;
display: flex;
@ -250,7 +256,8 @@ $chat-max-width: 696px; @@ -250,7 +256,8 @@ $chat-max-width: 696px;
}
&__container {
min-width: 60px;
//min-width: 60px;
min-width: 56px;
max-width: 85%;
border-radius: 12px;
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.15);
@ -685,10 +692,10 @@ $chat-max-width: 696px; @@ -685,10 +692,10 @@ $chat-max-width: 696px;
.message {
position: absolute;
bottom: .1rem;
right: .1rem;
right: .2rem;
border-radius: 12px;
background-color: rgba(0, 0, 0, .4);
padding: 0 .3rem;
padding: 0 .2rem;
z-index: 2;
.time {
@ -1064,7 +1071,7 @@ $chat-max-width: 696px; @@ -1064,7 +1071,7 @@ $chat-max-width: 696px;
}
}
&-time {
&-time, &-subtitle {
color: #68AB5A;
}

9
src/scss/partials/_chatlist.scss

@ -120,7 +120,7 @@ @@ -120,7 +120,7 @@
/* font-size: .9rem; */
//font-size: .8rem;
font-size: .75rem;
padding: 2px 0px 0px 0px;
padding: 1px 0px 0px 0px;
}
.user-last-message + span:not(.tgico-pinnedchat) {
@ -153,7 +153,12 @@ @@ -153,7 +153,12 @@
color: $color-gray;
flex: 1 1 auto;
padding-right: 3.5px;
padding-left: 10px;
padding-left: 9px;
padding-top: 1px;
p:last-child {
margin-top: -3px;
}
}
.user-title {

92
src/scss/partials/_ckin.scss

@ -175,23 +175,23 @@ @@ -175,23 +175,23 @@
transform: translateY(0);
}
.default .progress {
position: relative;
.default {
.media-progress {
margin: 0 16px;
height: 5px;
transition: height 0.3s;
background: rgba(255, 255, 255, 0.38);
cursor: pointer;
border-radius: 4px;
overflow: visible;
}
.default .progress__filled {
&__filled {
background: #63a2e3;
transform-origin: left;
border-radius: 4px;
height: 5px;
transform: scaleX(0);
}
}
}
@media (max-width: 480px) {
@ -204,7 +204,11 @@ video::-webkit-media-controls-enclosure { @@ -204,7 +204,11 @@ video::-webkit-media-controls-enclosure {
display: none !important;
}
.progress input {
.media-progress {
position: relative;
cursor: pointer;
input[type=range] {
-webkit-appearance: none;
-moz-appearance: none;
background: transparent;
@ -212,21 +216,28 @@ video::-webkit-media-controls-enclosure { @@ -212,21 +216,28 @@ video::-webkit-media-controls-enclosure {
cursor: pointer;
padding: 0;
outline: none;
}
.progress input[type=range]:focus {
&:focus {
outline: none;
}
.progress input[type=range]::-webkit-slider-runnable-track {
&::-webkit-slider-runnable-track {
background: transparent;
}
&::-moz-range-track {
outline: none;
}
}
&::-webkit-slider-runnable-track {
width: 100%;
cursor: pointer;
border-radius: 1.3px;
-webkit-appearance: none;
transition: all 0.4s ease;
}
}
.progress input[type=range]::-webkit-slider-thumb {
&::-webkit-slider-thumb {
height: 15px;
width: 15px;
border-radius: 16px;
@ -234,22 +245,18 @@ video::-webkit-media-controls-enclosure { @@ -234,22 +245,18 @@ video::-webkit-media-controls-enclosure {
cursor: pointer;
-webkit-appearance: none;
margin-left: -1px;
}
.progress input[type=range]:focus::-webkit-slider-runnable-track {
background: transparent;
}
}
.progress input[type=range]::-moz-range-track {
&::-moz-range-track {
width: 100%;
height: 8.4px;
cursor: pointer;
border: 1px solid transparent;
background: transparent;
border-radius: 1.3px;
}
}
.progress input[type=range]::-moz-range-thumb {
&::-moz-range-thumb {
height: 14px;
width: 14px;
border-radius: 50px;
@ -257,40 +264,36 @@ video::-webkit-media-controls-enclosure { @@ -257,40 +264,36 @@ video::-webkit-media-controls-enclosure {
background: #63a2e3;
cursor: pointer;
margin-top: 5px;
}
}
}
.progress input[type=range]:focus::-moz-range-track {
outline: none;
&__seek {
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
margin: 0;
}
}
input[type=range]::-ms-track {
input[type=range] {
&::-ms-track {
visibility: hidden;
}
}
input[type=range]::-ms-ticks {
&::-ms-ticks {
background: none;
color: none;
border: none;
}
}
input[type=range]::-ms-thumb {
&::-ms-thumb {
visibility: hidden;
}
}
input[type=range]::-ms-tooltip {
&::-ms-tooltip {
visibility: hidden;
}
.seek {
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
margin: 0;
}
.seek:hover + .seek-tooltip {
display: block;
}
}
.left-controls {
@ -342,9 +345,10 @@ video[data-ckin="circle"] { @@ -342,9 +345,10 @@ video[data-ckin="circle"] {
top: 0;
left: 0;
cursor: pointer;
}
.progress-ring__circle {
&__circle {
transition: stroke-dashoffset;
}
}
.ckin__player.circle {

25
src/scss/partials/_emojiDropdown.scss

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
overflow: hidden;
transition: all 0.2s ease-out;
transform: scale(0);
//transform: scale(0);
transform-origin: 0 100%;
&.active {
@ -232,4 +232,27 @@ @@ -232,4 +232,27 @@
}
}
}
#content-gifs {
.gifs-masonry {
display: flex;
flex-wrap: wrap;
> div {
flex: 1 0 auto;
max-width: 100%;
height: 100px;
margin: 2.5px;
cursor: pointer;
background: #000;
position: relative;
video {
object-fit: cover;
width: 100%;
height: 100%;
}
}
}
}
}

82
src/scss/partials/_rightSIdebar.scss

@ -28,6 +28,7 @@ @@ -28,6 +28,7 @@
.sidebar-header {
flex: 0 0 auto;
padding: 10px 20px 11px 15px;
}
#search-private-container {
@ -38,17 +39,39 @@ @@ -38,17 +39,39 @@
}
}
.profile-content {
.profile {
&-content {
flex: 1 1 auto;
display: flex;
flex-direction: column;
height: 100%;
.profile-name {
[type="checkbox"] + span {
padding-left: 54px;
margin-left: -54px;
}
&-wrapper {
flex: 0 0 auto;
display: flex;
flex-direction: column;
}
.content-container {
width: 100%;
max-width: 100%;
//overflow: hidden;
flex: 1 1 auto;
position: relative;
//height: 1%; // fix safari
}
}
&-name {
text-align: center;
font-size: 23px;
font-size: 24px;
line-height: 1.4;
font-weight: 500;
margin-bottom: 3px;
span.emoji {
vertical-align: inherit;
@ -56,26 +79,27 @@ @@ -56,26 +79,27 @@
}
}
.profile-subtitle {
&-subtitle {
text-align: center;
color: $darkgrey;
font-size: 14px;
margin-bottom: 2px;
&.online {
color: $darkblue;
}
}
.profile-row {
&-row {
display: flex;
width: 100%;
flex-direction: column;
padding-left: 80px;
padding-top: 2px;
padding-right: 12px;
font-size: 15px;
position: relative;
margin-top: 1.75rem;
margin-top: 31px;
line-height: 1.4;
&:before {
position: absolute;
@ -88,6 +112,7 @@ @@ -88,6 +112,7 @@
p {
color: #000;
margin: 0;
font-size: 1rem;
}
&-bio {
@ -96,48 +121,28 @@ @@ -96,48 +121,28 @@
height: 24px;
}
}
}
p.profile-row-label {
color: $placeholder-color;
font-size: 14px;
margin-top: 1px;
&-label {
color: $placeholder-color !important;
font-size: 14px !important;
}
}
.profile-avatar.user-avatar {
&-avatar.user-avatar {
width: 120px;
height: 120px;
margin: 0 auto 20px;
font-size: 4rem;
margin: 0 auto 21px;
font-size: 4rem !important;
&.tgico-avatar_deletedaccount {
font-size: 6rem;
}
}
[type="checkbox"] + span {
padding-left: 54px;
margin-left: -54px;
}
&-wrapper {
flex: 0 0 auto;
}
.content-container {
width: 100%;
max-width: 100%;
//overflow: hidden;
flex: 1 1 auto;
position: relative;
//height: 1%; // fix safari
}
.profile-tabs {
margin-top: 40px;
}
&-tabs {
margin-top: 36px;
.profile-tabs-content {
&-content {
min-height: 100%;
position: absolute; // FIX THE SAFARI!
/* width: 500%;
@ -304,4 +309,5 @@ @@ -304,4 +309,5 @@
}
}
}
}
}

8
src/scss/partials/_sidebar.scss

@ -19,9 +19,9 @@ @@ -19,9 +19,9 @@
&__title {
flex: 1;
padding-left: 2rem;
font-weight: 500;
font-size: 1.4rem;
padding-left: 23px;
font-size: 20px;
}
.btn-icon + .btn-icon {
@ -29,6 +29,10 @@ @@ -29,6 +29,10 @@
}
}
&-close-button {
padding-left: 10px;
}
&-content {
width: 100%;
max-height: 100%;

7
src/scss/style.scss

@ -485,7 +485,12 @@ input { @@ -485,7 +485,12 @@ input {
}
}
&-time {
&-title {
font-size: 1rem;
color: #000;
}
&-time, &-subtitle {
font-size: 14px;
color: $color-gray;
margin-top: 3px;

Loading…
Cancel
Save