From 2a5467e074f5a0f64025ab732069cc0b61f982da Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Tue, 6 Apr 2021 19:06:42 +0400 Subject: [PATCH] temp profile commit improved ESG animation --- .../emoticonsDropdown/tabs/emoji.ts | 29 +- .../sidebarRight/tabs/sharedMedia.ts | 724 ++++++++++++------ src/lang.ts | 2 +- src/lib/appManagers/appChatsManager.ts | 10 +- src/lib/appManagers/appPeersManager.ts | 8 +- src/lib/appManagers/appPhotosManager.ts | 5 +- src/lib/appManagers/appUsersManager.ts | 10 +- src/scss/partials/_chat.scss | 6 +- src/scss/partials/_chatDrop.scss | 4 +- src/scss/partials/_emojiDropdown.scss | 32 +- src/scss/partials/_profile.scss | 155 ++++ src/scss/partials/_rightSidebar.scss | 99 --- src/scss/style.scss | 8 +- 13 files changed, 715 insertions(+), 377 deletions(-) create mode 100644 src/scss/partials/_profile.scss diff --git a/src/components/emoticonsDropdown/tabs/emoji.ts b/src/components/emoticonsDropdown/tabs/emoji.ts index 5dd1de92..b96aa108 100644 --- a/src/components/emoticonsDropdown/tabs/emoji.ts +++ b/src/components/emoticonsDropdown/tabs/emoji.ts @@ -1,8 +1,11 @@ import { EmoticonsDropdown, EmoticonsTab } from ".."; +import findUpClassName from "../../../helpers/dom/findUpClassName"; +import { fastRaf } from "../../../helpers/schedulers"; import appImManager from "../../../lib/appManagers/appImManager"; import appStateManager from "../../../lib/appManagers/appStateManager"; import Config from "../../../lib/config"; import { RichTextProcessor } from "../../../lib/richtextprocessor"; +import rootScope from "../../../lib/rootScope"; import { putPreloader } from "../../misc"; import Scrollable from "../../scrollable"; import StickyIntersector from "../../stickyIntersector"; @@ -162,7 +165,29 @@ export default class EmojiTab implements EmoticonsTab { } if(spanEmoji.firstElementChild && !RichTextProcessor.emojiSupported) { - (spanEmoji.firstElementChild as HTMLImageElement).setAttribute('loading', 'lazy'); + const image = spanEmoji.firstElementChild as HTMLImageElement; + image.setAttribute('loading', 'lazy'); + + const placeholder = document.createElement('span'); + placeholder.classList.add('emoji-placeholder'); + + if(rootScope.settings.animationsEnabled) { + image.style.opacity = '0'; + placeholder.style.opacity = '1'; + } + + image.addEventListener('load', () => { + fastRaf(() => { + if(rootScope.settings.animationsEnabled) { + image.style.opacity = ''; + placeholder.style.opacity = ''; + } + + spanEmoji.classList.remove('empty'); + }); + }, {once: true}); + + spanEmoji.append(placeholder); } //spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement; @@ -185,7 +210,7 @@ export default class EmojiTab implements EmoticonsTab { //if(target.tagName !== 'SPAN') return; if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) { - target = target.firstChild as HTMLElement; + target = findUpClassName(target, 'category-item').firstChild as HTMLElement; } else if(target.tagName === 'DIV') return; //console.log('contentEmoji div', target); diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 1ba1d9e5..db56b269 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -8,10 +8,9 @@ import { RichTextProcessor } from "../../../lib/richtextprocessor"; import rootScope from "../../../lib/rootScope"; import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper."; import AvatarElement from "../../avatar"; -import Scrollable from "../../scrollable"; -import SidebarSlider, { SliderSuperTab, SliderTab } from "../../slider"; +import SidebarSlider, { SliderSuperTab } from "../../slider"; import CheckboxField from "../../checkboxField"; -import { attachClickEvent } from "../../../helpers/dom"; +import { attachClickEvent, replaceContent } from "../../../helpers/dom"; import appSidebarRight from ".."; import { TransitionSlider } from "../../transition"; import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager"; @@ -20,7 +19,7 @@ import PeerTitle from "../../peerTitle"; import AppEditChannelTab from "./editChannel"; import AppEditContactTab from "./editContact"; import appChatsManager, { Channel } from "../../../lib/appManagers/appChatsManager"; -import { Chat } from "../../../layer"; +import { Chat, UserProfilePhoto } from "../../../layer"; import Button from "../../button"; import ButtonIcon from "../../buttonIcon"; import I18n, { i18n } from "../../../lib/langPack"; @@ -28,107 +27,237 @@ import { SettingSection } from "../../sidebarLeft"; import Row from "../../row"; import { copyTextToClipboard } from "../../../helpers/clipboard"; import { toast } from "../../toast"; +import { fastRaf } from "../../../helpers/schedulers"; +import { safeAssign } from "../../../helpers/object"; +import { forEachReverse } from "../../../helpers/array"; +import appPhotosManager from "../../../lib/appManagers/appPhotosManager"; +import renderImageFromUrl from "../../../helpers/dom/renderImageFromUrl"; let setText = (text: string, row: Row) => { - window.requestAnimationFrame(() => { + fastRaf(() => { row.title.innerHTML = text; row.container.style.display = ''; }); }; -// TODO: отредактированное сообщение не изменится -export default class AppSharedMediaTab extends SliderSuperTab { - public editBtn: HTMLElement; +type ListLoaderResult = {count: number, items: any[]}; +class ListLoader { + public current: T; + public previous: T[] = []; + public next: T[] = []; - private peerId = 0; - private threadId = 0; + public tempId = 0; + public loadMore: (anchor: T, older: boolean) => Promise>; + public processItem: (item: any) => false | T; + public loadCount = 50; + public reverse = false; // reverse means next = higher msgid - public profileContentEl: HTMLDivElement; - public profileElements: { - avatar: AvatarElement, - name: HTMLDivElement, - subtitle: HTMLDivElement, - bio: Row, - username: Row, - phone: Row, - notifications: Row - } = {} as any; + public loadedAllUp = false; + public loadedAllDown = false; + public loadPromiseUp: Promise; + public loadPromiseDown: Promise; - public historiesStorage: { - [peerId: number]: Partial<{ - [type in SearchSuperType]: {mid: number, peerId: number}[] - }> - } = {}; + constructor(options: { + loadMore: ListLoader['loadMore'], + loadCount: ListLoader['loadCount'], + processItem?: ListLoader['processItem'], + }) { + safeAssign(this, options); - private log = logger('SM'/* , LogLevels.error */); - setPeerStatusInterval: number; - cleaned: boolean; - searchSuper: AppSearchSuper; - private setBioTimeout: number; + } - constructor(slider: SidebarSlider) { - super(slider, false); + public go(length: number) { + let items: T[], item: T; + if(length > 0) { + items = this.next.splice(0, length); + item = items.pop(); + this.previous.push(...items); + } else { + items = this.previous.splice(this.previous.length - length, length); + item = items.shift(); + this.next.unshift(...items); + } } - protected init() { - this.container.id = 'shared-media-container'; - this.container.classList.add('profile-container'); + public load(older: boolean) { + if(older && this.loadedAllDown) return Promise.resolve(); + else if(!older && this.loadedAllUp) return Promise.resolve(); - // * header - const newCloseBtn = Button('btn-icon sidebar-close-button', {noRipple: true}); - this.closeBtn.replaceWith(newCloseBtn); - this.closeBtn = newCloseBtn; + if(older && this.loadPromiseDown) return this.loadPromiseDown; + else if(!older && this.loadPromiseUp) return this.loadPromiseUp; - const animatedCloseIcon = document.createElement('div'); - animatedCloseIcon.classList.add('animated-close-icon'); - newCloseBtn.append(animatedCloseIcon); + /* const loadCount = 50; + const backLimit = older ? 0 : loadCount; */ + + let anchor: T; + if(older) { + anchor = this.reverse ? this.previous[0] : this.next[this.next.length - 1]; + } else { + anchor = this.reverse ? this.next[this.next.length - 1] : this.previous[0]; + } - const transitionContainer = document.createElement('div'); - transitionContainer.className = 'transition slide-fade'; - - const transitionFirstItem = document.createElement('div'); - transitionFirstItem.classList.add('transition-item'); + const promise = this.loadMore(anchor, older).then(result => { + if(result.items.length < this.loadCount) { + if(older) this.loadedAllDown = true; + else this.loadedAllUp = true; + } - this.title.append(i18n('Telegram.PeerInfoController')); - this.editBtn = ButtonIcon('edit'); - //const moreBtn = ButtonIcon('more'); + const method = older ? result.items.forEach.bind(result.items) : forEachReverse.bind(null, result.items); + method((item: any) => { + const processed = this.processItem ? this.processItem(item) : item; - transitionFirstItem.append(this.title, this.editBtn/* , moreBtn */); + if(!processed) return; - const transitionLastItem = document.createElement('div'); - transitionLastItem.classList.add('transition-item'); + if(older) { + if(this.reverse) this.previous.unshift(processed); + else this.next.push(processed); + } else { + if(this.reverse) this.next.push(processed); + else this.previous.unshift(processed); + } + }); + }, () => {}).then(() => { + if(older) this.loadPromiseDown = null; + else this.loadPromiseUp = null; + }); - const secondTitle: HTMLElement = this.title.cloneNode() as any; - secondTitle.append(i18n('PeerInfo.SharedMedia')); + if(older) this.loadPromiseDown = promise; + else this.loadPromiseUp = promise; - transitionLastItem.append(secondTitle); + return promise; + } +} - transitionContainer.append(transitionFirstItem, transitionLastItem); +class PeerProfileAvatars { + public static BASE_CLASS = 'profile-avatars'; + public container: HTMLElement; + public avatars: HTMLElement; + public info: HTMLElement; + public listLoader: ListLoader; + public peerId: number; - this.header.append(transitionContainer); + constructor() { + this.container = document.createElement('div'); + this.container.classList.add(PeerProfileAvatars.BASE_CLASS + '-container'); - // * body - - this.profileContentEl = document.createElement('div'); - this.profileContentEl.classList.add('profile-content'); + this.avatars = document.createElement('div'); + this.avatars.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatars'); + + this.info = document.createElement('div'); + this.info.classList.add(PeerProfileAvatars.BASE_CLASS + '-info'); + + this.container.append(this.avatars, this.info); + + attachClickEvent(this.container, (_e) => { + const rect = this.container.getBoundingClientRect(); + + const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent; + const x = e.pageX; + + const centerX = rect.right - (rect.width / 2); + const toRight = x > centerX; - const section = new SettingSection({ + const id = this.listLoader.previous.length; + const nextId = Math.max(0, id + (toRight ? 1 : -1)); + this.avatars.style.transform = `translateX(-${100 * nextId}%)`; + }); + } + + public setPeer(peerId: number) { + this.peerId = peerId; + + const photo = appPeersManager.getPeerPhoto(peerId); + if(!photo) { + return; + } + + const loadCount = 50; + const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader({ + loadCount, + loadMore: (anchor, older) => { + return appPhotosManager.getUserPhotos(peerId, anchor || listLoader.current, loadCount).then(result => { + return { + count: result.count, + items: result.photos + }; + }); + }, + processItem: this.processItem + }); + + listLoader.current = (photo as UserProfilePhoto.userProfilePhoto).photo_id; + this.processItem(listLoader.current); + + listLoader.load(true); + } + + public processItem = (photoId: string) => { + const avatar = document.createElement('div'); + avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar'); + + const photo = appPhotosManager.getPhoto(photoId); + if(photo) { + appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 420, 420, false)).then(() => { + const img = new Image(); + renderImageFromUrl(img, photo.url, () => { + avatar.append(img); + }); + }); + } else { + const photo = appPeersManager.getPeerPhoto(this.peerId); + appProfileManager.putAvatar(avatar, this.peerId, photo, 'photo_big'); + } + + this.avatars.append(avatar); + + return photoId; + }; +} + +class PeerProfile { + public element: HTMLElement; + private avatars: PeerProfileAvatars; + private avatar: AvatarElement; + private section: SettingSection; + private name: HTMLDivElement; + private subtitle: HTMLDivElement; + private bio: Row; + private username: Row; + private phone: Row; + private notifications: Row; + + private cleaned: boolean; + private setBioTimeout: number; + private setPeerStatusInterval: number; + + private peerId = 0; + private threadId: number; + + public init() { + this.init = null; + + this.element = document.createElement('div'); + this.element.classList.add('profile-content'); + + this.section = new SettingSection({ noDelimiter: true }); - this.profileElements.avatar = new AvatarElement(); - this.profileElements.avatar.classList.add('profile-avatar', 'avatar-120'); - this.profileElements.avatar.setAttribute('dialog', '1'); - this.profileElements.avatar.setAttribute('clickable', ''); + this.avatars = new PeerProfileAvatars(); - this.profileElements.name = document.createElement('div'); - this.profileElements.name.classList.add('profile-name'); + this.avatar = new AvatarElement(); + this.avatar.classList.add('profile-avatar', 'avatar-120'); + this.avatar.setAttribute('dialog', '1'); + this.avatar.setAttribute('clickable', ''); - this.profileElements.subtitle = document.createElement('div'); - this.profileElements.subtitle.classList.add('profile-subtitle'); + this.name = document.createElement('div'); + this.name.classList.add('profile-name'); - this.profileElements.bio = new Row({ + this.subtitle = document.createElement('div'); + this.subtitle.classList.add('profile-subtitle'); + + this.bio = new Row({ title: ' ', subtitleLangKey: 'UserBio', icon: 'info', @@ -144,9 +273,9 @@ export default class AppSharedMediaTab extends SliderSuperTab { } }); - this.profileElements.bio.title.classList.add('pre-wrap'); + this.bio.title.classList.add('pre-wrap'); - this.profileElements.username = new Row({ + this.username = new Row({ title: ' ', subtitleLangKey: 'Username', icon: 'username', @@ -157,7 +286,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { } }); - this.profileElements.phone = new Row({ + this.phone = new Row({ title: ' ', subtitleLangKey: 'Phone', icon: 'phone', @@ -168,14 +297,267 @@ export default class AppSharedMediaTab extends SliderSuperTab { } }); - this.profileElements.notifications = new Row({ + this.notifications = new Row({ checkboxField: new CheckboxField({text: 'Notifications'}) }); - section.content.append(this.profileElements.avatar, this.profileElements.name, this.profileElements.subtitle, - this.profileElements.bio.container, this.profileElements.username.container, this.profileElements.phone.container, this.profileElements.notifications.container); - this.profileContentEl.append(section.container); - this.scrollable.append(this.profileContentEl); + this.section.content.append(/* this.name, this.subtitle, */ + this.phone.container, this.username.container, this.bio.container, this.notifications.container); + this.element.append(this.avatars.container, this.section.container); + + this.notifications.checkboxField.input.addEventListener('change', (e) => { + if(!e.isTrusted) { + return; + } + + //let checked = this.notificationsCheckbox.checked; + appMessagesManager.mutePeer(this.peerId); + }); + + rootScope.on('dialog_notify_settings', (dialog) => { + if(this.peerId === dialog.peerId) { + const muted = appNotificationsManager.isPeerLocalMuted(this.peerId, false); + this.notifications.checkboxField.checked = !muted; + } + }); + + rootScope.on('peer_typings', (e) => { + const {peerId} = e; + + if(this.peerId === peerId) { + this.setPeerStatus(); + } + }); + + rootScope.on('peer_bio_edit', (peerId) => { + if(peerId === this.peerId) { + this.setBio(true); + } + }); + + rootScope.on('user_update', (e) => { + const userId = e; + + if(this.peerId === userId) { + this.setPeerStatus(); + } + }); + + this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3); + } + + public setPeerStatus = (needClear = false) => { + if(!this.peerId) return; + + const peerId = this.peerId; + if(needClear) { + this.subtitle.innerHTML = '‎'; // ! HERE U CAN FIND WHITESPACE + } + + appImManager.getPeerStatus(this.peerId).then((subtitle) => { + if(peerId !== this.peerId) { + return; + } + + this.subtitle.textContent = ''; + this.subtitle.append(subtitle || ''); + }); + }; + + public cleanupHTML() { + this.bio.container.style.display = 'none'; + this.phone.container.style.display = 'none'; + this.username.container.style.display = 'none'; + this.notifications.container.style.display = ''; + this.notifications.checkboxField.checked = true; + if(this.setBioTimeout) { + window.clearTimeout(this.setBioTimeout); + this.setBioTimeout = 0; + } + } + + public fillProfileElements() { + if(!this.cleaned) return; + this.cleaned = false; + + const peerId = this.peerId; + + this.cleanupHTML(); + + this.avatar.setAttribute('peer', '' + peerId); + + this.avatars.setPeer(peerId); + this.avatars.info.append(this.name, this.subtitle); + + // username + if(peerId !== rootScope.myId) { + let username = appPeersManager.getPeerUsername(peerId); + if(username) { + setText(appPeersManager.getPeerUsername(peerId), this.username); + } + + const muted = appNotificationsManager.isPeerLocalMuted(peerId, false); + this.notifications.checkboxField.checked = !muted; + } else { + window.requestAnimationFrame(() => { + this.notifications.container.style.display = 'none'; + }); + } + + //let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement; + if(peerId > 0) { + //membersLi.style.display = 'none'; + + let user = appUsersManager.getUser(peerId); + if(user.phone && peerId !== rootScope.myId) { + setText(user.rPhone, this.phone); + } + }/* else { + //membersLi.style.display = appPeersManager.isBroadcast(peerId) ? 'none' : ''; + } */ + + this.setBio(); + + replaceContent(this.name, new PeerTitle({ + peerId, + dialog: true + }).element); + + this.setPeerStatus(true); + } + + public setBio(override?: true) { + if(this.setBioTimeout) { + window.clearTimeout(this.setBioTimeout); + this.setBioTimeout = 0; + } + + const peerId = this.peerId; + const threadId = this.threadId; + + if(!peerId) { + return; + } + + let promise: Promise; + if(peerId > 0) { + promise = appProfileManager.getProfile(peerId, override).then(userFull => { + if(this.peerId !== peerId || this.threadId !== threadId) { + //this.log.warn('peer changed'); + return false; + } + + if(userFull.rAbout && peerId !== rootScope.myId) { + setText(userFull.rAbout, this.bio); + } + + //this.log('userFull', userFull); + return true; + }); + } else { + promise = appProfileManager.getChatFull(-peerId, override).then((chatFull) => { + if(this.peerId !== peerId || this.threadId !== threadId) { + //this.log.warn('peer changed'); + return false; + } + + //this.log('chatInfo res 2:', chatFull); + + if(chatFull.about) { + setText(RichTextProcessor.wrapRichText(chatFull.about), this.bio); + } + + return true; + }); + } + + promise.then((canSetNext) => { + if(canSetNext) { + this.setBioTimeout = window.setTimeout(() => this.setBio(true), 60e3); + } + }); + } + + public setPeer(peerId: number, threadId = 0) { + if(this.peerId === peerId && this.threadId === peerId) return; + + if(this.init) { + this.init(); + } + + this.peerId = peerId; + this.threadId = threadId; + + this.cleaned = true; + } +} + +// TODO: отредактированное сообщение не изменится +export default class AppSharedMediaTab extends SliderSuperTab { + public editBtn: HTMLElement; + + private peerId = 0; + private threadId = 0; + + public historiesStorage: { + [peerId: number]: Partial<{ + [type in SearchSuperType]: {mid: number, peerId: number}[] + }> + } = {}; + + private log = logger('SM'/* , LogLevels.error */); + private cleaned: boolean; + private searchSuper: AppSearchSuper; + + public profile: PeerProfile; + + constructor(slider: SidebarSlider) { + super(slider, false); + } + + protected init() { + this.container.id = 'shared-media-container'; + this.container.classList.add('profile-container'); + + // * header + const newCloseBtn = Button('btn-icon sidebar-close-button', {noRipple: true}); + this.closeBtn.replaceWith(newCloseBtn); + this.closeBtn = newCloseBtn; + + const animatedCloseIcon = document.createElement('div'); + animatedCloseIcon.classList.add('animated-close-icon'); + newCloseBtn.append(animatedCloseIcon); + + const transitionContainer = document.createElement('div'); + transitionContainer.className = 'transition slide-fade'; + + const transitionFirstItem = document.createElement('div'); + transitionFirstItem.classList.add('transition-item'); + + this.title.append(i18n('Profile')); + this.editBtn = ButtonIcon('edit'); + //const moreBtn = ButtonIcon('more'); + + transitionFirstItem.append(this.title, this.editBtn/* , moreBtn */); + + const transitionLastItem = document.createElement('div'); + transitionLastItem.classList.add('transition-item'); + + const secondTitle: HTMLElement = this.title.cloneNode() as any; + secondTitle.append(i18n('PeerInfo.SharedMedia')); + + transitionLastItem.append(secondTitle); + + transitionContainer.append(transitionFirstItem, transitionLastItem); + + this.header.append(transitionContainer); + + // * body + + this.profile = new PeerProfile(); + this.profile.init(); + + this.scrollable.append(this.profile.element); const HEADER_HEIGHT = 56; this.scrollable.onAdditionalScroll = () => { @@ -229,46 +611,6 @@ export default class AppSharedMediaTab extends SliderSuperTab { //this.container.prepend(this.closeBtn.parentElement); - this.profileElements.notifications.checkboxField.input.addEventListener('change', (e) => { - if(!e.isTrusted) { - return; - } - - //let checked = this.profileElements.notificationsCheckbox.checked; - appMessagesManager.mutePeer(this.peerId); - }); - - rootScope.on('dialog_notify_settings', (dialog) => { - if(this.peerId === dialog.peerId) { - const muted = appNotificationsManager.isPeerLocalMuted(this.peerId, false); - this.profileElements.notifications.checkboxField.checked = !muted; - } - }); - - rootScope.on('peer_typings', (e) => { - const {peerId} = e; - - if(this.peerId === peerId) { - this.setPeerStatus(); - } - }); - - rootScope.on('peer_bio_edit', (peerId) => { - if(peerId === this.peerId) { - this.setBio(true); - } - }); - - rootScope.on('user_update', (e) => { - const userId = e; - - if(this.peerId === userId) { - this.setPeerStatus(); - } - }); - - this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3); - this.searchSuper = new AppSearchSuper([{ inputFilter: 'inputMessagesFilterPhotoVideo', name: 'SharedMediaTab2', @@ -287,27 +629,9 @@ export default class AppSharedMediaTab extends SliderSuperTab { type: 'music' }], this.scrollable/* , undefined, undefined, false */); - this.profileContentEl.append(this.searchSuper.container); + this.profile.element.append(this.searchSuper.container); } - public setPeerStatus = (needClear = false) => { - if(!this.peerId) return; - - const peerId = this.peerId; - if(needClear) { - this.profileElements.subtitle.innerHTML = '‎'; // ! HERE U CAN FIND WHITESPACE - } - - appImManager.getPeerStatus(this.peerId).then((subtitle) => { - if(peerId !== this.peerId) { - return; - } - - this.profileElements.subtitle.textContent = ''; - this.profileElements.subtitle.append(subtitle || ''); - }); - }; - public renderNewMessages(peerId: number, mids: number[]) { if(this.init) return; // * not inited yet @@ -369,17 +693,9 @@ export default class AppSharedMediaTab extends SliderSuperTab { } public cleanupHTML() { - this.profileElements.bio.container.style.display = 'none'; - this.profileElements.phone.container.style.display = 'none'; - this.profileElements.username.container.style.display = 'none'; - this.profileElements.notifications.container.style.display = ''; - this.profileElements.notifications.checkboxField.checked = true; + this.profile.cleanupHTML(); + this.editBtn.style.display = 'none'; - if(this.setBioTimeout) { - window.clearTimeout(this.setBioTimeout); - this.setBioTimeout = 0; - } - this.searchSuper.cleanupHTML(); this.searchSuper.selectTab(0, false); } @@ -403,122 +719,28 @@ export default class AppSharedMediaTab extends SliderSuperTab { //threadId, historyStorage: this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {}) }); - this.cleaned = true; - } - public loadSidebarMedia(single: boolean) { - this.searchSuper.load(single); + this.profile.setPeer(peerId, threadId); + this.cleaned = true; } public fillProfileElements() { - if(!this.cleaned) return; - this.cleaned = false; - - const peerId = this.peerId; - - this.cleanupHTML(); - - this.profileElements.avatar.setAttribute('peer', '' + peerId); - - // username - if(peerId !== rootScope.myId) { - let username = appPeersManager.getPeerUsername(peerId); - if(username) { - setText(appPeersManager.getPeerUsername(peerId), this.profileElements.username); - } - - const muted = appNotificationsManager.isPeerLocalMuted(peerId, false); - this.profileElements.notifications.checkboxField.checked = !muted; - } else { - window.requestAnimationFrame(() => { - this.profileElements.notifications.container.style.display = 'none'; - }); - } - - //let membersLi = this.profileTabs.firstElementChild.children[0] as HTMLLIElement; - if(peerId > 0) { - //membersLi.style.display = 'none'; - - let user = appUsersManager.getUser(peerId); - if(user.phone && peerId !== rootScope.myId) { - setText(user.rPhone, this.profileElements.phone); - } - }/* else { - //membersLi.style.display = appPeersManager.isBroadcast(peerId) ? 'none' : ''; - } */ - - this.setBio(); + this.profile.fillProfileElements(); - this.profileElements.name.innerHTML = ''; - this.profileElements.name.append(new PeerTitle({ - peerId, - dialog: true - }).element); - - if(peerId > 0) { - if(peerId !== rootScope.myId && appUsersManager.isContact(peerId)) { + if(this.peerId > 0) { + if(this.peerId !== rootScope.myId && appUsersManager.isContact(this.peerId)) { this.editBtn.style.display = ''; } } else { - const chat: Chat = appChatsManager.getChat(-peerId); + const chat: Chat = appChatsManager.getChat(-this.peerId); if(chat._ === 'chat' || (chat as Chat.channel).admin_rights) { this.editBtn.style.display = ''; } } - - this.setPeerStatus(true); } - public setBio(override?: true) { - if(this.setBioTimeout) { - window.clearTimeout(this.setBioTimeout); - this.setBioTimeout = 0; - } - - const peerId = this.peerId; - const threadId = this.threadId; - - if(!peerId) { - return; - } - - let promise: Promise; - if(peerId > 0) { - promise = appProfileManager.getProfile(peerId, override).then(userFull => { - if(this.peerId !== peerId || this.threadId !== threadId) { - this.log.warn('peer changed'); - return false; - } - - if(userFull.rAbout && peerId !== rootScope.myId) { - setText(userFull.rAbout, this.profileElements.bio); - } - - //this.log('userFull', userFull); - return true; - }); - } else { - promise = appProfileManager.getChatFull(-peerId, override).then((chatFull) => { - if(this.peerId !== peerId || this.threadId !== threadId) { - this.log.warn('peer changed'); - return false; - } - - //this.log('chatInfo res 2:', chatFull); - - if(chatFull.about) { - setText(RichTextProcessor.wrapRichText(chatFull.about), this.profileElements.bio); - } - - return true; - }); - } - - promise.then((canSetNext) => { - if(canSetNext) { - this.setBioTimeout = window.setTimeout(() => this.setBio(true), 60e3); - } - }); + public loadSidebarMedia(single: boolean) { + this.searchSuper.load(single); } onOpenAfterTimeout() { diff --git a/src/lang.ts b/src/lang.ts index 25ac0fb7..ae51a6ff 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -89,6 +89,7 @@ const lang = { "TwoStepAuth.EmailCodeChangeEmail": "Change Email", "MarkupTooltip.LinkPlaceholder": "Enter URL...", "MediaViewer.Context.Download": "Download", + "Profile": "Profile", // * android "ActionCreateChannel": "Channel created", @@ -484,7 +485,6 @@ const lang = { "Telegram.GeneralSettingsViewController": "General Settings", "Telegram.InstalledStickerPacksController": "Stickers", "Telegram.NotificationSettingsViewController": "Notifications", - "Telegram.PeerInfoController": "Info", "Telegram.LanguageViewController": "Language", "Stickers.SearchAdd": "Add", "Stickers.SearchAdded": "Added", diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 8c370eac..d5247cc6 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -1,7 +1,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import { numberThousandSplitter } from "../../helpers/number"; import { isObject, safeReplaceObject, copy, deepEqual } from "../../helpers/object"; -import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipant, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer"; +import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipant, ChatParticipants, ChatPhoto, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer"; import { i18n, LangPackKey } from "../langPack"; import apiManagerProxy from "../mtproto/mtprotoworker"; import apiManager from '../mtproto/mtprotoworker'; @@ -25,7 +25,7 @@ export class AppChatsManager { //public usernames: any = {}; //public channelAccess: any = {}; //public megagroups: {[id: number]: true} = {}; - public cachedPhotoLocations: {[id: number]: any} = {}; + public cachedPhotoLocations: {[id: number]: ChatPhoto} = {}; public megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}} = {}; @@ -371,10 +371,12 @@ export class AppChatsManager { } public getChatPhoto(id: number) { - const chat = this.getChat(id); + const chat: Chat.chat = this.getChat(id); if(this.cachedPhotoLocations[id] === undefined) { - this.cachedPhotoLocations[id] = chat && chat.photo ? chat.photo : {empty: true}; + this.cachedPhotoLocations[id] = chat && chat.photo || { + _: 'chatPhotoEmpty' + }; } return this.cachedPhotoLocations[id]; diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index 73064faf..d3f7846e 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -1,6 +1,6 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import { isObject } from "../../helpers/object"; -import { DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update } from "../../layer"; +import { ChatPhoto, DialogPeer, InputDialogPeer, InputNotifyPeer, InputPeer, Peer, Update, UserProfilePhoto } from "../../layer"; import { LangPackKey } from "../langPack"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -46,10 +46,12 @@ export class AppPeersManager { return peerId > 0 || appChatsManager.hasRights(-peerId, 'pin_messages'); } - public getPeerPhoto(peerId: number) { - return peerId > 0 + public getPeerPhoto(peerId: number): UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto { + const photo = peerId > 0 ? appUsersManager.getUserPhoto(peerId) : appChatsManager.getChatPhoto(-peerId); + + return photo._ !== 'chatPhotoEmpty' && photo._ !== 'userProfilePhotoEmpty' ? photo : null; } public getPeerMigratedTo(peerId: number) { diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 0b9cb2b8..7f90bd7f 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -129,10 +129,9 @@ export class AppPhotosManager { max_id: maxId }).then((photosResult) => { appUsersManager.saveApiUsers(photosResult.users); - const photoIds: string[] = []; - photosResult.photos.forEach((photo, idx) => { + const photoIds: string[] = photosResult.photos.map((photo, idx) => { photosResult.photos[idx] = this.savePhoto(photo, {type: 'profilePhoto', peerId: userId}); - photoIds.push(photo.id); + return photo.id; }); return { diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 0cfe74ca..10475088 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -2,7 +2,7 @@ import { formatPhoneNumber } from "../../components/misc"; import { MOUNT_CLASS_TO } from "../../config/debug"; import { tsNow } from "../../helpers/date"; import { safeReplaceObject, isObject } from "../../helpers/object"; -import { InputUser, Update, User as MTUser, UserStatus } from "../../layer"; +import { InputUser, Update, User as MTUser, UserProfilePhoto, UserStatus } from "../../layer"; import I18n, { i18n, LangPackKey } from "../langPack"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; @@ -24,7 +24,7 @@ export class AppUsersManager { private users: {[userId: number]: User} = {}; private usernames: {[username: string]: number} = {}; //public userAccess: {[userId: number]: string} = {}; - private cachedPhotoLocations: any = {}; + private cachedPhotoLocations: {[userId: number]: UserProfilePhoto} = {}; private contactsIndex = searchIndexManager.createIndex(); private contactsFillPromise: Promise>; public contactsList: Set = new Set(); @@ -501,10 +501,12 @@ export class AppUsersManager { } public getUserPhoto(id: number) { - var user = this.getUser(id); + const user = this.getUser(id); if(this.cachedPhotoLocations[id] === undefined) { - this.cachedPhotoLocations[id] = user && user.photo ? user.photo : {empty: true}; + this.cachedPhotoLocations[id] = user && user.photo || { + _: 'userProfilePhotoEmpty' + }; } return this.cachedPhotoLocations[id]; diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 0dc9009e..b6b74e43 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1041,7 +1041,7 @@ $chat-helper-size: 39px; cursor: pointer; //--translateY: 0; opacity: 1; - //transition: opacity var(--layer-transition), visibility 0s 0s !important; + transition: opacity var(--layer-transition), visibility 0s 0s !important; visibility: visible; /* &.is-broadcast { @@ -1164,8 +1164,8 @@ $chat-helper-size: 39px; overflow: visible; //--translateY: calc(var(--chat-input-size) + 10px); //--translateY: calc(100% + 10px); - //transition: opacity var(--layer-transition), visibility 0s .2s !important; - transition: opacity var(--layer-transition); + transition: opacity var(--layer-transition), visibility 0s .2s !important; + //transition: opacity var(--layer-transition); transform: none !important; body.animation-level-0 & { diff --git a/src/scss/partials/_chatDrop.scss b/src/scss/partials/_chatDrop.scss index 245c96f6..f774adbe 100644 --- a/src/scss/partials/_chatDrop.scss +++ b/src/scss/partials/_chatDrop.scss @@ -41,7 +41,7 @@ } .drop { - background-color: #fff; + background-color: var(--surface-color); position: relative; //height: 100%; border-radius: $border-radius-big; @@ -130,4 +130,4 @@ body.is-dragging { .page-chats { pointer-events: none; } -} \ No newline at end of file +} diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index 84c3a255..a26bb112 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -19,12 +19,14 @@ box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14); z-index: 3; border-radius: 10px; - transition: transform .2s ease-out; + transition: transform .2s, opacity .2s; + transition-timing-function: cubic-bezier(.4, 0, .2, 1); transform: scale(0); + opacity: 0; transform-origin: 0 100%; &.active { - transition: transform .2s ease-in; + opacity: 1; transform: scale(1); } @@ -182,6 +184,32 @@ width: 42px; height: 42px; + + html:not(.emoji-supported) & { + position: relative; + } + + .emoji-placeholder { + position: absolute; + left: 7px; + top: 7px; + width: 1.75rem; + height: 1.75rem; + border-radius: 50%; + background-color: var(--light-secondary-text-color); + + @include animation-level(2) { + opacity: 0; + transition: opacity .2s ease-in-out; + } + } + + @include animation-level(2) { + img { + opacity: 1; + transition: opacity .2s ease-in-out; + } + } .emoji { width: 100%; diff --git a/src/scss/partials/_profile.scss b/src/scss/partials/_profile.scss new file mode 100644 index 00000000..2e9b3076 --- /dev/null +++ b/src/scss/partials/_profile.scss @@ -0,0 +1,155 @@ +.profile { + &-avatars { + &-container { + width: 100%; + height: 26.25rem; + overflow: hidden; + position: relative; + cursor: pointer; + + /* &:before, &:after { + position: absolute; + content: " "; + height: 100%; + width: + } */ + } + + &-avatars { + width: inherit; + height: inherit; + display: flex; + flex-wrap: nowrap; + + } + + &-avatar { + width: inherit; + height: inherit; + display: flex; + + /* img, video { + width: 100%; + height: 100%; + object-fit: cover; + } */ + } + + &-info { + position: absolute; + bottom: 0; + display: flex; + flex-direction: column; + align-items: flex-start; + left: 1.5rem; + bottom: .5625rem; + + .profile-name, .profile-subtitle { + color: #fff; + margin: 0; + } + + .profile-name { + margin-bottom: -1px; + } + + .profile-subtitle { + opacity: .7; + } + } + } + + &-content { + /* flex: 1 1 auto; */ + flex: 0 0 auto; + display: flex; + flex-direction: column; + /* height: 100%; */ + position: relative; + width: 100%; + + .checkbox-field { + margin: 0; + padding: 0; + margin-left: -54px; + } + + .checkbox-caption { + padding-left: 54px; + } + + &-wrapper { + flex: 1 1 auto; + display: flex; + flex-direction: column; + padding-bottom: 13px; + + @include respond-to(not-handhelds) { + padding-top: 15px; + } + } + + .sidebar-left-section { + //padding-top: .5625rem; + padding-bottom: 0; + } + } + + &-container { + > .scrollable { + display: flex; + flex-direction: column; + //transform: none; + } + } + + &-name { + text-align: center; + font-size: 20px; + line-height: 1.3125; + font-weight: 500; + text-overflow: ellipsis; + overflow: hidden; + word-break: break-word; + max-width: 340px; + margin: 0 auto; + color: var(--primary-text-color); + + span.emoji { + vertical-align: inherit; + min-width: min-content; + } + } + + &-subtitle { + text-align: center; + color: var(--secondary-text-color); + font-size: 14px; + margin-bottom: .875rem; + margin-top: 1px; + + @include respond-to(handhelds) { + margin-top: 3px; + } + + .online { + color: var(--primary-color); + } + } + + &-avatar { + margin: .5rem auto 10px; + display: block; + //flex: 0 0 auto; + + @include respond-to(handhelds) { + margin: 0 auto 10px; + --size: 100px; + --multiplier: .54; + } + } + + &-name, &-subtitle, &-avatar { + flex: 0 0 auto; + } +} diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index 22cada31..eb5d65be 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -93,105 +93,6 @@ } } -.profile { - &-content { - /* flex: 1 1 auto; */ - flex: 0 0 auto; - display: flex; - flex-direction: column; - /* height: 100%; */ - position: relative; - width: 100%; - - .checkbox-field { - margin: 0; - padding: 0; - margin-left: -54px; - } - - .checkbox-caption { - padding-left: 54px; - } - - &-wrapper { - flex: 1 1 auto; - display: flex; - flex-direction: column; - padding-bottom: 13px; - - @include respond-to(not-handhelds) { - padding-top: 15px; - } - } - - .sidebar-left-section { - padding-bottom: 0; - } - } - - &-container { - > .scrollable { - display: flex; - flex-direction: column; - //transform: none; - } - - .row-title { - word-break: break-word; - } - } - - &-name { - text-align: center; - font-size: 20px; - line-height: 1.4; - font-weight: 500; - text-overflow: ellipsis; - overflow: hidden; - word-break: break-word; - max-width: 340px; - margin: 0 auto; - color: var(--primary-text-color); - - span.emoji { - vertical-align: inherit; - min-width: min-content; - } - } - - &-subtitle { - text-align: center; - color: var(--secondary-text-color); - font-size: 14px; - margin-bottom: .875rem; - margin-top: 1px; - - @include respond-to(handhelds) { - margin-top: 3px; - } - - .online { - color: var(--primary-color); - } - } - - &-avatar { - margin: .5rem auto 10px; - display: block; - //flex: 0 0 auto; - - @include respond-to(handhelds) { - margin: 0 auto 10px; - --size: 100px; - --multiplier: .54; - } - } - - &-name, &-subtitle, &-avatar { - flex: 0 0 auto; - } -} - #shared-media-container { /* .search-super { top: 100%; diff --git a/src/scss/style.scss b/src/scss/style.scss index f27d08f3..e0d55c36 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -201,6 +201,7 @@ html.night { @import "partials/chatDrop"; @import "partials/crop"; @import "partials/sidebar"; +@import "partials/profile"; @import "partials/leftSidebar"; @import "partials/rightSidebar"; @import "partials/mediaViewer"; @@ -998,9 +999,9 @@ middle-ellipsis-element { } .row { - min-height: 3.5rem; + min-height: 3rem; position: relative; - padding: .9375rem 1rem; + padding: .6875rem 1rem; display: flex; flex-direction: column; justify-content: center; @@ -1027,7 +1028,7 @@ middle-ellipsis-element { color: var(--primary-text-color); line-height: var(--line-height); - @include text-overflow(); + @include text-overflow(false); &-right { flex: 0 0 auto !important; @@ -1046,6 +1047,7 @@ middle-ellipsis-element { position: absolute; left: 1rem; font-size: 1.5rem; + margin-top: .25rem; color: var(--secondary-text-color); pointer-events: none; }