Multiple pinned messages in topbar & animations
Layer 121
This commit is contained in:
parent
43cf1baa8a
commit
bc71749b46
@ -165,13 +165,13 @@ export default class ChatContextMenu {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
// for new layer
|
||||
// return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'pin'));
|
||||
return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID == rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
|
||||
return this.msgID > 0 && message._ != 'messageService' && /* appImManager.pinnedMsgID != this.msgID && */ (this.peerID == rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
|
||||
}
|
||||
}, {
|
||||
icon: 'unpin',
|
||||
text: 'Unpin',
|
||||
onClick: this.onUnpinClick,
|
||||
verify: () => appImManager.pinnedMsgID == this.msgID && appPeersManager.canPinMessage(this.peerID)
|
||||
verify: () => /* appImManager.pinnedMsgID == this.msgID && */ appPeersManager.canPinMessage(this.peerID)
|
||||
}, {
|
||||
icon: 'revote',
|
||||
text: 'Revote',
|
||||
@ -269,7 +269,7 @@ export default class ChatContextMenu {
|
||||
};
|
||||
|
||||
private onUnpinClick = () => {
|
||||
new PopupPinMessage(rootScope.selectedPeerID, 0);
|
||||
new PopupPinMessage(rootScope.selectedPeerID, this.msgID, true);
|
||||
};
|
||||
|
||||
private onRetractVote = () => {
|
||||
|
@ -39,6 +39,11 @@ export namespace MessageRender {
|
||||
time = '<i class="edited">edited</i> ' + time;
|
||||
}
|
||||
|
||||
if(message.pFlags.pinned) {
|
||||
bubble.classList.add('is-pinned');
|
||||
time = '<i class="tgico tgico-pinnedchat"></i>' + time;
|
||||
}
|
||||
|
||||
const title = getFullDate(date)
|
||||
+ (message.edit_date ? `\nEdited: ${getFullDate(new Date(message.edit_date * 1000))}` : '')
|
||||
+ (message.fwd_from ? `\nOriginal: ${getFullDate(new Date(message.fwd_from.date * 1000))}` : '');
|
||||
|
388
src/components/chat/pinnedMessage.ts
Normal file
388
src/components/chat/pinnedMessage.ts
Normal file
@ -0,0 +1,388 @@
|
||||
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import { ScreenSize } from "../../helpers/mediaSizes";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import PopupPinMessage from "../popupUnpinMessage";
|
||||
import PinnedContainer from "./pinnedContainer";
|
||||
import PinnedMessageBorder from "./pinnedMessageBorder";
|
||||
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { findUpClassName } from "../../helpers/dom";
|
||||
|
||||
class AnimatedSuper {
|
||||
static DURATION = 200;
|
||||
static BASE_CLASS = 'animated-super';
|
||||
container: HTMLDivElement;
|
||||
rows: {[index: string]: {element: HTMLElement, timeout?: number, new?: true}} = {};
|
||||
clearTimeout: number;
|
||||
|
||||
constructor() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = AnimatedSuper.BASE_CLASS;
|
||||
}
|
||||
|
||||
public getRow(index: number, animateFirst = false) {
|
||||
if(this.rows[index]) return this.rows[index].element;
|
||||
const row = document.createElement('div');
|
||||
const isFirst = !Object.keys(this.rows).length && !animateFirst;
|
||||
row.className = AnimatedSuper.BASE_CLASS + '-row' + (isFirst ? '' : ' is-hiding hide');
|
||||
this.rows[index] = {element: row, new: true};
|
||||
this.container.append(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
public clearRow(index: number) {
|
||||
if(!this.rows[index]) return;
|
||||
this.rows[index].element.remove();
|
||||
delete this.rows[index];
|
||||
}
|
||||
|
||||
public clearRows(currentIndex?: number) {
|
||||
if(this.clearTimeout) clearTimeout(this.clearTimeout);
|
||||
this.clearTimeout = window.setTimeout(() => {
|
||||
for(const i in this.rows) {
|
||||
if(+i === currentIndex) continue;
|
||||
this.clearRow(+i);
|
||||
}
|
||||
}, AnimatedSuper.DURATION);
|
||||
}
|
||||
|
||||
public /* async */ animate(index: number, previousIndex: number, fromTop = index > previousIndex, ignorePrevious = false) {
|
||||
if(index == previousIndex) {
|
||||
this.clearRows(index);
|
||||
return;
|
||||
}
|
||||
|
||||
//const fromTop = index > previousIndex;
|
||||
|
||||
const row = this.rows[index];
|
||||
const previousRow = this.rows[previousIndex];
|
||||
//const height = this.container.getBoundingClientRect().height;
|
||||
|
||||
if(!previousRow && !ignorePrevious) {
|
||||
if(row.new) {
|
||||
row.element.classList.remove('hide');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const sides = ['from-top', 'from-bottom'];
|
||||
if(!fromTop) sides.reverse();
|
||||
|
||||
row.element.classList.add(sides[0]);
|
||||
row.element.classList.remove(sides[1]);
|
||||
if(previousRow) {
|
||||
previousRow.element.classList.add(sides[1]);
|
||||
previousRow.element.classList.remove(sides[0]);
|
||||
}
|
||||
|
||||
if(row.new) {
|
||||
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||||
row.element.classList.remove('hide');
|
||||
void row.element.offsetLeft; // reflow
|
||||
delete row.new;
|
||||
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
|
||||
}
|
||||
|
||||
row.element.classList.toggle('is-hiding', false);
|
||||
previousRow && previousRow.element.classList.toggle('is-hiding', true);
|
||||
//SetTransition(row.element, 'is-hiding', false, AnimatedSuper.DURATION);
|
||||
//previousRow && SetTransition(previousRow.element, 'is-hiding', true, AnimatedSuper.DURATION);
|
||||
|
||||
this.clearRows(index);
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatedCounter {
|
||||
static BASE_CLASS = 'animated-counter';
|
||||
container: HTMLElement;
|
||||
decimals: {
|
||||
container: HTMLElement,
|
||||
placeholder: HTMLElement,
|
||||
animatedSuper: AnimatedSuper
|
||||
}[] = [];
|
||||
previousNumber = 0;
|
||||
clearTimeout: number;
|
||||
|
||||
constructor(private reverse = false) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = AnimatedCounter.BASE_CLASS;
|
||||
}
|
||||
|
||||
getDecimal(index: number) {
|
||||
if(this.decimals[index]) return this.decimals[index];
|
||||
const item = document.createElement('div');
|
||||
item.className = AnimatedCounter.BASE_CLASS + '-decimal';
|
||||
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.className = AnimatedCounter.BASE_CLASS + '-decimal-placeholder';
|
||||
|
||||
const animatedSuper = new AnimatedSuper();
|
||||
animatedSuper.container.className = AnimatedCounter.BASE_CLASS + '-decimal-wrapper';
|
||||
|
||||
item.append(placeholder, animatedSuper.container);
|
||||
|
||||
this.container.append(item);
|
||||
|
||||
return this.decimals[index] = {container: item, placeholder, animatedSuper};
|
||||
}
|
||||
|
||||
clear(number: number) {
|
||||
if(this.clearTimeout) clearTimeout(this.clearTimeout);
|
||||
|
||||
const decimals = ('' + number).length;
|
||||
if(decimals >= this.decimals.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearTimeout = window.setTimeout(() => {
|
||||
const byDecimal = this.decimals.splice(decimals, this.decimals.length - decimals);
|
||||
byDecimal.forEach((decimal) => {
|
||||
decimal.container.remove();
|
||||
});
|
||||
}, AnimatedSuper.DURATION);
|
||||
}
|
||||
|
||||
/* prepareNumber(number: number) {
|
||||
const decimals = ('' + number).length;
|
||||
if(this.decimals.length < decimals) {
|
||||
for(let i = this.decimals.length; i < decimals; ++i) {
|
||||
this.getDecimal(i);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
hideLeft(number: number) {
|
||||
const decimals = ('' + number).length;
|
||||
const byDecimal = this.decimals.slice(decimals);//this.decimals.splice(deleteCount, this.decimals.length - deleteCount);
|
||||
const EMPTY_INDEX = 0;
|
||||
byDecimal.forEach((decimal) => {
|
||||
const row = decimal.animatedSuper.getRow(EMPTY_INDEX, true);
|
||||
decimal.animatedSuper.animate(EMPTY_INDEX, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
|
||||
//decimal.container.remove();
|
||||
//decimal.animatedSuper.clearRows();
|
||||
});
|
||||
|
||||
this.clear(number);
|
||||
}
|
||||
|
||||
setCount(number: number) {
|
||||
//this.prepareNumber(number);
|
||||
|
||||
const byDecimal = Array.from('' + number).map(n => +n);
|
||||
byDecimal.forEach((decimalNumber, idx) => {
|
||||
const decimal = this.getDecimal(idx);
|
||||
const row = decimal.animatedSuper.getRow(number, true);
|
||||
row.innerText = decimal.placeholder.innerText = '' + decimalNumber;
|
||||
decimal.animatedSuper.animate(number, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
|
||||
});
|
||||
|
||||
/* const sides = ['from-top', 'from-bottom'];
|
||||
if(this.reverse) {
|
||||
sides.reverse();
|
||||
}
|
||||
|
||||
const isHigher = number > this.previousNumber;
|
||||
if(!isHigher) {
|
||||
sides.reverse();
|
||||
}
|
||||
|
||||
this.container.classList.add(sides[0]);
|
||||
this.container.classList.remove(sides[1]); */
|
||||
|
||||
this.hideLeft(number);
|
||||
//this.clear(number);
|
||||
this.previousNumber = number;
|
||||
}
|
||||
}
|
||||
|
||||
export default class PinnedMessage {
|
||||
public pinnedMessageContainer: PinnedContainer;
|
||||
public pinnedMessageBorder: PinnedMessageBorder;
|
||||
public pinnedIndex = 0;
|
||||
public wasPinnedIndex = 0;
|
||||
public locked = false;
|
||||
public waitForScrollBottom = false;
|
||||
|
||||
public animatedSubtitle: AnimatedSuper;
|
||||
public animatedMedia: AnimatedSuper;
|
||||
public animatedCounter: AnimatedCounter;
|
||||
|
||||
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
|
||||
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
|
||||
if(appPeersManager.canPinMessage(this.appImManager.peerID)) {
|
||||
new PopupPinMessage(this.appImManager.peerID, 0);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.pinnedMessageBorder = new PinnedMessageBorder();
|
||||
this.pinnedMessageContainer.divAndCaption.border.replaceWith(this.pinnedMessageBorder.render(1, 0));
|
||||
this.appImManager.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.appImManager.btnJoin);
|
||||
|
||||
this.animatedSubtitle = new AnimatedSuper();
|
||||
this.pinnedMessageContainer.divAndCaption.subtitle.append(this.animatedSubtitle.container);
|
||||
|
||||
this.animatedMedia = new AnimatedSuper();
|
||||
this.animatedMedia.container.classList.add('pinned-message-media-container');
|
||||
this.pinnedMessageContainer.divAndCaption.content.prepend(this.animatedMedia.container);
|
||||
|
||||
this.animatedCounter = new AnimatedCounter(true);
|
||||
this.pinnedMessageContainer.divAndCaption.title.innerHTML = 'Pinned Message ';
|
||||
this.pinnedMessageContainer.divAndCaption.title.append(this.animatedCounter.container);
|
||||
|
||||
rootScope.on('peer_pinned_messages', (e) => {
|
||||
const peerID = e.detail;
|
||||
|
||||
if(peerID == this.appImManager.peerID) {
|
||||
this.setPinnedMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setCorrectIndex(lastScrollDirection?: number) {
|
||||
if(this.locked) {
|
||||
return;
|
||||
}/* else if(this.waitForScrollBottom) {
|
||||
if(lastScrollDirection === 1) {
|
||||
this.waitForScrollBottom = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} */
|
||||
|
||||
///const perf = performance.now();
|
||||
const rect = this.appImManager.scrollable.container.getBoundingClientRect();
|
||||
const x = Math.ceil(rect.left + ((rect.right - rect.left) / 2) + 1);
|
||||
const y = Math.floor(rect.top + rect.height - 1);
|
||||
let el: HTMLElement = document.elementFromPoint(x, y) as any;
|
||||
//this.appImManager.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el, x, y);
|
||||
el = findUpClassName(el, 'bubble');
|
||||
|
||||
if(el && el.dataset.mid !== undefined) {
|
||||
const mid = +el.dataset.mid;
|
||||
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID).then(mids => {
|
||||
let currentIndex = mids.findIndex(_mid => _mid <= mid);
|
||||
if(currentIndex === -1) {
|
||||
currentIndex = mids.length ? mids.length - 1 : 0;
|
||||
}
|
||||
|
||||
this.appImManager.log('pinned currentIndex', currentIndex);
|
||||
|
||||
const changed = this.pinnedIndex != currentIndex;
|
||||
if(changed) {
|
||||
if(this.waitForScrollBottom) {
|
||||
if(lastScrollDirection === 1) { // если проскроллил вниз - разблокировать
|
||||
this.waitForScrollBottom = false;
|
||||
} else if(this.pinnedIndex > currentIndex) { // если не скроллил вниз и пытается поставить нижний пиннед - выйти
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.pinnedIndex = currentIndex;
|
||||
this.setPinnedMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async followPinnedMessage(mid: number) {
|
||||
const message = this.appMessagesManager.getMessage(mid);
|
||||
if(message && !message.deleted) {
|
||||
this.locked = true;
|
||||
|
||||
try {
|
||||
const mids = await this.appMessagesManager.getPinnedMessages(message.peerID);
|
||||
const index = mids.indexOf(mid);
|
||||
|
||||
this.pinnedIndex = index >= (mids.length - 1) ? 0 : index + 1;
|
||||
this.setPinnedMessage();
|
||||
|
||||
const setPeerPromise = this.appImManager.setPeer(message.peerID, mid);
|
||||
if(setPeerPromise instanceof Promise) {
|
||||
await setPeerPromise;
|
||||
}
|
||||
|
||||
await this.appImManager.scrollable.scrollLockedPromise;
|
||||
} catch(err) {
|
||||
this.appImManager.log.error('[PM]: followPinnedMessage error:', err);
|
||||
}
|
||||
|
||||
// подождём, пока скролл остановится
|
||||
setTimeout(() => {
|
||||
this.locked = false;
|
||||
this.waitForScrollBottom = true;
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
public onChangeScreen(from: ScreenSize, to: ScreenSize) {
|
||||
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile
|
||||
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
|
||||
}
|
||||
|
||||
public setPinnedMessage() {
|
||||
/////this.log('setting pinned message', message);
|
||||
//return;
|
||||
const promise: Promise<any> = this.appImManager.setPeerPromise || this.appImManager.messagesQueuePromise || Promise.resolve();
|
||||
Promise.all([
|
||||
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID),
|
||||
promise
|
||||
]).then(([mids]) => {
|
||||
//const mids = results[0];
|
||||
if(mids.length) {
|
||||
const pinnedIndex = this.pinnedIndex >= mids.length ? mids.length - 1 : this.pinnedIndex;
|
||||
const message = this.appMessagesManager.getMessage(mids[pinnedIndex]);
|
||||
|
||||
//this.animatedCounter.prepareNumber(mids.length);
|
||||
|
||||
//setTimeout(() => {
|
||||
const isLast = pinnedIndex === 0;
|
||||
this.animatedCounter.container.classList.toggle('is-last', isLast);
|
||||
//SetTransition(this.animatedCounter.container, 'is-last', isLast, AnimatedSuper.DURATION);
|
||||
if(!isLast) {
|
||||
this.animatedCounter.setCount(mids.length - pinnedIndex);
|
||||
}
|
||||
//}, 100);
|
||||
|
||||
//this.pinnedMessageContainer.fill(undefined, message.message, message);
|
||||
this.pinnedMessageContainer.toggle(false);
|
||||
|
||||
const fromTop = pinnedIndex > this.wasPinnedIndex;
|
||||
|
||||
this.appImManager.log('[PM]: setPinnedMessage: fromTop', fromTop, pinnedIndex, this.wasPinnedIndex);
|
||||
|
||||
const writeTo = this.animatedSubtitle.getRow(pinnedIndex);
|
||||
const writeMediaTo = this.animatedMedia.getRow(pinnedIndex);
|
||||
writeMediaTo.classList.add('pinned-message-media');
|
||||
const isMediaSet = wrapReplyDivAndCaption({
|
||||
title: undefined,
|
||||
titleEl: null,
|
||||
subtitle: message.message,
|
||||
subtitleEl: writeTo,
|
||||
message,
|
||||
mediaEl: writeMediaTo
|
||||
});
|
||||
|
||||
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-media', isMediaSet);
|
||||
|
||||
if(this.wasPinnedIndex != this.pinnedIndex) {
|
||||
this.animatedSubtitle.animate(pinnedIndex, this.wasPinnedIndex);
|
||||
if(isMediaSet) {
|
||||
this.animatedMedia.animate(pinnedIndex, this.wasPinnedIndex);
|
||||
} else {
|
||||
this.animatedMedia.clearRows();
|
||||
}
|
||||
}
|
||||
|
||||
this.pinnedMessageBorder.render(mids.length, mids.length - pinnedIndex - 1);
|
||||
this.wasPinnedIndex = pinnedIndex;
|
||||
this.pinnedMessageContainer.divAndCaption.container.dataset.mid = '' + message.mid;
|
||||
} else {
|
||||
this.pinnedMessageContainer.toggle(true);
|
||||
this.wasPinnedIndex = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
177
src/components/chat/pinnedMessageBorder.ts
Normal file
177
src/components/chat/pinnedMessageBorder.ts
Normal file
@ -0,0 +1,177 @@
|
||||
// https://github.com/evgeny-nadymov/telegram-react/blob/master/src/Components/ColumnMiddle/PinnedMessageBorder.js
|
||||
|
||||
export default class PinnedMessageBorder {
|
||||
private border: HTMLElement;
|
||||
private wrapper: HTMLElement;
|
||||
private svg: SVGSVGElement;
|
||||
private defs: SVGDefsElement;
|
||||
private clipPath: SVGClipPathElement;
|
||||
private path: SVGPathElement;
|
||||
private mark: HTMLElement;
|
||||
|
||||
private count: number;
|
||||
private index: number;
|
||||
|
||||
private drawRect = (x: number, y: number, width: number, height: number, radius: number) => {
|
||||
return `M${x},${y + radius}a${radius},${radius},0,0,1,${width},0v${height - 2 * radius}a${radius},${radius},0,0,1,${-width},0Z`;
|
||||
};
|
||||
|
||||
private getClipPath = (id: string, barHeight: number, count: number) => {
|
||||
const radius = 1;
|
||||
|
||||
let d = '';
|
||||
if(count === 3) {
|
||||
d = this.drawRect(0, 0, 2, barHeight, radius)
|
||||
+ this.drawRect(0, 11, 2, barHeight + 1, radius)
|
||||
+ this.drawRect(0, 23, 2, barHeight, radius);
|
||||
} else {
|
||||
for(let i = 0; i < count; i++) {
|
||||
d += this.drawRect(0, (barHeight + 2) * i, 2, barHeight, radius);
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.clipPath) {
|
||||
this.clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
||||
this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
|
||||
this.clipPath.append(this.path);
|
||||
}
|
||||
|
||||
this.clipPath.id = id;
|
||||
this.path.setAttributeNS(null, 'd', d);
|
||||
|
||||
return this.clipPath;
|
||||
};
|
||||
|
||||
private getBarHeight = (count: number, index: number) => {
|
||||
let barHeight = 32;
|
||||
if(count === 1) {
|
||||
barHeight = 32;
|
||||
} else if(count === 2) {
|
||||
barHeight = 15;
|
||||
} else if(count === 3) {
|
||||
barHeight = 9;
|
||||
} else if(count === 4) {
|
||||
barHeight = 7;
|
||||
} else if(count > 3) {
|
||||
barHeight = 7;
|
||||
}
|
||||
|
||||
return barHeight;
|
||||
};
|
||||
|
||||
private getMarkHeight = (count: number, index: number) => {
|
||||
let barHeight = 32;
|
||||
if(count === 1) {
|
||||
barHeight = 32;
|
||||
} else if(count === 2) {
|
||||
barHeight = 15;
|
||||
} else if(count === 3) {
|
||||
barHeight = index === 1 ? 10 : 9;
|
||||
} else if(count === 4) {
|
||||
barHeight = 7;
|
||||
} else if(count > 3) {
|
||||
barHeight = 7;
|
||||
}
|
||||
|
||||
return barHeight;
|
||||
};
|
||||
|
||||
private getMarkTranslateY = (index: number, barHeight: number, count: number) => {
|
||||
if(count === 1) {
|
||||
return 0;
|
||||
} else if(count === 2) {
|
||||
return index === 0 ? 0 : barHeight + 2;
|
||||
}
|
||||
|
||||
if(count === 3) {
|
||||
if(index === 0) {
|
||||
return 0;
|
||||
} else if (index === 1) {
|
||||
return 11;
|
||||
}
|
||||
|
||||
return 23;
|
||||
} else {
|
||||
return (barHeight + 2) * index;
|
||||
}
|
||||
};
|
||||
|
||||
private getTrackTranslateY = (index: number, count: number, barHeight: number, trackHeight: number) => {
|
||||
if(count <= 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(index <= 1) {
|
||||
return 0;
|
||||
} else if(index >= count - 2) {
|
||||
return trackHeight - 32;
|
||||
}
|
||||
|
||||
return (barHeight + 4) / 2 + (index - 2) * (barHeight + 2);
|
||||
};
|
||||
|
||||
private getTrackHeight = (count: number, barHeight: number) => {
|
||||
return count <= 3 ? 32 : barHeight * count + 2 * (count - 1);
|
||||
};
|
||||
|
||||
public render(count: number, index: number) {
|
||||
if(!this.border) {
|
||||
this.border = document.createElement('div');
|
||||
this.border.classList.add('pinned-message-border');
|
||||
|
||||
this.wrapper = document.createElement('div');
|
||||
this.border.append(this.wrapper);
|
||||
}
|
||||
|
||||
if(count === 1) {
|
||||
if(this.count !== count) {
|
||||
this.wrapper.className = 'pinned-message-border-wrapper-1';
|
||||
this.border.classList.remove('pinned-message-border-mask');
|
||||
this.wrapper.innerHTML = '';
|
||||
}
|
||||
|
||||
return this.border;
|
||||
}
|
||||
|
||||
const barHeight = this.getBarHeight(count, index);
|
||||
const markHeight = this.getMarkHeight(count, index);
|
||||
const trackHeight = this.getTrackHeight(count, barHeight);
|
||||
|
||||
const clipPathId = `clipPath_${count}`;
|
||||
const clipPath = this.getClipPath(clipPathId, barHeight, count);
|
||||
|
||||
const markTranslateY = this.getMarkTranslateY(index, barHeight, count);
|
||||
const trackTranslateY = this.getTrackTranslateY(index, count, barHeight, trackHeight);
|
||||
|
||||
this.border.classList.toggle('pinned-message-border-mask', count > 4);
|
||||
|
||||
this.wrapper.className = 'pinned-message-border-wrapper';
|
||||
this.wrapper.style.cssText = `clip-path: url(#${clipPathId}); width: 2px; height: ${trackHeight}px; transform: translateY(-${trackTranslateY}px);`;
|
||||
|
||||
if(!this.svg) {
|
||||
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
this.svg.setAttributeNS(null, 'height', '0');
|
||||
this.svg.setAttributeNS(null, 'width', '0');
|
||||
|
||||
this.defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
|
||||
this.defs.append(clipPath);
|
||||
|
||||
this.svg.append(this.defs);
|
||||
|
||||
this.mark = document.createElement('div');
|
||||
this.mark.classList.add('pinned-message-border-mark');
|
||||
}
|
||||
|
||||
if(!this.svg.parentElement) {
|
||||
this.wrapper.append(this.svg, this.mark);
|
||||
}
|
||||
|
||||
this.mark.style.cssText = `height: ${markHeight}px; transform: translateY(${markTranslateY}px);`;
|
||||
|
||||
this.count = count;
|
||||
this.index = index;
|
||||
|
||||
return this.border;
|
||||
}
|
||||
}
|
@ -5,82 +5,103 @@ import DivAndCaption from "../divAndCaption";
|
||||
import { renderImageFromUrl } from "../misc";
|
||||
import { wrapSticker } from "../wrappers";
|
||||
|
||||
export function wrapReplyDivAndCaption(options: {
|
||||
title: string,
|
||||
titleEl: HTMLElement,
|
||||
subtitle: string,
|
||||
subtitleEl: HTMLElement,
|
||||
message: any,
|
||||
mediaEl: HTMLElement
|
||||
}) {
|
||||
let {title, titleEl, subtitle, subtitleEl, mediaEl, message} = options;
|
||||
if(title !== undefined) {
|
||||
if(title.length > 150) {
|
||||
title = title.substr(0, 140) + '...';
|
||||
}
|
||||
|
||||
title = title ? RichTextProcessor.wrapEmojiText(title) : '';
|
||||
titleEl.innerHTML = title;
|
||||
}
|
||||
|
||||
if(subtitle.length > 150) {
|
||||
subtitle = subtitle.substr(0, 140) + '...';
|
||||
}
|
||||
|
||||
const media = message && message.media;
|
||||
let setMedia = false;
|
||||
if(media && mediaEl) {
|
||||
subtitle = message.rReply;
|
||||
|
||||
//console.log('wrap reply', media);
|
||||
|
||||
if(media.photo || (media.document && ['video', 'sticker', 'gif'].indexOf(media.document.type) !== -1)) {
|
||||
if(media.document?.type == 'sticker') {
|
||||
setMedia = true;
|
||||
wrapSticker({
|
||||
doc: media.document,
|
||||
div: mediaEl,
|
||||
lazyLoadQueue: appImManager.lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
onlyThumb: media.document.sticker == 2,
|
||||
width: 32,
|
||||
height: 32
|
||||
});
|
||||
} else {
|
||||
const photo = media.photo || media.document;
|
||||
|
||||
const cacheContext = appPhotosManager.getCacheContext(photo);
|
||||
|
||||
if(!cacheContext.downloaded) {
|
||||
const sizes = photo.sizes || photo.thumbs;
|
||||
if(sizes && sizes[0].bytes) {
|
||||
setMedia = true;
|
||||
renderImageFromUrl(mediaEl, appPhotosManager.getPreviewURLFromThumb(sizes[0]));
|
||||
}
|
||||
}
|
||||
|
||||
const size = appPhotosManager.choosePhotoSize(photo, 32, 32/* mediaSizes.active.regular.width, mediaSizes.active.regular.height */);
|
||||
if(size._ != 'photoSizeEmpty') {
|
||||
setMedia = true;
|
||||
appPhotosManager.preloadPhoto(photo, size)
|
||||
.then(() => {
|
||||
renderImageFromUrl(mediaEl, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
subtitle = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';
|
||||
}
|
||||
|
||||
subtitleEl.innerHTML = subtitle;
|
||||
return setMedia;
|
||||
}
|
||||
|
||||
export default class ReplyContainer extends DivAndCaption<(title: string, subtitle: string, message?: any) => void> {
|
||||
private mediaEl: HTMLElement;
|
||||
|
||||
constructor(protected className: string) {
|
||||
super(className, (title: string, subtitle: string = '', message?: any) => {
|
||||
if(title.length > 150) {
|
||||
title = title.substr(0, 140) + '...';
|
||||
}
|
||||
|
||||
if(subtitle.length > 150) {
|
||||
subtitle = subtitle.substr(0, 140) + '...';
|
||||
if(!this.mediaEl) {
|
||||
this.mediaEl = document.createElement('div');
|
||||
this.mediaEl.classList.add(this.className + '-media');
|
||||
}
|
||||
|
||||
const isMediaSet = wrapReplyDivAndCaption({
|
||||
title,
|
||||
titleEl: this.title,
|
||||
subtitle,
|
||||
subtitleEl: this.subtitle,
|
||||
mediaEl: this.mediaEl,
|
||||
message
|
||||
});
|
||||
|
||||
title = title ? RichTextProcessor.wrapEmojiText(title) : '';
|
||||
|
||||
if(this.mediaEl) {
|
||||
this.mediaEl.remove();
|
||||
this.container.classList.remove('is-media');
|
||||
}
|
||||
|
||||
const media = message && message.media;
|
||||
if(media) {
|
||||
subtitle = message.rReply;
|
||||
|
||||
//console.log('wrap reply', media);
|
||||
|
||||
if(media.photo || (media.document && ['video', 'sticker', 'gif'].indexOf(media.document.type) !== -1)) {
|
||||
let good = false;
|
||||
const replyMedia = document.createElement('div');
|
||||
replyMedia.classList.add(this.className + '-media');
|
||||
|
||||
if(media.document?.type == 'sticker') {
|
||||
good = true;
|
||||
wrapSticker({
|
||||
doc: media.document,
|
||||
div: replyMedia,
|
||||
lazyLoadQueue: appImManager.lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
onlyThumb: media.document.sticker == 2,
|
||||
width: 32,
|
||||
height: 32
|
||||
});
|
||||
} else {
|
||||
const photo = media.photo || media.document;
|
||||
|
||||
const cacheContext = appPhotosManager.getCacheContext(photo);
|
||||
|
||||
if(!cacheContext.downloaded) {
|
||||
const sizes = photo.sizes || photo.thumbs;
|
||||
if(sizes && sizes[0].bytes) {
|
||||
good = true;
|
||||
renderImageFromUrl(replyMedia, appPhotosManager.getPreviewURLFromThumb(sizes[0]));
|
||||
}
|
||||
}
|
||||
|
||||
const size = appPhotosManager.choosePhotoSize(photo, 32, 32/* mediaSizes.active.regular.width, mediaSizes.active.regular.height */);
|
||||
if(size._ != 'photoSizeEmpty') {
|
||||
good = true;
|
||||
appPhotosManager.preloadPhoto(photo, size)
|
||||
.then(() => {
|
||||
renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(good) {
|
||||
this.content.prepend(this.mediaEl = replyMedia);
|
||||
this.container.classList.add('is-media');
|
||||
}
|
||||
}
|
||||
this.container.classList.toggle('is-media', isMediaSet);
|
||||
if(isMediaSet) {
|
||||
this.content.prepend(this.mediaEl);
|
||||
} else {
|
||||
subtitle = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';
|
||||
this.mediaEl.remove();
|
||||
}
|
||||
|
||||
this.title.innerHTML = title;
|
||||
this.subtitle.innerHTML = subtitle;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { findUpTag, whichChild } from "../helpers/dom";
|
||||
import Transition from "./transition";
|
||||
import { TransitionSlider } from "./transition";
|
||||
|
||||
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
|
||||
const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
|
||||
const selectTab = TransitionSlider(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
|
||||
|
||||
if(tabs) {
|
||||
const useStripe = !tabs.classList.contains('no-stripe');
|
||||
|
@ -3,18 +3,11 @@ import { PopupButton } from "./popup";
|
||||
import PopupPeer from "./popupPeer";
|
||||
|
||||
export default class PopupPinMessage {
|
||||
constructor(peerID: number, mid: number) {
|
||||
constructor(peerID: number, mid: number, unpin?: true) {
|
||||
let title: string, description: string, buttons: PopupButton[] = [];
|
||||
|
||||
const callback = () => appMessagesManager.updatePinnedMessage(peerID, mid);
|
||||
if(mid) {
|
||||
title = 'Pin Message?';
|
||||
description = 'Would you like to pin this message?';
|
||||
buttons.push({
|
||||
text: 'PIN',
|
||||
callback
|
||||
});
|
||||
} else {
|
||||
const callback = () => appMessagesManager.updatePinnedMessage(peerID, mid, unpin);
|
||||
if(unpin) {
|
||||
title = `Unpin Message?`;
|
||||
description = 'Would you like to unpin this message?';
|
||||
buttons.push({
|
||||
@ -22,6 +15,13 @@ export default class PopupPinMessage {
|
||||
isDanger: true,
|
||||
callback
|
||||
});
|
||||
} else {
|
||||
title = 'Pin Message?';
|
||||
description = 'Would you like to pin this message?';
|
||||
buttons.push({
|
||||
text: 'PIN',
|
||||
callback
|
||||
});
|
||||
}
|
||||
|
||||
buttons.push({
|
||||
|
@ -124,12 +124,14 @@ export type SliceSidesContainer = {[k in SliceSides]: boolean};
|
||||
export default class Scrollable extends ScrollableBase {
|
||||
public splitUp: HTMLElement;
|
||||
|
||||
public onAdditionalScroll: () => void = null;
|
||||
public onScrolledTop: () => void = null;
|
||||
public onScrolledBottom: () => void = null;
|
||||
|
||||
public onScrollMeasure: number = null;
|
||||
|
||||
private lastScrollTop: number = 0;
|
||||
public lastScrollTop: number = 0;
|
||||
public lastScrollDirection: number = 0;
|
||||
|
||||
public loadedAll: SliceSidesContainer = {top: true, bottom: false};
|
||||
|
||||
@ -150,37 +152,43 @@ export default class Scrollable extends ScrollableBase {
|
||||
//this.log('onScroll call', this.onScrollMeasure);
|
||||
//}
|
||||
|
||||
if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp)) return;
|
||||
if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
|
||||
this.onScrollMeasure = window.requestAnimationFrame(() => {
|
||||
this.checkForTriggers();
|
||||
|
||||
this.onScrollMeasure = 0;
|
||||
this.lastScrollTop = this.scrollTop;
|
||||
|
||||
const scrollTop = this.container.scrollTop;
|
||||
this.lastScrollDirection = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1); // * 1 - bottom, -1 - top
|
||||
this.lastScrollTop = scrollTop;
|
||||
|
||||
if(this.onAdditionalScroll) {
|
||||
this.onAdditionalScroll();
|
||||
}
|
||||
|
||||
if(this.checkForTriggers) {
|
||||
this.checkForTriggers();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
public checkForTriggers = () => {
|
||||
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
|
||||
|
||||
const container = this.container;
|
||||
const scrollHeight = container.scrollHeight;
|
||||
const scrollHeight = this.container.scrollHeight;
|
||||
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
|
||||
return;
|
||||
}
|
||||
|
||||
const {clientHeight, scrollTop} = container;
|
||||
const clientHeight = this.container.clientHeight;
|
||||
const maxScrollTop = scrollHeight - clientHeight;
|
||||
|
||||
// 1 - bottom, -1 - top
|
||||
const direction = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1);
|
||||
const scrollTop = this.lastScrollTop;
|
||||
|
||||
//this.log('checkForTriggers:', scrollTop, maxScrollTop);
|
||||
|
||||
if(this.onScrolledTop && scrollTop <= this.onScrollOffset && direction <= 0/* && direction === -1 */) {
|
||||
if(this.onScrolledTop && scrollTop <= this.onScrollOffset && this.lastScrollDirection <= 0/* && direction === -1 */) {
|
||||
this.onScrolledTop();
|
||||
}
|
||||
|
||||
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && direction >= 0/* && direction === 1 */) {
|
||||
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && this.lastScrollDirection >= 0/* && direction === 1 */) {
|
||||
this.onScrolledBottom();
|
||||
}
|
||||
};
|
||||
|
@ -11,11 +11,11 @@ import rootScope from "../../lib/rootScope";
|
||||
import { CLICK_EVENT_NAME, findUpClassName, findUpTag } from "../../helpers/dom";
|
||||
import AppSearch, { SearchGroup } from "../appSearch";
|
||||
import "../avatar";
|
||||
import { parseMenuButtonsTo, putPreloader } from "../misc";
|
||||
import { parseMenuButtonsTo } from "../misc";
|
||||
import { ScrollableX } from "../scrollable";
|
||||
import SearchInput from "../searchInput";
|
||||
import SidebarSlider from "../slider";
|
||||
import Transition from "../transition";
|
||||
import { TransitionSlider } from "../transition";
|
||||
import AppAddMembersTab from "./tabs/addMembers";
|
||||
import AppArchivedTab from "./tabs/archivedTab";
|
||||
import AppChatFoldersTab from "./tabs/chatFolders";
|
||||
@ -226,7 +226,7 @@ export class AppSidebarLeft extends SidebarSlider {
|
||||
|
||||
let hideNewBtnMenuTimeout: number;
|
||||
//const transition = Transition.bind(null, this.searchContainer.parentElement, 150);
|
||||
const transition = Transition(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
|
||||
const transition = TransitionSlider(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
|
||||
if(hideNewBtnMenuTimeout) clearTimeout(hideNewBtnMenuTimeout);
|
||||
|
||||
if(id == 0) {
|
||||
|
@ -4,7 +4,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea
|
||||
clearTimeout(+timeout);
|
||||
}
|
||||
|
||||
if(forwards) {
|
||||
if(forwards && className) {
|
||||
element.classList.add(className);
|
||||
}
|
||||
|
||||
|
@ -7,30 +7,61 @@ export interface SliderTab {
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
export class SuperSliderTab implements SliderTab {
|
||||
public id: number;
|
||||
public closeBtn: HTMLElement;
|
||||
|
||||
// fix incompability
|
||||
public onOpen: SliderTab['onOpen'];
|
||||
|
||||
constructor(protected slider: SidebarSlider, public container: HTMLElement) {
|
||||
this.closeBtn = this.container.querySelector('.sidebar-close-button');
|
||||
this.id = this.slider.addTab(this);
|
||||
}
|
||||
|
||||
public close() {
|
||||
return this.slider.closeTab(this.id);
|
||||
}
|
||||
|
||||
public open() {
|
||||
return this.slider.selectTab(this);
|
||||
}
|
||||
}
|
||||
|
||||
const TRANSITION_TIME = 250;
|
||||
|
||||
export default class SidebarSlider {
|
||||
protected _selectTab: (id: number) => void;
|
||||
public historyTabIDs: number[] = [];
|
||||
|
||||
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, canHideFirst = false) {
|
||||
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, private canHideFirst = false) {
|
||||
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITION_TIME);
|
||||
if(!canHideFirst) {
|
||||
this._selectTab(0);
|
||||
}
|
||||
|
||||
let onCloseBtnClick = () => {
|
||||
//console.log('sidebar-close-button click:', this.historyTabIDs);
|
||||
let closingID = this.historyTabIDs.pop(); // pop current
|
||||
this.onCloseTab(closingID);
|
||||
this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] ?? (canHideFirst ? -1 : 0));
|
||||
};
|
||||
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
|
||||
el.addEventListener('click', onCloseBtnClick);
|
||||
el.addEventListener('click', () => this.closeTab());
|
||||
});
|
||||
}
|
||||
|
||||
public selectTab(id: number) {
|
||||
public closeTab = (tabID?: number) => {
|
||||
if(tabID !== undefined && this.historyTabIDs[this.historyTabIDs.length - 1] != tabID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//console.log('sidebar-close-button click:', this.historyTabIDs);
|
||||
let closingID = this.historyTabIDs.pop(); // pop current
|
||||
this.onCloseTab(closingID);
|
||||
this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] ?? (this.canHideFirst ? -1 : 0));
|
||||
return true;
|
||||
};
|
||||
|
||||
public selectTab(id: number | SuperSliderTab): boolean {
|
||||
if(id instanceof SuperSliderTab) {
|
||||
id = id.id;
|
||||
}
|
||||
|
||||
if(this.historyTabIDs[this.historyTabIDs.length - 1] == id) {
|
||||
return false;
|
||||
}
|
||||
@ -72,4 +103,22 @@ export default class SidebarSlider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public addTab(tab: SuperSliderTab) {
|
||||
let id: number;
|
||||
if(tab.container.parentElement) {
|
||||
id = Array.from(this.sidebarEl.children).findIndex(el => el == tab.container);
|
||||
} else {
|
||||
id = this.sidebarEl.childElementCount;
|
||||
this.sidebarEl.append(tab.container);
|
||||
|
||||
if(tab.closeBtn) {
|
||||
tab.closeBtn.addEventListener('click', () => this.closeTab());
|
||||
}
|
||||
}
|
||||
|
||||
this.tabs[id] = tab;
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
@ -11,6 +11,10 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
|
||||
|
||||
tabContent.style.transform = '';
|
||||
tabContent.style.filter = '';
|
||||
|
||||
return () => {
|
||||
prevTabContent.style.transform = prevTabContent.style.filter = '';
|
||||
};
|
||||
}
|
||||
|
||||
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
@ -24,16 +28,57 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
|
||||
return () => {
|
||||
prevTabContent.style.transform = '';
|
||||
};
|
||||
}
|
||||
|
||||
const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade', transitionTime: number, onTransitionEnd?: (id: number) => void) => {
|
||||
/* function slideTabsVertical(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
const height = prevTabContent.getBoundingClientRect().height;
|
||||
const elements = [tabContent, prevTabContent];
|
||||
if(toRight) elements.reverse();
|
||||
elements[0].style.transform = `translate3d(0, ${-height}px, 0)`;
|
||||
elements[1].style.transform = `translate3d(0, ${height}px, 0)`;
|
||||
|
||||
tabContent.classList.add('active');
|
||||
void tabContent.offsetWidth; // reflow
|
||||
|
||||
tabContent.style.transform = '';
|
||||
|
||||
return () => {
|
||||
prevTabContent.style.transform = '';
|
||||
};
|
||||
} */
|
||||
|
||||
export const TransitionSlider = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade'/* | 'counter' */, transitionTime: number, onTransitionEnd?: (id: number) => void) => {
|
||||
let animationFunction: TransitionFunction = null;
|
||||
|
||||
switch(type) {
|
||||
case 'tabs':
|
||||
animationFunction = slideTabs;
|
||||
break;
|
||||
case 'navigation':
|
||||
animationFunction = slideNavigation;
|
||||
break;
|
||||
/* case 'counter':
|
||||
animationFunction = slideTabsVertical;
|
||||
break; */
|
||||
/* default:
|
||||
break; */
|
||||
}
|
||||
|
||||
return Transition(content, animationFunction, transitionTime, onTransitionEnd);
|
||||
};
|
||||
|
||||
type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void);
|
||||
|
||||
const Transition = (content: HTMLElement, animationFunction: TransitionFunction, transitionTime: number, onTransitionEnd?: (id: number) => void) => {
|
||||
const hideTimeouts: {[id: number]: number} = {};
|
||||
//const deferred: (() => void)[] = [];
|
||||
let transitionEndTimeout: number;
|
||||
let prevTabContent: HTMLElement = null;
|
||||
|
||||
const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation);
|
||||
|
||||
function selectTab(id: number, animate = true) {
|
||||
const self = selectTab;
|
||||
if(id == self.prevId) return false;
|
||||
@ -44,7 +89,7 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
|
||||
const tabContent = content.children[id] as HTMLElement;
|
||||
|
||||
// * means animation isn't needed
|
||||
if(content.dataset.slider == 'none' || !animate) {
|
||||
if(/* content.dataset.slider == 'none' || */!animate) {
|
||||
if(p) {
|
||||
p.classList.remove('active');
|
||||
}
|
||||
@ -67,11 +112,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
|
||||
const toRight = self.prevId < id;
|
||||
content.classList.toggle('backwards', !toRight);
|
||||
|
||||
let afterTimeout: ReturnType<TransitionFunction>;
|
||||
if(!tabContent) {
|
||||
//prevTabContent.classList.remove('active');
|
||||
} else if(self.prevId != -1) {
|
||||
if(animationFunction) {
|
||||
animationFunction(tabContent, prevTabContent, toRight);
|
||||
afterTimeout = animationFunction(tabContent, prevTabContent, toRight);
|
||||
}
|
||||
|
||||
tabContent.classList.add('to');
|
||||
@ -83,10 +129,11 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
|
||||
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
|
||||
if(p/* && false */) {
|
||||
hideTimeouts[_prevId] = window.setTimeout(() => {
|
||||
p.style.transform = p.style.filter = '';
|
||||
p.classList.remove('active', 'from');
|
||||
if(afterTimeout) {
|
||||
afterTimeout();
|
||||
}
|
||||
|
||||
content.classList.remove('animating', 'backwards');
|
||||
p.classList.remove('active', 'from');
|
||||
|
||||
if(tabContent) {
|
||||
tabContent.classList.remove('to');
|
||||
@ -96,13 +143,16 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
|
||||
delete hideTimeouts[_prevId];
|
||||
}, transitionTime);
|
||||
|
||||
if(onTransitionEnd) {
|
||||
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
|
||||
transitionEndTimeout = window.setTimeout(() => {
|
||||
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
|
||||
transitionEndTimeout = window.setTimeout(() => {
|
||||
if(onTransitionEnd) {
|
||||
onTransitionEnd(self.prevId);
|
||||
transitionEndTimeout = 0;
|
||||
}, transitionTime);
|
||||
}
|
||||
}
|
||||
|
||||
content.classList.remove('animating', 'backwards');
|
||||
|
||||
transitionEndTimeout = 0;
|
||||
}, transitionTime);
|
||||
}
|
||||
|
||||
self.prevId = id;
|
||||
|
@ -526,7 +526,7 @@ export function wrapPhoto(photo: MyPhoto | MyDocument, message: any, container:
|
||||
|
||||
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {
|
||||
doc: MyDocument,
|
||||
div: HTMLDivElement,
|
||||
div: HTMLElement,
|
||||
middleware?: () => boolean,
|
||||
lazyLoadQueue?: LazyLoadQueue,
|
||||
group?: string,
|
||||
|
3516
src/layer.d.ts
vendored
3516
src/layer.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@ import PinnedContainer from '../../components/chat/pinnedContainer';
|
||||
import ReplyContainer from '../../components/chat/replyContainer';
|
||||
import { ChatSearch } from '../../components/chat/search';
|
||||
import ChatSelection from '../../components/chat/selection';
|
||||
import CheckboxField from '../../components/checkbox';
|
||||
import { horizontalMenu } from '../../components/horizontalMenu';
|
||||
import LazyLoadQueue from '../../components/lazyLoadQueue';
|
||||
import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc';
|
||||
@ -40,7 +39,7 @@ import apiManager from '../mtproto/mtprotoworker';
|
||||
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
import rootScope from '../rootScope';
|
||||
import { cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
|
||||
import { attachClickEvent, cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
|
||||
import apiUpdatesManager from './apiUpdatesManager';
|
||||
import appChatsManager, { Channel, Chat } from "./appChatsManager";
|
||||
import appDialogsManager from "./appDialogsManager";
|
||||
@ -54,6 +53,7 @@ import appProfileManager from "./appProfileManager";
|
||||
import appStickersManager from './appStickersManager';
|
||||
import appUsersManager from "./appUsersManager";
|
||||
import { months } from '../../helpers/date';
|
||||
import PinnedMessage from '../../components/chat/pinnedMessage';
|
||||
|
||||
|
||||
//console.log('appImManager included33!');
|
||||
@ -108,8 +108,7 @@ export class AppImManager {
|
||||
public offline = false;
|
||||
public updateStatusInterval = 0;
|
||||
|
||||
public pinnedMsgID = 0;
|
||||
private pinnedMessageContainer: PinnedContainer = null;
|
||||
public pinnedMessage: PinnedMessage;
|
||||
|
||||
public lazyLoadQueue = new LazyLoadQueue();
|
||||
|
||||
@ -136,7 +135,6 @@ export class AppImManager {
|
||||
public bubbleGroups = new BubbleGroups();
|
||||
|
||||
private scrolledDown = true;
|
||||
private onScrollRAF = 0;
|
||||
private isScrollingTimeout = 0;
|
||||
|
||||
private unreadedObserver: IntersectionObserver = null;
|
||||
@ -194,20 +192,13 @@ export class AppImManager {
|
||||
window.addEventListener('resize', () => this.setUtilsWidth(true));
|
||||
mediaSizes.addListener('changeScreen', (from, to) => {
|
||||
this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile);
|
||||
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile
|
||||
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
|
||||
this.pinnedMessage.onChangeScreen(from, to);
|
||||
this.setUtilsWidth(true);
|
||||
});
|
||||
|
||||
// * fix topbar overflow section end
|
||||
|
||||
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
|
||||
if(appPeersManager.canPinMessage(this.peerID)) {
|
||||
new PopupPinMessage(this.peerID, 0);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
this.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.btnJoin);
|
||||
this.pinnedMessage = new PinnedMessage(this, appMessagesManager);
|
||||
|
||||
// will call when message is sent (only 1)
|
||||
rootScope.on('history_append', (e) => {
|
||||
@ -387,17 +378,6 @@ export class AppImManager {
|
||||
const renderMaxID = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[groupID], 'asc').pop();
|
||||
this.renderMessage(appMessagesManager.getMessage(renderMaxID), true, false, this.bubbles[maxID], false);
|
||||
});
|
||||
|
||||
rootScope.on('peer_pinned_message', (e) => {
|
||||
const peerID = e.detail;
|
||||
|
||||
if(peerID == this.peerID) {
|
||||
const promise: Promise<any> = this.setPeerPromise || this.messagesQueuePromise || Promise.resolve();
|
||||
promise.then(() => {
|
||||
this.setPinnedMessage();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.on('messages_downloaded', (e) => {
|
||||
const mids: number[] = e.detail;
|
||||
@ -464,9 +444,13 @@ export class AppImManager {
|
||||
cancelEvent(e);
|
||||
|
||||
const mid = +pinned.dataset.mid;
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
if(pinned.classList.contains('pinned-message')) {
|
||||
this.pinnedMessage.followPinnedMessage(mid);
|
||||
} else {
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
|
||||
this.setPeer(message.peerID, mid);
|
||||
this.setPeer(message.peerID, mid);
|
||||
}
|
||||
} else {
|
||||
appSidebarRight.toggleSidebar(true);
|
||||
}
|
||||
@ -688,7 +672,7 @@ export class AppImManager {
|
||||
this.mutePeer(this.peerID);
|
||||
});
|
||||
|
||||
this.btnJoin.addEventListener(CLICK_EVENT_NAME, (e) => {
|
||||
attachClickEvent(this.btnJoin, (e) => {
|
||||
cancelEvent(e);
|
||||
|
||||
this.btnJoin.setAttribute('disabled', 'true');
|
||||
@ -696,7 +680,7 @@ export class AppImManager {
|
||||
this.btnJoin.removeAttribute('disabled');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.menuButtons.mute.addEventListener(CLICK_EVENT_NAME, (e) => {
|
||||
this.mutePeer(this.peerID);
|
||||
});
|
||||
@ -905,20 +889,6 @@ export class AppImManager {
|
||||
//console.log('got history date:', history);
|
||||
});
|
||||
};
|
||||
|
||||
public setPinnedMessage() {
|
||||
/////this.log('setting pinned message', message);
|
||||
//return;
|
||||
const message = appMessagesManager.getPinnedMessage(this.peerID);
|
||||
if(message && !message.deleted) {
|
||||
this.pinnedMessageContainer.fill('Pinned Message', message.message, message);
|
||||
this.pinnedMessageContainer.toggle(false);
|
||||
this.pinnedMsgID = message.mid;
|
||||
} else if(this.pinnedMsgID) {
|
||||
this.pinnedMsgID = 0;
|
||||
this.pinnedMessageContainer.toggle(true);
|
||||
}
|
||||
}
|
||||
|
||||
public updateStatus() {
|
||||
if(!this.myID) return Promise.resolve();
|
||||
@ -973,39 +943,34 @@ export class AppImManager {
|
||||
}
|
||||
}
|
||||
|
||||
public onScroll() {
|
||||
if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF);
|
||||
|
||||
public onScroll = () => {
|
||||
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
|
||||
if(this.scrollable.scrollLocked && this.scrolledDown) return;
|
||||
|
||||
this.onScrollRAF = window.requestAnimationFrame(() => {
|
||||
//lottieLoader.checkAnimations(false, 'chat');
|
||||
|
||||
if(!isTouchSupported) {
|
||||
if(this.isScrollingTimeout) {
|
||||
clearTimeout(this.isScrollingTimeout);
|
||||
} else if(!this.chatInner.classList.contains('is-scrolling')) {
|
||||
this.chatInner.classList.add('is-scrolling');
|
||||
}
|
||||
if(!isTouchSupported) {
|
||||
if(this.isScrollingTimeout) {
|
||||
clearTimeout(this.isScrollingTimeout);
|
||||
} else if(!this.chatInner.classList.contains('is-scrolling')) {
|
||||
this.chatInner.classList.add('is-scrolling');
|
||||
}
|
||||
|
||||
this.isScrollingTimeout = window.setTimeout(() => {
|
||||
this.chatInner.classList.remove('is-scrolling');
|
||||
this.isScrollingTimeout = 0;
|
||||
}, 1350);
|
||||
}
|
||||
|
||||
if(this.scrollable.isScrolledDown) {
|
||||
this.bubblesContainer.classList.add('scrolled-down');
|
||||
this.scrolledDown = true;
|
||||
} else if(this.bubblesContainer.classList.contains('scrolled-down')) {
|
||||
this.bubblesContainer.classList.remove('scrolled-down');
|
||||
this.scrolledDown = false;
|
||||
}
|
||||
this.isScrollingTimeout = window.setTimeout(() => {
|
||||
this.chatInner.classList.remove('is-scrolling');
|
||||
this.isScrollingTimeout = 0;
|
||||
}, 1350);
|
||||
}
|
||||
|
||||
if(this.scrollable.isScrolledDown) {
|
||||
this.bubblesContainer.classList.add('scrolled-down');
|
||||
this.scrolledDown = true;
|
||||
} else if(this.bubblesContainer.classList.contains('scrolled-down')) {
|
||||
this.bubblesContainer.classList.remove('scrolled-down');
|
||||
this.scrolledDown = false;
|
||||
}
|
||||
|
||||
this.onScrollRAF = 0;
|
||||
});
|
||||
}
|
||||
this.pinnedMessage.setCorrectIndex(this.scrollable.lastScrollDirection);
|
||||
};
|
||||
|
||||
public setScroll() {
|
||||
this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', 300);
|
||||
@ -1024,11 +989,11 @@ export class AppImManager {
|
||||
|
||||
this.bubblesContainer/* .firstElementChild */.append(this.goDownBtn);
|
||||
|
||||
this.scrollable.onAdditionalScroll = this.onScroll;
|
||||
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
|
||||
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
|
||||
//this.scrollable.attachSentinels(undefined, 300);
|
||||
|
||||
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
|
||||
this.bubblesContainer.classList.add('scrolled-down');
|
||||
|
||||
if(isTouchSupported) {
|
||||
@ -1465,7 +1430,7 @@ export class AppImManager {
|
||||
|
||||
this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : '';
|
||||
|
||||
this.setPinnedMessage();
|
||||
this.pinnedMessage.setPinnedMessage();
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
/* noTransition.forEach(el => {
|
||||
|
@ -72,7 +72,8 @@ type MyInputMessagesFilter = 'inputMessagesFilterEmpty'
|
||||
| 'inputMessagesFilterMusic'
|
||||
| 'inputMessagesFilterUrl'
|
||||
| 'inputMessagesFilterMyMentions'
|
||||
| 'inputMessagesFilterChatPhotos';
|
||||
| 'inputMessagesFilterChatPhotos'
|
||||
| 'inputMessagesFilterPinned';
|
||||
|
||||
export class AppMessagesManager {
|
||||
public messagesStorage: {[mid: string]: any} = {};
|
||||
@ -81,7 +82,10 @@ export class AppMessagesManager {
|
||||
public historiesStorage: {
|
||||
[peerID: string]: HistoryStorage
|
||||
} = {};
|
||||
public pinnedMessages: {[peerID: string]: number} = {};
|
||||
|
||||
// * mids - descend sorted
|
||||
public pinnedMessagesStorage: {[peerID: string]: Partial<{promise: Promise<number[]>, mids: number[]}>} = {};
|
||||
|
||||
public pendingByRandomID: {[randomID: string]: [number, number]} = {};
|
||||
public pendingByMessageID: any = {};
|
||||
public pendingAfterMsgs: any = {};
|
||||
@ -1797,11 +1801,11 @@ export class AppMessagesManager {
|
||||
});
|
||||
}
|
||||
|
||||
public savePinnedMessage(peerID: number, mid: number) {
|
||||
/* public savePinnedMessage(peerID: number, mid: number) {
|
||||
if(!mid) {
|
||||
delete this.pinnedMessages[peerID];
|
||||
delete this.pinnedMessagesStorage[peerID];
|
||||
} else {
|
||||
this.pinnedMessages[peerID] = mid;
|
||||
this.pinnedMessagesStorage[peerID] = mid;
|
||||
|
||||
if(!this.messagesStorage.hasOwnProperty(mid)) {
|
||||
this.wrapSingleMessage(mid).then(() => {
|
||||
@ -1813,18 +1817,38 @@ export class AppMessagesManager {
|
||||
}
|
||||
|
||||
rootScope.broadcast('peer_pinned_message', peerID);
|
||||
} */
|
||||
|
||||
public getPinnedMessagesStorage(peerID: number) {
|
||||
return this.pinnedMessagesStorage[peerID] ?? (this.pinnedMessagesStorage[peerID] = {});
|
||||
}
|
||||
|
||||
public getPinnedMessage(peerID: number) {
|
||||
return this.getMessage(this.pinnedMessages[peerID] || 0);
|
||||
public getPinnedMessages(peerID: number) {
|
||||
const storage = this.getPinnedMessagesStorage(peerID);
|
||||
if(storage.mids) {
|
||||
return Promise.resolve(storage.mids);
|
||||
} else if(storage.promise) {
|
||||
return storage.promise;
|
||||
}
|
||||
|
||||
return storage.promise = new Promise<number[]>((resolve, reject) => {
|
||||
this.getSearch(peerID, '', {_: 'inputMessagesFilterPinned'}, 0, 50).then(result => {
|
||||
resolve(storage.mids = result.history);
|
||||
}, reject);
|
||||
}).finally(() => {
|
||||
storage.promise = null;
|
||||
});
|
||||
}
|
||||
|
||||
public updatePinnedMessage(peerID: number, msgID: number) {
|
||||
public updatePinnedMessage(peerID: number, mid: number, unpin?: true, silent?: true, oneSide?: true) {
|
||||
apiManager.invokeApi('messages.updatePinnedMessage', {
|
||||
peer: appPeersManager.getInputPeerByID(peerID),
|
||||
id: msgID
|
||||
unpin,
|
||||
silent,
|
||||
pm_oneside: oneSide,
|
||||
id: mid
|
||||
}).then(updates => {
|
||||
/////this.log('pinned updates:', updates);
|
||||
this.log('pinned updates:', updates);
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
});
|
||||
}
|
||||
@ -2667,7 +2691,8 @@ export class AppMessagesManager {
|
||||
//this.log(dT(), 'search', useSearchCache, sameSearchCache, this.lastSearchResults, maxID);
|
||||
|
||||
if(peerID && !maxID && !query) {
|
||||
var historyStorage = this.historiesStorage[peerID];
|
||||
const historyStorage = this.historiesStorage[peerID];
|
||||
let filtering = true;
|
||||
|
||||
if(historyStorage !== undefined && historyStorage.history.length) {
|
||||
var neededContents: {
|
||||
@ -2729,49 +2754,53 @@ export class AppMessagesManager {
|
||||
break; */
|
||||
|
||||
default:
|
||||
return Promise.resolve({
|
||||
filtering = false;
|
||||
break;
|
||||
/* return Promise.resolve({
|
||||
count: 0,
|
||||
next_rate: 0,
|
||||
history: [] as number[]
|
||||
});
|
||||
}); */
|
||||
}
|
||||
|
||||
for(let i = 0, length = historyStorage.history.length; i < length; i++) {
|
||||
const message = this.messagesStorage[historyStorage.history[i]];
|
||||
|
||||
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ == 'messageEntityMention'));
|
||||
|
||||
let found = false;
|
||||
if(message.media && neededContents[message.media._] && !message.fwd_from) {
|
||||
if(message.media._ == 'messageMediaDocument') {
|
||||
if((neededDocTypes.length && !neededDocTypes.includes(message.media.document.type))
|
||||
|| excludeDocTypes.includes(message.media.document.type)) {
|
||||
continue;
|
||||
if(filtering) {
|
||||
for(let i = 0, length = historyStorage.history.length; i < length; i++) {
|
||||
const message = this.messagesStorage[historyStorage.history[i]];
|
||||
|
||||
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ == 'messageEntityMention'));
|
||||
|
||||
let found = false;
|
||||
if(message.media && neededContents[message.media._] && !message.fwd_from) {
|
||||
if(message.media._ == 'messageMediaDocument') {
|
||||
if((neededDocTypes.length && !neededDocTypes.includes(message.media.document.type))
|
||||
|| excludeDocTypes.includes(message.media.document.type)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
found = true;
|
||||
} else if(neededContents['url'] && message.message) {
|
||||
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl'];
|
||||
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) {
|
||||
|
||||
found = true;
|
||||
} else if(neededContents['url'] && message.message) {
|
||||
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl'];
|
||||
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) {
|
||||
found = true;
|
||||
}
|
||||
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) {
|
||||
found = true;
|
||||
}
|
||||
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
if(found) {
|
||||
foundMsgs.push(message.mid);
|
||||
if(foundMsgs.length >= limit) {
|
||||
break;
|
||||
|
||||
if(found) {
|
||||
foundMsgs.push(message.mid);
|
||||
if(foundMsgs.length >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this.log.warn(dT(), 'before append', foundMsgs)
|
||||
if(foundMsgs.length < limit && this.lastSearchResults.length && sameSearchCache) {
|
||||
var minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : false;
|
||||
if(filtering && foundMsgs.length < limit && this.lastSearchResults.length && sameSearchCache) {
|
||||
let minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : false;
|
||||
for(let i = 0; i < this.lastSearchResults.length; i++) {
|
||||
if(minID === false || this.lastSearchResults[i] < minID) {
|
||||
foundMsgs.push(this.lastSearchResults[i]);
|
||||
@ -3435,19 +3464,19 @@ export class AppMessagesManager {
|
||||
});
|
||||
|
||||
const groupID = (message as Message.message).grouped_id;
|
||||
if(this.pinnedMessages[peerID]) {
|
||||
/* if(this.pinnedMessagesStorage[peerID]) {
|
||||
let pinnedMid: number;
|
||||
if(groupID) {
|
||||
const mids = this.getMidsByAlbum(groupID);
|
||||
pinnedMid = mids.find(mid => this.pinnedMessages[peerID] == mid);
|
||||
} else if(this.pinnedMessages[peerID] == mid) {
|
||||
pinnedMid = mids.find(mid => this.pinnedMessagesStorage[peerID] == mid);
|
||||
} else if(this.pinnedMessagesStorage[peerID] == mid) {
|
||||
pinnedMid = mid;
|
||||
}
|
||||
|
||||
if(pinnedMid) {
|
||||
rootScope.broadcast('peer_pinned_message', peerID);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
if(isTopMessage || groupID) {
|
||||
const updatedDialogs: {[peerID: number]: Dialog} = {};
|
||||
@ -3627,9 +3656,9 @@ export class AppMessagesManager {
|
||||
}
|
||||
}
|
||||
|
||||
if(this.pinnedMessages[peerID] == mid) {
|
||||
/* if(this.pinnedMessagesStorage[peerID] == mid) {
|
||||
this.savePinnedMessage(peerID, 0);
|
||||
}
|
||||
} */
|
||||
|
||||
const peerMessagesToHandle = this.newMessagesToHandle[peerID];
|
||||
if(peerMessagesToHandle && peerMessagesToHandle.length) {
|
||||
@ -3791,13 +3820,41 @@ export class AppMessagesManager {
|
||||
break;
|
||||
}
|
||||
|
||||
// 'updateChannelPinnedMessage' will be handled by appProfileManager
|
||||
case 'updateChatPinnedMessage':
|
||||
case 'updateUserPinnedMessage': {
|
||||
// hz nado li tut appMessagesIDsManager.getFullMessageID(update.max_id, channelID);
|
||||
const peerID = appPeersManager.getPeerID(update);
|
||||
this.savePinnedMessage(peerID, update.id);
|
||||
|
||||
case 'updatePinnedMessages':
|
||||
case 'updatePinnedChannelMessages': {
|
||||
const channelID = update._ == 'updatePinnedChannelMessages' ? update.channel_id : undefined;
|
||||
const peerID = channelID ? -channelID : appPeersManager.getPeerID((update as Update.updatePinnedMessages).peer);
|
||||
|
||||
const storage = this.getPinnedMessagesStorage(peerID);
|
||||
if(!storage.mids) {
|
||||
break;
|
||||
}
|
||||
|
||||
const messages = channelID ? update.messages.map(messageID => appMessagesIDsManager.getFullMessageID(messageID, channelID)) : update.messages;
|
||||
|
||||
const missingMessages = messages.filter(mid => !this.messagesStorage[mid]);
|
||||
const getMissingPromise = missingMessages.length ? Promise.all(missingMessages.map(mid => this.wrapSingleMessage(mid))) : Promise.resolve();
|
||||
getMissingPromise.finally(() => {
|
||||
const werePinned = update.pFlags?.pinned;
|
||||
if(werePinned) {
|
||||
for(const mid of messages) {
|
||||
storage.mids.push(mid);
|
||||
const message = this.messagesStorage[mid];
|
||||
message.pFlags.pinned = true;
|
||||
}
|
||||
|
||||
storage.mids.sort((a, b) => b - a);
|
||||
} else {
|
||||
for(const mid of messages) {
|
||||
storage.mids.findAndSplice(_mid => _mid == mid);
|
||||
const message = this.messagesStorage[mid];
|
||||
delete message.pFlags.pinned;
|
||||
}
|
||||
}
|
||||
|
||||
rootScope.broadcast('peer_pinned_messages', peerID);
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -10,7 +10,7 @@ type BroadcastEvents = {
|
||||
'user_update': number,
|
||||
'user_auth': UserAuth,
|
||||
'peer_changed': number,
|
||||
'peer_pinned_message': number,
|
||||
'peer_pinned_messages': number,
|
||||
|
||||
'filter_delete': MyDialogFilter,
|
||||
'filter_update': MyDialogFilter,
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -686,269 +686,6 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-message, .reply {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin-right: 1rem;
|
||||
max-height: 35px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
/* padding: .25rem; */
|
||||
|
||||
&.is-media {
|
||||
.pinned-message-content, .reply-content {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.emoji:first-child {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-border {
|
||||
height: 2rem;
|
||||
border-radius: 1px;
|
||||
min-width: 2px;
|
||||
background: $color-blue;
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin-left: 8px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
&-title, &-subtitle {
|
||||
font-size: 14px;
|
||||
//line-height: 18px;
|
||||
//line-height: 1;
|
||||
//line-height: 15px;
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
// @include respond-to(handhelds) {
|
||||
// line-height: 13px;
|
||||
// }
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
&-media {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border-radius: .5rem;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
|
||||
/* > img, > video {
|
||||
|
||||
} */
|
||||
|
||||
// sticker
|
||||
/* > img {
|
||||
object-fit: contain !important;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
} */
|
||||
> img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
//color: $color-blue;
|
||||
color: #707579;
|
||||
|
||||
/* &.document-title {
|
||||
color: #707579;
|
||||
} */
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* span.emoji {
|
||||
font-size: 1rem;
|
||||
vertical-align: unset;
|
||||
} */
|
||||
}
|
||||
|
||||
.reply {
|
||||
html.no-touch &:hover {
|
||||
background-color: var(--color-gray-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-container {
|
||||
flex: 0 0 auto;
|
||||
overflow: visible;
|
||||
|
||||
&.is-floating {
|
||||
position: absolute !important;
|
||||
top: 100%;
|
||||
width: 100% !important;
|
||||
background: #fff !important;
|
||||
left: 0;
|
||||
max-height: 100% !important;
|
||||
height: 52px;
|
||||
|
||||
.pinned-container-close {
|
||||
position: absolute;
|
||||
font-size: 1.4rem;
|
||||
right: 9px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pinned-container-wrapper {
|
||||
padding: 0 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, .15);
|
||||
|
||||
&:before {
|
||||
width: 100%;
|
||||
content: " ";
|
||||
height: 52px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
/* box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, .15); */
|
||||
box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-close, .pinned-audio-ico {
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
padding: .25rem;
|
||||
border-radius: 4px;
|
||||
|
||||
html.no-touch &:hover {
|
||||
background-color: var(--color-gray-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-message {
|
||||
display: none;
|
||||
width: auto;
|
||||
|
||||
&:not(.is-floating) {
|
||||
max-width: 239px;
|
||||
|
||||
.pinned-message-close {
|
||||
display: flex;
|
||||
margin-right: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-floating .pinned-message-subtitle {
|
||||
max-width: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-audio {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
//width: 210px;
|
||||
|
||||
&:not(.is-floating) {
|
||||
padding-right: 1.75rem;
|
||||
max-width: 210px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.is-floating .pinned-audio-ico {
|
||||
margin-left: -.25rem;
|
||||
}
|
||||
|
||||
&-ico {
|
||||
color: #50a2e9;
|
||||
margin-right: .375rem;
|
||||
|
||||
&:before {
|
||||
content: $tgico-largeplay;
|
||||
}
|
||||
|
||||
&.flip-icon:before {
|
||||
content: $tgico-largepause;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
color: #707579;
|
||||
}
|
||||
|
||||
&-title, &-subtitle {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
max-width: 240px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-message {
|
||||
|
383
src/scss/partials/_chatPinned.scss
Normal file
383
src/scss/partials/_chatPinned.scss
Normal file
@ -0,0 +1,383 @@
|
||||
.pinned-message {
|
||||
&-border {
|
||||
position: relative;
|
||||
height: 35px;
|
||||
width: 2px;
|
||||
padding: 1.5px 0;
|
||||
//padding: 0 0 6px 6px;
|
||||
//overflow: hidden;
|
||||
|
||||
&-wrapper-1 {
|
||||
height: 32px;
|
||||
width: 2px;
|
||||
border-radius: 1px;
|
||||
background: $color-blue;
|
||||
}
|
||||
|
||||
&-mask {
|
||||
mask-image: linear-gradient(to bottom, transparent 0, black 6px, black 38px, transparent 44px);
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
color: $color-blue;
|
||||
background: #50a2e988;
|
||||
position: relative;
|
||||
will-change: transform;
|
||||
transition: transform 0.25s ease-in-out;
|
||||
}
|
||||
|
||||
&-bars {
|
||||
stroke: currentColor;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
&-mark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
background: currentColor;
|
||||
border-radius: 1px;
|
||||
will-change: transform;
|
||||
transition: transform 0.25s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-message, .reply {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin-right: 1rem;
|
||||
max-height: 35px;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
/* padding: .25rem; */
|
||||
|
||||
&.is-media {
|
||||
.emoji:first-child {
|
||||
margin-right: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
margin-left: 8px;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
position: relative;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
&-title, &-subtitle {
|
||||
font-size: 14px;
|
||||
//line-height: 18px;
|
||||
//line-height: 1;
|
||||
//line-height: 15px;
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
// @include respond-to(handhelds) {
|
||||
// line-height: 13px;
|
||||
// }
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
&-media {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
border-radius: .5rem;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
|
||||
/* > img, > video {
|
||||
|
||||
} */
|
||||
|
||||
// sticker
|
||||
/* > img {
|
||||
object-fit: contain !important;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
} */
|
||||
> img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
font-style: normal;
|
||||
//color: $color-blue;
|
||||
color: #707579;
|
||||
|
||||
/* &.document-title {
|
||||
color: #707579;
|
||||
} */
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* span.emoji {
|
||||
font-size: 1rem;
|
||||
vertical-align: unset;
|
||||
} */
|
||||
}
|
||||
|
||||
.reply {
|
||||
&.is-media {
|
||||
.reply-content {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
html.no-touch &:hover {
|
||||
background-color: var(--color-gray-hover);
|
||||
}
|
||||
|
||||
&-border {
|
||||
height: 2rem;
|
||||
border-radius: 1px;
|
||||
min-width: 2px;
|
||||
background: $color-blue;
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-container {
|
||||
flex: 0 0 auto;
|
||||
overflow: visible;
|
||||
|
||||
&.is-floating {
|
||||
position: absolute !important;
|
||||
top: 100%;
|
||||
width: 100% !important;
|
||||
background: #fff !important;
|
||||
left: 0;
|
||||
max-height: 100% !important;
|
||||
height: 52px;
|
||||
|
||||
.pinned-container-close {
|
||||
position: absolute;
|
||||
font-size: 1.4rem;
|
||||
right: 9px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.pinned-container-wrapper {
|
||||
padding: 0 1rem;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, .15);
|
||||
|
||||
&:before {
|
||||
width: 100%;
|
||||
content: " ";
|
||||
height: 52px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
/* box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, .15); */
|
||||
box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&-close, .pinned-audio-ico {
|
||||
font-size: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&-close {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
padding: .25rem;
|
||||
border-radius: 4px;
|
||||
|
||||
html.no-touch &:hover {
|
||||
background-color: var(--color-gray-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-message {
|
||||
display: none;
|
||||
width: auto;
|
||||
|
||||
&-media-container {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
position: absolute;
|
||||
transition: transform var(--pm-transition)/* , opacity var(--pm-transition) */;
|
||||
}
|
||||
|
||||
/* &-media {
|
||||
transform: none !important;
|
||||
|
||||
&.is-hiding {
|
||||
&.backwards {
|
||||
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
// * fix blink in safari, can't add translateX from nowhere...
|
||||
&-title, &-subtitle {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
&.is-media {
|
||||
.pinned-message-title, .pinned-message-subtitle {
|
||||
transform: translateX(2.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-media) &-media-container {
|
||||
transform: scale(0);
|
||||
//opacity: 0;
|
||||
}
|
||||
|
||||
&:not(.is-floating) {
|
||||
width: 15.5rem;
|
||||
|
||||
.pinned-message-close {
|
||||
display: flex;
|
||||
margin-right: .75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-floating .pinned-message-subtitle {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
&-content {
|
||||
> .pinned-message-title, > .pinned-message-subtitle {
|
||||
position: relative;
|
||||
height: 50%;
|
||||
overflow: visible;
|
||||
transition: transform var(--pm-transition);
|
||||
}
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
.animated-super-row {
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.animated-counter {
|
||||
transition: transform var(--pm-transition), opacity var(--pm-transition);
|
||||
|
||||
&:before {
|
||||
content: "#";
|
||||
}
|
||||
|
||||
&.is-last {
|
||||
&:not(.backwards) {
|
||||
transform: scale(0.68);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* .animated-super-row {
|
||||
transition: none !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pinned-audio {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
//width: 210px;
|
||||
|
||||
&:not(.is-floating) {
|
||||
padding-right: 1.75rem;
|
||||
max-width: 210px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.is-floating .pinned-audio-ico {
|
||||
margin-left: -.25rem;
|
||||
}
|
||||
|
||||
&-ico {
|
||||
color: #50a2e9;
|
||||
margin-right: .375rem;
|
||||
|
||||
&:before {
|
||||
content: $tgico-largeplay;
|
||||
}
|
||||
|
||||
&.flip-icon:before {
|
||||
content: $tgico-largepause;
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
font-weight: 500;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
color: #707579;
|
||||
}
|
||||
|
||||
&-title, &-subtitle {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
overflow: hidden;
|
||||
max-width: 240px;
|
||||
}
|
||||
}
|
@ -74,7 +74,7 @@
|
||||
overflow: visible;
|
||||
|
||||
i {
|
||||
bottom: calc(-.625rem - -2px);
|
||||
bottom: calc(-.625rem - -1.75px); // * 1.75px will fix for high DPR
|
||||
padding-right: 1rem !important;
|
||||
margin-left: -.5rem !important;
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ $messages-container-width: 728px;
|
||||
--z-below: -1;
|
||||
--color-gray: #c4c9cc;
|
||||
--color-gray-hover: rgba(112, 117, 121, .08);
|
||||
--pm-transition: .2s ease-in-out;
|
||||
--layer-transition: .2s ease-in-out;
|
||||
//--layer-transition: .3s cubic-bezier(.33, 1, .68, 1);
|
||||
//--layer-transition: none;
|
||||
@ -108,6 +109,7 @@ $messages-container-width: 728px;
|
||||
@import "partials/chatlist";
|
||||
@import "partials/chat";
|
||||
@import "partials/chatBubble";
|
||||
@import "partials/chatPinned";
|
||||
@import "partials/sidebar";
|
||||
@import "partials/leftSidebar";
|
||||
@import "partials/rightSidebar";
|
||||
@ -804,6 +806,78 @@ img.emoji {
|
||||
}
|
||||
}
|
||||
|
||||
.animated-super {
|
||||
&-row {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: transform var(--pm-transition), opacity var(--pm-transition);
|
||||
|
||||
&.is-hiding {
|
||||
opacity: 0;
|
||||
|
||||
&.from-top {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
&.from-bottom {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
/* &.backwards {
|
||||
opacity: 1;
|
||||
transform: translateY(0) !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.animated-counter {
|
||||
display: inline-flex;
|
||||
|
||||
&-decimal {
|
||||
position: relative;
|
||||
|
||||
&-placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
&-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* &:not(:first-child) {
|
||||
.animated-super {
|
||||
&-row {
|
||||
&.is-hiding {
|
||||
&.from-top {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
&.from-bottom {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
/* &.from-top {
|
||||
.animated-super-row.is-hiding {
|
||||
&.from-top {
|
||||
transform: translateY(100%) !important;
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
.transition {
|
||||
.transition-item {
|
||||
position: absolute;
|
||||
|
Loading…
x
Reference in New Issue
Block a user