Browse Source

Improved media viewer

master
morethanwords 4 years ago
parent
commit
f85fa8965b
  1. 40
      src/components/horizontalMenu.ts
  2. 15
      src/components/scrollable_new.ts
  3. 2
      src/components/slider.ts
  4. 14
      src/index.hbs
  5. 4
      src/lib/appManagers/appDialogsManager.ts
  6. 62
      src/lib/appManagers/appDocsManager.ts
  7. 287
      src/lib/appManagers/appMediaViewer.ts
  8. 3
      src/lib/config.ts
  9. 39
      src/lib/mediaPlayer.ts
  10. 13
      src/lib/mtproto/mtproto.service.ts
  11. 2
      src/pages/pagesManager.ts
  12. 6
      src/scss/partials/_ckin.scss
  13. 15
      src/scss/partials/_leftSidebar.scss
  14. 24
      src/scss/partials/_mediaViewer.scss
  15. 147
      src/scss/partials/_preloader.scss
  16. 6
      src/scss/partials/_rightSidebar.scss
  17. 42
      src/scss/partials/_slider.scss
  18. 136
      src/scss/style.scss

40
src/components/horizontalMenu.ts

@ -42,7 +42,7 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
tabContent.style.transform = ''; tabContent.style.transform = '';
} }
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) { export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
const hideTimeouts: {[id: number]: number} = {}; const hideTimeouts: {[id: number]: number} = {};
let prevTabContent: HTMLElement = null; let prevTabContent: HTMLElement = null;
let prevId = -1; let prevId = -1;
@ -99,13 +99,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
}; };
if(tabs) { if(tabs) {
let activeStripe: HTMLSpanElement; const useStripe = !tabs.classList.contains('no-stripe');
if(!tabs.classList.contains('no-stripe')) {
activeStripe = document.createElement('span');
activeStripe.classList.add('menu-horizontal__stripe');
tabs.append(activeStripe);
}
const tagName = 'LI';//tabs.firstElementChild.tagName; const tagName = 'LI';//tabs.firstElementChild.tagName;
tabs.addEventListener('click', function(e) { tabs.addEventListener('click', function(e) {
@ -139,16 +133,28 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement; const prev = tabs.querySelector(tagName.toLowerCase() + '.active') as HTMLElement;
prev && prev.classList.remove('active'); prev && prev.classList.remove('active');
if(activeStripe) { // stripe from ZINCHUK
const tabsRect = tabs.getBoundingClientRect(); if(useStripe && prevId != -1) {
const targetRect = target.getBoundingClientRect(); const indicator = target.querySelector('i')!;
const width = 50; const currentIndicator = target.parentElement.children[prevId].querySelector('i')!;
activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`;
/* const textRect = target.firstElementChild.getBoundingClientRect(); currentIndicator.classList.remove('animate');
activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */ indicator.classList.remove('animate');
//activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`;
//console.log('tabs click:', tabsRect, textRect); // We move and resize our indicator so it repeats the position and size of the previous one.
const shiftLeft = currentIndicator.parentElement.parentElement.offsetLeft - indicator.parentElement.parentElement.offsetLeft;
const scaleFactor = currentIndicator.clientWidth / indicator.clientWidth;
indicator.style.transform = `translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`;
console.log(`translate3d(${shiftLeft}px, 0, 0) scale3d(${scaleFactor}, 1, 1)`);
requestAnimationFrame(() => {
// Now we remove the transform to let it animate to its own position and size.
indicator.classList.add('animate');
indicator.style.transform = 'none';
});
} }
// stripe END
target.classList.add('active'); target.classList.add('active');
selectTab(id); selectTab(id);

15
src/components/scrollable_new.ts

@ -1,6 +1,6 @@
import { logger, LogLevels } from "../lib/logger"; import { logger, LogLevels } from "../lib/logger";
import smoothscroll from '../vendor/smoothscroll'; import smoothscroll from '../vendor/smoothscroll';
import { touchSupport, isSafari } from "../lib/config"; import { touchSupport, isSafari, mediaSizes } from "../lib/config";
//import { isInDOM } from "../lib/utils"; //import { isInDOM } from "../lib/utils";
(window as any).__forceSmoothScrollPolyfill__ = true; (window as any).__forceSmoothScrollPolyfill__ = true;
smoothscroll.polyfill(); smoothscroll.polyfill();
@ -207,7 +207,7 @@ export default class Scrollable {
const binded = this.onScroll.bind(this); const binded = this.onScroll.bind(this);
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
this.overflowContainer = window.innerWidth <= 720 && false ? document.documentElement : this.container; this.overflowContainer = mediaSizes.isMobile && false ? document.documentElement : this.container;
this.onScroll(); this.onScroll();
}); });
this.container.addEventListener('scroll', binded, {passive: true, capture: true}); this.container.addEventListener('scroll', binded, {passive: true, capture: true});
@ -221,7 +221,7 @@ export default class Scrollable {
} }
//this.onScroll(); //this.onScroll();
this.overflowContainer = window.innerWidth <= 720 && false ? document.documentElement : this.container; this.overflowContainer = mediaSizes.isMobile && false ? document.documentElement : this.container;
/* scrollables.set(this.container, this); /* scrollables.set(this.container, this);
scrollsIntersector.observe(this.container); */ scrollsIntersector.observe(this.container); */
@ -342,8 +342,13 @@ export default class Scrollable {
public checkForTriggers(container: HTMLElement) { public checkForTriggers(container: HTMLElement) {
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return; if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
const scrollTop = container.scrollTop; const scrollHeight = container.scrollHeight;
const maxScrollTop = container.scrollHeight - container.clientHeight; if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
return;
}
const {clientHeight, scrollTop} = container;
const maxScrollTop = scrollHeight - clientHeight;
//this.log('checkForTriggers:', scrollTop, maxScrollTop); //this.log('checkForTriggers:', scrollTop, maxScrollTop);

2
src/components/slider.ts

@ -7,7 +7,7 @@ export interface SliderTab {
onCloseAfterTimeout?: () => void onCloseAfterTimeout?: () => void
} }
const TRANSITIONTIME = 420; const TRANSITIONTIME = 250;
export default class SidebarSlider { export default class SidebarSlider {
protected _selectTab: (id: number) => void; protected _selectTab: (id: number) => void;

14
src/index.hbs

@ -212,9 +212,9 @@
<div class="sidebar-content"> <div class="sidebar-content">
<div id="chats-container"> <div id="chats-container">
<div class="folders-tabs-scrollable hide"> <div class="folders-tabs-scrollable hide">
<nav class="menu-horizontal" id="folders-tabs"> <nav class="menu-horizontal no-stripe" id="folders-tabs">
<ul> <ul>
<li class="rp"><span>All</span><span class="unread-count"></span></li> <li class="rp"><span>All<span class="unread-count"></span><i></i></span></li>
</ul> </ul>
</nav> </nav>
</div> </div>
@ -629,11 +629,11 @@
<div class="content-container"> <div class="content-container">
<nav class="profile-tabs menu-horizontal"> <nav class="profile-tabs menu-horizontal">
<ul> <ul>
<li class="profile-tabs-members rp" style="display: none;"><span>Members</span></li> <li class="profile-tabs-members rp" style="display: none;"><span>Members<i></i></span></li>
<li class="profile-tabs-media rp"><span>Media</span></li> <li class="profile-tabs-media rp"><span>Media<i></i></span></li>
<li class="profile-tabs-docs rp"><span>Docs</span></li> <li class="profile-tabs-docs rp"><span>Docs<i></i></span></li>
<li class="profile-tabs-links rp"><span>Links</span></li> <li class="profile-tabs-links rp"><span>Links<i></i></span></li>
<li class="profile-tabs-audio rp"><span>Audio</span></li> <li class="profile-tabs-audio rp"><span>Audio<i></i></span></li>
</ul> </ul>
</nav> </nav>
<div class="profile-tabs-content tabs-container"> <div class="profile-tabs-content tabs-container">

4
src/lib/appManagers/appDialogsManager.ts

@ -703,7 +703,9 @@ export class AppDialogsManager {
span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title); span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title);
const unreadSpan = document.createElement('span'); const unreadSpan = document.createElement('span');
unreadSpan.classList.add('unread-count'); unreadSpan.classList.add('unread-count');
li.append(span, unreadSpan); const i = document.createElement('i');
span.append(unreadSpan, i);
li.append(span);
ripple(li); ripple(li);
this.folders.menu.firstElementChild.append(li); this.folders.menu.firstElementChild.append(li);

62
src/lib/appManagers/appDocsManager.ts

@ -232,68 +232,6 @@ class AppDocsManager {
return getFileNameByLocation(this.getInput(doc, thumbSize), {fileName: doc.file_name}); return getFileNameByLocation(this.getInput(doc, thumbSize), {fileName: doc.file_name});
} }
public downloadDoc(docID: string | MTDocument, toFileEntry?: any): CancellablePromise<Blob> {
const doc = this.getDoc(docID);
if(doc._ == 'documentEmpty') {
return Promise.reject();
}
if(doc.downloaded && !toFileEntry) {
if(doc.url) return Promise.resolve(null);
/* const cachedBlob = apiFileManager.getCachedFile(inputFileLocation);
if(cachedBlob) {
return Promise.resolve(cachedBlob);
} */
}
if(this.downloadPromises[doc.id]) {
return this.downloadPromises[doc.id];
}
const deferred = deferredPromise<Blob>();
const url = this.getFileURL(doc);
fetch(url).then(res => res.blob())
/* downloadPromise */.then((blob) => {
if(blob) {
doc.downloaded = true;
if(doc.type == 'voice' && !opusDecodeController.isPlaySupported()/* && false */) {
let reader = new FileReader();
reader.onloadend = (e) => {
let uint8 = new Uint8Array(e.target.result as ArrayBuffer);
//console.log('sending uint8 to decoder:', uint8);
opusDecodeController.decode(uint8).then(result => {
doc.url = result.url;
deferred.resolve(blob);
}, (err) => {
delete doc.downloaded;
deferred.reject(err);
});
};
reader.readAsArrayBuffer(blob);
return;
} else if(doc.type && doc.sticker != 2) {
doc.url = url;
}
}
deferred.resolve(blob);
}, (e) => {
console.log('document download failed', e);
deferred.reject(e);
}).finally(() => {
//deferred.notify = downloadPromise.notify = deferred.cancel = downloadPromise.cancel = null;
});
return this.downloadPromises[doc.id] = deferred;
}
public downloadDocNew(docID: string | MTDocument/* , method: ResponseMethod = 'blob' */): DownloadBlob { public downloadDocNew(docID: string | MTDocument/* , method: ResponseMethod = 'blob' */): DownloadBlob {
const doc = this.getDoc(docID); const doc = this.getDoc(docID);

287
src/lib/appManagers/appMediaViewer.ts

@ -13,6 +13,11 @@ import LazyLoadQueue from "../../components/lazyLoadQueue";
import appForward from "../../components/appForward"; import appForward from "../../components/appForward";
import { isSafari, mediaSizes } from "../config"; import { isSafari, mediaSizes } from "../config";
import appAudio from "../../components/appAudio"; import appAudio from "../../components/appAudio";
import { deferredPromise } from "../polyfill";
import { MTDocument } from "../../types";
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
export class AppMediaViewer { export class AppMediaViewer {
public wholeDiv = document.querySelector('.media-viewer-whole') as HTMLDivElement; public wholeDiv = document.querySelector('.media-viewer-whole') as HTMLDivElement;
@ -32,6 +37,7 @@ export class AppMediaViewer {
public currentMessageID = 0; public currentMessageID = 0;
private preloader: ProgressivePreloader = null; private preloader: ProgressivePreloader = null;
private preloaderStreamable: ProgressivePreloader = null;
private lastTarget: HTMLElement = null; private lastTarget: HTMLElement = null;
private prevTargets: { private prevTargets: {
@ -43,8 +49,6 @@ export class AppMediaViewer {
//private loadMore: () => void = null; //private loadMore: () => void = null;
public log: ReturnType<typeof logger>; public log: ReturnType<typeof logger>;
public onKeyDownBinded: any;
public onClickBinded: any;
private peerID = 0; private peerID = 0;
private loadMediaPromiseUp: Promise<void> = null; private loadMediaPromiseUp: Promise<void> = null;
@ -58,18 +62,21 @@ export class AppMediaViewer {
private pageEl = document.getElementById('page-chats') as HTMLDivElement; private pageEl = document.getElementById('page-chats') as HTMLDivElement;
private setMoverPromise: Promise<void>; private setMoverPromise: Promise<void>;
private setMoverAnimationPromise: Promise<void>;
private lazyLoadQueue: LazyLoadQueue; private lazyLoadQueue: LazyLoadQueue;
constructor() { constructor() {
this.log = logger('AMV'); this.log = logger('AMV');
this.preloader = new ProgressivePreloader(); this.preloader = new ProgressivePreloader();
this.preloaderStreamable = new ProgressivePreloader(undefined, false);
this.preloaderStreamable.preloader.classList.add('preloader-streamable');
this.lazyLoadQueue = new LazyLoadQueue(undefined, true); this.lazyLoadQueue = new LazyLoadQueue(undefined, true);
parseMenuButtonsTo(this.buttons, this.wholeDiv.querySelectorAll(`[class*='menu']`) as NodeListOf<HTMLElement>); parseMenuButtonsTo(this.buttons, this.wholeDiv.querySelectorAll(`[class*='menu']`) as NodeListOf<HTMLElement>);
this.onKeyDownBinded = this.onKeyDown.bind(this);
const close = (e: MouseEvent) => { const close = (e: MouseEvent) => {
cancelEvent(e); cancelEvent(e);
//this.overlaysDiv.classList.remove('active'); //this.overlaysDiv.classList.remove('active');
@ -97,10 +104,10 @@ export class AppMediaViewer {
}, 200); }, 200);
} }
window.removeEventListener('keydown', this.onKeyDownBinded); window.removeEventListener('keydown', this.onKeyDown);
}; };
[this.buttons.close, this.buttons["mobile-close"]].forEach(el => { [this.buttons.close, this.buttons["mobile-close"], this.preloaderStreamable.preloader].forEach(el => {
el.addEventListener('click', close); el.addEventListener('click', close);
}); });
@ -130,25 +137,8 @@ export class AppMediaViewer {
} }
}); });
const download = (e: MouseEvent) => {
let message = appMessagesManager.getMessage(this.currentMessageID);
if(message.media.photo) {
appPhotosManager.savePhotoFile(message.media.photo);
} else {
let document: any = null;
if(message.media.webpage) document = message.media.webpage.document;
else document = message.media.document;
if(document) {
console.log('will save document:', document);
appDocsManager.saveDocFile(document.id);
}
}
};
[this.buttons.download, this.buttons["menu-download"]].forEach(el => { [this.buttons.download, this.buttons["menu-download"]].forEach(el => {
el.addEventListener('click', download); el.addEventListener('click', this.onClickDownload);
}); });
const forward = (e: MouseEvent) => { const forward = (e: MouseEvent) => {
@ -159,31 +149,50 @@ export class AppMediaViewer {
el.addEventListener('click', forward); el.addEventListener('click', forward);
}); });
this.onClickBinded = (e: MouseEvent) => { this.wholeDiv.addEventListener('click', this.onClick);
let target = e.target as HTMLElement;
if(target.tagName == 'A') return;
cancelEvent(e);
let mover: HTMLElement = null;
['media-viewer-mover', 'media-viewer-buttons', 'media-viewer-author'].find(s => {
try {
mover = findUpClassName(target, s);
if(mover) return true;
} catch(err) {return false;}
});
if(/* target == this.mediaViewerDiv */!mover || target.tagName == 'IMG' || target.tagName == 'image') {
this.buttons.close.click();
}
};
this.wholeDiv.addEventListener('click', this.onClickBinded);
//this.content.mover.addEventListener('click', this.onClickBinded); //this.content.mover.addEventListener('click', this.onClickBinded);
//this.content.mover.append(this.buttons.prev, this.buttons.next); //this.content.mover.append(this.buttons.prev, this.buttons.next);
this.setNewMover(); this.setNewMover();
} }
public onKeyDown(e: KeyboardEvent) { onClickDownload = (e: MouseEvent) => {
const message = appMessagesManager.getMessage(this.currentMessageID);
if(message.media.photo) {
appPhotosManager.savePhotoFile(message.media.photo);
} else {
let document: MTDocument = null;
if(message.media.webpage) document = message.media.webpage.document;
else document = message.media.document;
if(document) {
//console.log('will save document:', document);
appDocsManager.saveDocFile(document);
}
}
};
onClick = (e: MouseEvent) => {
if(this.setMoverAnimationPromise) return;
const target = e.target as HTMLElement;
if(target.tagName == 'A') return;
cancelEvent(e);
let mover: HTMLElement = null;
['media-viewer-mover', 'media-viewer-buttons', 'media-viewer-author'].find(s => {
try {
mover = findUpClassName(target, s);
if(mover) return true;
} catch(err) {return false;}
});
if(/* target == this.mediaViewerDiv */!mover || target.tagName == 'IMG' || target.tagName == 'image') {
this.buttons.close.click();
}
};
onKeyDown = (e: KeyboardEvent) => {
//this.log('onKeyDown', e); //this.log('onKeyDown', e);
if(e.key == 'ArrowRight') { if(e.key == 'ArrowRight') {
@ -191,9 +200,9 @@ export class AppMediaViewer {
} else if(e.key == 'ArrowLeft') { } else if(e.key == 'ArrowLeft') {
this.buttons.prev.click(); this.buttons.prev.click();
} }
} };
public async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) { private async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
const mover = this.content.mover; const mover = this.content.mover;
if(!closing) { if(!closing) {
@ -201,6 +210,8 @@ export class AppMediaViewer {
//mover.append(this.buttons.prev, this.buttons.next); //mover.append(this.buttons.prev, this.buttons.next);
} }
this.removeCenterFromMover(mover);
const wasActive = fromRight !== 0; const wasActive = fromRight !== 0;
const delay = wasActive ? 350 : 200; const delay = wasActive ? 350 : 200;
@ -240,6 +251,17 @@ export class AppMediaViewer {
top = rect.top; top = rect.top;
} }
transform += `translate(${left}px,${top}px) `;
/* if(wasActive) {
left = fromRight === 1 ? appPhotosManager.windowW / 2 : -(containerRect.width + appPhotosManager.windowW / 2);
transform += `translate(${left}px,-50%) `;
} else {
left = rect.left - (appPhotosManager.windowW / 2);
top = rect.top - (appPhotosManager.windowH / 2);
transform += `translate(${left}px,${top}px) `;
} */
let aspecter: HTMLDivElement; let aspecter: HTMLDivElement;
if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement) { if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement) {
if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) { if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) {
@ -260,15 +282,13 @@ export class AppMediaViewer {
} }
} else { } else {
aspecter = document.createElement('div'); aspecter = document.createElement('div');
aspecter.classList.add('media-viewer-aspecter', 'disable-hover'); aspecter.classList.add('media-viewer-aspecter'/* , 'disable-hover' */);
mover.prepend(aspecter); mover.prepend(aspecter);
} }
aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale(${containerRect.width / rect.width}, ${containerRect.height / rect.height});`; aspecter.style.cssText = `width: ${rect.width}px; height: ${rect.height}px; transform: scale(${containerRect.width / rect.width}, ${containerRect.height / rect.height});`;
} }
transform += `translate(${left}px,${top}px) `;
mover.style.width = containerRect.width + 'px'; mover.style.width = containerRect.width + 'px';
mover.style.height = containerRect.height + 'px'; mover.style.height = containerRect.height + 'px';
@ -294,6 +314,13 @@ export class AppMediaViewer {
let path: SVGPathElement; let path: SVGPathElement;
const isOut = target.classList.contains('is-out'); const isOut = target.classList.contains('is-out');
const deferred = this.setMoverAnimationPromise = deferredPromise<void>();
const ret = {onAnimationEnd: deferred};
this.setMoverAnimationPromise.then(() => {
this.setMoverAnimationPromise = null;
});
if(!closing) { if(!closing) {
let mediaElement: HTMLImageElement | HTMLVideoElement; let mediaElement: HTMLImageElement | HTMLVideoElement;
let src: string; let src: string;
@ -323,6 +350,10 @@ export class AppMediaViewer {
newSvg.setAttributeNS(null, 'width', '' + width); newSvg.setAttributeNS(null, 'width', '' + width);
newSvg.setAttributeNS(null, 'height', '' + height); newSvg.setAttributeNS(null, 'height', '' + height);
// нижние два свойства для масштабирования
newSvg.setAttributeNS(null, 'viewBox', `0 0 ${width} ${height}`);
newSvg.setAttributeNS(null, 'preserveAspectRatio', 'xMidYMid meet');
newSvg.insertAdjacentHTML('beforeend', target.firstElementChild.outerHTML.replace(clipID, newClipID)); newSvg.insertAdjacentHTML('beforeend', target.firstElementChild.outerHTML.replace(clipID, newClipID));
newSvg.insertAdjacentHTML('beforeend', target.lastElementChild.outerHTML.replace(clipID, newClipID)); newSvg.insertAdjacentHTML('beforeend', target.lastElementChild.outerHTML.replace(clipID, newClipID));
@ -393,6 +424,11 @@ export class AppMediaViewer {
mover.classList.add(wasActive ? 'moving' : 'active'); mover.classList.add(wasActive ? 'moving' : 'active');
}); });
} else { } else {
/* if(mover.classList.contains('center')) {
mover.classList.remove('center');
void mover.offsetLeft; // reflow
} */
if(target instanceof SVGSVGElement) { if(target instanceof SVGSVGElement) {
path = mover.querySelector('path'); path = mover.querySelector('path');
@ -421,17 +457,21 @@ export class AppMediaViewer {
mover.innerHTML = ''; mover.innerHTML = '';
mover.classList.remove('moving', 'active', 'hiding'); mover.classList.remove('moving', 'active', 'hiding');
mover.style.display = 'none'; mover.style.display = 'none';
deferred.resolve();
}, delay); }, delay);
return; return ret;
} }
//await new Promise((resolve) => setTimeout(resolve, 0)); //await new Promise((resolve) => setTimeout(resolve, 0));
await new Promise((resolve) => window.requestAnimationFrame(resolve)); await new Promise((resolve) => window.requestAnimationFrame(resolve));
// чтобы проверить установленную позицию - раскомментировать
//throw ''; //throw '';
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`; mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
if(aspecter) { if(aspecter) {
this.setFullAspect(aspecter, containerRect, rect); this.setFullAspect(aspecter, containerRect, rect);
@ -449,22 +489,33 @@ export class AppMediaViewer {
mover.classList.remove('moving'); mover.classList.remove('moving');
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
if(mover.querySelector('video')) { if(mover.querySelector('video') || true) {
mover.classList.remove('active'); mover.classList.remove('active');
aspecter.style.cssText = ''; aspecter.style.cssText = '';
void mover.offsetLeft; // reflow void mover.offsetLeft; // reflow
} }
aspecter.classList.remove('disable-hover'); //aspecter.classList.remove('disable-hover');
} }
// эти строки нужны для установки центральной позиции, в случае ресайза это будет нужно
mover.classList.add('center', 'no-transition');
/* mover.style.left = mover.style.top = '50%';
mover.style.transform = 'translate(-50%, -50%)';
void mover.offsetLeft; // reflow */
// это уже нужно для будущих анимаций
mover.classList.add('active'); mover.classList.add('active');
delete mover.dataset.timeout; delete mover.dataset.timeout;
deferred.resolve();
}, delay); }, delay);
if(path) { if(path) {
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius); this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
} }
return ret;
} }
private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) { private setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
@ -493,7 +544,7 @@ export class AppMediaViewer {
//} //}
} }
public sizeTailPath(path: SVGPathElement, rect: DOMRect, scaleX: number, delay: number, upscale: boolean, isOut: boolean, borderRadius: string) { private sizeTailPath(path: SVGPathElement, rect: DOMRect, scaleX: number, delay: number, upscale: boolean, isOut: boolean, borderRadius: string) {
const start = Date.now(); const start = Date.now();
const {width, height} = rect; const {width, height} = rect;
delay = delay / 2; delay = delay / 2;
@ -521,9 +572,21 @@ export class AppMediaViewer {
step(); step();
} }
public moveTheMover(mover: HTMLDivElement, toLeft = true) { private removeCenterFromMover(mover: HTMLDivElement) {
if(mover.classList.contains('center')) {
const rect = mover.getBoundingClientRect();
mover.style.transform = `translate(${rect.left}px,${rect.top}px)`;
mover.classList.remove('center');
void mover.offsetLeft; // reflow
mover.classList.remove('no-transition');
}
}
private moveTheMover(mover: HTMLDivElement, toLeft = true) {
const windowW = appPhotosManager.windowW; const windowW = appPhotosManager.windowW;
this.removeCenterFromMover(mover);
//mover.classList.remove('active'); //mover.classList.remove('active');
mover.classList.add('moving'); mover.classList.add('moving');
@ -534,9 +597,8 @@ export class AppMediaViewer {
const rect = mover.getBoundingClientRect(); const rect = mover.getBoundingClientRect();
const newTransform = mover.style.transform.replace(/translate\((.+?),/, (match, p1) => { const newTransform = mover.style.transform.replace(/translate\((.+?),/, (match, p1) => {
/////////this.log('replace func', match, p1); const x = toLeft ? -rect.width : windowW;
let x = +p1.slice(0, -2); //const x = toLeft ? -(rect.right + (rect.width / 2)) : windowW / 2;
x = toLeft ? -rect.width : windowW;
return match.replace(p1, x + 'px'); return match.replace(p1, x + 'px');
}); });
@ -549,7 +611,7 @@ export class AppMediaViewer {
}, 350); }, 350);
} }
public setNewMover() { private setNewMover() {
const newMover = document.createElement('div'); const newMover = document.createElement('div');
newMover.classList.add('media-viewer-mover'); newMover.classList.add('media-viewer-mover');
@ -560,8 +622,6 @@ export class AppMediaViewer {
this.wholeDiv.append(newMover); this.wholeDiv.append(newMover);
} }
newMover.addEventListener('click', this.onClickBinded);
return this.content.mover = newMover; return this.content.mover = newMover;
} }
@ -573,7 +633,7 @@ export class AppMediaViewer {
} */ } */
// нет смысла делать проверку для reverse и loadMediaPromise // нет смысла делать проверку для reverse и loadMediaPromise
public loadMoreMedia(older = true) { private loadMoreMedia(older = true) {
//if(!older && this.reverse) return; //if(!older && this.reverse) return;
if(older && this.loadedAllMediaDown) return; if(older && this.loadedAllMediaDown) return;
@ -648,7 +708,7 @@ export class AppMediaViewer {
return promise; return promise;
} }
public updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') { private updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') {
//if(target instanceof SVGSVGElement) { //if(target instanceof SVGSVGElement) {
const el = target.querySelector(tagName) as HTMLElement; const el = target.querySelector(tagName) as HTMLElement;
renderImageFromUrl(el, url); renderImageFromUrl(el, url);
@ -737,8 +797,7 @@ export class AppMediaViewer {
} }
let oldAvatar = this.author.avatarEl; let oldAvatar = this.author.avatarEl;
// @ts-ignore this.author.avatarEl = (this.author.avatarEl.cloneNode() as AvatarElement);
this.author.avatarEl = this.author.avatarEl.cloneNode();
this.author.avatarEl.setAttribute('peer', '' + message.fromID); this.author.avatarEl.setAttribute('peer', '' + message.fromID);
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar); oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
@ -749,7 +808,7 @@ export class AppMediaViewer {
this.moveTheMover(this.content.mover, fromRight === 1); this.moveTheMover(this.content.mover, fromRight === 1);
this.setNewMover(); this.setNewMover();
} else { } else {
window.addEventListener('keydown', this.onKeyDownBinded); window.addEventListener('keydown', this.onKeyDown);
this.wholeDiv.classList.add('active'); this.wholeDiv.classList.add('active');
} }
@ -767,18 +826,20 @@ export class AppMediaViewer {
target = target.querySelector('img, video') || target; target = target.querySelector('img, video') || target;
} */ } */
const preloader = media.supportsStreaming ? this.preloaderStreamable : this.preloader;
let setMoverPromise: Promise<void>; let setMoverPromise: Promise<void>;
if(isVideo) { if(isVideo) {
////////this.log('will wrap video', media, size); ////////this.log('will wrap video', media, size);
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => { setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
//return; // set and don't move //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
//return; //return;
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover; const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
const video = mover.querySelector('video') || document.createElement('video'); const video = mover.querySelector('video') || document.createElement('video');
video.src = ''; //video.src = '';
video.setAttribute('playsinline', ''); video.setAttribute('playsinline', '');
if(media.type == 'gif') { if(media.type == 'gif') {
@ -787,58 +848,104 @@ export class AppMediaViewer {
video.loop = true; video.loop = true;
} }
if(!video.parentElement) {
div.append(video);
}
const canPlayThrough = new Promise((resolve) => {
video.addEventListener('canplaythrough', resolve, {once: true});
});
const createPlayer = () => { const createPlayer = () => {
if(media.type != 'gif') { if(media.type != 'gif') {
video.dataset.ckin = 'default'; video.dataset.ckin = 'default';
video.dataset.overlay = '1'; video.dataset.overlay = '1';
if(!video.parentElement) {
div.append(video);
}
// fix for simultaneous play // fix for simultaneous play
appAudio.pause(); appAudio.pause();
appAudio.willBePlayedAudio = null; appAudio.willBePlayedAudio = null;
new VideoPlayer(video, true, media.supportsStreaming); Promise.all([canPlayThrough, onAnimationEnd]).then(() => {
const player = new VideoPlayer(video, true, media.supportsStreaming);
/* div.append(video);
mover.append(player.wrapper); */
});
} }
}; };
if(!video.src || (media.url && media.url != video.src)) { if(media.supportsStreaming) {
onAnimationEnd.then(() => {
if(video.readyState < video.HAVE_FUTURE_DATA) {
preloader.attach(mover, true);
}
/* canPlayThrough.then(() => {
preloader.detach();
}); */
});
const attachCanPlay = () => {
video.addEventListener('canplay', () => {
//this.log('video waited and progress loaded');
preloader.detach();
video.parentElement.classList.remove('is-buffering');
}, {once: true});
};
video.addEventListener('waiting', (e) => {
const loading = video.networkState === video.NETWORK_LOADING;
const isntEnoughData = video.readyState < video.HAVE_FUTURE_DATA;
//this.log('video waiting for progress', loading, isntEnoughData);
if(loading && isntEnoughData) {
attachCanPlay();
preloader.attach(mover, true);
// поставлю класс для плеера, чтобы убрать большую иконку пока прелоадер на месте
video.parentElement.classList.add('is-buffering');
}
});
attachCanPlay();
}
//if(!video.src || media.url != video.src) {
const load = () => { const load = () => {
const promise = appDocsManager.downloadDoc(media.id); const promise = media.supportsStreaming ? Promise.resolve() : appDocsManager.downloadDocNew(media);
//if(!streamable) { if(!media.supportsStreaming) {
this.preloader.attach(mover, true, promise); onAnimationEnd.then(() => {
//} preloader.attach(mover, true, promise);
});
}
promise.then(async() => { (promise as Promise<any>).then(async() => {
if(this.currentMessageID != message.mid) { if(this.currentMessageID != message.mid) {
this.log.warn('media viewer changed video'); this.log.warn('media viewer changed video');
return; return;
} }
const url = media.url; const url = media.url;
if(target instanceof SVGSVGElement && (video.parentElement || !isSafari)) { // if video exists if(target instanceof SVGSVGElement/* && (video.parentElement || !isSafari) */) { // if video exists
if(!video.parentElement) { //if(!video.parentElement) {
div.firstElementChild.lastElementChild.append(video); div.firstElementChild.lastElementChild.append(video);
} //}
this.updateMediaSource(mover, url, 'video'); this.updateMediaSource(mover, url, 'video');
//this.updateMediaSource(target, url, 'source');
} else { } else {
//const promise = new Promise((resolve) => video.addEventListener('loadeddata', resolve, {once: true})); //const promise = new Promise((resolve) => video.addEventListener('loadeddata', resolve, {once: true}));
renderImageFromUrl(video, url); renderImageFromUrl(video, url);
//await promise; //await promise;
const first = div.firstElementChild as HTMLImageElement; /* const first = div.firstElementChild as HTMLImageElement;
if(!(first instanceof HTMLVideoElement) && first) { if(!(first instanceof HTMLVideoElement) && first) {
first.remove(); first.remove();
} }
if(!video.parentElement) { if(!video.parentElement) {
div.prepend(video); div.prepend(video);
} } */
} }
// я хз что это такое, видео появляются просто чёрными и не включаются без этого кода снизу // я хз что это такое, видео появляются просто чёрными и не включаются без этого кода снизу
@ -861,17 +968,19 @@ export class AppMediaViewer {
load, load,
wasSeen: true wasSeen: true
}); });
} else createPlayer(); //} else createPlayer();
}); });
} else { } else {
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => { setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
//return; // set and don't move //return; // set and don't move
//if(wasActive) return; //if(wasActive) return;
//return; //return;
let load = () => { let load = () => {
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size); let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
this.preloader.attach(mover, true, cancellablePromise); onAnimationEnd.then(() => {
this.preloader.attach(mover, true, cancellablePromise);
});
cancellablePromise.then(() => { cancellablePromise.then(() => {
if(this.currentMessageID != message.mid) { if(this.currentMessageID != message.mid) {
this.log.warn('media viewer changed photo'); this.log.warn('media viewer changed photo');
@ -914,7 +1023,9 @@ export class AppMediaViewer {
}); });
} }
return this.setMoverPromise = setMoverPromise.then(() => { return this.setMoverPromise = setMoverPromise.catch(() => {
this.setMoverAnimationPromise = null;
}).finally(() => {
this.setMoverPromise = null; this.setMoverPromise = null;
}); });
} }

3
src/lib/config.ts

@ -77,7 +77,8 @@ class MediaSizes {
private handleResize() { private handleResize() {
const innerWidth = window.innerWidth; const innerWidth = window.innerWidth;
this.isMobile = innerWidth <= 720; //this.isMobile = innerWidth <= 720;
this.isMobile = innerWidth <= 896;
this.active = this.isMobile ? this.sizes.handhelds : this.sizes.desktop; this.active = this.isMobile ? this.sizes.handhelds : this.sizes.desktop;
/* if(this.isMobile) { /* if(this.isMobile) {

39
src/lib/mediaPlayer.ts

@ -1,4 +1,4 @@
import { cancelEvent } from "./utils"; import { cancelEvent, whichChild } from "./utils";
export class ProgressLine { export class ProgressLine {
public container: HTMLDivElement; public container: HTMLDivElement;
@ -179,7 +179,7 @@ export class MediaProgressLine extends ProgressLine {
const scrubTime = super.scrub(e); const scrubTime = super.scrub(e);
this.media.currentTime = scrubTime; this.media.currentTime = scrubTime;
return scrubTime; return scrubTime;
}; }
protected setLoadProgress() { protected setLoadProgress() {
const buf = this.media.buffered; const buf = this.media.buffered;
@ -249,6 +249,9 @@ export default class VideoPlayer {
public progress: MediaProgressLine; public progress: MediaProgressLine;
private skin: string; private skin: string;
/* private videoParent: HTMLElement;
private videoWhichChild: number; */
constructor(public video: HTMLVideoElement, play = false, streamable = false) { constructor(public video: HTMLVideoElement, play = false, streamable = false) {
this.wrapper = document.createElement('div'); this.wrapper = document.createElement('div');
this.wrapper.classList.add('ckin__player'); this.wrapper.classList.add('ckin__player');
@ -358,6 +361,14 @@ export default class VideoPlayer {
this.togglePlay(); this.togglePlay();
}); });
/* player.addEventListener('click', (e) => {
if(e.target != player) {
return;
}
this.togglePlay();
}); */
video.addEventListener('play', () => { video.addEventListener('play', () => {
this.updateButton(toggle); this.updateButton(toggle);
}); });
@ -513,12 +524,22 @@ export default class VideoPlayer {
public toggleFullScreen(fullScreenButton: HTMLElement) { public toggleFullScreen(fullScreenButton: HTMLElement) {
// alternative standard method // alternative standard method
let player = this.wrapper; const player = this.wrapper;
// @ts-ignore // @ts-ignore
if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) { if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
player.classList.add('ckin__fullscreen'); player.classList.add('ckin__fullscreen');
/* const videoParent = this.video.parentElement;
const videoWhichChild = whichChild(this.video);
const needVideoRemount = videoParent != player;
if(needVideoRemount) {
this.videoParent = videoParent;
this.videoWhichChild = videoWhichChild;
player.prepend(this.video);
} */
if(player.requestFullscreen) { if(player.requestFullscreen) {
player.requestFullscreen(); player.requestFullscreen();
// @ts-ignore // @ts-ignore
@ -541,6 +562,18 @@ export default class VideoPlayer {
} else { } else {
player.classList.remove('ckin__fullscreen'); player.classList.remove('ckin__fullscreen');
/* if(this.videoParent) {
const {videoWhichChild, videoParent} = this;
if(!videoWhichChild) {
videoParent.prepend(this.video);
} else {
videoParent.insertBefore(this.video, videoParent.children[videoWhichChild]);
}
this.videoParent = null;
this.videoWhichChild = -1;
} */
// @ts-ignore // @ts-ignore
if(document.cancelFullScreen) { if(document.cancelFullScreen) {
// @ts-ignore // @ts-ignore

13
src/lib/mtproto/mtproto.service.ts

@ -258,11 +258,14 @@ const onFetch = (event: FetchEvent): void => {
headers['Content-Length'] = `${ab.byteLength}`; headers['Content-Length'] = `${ab.byteLength}`;
} }
resolve(new Response(ab, { // simulate slow connection
status: 206, //setTimeout(() => {
statusText: 'Partial Content', resolve(new Response(ab, {
headers, status: 206,
})); statusText: 'Partial Content',
headers,
}));
//}, 2.5e3);
}).catch(err => {}); }).catch(err => {});
}) })
])); ]));

