Compare commits

...

4 Commits

Author SHA1 Message Date
Robert Pintilii a776e427cb fix(raise-hand) Fix multiple raise hand from notification (#11586)
Only dispatch raise hand if hand was not already raised
2022-06-02 10:23:25 +03:00
Robert Pintilii de3338f185 feat: Add name overwrite API (#11543) 2022-06-02 10:22:13 +03:00
Avram Tudor e1daaecbae feat(prejoin) allow disabling prejoin display name editing (#11575) 2022-05-31 19:35:08 -05:00
Avram Tudor 1ad90df517 feat(undock) expose buttons for docking / undocking iframe (#11560) 2022-05-31 19:34:55 -05:00
20 changed files with 381 additions and 16 deletions

View File

@ -495,6 +495,11 @@ var config = {
// Hides add breakout room button
// hideAddRoomButton: false,
// Hides the participant name editing field in the prejoin screen.
// If requireDisplayName is also set as true, a name should still be provided through
// either the jwt or the userInfo from the iframe api init object in order for this to have an effect.
// hidePrejoinDisplayName: false,
// Require users to always specify a display name.
// requireDisplayName: true,
@ -609,6 +614,7 @@ var config = {
// 'chat',
// 'closedcaptions',
// 'desktop',
// 'dock-iframe'
// 'download',
// 'embedmeeting',
// 'etherpad',
@ -637,6 +643,7 @@ var config = {
// 'stats',
// 'tileview',
// 'toggle-camera',
// 'undock-iframe',
// 'videoquality',
// '__end'
// ],

View File

@ -3,6 +3,19 @@
width: 100%;
}
&-avatar {
margin: 8px auto 16px;
&-name {
color: white;
font-size: 16px;
font-weight: 600;
line-height: 26px;
margin-bottom: 32px;
text-align: center;
}
}
&-error {
background-color: #E04757;
border-radius: 6px;

View File

@ -1014,6 +1014,7 @@
"chat": "Open / Close chat",
"clap": "Clap",
"collapse": "Collapse",
"dock": "Dock in main window",
"document": "Toggle shared document",
"download": "Download our apps",
"embedMeeting": "Embed meeting",
@ -1064,6 +1065,7 @@
"tileView": "Toggle tile view",
"toggleCamera": "Toggle camera",
"toggleFilmstrip": "Toggle filmstrip",
"undock": "Undock into separate window",
"videoblur": "Toggle video blur",
"videomute": "Start / Stop camera"
},
@ -1080,6 +1082,7 @@
"closeChat": "Close chat",
"closeReactionsMenu": "Close reactions menu",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"dock": "Dock in main window",
"documentClose": "Close shared document",
"documentOpen": "Open shared document",
"download": "Download our apps",
@ -1148,6 +1151,7 @@
"talkWhileMutedPopup": "Trying to speak? You are muted.",
"tileViewToggle": "Toggle tile view",
"toggleCamera": "Toggle camera",
"undock": "Undock into separate window",
"videoSettings": "Video settings",
"videomute": "Start / Stop camera"
},

View File

@ -40,7 +40,8 @@ import {
isParticipantModerator,
isLocalParticipantModerator,
hasRaisedHand,
grantModerator
grantModerator,
overwriteParticipantsNames
} from '../../react/features/base/participants';
import { updateSettings } from '../../react/features/base/settings';
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
@ -428,6 +429,11 @@ function initCommands() {
logger.error('Failed sending endpoint text message', err);
}
},
'overwrite-names': participantList => {
logger.debug('Overwrite names command received');
APP.store.dispatch(overwriteParticipantsNames(participantList));
},
'toggle-e2ee': enabled => {
logger.debug('Toggle E2EE key command received');
APP.store.dispatch(toggleE2EE(enabled));
@ -1455,6 +1461,22 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the iframe
* docked state has been changed. The responsibility for implementing
* the dock / undock functionality lies with the external application.
*
* @param {boolean} docked - Whether or not the iframe has been set to
* be docked or undocked.
* @returns {void}
*/
notifyIframeDockStateChanged(docked: boolean) {
this._sendEvent({
name: 'iframe-dock-state-changed',
docked
});
}
/**
* Notify external application of a participant, remote or local, being
* removed from the conference by another participant.
@ -1660,6 +1682,19 @@ class API {
});
}
/**
* Notify external application that the breakout rooms changed.
*
* @param {Array} rooms - Array of breakout rooms.
* @returns {void}
*/
notifyBreakoutRoomsUpdated(rooms) {
this._sendEvent({
name: 'breakout-rooms-updated',
rooms
});
}
/**
* Disposes the allocated resources.
*

View File

@ -46,6 +46,7 @@ const commands = {
kickParticipant: 'kick-participant',
muteEveryone: 'mute-everyone',
overwriteConfig: 'overwrite-config',
overwriteNames: 'overwrite-names',
password: 'password',
pinParticipant: 'pin-participant',
rejectParticipant: 'reject-participant',
@ -94,6 +95,7 @@ const events = {
'avatar-changed': 'avatarChanged',
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'breakout-rooms-updated': 'breakoutRoomsUpdated',
'browser-support': 'browserSupport',
'camera-error': 'cameraError',
'chat-updated': 'chatUpdated',
@ -108,6 +110,7 @@ const events = {
'feedback-submitted': 'feedbackSubmitted',
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
'filmstrip-display-changed': 'filmstripDisplayChanged',
'iframe-dock-state-changed': 'iframeDockStateChanged',
'incoming-message': 'incomingMessage',
'knocking-participant': 'knockingParticipant',
'log': 'log',

View File

@ -78,6 +78,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
let descriptionKey;
let titleKey;
let uid;
const localParticipant = getLocalParticipant(getState);
const raisedHand = hasRaisedHand(localParticipant);
switch (action.mediaType) {
case MEDIA_TYPE.AUDIO: {
@ -100,7 +102,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
dispatch(showNotification({
customActionNameKey: [ 'notify.raiseHandAction' ],
customActionHandler: [ () => batch(() => {
dispatch(raiseHand(true));
!raisedHand && dispatch(raiseHand(true));
dispatch(hideNotification(uid));
}) ],
descriptionKey,

View File

@ -167,6 +167,7 @@ export default [
'hideConferenceSubject',
'hideDisplayName',
'hideDominantSpeakerBadge',
'hidePrejoinDisplayName',
'hideRecordingLabel',
'hideParticipantsStats',
'hideConferenceTimer',

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.3332 3.33329C18.3332 2.41282 17.587 1.66663 16.6665 1.66663H8.33317C7.4127 1.66663 6.6665 2.41282 6.6665 3.33329V4.99996H3.33317C2.4127 4.99996 1.6665 5.74615 1.6665 6.66663V16.6666C1.6665 17.5871 2.4127 18.3333 3.33317 18.3333H13.3332C14.2536 18.3333 14.9998 17.5871 14.9998 16.6666V15H16.6665C17.587 15 18.3332 14.2538 18.3332 13.3333V3.33329ZM8.33317 3.33329H16.6665L16.6665 13.3333H14.9998V6.66663C14.9998 5.74615 14.2536 4.99996 13.3332 4.99996H8.33317V3.33329ZM3.33317 6.66663V16.6666H13.3332V6.66663H3.33317ZM6.6665 12.1024V9.99996C6.6665 9.53972 6.29341 9.16663 5.83317 9.16663C5.37293 9.16663 4.99984 9.53972 4.99984 9.99996V14.1296V14.1666C4.99984 14.5693 5.28549 14.9053 5.66523 14.983C5.71947 14.9941 5.77564 15 5.83317 15L5.83356 14.9992C5.83397 14.9992 5.83439 14.9992 5.8348 14.9992L5.83445 15H5.87022H9.99984C10.4601 15 10.8332 14.6269 10.8332 14.1666C10.8332 13.7064 10.4601 13.3333 9.99984 13.3333H7.89741L11.4116 9.81913C11.7515 9.47922 11.7515 8.92813 11.4116 8.58822C11.0717 8.24832 10.5206 8.24832 10.1807 8.58822L6.6665 12.1024Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -44,6 +44,7 @@ export { default as IconDeviceBluetooth } from './bluetooth.svg';
export { default as IconDeviceEarpiece } from './phone-talk.svg';
export { default as IconDeviceHeadphone } from './headset.svg';
export { default as IconDeviceSpeaker } from './volume.svg';
export { default as IconDock } from './dock.svg';
export { default as IconDeviceDocument } from './document.svg';
export { default as IconDominantSpeaker } from './dominant-speaker.svg';
export { default as IconDownload } from './download.svg';
@ -130,6 +131,7 @@ export { default as IconSwitchCamera } from './switch-camera.svg';
export { default as IconTileView } from './tiles-many.svg';
export { default as IconToggleRecording } from './camera-take-picture.svg';
export { default as IconTrash } from './trash.svg';
export { default as IconUndock } from './undock.svg';
export { default as IconUnpin } from './unpin.svg';
export { default as IconVideoOff } from './video-off.svg';
export { default as IconVideoQualityAudioOnly } from './AUD.svg';

View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.6665 1.66663H6.6665C5.74603 1.66663 4.99984 2.41282 4.99984 3.33329V4.99996H3.33317C2.4127 4.99996 1.6665 5.74615 1.6665 6.66663V16.6666C1.6665 17.5871 2.4127 18.3333 3.33317 18.3333H13.3332C14.2536 18.3333 14.9998 17.5871 14.9998 16.6666V15H16.6665C17.587 15 18.3332 14.2538 18.3332 13.3333V3.33329C18.3332 2.41282 17.587 1.66663 16.6665 1.66663ZM13.3332 16.6666V15H6.6665C5.74603 15 4.99984 14.2538 4.99984 13.3333V6.66663H3.33317V16.6666H13.3332ZM6.6665 3.33329V13.3333H16.6665V3.33329H6.6665ZM9.99984 4.99996C9.5396 4.99996 9.1665 5.37306 9.1665 5.83329C9.1665 6.29353 9.5396 6.66663 9.99984 6.66663H12.1023L8.5881 10.1808C8.24819 10.5207 8.24819 11.0718 8.5881 11.4117C8.928 11.7516 9.4791 11.7516 9.819 11.4117L13.3332 7.89753V9.99996C13.3332 10.4602 13.7063 10.8333 14.1665 10.8333C14.6267 10.8333 14.9998 10.4602 14.9998 9.99996V5.83329C14.9998 5.37306 14.6267 4.99996 14.1665 4.99996H14.1295H9.99984Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -212,3 +212,22 @@ export const RAISE_HAND_UPDATED = 'RAISE_HAND_UPDATED';
* }
*/
export const LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED = 'LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED'
/**
* The type of Redux action which overwrites the name of a participant.
* {
* type: OVERWRITE_PARTICIPANT_NAME,
* id: string,
* name: string
* }
*/
export const OVERWRITE_PARTICIPANT_NAME = 'OVERWRITE_PARTICIPANT_NAME';
/**
* The type of Redux action which overwrites the names of multiple participants.
* {
* type: OVERWRITE_PARTICIPANTS_NAMES,
* participantsList: Array<Object>,
* }
*/
export const OVERWRITE_PARTICIPANTS_NAMES = 'OVERWRITE_PARTICIPANTS_NAMES';

View File

@ -18,7 +18,9 @@ import {
PIN_PARTICIPANT,
SCREENSHARE_PARTICIPANT_NAME_CHANGED,
SET_LOADABLE_AVATAR_URL,
RAISE_HAND_UPDATED
RAISE_HAND_UPDATED,
OVERWRITE_PARTICIPANT_NAME,
OVERWRITE_PARTICIPANTS_NAMES
} from './actionTypes';
import {
DISCO_REMOTE_CONTROL_FEATURE
@ -660,3 +662,31 @@ export function localParticipantAudioLevelChanged(level) {
level
};
}
/**
* Overwrites the name of the participant with the given id.
*
* @param {string} id - Participant id;.
* @param {string} name - New participant name.
* @returns {Object}
*/
export function overwriteParticipantName(id, name) {
return {
type: OVERWRITE_PARTICIPANT_NAME,
id,
name
};
}
/**
* Overwrites the names of the given participants.
*
* @param {Array<Object>} participantList - The list of participants to overwrite.
* @returns {Object}
*/
export function overwriteParticipantsNames(participantList) {
return {
type: OVERWRITE_PARTICIPANTS_NAMES,
participantList
};
}

View File

@ -5,6 +5,8 @@ import { batch } from 'react-redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { approveParticipant } from '../../av-moderation/actions';
import { UPDATE_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
import { getBreakoutRooms } from '../../breakout-rooms/functions';
import { toggleE2EE } from '../../e2ee/actions';
import { MAX_MODE } from '../../e2ee/constants';
import {
@ -34,6 +36,8 @@ import {
LOCAL_PARTICIPANT_AUDIO_LEVEL_CHANGED,
LOCAL_PARTICIPANT_RAISE_HAND,
MUTE_REMOTE_PARTICIPANT,
OVERWRITE_PARTICIPANTS_NAMES,
OVERWRITE_PARTICIPANT_NAME,
PARTICIPANT_DISPLAY_NAME_CHANGED,
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
@ -44,6 +48,7 @@ import {
localParticipantIdChanged,
localParticipantJoined,
localParticipantLeft,
overwriteParticipantName,
participantLeft,
participantUpdated,
raiseHand,
@ -68,6 +73,7 @@ import {
hasRaisedHand,
isLocalParticipantModerator
} from './functions';
import logger from './logger';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';
import './subscriber';
@ -231,6 +237,74 @@ MiddlewareRegistry.register(store => next => action => {
case PARTICIPANT_UPDATED:
return _participantJoinedOrUpdated(store, next, action);
case OVERWRITE_PARTICIPANTS_NAMES: {
const { participantList } = action;
if (!Array.isArray(participantList)) {
logger.error('Overwrite names failed. Argument is not an array.');
return;
}
batch(() => {
participantList.forEach(p => {
store.dispatch(overwriteParticipantName(p.id, p.name));
});
});
break;
}
case OVERWRITE_PARTICIPANT_NAME: {
const { dispatch, getState } = store;
const state = getState();
const { id, name } = action;
let breakoutRoom = false, identifier = id;
if (id.indexOf('@') !== -1) {
identifier = id.slice(id.indexOf('/') + 1);
breakoutRoom = true;
action.id = identifier;
}
if (breakoutRoom) {
const rooms = getBreakoutRooms(state);
const roomCounter = state['features/breakout-rooms'].roomCounter;
const newRooms = {};
Object.entries(rooms).forEach(([ key, r ]) => {
const participants = r?.participants || {};
const jid = Object.keys(participants).find(p =>
p.slice(p.indexOf('/') + 1) === identifier);
if (jid) {
newRooms[key] = {
...r,
participants: {
...participants,
[jid]: {
...participants[jid],
displayName: name
}
}
};
} else {
newRooms[key] = r;
}
});
dispatch({
type: UPDATE_BREAKOUT_ROOMS,
rooms,
roomCounter,
updatedNames: true
});
} else {
dispatch(participantUpdated({
id: identifier,
name
}));
}
break;
}
}
return next(action);
@ -491,6 +565,7 @@ function _maybePlaySounds({ getState, dispatch }, action) {
*/
function _participantJoinedOrUpdated(store, next, action) {
const { dispatch, getState } = store;
const { overwrittenNameList } = store.getState()['features/base/participants'];
const { participant: { avatarURL, email, id, local, name, raisedHandTimestamp } } = action;
// Send an external update of the local participant's raised hand state
@ -508,6 +583,10 @@ function _participantJoinedOrUpdated(store, next, action) {
}
}
if (overwrittenNameList[id]) {
action.participant.name = overwrittenNameList[id];
}
// Allow the redux update to go through and compare the old avatar
// to the new avatar and emit out change events if necessary.
const result = next(action);

View File

@ -7,6 +7,7 @@ import { ReducerRegistry, set } from '../redux';
import {
DOMINANT_SPEAKER_CHANGED,
OVERWRITE_PARTICIPANT_NAME,
PARTICIPANT_ID_CHANGED,
PARTICIPANT_JOINED,
PARTICIPANT_LEFT,
@ -63,6 +64,7 @@ const DEFAULT_STATE = {
haveParticipantWithScreenSharingFeature: false,
local: undefined,
localScreenShare: undefined,
overwrittenNameList: {},
pinnedParticipant: undefined,
raisedHandsQueue: [],
remote: new Map(),
@ -413,6 +415,17 @@ ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, a
return { ...state };
}
case OVERWRITE_PARTICIPANT_NAME: {
const { id, name } = action;
return {
...state,
overwrittenNameList: {
...state.overwrittenNameList,
[id]: name
}
};
}
}
return state;

View File

@ -7,9 +7,10 @@ import { editMessage, MESSAGE_TYPE_REMOTE } from '../chat';
import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
import { moveToRoom } from './actions';
import { getBreakoutRooms } from './functions';
import logger from './logger';
declare var APP: Object;
/**
* Registers a change handler for state['features/base/conference'].conference to
* set the event listeners needed for the breakout rooms feature to operate.
@ -25,6 +26,9 @@ StateListenerRegistry.register(
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED, ({ rooms, roomCounter }) => {
logger.debug('Room list updated');
if (typeof APP !== 'undefined') {
APP.API.notifyBreakoutRoomsUpdated(rooms);
}
dispatch({
type: UPDATE_BREAKOUT_ROOMS,
rooms,
@ -36,16 +40,50 @@ StateListenerRegistry.register(
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { type } = action;
const result = next(action);
switch (type) {
case UPDATE_BREAKOUT_ROOMS: {
const { messages } = getState()['features/chat'];
// edit name if it was overwritten
if (!action.updatedNames) {
const { overwrittenNameList } = getState()['features/base/participants'];
if (Object.keys(overwrittenNameList).length > 0) {
const newRooms = {};
Object.entries(action.rooms).forEach(([ key, r ]) => {
let participants = r?.participants || {};
let jid;
for (const id of Object.keys(overwrittenNameList)) {
jid = Object.keys(participants).find(p => p.slice(p.indexOf('/') + 1) === id);
if (jid) {
participants = {
...participants,
[jid]: {
...participants[jid],
displayName: overwrittenNameList[id]
}
};
}
}
newRooms[key] = {
...r,
participants
};
});
action.rooms = newRooms;
}
}
// edit the chat history to match names for participants in breakout rooms
const { messages } = getState()['features/chat'];
messages && messages.forEach(m => {
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) {
const rooms = getBreakoutRooms(getState);
const rooms = action.rooms;
for (const room of Object.values(rooms)) {
// $FlowExpectedError
@ -65,5 +103,5 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
}
}
return result;
return next(action);
});

View File

@ -3,11 +3,13 @@
import InlineDialog from '@atlaskit/inline-dialog';
import React, { Component } from 'react';
import { Avatar } from '../../base/avatar';
import { getRoomName } from '../../base/conference';
import { isNameReadOnly } from '../../base/config';
import { translate } from '../../base/i18n';
import { IconArrowDown, IconArrowUp, IconPhone, IconVolumeOff } from '../../base/icons';
import { isVideoMutedByUser } from '../../base/media';
import { getLocalParticipant } from '../../base/participants';
import { ActionButton, InputField, PreMeetingScreen } from '../../base/premeeting';
import { connect } from '../../base/redux';
import { getDisplayName, updateSettings } from '../../base/settings';
@ -21,7 +23,8 @@ import {
isDeviceStatusVisible,
isDisplayNameRequired,
isJoinByPhoneButtonVisible,
isJoinByPhoneDialogVisible
isJoinByPhoneDialogVisible,
isPrejoinDisplayNameVisible
} from '../functions';
import DropdownButton from './DropdownButton';
@ -29,6 +32,11 @@ import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
type Props = {
/**
* Indicates whether the display name is editable.
*/
canEditDisplayName: boolean,
/**
* Flag signaling if the device status is visible or not.
*/
@ -59,6 +67,11 @@ type Props = {
*/
updateSettings: Function,
/**
* Local participant id.
*/
participantId: string,
/**
* The prejoin config.
*/
@ -145,6 +158,8 @@ class Prejoin extends Component<Props, State> {
this._showDialogKeyPress = this._showDialogKeyPress.bind(this);
this._onJoinKeyPress = this._onJoinKeyPress.bind(this);
this._getExtraJoinButtons = this._getExtraJoinButtons.bind(this);
this.showDisplayNameField = props.canEditDisplayName || props.showErrorOnJoin;
}
_onJoinButtonClick: () => void;
@ -330,6 +345,7 @@ class Prejoin extends Component<Props, State> {
joinConference,
joinConferenceWithoutAudio,
name,
participantId,
prejoinConfig,
readOnlyName,
showCameraPreview,
@ -360,7 +376,7 @@ class Prejoin extends Component<Props, State> {
<div
className = 'prejoin-input-area'
data-testid = 'prejoin.screen'>
<InputField
{this.showDisplayNameField ? (<InputField
autoComplete = { 'name' }
autoFocus = { true }
className = { showError ? 'error' : '' }
@ -370,6 +386,16 @@ class Prejoin extends Component<Props, State> {
placeHolder = { t('dialog.enterDisplayName') }
readOnly = { readOnlyName }
value = { name } />
) : (
<>
<Avatar
className = 'prejoin-avatar'
displayName = { name }
participantId = { participantId }
size = { 72 } />
<div className = 'prejoin-avatar-name'>{name}</div>
</>
)}
{showError && <div
className = 'prejoin-error'
@ -423,18 +449,21 @@ class Prejoin extends Component<Props, State> {
function mapStateToProps(state): Object {
const name = getDisplayName(state);
const showErrorOnJoin = isDisplayNameRequired(state) && !name;
const { id: participantId } = getLocalParticipant(state);
return {
name,
canEditDisplayName: isPrejoinDisplayNameVisible(state),
deviceStatusVisible: isDeviceStatusVisible(state),
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
name,
participantId,
prejoinConfig: state['features/base/config'].prejoinConfig,
readOnlyName: isNameReadOnly(state),
roomName: getRoomName(state),
showCameraPreview: !isVideoMutedByUser(state),
showDialog: isJoinByPhoneDialogVisible(state),
showErrorOnJoin,
hasJoinByPhoneButton: isJoinByPhoneButtonVisible(state),
readOnlyName: isNameReadOnly(state),
showCameraPreview: !isVideoMutedByUser(state),
videoTrack: getLocalJitsiVideoTrack(state),
prejoinConfig: state['features/base/config'].prejoinConfig
videoTrack: getLocalJitsiVideoTrack(state)
};
}

View File

@ -36,6 +36,16 @@ export function isDisplayNameRequired(state: Object): boolean {
|| state['features/base/config'].requireDisplayName;
}
/**
* Selector for determining if the prejoin display name field is visible.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isPrejoinDisplayNameVisible(state: Object): boolean {
return !state['features/base/config'].hidePrejoinDisplayName;
}
/**
* Returns the text for the prejoin status bar.
*

View File

@ -0,0 +1,29 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconDock } from '../../../base/icons';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
declare var APP: Object;
/**
* Implementation of a button for notifying integrators that iframe should be docked.
*/
class DockIframeButton extends AbstractButton<AbstractButtonProps, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.dock';
icon = IconDock;
label = 'toolbar.dock';
tooltip = 'toolbar.dock';
/**
* Handles clicking / pressing the button by triggering external api event.
*
* @protected
* @returns {void}
*/
_handleClick() {
APP.API.notifyIframeDockStateChanged(true);
}
}
export default translate(DockIframeButton);

View File

@ -88,6 +88,7 @@ import MuteEveryoneButton from '../MuteEveryoneButton';
import MuteEveryonesVideoButton from '../MuteEveryonesVideoButton';
import AudioSettingsButton from './AudioSettingsButton';
import DockIframeButton from './DockIframeButton';
import FullscreenButton from './FullscreenButton';
import LinkToSalesforceButton from './LinkToSalesforceButton';
import OverflowMenuButton from './OverflowMenuButton';
@ -95,6 +96,7 @@ import ProfileButton from './ProfileButton';
import Separator from './Separator';
import ShareDesktopButton from './ShareDesktopButton';
import ToggleCameraButton from './ToggleCameraButton';
import UndockIframeButton from './UndockIframeButton';
import VideoSettingsButton from './VideoSettingsButton';
/**
@ -779,6 +781,18 @@ class Toolbox extends Component<Props> {
group: 3
};
const dockIframe = {
key: 'dock-iframe',
Content: DockIframeButton,
group: 3
};
const undockIframe = {
key: 'undock-iframe',
Content: UndockIframeButton,
group: 3
};
const speakerStats = {
key: 'stats',
Content: SpeakerStatsButton,
@ -845,6 +859,8 @@ class Toolbox extends Component<Props> {
shareAudio,
etherpad,
virtualBackground,
dockIframe,
undockIframe,
speakerStats,
settings,
shortcuts,

View File

@ -0,0 +1,29 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconUndock } from '../../../base/icons';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
declare var APP: Object;
/**
* Implementation of a button for notifying integrators that iframe should be undocked.
*/
class UndockIframeButton extends AbstractButton<AbstractButtonProps, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.undock';
icon = IconUndock;
label = 'toolbar.undock';
tooltip = 'toolbar.undock';
/**
* Handles clicking / pressing the button by triggering external api event.
*
* @protected
* @returns {void}
*/
_handleClick() {
APP.API.notifyIframeDockStateChanged(false);
}
}
export default translate(UndockIframeButton);