diff --git a/package-lock.json b/package-lock.json index fd811677..dfe7db45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11779,6 +11779,21 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "qr-code-styling": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/qr-code-styling/-/qr-code-styling-1.0.1.tgz", + "integrity": "sha512-+pxRN6qMU7CJ9tJHKGfAGZS/KmNjpJFnc5I6lI/cW5/bEC9/bsGO5ADip8ob7/xeSoppH+P73ylA7Kn/07tclA==", + "dev": true, + "requires": { + "qrcode-generator": "^1.4.3" + } + }, + "qrcode-generator": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.4.tgz", + "integrity": "sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==", + "dev": true + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", diff --git a/package.json b/package.json index 9531a742..bf115537 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "npm": "^6.14.4", "on-build-webpack": "^0.1.0", "pako": "^1.0.11", + "qr-code-styling": "^1.0.1", "resolve-url-loader": "^3.1.1", "sass-loader": "^8.0.2", "streamsaver": "^2.0.4", diff --git a/src/components/appForward.ts b/src/components/appForward.ts new file mode 100644 index 00000000..278d4594 --- /dev/null +++ b/src/components/appForward.ts @@ -0,0 +1,230 @@ +import appSidebarRight from "../lib/appManagers/appSidebarRight"; +import Scrollable from "./scrollable_new"; +import appProfileManager from "../lib/appManagers/appProfileManager"; +import { appPeersManager } from "../lib/services"; +import appMessagesManager from "../lib/appManagers/appMessagesManager"; +import appDialogsManager from "../lib/appManagers/appDialogsManager"; +import appChatsManager from "../lib/appManagers/appChatsManager"; +import appUsersManager from "../lib/appManagers/appUsersManager"; +import { $rootScope, findUpTag, findUpClassName, cancelEvent } from "../lib/utils"; +import { putPreloader } from "./misc"; + +class AppSelectPeers { + public container = document.createElement('div'); + private chatList = document.createElement('ul'); + private chatsContainer = document.createElement('div'); + private scrollable: Scrollable; + private selectedScrollable: Scrollable; + + private selectedContainer = document.createElement('div'); + private searchInput = document.createElement('input'); + + private selected: {[peerID: number]: HTMLDivElement} = {}; + + public freezed = false; + + constructor(private appendTo: HTMLDivElement, private onChange: (length: number) => void) { + this.container.classList.add('selector'); + + let topContainer = document.createElement('div'); + topContainer.classList.add('selector-search-container'); + + this.selectedContainer.classList.add('selector-search'); + this.searchInput.placeholder = 'Select chat'; + this.searchInput.type = 'text'; + this.selectedContainer.append(this.searchInput); + topContainer.append(this.selectedContainer); + this.selectedScrollable = new Scrollable(topContainer); + + let delimiter = document.createElement('hr'); + + this.chatsContainer.classList.add('chats-container'); + this.chatsContainer.append(this.chatList); + this.scrollable = new Scrollable(this.chatsContainer); + + // в десктопе - сначала без группы, потом архивные, потом контакты без сообщений + let offsetIndex = 0; + appMessagesManager.getConversations(offsetIndex, 50, 0).then(value => { + let dialogs = value.dialogs; + let myID = $rootScope.myID; + + offsetIndex = dialogs[value.dialogs.length - 1].index || 0; + + if(dialogs[0].peerID != myID) { + dialogs.findAndSplice(d => d.peerID == myID); + dialogs.unshift({ + peerID: myID, + pFlags: {} + } as any); + } + + dialogs.forEach(dialog => { + let peerID = dialog.peerID; + + let {dom} = appDialogsManager.addDialog(dialog, this.chatList, false, false); + dom.containerEl.insertAdjacentHTML('afterbegin', '
'); + + let subtitle = ''; + if(peerID < 0) { + subtitle = appChatsManager.getChatMembersString(-peerID); + } else if(peerID == myID) { + subtitle = 'chat with yourself'; + } else { + subtitle = appUsersManager.getUserStatusString(peerID); + if(subtitle == 'online') { + subtitle = `${subtitle}`; + } + } + + dom.lastMessageSpan.innerHTML = subtitle; + }); + }); + + this.chatList.addEventListener('click', (e) => { + let target = e.target as HTMLElement; + cancelEvent(e); + + if(this.freezed) return; + + if(target.tagName != 'LI') { + target = findUpTag(target, 'LI'); + } + + if(!target) return; + + let peerID = +target.getAttribute('data-peerID'); + target.classList.toggle('active'); + if(peerID in this.selected) { + this.remove(peerID); + } else { + this.add(peerID); + } + + let checkbox = target.querySelector('input') as HTMLInputElement; + checkbox.checked = !checkbox.checked; + }); + + this.selectedContainer.addEventListener('click', (e) => { + if(this.freezed) return; + let target = e.target as HTMLElement; + target = findUpClassName(target, 'selector-user'); + + if(!target) return; + + let peerID = target.dataset.peerID; + let li = this.chatList.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement; + li.click(); + }); + + this.container.append(topContainer, delimiter, this.chatsContainer); + appendTo.append(this.container); + } + + private add(peerID: number) { + let div = document.createElement('div'); + div.classList.add('selector-user', 'scale-in'); + div.dataset.peerID = '' + peerID; + this.selected[peerID] = div; + + let title = appPeersManager.getPeerTitle(peerID, false, true); + + let avatarDiv = document.createElement('div'); + avatarDiv.classList.add('user-avatar', 'tgico'); + appProfileManager.putPhoto(avatarDiv, peerID); + + div.innerHTML = title; + div.insertAdjacentElement('afterbegin', avatarDiv); + + this.selectedContainer.insertBefore(div, this.searchInput); + this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight; + this.onChange(Object.keys(this.selected).length); + } + + private remove(peerID: number) { + let div = this.selected[peerID]; + div.classList.remove('scale-in'); + void div.offsetWidth; + div.classList.add('scale-out'); + div.addEventListener('animationend', () => { + delete this.selected[peerID]; + div.remove(); + this.onChange(Object.keys(this.selected).length); + }, {once: true}); + } + + public getSelected() { + return Object.keys(this.selected).map(p => +p); + } +} + +class AppForward { + private container = document.getElementById('forward-container') as HTMLDivElement; + private closeBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement; + private sendBtn = this.container.querySelector('.btn-circle') as HTMLButtonElement; + + private selector: AppSelectPeers; + private msgIDs: number[] = []; + + constructor() { + this.closeBtn.addEventListener('click', () => { + this.cleanup(); + this.container.classList.remove('active'); + appSidebarRight.onSidebarScroll(); + }); + + this.sendBtn.addEventListener('click', () => { + let peerIDs = this.selector.getSelected(); + + if(this.msgIDs.length && peerIDs.length) { + this.sendBtn.classList.remove('tgico-send'); + this.sendBtn.disabled = true; + putPreloader(this.sendBtn); + this.selector.freezed = true; + + let s = () => { + let promises = peerIDs.splice(0, 3).map(peerID => { + return appMessagesManager.forwardMessages(peerID, this.msgIDs); + }); + + Promise.all(promises).then(() => { + if(peerIDs.length) { + return s(); + } else { + this.closeBtn.click(); + } + }); + }; + + s(); + } + }); + } + + public cleanup() { + if(this.selector) { + this.selector.container.remove(); + this.selector = null; + } + } + + public init(ids: number[]) { + this.cleanup(); + this.msgIDs = ids; + + appSidebarRight.toggleSidebar(true); + this.container.classList.add('active'); + this.sendBtn.innerHTML = ''; + this.sendBtn.classList.add('tgico-send'); + this.sendBtn.disabled = false; + + this.selector = new AppSelectPeers(this.container, (length) => { + if(length) { + this.sendBtn.classList.add('is-visible'); + } else { + this.sendBtn.classList.remove('is-visible'); + } + }); + } +} + +export default new AppForward(); \ No newline at end of file diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts index 097ca3d8..5f236775 100644 --- a/src/components/appSearch.ts +++ b/src/components/appSearch.ts @@ -4,17 +4,19 @@ import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager"; import appUsersManager from "../lib/appManagers/appUsersManager"; import appPeersManager from '../lib/appManagers/appPeersManager'; import appMessagesManager from "../lib/appManagers/appMessagesManager"; -import { numberWithCommas, escapeRegExp } from "../lib/utils"; +import { escapeRegExp } from "../lib/utils"; import { formatPhoneNumber } from "./misc"; +import appChatsManager from "../lib/appManagers/appChatsManager"; export class SearchGroup { container: HTMLDivElement; nameEl: HTMLDivElement; list: HTMLUListElement; - constructor(public name: string, public type: string) { + constructor(public name: string, public type: string, private clearable = true, className?: string) { this.list = document.createElement('ul'); this.container = document.createElement('div'); + if(className) this.container.classList.add(className); this.nameEl = document.createElement('div'); this.nameEl.classList.add('search-group__name'); this.nameEl.innerText = name; @@ -28,7 +30,10 @@ export class SearchGroup { clear() { this.container.style.display = 'none'; - this.list.innerHTML = ''; + + if(this.clearable) { + this.list.innerHTML = ''; + } } setActive() { @@ -47,7 +52,7 @@ export default class AppSearch { private query = ''; - private listsContainer: HTMLDivElement = null; + public listsContainer: HTMLDivElement = null; private peerID = 0; // 0 - means global @@ -155,11 +160,7 @@ export default class AppSearch { if(showMembersCount && (peer.participants_count || peer.participants)) { let regExp = new RegExp(`(${escapeRegExp(query)})`, 'gi'); dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '$1'); - - let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); - let participants_count = peer.participants_count || peer.participants.participants.length; - let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); - dom.lastMessageSpan.innerText = subtitle; + dom.lastMessageSpan.innerText = appChatsManager.getChatMembersString(-peerID); } else { let username = appPeersManager.getPeerUsername(peerID); if(!username) { diff --git a/src/components/bubbleGroups.ts b/src/components/bubbleGroups.ts index fc02cb92..4dbd0223 100644 --- a/src/components/bubbleGroups.ts +++ b/src/components/bubbleGroups.ts @@ -154,6 +154,7 @@ export default class BubbleGroups { cleanup() { this.bubblesByGroups = []; + this.groups = []; /* for(let value of this.updateRAFs.values()) { window.cancelAnimationFrame(value); } diff --git a/src/components/chatInput.ts b/src/components/chatInput.ts index e9406fe4..f1a4ffee 100644 --- a/src/components/chatInput.ts +++ b/src/components/chatInput.ts @@ -393,7 +393,7 @@ export class ChatInput { if(!file) continue; willAttach.type = file.type.indexOf('image/') === 0 ? 'media' : "document"; - attachFile(file); + attachFiles([file]); } } }, true); @@ -559,12 +559,12 @@ export class ChatInput { this.onMessageSent(!this.editMsgID); }; - public setTopInfo(title: string, subtitle: string, input?: string, media?: any) { + public setTopInfo(title: string, subtitle: string, input?: string, message?: any) { //appImManager.scrollPosition.prepareFor('down'); if(this.replyElements.container.lastElementChild.tagName == 'DIV') { this.replyElements.container.lastElementChild.remove(); - this.replyElements.container.append(wrapReply(title, subtitle, media)); + this.replyElements.container.append(wrapReply(title, subtitle, message)); } //this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; //this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; diff --git a/src/components/emoticonsDropdown.ts b/src/components/emoticonsDropdown.ts index c86596ab..c38d30a4 100644 --- a/src/components/emoticonsDropdown.ts +++ b/src/components/emoticonsDropdown.ts @@ -141,6 +141,15 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, let div = document.createElement('div'); div.classList.add('emoji-category'); + let titleDiv = document.createElement('div'); + titleDiv.classList.add('category-title'); + titleDiv.innerText = category; + + let itemsDiv = document.createElement('div'); + itemsDiv.classList.add('category-items'); + + div.append(titleDiv, itemsDiv); + let emojis = sorted[category]; emojis.forEach(details => { let emoji = details.unified; @@ -161,7 +170,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, //spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement; //spanEmoji.setAttribute('emoji', emoji); - div.appendChild(spanEmoji); + itemsDiv.appendChild(spanEmoji); }); divs[category] = div; @@ -272,17 +281,23 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, let heights: number[] = []; let heightRAF = 0; - let categoryPush = (categoryDiv: HTMLDivElement, docs: MTDocument[], prepend?: boolean) => { + let categoryPush = (categoryDiv: HTMLDivElement, categoryTitle: string, docs: MTDocument[], prepend?: boolean) => { //if((docs.length % 5) != 0) categoryDiv.classList.add('not-full'); - let container = document.createElement('div'); - categoryDiv.append(container); + let itemsDiv = document.createElement('div'); + itemsDiv.classList.add('category-items'); + + let titleDiv = document.createElement('div'); + titleDiv.classList.add('category-title'); + titleDiv.innerText = categoryTitle; + + categoryDiv.append(titleDiv, itemsDiv); docs.forEach(doc => { let div = document.createElement('div'); wrapSticker(doc, div, undefined, lazyLoadQueue, EMOTICONSSTICKERGROUP, true, false, true); - container.append(div); + itemsDiv.append(div); }); if(prepend) stickersScroll.prepend(categoryDiv); @@ -356,7 +371,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, //stickersScroll.prepend(categoryDiv); - categoryPush(categoryDiv, stickers.stickers, true); + categoryPush(categoryDiv, 'Recent', stickers.stickers, true); }), apiManager.invokeApi('messages.getAllStickers', {hash: 0}).then(async(res) => { @@ -413,7 +428,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, wrapSticker(stickerSet.documents[0], li as any, undefined, undefined, EMOTICONSSTICKERGROUP); // kostil } - categoryPush(categoryDiv, stickerSet.documents, false); + categoryPush(categoryDiv, stickerSet.set.title, stickerSet.documents, false); } }) ]); diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 1120a942..bd478e62 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -15,6 +15,7 @@ export default class LazyLoadQueue { private unlockResolve: () => void = null; private log = console.log.bind(console, '[LL]:'); + private debug = false; constructor(private parallelLimit = 5) { @@ -69,13 +70,14 @@ export default class LazyLoadQueue { let tempID = this.tempID; - this.log('will load media', this.lockPromise, item); + this.debug && this.log('will load media', this.lockPromise, item); try { if(this.lockPromise) { let perf = performance.now(); - await this.lockPromise; - this.log('waited lock:', performance.now() - perf); + await this.lockPromise; + + this.debug && this.log('waited lock:', performance.now() - perf); } await new Promise((resolve, reject) => window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))); @@ -88,7 +90,7 @@ export default class LazyLoadQueue { this.loadingMedia--; } - this.log('loaded media'); + this.debug && this.log('loaded media'); if(this.lazyLoadMedia.length) { this.processQueue(); diff --git a/src/components/misc.ts b/src/components/misc.ts index 69bc2d75..2e4ef7a5 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -107,10 +107,13 @@ let set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceEl }; export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLSourceElement, url: string) { - if(loadedURLs[url]) return set(elem, url); + if(loadedURLs[url]) { + set(elem, url); + return true; + } if(elem instanceof HTMLSourceElement) { - return elem.src = url; + elem.src = url; } else { let loader = new Image(); loader.src = url; @@ -119,6 +122,8 @@ export function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGIma loadedURLs[url] = true; }; } + + return false; } export function putPreloader(elem: Element, returnDiv = false) { diff --git a/src/components/preloader.ts b/src/components/preloader.ts index 68e66fbb..9063f001 100644 --- a/src/components/preloader.ts +++ b/src/components/preloader.ts @@ -49,11 +49,14 @@ export default class ProgressivePreloader { this.promise = promise; let tempID = --this.tempID; - promise.then(() => { + let onEnd = () => { if(tempID == this.tempID) { this.detach(); } - }); + + promise.notify = null; + }; + promise.then(onEnd, onEnd); promise.notify = (details: {done: number, total: number}) => { if(tempID != this.tempID) return; diff --git a/src/components/scrollable_new.ts b/src/components/scrollable_new.ts index 80abb276..66b42ba6 100644 --- a/src/components/scrollable_new.ts +++ b/src/components/scrollable_new.ts @@ -64,7 +64,7 @@ export default class Scrollable { private virtualTempIDBottom = 0; private lastTopID = 0; private lastBottomID = 0; - private lastScrollDirection = true; // true = bottom + private lastScrollDirection = 0; // true = bottom private setVisible(element: HTMLElement) { if(this.visible.has(element)) return; @@ -116,11 +116,11 @@ export default class Scrollable { //this.debug && this.log('intersection entry:', entry, isTop, isBottom, this.lastTopID, this.lastBottomID); }); - if(!filtered.length) { + if(!filtered.length || this.lastScrollDirection === 0) { return; } - if(this.lastScrollDirection) { // bottom + if(this.lastScrollDirection === 1) { // bottom let target = filtered[filtered.length - 1].target as HTMLElement; this.lastBottomID = +target.dataset.virtual; @@ -300,6 +300,7 @@ export default class Scrollable { this.disableHoverTimeout = setTimeout(() => { appendTo.classList.remove('disable-hover'); + this.lastScrollDirection = 0; if(!this.measureMutex.isFulfilled) { this.measureMutex.resolve(); @@ -346,8 +347,12 @@ export default class Scrollable { } } - this.lastScrollDirection = this.lastScrollTop < scrollTop; - this.lastScrollTop = scrollTop; + if(this.lastScrollTop != scrollTop) { + this.lastScrollDirection = this.lastScrollTop < scrollTop ? 1 : -1; + this.lastScrollTop = scrollTop; + } else { + this.lastScrollDirection = 0; + } this.onScrollMeasure = 0; }); } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 9599ce18..46ef2c86 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -115,6 +115,23 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai let source = document.createElement('source'); video.append(source); + + let span: HTMLSpanElement; + if(doc.type != 'round') { + span = document.createElement('span'); + span.classList.add('video-time'); + container.append(span); + + if(doc.type != 'gif') { + span.innerText = (doc.duration + '').toHHMMSS(false); + + let spanPlay = document.createElement('span'); + spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); + container.append(spanPlay); + } else { + span.innerText = 'GIF'; + } + } let loadVideo = () => { let promise = appDocsManager.downloadDoc(doc); @@ -133,7 +150,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai //return; - console.log('loaded doc:', doc, doc.url, blob, container); + //console.log('loaded doc:', doc, doc.url, blob, container); renderImageFromUrl(source, doc.url); source.type = doc.mime_type; @@ -164,7 +181,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai downloadDiv.classList.add('download'); let span = document.createElement('span'); - span.classList.add('tgico-download'); + span.classList.add('btn-circle', 'tgico-download'); downloadDiv.append(span); downloadDiv.addEventListener('click', () => { @@ -172,7 +189,7 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai loadVideo(); }); - container.append(downloadDiv); + container.prepend(downloadDiv); return; } @@ -642,7 +659,7 @@ export function wrapPhoto(photoID: string, message: any, container: HTMLDivEleme } } - console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); + //console.log('wrapPhoto downloaded:', photo, photo.downloaded, container); // так нельзя делать, потому что может быть загружен неправильный размер картинки /* if(photo.downloaded && photo.url) { @@ -727,6 +744,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( return lazyLoadQueue ? (lazyLoadQueue.push({div, load}), Promise.resolve()) : load(); } + let downloaded = doc.downloaded; let load = () => appDocsManager.downloadDoc(doc.id).then(blob => { //console.log('loaded sticker:', blob, div); if(middleware && !middleware()) return; @@ -803,6 +821,16 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( } else if(stickerType == 1) { let img = new Image(); + if(!downloaded && (!div.firstElementChild || div.firstElementChild.tagName != 'IMG')) { + img.style.opacity = '' + 0; + + img.onload = () => { + window.requestAnimationFrame(() => { + img.style.opacity = ''; + }); + }; + } + if(!doc.url) { appWebpManager.polyfillImage(img, blob).then((url) => { doc.url = url; @@ -825,7 +853,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat'}), Promise.resolve()) : load(); } -export function wrapReply(title: string, subtitle: string, media?: any) { +export function wrapReply(title: string, subtitle: string, message?: any) { let div = document.createElement('div'); div.classList.add('reply'); @@ -843,11 +871,15 @@ export function wrapReply(title: string, subtitle: string, media?: any) { replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; + let media = message && message.media; if(media) { - if(media.photo) { + if(message.grouped_id) { + replySubtitle.innerHTML = 'Album'; + } else if(media.photo) { replySubtitle.innerHTML = 'Photo'; } else if(media.document && media.document.type) { - replySubtitle.innerHTML = media.document.type; + let type = media.document.type as string; + replySubtitle.innerHTML = type.charAt(0).toUpperCase() + type.slice(1); // capitalizeFirstLetter } else if(media.webpage) { replySubtitle.innerHTML = RichTextProcessor.wrapPlainText(media.webpage.url); } else { diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts index f7291510..8075b7c7 100644 --- a/src/lib/appManagers/apiUpdatesManager.ts +++ b/src/lib/appManagers/apiUpdatesManager.ts @@ -24,8 +24,8 @@ export class ApiUpdatesManager { }; public channelStates: any = {}; - public myID = 0; + private attached = false; constructor() { apiManager.getUserID().then((id) => { @@ -501,6 +501,9 @@ export class ApiUpdatesManager { } public attach() { + if(this.attached) return; + + this.attached = true; apiManager.setUpdatesProcessor(this.processUpdateMessage.bind(this)); apiManager.invokeApi('updates.getState', {}, {noErrorBox: true}).then((stateResult: any) => { this.updatesState.seq = stateResult.seq; diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index e3e1551a..f1aa0abc 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -1,4 +1,4 @@ -import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy } from "../utils"; +import { $rootScope, isObject, SearchIndexManager, safeReplaceObject, copy, numberWithCommas } from "../utils"; import { RichTextProcessor } from "../richtextprocessor"; import appUsersManager from "./appUsersManager"; @@ -78,6 +78,7 @@ export class AppChatsManager { } public getChat(id: number) { + if(id < 0) id = -id; return this.chats[id] || {id: id, deleted: true, access_hash: this.channelAccess[id]}; } @@ -204,6 +205,14 @@ export class AppChatsManager { return 'g' + id; } + public getChatMembersString(id: number) { + let chat = this.getChat(id); + + let isChannel = this.isChannel(id) && !this.isMegagroup(id); + let participants_count = chat.participants_count || chat.participants.participants.length; + return numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); + } + public wrapForFull(id: number, fullChat: any) { var chatFull = copy(fullChat); var chat = this.getChat(id); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index caf21825..8b009aec 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -1,8 +1,8 @@ import { langPack, findUpClassName, $rootScope, escapeRegExp, whichChild } from "../utils"; import appImManager, { AppImManager } from "./appImManager"; import appPeersManager from './appPeersManager'; -import appMessagesManager, { AppMessagesManager } from "./appMessagesManager"; -import appUsersManager from "./appUsersManager"; +import appMessagesManager, { AppMessagesManager, Dialog } from "./appMessagesManager"; +import appUsersManager, { User } from "./appUsersManager"; import { RichTextProcessor } from "../richtextprocessor"; import { ripple, putPreloader } from "../../components/misc"; //import Scrollable from "../../components/scrollable"; @@ -208,7 +208,7 @@ export class AppDialogsManager { console.time('getDialogs time'); let loadCount = 50/*this.chatsLoadCount */; - this.loadDialogsPromise = appMessagesManager.getConversations('', offset, loadCount, +archived); + this.loadDialogsPromise = appMessagesManager.getConversations(offset, loadCount, +archived); let result = await this.loadDialogsPromise; @@ -535,8 +535,8 @@ export class AppDialogsManager { let d = []; d.push(duration % 60 + ' s'); - if(duration > 60) d.push((duration / 60 | 0) + ' min'); - //if(duration > 3600) d.push((duration / 3600 | 0) + ' h'); + if(duration >= 60) d.push((duration / 60 | 0) + ' min'); + //if(duration >= 3600) d.push((duration / 3600 | 0) + ' h'); suffix = ' (' + d.reverse().join(' ') + ')'; } } @@ -674,22 +674,33 @@ export class AppDialogsManager { return this.doms[peerID] || this.domsArchived[peerID]; } - public addDialog(dialog: { - peerID: number, - pFlags: any, - peer: any, - folder_id?: number - }, container?: HTMLUListElement, drawStatus = true) { + public addDialog(_dialog: Dialog | number, container?: HTMLUListElement, drawStatus = true, rippleEnabled = true, onlyFirstName = false) { + let dialog: Dialog; + + if(typeof(_dialog) === 'number') { + let originalDialog = appMessagesManager.getDialogByPeerID(_dialog)[0]; + if(!originalDialog) { + originalDialog = { + peerID: _dialog, + pFlags: {} + } as any; + } + + dialog = originalDialog; + } else { + dialog = _dialog; + } + let peerID: number = dialog.peerID; if((this.doms[peerID] || this.domsArchived[peerID]) && !container) return; - let title = appPeersManager.getPeerTitle(peerID); + let title = appPeersManager.getPeerTitle(peerID, false, onlyFirstName); let avatarDiv = document.createElement('div'); avatarDiv.classList.add('user-avatar'); - if(drawStatus && peerID != $rootScope.myID) { + if(drawStatus && peerID != $rootScope.myID && dialog.peer) { let peer = dialog.peer; switch(peer._) { @@ -714,7 +725,7 @@ export class AppDialogsManager { titleSpan.classList.add('user-title'); if(peerID == $rootScope.myID) { - title = 'Saved Messages'; + title = onlyFirstName ? 'Saved' : 'Saved Messages'; } //console.log('trying to load photo for:', title); @@ -733,21 +744,24 @@ export class AppDialogsManager { paddingDiv.classList.add('rp'); paddingDiv.append(avatarDiv, captionDiv); - ripple(paddingDiv, (id) => { - this.log('dialogs click element'); - this.lastClickID = id; - - return new Promise((resolve, reject) => { - this.rippleCallback = resolve; - //setTimeout(() => resolve(), 100); - //window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve())); + if(rippleEnabled) { + ripple(paddingDiv, (id) => { + this.log('dialogs click element'); + this.lastClickID = id; + + return new Promise((resolve, reject) => { + this.rippleCallback = resolve; + //setTimeout(() => resolve(), 100); + //window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve())); + }); + }, (id) => { + //console.log('appDialogsManager: ripple onEnd called!'); + if(id == this.lastGoodClickID) { + appImManager.lazyLoadQueue.unlock(); + } }); - }, (id) => { - //console.log('appDialogsManager: ripple onEnd called!'); - if(id == this.lastGoodClickID) { - appImManager.lazyLoadQueue.unlock(); - } - }); + } + let li = document.createElement('li'); li.append(paddingDiv); @@ -807,22 +821,26 @@ export class AppDialogsManager { return {dom, dialog}; } - public setTyping(dialog: any, user: any) { + public setTyping(dialog: Dialog, user: User) { let dom = this.getDialogDom(dialog.peerID); let str = ''; + if(dialog.peerID < 0) { + let s = user.rFirstName || user.username; + if(!s) return; + str = s + ' '; + } let senderBold = document.createElement('i'); - if(dialog.peerID < 0) str = (user.first_name || user.last_name || user.username) + ' '; str += 'typing...'; - senderBold.innerText = str; + senderBold.innerHTML = str; dom.lastMessageSpan.innerHTML = ''; dom.lastMessageSpan.append(senderBold); dom.lastMessageSpan.classList.add('user-typing'); } - public unsetTyping(dialog: any) { + public unsetTyping(dialog: Dialog) { let dom = this.getDialogDom(dialog.peerID); dom.lastMessageSpan.classList.remove('user-typing'); this.setLastMessage(dialog, null, dom); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 3dc047c0..503b2959 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -26,6 +26,7 @@ import Scrollable from '../../components/scrollable_new'; import BubbleGroups from '../../components/bubbleGroups'; import LazyLoadQueue from '../../components/lazyLoadQueue'; import appDocsManager from './appDocsManager'; +import appForward from '../../components/appForward'; console.log('appImManager included!'); @@ -333,6 +334,12 @@ export class AppImManager { if(!bubble) return; + //this.log('chatInner click:', target); + if(target.tagName == 'SPAN') { + (target.parentElement.querySelector('video') as HTMLElement).click(); // hot-fix for time and play button + return; + } + if((target.tagName == 'IMG' && !target.classList.contains('emoji') && !target.parentElement.classList.contains('user-avatar')) || target.tagName == 'image' || target.classList.contains('album-item') @@ -458,7 +465,9 @@ export class AppImManager { return; } - if(e.key == 'Meta' || e.key == 'Control') { + if(e.key == 'Escape' && this.peerID != 0) { // hide current dialog + this.setPeer(0); + } else if(e.key == 'Meta' || e.key == 'Control') { return; } else if(e.key == 'c' && (e.ctrlKey || e.metaKey) && target.tagName != 'INPUT') { return; @@ -566,14 +575,18 @@ export class AppImManager { this.contextMenu.querySelector('.menu-reply').addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message.media); + this.chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); this.chatInputC.replyToMsgID = this.contextMenuMsgID; this.chatInputC.editMsgID = 0; }); + + this.contextMenu.querySelector('.menu-forward').addEventListener('click', () => { + appForward.init([this.contextMenuMsgID]); + }); this.contextMenuEdit.addEventListener('click', () => { let message = appMessagesManager.getMessage(this.contextMenuMsgID); - this.chatInputC.setTopInfo('Editing', message.message, message.message, message.media); + this.chatInputC.setTopInfo('Editing', message.message, message.message, message); this.chatInputC.replyToMsgID = 0; this.chatInputC.editMsgID = this.contextMenuMsgID; }); @@ -848,40 +861,11 @@ export class AppImManager { if(this.myID == this.peerID) { this.subtitleEl.innerText = appSidebarRight.profileElements.subtitle.innerText = ''; } else if(user && user.status) { - let subtitle = ''; - switch(user.status._) { - case 'userStatusRecently': { - subtitle += 'last seen recently'; - break; - } - - case 'userStatusOffline': { - subtitle = 'last seen '; - - let date = user.status.was_online; - let now = Date.now() / 1000; - - if((now - date) < 60) { - subtitle += ' just now'; - } else if((now - date) < 3600) { - subtitle += ((now - date) / 60 | 0) + ' minutes ago'; - } else if(now - date < 86400) { - subtitle += ((now - date) / 3600 | 0) + ' hours ago'; - } else { - let d = new Date(date * 1000); - subtitle += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' + - ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); - } - - break; - } + let subtitle = appUsersManager.getUserStatusString(user.id); - case 'userStatusOnline': { - this.subtitleEl.classList.add('online'); - appSidebarRight.profileElements.subtitle.classList.add('online'); - subtitle = 'online'; - break; - } + if(subtitle == 'online') { + this.subtitleEl.classList.add('online'); + appSidebarRight.profileElements.subtitle.classList.add('online'); } appSidebarRight.profileElements.subtitle.innerText = subtitle; @@ -951,6 +935,10 @@ export class AppImManager { appSidebarRight.toggleSidebar(false); this.topbar.style.display = this.chatInput.style.display = this.goDownBtn.style.display = 'none'; this.cleanup(); + if(appDialogsManager.lastActiveListElement) { + appDialogsManager.lastActiveListElement.classList.remove('active'); + appDialogsManager.lastActiveListElement = null; + } return false; } @@ -1087,9 +1075,9 @@ export class AppImManager { return true; })/* .catch(err => { this.log.error(err); - }) */, + }) *//* , - appSidebarRight.fillProfileElements()/* , + appSidebarRight.fillProfileElements() *//* , appSidebarRight.loadSidebarMedia(true) */ ]).catch(err => { this.log.error('setPeer promises error:', err); @@ -1152,6 +1140,7 @@ export class AppImManager { let bubble = this.bubbles[id]; delete this.bubbles[id]; + this.bubbleGroups.removeBubble(bubble, id); this.unreadedObserver.unobserve(bubble); this.scrollable.removeElement(bubble); //bubble.remove(); @@ -1236,7 +1225,7 @@ export class AppImManager { // reverse means top public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) { - this.log('message to render:', message); + //this.log('message to render:', message); if(message.deleted) return; else if(message.grouped_id) { // will render only last album's message let storage = appMessagesManager.groupedMessagesStorage[message.grouped_id]; @@ -1614,7 +1603,7 @@ export class AppImManager { case 'messageMediaDocument': { let doc = message.media.document; - this.log('messageMediaDocument', doc, bubble); + //this.log('messageMediaDocument', doc, bubble); if(doc.sticker/* && doc.size <= 1e6 */) { bubble.classList.add('sticker'); @@ -1639,7 +1628,7 @@ export class AppImManager { break; } else if(doc.type == 'video' || doc.type == 'gif' || doc.type == 'round'/* && doc.size <= 20e6 */) { - this.log('never get free 2', doc); + //this.log('never get free 2', doc); if(doc.type == 'round') { bubble.classList.add('round'); @@ -1700,7 +1689,7 @@ export class AppImManager { } case 'messageMediaContact': { - this.log('wrapping contact', message); + //this.log('wrapping contact', message); let contactDiv = document.createElement('div'); contactDiv.classList.add('contact'); @@ -1791,7 +1780,7 @@ export class AppImManager { bubble.setAttribute('data-original-mid', message.reply_to_mid); } - bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage.media)); + bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage)); bubble.classList.add('is-reply'); } @@ -2033,7 +2022,7 @@ export class AppImManager { let pageCount = this.bubblesContainer.clientHeight / 38/* * 1.25 */ | 0; //let loadCount = Object.keys(this.bubbles).length > 0 ? 50 : pageCount; - let realLoadCount = 50; + let realLoadCount = Object.keys(this.bubbles).length > 0 ? 40 : pageCount;//let realLoadCount = 50; let loadCount = realLoadCount; if(testScroll) { @@ -2306,4 +2295,5 @@ export class AppImManager { } const appImManager = new AppImManager(); +(window as any).appImManager = appImManager; export default appImManager; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index a1eae2f4..07a17001 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -38,7 +38,7 @@ export type HistoryResult = { unreadSkip: boolean }; -type Dialog = { +export type Dialog = { _: 'dialog', top_message: number, read_inbox_max_id: number, @@ -68,9 +68,14 @@ export class AppMessagesManager { [peerID: string]: HistoryStorage } = {}; public dialogsStorage: { - count: any, - dialogs: Dialog[] - } = {count: null, dialogs: []}; + count: number, + dialogs: { + [folderID: number]: Dialog[] + } + } = { + count: null, + dialogs: {} + }; public pendingByRandomID: {[randomID: string]: [number, number]} = {}; public pendingByMessageID: any = {}; public pendingAfterMsgs: any = {}; @@ -1137,37 +1142,12 @@ export class AppMessagesManager { }; } - public getConversations(query?: string, offsetIndex?: number, limit = 20, folderID = -1) { - //var curDialogStorage = this.dialogsStorage; - //var isSearch = typeof(query) == 'string' && query.length; - let curDialogStorage = this.dialogsStorage.dialogs; + public getConversations(offsetIndex?: number, limit = 20, folderID = 0) { + let curDialogStorage = this.dialogsStorage.dialogs[folderID] ?? (this.dialogsStorage.dialogs[folderID] = []); - if(folderID > 0) { - curDialogStorage = curDialogStorage.filter(d => d.folder_id == folderID); - } else { - curDialogStorage = curDialogStorage.filter(d => d.folder_id != 1); - } - - /* if(isSearch) { - if(!limit || this.cachedResults.query !== query) { - this.cachedResults.query = query; - - var results: any = SearchIndexManager.search(query, this.dialogsIndex); - - this.cachedResults.dialogs = []; - this.dialogsStorage.dialogs.forEach((dialog: any) => { - if(results[dialog.peerID]) { - this.cachedResults.dialogs.push(dialog); - } - }) - this.cachedResults.count = this.cachedResults.dialogs.length; - } - curDialogStorage = this.cachedResults; - } else { */ - this.cachedResults.query = false; - //} + this.cachedResults.query = false; - var offset = 0; + let offset = 0; if(offsetIndex > 0) { for(; offset < curDialogStorage.length; offset++) { if(offsetIndex > curDialogStorage[offset].index) { @@ -1176,7 +1156,7 @@ export class AppMessagesManager { } } - if(/* isSearch || */this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) { + if(this.allDialogsLoaded[folderID] || curDialogStorage.length >= offset + limit) { return Promise.resolve({ dialogs: curDialogStorage.slice(offset, offset + limit), count: curDialogStorage.length @@ -1184,17 +1164,11 @@ export class AppMessagesManager { } return this.getTopMessages(limit, folderID).then(count => { - let curDialogStorage = this.dialogsStorage.dialogs; - - if(folderID > 0) { - curDialogStorage = curDialogStorage.filter(d => d.folder_id == folderID); - } else { - curDialogStorage = curDialogStorage.filter(d => d.folder_id != 1); - } + let curDialogStorage = this.dialogsStorage.dialogs[folderID]; offset = 0; if(offsetIndex > 0) { - for(offset = 0; offset < curDialogStorage.length; offset++) { + for(; offset < curDialogStorage.length; offset++) { if(offsetIndex > curDialogStorage[offset].index) { break; } @@ -1210,20 +1184,14 @@ export class AppMessagesManager { }); } - public getTopMessages(limit: number, folderID = -1): Promise { - var dialogs = this.dialogsStorage.dialogs; + public getTopMessages(limit: number, folderID: number): Promise { + var dialogs = this.dialogsStorage.dialogs[folderID]; var offsetDate = 0; var offsetID = 0; var offsetPeerID = 0; var offsetIndex = 0; var flags = 0; - if(folderID > 0) { - dialogs = dialogs.filter(d => d.folder_id == folderID); - } else { - dialogs = dialogs.filter(d => d.folder_id != 1); - } - if(this.dialogsOffsetDate[folderID]) { offsetDate = this.dialogsOffsetDate[folderID] + serverTimeManager.serverTimeOffset; offsetIndex = this.dialogsOffsetDate[folderID] * 0x10000; @@ -1260,13 +1228,10 @@ export class AppMessagesManager { var maxSeenIdIncremented = offsetDate ? true : false; var hasPrepend = false; - //dialogsResult.dialogs.reverse(); let length = dialogsResult.dialogs.length; let noIDsDialogs: any = {}; for(let i = length - 1; i >= 0; --i) { let dialog = dialogsResult.dialogs[i]; - //} - //dialogsResult.dialogs.forEach((dialog: any) => { this.saveConversation(dialog); if(offsetIndex && dialog.index > offsetIndex) { @@ -1284,8 +1249,6 @@ export class AppMessagesManager { maxSeenIdIncremented = true; } } - //}); - //dialogsResult.dialogs.reverse(); if(Object.keys(noIDsDialogs).length) { //setTimeout(() => { // test bad situation @@ -1315,6 +1278,54 @@ export class AppMessagesManager { }); } + public forwardMessages(peerID: number, mids: number[], options: Partial<{ + withMyScore: boolean + }> = {}) { + peerID = AppPeersManager.getPeerMigratedTo(peerID) || peerID; + mids = mids.sort((a, b) => a - b); + + var flags = 0; + if(options.withMyScore) { + flags |= 256; + } + + let splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids); + let promises: any[] = []; + + for(let channelID in splitted.msgIDs) { + let msgIDs = splitted.msgIDs[channelID]; + let len = msgIDs.length; + let randomIDs = []; + for(let i = 0; i < len; i++) { + randomIDs.push([nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]); + } + + let sentRequestOptions: any = {}; + if(this.pendingAfterMsgs[peerID]) { + sentRequestOptions.afterMessageID = this.pendingAfterMsgs[peerID].messageID; + } + + let promise = apiManager.invokeApi('messages.forwardMessages', { + flags: flags, + from_peer: AppPeersManager.getInputPeerByID(-channelID), + id: msgIDs, + random_id: randomIDs, + to_peer: AppPeersManager.getInputPeerByID(peerID) + }, sentRequestOptions).then((updates) => { + apiUpdatesManager.processUpdateMessage(updates); + }, () => {}).then(() => { + if(this.pendingAfterMsgs[peerID] === sentRequestOptions) { + delete this.pendingAfterMsgs[peerID]; + } + }); + + this.pendingAfterMsgs[peerID] = sentRequestOptions; + promises.push(promise); + } + + return Promise.all(promises); + } + public generateDialogIndex(date?: any) { if(date === undefined) { date = tsNow(true) + serverTimeManager.serverTimeOffset; @@ -1322,9 +1333,9 @@ export class AppMessagesManager { return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF); } - public pushDialogToStorage(dialog: any, offsetDate?: number) { - var dialogs = this.dialogsStorage.dialogs/* .filter(d => d.folder_id == dialog.folder_id) */; - var pos = this.getDialogByPeerID(dialog.peerID)[1]; + public pushDialogToStorage(dialog: Dialog, offsetDate?: number) { + let dialogs = this.dialogsStorage.dialogs[dialog.folder_id] ?? (this.dialogsStorage.dialogs[dialog.folder_id] = []); + let pos = this.getDialogByPeerID(dialog.peerID)[1]; if(pos !== undefined) { dialogs.splice(pos, 1); } @@ -1339,15 +1350,14 @@ export class AppMessagesManager { this.dialogsOffsetDate[dialog.folder_id] = offsetDate; } - var index = dialog.index; - var i; - var len = dialogs.length; + let index = dialog.index; + let len = dialogs.length; if(!len || index < dialogs[len - 1].index) { dialogs.push(dialog); } else if(index >= dialogs[0].index) { dialogs.unshift(dialog); } else { - for(i = 0; i < len; i++) { + for(let i = 0; i < len; i++) { if(index > dialogs[i].index) { dialogs.splice(i, 0, dialog); break; @@ -1377,22 +1387,10 @@ export class AppMessagesManager { public getDialogByPeerID(peerID: number): [Dialog, number] | [] { let dialogs = this.dialogsStorage.dialogs; - let byFolders: {[id: number]: number} = {}; - for(let i = 0, length = dialogs.length; i < length; i++) { - let dialog = dialogs[i]; - if(!byFolders[dialog.folder_id]) byFolders[dialog.folder_id] = 0; - byFolders[dialog.folder_id]++; - - if(dialog.peerID == peerID) { - //return [dialog, i]; - let sum = 0; - for(let id in byFolders) { - if(+id != dialog.folder_id) { - sum += byFolders[id]; - } - } - - return [dialog, i - sum]; + for(let folderID in dialogs) { + let index = dialogs[folderID].findIndex(dialog => dialog.peerID == peerID); + if(index !== -1) { + return [dialogs[folderID][index], index]; } } @@ -1627,7 +1625,7 @@ export class AppMessagesManager { }) } - public migrateChecks(migrateFrom: any, migrateTo: any) { + public migrateChecks(migrateFrom: number, migrateTo: number) { if(!this.migratedFromTo[migrateFrom] && !this.migratedToFrom[migrateTo] && appChatsManager.hasChat(-migrateTo)) { @@ -1641,9 +1639,10 @@ export class AppMessagesManager { setTimeout(() => { var foundDialog = this.getDialogByPeerID(migrateFrom); if(foundDialog.length) { - this.dialogsStorage.dialogs.splice(foundDialog[1], 1); + this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: migrateFrom}); } + $rootScope.$broadcast('dialog_migrate', {migrateFrom: migrateFrom, migrateTo: migrateTo}); }, 100); } @@ -1734,7 +1733,7 @@ export class AppMessagesManager { } else { var foundDialog = this.getDialogByPeerID(peerID); if(foundDialog.length) { - this.dialogsStorage.dialogs.splice(foundDialog[1], 1); + this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: peerID}); } } @@ -1853,256 +1852,8 @@ export class AppMessagesManager { if(channelID && dialog.pts) { apiUpdatesManager.addChannelState(channelID, dialog.pts); } - - /*if(Config.Modes.packed && !channelID && dialog.unread_count > 0 && - this.maxSeenID && dialog.top_message > this.maxSeenID && - message.pFlags.unread && !message.pFlags.out) { - var notifyPeer = message.flags & 16 ? message.from_id : peerID - NotificationsManager.getPeerMuted(notifyPeer).then((muted: any) => { - if(!muted) { - this.notifyAboutMessage(message); - } - }); - }*/ // WARNING } - /*public handleNotifications() { - clearTimeout(this.notificationsHandlePromise); - this.notificationsHandlePromise = 0; - - var timeout = $rootScope.idle.isIDLE /* && StatusManager.isOtherDeviceActive() * ? 30000 : 1000; - Object.keys(this.notificationsToHandle).forEach((key: any) => { - let notifyPeerToHandle = this.notificationsToHandle[key]; - notifyPeerToHandle.isMutedPromise.then((muted: boolean) => { - var topMessage = notifyPeerToHandle.top_message - if(muted || - !topMessage.pFlags.unread) { - return; - } - - setTimeout(() => { - if(topMessage.pFlags.unread) { - this.notifyAboutMessage(topMessage, { - fwd_count: notifyPeerToHandle.fwd_count - }); - } - }, timeout); - }); - }); - - this.notificationsToHandle = {}; - }*/ - - /*public notifyAboutMessage(message: any, options: any = {}) { - var peerID = this.getMessagePeer(message); - var peerString: string; - var notification: any = {}; - var notificationMessage = '', - notificationPhoto; - - var notifySettings: any = {}; //NotificationsManager.getNotifySettings(); // warning - - if(message.fwdFromID && options.fwd_count) { - notificationMessage = options.fwd_count;// this.fwdMessagesPluralize(options.fwd_count); // warning - } else if(message.message) { - if(notifySettings.nopreview) { - notificationMessage = 'conversation_message_sent'; - } else { - notificationMessage = RichTextProcessor.wrapPlainText(message.message); - } - } else if(message.media) { - var captionEmoji = ''; - switch (message.media._) { - case 'messageMediaPhoto': - notificationMessage = _('conversation_media_photo_raw'); - captionEmoji = 'рџ–ј'; - break - case 'messageMediaDocument': - switch (message.media.document.type) { - case 'gif': - notificationMessage = _('conversation_media_gif_raw'); - captionEmoji = 'рџЋ¬' - break - case 'sticker': - notificationMessage = _('conversation_media_sticker'); - var stickerEmoji = message.media.document.stickerEmojiRaw; - if(stickerEmoji !== undefined) { - notificationMessage = RichTextProcessor.wrapPlainText(stickerEmoji) + ' ' + notificationMessage; - } - break; - case 'video': - notificationMessage = _('conversation_media_video_raw'); - captionEmoji = 'рџ“№'; - break; - case 'round': - notificationMessage = _('conversation_media_round_raw'); - captionEmoji = 'рџ“№'; - break; - case 'voice': - case 'audio': - notificationMessage = _('conversation_media_audio_raw'); - break; - default: - if(message.media.document.file_name) { - notificationMessage = RichTextProcessor.wrapPlainText('рџ“Ћ ' + message.media.document.file_name); - } else { - notificationMessage = _('conversation_media_document_raw'); - captionEmoji = 'рџ“Ћ'; - } - break; - } - break; - - case 'messageMediaGeo': - case 'messageMediaVenue': - notificationMessage = _('conversation_media_location_raw'); - captionEmoji = 'рџ“Ќ'; - break; - case 'messageMediaContact': - notificationMessage = _('conversation_media_contact_raw'); - break; - case 'messageMediaGame': - notificationMessage = RichTextProcessor.wrapPlainText('рџЋ® ' + message.media.game.title); - break; - case 'messageMediaUnsupported': - notificationMessage = _('conversation_media_unsupported_raw'); - break; - default: - notificationMessage = _('conversation_media_attachment_raw'); - break; - } - - if(captionEmoji != '' && - message.media.caption) { - notificationMessage = RichTextProcessor.wrapPlainText(captionEmoji + ' ' + message.media.caption); - } - } else if(message._ == 'messageService') { - switch(message.action._) { - case 'messageActionChatCreate': - notificationMessage = _('conversation_group_created_raw'); - break - case 'messageActionChatEditTitle': - notificationMessage = _('conversation_group_renamed_raw'); - break - case 'messageActionChatEditPhoto': - notificationMessage = _('conversation_group_photo_updated_raw'); - break - case 'messageActionChatDeletePhoto': - notificationMessage = _('conversation_group_photo_removed_raw'); - break - case 'messageActionChatAddUser': - case 'messageActionChatAddUsers': - notificationMessage = _('conversation_invited_user_message_raw'); - break - case 'messageActionChatReturn': - notificationMessage = _('conversation_returned_to_group_raw'); - break - case 'messageActionChatJoined': - notificationMessage = _('conversation_joined_group_raw'); - break - case 'messageActionChatDeleteUser': - notificationMessage = _('conversation_kicked_user_message_raw'); - break - case 'messageActionChatLeave': - notificationMessage = _('conversation_left_group_raw'); - break - case 'messageActionChatJoinedByLink': - notificationMessage = _('conversation_joined_by_link_raw'); - break - case 'messageActionChannelCreate': - notificationMessage = _('conversation_created_channel_raw'); - break - case 'messageActionChannelEditTitle': - notificationMessage = _('conversation_changed_channel_name_raw'); - break - case 'messageActionChannelEditPhoto': - notificationMessage = _('conversation_changed_channel_photo_raw') - break - case 'messageActionChannelDeletePhoto': - notificationMessage = _('conversation_removed_channel_photo_raw') - break - case 'messageActionPinMessage': - notificationMessage = _('conversation_pinned_message_raw') - break - case 'messageActionGameScore': - notificationMessage = message.action.score;//this.gameScorePluralize(message.action.score); // warning - break - - case 'messageActionPhoneCall': - switch(message.action.type) { - case 'out_missed': - notificationMessage = _('message_service_phonecall_canceled_raw') - break - case 'in_missed': - notificationMessage = _('message_service_phonecall_missed_raw') - break - case 'out_ok': - notificationMessage = _('message_service_phonecall_outgoing_raw') - break - case 'in_ok': - notificationMessage = _('message_service_phonecall_incoming_raw') - break - } - break - } - } - - if(peerID > 0) { - var fromUser = appUsersManager.getUser(message.from_id); - var fromPhoto = appUsersManager.getUserPhoto(message.from_id); - - notification.title = (fromUser.first_name || '') + - (fromUser.first_name && fromUser.last_name ? ' ' : '') + - (fromUser.last_name || '') - if(!notification.title) { - notification.title = fromUser.phone || _('conversation_unknown_user_raw') - } - - notificationPhoto = fromPhoto - - peerString = appUsersManager.getUserString(peerID) - } else { - notification.title = appChatsManager.getChat(-peerID).title || _('conversation_unknown_chat_raw') - - if(message.from_id > 0) { - var fromUser = appUsersManager.getUser(message.from_id) - notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) + - ' @ ' + - notification.title - } - - notificationPhoto = appChatsManager.getChatPhoto(-peerID) - - peerString = appChatsManager.getChatString(-peerID) - } - - notification.title = RichTextProcessor.wrapPlainText(notification.title) - - notification.onclick = function () { - $rootScope.$broadcast('history_focus', { - peerString: peerString, - messageID: message.flags & 16 ? message.mid : 0 - }) - } - - notification.message = notificationMessage - notification.key = 'msg' + message.mid - notification.tag = peerString - notification.silent = message.pFlags.silent || false - - if(notificationPhoto.location && !notificationPhoto.location.empty) { - apiFileManager.downloadSmallFile(notificationPhoto.location/* , notificationPhoto.size *) - .then((blob) => { - if(message.pFlags.unread) { - notification.image = blob - // NotificationsManager.notify(notification) // warning - } - }) - } else { - // NotificationsManager.notify(notification) // warning - } - }*/ - public mergeReplyKeyboard(historyStorage: any, message: any) { // console.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup) if(!message.reply_markup && @@ -2516,15 +2267,6 @@ export class AppMessagesManager { } return false; - /* if(foundDialog) { - // console.log('done read history', peerID) - foundDialog.unread_count = 0 - $rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0}) - $rootScope.$broadcast('messages_read') - if(historyStorage && historyStorage.history.length) { - foundDialog.read_inbox_max_id = historyStorage.history[0] - } - } */ }).finally(() => { delete historyStorage.readPromise; }); @@ -2593,12 +2335,12 @@ export class AppMessagesManager { } public handleUpdate(update: any) { - console.log('AMM: handleUpdate:', update._); + //console.log('AMM: handleUpdate:', update._); switch(update._) { case 'updateMessageID': { var randomID = update.random_id; var pendingData = this.pendingByRandomID[randomID]; - console.log('AMM updateMessageID:', update, pendingData); + //console.log('AMM updateMessageID:', update, pendingData); if(pendingData) { var peerID: number = pendingData[0]; var tempID = pendingData[1]; @@ -2720,36 +2462,10 @@ export class AppMessagesManager { this.newDialogsHandlePromise = window.setTimeout(this.handleNewDialogs.bind(this), 0); } - /*if(inboxUnread && - ($rootScope.selectedPeerID != peerID || $rootScope.idle.isIDLE)) { - var notifyPeer = message.flags & 16 ? message.from_id : peerID; - var notifyPeerToHandle = this.notificationsToHandle[notifyPeer]; - if(notifyPeerToHandle === undefined) { - notifyPeerToHandle = this.notificationsToHandle[notifyPeer] = { - isMutedPromise: Promise.resolve()/* NotificationsManager.getPeerMuted(notifyPeer), // WARNING - fwd_count: 0, - from_id: 0 - }; - } - - if(notifyPeerToHandle.from_id != message.from_id) { - notifyPeerToHandle.from_id = message.from_id; - notifyPeerToHandle.fwd_count = 0; - } - if(message.fwdFromID) { - notifyPeerToHandle.fwd_count++; - } - - notifyPeerToHandle.top_message = message; - - if(!this.notificationsHandlePromise) { - this.notificationsHandlePromise = window.setTimeout(this.handleNotifications.bind(this), 1000); - } - } */ break; } - case 'updateDialogPinned': { + /* case 'updateDialogPinned': { var peerID = AppPeersManager.getPeerID(update.peer); var foundDialog = this.getDialogByPeerID(peerID); @@ -2826,7 +2542,7 @@ export class AppMessagesManager { } }) break; - } + } */ case 'updateEditMessage': case 'updateEditChannelMessage': { @@ -3092,37 +2808,41 @@ export class AppMessagesManager { delete this.historiesStorage[peerID]; $rootScope.$broadcast('history_forbidden', peerID); } + if(hasDialog != needDialog) { if(needDialog) { this.reloadConversation(-channelID); } else { if(foundDialog[0]) { - this.dialogsStorage.dialogs.splice(foundDialog[1], 1); + this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); $rootScope.$broadcast('dialog_drop', {peerID: peerID}); } } } + break; } case 'updateChannelReload': { - var channelID: number = update.channel_id; - var peerID = -channelID; - var foundDialog = this.getDialogByPeerID(peerID); + let channelID: number = update.channel_id; + let peerID = -channelID; + let foundDialog = this.getDialogByPeerID(peerID); if(foundDialog[0]) { - this.dialogsStorage.dialogs.splice(foundDialog[1], 1); + this.dialogsStorage.dialogs[foundDialog[0].folder_id].splice(foundDialog[1], 1); } + delete this.historiesStorage[peerID]; this.reloadConversation(-channelID).then(() => { $rootScope.$broadcast('history_reload', peerID); }); + break; } case 'updateChannelMessageViews': { - var views = update.views; - var mid = appMessagesIDsManager.getFullMessageID(update.id, update.channel_id); - var message = this.getMessage(mid); + let views = update.views; + let mid = appMessagesIDsManager.getFullMessageID(update.id, update.channel_id); + let message = this.getMessage(mid); if(message && message.views && message.views < views) { message.views = views; $rootScope.$broadcast('message_views', { @@ -3567,57 +3287,6 @@ export class AppMessagesManager { }); } - /* public wrapForDialog(msgID: number, dialog?: any) { - var useCache = msgID && dialog !== undefined; - var unreadCount = dialog && dialog.unread_count; - - if(useCache && this.messagesForDialogs[msgID] !== undefined) { - delete this.messagesForDialogs[msgID].typing; - this.messagesForDialogs[msgID].unreadCount = unreadCount; - return this.messagesForDialogs[msgID]; - } - - var message = copy(this.messagesStorage[msgID]); - - if(!message || !message.to_id) { - if(dialog && dialog.peerID) { - message = { - _: 'message', - to_id: AppPeersManager.getOutputPeer(dialog.peerID), - deleted: true, - date: tsNow(true), - pFlags: {out: true} - } - } else { - return message; - } - } - - message.peerID = this.getMessagePeer(message); - message.peerData = AppPeersManager.getPeer(message.peerID); - message.peerString = AppPeersManager.getPeerString(message.peerID); - message.unreadCount = unreadCount; - message.index = dialog && dialog.index || (message.date * 0x10000); - message.pinned = dialog && dialog.pFlags.pinned || false; - - if(message._ == 'messageService' && message.action.user_id) { - message.action.user = appUsersManager.getUser(message.action.user_id); - } - - if(message.message && message.message.length) { - message.richMessage = RichTextProcessor.wrapRichText(message.message.substr(0, 128), {noLinks: true, noLinebreaks: true}); - } - - message.dateText = message.date; //dateOrTimeFilter(message.date); // warning - - if(useCache) { - message.draft = '';//DraftsManager.getServerDraft(message.peerID); // warning - this.messagesForDialogs[msgID] = message; - } - - return message; - } */ - public fetchSingleMessages() { if(this.fetchSingleMessagesPromise) { return this.fetchSingleMessagesPromise; diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index 665cbfb8..b757793d 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -37,7 +37,7 @@ const AppPeersManager = { return false; }, - getPeerTitle: (peerID: number | any, plainText = false) => { + getPeerTitle: (peerID: number | any, plainText = false, onlyFirstName = false) => { let peer: any = {}; if(!isObject(peerID)) { peer = AppPeersManager.getPeer(peerID); @@ -53,6 +53,10 @@ const AppPeersManager = { } else { title = peer.title; } + + if(onlyFirstName) { + title = title.split(' ')[0]; + } return plainText ? title : RichTextProcessor.wrapEmojiText(title); }, diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index c5e066e2..ec90604c 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -27,6 +27,12 @@ export class AppPhotosManager { private photos: { [id: string]: MTPhoto } = {}; + private documentThumbsCache: { + [docID: string]: { + downloaded: number, + url: string + } + } = {}; public windowW = 0; public windowH = 0; @@ -243,7 +249,7 @@ export class AppPhotosManager { element.setAttributeNS(null, 'width', '' + w); element.setAttributeNS(null, 'height', '' + h); - console.log('set dimensions to svg element:', element, w, h); + //console.log('set dimensions to svg element:', element, w, h); if(element.firstElementChild) { let imageSvg = element.firstElementChild as SVGImageElement; @@ -268,7 +274,10 @@ export class AppPhotosManager { photoSize = this.choosePhotoSize(photo, fullWidth, fullHeight); } - if(photo.downloaded >= photoSize.size && photo.url) { + let isDocument = photo._ == 'document'; + let cacheContext = isDocument ? (this.documentThumbsCache[photo.id] ?? (this.documentThumbsCache[photo.id] = {downloaded: -1, url: ''})) : photo; + + if(cacheContext.downloaded >= photoSize.size && cacheContext.url) { return Promise.resolve(); } @@ -280,7 +289,7 @@ export class AppPhotosManager { // maybe it's a thumb let isPhoto = photoSize.size && photo.access_hash && photo.file_reference; let location = isPhoto ? { - _: photo._ == 'document' ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', + _: isDocument ? 'inputDocumentFileLocation' : 'inputPhotoFileLocation', id: photo.id, access_hash: photo.access_hash, file_reference: photo.file_reference, @@ -296,20 +305,21 @@ export class AppPhotosManager { promise = apiFileManager.downloadSmallFile(location); } - if(typeof(photoID) === 'string') { - let photo = this.photos[photoID]; - promise.then(blob => { - if(!photo.downloaded || photo.downloaded < blob.size) { - photo.downloaded = blob.size; - photo.url = URL.createObjectURL(blob); + promise.then(blob => { + if(!cacheContext.downloaded || cacheContext.downloaded < blob.size) { + cacheContext.downloaded = blob.size; + cacheContext.url = URL.createObjectURL(blob); - console.log('wrote photo:', photo, photoSize, blob); - } - }); - } + //console.log('wrote photo:', photo, photoSize, cacheContext, blob); + } + }); return promise; } + + public getDocumentCachedThumb(docID: string) { + return this.documentThumbsCache[docID]; + } public getPhoto(photoID: any): MTPhoto { return isObject(photoID) ? photoID : this.photos[photoID]; diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index 8e994643..1256e798 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -5,6 +5,16 @@ import appImManager from "./appImManager"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; import AppSearch, { SearchGroup } from "../../components/appSearch"; +import { horizontalMenu } from "../../components/misc"; +import appUsersManager from "./appUsersManager"; +import Scrollable from "../../components/scrollable_new"; +import appPhotosManager from "./appPhotosManager"; +import { appPeersManager } from "../services"; + +const SLIDERITEMSIDS = { + archived: 1, + contacts: 2 +}; class AppSidebarLeft { private sidebarEl = document.getElementById('column-left') as HTMLDivElement; @@ -16,18 +26,36 @@ class AppSidebarLeft { private menuEl = this.toolsBtn.querySelector('.btn-menu'); private savedBtn = this.menuEl.querySelector('.menu-saved'); private archivedBtn = this.menuEl.querySelector('.menu-archive'); + private contactsBtn = this.menuEl.querySelector('.menu-contacts'); private logOutBtn = this.menuEl.querySelector('.menu-logout'); public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; //private log = logger('SL'); - private globalSearch = new AppSearch(this.searchContainer, this.searchInput, { + private searchGroups = { contacts: new SearchGroup('Contacts and Chats', 'contacts'), globalContacts: new SearchGroup('Global Search', 'contacts'), - messages: new SearchGroup('Global Search', 'messages') - }); + messages: new SearchGroup('Global Search', 'messages'), + people: new SearchGroup('People', 'contacts', false, 'search-group-people'), + recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent') + }; + private globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups); + + private _selectTab: (id: number) => void; + private historyTabIDs: number[] = []; + + private contactsList: HTMLUListElement; + private contactsScrollable: Scrollable; + private contactsPromise: Promise; + private contactsInput: HTMLInputElement; constructor() { + let peopleContainer = document.createElement('div'); + peopleContainer.classList.add('search-group-scrollable'); + peopleContainer.append(this.searchGroups.people.list); + this.searchGroups.people.container.append(peopleContainer); + let peopleScrollable = new Scrollable(peopleContainer, 'x'); + this.savedBtn.addEventListener('click', (e) => { ///////this.log('savedbtn click'); setTimeout(() => { // menu doesn't close if no timeout (lol) @@ -37,11 +65,12 @@ class AppSidebarLeft { }); this.archivedBtn.addEventListener('click', (e) => { - appDialogsManager.chatsArchivedContainer.classList.add('active'); - this.toolsBtn.classList.remove('active'); - this.backBtn.classList.add('active'); - //this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle'); - //this.toolsBtn.classList.add('tgico-back'); + this.selectTab(SLIDERITEMSIDS.archived); + }); + + this.contactsBtn.addEventListener('click', (e) => { + this.openContacts(); + this.selectTab(SLIDERITEMSIDS.contacts); }); this.logOutBtn.addEventListener('click', (e) => { @@ -51,6 +80,8 @@ class AppSidebarLeft { this.searchInput.addEventListener('focus', (e) => { this.toolsBtn.classList.remove('active'); this.backBtn.classList.add('active'); + this.searchContainer.classList.remove('hide'); + void this.searchContainer.offsetWidth; // reflow this.searchContainer.classList.add('active'); /* if(!this.globalSearch.searchInput.value) { @@ -59,11 +90,10 @@ class AppSidebarLeft { } } */ - this.searchInput.addEventListener('blur', (e) => { + false && this.searchInput.addEventListener('blur', (e) => { if(!this.searchInput.value) { this.toolsBtn.classList.add('active'); this.backBtn.classList.remove('active'); - this.searchContainer.classList.remove('active'); this.backBtn.click(); } @@ -78,16 +108,129 @@ class AppSidebarLeft { this.toolsBtn.classList.add('active'); this.backBtn.classList.remove('active'); this.searchContainer.classList.remove('active'); - this.globalSearch.reset(); + + setTimeout(() => { + this.searchContainer.classList.add('hide'); + this.globalSearch.reset(); + + this.searchGroups.people.setActive(); + //this.searchGroups.recent.setActive(); + }, 150); }); $rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => { this.archivedCount.innerText = '' + e.detail.count; }); - /* appUsersManager.getTopPeers().then(categories => { - this.log('got top categories:', categories); - }); */ + this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, 420); + this._selectTab(0); + + Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => { + el.addEventListener('click', () => { + console.log('sidebar-close-button click:', this.historyTabIDs); + let closingID = this.historyTabIDs.pop(); // pop current + + // need to clear, and left 1 page for smooth slide + if(closingID == SLIDERITEMSIDS.contacts) { + let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0; + (Array.from(this.contactsList.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove()); + setTimeout(() => { + this.contactsList.innerHTML = ''; + }, 420); + } + + this._selectTab(this.historyTabIDs.pop() || 0); + }); + }); + + appUsersManager.getTopPeers().then(categories => { + console.log('got top categories:', categories); + + let category = categories[0]; + if(!category || !category.peers) { + return; + } + + category.peers.forEach((topPeer: { + _: 'topPeer', + peer: any, + rating: number + }) => { + let peerID = appPeersManager.getPeerID(topPeer.peer); + let {dialog, dom} = appDialogsManager.addDialog(peerID, this.searchGroups.people.list, false, true, true); + + this.searchGroups.people.setActive(); + }); + }); + + let contactsContainer = this.sidebarEl.querySelector('#contacts-container'); + this.contactsInput = contactsContainer.querySelector('#contacts-search'); + this.contactsList = contactsContainer.querySelector('#contacts') as HTMLUListElement; + appDialogsManager.setListClickListener(this.contactsList); + this.contactsScrollable = new Scrollable(this.contactsList.parentElement); + + let prevValue = ''; + this.contactsInput.addEventListener('input', () => { + let value = this.contactsInput.value; + if(prevValue != value) { + this.contactsList.innerHTML = ''; + this.openContacts(prevValue = value); + } + }); + + // preload contacts + appUsersManager.getContacts(); + } + + public openContacts(query?: string) { + if(this.contactsPromise) return this.contactsPromise; + this.contactsScrollable.onScrolledBottom = null; + + this.contactsPromise = appUsersManager.getContacts(query).then(contacts => { + this.contactsPromise = null; + + if(this.historyTabIDs[this.historyTabIDs.length - 1] != SLIDERITEMSIDS.contacts) { + console.warn('user closed contacts before it\'s loaded'); + return; + } + + let sorted = contacts + .map(userID => { + let user = appUsersManager.getUser(userID); + let status = appUsersManager.getUserStatusForSort(user.status); + + return {user, status}; + }) + .sort((a, b) => b.status - a.status); + + let renderPage = () => { + let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0; + let arr = sorted.splice(0, pageCount); + + arr.forEach(({user}) => { + let {dialog, dom} = appDialogsManager.addDialog(user.id, this.contactsList, false); + + let status = appUsersManager.getUserStatusString(user.id); + dom.lastMessageSpan.innerHTML = status == 'online' ? `${status}` : status; + }); + + if(!sorted.length) renderPage = undefined; + }; + + renderPage(); + this.contactsScrollable.onScrolledBottom = () => { + if(renderPage) { + renderPage(); + } else { + this.contactsScrollable.onScrolledBottom = null; + } + }; + }); + } + + public selectTab(id: number) { + this.historyTabIDs.push(id); + this._selectTab(id); } } diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index 2fa63a7d..46584cde 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -59,12 +59,6 @@ class AppSidebarRight { private sharedMediaSelected: HTMLDivElement = null; private lazyLoadQueueSidebar = new LazyLoadQueue(5); - /* public minMediaID: { - [type: string]: number - } = {}; */ - public cleared: { - [type: string]: boolean - } = {}; public historiesStorage: { [peerID: number]: { @@ -121,7 +115,7 @@ class AppSidebarRight { this.scroll.scrollTop -= this.profileTabs.offsetTop; } - this.log('setVirtualContainer', id, this.sharedMediaSelected); + this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount); this.scroll.setVirtualContainer(this.sharedMediaSelected); if(this.prevTabID != -1 && !this.sharedMediaSelected.childElementCount) { // quick brown fix @@ -132,7 +126,10 @@ class AppSidebarRight { this.prevTabID = id; this.scroll.onScroll(); - }, this.onSidebarScroll.bind(this)); + }, () => { + this.onSidebarScroll.bind(this); + this.scroll.onScroll(); + }); let sidebarCloseBtn = this.sidebarEl.querySelector('.sidebar-close-button') as HTMLButtonElement; sidebarCloseBtn.addEventListener('click', () => { @@ -212,24 +209,18 @@ class AppSidebarRight { this.sidebarEl.classList.toggle('active'); } - - public performSearchResult(ids: number[], type: string) { - let peerID = this.peerID; - let sharedMediaDiv: HTMLDivElement; + public filterMessagesByType(ids: number[], type: string) { let messages: any[] = []; for(let mid of ids) { let message = appMessagesManager.getMessage(mid); if(message.media) messages.push(message); } - - let elemsToAppend: HTMLElement[] = []; - - // https://core.telegram.org/type/MessagesFilter + + let filtered: any[] = []; + switch(type) { case 'inputMessagesFilterPhotoVideo': { - sharedMediaDiv = this.sharedMedia.contentMedia; - for(let message of messages) { let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); if(!media) { @@ -241,14 +232,85 @@ class AppSidebarRight { //this.log('broken video', media); continue; } + + filtered.push(message); + } + + break; + } + + case 'inputMessagesFilterDocument': { + for(let message of messages) { + if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') { + continue; + } + + let doc = message.media.document; + if(doc.attributes) { + if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) { + continue; + } + } + + filtered.push(message); + } + break; + } + + case 'inputMessagesFilterUrl': { + for(let message of messages) { + if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') { + continue; + } + filtered.push(message); + } + + break; + } + + case 'inputMessagesFilterMusic': { + for(let message of messages) { + if(!message.media.document || message.media.document.type != 'audio') { + continue; + } + + filtered.push(message); + } + + break; + } + + default: + break; + } + + return filtered; + } + + public performSearchResult(messages: any[], type: string) { + let peerID = this.peerID; + + let sharedMediaDiv: HTMLDivElement; + + let elemsToAppend: HTMLElement[] = []; + + // https://core.telegram.org/type/MessagesFilter + switch(type) { + case 'inputMessagesFilterPhotoVideo': { + sharedMediaDiv = this.sharedMedia.contentMedia; + + for(let message of messages) { + let media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document); + let div = document.createElement('div'); //console.log(message, photo); let isPhoto = media._ == 'photo'; let photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null; - if(!photo || !photo.downloaded) { + let isDownloaded = (photo && photo.downloaded) || appPhotosManager.getDocumentCachedThumb(media.id); + if(!isDownloaded) { //this.log('inputMessagesFilterPhotoVideo', message, media, photo, div); let sizes = media.sizes || media.thumbs; @@ -260,33 +322,39 @@ class AppSidebarRight { } //this.log('inputMessagesFilterPhotoVideo', message, media); + + if(!isPhoto) { + let span = document.createElement('span'); + span.classList.add('video-time'); + div.append(span); + + if(media.type != 'gif') { + span.innerText = (media.duration + '').toHHMMSS(false); + + /* let spanPlay = document.createElement('span'); + spanPlay.classList.add('video-play', 'tgico-largeplay', 'btn-circle', 'position-center'); + div.append(spanPlay); */ + } else { + span.innerText = 'GIF'; + } + } let load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200)) - .then((blob) => { + .then(() => { if($rootScope.selectedPeerID != peerID) { this.log.warn('peer changed'); return; } - if(photo && photo.url) { - renderImageFromUrl(div, photo.url); - } else { - let url = URL.createObjectURL(blob); - this.urlsToRevoke.push(url); - - let img = new Image(); - img.src = url; - img.onload = () => { - div.style.backgroundImage = 'url(' + url + ')'; - }; + let url = (photo && photo.url) || appPhotosManager.getDocumentCachedThumb(media.id).url; + if(url) { + renderImageFromUrl(div, url); } - - //div.style.backgroundImage = 'url(' + url + ')'; }); div.dataset.mid = '' + message.mid; - if(photo && photo.downloaded) load(); + if(isDownloaded) load(); else this.lazyLoadQueueSidebar.push({div, load}); this.lastSharedMediaDiv.append(div); @@ -310,19 +378,6 @@ class AppSidebarRight { sharedMediaDiv = this.sharedMedia.contentDocuments; for(let message of messages) { - if(!message.media.document || message.media.document.type == 'voice' || message.media.document.type == 'audio') { - continue; - } - - let doc = message.media.document; - if(doc.attributes) { - if(doc.attributes.find((a: any) => a._ == "documentAttributeSticker")) { - continue; - } - } - - //this.log('come back down to my knees', message); - let div = wrapDocument(message.media.document, true); elemsToAppend.push(div); } @@ -333,10 +388,6 @@ class AppSidebarRight { sharedMediaDiv = this.sharedMedia.contentLinks; for(let message of messages) { - if(!message.media.webpage || message.media.webpage._ == 'webPageEmpty') { - continue; - } - let webpage = message.media.webpage; let div = document.createElement('div'); @@ -391,10 +442,6 @@ class AppSidebarRight { sharedMediaDiv = this.sharedMedia.contentAudio; for(let message of messages) { - if(!message.media.document || message.media.document.type != 'audio') { - continue; - } - let div = wrapAudio(message.media.document, true); elemsToAppend.push(div); } @@ -412,8 +459,10 @@ class AppSidebarRight { if(elemsToAppend.length) { //window.requestAnimationFrame(() => { - elemsToAppend.forEach(el => this.scroll.append(el, false)); + //elemsToAppend.forEach(el => this.scroll.append(el, false)); //}); + + sharedMediaDiv.append(...elemsToAppend); } if(sharedMediaDiv) { @@ -439,6 +488,8 @@ class AppSidebarRight { let typesToLoad = single ? [this.sharedMediaType] : this.sharedMediaTypes; typesToLoad = typesToLoad.filter(type => !this.loadedAllMedia[type]); if(!typesToLoad.length) return; + + let loadCount = (appPhotosManager.windowH / 130 | 0) * 3; // that's good for all types let historyStorage = this.historiesStorage[peerID] ?? (this.historiesStorage[peerID] = {}); @@ -447,19 +498,35 @@ class AppSidebarRight { let history = historyStorage[type] ?? (historyStorage[type] = []); - let loadCount = (appPhotosManager.windowH / 130 | 0) * 3; - // render from cache - if(history.length && this.usedFromHistory[type] < history.length && this.cleared[type]) { - let ids = history.slice(this.usedFromHistory[type], this.usedFromHistory[type] + loadCount); - this.log('loadSidebarMedia: will render from cache', this.usedFromHistory[type], history, ids, loadCount); - this.usedFromHistory[type] += ids.length; - this.performSearchResult(ids, type); + if(history.length && this.usedFromHistory[type] < history.length) { + let messages: any[] = []; + let used = this.usedFromHistory[type]; + + do { + let ids = history.slice(used, used + loadCount); + this.log('loadSidebarMedia: will render from cache', used, history, ids, loadCount); + used += ids.length; + + messages.push(...this.filterMessagesByType(ids, type)); + } while(messages.length < loadCount && used < history.length); + + // если перебор + if(messages.length > loadCount) { + let diff = messages.length - loadCount; + messages = messages.slice(0, messages.length - diff); + used -= diff; + } + + this.usedFromHistory[type] = used; + if(messages.length) { + this.performSearchResult(messages, type); + } + return Promise.resolve(); } // заливать новую картинку сюда только после полной отправки! - //let maxID = this.minMediaID[type] || 0; let maxID = history[history.length - 1] || 0; let ids = !maxID && appMessagesManager.historiesStorage[peerID] @@ -474,7 +541,7 @@ class AppSidebarRight { ids = ids.concat(value.history); history.push(...ids); - this.log('loadSidebarMedia: search house of glass', type, value, ids, this.cleared); + this.log('loadSidebarMedia: search house of glass', type, value, ids); if($rootScope.selectedPeerID != peerID) { this.log.warn('peer changed'); @@ -484,14 +551,11 @@ class AppSidebarRight { if(value.history.length < loadCount) { this.loadedAllMedia[type] = true; } - - if(this.cleared[type]) { - //ids = history; - delete this.cleared[type]; - } + + this.usedFromHistory[type] = history.length; if(ids.length) { - this.performSearchResult(ids, type); + this.performSearchResult(this.filterMessagesByType(ids, type), type); } }, (err) => { this.log.error('load error:', err); @@ -550,8 +614,6 @@ class AppSidebarRight { this.urlsToRevoke.length = 0; this.sharedMediaTypes.forEach(type => { - //this.minMediaID[type] = 0; - this.cleared[type] = true; this.usedFromHistory[type] = 0; }); diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 8d668bbf..62194a5f 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -5,14 +5,41 @@ import appChatsManager from "./appChatsManager"; import apiManager from '../mtproto/mtprotoworker'; import serverTimeManager from "../mtproto/serverTimeManager"; +export type User = { + _: 'user', + access_hash: string, + first_name: string, + last_name: string, + username: string, + flags: number, + id: number, + phone: string, + photo: any, + + status?: Partial<{ + _: 'userStatusOffline' | 'userStatusOnline' | 'userStatusRecently' | 'userStatusLastWeek' | 'userStatusLastMonth' | 'userStatusEmpty', + wasStatus: any, + was_online: number, + expires: number + }>, + + initials?: string, + num?: number, + pFlags: Partial<{verified: boolean, support: boolean, self: boolean, bot: boolean, min: number, deleted: boolean}>, + rFirstName?: string, + rFullName?: string, + sortName?: string, + sortStatus?: number, +}; + export class AppUsersManager { - public users: any = {}; - public usernames: any = {}; - public userAccess: {[x: number]: string} = {}; + public users: {[userID: number]: User} = {}; + public usernames: {[username: string]: number} = {}; + public userAccess: {[userID: number]: string} = {}; public cachedPhotoLocations: any = {}; public contactsIndex = SearchIndexManager.createIndex(); - public contactsFillPromise: any; - public contactsList: any; + public contactsFillPromise: Promise; + public contactsList: number[]; public myID: number; constructor() { @@ -80,28 +107,27 @@ export class AppUsersManager { }); } - /* public fillContacts () { + public fillContacts() { if(this.contactsFillPromise) { return this.contactsFillPromise; } - return this.contactsFillPromise = MTProto.apiManager.invokeApi('contacts.getContacts', { + return this.contactsFillPromise = apiManager.invokeApi('contacts.getContacts', { hash: 0 }).then((result: any) => { - var userID, searchText; - var i; + var userID; this.contactsList = []; this.saveApiUsers(result.users); - for(var i = 0; i < result.contacts.length; i++) { - userID = result.contacts[i].user_id + result.contacts.forEach((contact: any) => { + userID = contact.user_id; this.contactsList.push(userID); - //SearchIndexManager.indexObject(userID, getUserSearchText(userID), contactsIndex); WARNING - } + SearchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex); + }); return this.contactsList; - }) - } */ + }); + } public getUserSearchText(id: number) { var user = this.users[id]; @@ -120,32 +146,28 @@ export class AppUsersManager { ' ' + serviceText; } - /* function getContacts (query) { - return fillContacts().then(function (contactsList) { - if (angular.isString(query) && query.length) { - var results = SearchIndexManager.search(query, contactsIndex) - var filteredContactsList = [] + public getContacts(query?: string) { + return this.fillContacts().then(contactsList => { + if(query) { + const results: any = SearchIndexManager.search(query, this.contactsIndex); + const filteredContactsList = contactsList.filter(id => !!results[id]); - for (var i = 0; i < contactsList.length; i++) { - if (results[contactsList[i]]) { - filteredContactsList.push(contactsList[i]) - } - } - contactsList = filteredContactsList + contactsList = filteredContactsList; + } + + contactsList.sort((userID1: number, userID2: number) => { + const sortName1 = (this.users[userID1] || {}).sortName || ''; + const sortName2 = (this.users[userID2] || {}).sortName || ''; + if(sortName1 == sortName2) { + return 0; } - contactsList.sort(function (userID1, userID2) { - var sortName1 = (users[userID1] || {}.sortName) || '' - var sortName2 = (users[userID2] || {}.sortName) || '' - if (sortName1 == sortName2) { - return 0 - } - return sortName1 > sortName2 ? 1 : -1 - }) + return sortName1 > sortName2 ? 1 : -1; + }); - return contactsList - }) - } */ + return contactsList; + }); + } public resolveUsername(username: string) { return this.usernames[username] || 0; @@ -235,7 +257,7 @@ export class AppUsersManager { this.userAccess[id] = accessHash; } - public getUserStatusForSort(status: any) { + public getUserStatusForSort(status: User['status']) { if(status) { var expires = status.expires || status.was_online; if(expires) { @@ -255,17 +277,77 @@ export class AppUsersManager { return 0; } - public getUser(id: any) { + public getUser(id: any): User { if(isObject(id)) { return id; } - return this.users[id] || {id: id, deleted: true, num: 1, access_hash: this.userAccess[id]}; + + return this.users[id] || {id: id, pFlags: {deleted: true}, num: 1, access_hash: this.userAccess[id]} as User; } public getSelf() { return this.getUser(this.myID); } + public getUserStatusString(userID: number) { + if(this.isBot(userID)) { + return 'bot'; + } + + let user = this.getUser(userID); + if(!user || !user.status) { + return ''; + } + + let str = ''; + switch(user.status._) { + case 'userStatusRecently': { + str = 'last seen recently'; + break; + } + + case 'userStatusLastWeek': { + str = 'last seen last week'; + break; + } + + case 'userStatusLastMonth': { + str = 'last seen last month'; + break; + } + + case 'userStatusOffline': { + str = 'last seen '; + + let date = user.status.was_online; + let now = Date.now() / 1000; + + if((now - date) < 60) { + str += ' just now'; + } else if((now - date) < 3600) { + let c = (now - date) / 60 | 0; + str += c + ' ' + (c == 1 ? 'minute' : 'minutes') + ' ago'; + } else if(now - date < 86400) { + let c = (now - date) / 3600 | 0; + str += c + ' ' + (c == 1 ? 'hour' : 'hours') + ' ago'; + } else { + let d = new Date(date * 1000); + str += ('0' + d.getDate()).slice(-2) + '.' + ('0' + (d.getMonth() + 1)).slice(-2) + ' at ' + + ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2); + } + + break; + } + + case 'userStatusOnline': { + str = 'online'; + break; + } + } + + return str; + } + public isBot(id: number) { return this.users[id] && this.users[id].pFlags.bot; } @@ -345,12 +427,6 @@ export class AppUsersManager { } } - public wrapForFull(id: number) { - var user = this.getUser(id); - - return user; - } - /* function importContact (phone, firstName, lastName) { return MtpApiManager.invokeApi('contacts.importContacts', { contacts: [{ @@ -426,7 +502,7 @@ export class AppUsersManager { flags: 1, correspondents: true, offset: 0, - limit: 5, + limit: 30, hash: 0, }).then((peers: any) => { //console.log(peers); @@ -477,7 +553,7 @@ export class AppUsersManager { var user = this.users[userID]; if(user) { - var status = offline ? { + var status: any = offline ? { _: 'userStatusOffline', was_online: tsNow(true) } : { diff --git a/src/lib/bin_utils.ts b/src/lib/bin_utils.ts index 69ccbabf..bc4c4a61 100644 --- a/src/lib/bin_utils.ts +++ b/src/lib/bin_utils.ts @@ -66,7 +66,7 @@ export function bytesFromHex(hexString: string) { return bytes; } -export function bytesToBase64(bytes: number[]) { +export function bytesToBase64(bytes: number[] | Uint8Array) { var mod3 var result = '' diff --git a/src/lib/lottieLoader.ts b/src/lib/lottieLoader.ts index cf28e1fd..c73cb8e9 100644 --- a/src/lib/lottieLoader.ts +++ b/src/lib/lottieLoader.ts @@ -20,7 +20,7 @@ class LottieLoader { public loadLottie() { if(this.loaded) return this.loaded; - this.loaded = new Promise((resolve, reject) => { + return this.loaded = new Promise((resolve, reject) => { (window as any).lottieLoaded = () => { console.log('lottie loaded'); this.lottie = (window as any).lottie; diff --git a/src/lib/mtproto/apiManager.ts b/src/lib/mtproto/apiManager.ts index cf3171b7..2db4525b 100644 --- a/src/lib/mtproto/apiManager.ts +++ b/src/lib/mtproto/apiManager.ts @@ -59,6 +59,10 @@ export class ApiManager { $rootScope.$broadcast('user_auth', fullUserAuth); /// #endif } + + public setBaseDcID(dcID: number) { + this.baseDcID = dcID; + } // mtpLogOut public async logOut() { @@ -68,9 +72,7 @@ export class ApiManager { for(let dcID = 1; dcID <= 5; dcID++) { storageKeys.push(prefix + dcID + '_auth_key'); - //storageKeys.push('dc' + dcID + '_auth_keyID'); - //storageKeys.push('t_dc' + dcID + '_auth_key'); - //storageKeys.push('t_dc' + dcID + '_auth_keyID'); + //storageKeys.push(prefix + dcID + '_auth_keyID'); } // WebPushApiManager.forceUnsubscribe(); // WARNING @@ -95,9 +97,9 @@ export class ApiManager { error.handled = true; this.telegramMeNotify(false); this.mtpClearStorage(); - }).then(() => { + })/* .then(() => { location.pathname = '/'; - }); + }) */; } public mtpClearStorage() { diff --git a/src/lib/mtproto/mtproto.worker.js b/src/lib/mtproto/mtproto.worker.js index abcd31a7..3baf0efd 100644 --- a/src/lib/mtproto/mtproto.worker.js +++ b/src/lib/mtproto/mtproto.worker.js @@ -26,15 +26,26 @@ ctx.onmessage = function(e) { ctx.postMessage({taskID: taskID, result: result}); }); - default: - return apiManager[e.data.task].apply(apiManager, e.data.args).then(result => { - //console.log(e.data.task + ' result:', result, taskID); - ctx.postMessage({taskID: taskID, result: result}); - }).catch(err => { - //console.error(e.data.task + ' err:', err, taskID); + default: { + try { + let result = apiManager[e.data.task].apply(apiManager, e.data.args); + if(result instanceof Promise) { + result.then(result => { + //console.log(e.data.task + ' result:', result, taskID); + ctx.postMessage({taskID: taskID, result: result}); + }).catch(err => { + //console.error(e.data.task + ' err:', err, taskID); + ctx.postMessage({taskID: taskID, error: err}); + }); + } else { + ctx.postMessage({taskID: taskID, result: result}); + } + } catch(err) { ctx.postMessage({taskID: taskID, error: err}); - }); + } + //throw new Error('Unknown task: ' + e.data.task); + } } } diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 22faa125..80a1d4f1 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -125,6 +125,10 @@ class ApiManagerProxy extends CryptoWorkerMethods { return this.performTaskWorker('invokeApi', method, params, options); } + public setBaseDcID(dcID: number) { + return this.performTaskWorker('setBaseDcID', dcID); + } + public setUserAuth(userAuth: {id: number}) { $rootScope.$broadcast('user_auth', userAuth); return this.performTaskWorker('setUserAuth', userAuth); diff --git a/src/lib/mtproto/serverTimeManager.ts b/src/lib/mtproto/serverTimeManager.ts index a4cd89fa..cb569bc9 100644 --- a/src/lib/mtproto/serverTimeManager.ts +++ b/src/lib/mtproto/serverTimeManager.ts @@ -8,7 +8,7 @@ export class ServerTimeManager { public midnightOffset = this.midnightNoOffset - (Math.floor(+this.midnightOffseted / 1000)); - public serverTimeOffset = 0; + public serverTimeOffset = 0; // in seconds public timeParams = { midnightOffset: this.midnightOffset, serverTimeOffset: this.serverTimeOffset diff --git a/src/pages/pageSignQR.ts b/src/pages/pageSignQR.ts new file mode 100644 index 00000000..da828a64 --- /dev/null +++ b/src/pages/pageSignQR.ts @@ -0,0 +1,158 @@ +//import apiManager from '../lib/mtproto/apiManager'; +import apiManager from '../lib/mtproto/mtprotoworker'; +import Page from './page'; +import pageIm from './pageIm'; +import pagePassword from './pagePassword'; +import { App } from '../lib/mtproto/mtproto_config'; +import { bytesToBase64, bytesCmp } from '../lib/bin_utils'; +import serverTimeManager from '../lib/mtproto/serverTimeManager'; +import { User } from '../lib/appManagers/appUsersManager'; + +/* interface Authorization { + _: 'authorization', + flags: number, + pFlags: Partial<{current: true, official_app: true, password_pending: true}>, + hash: number[], + device_model: string, + platform: string, + system_version: string, + api_id: number, + app_name: string, + app_version: string, + date_created: number, + date_active: number, + ip: string, + country: string, + region: string +}; */ + +interface AuthAuthorization { + flags: number, + pFlags: Partial<{tmp_sessions: number}>, + user: User +} + +interface LoginToken { + _: 'auth.loginToken', + expires: number, + token: Uint8Array +}; + +interface LoginTokenMigrateTo { + _: 'auth.loginTokenMigrateTo', + dc_id: number, + token: Uint8Array +}; + +interface LoginTokenSuccess { + _: 'auth.loginTokenSuccess', + authorization: AuthAuthorization +}; + +let onFirstMount = async() => { + const pageElement = page.pageEl; + const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement; + + const results = await Promise.all([ + import('qr-code-styling' as any) + ]); + const QRCodeStyling = results[0].default; + + let stop = false; + document.addEventListener('user_auth', () => { + stop = true; + }, {once: true}); + + let options: {dcID?: number} = {}; + let prevToken: Uint8Array; + + do { + if(stop) { + break; + } + + try { + let loginToken: LoginToken | LoginTokenMigrateTo | LoginTokenSuccess = await apiManager.invokeApi('auth.exportLoginToken', { + api_id: App.id, + api_hash: App.hash, + except_ids: [] + }/* , options */); + + if(loginToken._ == 'auth.loginTokenMigrateTo') { + if(!options.dcID) { + options.dcID = loginToken.dc_id; + apiManager.setBaseDcID(loginToken.dc_id); + //continue; + } + + loginToken = await apiManager.invokeApi('auth.importLoginToken', { + token: loginToken.token + }, options) as LoginToken; + } + + if(loginToken._ == 'auth.loginTokenSuccess') { + let authorization = loginToken.authorization; + apiManager.setUserAuth({ + id: authorization.user.id + }); + pageIm.mount(); + break; + } + + /* // to base64 + var decoder = new TextDecoder('utf8'); + var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */ + + if(!prevToken || !bytesCmp(prevToken, loginToken.token)) { + prevToken = loginToken.token; + + let encoded = bytesToBase64(loginToken.token); + let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""); + + imageDiv.innerHTML = ''; + const qrCode = new QRCodeStyling({ + width: 166, + height: 166, + data: url, + image: "assets/img/logo_padded.svg", + dotsOptions: { + color: "#000000", + type: "rounded" + }, + imageOptions: { + imageSize: .75 + }, + backgroundOptions: { + color: "#ffffff" + }, + qrOptions: { + errorCorrectionLevel: "L" + } + }); + qrCode.append(imageDiv); + } + + let timestamp = Date.now() / 1000; + let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset; + + await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0)); + } catch(err) { + switch(err.type) { + case 'SESSION_PASSWORD_NEEDED': + console.warn('pageSignQR: SESSION_PASSWORD_NEEDED'); + err.handled = true; + pagePassword.mount(); + break; + default: + console.error('pageSignQR: default error:', err); + break; + } + } + } while(true); +}; + +const page = new Page('page-signQR', true, () => { + onFirstMount(); +}); + +export default page; diff --git a/src/pages/pageSignUp.ts b/src/pages/pageSignUp.ts index 0bf671e4..3e3eb526 100644 --- a/src/pages/pageSignUp.ts +++ b/src/pages/pageSignUp.ts @@ -5,7 +5,6 @@ import pageIm from './pageIm'; import apiManager from '../lib/mtproto/mtprotoworker'; import apiFileManager from '../lib/mtproto/apiFileManager'; import Page from './page'; -import { calcImageInBox } from '../lib/utils'; let authCode: { 'phone_number': string, diff --git a/src/scss/components/_global.scss b/src/scss/components/_global.scss index 965253d2..22fa24d3 100644 --- a/src/scss/components/_global.scss +++ b/src/scss/components/_global.scss @@ -32,13 +32,6 @@ a { align-items: center; } - -// classic clearfix -.clearfix { - clear: both; -} - - // Z-levels .z-depth-0 { box-shadow: none !important; @@ -82,54 +75,6 @@ a { 0 11px 15px -7px rgba(0,0,0,0.2); } -.hoverable { - transition: box-shadow .25s; - - &:hover { - box-shadow: 0 8px 17px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - } -} - -// Icon Styles - -i { - line-height: inherit; - - &.left { - float: left; - margin-right: 15px; - } - &.right { - float: right; - margin-left: 15px; - } - &.tiny { - font-size: 1rem; - } - &.small { - font-size: 2rem; - } - &.medium { - font-size: 4rem; - } - &.large { - font-size: 6rem; - } -} - -/********************* -Transition Classes -**********************/ - -ul.staggered-list li { - opacity: 0; -} - -.fade-in { - opacity: 0; - transform-origin: 0 50%; -} - /******************* Utility Classes *******************/ @@ -138,29 +83,11 @@ Utility Classes display: none !important; } -// Text Align -.left-align { - text-align: left; -} -.right-align { - text-align: right -} -.center, .center-align { - text-align: center; -} - -.left { - float: left !important; -} -.right { - float: right !important; -} - // No Text Select .no-select { user-select: none; } -.circle { - border-radius: 50%; +.center-align { + text-align: center; } diff --git a/src/scss/components/_typography.scss b/src/scss/components/_typography.scss index 2a94424f..97a4be83 100644 --- a/src/scss/components/_typography.scss +++ b/src/scss/components/_typography.scss @@ -1,39 +1,16 @@ - a { text-decoration: none; } -html{ +html { line-height: 1.5; - /* @media only screen and (min-width: 0) { - font-size: 14px; - } - - @media only screen and (min-width: $medium-screen) { - font-size: 14.5px; - } - - @media only screen and (min-width: $large-screen) { - font-size: 15px; - } */ - font-weight: normal; } h1, h2, h3, h4, h5, h6 { - font-weight: 400; line-height: 1.3; } -// Header Styles -h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } -/* h1 { font-size: $h1-fontsize; line-height: 110%; margin: ($h1-fontsize / 1.5) 0 ($h1-fontsize / 2.5) 0;} -h2 { font-size: $h2-fontsize; line-height: 110%; margin: ($h2-fontsize / 1.5) 0 ($h2-fontsize / 2.5) 0;} -h3 { font-size: $h3-fontsize; line-height: 110%; margin: ($h3-fontsize / 1.5) 0 ($h3-fontsize / 2.5) 0;} -h4 { font-size: $h4-fontsize; line-height: 110%; margin: ($h4-fontsize / 1.5) 0 ($h4-fontsize / 2.5) 0;} -h5 { font-size: $h5-fontsize; line-height: 110%; margin: ($h5-fontsize / 1.5) 0 ($h5-fontsize / 2.5) 0;} -h6 { font-size: $h6-fontsize; line-height: 110%; margin: ($h6-fontsize / 1.5) 0 ($h6-fontsize / 2.5) 0;} */ - // Text Styles em { font-style: italic; } strong { font-weight: 500; } diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 64b0fc00..6434f800 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1,4 +1,5 @@ $chat-max-width: 696px; +$time-background: rgba(0, 0, 0, 0.35); #bubble-contextmenu > div { padding: 0 84px 0 16px; @@ -75,6 +76,10 @@ $chat-max-width: 696px; .chat-more-button { margin-left: 8px; + + .btn-menu { + top: calc(100% + 7px); + } } .chat-info { @@ -134,7 +139,7 @@ $chat-max-width: 696px; /* position: absolute; bottom: 0; left: 0; */ - position: relative; + //position: relative; // неизвестно зачем это было //display: flex; // for end //flex-direction: unset; @@ -163,6 +168,10 @@ $chat-max-width: 696px; width: 50px; height: 50px; } + + &-container .preloader-circular { + background-color: $time-background; + } } #bubbles-inner { @@ -185,6 +194,8 @@ $chat-max-width: 696px; } &.is-channel:not(.is-chat) { + padding-bottom: 55px; + .bubble__container { max-width: 100%; } @@ -250,9 +261,11 @@ $chat-max-width: 696px; .bubble { padding-top: 5px; - display: grid; + /* display: grid; grid-template-columns: 1fr $chat-max-width 1fr; - grid-row-gap: 0px; + grid-row-gap: 0px; */ + max-width: $chat-max-width; + margin: 0 auto; &.is-date { position: -webkit-sticky; @@ -268,10 +281,10 @@ $chat-max-width: 696px; } } - &:before, &:after { + /* &:before, &:after { content: " "; width: 100%; - } + } */ &__container { //min-width: 60px; @@ -303,7 +316,8 @@ $chat-max-width: 696px; padding: 5px 0; .bubble__container { - justify-self: center; + /* justify-self: center; */ + margin: 0 auto; max-width: 100%; } } @@ -536,6 +550,8 @@ $chat-max-width: 696px; height: auto; max-width: 100%; cursor: pointer; + opacity: 1; + transition: opacity .3s ease; } .download { @@ -550,15 +566,15 @@ $chat-max-width: 696px; align-items: center; span { - width: 54px; - height: 54px; - line-height: 54px; - background-color: rgba(0, 0, 0, .7); - border-radius: 50%; + background-color: $time-background; font-size: 23px; color: #fff; text-align: center; } + + & ~ .video-play { + display: none; + } } } @@ -894,7 +910,7 @@ $chat-max-width: 696px; bottom: .1rem; right: .2rem; border-radius: 12px; - background-color: rgba(0, 0, 0, .4); + background-color: $time-background; padding: 0 .2rem; z-index: 2; @@ -948,6 +964,32 @@ $chat-max-width: 696px; } } + span.video-time { + position: absolute; + top: 3px; + left: 3px; + border-radius: 12px; + background-color: $time-background; + padding: 0px 6px 0px 6px; + z-index: 2; + font-size: 12px; + color: white; + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + } + + span.video-play { + background-color: $time-background; + color: #fff; + text-align: center; + font-size: 34px; + line-height: 60px; + cursor: pointer; + } + &.is-edited.channel-post .time { min-width: calc(5rem + 46px); } @@ -998,6 +1040,10 @@ $chat-max-width: 696px; } } } + + &:not(.webpage):not(.is-album):not(.sticker):not(.round) .attachment, .album-item { + background-color: #000; + } &.hide-name:not(.is-reply):not(.is-message-empty) .message { //padding-top: .2675rem; @@ -1141,6 +1187,22 @@ $chat-max-width: 696px; .audio-subtitle, .contact-number, .audio-time { color: #707579 !important; } + + .message.audio-message { + .media-progress { + &__seek { + background: rgba(193, 207, 220, 0.39); + } + + &__filled { + background-color: #0089ff; + } + + input::-webkit-slider-thumb { + background: #63a2e3; + } + } + } } .is-out { diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index f33ff7d4..d121cd98 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -262,46 +262,57 @@ font-weight: 500; } - &:not(.search-group-messages) { - .user-avatar { - width: 48px; - height: 48px; - } - } - &-contacts { padding: 16px 0 7px; - li { - //margin-bottom: 2px; - padding-bottom: 4px; - padding-top: 2px; - } - - li > .rp { - padding: 9px 11.5px !important; - height: 66px; - } - .search-group__name { padding-bottom: 17px; } + } + } +} - .user-caption { - padding: 1px 3.5px 1px 13px; - } +// use together like class="chats-container contacts-container" +.contacts-container, .search-group-contacts { + .user-avatar { + width: 48px; + height: 48px; + } - .user-title, b, .user-last-message b { - font-weight: normal; - } + li { + //margin-bottom: 2px; + padding-bottom: 4px; + padding-top: 2px; + } - p { - height: 24px; - } + li > .rp { + padding: 9px 11.5px !important; + height: 66px; + } - span.user-last-message { - font-size: 14px; - } - } + .user-caption { + padding: 1px 3.5px 1px 13px; + } + + .user-title, b, .user-last-message b { + font-weight: normal; + } + + p { + height: 24px; + } + + span.user-last-message { + font-size: 14px; + } +} + +#contacts-container { + .sidebar-header { + margin-bottom: 1px; + } + + .input-search { + margin-left: 16px; } } diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss index ef497597..c25f0da4 100644 --- a/src/scss/partials/_emojiDropdown.scss +++ b/src/scss/partials/_emojiDropdown.scss @@ -49,42 +49,57 @@ .tabs-container { /* width: 300%; */ height: 100%; + + .category-title { + position: sticky; + top: 0; + font-size: .85rem; + color: $color-gray; + background: linear-gradient(to bottom,#fff 0,rgba(255,255,255,.9) 60%,rgba(255,255,255,0) 100%); + z-index: 2; + padding: .53333rem 6PX .66667rem; + width: 100%; + } .emoji-category { - font-size: 2.25rem; - line-height: 2.25rem; padding-top: 1px; + position: relative; - display: grid; - grid-column-gap: 2.44px; - grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + .category-items { + display: grid; + grid-column-gap: 2.44px; + grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; + + font-size: 2.25rem; + line-height: 2.25rem; + + > * { + margin: 0; + padding: 4px 4px; + line-height: inherit; + border-radius: 8px; + cursor: pointer; + user-select: none; + -webkit-user-select: none; + + width: 42px; + height: 42px; + + .emoji { + width: 100%; + height: 100%; + } + + &:hover { + background-color: rgba(112, 117, 121, 0.08); + } + } + } &:first-child { padding-top: 5px; } - - > * { - margin: 0; - padding: 4px 4px; - line-height: inherit; - border-radius: 8px; - cursor: pointer; - user-select: none; - -webkit-user-select: none; - - width: 42px; - height: 42px; - - .emoji { - width: 100%; - height: 100%; - } - - &:hover { - background-color: rgba(112, 117, 121, 0.08); - } - } - + /* &::after { content: ""; flex: auto; @@ -92,58 +107,53 @@ } .sticker-category { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; + position: relative; &::after { content: ""; flex: auto; } - } - - .sticker-category { + /* &.not-full::after { content: ""; flex: auto; } */ - > div { + .category-items { width: 100%; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; - } - - > div > div { - width: 80px; - height: 80px; - display: flex; - align-items: center; - /* overflow: hidden; */ - cursor: pointer; - user-select: none; - -webkit-user-select: none; - /* margin: 3.5px 0; - margin-right: 6.25px; */ - padding: 1px 2.5px; - justify-content: center; - border-radius: 12px; - padding: 0; - - &:hover { - background-color: rgba(112, 117, 121, 0.08); - } - - /* &:nth-child(5n+5) { - margin-right: 0; - } */ - - > * { - max-width: 100%; - max-height: 100%; + + > div { + width: 80px; + height: 80px; + display: flex; + align-items: center; + /* overflow: hidden; */ + cursor: pointer; + user-select: none; + -webkit-user-select: none; + /* margin: 3.5px 0; + margin-right: 6.25px; */ + padding: 1px 2.5px; + justify-content: center; + border-radius: 12px; + padding: 0; + + &:hover { + background-color: rgba(112, 117, 121, 0.08); + } + + /* &:nth-child(5n+5) { + margin-right: 0; + } */ + + > * { + max-width: 100%; + max-height: 100%; + } } } } diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss index 1095cb61..fb119e03 100644 --- a/src/scss/partials/_ico.scss +++ b/src/scss/partials/_ico.scss @@ -4,3 +4,4 @@ $tgico-font-path: "../../assets/fonts" !default; $tgico-check: "\e900"; $tgico-checks: "\e95a"; $tgico-sending: "\e919"; +$tgico-close: "\e943"; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 50da26d0..c69a28e3 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -8,6 +8,10 @@ position: relative; } + .sidebar-slider { + height: 100%; + } + .sidebar-header__btn-container { position: relative; width: 39.75px; @@ -67,5 +71,70 @@ } } - + .search-group-people { + ul { + display: flex; + flex-direction: row; + padding-left: 4px; + margin-top: -1px; + padding-bottom: 1px; + } + + li { + margin-right: 5px; + padding: 0; + } + + .rp { + height: 98px; + max-height: 98px; + border-radius: 10px; + max-width: 78px; + width: 78px; + align-items: center; + position: relative; + display: flex; + flex-direction: column; + cursor: pointer; + padding: 12px 0 0 !important; + overflow: hidden; + margin: 0; + } + + .user-avatar { + width: 54px; + height: 54px; + } + + .user-caption { + max-width: 65px; + padding: 2px 0px 9px; + font-size: 12px; + } + + .user-title { + max-width: unset; + } + + .search-group-scrollable { + position: relative; + + > .scrollable { + position: relative; + } + } + } +} + +#search-container { + transition: 150ms ease-in-out opacity,150ms ease-in-out transform; + transform: scale(1.1, 1.1); + opacity: 0; + display: flex; + + &.active { + transform: scale(1, 1); + transform-origin: center; + opacity: 1; + } } diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index 26b9da80..2952aa16 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -122,12 +122,12 @@ .media-viewer-caption { flex: 1; text-align: center; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; color: $darkgrey; transition: .2s; + max-width: 50vw; + word-break: break-word; + overflow: hidden; + text-overflow: ellipsis; &:hover { color: #fff; diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss index b04dad76..54df6e9f 100644 --- a/src/scss/partials/_rightSIdebar.scss +++ b/src/scss/partials/_rightSIdebar.scss @@ -36,6 +36,14 @@ flex: 1 1 auto; } } + + .sidebar-search { + display: none; + + &.active { + display: flex; + } + } } .profile { @@ -201,7 +209,7 @@ width: 100%; display: flex; flex-direction: column; - padding-top: 4px; + padding: 4px 7.5px 7.5px; > div { display: grid; @@ -220,9 +228,8 @@ background-position: center center; display: flex; - //background-color: #000; - justify-content: center; - align-items: center; + background-color: #000; + position: relative; &::before { content: ""; @@ -233,6 +240,28 @@ } } } + + span.video-time { + position: relative; + left: 5px; + top: 4px; + height: 18px; + border-radius: 4px; + background-color: $time-background; + padding: 0px 6px 0px 5px; + z-index: 2; + font-size: 12px; + color: white; + } + + /* span.video-play { + background-color: $time-background; + color: #fff; + text-align: center; + font-size: 34px; + line-height: 60px; + cursor: pointer; + } */ } #content-docs { diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss new file mode 100644 index 00000000..4ce4869f --- /dev/null +++ b/src/scss/partials/_selector.scss @@ -0,0 +1,151 @@ +@keyframes scaleIn { + 0% { + transform: scale(.2); + } + + to { + transform: scale(1); + } +} + +.selector { + height: 100%; + display: flex; + flex-direction: column; + + &-search-container { + flex: 1 1 auto; + position: relative; + max-height: 132px; + + .scrollable { + position: relative; + } + } + + &-search { + padding: 0 24px 0 24px; + display: flex; + flex-flow: wrap; + + input { + border: none; + padding: 7px 0px 19px 0px; + outline: none; + flex: 1 1 auto; + } + } + + &-user { + color: #000; + background-color: rgba(112, 117, 121, 0.08); + font-size: 16px; + padding: 0 17px 0px 0px; + line-height: 31px; + margin-left: -4px; + margin-right: 12px; + height: 32px; + margin-bottom: 7px; + border-radius: 24px; + user-select: none; + flex: 0 0 auto; + transition: .2s all; + + &:hover { + background-color: #fae2e3; + cursor: pointer; + + .user-avatar:after { + opacity: 1; + } + } + + &.scale-in { + animation: scaleIn .15s ease forwards; + } + + &.scale-out { + animation: scaleIn .1s ease forwards; + animation-direction: reverse; + } + + .user-avatar { + height: 32px !important; + width: 32px !important; + float: left; + margin-right: 8px; + overflow: hidden; + font-size: 14px; + + &:after { + position: absolute; + content: $tgico-close; + left: 0; + top: 0; + background-color: #df3f40; + height: 100%; + width: 100%; + z-index: 2; + font-size: 23px; + line-height: 32px; + opacity: 0; + transition: .2s opacity; + transform: scaleX(-1); + } + } + } + + .chats-container { + height: 100%; + flex: 1 1 auto; + } + + ul { + .user-avatar { + height: 48px; + width: 48px; + } + + .user-caption { + padding: 1px 3.5px 1px 12px; + } + + p { + height: 24px; + } + + span.user-title { + font-weight: normal; + } + + span.user-last-message { + font-size: 14px; + } + + li { + padding-bottom: 0; + + > .rp { + margin: 0px 9px 0px 8px; + padding: 12px 8.5px; + } + } + } + + hr { + width: 100%; + height: 1px; + border: none; + background-color: #DADCE0; + margin: 0 0 8px; + } + + [type="checkbox"] + span { + padding-left: calc(9px + 2.25rem); + } + + .checkbox { + margin-top: 11px; + padding-left: 11px; + } +} \ No newline at end of file diff --git a/src/scss/partials/_slider.scss b/src/scss/partials/_slider.scss new file mode 100644 index 00000000..b2ee1fb5 --- /dev/null +++ b/src/scss/partials/_slider.scss @@ -0,0 +1,79 @@ +.menu-horizontal { + color: $darkgrey; + border-bottom: 1px solid $lightgrey; + position: relative; + + ul { + width: 100%; + height: 100%; + margin: 0; + display: flex; + justify-content: space-around; + align-items: center; + position: relative; + z-index: 2; + } + + li { + display: inline-block; + padding: .75rem 1rem; + cursor: pointer; + text-align: center; + flex: 1; + user-select: none; + font-size: 1rem; + font-weight: 500; + + &.active { + color: $blue; + } + } + + &__stripe { + position: absolute; + background: $blue; + //left: 0; + left: -2px; + transition: .3s transform, .3s width; + //transition: .3s transform; + bottom: -1px; + height: 4px; + width: 1px; // need if using transform + transform: scaleX(1) translateX(0px); + border-top-left-radius: 2px; + border-top-right-radius: 2px; + z-index: 1; + } +} + +.tabs-container { + min-width: 100%; + width: 100%; + display: flex; + /* overflow: hidden; */ + overflow-x: hidden; + + &.animated { + transition: .3s transform; + } + + > div { + width: 100%; + max-width: 100%; + overflow: hidden; + /* transition: .2s all; */ + display: none; + + &.active { + display: flex; + flex-direction: column; + } + + > div:not(.scroll-padding) { + width: 100%; + max-width: 100%; + /* overflow: hidden; */ + position: relative; + } + } +} \ No newline at end of file diff --git a/src/scss/style.scss b/src/scss/style.scss index f2869bd6..27a350c2 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -42,6 +42,8 @@ $large-screen: 1680px; @import "partials/ckin"; @import "partials/emojiDropdown"; @import "partials/scrollable"; +@import "partials/slider"; +@import "partials/selector"; @import "partials/popups/popup"; @import "partials/popups/editAvatar"; @@ -169,6 +171,19 @@ input { } } +.btn-corner { + position: absolute !important; + bottom: 20px; + right: 20px; + transition: .2s ease; + transform: translateY(calc(100% + 20px)); + z-index: 3; + + &.is-visible { + transform: translateY(0px); + } +} + .danger { color: $color-error!important; } @@ -263,6 +278,16 @@ input { } } +@keyframes fadeIn { + 0% { + opacity: 0; + } + + to { + opacity: 1; + } +} + .user-avatar { color: #fff; width: 54px; @@ -286,6 +311,10 @@ input { height: 100%; border-radius: inherit; user-select: none; + + &.fade-in { + animation: fadeIn .2s ease forwards; + } } &[class*=" tgico-"] { @@ -624,6 +653,23 @@ input { } } +/* .page-signQR { + .auth-image { + position: relative; + + .sign-logo { + width: 36px; + height: 36px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #fff; + border-radius: 50%; + } + } +} */ + .page-signUp { .auth-image { border-radius: 50%; @@ -837,6 +883,7 @@ input { &:not(:checked) + span:after { background-color: transparent; + border-color: #8d969c; } &:checked + span:after { @@ -997,19 +1044,19 @@ input:focus, button:focus { } } -/* // scaling... any units -$width: 100px; - -.loader { - position: relative; - margin: 0 auto; - width: $width; - &:before { - content: ''; - display: block; - padding-top: 100%; +.btn-primary.btn-circle { + .preloader-circular { + height: calc(100% - 20px); + right: auto; + left: auto; + margin: 0; + top: 10px; + + .preloader-path { + stroke: #fff; + } } -} */ +} .preloader { &-circular { @@ -1164,6 +1211,7 @@ img.emoji { border-radius: 50%; height: 54px; width: 54px; + line-height: 54px; path { fill: white; @@ -1271,91 +1319,17 @@ img.emoji { user-select: text; } -.menu-horizontal { - color: $darkgrey; - border-bottom: 1px solid $lightgrey; - position: relative; - - ul { - width: 100%; - height: 100%; - margin: 0; - display: flex; - justify-content: space-around; - align-items: center; - position: relative; - z-index: 2; - } - - li { - display: inline-block; - padding: .75rem 1rem; - cursor: pointer; - text-align: center; - flex: 1; - user-select: none; - font-size: 1rem; - font-weight: 500; - - &.active { - color: $blue; - } - } - - &__stripe { - position: absolute; - background: $blue; - //left: 0; - left: -2px; - transition: .3s transform, .3s width; - //transition: .3s transform; - bottom: -1px; - height: 4px; - width: 1px; // need if using transform - transform: scaleX(1) translateX(0px); - border-top-left-radius: 2px; - border-top-right-radius: 2px; - z-index: 1; - } -} - -.tabs-container { - min-width: 100%; - width: 100%; - display: flex; - /* overflow: hidden; */ - overflow-x: hidden; - - &.animated { - transition: .3s transform; - } - - > div { - width: 100%; - max-width: 100%; - overflow: hidden; - /* transition: .2s all; */ - display: none; - - &.active { - display: flex; - flex-direction: column; - } - - > div:not(.scroll-padding) { - width: 100%; - padding: 7.5px; - max-width: 100%; - overflow: hidden; - position: relative; - } - } -} - .justify-start { justify-content: flex-start!important; } +.position-center { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + .page-chats { /* display: grid; */ /* grid-template-columns: 25% 50%; */ @@ -1396,8 +1370,7 @@ img.emoji { } } - #search-container, #chats-archived-container, .sidebar-search { - display: none; + #search-container, .sidebar-search { flex-direction: column; width: 100%; max-height: 100%; @@ -1408,10 +1381,6 @@ img.emoji { top: 0; z-index: 3; background: #fff; - - &.active { - display: flex; - } } @media (min-width: $large-screen) {