mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-10 08:40:18 +00:00
Compare commits
39 Commits
android-ap
...
7265
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a112d38943 | ||
|
|
b705c63a65 | ||
|
|
b4c8f7d097 | ||
|
|
74bac9806f | ||
|
|
65248d7d29 | ||
|
|
dc037bc8dd | ||
|
|
a22db037c7 | ||
|
|
44cc0f7e9a | ||
|
|
eafb337cd1 | ||
|
|
9b3be66287 | ||
|
|
1a10a00f74 | ||
|
|
a196bc27b8 | ||
|
|
ac8e4d9828 | ||
|
|
a6ade336b7 | ||
|
|
350443ad34 | ||
|
|
4c37ef7a2c | ||
|
|
3eedc2a49d | ||
|
|
aaeb1a90e5 | ||
|
|
ed89f9af20 | ||
|
|
863ad0b0e6 | ||
|
|
e2d701a8cc | ||
|
|
2710273069 | ||
|
|
07af18e284 | ||
|
|
519e37f567 | ||
|
|
7cf61eb776 | ||
|
|
f81446909c | ||
|
|
b4115593c0 | ||
|
|
f8bd8b616e | ||
|
|
be55ccd6f4 | ||
|
|
e7db18bd80 | ||
|
|
dff41e0fcb | ||
|
|
43be4324af | ||
|
|
c33baf4c96 | ||
|
|
f95a356025 | ||
|
|
1ba7765898 | ||
|
|
0346fca434 | ||
|
|
d267b2499d | ||
|
|
e3e5f1fbfa | ||
|
|
4697192b43 |
@@ -45,6 +45,12 @@ class AudioDeviceHandlerGeneric implements
|
||||
*/
|
||||
private AudioModeModule module;
|
||||
|
||||
/**
|
||||
* Constant defining a Hearing Aid. Only available on API level >= 28.
|
||||
* The value of: AudioDeviceInfo.TYPE_HEARING_AID
|
||||
*/
|
||||
private static final int TYPE_HEARING_AID = 23;
|
||||
|
||||
/**
|
||||
* Constant defining a USB headset. Only available on API level >= 26.
|
||||
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
|
||||
@@ -85,6 +91,7 @@ class AudioDeviceHandlerGeneric implements
|
||||
break;
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
|
||||
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
|
||||
case TYPE_HEARING_AID:
|
||||
case TYPE_USB_HEADSET:
|
||||
devices.add(AudioModeModule.DEVICE_HEADPHONES);
|
||||
break;
|
||||
|
||||
183
conference.js
183
conference.js
@@ -559,7 +559,7 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
let tryCreateLocalTracks = Promise.resolve([]);
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
|
||||
@@ -600,76 +600,65 @@ export default {
|
||||
|
||||
return [];
|
||||
});
|
||||
} else if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
} else if (requestedAudio || requestedVideo) {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
|
||||
// In this case we expect that the permission prompt is still visible. There is no point of
|
||||
// executing GUM with different source. Also at the time of writing the following
|
||||
// inconsistency have been noticed in some browsers - if the permissions prompt is visible
|
||||
// and another GUM is executed the prompt does not change its content but if the user
|
||||
// clicks allow the user action isassociated with the latest GUM call.
|
||||
errors.audioOnlyError = err;
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return createLocalTracksF(audioOptions);
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
|
||||
return [];
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
.catch(async error => {
|
||||
if (error.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
|
||||
errors.audioAndVideoError = error;
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// Retry with separate gUM calls.
|
||||
const gUMPromises = [];
|
||||
const tracks = [];
|
||||
|
||||
if (requestedAudio) {
|
||||
gUMPromises.push(createLocalTracksF(audioOptions));
|
||||
}
|
||||
|
||||
if (requestedVideo) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(gUMPromises);
|
||||
let errorMsg;
|
||||
|
||||
results.forEach((result, idx) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
tracks.push(result.value[0]);
|
||||
} else {
|
||||
errorMsg = result.reason;
|
||||
const isAudio = idx === 0;
|
||||
|
||||
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
|
||||
if (isAudio) {
|
||||
errors.audioOnlyError = errorMsg;
|
||||
} else {
|
||||
errors.videoOnlyError = errorMsg;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.audioOnlyError && errors.videoOnlyError) {
|
||||
errors.audioAndVideoError = errorMsg;
|
||||
}
|
||||
|
||||
return tracks;
|
||||
});
|
||||
}
|
||||
|
||||
// Hide the permissions prompt/overlay as soon as the tracks are
|
||||
// created. Don't wait for the connection to be made, since in some
|
||||
// cases, when auth is required, for instance, that won't happen until
|
||||
// the user inputs their credentials, but the dialog would be
|
||||
// overshadowed by the overlay.
|
||||
// Hide the permissions prompt/overlay as soon as the tracks are created. Don't wait for the connection to
|
||||
// be established, as in some cases like when auth is required, connection won't be established until the user
|
||||
// inputs their credentials, but the dialog would be overshadowed by the overlay.
|
||||
tryCreateLocalTracks.then(tracks => {
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
|
||||
@@ -810,43 +799,51 @@ export default {
|
||||
const initialOptions = {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: getStartWithAudioMuted(state)
|
||||
|| isUserInteractionRequiredForUnmute(state),
|
||||
startWithVideoMuted: getStartWithVideoMuted(state)
|
||||
|| isUserInteractionRequiredForUnmute(state)
|
||||
startWithAudioMuted: getStartWithAudioMuted(state) || isUserInteractionRequiredForUnmute(state),
|
||||
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
|
||||
};
|
||||
|
||||
this.roomName = roomName;
|
||||
|
||||
try {
|
||||
// Initialize the device list first. This way, when creating tracks
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
// cases where the exact ID match is no longer available, such as
|
||||
// when the camera device has switched USB ports.
|
||||
// when in startSilent mode we want to start with audio muted
|
||||
// Initialize the device list first. This way, when creating tracks based on preferred devices, loose label
|
||||
// matching can be done in cases where the exact ID match is no longer available, such as -
|
||||
// 1. When the camera device has switched USB ports.
|
||||
// 2. When in startSilent mode we want to start with audio muted
|
||||
await this._initDeviceList();
|
||||
} catch (error) {
|
||||
logger.warn('initial device list initialization failed', error);
|
||||
}
|
||||
|
||||
const handleStartAudioMuted = (options, tracks) => {
|
||||
if (options.startWithAudioMuted) {
|
||||
// Filter out the local tracks based on various config options, i.e., when user joins muted or is muted by
|
||||
// focus. However, audio track will always be created even though it is not added to the conference since we
|
||||
// want audio related features (noisy mic, talk while muted, etc.) to work even if the mic is muted.
|
||||
const handleInitialTracks = (options, tracks) => {
|
||||
let localTracks = tracks;
|
||||
|
||||
// No local tracks are added when user joins as a visitor.
|
||||
if (iAmVisitor(state)) {
|
||||
return [];
|
||||
}
|
||||
if (options.startWithAudioMuted || room?.isStartAudioMuted()) {
|
||||
// Always add the track on Safari because of a known issue where audio playout doesn't happen
|
||||
// if the user joins audio and video muted, i.e., if there is no local media capture.
|
||||
if (browser.isWebKitBased()) {
|
||||
this.muteAudio(true, true);
|
||||
} else {
|
||||
return tracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
}
|
||||
if (room?.isStartVideoMuted()) {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
|
||||
}
|
||||
|
||||
return tracks;
|
||||
return localTracks;
|
||||
};
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
_connectionPromise = connect(roomName).then(c => {
|
||||
// we want to initialize it early, in case of errors to be able
|
||||
// to gather logs
|
||||
// We want to initialize it early, in case of errors to be able to gather logs.
|
||||
APP.connection = c;
|
||||
|
||||
return c;
|
||||
@@ -859,48 +856,28 @@ export default {
|
||||
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
|
||||
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
const tracks = await tryCreateLocalTracks;
|
||||
const localTracks = await tryCreateLocalTracks;
|
||||
|
||||
// Initialize device list a second time to ensure device labels
|
||||
// get populated in case of an initial gUM acceptance; otherwise
|
||||
// they may remain as empty strings.
|
||||
// Initialize device list a second time to ensure device labels get populated in case of an initial gUM
|
||||
// acceptance; otherwise they may remain as empty strings.
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
return APP.store.dispatch(initPrejoin(tracks, errors));
|
||||
return APP.store.dispatch(initPrejoin(localTracks, errors));
|
||||
}
|
||||
|
||||
logger.debug('Prejoin screen no longer displayed at the time when tracks were created');
|
||||
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
|
||||
let localTracks = handleStartAudioMuted(initialOptions, tracks);
|
||||
|
||||
// In case where gUM is slow and resolves after the startAudio/VideoMuted coming from jicofo, we can be
|
||||
// join unmuted even though jicofo had instruct us to mute, so let's respect that before passing the tracks
|
||||
if (!browser.isWebKitBased()) {
|
||||
if (room?.isStartAudioMuted()) {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
}
|
||||
|
||||
if (room?.isStartVideoMuted()) {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.VIDEO);
|
||||
}
|
||||
|
||||
// Do not add the tracks if the user has joined the call as a visitor.
|
||||
if (iAmVisitor(state)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this._setLocalAudioVideoStreams(localTracks);
|
||||
return this._setLocalAudioVideoStreams(handleInitialTracks(initialOptions, localTracks));
|
||||
}
|
||||
|
||||
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
|
||||
|
||||
this._initDeviceList(true);
|
||||
|
||||
return this.startConference(con, handleStartAudioMuted(initialOptions, tracks));
|
||||
return this.startConference(con, handleInitialTracks(initialOptions, tracks));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1073,7 +1050,7 @@ export default {
|
||||
*/
|
||||
muteVideo(mute, showUI = true) {
|
||||
if (this.videoSwitchInProgress) {
|
||||
console.warn('muteVideo - unable to perform operations while video switch is in progress');
|
||||
logger.warn('muteVideo - unable to perform operations while video switch is in progress');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -74,10 +74,6 @@
|
||||
a:active {
|
||||
color: black;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-corner {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ PODS:
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (2.3.0)
|
||||
- JitsiWebRTC (111.0.1)
|
||||
- JitsiWebRTC (111.0.2)
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
@@ -725,7 +725,7 @@ SPEC CHECKSUMS:
|
||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
|
||||
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
|
||||
JitsiWebRTC: 9619c1f71cc16eeca76df68aa2d213c6d63274a8
|
||||
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
|
||||
@@ -98,7 +98,6 @@ platform :ios do
|
||||
demo_account_required: false,
|
||||
distribute_external: true,
|
||||
groups: ENV["JITSI_BETA_TESTING_GROUPS"],
|
||||
reject_build_waiting_for_review: true,
|
||||
uses_non_exempt_encryption: false
|
||||
)
|
||||
|
||||
|
||||
@@ -870,9 +870,11 @@
|
||||
"lookGood": "Your microphone is working properly",
|
||||
"or": "or",
|
||||
"premeeting": "Pre meeting",
|
||||
"proceedAnyway": "Proceed anyway",
|
||||
"screenSharingError": "Screen sharing error:",
|
||||
"showScreen": "Enable pre meeting screen",
|
||||
"startWithPhone": "Start with phone audio",
|
||||
"unsafeRoomConsent": "I understand the risks, I want to join the meeting",
|
||||
"videoOnlyError": "Video error:",
|
||||
"videoTrackError": "Could not create video track.",
|
||||
"viewAllNumbers": "view all numbers"
|
||||
@@ -974,8 +976,14 @@
|
||||
"security": {
|
||||
"about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
|
||||
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
|
||||
"insecureRoomNameWarning": "The room name is unsafe. Unwanted participants may join your conference. Consider securing your meeting using the security button.",
|
||||
"title": "Security Options"
|
||||
"insecureRoomNameWarningNative": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting ",
|
||||
"insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
|
||||
"title": "Security Options",
|
||||
"unsafeRoomActions": {
|
||||
"meeting": "Consider securing your meeting using the security button.",
|
||||
"prejoin": "Consider using a more unique meeting name.",
|
||||
"welcome": "Consider using a more unique meeting name, or pick one of the suggestions."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"audio": "Audio",
|
||||
@@ -1150,6 +1158,7 @@
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Raise your hand",
|
||||
"reactions": "Reactions",
|
||||
"reactionsMenu": "Reactions menu",
|
||||
"recording": "Toggle recording",
|
||||
"remoteMute": "Mute participant",
|
||||
@@ -1247,6 +1256,7 @@
|
||||
"reactionLike": "Send thumbs up reaction",
|
||||
"reactionSilence": "Send silence reaction",
|
||||
"reactionSurprised": "Send surprised reaction",
|
||||
"reactions": "Reactions",
|
||||
"security": "Security options",
|
||||
"selectBackground": "Select background",
|
||||
"shareRoom": "Invite someone",
|
||||
|
||||
@@ -113,6 +113,8 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
|
||||
import { setTileView, toggleTileView } from '../../react/features/video-layout/actions.any';
|
||||
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality/actions';
|
||||
import { toggleWhiteboard } from '../../react/features/whiteboard/actions.any';
|
||||
import { WhiteboardStatus } from '../../react/features/whiteboard/types';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import {
|
||||
@@ -833,6 +835,9 @@ function initCommands() {
|
||||
} else {
|
||||
logger.error(' End Conference not supported');
|
||||
}
|
||||
},
|
||||
'toggle-whiteboard': () => {
|
||||
APP.store.dispatch(toggleWhiteboard());
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
@@ -2014,6 +2019,20 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) if whiteboard state is
|
||||
* changed.
|
||||
*
|
||||
* @param {WhiteboardStatus} status - The new whiteboard status.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyWhiteboardStatusChanged(status: WhiteboardStatus) {
|
||||
this._sendEvent({
|
||||
name: 'whiteboard-status-changed',
|
||||
status
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
19
modules/API/external/external_api.js
vendored
19
modules/API/external/external_api.js
vendored
@@ -90,7 +90,8 @@ const commands = {
|
||||
toggleSubtitles: 'toggle-subtitles',
|
||||
toggleTileView: 'toggle-tile-view',
|
||||
toggleVirtualBackgroundDialog: 'toggle-virtual-background',
|
||||
toggleVideo: 'toggle-video'
|
||||
toggleVideo: 'toggle-video',
|
||||
toggleWhiteboard: 'toggle-whiteboard'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -154,7 +155,8 @@ const events = {
|
||||
'subject-change': 'subjectChange',
|
||||
'suspend-detected': 'suspendDetected',
|
||||
'tile-view-changed': 'tileViewChanged',
|
||||
'toolbar-button-clicked': 'toolbarButtonClicked'
|
||||
'toolbar-button-clicked': 'toolbarButtonClicked',
|
||||
'whiteboard-status-changed': 'whiteboardStatusChanged'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -314,6 +316,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @param {string} [options.e2eeKey] - The key used for End-to-End encryption.
|
||||
* THIS IS EXPERIMENTAL.
|
||||
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
|
||||
* @param {string} [options.sandbox] - Sandbox directive for the created iframe, if desired.
|
||||
*/
|
||||
constructor(domain, ...args) {
|
||||
super();
|
||||
@@ -331,7 +334,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
devices,
|
||||
userInfo,
|
||||
e2eeKey,
|
||||
release
|
||||
release,
|
||||
sandbox = ''
|
||||
} = parseArguments(args);
|
||||
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
|
||||
|
||||
@@ -349,7 +353,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
},
|
||||
release
|
||||
});
|
||||
this._createIFrame(height, width, onload);
|
||||
this._createIFrame(height, width, onload, sandbox);
|
||||
this._transport = new Transport({
|
||||
backend: new PostMessageTransportBackend({
|
||||
postisOptions: {
|
||||
@@ -382,11 +386,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* parseSizeParam for format details.
|
||||
* @param {Function} onload - The function that will listen
|
||||
* for onload event.
|
||||
* @param {string} sandbox - Sandbox directive for the created iframe, if desired.
|
||||
* @returns {void}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_createIFrame(height, width, onload) {
|
||||
_createIFrame(height, width, onload, sandbox) {
|
||||
const frameName = `jitsiConferenceFrame${id}`;
|
||||
|
||||
this._frame = document.createElement('iframe');
|
||||
@@ -397,6 +402,10 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
this._frame.setAttribute('allowFullScreen', 'true');
|
||||
this._frame.style.border = 0;
|
||||
|
||||
if (sandbox) {
|
||||
this._frame.sandbox = sandbox;
|
||||
}
|
||||
|
||||
if (onload) {
|
||||
// waits for iframe resources to load
|
||||
// and fires event when it is done
|
||||
|
||||
722
package-lock.json
generated
722
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@@ -65,7 +65,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1626.0.0+a41aa571/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1631.0.0+f0dd4039/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -76,7 +76,7 @@
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-focus-lock": "2.5.1",
|
||||
"react-focus-lock": "2.9.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.68.6",
|
||||
@@ -140,6 +140,7 @@
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/react-linkify": "1.0.1",
|
||||
"@types/react-native": "0.68.9",
|
||||
"@types/react-native-keep-awake": "2.0.3",
|
||||
"@types/react-native-video": "5.0.14",
|
||||
"@types/react-redux": "7.1.24",
|
||||
"@types/react-window": "1.8.5",
|
||||
@@ -149,8 +150,8 @@
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@types/w3c-web-hid": "1.0.3",
|
||||
"@types/zxcvbn": "4.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.30.5",
|
||||
"@typescript-eslint/parser": "5.30.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.59.5",
|
||||
"@typescript-eslint/parser": "5.59.5",
|
||||
"babel-loader": "8.2.3",
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
@@ -170,8 +171,8 @@
|
||||
"sass": "1.26.8",
|
||||
"style-loader": "3.3.1",
|
||||
"traverse": "0.6.6",
|
||||
"ts-loader": "9.4.1",
|
||||
"typescript": "4.7.4",
|
||||
"ts-loader": "9.4.2",
|
||||
"typescript": "5.0.4",
|
||||
"unorm": "1.6.0",
|
||||
"webpack": "5.76.0",
|
||||
"webpack-bundle-analyzer": "4.4.2",
|
||||
@@ -179,7 +180,7 @@
|
||||
"webpack-dev-server": "4.7.3"
|
||||
},
|
||||
"overrides": {
|
||||
"strophe.js@1.6.0": {
|
||||
"strophe.js@1.5.0": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { setRoom } from '../base/conference/actions';
|
||||
import {
|
||||
configWillLoad,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection/actions';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
|
||||
import { createDesiredLocalTracks } from '../base/tracks/actions';
|
||||
import isInsecureRoomName from '../base/util/isInsecureRoomName';
|
||||
import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import {
|
||||
appendURLParam,
|
||||
@@ -20,14 +20,11 @@ import {
|
||||
parseURIString,
|
||||
toURLString
|
||||
} from '../base/util/uri';
|
||||
// @ts-ignore
|
||||
import { isPrejoinPageEnabled } from '../mobile/navigation/functions';
|
||||
import {
|
||||
goBackToRoot,
|
||||
navigateRoot
|
||||
// @ts-ignore
|
||||
} from '../mobile/navigation/rootNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
import { clearNotifications } from '../notifications/actions';
|
||||
|
||||
@@ -55,7 +52,7 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
|
||||
|
||||
// If the specified location (URI) does not identify a host, use the app's
|
||||
// default.
|
||||
if (!location || !location.host) {
|
||||
if (!location?.host) {
|
||||
const defaultLocation = parseURIString(getDefaultURL(getState));
|
||||
|
||||
if (location) {
|
||||
@@ -140,10 +137,14 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
|
||||
dispatch(setRoom(room));
|
||||
|
||||
if (room) {
|
||||
if (isInsecureRoomName(room)) {
|
||||
navigateRoot(screen.unsafeRoomWarning);
|
||||
|
||||
return;
|
||||
}
|
||||
dispatch(createDesiredLocalTracks());
|
||||
dispatch(clearNotifications());
|
||||
|
||||
// @ts-ignore
|
||||
const { hidePrejoin } = options;
|
||||
|
||||
if (!hidePrejoin && isPrejoinPageEnabled(getState())) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export function appNavigate(uri?: string) {
|
||||
|
||||
// If the specified location (URI) does not identify a host, use the app's
|
||||
// default.
|
||||
if (!location || !location.host) {
|
||||
if (!location?.host) {
|
||||
const defaultLocation = parseURIString(getDefaultURL(getState));
|
||||
|
||||
if (location) {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { IStateful } from '../base/app/types';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { isSupportedBrowser } from '../base/environment/environment';
|
||||
import { toState } from '../base/redux/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import Conference from '../conference/components/web/Conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking/functions';
|
||||
import UnsupportedDesktopBrowser from '../unsupported-browser/components/UnsupportedDesktopBrowser';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../base/devices/reducer';
|
||||
import '../base/premeeting/reducer';
|
||||
import '../base/tooltip/reducer';
|
||||
import '../e2ee/reducer';
|
||||
import '../face-landmarks/reducer';
|
||||
|
||||
@@ -20,6 +20,7 @@ import { ILoggingState } from '../base/logging/reducer';
|
||||
import { IMediaState } from '../base/media/reducer';
|
||||
import { INetInfoState } from '../base/net-info/reducer';
|
||||
import { IParticipantsState } from '../base/participants/reducer';
|
||||
import { IPreMeetingState } from '../base/premeeting/types';
|
||||
import { IResponsiveUIState } from '../base/responsive-ui/reducer';
|
||||
import { ISettingsState } from '../base/settings/reducer';
|
||||
import { ISoundsState } from '../base/sounds/reducer';
|
||||
@@ -110,6 +111,7 @@ export interface IReduxState {
|
||||
'features/base/net-info': INetInfoState;
|
||||
'features/base/no-src-data': INoSrcDataState;
|
||||
'features/base/participants': IParticipantsState;
|
||||
'features/base/premeeting': IPreMeetingState;
|
||||
'features/base/responsive-ui': IResponsiveUIState;
|
||||
'features/base/settings': ISettingsState;
|
||||
'features/base/sounds': ISoundsState;
|
||||
|
||||
@@ -27,13 +27,13 @@ export function cancelLogin() {
|
||||
// a reaction to CONNECTION_FAILED). Since the
|
||||
// app/user is going to navigate to WelcomePage, the SDK
|
||||
// clients/consumers need an event.
|
||||
const { error, passwordRequired }
|
||||
const { error = { recoverable: undefined }, passwordRequired }
|
||||
= getState()['features/base/connection'];
|
||||
|
||||
passwordRequired
|
||||
&& dispatch(
|
||||
connectionFailed(
|
||||
passwordRequired, // @ts-ignore
|
||||
passwordRequired,
|
||||
set(error, 'recoverable', false) as any));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
export { default as LoginDialog } from './native/LoginDialog';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
export { default as WaitForOwnerDialog } from './native/WaitForOwnerDialog';
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
openLoginDialog,
|
||||
openWaitForOwnerDialog,
|
||||
stopWaitForOwner,
|
||||
waitForOwner } from './actions.native'; // @ts-ignore
|
||||
waitForOwner } from './actions.native';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
||||
|
||||
@@ -292,7 +292,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
if (!config || !config.hosts) {
|
||||
if (!config?.hosts) {
|
||||
logger.warn('Wrong configuration, missing hosts.');
|
||||
|
||||
return;
|
||||
@@ -300,7 +300,8 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
|
||||
if (!vnode) {
|
||||
// this is redirecting back to main, lets restore config
|
||||
// no point of updating disableFocus, we can skip the initial iq to jicofo
|
||||
// not updating disableFocus, as if the room capacity is full the promotion to the main room will fail
|
||||
// and the visitor will be redirected back to a vnode from jicofo
|
||||
if (config.oldConfig && username) {
|
||||
return {
|
||||
hosts: {
|
||||
@@ -310,6 +311,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
focusUserJid: focusJid,
|
||||
disableLocalStats: false,
|
||||
bosh: config.oldConfig.bosh && appendURLParam(config.oldConfig.bosh, 'customusername', username),
|
||||
p2p: config.oldConfig.p2p,
|
||||
websocket: config.oldConfig.websocket
|
||||
&& appendURLParam(config.oldConfig.websocket, 'customusername', username),
|
||||
oldConfig: undefined // clears it up
|
||||
@@ -326,6 +328,7 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
},
|
||||
focusUserJid: config.focusUserJid,
|
||||
bosh: config.bosh,
|
||||
p2p: config.p2p,
|
||||
websocket: config.websocket
|
||||
};
|
||||
|
||||
@@ -341,6 +344,10 @@ export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
disableFocus: true, // This flag disables sending the initial conference request
|
||||
disableLocalStats: true,
|
||||
bosh: config.bosh && appendURLParam(config.bosh, 'vnode', vnode),
|
||||
p2p: {
|
||||
...config.p2p,
|
||||
enabled: false
|
||||
},
|
||||
websocket: config.websocket && appendURLParam(config.websocket, 'vnode', vnode)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -289,6 +289,12 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
|
||||
dispatch(conferenceWillLeave(conference));
|
||||
};
|
||||
|
||||
if (!iAmVisitor(getState())) {
|
||||
// if a visitor is promoted back to main room and want to join an empty breakout room
|
||||
// we need to send iq to jicofo, so it can join/create the breakout room
|
||||
dispatch(overwriteConfig({ disableFocus: false }));
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface IJitsiConference {
|
||||
authenticateAndUpgradeRole: Function;
|
||||
avModerationApprove: Function;
|
||||
avModerationReject: Function;
|
||||
callUUID?: string;
|
||||
createVideoSIPGWSession: Function;
|
||||
dial: Function;
|
||||
disableAVModeration: Function;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
|
||||
@@ -18,6 +18,7 @@ type ToolbarButtons = 'camera' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
'reactions' |
|
||||
'recording' |
|
||||
'security' |
|
||||
'select-background' |
|
||||
@@ -169,6 +170,7 @@ export interface IConfig {
|
||||
}>;
|
||||
callDisplayName?: string;
|
||||
callFlowsEnabled?: boolean;
|
||||
callHandle?: string;
|
||||
callStatsConfigParams?: {
|
||||
additionalIDs?: {
|
||||
customerID?: string;
|
||||
@@ -190,6 +192,7 @@ export interface IConfig {
|
||||
};
|
||||
callStatsID?: string;
|
||||
callStatsSecret?: string;
|
||||
callUUID?: string;
|
||||
channelLastN?: number;
|
||||
chromeExtensionBanner?: {
|
||||
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;
|
||||
@@ -405,6 +408,7 @@ export interface IConfig {
|
||||
legalUrls?: {
|
||||
helpCentre: string;
|
||||
privacy: string;
|
||||
security: string;
|
||||
terms: string;
|
||||
};
|
||||
liveStreaming?: {
|
||||
|
||||
@@ -64,7 +64,7 @@ export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-bac
|
||||
/**
|
||||
* The toolbar buttons to show when in visitors mode.
|
||||
*/
|
||||
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'tileview' ];
|
||||
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'settings', 'tileview' ];
|
||||
|
||||
/**
|
||||
* The set of feature flags.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface IConfigState extends IConfig {
|
||||
domain: string;
|
||||
muc: string;
|
||||
};
|
||||
p2p?: object;
|
||||
websocket?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ type Props = {
|
||||
/**
|
||||
* Function to render a bottom sheet footer element, if necessary.
|
||||
*/
|
||||
renderFooter?: Function;
|
||||
renderFooter?: () => React.ReactNode;
|
||||
|
||||
/**
|
||||
* Function to render a bottom sheet header element, if necessary.
|
||||
@@ -109,9 +109,7 @@ class BottomSheet extends PureComponent<Props> {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<SlidingView // @ts-ignore
|
||||
accessibilityRole = 'menu'
|
||||
accessibilityViewIsModal = { true }
|
||||
<SlidingView
|
||||
onHide = { this._onCancel }
|
||||
position = 'bottom'
|
||||
show = { Boolean(showSlidingView) }>
|
||||
|
||||
@@ -18,7 +18,7 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
/**
|
||||
* The dialog descriptionKey.
|
||||
*/
|
||||
descriptionKey: string;
|
||||
descriptionKey?: string;
|
||||
|
||||
/**
|
||||
* An optional initial value to initiate the field with.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { randomInt } from '@jitsi/js-utils/random';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
@@ -11,7 +11,6 @@ import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.n
|
||||
import { hideDialog } from '../../actions';
|
||||
import logger from '../../logger';
|
||||
|
||||
// @ts-ignore
|
||||
import ConfirmDialog from './ConfirmDialog';
|
||||
|
||||
|
||||
@@ -39,9 +38,7 @@ interface IPageReloadDialogState {
|
||||
* Shows a warning message and counts down towards the re-load.
|
||||
*/
|
||||
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
|
||||
|
||||
// @ts-ignore
|
||||
_interval: IntervalID;
|
||||
_interval?: number;
|
||||
_timeoutSeconds: number;
|
||||
|
||||
/**
|
||||
@@ -105,7 +102,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
||||
_onCancel() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
clearInterval(this._interval);
|
||||
clearInterval(this._interval ?? 0);
|
||||
dispatch(appNavigate(undefined));
|
||||
|
||||
return true;
|
||||
@@ -145,7 +142,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
||||
_onReloadNow() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
clearInterval(this._interval);
|
||||
clearInterval(this._interval ?? 0);
|
||||
dispatch(reloadNow());
|
||||
|
||||
return true;
|
||||
@@ -200,8 +197,6 @@ function mapStateToProps(state: IReduxState) {
|
||||
const { fatalError } = state['features/overlay'];
|
||||
|
||||
const fatalConnectionError
|
||||
|
||||
// @ts-ignore
|
||||
= connectionError && isFatalJitsiConnectionError(connectionError);
|
||||
const fatalConfigError = fatalError === configError;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
|
||||
import { browser } from '../lib-jitsi-meet';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { isOnline } from '../net-info/selectors';
|
||||
|
||||
// @ts-ignore
|
||||
import JitsiMeetJS from './_';
|
||||
import {
|
||||
LIB_DID_DISPOSE,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IStateful } from '../app/types';
|
||||
import { ConnectionFailedError } from '../connection/actions.any';
|
||||
import { toState } from '../redux/functions';
|
||||
|
||||
// @ts-ignore
|
||||
@@ -91,7 +92,7 @@ export function isFatalJitsiConferenceError(error: Error | string) {
|
||||
* indicates a fatal {@code JitsiConnection} error, {@code true}; otherwise,
|
||||
* {@code false}.
|
||||
*/
|
||||
export function isFatalJitsiConnectionError(error: Error | string) {
|
||||
export function isFatalJitsiConnectionError(error: Error | string | ConnectionFailedError) {
|
||||
if (typeof error !== 'string') {
|
||||
error = error.name; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Re-export JitsiMeetJS from the library lib-jitsi-meet to (the other features
|
||||
// of) the project jitsi-meet.
|
||||
// @ts-ignore
|
||||
import JitsiMeetJS from './_';
|
||||
export { JitsiMeetJS as default };
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { SET_NETWORK_INFO } from '../net-info/actionTypes';
|
||||
import { PARTICIPANT_LEFT } from '../participants/actionTypes';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
// @ts-ignore
|
||||
import JitsiMeetJS from './_';
|
||||
import { LIB_WILL_INIT } from './actionTypes';
|
||||
import { disposeLib, initLib } from './actions';
|
||||
|
||||
@@ -90,7 +90,7 @@ export default class JitsiMeetLogStorage {
|
||||
storeLogsCallstats(logEntries: Array<string | any>) {
|
||||
const conference = getCurrentConference(this.getState());
|
||||
|
||||
if (!conference || !conference.isCallstatsEnabled()) {
|
||||
if (!conference?.isCallstatsEnabled()) {
|
||||
// Discard the logs if CallStats is not enabled.
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { format } from 'util';
|
||||
|
||||
// Some code adapted from https://github.com/houserater/react-native-lumberjack
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Logger, { getLogger as _getLogger } from '@jitsi/logger';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Logger from '@jitsi/logger';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
// @ts-ignore
|
||||
export { default as Audio } from './native/Audio';
|
||||
|
||||
// @ts-ignore
|
||||
export { default as Video } from './native/Video';
|
||||
|
||||
@@ -7,7 +7,6 @@ import { IReduxState, IStore } from '../../../../app/types';
|
||||
import { ASPECT_RATIO_WIDE } from '../../../responsive-ui/constants';
|
||||
import { storeVideoTransform } from '../../actions';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
@@ -125,12 +124,12 @@ class VideoTransform extends Component<IProps, IState> {
|
||||
/**
|
||||
* The gesture handler object.
|
||||
*/
|
||||
gestureHandlers: Object;
|
||||
gestureHandlers: any;
|
||||
|
||||
/**
|
||||
* The initial distance of the fingers on pinch start.
|
||||
*/
|
||||
initialDistance: number;
|
||||
initialDistance?: number;
|
||||
|
||||
/**
|
||||
* The initial position of the finger on touch start.
|
||||
@@ -234,8 +233,6 @@ class VideoTransform extends Component<IProps, IState> {
|
||||
videoTransformedViewContainerStyles,
|
||||
style
|
||||
] }
|
||||
|
||||
// @ts-ignore
|
||||
{ ...this.gestureHandlers.panHandlers }>
|
||||
<SafeAreaView
|
||||
edges = { [ 'bottom', 'left' ] }
|
||||
@@ -489,7 +486,7 @@ class VideoTransform extends Component<IProps, IState> {
|
||||
* @param {?Object | number} value - The value of the gesture, if any.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onGesture(type: string, value: any) {
|
||||
_onGesture(type: string, value?: any) {
|
||||
let transform;
|
||||
|
||||
switch (type) {
|
||||
@@ -600,7 +597,7 @@ class VideoTransform extends Component<IProps, IState> {
|
||||
this._onGesture('scale', scale);
|
||||
}
|
||||
} else if (gestureState.numberActiveTouches === 1
|
||||
&& isNaN(this.initialDistance)
|
||||
&& isNaN(this.initialDistance ?? 0)
|
||||
&& this._didMove(gestureState)) {
|
||||
// this is a move event
|
||||
const position = this._getTouchPosition(evt);
|
||||
@@ -623,11 +620,9 @@ class VideoTransform extends Component<IProps, IState> {
|
||||
*/
|
||||
_onPanResponderRelease() {
|
||||
if (this.lastTap && Date.now() - this.lastTap < TAP_TIMEOUT_MS) {
|
||||
// @ts-ignore
|
||||
this._onGesture('press');
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
delete this.initialDistance;
|
||||
this.initialPosition = {
|
||||
x: 0,
|
||||
|
||||
@@ -185,7 +185,7 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(track?: ITrack) {
|
||||
if (!track || !track.jitsiTrack) {
|
||||
if (!track?.jitsiTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ class Video extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(videoTrack?: Partial<ITrack>) {
|
||||
if (!videoTrack || !videoTrack.jitsiTrack) {
|
||||
if (!videoTrack?.jitsiTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import NetInfo from '@react-native-community/netinfo';
|
||||
import type { NetInfoState, NetInfoSubscription } from '@react-native-community/netinfo';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import { ONLINE_STATE_CHANGED_EVENT } from './events';
|
||||
@@ -15,7 +15,7 @@ export default class NetworkInfoService extends EventEmitter {
|
||||
/**
|
||||
* Stores the native subscription for future cleanup.
|
||||
*/
|
||||
_subscription: NetInfoSubscription;
|
||||
_subscription?: NetInfoSubscription;
|
||||
|
||||
/**
|
||||
* Converts library's structure to {@link NetworkInfo} used by jitsi-meet.
|
||||
@@ -26,10 +26,8 @@ export default class NetworkInfoService extends EventEmitter {
|
||||
*/
|
||||
static _convertNetInfoState(netInfoState: NetInfoState): NetworkInfo {
|
||||
return {
|
||||
// @ts-ignore
|
||||
isOnline: netInfoState.isInternetReachable,
|
||||
isOnline: Boolean(netInfoState.isInternetReachable),
|
||||
|
||||
// @ts-ignore
|
||||
details: netInfoState.details,
|
||||
networkType: netInfoState.type
|
||||
};
|
||||
@@ -51,8 +49,7 @@ export default class NetworkInfoService extends EventEmitter {
|
||||
*/
|
||||
start() {
|
||||
this._subscription = NetInfo.addEventListener(netInfoState => {
|
||||
// @ts-ignore
|
||||
this.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
|
||||
super.emit(ONLINE_STATE_CHANGED_EVENT, NetworkInfoService._convertNetInfoState(netInfoState));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,8 +61,6 @@ export default class NetworkInfoService extends EventEmitter {
|
||||
stop() {
|
||||
if (this._subscription) {
|
||||
this._subscription();
|
||||
|
||||
// @ts-ignore
|
||||
this._subscription = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ export type NetworkInfo = {
|
||||
* If {@link networkType} is {@link NetInfoStateType.cellular} then it may provide the info about the type of
|
||||
* cellular network.
|
||||
*/
|
||||
cellularGeneration?: NetInfoCellularGeneration;
|
||||
cellularGeneration?: NetInfoCellularGeneration | null;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the connection is expensive.
|
||||
*/
|
||||
isConnectionExpensive?: boolean;
|
||||
};
|
||||
} | null;
|
||||
|
||||
/**
|
||||
* Tells whether or not the internet is reachable.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { getGravatarURL } from '@jitsi/js-utils/avatar';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
@@ -200,7 +200,7 @@ export function getVirtualScreenshareParticipantByOwnerId(stateful: IStateful, i
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getNormalizedDisplayName(name: string) {
|
||||
if (!name || !name.trim()) {
|
||||
if (!name?.trim()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IReduxState } from '../../../app/types';
|
||||
import DialogPortal from '../../../toolbox/components/web/DialogPortal';
|
||||
import Drawer from '../../../toolbox/components/web/Drawer';
|
||||
import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
|
||||
import { isElementInTheViewport } from '../../ui/functions.web';
|
||||
import { getContextMenuStyle } from '../functions.web';
|
||||
|
||||
/**
|
||||
@@ -258,7 +259,16 @@ class Popover extends Component<IProps, IState> {
|
||||
'aria-labelledby': headingId,
|
||||
'aria-label': !headingId && headingLabel ? headingLabel : undefined
|
||||
}}
|
||||
returnFocus = { true }>
|
||||
returnFocus = {
|
||||
|
||||
// If we return the focus to an element outside the viewport the page will scroll to
|
||||
// this element which in our case is undesirable and the element is outside of the
|
||||
// viewport on purpose (to be hidden). For example if we return the focus to the toolbox
|
||||
// when it is hidden the whole page will move up in order to show the toolbox. This is
|
||||
// usually followed up with displaying the toolbox (because now it is on focus) but
|
||||
// because of the animation the whole scenario looks like jumping large video.
|
||||
isElementInTheViewport
|
||||
}>
|
||||
{this._renderContent()}
|
||||
</ReactFocusLock>
|
||||
</DialogPortal>
|
||||
@@ -304,7 +314,8 @@ class Popover extends Component<IProps, IState> {
|
||||
&& !this.props.overflowDrawer
|
||||
&& this._contextMenuRef
|
||||
&& this._contextMenuRef.contains
|
||||
&& !this._contextMenuRef.contains(event.target as Node)) {
|
||||
&& !this._contextMenuRef.contains(event.target as Node)
|
||||
&& !this._containerRef?.current?.contains(event.target as Node)) {
|
||||
this._onHideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
9
react/features/base/premeeting/actionTypes.ts
Normal file
9
react/features/base/premeeting/actionTypes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Type for setting the user's consent for unsafe room joining.
|
||||
*
|
||||
* {
|
||||
* type: SET_UNSAFE_ROOM_CONSENT,
|
||||
* consent: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_UNSAFE_ROOM_CONSENT = 'SET_UNSAFE_ROOM_CONSENT'
|
||||
17
react/features/base/premeeting/actions.web.ts
Normal file
17
react/features/base/premeeting/actions.web.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the consent of the user for joining the unsafe room.
|
||||
*
|
||||
* @param {boolean} consent - The user's consent.
|
||||
* @returns {{
|
||||
* type: SET_UNSAFE_ROOM_CONSENT,
|
||||
* consent: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setUnsafeRoomConsent(consent: boolean) {
|
||||
return {
|
||||
type: SET_UNSAFE_ROOM_CONSENT,
|
||||
consent
|
||||
};
|
||||
}
|
||||
@@ -12,9 +12,8 @@ import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/funct
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
|
||||
import ConnectionStatus from './ConnectionStatus';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import Preview from './Preview';
|
||||
import UnsafeRoomWarning from './UnsafeRoomWarning';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -58,6 +57,11 @@ interface IProps {
|
||||
*/
|
||||
showDeviceStatus: boolean;
|
||||
|
||||
/**
|
||||
* If should show unsafe room warning when joining.
|
||||
*/
|
||||
showUnsafeRoomWarning?: boolean;
|
||||
|
||||
/**
|
||||
* The 'Skip prejoin' button to be rendered (if any).
|
||||
*/
|
||||
@@ -163,6 +167,7 @@ const PreMeetingScreen = ({
|
||||
children,
|
||||
className,
|
||||
showDeviceStatus,
|
||||
showUnsafeRoomWarning,
|
||||
skipPrejoinButton,
|
||||
title,
|
||||
videoMuted,
|
||||
@@ -193,6 +198,7 @@ const PreMeetingScreen = ({
|
||||
{children}
|
||||
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
|
||||
{skipPrejoinButton}
|
||||
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
|
||||
{showDeviceStatus && <DeviceStatus />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import Checkbox from '../../../ui/components/web/Checkbox';
|
||||
import getUnsafeRoomText from '../../../util/getUnsafeRoomText.web';
|
||||
import { setUnsafeRoomConsent } from '../../actions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
warning: {
|
||||
backgroundColor: theme.palette.warning01,
|
||||
color: theme.palette.text04,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
padding: theme.spacing(3),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
marginBottom: theme.spacing(3)
|
||||
},
|
||||
consent: {
|
||||
padding: `0 ${theme.spacing(3)}`,
|
||||
'@media (max-width: 720px)': {
|
||||
marginBottom: theme.spacing(3)
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const UnsafeRoomWarning = () => {
|
||||
const { t } = useTranslation();
|
||||
const { classes } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const { unsafeRoomConsent } = useSelector((state: IReduxState) => state['features/base/premeeting']);
|
||||
const toggleConsent = useCallback(
|
||||
() => dispatch(setUnsafeRoomConsent(!unsafeRoomConsent))
|
||||
, [ unsafeRoomConsent, dispatch ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className = { classes.warning }>
|
||||
{getUnsafeRoomText(t, 'prejoin')}
|
||||
</div>
|
||||
<Checkbox
|
||||
checked = { unsafeRoomConsent }
|
||||
className = { classes.consent }
|
||||
label = { t('prejoin.unsafeRoomConsent') }
|
||||
onChange = { toggleConsent } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UnsafeRoomWarning;
|
||||
33
react/features/base/premeeting/reducer.web.ts
Normal file
33
react/features/base/premeeting/reducer.web.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import ReducerRegistry from '../redux/ReducerRegistry';
|
||||
|
||||
import { SET_UNSAFE_ROOM_CONSENT } from './actionTypes';
|
||||
import { IPreMeetingState } from './types';
|
||||
|
||||
|
||||
const DEFAULT_STATE: IPreMeetingState = {
|
||||
unsafeRoomConsent: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of known and used devices.
|
||||
*
|
||||
* @param {IDevicesState} state - The Redux state of the feature features/base/devices.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {IPreMeetingState}
|
||||
*/
|
||||
ReducerRegistry.register<IPreMeetingState>(
|
||||
'features/base/premeeting',
|
||||
(state = DEFAULT_STATE, action): IPreMeetingState => {
|
||||
switch (action.type) {
|
||||
case SET_UNSAFE_ROOM_CONSENT: {
|
||||
return {
|
||||
...state,
|
||||
unsafeRoomConsent: action.consent
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
3
react/features/base/premeeting/types.ts
Normal file
3
react/features/base/premeeting/types.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export interface IPreMeetingState {
|
||||
unsafeRoomConsent?: boolean;
|
||||
}
|
||||
@@ -1,5 +1,2 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
export { default as Container } from './native/Container';
|
||||
// @ts-ignore
|
||||
export { default as Text } from './native/Text';
|
||||
|
||||
@@ -42,7 +42,7 @@ interface IProps {
|
||||
/**
|
||||
* Style of the animated view.
|
||||
*/
|
||||
style: StyleType;
|
||||
style?: StyleType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import md5 from 'js-md5';
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
@@ -7,9 +5,7 @@ import { translate } from '../../../../base/i18n/functions';
|
||||
import { IconGear } from '../../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../../base/toolbox/components/AbstractButton';
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../../../../mobile/navigation/routes';
|
||||
import { SETTINGS_ENABLED } from '../../../flags/constants';
|
||||
import { getFeatureFlag } from '../../../flags/functions';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import statsEmitter from '../../../connection-indicator/statsEmitter';
|
||||
import { getLocalParticipant } from '../../participants/functions';
|
||||
import { isTestModeEnabled } from '../functions';
|
||||
@@ -10,7 +11,7 @@ import TestHint from './TestHint';
|
||||
/**
|
||||
* Defines the TestConnectionInfo's properties.
|
||||
*/
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The JitsiConference's connection state. It's the lib-jitsi-meet's event
|
||||
@@ -20,30 +21,30 @@ type Props = {
|
||||
* 'conference.connectionInterrupted'
|
||||
* 'conference.connectionRestored'.
|
||||
*/
|
||||
_conferenceConnectionState: string,
|
||||
_conferenceConnectionState: string;
|
||||
|
||||
/**
|
||||
* This will be a boolean converted to a string. The value will be 'true'
|
||||
* once the conference is joined (the XMPP MUC room to be specific).
|
||||
*/
|
||||
_conferenceJoinedState: string,
|
||||
_conferenceJoinedState: string;
|
||||
|
||||
/**
|
||||
* The local participant's ID. Required to be able to observe the local RTP
|
||||
* stats.
|
||||
*/
|
||||
_localUserId: string,
|
||||
_localUserId: string;
|
||||
|
||||
/**
|
||||
* The local participant's role.
|
||||
*/
|
||||
_localUserRole: string,
|
||||
_localUserRole: string;
|
||||
|
||||
/**
|
||||
* Indicates whether or not the test mode is currently on. Otherwise the
|
||||
* TestConnectionInfo component will not render.
|
||||
*/
|
||||
_testMode: boolean
|
||||
_testMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,15 +65,15 @@ type State = {
|
||||
/**
|
||||
* The local download RTP bitrate.
|
||||
*/
|
||||
download: number,
|
||||
download: number;
|
||||
|
||||
/**
|
||||
* The local upload RTP bitrate.
|
||||
*/
|
||||
upload: number
|
||||
}
|
||||
}
|
||||
}
|
||||
upload: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The component will expose some of the app state to the jitsi-meet-torture
|
||||
@@ -81,8 +82,7 @@ type State = {
|
||||
* this information, but there's no such option on React Native(maybe that's
|
||||
* a good thing).
|
||||
*/
|
||||
class TestConnectionInfo extends Component<Props, State> {
|
||||
_onStatsUpdated: Object => void;
|
||||
class TestConnectionInfo extends Component<IProps, State> {
|
||||
|
||||
/**
|
||||
* Initializes new <tt>TestConnectionInfo</tt> instance.
|
||||
@@ -90,7 +90,7 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onStatsUpdated = this._onStatsUpdated.bind(this);
|
||||
@@ -114,7 +114,8 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
_onStatsUpdated(stats = {}) {
|
||||
_onStatsUpdated(stats = { bitrate: { download: undefined,
|
||||
upload: undefined } }) {
|
||||
this.setState({
|
||||
stats: {
|
||||
bitrate: {
|
||||
@@ -143,7 +144,7 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
* @inheritdoc
|
||||
* returns {void}
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
if (prevProps._localUserId !== this.props._localUserId) {
|
||||
statsEmitter.unsubscribeToClientStats(
|
||||
prevProps._localUserId, this._onStatsUpdated);
|
||||
@@ -175,7 +176,7 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment accessible = { false } >
|
||||
<Fragment>
|
||||
<TestHint
|
||||
id = 'org.jitsi.meet.conference.connectionState'
|
||||
value = { this.props._conferenceConnectionState } />
|
||||
@@ -184,7 +185,7 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
value = { this.props._conferenceJoinedState } />
|
||||
<TestHint
|
||||
id = 'org.jitsi.meet.conference.grantModeratorAvailable'
|
||||
value = { true } />
|
||||
value = { 'true' } />
|
||||
<TestHint
|
||||
id = 'org.jitsi.meet.conference.localParticipantRole'
|
||||
value = { this.props._localUserRole } />
|
||||
@@ -202,9 +203,9 @@ class TestConnectionInfo extends Component<Props, State> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const conferenceJoined
|
||||
= Boolean(state['features/base/conference'].conference);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
@@ -212,8 +213,8 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
_conferenceConnectionState: state['features/testing'].connectionState,
|
||||
_conferenceJoinedState: conferenceJoined.toString(),
|
||||
_localUserId: localParticipant?.id,
|
||||
_localUserRole: localParticipant?.role,
|
||||
_localUserId: localParticipant?.id ?? '',
|
||||
_localUserRole: localParticipant?.role ?? '',
|
||||
_testMode: isTestModeEnabled(state)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
export default Component;
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyPress(event?: React.KeyboardEvent) {
|
||||
if (event?.key === 'Enter' || event?.key === ' ') {
|
||||
if (event?.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
this.props.onClick();
|
||||
}
|
||||
|
||||
@@ -33,17 +33,17 @@ interface IProps {
|
||||
/**
|
||||
* Icon of the button.
|
||||
*/
|
||||
icon: Function;
|
||||
icon?: Function;
|
||||
|
||||
/**
|
||||
* Flag used for disabling the small icon.
|
||||
*/
|
||||
iconDisabled: boolean;
|
||||
iconDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The ID of the icon button.
|
||||
*/
|
||||
iconId: string;
|
||||
iconId?: string;
|
||||
|
||||
/**
|
||||
* Popover close callback.
|
||||
@@ -65,6 +65,11 @@ interface IProps {
|
||||
*/
|
||||
styles?: Object;
|
||||
|
||||
/**
|
||||
* Whether the trigger for open/ close should be click or hover.
|
||||
*/
|
||||
trigger?: 'hover' | 'click';
|
||||
|
||||
/**
|
||||
* Whether or not the popover is visible.
|
||||
*/
|
||||
@@ -77,7 +82,7 @@ interface IProps {
|
||||
* @param {Object} props - Component's props.
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
export default function ToolboxButtonWithIconPopup(props: IProps) {
|
||||
export default function ToolboxButtonWithPopup(props: IProps) {
|
||||
const {
|
||||
ariaControls,
|
||||
ariaExpanded,
|
||||
@@ -91,9 +96,29 @@ export default function ToolboxButtonWithIconPopup(props: IProps) {
|
||||
onPopoverOpen,
|
||||
popoverContent,
|
||||
styles,
|
||||
trigger,
|
||||
visible
|
||||
} = props;
|
||||
|
||||
if (!icon) {
|
||||
return (
|
||||
<div
|
||||
className = 'settings-button-container'
|
||||
style = { styles }>
|
||||
<Popover
|
||||
content = { popoverContent }
|
||||
headingLabel = { ariaLabel }
|
||||
onPopoverClose = { onPopoverClose }
|
||||
onPopoverOpen = { onPopoverOpen }
|
||||
position = 'top'
|
||||
trigger = { trigger }
|
||||
visible = { visible }>
|
||||
{children}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const iconProps: any = {};
|
||||
|
||||
if (iconDisabled) {
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -7,7 +9,7 @@ import { HIDE_TOOLTIP, SHOW_TOOLTIP } from './actionTypes';
|
||||
* Used as unique identifier for tooltip.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showTooltip(content: string) {
|
||||
export function showTooltip(content: string | ReactElement) {
|
||||
return {
|
||||
type: SHOW_TOOLTIP,
|
||||
content
|
||||
@@ -21,7 +23,7 @@ export function showTooltip(content: string) {
|
||||
* Used as unique identifier for tooltip.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function hideTooltip(content: string) {
|
||||
export function hideTooltip(content: string | ReactElement) {
|
||||
return {
|
||||
type: HIDE_TOOLTIP,
|
||||
content
|
||||
|
||||
@@ -16,7 +16,7 @@ const ANIMATION_DURATION = 0.2;
|
||||
interface IProps {
|
||||
children: ReactElement;
|
||||
containerClassName?: string;
|
||||
content: string;
|
||||
content: string | ReactElement;
|
||||
position?: TOOLTIP_POSITION;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
|
||||
@@ -10,8 +10,6 @@ import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen
|
||||
import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/functions';
|
||||
import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions';
|
||||
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { IStateful } from '../app/types';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import { getStartWithAudioMuted } from '../media/functions';
|
||||
import { toState } from '../redux/functions';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
@@ -94,10 +96,10 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
* Returns an object containing a promise which resolves with the created tracks and the errors resulting from that
|
||||
* process.
|
||||
*
|
||||
* @returns {Promise<JitsiLocalTrack>}
|
||||
* @returns {Promise<JitsiLocalTrack[]>}
|
||||
*
|
||||
* @todo Refactor to not use APP.
|
||||
*/
|
||||
@@ -106,7 +108,13 @@ export function createPrejoinTracks() {
|
||||
const initialDevices = [ 'audio' ];
|
||||
const requestedAudio = true;
|
||||
let requestedVideo = false;
|
||||
const { startAudioOnly, startWithAudioMuted, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
const { startAudioOnly, startWithVideoMuted } = APP.store.getState()['features/base/settings'];
|
||||
const startWithAudioMuted = getStartWithAudioMuted(APP.store.getState());
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
// spend much time displaying the overlay screen. If GUM is not resolved within 15 seconds it will
|
||||
// probably never resolve.
|
||||
const timeout = browser.isElectron() ? 15000 : 60000;
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
@@ -121,62 +129,66 @@ export function createPrejoinTracks() {
|
||||
requestedVideo = true;
|
||||
}
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
let tryCreateLocalTracks: any = Promise.resolve([]);
|
||||
|
||||
if (!requestedAudio && !requestedVideo) {
|
||||
// Resolve with no tracks
|
||||
tryCreateLocalTracks = Promise.resolve([]);
|
||||
} else {
|
||||
if (requestedAudio || requestedVideo) {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
firePermissionPromptIsShownEvent: true
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}, APP.store)
|
||||
.catch((err: Error) => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
.catch(async (err: Error) => {
|
||||
if (err.name === JitsiTrackErrors.TIMEOUT && !browser.isElectron()) {
|
||||
errors.audioAndVideoError = err;
|
||||
|
||||
// Try audio only...
|
||||
errors.audioAndVideoError = err;
|
||||
return [];
|
||||
}
|
||||
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
// Retry with separate gUM calls.
|
||||
const gUMPromises: any = [];
|
||||
const tracks: any = [];
|
||||
|
||||
return [];
|
||||
} else if (requestedVideo && !requestedAudio) {
|
||||
errors.videoOnlyError = err;
|
||||
if (requestedAudio) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.AUDIO ],
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
|
||||
return [];
|
||||
if (requestedVideo) {
|
||||
gUMPromises.push(createLocalTracksF({
|
||||
devices: [ MEDIA_TYPE.VIDEO ],
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
timeout
|
||||
}));
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(gUMPromises);
|
||||
let errorMsg;
|
||||
|
||||
results.forEach((result, idx) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
tracks.push(result.value[0]);
|
||||
} else {
|
||||
errorMsg = result.reason;
|
||||
const isAudio = idx === 0;
|
||||
|
||||
logger.error(`${isAudio ? 'Audio' : 'Video'} track creation failed with error ${errorMsg}`);
|
||||
if (isAudio) {
|
||||
errors.audioOnlyError = errorMsg;
|
||||
} else {
|
||||
errors.videoOnlyError = errorMsg;
|
||||
}
|
||||
logger.error('Should never happen');
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedAudio) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.audioOnlyError = err;
|
||||
}
|
||||
});
|
||||
|
||||
// Try video only...
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
firePermissionPromptIsShownEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
// Log this just in case...
|
||||
if (!requestedVideo) {
|
||||
logger.error('The impossible just happened', err);
|
||||
}
|
||||
errors.videoOnlyError = err;
|
||||
if (errors.audioOnlyError && errors.videoOnlyError) {
|
||||
errors.audioAndVideoError = errorMsg;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
return tracks;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -138,7 +138,7 @@ function _handleNoDataFromSourceErrors(store: IStore, action: any) {
|
||||
|
||||
const track = getTrackByJitsiTrack(getState()['features/base/tracks'], action.track.jitsiTrack);
|
||||
|
||||
if (!track || !track.local) {
|
||||
if (!track?.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ export const font = {
|
||||
|
||||
export const shape = {
|
||||
borderRadius: 6,
|
||||
circleRadius: 50,
|
||||
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)'
|
||||
};
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
import { TouchableRipple } from 'react-native-paper';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import styles from '../../../react/components/native/styles';
|
||||
import { IIconButtonProps } from '../../../react/types';
|
||||
import { BUTTON_TYPES } from '../../constants.native';
|
||||
|
||||
@@ -5,6 +5,7 @@ import { keyframes } from 'tss-react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { isElementInTheViewport } from '../../functions.web';
|
||||
|
||||
import { DialogTransitionContext } from './DialogTransition';
|
||||
|
||||
@@ -184,7 +185,16 @@ const BaseDialog = ({
|
||||
onClick = { onBackdropClick } />
|
||||
<FocusLock
|
||||
className = { classes.focusLock }
|
||||
returnFocus = { true }>
|
||||
returnFocus = {
|
||||
|
||||
// If we return the focus to an element outside the viewport the page will scroll to
|
||||
// this element which in our case is undesirable and the element is outside of the
|
||||
// viewport on purpose (to be hidden). For example if we return the focus to the toolbox
|
||||
// when it is hidden the whole page will move up in order to show the toolbox. This is
|
||||
// usually followed up with displaying the toolbox (because now it is on focus) but
|
||||
// because of the animation the whole scenario looks like jumping large video.
|
||||
isElementInTheViewport
|
||||
}>
|
||||
<div
|
||||
aria-describedby = { description }
|
||||
aria-labelledby = { title ?? t(titleKey ?? '') }
|
||||
|
||||
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
|
||||
'& input[type="checkbox"]': {
|
||||
appearance: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
margin: 0,
|
||||
margin: '3px',
|
||||
font: 'inherit',
|
||||
color: theme.palette.icon03,
|
||||
width: '18px',
|
||||
|
||||
@@ -153,7 +153,7 @@ export interface IDialogTab<P> {
|
||||
labelKey: string;
|
||||
name: string;
|
||||
props?: IObject;
|
||||
propsUpdateFunction?: (tabState: IObject, newProps: P) => P;
|
||||
propsUpdateFunction?: (tabState: IObject, newProps: P, tabStates?: (IObject | undefined)[]) => P;
|
||||
submit?: Function;
|
||||
}
|
||||
|
||||
@@ -257,7 +257,8 @@ const DialogWithTabs = ({
|
||||
if (tabConfiguration.propsUpdateFunction) {
|
||||
return tabConfiguration.propsUpdateFunction(
|
||||
currentTabState ?? {},
|
||||
tabConfiguration.props ?? {});
|
||||
tabConfiguration.props ?? {},
|
||||
tabStates);
|
||||
}
|
||||
|
||||
return { ...currentTabState };
|
||||
|
||||
@@ -57,3 +57,28 @@ export const findAncestorByClass = (target: HTMLElement | null, cssClass: string
|
||||
|
||||
return findAncestorByClass(target.parentElement, cssClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the passed element is visible in the viewport.
|
||||
*
|
||||
* @param {Element} element - The element.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isElementInTheViewport(element?: Element): boolean {
|
||||
if (!element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!document.body.contains(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { innerHeight, innerWidth } = window;
|
||||
const { bottom, left, right, top } = element.getBoundingClientRect();
|
||||
|
||||
if (bottom <= innerHeight && top >= 0 && left >= 0 && right <= innerWidth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
1
react/features/base/util/contants.ts
Normal file
1
react/features/base/util/contants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const SECURITY_URL = 'https://jitsi.org/security/';
|
||||
31
react/features/base/util/getUnsafeRoomText.native.ts
Normal file
31
react/features/base/util/getUnsafeRoomText.native.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import Link from '../react/components/native/Link';
|
||||
import BaseTheme from '../ui/components/BaseTheme.native';
|
||||
|
||||
import { SECURITY_URL } from './contants';
|
||||
|
||||
/**
|
||||
* Gets the unsafe room text for the given context.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @param {Function} t - The translation function.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
|
||||
* @returns {Text}
|
||||
*/
|
||||
export default function getUnsafeRoomText(state: IReduxState, t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
|
||||
const securityUrl = state['features/base/config'].legalUrls?.security ?? SECURITY_URL;
|
||||
const link = React.createElement(Link, {
|
||||
url: securityUrl,
|
||||
children: 'here',
|
||||
style: { color: BaseTheme.palette.action01 } });
|
||||
|
||||
const options = {
|
||||
recommendAction: t(`security.unsafeRoomActions.${context}`)
|
||||
};
|
||||
|
||||
return React.createElement(Text, { children: [ t('security.insecureRoomNameWarningNative', options), link, '.' ] });
|
||||
}
|
||||
20
react/features/base/util/getUnsafeRoomText.web.ts
Normal file
20
react/features/base/util/getUnsafeRoomText.web.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { translateToHTML } from '../i18n/functions';
|
||||
|
||||
import { SECURITY_URL } from './contants';
|
||||
|
||||
/**
|
||||
* Gets the unsafe room text for the given context.
|
||||
*
|
||||
* @param {Function} t - The translation function.
|
||||
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function getUnsafeRoomText(t: Function, context: 'meeting' | 'prejoin' | 'welcome') {
|
||||
const securityUrl = APP.store.getState()['features/base/config'].legalUrls?.security ?? SECURITY_URL;
|
||||
const options = {
|
||||
recommendAction: t(`security.unsafeRoomActions.${context}`),
|
||||
securityUrl
|
||||
};
|
||||
|
||||
return translateToHTML(t, 'security.insecureRoomNameWarningWeb', options);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
|
||||
import { reportError } from './helpers';
|
||||
|
||||
@@ -6,8 +6,6 @@ import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
|
||||
import { refreshCalendar } from './actions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import UpdateCalendarEventDialog from './components/UpdateCalendarEventDialog.native';
|
||||
import { addLinkToCalendarEntry } from './functions.native';
|
||||
|
||||
@@ -40,8 +38,6 @@ export function updateCalendarEvent(eventId: string) {
|
||||
const roomName = generateRoomWithoutSeparator();
|
||||
|
||||
addLinkToCalendarEntry(getState(), eventId, `${defaultUrl}/${roomName}`)
|
||||
|
||||
// @ts-ignore
|
||||
.finally(() => {
|
||||
dispatch(refreshCalendar(false, false));
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ function _isDisplayableCalendarEntry(entry: { allDay: boolean; attendees: Object
|
||||
* @returns {void}
|
||||
*/
|
||||
export function _updateCalendarEntries(events: Array<Object>) {
|
||||
if (!events || !events.length) {
|
||||
if (!events?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
import { IStore } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
@@ -17,11 +16,8 @@ import {
|
||||
} from './constants';
|
||||
import { _updateCalendarEntries } from './functions.web';
|
||||
import logger from './logger';
|
||||
// @ts-ignore
|
||||
import { googleCalendarApi } from './web/googleCalendar';
|
||||
// @ts-ignore
|
||||
import { microsoftCalendarApi } from './web/microsoftCalendar';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
/**
|
||||
* Determines whether the calendar feature is enabled by the web.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Client } from '@microsoft/microsoft-graph-client';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import base64js from 'base64-js';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { findWindows } from 'windows-iana';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/* eslint-disable lines-around-comment, max-len */
|
||||
|
||||
import { IParticipant } from '../base/participants/types';
|
||||
import { navigate }
|
||||
// @ts-ignore
|
||||
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
|
||||
import { OPEN_CHAT } from './actionTypes';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import { Route, useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
@@ -9,7 +8,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { closeChat } from '../../actions.native';
|
||||
import AbstractChat, {
|
||||
type Props as AbstractProps,
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractChat';
|
||||
|
||||
@@ -18,24 +17,24 @@ import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
import styles from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
navigation: Object,
|
||||
navigation: any;
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
route: Object
|
||||
};
|
||||
route: Route<'', { privateMessageRecipient: { name: string; }; }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
*/
|
||||
class Chat extends AbstractChat<Props> {
|
||||
class Chat extends AbstractChat<IProps> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -51,17 +50,16 @@ class Chat extends AbstractChat<Props> {
|
||||
hasBottomTextInput = { true }
|
||||
hasTabNavigator = { true }
|
||||
style = { styles.chatContainer }>
|
||||
{/* @ts-ignore */}
|
||||
<MessageContainer messages = { _messages } />
|
||||
<MessageRecipient privateMessageRecipient = { privateMessageRecipient } />
|
||||
<ChatInputBar onSend = { this._onSendMessage } />
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
_onSendMessage: (string) => void;
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(props => {
|
||||
export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
const { _nbUnreadMessages, dispatch, navigation, t } = props;
|
||||
const unreadMessagesNr = _nbUnreadMessages > 0;
|
||||
|
||||
@@ -78,7 +76,9 @@ export default translate(connect(_mapStateToProps)(props => {
|
||||
)
|
||||
});
|
||||
|
||||
return () => isFocused && dispatch(closeChat());
|
||||
return () => {
|
||||
isFocused && dispatch(closeChat());
|
||||
};
|
||||
}, [ isFocused, _nbUnreadMessages ]);
|
||||
|
||||
return (
|
||||
@@ -1,5 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { CHAT_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
@@ -10,23 +11,23 @@ import { screen } from '../../../mobile/navigation/routes';
|
||||
import { getUnreadPollCount } from '../../../polls/functions';
|
||||
import { getUnreadCount } from '../../functions';
|
||||
|
||||
type Props = AbstractButtonProps & {
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled: boolean,
|
||||
_isPollsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* The unread message count.
|
||||
*/
|
||||
_unreadMessageCount: number
|
||||
};
|
||||
_unreadMessageCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an {@link AbstractButton} to open the chat screen on mobile.
|
||||
*/
|
||||
class ChatButton extends AbstractButton<Props, *> {
|
||||
class ChatButton extends AbstractButton<IProps> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.chat';
|
||||
icon = IconMessage;
|
||||
label = 'toolbar.chat';
|
||||
@@ -60,9 +61,9 @@ class ChatButton extends AbstractButton<Props, *> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {Object} ownProps - The properties explicitly passed to the component instance.
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
const { visible = enabled } = ownProps;
|
||||
@@ -25,10 +25,6 @@ class ChatPrivacyDialog extends AbstractChatPrivacyDialog {
|
||||
onSubmit = { this._onSendPrivateMessage } />
|
||||
);
|
||||
}
|
||||
|
||||
_onSendGroupMessage: () => boolean;
|
||||
|
||||
_onSendPrivateMessage: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps, _mapDispatchToProps)(ChatPrivacyDialog));
|
||||
@@ -1,63 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableHighlight, View } from 'react-native';
|
||||
import { Text, TouchableHighlight, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import { IconCloseLarge } from '../../../base/icons/svg';
|
||||
import { ILocalParticipant } from '../../../base/participants/types';
|
||||
import {
|
||||
setParams
|
||||
} from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../../actions.any';
|
||||
import AbstractMessageRecipient, {
|
||||
type Props as AbstractProps
|
||||
IProps as AbstractProps
|
||||
} from '../AbstractMessageRecipient';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Is lobby messaging active.
|
||||
*/
|
||||
isLobbyChatActive: boolean,
|
||||
isLobbyChatActive: boolean;
|
||||
|
||||
/**
|
||||
* The participant string for lobby chat messaging.
|
||||
*/
|
||||
lobbyMessageRecipient: Object,
|
||||
lobbyMessageRecipient?: {
|
||||
id: string;
|
||||
name: string;
|
||||
} | ILocalParticipant;
|
||||
|
||||
/**
|
||||
* The participant object set for private messaging.
|
||||
*/
|
||||
privateMessageRecipient: Object,
|
||||
};
|
||||
privateMessageRecipient: { name: string; };
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to implement the displaying of the recipient of the next message.
|
||||
*/
|
||||
class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
class MessageRecipient extends AbstractMessageRecipient<IProps> {
|
||||
|
||||
/**
|
||||
* Constructor of the component.
|
||||
*
|
||||
* @param {Props} props - The props of the component.
|
||||
* @param {IProps} props - The props of the component.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onResetPrivateMessageRecipient = this._onResetPrivateMessageRecipient.bind(this);
|
||||
this._onResetLobbyMessageRecipient = this._onResetLobbyMessageRecipient.bind(this);
|
||||
}
|
||||
|
||||
_onResetLobbyMessageRecipient: () => void;
|
||||
|
||||
/**
|
||||
* Resets lobby message recipient from state.
|
||||
*
|
||||
@@ -69,8 +71,6 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
dispatch(setLobbyChatActiveState(false));
|
||||
}
|
||||
|
||||
_onResetPrivateMessageRecipient: () => void;
|
||||
|
||||
/**
|
||||
* Resets private message recipient from state.
|
||||
*
|
||||
@@ -102,10 +102,10 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
|
||||
if (isLobbyChatActive) {
|
||||
return (
|
||||
<View style = { styles.lobbyMessageRecipientContainer }>
|
||||
<View style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.lobbyChatMessageTo', {
|
||||
recipient: lobbyMessageRecipient.name
|
||||
recipient: lobbyMessageRecipient?.name
|
||||
}) }
|
||||
</Text>
|
||||
<TouchableHighlight
|
||||
@@ -123,7 +123,7 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style = { styles.messageRecipientContainer }>
|
||||
<View style = { styles.messageRecipientContainer as ViewStyle }>
|
||||
<Text style = { styles.messageRecipientText }>
|
||||
{ t('chat.messageTo', {
|
||||
recipient: privateMessageRecipient.name
|
||||
@@ -145,9 +145,10 @@ class MessageRecipient extends AbstractMessageRecipient<Props> {
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
* @param {any} _ownProps - Component's own props.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { lobbyMessageRecipient, isLobbyChatActive } = state['features/chat'];
|
||||
|
||||
return {
|
||||
@@ -29,11 +29,11 @@ const styles = (theme: Theme) => {
|
||||
chatMessage: {
|
||||
display: 'inline-flex',
|
||||
padding: '12px',
|
||||
marginRight: '12px',
|
||||
backgroundColor: theme.palette.ui02,
|
||||
borderRadius: '4px 12px 12px 12px',
|
||||
maxWidth: '100%',
|
||||
marginTop: '4px',
|
||||
boxSizing: 'border-box' as const,
|
||||
|
||||
'&.privatemessage': {
|
||||
backgroundColor: theme.palette.support05
|
||||
@@ -62,7 +62,8 @@ const styles = (theme: Theme) => {
|
||||
replyWrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row' as const,
|
||||
alignItems: 'center'
|
||||
alignItems: 'center',
|
||||
maxWidth: '100%'
|
||||
},
|
||||
|
||||
messageContent: {
|
||||
@@ -126,7 +127,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { classes.chatMessageWrapper }
|
||||
className = { clsx(classes.chatMessageWrapper, type) }
|
||||
id = { this.props.message.messageId }
|
||||
tabIndex = { -1 }>
|
||||
<div
|
||||
|
||||
@@ -25,7 +25,11 @@ const useStyles = makeStyles()(theme => {
|
||||
messageGroup: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
maxWidth: '100%'
|
||||
maxWidth: '100%',
|
||||
|
||||
'&.remote': {
|
||||
maxWidth: 'calc(100% - 40px)' // 100% - avatar and margin
|
||||
}
|
||||
},
|
||||
|
||||
groupContainer: {
|
||||
|
||||
@@ -23,16 +23,17 @@ function KeyboardAvoider() {
|
||||
* @returns {void}
|
||||
*/
|
||||
function handleViewportResize() {
|
||||
const { innerWidth, visualViewport: { width, height } } = window;
|
||||
const { innerWidth, visualViewport } = window;
|
||||
const { width, height } = visualViewport ?? {};
|
||||
|
||||
// Compare the widths to make sure the {@code visualViewport} didn't resize due to zooming.
|
||||
if (width === innerWidth) {
|
||||
if (height < storedHeight) {
|
||||
setElementHeight(storedHeight - height);
|
||||
if (Number(height) < storedHeight) {
|
||||
setElementHeight(storedHeight - Number(height));
|
||||
} else {
|
||||
setElementHeight(0);
|
||||
}
|
||||
setStoredHeight(height);
|
||||
setStoredHeight(Number(height));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +41,10 @@ function KeyboardAvoider() {
|
||||
// Call the handler in case the keyboard is open when the {@code KeyboardAvoider} is mounted.
|
||||
handleViewportResize();
|
||||
|
||||
window.visualViewport.addEventListener('resize', handleViewportResize);
|
||||
window.visualViewport?.addEventListener('resize', handleViewportResize);
|
||||
|
||||
return () => {
|
||||
window.visualViewport.removeEventListener('resize', handleViewportResize);
|
||||
window.visualViewport?.removeEventListener('resize', handleViewportResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -28,6 +28,13 @@ const useStyles = makeStyles()(theme => {
|
||||
color: theme.palette.text01
|
||||
},
|
||||
|
||||
text: {
|
||||
maxWidth: 'calc(100% - 30px)',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'break-spaces',
|
||||
wordBreak: 'break-all'
|
||||
},
|
||||
|
||||
iconButton: {
|
||||
padding: '2px',
|
||||
|
||||
@@ -72,7 +79,7 @@ const MessageRecipient = ({
|
||||
className = { classes.container }
|
||||
id = 'chat-recipient'
|
||||
role = 'alert'>
|
||||
<span>
|
||||
<span className = { classes.text }>
|
||||
{t(_isLobbyChatActive ? 'chat.lobbyChatMessageTo' : 'chat.messageTo', {
|
||||
recipient: _isLobbyChatActive ? _lobbyMessageRecipient : _privateMessageRecipient
|
||||
})}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import aliases from 'react-emoji-render/data/aliases';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import AlertDialog from '../base/dialog/components/native/AlertDialog';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import isInsecureRoomName from '../../base/util/isInsecureRoomName';
|
||||
|
||||
interface IProps {
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* True of the label should be visible.
|
||||
*/
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* Function to be used to translate i18n labels.
|
||||
*/
|
||||
t: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
@@ -19,16 +17,16 @@ import {
|
||||
LabelHitSlop
|
||||
} from './constants';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Creates a function to be invoked when the onPress of the touchables are
|
||||
* triggered.
|
||||
*/
|
||||
createOnPress: Function
|
||||
createOnPress: Function;
|
||||
}
|
||||
|
||||
const AlwaysOnLabels = ({ createOnPress }: Props) => {
|
||||
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import {
|
||||
@@ -8,12 +6,14 @@ import {
|
||||
Platform,
|
||||
SafeAreaView,
|
||||
StatusBar,
|
||||
View
|
||||
View,
|
||||
ViewStyle
|
||||
} from 'react-native';
|
||||
import { withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { appNavigate } from '../../../app/actions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { getParticipantCount } from '../../../base/participants/functions';
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
ASPECT_RATIO_NARROW,
|
||||
ASPECT_RATIO_WIDE
|
||||
} from '../../../base/responsive-ui/constants';
|
||||
import { StyleType } from '../../../base/styles/functions.any';
|
||||
import TestConnectionInfo from '../../../base/testing/components/TestConnectionInfo';
|
||||
import { isCalendarEnabled } from '../../../calendar-sync/functions.native';
|
||||
import DisplayNameLabel from '../../../display-name/components/native/DisplayNameLabel';
|
||||
@@ -63,27 +64,27 @@ import styles from './styles';
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Conference}.
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Application's aspect ratio.
|
||||
*/
|
||||
_aspectRatio: Symbol,
|
||||
_aspectRatio: Symbol;
|
||||
|
||||
/**
|
||||
* Whether the audio only is enabled or not.
|
||||
*/
|
||||
_audioOnlyEnabled: boolean,
|
||||
_audioOnlyEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Branding styles for conference.
|
||||
*/
|
||||
_brandingStyles: Object,
|
||||
_brandingStyles: StyleType;
|
||||
|
||||
/**
|
||||
* Whether the calendar feature is enabled or not.
|
||||
*/
|
||||
_calendarEnabled: boolean,
|
||||
_calendarEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines that we are still connecting to the
|
||||
@@ -91,101 +92,101 @@ type Props = AbstractProps & {
|
||||
* joining the room. If truthy, then an activity/loading indicator will be
|
||||
* rendered.
|
||||
*/
|
||||
_connecting: boolean,
|
||||
_connecting: boolean;
|
||||
|
||||
/**
|
||||
* Set to {@code true} when the filmstrip is currently visible.
|
||||
*/
|
||||
_filmstripVisible: boolean,
|
||||
_filmstripVisible: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether fullscreen (immersive) mode is enabled.
|
||||
*/
|
||||
_fullscreenEnabled: boolean,
|
||||
_fullscreenEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines if the conference type is one to one.
|
||||
*/
|
||||
_isOneToOneConference: boolean,
|
||||
_isOneToOneConference: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines if the participants pane is open.
|
||||
*/
|
||||
_isParticipantsPaneOpen: boolean,
|
||||
_isParticipantsPaneOpen: boolean;
|
||||
|
||||
/**
|
||||
* The ID of the participant currently on stage (if any).
|
||||
*/
|
||||
_largeVideoParticipantId: string,
|
||||
_largeVideoParticipantId: string;
|
||||
|
||||
/**
|
||||
* Local participant's display name.
|
||||
*/
|
||||
_localParticipantDisplayName: string,
|
||||
_localParticipantDisplayName: string;
|
||||
|
||||
/**
|
||||
* Whether Picture-in-Picture is enabled.
|
||||
*/
|
||||
_pictureInPictureEnabled: boolean,
|
||||
_pictureInPictureEnabled: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the UI is reduced (to accommodate
|
||||
* smaller display areas).
|
||||
*/
|
||||
_reducedUI: boolean,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*/
|
||||
_toolboxVisible: boolean,
|
||||
_reducedUI: boolean;
|
||||
|
||||
/**
|
||||
* Indicates if we should auto-knock.
|
||||
*/
|
||||
_shouldEnableAutoKnock: boolean,
|
||||
_shouldEnableAutoKnock: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the lobby screen should be visible.
|
||||
*/
|
||||
_showLobby: boolean,
|
||||
_showLobby: boolean;
|
||||
|
||||
/**
|
||||
* Indicates whether the car mode is enabled.
|
||||
*/
|
||||
_startCarMode: boolean,
|
||||
_startCarMode: boolean;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is visible.
|
||||
*/
|
||||
_toolboxVisible: boolean;
|
||||
|
||||
/**
|
||||
* The redux {@code dispatch} function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* Object containing the safe area insets.
|
||||
*/
|
||||
insets: Object,
|
||||
insets: EdgeInsets;
|
||||
|
||||
/**
|
||||
* Default prop for navigating between screen components(React Navigation).
|
||||
*/
|
||||
navigation: Object
|
||||
};
|
||||
navigation: any;
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The label that is currently expanded.
|
||||
*/
|
||||
visibleExpandedLabel: ?string
|
||||
}
|
||||
visibleExpandedLabel?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The conference page of the mobile (i.e. React Native) application.
|
||||
*/
|
||||
class Conference extends AbstractConference<Props, State> {
|
||||
class Conference extends AbstractConference<IProps, State> {
|
||||
/**
|
||||
* Timeout ref.
|
||||
*/
|
||||
_expandedLabelTimeout: Object;
|
||||
_expandedLabelTimeout: any;
|
||||
|
||||
/**
|
||||
* Initializes a new Conference instance.
|
||||
@@ -193,14 +194,14 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
visibleExpandedLabel: undefined
|
||||
};
|
||||
|
||||
this._expandedLabelTimeout = React.createRef();
|
||||
this._expandedLabelTimeout = React.createRef<number>();
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onClick = this._onClick.bind(this);
|
||||
@@ -235,7 +236,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: IProps) {
|
||||
const {
|
||||
_shouldEnableAutoKnock,
|
||||
_showLobby,
|
||||
@@ -267,7 +268,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
// Tear handling any hardware button presses for back navigation down.
|
||||
BackHandler.removeEventListener('hardwareBackPress', this._onHardwareBackPress);
|
||||
|
||||
clearTimeout(this._expandedLabelTimeout.current);
|
||||
clearTimeout(this._expandedLabelTimeout.current ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,8 +302,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_onClick: () => void;
|
||||
|
||||
/**
|
||||
* Changes the value of the toolboxVisible state, thus allowing us to switch
|
||||
* between Toolbox and Filmstrip and change their visibility.
|
||||
@@ -314,8 +313,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
this._setToolboxVisible(!this.props._toolboxVisible);
|
||||
}
|
||||
|
||||
_onHardwareBackPress: () => boolean;
|
||||
|
||||
/**
|
||||
* Handles a hardware button press for back navigation. Enters Picture-in-Picture mode
|
||||
* (if supported) or leaves the associated {@code Conference} otherwise.
|
||||
@@ -340,8 +337,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
return true;
|
||||
}
|
||||
|
||||
_createOnPress: (string) => void;
|
||||
|
||||
/**
|
||||
* Creates a function to be invoked when the onPress of the touchables are
|
||||
* triggered.
|
||||
@@ -350,7 +345,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* triggered.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_createOnPress(label) {
|
||||
_createOnPress(label: string) {
|
||||
return () => {
|
||||
const { visibleExpandedLabel } = this.state;
|
||||
|
||||
@@ -434,7 +429,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.toolboxAndFilmstripContainer }>
|
||||
style = { styles.toolboxAndFilmstripContainer as ViewStyle }>
|
||||
|
||||
<Captions onPress = { this._onClick } />
|
||||
|
||||
@@ -463,17 +458,17 @@ class Conference extends AbstractConference<Props, State> {
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = {
|
||||
_toolboxVisible
|
||||
(_toolboxVisible
|
||||
? styles.titleBarSafeViewColor
|
||||
: styles.titleBarSafeViewTransparent }>
|
||||
: styles.titleBarSafeViewTransparent) as ViewStyle }>
|
||||
<TitleBar _createOnPress = { this._createOnPress } />
|
||||
</SafeAreaView>
|
||||
<SafeAreaView
|
||||
pointerEvents = 'box-none'
|
||||
style = {
|
||||
_toolboxVisible
|
||||
(_toolboxVisible
|
||||
? [ styles.titleBarSafeViewTransparent, { top: this.props.insets.top + 50 } ]
|
||||
: styles.titleBarSafeViewTransparent
|
||||
: styles.titleBarSafeViewTransparent) as ViewStyle
|
||||
}>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
@@ -482,7 +477,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
</View>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { alwaysOnTitleBarStyles }>
|
||||
style = { alwaysOnTitleBarStyles as ViewStyle }>
|
||||
{/* eslint-disable-next-line react/jsx-no-bind */}
|
||||
<AlwaysOnLabels createOnPress = { this._createOnPress } />
|
||||
</View>
|
||||
@@ -532,7 +527,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_renderNotificationsContainer() {
|
||||
const notificationsStyle = {};
|
||||
const notificationsStyle: ViewStyle = {};
|
||||
|
||||
// In the landscape mode (wide) there's problem with notifications being
|
||||
// shadowed by the filmstrip rendered on the right. This makes the "x"
|
||||
@@ -559,8 +554,6 @@ class Conference extends AbstractConference<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
_setToolboxVisible: (boolean) => void;
|
||||
|
||||
/**
|
||||
* Dispatches an action changing the visibility of the {@link Toolbox}.
|
||||
*
|
||||
@@ -569,7 +562,7 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* {@code Toolbox} or {@code false} to hide it.
|
||||
* @returns {void}
|
||||
*/
|
||||
_setToolboxVisible(visible) {
|
||||
_setToolboxVisible(visible: boolean) {
|
||||
this.props.dispatch(setToolboxVisible(visible));
|
||||
}
|
||||
}
|
||||
@@ -578,10 +571,11 @@ class Conference extends AbstractConference<Props, State> {
|
||||
* Maps (parts of) the redux state to the associated {@code Conference}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {any} _ownProps - Component's own props.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen } = state['features/participants-pane'];
|
||||
const { aspectRatio, reducedUI } = state['features/base/responsive-ui'];
|
||||
const { backgroundColor } = state['features/dynamic-branding'];
|
||||
@@ -627,7 +621,7 @@ export default withSafeAreaInsets(connect(_mapStateToProps)(props => {
|
||||
return () => setPictureInPictureEnabled(false);
|
||||
}, [ isFocused ]);
|
||||
|
||||
return (
|
||||
return ( // @ts-ignore
|
||||
<Conference { ...props } />
|
||||
);
|
||||
}));
|
||||
@@ -1,26 +1,25 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme';
|
||||
|
||||
import { EXPANDED_LABELS } from './constants';
|
||||
|
||||
type Props = {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The selected label to show details.
|
||||
*/
|
||||
visibleExpandedLabel: ?string
|
||||
visibleExpandedLabel?: string;
|
||||
}
|
||||
|
||||
const ExpandedLabelPopup = ({ visibleExpandedLabel }: Props) => {
|
||||
const ExpandedLabelPopup = ({ visibleExpandedLabel }: IProps) => {
|
||||
if (visibleExpandedLabel) {
|
||||
const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel];
|
||||
const expandedLabel = EXPANDED_LABELS[visibleExpandedLabel as keyof typeof EXPANDED_LABELS];
|
||||
|
||||
if (expandedLabel) {
|
||||
const LabelComponent = expandedLabel.component || expandedLabel;
|
||||
const { props, alwaysOn } = expandedLabel || {};
|
||||
const LabelComponent = expandedLabel.component;
|
||||
|
||||
const { props, alwaysOn } = expandedLabel;
|
||||
const style = {
|
||||
top: alwaysOn ? BaseTheme.spacing[6] : BaseTheme.spacing[1]
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import ExpandedLabel, { Props as AbstractProps } from '../../../base/label/components/native/ExpandedLabel';
|
||||
|
||||
import { INSECURE_ROOM_NAME_LABEL_COLOR } from './styles';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* A react {@code Component} that implements an expanded label as tooltip-like
|
||||
* component to explain the meaning of the {@code InsecureRoomNameExpandedLabel}.
|
||||
*/
|
||||
class InsecureRoomNameExpandedLabel extends ExpandedLabel<Props> {
|
||||
/**
|
||||
* Returns the color this expanded label should be rendered with.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getColor() {
|
||||
return INSECURE_ROOM_NAME_LABEL_COLOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label specific text of this {@code ExpandedLabel}.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getLabel() {
|
||||
return this.props.t('security.insecureRoomNameWarning');
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(InsecureRoomNameExpandedLabel);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user