Eduard Kuzmenko
3 years ago
52 changed files with 2358 additions and 315 deletions
@ -0,0 +1,185 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import callbackify from "../../helpers/callbackify"; |
||||||
|
import { formatNumber } from "../../helpers/number"; |
||||||
|
import { MessageUserReaction, ReactionCount } from "../../layer"; |
||||||
|
import appReactionsManager from "../../lib/appManagers/appReactionsManager"; |
||||||
|
import RLottiePlayer from "../../lib/rlottie/rlottiePlayer"; |
||||||
|
import SetTransition from "../singleTransition"; |
||||||
|
import StackedAvatars from "../stackedAvatars"; |
||||||
|
import { wrapSticker, wrapStickerAnimation } from "../wrappers"; |
||||||
|
|
||||||
|
const CLASS_NAME = 'reaction'; |
||||||
|
const TAG_NAME = CLASS_NAME + '-element'; |
||||||
|
const REACTION_INLINE_SIZE = 14; |
||||||
|
const REACTION_BLOCK_SIZE = 16; |
||||||
|
|
||||||
|
export const REACTION_DISPLAY_INLINE_COUNTER_AT = 2; |
||||||
|
export const REACTION_DISPLAY_BLOCK_COUNTER_AT = 4; |
||||||
|
|
||||||
|
export type ReactionLayoutType = 'block' | 'inline'; |
||||||
|
|
||||||
|
export default class ReactionElement extends HTMLElement { |
||||||
|
private type: ReactionLayoutType; |
||||||
|
private counter: HTMLElement; |
||||||
|
private stickerContainer: HTMLElement; |
||||||
|
private stackedAvatars: StackedAvatars; |
||||||
|
private canRenderAvatars: boolean; |
||||||
|
private _reactionCount: ReactionCount; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
this.classList.add(CLASS_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
public get reactionCount() { |
||||||
|
return this._reactionCount; |
||||||
|
} |
||||||
|
|
||||||
|
public set reactionCount(reactionCount: ReactionCount) { |
||||||
|
this._reactionCount = reactionCount; |
||||||
|
} |
||||||
|
|
||||||
|
public get count() { |
||||||
|
return this.reactionCount.count; |
||||||
|
} |
||||||
|
|
||||||
|
public init(type: ReactionLayoutType) { |
||||||
|
this.type = type; |
||||||
|
this.classList.add(CLASS_NAME + '-' + type); |
||||||
|
} |
||||||
|
|
||||||
|
public setCanRenderAvatars(canRenderAvatars: boolean) { |
||||||
|
this.canRenderAvatars = canRenderAvatars; |
||||||
|
} |
||||||
|
|
||||||
|
public render(doNotRenderSticker?: boolean) { |
||||||
|
const hadStickerContainer = !!this.stickerContainer; |
||||||
|
if(!hadStickerContainer) { |
||||||
|
this.stickerContainer = document.createElement('div'); |
||||||
|
this.stickerContainer.classList.add(CLASS_NAME + '-sticker'); |
||||||
|
this.append(this.stickerContainer); |
||||||
|
} |
||||||
|
|
||||||
|
const reactionCount = this.reactionCount; |
||||||
|
if(!doNotRenderSticker && !hadStickerContainer) { |
||||||
|
const availableReaction = appReactionsManager.getReaction(reactionCount.reaction); |
||||||
|
callbackify(availableReaction, (availableReaction) => { |
||||||
|
const size = this.type === 'inline' ? REACTION_INLINE_SIZE : REACTION_BLOCK_SIZE; |
||||||
|
wrapSticker({ |
||||||
|
div: this.stickerContainer, |
||||||
|
doc: availableReaction.static_icon, |
||||||
|
width: size, |
||||||
|
height: size |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public renderCounter() { |
||||||
|
const reactionCount = this.reactionCount; |
||||||
|
const displayOn = this.type === 'inline' ? REACTION_DISPLAY_INLINE_COUNTER_AT : REACTION_DISPLAY_BLOCK_COUNTER_AT; |
||||||
|
if(reactionCount.count >= displayOn || (this.type === 'block' && !this.canRenderAvatars)) { |
||||||
|
if(!this.counter) { |
||||||
|
this.counter = document.createElement(this.type === 'inline' ? 'i' : 'span'); |
||||||
|
this.counter.classList.add(CLASS_NAME + '-counter'); |
||||||
|
} |
||||||
|
|
||||||
|
const formatted = formatNumber(reactionCount.count); |
||||||
|
if(this.counter.textContent !== formatted) { |
||||||
|
this.counter.textContent = formatted; |
||||||
|
} |
||||||
|
|
||||||
|
if(!this.counter.parentElement) { |
||||||
|
this.append(this.counter); |
||||||
|
} |
||||||
|
} else if(this.counter?.parentElement) { |
||||||
|
this.counter.remove(); |
||||||
|
this.counter = undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public renderAvatars(recentReactions: MessageUserReaction[]) { |
||||||
|
if(this.type === 'inline') { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if(this.reactionCount.count >= REACTION_DISPLAY_BLOCK_COUNTER_AT || !this.canRenderAvatars) { |
||||||
|
if(this.stackedAvatars) { |
||||||
|
this.stackedAvatars.container.remove(); |
||||||
|
this.stackedAvatars = undefined; |
||||||
|
} |
||||||
|
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if(!this.stackedAvatars) { |
||||||
|
this.stackedAvatars = new StackedAvatars({ |
||||||
|
avatarSize: 16 |
||||||
|
}); |
||||||
|
|
||||||
|
this.append(this.stackedAvatars.container); |
||||||
|
} |
||||||
|
|
||||||
|
this.stackedAvatars.render(recentReactions.map(reaction => reaction.user_id.toPeerId())); |
||||||
|
} |
||||||
|
|
||||||
|
public setIsChosen(isChosen = !!this.reactionCount.pFlags.chosen) { |
||||||
|
if(this.type === 'inline') return; |
||||||
|
const wasChosen = this.classList.contains('is-chosen') && !this.classList.contains('backwards'); |
||||||
|
if(wasChosen !== isChosen) { |
||||||
|
SetTransition(this, 'is-chosen', isChosen, this.isConnected ? 300 : 0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public fireAroundAnimation() { |
||||||
|
callbackify(appReactionsManager.getReaction(this.reactionCount.reaction), (availableReaction) => { |
||||||
|
const size = (this.type === 'inline' ? REACTION_INLINE_SIZE : REACTION_BLOCK_SIZE) + 14; |
||||||
|
const div = document.createElement('div'); |
||||||
|
div.classList.add(CLASS_NAME + '-sticker-activate'); |
||||||
|
|
||||||
|
Promise.all([ |
||||||
|
wrapSticker({ |
||||||
|
div: div, |
||||||
|
doc: availableReaction.center_icon, |
||||||
|
width: size, |
||||||
|
height: size, |
||||||
|
withThumb: false, |
||||||
|
needUpscale: true, |
||||||
|
play: false, |
||||||
|
skipRatio: 1, |
||||||
|
group: 'none', |
||||||
|
needFadeIn: false |
||||||
|
}) as Promise<RLottiePlayer>, |
||||||
|
|
||||||
|
wrapStickerAnimation({ |
||||||
|
doc: availableReaction.around_animation, |
||||||
|
size: 80, |
||||||
|
target: this.stickerContainer, |
||||||
|
side: 'center', |
||||||
|
skipRatio: 1, |
||||||
|
play: false |
||||||
|
}).stickerPromise |
||||||
|
]).then(([activatePlayer, aroundPlayer]) => { |
||||||
|
activatePlayer.addEventListener('enterFrame', (frameNo) => { |
||||||
|
if(frameNo === activatePlayer.maxFrame) { |
||||||
|
activatePlayer.remove(); |
||||||
|
div.remove(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
activatePlayer.addEventListener('firstFrame', () => { |
||||||
|
this.stickerContainer.prepend(div); |
||||||
|
activatePlayer.play(); |
||||||
|
aroundPlayer.play(); |
||||||
|
}, {once: true}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
customElements.define(TAG_NAME, ReactionElement); |
@ -0,0 +1,198 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import { forEachReverse } from "../../helpers/array"; |
||||||
|
import positionElementByIndex from "../../helpers/dom/positionElementByIndex"; |
||||||
|
import { Message, ReactionCount } from "../../layer"; |
||||||
|
import appReactionsManager from "../../lib/appManagers/appReactionsManager"; |
||||||
|
import rootScope from "../../lib/rootScope"; |
||||||
|
import ReactionElement, { ReactionLayoutType, REACTION_DISPLAY_BLOCK_COUNTER_AT } from "./reaction"; |
||||||
|
|
||||||
|
const CLASS_NAME = 'reactions'; |
||||||
|
const TAG_NAME = CLASS_NAME + '-element'; |
||||||
|
|
||||||
|
const elements: Map<string, Set<ReactionsElement>> = new Map(); |
||||||
|
rootScope.addEventListener('message_reactions', ({message, changedResults}) => { |
||||||
|
const key = message.peerId + '_' + message.mid; |
||||||
|
const set = elements.get(key); |
||||||
|
if(!set) { |
||||||
|
rootScope.dispatchEvent('missed_reactions_element', {message, changedResults}); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
for(const element of set) { |
||||||
|
element.update(message, changedResults); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
export default class ReactionsElement extends HTMLElement { |
||||||
|
private message: Message.message; |
||||||
|
private key: string; |
||||||
|
private isPlaceholder: boolean; |
||||||
|
private type: ReactionLayoutType; |
||||||
|
private sorted: ReactionElement[]; |
||||||
|
private onConnectCallback: () => void; |
||||||
|
|
||||||
|
constructor() { |
||||||
|
super(); |
||||||
|
this.classList.add(CLASS_NAME); |
||||||
|
this.sorted = []; |
||||||
|
} |
||||||
|
|
||||||
|
connectedCallback() { |
||||||
|
let set = elements.get(this.key); |
||||||
|
if(!set) { |
||||||
|
elements.set(this.key, set = new Set()); |
||||||
|
} |
||||||
|
|
||||||
|
set.add(this); |
||||||
|
|
||||||
|
if(this.onConnectCallback && this.isConnected) { |
||||||
|
this.onConnectCallback(); |
||||||
|
this.onConnectCallback = undefined; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
disconnectedCallback() { |
||||||
|
const set = elements.get(this.key); |
||||||
|
set.delete(this); |
||||||
|
if(!set.size) { |
||||||
|
elements.delete(this.key); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public getReactionCount(reactionElement: ReactionElement) { |
||||||
|
return this.sorted[this.sorted.indexOf(reactionElement)].reactionCount; |
||||||
|
} |
||||||
|
|
||||||
|
public getMessage() { |
||||||
|
return this.message; |
||||||
|
} |
||||||
|
|
||||||
|
public init(message: Message.message, type: ReactionLayoutType, isPlaceholder?: boolean) { |
||||||
|
if(this.key !== undefined) { |
||||||
|
this.disconnectedCallback(); |
||||||
|
} |
||||||
|
|
||||||
|
this.message = message; |
||||||
|
this.key = this.message.peerId + '_' + this.message.mid; |
||||||
|
this.isPlaceholder = isPlaceholder; |
||||||
|
|
||||||
|
if(this.type !== type) { |
||||||
|
this.type = type; |
||||||
|
this.classList.add(CLASS_NAME + '-' + type); |
||||||
|
} |
||||||
|
|
||||||
|
this.connectedCallback(); |
||||||
|
} |
||||||
|
|
||||||
|
public changeMessage(message: Message.message) { |
||||||
|
return this.init(message, this.type, this.isPlaceholder); |
||||||
|
} |
||||||
|
|
||||||
|
public update(message: Message.message, changedResults?: ReactionCount[]) { |
||||||
|
this.message = message; |
||||||
|
this.render(changedResults); |
||||||
|
} |
||||||
|
|
||||||
|
public render(changedResults?: ReactionCount[]) { |
||||||
|
const reactions = this.message.reactions; |
||||||
|
const hasReactions = !!(reactions && reactions.results.length); |
||||||
|
this.classList.toggle('has-no-reactions', !hasReactions); |
||||||
|
if(!hasReactions && !this.sorted.length) return; |
||||||
|
|
||||||
|
const availableReactionsResult = appReactionsManager.getAvailableReactions(); |
||||||
|
// callbackify(availableReactionsResult, () => {
|
||||||
|
const counts = hasReactions ? ( |
||||||
|
availableReactionsResult instanceof Promise ? |
||||||
|
reactions.results : |
||||||
|
reactions.results.filter(reactionCount => { |
||||||
|
return appReactionsManager.isReactionActive(reactionCount.reaction); |
||||||
|
}) |
||||||
|
) : []; |
||||||
|
|
||||||
|
forEachReverse(this.sorted, (reactionElement, idx, arr) => { |
||||||
|
const reaction = reactionElement.reactionCount.reaction; |
||||||
|
const found = counts.some(reactionCount => reactionCount.reaction === reaction); |
||||||
|
if(!found) { |
||||||
|
arr.splice(idx, 1); |
||||||
|
reactionElement.remove(); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const totalReactions = counts.reduce((acc, c) => acc + c.count, 0); |
||||||
|
const canRenderAvatars = reactions && !!reactions.pFlags.can_see_list && totalReactions < REACTION_DISPLAY_BLOCK_COUNTER_AT; |
||||||
|
this.sorted = counts.map((reactionCount, idx) => { |
||||||
|
const reactionElementIdx = this.sorted.findIndex(reactionElement => reactionElement.reactionCount.reaction === reactionCount.reaction); |
||||||
|
let reactionElement = reactionElementIdx !== -1 && this.sorted[reactionElementIdx]; |
||||||
|
if(!reactionElement) { |
||||||
|
reactionElement = new ReactionElement(); |
||||||
|
reactionElement.init(this.type); |
||||||
|
} |
||||||
|
|
||||||
|
positionElementByIndex(reactionElement, this, idx); |
||||||
|
|
||||||
|
const recentReactions = reactions.recent_reactions ? reactions.recent_reactions.filter(reaction => reaction.reaction === reactionCount.reaction) : []; |
||||||
|
reactionElement.reactionCount = {...reactionCount}; |
||||||
|
reactionElement.setCanRenderAvatars(canRenderAvatars); |
||||||
|
reactionElement.render(this.isPlaceholder); |
||||||
|
reactionElement.renderCounter(); |
||||||
|
reactionElement.renderAvatars(recentReactions); |
||||||
|
reactionElement.setIsChosen(); |
||||||
|
|
||||||
|
return reactionElement; |
||||||
|
}); |
||||||
|
|
||||||
|
// this.sorted.forEach((reactionElement, idx) => {
|
||||||
|
// /* if(this.type === 'block' && this.childElementCount !== this.sorted.length) { // because of appended time
|
||||||
|
// idx += 1;
|
||||||
|
// } */
|
||||||
|
|
||||||
|
// positionElementByIndex(reactionElement, this, idx);
|
||||||
|
// });
|
||||||
|
|
||||||
|
if(!this.isPlaceholder && changedResults?.length) { |
||||||
|
if(this.isConnected) { |
||||||
|
this.handleChangedResults(changedResults); |
||||||
|
} else { |
||||||
|
this.onConnectCallback = () => { |
||||||
|
this.handleChangedResults(changedResults); |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
// });
|
||||||
|
|
||||||
|
// ! тут вообще не должно быть этого кода, но пока он побудет тут
|
||||||
|
if(!this.sorted.length && this.type === 'block') { |
||||||
|
const parentElement = this.parentElement; |
||||||
|
this.remove(); |
||||||
|
|
||||||
|
if(parentElement.classList.contains('document-message') && !parentElement.childNodes.length) { |
||||||
|
parentElement.remove(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
const timeSpan = this.querySelector('.time'); |
||||||
|
if(timeSpan) { |
||||||
|
parentElement.append(timeSpan); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private handleChangedResults(changedResults: ReactionCount[]) { |
||||||
|
// ! temp
|
||||||
|
if(this.message.peerId !== rootScope.peerId) return; |
||||||
|
|
||||||
|
changedResults.forEach(reactionCount => { |
||||||
|
const reactionElement = this.sorted.find(reactionElement => reactionElement.reactionCount.reaction === reactionCount.reaction); |
||||||
|
if(reactionElement) { |
||||||
|
reactionElement.fireAroundAnimation(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
customElements.define(TAG_NAME, ReactionsElement); |
@ -0,0 +1,220 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; |
||||||
|
import PopupElement from "."; |
||||||
|
import { Message } from "../../layer"; |
||||||
|
import { generateDelimiter, SettingSection } from "../sidebarLeft"; |
||||||
|
import ReactionsElement from "../chat/reactions"; |
||||||
|
import { horizontalMenu } from "../horizontalMenu"; |
||||||
|
import Scrollable from "../scrollable"; |
||||||
|
import ScrollableLoader from "../../helpers/scrollableLoader"; |
||||||
|
import appDialogsManager from "../../lib/appManagers/appDialogsManager"; |
||||||
|
import replaceContent from "../../helpers/dom/replaceContent"; |
||||||
|
import appUsersManager from "../../lib/appManagers/appUsersManager"; |
||||||
|
import appReactionsManager from "../../lib/appManagers/appReactionsManager"; |
||||||
|
import { wrapSticker } from "../wrappers"; |
||||||
|
import ReactionElement from "../chat/reaction"; |
||||||
|
|
||||||
|
export default class PopupReactedList extends PopupElement { |
||||||
|
constructor( |
||||||
|
private appMessagesManager: AppMessagesManager, |
||||||
|
private message: Message.message |
||||||
|
) { |
||||||
|
super('popup-reacted-list', /* [{ |
||||||
|
langKey: 'Close', |
||||||
|
isCancel: true |
||||||
|
}] */null, {closable: true, overlayClosable: true, body: true}); |
||||||
|
|
||||||
|
this.init(); |
||||||
|
} |
||||||
|
|
||||||
|
private async init() { |
||||||
|
const message = this.appMessagesManager.getGroupsFirstMessage(this.message); |
||||||
|
|
||||||
|
const canViewReadParticipants = this.appMessagesManager.canViewMessageReadParticipants(message); |
||||||
|
|
||||||
|
// this.body.append(generateDelimiter());
|
||||||
|
|
||||||
|
const reactionsElement = new ReactionsElement(); |
||||||
|
const newMessage: Message.message = { |
||||||
|
...message, |
||||||
|
mid: 0, |
||||||
|
id: 0, |
||||||
|
reactions: { |
||||||
|
_: 'messageReactions', |
||||||
|
results: [], |
||||||
|
|
||||||
|
...message.reactions, |
||||||
|
|
||||||
|
pFlags: {}, |
||||||
|
recent_reactions: [] |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
newMessage.reactions.results = newMessage.reactions.results.map(reactionCount => { |
||||||
|
return { |
||||||
|
...reactionCount, |
||||||
|
pFlags: {} |
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
reactionsElement.init(newMessage, 'block'); |
||||||
|
reactionsElement.render(); |
||||||
|
reactionsElement.classList.add('no-stripe'); |
||||||
|
reactionsElement.classList.remove('has-no-reactions'); |
||||||
|
|
||||||
|
reactionsElement.append(this.btnClose); |
||||||
|
|
||||||
|
this.header.append(reactionsElement); |
||||||
|
|
||||||
|
const tabsContainer = document.createElement('div'); |
||||||
|
tabsContainer.classList.add('tabs-container'); |
||||||
|
tabsContainer.dataset.animation = 'tabs'; |
||||||
|
|
||||||
|
const loaders: Map<HTMLElement, ScrollableLoader> = new Map(); |
||||||
|
|
||||||
|
let hasAllReactions = false; |
||||||
|
if(newMessage.reactions.results.length) { |
||||||
|
const reaction = this.createFakeReaction('reactions', newMessage.reactions.results.reduce((acc, r) => acc + r.count, 0)); |
||||||
|
|
||||||
|
reactionsElement.prepend(reaction); |
||||||
|
newMessage.reactions.results.unshift(reaction.reactionCount); |
||||||
|
hasAllReactions = true; |
||||||
|
} |
||||||
|
|
||||||
|
let hasReadParticipants = false; |
||||||
|
if(canViewReadParticipants) { |
||||||
|
try { |
||||||
|
const readUserIds = await this.appMessagesManager.getMessageReadParticipants(message.peerId, message.mid); |
||||||
|
if(!readUserIds.length) { |
||||||
|
throw ''; |
||||||
|
} |
||||||
|
|
||||||
|
const reaction = this.createFakeReaction('checks', readUserIds.length); |
||||||
|
|
||||||
|
reactionsElement.prepend(reaction); |
||||||
|
newMessage.reactions.results.unshift(reaction.reactionCount); |
||||||
|
hasReadParticipants = true; |
||||||
|
} catch(err) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
newMessage.reactions.results.forEach(reactionCount => { |
||||||
|
const scrollable = new Scrollable(undefined); |
||||||
|
scrollable.container.classList.add('tabs-tab'); |
||||||
|
|
||||||
|
const section = new SettingSection({ |
||||||
|
noShadow: true, |
||||||
|
noDelimiter: true |
||||||
|
}); |
||||||
|
|
||||||
|
const chatlist = appDialogsManager.createChatList({ |
||||||
|
dialogSize: 72 |
||||||
|
}); |
||||||
|
|
||||||
|
appDialogsManager.setListClickListener(chatlist, () => { |
||||||
|
this.hide(); |
||||||
|
}, undefined, false, true); |
||||||
|
|
||||||
|
section.content.append(chatlist); |
||||||
|
scrollable.container.append(section.container); |
||||||
|
|
||||||
|
const skipReadParticipants = reactionCount.reaction !== 'checks'; |
||||||
|
const skipReactionsList = reactionCount.reaction === 'checks'; |
||||||
|
if(['checks', 'reactions'].includes(reactionCount.reaction)) { |
||||||
|
reactionCount.reaction = undefined; |
||||||
|
} |
||||||
|
|
||||||
|
let nextOffset: string; |
||||||
|
const loader = new ScrollableLoader({ |
||||||
|
scrollable, |
||||||
|
getPromise: async() => { |
||||||
|
const result = await this.appMessagesManager.getMessageReactionsListAndReadParticipants(message, undefined, reactionCount.reaction, nextOffset, skipReadParticipants, skipReactionsList); |
||||||
|
nextOffset = result.nextOffset; |
||||||
|
|
||||||
|
result.combined.forEach(({peerId, reaction}) => { |
||||||
|
const {dom} = appDialogsManager.addDialogNew({ |
||||||
|
dialog: peerId, |
||||||
|
autonomous: true, |
||||||
|
container: chatlist, |
||||||
|
avatarSize: 54, |
||||||
|
rippleEnabled: false, |
||||||
|
meAsSaved: false, |
||||||
|
drawStatus: false |
||||||
|
}); |
||||||
|
|
||||||
|
if(reaction) { |
||||||
|
const stickerContainer = document.createElement('div'); |
||||||
|
stickerContainer.classList.add('reacted-list-reaction-icon'); |
||||||
|
const availableReaction = appReactionsManager.getReactionCached(reaction); |
||||||
|
|
||||||
|
wrapSticker({ |
||||||
|
doc: availableReaction.static_icon, |
||||||
|
div: stickerContainer, |
||||||
|
width: 24, |
||||||
|
height: 24 |
||||||
|
}); |
||||||
|
|
||||||
|
dom.listEl.append(stickerContainer); |
||||||
|
} |
||||||
|
|
||||||
|
replaceContent(dom.lastMessageSpan, appUsersManager.getUserStatusString(peerId.toUserId())); |
||||||
|
}); |
||||||
|
|
||||||
|
return !nextOffset; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
loaders.set(scrollable.container, loader); |
||||||
|
|
||||||
|
tabsContainer.append(scrollable.container); |
||||||
|
}); |
||||||
|
|
||||||
|
this.body.append(tabsContainer); |
||||||
|
|
||||||
|
const selectTab = horizontalMenu(reactionsElement, tabsContainer, (id, tabContent) => { |
||||||
|
if(id === (reactionsElement.childElementCount - 1)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
const reaction = reactionsElement.children[id] as ReactionElement; |
||||||
|
const prevId = selectTab.prevId(); |
||||||
|
if(prevId !== -1) { |
||||||
|
(reactionsElement.children[prevId] as ReactionElement).setIsChosen(false); |
||||||
|
} |
||||||
|
|
||||||
|
reaction.setIsChosen(true); |
||||||
|
|
||||||
|
const loader = loaders.get(tabContent); |
||||||
|
loader.load(); |
||||||
|
}); |
||||||
|
|
||||||
|
// selectTab(hasAllReactions && hasReadParticipants ? 1 : 0, false);
|
||||||
|
selectTab(0, false); |
||||||
|
|
||||||
|
this.show(); |
||||||
|
} |
||||||
|
|
||||||
|
private createFakeReaction(icon: string, count: number) { |
||||||
|
const reaction = new ReactionElement(); |
||||||
|
reaction.init('block'); |
||||||
|
reaction.reactionCount = { |
||||||
|
_: 'reactionCount', |
||||||
|
count: count, |
||||||
|
reaction: icon |
||||||
|
}; |
||||||
|
reaction.setCanRenderAvatars(false); |
||||||
|
reaction.renderCounter(); |
||||||
|
|
||||||
|
const allReactionsSticker = document.createElement('div'); |
||||||
|
allReactionsSticker.classList.add('reaction-counter', 'reaction-sticker-icon', 'tgico-' + icon); |
||||||
|
reaction.prepend(allReactionsSticker); |
||||||
|
|
||||||
|
return reaction; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import AvatarElement from "./avatar"; |
||||||
|
import type { LazyLoadQueueIntersector } from "./lazyLoadQueue"; |
||||||
|
|
||||||
|
const CLASS_NAME = 'stacked-avatars'; |
||||||
|
const AVATAR_CLASS_NAME = CLASS_NAME + '-avatar'; |
||||||
|
const AVATAR_CONTAINER_CLASS_NAME = AVATAR_CLASS_NAME + '-container'; |
||||||
|
|
||||||
|
export default class StackedAvatars { |
||||||
|
public container: HTMLElement; |
||||||
|
private lazyLoadQueue: LazyLoadQueueIntersector; |
||||||
|
private avatarSize: number; |
||||||
|
|
||||||
|
constructor(options: { |
||||||
|
lazyLoadQueue?: LazyLoadQueueIntersector, |
||||||
|
avatarSize: number |
||||||
|
}) { |
||||||
|
this.lazyLoadQueue = options.lazyLoadQueue; |
||||||
|
this.avatarSize = options.avatarSize; |
||||||
|
|
||||||
|
this.container = document.createElement('div'); |
||||||
|
this.container.classList.add(CLASS_NAME); |
||||||
|
|
||||||
|
this.container.style.setProperty('--avatar-size', options.avatarSize + 'px'); |
||||||
|
} |
||||||
|
|
||||||
|
public render(peerIds: PeerId[], loadPromises?: Promise<any>[]) { |
||||||
|
const children = this.container.children; |
||||||
|
peerIds.slice().reverse().forEach((peerId, idx) => { |
||||||
|
let avatarContainer = children[idx] as HTMLElement; |
||||||
|
if(!avatarContainer) { |
||||||
|
avatarContainer = document.createElement('div'); |
||||||
|
avatarContainer.classList.add(AVATAR_CONTAINER_CLASS_NAME); |
||||||
|
} |
||||||
|
|
||||||
|
let avatarElem = avatarContainer.firstElementChild as AvatarElement; |
||||||
|
if(!avatarElem) { |
||||||
|
avatarElem = new AvatarElement(); |
||||||
|
avatarElem.setAttribute('dialog', '0'); |
||||||
|
avatarElem.classList.add('avatar-' + this.avatarSize, AVATAR_CLASS_NAME); |
||||||
|
avatarElem.lazyLoadQueue = this.lazyLoadQueue; |
||||||
|
avatarElem.loadPromises = loadPromises; |
||||||
|
} |
||||||
|
|
||||||
|
avatarElem.setAttribute('peer', '' + peerId); |
||||||
|
|
||||||
|
if(!avatarElem.parentNode) { |
||||||
|
avatarContainer.append(avatarElem); |
||||||
|
} |
||||||
|
|
||||||
|
if(!avatarContainer.parentNode) { |
||||||
|
this.container.append(avatarContainer); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// if were 3 and became 2
|
||||||
|
(Array.from(children) as HTMLElement[]).slice(peerIds.length).forEach(el => el.remove()); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import {Awaited} from '../types'; |
||||||
|
|
||||||
|
export default function callbackifyAll<T extends readonly unknown[] | [], R extends any>( |
||||||
|
values: T, |
||||||
|
callback: (result: { -readonly [P in keyof T]: Awaited<T[P]> }) => R |
||||||
|
): PromiseLike<R> | R { |
||||||
|
if(values.some(value => value instanceof Promise)) { |
||||||
|
return Promise.all(values).then(callback as any); |
||||||
|
} else { |
||||||
|
return callback(values as any); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
export default function getTimeFormat(): 'h12' | 'h23' { |
||||||
|
// try {
|
||||||
|
// const resolvedOptions = Intl.DateTimeFormat(navigator.language, {hour: 'numeric'}).resolvedOptions();
|
||||||
|
// if('hourCycle' in resolvedOptions) {
|
||||||
|
// return (resolvedOptions as any).hourCycle === 'h12' ? 'h12' : 'h23';
|
||||||
|
// } else {
|
||||||
|
// return resolvedOptions.hour12 ? 'h12' : 'h23';
|
||||||
|
// }
|
||||||
|
// } catch(err) {
|
||||||
|
return new Date().toLocaleString().match(/\s(AM|PM)/) ? 'h12' : 'h23'; |
||||||
|
// }
|
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,143 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb |
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
||||||
|
*/ |
||||||
|
|
||||||
|
/* @keyframes reaction-activate { |
||||||
|
0% { |
||||||
|
opacity: 1; |
||||||
|
transform: scale(1.75); |
||||||
|
} |
||||||
|
|
||||||
|
95% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
to { |
||||||
|
opacity: 0; |
||||||
|
transform: scale(1.25); |
||||||
|
} |
||||||
|
} */ |
||||||
|
|
||||||
|
.reaction { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
|
||||||
|
&-sticker { |
||||||
|
position: relative; |
||||||
|
width: var(--reaction-size); |
||||||
|
height: var(--reaction-size); |
||||||
|
|
||||||
|
&-activate { |
||||||
|
--offset: -.4375rem; |
||||||
|
// --offset: 0; |
||||||
|
position: absolute; |
||||||
|
top: var(--offset); |
||||||
|
right: var(--offset); |
||||||
|
bottom: var(--offset); |
||||||
|
left: var(--offset); |
||||||
|
// animation: reaction-activate 2s linear forwards; |
||||||
|
|
||||||
|
& + .media-sticker { |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&-inline { |
||||||
|
--reaction-size: .875rem; |
||||||
|
min-width: var(--reaction-size); |
||||||
|
min-height: var(--reaction-size); |
||||||
|
} |
||||||
|
|
||||||
|
&-inline &-counter { |
||||||
|
font-size: inherit !important; |
||||||
|
order: -1; |
||||||
|
margin-right: .0625rem !important; |
||||||
|
} |
||||||
|
|
||||||
|
&-block { |
||||||
|
--additional-height: .625rem; |
||||||
|
--margin: .25rem; |
||||||
|
// --reaction-size: 1.0625rem; |
||||||
|
--reaction-size: 1rem; |
||||||
|
--background-color: var(--message-highlightning-color); |
||||||
|
--chosen-background-color: var(--message-primary-color); |
||||||
|
--counter-color: #fff; |
||||||
|
--reaction-total-size: calc(var(--reaction-size) + var(--additional-height)); |
||||||
|
height: var(--reaction-total-size); |
||||||
|
border-radius: var(--reaction-total-size); |
||||||
|
// padding: 0 .375rem 0 .625rem; |
||||||
|
padding: 0 .5rem; |
||||||
|
background-color: var(--background-color); |
||||||
|
cursor: pointer; |
||||||
|
position: relative; |
||||||
|
margin-top: var(--margin); |
||||||
|
margin-right: var(--margin); |
||||||
|
color: var(--counter-color); |
||||||
|
|
||||||
|
&:last-child { |
||||||
|
margin-right: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&:before { |
||||||
|
content: " "; |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
right: 0; |
||||||
|
bottom: 0; |
||||||
|
left: 0; |
||||||
|
background-color: var(--chosen-background-color); |
||||||
|
// visibility: hidden; |
||||||
|
border-radius: inherit; |
||||||
|
// transform: scale3d(.2, .2, .2); |
||||||
|
transform: scale3d(0, 0, 0); |
||||||
|
opacity: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&.is-chosen { |
||||||
|
&:not(.backwards) { |
||||||
|
&:before { |
||||||
|
transform: scale3d(1, 1, 1); |
||||||
|
opacity: 1; |
||||||
|
// visibility: visible; |
||||||
|
} |
||||||
|
|
||||||
|
.stacked-avatars { |
||||||
|
--border-color: var(--chosen-background-color); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&.animating { |
||||||
|
&:before { |
||||||
|
transition: transform var(--transition-standard-in), opacity var(--transition-standard-in); |
||||||
|
// transition: transform var(--transition-standard-in); |
||||||
|
} |
||||||
|
|
||||||
|
.reaction-counter { |
||||||
|
transition: color var(--transition-standard-in); |
||||||
|
// transition: color 1s linear; |
||||||
|
} |
||||||
|
|
||||||
|
.stacked-avatars-avatar-container { |
||||||
|
transition: border-color var(--transition-standard-in); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.stacked-avatars { |
||||||
|
--border-color: transparent; |
||||||
|
margin-left: .25rem; |
||||||
|
// margin-right: .0625rem; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&-block &-counter { |
||||||
|
font-size: .875rem !important; |
||||||
|
font-weight: 500; |
||||||
|
margin: 0 .125rem 0 .375rem; |
||||||
|
line-height: 1; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb |
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
||||||
|
*/ |
||||||
|
|
||||||
|
.reactions { |
||||||
|
&-block { |
||||||
|
display: flex; |
||||||
|
flex-wrap: wrap; |
||||||
|
user-select: none; |
||||||
|
|
||||||
|
&.has-no-reactions { |
||||||
|
display: unset; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&-inline { |
||||||
|
display: inline-flex; |
||||||
|
|
||||||
|
&:not(:empty) { |
||||||
|
margin-right: .125rem; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,36 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb |
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
||||||
|
*/ |
||||||
|
|
||||||
|
.stacked-avatars { |
||||||
|
--border-color: var(--surface-color); |
||||||
|
--border-size: 1px; |
||||||
|
--margin-right: -.3125rem; |
||||||
|
--avatar-size: 1rem; |
||||||
|
--avatar-total-size: calc(var(--avatar-size) + var(--border-size) * 2); |
||||||
|
display: flex; |
||||||
|
flex-direction: row-reverse; |
||||||
|
|
||||||
|
&-avatar { |
||||||
|
width: var(--avatar-size); |
||||||
|
height: var(--avatar-size); |
||||||
|
z-index: 0; // * fix border blinking |
||||||
|
|
||||||
|
&-container { |
||||||
|
width: var(--avatar-total-size); |
||||||
|
height: var(--avatar-total-size); |
||||||
|
border: var(--border-size) solid var(--border-color); |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
border-radius: 50%; |
||||||
|
position: relative; |
||||||
|
|
||||||
|
&:not(:first-child) { |
||||||
|
margin-right: var(--margin-right); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb |
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
||||||
|
*/ |
||||||
|
|
||||||
|
.popup-reacted-list { |
||||||
|
$parent: ".popup"; |
||||||
|
|
||||||
|
#{$parent} { |
||||||
|
&-container { |
||||||
|
width: 25rem; |
||||||
|
height: 600px; |
||||||
|
max-height: 600px; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
|
||||||
|
&-header { |
||||||
|
min-height: 3.5625rem; |
||||||
|
margin: 0; |
||||||
|
padding: .25rem .75rem .75rem; |
||||||
|
border-bottom: 1px solid var(--border-color); |
||||||
|
} |
||||||
|
|
||||||
|
&-close { |
||||||
|
margin-top: .375rem; |
||||||
|
margin-right: .5rem; |
||||||
|
height: 40px; |
||||||
|
order: -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.reaction { |
||||||
|
--additional-height: .75rem; |
||||||
|
--reaction-size: 1.5rem; |
||||||
|
--margin: .5rem; |
||||||
|
// --background-color: var(--secondary-color); |
||||||
|
--background-color: var(--light-filled-primary-color); |
||||||
|
--counter-color: var(--primary-color); |
||||||
|
|
||||||
|
&.is-chosen { |
||||||
|
&:not(.backwards) { |
||||||
|
// --counter-color: var(--primary-text-color); |
||||||
|
--counter-color: #fff; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
&-sticker-icon { |
||||||
|
font-size: 1.25rem !important; |
||||||
|
margin: 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.sidebar-left-section { |
||||||
|
margin-bottom: 0 !important; |
||||||
|
} |
||||||
|
|
||||||
|
/* .gradient-delimiter { |
||||||
|
flex: 0 0 auto; |
||||||
|
} */ |
||||||
|
|
||||||
|
.tabs-container { |
||||||
|
flex: 1 1 auto; |
||||||
|
overflow: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
.tabs-tab { |
||||||
|
background-color: var(--surface-color); |
||||||
|
} |
||||||
|
|
||||||
|
.reacted-list-reaction-icon { |
||||||
|
width: 1.5rem; |
||||||
|
height: 1.5rem; |
||||||
|
margin: 0; |
||||||
|
top: 50%; |
||||||
|
transform: translateY(-50%); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue