Compare commits

...

17 Commits

Author SHA1 Message Date
Tudor-Ovidiu Avram
b51d88fb58 fix(subject) remove gradient if no info in topbar 2021-01-15 12:29:36 -06:00
Tudor-Ovidiu Avram
d816e79011 fix(jaas) disable directory integration 2021-01-14 08:40:16 -06:00
hmuresan
ec8a4be86a feat(external-api) set privateMessage flag on incoming-message 2021-01-14 08:40:16 -06:00
Hristo Terezov
51aa683d17 Adds manually missing lines from PR #8207 2021-01-13 19:45:01 -06:00
Mihai-Andrei Uscat
af0f6d4812 fix(password): Fix add password button on Safari 2021-01-12 15:08:08 -06:00
Vlad Piersec
4ab8d6dc4e fix(branding): Use config url for dynamic branding 2021-01-12 15:06:50 -06:00
hmuresan
c10f294d06 feat(external_api) add command and event listener for CS 2021-01-12 15:05:56 -06:00
Tudor-Ovidiu Avram
9497062703 feat(external_api) allow initial gUM requests to be disabled 2021-01-12 15:05:31 -06:00
Tudor-Ovidiu Avram
39a16ccaf0 feat(external_api) allow notifications to be configured 2021-01-12 15:04:49 -06:00
hmuresan
cb9ec2b3aa feat(external_api) set and cancel private chat through external API
- allow managing chat through API when chat button is not present on UI
2021-01-12 15:04:26 -06:00
Tudor-Ovidiu Avram
165a485068 feat(jaas) add config for displaying participants stats and conference subject 2021-01-12 15:03:33 -06:00
Tudor-Ovidiu Avram
adbecbcb0a fix(filmstrip) fix button not considering interface config settings 2021-01-12 15:01:01 -06:00
Tudor-Ovidiu Avram
edde5256e4 feat(jaas) allow tile view to be disabled 2021-01-12 15:00:30 -06:00
Tudor-Ovidiu Avram
9f9fae10d1 fix(jaas) update recording label and hide option for jaas users 2021-01-12 14:58:22 -06:00
Jaya Allamsetty
ffae6917ae fix: Fixes VP9 support on Chrome.
chore(deps) lib-jitsi-meet@latest
2020-12-15 15:45:01 -06:00
Tudor-Ovidiu Avram
4227aa3eeb Revert "fix(external_api) replace special chars in roomName before constructing URL"
This reverts commit 6f90458ff1.
2020-12-15 10:59:14 -06:00
Hristo Terezov
c3bd9fbd6a feat(analytics): Add tenant. 2020-12-15 10:10:24 -06:00
30 changed files with 332 additions and 89 deletions

View File

