Prepare audio & gif tab & minor fixes
This commit is contained in:
parent
dd877bee83
commit
316693a88e
@ -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);
|
||||
|
@ -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';
|
||||
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,
|
||||
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
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,7 +2,7 @@ import { isElementInViewport } from "../lib/utils";
|
||||
|
||||
type LazyLoadElement = {
|
||||
div: HTMLDivElement,
|
||||
load: () => Promise<void>,
|
||||
load: () => Promise<any>,
|
||||
wasSeen?: boolean
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
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
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
audio.currentTime = scrubTime;
|
||||
}
|
||||
|
||||
audio.append(source);
|
||||
audio.style.display = 'none';
|
||||
audio.append(source);
|
||||
div.append(audio);
|
||||
});
|
||||
|
||||
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
messageWrapped = RichTextProcessor.wrapRichText(messageText.replace(/\n/g, ' '), {
|
||||
messageWrapped = RichTextProcessor.wrapRichText(messageText, {
|
||||
noLinebreakers: true,
|
||||
entities: entities,
|
||||
noTextFormat: true
|
||||
|
@ -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 {
|
||||
}, 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') {
|
||||
|
@ -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 {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 {
|
||||
'inputMessagesFilterPhotoVideo',
|
||||
'inputMessagesFilterDocument',
|
||||
'inputMessagesFilterUrl',
|
||||
'inputMessagesFilterVoice'
|
||||
'inputMessagesFilterMusic'
|
||||
];
|
||||
public sharedMediaType: string = '';
|
||||
private sharedMediaSelected: HTMLDivElement = null;
|
||||
@ -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 {
|
||||
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 {
|
||||
break;
|
||||
}
|
||||
|
||||
/* case 'inputMessagesFilterVoice': {
|
||||
//this.log('wrapping audio', message.media);
|
||||
if(!message.media || !message.media.document || message.media.document.type != 'voice') {
|
||||
break;
|
||||
case 'inputMessagesFilterMusic': {
|
||||
sharedMediaDiv = this.sharedMedia.contentAudio;
|
||||
|
||||
for(let message of messages) {
|
||||
if(!message.media.document || message.media.document.type != 'audio') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let doc = message.media.document;
|
||||
|
||||
this.log('wrapping audio', doc);
|
||||
|
||||
let audioDiv = wrapAudio(doc);
|
||||
|
||||
this.sharedMedia.contentAudio.append(audioDiv);
|
||||
|
||||
let div = wrapDocument(message.media.document, true);
|
||||
elemsToAppend.push(div);
|
||||
}
|
||||
break;
|
||||
} */
|
||||
}
|
||||
|
||||
default:
|
||||
//console.warn('death is my friend', message);
|
||||
|
File diff suppressed because one or more lines are too long
375
src/lib/mediaPlayer.ts
Normal file
375
src/lib/mediaPlayer.ts
Normal file
@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return newDeserializer.fetchObject(type, field);
|
||||
}
|
||||
|
||||
if(constructorCmp != 0x1cb5c415) {
|
||||
if(constructorCmp != vector) {
|
||||
throw new Error('Invalid vector constructor ' + constructor);
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: transparent;
|
||||
@ -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;
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
&__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;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
&-time {
|
||||
&-time, &-subtitle {
|
||||
color: #68AB5A;
|
||||
}
|
||||
|
||||
|
@ -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 @@
|
||||
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 {
|
||||
|
@ -175,24 +175,24 @@
|
||||
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) {
|
||||
.ckin__player button {
|
||||
@ -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,13 +216,20 @@ video::-webkit-media-controls-enclosure {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.progress input[type=range]:focus {
|
||||
&::-moz-range-track {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.progress input[type=range]::-webkit-slider-runnable-track {
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: 1.3px;
|
||||
@ -226,7 +237,7 @@ video::-webkit-media-controls-enclosure {
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.progress input[type=range]::-webkit-slider-thumb {
|
||||
&::-webkit-slider-thumb {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
border-radius: 16px;
|
||||
@ -236,11 +247,7 @@ video::-webkit-media-controls-enclosure {
|
||||
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;
|
||||
@ -249,7 +256,7 @@ video::-webkit-media-controls-enclosure {
|
||||
border-radius: 1.3px;
|
||||
}
|
||||
|
||||
.progress input[type=range]::-moz-range-thumb {
|
||||
&::-moz-range-thumb {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
border-radius: 50px;
|
||||
@ -258,39 +265,35 @@ video::-webkit-media-controls-enclosure {
|
||||
cursor: pointer;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.progress input[type=range]:focus::-moz-range-track {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-track {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-ticks {
|
||||
background: none;
|
||||
color: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-thumb {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
input[type=range]::-ms-tooltip {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.seek {
|
||||
&__seek {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.seek:hover + .seek-tooltip {
|
||||
display: block;
|
||||
input[type=range] {
|
||||
&::-ms-track {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&::-ms-ticks {
|
||||
background: none;
|
||||
color: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&::-ms-tooltip {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.left-controls {
|
||||
@ -342,10 +345,11 @@ video[data-ckin="circle"] {
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.progress-ring__circle {
|
||||
|
||||
&__circle {
|
||||
transition: stroke-dashoffset;
|
||||
}
|
||||
}
|
||||
|
||||
.ckin__player.circle {
|
||||
position: relative;
|
||||
|
@ -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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
.sidebar-header {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px 20px 11px 15px;
|
||||
}
|
||||
|
||||
#search-private-container {
|
||||
@ -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 @@
|
||||
}
|
||||
}
|
||||
|
||||
.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 @@
|
||||
p {
|
||||
color: #000;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&-bio {
|
||||
@ -96,48 +121,28 @@
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&-label {
|
||||
color: $placeholder-color !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
p.profile-row-label {
|
||||
color: $placeholder-color;
|
||||
font-size: 14px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
&-tabs {
|
||||
margin-top: 36px;
|
||||
|
||||
&-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;
|
||||
}
|
||||
|
||||
.profile-tabs-content {
|
||||
&-content {
|
||||
min-height: 100%;
|
||||
position: absolute; // FIX THE SAFARI!
|
||||
/* width: 500%;
|
||||
@ -305,3 +310,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-close-button {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
|
@ -485,7 +485,12 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
&-time {
|
||||
&-title {
|
||||
font-size: 1rem;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&-time, &-subtitle {
|
||||
font-size: 14px;
|
||||
color: $color-gray;
|
||||
margin-top: 3px;
|
||||
|
Loading…
Reference in New Issue
Block a user