Compare commits
15 Commits
jitihouse/
...
release-60
Author | SHA1 | Date |
---|---|---|
Avram Tudor | 813b6fb794 | |
Avram Tudor | 40dd3e5193 | |
Hristo Terezov | 8a9410cfdb | |
Hristo Terezov | 24da3fb532 | |
Hristo Terezov | 066dddcf3f | |
Hristo Terezov | da723ddc0d | |
Hristo Terezov | 6b0bd811e8 | |
Robert Pintilii | 9433d65995 | |
Mihaela Dumitru | 2bf526afa5 | |
Jaya Allamsetty | ab0bdaae70 | |
Hristo Terezov | 9e70cae99f | |
Saúl Ibarra Corretgé | ba76cc7544 | |
Mihaela Dumitru | 1dc38e5be6 | |
Jaya Allamsetty | 4c693c98ce | |
Jaya Allamsetty | ff701e6181 |
19
config.js
19
config.js
|
@ -485,6 +485,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,
|
||||
|
||||
|
@ -599,6 +604,7 @@ var config = {
|
|||
// 'chat',
|
||||
// 'closedcaptions',
|
||||
// 'desktop',
|
||||
// 'dock-iframe'
|
||||
// 'download',
|
||||
// 'embedmeeting',
|
||||
// 'etherpad',
|
||||
|
@ -627,6 +633,7 @@ var config = {
|
|||
// 'stats',
|
||||
// 'tileview',
|
||||
// 'toggle-camera',
|
||||
// 'undock-iframe',
|
||||
// 'videoquality',
|
||||
// '__end'
|
||||
// ],
|
||||
|
@ -1093,8 +1100,18 @@ var config = {
|
|||
// breakoutRooms: {
|
||||
// // Hides the add breakout room button. This replaces `hideAddRoomButton`.
|
||||
// hideAddRoomButton: false,
|
||||
// // Hides the auto assign participants button.
|
||||
// hideAutoAssignButton: false,
|
||||
// // Hides the participants pane footer menu.
|
||||
// hideFooterMenu: false,
|
||||
// // Hides the join breakout room button.
|
||||
// hideJoinRoomButton: false
|
||||
// hideJoinRoomButton: false,
|
||||
// // Hides the moderator settings tab.
|
||||
// hideModeratorSettingsTab: false,
|
||||
// // Hides the more actions button.
|
||||
// hideMoreActionsButton: false,
|
||||
// // Hides the mute all button.
|
||||
// hideMuteAllButton: false
|
||||
// },
|
||||
|
||||
// When true the user cannot add more images to be used as virtual background.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1013,6 +1013,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",
|
||||
|
@ -1063,6 +1064,7 @@
|
|||
"tileView": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"toggleFilmstrip": "Toggle filmstrip",
|
||||
"undock": "Undock into separate window",
|
||||
"videoblur": "Toggle video blur",
|
||||
"videomute": "Start / Stop camera"
|
||||
},
|
||||
|
@ -1079,6 +1081,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",
|
||||
|
@ -1147,6 +1150,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"
|
||||
},
|
||||
|
|
|
@ -1441,6 +1441,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.
|
||||
|
|
|
@ -106,6 +106,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',
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1415.0.0+fa916d41/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#fe484f46ee88812f21e091516889802e48db6c2d",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
|
@ -11796,8 +11796,8 @@
|
|||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1415.0.0+fa916d41/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-xCDIkUykAYPLocmnOItFC1PYNYVMTp57XwJ1PXvOwHV4lZO9RBG36ln5QBUonD2P0X6di2UGiRzOi9l4FaHoLQ==",
|
||||
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#fe484f46ee88812f21e091516889802e48db6c2d",
|
||||
"integrity": "sha512-M9O/XF2itBNTklCOpKTeNT2hseZIztLkwvL7usxWmzyaU+R4BAeEdPLSBwwBe4S2CuPmnvPDtBstpRCt/WhPqg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
|
@ -28899,8 +28899,9 @@
|
|||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1415.0.0+fa916d41/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-xCDIkUykAYPLocmnOItFC1PYNYVMTp57XwJ1PXvOwHV4lZO9RBG36ln5QBUonD2P0X6di2UGiRzOi9l4FaHoLQ==",
|
||||
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#fe484f46ee88812f21e091516889802e48db6c2d",
|
||||
"integrity": "sha512-M9O/XF2itBNTklCOpKTeNT2hseZIztLkwvL7usxWmzyaU+R4BAeEdPLSBwwBe4S2CuPmnvPDtBstpRCt/WhPqg==",
|
||||
"from": "lib-jitsi-meet@github:jitsi/lib-jitsi-meet#fe484f46ee88812f21e091516889802e48db6c2d",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1415.0.0+fa916d41/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#fe484f46ee88812f21e091516889802e48db6c2d",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.2",
|
||||
|
|
|
@ -167,6 +167,7 @@ export default [
|
|||
'hideConferenceSubject',
|
||||
'hideDisplayName',
|
||||
'hideDominantSpeakerBadge',
|
||||
'hidePrejoinDisplayName',
|
||||
'hideRecordingLabel',
|
||||
'hideParticipantsStats',
|
||||
'hideConferenceTimer',
|
||||
|
|
|
@ -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 |
|
@ -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';
|
||||
|
|
|
@ -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 |
|
@ -5,6 +5,7 @@ import { batch } from 'react-redux';
|
|||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { _RESET_BREAKOUT_ROOMS } from '../../breakout-rooms/actionTypes';
|
||||
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
|
@ -330,6 +331,7 @@ StateListenerRegistry.register(
|
|||
for (const track of remoteTracks) {
|
||||
dispatch(trackRemoved(track.jitsiTrack));
|
||||
}
|
||||
dispatch({ type: _RESET_BREAKOUT_ROOMS });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { getParticipantCount, isLocalParticipantModerator } from '../base/participants';
|
||||
import { toState } from '../base/redux';
|
||||
|
||||
import { FEATURE_KEY } from './constants';
|
||||
|
@ -85,3 +86,40 @@ export const getBreakoutRoomsConfig = (stateful: Function | Object) => {
|
|||
|
||||
return breakoutRooms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the add breakout room button is visible.
|
||||
*
|
||||
* @param {Function | Object} stateful - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isAddBreakoutRoomButtonVisible = (stateful: Function | Object) => {
|
||||
const state = toState(stateful);
|
||||
const isLocalModerator = isLocalParticipantModerator(state);
|
||||
const { conference } = state['features/base/conference'];
|
||||
const isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
|
||||
const { hideAddRoomButton } = getBreakoutRoomsConfig(state);
|
||||
|
||||
return isLocalModerator && isBreakoutRoomsSupported && !hideAddRoomButton;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the auto assign participants to breakout rooms button is visible.
|
||||
*
|
||||
* @param {Function | Object} stateful - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isAutoAssignParticipantsVisible = (stateful: Function | Object) => {
|
||||
const state = toState(stateful);
|
||||
const rooms = getBreakoutRooms(state);
|
||||
const inBreakoutRoom = isInBreakoutRoom(state);
|
||||
const isLocalModerator = isLocalParticipantModerator(state);
|
||||
const participantsCount = getParticipantCount(state);
|
||||
const { hideAutoAssignButton } = getBreakoutRoomsConfig(state);
|
||||
|
||||
return !inBreakoutRoom
|
||||
&& isLocalModerator
|
||||
&& participantsCount > 2
|
||||
&& Object.keys(rooms).length > 1
|
||||
&& !hideAutoAssignButton;
|
||||
};
|
||||
|
|
|
@ -14,36 +14,32 @@ import logger from './logger';
|
|||
* Submits the settings related to device selection.
|
||||
*
|
||||
* @param {Object} newState - The new settings.
|
||||
* @param {boolean} isDisplayedOnWelcomePage - Indicates whether the device selection dialog is displayed on the
|
||||
* welcome page or not.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function submitDeviceSelectionTab(newState) {
|
||||
export function submitDeviceSelectionTab(newState, isDisplayedOnWelcomePage) {
|
||||
return (dispatch, getState) => {
|
||||
const currentState = getDeviceSelectionDialogProps(getState());
|
||||
const currentState = getDeviceSelectionDialogProps(getState(), isDisplayedOnWelcomePage);
|
||||
|
||||
if (newState.selectedVideoInputId
|
||||
&& newState.selectedVideoInputId
|
||||
!== currentState.selectedVideoInputId) {
|
||||
if (newState.selectedVideoInputId && (newState.selectedVideoInputId !== currentState.selectedVideoInputId)) {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: newState.selectedVideoInputId,
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(
|
||||
setVideoInputDevice(newState.selectedVideoInputId));
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.selectedAudioInputId
|
||||
&& newState.selectedAudioInputId
|
||||
!== currentState.selectedAudioInputId) {
|
||||
if (newState.selectedAudioInputId && newState.selectedAudioInputId !== currentState.selectedAudioInputId) {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: newState.selectedAudioInputId,
|
||||
userSelectedMicDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedAudioInputId, 'audioInput')
|
||||
}));
|
||||
|
||||
dispatch(
|
||||
setAudioInputDevice(newState.selectedAudioInputId));
|
||||
dispatch(setAudioInputDevice(newState.selectedAudioInputId));
|
||||
}
|
||||
|
||||
if (newState.selectedAudioOutputId
|
||||
|
|
|
@ -27,29 +27,37 @@ import {
|
|||
*
|
||||
* @param {(Function|Object)} stateful -The (whole) redux state, or redux's
|
||||
* {@code getState} function to be used to retrieve the state.
|
||||
* @param {boolean} isDisplayedOnWelcomePage - Indicates whether the device selection dialog is displayed on the
|
||||
* welcome page or not.
|
||||
* @returns {Object} - The properties for the device selection dialog.
|
||||
*/
|
||||
export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
||||
export function getDeviceSelectionDialogProps(stateful: Object | Function, isDisplayedOnWelcomePage) {
|
||||
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
||||
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
||||
const disablePreviews = isIosMobileBrowser();
|
||||
|
||||
const state = toState(stateful);
|
||||
const settings = state['features/base/settings'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { permissions } = state['features/base/devices'];
|
||||
const isMobileSafari = isIosMobileBrowser();
|
||||
const cameraChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('input');
|
||||
const inputDeviceChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('input');
|
||||
const speakerChangeSupported = JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output');
|
||||
const userSelectedCamera = getUserSelectedCameraDeviceId(state);
|
||||
const userSelectedMic = getUserSelectedMicDeviceId(state);
|
||||
let disableAudioInputChange = !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported();
|
||||
let disableVideoInputSelect = !cameraChangeSupported;
|
||||
let selectedAudioInputId = isMobileSafari ? userSelectedMic : settings.micDeviceId;
|
||||
|
||||
// When the previews are disabled we don't need multiple audio input support in order to chage the mic. This is the
|
||||
// case for Safari on iOS.
|
||||
let disableAudioInputChange
|
||||
= !JitsiMeetJS.mediaDevices.isMultipleAudioInputSupported() && !(disablePreviews && inputDeviceChangeSupported);
|
||||
let disableVideoInputSelect = !inputDeviceChangeSupported;
|
||||
let selectedAudioInputId = settings.micDeviceId;
|
||||
let selectedAudioOutputId = getAudioOutputDeviceId();
|
||||
let selectedVideoInputId = isMobileSafari ? userSelectedCamera : settings.cameraDeviceId;
|
||||
let selectedVideoInputId = settings.cameraDeviceId;
|
||||
|
||||
// audio input change will be a problem only when we are in a
|
||||
// conference and this is not supported, when we open device selection on
|
||||
// welcome page changing input devices will not be a problem
|
||||
// on welcome page we also show only what we have saved as user selected devices
|
||||
if (!conference) {
|
||||
if (isDisplayedOnWelcomePage) {
|
||||
disableAudioInputChange = false;
|
||||
disableVideoInputSelect = false;
|
||||
selectedAudioInputId = userSelectedMic;
|
||||
|
@ -66,10 +74,10 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
|||
disableVideoInputSelect,
|
||||
hasAudioPermission: permissions.audio,
|
||||
hasVideoPermission: permissions.video,
|
||||
hideAudioInputPreview: disableAudioInputChange || !JitsiMeetJS.isCollectingLocalStats(),
|
||||
hideAudioOutputPreview: !speakerChangeSupported,
|
||||
hideAudioInputPreview: disableAudioInputChange || !JitsiMeetJS.isCollectingLocalStats() || disablePreviews,
|
||||
hideAudioOutputPreview: !speakerChangeSupported || disablePreviews,
|
||||
hideAudioOutputSelect: !speakerChangeSupported,
|
||||
hideVideoInputPreview: !cameraChangeSupported,
|
||||
hideVideoInputPreview: !inputDeviceChangeSupported || disablePreviews,
|
||||
selectedAudioInputId,
|
||||
selectedAudioOutputId,
|
||||
selectedVideoInputId
|
||||
|
|
|
@ -713,5 +713,5 @@ export function shouldDisplayStageFilmstrip(state, minParticipantCount = 2) {
|
|||
export function isStageFilmstripEnabled(state) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return !filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP;
|
||||
return !(filmstrip?.disableStageFilmstrip ?? true) && interfaceConfig.VERTICAL_FILMSTRIP;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ const DEFAULT_STATE = {
|
|||
* @public
|
||||
* @type {Number}
|
||||
*/
|
||||
maxStageParticipants: 4,
|
||||
maxStageParticipants: 1,
|
||||
|
||||
/**
|
||||
* The custom audio volume levels per participant.
|
||||
|
|
|
@ -61,8 +61,13 @@ StateListenerRegistry.register(
|
|||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
return { layout: getCurrentLayout(state),
|
||||
width: state['features/base/responsive-ui'].clientWidth };
|
||||
const { clientHeight, clientWidth } = state['features/base/responsive-ui'];
|
||||
|
||||
return {
|
||||
layout: getCurrentLayout(state),
|
||||
height: clientHeight,
|
||||
width: clientWidth
|
||||
};
|
||||
},
|
||||
/* listener */ ({ layout }, store) => {
|
||||
switch (layout) {
|
||||
|
|
|
@ -4,13 +4,14 @@ import React, { useCallback } from 'react';
|
|||
import { useSelector } from 'react-redux';
|
||||
|
||||
import useContextMenu from '../../../../../base/components/context-menu/useContextMenu';
|
||||
import { getParticipantCount, isLocalParticipantModerator } from '../../../../../base/participants';
|
||||
import { isLocalParticipantModerator } from '../../../../../base/participants';
|
||||
import { equals } from '../../../../../base/redux';
|
||||
import {
|
||||
getBreakoutRooms,
|
||||
isInBreakoutRoom,
|
||||
getCurrentRoomId,
|
||||
getBreakoutRoomsConfig
|
||||
getBreakoutRoomsConfig,
|
||||
isAutoAssignParticipantsVisible
|
||||
} from '../../../../../breakout-rooms/functions';
|
||||
import { showOverflowDrawer } from '../../../../../toolbox/functions';
|
||||
|
||||
|
@ -36,7 +37,7 @@ export const RoomList = ({ searchString }: Props) => {
|
|||
.sort((p1: Object, p2: Object) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const inBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const participantsCount = useSelector(getParticipantCount);
|
||||
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
|
||||
const { hideJoinRoomButton } = useSelector(getBreakoutRoomsConfig);
|
||||
const _overflowDrawer = useSelector(showOverflowDrawer);
|
||||
const [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
|
||||
|
@ -46,11 +47,7 @@ export const RoomList = ({ searchString }: Props) => {
|
|||
return (
|
||||
<>
|
||||
{inBreakoutRoom && <LeaveButton />}
|
||||
{!inBreakoutRoom
|
||||
&& isLocalModerator
|
||||
&& participantsCount > 2
|
||||
&& rooms.length > 1
|
||||
&& <AutoAssignButton />}
|
||||
{showAutoAssign && <AutoAssignButton />}
|
||||
<div id = 'breakout-rooms-list'>
|
||||
{rooms.map((room: Object) => (
|
||||
<React.Fragment key = { room.id }>
|
||||
|
|
|
@ -8,20 +8,19 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import {
|
||||
getParticipantCount,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { equals } from '../../../base/redux';
|
||||
import {
|
||||
getBreakoutRooms,
|
||||
getBreakoutRoomsConfig,
|
||||
getCurrentRoomId,
|
||||
isAddBreakoutRoomButtonVisible,
|
||||
isAutoAssignParticipantsVisible,
|
||||
isInBreakoutRoom
|
||||
} from '../../../breakout-rooms/functions';
|
||||
import { getKnockingParticipants } from '../../../lobby/functions';
|
||||
import MuteEveryoneDialog
|
||||
from '../../../video-menu/components/native/MuteEveryoneDialog';
|
||||
import { isFooterMenuVisible, isMoreActionsVisible, isMuteAllVisible } from '../../functions';
|
||||
import {
|
||||
AddBreakoutRoomButton,
|
||||
AutoAssignButton,
|
||||
|
@ -49,21 +48,18 @@ const ParticipantsPane = () => {
|
|||
[ dispatch ]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { hideAddRoomButton } = useSelector(getBreakoutRoomsConfig);
|
||||
const { conference } = useSelector(state => state['features/base/conference']);
|
||||
|
||||
// $FlowExpectedError
|
||||
const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
|
||||
const currentRoomId = useSelector(getCurrentRoomId);
|
||||
const rooms: Array<Object> = Object.values(useSelector(getBreakoutRooms, equals))
|
||||
.filter((room: Object) => room.id !== currentRoomId)
|
||||
.sort((p1: Object, p2: Object) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const inBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const participantsCount = useSelector(getParticipantCount);
|
||||
const autoAssign = !inBreakoutRoom && isLocalModerator
|
||||
&& participantsCount > 2 && rooms.length > 1;
|
||||
const addBreakoutRoom
|
||||
= _isBreakoutRoomsSupported && !hideAddRoomButton && isLocalModerator;
|
||||
const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
|
||||
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
|
||||
const showFooterMenu = useSelector(isFooterMenuVisible);
|
||||
const showMoreActions = useSelector(isMoreActionsVisible);
|
||||
const showMuteAll = useSelector(isMuteAllVisible);
|
||||
const lobbyParticipants = useSelector(getKnockingParticipants);
|
||||
|
||||
return (
|
||||
|
@ -76,7 +72,7 @@ const ParticipantsPane = () => {
|
|||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{
|
||||
autoAssign && <AutoAssignButton />
|
||||
showAutoAssign && <AutoAssignButton />
|
||||
}
|
||||
{
|
||||
inBreakoutRoom && <LeaveBreakoutRoomButton />
|
||||
|
@ -89,23 +85,31 @@ const ParticipantsPane = () => {
|
|||
searchString = { searchString } />))
|
||||
}
|
||||
{
|
||||
addBreakoutRoom && <AddBreakoutRoomButton />
|
||||
showAddBreakoutRoom && <AddBreakoutRoomButton />
|
||||
}
|
||||
{
|
||||
isLocalModerator
|
||||
showFooterMenu
|
||||
&& <View style = { styles.participantsPaneFooter }>
|
||||
<Button
|
||||
children = { t('participantsPane.actions.muteAll') }
|
||||
labelStyle = { styles.muteAllLabel }
|
||||
mode = 'contained'
|
||||
onPress = { muteAll }
|
||||
style = { styles.muteAllMoreButton } />
|
||||
<Button
|
||||
icon = { HorizontalDotsIcon }
|
||||
labelStyle = { styles.moreIcon }
|
||||
mode = 'contained'
|
||||
onPress = { openMoreMenu }
|
||||
style = { styles.moreButton } />
|
||||
{
|
||||
showMuteAll && (
|
||||
<Button
|
||||
children = { t('participantsPane.actions.muteAll') }
|
||||
labelStyle = { styles.muteAllLabel }
|
||||
mode = 'contained'
|
||||
onPress = { muteAll }
|
||||
style = { styles.muteAllMoreButton } />
|
||||
)
|
||||
}
|
||||
{
|
||||
showMoreActions && (
|
||||
<Button
|
||||
icon = { HorizontalDotsIcon }
|
||||
labelStyle = { styles.moreIcon }
|
||||
mode = 'contained'
|
||||
onPress = { openMoreMenu }
|
||||
style = { styles.moreButton } />
|
||||
)
|
||||
}
|
||||
</View>
|
||||
}
|
||||
</JitsiScreen>
|
||||
|
|
|
@ -249,6 +249,7 @@ export default {
|
|||
bottom: 0,
|
||||
flexDirection: 'row',
|
||||
height: BaseTheme.spacing[12],
|
||||
justifyContent: 'flex-end',
|
||||
left: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
|
@ -277,7 +278,8 @@ export default {
|
|||
},
|
||||
|
||||
moreButton: {
|
||||
...smallButton
|
||||
...smallButton,
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
moreIcon: {
|
||||
|
@ -291,8 +293,7 @@ export default {
|
|||
},
|
||||
|
||||
muteAllMoreButton: {
|
||||
...muteAllButton,
|
||||
right: BaseTheme.spacing[3]
|
||||
...muteAllButton
|
||||
},
|
||||
|
||||
muteAllLabel: {
|
||||
|
|
|
@ -7,12 +7,17 @@ import participantsPaneTheme from '../../../base/components/themes/participantsP
|
|||
import { openDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { getBreakoutRoomsConfig } from '../../../breakout-rooms/functions';
|
||||
import { isAddBreakoutRoomButtonVisible } from '../../../breakout-rooms/functions';
|
||||
import { MuteEveryoneDialog } from '../../../video-menu/components/';
|
||||
import { close } from '../../actions';
|
||||
import { findAncestorByClass, getParticipantsPaneOpen } from '../../functions';
|
||||
import {
|
||||
findAncestorByClass,
|
||||
getParticipantsPaneOpen,
|
||||
isFooterMenuVisible,
|
||||
isMoreActionsVisible,
|
||||
isMuteAllVisible
|
||||
} from '../../functions';
|
||||
import { AddBreakoutRoomButton } from '../breakout-rooms/components/web/AddBreakoutRoomButton';
|
||||
import { RoomList } from '../breakout-rooms/components/web/RoomList';
|
||||
|
||||
|
@ -36,15 +41,25 @@ type Props = {
|
|||
*/
|
||||
_overflowDrawer: boolean,
|
||||
|
||||
/**
|
||||
* Is the participants pane open.
|
||||
*/
|
||||
_paneOpen: boolean,
|
||||
|
||||
/**
|
||||
* Should the add breakout room button be displayed?
|
||||
*/
|
||||
_showAddRoomButton: boolean,
|
||||
|
||||
/**
|
||||
* Is the participants pane open.
|
||||
* Whether to show the more actions button.
|
||||
*/
|
||||
_paneOpen: boolean,
|
||||
_showMoreActionsButton: boolean,
|
||||
|
||||
/**
|
||||
* Whether to show the mute all button.
|
||||
*/
|
||||
_showMuteAllButton: boolean,
|
||||
|
||||
/**
|
||||
* Whether to show the footer menu.
|
||||
|
@ -202,6 +217,8 @@ class ParticipantsPane extends Component<Props, State> {
|
|||
_paneOpen,
|
||||
_showAddRoomButton,
|
||||
_showFooter,
|
||||
_showMoreActionsButton,
|
||||
_showMuteAllButton,
|
||||
classes,
|
||||
t
|
||||
} = this.props;
|
||||
|
@ -240,24 +257,28 @@ class ParticipantsPane extends Component<Props, State> {
|
|||
</div>
|
||||
{_showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
<FooterButton
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
onClick = { this._onMuteAll }>
|
||||
{t('participantsPane.actions.muteAll')}
|
||||
</FooterButton>
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
{_showMuteAllButton && (
|
||||
<FooterButton
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
id = 'participants-pane-context-menu'
|
||||
isIconButton = { true }
|
||||
onClick = { this._onToggleContext }>
|
||||
<Icon src = { IconHorizontalPoints } />
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
onClick = { this._onMuteAll }>
|
||||
{t('participantsPane.actions.muteAll')}
|
||||
</FooterButton>
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { this._onDrawerClose }
|
||||
onMouseLeave = { this._onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
{_showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<FooterButton
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
id = 'participants-pane-context-menu'
|
||||
isIconButton = { true }
|
||||
onClick = { this._onToggleContext }>
|
||||
<Icon src = { IconHorizontalPoints } />
|
||||
</FooterButton>
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { this._onDrawerClose }
|
||||
onMouseLeave = { this._onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -374,18 +395,16 @@ class ParticipantsPane extends Component<Props, State> {
|
|||
*/
|
||||
function _mapStateToProps(state: Object) {
|
||||
const isPaneOpen = getParticipantsPaneOpen(state);
|
||||
const { hideAddRoomButton } = getBreakoutRoomsConfig(state);
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
// $FlowExpectedError
|
||||
const _isBreakoutRoomsSupported = conference?.getBreakoutRooms()?.isSupported();
|
||||
const _isLocalParticipantModerator = isLocalParticipantModerator(state);
|
||||
|
||||
return {
|
||||
_isBreakoutRoomsSupported,
|
||||
_paneOpen: isPaneOpen,
|
||||
_showAddRoomButton: _isBreakoutRoomsSupported && !hideAddRoomButton && _isLocalParticipantModerator,
|
||||
_showFooter: isPaneOpen && isLocalParticipantModerator(state)
|
||||
_showAddRoomButton: isAddBreakoutRoomButtonVisible(state),
|
||||
_showFooter: isFooterMenuVisible(state),
|
||||
_showMuteAllButton: isMuteAllVisible(state),
|
||||
_showMoreActionsButton: isMoreActionsVisible(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '../base/participants/functions';
|
||||
import { toState } from '../base/redux';
|
||||
import { normalizeAccents } from '../base/util/strings';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { getBreakoutRoomsConfig, isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import { QUICK_ACTION_BUTTON, REDUCER_KEY, MEDIA_STATE } from './constants';
|
||||
|
||||
|
@ -261,3 +261,49 @@ export function participantMatchesSearch(participant: Object, searchString: stri
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the participants pane footer menu is visible.
|
||||
*
|
||||
* @param {Object} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isFooterMenuVisible = (state: Object) => {
|
||||
const isLocalModerator = isLocalParticipantModerator(state);
|
||||
const inBreakoutRoom = isInBreakoutRoom(state);
|
||||
const { hideFooterMenu } = getBreakoutRoomsConfig(state);
|
||||
|
||||
return inBreakoutRoom
|
||||
? !hideFooterMenu && isLocalModerator
|
||||
: isLocalModerator;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the more actions button is visible.
|
||||
*
|
||||
* @param {Object} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isMoreActionsVisible = (state: Object) => {
|
||||
const inBreakoutRoom = isInBreakoutRoom(state);
|
||||
const { hideMoreActionsButton } = getBreakoutRoomsConfig(state);
|
||||
|
||||
return inBreakoutRoom
|
||||
? !hideMoreActionsButton
|
||||
: true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the mute all button is visible.
|
||||
*
|
||||
* @param {Object} state - Global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isMuteAllVisible = (state: Object) => {
|
||||
const inBreakoutRoom = isInBreakoutRoom(state);
|
||||
const { hideMuteAllButton } = getBreakoutRoomsConfig(state);
|
||||
|
||||
return inBreakoutRoom
|
||||
? !hideMuteAllButton
|
||||
: true;
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { getDialOutStatusUrl, getDialOutUrl, updateConfig } from '../base/config
|
|||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
|
||||
import { isVideoMutedByUser, MEDIA_TYPE } from '../base/media';
|
||||
import { updateSettings } from '../base/settings';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalAudioTrack,
|
||||
|
@ -359,7 +360,11 @@ export function replaceAudioTrackById(deviceId: string) {
|
|||
const newTrack = await createLocalTrack('audio', deviceId);
|
||||
const oldTrack = getLocalAudioTrack(tracks)?.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack)).then(() => {
|
||||
dispatch(updateSettings({
|
||||
micDeviceId: newTrack.getDeviceId()
|
||||
}));
|
||||
});
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.audioTrackError'));
|
||||
logger.log('Error replacing audio track', err);
|
||||
|
@ -386,7 +391,11 @@ export function replaceVideoTrackById(deviceId: Object) {
|
|||
);
|
||||
const oldTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack));
|
||||
dispatch(replaceLocalTrack(oldTrack, newTrack)).then(() => {
|
||||
dispatch(updateSettings({
|
||||
cameraDeviceId: newTrack.getDeviceId()
|
||||
}));
|
||||
});
|
||||
wasVideoMuted && newTrack.mute();
|
||||
} catch (err) {
|
||||
dispatch(setDeviceStatusWarning('prejoin.videoTrackError'));
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -43,10 +43,15 @@ export function openLogoutDialog(onLogout: Function) {
|
|||
*
|
||||
* @param {string} defaultTab - The tab in {@code SettingsDialog} that should be
|
||||
* displayed initially.
|
||||
* @param {boolean} isDisplayedOnWelcomePage - Indicates whether the device selection dialog is displayed on the
|
||||
* welcome page or not.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openSettingsDialog(defaultTab: string) {
|
||||
return openDialog(SettingsDialog, { defaultTab });
|
||||
export function openSettingsDialog(defaultTab: string, isDisplayedOnWelcomePage: boolean) {
|
||||
return openDialog(SettingsDialog, {
|
||||
defaultTab,
|
||||
isDisplayedOnWelcomePage
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -531,7 +531,11 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderMaxStageParticipantsSelect() {
|
||||
const { maxStageParticipants, t } = this.props;
|
||||
const { maxStageParticipants, t, stageFilmstripEnabled } = this.props;
|
||||
|
||||
if (!stageFilmstripEnabled) {
|
||||
return null;
|
||||
}
|
||||
const maxParticipantsItems = Array(MAX_ACTIVE_PARTICIPANTS).fill(0)
|
||||
.map((no, index) => (
|
||||
<DropdownItem
|
||||
|
|
|
@ -21,7 +21,13 @@ type Props = AbstractButtonProps & {
|
|||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Indicates whether the device selection dialog is displayed on the
|
||||
* welcome page or not.
|
||||
*/
|
||||
isDisplayedOnWelcomePage: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -40,10 +46,10 @@ class SettingsButton extends AbstractButton<Props, *> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
const { defaultTab = SETTINGS_TABS.DEVICES, dispatch } = this.props;
|
||||
const { defaultTab = SETTINGS_TABS.DEVICES, dispatch, isDisplayedOnWelcomePage = false } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent('settings'));
|
||||
dispatch(openSettingsDialog(defaultTab));
|
||||
dispatch(openSettingsDialog(defaultTab, isDisplayedOnWelcomePage));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,13 @@ type Props = {
|
|||
/**
|
||||
* Invoked to save changed settings.
|
||||
*/
|
||||
dispatch: Function
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Indicates whether the device selection dialog is displayed on the
|
||||
* welcome page or not.
|
||||
*/
|
||||
isDisplayedOnWelcomePage: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -253,7 +259,7 @@ class SettingsDialog extends Component<Props> {
|
|||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const { classes } = ownProps;
|
||||
const { classes, isDisplayedOnWelcomePage } = ownProps;
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
|
||||
// The settings sections to display.
|
||||
|
@ -276,7 +282,7 @@ function _mapStateToProps(state, ownProps) {
|
|||
component: DeviceSelection,
|
||||
label: 'settings.devices',
|
||||
onMount: getAvailableDevices,
|
||||
props: getDeviceSelectionDialogProps(state),
|
||||
props: getDeviceSelectionDialogProps(state, isDisplayedOnWelcomePage),
|
||||
propsUpdateFunction: (tabState, newProps) => {
|
||||
// Ensure the device selection tab gets updated when new devices
|
||||
// are found by taking the new props and only preserving the
|
||||
|
@ -292,7 +298,7 @@ function _mapStateToProps(state, ownProps) {
|
|||
};
|
||||
},
|
||||
styles: `settings-pane ${classes.settingsDialog} devices-pane`,
|
||||
submit: submitDeviceSelectionTab
|
||||
submit: newState => submitDeviceSelectionTab(newState, isDisplayedOnWelcomePage)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import {
|
|||
import { toState } from '../base/redux';
|
||||
import { getHideSelfView } from '../base/settings';
|
||||
import { parseStandardURIString } from '../base/util';
|
||||
import { getBreakoutRoomsConfig, isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { isStageFilmstripEnabled } from '../filmstrip/functions';
|
||||
import { isFollowMeActive } from '../follow-me';
|
||||
import { isReactionsEnabled } from '../reactions/functions.any';
|
||||
|
||||
|
@ -115,6 +117,7 @@ export function getMoreTabProps(stateful: Object | Function) {
|
|||
const language = i18next.language || DEFAULT_LANGUAGE;
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
const enabledNotifications = getNotificationsMap(stateful);
|
||||
const stageFilmstripEnabled = isStageFilmstripEnabled(state);
|
||||
|
||||
// when self view is controlled by the config we hide the settings
|
||||
const { disableSelfView, disableSelfViewSettings } = state['features/base/config'];
|
||||
|
@ -131,7 +134,8 @@ export function getMoreTabProps(stateful: Object | Function) {
|
|||
showNotificationsSettings: Object.keys(enabledNotifications).length > 0,
|
||||
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
|
||||
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled,
|
||||
maxStageParticipants: state['features/filmstrip'].maxStageParticipants
|
||||
maxStageParticipants: state['features/filmstrip'].maxStageParticipants,
|
||||
stageFilmstripEnabled
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -177,10 +181,16 @@ export function getModeratorTabProps(stateful: Object | Function) {
|
|||
*/
|
||||
export function shouldShowModeratorSettings(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
|
||||
return Boolean(
|
||||
const inBreakoutRoom = isInBreakoutRoom(state);
|
||||
const { hideModeratorSettingsTab } = getBreakoutRoomsConfig(state);
|
||||
const hasModeratorRights = Boolean(
|
||||
isSettingEnabled('moderator')
|
||||
&& isLocalParticipantModerator(state));
|
||||
&& isLocalParticipantModerator(state)
|
||||
);
|
||||
|
||||
return inBreakoutRoom
|
||||
? hasModeratorRights && !hideModeratorSettingsTab
|
||||
: hasModeratorRights;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,14 +52,13 @@ export async function createVirtualBackgroundEffect(virtualBackground: Object, d
|
|||
tflite = await timeout(tfliteTimeout, createTFLiteModule());
|
||||
}
|
||||
} catch (err) {
|
||||
isWasmDisabled = true;
|
||||
|
||||
if (err?.message === '408') {
|
||||
logger.error('Failed to download tflite model!');
|
||||
dispatch(showWarningNotification({
|
||||
titleKey: 'virtualBackground.backgroundEffectError'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
} else {
|
||||
isWasmDisabled = true;
|
||||
logger.error('Looks like WebAssembly is disabled or not supported on this browser', err);
|
||||
dispatch(showWarningNotification({
|
||||
titleKey: 'virtualBackground.webAssemblyWarning',
|
||||
|
|
|
@ -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);
|
|
@ -89,6 +89,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';
|
||||
|
@ -96,6 +97,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';
|
||||
|
||||
/**
|
||||
|
@ -786,6 +788,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,
|
||||
|
@ -853,6 +867,8 @@ class Toolbox extends Component<Props> {
|
|||
shareAudio,
|
||||
etherpad,
|
||||
virtualBackground,
|
||||
dockIframe,
|
||||
undockIframe,
|
||||
speakerStats,
|
||||
settings,
|
||||
shortcuts,
|
||||
|
|
|
@ -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);
|
|
@ -191,7 +191,8 @@ class WelcomePage extends AbstractWelcomePage {
|
|||
<div className = 'header'>
|
||||
<div className = 'welcome-page-settings'>
|
||||
<SettingsButton
|
||||
defaultTab = { SETTINGS_TABS.CALENDAR } />
|
||||
defaultTab = { SETTINGS_TABS.CALENDAR }
|
||||
isDisplayedOnWelcomePage = { true } />
|
||||
{ showAdditionalToolbarContent
|
||||
? <div
|
||||
className = 'settings-toolbar-content'
|
||||
|
|
Loading…
Reference in New Issue