2
src/pages/pagesManager.ts

@ -11,7 +11,7 @@ class PagesManager {
constructor() { constructor() {
this.pagesDiv = document.getElementById('auth-pages') as HTMLDivElement; this.pagesDiv = document.getElementById('auth-pages') as HTMLDivElement;
this.selectTab = horizontalMenu(null, this.pagesDiv.firstElementChild as HTMLDivElement, null, null, 420); this.selectTab = horizontalMenu(null, this.pagesDiv.firstElementChild as HTMLDivElement, null, null);
} }
public setPage(page: Page) { public setPage(page: Page) {

6
src/scss/partials/_ckin.scss

@ -220,6 +220,12 @@
} }
} }
&.is-buffering {
> .toggle {
display: none !important;
}
}
} }
@media (max-width: 480px) { @media (max-width: 480px) {

15
src/scss/partials/_leftSidebar.scss

@ -20,9 +20,21 @@
} }
li { li {
padding: 9px 16px 7px 16px; height: 43px;
padding: 0 16px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center;
> span:first-child {
overflow: visible;
i {
bottom: calc(-.625rem - -2px);
padding-right: 1rem !important;
margin-left: -.5rem !important;
}
}
} }
&__stripe { &__stripe {
@ -38,7 +50,6 @@
font-weight: 500; font-weight: 500;
color: white; color: white;
line-height: 22px; line-height: 22px;
margin-top: 3px;
min-width: 20px; min-width: 20px;
padding: 0 6px; padding: 0 6px;

24
src/scss/partials/_mediaViewer.scss

@ -177,10 +177,15 @@ $move-duration: .35s;
//transition: .5s all; //transition: .5s all;
left: 0; left: 0;
top: 0; top: 0;
/* left: 50%;
top: 50%; */
transform-origin: top left; transform-origin: top left;
overflow: hidden; overflow: hidden;
//border-radius: 0; //border-radius: 0;
max-width: 100%;
max-height: 100%;
.ckin__player { .ckin__player {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -189,6 +194,11 @@ $move-duration: .35s;
top: 0; top: 0;
} }
> svg {
width: 100%;
height: 100%;
}
img, video { img, video {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -210,6 +220,20 @@ $move-duration: .35s;
transition: $move-duration transform ease; transition: $move-duration transform ease;
} }
/* &.center {
transition: none !important;
} */
&.no-transition {
transition: none !important;
}
&.center {
left: 50% !important;
top: 50% !important;
transform: translate(-50%, -50%) !important;
}
&.hiding { &.hiding {
img, video { img, video {
transition: $open-duration opacity; transition: $open-duration opacity;

147
src/scss/partials/_preloader.scss

@ -0,0 +1,147 @@
.preloader {
&-circular {
animation: rotate 2s linear infinite;
height: 100%;
transform-origin: center center;
/* width: 100%; */
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
&-path {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: dash 1.5s ease-in-out infinite/* , color 6s ease-in-out infinite */;
stroke-linecap: round;
stroke: white;
stroke-width: 3;
}
&-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 50px;
height: 50px;
/* cursor: pointer; */
}
}
.preloader-container {
.you-spin-me-round {
width: 100%;
height: 100%;
animation: rotate 2s linear infinite;
}
.preloader-circular {
animation: none;
cursor: pointer;
background-color: rgba(0, 0, 0, .7);
border-radius: 50%;
width: 100%;
height: 100%;
}
.preloader-path-new {
stroke-dasharray: 5, 200;
stroke-dashoffset: 0;
transition: stroke-dasharray 400ms ease-in-out;
stroke-linecap: round;
stroke: white;
stroke-width: 1.5;
}
&.preloader-swing {
cursor: default;
.preloader-circular {
cursor: default;
}
.preloader-path-new {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: dashNew 1.5s ease-in-out infinite/* , color 6s ease-in-out infinite */;
}
}
.preloader-close {
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
color: #fff;
stroke: #fff;
width: 34%;
height: 34%;
html.no-touch &:hover {
background: none;
}
}
&.preloader-streamable {
&, svg {
cursor: pointer !important;
}
&:after {
content: "";
position: absolute;
width: .8125rem;
height: .8125rem;
border-radius: .125rem;
background-color: #fff;
left: 50%;
top: 50%;
transform: translate3d(-50%,-50%,0);
}
}
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124px;
}
}
@keyframes dashNew {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -286%;
}
}

6
src/scss/partials/_rightSidebar.scss

@ -191,6 +191,7 @@
height: 120px; height: 120px;
margin: 1px auto 10px; margin: 1px auto 10px;
font-size: 2.5rem !important; font-size: 2.5rem !important;
flex: 0 0 auto;
@include respond-to(handhelds) { @include respond-to(handhelds) {
margin: 0 auto 10px; margin: 0 auto 10px;
@ -211,6 +212,11 @@
z-index: 2; z-index: 2;
background-color: #fff; background-color: #fff;
i {
padding-right: 1.5rem !important;
margin-left: -.75rem !important;
}
&-content { &-content {
//min-height: 100%; //min-height: 100%;
min-height: calc(100% - 49px); min-height: calc(100% - 49px);

42
src/scss/partials/_slider.scss

@ -1,3 +1,5 @@
$slider-time: .25s;
.menu-horizontal { .menu-horizontal {
color: $color-gray; color: $color-gray;
border-bottom: 1px solid $lightgrey; border-bottom: 1px solid $lightgrey;
@ -27,14 +29,24 @@
font-weight: 500; font-weight: 500;
position: relative; position: relative;
border-top-left-radius: 6px; border-top-left-radius: 6px;
border-top-right-radius: 6px; border-top-right-radius: 6px;
> span {
position: relative;
display: inline-flex;
align-items: center;
}
&.active { &.active {
color: $color-blue; color: $color-blue;
i {
opacity: 1;
}
} }
} }
&__stripe { /* &__stripe {
position: absolute; position: absolute;
background: $color-blue; background: $color-blue;
left: 0; left: 0;
@ -52,6 +64,26 @@
@include respond-to(handhelds) { @include respond-to(handhelds) {
display: none; display: none;
} }
} */
i {
position: absolute;
bottom: calc(-.625rem - 2px);
left: 0;
opacity: 0;
background-color: $color-blue;
height: .1875rem;
width: 100%;
border-radius: .1875rem .1875rem 0 0;
pointer-events: none;
padding-right: .5rem;
margin-left: -.25rem;
box-sizing: content-box;
transform-origin: left;
&.animate {
transition: transform $slider-time;
}
} }
} }
@ -74,7 +106,7 @@
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
//transition: transform .42s, filter .42s; //transition: transform .42s, filter .42s;
transition: transform .2s, filter .2s; transition: transform $slider-time, filter $slider-time;
display: none; display: none;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
@ -98,4 +130,8 @@
position: relative; position: relative;
} }
} }
&[data-slider="tabs"] {
transition: transform $slider-time;
}
} }

