Compare commits
24 Commits
jitihouse/
...
release-59
Author | SHA1 | Date |
---|---|---|
Horatiu Muresan | 9be0ee3bda | |
Hristo Terezov | e367621e91 | |
Saúl Ibarra Corretgé | ea57ab1c2a | |
Calin Chitu | a274e3a3de | |
Mihaela Dumitru | 41e790771f | |
Saúl Ibarra Corretgé | 331bd2014b | |
Horatiu Muresan | a27948b3af | |
Avram Tudor | 43beb627a2 | |
Mihaela Dumitru | bd994f2103 | |
Robert Pintilii | 6a3755f5d6 | |
Avram Tudor | 97f355ec22 | |
Avram Tudor | 79fe929eb7 | |
Avram Tudor | ef2a490961 | |
Robert Pintilii | 3bbba6f305 | |
Robert Pintilii | 7a573fd580 | |
Jaya Allamsetty | 1484e0fd0b | |
Saúl Ibarra Corretgé | 2909176a73 | |
Avram Tudor | 64d3faa52a | |
Avram Tudor | 842755674a | |
Robert Pintilii | c007a6194e | |
Robert Pintilii | 71a8d7937c | |
Hristo Terezov | 10785cb1c7 | |
Avram Tudor | 5cdee7d989 | |
Robert Pintilii | b2d8a6115a |
|
@ -1637,29 +1637,32 @@ export default {
|
|||
|
||||
APP.store.dispatch(setScreenAudioShareState(false));
|
||||
|
||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||
.then(([ stream ]) => {
|
||||
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
||||
if (didHaveVideo && !ignoreDidHaveVideo) {
|
||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||
.then(([ stream ]) => {
|
||||
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
||||
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('failed to switch back to local video', error);
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('failed to switch back to local video', error);
|
||||
|
||||
return this.useVideoStream(null).then(() =>
|
||||
return this.useVideoStream(null).then(() =>
|
||||
|
||||
// Still fail with the original err
|
||||
Promise.reject(error)
|
||||
);
|
||||
// Still fail with the original err
|
||||
Promise.reject(error)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
promise = promise.then(() => {
|
||||
logger.debug('_turnScreenSharingOff using null for useVideoStream');
|
||||
|
||||
return this.useVideoStream(null);
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(
|
||||
() => {
|
||||
// Mute the video if camera video needs to be ignored or if video was muted before switching to screen
|
||||
// share.
|
||||
if (ignoreDidHaveVideo || !didHaveVideo) {
|
||||
APP.store.dispatch(setVideoMuted(true, MEDIA_TYPE.VIDEO));
|
||||
}
|
||||
this.videoSwitchInProgress = false;
|
||||
sendAnalytics(createScreenSharingEvent('stopped',
|
||||
duration === 0 ? null : duration));
|
||||
|
|
|
@ -841,7 +841,7 @@
|
|||
"raisedHandsLabel": "Number of raised hands",
|
||||
"record": {
|
||||
"already": {
|
||||
"linked": "Record is already linked to this session."
|
||||
"linked": "The meeting is already linked to this Salesforce object."
|
||||
},
|
||||
"type": {
|
||||
"account": "Account",
|
||||
|
|
|
@ -39,7 +39,8 @@ import {
|
|||
raiseHand,
|
||||
isParticipantModerator,
|
||||
isLocalParticipantModerator,
|
||||
hasRaisedHand
|
||||
hasRaisedHand,
|
||||
grantModerator
|
||||
} from '../../react/features/base/participants';
|
||||
import { updateSettings } from '../../react/features/base/settings';
|
||||
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
||||
|
@ -164,6 +165,14 @@ function initCommands() {
|
|||
}
|
||||
APP.store.dispatch(autoAssignToBreakoutRooms());
|
||||
},
|
||||
'grant-moderator': participantId => {
|
||||
if (!isLocalParticipantModerator(APP.store.getState())) {
|
||||
logger.error('Missing moderator rights to grant moderator right to another participant');
|
||||
|
||||
return;
|
||||
}
|
||||
APP.store.dispatch(grantModerator(participantId));
|
||||
},
|
||||
'display-name': displayName => {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
|
|
|
@ -38,7 +38,7 @@ const commands = {
|
|||
displayName: 'display-name',
|
||||
e2eeKey: 'e2ee-key',
|
||||
email: 'email',
|
||||
toggleLobby: 'toggle-lobby',
|
||||
grantModerator: 'grant-moderator',
|
||||
hangup: 'video-hangup',
|
||||
initiatePrivateChat: 'initiate-private-chat',
|
||||
joinBreakoutRoom: 'join-breakout-room',
|
||||
|
@ -73,6 +73,7 @@ const commands = {
|
|||
toggleChat: 'toggle-chat',
|
||||
toggleE2EE: 'toggle-e2ee',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleLobby: 'toggle-lobby',
|
||||
toggleModeration: 'toggle-moderation',
|
||||
toggleParticipantsPane: 'toggle-participants-pane',
|
||||
toggleRaiseHand: 'toggle-raise-hand',
|
||||
|
|
|
@ -108,13 +108,13 @@ UI.start = function() {
|
|||
$('body').addClass('mobile-browser');
|
||||
} else {
|
||||
$('body').addClass('desktop-browser');
|
||||
}
|
||||
|
||||
if (config.backgroundAlpha !== undefined) {
|
||||
const backgroundColor = $('body').css('background-color');
|
||||
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
|
||||
if (config.backgroundAlpha !== undefined) {
|
||||
const backgroundColor = $('body').css('background-color');
|
||||
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
|
||||
|
||||
$('body').css('background-color', alphaColor);
|
||||
}
|
||||
$('body').css('background-color', alphaColor);
|
||||
}
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
|
|
|
@ -73,7 +73,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/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
|
@ -11789,8 +11789,8 @@
|
|||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-aIWaPY62nEZ9x13JDvv92UFhAvCSdC0ogCv4KpR1+Bwb6YmOPks+GHWpirZGk2RzgOL3cry43SDTVL4Tc0VyNA==",
|
||||
"resolved": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#c286b17f732d350b945553e973753b6701e42127",
|
||||
"integrity": "sha512-hj81bry+CtmoJAtSZKubKcGsA7SLqZCRSJskICd/fsvGnutbJrkBfJ6s6SPG1lFmHUkwccWyOwqfu9pK0Qp7WQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
|
@ -28784,8 +28784,9 @@
|
|||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-aIWaPY62nEZ9x13JDvv92UFhAvCSdC0ogCv4KpR1+Bwb6YmOPks+GHWpirZGk2RzgOL3cry43SDTVL4Tc0VyNA==",
|
||||
"version": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#c286b17f732d350b945553e973753b6701e42127",
|
||||
"integrity": "sha512-hj81bry+CtmoJAtSZKubKcGsA7SLqZCRSJskICd/fsvGnutbJrkBfJ6s6SPG1lFmHUkwccWyOwqfu9pK0Qp7WQ==",
|
||||
"from": "lib-jitsi-meet@https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
|
|
@ -78,7 +78,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/v1399.0.0+1a98d919/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#c286b17f732d350b945553e973753b6701e42127",
|
||||
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
|
|
|
@ -148,7 +148,7 @@ const ContextMenu = ({
|
|||
|
||||
setIsHidden(false);
|
||||
} else {
|
||||
setIsHidden(true);
|
||||
hidden === undefined && setIsHidden(true);
|
||||
}
|
||||
}, [ entity, offsetTarget, _overflowDrawer ]);
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
showErrorNotification
|
||||
} from '../../notifications';
|
||||
import { showSalesforceNotification } from '../../salesforce';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
||||
import { validateJwt } from '../jwt';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
|
@ -240,9 +239,6 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
|
|||
dispatch(openDisplayNamePrompt(undefined));
|
||||
}
|
||||
|
||||
|
||||
dispatch(showSalesforceNotification());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,6 @@ class Chat extends AbstractChat<Props> {
|
|||
<ChatInput
|
||||
onResize = { this._onChatInputResize }
|
||||
onSend = { this._onSendMessage } />
|
||||
<KeyboardAvoider />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -222,12 +222,12 @@ class Conference extends AbstractConference<Props, *> {
|
|||
id = 'layout_wrapper'
|
||||
onMouseEnter = { this._onMouseEnter }
|
||||
onMouseLeave = { this._onMouseLeave }
|
||||
onMouseMove = { this._onMouseMove } >
|
||||
onMouseMove = { this._onMouseMove }
|
||||
ref = { this._setBackground }>
|
||||
<div
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }
|
||||
ref = { this._setBackground }>
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
<ConferenceInfo />
|
||||
|
||||
<Notice />
|
||||
|
|
|
@ -12,6 +12,7 @@ import { MiddlewareRegistry, StateListenerRegistry } from '../base/redux';
|
|||
import { SET_REDUCED_UI } from '../base/responsive-ui';
|
||||
import { FeedbackDialog } from '../feedback';
|
||||
import { setFilmstripEnabled } from '../filmstrip';
|
||||
import { showSalesforceNotification } from '../salesforce/actions';
|
||||
import { setToolboxEnabled } from '../toolbox/actions';
|
||||
|
||||
import { notifyKickedOut } from './actions';
|
||||
|
@ -21,13 +22,12 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
case SET_REDUCED_UI: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { reducedUI } = state['features/base/responsive-ui'];
|
||||
_conferenceJoined(store);
|
||||
|
||||
dispatch(setToolboxEnabled(!reducedUI));
|
||||
dispatch(setFilmstripEnabled(!reducedUI));
|
||||
break;
|
||||
|
||||
case SET_REDUCED_UI: {
|
||||
_setReducedUI(store);
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -80,3 +80,37 @@ StateListenerRegistry.register(
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Configures the UI. In reduced UI mode some components will
|
||||
* be hidden if there is no space to render them.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _setReducedUI({ dispatch, getState }) {
|
||||
const { reducedUI } = getState()['features/base/responsive-ui'];
|
||||
|
||||
dispatch(setToolboxEnabled(!reducedUI));
|
||||
dispatch(setFilmstripEnabled(!reducedUI));
|
||||
}
|
||||
|
||||
/**
|
||||
* Does extra sync up on properties that may need to be updated after the
|
||||
* conference was joined.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _conferenceJoined({ dispatch, getState }) {
|
||||
_setReducedUI({
|
||||
dispatch,
|
||||
getState
|
||||
});
|
||||
|
||||
dispatch(showSalesforceNotification());
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export function getDeviceSelectionDialogProps(stateful: Object | Function) {
|
|||
// 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 (!conference && !isMobileSafari) {
|
||||
disableAudioInputChange = false;
|
||||
disableVideoInputSelect = false;
|
||||
selectedAudioInputId = userSelectedMic;
|
||||
|
|
|
@ -22,6 +22,7 @@ const useStyles = makeStyles(theme => {
|
|||
justifyContent: 'center',
|
||||
marginBottom: theme.spacing(7),
|
||||
transition: 'margin-bottom 0.3s',
|
||||
pointerEvents: 'none',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
|
|
|
@ -35,7 +35,7 @@ const queue = [];
|
|||
let lastValidFaceBox;
|
||||
|
||||
const detect = async message => {
|
||||
const { baseUrl, imageBitmap, isHorizontallyFlipped, threshold } = message.data;
|
||||
const { baseUrl, image, isHorizontallyFlipped, threshold } = message.data;
|
||||
|
||||
if (initInProgress || initError) {
|
||||
return;
|
||||
|
@ -70,8 +70,8 @@ const detect = async message => {
|
|||
|
||||
tf.engine().startScope();
|
||||
|
||||
const image = tf.browser.fromPixels(imageBitmap);
|
||||
const detections = await model.estimateFaces(image, false, isHorizontallyFlipped, false);
|
||||
const imageTensor = tf.browser.fromPixels(image);
|
||||
const detections = await model.estimateFaces(imageTensor, false, isHorizontallyFlipped, false);
|
||||
|
||||
tf.engine().endScope();
|
||||
|
||||
|
@ -80,10 +80,10 @@ const detect = async message => {
|
|||
if (detections.length) {
|
||||
faceBox = {
|
||||
// normalize to percentage based
|
||||
left: Math.round(Math.min(...detections.map(d => d.topLeft[0])) * 100 / imageBitmap.width),
|
||||
right: Math.round(Math.max(...detections.map(d => d.bottomRight[0])) * 100 / imageBitmap.width),
|
||||
top: Math.round(Math.min(...detections.map(d => d.topLeft[1])) * 100 / imageBitmap.height),
|
||||
bottom: Math.round(Math.max(...detections.map(d => d.bottomRight[1])) * 100 / imageBitmap.height)
|
||||
left: Math.round(Math.min(...detections.map(d => d.topLeft[0])) * 100 / image.width),
|
||||
right: Math.round(Math.max(...detections.map(d => d.bottomRight[0])) * 100 / image.width),
|
||||
top: Math.round(Math.min(...detections.map(d => d.topLeft[1])) * 100 / image.height),
|
||||
bottom: Math.round(Math.max(...detections.map(d => d.bottomRight[1])) * 100 / image.height)
|
||||
};
|
||||
|
||||
if (lastValidFaceBox && Math.abs(lastValidFaceBox.left - faceBox.left) < threshold) {
|
||||
|
|
|
@ -44,6 +44,7 @@ export async function sendDataToWorker(
|
|||
}
|
||||
|
||||
let imageBitmap;
|
||||
let image;
|
||||
|
||||
try {
|
||||
imageBitmap = await imageCapture.grabFrame();
|
||||
|
@ -53,13 +54,28 @@ export async function sendDataToWorker(
|
|||
return;
|
||||
}
|
||||
|
||||
if (typeof OffscreenCanvas === 'undefined') {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
canvas.width = imageBitmap.width;
|
||||
canvas.height = imageBitmap.height;
|
||||
context.drawImage(imageBitmap, 0, 0);
|
||||
|
||||
image = context.getImageData(0, 0, imageBitmap.width, imageBitmap.height);
|
||||
} else {
|
||||
image = imageBitmap;
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
id: DETECT_FACE_BOX,
|
||||
baseUrl: getBaseUrl(),
|
||||
imageBitmap,
|
||||
image,
|
||||
threshold,
|
||||
isHorizontallyFlipped
|
||||
});
|
||||
|
||||
imageBitmap.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,17 +5,10 @@ import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../base/participants';
|
|||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { CLIENT_RESIZED } from '../base/responsive-ui';
|
||||
import { SETTINGS_UPDATED } from '../base/settings';
|
||||
import {
|
||||
getCurrentLayout,
|
||||
LAYOUTS
|
||||
} from '../video-layout';
|
||||
|
||||
import { SET_USER_FILMSTRIP_WIDTH } from './actionTypes';
|
||||
import {
|
||||
setFilmstripWidth,
|
||||
setHorizontalViewDimensions,
|
||||
setTileViewDimensions,
|
||||
setVerticalViewDimensions
|
||||
setFilmstripWidth
|
||||
} from './actions';
|
||||
import { DEFAULT_FILMSTRIP_WIDTH, MIN_STAGE_VIEW_WIDTH } from './constants';
|
||||
import { updateRemoteParticipants, updateRemoteParticipantsOnLeave } from './functions';
|
||||
|
@ -40,21 +33,6 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
switch (action.type) {
|
||||
case CLIENT_RESIZED: {
|
||||
const state = store.getState();
|
||||
const layout = getCurrentLayout(state);
|
||||
|
||||
switch (layout) {
|
||||
case LAYOUTS.TILE_VIEW: {
|
||||
store.dispatch(setTileViewDimensions());
|
||||
break;
|
||||
}
|
||||
case LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW:
|
||||
store.dispatch(setHorizontalViewDimensions());
|
||||
break;
|
||||
|
||||
case LAYOUTS.VERTICAL_FILMSTRIP_VIEW:
|
||||
store.dispatch(setVerticalViewDimensions());
|
||||
break;
|
||||
}
|
||||
|
||||
if (isFilmstripResizable(state)) {
|
||||
const { width: filmstripWidth } = state['features/filmstrip'];
|
||||
|
|
|
@ -51,8 +51,11 @@ StateListenerRegistry.register(
|
|||
* Listens for changes in the selected layout to calculate the dimensions of the tile view grid and horizontal view.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => getCurrentLayout(state),
|
||||
/* listener */ (layout, store) => {
|
||||
/* selector */ state => {
|
||||
return { layout: getCurrentLayout(state),
|
||||
width: state['features/base/responsive-ui'].clientWidth };
|
||||
},
|
||||
/* listener */ ({ layout }, store) => {
|
||||
switch (layout) {
|
||||
case LAYOUTS.TILE_VIEW:
|
||||
store.dispatch(setTileViewDimensions());
|
||||
|
@ -64,6 +67,8 @@ StateListenerRegistry.register(
|
|||
store.dispatch(setVerticalViewDimensions());
|
||||
break;
|
||||
}
|
||||
}, {
|
||||
deepEquals: true
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
@ -100,7 +100,7 @@ function mapStateToProps(state): Object {
|
|||
const { premeetingBackground } = state['features/dynamic-branding'];
|
||||
|
||||
return {
|
||||
...abstractMapStateToProps,
|
||||
...abstractMapStateToProps(state),
|
||||
_premeetingBackground: premeetingBackground
|
||||
};
|
||||
}
|
||||
|
|
|
@ -60,6 +60,18 @@ const PollCreate = (props: AbstractProps) => {
|
|||
}, [ lastFocus ]);
|
||||
|
||||
const checkModifiers = useCallback(ev => {
|
||||
// Composition events used to add accents to characters
|
||||
// despite their absence from standard US keyboards,
|
||||
// to build up logograms of many Asian languages
|
||||
// from their base components or categories and so on.
|
||||
if (ev.isComposing || ev.keyCode === 229) {
|
||||
// keyCode 229 means that user pressed some button,
|
||||
// but input method is still processing that.
|
||||
// This is a standard behavior for some input methods
|
||||
// like entering japanese or сhinese hieroglyphs.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Because this isn't done automatically on MacOS
|
||||
if (ev.key === 'Enter' && ev.metaKey) {
|
||||
ev.preventDefault();
|
||||
|
@ -90,7 +102,11 @@ const PollCreate = (props: AbstractProps) => {
|
|||
}
|
||||
|
||||
if (ev.key === 'Enter') {
|
||||
addAnswer(i + 1);
|
||||
// We add a new option input
|
||||
// only if we are on the last option input
|
||||
if (i === answers.length - 1) {
|
||||
addAnswer(i + 1);
|
||||
}
|
||||
requestFocus(i + 1);
|
||||
ev.preventDefault();
|
||||
} else if (ev.key === 'Backspace' && ev.target.value === '' && answers.length > 1) {
|
||||
|
|
|
@ -132,14 +132,13 @@ export function highlightMeetingMoment() {
|
|||
return async (dispatch: Function, getState: Function) => {
|
||||
dispatch(setHighlightMomentButtonState(true));
|
||||
|
||||
try {
|
||||
await sendMeetingHighlight(getState());
|
||||
const success = await sendMeetingHighlight(getState());
|
||||
|
||||
if (success) {
|
||||
dispatch(showNotification({
|
||||
descriptionKey: 'recording.highlightMomentSucessDescription',
|
||||
titleKey: 'recording.highlightMomentSuccess'
|
||||
}));
|
||||
} catch (err) {
|
||||
logger.error('Could not highlight meeting moment', err);
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}
|
||||
|
||||
dispatch(setHighlightMomentButtonState(false));
|
||||
|
|
|
@ -1,17 +1,38 @@
|
|||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { getActiveSession, isHighlightMeetingMomentDisabled } from '../..';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import {
|
||||
hideNotification,
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
NOTIFICATION_TYPE,
|
||||
showNotification
|
||||
} from '../../../notifications';
|
||||
import { highlightMeetingMoment } from '../../actions.any';
|
||||
import { StartRecordingDialog } from '../../components';
|
||||
import { PROMPT_RECORDING_NOTIFICATION_ID } from '../../constants';
|
||||
import { getRecordButtonProps } from '../../functions';
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* Whether or not the conference is in audio only mode.
|
||||
* Indicates whether or not the button is disabled.
|
||||
*/
|
||||
_audioOnly: boolean,
|
||||
_disabled: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether or not a highlight request is in progress.
|
||||
*/
|
||||
_isHighlightInProgress: boolean,
|
||||
|
||||
/**
|
||||
* Indicates whether or not the button should be visible.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
|
@ -24,7 +45,7 @@ export type Props = {
|
|||
*/
|
||||
export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
||||
/**
|
||||
* Initializes a new AbstractVideoTrack instance.
|
||||
* Initializes a new AbstractHighlightButton instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
|
@ -43,9 +64,27 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onClick() {
|
||||
const { _disabled, dispatch } = this.props;
|
||||
const { _disabled, _isHighlightInProgress, dispatch } = this.props;
|
||||
|
||||
if (!_disabled) {
|
||||
if (_isHighlightInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_disabled) {
|
||||
dispatch(showNotification({
|
||||
descriptionKey: 'recording.highlightMomentDisabled',
|
||||
titleKey: 'recording.highlightMoment',
|
||||
uid: PROMPT_RECORDING_NOTIFICATION_ID,
|
||||
customActionNameKey: [ 'localRecording.start' ],
|
||||
customActionHandler: [ () => {
|
||||
batch(() => {
|
||||
dispatch(hideNotification(PROMPT_RECORDING_NOTIFICATION_ID));
|
||||
dispatch(openDialog(StartRecordingDialog));
|
||||
});
|
||||
} ],
|
||||
appearance: NOTIFICATION_TYPE.NORMAL
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
} else {
|
||||
dispatch(highlightMeetingMoment());
|
||||
}
|
||||
}
|
||||
|
@ -53,12 +92,14 @@ export default class AbstractHighlightButton<P: Props> extends Component<P> {
|
|||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code AbstractVideoQualityLabel}'s props.
|
||||
* {@code AbstractHighlightButton}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean
|
||||
* _disabled: boolean,
|
||||
* _isHighlightInProgress: boolean,
|
||||
* _visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _abstractMapStateToProps(state: Object) {
|
||||
|
@ -66,8 +107,17 @@ export function _abstractMapStateToProps(state: Object) {
|
|||
const isButtonDisabled = isHighlightMeetingMomentDisabled(state);
|
||||
const { webhookProxyUrl } = state['features/base/config'];
|
||||
|
||||
const {
|
||||
disabled: isRecordButtonDisabled,
|
||||
visible: isRecordButtonVisible
|
||||
} = getRecordButtonProps(state);
|
||||
|
||||
const canStartRecording = isRecordButtonVisible && !isRecordButtonDisabled;
|
||||
const _visible = (canStartRecording || isRecordingRunning) && Boolean(webhookProxyUrl);
|
||||
|
||||
return {
|
||||
_disabled: !isRecordingRunning || isButtonDisabled,
|
||||
_visible: Boolean(webhookProxyUrl)
|
||||
_disabled: !isRecordingRunning,
|
||||
_isHighlightInProgress: isButtonDisabled,
|
||||
_visible
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,16 +6,10 @@ import {
|
|||
} from '../../../analytics';
|
||||
import { IconToggleRecording } from '../../../base/icons';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
isLocalParticipantModerator
|
||||
} from '../../../base/participants';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
||||
import { FEATURES } from '../../../jaas/constants';
|
||||
import { getActiveSession } from '../../functions';
|
||||
|
||||
import { getActiveSession, getRecordButtonProps } from '../../functions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
|
@ -131,57 +125,20 @@ export default class AbstractRecordButton<P: Props> extends AbstractButton<P, *>
|
|||
* {@code RecordButton} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Props} ownProps - The own props of the Component.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _disabled: boolean,
|
||||
* _isRecordingRunning: boolean,
|
||||
* _tooltip: string,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||
let { visible } = ownProps;
|
||||
|
||||
// a button can be disabled/enabled if enableFeaturesBasedOnToken
|
||||
// is on or if the livestreaming is running.
|
||||
let _disabled;
|
||||
let _tooltip = '';
|
||||
|
||||
if (typeof visible === 'undefined') {
|
||||
// If the containing component provides the visible prop, that is one
|
||||
// above all, but if not, the button should be autonomus and decide on
|
||||
// its own to be visible or not.
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const {
|
||||
enableFeaturesBasedOnToken,
|
||||
fileRecordingsEnabled
|
||||
} = state['features/base/config'];
|
||||
const { features = {} } = getLocalParticipant(state);
|
||||
|
||||
visible = isModerator && fileRecordingsEnabled;
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
visible = visible && String(features.recording) === 'true';
|
||||
_disabled = String(features.recording) === 'disabled';
|
||||
if (!visible && !_disabled) {
|
||||
_disabled = true;
|
||||
visible = true;
|
||||
_tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// disable the button if the livestreaming is running.
|
||||
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
||||
_disabled = true;
|
||||
_tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
||||
}
|
||||
|
||||
// disable the button if we are in a breakout room.
|
||||
if (isInBreakoutRoom(state)) {
|
||||
_disabled = true;
|
||||
visible = false;
|
||||
}
|
||||
export function _mapStateToProps(state: Object): Object {
|
||||
const {
|
||||
disabled: _disabled,
|
||||
tooltip: _tooltip,
|
||||
visible
|
||||
} = getRecordButtonProps(state);
|
||||
|
||||
return {
|
||||
_disabled,
|
||||
|
|
|
@ -231,8 +231,7 @@ class StartRecordingDialogContent extends Component<Props> {
|
|||
t
|
||||
} = this.props;
|
||||
|
||||
if (isVpaas
|
||||
|| selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) {
|
||||
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
import { withStyles } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
|
||||
import { openDialog } from '../../../../base/dialog';
|
||||
import { translate } from '../../../../base/i18n';
|
||||
import { IconHighlight } from '../../../../base/icons';
|
||||
import { Label } from '../../../../base/label';
|
||||
import { connect } from '../../../../base/redux';
|
||||
import { Tooltip } from '../../../../base/tooltip';
|
||||
import BaseTheme from '../../../../base/ui/components/BaseTheme';
|
||||
import { StartRecordingDialog } from '../../../components';
|
||||
import AbstractHighlightButton, {
|
||||
_abstractMapStateToProps,
|
||||
type Props as AbstractProps
|
||||
|
@ -28,6 +30,17 @@ type Props = AbstractProps & {
|
|||
_visible: boolean,
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link HighlightButton}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* Whether the notification which prompts for starting recording is open is not.
|
||||
*/
|
||||
isNotificationOpen: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
*
|
||||
|
@ -37,13 +50,36 @@ type Props = AbstractProps & {
|
|||
*/
|
||||
const styles = theme => {
|
||||
return {
|
||||
regular: {
|
||||
background: theme.palette.field02,
|
||||
margin: '0 4px 4px 4px'
|
||||
container: {
|
||||
position: 'relative'
|
||||
},
|
||||
disabled: {
|
||||
background: theme.palette.text02,
|
||||
margin: '0 4px 4px 4px'
|
||||
},
|
||||
regular: {
|
||||
background: theme.palette.field02,
|
||||
margin: '0 4px 4px 4px'
|
||||
},
|
||||
highlightNotification: {
|
||||
backgroundColor: theme.palette.field02,
|
||||
borderRadius: '6px',
|
||||
boxShadow: '0px 6px 20px rgba(0, 0, 0, 0.25)',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.uiBackground,
|
||||
fontSize: '14px',
|
||||
fontWeight: '400',
|
||||
left: '4px',
|
||||
padding: '16px',
|
||||
position: 'absolute',
|
||||
top: '32px',
|
||||
width: 320
|
||||
},
|
||||
highlightNotificationButton: {
|
||||
color: theme.palette.field01Focus,
|
||||
cursor: 'pointer',
|
||||
fontWeight: '600',
|
||||
marginTop: '8px'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -52,7 +88,82 @@ const styles = theme => {
|
|||
* React {@code Component} responsible for displaying an action that
|
||||
* allows users to highlight a meeting moment.
|
||||
*/
|
||||
export class HighlightButton extends AbstractHighlightButton<Props> {
|
||||
export class HighlightButton extends AbstractHighlightButton<Props, State> {
|
||||
/**
|
||||
* Initializes a new HighlightButton instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isNotificationOpen: false
|
||||
};
|
||||
|
||||
this._onOpenDialog = this._onOpenDialog.bind(this);
|
||||
this._onWindowClickListener = this._onWindowClickListener.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
window.addEventListener('click', this._onWindowClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentWillUnmount()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('click', this._onWindowClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the start recording button.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenDialog() {
|
||||
this.props.dispatch(openDialog(StartRecordingDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicking / pressing the highlight button.
|
||||
*
|
||||
* @override
|
||||
* @param {Event} e - The click event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onClick(e) {
|
||||
e.stopPropagation();
|
||||
|
||||
const { _disabled } = this.props;
|
||||
|
||||
if (_disabled) {
|
||||
this.setState({
|
||||
isNotificationOpen: true
|
||||
});
|
||||
} else {
|
||||
super._onClick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Window click event listener.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onWindowClickListener() {
|
||||
this.setState({
|
||||
isNotificationOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
|
@ -77,16 +188,28 @@ export class HighlightButton extends AbstractHighlightButton<Props> {
|
|||
const tooltipKey = _disabled ? 'recording.highlightMomentDisabled' : 'recording.highlightMoment';
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
content = { t(tooltipKey) }
|
||||
position = { 'bottom' }>
|
||||
<Label
|
||||
className = { className }
|
||||
icon = { IconHighlight }
|
||||
iconColor = { _disabled ? BaseTheme.palette.text03 : BaseTheme.palette.field01 }
|
||||
id = 'highlightMeetingLabel'
|
||||
onClick = { this._onClick } />
|
||||
</Tooltip>
|
||||
<div className = { classes.container }>
|
||||
<Tooltip
|
||||
content = { t(tooltipKey) }
|
||||
position = { 'bottom' }>
|
||||
<Label
|
||||
className = { className }
|
||||
icon = { IconHighlight }
|
||||
iconColor = { _disabled ? BaseTheme.palette.text03 : BaseTheme.palette.field01 }
|
||||
id = 'highlightMeetingLabel'
|
||||
onClick = { this._onClick } />
|
||||
</Tooltip>
|
||||
{this.state.isNotificationOpen && (
|
||||
<div className = { classes.highlightNotification }>
|
||||
{t('recording.highlightMomentDisabled')}
|
||||
<div
|
||||
className = { classes.highlightNotificationButton }
|
||||
onClick = { this._onOpenDialog }>
|
||||
{t('localRecording.start')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,11 +50,7 @@ class RecordingButton extends AbstractRecordButton<Props> {
|
|||
export function _mapStateToProps(state: Object, ownProps: Props): Object {
|
||||
const abstractProps = _abstractMapStateToProps(state, ownProps);
|
||||
const toolbarButtons = getToolbarButtons(state);
|
||||
let { visible } = ownProps;
|
||||
|
||||
if (typeof visible === 'undefined') {
|
||||
visible = toolbarButtons.includes('recording') && abstractProps.visible;
|
||||
}
|
||||
const visible = toolbarButtons.includes('recording') && abstractProps.visible;
|
||||
|
||||
return {
|
||||
...abstractProps,
|
||||
|
|
|
@ -17,6 +17,13 @@ export const LIVE_STREAMING_OFF_SOUND_ID = 'LIVE_STREAMING_OFF_SOUND';
|
|||
*/
|
||||
export const LIVE_STREAMING_ON_SOUND_ID = 'LIVE_STREAMING_ON_SOUND';
|
||||
|
||||
/**
|
||||
* The identifier of the prompt to start recording notification.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const PROMPT_RECORDING_NOTIFICATION_ID = 'PROMPT_RECORDING_NOTIFICATION_ID';
|
||||
|
||||
/**
|
||||
* The identifier of the sound to be played when a recording session is stopped.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// @flow
|
||||
|
||||
import { JitsiRecordingConstants } from '../base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { getLocalParticipant, isLocalParticipantModerator } from '../base/participants';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../dropbox';
|
||||
import { extractFqnFromPath } from '../dynamic-branding';
|
||||
|
||||
|
@ -119,6 +120,65 @@ export function getSessionStatusToShow(state: Object, mode: string): ?string {
|
|||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the recording button props.
|
||||
*
|
||||
* @param {Object} state - The redux state to search in.
|
||||
*
|
||||
* @returns {{
|
||||
* disabled: boolean,
|
||||
* tooltip: string,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function getRecordButtonProps(state: Object): ?string {
|
||||
let visible;
|
||||
|
||||
// a button can be disabled/enabled if enableFeaturesBasedOnToken
|
||||
// is on or if the livestreaming is running.
|
||||
let disabled;
|
||||
let tooltip = '';
|
||||
|
||||
// If the containing component provides the visible prop, that is one
|
||||
// above all, but if not, the button should be autonomus and decide on
|
||||
// its own to be visible or not.
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const {
|
||||
enableFeaturesBasedOnToken,
|
||||
fileRecordingsEnabled
|
||||
} = state['features/base/config'];
|
||||
const { features = {} } = getLocalParticipant(state);
|
||||
|
||||
visible = isModerator && fileRecordingsEnabled;
|
||||
|
||||
if (enableFeaturesBasedOnToken) {
|
||||
visible = visible && String(features.recording) === 'true';
|
||||
disabled = String(features.recording) === 'disabled';
|
||||
if (!visible && !disabled) {
|
||||
disabled = true;
|
||||
visible = true;
|
||||
tooltip = 'dialog.recordingDisabledTooltip';
|
||||
}
|
||||
}
|
||||
|
||||
// disable the button if the livestreaming is running.
|
||||
if (getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
||||
disabled = true;
|
||||
tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
||||
}
|
||||
|
||||
// disable the button if we are in a breakout room.
|
||||
if (isInBreakoutRoom(state)) {
|
||||
disabled = true;
|
||||
visible = false;
|
||||
}
|
||||
|
||||
return {
|
||||
disabled,
|
||||
tooltip,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the resource id.
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '../notifications';
|
||||
|
||||
import { SalesforceLinkDialog } from './components';
|
||||
import { isSalesforceEnabled } from './functions';
|
||||
|
||||
/**
|
||||
* Displays the notification for linking the meeting to Salesforce.
|
||||
|
@ -18,9 +19,7 @@ import { SalesforceLinkDialog } from './components';
|
|||
*/
|
||||
export function showSalesforceNotification() {
|
||||
return (dispatch: Object, getState: Function) => {
|
||||
const { salesforceUrl } = getState()['features/base/config'];
|
||||
|
||||
if (!salesforceUrl) {
|
||||
if (!isSalesforceEnabled(getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
// @flow
|
||||
|
||||
import { doGetJSON } from '../base/util';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
/**
|
||||
* Determines whether Salesforce is enabled for the current conference.
|
||||
*
|
||||
* @param {Function|Object} state - The redux store, the redux
|
||||
* {@code getState} function, or the redux state itself.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isSalesforceEnabled = (state: Function | Object) => {
|
||||
const { salesforceUrl } = state['features/base/config'];
|
||||
const isBreakoutRoom = isInBreakoutRoom(state);
|
||||
|
||||
return Boolean(salesforceUrl) && !isBreakoutRoom;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches the Salesforce records that were most recently interacted with.
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
export * from './components';
|
||||
export * from './actions';
|
|
@ -200,10 +200,10 @@ function _mapStateToProps(state) {
|
|||
|
||||
return {
|
||||
...newProps,
|
||||
followMeEnabled: tabState.followMeEnabled,
|
||||
startAudioMuted: tabState.startAudioMuted,
|
||||
startVideoMuted: tabState.startVideoMuted,
|
||||
startReactionsMuted: tabState.startReactionsMuted
|
||||
followMeEnabled: tabState?.followMeEnabled,
|
||||
startAudioMuted: tabState?.startAudioMuted,
|
||||
startVideoMuted: tabState?.startVideoMuted,
|
||||
startReactionsMuted: tabState?.startReactionsMuted
|
||||
};
|
||||
},
|
||||
styles: 'settings-pane moderator-pane',
|
||||
|
@ -242,11 +242,11 @@ function _mapStateToProps(state) {
|
|||
|
||||
return {
|
||||
...newProps,
|
||||
currentFramerate: tabState.currentFramerate,
|
||||
currentLanguage: tabState.currentLanguage,
|
||||
hideSelfView: tabState.hideSelfView,
|
||||
showPrejoinPage: tabState.showPrejoinPage,
|
||||
enabledNotifications: tabState.enabledNotifications
|
||||
currentFramerate: tabState?.currentFramerate,
|
||||
currentLanguage: tabState?.currentLanguage,
|
||||
hideSelfView: tabState?.hideSelfView,
|
||||
showPrejoinPage: tabState?.showPrejoinPage,
|
||||
enabledNotifications: tabState?.enabledNotifications
|
||||
};
|
||||
},
|
||||
styles: 'settings-pane more-pane',
|
||||
|
|
|
@ -8,6 +8,7 @@ import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/
|
|||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import { isSalesforceEnabled } from '../../../salesforce/functions';
|
||||
|
||||
/**
|
||||
* Implementation of a button for opening the Salesforce link dialog.
|
||||
|
@ -39,7 +40,7 @@ class LinkToSalesforceButton extends AbstractButton<AbstractButtonProps, *> {
|
|||
*/
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
visible: Boolean(state['features/base/config'].salesforceUrl)
|
||||
visible: isSalesforceEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { translate } from '../../../base/i18n';
|
|||
import { IconSalesforce } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { SalesforceLinkDialog } from '../../../salesforce';
|
||||
import { SalesforceLinkDialog } from '../../../salesforce/components';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LinkToSalesforce}.
|
||||
|
|
|
@ -52,6 +52,7 @@ import {
|
|||
LiveStreamButton,
|
||||
RecordButton
|
||||
} from '../../../recording';
|
||||
import { isSalesforceEnabled } from '../../../salesforce/functions';
|
||||
import {
|
||||
isScreenAudioSupported,
|
||||
isScreenVideoShared,
|
||||
|
@ -867,6 +868,27 @@ class Toolbox extends Component<Props> {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the notify mode of the given toolbox button.
|
||||
*
|
||||
* @param {string} btnName - The toolbar button's name.
|
||||
* @returns {string|undefined} - The button's notify mode.
|
||||
*/
|
||||
_getButtonNotifyMode(btnName) {
|
||||
const notify = this.props._buttonsWithNotifyClick?.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === btnName)
|
||||
|| (typeof btn === 'object' && btn.key === btnName)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
return typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the notify click mode for the buttons.
|
||||
*
|
||||
|
@ -880,19 +902,7 @@ class Toolbox extends Component<Props> {
|
|||
|
||||
Object.values(buttons).forEach((button: any) => {
|
||||
if (typeof button === 'object') {
|
||||
const notify = this.props._buttonsWithNotifyClick.find(
|
||||
(btn: string | Object) =>
|
||||
(typeof btn === 'string' && btn === button.key)
|
||||
|| (typeof btn === 'object' && btn.key === button.key)
|
||||
);
|
||||
|
||||
if (notify) {
|
||||
const notifyMode = typeof notify === 'string' || notify.preventExecution
|
||||
? NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
|
||||
: NOTIFY_CLICK_MODE.ONLY_NOTIFY;
|
||||
|
||||
button.notifyMode = notifyMode;
|
||||
}
|
||||
button.notifyMode = this._getButtonNotifyMode(button.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1363,8 +1373,10 @@ class Toolbox extends Component<Props> {
|
|||
)}
|
||||
|
||||
<HangupButton
|
||||
buttonKey = 'hangup'
|
||||
customClass = 'hangup-button'
|
||||
key = 'hangup-button'
|
||||
notifyMode = { this._getButtonNotifyMode('hangup') }
|
||||
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1391,8 +1403,7 @@ function _mapStateToProps(state, ownProps) {
|
|||
disableProfile,
|
||||
enableFeaturesBasedOnToken,
|
||||
iAmRecorder,
|
||||
iAmSipGateway,
|
||||
salesforceUrl
|
||||
iAmSipGateway
|
||||
} = state['features/base/config'];
|
||||
const {
|
||||
fullScreen,
|
||||
|
@ -1441,7 +1452,7 @@ function _mapStateToProps(state, ownProps) {
|
|||
_isIosMobile: isIosMobileBrowser(),
|
||||
_isMobile: isMobileBrowser(),
|
||||
_isVpaasMeeting: isVpaasMeeting(state),
|
||||
_hasSalesforce: Boolean(salesforceUrl),
|
||||
_hasSalesforce: isSalesforceEnabled(state),
|
||||
_localParticipantID: localParticipant?.id,
|
||||
_localVideo: localVideo,
|
||||
_overflowMenuVisible: overflowMenuVisible,
|
||||
|
|
|
@ -279,14 +279,16 @@ function _updateReceiverVideoConstraints({ getState }) {
|
|||
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
||||
|
||||
visibleRemoteTrackSourceNames.forEach(sourceName => {
|
||||
receiverConstraints.constraints[sourceName] = { 'maxHeight': qualityLevel };
|
||||
receiverConstraints.constraints[sourceName] = { 'maxHeight': Math.min(qualityLevel,
|
||||
maxFrameHeight) };
|
||||
});
|
||||
}
|
||||
|
||||
if (largeVideoSourceName) {
|
||||
let quality = maxFrameHeight;
|
||||
|
||||
if (!remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||
if (navigator.product !== 'ReactNative'
|
||||
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||
quality = getVideoQualityForLargeVideo();
|
||||
}
|
||||
receiverConstraints.constraints[largeVideoSourceName] = { 'maxHeight': quality };
|
||||
|
@ -326,14 +328,16 @@ function _updateReceiverVideoConstraints({ getState }) {
|
|||
const qualityLevel = getVideoQualityForResizableFilmstripThumbnails(state);
|
||||
|
||||
visibleRemoteParticipants.forEach(participantId => {
|
||||
receiverConstraints.constraints[participantId] = { 'maxHeight': qualityLevel };
|
||||
receiverConstraints.constraints[participantId] = { 'maxHeight': Math.min(qualityLevel,
|
||||
maxFrameHeight) };
|
||||
});
|
||||
}
|
||||
|
||||
if (largeVideoParticipantId) {
|
||||
let quality = maxFrameHeight;
|
||||
|
||||
if (!remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||
if (navigator.product !== 'ReactNative'
|
||||
&& !remoteScreenShares.find(id => id === largeVideoParticipantId)) {
|
||||
quality = getVideoQualityForLargeVideo();
|
||||
}
|
||||
receiverConstraints.constraints[largeVideoParticipantId] = { 'maxHeight': quality };
|
||||
|
|
Loading…
Reference in New Issue