@@ -473,8 +473,8 @@ export default {
*/
createInitialLocalTracks(options = {}) {
const errors = {};
const initialDevices = [ 'audio' ];
const requestedAudio = true;
const initialDevices = config.disableInitialGUM ? [] : [ 'audio' ];
const requestedAudio = !config.disableInitialGUM;
let requestedVideo = false;
// Always get a handle on the audio input device so that we have statistics even if the user joins the
@@ -485,19 +485,22 @@ export default {
this.muteAudio(true, true);
}
if (!options.startWithVideoMuted
if (!config.disableInitialGUM
&& !options.startWithVideoMuted
&& !options.startAudioOnly
&& !options.startScreenSharing) {
initialDevices.push('video');
requestedVideo = true;
}
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
if (!config.disableInitialGUM) {
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.PERMISSION_PROMPT_IS_SHOWN,
browserName =>
APP.store.dispatch(
mediaPermissionPromptVisibilityChanged(true, browserName))
);
}
let tryCreateLocalTracks;

View File

@@ -351,6 +351,10 @@ var config = {
// will be joined when no room is specified.
enableWelcomePage: true,
// Disable initial browser getUserMedia requests.
// This is useful for scenarios where users might want to start a conference for screensharing only
// disableInitialGUM: false,
// Enabling the close page will ignore the welcome page redirection when
// a call is hangup.
// enableClosePage: false,
@@ -621,13 +625,25 @@ var config = {
logoImageUrl: 'https://example.com/logo-img.png'
}
*/
// brandingDataUrl: '',
// dynamicBrandingUrl: '',
// The URL of the moderated rooms microservice, if available. If it
// is present, a link to the service will be rendered on the welcome page,
// otherwise the app doesn't render it.
// moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com',
// Hides the conference subject
// hideConferenceSubject: true
// Hides the conference timer.
// hideConferenceTimer: true,
// Hides the participants stats
// hideParticipantsStats: true
// Sets the conference subject
// subject: 'Conference Subject',
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -681,6 +697,65 @@ var config = {
ignoreStartMuted
*/
/**
Use this array to configure which notifications will be shown to the user
The items correspond to the title or description key of that notification
Some of these notifications also depend on some other internal logic to be displayed or not,
so adding them here will not ensure they will always be displayed
A falsy value for this prop will result in having all notifications enabled (e.g null, undefined, false)
*/
// notifications: [
// 'connection.CONNFAIL', // shown when the connection fails,
// 'dialog.cameraNotSendingData', // shown when there's no feed from user's camera
// 'dialog.kickTitle', // shown when user has been kicked
// 'dialog.liveStreaming', // livestreaming notifications (pending, on, off, limits)
// 'dialog.lockTitle', // shown when setting conference password fails
// 'dialog.maxUsersLimitReached', // shown when maximmum users limit has been reached
// 'dialog.micNotSendingData', // shown when user's mic is not sending any audio
// 'dialog.passwordNotSupportedTitle', // shown when setting conference password fails due to password format
// 'dialog.recording', // recording notifications (pending, on, off, limits)
// 'dialog.remoteControlTitle', // remote control notifications (allowed, denied, start, stop, error)
// 'dialog.reservationError',
// 'dialog.serviceUnavailable', // shown when server is not reachable
// 'dialog.sessTerminated', // shown when there is a failed conference session
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
// 'dialog.transcribing', // transcribing notifications (pending, off)
// 'dialOut.statusMessage', // shown when dial out status is updated.
// 'liveStreaming.busy', // shown when livestreaming service is busy
// 'liveStreaming.failedToStart', // shown when livestreaming fails to start
// 'liveStreaming.unavailableTitle', // shown when livestreaming service is not reachable
// 'lobby.joinRejectedMessage', // shown when while in a lobby, user's request to join is rejected
// 'lobby.notificationTitle', // shown when lobby is toggled and when join requests are allowed / denied
// 'localRecording.localRecording', // shown when a local recording is started
// 'notify.disconnected', // shown when a participant has left
// 'notify.grantedTo', // shown when moderator rights were granted to a participant
// 'notify.invitedOneMember', // shown when 1 participant has been invited
// 'notify.invitedThreePlusMembers', // shown when 3+ participants have been invited
// 'notify.invitedTwoMembers', // shown when 2 participants have been invited
// 'notify.kickParticipant', // shown when a participant is kicked
// 'notify.mutedRemotelyTitle', // shown when user is muted by a remote party
// 'notify.mutedTitle', // shown when user has been muted upon joining,
// 'notify.newDeviceAudioTitle', // prompts the user to use a newly detected audio device
// 'notify.newDeviceCameraTitle', // prompts the user to use a newly detected camera
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.startSilentTitle', // shown when user joined with no audio
// 'prejoin.errorDialOut',
// 'prejoin.errorDialOutDisconnected',
// 'prejoin.errorDialOutFailed',
// 'prejoin.errorDialOutStatus',
// 'prejoin.errorStatusCode',
// 'prejoin.errorValidation',
// 'recording.busy', // shown when recording service is busy
// 'recording.failedToStart', // shown when recording fails to start
// 'recording.unavailableTitle', // shown when recording service is not reachable
// 'toolbar.noAudioSignalTitle', // shown when a broken mic is detected
// 'toolbar.noisyAudioInputTitle', // shown when noise is detected for the current microphone
// 'toolbar.talkWhileMutedPopup', // shown when user tries to speak while muted
// 'transcribing.failedToStart' // shown when transcribing fails to start
// ]
// Allow all above example options to include a trailing comma and
// prevent fear when commenting out the last value.

View File

@@ -14,12 +14,15 @@
text-overflow: ellipsis;
box-sizing: border-box;
white-space: nowrap;
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
&.visible {
top: 0px;
}
&.gradient {
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
}
&-text {
vertical-align: middle;
}

View File

@@ -601,6 +601,7 @@
"pending": "Preparing to record the meeting...",
"rec": "REC",
"serviceDescription": "Your recording will be saved by the recording service",
"serviceDescriptionCloud": "Cloud recording",
"serviceName": "Recording service",
"signIn": "Sign in",
"signOut": "Sign out",

View File

@@ -14,7 +14,8 @@ import {
} from '../../react/features/base/conference';
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
import { pinParticipant } from '../../react/features/base/participants';
import { pinParticipant, getParticipantById } from '../../react/features/base/participants';
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
@@ -330,6 +331,24 @@ function initCommands() {
} else {
logger.error('No recording or streaming session found');
}
},
'initiate-private-chat': participantId => {
const state = APP.store.getState();
const participant = getParticipantById(state, participantId);
if (participant) {
const { isOpen: isChatOpen } = state['features/chat'];
if (!isChatOpen) {
APP.UI.toggleChat();
}
APP.store.dispatch(setPrivateMessageRecipient(participant));
} else {
logger.error('No participant found for the given participantId');
}
},
'cancel-private-chat': () => {
APP.store.dispatch(setPrivateMessageRecipient());
}
};
transport.on('event', ({ data, name }) => {
@@ -413,6 +432,15 @@ function initCommands() {
case 'is-sharing-screen':
callback(Boolean(APP.conference.isSharingScreen));
break;
case 'get-content-sharing-participants': {
const tracks = getState()['features/base/tracks'];
const sharingParticipantIds = tracks.filter(tr => tr.videoType === 'desktop').map(t => t.participantId);
callback({
sharingParticipantIds
});
break;
}
default:
return false;
}
@@ -564,8 +592,8 @@ class API {
* @returns {void}
*/
notifyReceivedChatMessage(
{ body, id, nick, ts }: {
body: *, id: string, nick: string, ts: *
{ body, id, nick, privateMessage, ts }: {
body: *, id: string, nick: string, privateMessage: boolean, ts: *
} = {}) {
if (APP.conference.isLocalId(id)) {
return;
@@ -576,6 +604,7 @@ class API {
from: id,
message: body,
nick,
privateMessage,
stamp: ts
});
}
@@ -656,6 +685,19 @@ class API {
});
}
/**
* Notify external application (if API is enabled) that the list of sharing participants changed.
*
* @param {Object} data - The event data.
* @returns {void}
*/
notifySharingParticipantsChanged(data: Object) {
this._sendEvent({
name: 'content-sharing-participants-changed',
data
});
}
/**
* Notify external application (if API is enabled) that the device list has
* changed.

View File

@@ -28,11 +28,13 @@ const ALWAYS_ON_TOP_FILENAMES = [
*/
const commands = {
avatarUrl: 'avatar-url',
cancelPrivateChat: 'cancel-private-chat',
displayName: 'display-name',
e2eeKey: 'e2ee-key',
email: 'email',
toggleLobby: 'toggle-lobby',
hangup: 'video-hangup',
intiatePrivateChat: 'initiate-private-chat',
muteEveryone: 'mute-everyone',
password: 'password',
pinParticipant: 'pin-participant',
@@ -62,6 +64,7 @@ const events = {
'audio-availability-changed': 'audioAvailabilityChanged',
'audio-mute-status-changed': 'audioMuteStatusChanged',
'camera-error': 'cameraError',
'content-sharing-participants-changed': 'contentSharingParticipantsChanged',
'device-list-changed': 'deviceListChanged',
'display-name-change': 'displayNameChange',
'email-change': 'emailChange',
@@ -722,6 +725,17 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
return getAvailableDevices(this._transport);
}
/**
* Gets a list of the currently sharing participant id's.
*
* @returns {Promise} - Resolves with the list of participant id's currently sharing.
*/
getContentSharingParticipants() {
return this._transport.sendRequest({
name: 'get-content-sharing-participants'
});
}
/**
* Returns Promise that resolves with current selected devices.
*

4
package-lock.json generated
View File

@@ -10776,8 +10776,8 @@
}
},
"lib-jitsi-meet": {
"version": "github:jitsi/lib-jitsi-meet#33307629947bf32055f2cbc1798add0871c9ec1e",
"from": "github:jitsi/lib-jitsi-meet#33307629947bf32055f2cbc1798add0871c9ec1e",
"version": "github:jitsi/lib-jitsi-meet#10e2fe3fec506d9d4d3a828fb7a2d7a92c0a9082",
"from": "github:jitsi/lib-jitsi-meet#10e2fe3fec506d9d4d3a828fb7a2d7a92c0a9082",
"requires": {
"@jitsi/js-utils": "1.0.2",
"@jitsi/sdp-interop": "1.0.3",

View File

@@ -56,7 +56,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#33307629947bf32055f2cbc1798add0871c9ec1e",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#10e2fe3fec506d9d4d3a828fb7a2d7a92c0a9082",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.19",
"moment": "2.19.4",

View File

@@ -11,7 +11,7 @@ import JitsiMeetJS, {
browser,
isAnalyticsEnabled
} from '../base/lib-jitsi-meet';
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
import { getJitsiMeetGlobalNS, loadScript, parseURIString } from '../base/util';
import { AmplitudeHandler, MatomoHandler } from './handlers';
import logger from './logger';
@@ -166,6 +166,8 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
} = config;
const { group, server } = state['features/base/jwt'];
const roomName = state['features/base/conference'].room;
const { locationURL = {} } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const permanentProperties = {};
if (server) {
@@ -187,6 +189,9 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
// Report if we are loaded in iframe
permanentProperties.inIframe = _inIframe();
// Report the tenant from the URL.
permanentProperties.tenant = tenant || '/';
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
if (deploymentInfo) {

View File

@@ -80,6 +80,7 @@ export default [
'disableAP',
'disableAudioLevels',
'disableDeepLinking',
'disableInitialGUM',
'disableH264',
'disableHPF',
'disableInviteFunctions',
@@ -91,6 +92,7 @@ export default [
'disableRtx',
'disableSimulcast',
'disableThirdPartyRequests',
'disableTileView',
'displayJids',
'doNotStoreRoom',
'e2eping',
@@ -116,6 +118,9 @@ export default [
'forceJVB121Ratio',
'gatherStats',
'googleApiApplicationClientID',
'hideConferenceSubject',
'hideParticipantsStats',
'hideConferenceTimer',
'hiddenDomain',
'hideLobbyButton',
'hosts',
@@ -126,6 +131,7 @@ export default [
'liveStreamingEnabled',
'localRecording',
'maxFullResolutionParticipants',
'notifications',
'openBridgeChannel',
'opusMaxAverageBitrate',
'p2p',

View File

@@ -35,6 +35,8 @@ import {
setTrackMuted
} from './functions';
import './subscriber';
declare var APP: Object;
/**

View File

@@ -0,0 +1,24 @@
// @flow
import _ from 'lodash';
import { StateListenerRegistry } from '../../base/redux';
declare var APP: Object;
/**
* Notifies when the list of currently sharing participants changes.
*/
StateListenerRegistry.register(
/* selector */ state =>
state['features/base/tracks'].filter(tr => tr.videoType === 'desktop').map(t => t.participantId),
/* listener */ (participantIDs, store, previousParticipantIDs) => {
if (typeof APP !== 'object') {
return;
}
if (!_.isEqual(_.sortBy(participantIDs), _.sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}
);

View File

@@ -363,6 +363,11 @@ export function parseURIString(uri: ?string) {
}
obj.room = room;
if (contextRootEndIndex > 1) {
// The part of the pathname from the beginning to the room name is the tenant.
obj.tenant = pathname.substring(1, contextRootEndIndex);
}
return obj;
}
@@ -514,7 +519,7 @@ export function urlObjectToString(o: Object): ?string {
// pathname
// Web's ExternalAPI roomName
const room = _fixRoom(o.roomName || o.room);
const room = o.roomName || o.room;
if (room
&& (url.pathname.endsWith('/')

View File

@@ -19,7 +19,6 @@ import {
import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { showToolbox } from '../toolbox/actions';
import { isButtonEnabled } from '../toolbox/functions';
import { SEND_MESSAGE, SET_PRIVATE_MESSAGE_RECIPIENT } from './actionTypes';
import { addMessage, clearMessages, toggleChat } from './actions';
@@ -152,10 +151,9 @@ StateListenerRegistry.register(
* @returns {void}
*/
function _addChatMsgListener(conference, store) {
if ((typeof APP !== 'undefined' && !isButtonEnabled('chat'))
|| store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if the chat button is not enabled in interfaceConfig
// or we are in iAmRecorder mode
if (store.getState()['features/base/config'].iAmRecorder) {
// We don't register anything on web if we are in iAmRecorder mode
return;
}
@@ -249,6 +247,7 @@ function _handleReceivedMessage({ dispatch, getState }, { id, message, privateMe
body: message,
id,
nick: displayName,
privateMessage,
ts: timestamp
});

View File

@@ -95,10 +95,14 @@ class NavigationBar extends Component<Props> {
* @returns {Props}
*/
function _mapStateToProps(state) {
const { hideConferenceTimer, hideConferenceSubject } = state['features/base/config'];
return {
_conferenceTimerEnabled: getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true),
_conferenceTimerEnabled:
getFeatureFlag(state, CONFERENCE_TIMER_ENABLED, true) && !hideConferenceTimer,
_meetingName: getConferenceName(state),
_meetingNameEnabled: getFeatureFlag(state, MEETING_NAME_ENABLED, true),
_meetingNameEnabled:
getFeatureFlag(state, MEETING_NAME_ENABLED, true) && !hideConferenceSubject,
_visible: isToolboxVisible(state)
};
}

View File

@@ -15,11 +15,21 @@ import ParticipantsCount from './ParticipantsCount';
*/
type Props = {
/**
* Whether the conference timer should be shown or not.
*/
_hideConferenceTimer: Boolean,
/**
* Whether then participant count should be shown or not.
*/
_showParticipantCount: boolean,
/**
* Whether the conference subject should be shown or not.
*/
_showSubject: boolean,
/**
* The subject or the of the conference.
* Falls back to conference name.
@@ -46,13 +56,18 @@ class Subject extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _showParticipantCount, _subject, _visible } = this.props;
const { _hideConferenceTimer, _showParticipantCount, _showSubject, _subject, _visible } = this.props;
let className = `subject ${_visible ? 'visible' : ''}`;
if (!_hideConferenceTimer || _showParticipantCount || _showSubject) {
className += ' gradient';
}
return (
<div className = { `subject ${_visible ? 'visible' : ''}` }>
<span className = 'subject-text'>{ _subject }</span>
<div className = { className }>
{ _showSubject && <span className = 'subject-text'>{ _subject }</span>}
{ _showParticipantCount && <ParticipantsCount /> }
<ConferenceTimer />
{ !_hideConferenceTimer && <ConferenceTimer /> }
</div>
);
}
@@ -65,15 +80,21 @@ class Subject extends Component<Props> {
* @param {Object} state - The Redux state.
* @private
* @returns {{
* _hideConferenceTimer: boolean,
* _showParticipantCount: boolean,
* _showSubject: boolean,
* _subject: string,
* _visible: boolean
* }}
*/
function _mapStateToProps(state) {
const participantCount = getParticipantCount(state);
const { hideConferenceTimer, hideConferenceSubject, hideParticipantsStats } = state['features/base/config'];
return {
_showParticipantCount: participantCount > 2,
_hideConferenceTimer: Boolean(hideConferenceTimer),
_showParticipantCount: participantCount > 2 && !hideParticipantsStats,
_showSubject: !hideConferenceSubject,
_subject: getConferenceName(state),
_visible: isToolboxVisible(state) && participantCount > 1
};

View File

@@ -9,10 +9,11 @@ import {
SET_DYNAMIC_BRANDING_FAILED,
SET_DYNAMIC_BRANDING_READY
} from './actionTypes';
import { extractFqnFromPath } from './functions';
import { getDynamicBrandingUrl } from './functions';
const logger = getLogger(__filename);
/**
* Fetches custom branding data.
* If there is no data or the request fails, sets the `customizationReady` flag
@@ -23,15 +24,14 @@ const logger = getLogger(__filename);
export function fetchCustomBrandingData() {
return async function(dispatch: Function, getState: Function) {
const state = getState();
const baseUrl = state['features/base/config'].brandingDataUrl;
const { customizationReady } = state['features/dynamic-branding'];
if (!customizationReady) {
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
const url = getDynamicBrandingUrl(state);
if (baseUrl && fqn) {
if (url) {
try {
const res = await doGetJSON(`${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`);
const res = await doGetJSON(url);
return dispatch(setDynamicBrandingData(res));
} catch (err) {

View File

@@ -13,3 +13,24 @@ export function extractFqnFromPath(path: string) {
return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
}
/**
* Returns the url used for fetching dynamic branding.
*
* @param {Object} state - The state of the app.
* @returns {string}
*/
export function getDynamicBrandingUrl(state: Object) {
const { dynamicBrandingUrl } = state['features/base/config'];
if (dynamicBrandingUrl) {
return dynamicBrandingUrl;
}
const baseUrl = state['features/base/config'].brandingDataUrl;
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
if (baseUrl && fqn) {
return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;
}
}

View File

@@ -13,6 +13,7 @@ import { translate } from '../../../base/i18n';
import { Icon, IconMenuDown, IconMenuUp } from '../../../base/icons';
import { connect } from '../../../base/redux';
import { dockToolbox } from '../../../toolbox/actions.web';
import { isButtonEnabled } from '../../../toolbox/functions.web';
import { getCurrentLayout, LAYOUTS } from '../../../video-layout';
import { setFilmstripHovered, setFilmstripVisible } from '../../actions';
import { shouldRemoteVideosBeVisible } from '../../functions';
@@ -197,7 +198,7 @@ class Filmstrip extends Component <Props> {
let toolbar = null;
if (!this.props._hideToolbar) {
if (!this.props._hideToolbar && isButtonEnabled('filmstrip')) {
toolbar = this._renderToggleButton();
}

View File

@@ -4,6 +4,7 @@ import { i18next } from '../base/i18n';
import { isLocalParticipantModerator } from '../base/participants';
import { toState } from '../base/redux';
import { doGetJSON, parseURIString } from '../base/util';
import { isVpaasMeeting } from '../billing-counter/functions';
import logger from './logger';
@@ -352,7 +353,7 @@ export function invitePeopleAndChatRooms( // eslint-disable-line max-params
export function isAddPeopleEnabled(state: Object): boolean {
const { peopleSearchUrl } = state['features/base/config'];
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl);
return state['features/base/jwt'].jwt && Boolean(peopleSearchUrl) && !isVpaasMeeting(state);
}
/**

View File

@@ -46,14 +46,14 @@ MiddlewareRegistry.register(({ getState, dispatch }) => next => action => {
recordingController.onWarning = (messageKey, messageParams) => {
dispatch(showNotification({
title: i18next.t('localRecording.localRecording'),
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
};
recordingController.onNotify = (messageKey, messageParams) => {
dispatch(showNotification({
title: i18next.t('localRecording.localRecording'),
titleKey: 'localRecording.localRecording',
description: i18next.t(messageKey, messageParams)
}, 10000));
};

View File

@@ -108,20 +108,20 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
};
}
const notification = showNotification({
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noAudioSignalTitle',
description: <DialInLink />,
descriptionKey,
customActionNameKey,
customActionHandler
});
dispatch(notification);
}));
dispatch(playSound(NO_AUDIO_SIGNAL_SOUND_ID));
// Store the current notification uid so we can check for this state and hide it in case
// a new track was added, thus changing the context of the notification
dispatch(setNoAudioSignalNotificationUid(notification.uid));
if (notification) {
// Store the current notification uid so we can check for this state and hide it in case
// a new track was added, thus changing the context of the notification
dispatch(setNoAudioSignalNotificationUid(notification.uid));
}
});
}

View File

@@ -37,17 +37,18 @@ MiddlewareRegistry.register(store => next => action => {
}
});
conference.on(
JitsiConferenceEvents.NOISY_MIC, () => {
const notification = showNotification({
JitsiConferenceEvents.NOISY_MIC, async () => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.noisyAudioInputTitle',
descriptionKey: 'toolbar.noisyAudioInputDesc'
});
}));
dispatch(notification);
dispatch(playSound(NOISY_AUDIO_INPUT_SOUND_ID));
// we store the last notification id so we can hide it if the mic is muted
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
if (notification) {
// we store the last notification id so we can hide it if the mic is muted
dispatch(setNoisyAudioInputNotificationUid(notification.uid));
}
});
break;
}

View File

@@ -76,19 +76,23 @@ export function showErrorNotification(props: Object) {
* @param {Object} props - The props needed to show the notification component.
* @param {number} timeout - How long the notification should display before
* automatically being hidden.
* @returns {{
* type: SHOW_NOTIFICATION,
* props: Object,
* timeout: number,
* uid: number
* }}
* @returns {Function}
*/
export function showNotification(props: Object = {}, timeout: ?number) {
return {
type: SHOW_NOTIFICATION,
props,
timeout,
uid: window.Date.now()
return function(dispatch: Function, getState: Function) {
const { notifications } = getState()['features/base/config'];
const shouldDisplay = !notifications
|| notifications.includes(props.descriptionKey)
|| notifications.includes(props.titleKey);
if (shouldDisplay) {
return dispatch({
type: SHOW_NOTIFICATION,
props,
timeout,
uid: window.Date.now()
});
}
};
}

View File

@@ -75,7 +75,7 @@ export function setLiveStreamKey(streamKey: string) {
* @returns {Function}
*/
export function showPendingRecordingNotification(streamType: string) {
return (dispatch: Function) => {
return async (dispatch: Function) => {
const isLiveStreaming
= streamType === JitsiMeetJS.constants.recording.mode.STREAM;
const dialogProps = isLiveStreaming ? {
@@ -85,15 +85,14 @@ export function showPendingRecordingNotification(streamType: string) {
descriptionKey: 'recording.pending',
titleKey: 'dialog.recording'
};
const showNotificationAction = showNotification({
const notification = await dispatch(showNotification({
isDismissAllowed: false,
...dialogProps
});
}));
dispatch(showNotificationAction);
dispatch(_setPendingRecordingNotificationUid(
showNotificationAction.uid, streamType));
if (notification) {
dispatch(_setPendingRecordingNotificationUid(notification.uid, streamType));
}
};
}

View File

@@ -162,7 +162,9 @@ class StartRecordingDialogContent extends Component<Props> {
* @returns {React$Component}
*/
_renderFileSharingContent() {
if (!this.props.fileRecordingsServiceSharingEnabled) {
const { fileRecordingsServiceSharingEnabled, isVpaas } = this.props;
if (!fileRecordingsServiceSharingEnabled || isVpaas) {
return null;
}
@@ -247,6 +249,7 @@ class StartRecordingDialogContent extends Component<Props> {
) : null;
const icon = isVpaas ? ICON_SHARE : JITSI_LOGO;
const label = isVpaas ? t('recording.serviceDescriptionCloud') : t('recording.serviceDescription');
return (
<Container
@@ -265,7 +268,7 @@ class StartRecordingDialogContent extends Component<Props> {
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.serviceDescription') }
{ label }
</Text>
{ switchContent }
</Container>

View File

@@ -106,7 +106,11 @@ function PasswordSection({
*/
function onPasswordSave() {
if (formRef.current) {
formRef.current.querySelector('form').requestSubmit();
const { value } = formRef.current.querySelector('form > input');
if (value) {
onPasswordSubmit(value);
}
}
}

View File

@@ -40,20 +40,20 @@ MiddlewareRegistry.register(store => next => action => {
}
});
conference.on(
JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
const notification = showNotification({
JitsiConferenceEvents.TALK_WHILE_MUTED, async () => {
const notification = await dispatch(showNotification({
titleKey: 'toolbar.talkWhileMutedPopup',
customActionNameKey: 'notify.unmute',
customActionHandler: () => dispatch(setAudioMuted(false))
});
dispatch(notification);
}));
dispatch(playSound(TALK_WHILE_MUTED_SOUND_ID));
// we store the last start muted notification id that we showed,
// so we can hide it when unmuted mic is detected
dispatch(setCurrentNotificationUid(notification.uid));
if (notification) {
// we store the last start muted notification id that we showed,
// so we can hide it when unmuted mic is detected
dispatch(setCurrentNotificationUid(notification.uid));
}
});
break;
}

View File

@@ -69,17 +69,16 @@ export function potentialTranscriberJoined(participantId: string) {
* @returns {Function}
*/
export function showPendingTranscribingNotification() {
return (dispatch: Function) => {
const showNotificationAction = showNotification({
return async (dispatch: Function) => {
const notification = await dispatch(showNotification({
descriptionKey: 'transcribing.pending',
isDismissAllowed: false,
titleKey: 'dialog.transcribing'
});
}));
dispatch(showNotificationAction);
dispatch(setPendingTranscribingNotificationUid(
showNotificationAction.uid));
if (notification) {
dispatch(setPendingTranscribingNotificationUid(notification.uid));
}
};
}

View File

@@ -82,6 +82,12 @@ export function shouldDisplayTileView(state: Object = {}) {
return false;
}
const { disableTileView } = state['features/base/config'];
if (disableTileView) {
return false;
}
const { tileViewEnabled } = state['features/video-layout'];
if (tileViewEnabled !== undefined) {