Improved media viewer
This commit is contained in:
parent
d4d5d6a996
commit
f85fa8965b
@ -42,7 +42,7 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
|
||||
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} = {};
|
||||
let prevTabContent: HTMLElement = null;
|
||||
let prevId = -1;
|
||||
@ -99,13 +99,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
|
||||
};
|
||||
|
||||
if(tabs) {
|
||||
let activeStripe: HTMLSpanElement;
|
||||
if(!tabs.classList.contains('no-stripe')) {
|
||||
activeStripe = document.createElement('span');
|
||||
activeStripe.classList.add('menu-horizontal__stripe');
|
||||
|
||||
tabs.append(activeStripe);
|
||||
}
|
||||
const useStripe = !tabs.classList.contains('no-stripe');
|
||||
|
||||
const tagName = 'LI';//tabs.firstElementChild.tagName;
|
||||
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;
|
||||
prev && prev.classList.remove('active');
|
||||
|
||||
if(activeStripe) {
|
||||
const tabsRect = tabs.getBoundingClientRect();
|
||||
const targetRect = target.getBoundingClientRect();
|
||||
const width = 50;
|
||||
activeStripe.style.cssText = `width: ${width}px; transform: translateX(${targetRect.left - tabsRect.left + ((targetRect.width - width) / 2)}px);`;
|
||||
/* const textRect = target.firstElementChild.getBoundingClientRect();
|
||||
activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; */
|
||||
//activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`;
|
||||
//console.log('tabs click:', tabsRect, textRect);
|
||||
// stripe from ZINCHUK
|
||||
if(useStripe && prevId != -1) {
|
||||
const indicator = target.querySelector('i')!;
|
||||
const currentIndicator = target.parentElement.children[prevId].querySelector('i')!;
|
||||
|
||||
currentIndicator.classList.remove('animate');
|
||||
indicator.classList.remove('animate');
|
||||
|
||||
// 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');
|
||||
selectTab(id);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { logger, LogLevels } from "../lib/logger";
|
||||
import smoothscroll from '../vendor/smoothscroll';
|
||||
import { touchSupport, isSafari } from "../lib/config";
|
||||
import { touchSupport, isSafari, mediaSizes } from "../lib/config";
|
||||
//import { isInDOM } from "../lib/utils";
|
||||
(window as any).__forceSmoothScrollPolyfill__ = true;
|
||||
smoothscroll.polyfill();
|
||||
@ -207,7 +207,7 @@ export default class Scrollable {
|
||||
const binded = this.onScroll.bind(this);
|
||||
|
||||
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.container.addEventListener('scroll', binded, {passive: true, capture: true});
|
||||
@ -221,7 +221,7 @@ export default class Scrollable {
|
||||
}
|
||||
//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);
|
||||
scrollsIntersector.observe(this.container); */
|
||||
@ -342,8 +342,13 @@ export default class Scrollable {
|
||||
public checkForTriggers(container: HTMLElement) {
|
||||
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
|
||||
|
||||
const scrollTop = container.scrollTop;
|
||||
const maxScrollTop = container.scrollHeight - container.clientHeight;
|
||||
const scrollHeight = container.scrollHeight;
|
||||
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
|
||||
return;
|
||||
}
|
||||
|
||||
const {clientHeight, scrollTop} = container;
|
||||
const maxScrollTop = scrollHeight - clientHeight;
|
||||
|
||||
//this.log('checkForTriggers:', scrollTop, maxScrollTop);
|
||||
|
||||
|
@ -7,7 +7,7 @@ export interface SliderTab {
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
const TRANSITIONTIME = 420;
|
||||
const TRANSITIONTIME = 250;
|
||||
|
||||
export default class SidebarSlider {
|
||||
protected _selectTab: (id: number) => void;
|
||||
|
@ -212,9 +212,9 @@
|
||||
<div class="sidebar-content">
|
||||
<div id="chats-container">
|
||||
<div class="folders-tabs-scrollable hide">
|
||||
<nav class="menu-horizontal" id="folders-tabs">
|
||||
<nav class="menu-horizontal no-stripe" id="folders-tabs">
|
||||
<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>
|
||||
</nav>
|
||||
</div>
|
||||
@ -629,11 +629,11 @@
|
||||
<div class="content-container">
|
||||
<nav class="profile-tabs menu-horizontal">
|
||||
<ul>
|
||||
<li class="profile-tabs-members rp" style="display: none;"><span>Members</span></li>
|
||||
<li class="profile-tabs-media rp"><span>Media</span></li>
|
||||
<li class="profile-tabs-docs rp"><span>Docs</span></li>
|
||||
<li class="profile-tabs-links rp"><span>Links</span></li>
|
||||
<li class="profile-tabs-audio rp"><span>Audio</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<i></i></span></li>
|
||||
<li class="profile-tabs-docs rp"><span>Docs<i></i></span></li>
|
||||
<li class="profile-tabs-links rp"><span>Links<i></i></span></li>
|
||||
<li class="profile-tabs-audio rp"><span>Audio<i></i></span></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="profile-tabs-content tabs-container">
|
||||
|
@ -703,7 +703,9 @@ export class AppDialogsManager {
|
||||
span.innerHTML = RichTextProcessor.wrapEmojiText(filter.title);
|
||||
const unreadSpan = document.createElement('span');
|
||||
unreadSpan.classList.add('unread-count');
|
||||
li.append(span, unreadSpan);
|
||||
const i = document.createElement('i');
|
||||
span.append(unreadSpan, i);
|
||||
li.append(span);
|
||||
ripple(li);
|
||||
|
||||
this.folders.menu.firstElementChild.append(li);
|
||||
|
@ -232,68 +232,6 @@ class AppDocsManager {
|
||||
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 {
|
||||
const doc = this.getDoc(docID);
|
||||
|
||||
|
@ -13,6 +13,11 @@ import LazyLoadQueue from "../../components/lazyLoadQueue";
|
||||
import appForward from "../../components/appForward";
|
||||
import { isSafari, mediaSizes } from "../config";
|
||||
import appAudio from "../../components/appAudio";
|
||||
import { deferredPromise } from "../polyfill";
|
||||
import { MTDocument } from "../../types";
|
||||
|
||||
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
|
||||
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
|
||||
|
||||
export class AppMediaViewer {
|
||||
public wholeDiv = document.querySelector('.media-viewer-whole') as HTMLDivElement;
|
||||
@ -32,6 +37,7 @@ export class AppMediaViewer {
|
||||
|
||||
public currentMessageID = 0;
|
||||
private preloader: ProgressivePreloader = null;
|
||||
private preloaderStreamable: ProgressivePreloader = null;
|
||||
|
||||
private lastTarget: HTMLElement = null;
|
||||
private prevTargets: {
|
||||
@ -43,8 +49,6 @@ export class AppMediaViewer {
|
||||
//private loadMore: () => void = null;
|
||||
|
||||
public log: ReturnType<typeof logger>;
|
||||
public onKeyDownBinded: any;
|
||||
public onClickBinded: any;
|
||||
|
||||
private peerID = 0;
|
||||
private loadMediaPromiseUp: Promise<void> = null;
|
||||
@ -58,18 +62,21 @@ export class AppMediaViewer {
|
||||
private pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
|
||||
private setMoverPromise: Promise<void>;
|
||||
private setMoverAnimationPromise: Promise<void>;
|
||||
|
||||
private lazyLoadQueue: LazyLoadQueue;
|
||||
|
||||
constructor() {
|
||||
this.log = logger('AMV');
|
||||
this.preloader = new ProgressivePreloader();
|
||||
|
||||
this.preloaderStreamable = new ProgressivePreloader(undefined, false);
|
||||
this.preloaderStreamable.preloader.classList.add('preloader-streamable');
|
||||
|
||||
this.lazyLoadQueue = new LazyLoadQueue(undefined, true);
|
||||
|
||||
parseMenuButtonsTo(this.buttons, this.wholeDiv.querySelectorAll(`[class*='menu']`) as NodeListOf<HTMLElement>);
|
||||
|
||||
this.onKeyDownBinded = this.onKeyDown.bind(this);
|
||||
|
||||
const close = (e: MouseEvent) => {
|
||||
cancelEvent(e);
|
||||
//this.overlaysDiv.classList.remove('active');
|
||||
@ -97,10 +104,10 @@ export class AppMediaViewer {
|
||||
}, 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);
|
||||
});
|
||||
|
||||
@ -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 => {
|
||||
el.addEventListener('click', download);
|
||||
el.addEventListener('click', this.onClickDownload);
|
||||
});
|
||||
|
||||
const forward = (e: MouseEvent) => {
|
||||
@ -159,31 +149,50 @@ export class AppMediaViewer {
|
||||
el.addEventListener('click', forward);
|
||||
});
|
||||
|
||||
this.onClickBinded = (e: MouseEvent) => {
|
||||
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.wholeDiv.addEventListener('click', this.onClick);
|
||||
//this.content.mover.addEventListener('click', this.onClickBinded);
|
||||
//this.content.mover.append(this.buttons.prev, this.buttons.next);
|
||||
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);
|
||||
|
||||
if(e.key == 'ArrowRight') {
|
||||
@ -191,15 +200,17 @@ export class AppMediaViewer {
|
||||
} else if(e.key == 'ArrowLeft') {
|
||||
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;
|
||||
|
||||
if(!closing) {
|
||||
mover.innerHTML = '';
|
||||
//mover.append(this.buttons.prev, this.buttons.next);
|
||||
}
|
||||
|
||||
this.removeCenterFromMover(mover);
|
||||
|
||||
const wasActive = fromRight !== 0;
|
||||
|
||||
@ -240,6 +251,17 @@ export class AppMediaViewer {
|
||||
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;
|
||||
if(target instanceof HTMLImageElement || target instanceof HTMLVideoElement) {
|
||||
if(mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter')) {
|
||||
@ -260,15 +282,13 @@ export class AppMediaViewer {
|
||||
}
|
||||
} else {
|
||||
aspecter = document.createElement('div');
|
||||
aspecter.classList.add('media-viewer-aspecter', 'disable-hover');
|
||||
aspecter.classList.add('media-viewer-aspecter'/* , 'disable-hover' */);
|
||||
mover.prepend(aspecter);
|
||||
}
|
||||
|
||||
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.height = containerRect.height + 'px';
|
||||
|
||||
@ -294,6 +314,13 @@ export class AppMediaViewer {
|
||||
let path: SVGPathElement;
|
||||
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) {
|
||||
let mediaElement: HTMLImageElement | HTMLVideoElement;
|
||||
let src: string;
|
||||
@ -323,6 +350,10 @@ export class AppMediaViewer {
|
||||
newSvg.setAttributeNS(null, 'width', '' + width);
|
||||
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.lastElementChild.outerHTML.replace(clipID, newClipID));
|
||||
|
||||
@ -393,6 +424,11 @@ export class AppMediaViewer {
|
||||
mover.classList.add(wasActive ? 'moving' : 'active');
|
||||
});
|
||||
} else {
|
||||
/* if(mover.classList.contains('center')) {
|
||||
mover.classList.remove('center');
|
||||
void mover.offsetLeft; // reflow
|
||||
} */
|
||||
|
||||
if(target instanceof SVGSVGElement) {
|
||||
path = mover.querySelector('path');
|
||||
|
||||
@ -421,17 +457,21 @@ export class AppMediaViewer {
|
||||
mover.innerHTML = '';
|
||||
mover.classList.remove('moving', 'active', 'hiding');
|
||||
mover.style.display = 'none';
|
||||
|
||||
deferred.resolve();
|
||||
}, delay);
|
||||
|
||||
return;
|
||||
return ret;
|
||||
}
|
||||
|
||||
//await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||||
|
||||
// чтобы проверить установленную позицию - раскомментировать
|
||||
//throw '';
|
||||
|
||||
mover.style.transform = `translate(${containerRect.left}px,${containerRect.top}px) scale(1,1)`;
|
||||
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
|
||||
|
||||
if(aspecter) {
|
||||
this.setFullAspect(aspecter, containerRect, rect);
|
||||
@ -449,22 +489,33 @@ export class AppMediaViewer {
|
||||
mover.classList.remove('moving');
|
||||
|
||||
if(aspecter) { // всё из-за видео, элементы управления скейлятся, так бы можно было этого не делать
|
||||
if(mover.querySelector('video')) {
|
||||
if(mover.querySelector('video') || true) {
|
||||
mover.classList.remove('active');
|
||||
aspecter.style.cssText = '';
|
||||
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');
|
||||
delete mover.dataset.timeout;
|
||||
|
||||
deferred.resolve();
|
||||
}, delay);
|
||||
|
||||
if(path) {
|
||||
this.sizeTailPath(path, containerRect, scaleX, delay, true, isOut, borderRadius);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 {width, height} = rect;
|
||||
delay = delay / 2;
|
||||
@ -521,9 +572,21 @@ export class AppMediaViewer {
|
||||
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;
|
||||
|
||||
this.removeCenterFromMover(mover);
|
||||
|
||||
//mover.classList.remove('active');
|
||||
mover.classList.add('moving');
|
||||
|
||||
@ -534,9 +597,8 @@ export class AppMediaViewer {
|
||||
const rect = mover.getBoundingClientRect();
|
||||
|
||||
const newTransform = mover.style.transform.replace(/translate\((.+?),/, (match, p1) => {
|
||||
/////////this.log('replace func', match, p1);
|
||||
let x = +p1.slice(0, -2);
|
||||
x = toLeft ? -rect.width : windowW;
|
||||
const x = toLeft ? -rect.width : windowW;
|
||||
//const x = toLeft ? -(rect.right + (rect.width / 2)) : windowW / 2;
|
||||
|
||||
return match.replace(p1, x + 'px');
|
||||
});
|
||||
@ -549,7 +611,7 @@ export class AppMediaViewer {
|
||||
}, 350);
|
||||
}
|
||||
|
||||
public setNewMover() {
|
||||
private setNewMover() {
|
||||
const newMover = document.createElement('div');
|
||||
newMover.classList.add('media-viewer-mover');
|
||||
|
||||
@ -560,8 +622,6 @@ export class AppMediaViewer {
|
||||
this.wholeDiv.append(newMover);
|
||||
}
|
||||
|
||||
newMover.addEventListener('click', this.onClickBinded);
|
||||
|
||||
return this.content.mover = newMover;
|
||||
}
|
||||
|
||||
@ -573,7 +633,7 @@ export class AppMediaViewer {
|
||||
} */
|
||||
|
||||
// нет смысла делать проверку для reverse и loadMediaPromise
|
||||
public loadMoreMedia(older = true) {
|
||||
private loadMoreMedia(older = true) {
|
||||
//if(!older && this.reverse) return;
|
||||
|
||||
if(older && this.loadedAllMediaDown) return;
|
||||
@ -648,7 +708,7 @@ export class AppMediaViewer {
|
||||
return promise;
|
||||
}
|
||||
|
||||
public updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') {
|
||||
private updateMediaSource(target: HTMLElement, url: string, tagName: 'video' | 'img') {
|
||||
//if(target instanceof SVGSVGElement) {
|
||||
const el = target.querySelector(tagName) as HTMLElement;
|
||||
renderImageFromUrl(el, url);
|
||||
@ -737,8 +797,7 @@ export class AppMediaViewer {
|
||||
}
|
||||
|
||||
let oldAvatar = this.author.avatarEl;
|
||||
// @ts-ignore
|
||||
this.author.avatarEl = this.author.avatarEl.cloneNode();
|
||||
this.author.avatarEl = (this.author.avatarEl.cloneNode() as AvatarElement);
|
||||
this.author.avatarEl.setAttribute('peer', '' + message.fromID);
|
||||
oldAvatar.parentElement.replaceChild(this.author.avatarEl, oldAvatar);
|
||||
|
||||
@ -749,7 +808,7 @@ export class AppMediaViewer {
|
||||
this.moveTheMover(this.content.mover, fromRight === 1);
|
||||
this.setNewMover();
|
||||
} else {
|
||||
window.addEventListener('keydown', this.onKeyDownBinded);
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
this.wholeDiv.classList.add('active');
|
||||
}
|
||||
|
||||
@ -767,18 +826,20 @@ export class AppMediaViewer {
|
||||
target = target.querySelector('img, video') || target;
|
||||
} */
|
||||
|
||||
const preloader = media.supportsStreaming ? this.preloaderStreamable : this.preloader;
|
||||
|
||||
let setMoverPromise: Promise<void>;
|
||||
if(isVideo) {
|
||||
////////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
|
||||
//if(wasActive) return;
|
||||
//return;
|
||||
|
||||
const div = mover.firstElementChild && mover.firstElementChild.classList.contains('media-viewer-aspecter') ? mover.firstElementChild : mover;
|
||||
const video = mover.querySelector('video') || document.createElement('video');
|
||||
video.src = '';
|
||||
//video.src = '';
|
||||
|
||||
video.setAttribute('playsinline', '');
|
||||
if(media.type == 'gif') {
|
||||
@ -787,58 +848,104 @@ export class AppMediaViewer {
|
||||
video.loop = true;
|
||||
}
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.append(video);
|
||||
}
|
||||
|
||||
const canPlayThrough = new Promise((resolve) => {
|
||||
video.addEventListener('canplaythrough', resolve, {once: true});
|
||||
});
|
||||
|
||||
const createPlayer = () => {
|
||||
if(media.type != 'gif') {
|
||||
video.dataset.ckin = 'default';
|
||||
video.dataset.overlay = '1';
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.append(video);
|
||||
}
|
||||
|
||||
// fix for simultaneous play
|
||||
appAudio.pause();
|
||||
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)) {
|
||||
const load = () => {
|
||||
const promise = appDocsManager.downloadDoc(media.id);
|
||||
|
||||
//if(!streamable) {
|
||||
this.preloader.attach(mover, true, promise);
|
||||
//}
|
||||
|
||||
promise.then(async() => {
|
||||
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 promise = media.supportsStreaming ? Promise.resolve() : appDocsManager.downloadDocNew(media);
|
||||
|
||||
if(!media.supportsStreaming) {
|
||||
onAnimationEnd.then(() => {
|
||||
preloader.attach(mover, true, promise);
|
||||
});
|
||||
}
|
||||
|
||||
(promise as Promise<any>).then(async() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
this.log.warn('media viewer changed video');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = media.url;
|
||||
if(target instanceof SVGSVGElement && (video.parentElement || !isSafari)) { // if video exists
|
||||
if(!video.parentElement) {
|
||||
if(target instanceof SVGSVGElement/* && (video.parentElement || !isSafari) */) { // if video exists
|
||||
//if(!video.parentElement) {
|
||||
div.firstElementChild.lastElementChild.append(video);
|
||||
}
|
||||
//}
|
||||
|
||||
this.updateMediaSource(mover, url, 'video');
|
||||
//this.updateMediaSource(target, url, 'source');
|
||||
} else {
|
||||
//const promise = new Promise((resolve) => video.addEventListener('loadeddata', resolve, {once: true}));
|
||||
renderImageFromUrl(video, url);
|
||||
|
||||
//await promise;
|
||||
const first = div.firstElementChild as HTMLImageElement;
|
||||
/* const first = div.firstElementChild as HTMLImageElement;
|
||||
if(!(first instanceof HTMLVideoElement) && first) {
|
||||
first.remove();
|
||||
}
|
||||
|
||||
if(!video.parentElement) {
|
||||
div.prepend(video);
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
// я хз что это такое, видео появляются просто чёрными и не включаются без этого кода снизу
|
||||
@ -861,17 +968,19 @@ export class AppMediaViewer {
|
||||
load,
|
||||
wasSeen: true
|
||||
});
|
||||
} else createPlayer();
|
||||
//} else createPlayer();
|
||||
});
|
||||
} else {
|
||||
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(() => {
|
||||
setMoverPromise = this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
||||
//return; // set and don't move
|
||||
//if(wasActive) return;
|
||||
//return;
|
||||
|
||||
let load = () => {
|
||||
let cancellablePromise = appPhotosManager.preloadPhoto(media.id, size);
|
||||
this.preloader.attach(mover, true, cancellablePromise);
|
||||
onAnimationEnd.then(() => {
|
||||
this.preloader.attach(mover, true, cancellablePromise);
|
||||
});
|
||||
cancellablePromise.then(() => {
|
||||
if(this.currentMessageID != message.mid) {
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
@ -77,7 +77,8 @@ class MediaSizes {
|
||||
|
||||
private handleResize() {
|
||||
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;
|
||||
|
||||
/* if(this.isMobile) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { cancelEvent } from "./utils";
|
||||
import { cancelEvent, whichChild } from "./utils";
|
||||
|
||||
export class ProgressLine {
|
||||
public container: HTMLDivElement;
|
||||
@ -179,7 +179,7 @@ export class MediaProgressLine extends ProgressLine {
|
||||
const scrubTime = super.scrub(e);
|
||||
this.media.currentTime = scrubTime;
|
||||
return scrubTime;
|
||||
};
|
||||
}
|
||||
|
||||
protected setLoadProgress() {
|
||||
const buf = this.media.buffered;
|
||||
@ -249,6 +249,9 @@ export default class VideoPlayer {
|
||||
public progress: MediaProgressLine;
|
||||
private skin: string;
|
||||
|
||||
/* private videoParent: HTMLElement;
|
||||
private videoWhichChild: number; */
|
||||
|
||||
constructor(public video: HTMLVideoElement, play = false, streamable = false) {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.classList.add('ckin__player');
|
||||
@ -353,11 +356,19 @@ export default class VideoPlayer {
|
||||
this.togglePlay();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
video.addEventListener('click', () => {
|
||||
this.togglePlay();
|
||||
});
|
||||
|
||||
/* player.addEventListener('click', (e) => {
|
||||
if(e.target != player) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.togglePlay();
|
||||
}); */
|
||||
|
||||
video.addEventListener('play', () => {
|
||||
this.updateButton(toggle);
|
||||
});
|
||||
@ -513,11 +524,21 @@ export default class VideoPlayer {
|
||||
|
||||
public toggleFullScreen(fullScreenButton: HTMLElement) {
|
||||
// alternative standard method
|
||||
let player = this.wrapper;
|
||||
|
||||
const player = this.wrapper;
|
||||
|
||||
// @ts-ignore
|
||||
if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement && !document.msFullscreenElement) {
|
||||
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) {
|
||||
player.requestFullscreen();
|
||||
@ -540,6 +561,18 @@ export default class VideoPlayer {
|
||||
fullScreenButton.setAttribute('title', 'Exit Full Screen');
|
||||
} else {
|
||||
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
|
||||
if(document.cancelFullScreen) {
|
||||
|
@ -257,12 +257,15 @@ const onFetch = (event: FetchEvent): void => {
|
||||
headers['Content-Range'] = `bytes ${offset}-${offset + ab.byteLength - 1}/${info.size || '*'}`;
|
||||
headers['Content-Length'] = `${ab.byteLength}`;
|
||||
}
|
||||
|
||||
resolve(new Response(ab, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers,
|
||||
}));
|
||||
|
||||
// simulate slow connection
|
||||
//setTimeout(() => {
|
||||
resolve(new Response(ab, {
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers,
|
||||
}));
|
||||
//}, 2.5e3);
|
||||
}).catch(err => {});
|
||||
})
|
||||
]));
|
||||
|
@ -11,7 +11,7 @@ class PagesManager {
|
||||
|
||||
constructor() {
|
||||
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) {
|
||||
|
@ -220,6 +220,12 @@
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.is-buffering {
|
||||
> .toggle {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
@ -20,9 +20,21 @@
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 9px 16px 7px 16px;
|
||||
height: 43px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
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 {
|
||||
@ -38,7 +50,6 @@
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
line-height: 22px;
|
||||
margin-top: 3px;
|
||||
min-width: 20px;
|
||||
padding: 0 6px;
|
||||
|
||||
|
@ -177,10 +177,15 @@ $move-duration: .35s;
|
||||
//transition: .5s all;
|
||||
left: 0;
|
||||
top: 0;
|
||||
/* left: 50%;
|
||||
top: 50%; */
|
||||
transform-origin: top left;
|
||||
overflow: hidden;
|
||||
//border-radius: 0;
|
||||
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
|
||||
.ckin__player {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -189,6 +194,11 @@ $move-duration: .35s;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
> svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
img, video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@ -210,6 +220,20 @@ $move-duration: .35s;
|
||||
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 {
|
||||
img, video {
|
||||
transition: $open-duration opacity;
|
||||
|
147
src/scss/partials/_preloader.scss
Normal file
147
src/scss/partials/_preloader.scss
Normal file
@ -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%;
|
||||
}
|
||||
}
|
@ -191,6 +191,7 @@
|
||||
height: 120px;
|
||||
margin: 1px auto 10px;
|
||||
font-size: 2.5rem !important;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin: 0 auto 10px;
|
||||
@ -211,6 +212,11 @@
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
|
||||
i {
|
||||
padding-right: 1.5rem !important;
|
||||
margin-left: -.75rem !important;
|
||||
}
|
||||
|
||||
&-content {
|
||||
//min-height: 100%;
|
||||
min-height: calc(100% - 49px);
|
||||
|
@ -1,3 +1,5 @@
|
||||
$slider-time: .25s;
|
||||
|
||||
.menu-horizontal {
|
||||
color: $color-gray;
|
||||
border-bottom: 1px solid $lightgrey;
|
||||
@ -27,14 +29,24 @@
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
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 {
|
||||
color: $color-blue;
|
||||
|
||||
i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__stripe {
|
||||
/* &__stripe {
|
||||
position: absolute;
|
||||
background: $color-blue;
|
||||
left: 0;
|
||||
@ -52,6 +64,26 @@
|
||||
@include respond-to(handhelds) {
|
||||
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%;
|
||||
max-width: 100%;
|
||||
//transition: transform .42s, filter .42s;
|
||||
transition: transform .2s, filter .2s;
|
||||
transition: transform $slider-time, filter $slider-time;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
@ -98,4 +130,8 @@
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-slider="tabs"] {
|
||||
transition: transform $slider-time;
|
||||
}
|
||||
}
|
@ -16,13 +16,16 @@ $lightgrey: #dadce0;
|
||||
|
||||
$light: rgba($color-gray, 0.08);
|
||||
|
||||
$small-screen: 720px;
|
||||
//$small-screen: 720px;
|
||||
$small-screen: 896px;
|
||||
//$small-screen: 900px;
|
||||
$large-screen: 1680px;
|
||||
//$large-screen: 16800px;
|
||||
|
||||
@mixin respond-to($media) {
|
||||
@if $media == handhelds {
|
||||
@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 {
|
||||
@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/selector";
|
||||
@import "partials/gifsMasonry";
|
||||
@import "partials/preloader";
|
||||
|
||||
@import "partials/popups/popup";
|
||||
@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 {
|
||||
display: inline-block;
|
||||
/* width: 100%;
|
||||
|
Loading…
x
Reference in New Issue
Block a user