feat(contact-list): convert to react
- Remove references to the model ContactList. - Replace ContactListView with an empty element for attaching the React Component ContactListPanel, which has the same features as the old ContactListView. - Create new selector for getting non-fake participants for ContactListPanel's props. - Create a ParticipantCounter component to place in the contact list button. Previously ContactListView updated that but now it's a react component hooked into the participant state. - Remove pub/sub that was used only by ContactListView.
This commit is contained in:
parent
ed53f54628
commit
31729d7949
|
@ -2,7 +2,6 @@
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
import {openConnection} from './connection';
|
import {openConnection} from './connection';
|
||||||
import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
|
|
||||||
|
|
||||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||||
import Recorder from './modules/recorder/Recorder';
|
import Recorder from './modules/recorder/Recorder';
|
||||||
|
@ -72,10 +71,7 @@ import {
|
||||||
mediaPermissionPromptVisibilityChanged,
|
mediaPermissionPromptVisibilityChanged,
|
||||||
suspendDetected
|
suspendDetected
|
||||||
} from './react/features/overlay';
|
} from './react/features/overlay';
|
||||||
import {
|
import { showDesktopSharingButton } from './react/features/toolbox';
|
||||||
isButtonEnabled,
|
|
||||||
showDesktopSharingButton
|
|
||||||
} from './react/features/toolbox';
|
|
||||||
|
|
||||||
const { participantConnectionStatus } = JitsiMeetJS.constants;
|
const { participantConnectionStatus } = JitsiMeetJS.constants;
|
||||||
|
|
||||||
|
@ -710,11 +706,6 @@ export default {
|
||||||
this._createRoom(tracks);
|
this._createRoom(tracks);
|
||||||
APP.remoteControl.init();
|
APP.remoteControl.init();
|
||||||
|
|
||||||
if (isButtonEnabled('contacts')
|
|
||||||
&& !interfaceConfig.filmStripOnly) {
|
|
||||||
APP.UI.ContactList = new ContactList(room);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if user didn't give access to mic or camera or doesn't have
|
// if user didn't give access to mic or camera or doesn't have
|
||||||
// them at all, we mark corresponding toolbar buttons as muted,
|
// them at all, we mark corresponding toolbar buttons as muted,
|
||||||
// so that the user can try unmute later on and add audio/video
|
// so that the user can try unmute later on and add audio/video
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
#contacts_container {
|
#contacts_container {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
> ul#contacts {
|
/**
|
||||||
|
* Override generic side toolbar styles to compensate for AtlasKit Button
|
||||||
|
* being used instead of custom button styling.
|
||||||
|
*/
|
||||||
|
.sideToolbarBlock {
|
||||||
|
.contact-list-panel-invite-button {
|
||||||
|
font-size: $modalButtonFontSize;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 9px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#contacts {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -21,8 +33,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#contacts {
|
#contacts {
|
||||||
|
.contact-list-item {
|
||||||
>li {
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
color: $baseLight;
|
color: $baseLight;
|
||||||
|
@ -39,7 +50,7 @@
|
||||||
background: $toolbarSelectBackground;
|
background: $toolbarSelectBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
> p {
|
.contact-list-item-name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
@ -62,4 +73,4 @@
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
max-width: 30px;
|
max-width: 30px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,8 +155,6 @@ UI.showChatError = function (err, msg) {
|
||||||
* @param {string} displayName new nickname
|
* @param {string} displayName new nickname
|
||||||
*/
|
*/
|
||||||
UI.changeDisplayName = function (id, displayName) {
|
UI.changeDisplayName = function (id, displayName) {
|
||||||
if (UI.ContactList)
|
|
||||||
UI.ContactList.onDisplayNameChange(id, displayName);
|
|
||||||
VideoLayout.onDisplayNameChanged(id, displayName);
|
VideoLayout.onDisplayNameChanged(id, displayName);
|
||||||
|
|
||||||
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
|
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
|
||||||
|
@ -201,9 +199,6 @@ UI.setLocalRaisedHandStatus
|
||||||
*/
|
*/
|
||||||
UI.initConference = function () {
|
UI.initConference = function () {
|
||||||
let id = APP.conference.getMyUserId();
|
let id = APP.conference.getMyUserId();
|
||||||
// Add myself to the contact list.
|
|
||||||
if (UI.ContactList)
|
|
||||||
UI.ContactList.addContact(id, true);
|
|
||||||
|
|
||||||
// Update default button states before showing the toolbar
|
// Update default button states before showing the toolbar
|
||||||
// if local role changes buttons state will be again updated.
|
// if local role changes buttons state will be again updated.
|
||||||
|
@ -427,9 +422,6 @@ UI.addUser = function (user) {
|
||||||
var id = user.getId();
|
var id = user.getId();
|
||||||
var displayName = user.getDisplayName();
|
var displayName = user.getDisplayName();
|
||||||
|
|
||||||
if (UI.ContactList)
|
|
||||||
UI.ContactList.addContact(id);
|
|
||||||
|
|
||||||
messageHandler.participantNotification(
|
messageHandler.participantNotification(
|
||||||
displayName,'notify.somebody', 'connected', 'notify.connected'
|
displayName,'notify.somebody', 'connected', 'notify.connected'
|
||||||
);
|
);
|
||||||
|
@ -455,9 +447,6 @@ UI.addUser = function (user) {
|
||||||
* @param {string} displayName user nickname
|
* @param {string} displayName user nickname
|
||||||
*/
|
*/
|
||||||
UI.removeUser = function (id, displayName) {
|
UI.removeUser = function (id, displayName) {
|
||||||
if (UI.ContactList)
|
|
||||||
UI.ContactList.removeContact(id);
|
|
||||||
|
|
||||||
messageHandler.participantNotification(
|
messageHandler.participantNotification(
|
||||||
displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
|
displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
|
||||||
);
|
);
|
||||||
|
@ -737,8 +726,6 @@ UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
|
||||||
*/
|
*/
|
||||||
function changeAvatar(id, avatarUrl) {
|
function changeAvatar(id, avatarUrl) {
|
||||||
VideoLayout.changeUserAvatar(id, avatarUrl);
|
VideoLayout.changeUserAvatar(id, avatarUrl);
|
||||||
if (UI.ContactList)
|
|
||||||
UI.ContactList.changeUserAvatar(id, avatarUrl);
|
|
||||||
if (APP.conference.isLocalId(id)) {
|
if (APP.conference.isLocalId(id)) {
|
||||||
Profile.changeAvatar(avatarUrl);
|
Profile.changeAvatar(avatarUrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* Class representing Contact model
|
|
||||||
* @class Contact
|
|
||||||
*/
|
|
||||||
export default class Contact {
|
|
||||||
constructor(opts) {
|
|
||||||
let {
|
|
||||||
id,
|
|
||||||
avatar,
|
|
||||||
name,
|
|
||||||
isLocal
|
|
||||||
} = opts;
|
|
||||||
|
|
||||||
this.id = id;
|
|
||||||
this.avatar = avatar || '';
|
|
||||||
this.name = name || '';
|
|
||||||
this.isLocal = isLocal || false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
/* global APP */
|
|
||||||
|
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
|
||||||
import ContactListView from './ContactListView';
|
|
||||||
import Contact from './Contact';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for the Contact list.
|
|
||||||
*
|
|
||||||
* @class ContactList
|
|
||||||
*/
|
|
||||||
class ContactList {
|
|
||||||
constructor(conference) {
|
|
||||||
this.conference = conference;
|
|
||||||
this.contacts = [];
|
|
||||||
this.roomLocked = false;
|
|
||||||
//setup ContactList Model into ContactList View
|
|
||||||
ContactListView.setup(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the current conference is locked.
|
|
||||||
*
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
isLocked() {
|
|
||||||
return APP.store.getState()['features/base/conference'].locked;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding new participant.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param isLocal
|
|
||||||
*/
|
|
||||||
addContact(id, isLocal) {
|
|
||||||
const exists = this.contacts.some(el => el.id === id);
|
|
||||||
|
|
||||||
if (!exists) {
|
|
||||||
let newContact = new Contact({ id, isLocal });
|
|
||||||
this.contacts.push(newContact);
|
|
||||||
APP.UI.emitEvent(UIEvents.CONTACT_ADDED, { id, isLocal });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removing participant.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @returns {Array|*}
|
|
||||||
*/
|
|
||||||
removeContact(id) {
|
|
||||||
this.contacts = this.contacts.filter((el) => el.id !== id);
|
|
||||||
APP.UI.emitEvent(UIEvents.CONTACT_REMOVED, { id });
|
|
||||||
return this.contacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changing the display name.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param name
|
|
||||||
*/
|
|
||||||
onDisplayNameChange (id, name) {
|
|
||||||
if(!name)
|
|
||||||
return;
|
|
||||||
if (id === 'localVideoContainer') {
|
|
||||||
id = APP.conference.getMyUserId();
|
|
||||||
}
|
|
||||||
|
|
||||||
let contacts = this.contacts.filter((el) => el.id === id);
|
|
||||||
contacts.forEach((el) => {
|
|
||||||
el.name = name;
|
|
||||||
});
|
|
||||||
APP.UI.emitEvent(UIEvents.DISPLAY_NAME_CHANGED, { id, name });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changing the avatar.
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param avatar
|
|
||||||
*/
|
|
||||||
changeUserAvatar (id, avatar) {
|
|
||||||
let contacts = this.contacts.filter((el) => el.id === id);
|
|
||||||
contacts.forEach((el) => {
|
|
||||||
el.avatar = avatar;
|
|
||||||
});
|
|
||||||
APP.UI.emitEvent(UIEvents.USER_AVATAR_CHANGED, { id, avatar });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ContactList;
|
|
|
@ -1,288 +1,57 @@
|
||||||
/* global $, APP, interfaceConfig */
|
/* global $, APP */
|
||||||
|
|
||||||
import { openInviteDialog } from '../../../../react/features/invite';
|
/* eslint-disable no-unused-vars */
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { I18nextProvider } from 'react-i18next';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import { i18next } from '../../../../react/features/base/i18n';
|
||||||
|
import { ContactListPanel } from '../../../../react/features/contact-list';
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
import Avatar from '../../avatar/Avatar';
|
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
|
||||||
import UIUtil from '../../util/UIUtil';
|
import UIUtil from '../../util/UIUtil';
|
||||||
|
|
||||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
||||||
|
|
||||||
let numberOfContacts = 0;
|
|
||||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
|
||||||
const htmlStr = `
|
|
||||||
<div id="contacts_container" class="sideToolbarContainer__inner">
|
|
||||||
<div class="title" data-i18n="contactlist"
|
|
||||||
data-i18n-options='{"pcount":"1"}'></div>
|
|
||||||
<ul id="contacts"></ul>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
function initHTML() {
|
|
||||||
$(`#${sidePanelsContainerId}`)
|
|
||||||
.append(htmlStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the number of participants in the contact list button and sets
|
|
||||||
* the glow
|
|
||||||
* @param delta indicates whether a new user has joined (1) or someone has
|
|
||||||
* left(-1)
|
|
||||||
*/
|
|
||||||
function updateNumberOfParticipants(delta) {
|
|
||||||
numberOfContacts += delta;
|
|
||||||
|
|
||||||
if (numberOfContacts <= 0) {
|
|
||||||
logger.error("Invalid number of participants: " + numberOfContacts);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#numberOfParticipants").text(numberOfContacts);
|
|
||||||
|
|
||||||
APP.translation.translateElement(
|
|
||||||
$("#contacts_container>div.title"), {pcount: numberOfContacts});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the avatar element.
|
|
||||||
*
|
|
||||||
* @return {object} the newly created avatar element
|
|
||||||
*/
|
|
||||||
function createAvatar(jid) {
|
|
||||||
let avatar = document.createElement('img');
|
|
||||||
avatar.className = "icon-avatar avatar";
|
|
||||||
avatar.src = Avatar.getAvatarUrl(jid);
|
|
||||||
|
|
||||||
return avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the display name paragraph.
|
|
||||||
*
|
|
||||||
* @param displayName the display name to set
|
|
||||||
*/
|
|
||||||
function createDisplayNameParagraph(key, displayName) {
|
|
||||||
let p = document.createElement('p');
|
|
||||||
if (displayName) {
|
|
||||||
p.innerHTML = displayName;
|
|
||||||
} else if(key) {
|
|
||||||
p.setAttribute("data-i18n",key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter for current contact element
|
|
||||||
* @param id
|
|
||||||
* @returns {JQuery}
|
|
||||||
*/
|
|
||||||
function getContactEl (id) {
|
|
||||||
return $(`#contacts>li[id="${id}"]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contact list.
|
* Contact list.
|
||||||
|
*
|
||||||
|
* FIXME: One day this view should no longer be called "contact list" because
|
||||||
|
* the term "contact" is not used elsewhere. Normally people in the conference
|
||||||
|
* are internally refered to as "participants" or externally as "members".
|
||||||
*/
|
*/
|
||||||
var ContactListView = {
|
var ContactListView = {
|
||||||
init () {
|
|
||||||
initHTML();
|
|
||||||
this.lockKey = 'roomLocked';
|
|
||||||
this.unlockKey = 'roomUnlocked';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setup ContactList Model into ContactList View
|
* Creates and appends the contact list to the side panel.
|
||||||
*
|
*
|
||||||
* @param model
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
setup (model) {
|
init() {
|
||||||
this.model = model;
|
const contactListPanelContainer = document.createElement('div');
|
||||||
this.addInviteButton();
|
|
||||||
this.registerListeners();
|
|
||||||
this.setLockDisplay(false);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Adds layout for invite button
|
|
||||||
*/
|
|
||||||
addInviteButton() {
|
|
||||||
let container = document.getElementById('contacts_container');
|
|
||||||
|
|
||||||
container.firstElementChild // this is the title
|
contactListPanelContainer.id = 'contacts_container';
|
||||||
.insertAdjacentHTML('afterend', this.getInviteButtonLayout());
|
contactListPanelContainer.className = 'sideToolbarContainer__inner';
|
||||||
|
|
||||||
APP.translation.translateElement($(container));
|
$('#sideToolbarContainer').append(contactListPanelContainer);
|
||||||
|
|
||||||
$(document).on('click', '#addParticipantsBtn', () => {
|
/* jshint ignore:start */
|
||||||
APP.store.dispatch(openInviteDialog());
|
ReactDOM.render(
|
||||||
});
|
<Provider store = { APP.store }>
|
||||||
},
|
<I18nextProvider i18n = { i18next }>
|
||||||
/**
|
<ContactListPanel />
|
||||||
* Returns layout for invite button
|
</I18nextProvider>
|
||||||
*/
|
</Provider>,
|
||||||
getInviteButtonLayout() {
|
contactListPanelContainer
|
||||||
let classes = 'button-control button-control_primary';
|
);
|
||||||
classes += ' button-control_full-width';
|
/* jshint ignore:end */
|
||||||
let key = 'addParticipants';
|
|
||||||
|
|
||||||
let lockedHtml = this.getLockDescriptionLayout(this.lockKey);
|
|
||||||
let unlockedHtml = this.getLockDescriptionLayout(this.unlockKey);
|
|
||||||
|
|
||||||
return (
|
|
||||||
`<div class="sideToolbarBlock first">
|
|
||||||
<button id="addParticipantsBtn"
|
|
||||||
data-i18n="${key}"
|
|
||||||
class="${classes}"></button>
|
|
||||||
<div>
|
|
||||||
${lockedHtml}
|
|
||||||
${unlockedHtml}
|
|
||||||
</div>
|
|
||||||
</div>`);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Adds layout for lock description
|
|
||||||
*/
|
|
||||||
getLockDescriptionLayout(key) {
|
|
||||||
let classes = "form-control__hint form-control_full-width";
|
|
||||||
let padlockSuffix = '';
|
|
||||||
if (key === this.lockKey) {
|
|
||||||
padlockSuffix = '-locked';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<p id="contactList${key}" class="${classes}">
|
|
||||||
<span class="icon-security${padlockSuffix}"></span>
|
|
||||||
<span data-i18n="${key}"></span>
|
|
||||||
</p>`;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Setup listeners
|
|
||||||
*/
|
|
||||||
registerListeners() {
|
|
||||||
let removeContact = this.onRemoveContact.bind(this);
|
|
||||||
let changeAvatar = this.changeUserAvatar.bind(this);
|
|
||||||
let displayNameChange = this.onDisplayNameChange.bind(this);
|
|
||||||
|
|
||||||
APP.UI.addListener( UIEvents.TOGGLE_ROOM_LOCK,
|
|
||||||
this.setLockDisplay.bind(this));
|
|
||||||
APP.UI.addListener( UIEvents.CONTACT_ADDED,
|
|
||||||
this.onAddContact.bind(this));
|
|
||||||
|
|
||||||
APP.UI.addListener(UIEvents.CONTACT_REMOVED, removeContact);
|
|
||||||
APP.UI.addListener(UIEvents.USER_AVATAR_CHANGED, changeAvatar);
|
|
||||||
APP.UI.addListener(UIEvents.DISPLAY_NAME_CHANGED, displayNameChange);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the view according to the passed in lock state.
|
* Indicates if the contact list is currently visible.
|
||||||
*
|
*
|
||||||
* @param {boolean} locked - True to display the locked UI state or false to
|
* @return {boolean) true if the contact list is currently visible.
|
||||||
* display the unlocked UI state.
|
|
||||||
*/
|
|
||||||
setLockDisplay(locked) {
|
|
||||||
let hideKey, showKey;
|
|
||||||
|
|
||||||
if (locked) {
|
|
||||||
hideKey = this.unlockKey;
|
|
||||||
showKey = this.lockKey;
|
|
||||||
} else {
|
|
||||||
hideKey = this.lockKey;
|
|
||||||
showKey = this.unlockKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
$(`#contactList${hideKey}`).hide();
|
|
||||||
$(`#contactList${showKey}`).show();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the chat is currently visible.
|
|
||||||
*
|
|
||||||
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
|
|
||||||
* otherwise
|
|
||||||
*/
|
*/
|
||||||
isVisible () {
|
isVisible () {
|
||||||
return UIUtil.isVisible(document.getElementById("contactlist"));
|
return UIUtil.isVisible(document.getElementById("contactlist"));
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for Adding a contact for the given id.
|
|
||||||
* @param isLocal is an id for the local user.
|
|
||||||
*/
|
|
||||||
onAddContact (data) {
|
|
||||||
let { id, isLocal } = data;
|
|
||||||
let contactlist = $('#contacts');
|
|
||||||
let newContact = document.createElement('li');
|
|
||||||
newContact.id = id;
|
|
||||||
newContact.className = "clickable";
|
|
||||||
newContact.onclick = (event) => {
|
|
||||||
if (event.currentTarget.className === "clickable") {
|
|
||||||
APP.UI.emitEvent(UIEvents.CONTACT_CLICKED, id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
|
|
||||||
newContact.appendChild(createAvatar(id));
|
|
||||||
|
|
||||||
newContact.appendChild(
|
|
||||||
createDisplayNameParagraph(
|
|
||||||
isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
|
|
||||||
isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
|
|
||||||
APP.translation.translateElement($(newContact));
|
|
||||||
|
|
||||||
if (APP.conference.isLocalId(id)) {
|
|
||||||
contactlist.prepend(newContact);
|
|
||||||
} else {
|
|
||||||
contactlist.append(newContact);
|
|
||||||
}
|
|
||||||
updateNumberOfParticipants(1);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler for removing
|
|
||||||
* a contact for the given id.
|
|
||||||
*/
|
|
||||||
onRemoveContact (data) {
|
|
||||||
let { id } = data;
|
|
||||||
let contact = getContactEl(id);
|
|
||||||
|
|
||||||
if (contact.length > 0) {
|
|
||||||
contact.remove();
|
|
||||||
updateNumberOfParticipants(-1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setClickable (id, isClickable) {
|
|
||||||
getContactEl(id).toggleClass('clickable', isClickable);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes display name of the user
|
|
||||||
* defined by its id
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
onDisplayNameChange (data) {
|
|
||||||
let { id, name } = data;
|
|
||||||
if(!name)
|
|
||||||
return;
|
|
||||||
if (id === 'localVideoContainer') {
|
|
||||||
id = APP.conference.getMyUserId();
|
|
||||||
}
|
|
||||||
let contactName = $(`#contacts #${id}>p`);
|
|
||||||
|
|
||||||
if (contactName.text() !== name) {
|
|
||||||
contactName.text(name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes user avatar
|
|
||||||
* @param data
|
|
||||||
*/
|
|
||||||
changeUserAvatar (data) {
|
|
||||||
let { id, avatar } = data;
|
|
||||||
// set the avatar in the contact list
|
|
||||||
let contact = $(`#${id}>img`);
|
|
||||||
if (contact.length > 0) {
|
|
||||||
contact.attr('src', avatar);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,7 @@ export function getAvatarURL({ avatarID, avatarURL, email, id }: {
|
||||||
* @returns {(Participant|undefined)}
|
* @returns {(Participant|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getLocalParticipant(stateOrGetState: Object | Function) {
|
export function getLocalParticipant(stateOrGetState: Object | Function) {
|
||||||
const participants = _getParticipants(stateOrGetState);
|
const participants = _getAllParticipants(stateOrGetState);
|
||||||
|
|
||||||
return participants.find(p => p.local);
|
return participants.find(p => p.local);
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ export function getLocalParticipant(stateOrGetState: Object | Function) {
|
||||||
export function getParticipantById(
|
export function getParticipantById(
|
||||||
stateOrGetState: Object | Function,
|
stateOrGetState: Object | Function,
|
||||||
id: string) {
|
id: string) {
|
||||||
const participants = _getParticipants(stateOrGetState);
|
const participants = _getAllParticipants(stateOrGetState);
|
||||||
|
|
||||||
return participants.find(p => p.id === id);
|
return participants.find(p => p.id === id);
|
||||||
}
|
}
|
||||||
|
@ -119,10 +119,22 @@ export function getParticipantById(
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
export function getParticipantCount(stateOrGetState: Object | Function) {
|
export function getParticipantCount(stateOrGetState: Object | Function) {
|
||||||
const participants = _getParticipants(stateOrGetState);
|
return getParticipants(stateOrGetState).length;
|
||||||
const realParticipants = participants.filter(p => !p.isBot);
|
}
|
||||||
|
|
||||||
return realParticipants.length;
|
|
||||||
|
/**
|
||||||
|
* Selectors for getting all known participants with fake participants filtered
|
||||||
|
* out.
|
||||||
|
*
|
||||||
|
* @param {(Function|Object|Participant[])} stateOrGetState - The redux state
|
||||||
|
* features/base/participants, the (whole) redux state, or redux's
|
||||||
|
* {@code getState} function to be used to retrieve the
|
||||||
|
* features/base/participants state.
|
||||||
|
* @returns {Participant[]}
|
||||||
|
*/
|
||||||
|
export function getParticipants(stateOrGetState: Object | Function) {
|
||||||
|
return _getAllParticipants(stateOrGetState).filter(p => !p.isBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,9 +147,7 @@ export function getParticipantCount(stateOrGetState: Object | Function) {
|
||||||
* @returns {(Participant|undefined)}
|
* @returns {(Participant|undefined)}
|
||||||
*/
|
*/
|
||||||
export function getPinnedParticipant(stateOrGetState: Object | Function) {
|
export function getPinnedParticipant(stateOrGetState: Object | Function) {
|
||||||
const participants = _getParticipants(stateOrGetState);
|
return _getAllParticipants(stateOrGetState).find(p => p.pinned);
|
||||||
|
|
||||||
return participants.find(p => p.pinned);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,7 +160,7 @@ export function getPinnedParticipant(stateOrGetState: Object | Function) {
|
||||||
* @private
|
* @private
|
||||||
* @returns {Participant[]}
|
* @returns {Participant[]}
|
||||||
*/
|
*/
|
||||||
function _getParticipants(stateOrGetState) {
|
function _getAllParticipants(stateOrGetState) {
|
||||||
return (
|
return (
|
||||||
Array.isArray(stateOrGetState)
|
Array.isArray(stateOrGetState)
|
||||||
? stateOrGetState
|
? stateOrGetState
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* global APP */
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { Avatar } from '../../base/participants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@code Component} for showing a participant's avatar and
|
||||||
|
* name and emits an event when it has been clicked.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class ContactListItem extends Component {
|
||||||
|
/**
|
||||||
|
* Default values for {@code ContactListItem} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The link to the participant's avatar image.
|
||||||
|
*/
|
||||||
|
avatarURI: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An id attribute to set on the root of {@code ContactListItem}. Used
|
||||||
|
* by the torture tests.
|
||||||
|
*/
|
||||||
|
id: React.PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The participant's display name.
|
||||||
|
*/
|
||||||
|
name: React.PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes new {@code ContactListItem} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once for every instance.
|
||||||
|
this._onClick = this._onClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className = 'clickable contact-list-item'
|
||||||
|
id = { this.props.id }
|
||||||
|
onClick = { this._onClick }>
|
||||||
|
{ this.props.avatarURI ? this._renderAvatar() : null }
|
||||||
|
<p className = 'contact-list-item-name'>
|
||||||
|
{ this.props.name }
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits an event notifying the contact list item for the passed in
|
||||||
|
* participant ID has been clicked.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onClick() {
|
||||||
|
// FIXME move this call to a pinning action, which is what's happening
|
||||||
|
// on the listener end, when the listener is properly hooked into redux.
|
||||||
|
APP.UI.emitEvent(UIEvents.CONTACT_CLICKED, this.props.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the React Element for displaying the participant's avatar image.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderAvatar() {
|
||||||
|
return (
|
||||||
|
<Avatar
|
||||||
|
className = 'icon-avatar avatar'
|
||||||
|
uri = { this.props.avatarURI } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ContactListItem;
|
|
@ -0,0 +1,182 @@
|
||||||
|
import Button from '@atlaskit/button';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import Avatar from '../../../../modules/UI/avatar/Avatar';
|
||||||
|
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
import { getParticipants } from '../../base/participants';
|
||||||
|
import { openInviteDialog } from '../../invite';
|
||||||
|
|
||||||
|
import ContactListItem from './ContactListItem';
|
||||||
|
|
||||||
|
const { PropTypes } = React;
|
||||||
|
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React component for showing a list of current conference participants, the
|
||||||
|
* current conference lock state, and a button to open the invite dialog.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class ContactListPanel extends Component {
|
||||||
|
/**
|
||||||
|
* Default values for {@code ContactListPanel} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Whether or not the conference is currently locked with a password.
|
||||||
|
*/
|
||||||
|
_locked: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The participants to show in the contact list.
|
||||||
|
*/
|
||||||
|
_participants: PropTypes.array,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to open an invite dialog.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code ContactListPanel} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once for every instance.
|
||||||
|
this._onOpenInviteDialog = this._onOpenInviteDialog.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _locked, _participants, t } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'contact-list-panel'>
|
||||||
|
<div className = 'title'>
|
||||||
|
{ t('contactlist', { pcount: _participants.length }) }
|
||||||
|
</div>
|
||||||
|
<div className = 'sideToolbarBlock first'>
|
||||||
|
<Button
|
||||||
|
appearance = 'primary'
|
||||||
|
className = 'contact-list-panel-invite-button'
|
||||||
|
id = 'addParticipantsBtn'
|
||||||
|
onClick = { this._onOpenInviteDialog }
|
||||||
|
type = 'button'>
|
||||||
|
{ t('addParticipants') }
|
||||||
|
</Button>
|
||||||
|
<div>
|
||||||
|
{ _locked
|
||||||
|
? this._renderLockedMessage()
|
||||||
|
: this._renderUnlockedMessage() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul id = 'contacts'>
|
||||||
|
{ this._renderContacts() }
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to open an invite dialog.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onOpenInviteDialog() {
|
||||||
|
this.props.dispatch(openInviteDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders React Elements for displaying information about each participant
|
||||||
|
* in the contact list.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement[]}
|
||||||
|
*/
|
||||||
|
_renderContacts() {
|
||||||
|
return this.props._participants.map(({ avatarId, id, name }) =>
|
||||||
|
( // eslint-disable-line no-extra-parens
|
||||||
|
<ContactListItem
|
||||||
|
avatarURI = { interfaceConfig.SHOW_CONTACTLIST_AVATARS
|
||||||
|
? Avatar.getAvatarUrl(avatarId) : null }
|
||||||
|
id = { id }
|
||||||
|
key = { id }
|
||||||
|
name = { name } />
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a React Element for informing the conference is currently locked.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderLockedMessage() {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className = 'form-control__hint form-control_full-width'
|
||||||
|
id = 'contactListroomLocked'>
|
||||||
|
<span className = 'icon-security-locked' />
|
||||||
|
<span>{ this.props.t('roomLocked') }</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a React Element for informing the conference is currently not
|
||||||
|
* locked.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderUnlockedMessage() {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className = 'form-control__hint form-control_full-width'
|
||||||
|
id = 'contactListroomUnlocked'>
|
||||||
|
<span className = 'icon-security' />
|
||||||
|
<span>{ this.props.t('roomUnlocked') }</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code ContactListPanel}'s
|
||||||
|
* props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _locked: boolean,
|
||||||
|
* _participants: Array
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
_locked: state['features/base/conference'].locked,
|
||||||
|
_participants: getParticipants(state)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(ContactListPanel));
|
|
@ -0,0 +1,58 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { getParticipantCount } from '../../base/participants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React component for showing a badge with the current count of conference
|
||||||
|
* participants.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class ParticipantCounter extends Component {
|
||||||
|
/**
|
||||||
|
* {@code ParticipantCounter} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The number of participants in the conference.
|
||||||
|
*/
|
||||||
|
_count: React.PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span className = 'badge-round'>
|
||||||
|
<span id = 'numberOfParticipants'>
|
||||||
|
{ this.props._count }
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated
|
||||||
|
* {@code ParticipantCounter}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _count: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
_count: getParticipantCount(state)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(ParticipantCounter);
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as ContactListPanel } from './ContactListPanel';
|
||||||
|
export { default as ParticipantCounter } from './ParticipantCounter';
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './components';
|
|
@ -1,11 +1,7 @@
|
||||||
/* global APP */
|
|
||||||
|
|
||||||
import AKFieldText from '@atlaskit/field-text';
|
import AKFieldText from '@atlaskit/field-text';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
|
||||||
|
|
||||||
import { setPassword } from '../../base/conference';
|
import { setPassword } from '../../base/conference';
|
||||||
import { Dialog } from '../../base/dialog';
|
import { Dialog } from '../../base/dialog';
|
||||||
import { translate } from '../../base/i18n';
|
import { translate } from '../../base/i18n';
|
||||||
|
@ -114,12 +110,6 @@ class PasswordRequiredPrompt extends Component {
|
||||||
// succeeds (maybe someone removed the password meanwhile). If it is
|
// succeeds (maybe someone removed the password meanwhile). If it is
|
||||||
// still locked, another password required will be received and the room
|
// still locked, another password required will be received and the room
|
||||||
// again will be marked as locked.
|
// again will be marked as locked.
|
||||||
if (!this.state.password || this.state.password === '') {
|
|
||||||
// XXX Temporary solution while some components are not listening
|
|
||||||
// for lock state updates in redux.
|
|
||||||
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.dispatch(
|
this.props.dispatch(
|
||||||
setPassword(conference, conference.join, this.state.password));
|
setPassword(conference, conference.join, this.state.password));
|
||||||
|
|
||||||
|
|
|
@ -27,12 +27,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
const { conference, error } = action;
|
const { conference, error } = action;
|
||||||
|
|
||||||
if (conference && error === JitsiConferenceErrors.PASSWORD_REQUIRED) {
|
if (conference && error === JitsiConferenceErrors.PASSWORD_REQUIRED) {
|
||||||
// XXX Temporary solution while some components are not listening
|
|
||||||
// for lock state updates in redux.
|
|
||||||
if (typeof APP !== 'undefined') {
|
|
||||||
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
store.dispatch(_showPasswordDialog(conference));
|
store.dispatch(_showPasswordDialog(conference));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -105,6 +105,7 @@ export default class StatelessToolbarButton extends AbstractToolbarButton {
|
||||||
onClick = { this._onClick }
|
onClick = { this._onClick }
|
||||||
ref = { this.props.createRefToButton }>
|
ref = { this.props.createRefToButton }>
|
||||||
{ this._renderInnerElementsIfRequired() }
|
{ this._renderInnerElementsIfRequired() }
|
||||||
|
{ this._renderChildComponentIfRequired() }
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -131,6 +132,22 @@ export default class StatelessToolbarButton extends AbstractToolbarButton {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render any configured child component for the toolbar button.
|
||||||
|
*
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderChildComponentIfRequired(): ReactElement<*> | null {
|
||||||
|
if (this.props.button.childComponent) {
|
||||||
|
const Child = this.props.button.childComponent;
|
||||||
|
|
||||||
|
return <Child />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If toolbar button should contain children elements
|
* If toolbar button should contain children elements
|
||||||
* renders them.
|
* renders them.
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { VideoQualityButton } from '../video-quality';
|
||||||
|
|
||||||
import UIEvents from '../../../service/UI/UIEvents';
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { ParticipantCounter } from '../contact-list';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
declare var JitsiMeetJS: Object;
|
declare var JitsiMeetJS: Object;
|
||||||
|
@ -101,20 +103,9 @@ const buttons: Object = {
|
||||||
* The descriptor of the contact list toolbar button.
|
* The descriptor of the contact list toolbar button.
|
||||||
*/
|
*/
|
||||||
contacts: {
|
contacts: {
|
||||||
|
childComponent: ParticipantCounter,
|
||||||
classNames: [ 'button', 'icon-contactList' ],
|
classNames: [ 'button', 'icon-contactList' ],
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
||||||
// XXX: Hotfix to solve race condition between toolbox rendering and
|
|
||||||
// contact list view that updates the number of active participants
|
|
||||||
// via jQuery. There is case when contact list view updates number of
|
|
||||||
// participants but toolbox has not been rendered yet. Since this issue
|
|
||||||
// is reproducible only for conferences with the only participant let's
|
|
||||||
// use 1 participant as a default value for this badge. Later after
|
|
||||||
// reactification of contact list let's use the value of active
|
|
||||||
// paricipants from Redux store.
|
|
||||||
html: <span className = 'badge-round'>
|
|
||||||
<span id = 'numberOfParticipants'>1</span>
|
|
||||||
</span>,
|
|
||||||
id: 'toolbar_contact_list',
|
id: 'toolbar_contact_list',
|
||||||
onClick() {
|
onClick() {
|
||||||
JitsiMeetJS.analytics.sendEvent(
|
JitsiMeetJS.analytics.sendEvent(
|
||||||
|
|
|
@ -118,35 +118,5 @@ export default {
|
||||||
/**
|
/**
|
||||||
* Notifies that the displayed particpant id on the largeVideo is changed.
|
* Notifies that the displayed particpant id on the largeVideo is changed.
|
||||||
*/
|
*/
|
||||||
LARGE_VIDEO_ID_CHANGED: "UI.large_video_id_changed",
|
LARGE_VIDEO_ID_CHANGED: "UI.large_video_id_changed"
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggling room lock
|
|
||||||
*/
|
|
||||||
TOGGLE_ROOM_LOCK: "UI.toggle_room_lock",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding contact to contact list
|
|
||||||
*/
|
|
||||||
CONTACT_ADDED: "UI.contact_added",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removing the contact from contact list
|
|
||||||
*/
|
|
||||||
CONTACT_REMOVED: "UI.contact_removed",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that a user avatar has changed.
|
|
||||||
*/
|
|
||||||
USER_AVATAR_CHANGED: "UI.user_avatar_changed",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display name changed.
|
|
||||||
*/
|
|
||||||
DISPLAY_NAME_CHANGED: "UI.display_name_changed",
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show custom popup/tooltip for a specified button.
|
|
||||||
*/
|
|
||||||
SHOW_CUSTOM_TOOLBAR_BUTTON_POPUP: "UI.show_custom_toolbar_button_popup"
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue