tweb/src/components/sidebarRight/tabs/sharedMedia.ts

550 lines
17 KiB
TypeScript
Raw Normal View History

2021-04-08 15:52:31 +02:00
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
2022-08-04 08:49:54 +02:00
import rootScope from '../../../lib/rootScope';
import AppSearchSuper, {SearchSuperType} from '../../appSearchSuper.';
import SidebarSlider, {SliderSuperTab} from '../../slider';
2023-01-06 20:27:29 +01:00
import TransitionSlider from '../../transition';
2022-08-04 08:49:54 +02:00
import AppEditChatTab from './editChat';
import PeerTitle from '../../peerTitle';
import AppEditContactTab from './editContact';
import Button from '../../button';
import ButtonIcon from '../../buttonIcon';
2023-01-06 20:27:29 +01:00
import I18n, {i18n, LangPackKey} from '../../../lib/langPack';
2022-08-04 08:49:54 +02:00
import {toastNew} from '../../toast';
import AppAddMembersTab from '../../sidebarLeft/tabs/addMembers';
import PopupPickUser from '../../popups/pickUser';
import PopupPeer, {PopupPeerButtonCallbackCheckboxes, PopupPeerCheckboxOptions} from '../../popups/peer';
import ButtonCorner from '../../buttonCorner';
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
import PeerProfile from '../../peerProfile';
import {Message} from '../../../layer';
2023-01-06 20:27:29 +01:00
import getMessageThreadId from '../../../lib/appManagers/utils/messages/getMessageThreadId';
import AppEditTopicTab from './editTopic';
2023-03-01 11:20:49 +01:00
import liteMode from '../../../helpers/liteMode';
2023-01-06 20:27:29 +01:00
type SharedMediaHistoryStorage = Partial<{
[type in SearchSuperType]: {mid: number, peerId: PeerId}[]
}>;
const historiesStorage: {
2023-01-06 20:27:29 +01:00
[peerId: PeerId]: {
[threadId: number]: SharedMediaHistoryStorage
}
} = {};
// TODO: отредактированное сообщение не изменится
export default class AppSharedMediaTab extends SliderSuperTab {
2021-04-09 18:44:43 +02:00
private editBtn: HTMLElement;
2021-10-21 15:16:43 +02:00
private peerId: PeerId;
2023-01-06 20:27:29 +01:00
private threadId: number;
private searchSuper: AppSearchSuper;
2021-04-09 18:44:43 +02:00
private profile: PeerProfile;
private peerChanged: boolean;
2023-01-06 20:27:29 +01:00
private titleI18n: I18n.IntlElement;
public isFirst: boolean;
2021-04-09 18:44:43 +02:00
public init() {
2022-08-04 08:49:54 +02:00
// const perf = performance.now();
2021-04-09 18:44:43 +02:00
this.container.classList.add('shared-media-container', 'profile-container');
// * header
const newCloseBtn = Button('btn-icon sidebar-close-button', {noRipple: true});
this.closeBtn.replaceWith(newCloseBtn);
this.closeBtn = newCloseBtn;
const animatedCloseIcon = document.createElement('div');
animatedCloseIcon.classList.add('animated-close-icon');
newCloseBtn.append(animatedCloseIcon);
2023-01-06 20:27:29 +01:00
if(this.isFirst) {
animatedCloseIcon.classList.add('state-back');
}
const transitionContainer = document.createElement('div');
transitionContainer.className = 'transition slide-fade';
2022-08-04 08:49:54 +02:00
const transitionFirstItem = document.createElement('div');
transitionFirstItem.classList.add('transition-item');
2023-01-06 20:27:29 +01:00
this.titleI18n = new I18n.IntlElement();
this.title.append(this.titleI18n.element);
this.editBtn = ButtonIcon('edit');
2022-08-04 08:49:54 +02:00
// const moreBtn = ButtonIcon('more');
transitionFirstItem.append(this.title, this.editBtn/* , moreBtn */);
const transitionLastItem = document.createElement('div');
transitionLastItem.classList.add('transition-item');
const secondTitle: HTMLElement = this.title.cloneNode() as any;
secondTitle.append(i18n('PeerInfo.SharedMedia'));
transitionLastItem.append(secondTitle);
transitionContainer.append(transitionFirstItem, transitionLastItem);
this.header.append(transitionContainer);
// * body
2022-07-26 07:22:46 +02:00
this.profile = new PeerProfile(this.managers, this.scrollable, this.listenerSetter);
this.profile.init();
2022-08-04 08:49:54 +02:00
this.scrollable.append(this.profile.element);
const HEADER_HEIGHT = 56;
2021-03-28 20:37:11 +02:00
this.scrollable.onAdditionalScroll = () => {
2022-08-04 08:49:54 +02:00
const rect = this.searchSuper.nav.getBoundingClientRect();
if(!rect.width) return;
const top = rect.top - 1;
2022-04-05 21:13:51 +02:00
setIsSharedMedia(top <= HEADER_HEIGHT);
};
const setIsSharedMedia = (isSharedMedia: boolean) => {
2023-01-06 20:27:29 +01:00
animatedCloseIcon.classList.toggle('state-back', this.isFirst || isSharedMedia);
this.searchSuper.container.classList.toggle('is-full-viewport', isSharedMedia);
transition(+isSharedMedia);
2021-03-11 23:39:57 +01:00
if(!isSharedMedia) {
this.searchSuper.cleanScrollPositions();
2021-03-11 23:39:57 +01:00
}
};
2023-01-06 20:27:29 +01:00
const transition = TransitionSlider({
content: transitionContainer,
type: 'slide-fade',
transitionTime: 400,
isHeavy: false
});
transition(0);
attachClickEvent(this.closeBtn, (e) => {
2023-01-06 20:27:29 +01:00
if(transition.prevId()) {
this.scrollable.scrollIntoViewNew({
2022-08-04 08:49:54 +02:00
element: this.scrollable.container.firstElementChild as HTMLElement,
position: 'start'
});
transition(0);
2023-01-06 20:27:29 +01:00
if(!this.isFirst) {
animatedCloseIcon.classList.remove('state-back');
}
2021-03-28 20:37:11 +02:00
} else if(!this.scrollable.isHeavyAnimationInProgress) {
2021-10-05 22:40:07 +02:00
this.slider.onCloseBtnClick();
}
2022-07-26 07:22:46 +02:00
}, {listenerSetter: this.listenerSetter});
2023-01-06 20:27:29 +01:00
attachClickEvent(this.editBtn, async() => {
let tab: AppEditChatTab | AppEditContactTab | AppEditTopicTab;
const {peerId, threadId} = this;
if(threadId && await this.managers.appPeersManager.isForum(peerId)) {
tab = this.slider.createTab(AppEditTopicTab)
} else if(peerId.isAnyChat()) {
2022-04-25 16:54:30 +02:00
tab = this.slider.createTab(AppEditChatTab);
2021-03-12 20:02:05 +01:00
} else {
2022-04-25 16:54:30 +02:00
tab = this.slider.createTab(AppEditContactTab);
2021-03-12 20:02:05 +01:00
}
if(tab) {
2023-01-06 20:27:29 +01:00
if(tab instanceof AppEditTopicTab) {
tab.open(peerId, this.threadId);
2021-03-17 14:05:32 +01:00
} else {
2023-01-06 20:27:29 +01:00
if(tab instanceof AppEditChatTab) {
tab.chatId = peerId.toChatId();
} else {
tab.peerId = peerId;
}
2022-08-04 08:49:54 +02:00
2023-01-06 20:27:29 +01:00
tab.open();
}
}
2022-07-26 07:22:46 +02:00
}, {listenerSetter: this.listenerSetter});
2021-03-12 20:02:05 +01:00
this.listenerSetter.add(rootScope)('contacts_update', (userId) => {
if(this.peerId === userId) {
this.toggleEditBtn();
}
});
this.listenerSetter.add(rootScope)('chat_update', (chatId) => {
2021-10-21 15:16:43 +02:00
if(this.peerId === chatId.toPeerId(true)) {
this.toggleEditBtn();
2021-09-15 07:02:03 +02:00
}
});
this.listenerSetter.add(rootScope)('history_multiappend', (message) => {
2023-01-06 20:27:29 +01:00
this.renderNewMessage(message);
2021-10-05 22:40:07 +02:00
});
2022-08-04 08:49:54 +02:00
this.listenerSetter.add(rootScope)('history_delete', ({peerId, msgs}) => {
2021-10-05 22:40:07 +02:00
this.deleteDeletedMessages(peerId, Array.from(msgs));
});
// Calls when message successfully sent and we have an id
this.listenerSetter.add(rootScope)('message_sent', ({message}) => {
2023-01-06 20:27:29 +01:00
this.renderNewMessage(message);
2021-10-05 22:40:07 +02:00
});
2022-08-04 08:49:54 +02:00
// this.container.prepend(this.closeBtn.parentElement);
2021-03-28 20:37:11 +02:00
this.searchSuper = new AppSearchSuper({
mediaTabs: [{
inputFilter: 'inputMessagesFilterEmpty',
name: 'PeerMedia.Members',
type: 'members'
}, {
inputFilter: 'inputMessagesFilterPhotoVideo',
name: 'SharedMediaTab2',
type: 'media'
}, {
inputFilter: 'inputMessagesFilterDocument',
name: 'SharedFilesTab2',
type: 'files'
}, {
inputFilter: 'inputMessagesFilterUrl',
name: 'SharedLinksTab2',
type: 'links'
}, {
inputFilter: 'inputMessagesFilterMusic',
name: 'SharedMusicTab2',
type: 'music'
}, {
inputFilter: 'inputMessagesFilterRoundVoice',
name: 'SharedVoiceTab2',
type: 'voice'
2022-08-04 08:49:54 +02:00
}],
2021-04-10 15:52:21 +02:00
scrollable: this.scrollable,
onChangeTab: (mediaTab) => {
2023-03-01 11:20:49 +01:00
const timeout = mediaTab.type === 'members' && liteMode.isAvailable('animations') ? 250 : 0;
2021-04-10 15:52:21 +02:00
setTimeout(() => {
btnAddMembers.classList.toggle('is-hidden', mediaTab.type !== 'members');
}, timeout);
2022-04-25 16:54:30 +02:00
},
managers: this.managers
});
2021-04-09 18:44:43 +02:00
2022-04-05 21:13:51 +02:00
this.searchSuper.scrollStartCallback = () => {
setIsSharedMedia(true);
};
2021-04-09 18:44:43 +02:00
this.profile.element.append(this.searchSuper.container);
2021-05-03 21:17:50 +02:00
const btnAddMembers = ButtonCorner({icon: 'addmember_filled'});
2021-04-09 18:44:43 +02:00
this.content.append(btnAddMembers);
2022-07-26 07:22:46 +02:00
attachClickEvent(btnAddMembers, async() => {
2021-10-21 15:16:43 +02:00
const peerId = this.peerId;
const id = this.peerId.toChatId();
const isChannel = await this.managers.appChatsManager.isChannel(id);
2021-04-10 15:52:21 +02:00
2021-10-21 15:16:43 +02:00
const showConfirmation = (peerIds: PeerId[], callback: (checked: PopupPeerButtonCallbackCheckboxes) => void) => {
let titleLangKey: LangPackKey, titleLangArgs: any[],
descriptionLangKey: LangPackKey, descriptionLangArgs: any[],
checkboxes: PopupPeerCheckboxOptions[];
2021-04-10 15:52:21 +02:00
if(peerIds.length > 1) {
titleLangKey = 'AddMembersAlertTitle';
titleLangArgs = [i18n('Members', [peerIds.length])];
descriptionLangKey = 'AddMembersAlertCountText';
descriptionLangArgs = peerIds.map((peerId) => {
const b = document.createElement('b');
b.append(new PeerTitle({peerId}).element);
return b;
});
if(!isChannel) {
checkboxes = [{
text: 'AddMembersForwardMessages',
checked: true
}];
}
2021-04-10 15:52:21 +02:00
} else {
titleLangKey = 'AddOneMemberAlertTitle';
descriptionLangKey = 'AddMembersAlertNamesText';
const b = document.createElement('b');
b.append(new PeerTitle({
peerId: peerIds[0]
}).element);
descriptionLangArgs = [b];
if(!isChannel) {
checkboxes = [{
text: 'AddOneMemberForwardMessages',
2021-08-28 00:01:24 +02:00
textArgs: [new PeerTitle({peerId: peerIds[0]}).element],
checked: true
}];
}
2021-04-10 15:52:21 +02:00
}
descriptionLangArgs.push(new PeerTitle({
2021-10-21 15:16:43 +02:00
peerId
}).element);
2021-04-10 15:52:21 +02:00
new PopupPeer('popup-add-members', {
2021-10-21 15:16:43 +02:00
peerId,
2021-04-10 15:52:21 +02:00
titleLangKey,
descriptionLangKey,
descriptionLangArgs,
buttons: [{
langKey: 'Add',
callback
}],
checkboxes
2021-04-10 15:52:21 +02:00
}).show();
};
const onError = (err: any) => {
if(err.type === 'USER_PRIVACY_RESTRICTED') {
toastNew({langPackKey: 'InviteToGroupError'});
}
};
2022-08-04 08:49:54 +02:00
2021-04-10 15:52:21 +02:00
if(isChannel) {
2022-04-25 16:54:30 +02:00
const tab = this.slider.createTab(AppAddMembersTab);
2021-04-10 15:52:21 +02:00
tab.open({
type: 'channel',
skippable: false,
takeOut: (peerIds) => {
showConfirmation(peerIds, () => {
2022-04-25 16:54:30 +02:00
const promise = this.managers.appChatsManager.inviteToChannel(id, peerIds);
promise.catch(onError);
tab.attachToPromise(promise);
2021-04-10 15:52:21 +02:00
});
return false;
},
title: 'GroupAddMembers',
placeholder: 'SendMessageTo'
});
} else {
new PopupPickUser({
peerTypes: ['contacts'],
placeholder: 'Search',
onSelect: (peerId) => {
setTimeout(() => {
showConfirmation([peerId], (checked) => {
2022-04-25 16:54:30 +02:00
this.managers.appChatsManager.addChatUser(id, peerId, checked.size ? undefined : 0)
.catch(onError);
2021-04-10 15:52:21 +02:00
});
}, 0);
2022-08-04 08:49:54 +02:00
}
2021-04-10 15:52:21 +02:00
});
}
2022-07-26 07:22:46 +02:00
}, {listenerSetter: this.listenerSetter});
2021-04-10 15:52:21 +02:00
2022-08-04 08:49:54 +02:00
// console.log('construct shared media time:', performance.now() - perf);
}
2023-01-06 20:27:29 +01:00
private _renderNewMessage(message: Message.message | Message.messageService, threadId?: number) {
const historyStorage = historiesStorage[message.peerId]?.[threadId];
if(!historyStorage) return;
for(const mediaTab of this.searchSuper.mediaTabs) {
const inputFilter = mediaTab.inputFilter;
2023-01-06 20:27:29 +01:00
const history = historyStorage[inputFilter];
if(!history) {
continue;
}
2023-01-06 20:27:29 +01:00
const filtered = this.searchSuper.filterMessagesByType([message], inputFilter)
.filter((message) => !history.find((m) => m.mid === message.mid && m.peerId === message.peerId));
if(filtered.length) {
history.unshift(...filtered.map((message) => ({mid: message.mid, peerId: message.peerId})));
2023-01-06 20:27:29 +01:00
if(this.peerId === message.peerId && this.searchSuper.usedFromHistory[inputFilter] !== -1) {
2020-12-25 00:19:34 +01:00
this.searchSuper.usedFromHistory[inputFilter] += filtered.length;
this.searchSuper.performSearchResult(filtered, mediaTab, false);
}
}
}
}
2023-01-06 20:27:29 +01:00
private async renderNewMessage(message: Message.message | Message.messageService) {
if(this.init) return; // * not inited yet
2023-01-06 20:27:29 +01:00
const {peerId} = message;
const isForum = await this.managers.appPeersManager.isForum(peerId);
const threadId = getMessageThreadId(message, isForum);
this._renderNewMessage(message);
if(threadId) {
this._renderNewMessage(message, threadId);
}
}
2023-01-06 20:27:29 +01:00
public _deleteDeletedMessages(historyStorage: SharedMediaHistoryStorage, peerId: PeerId, mids: number[]) {
for(const mid of mids) {
for(const type of this.searchSuper.mediaTabs) {
2020-12-25 00:19:34 +01:00
const inputFilter = type.inputFilter;
2023-01-06 20:27:29 +01:00
const history = historyStorage[inputFilter];
if(!history) continue;
const idx = history.findIndex((m) => m.mid === mid);
if(idx === -1) {
continue;
}
history.splice(idx, 1);
if(this.peerId === peerId) {
const container = this.searchSuper.tabs[inputFilter];
const div = container.querySelector(`[data-mid="${mid}"][data-peer-id="${peerId}"]`) as HTMLElement;
if(div) {
if(this.searchSuper.selection.isSelecting) {
this.searchSuper.selection.toggleByElement(div);
}
div.remove();
}
if(this.searchSuper.usedFromHistory[inputFilter] >= (idx + 1)) {
--this.searchSuper.usedFromHistory[inputFilter];
}
}
// can have element in different tabs somehow
// break;
}
}
2023-01-06 20:27:29 +01:00
}
public deleteDeletedMessages(peerId: PeerId, mids: number[]) {
if(this.init) return; // * not inited yet
const h = historiesStorage[peerId];
if(!h) return;
for(const threadId in h) {
this._deleteDeletedMessages(h[threadId], peerId, mids);
}
2021-03-28 20:37:11 +02:00
this.scrollable.onScroll();
}
2023-01-06 20:27:29 +01:00
private async cleanupHTML() {
// const perf = performance.now();
2023-01-06 20:27:29 +01:00
const isAnyChat = this.peerId.isAnyChat();
const [canViewMembers, hasRights] = await Promise.all([
isAnyChat ? this.searchSuper.canViewMembers() : false,
isAnyChat ? this.managers.appChatsManager.hasRights(this.peerId.toChatId(), 'invite_users') : false
]);
return () => {
this.profile.cleanupHTML();
this.editBtn.classList.add('hide');
this.searchSuper.cleanupHTML(true);
this.container.classList.toggle('can-add-members', canViewMembers && hasRights);
};
// console.log('cleanupHTML shared media time:', performance.now() - perf);
}
public setLoadMutex(promise: Promise<any>) {
2020-12-25 00:19:34 +01:00
this.searchSuper.loadMutex = promise;
}
2023-01-06 20:27:29 +01:00
public setPeer(peerId: PeerId, threadId?: number) {
if(this.peerId === peerId && this.threadId === threadId) return false;
2021-04-09 18:44:43 +02:00
this.peerId = peerId;
this.threadId = threadId;
this.peerChanged = true;
2021-04-09 18:44:43 +02:00
if(this.init) {
this.init();
this.init = null;
}
2020-12-25 13:53:20 +01:00
this.searchSuper.setQuery({
2022-08-04 08:49:54 +02:00
peerId,
2023-01-06 20:27:29 +01:00
threadId,
historyStorage: (historiesStorage[peerId] ??= {})[threadId] ??= {}
2020-12-25 13:53:20 +01:00
});
2020-12-25 00:19:34 +01:00
this.profile.setPeer(peerId, threadId);
2022-08-04 08:49:54 +02:00
return true;
}
2023-01-06 20:27:29 +01:00
private async changeTitleKey() {
const isForum = this.managers.appPeersManager.isForum(this.peerId);
return () => {
this.titleI18n.compareAndUpdate({key: this.threadId && isForum ? 'AccDescrTopic' : 'Profile'});
};
}
public async fillProfileElements() {
if(!this.peerChanged) {
return;
}
this.peerChanged = false;
2023-01-06 20:27:29 +01:00
const callbacks = await Promise.all([
this.cleanupHTML(),
this.toggleEditBtn(true),
this.profile.fillProfileElements(),
this.changeTitleKey()
]);
return () => {
callbacks.forEach((callback) => {
callback?.();
});
};
}
2023-01-06 20:27:29 +01:00
private async toggleEditBtn<T extends boolean>(manual?: T): Promise<T extends true ? () => void : void> {
let show: boolean;
2021-10-21 15:16:43 +02:00
if(this.peerId.isUser()) {
show = this.peerId !== rootScope.myId && await this.managers.appUsersManager.isContact(this.peerId.toUserId());
2021-03-13 13:24:00 +01:00
} else {
2023-01-16 18:26:51 +01:00
const chatId = this.peerId.toChatId();
const isTopic = this.threadId && await this.managers.appChatsManager.isForum(chatId);
show = await this.managers.appChatsManager.hasRights(chatId, isTopic ? 'manage_topics' : 'change_info');
2021-03-13 13:24:00 +01:00
}
2023-01-06 20:27:29 +01:00
const callback = () => {
this.editBtn.classList.toggle('hide', !show);
};
return manual ? callback : callback() as any;
2021-03-13 03:22:42 +01:00
}
public loadSidebarMedia(single: boolean, justLoad?: boolean) {
this.searchSuper.load(single, justLoad);
}
onOpenAfterTimeout() {
2023-01-06 20:27:29 +01:00
super.onOpenAfterTimeout();
2021-03-28 20:37:11 +02:00
this.scrollable.onScroll();
}
2021-04-09 18:44:43 +02:00
2023-01-06 20:27:29 +01:00
onCloseAfterTimeout() {
super.onCloseAfterTimeout();
if(this.destroyable) {
this.profile.destroy();
this.searchSuper.destroy();
}
}
public destroy() {
this.destroyable = true;
this.onCloseAfterTimeout();
}
}