136
src/scss/style.scss

@ -16,13 +16,16 @@ $lightgrey: #dadce0;
$light: rgba($color-gray, 0.08); $light: rgba($color-gray, 0.08);
$small-screen: 720px; //$small-screen: 720px;
$small-screen: 896px;
//$small-screen: 900px;
$large-screen: 1680px; $large-screen: 1680px;
//$large-screen: 16800px; //$large-screen: 16800px;
@mixin respond-to($media) { @mixin respond-to($media) {
@if $media == handhelds { @if $media == handhelds {
@media only screen and (max-width: $small-screen) { @content; } @media only screen and (max-width: $small-screen) { @content; }
//@media only screen and (orientation: landscape) and (max-device-width: 896px) { @content; } // iPhone 11 Pro Max
} }
@else if $media == medium-screens { @else if $media == medium-screens {
@media only screen and (min-width: $small-screen + 1) and (max-width: $large-screen) { @content; } @media only screen and (min-width: $small-screen + 1) and (max-width: $large-screen) { @content; }
@ -49,6 +52,7 @@ $large-screen: 1680px;
@import "partials/slider"; @import "partials/slider";
@import "partials/selector"; @import "partials/selector";
@import "partials/gifsMasonry"; @import "partials/gifsMasonry";
@import "partials/preloader";
@import "partials/popups/popup"; @import "partials/popups/popup";
@import "partials/popups/editAvatar"; @import "partials/popups/editAvatar";
@ -1188,136 +1192,6 @@ input:focus, button:focus {
} }
} }
.preloader {
&-circular {
animation: rotate 2s linear infinite;
height: 100%;
transform-origin: center center;
/* width: 100%; */
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
&-path {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: dash 1.5s ease-in-out infinite/* , color 6s ease-in-out infinite */;
stroke-linecap: round;
stroke: white;
stroke-width: 3;
}
&-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
width: 50px;
height: 50px;
/* cursor: pointer; */
}
}
.preloader-container {
.you-spin-me-round {
width: 100%;
height: 100%;
animation: rotate 2s linear infinite;
}
.preloader-circular {
animation: none;
cursor: pointer;
background-color: rgba(0, 0, 0, .7);
border-radius: 50%;
width: 100%;
height: 100%;
}
.preloader-path-new {
stroke-dasharray: 5, 200;
stroke-dashoffset: 0;
transition: stroke-dasharray 400ms ease-in-out;
stroke-linecap: round;
stroke: white;
stroke-width: 1.5;
}
&.preloader-swing {
cursor: default;
.preloader-circular {
cursor: default;
}
.preloader-path-new {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
animation: dashNew 1.5s ease-in-out infinite/* , color 6s ease-in-out infinite */;
}
}
.preloader-close {
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
color: #fff;
stroke: #fff;
width: 34%;
height: 34%;
html.no-touch &:hover {
background: none;
}
}
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -124px;
}
}
@keyframes dashNew {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -35px;
}
100% {
stroke-dasharray: 89, 200;
stroke-dashoffset: -286%;
}
}
.emoji { .emoji {
display: inline-block; display: inline-block;
/* width: 100%; /* width: 100%;

Loading…
Cancel
Save