Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
287 lines
9.7 KiB
287 lines
9.7 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import PopupGroupCall from "."; |
|
import filterAsync from "../../helpers/array/filterAsync"; |
|
import contextMenuController from "../../helpers/contextMenuController"; |
|
import { attachContextMenuListener } from "../../helpers/dom/attachContextMenuListener"; |
|
import cancelEvent from "../../helpers/dom/cancelEvent"; |
|
import findUpClassName from "../../helpers/dom/findUpClassName"; |
|
import { addFullScreenListener, isFullScreen } from "../../helpers/dom/fullScreen"; |
|
import ListenerSetter from "../../helpers/listenerSetter"; |
|
import noop from "../../helpers/noop"; |
|
import safeAssign from "../../helpers/object/safeAssign"; |
|
import positionMenu from "../../helpers/positionMenu"; |
|
import ScrollableLoader from "../../helpers/scrollableLoader"; |
|
import { GroupCallParticipant } from "../../layer"; |
|
import type { AppChatsManager } from "../../lib/appManagers/appChatsManager"; |
|
import type { AppGroupCallsManager } from "../../lib/appManagers/appGroupCallsManager"; |
|
import appImManager from "../../lib/appManagers/appImManager"; |
|
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; |
|
import { AppManagers } from "../../lib/appManagers/managers"; |
|
import getPeerId from "../../lib/appManagers/utils/peers/getPeerId"; |
|
import GroupCallInstance from "../../lib/calls/groupCallInstance"; |
|
import rootScope from "../../lib/rootScope"; |
|
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu"; |
|
import confirmationPopup from "../confirmationPopup"; |
|
import PeerTitle from "../peerTitle"; |
|
import PopupElement from "../popups"; |
|
import Scrollable from "../scrollable"; |
|
import GroupCallParticipantsList from "./participantsList"; |
|
import GroupCallParticipantsVideoElement from "./participantVideos"; |
|
|
|
export class GroupCallParticipantContextMenu { |
|
private buttons: (ButtonMenuItemOptions & {verify: (peerId: PeerId) => boolean | Promise<boolean>})[]; |
|
private element: HTMLDivElement; |
|
private chatId: ChatId; |
|
private targetPeerId: PeerId; |
|
private participant: GroupCallParticipant; |
|
private instance: GroupCallInstance; |
|
private canManageCall: boolean; |
|
private managers: AppManagers; |
|
|
|
constructor(options: { |
|
listenerSetter: ListenerSetter, |
|
onContextElement: HTMLElement, |
|
managers: AppManagers, |
|
instance: GroupCallInstance, |
|
}) { |
|
this.buttons = [{ |
|
icon: 'gc_microphoneoff', |
|
text: 'VoiceChat.MutePeer', |
|
verify: () => this.canManageCall && this.participant.pFlags.can_self_unmute, |
|
onClick: () => this.toggleParticipantMuted(true) |
|
}, { |
|
icon: 'gc_microphone', |
|
text: 'VoiceChat.UnmutePeer', |
|
verify: () => this.canManageCall && !this.participant.pFlags.can_self_unmute, |
|
onClick: () => this.toggleParticipantMuted(false) |
|
}, { |
|
icon: 'gc_microphoneoff', |
|
text: 'VoiceChat.MuteForMe', |
|
verify: () => !this.canManageCall && !this.participant.pFlags.muted_by_you, |
|
onClick: () => this.toggleParticipantMuted(true) |
|
}, { |
|
icon: 'gc_microphone', |
|
text: 'VoiceChat.UnmuteForMe', |
|
verify: () => !this.canManageCall && this.participant.pFlags.muted_by_you, |
|
onClick: () => this.toggleParticipantMuted(false) |
|
}, { |
|
icon: 'newprivate', |
|
text: 'VoiceChat.OpenProfile', |
|
verify: () => true, |
|
onClick: this.onOpenProfileClick |
|
}, { |
|
icon: 'deleteuser danger', |
|
text: 'VoiceChat.RemovePeer', |
|
verify: () => this.managers.appChatsManager.hasRights(this.chatId, 'ban_users'), |
|
onClick: async() => { |
|
confirmationPopup({ |
|
peerId: this.targetPeerId, |
|
title: new PeerTitle({peerId: this.targetPeerId}).element, |
|
descriptionLangKey: await this.managers.appChatsManager.isBroadcast(this.chatId) ? 'VoiceChat.RemovePeer.Confirm.Channel' : 'VoiceChat.RemovePeer.Confirm', |
|
descriptionLangArgs: [new PeerTitle({peerId: this.targetPeerId}).element], |
|
button: { |
|
langKey: 'VoiceChat.RemovePeer.Confirm.OK', |
|
isDanger: true |
|
} |
|
}).then(() => { |
|
this.managers.appChatsManager.kickFromChat(this.chatId, this.targetPeerId); |
|
}, noop); |
|
} |
|
}]; |
|
|
|
const {listenerSetter} = options; |
|
this.managers = options.managers; |
|
this.instance = options.instance; |
|
this.chatId = this.instance.chatId; |
|
|
|
this.element = ButtonMenu(this.buttons, listenerSetter); |
|
this.element.classList.add('group-call-participant-menu', 'night'); |
|
|
|
attachContextMenuListener(options.onContextElement, async(e: any) => { |
|
const li = findUpClassName(e.target, 'group-call-participant'); |
|
if(!li) { |
|
return; |
|
} |
|
|
|
if(this.element.parentElement !== appendTo) { |
|
appendTo.append(this.element); |
|
} |
|
|
|
cancelEvent(e); |
|
|
|
const peerId = this.targetPeerId = li.dataset.peerId.toPeerId(); |
|
this.participant = await this.instance.getParticipantByPeerId(peerId); |
|
if(this.participant.pFlags.self) { |
|
return; |
|
} |
|
|
|
this.canManageCall = await this.managers.appChatsManager.hasRights(this.chatId, 'manage_call'); |
|
|
|
await filterAsync(this.buttons, async(button) => { |
|
const good = await button.verify(peerId); |
|
button.element.classList.toggle('hide', !good); |
|
return good; |
|
}); |
|
|
|
positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, this.element, 'right'); |
|
contextMenuController.openBtnMenu(this.element); |
|
}, listenerSetter); |
|
|
|
listenerSetter.add(rootScope)('group_call_participant', ({groupCallId, participant}) => { |
|
if(this.instance.id === groupCallId) { |
|
const peerId = getPeerId(participant.peer); |
|
if(this.targetPeerId === peerId) { |
|
contextMenuController.closeBtnMenu(); |
|
} |
|
} |
|
}); |
|
|
|
let appendTo: HTMLElement = document.body; |
|
addFullScreenListener(document.body, () => { |
|
const isFull = isFullScreen(); |
|
appendTo = isFull ? PopupElement.getPopups(PopupGroupCall)[0].getContainer(): document.body; |
|
|
|
if(!isFull) { |
|
contextMenuController.closeBtnMenu(); |
|
} |
|
}, listenerSetter); |
|
} |
|
|
|
private onOpenProfileClick = () => { |
|
const popup = PopupElement.getPopups(PopupGroupCall)[0]; |
|
if(popup) { |
|
popup.hide(); |
|
} |
|
|
|
appImManager.setInnerPeer({peerId: this.targetPeerId}); |
|
}; |
|
|
|
private toggleParticipantMuted = (muted: boolean) => { |
|
this.instance.editParticipant(this.participant, { |
|
muted |
|
}); |
|
}; |
|
}; |
|
|
|
export default class GroupCallParticipantsElement { |
|
private container: HTMLDivElement; |
|
private sortedList: GroupCallParticipantsList; |
|
private instance: GroupCallInstance; |
|
private listenerSetter: ListenerSetter; |
|
private groupCallParticipantsVideo: GroupCallParticipantsVideoElement; |
|
private contextMenu: GroupCallParticipantContextMenu; |
|
private managers: AppManagers; |
|
|
|
constructor(options: { |
|
appendTo: HTMLElement, |
|
instance: GroupCallInstance, |
|
listenerSetter: ListenerSetter, |
|
managers: AppManagers |
|
}) { |
|
safeAssign(this, options); |
|
|
|
const className = 'group-call-participants'; |
|
|
|
const scrollable = new Scrollable(undefined); |
|
scrollable.container.classList.add(className + '-scrollable'); |
|
|
|
const container = this.container = document.createElement('div'); |
|
container.classList.add(className); |
|
|
|
// const invite = Button(`btn-primary btn-transparent ${className}-invite`, {icon: 'adduser', text: 'VoiceChat.Invite.InviteMembers'}); |
|
|
|
const sortedList = this.sortedList = new GroupCallParticipantsList(this.instance); |
|
|
|
const {instance, listenerSetter} = this; |
|
this.contextMenu = new GroupCallParticipantContextMenu({ |
|
...options, |
|
onContextElement: sortedList.list, |
|
listenerSetter, |
|
instance |
|
}); |
|
|
|
this.groupCallParticipantsVideo = new GroupCallParticipantsVideoElement({ |
|
...options, |
|
appendTo: scrollable.container, |
|
displayPinned: false |
|
}); |
|
|
|
scrollable.append(/* invite, */sortedList.list); |
|
container.append(scrollable.container); |
|
|
|
options.appendTo.append(container); |
|
|
|
listenerSetter.add(rootScope)('group_call_participant', ({groupCallId, participant}) => { |
|
if(this.instance.id === groupCallId) { |
|
this.updateParticipant(participant); |
|
} |
|
}); |
|
|
|
const scrollableLoader = new ScrollableLoader({ |
|
scrollable, |
|
getPromise: () => { |
|
return this.managers.appGroupCallsManager.getGroupCallParticipants(this.instance.id).then(({participants, isEnd}) => { |
|
participants.forEach((participant) => { |
|
this.updateParticipant(participant); |
|
}); |
|
|
|
return isEnd; |
|
}); |
|
} |
|
}); |
|
|
|
this.setInstance(instance); |
|
} |
|
|
|
private updateParticipant(participant: GroupCallParticipant) { |
|
const peerId = getPeerId(participant.peer); |
|
const has = this.sortedList.has(peerId); |
|
if(participant.pFlags.left) { |
|
if(has) { |
|
this.sortedList.delete(peerId); |
|
} |
|
|
|
return; |
|
} |
|
|
|
if(!has) { |
|
this.sortedList.add(peerId); |
|
return; |
|
} |
|
|
|
this.sortedList.update(peerId); |
|
} |
|
|
|
public async setInstance(instance: GroupCallInstance) { |
|
// @ts-ignore |
|
/* const users = appUsersManager.users; |
|
for(const userId in users) { |
|
const participant: GroupCallParticipant = { |
|
_: 'groupCallParticipant', |
|
date: 0, |
|
peer: {_: 'peerUser', user_id: userId.toPeerId()}, |
|
pFlags: { |
|
muted: true |
|
}, |
|
source: 1 |
|
}; |
|
|
|
instance.participants.set(userId.toPeerId(), participant); |
|
this.updateParticipant(participant); |
|
} */ |
|
const participants = await instance.participants; |
|
participants.forEach((participant) => { |
|
this.updateParticipant(participant); |
|
}); |
|
} |
|
|
|
public destroy() { |
|
this.sortedList.destroy(); |
|
this.groupCallParticipantsVideo.destroy(); |
|
} |
|
}
|
|
|