mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-09 16:20:19 +00:00
Compare commits
24 Commits
toolbar-ho
...
fix-p2p-co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ddab7464c | ||
|
|
9076fb3e4a | ||
|
|
d8079a4232 | ||
|
|
c992a8274c | ||
|
|
b5e059a0a9 | ||
|
|
0be3e2b103 | ||
|
|
ae138c1a15 | ||
|
|
0f8de50d26 | ||
|
|
ff7b6010bd | ||
|
|
270cdd017d | ||
|
|
f4cae6350b | ||
|
|
f0126a43f3 | ||
|
|
b5deb65815 | ||
|
|
6d9bbe0376 | ||
|
|
da634c211e | ||
|
|
61d96a5fd5 | ||
|
|
07e46b9399 | ||
|
|
3139111d36 | ||
|
|
205f88ec5a | ||
|
|
c8d6cdf6c8 | ||
|
|
7022243a95 | ||
|
|
b2ae72249d | ||
|
|
8ee6d179d5 | ||
|
|
bebcfa3fd7 |
2
Makefile
2
Makefile
@@ -122,7 +122,7 @@ deploy-local:
|
||||
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-excalidraw-dev deploy-face-landmarks
|
||||
$(WEBPACK_DEV_SERVER)
|
||||
|
||||
source-package:
|
||||
source-package: compile deploy
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html resources/*.txt fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp css/all.css source_package/jitsi-meet/css && \
|
||||
|
||||
@@ -47,7 +47,7 @@ dependencies {
|
||||
|
||||
implementation 'com.facebook.fresco:animated-gif:2.5.0'
|
||||
implementation 'com.dropbox.core:dropbox-core-sdk:4.0.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||
implementation 'com.squareup.duktape:duktape-android:1.3.0'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'androidx.startup:startup-runtime:1.1.0'
|
||||
|
||||
@@ -1767,7 +1767,7 @@ var config = {
|
||||
// // to control the performance.
|
||||
// userLimit: 25,
|
||||
// // The url for more info about the whiteboard and its usage limitations.
|
||||
// limitUrl: 'https://example.com/blog/whiteboard-limits,
|
||||
// limitUrl: 'https://example.com/blog/whiteboard-limits',
|
||||
// },
|
||||
|
||||
// The watchRTC initialize config params as described :
|
||||
|
||||
@@ -45,7 +45,7 @@ target 'JitsiMeetSDK' do
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
pod 'CocoaLumberjack', '3.7.2'
|
||||
pod 'CocoaLumberjack', '3.7.4'
|
||||
pod 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
end
|
||||
|
||||
@@ -70,7 +70,7 @@ target 'JitsiMeetSDKLite' do
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
pod 'CocoaLumberjack', '3.7.2'
|
||||
pod 'CocoaLumberjack', '3.7.4'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
@@ -10,9 +10,9 @@ PODS:
|
||||
- AppAuth/ExternalUserAgent (1.6.2):
|
||||
- AppAuth/Core
|
||||
- boost (1.76.0)
|
||||
- CocoaLumberjack (3.7.2):
|
||||
- CocoaLumberjack/Core (= 3.7.2)
|
||||
- CocoaLumberjack/Core (3.7.2)
|
||||
- CocoaLumberjack (3.7.4):
|
||||
- CocoaLumberjack/Core (= 3.7.4)
|
||||
- CocoaLumberjack/Core (3.7.4)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.72.14)
|
||||
- FBReactNativeSpec (0.72.14):
|
||||
@@ -453,7 +453,7 @@ PODS:
|
||||
- react-native-video/Video (6.0.0-alpha.11):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (124.0.0):
|
||||
- react-native-webrtc (124.0.1):
|
||||
- JitsiWebRTC (~> 124.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (13.8.7):
|
||||
@@ -597,7 +597,7 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- "amplitude-react-native (from `../node_modules/@amplitude/react-native`)"
|
||||
- boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`)
|
||||
- CocoaLumberjack (= 3.7.2)
|
||||
- CocoaLumberjack (= 3.7.4)
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
- FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`)
|
||||
@@ -829,7 +829,7 @@ SPEC CHECKSUMS:
|
||||
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
|
||||
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
|
||||
boost: 7dcd2de282d72e344012f7d6564d024930a6a440
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
CocoaLumberjack: 543c79c114dadc3b1aba95641d8738b06b05b646
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: d98eefb42c5a64cb28ef966bd9096c76770d8f24
|
||||
FBReactNativeSpec: 53d4eb00e8e1b6e987a3dd5906d2afe131cc54c8
|
||||
@@ -882,7 +882,7 @@ SPEC CHECKSUMS:
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
|
||||
react-native-webrtc: ae5f9793fc58138ecb5bdf0afe7cfdb3f09888ed
|
||||
react-native-webrtc: 8b9b90ab7ad5b77811a22a3c3ac1fe2a64737e29
|
||||
react-native-webview: 95464c1249cae28186d3b7c6f494c6a124b16160
|
||||
React-NativeModulesApple: 9b58b8f5c1d8ea68d0a63f569a9e786d01281ea6
|
||||
React-perflogger: daabc494c6328efc1784a4b49b8b74fca305d11c
|
||||
@@ -915,6 +915,6 @@ SPEC CHECKSUMS:
|
||||
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
|
||||
Yoga: c32e0be1a17f8f1f0e633a3122f7666441f52c82
|
||||
|
||||
PODFILE CHECKSUM: ec00682c7062a323dff24a3c3643ca0bbb420d01
|
||||
PODFILE CHECKSUM: 64167bdb1a26f5768c6bfc81ef60633b31abf8e5
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"hsb": "Hornjoserbšćina",
|
||||
"hu": "Magyar",
|
||||
"hy": "Հայերեն",
|
||||
"id": "Bahasa",
|
||||
"is": "Íslenska",
|
||||
"it": "Italiano",
|
||||
"ja": "日本語",
|
||||
|
||||
1559
lang/main-id.json
Normal file
1559
lang/main-id.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -100,7 +100,7 @@ import {
|
||||
} from '../../react/features/participants-pane/actions';
|
||||
import { getParticipantsPaneOpen, isForceMuted } from '../../react/features/participants-pane/functions';
|
||||
import { startLocalVideoRecording, stopLocalVideoRecording } from '../../react/features/recording/actions.any';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession, supportsLocalRecording } from '../../react/features/recording/functions';
|
||||
import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/features/screen-share/actions';
|
||||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||
@@ -629,6 +629,7 @@ function initCommands() {
|
||||
* @param { string } arg.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } arg.youtubeBroadcastID - The youtube broadcast ID.
|
||||
* @param { Object } arg.extraMetadata - Any extra metadata params for file recording.
|
||||
* @param { boolean } arg.transcription - Whether a transcription should be started or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
'start-recording': ({
|
||||
@@ -640,7 +641,8 @@ function initCommands() {
|
||||
rtmpBroadcastID,
|
||||
youtubeStreamKey,
|
||||
youtubeBroadcastID,
|
||||
extraMetadata = {}
|
||||
extraMetadata = {},
|
||||
transcription
|
||||
}) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
@@ -715,25 +717,33 @@ function initCommands() {
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey || rtmpStreamKey
|
||||
};
|
||||
} else {
|
||||
logger.error('Invalid recording mode provided');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isScreenshotCaptureEnabled(state, true, false)) {
|
||||
APP.store.dispatch(toggleScreenshotCaptureSummary(true));
|
||||
}
|
||||
conference.startRecording(recordingConfig);
|
||||
|
||||
// Start audio / video recording, if requested.
|
||||
if (typeof recordingConfig !== 'undefined') {
|
||||
conference.startRecording(recordingConfig);
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
APP.store.dispatch(setRequestingSubtitles(true, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops a recording or streaming in progress.
|
||||
*
|
||||
* @param {string} mode - `local`, `file` or `stream`.
|
||||
* @param {boolean} transcription - Whether the transcription needs to be stopped.
|
||||
* @returns {void}
|
||||
*/
|
||||
'stop-recording': mode => {
|
||||
'stop-recording': (mode, transcription) => {
|
||||
const state = APP.store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
@@ -743,6 +753,13 @@ function initCommands() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
APP.store.dispatch(setRequestingSubtitles(false, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: false
|
||||
});
|
||||
}
|
||||
|
||||
if (mode === 'local') {
|
||||
APP.store.dispatch(stopLocalVideoRecording());
|
||||
|
||||
@@ -1917,14 +1934,16 @@ class API {
|
||||
* @param {boolean} on - True if recording is on, false otherwise.
|
||||
* @param {string} mode - Stream or file or local.
|
||||
* @param {string} error - Error type or null if success.
|
||||
* @param {boolean} transcription - True if a transcription is being recorded, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyRecordingStatusChanged(on, mode, error) {
|
||||
notifyRecordingStatusChanged(on, mode, error, transcription) {
|
||||
this._sendEvent({
|
||||
name: 'recording-status-changed',
|
||||
on,
|
||||
mode,
|
||||
error
|
||||
error,
|
||||
transcription
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
6
modules/API/external/external_api.js
vendored
6
modules/API/external/external_api.js
vendored
@@ -1446,6 +1446,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @param { string } options.youtubeStreamKey - The youtube stream key.
|
||||
* @param { string } options.youtubeBroadcastID - The youtube broadcast ID.
|
||||
* @param {Object } options.extraMetadata - Any extra metadata params for file recording.
|
||||
* @param { boolean } arg.transcription - Whether a transcription should be started or not.
|
||||
* @returns {void}
|
||||
*/
|
||||
startRecording(options) {
|
||||
@@ -1456,10 +1457,11 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* Stops a recording or streaming session that is in progress.
|
||||
*
|
||||
* @param {string} mode - `file` or `stream`.
|
||||
* @param {boolean} transcription - Whether the transcription needs to be stopped.
|
||||
* @returns {void}
|
||||
*/
|
||||
stopRecording(mode) {
|
||||
this.executeCommand('stopRecording', mode);
|
||||
stopRecording(mode, transcription) {
|
||||
this.executeCommand('stopRecording', mode, transcription);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -60,7 +60,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/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1837.0.0+6bcc577a/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -99,7 +99,7 @@
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.11",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "124.0.0",
|
||||
"react-native-webrtc": "124.0.1",
|
||||
"react-native-webview": "13.8.7",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
@@ -7911,11 +7911,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -10237,9 +10237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@@ -12304,8 +12304,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-QczSqHYTgNuVcI4eXkTV7A52Jol7Bb+5TIGz4wJEe3rkF07r7HZwaRRIishLshzhRzmwZnRM3tsgzUBhR3R12Q==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1837.0.0+6bcc577a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-MF1uCujJIWEYbUa9lGF7B8GLxUUTZOINwHUJgPUCMLdZ2CbH9QYh/OpxjVsygUs1yoCUWmNv5aGOXYlAaSOYoQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -15980,9 +15980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-webrtc": {
|
||||
"version": "124.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.0.tgz",
|
||||
"integrity": "sha512-XG7/EAdzgpBOaHc7Jv856pV5aUV/cOBoSgoOmx/nib67VnbF21E/lHGDwlQ286yT5W0dxNtmhJ1P9EEhCLz05g==",
|
||||
"version": "124.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.1.tgz",
|
||||
"integrity": "sha512-ne1AXNvpa3yC31SkzrtsqHhq7fjp10QTcl2xCGkVDOhzZtaf/2+c35xs1ps5zjUTIsx+XKh3mjAN7DKqteFDvg==",
|
||||
"dependencies": {
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
@@ -24870,11 +24870,11 @@
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -26588,9 +26588,9 @@
|
||||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
@@ -28085,8 +28085,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-QczSqHYTgNuVcI4eXkTV7A52Jol7Bb+5TIGz4wJEe3rkF07r7HZwaRRIishLshzhRzmwZnRM3tsgzUBhR3R12Q==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1837.0.0+6bcc577a/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-MF1uCujJIWEYbUa9lGF7B8GLxUUTZOINwHUJgPUCMLdZ2CbH9QYh/OpxjVsygUs1yoCUWmNv5aGOXYlAaSOYoQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -30806,9 +30806,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "124.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.0.tgz",
|
||||
"integrity": "sha512-XG7/EAdzgpBOaHc7Jv856pV5aUV/cOBoSgoOmx/nib67VnbF21E/lHGDwlQ286yT5W0dxNtmhJ1P9EEhCLz05g==",
|
||||
"version": "124.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.1.tgz",
|
||||
"integrity": "sha512-ne1AXNvpa3yC31SkzrtsqHhq7fjp10QTcl2xCGkVDOhzZtaf/2+c35xs1ps5zjUTIsx+XKh3mjAN7DKqteFDvg==",
|
||||
"requires": {
|
||||
"base64-js": "1.5.1",
|
||||
"debug": "4.3.4",
|
||||
|
||||
@@ -66,7 +66,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/v1830.0.0+5a14bd43/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1837.0.0+6bcc577a/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -105,7 +105,7 @@
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.11",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "124.0.0",
|
||||
"react-native-webrtc": "124.0.1",
|
||||
"react-native-webview": "13.8.7",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
|
||||
@@ -19,3 +19,14 @@ export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the current environment is an ipad device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isIpadMobileBrowser() {
|
||||
|
||||
// @ts-ignore
|
||||
return isIosMobileBrowser() && Platform.isPad;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ export const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
|
||||
export const JitsiConferenceEvents = JitsiMeetJS.events.conference;
|
||||
export const JitsiConnectionErrors = JitsiMeetJS.errors.connection;
|
||||
export const JitsiConnectionEvents = JitsiMeetJS.events.connection;
|
||||
export const JitsiConnectionQualityEvents
|
||||
= JitsiMeetJS.events.connectionQuality;
|
||||
export const JitsiConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
|
||||
export const JitsiDetectionEvents = JitsiMeetJS.events.detection;
|
||||
export const JitsiE2ePingEvents = JitsiMeetJS.events.e2eping;
|
||||
export const JitsiMediaDevicesEvents = JitsiMeetJS.events.mediaDevices;
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
// @ts-ignore
|
||||
const { userAgent, maxTouchPoints, platform } = navigator;
|
||||
let OS = '';
|
||||
const { maxTouchPoints, platform, userAgent } = navigator;
|
||||
|
||||
let OS = '',
|
||||
isPad = false;
|
||||
|
||||
if (userAgent.match(/Android/i)) {
|
||||
OS = 'android';
|
||||
} else if (userAgent.match(/iP(ad|hone|od)/i) || (maxTouchPoints && maxTouchPoints > 2 && /MacIntel/.test(platform))) {
|
||||
OS = 'ios';
|
||||
} else if (userAgent.match(/iP(ad)/i)) {
|
||||
OS = 'ios';
|
||||
isPad = true;
|
||||
} else if (userAgent.match(/Mac(intosh| OS X)/i)) {
|
||||
OS = 'macos';
|
||||
} else if (userAgent.match(/Windows/i)) {
|
||||
@@ -18,6 +23,13 @@ if (userAgent.match(/Android/i)) {
|
||||
* Provides a minimal equivalent of react-native's Platform abstraction.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Returns a boolean which defines if device is an iPad.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isPad,
|
||||
|
||||
/**
|
||||
* The operating system on which the application is executing.
|
||||
*
|
||||
|
||||
@@ -35,6 +35,10 @@ const REDUCED_UI_THRESHOLD = 300;
|
||||
*/
|
||||
export function clientResized(clientWidth: number, clientHeight: number) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!clientWidth && !clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
let availableWidth = clientWidth;
|
||||
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { CONFERENCE_JOINED } from '../conference/actionTypes';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { clientResized } from './actions';
|
||||
@@ -27,6 +28,19 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
_appWillMount(store);
|
||||
break;
|
||||
|
||||
case CONFERENCE_JOINED: {
|
||||
const { clientHeight = 0, clientWidth = 0 } = store.getState()['features/base/responsive-ui'];
|
||||
|
||||
if (!clientHeight && !clientWidth) {
|
||||
const {
|
||||
innerHeight,
|
||||
innerWidth
|
||||
} = window;
|
||||
|
||||
store.dispatch(clientResized(innerWidth, innerHeight));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { isTrackStreamingStatusActive } from '../../connection-indicator/functions';
|
||||
import { VIDEO_CODEC } from '../../video-quality/constants';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
import { getParticipantById, isScreenShareParticipant } from '../participants/functions';
|
||||
import { getTrackByMediaTypeAndParticipant, getVideoTrackByParticipant } from '../tracks/functions';
|
||||
import {
|
||||
getLocalVideoTrack,
|
||||
getTrackByMediaTypeAndParticipant,
|
||||
getVideoTrackByParticipant
|
||||
} from '../tracks/functions';
|
||||
|
||||
/**
|
||||
* Indicates whether the test mode is enabled. When it's enabled
|
||||
@@ -51,6 +56,78 @@ export function isLargeVideoReceived({ getState }: IStore): boolean {
|
||||
return Boolean(videoTrack && !videoTrack.muted && isTrackStreamingStatusActive(videoTrack));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in AV1.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingAv1({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.AV1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in H.264.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingH264({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.H264) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP8.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp8({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the local video track is encoded in VP9.
|
||||
*
|
||||
* @param {IStore} store - The redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalCameraEncodingVp9({ getState }: IStore): boolean {
|
||||
const state = getState();
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localtrack = getLocalVideoTrack(tracks);
|
||||
|
||||
if (localtrack?.codec?.toLowerCase() === VIDEO_CODEC.VP9) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the last media event received for a remote video indicates that the video is playing, if not muted.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,10 @@ import { setConnectionState } from './actions';
|
||||
import {
|
||||
getRemoteVideoType,
|
||||
isLargeVideoReceived,
|
||||
isLocalCameraEncodingAv1,
|
||||
isLocalCameraEncodingH264,
|
||||
isLocalCameraEncodingVp8,
|
||||
isLocalCameraEncodingVp9,
|
||||
isRemoteVideoReceived,
|
||||
isTestModeEnabled
|
||||
} from './functions';
|
||||
@@ -86,6 +90,10 @@ function _bindTortureHelpers(store: IStore) {
|
||||
getJitsiMeetGlobalNS().testing = {
|
||||
getRemoteVideoType: getRemoteVideoType.bind(null, store),
|
||||
isLargeVideoReceived: isLargeVideoReceived.bind(null, store),
|
||||
isLocalCameraEncodingAv1: isLocalCameraEncodingAv1.bind(null, store),
|
||||
isLocalCameraEncodingH264: isLocalCameraEncodingH264.bind(null, store),
|
||||
isLocalCameraEncodingVp8: isLocalCameraEncodingVp8.bind(null, store),
|
||||
isLocalCameraEncodingVp9: isLocalCameraEncodingVp9.bind(null, store),
|
||||
isRemoteVideoReceived: isRemoteVideoReceived.bind(null, store)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
getTrackByJitsiTrack
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
import { ITrackOptions } from './types';
|
||||
import { ITrack, ITrackOptions } from './types';
|
||||
|
||||
/**
|
||||
* Add a given local track to the conference.
|
||||
@@ -450,6 +450,32 @@ export function trackAdded(track: any) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track's codec has been signaled to have been changed.
|
||||
*
|
||||
* @param {JitsiLocalTrack} track - JitsiLocalTrack instance.
|
||||
* @param {string} codec - The video codec.
|
||||
* @returns {{
|
||||
* type: TRACK_UPDATED,
|
||||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackCodecChanged(track: ITrack, codec: string): {
|
||||
track: {
|
||||
codec: string;
|
||||
jitsiTrack: any;
|
||||
};
|
||||
type: 'TRACK_UPDATED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
codec,
|
||||
jitsiTrack: track
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a track's muted state has been signaled to be
|
||||
* changed.
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface ITrackOptions {
|
||||
* any.
|
||||
*/
|
||||
export interface ITrack {
|
||||
codec: string;
|
||||
getOriginalStream: Function;
|
||||
isReceivingData: boolean;
|
||||
jitsiTrack: any;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
@@ -33,6 +33,19 @@ const BreakoutRooms = () => {
|
||||
.sort((p1, p2) => (p1?.name || '').localeCompare(p2?.name || ''));
|
||||
const showAddBreakoutRoom = useSelector(isAddBreakoutRoomButtonVisible);
|
||||
const showAutoAssign = useSelector(isAutoAssignParticipantsVisible);
|
||||
const renderListHeaderComponent = useMemo(() => (
|
||||
<>
|
||||
{ showAutoAssign && <AutoAssignButton /> }
|
||||
{ inBreakoutRoom && <LeaveBreakoutRoomButton /> }
|
||||
{
|
||||
isBreakoutRoomsSupported
|
||||
&& rooms.map(room => (<CollapsibleRoom
|
||||
key = { room.id }
|
||||
room = { room }
|
||||
roomId = { room.id } />))
|
||||
}
|
||||
</>
|
||||
), [ showAutoAssign, inBreakoutRoom, isBreakoutRoomsSupported, rooms ]);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
@@ -42,21 +55,7 @@ const BreakoutRooms = () => {
|
||||
|
||||
{ /* Fixes warning regarding nested lists */ }
|
||||
<FlatList
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
ListHeaderComponent = { () => (
|
||||
<>
|
||||
{ showAutoAssign && <AutoAssignButton /> }
|
||||
{ inBreakoutRoom && <LeaveBreakoutRoomButton /> }
|
||||
{
|
||||
isBreakoutRoomsSupported
|
||||
&& rooms.map(room => (<CollapsibleRoom
|
||||
key = { room.id }
|
||||
room = { room }
|
||||
roomId = { room.id } />))
|
||||
}
|
||||
</>
|
||||
) }
|
||||
ListHeaderComponent = { renderListHeaderComponent }
|
||||
data = { [] as ReadonlyArray<undefined> }
|
||||
keyExtractor = { keyExtractor }
|
||||
renderItem = { null }
|
||||
|
||||
@@ -2,12 +2,11 @@ import React, { useCallback } from 'react';
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { openHighlightDialog } from '../../../recording/actions.native';
|
||||
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
|
||||
import RecordingLabel from '../../../recording/components/native/RecordingLabel';
|
||||
import { getActiveSession } from '../../../recording/functions';
|
||||
import { isLiveStreamingRunning } from '../../../recording/functions';
|
||||
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
|
||||
|
||||
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
|
||||
@@ -30,8 +29,7 @@ interface IProps {
|
||||
|
||||
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
|
||||
const dispatch = useDispatch();
|
||||
const isStreaming = useSelector((state: IReduxState) =>
|
||||
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)));
|
||||
const isStreaming = useSelector(isLiveStreamingRunning);
|
||||
const openHighlightDialogCallback = useCallback(() =>
|
||||
dispatch(openHighlightDialog()), [ dispatch ]);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { IJitsiConference } from '../base/conference/reducer';
|
||||
import {
|
||||
JitsiConnectionQualityEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { trackCodecChanged } from '../base/tracks/actions.any';
|
||||
import { getLocalTracks } from '../base/tracks/functions.any';
|
||||
|
||||
/**
|
||||
* Contains all the callbacks to be notified when stats are updated.
|
||||
@@ -129,6 +131,10 @@ const statsEmitter = {
|
||||
codec: allUserCodecs[localUserId as keyof typeof allUserCodecs]
|
||||
});
|
||||
|
||||
modifiedLocalStats.codec
|
||||
&& Object.keys(modifiedLocalStats.codec).length
|
||||
&& this._updateLocalCodecs(modifiedLocalStats.codec);
|
||||
|
||||
this._emitStatsUpdate(localUserId, modifiedLocalStats);
|
||||
|
||||
// Get all the unique user ids from the framerate and resolution stats
|
||||
@@ -162,6 +168,33 @@ const statsEmitter = {
|
||||
|
||||
this._emitStatsUpdate(id, remoteUserStats);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the codec associated with the local tracks.
|
||||
* This is currently used for torture tests.
|
||||
*
|
||||
* @param {any} codecs - Codec information per local SSRC.
|
||||
* @returns {void}
|
||||
*/
|
||||
_updateLocalCodecs(codecs: any) {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const tracks = APP.store.getState()['features/base/tracks'];
|
||||
const localTracks = getLocalTracks(tracks);
|
||||
|
||||
for (const track of localTracks) {
|
||||
const ssrc = track.jitsiTrack?.getSsrc();
|
||||
|
||||
if (ssrc && Object.keys(codecs).find(key => Number(key) === ssrc)) {
|
||||
const codecsPerSsrc = codecs[ssrc];
|
||||
const codec = codecsPerSsrc.audio ?? codecsPerSsrc.video;
|
||||
|
||||
if (track.codec !== codec) {
|
||||
APP.store.dispatch(trackCodecChanged(track.jitsiTrack, codec));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
25
react/features/embed-meeting/hooks.ts
Normal file
25
react/features/embed-meeting/hooks.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
|
||||
import EmbedMeetingButton from './components/EmbedMeetingButton';
|
||||
|
||||
const embed = {
|
||||
key: 'embedmeeting',
|
||||
Content: EmbedMeetingButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the embed button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useEmbedButton() {
|
||||
const _isVpaasMeeting = useSelector(isVpaasMeeting);
|
||||
|
||||
if (!isMobileBrowser() && !_isVpaasMeeting) {
|
||||
return embed;
|
||||
}
|
||||
}
|
||||
24
react/features/etherpad/hooks.ts
Normal file
24
react/features/etherpad/hooks.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
import SharedDocumentButtonWeb from './components/SharedDocumentButton';
|
||||
|
||||
const etherpad = {
|
||||
key: 'etherpad',
|
||||
Content: SharedDocumentButtonWeb,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the etherpad button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useEtherpadButton() {
|
||||
const visible = useSelector((state: IReduxState) => Boolean(state['features/etherpad'].documentUrl));
|
||||
|
||||
if (visible) {
|
||||
return etherpad;
|
||||
}
|
||||
}
|
||||
23
react/features/feedback/hooks.web.ts
Normal file
23
react/features/feedback/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import FeedbackButtonWeb from './components/FeedbackButton.web';
|
||||
import { shouldSendJaaSFeedbackMetadata } from './functions.web';
|
||||
|
||||
const feedback = {
|
||||
key: 'feedback',
|
||||
Content: FeedbackButtonWeb,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the feedback button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useFeedbackButton() {
|
||||
const visible = useSelector(shouldSendJaaSFeedbackMetadata);
|
||||
|
||||
if (visible) {
|
||||
return feedback;
|
||||
}
|
||||
}
|
||||
25
react/features/keyboard-shortcuts/hooks.web.ts
Normal file
25
react/features/keyboard-shortcuts/hooks.web.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isMobileBrowser } from '../base/environment/utils';
|
||||
|
||||
import KeyboardShortcutsButton from './components/web/KeyboardShortcutsButton';
|
||||
import { areKeyboardShortcutsEnabled } from './functions';
|
||||
|
||||
const shortcuts = {
|
||||
key: 'shortcuts',
|
||||
Content: KeyboardShortcutsButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the keyboard shortcuts button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useKeyboardShortcutsButton() {
|
||||
const _areKeyboardShortcutsEnabled = useSelector(areKeyboardShortcutsEnabled);
|
||||
|
||||
if (!isMobileBrowser() && _areKeyboardShortcutsEnabled) {
|
||||
return shortcuts;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
const contentColumn = {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
paddingLeft: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
const notification = {
|
||||
@@ -14,9 +14,9 @@ const notification = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: 'auto',
|
||||
marginBottom: BaseTheme.spacing[2],
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
maxWidth: 400,
|
||||
paddingBottom: BaseTheme.spacing[2],
|
||||
paddingHorizontal: BaseTheme.spacing[2],
|
||||
maxWidth: 432,
|
||||
width: 'auto'
|
||||
};
|
||||
|
||||
@@ -42,20 +42,20 @@ export default {
|
||||
*/
|
||||
|
||||
contentContainer: {
|
||||
marginHorizontal: BaseTheme.spacing[2]
|
||||
paddingHorizontal: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
contentText: {
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[4],
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
paddingLeft: BaseTheme.spacing[5],
|
||||
paddingTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
contentTextTitle: {
|
||||
color: BaseTheme.palette.text04,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: BaseTheme.spacing[4],
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
paddingLeft: BaseTheme.spacing[5],
|
||||
paddingTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -90,17 +90,19 @@ export default {
|
||||
|
||||
iconContainer: {
|
||||
position: 'absolute',
|
||||
left: BaseTheme.spacing[2],
|
||||
top: 12
|
||||
},
|
||||
|
||||
btn: {
|
||||
marginLeft: BaseTheme.spacing[4]
|
||||
paddingLeft: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
btnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginLeft: BaseTheme.spacing[3]
|
||||
paddingLeft: BaseTheme.spacing[4],
|
||||
paddingTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
withToolbox: {
|
||||
|
||||
@@ -21,6 +21,13 @@ const ParticipantsPane = () => {
|
||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||
const keyExtractor
|
||||
= useCallback((e: undefined, i: number) => i.toString(), []);
|
||||
const renderListHeaderComponent = () => (
|
||||
<>
|
||||
<VisitorsList />
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList />
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
@@ -31,13 +38,7 @@ const ParticipantsPane = () => {
|
||||
<FlatList
|
||||
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
ListHeaderComponent = { () => (
|
||||
<>
|
||||
<VisitorsList />
|
||||
<LobbyParticipantList />
|
||||
<MeetingParticipantList />
|
||||
</>
|
||||
) }
|
||||
ListHeaderComponent = { renderListHeaderComponent }
|
||||
data = { [] as ReadonlyArray<undefined> }
|
||||
keyExtractor = { keyExtractor }
|
||||
renderItem = { null }
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { handleLobbyChatInitialized } from '../chat/actions.web';
|
||||
import { approveKnockingParticipant, rejectKnockingParticipant } from '../lobby/actions.web';
|
||||
|
||||
import ParticipantsPaneButton from './components/web/ParticipantsPaneButton';
|
||||
import { isParticipantsPaneEnabled } from './functions';
|
||||
|
||||
interface IDrawerParticipant {
|
||||
displayName?: string;
|
||||
participantID: string;
|
||||
}
|
||||
|
||||
const participants = {
|
||||
key: 'participants-pane',
|
||||
Content: ParticipantsPaneButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook used to create admit/reject lobby actions.
|
||||
*
|
||||
@@ -57,3 +66,16 @@ export function useParticipantDrawer(): [
|
||||
openDrawerForParticipant
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the participants pane button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useParticipantPaneButton() {
|
||||
const participantsPaneEnabled = useSelector(isParticipantsPaneEnabled);
|
||||
|
||||
if (participantsPaneEnabled) {
|
||||
return participants;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { editPoll } from '../../actions';
|
||||
import { isSubmitAnswerDisabled } from '../../functions';
|
||||
import AbstractPollAnswer, { AbstractProps } from '../AbstractPollAnswer';
|
||||
|
||||
import { chatStyles, dialogStyles } from './styles';
|
||||
import { dialogStyles, pollsStyles } from './styles';
|
||||
|
||||
const PollAnswer = (props: AbstractProps) => {
|
||||
const {
|
||||
@@ -39,17 +39,17 @@ const PollAnswer = (props: AbstractProps) => {
|
||||
t('polls.by', { name: localParticipant?.name })
|
||||
}
|
||||
</Text>
|
||||
<View style = { chatStyles.answerContent as ViewStyle }>
|
||||
<View style = { pollsStyles.answerContent as ViewStyle }>
|
||||
{
|
||||
poll.answers.map((answer, index: number) => (
|
||||
<View
|
||||
key = { index }
|
||||
style = { chatStyles.switchRow as ViewStyle } >
|
||||
style = { pollsStyles.switchRow as ViewStyle } >
|
||||
<Switch
|
||||
checked = { checkBoxStates[index] }
|
||||
disabled = { poll.saved }
|
||||
onChange = { state => setCheckbox(index, state) } />
|
||||
<Text style = { chatStyles.switchLabel as TextStyle }>
|
||||
<Text style = { pollsStyles.switchLabel as TextStyle }>
|
||||
{ answer.name }
|
||||
</Text>
|
||||
</View>
|
||||
@@ -58,7 +58,7 @@ const PollAnswer = (props: AbstractProps) => {
|
||||
</View>
|
||||
{
|
||||
pollSaved
|
||||
? <View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
? <View style = { pollsStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.edit'
|
||||
labelKey = 'polls.answer.edit'
|
||||
@@ -66,28 +66,28 @@ const PollAnswer = (props: AbstractProps) => {
|
||||
setCreateMode(true);
|
||||
dispatch(editPoll(pollId, true));
|
||||
} }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.send'
|
||||
labelKey = 'polls.answer.send'
|
||||
onClick = { sendPoll }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
: <View style = { chatStyles.buttonRow as ViewStyle }>
|
||||
: <View style = { pollsStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.skip'
|
||||
labelKey = 'polls.answer.skip'
|
||||
onClick = { changingVote ? skipChangeVote : skipAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.answer.submit'
|
||||
disabled = { isSubmitAnswerDisabled(checkBoxStates) }
|
||||
labelKey = 'polls.answer.submit'
|
||||
onClick = { submitAnswer }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { FlatList, Platform, View, ViewStyle } from 'react-native';
|
||||
import { TextInput } from 'react-native-gesture-handler';
|
||||
import { Divider } from 'react-native-paper';
|
||||
@@ -7,13 +7,11 @@ import { useDispatch } from 'react-redux';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import Input from '../../../base/ui/components/native/Input';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import styles
|
||||
from '../../../settings/components/native/styles';
|
||||
import { editPoll } from '../../actions';
|
||||
import { ANSWERS_LIMIT, CHAR_LIMIT } from '../../constants';
|
||||
import AbstractPollCreate, { AbstractProps } from '../AbstractPollCreate';
|
||||
|
||||
import { chatStyles, dialogStyles } from './styles';
|
||||
import { dialogStyles, pollsStyles } from './styles';
|
||||
|
||||
const PollCreate = (props: AbstractProps) => {
|
||||
const {
|
||||
@@ -98,7 +96,7 @@ const PollCreate = (props: AbstractProps) => {
|
||||
);
|
||||
|
||||
const pollCreateButtonsContainerStyles = Platform.OS === 'android'
|
||||
? chatStyles.pollCreateButtonsContainerAndroid : chatStyles.pollCreateButtonsContainerIos;
|
||||
? pollsStyles.pollCreateButtonsContainerAndroid : pollsStyles.pollCreateButtonsContainerIos;
|
||||
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
const renderListItem = ({ index }: { index: number; }) => {
|
||||
@@ -119,7 +117,6 @@ const PollCreate = (props: AbstractProps) => {
|
||||
id = { `polls-answer-input-${index}` }
|
||||
label = { t('polls.create.pollOption', { index: index + 1 }) }
|
||||
maxLength = { CHAR_LIMIT }
|
||||
multiline = { true }
|
||||
onChange = { name => setAnswer(index,
|
||||
{
|
||||
name,
|
||||
@@ -140,26 +137,30 @@ const PollCreate = (props: AbstractProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style = { chatStyles.pollCreateContainer as ViewStyle }>
|
||||
<View style = { chatStyles.pollCreateSubContainer as ViewStyle }>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ container: dialogStyles.customContainer }}
|
||||
label = { t('polls.create.pollQuestion') }
|
||||
maxLength = { CHAR_LIMIT }
|
||||
multiline = { true }
|
||||
onChange = { setQuestion }
|
||||
onSubmitEditing = { onQuestionKeyDown }
|
||||
placeholder = { t('polls.create.questionPlaceholder') }
|
||||
const renderListHeaderComponent = useMemo(() => (
|
||||
<>
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
blurOnSubmit = { false }
|
||||
customStyles = {{ container: dialogStyles.customContainer }}
|
||||
label = { t('polls.create.pollQuestion') }
|
||||
maxLength = { CHAR_LIMIT }
|
||||
onChange = { setQuestion }
|
||||
onSubmitEditing = { onQuestionKeyDown }
|
||||
placeholder = { t('polls.create.questionPlaceholder') }
|
||||
|
||||
// This is set to help the touch event not be propagated to any subviews.
|
||||
pointerEvents = { 'auto' }
|
||||
value = { question } />
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.fieldSeparator } />
|
||||
// This is set to help the touch event not be propagated to any subviews.
|
||||
pointerEvents = { 'auto' }
|
||||
value = { question } />
|
||||
<Divider style = { pollsStyles.fieldSeparator as ViewStyle } />
|
||||
</>
|
||||
), [ question ]);
|
||||
|
||||
return (
|
||||
<View style = { pollsStyles.pollCreateContainer as ViewStyle }>
|
||||
<View style = { pollsStyles.pollCreateSubContainer as ViewStyle }>
|
||||
<FlatList
|
||||
ListHeaderComponent = { renderListHeaderComponent }
|
||||
data = { answers }
|
||||
extraData = { answers }
|
||||
keyExtractor = { (item, index) => index.toString() }
|
||||
@@ -175,10 +176,10 @@ const PollCreate = (props: AbstractProps) => {
|
||||
addAnswer();
|
||||
requestFocus(answers.length);
|
||||
} }
|
||||
style = { chatStyles.pollCreateAddButton }
|
||||
style = { pollsStyles.pollCreateAddButton }
|
||||
type = { SECONDARY } />
|
||||
<View
|
||||
style = { chatStyles.buttonRow as ViewStyle }>
|
||||
style = { pollsStyles.buttonRow as ViewStyle }>
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.cancel'
|
||||
labelKey = 'polls.create.cancel'
|
||||
@@ -188,14 +189,14 @@ const PollCreate = (props: AbstractProps) => {
|
||||
&& editingPoll?.editing
|
||||
&& dispatch(editPoll(editingPollId, false));
|
||||
} }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { SECONDARY } />
|
||||
<Button
|
||||
accessibilityLabel = 'polls.create.save'
|
||||
disabled = { isSubmitDisabled }
|
||||
labelKey = 'polls.create.save'
|
||||
onClick = { onSubmit }
|
||||
style = { chatStyles.pollCreateButton }
|
||||
style = { pollsStyles.pollCreateButton }
|
||||
type = { PRIMARY } />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { shouldShowResults } from '../../functions';
|
||||
|
||||
import PollAnswer from './PollAnswer';
|
||||
import PollResults from './PollResults';
|
||||
import { chatStyles } from './styles';
|
||||
import { pollsStyles } from './styles';
|
||||
|
||||
interface IProps {
|
||||
|
||||
@@ -27,7 +27,7 @@ const PollItem = ({ pollId, setCreateMode }: IProps) => {
|
||||
|
||||
return (
|
||||
<View
|
||||
style = { chatStyles.pollItemContainer as ViewStyle }>
|
||||
style = { pollsStyles.pollItemContainer as ViewStyle }>
|
||||
{ showResults
|
||||
? <PollResults
|
||||
key = { pollId }
|
||||
|
||||
@@ -6,7 +6,7 @@ import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import AbstractPollResults from '../AbstractPollResults';
|
||||
import type { AbstractProps, AnswerInfo } from '../AbstractPollResults';
|
||||
|
||||
import { chatStyles, dialogStyles, resultsStyles } from './styles';
|
||||
import { dialogStyles, pollsStyles, resultsStyles } from './styles';
|
||||
|
||||
/**
|
||||
* Component that renders the poll results.
|
||||
@@ -100,14 +100,14 @@ const PollResults = (props: AbstractProps) => {
|
||||
data = { answers }
|
||||
keyExtractor = { (item, index) => index.toString() }
|
||||
renderItem = { answer => renderRow(answer.item) } />
|
||||
<View style = { chatStyles.bottomLinks as ViewStyle }>
|
||||
<View style = { pollsStyles.bottomLinks as ViewStyle }>
|
||||
<Button
|
||||
labelKey = {
|
||||
showDetails
|
||||
? 'polls.results.hideDetailedResults'
|
||||
: 'polls.results.showDetailedResults'
|
||||
}
|
||||
labelStyle = { chatStyles.toggleText }
|
||||
labelStyle = { pollsStyles.toggleText }
|
||||
onClick = { toggleIsDetailed }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
<Button
|
||||
@@ -116,7 +116,7 @@ const PollResults = (props: AbstractProps) => {
|
||||
? 'polls.results.changeVote'
|
||||
: 'polls.results.vote'
|
||||
}
|
||||
labelStyle = { chatStyles.toggleText }
|
||||
labelStyle = { pollsStyles.toggleText }
|
||||
onClick = { changeVote }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
</View>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IconMessage } from '../../../base/icons/svg';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
import PollItem from './PollItem';
|
||||
import { chatStyles } from './styles';
|
||||
import { pollsStyles } from './styles';
|
||||
|
||||
interface IPollListProps {
|
||||
setCreateMode: (mode: boolean) => void;
|
||||
@@ -42,12 +42,12 @@ const PollsList = ({ setCreateMode }: IPollListProps) => {
|
||||
<>
|
||||
{
|
||||
listPolls.length === 0
|
||||
&& <View style = { chatStyles.noPollContent as ViewStyle }>
|
||||
&& <View style = { pollsStyles.noPollContent as ViewStyle }>
|
||||
<Icon
|
||||
color = { BaseTheme.palette.icon03 }
|
||||
size = { 160 }
|
||||
src = { IconMessage } />
|
||||
<Text style = { chatStyles.noPollText as TextStyle } >
|
||||
<Text style = { pollsStyles.noPollText as TextStyle } >
|
||||
{
|
||||
t('polls.results.empty')
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { AbstractProps } from '../AbstractPollsPane';
|
||||
|
||||
import PollCreate from './PollCreate';
|
||||
import PollsList from './PollsList';
|
||||
import { chatStyles } from './styles';
|
||||
import { pollsStyles } from './styles';
|
||||
|
||||
const PollsPane = (props: AbstractProps) => {
|
||||
const { createMode, onCreate, setCreateMode, t } = props;
|
||||
@@ -42,14 +42,14 @@ const PollsPane = (props: AbstractProps) => {
|
||||
}, [ isPollsTabFocused, nbUnreadPolls ]);
|
||||
|
||||
const createPollButtonStyles = Platform.OS === 'android'
|
||||
? chatStyles.createPollButtonAndroid : chatStyles.createPollButtonIos;
|
||||
? pollsStyles.createPollButtonAndroid : pollsStyles.createPollButtonIos;
|
||||
|
||||
return (
|
||||
<JitsiScreen
|
||||
contentContainerStyle = { chatStyles.pollPane as StyleType }
|
||||
contentContainerStyle = { pollsStyles.pollPane as StyleType }
|
||||
disableForcedKeyboardDismiss = { true }
|
||||
hasExtraHeaderHeight = { true }
|
||||
style = { chatStyles.pollPaneContainer as StyleType }>
|
||||
style = { pollsStyles.pollPaneContainer as StyleType }>
|
||||
{
|
||||
createMode
|
||||
? <PollCreate setCreateMode = { setCreateMode } />
|
||||
|
||||
@@ -30,7 +30,6 @@ export const dialogStyles = createStyleSheet({
|
||||
},
|
||||
|
||||
optionRemoveButton: {
|
||||
marginTop: BaseTheme.spacing[2],
|
||||
width: 128
|
||||
},
|
||||
|
||||
@@ -110,7 +109,7 @@ export const resultsStyles = createStyleSheet({
|
||||
}
|
||||
});
|
||||
|
||||
export const chatStyles = createStyleSheet({
|
||||
export const pollsStyles = createStyleSheet({
|
||||
|
||||
noPollContent: {
|
||||
alignItems: 'center',
|
||||
@@ -195,7 +194,6 @@ export const chatStyles = createStyleSheet({
|
||||
|
||||
pollCreateButton: {
|
||||
marginHorizontal: BaseTheme.spacing[1],
|
||||
maxWidth: 160,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
@@ -229,26 +227,9 @@ export const chatStyles = createStyleSheet({
|
||||
marginHorizontal: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
unreadPollsCounterContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
unreadPollsCounterDescription: {
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
|
||||
unreadPollsCounterCircle: {
|
||||
backgroundColor: BaseTheme.palette.warning01,
|
||||
borderRadius: BaseTheme.spacing[3] / 2,
|
||||
height: BaseTheme.spacing[3],
|
||||
justifyContent: 'center',
|
||||
marginLeft: BaseTheme.spacing[2],
|
||||
width: BaseTheme.spacing[3]
|
||||
},
|
||||
|
||||
unreadPollsCounter: {
|
||||
alignSelf: 'center',
|
||||
color: BaseTheme.palette.text04
|
||||
fieldSeparator: {
|
||||
borderBottomWidth: 1,
|
||||
borderColor: BaseTheme.palette.ui05,
|
||||
marginTop: BaseTheme.spacing[3]
|
||||
}
|
||||
});
|
||||
|
||||
23
react/features/reactions/hooks.web.ts
Normal file
23
react/features/reactions/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ReactionsMenuButton from './components/web/ReactionsMenuButton';
|
||||
import { isReactionsButtonEnabled } from './functions';
|
||||
|
||||
const reactions = {
|
||||
key: 'reactions',
|
||||
Content: ReactionsMenuButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the reactions button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useReactionsButton() {
|
||||
const reactionsButtonEnabled = useSelector(isReactionsButtonEnabled);
|
||||
|
||||
if (reactionsButtonEnabled) {
|
||||
return reactions;
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import { IReduxState } from '../../app/types';
|
||||
import { JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import { isRecorderTranscriptionsRunning } from '../../transcribing/functions';
|
||||
import {
|
||||
getActiveSession,
|
||||
getSessionStatusToShow,
|
||||
isLiveStreamingRunning,
|
||||
isRecordingRunning,
|
||||
isRemoteParticipantRecordingLocally
|
||||
} from '../functions';
|
||||
@@ -81,9 +81,9 @@ export function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { mode } = ownProps;
|
||||
const isLiveStreamingLabel = mode === JitsiRecordingConstants.mode.STREAM;
|
||||
const _isTranscribing = isRecorderTranscriptionsRunning(state);
|
||||
const isLivestreamingRunning = Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM));
|
||||
const _isLivestreamingRunning = isLiveStreamingRunning(state);
|
||||
const _isVisible = isLiveStreamingLabel
|
||||
? isLivestreamingRunning // this is the livestreaming label
|
||||
? _isLivestreamingRunning // this is the livestreaming label
|
||||
: isRecordingRunning(state) || isRemoteParticipantRecordingLocally(state)
|
||||
|| _isTranscribing; // this is the recording label
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@ import { IReduxState } from '../../../app/types';
|
||||
import { IconSites } from '../../../base/icons/svg';
|
||||
import { MEET_FEATURES } from '../../../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../../../base/jwt/functions';
|
||||
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
import { maybeShowPremiumFeatureDialog } from '../../../jaas/actions';
|
||||
import { isRecorderTranscriptionsRunning } from '../../../transcribing/functions';
|
||||
import { getActiveSession, isCloudRecordingRunning } from '../../functions';
|
||||
import { isCloudRecordingRunning, isLiveStreamingButtonVisible, isLiveStreamingRunning } from '../../functions';
|
||||
|
||||
import { getLiveStreaming } from './functions';
|
||||
|
||||
@@ -133,11 +132,12 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
const liveStreaming = getLiveStreaming(state);
|
||||
|
||||
if (isModerator) {
|
||||
visible = liveStreaming.enabled ? isJwtFeatureEnabled(state, 'livestreaming', true) : false;
|
||||
} else {
|
||||
visible = false;
|
||||
}
|
||||
visible = isLiveStreamingButtonVisible({
|
||||
localParticipantIsModerator: isModerator,
|
||||
liveStreamingEnabled: liveStreaming?.enabled,
|
||||
liveStreamingEnabledInJwt: isJwtFeatureEnabled(state, 'livestreaming', true),
|
||||
isInBreakoutRoom: isInBreakoutRoom(state)
|
||||
});
|
||||
}
|
||||
|
||||
// disable the button if the recording is running.
|
||||
@@ -149,12 +149,11 @@ export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
// disable the button if we are in a breakout room.
|
||||
if (isInBreakoutRoom(state)) {
|
||||
_disabled = true;
|
||||
visible = false;
|
||||
}
|
||||
|
||||
return {
|
||||
_disabled,
|
||||
_isLiveStreamRunning: Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)),
|
||||
_isLiveStreamRunning: isLiveStreamingRunning(state),
|
||||
_tooltip,
|
||||
visible
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
isLocalParticipantModerator
|
||||
} from '../base/participants/functions';
|
||||
import { registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
import { isInBreakoutRoom as isInBreakoutRoomF } from '../breakout-rooms/functions';
|
||||
import { isEnabled as isDropboxEnabled } from '../dropbox/functions';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
|
||||
import { canAddTranscriber, isRecorderTranscriptionsRunning } from '../transcribing/functions';
|
||||
@@ -168,6 +168,16 @@ export function isCloudRecordingRunning(state: IReduxState) {
|
||||
return Boolean(getActiveSession(state, JitsiRecordingConstants.mode.FILE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a live streaming running.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state to search in.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLiveStreamingRunning(state: IReduxState) {
|
||||
return Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there is a recording session running.
|
||||
*
|
||||
@@ -262,13 +272,13 @@ export function getRecordButtonProps(state: IReduxState) {
|
||||
}
|
||||
|
||||
// disable the button if the livestreaming is running.
|
||||
if (visible && getActiveSession(state, JitsiRecordingConstants.mode.STREAM)) {
|
||||
if (visible && isLiveStreamingRunning(state)) {
|
||||
disabled = true;
|
||||
tooltip = 'dialog.recordingDisabledBecauseOfActiveLiveStreamingTooltip';
|
||||
}
|
||||
|
||||
// disable the button if we are in a breakout room.
|
||||
if (isInBreakoutRoom(state)) {
|
||||
if (isInBreakoutRoomF(state)) {
|
||||
disabled = true;
|
||||
visible = false;
|
||||
}
|
||||
@@ -404,3 +414,31 @@ export function registerRecordingAudioFiles(dispatch: IStore['dispatch'], should
|
||||
RECORDING_ON_SOUND_ID,
|
||||
getSoundFileSrc(RECORDING_ON_SOUND_FILE, language)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the live streaming button should be visible.
|
||||
*
|
||||
* @param {boolean} localParticipantIsModerator - True if the local participant is moderator.
|
||||
* @param {boolean} liveStreamingEnabled - True if the live streaming is enabled.
|
||||
* @param {boolean} liveStreamingEnabledInJwt - True if the lives treaming feature is enabled in JWT.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLiveStreamingButtonVisible({
|
||||
localParticipantIsModerator,
|
||||
liveStreamingEnabled,
|
||||
liveStreamingEnabledInJwt,
|
||||
isInBreakoutRoom
|
||||
}: {
|
||||
isInBreakoutRoom: boolean;
|
||||
liveStreamingEnabled: boolean;
|
||||
liveStreamingEnabledInJwt: boolean;
|
||||
localParticipantIsModerator: boolean;
|
||||
}) {
|
||||
let visible = false;
|
||||
|
||||
if (localParticipantIsModerator && !isInBreakoutRoom) {
|
||||
visible = liveStreamingEnabled ? liveStreamingEnabledInJwt : false;
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
63
react/features/recording/hooks.web.ts
Normal file
63
react/features/recording/hooks.web.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { isLocalParticipantModerator } from '../base/participants/functions';
|
||||
import { isInBreakoutRoom } from '../breakout-rooms/functions';
|
||||
|
||||
import { getLiveStreaming } from './components/LiveStream/functions';
|
||||
import LiveStreamButton from './components/LiveStream/web/LiveStreamButton';
|
||||
import RecordButton from './components/Recording/web/RecordButton';
|
||||
import { getRecordButtonProps, isLiveStreamingButtonVisible } from './functions';
|
||||
|
||||
|
||||
const recording = {
|
||||
key: 'recording',
|
||||
Content: RecordButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const livestreaming = {
|
||||
key: 'livestreaming',
|
||||
Content: LiveStreamButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the recording button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useRecordingButton() {
|
||||
const recordingProps = useSelector(getRecordButtonProps);
|
||||
const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons);
|
||||
|
||||
if (toolbarButtons?.includes('recording') && recordingProps.visible) {
|
||||
return recording;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the livestreaming button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useLiveStreamingButton() {
|
||||
const toolbarButtons = useSelector((state: IReduxState) => state['features/toolbox'].toolbarButtons);
|
||||
const localParticipantIsModerator = useSelector(isLocalParticipantModerator);
|
||||
const liveStreaming = useSelector(getLiveStreaming);
|
||||
const liveStreamingEnabledInJwt
|
||||
= useSelector((state: IReduxState) => isJwtFeatureEnabled(state, 'livestreaming', true));
|
||||
const _isInBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
|
||||
if (toolbarButtons?.includes('recording')
|
||||
&& isLiveStreamingButtonVisible({
|
||||
localParticipantIsModerator,
|
||||
liveStreamingEnabled: liveStreaming?.enabled,
|
||||
liveStreamingEnabledInJwt,
|
||||
isInBreakoutRoom: _isInBreakoutRoom
|
||||
})) {
|
||||
return livestreaming;
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
(recorderSession: any) => {
|
||||
if (recorderSession) {
|
||||
recorderSession.getID() && dispatch(updateRecordingSessionData(recorderSession));
|
||||
recorderSession.getError() && _showRecordingErrorNotification(recorderSession, dispatch);
|
||||
recorderSession.getError() && _showRecordingErrorNotification(recorderSession, dispatch, getState);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -133,7 +133,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
dispatch(updateLocalRecordingStatus(true, onlySelf));
|
||||
sendAnalytics(createRecordingEvent('started', `local${onlySelf ? '.self' : ''}`));
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(true, 'local');
|
||||
APP.API.notifyRecordingStatusChanged(
|
||||
true, 'local', undefined, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
} catch (err: any) {
|
||||
logger.error('Capture failed', err);
|
||||
@@ -154,7 +155,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
};
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, 'local', err.message);
|
||||
APP.API.notifyRecordingStatusChanged(
|
||||
false, 'local', err.message, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
@@ -172,7 +174,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
dispatch(playSound(RECORDING_OFF_SOUND_ID));
|
||||
}
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, 'local');
|
||||
APP.API.notifyRecordingStatusChanged(
|
||||
false, 'local', undefined, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -237,7 +240,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(true, mode);
|
||||
APP.API.notifyRecordingStatusChanged(
|
||||
true, mode, undefined, isRecorderTranscriptionsRunning(state));
|
||||
}
|
||||
}
|
||||
} else if (updatedSessionData?.status === OFF && oldSessionData?.status !== OFF) {
|
||||
@@ -269,7 +273,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, mode);
|
||||
APP.API.notifyRecordingStatusChanged(
|
||||
false, mode, undefined, isRecorderTranscriptionsRunning(state));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,14 +317,15 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
|
||||
* in recording session.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} recorderSession - The recorder session model from the
|
||||
* @param {Object} session - The recorder session model from the
|
||||
* lib.
|
||||
* @param {Dispatch} dispatch - The Redux Dispatch function.
|
||||
* @param {Function} getState - The Redux getState function.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _showRecordingErrorNotification(recorderSession: any, dispatch: IStore['dispatch']) {
|
||||
const mode = recorderSession.getMode();
|
||||
const error = recorderSession.getError();
|
||||
function _showRecordingErrorNotification(session: any, dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
const mode = session.getMode();
|
||||
const error = session.getError();
|
||||
const isStreamMode = mode === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
switch (error) {
|
||||
@@ -367,6 +373,6 @@ function _showRecordingErrorNotification(recorderSession: any, dispatch: IStore[
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, mode, error);
|
||||
APP.API.notifyRecordingStatusChanged(false, mode, error, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const KEYS = {
|
||||
ALT: 'alt',
|
||||
CONTROL: 'control',
|
||||
SHIFT: 'shift',
|
||||
CAPS_LOCK: 'caps_lock', // not supported by robotjs
|
||||
CAPS_LOCK: 'capslock',
|
||||
SPACE: 'space',
|
||||
PRINTSCREEN: 'printscreen',
|
||||
INSERT: 'insert',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getFeatureFlag } from '../../../base/flags/functions';
|
||||
import { IconSecurityOff, IconSecurityOn } from '../../../base/icons/svg';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import { isSecurityDialogButtonVisible } from '../../functions';
|
||||
|
||||
export interface IProps extends AbstractButtonProps {
|
||||
|
||||
@@ -71,17 +72,21 @@ export default class AbstractSecurityDialogButton<P extends IProps, S>
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { hideLobbyButton } = getSecurityUiConfig(state);
|
||||
const { locked } = state['features/base/conference'];
|
||||
const { lobbyEnabled } = state['features/lobby'];
|
||||
const lobbySupported = conference?.isLobbySupported();
|
||||
const lobby = lobbySupported && isLocalParticipantModerator(state) && !hideLobbyButton;
|
||||
const enabledFlag = getFeatureFlag(state, SECURITY_OPTIONS_ENABLED, true);
|
||||
const enabledLobbyModeFlag = getFeatureFlag(state, LOBBY_MODE_ENABLED, true) && lobby;
|
||||
const enabledSecurityOptionsFlag = getFeatureFlag(state, SECURITY_OPTIONS_ENABLED, true);
|
||||
const enabledLobbyModeFlag = getFeatureFlag(state, LOBBY_MODE_ENABLED, true);
|
||||
const enabledMeetingPassFlag = getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true);
|
||||
|
||||
return {
|
||||
_locked: Boolean(locked || lobbyEnabled),
|
||||
visible: enabledFlag && (enabledLobbyModeFlag || enabledMeetingPassFlag)
|
||||
visible: isSecurityDialogButtonVisible({
|
||||
conference,
|
||||
securityUIConfig: getSecurityUiConfig(state),
|
||||
isModerator: isLocalParticipantModerator(state),
|
||||
enabledLobbyModeFlag,
|
||||
enabledMeetingPassFlag,
|
||||
enabledSecurityOptionsFlag
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
28
react/features/security/functions.ts
Normal file
28
react/features/security/functions.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Returns true if the security dialog button should be visible and false otherwise.
|
||||
*
|
||||
* @param {Object} options - The parameters needed to determine the security dialog button visibility.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isSecurityDialogButtonVisible({
|
||||
conference,
|
||||
securityUIConfig,
|
||||
isModerator,
|
||||
enabledLobbyModeFlag,
|
||||
enabledSecurityOptionsFlag,
|
||||
enabledMeetingPassFlag
|
||||
}: {
|
||||
conference: any;
|
||||
enabledLobbyModeFlag: boolean;
|
||||
enabledMeetingPassFlag: boolean;
|
||||
enabledSecurityOptionsFlag: boolean;
|
||||
isModerator: boolean;
|
||||
securityUIConfig: { hideLobbyButton?: boolean; };
|
||||
}) {
|
||||
const { hideLobbyButton } = securityUIConfig;
|
||||
const lobbySupported = conference?.isLobbySupported();
|
||||
const lobby = lobbySupported && isModerator && !hideLobbyButton;
|
||||
|
||||
|
||||
return enabledSecurityOptionsFlag && ((enabledLobbyModeFlag && lobby) || enabledMeetingPassFlag);
|
||||
}
|
||||
45
react/features/security/hooks.web.ts
Normal file
45
react/features/security/hooks.web.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getSecurityUiConfig } from '../base/config/functions.any';
|
||||
import { LOBBY_MODE_ENABLED, MEETING_PASSWORD_ENABLED, SECURITY_OPTIONS_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { isLocalParticipantModerator } from '../base/participants/functions';
|
||||
|
||||
import SecurityDialogButton from './components/security-dialog/web/SecurityDialogButton';
|
||||
import { isSecurityDialogButtonVisible } from './functions';
|
||||
|
||||
const security = {
|
||||
key: 'security',
|
||||
alias: 'info',
|
||||
Content: SecurityDialogButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the security dialog button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useSecurityDialogButton() {
|
||||
const conference = useSelector((state: IReduxState) => state['features/base/conference'].conference);
|
||||
const securityUIConfig = useSelector(getSecurityUiConfig);
|
||||
const isModerator = useSelector(isLocalParticipantModerator);
|
||||
const enabledLobbyModeFlag
|
||||
= useSelector((state: IReduxState) => getFeatureFlag(state, LOBBY_MODE_ENABLED, true));
|
||||
const enabledSecurityOptionsFlag
|
||||
= useSelector((state: IReduxState) => getFeatureFlag(state, SECURITY_OPTIONS_ENABLED, true));
|
||||
const enabledMeetingPassFlag
|
||||
= useSelector((state: IReduxState) => getFeatureFlag(state, MEETING_PASSWORD_ENABLED, true));
|
||||
|
||||
if (isSecurityDialogButtonVisible({
|
||||
conference,
|
||||
securityUIConfig,
|
||||
isModerator,
|
||||
enabledLobbyModeFlag,
|
||||
enabledSecurityOptionsFlag,
|
||||
enabledMeetingPassFlag
|
||||
})) {
|
||||
return security;
|
||||
}
|
||||
}
|
||||
23
react/features/speaker-stats/hooks.web.ts
Normal file
23
react/features/speaker-stats/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import SpeakerStatsButton from './components/web/SpeakerStatsButton';
|
||||
import { isSpeakerStatsDisabled } from './functions';
|
||||
|
||||
const speakerStats = {
|
||||
key: 'stats',
|
||||
Content: SpeakerStatsButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the speaker stats button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useSpeakerStatsButton() {
|
||||
const disabled = useSelector(isSpeakerStatsDisabled);
|
||||
|
||||
if (!disabled) {
|
||||
return speakerStats;
|
||||
}
|
||||
}
|
||||
23
react/features/subtitles/hooks.web.ts
Normal file
23
react/features/subtitles/hooks.web.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ClosedCaptionButton from './components/web/ClosedCaptionButton';
|
||||
import { canStartSubtitles } from './functions.any';
|
||||
|
||||
const cc = {
|
||||
key: 'closedcaptions',
|
||||
Content: ClosedCaptionButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the CC button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useClosedCaptionButton() {
|
||||
const isStartSubtitlesButtonVisible = useSelector(canStartSubtitles);
|
||||
|
||||
if (isStartSubtitlesButtonVisible) {
|
||||
return cc;
|
||||
}
|
||||
}
|
||||
1
react/features/toolbox/components/index.native.ts
Normal file
1
react/features/toolbox/components/index.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as CustomOptionButton } from './native/CustomOptionButton';
|
||||
1
react/features/toolbox/components/index.web.ts
Normal file
1
react/features/toolbox/components/index.web.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as CustomOptionButton } from './web/CustomOptionButton';
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
isButtonEnabled,
|
||||
isToolboxVisible
|
||||
} from '../../functions.web';
|
||||
import { useKeyboardShortcuts } from '../../hooks.web';
|
||||
import { useKeyboardShortcuts, useToolboxButtons } from '../../hooks.web';
|
||||
import { IToolboxButton } from '../../types';
|
||||
import HangupButton from '../HangupButton';
|
||||
|
||||
@@ -105,6 +105,7 @@ export default function Toolbox({
|
||||
const toolbarVisible = useSelector(isToolboxVisible);
|
||||
const mainToolbarButtonsThresholds
|
||||
= useSelector((state: IReduxState) => state['features/toolbox'].mainToolbarButtonsThresholds);
|
||||
const allButtons = useToolboxButtons(customToolbarButtons);
|
||||
|
||||
useKeyboardShortcuts(toolbarButtonsToUse);
|
||||
|
||||
@@ -216,7 +217,7 @@ export default function Toolbox({
|
||||
const containerClassName = `toolbox-content${isMobile || isNarrowLayout ? ' toolbox-content-mobile' : ''}`;
|
||||
|
||||
const { mainMenuButtons, overflowMenuButtons } = getVisibleButtons({
|
||||
customToolbarButtons,
|
||||
allButtons,
|
||||
buttonsWithNotifyClick,
|
||||
toolbarButtons: toolbarButtonsToUse,
|
||||
clientWidth,
|
||||
@@ -225,10 +226,9 @@ export default function Toolbox({
|
||||
});
|
||||
const raiseHandInOverflowMenu = overflowMenuButtons.some(({ key }) => key === 'raisehand');
|
||||
const showReactionsInOverflowMenu = _shouldDisplayReactionsButtons
|
||||
&& (
|
||||
(!reactionsButtonEnabled && (raiseHandInOverflowMenu || isNarrowLayout || isMobile))
|
||||
|| overflowMenuButtons.some(({ key }) => key === 'reactions')
|
||||
);
|
||||
&& (
|
||||
(!reactionsButtonEnabled && (raiseHandInOverflowMenu || isNarrowLayout || isMobile))
|
||||
|| overflowMenuButtons.some(({ key }) => key === 'reactions'));
|
||||
const showRaiseHandInReactionsMenu = showReactionsInOverflowMenu && raiseHandInOverflowMenu;
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,41 +3,9 @@ import { hasAvailableDevices } from '../base/devices/functions';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { IGUMPendingState } from '../base/media/types';
|
||||
import ChatButton from '../chat/components/web/ChatButton';
|
||||
import EmbedMeetingButton from '../embed-meeting/components/EmbedMeetingButton';
|
||||
import SharedDocumentButton from '../etherpad/components/SharedDocumentButton.web';
|
||||
import FeedbackButton from '../feedback/components/FeedbackButton.web';
|
||||
import InviteButton from '../invite/components/add-people-dialog/web/InviteButton';
|
||||
import KeyboardShortcutsButton from '../keyboard-shortcuts/components/web/KeyboardShortcutsButton';
|
||||
import NoiseSuppressionButton from '../noise-suppression/components/NoiseSuppressionButton';
|
||||
import ParticipantsPaneButton from '../participants-pane/components/web/ParticipantsPaneButton';
|
||||
import RaiseHandContainerButton from '../reactions/components/web/RaiseHandContainerButtons';
|
||||
import ReactionsMenuButton from '../reactions/components/web/ReactionsMenuButton';
|
||||
import LiveStreamButton from '../recording/components/LiveStream/web/LiveStreamButton';
|
||||
import RecordButton from '../recording/components/Recording/web/RecordButton';
|
||||
import ShareAudioButton from '../screen-share/components/web/ShareAudioButton';
|
||||
import { isScreenMediaShared } from '../screen-share/functions';
|
||||
import SecurityDialogButton from '../security/components/security-dialog/web/SecurityDialogButton';
|
||||
import SettingsButton from '../settings/components/web/SettingsButton';
|
||||
import SharedVideoButton from '../shared-video/components/web/SharedVideoButton';
|
||||
import SpeakerStatsButton from '../speaker-stats/components/web/SpeakerStatsButton';
|
||||
import ClosedCaptionButton from '../subtitles/components/web/ClosedCaptionButton';
|
||||
import TileViewButton from '../video-layout/components/TileViewButton';
|
||||
import VideoQualityButton from '../video-quality/components/VideoQualityButton.web';
|
||||
import VideoBackgroundButton from '../virtual-background/components/VideoBackgroundButton';
|
||||
import WhiteboardButton from '../whiteboard/components/web/WhiteboardButton';
|
||||
import { isWhiteboardVisible } from '../whiteboard/functions';
|
||||
|
||||
import DownloadButton from './components/DownloadButton';
|
||||
import HelpButton from './components/HelpButton';
|
||||
import AudioSettingsButton from './components/web/AudioSettingsButton';
|
||||
import CustomOptionButton from './components/web/CustomOptionButton';
|
||||
import FullscreenButton from './components/web/FullscreenButton';
|
||||
import LinkToSalesforceButton from './components/web/LinkToSalesforceButton';
|
||||
import ProfileButton from './components/web/ProfileButton';
|
||||
import ShareDesktopButton from './components/web/ShareDesktopButton';
|
||||
import ToggleCameraButton from './components/web/ToggleCameraButton';
|
||||
import VideoSettingsButton from './components/web/VideoSettingsButton';
|
||||
import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants';
|
||||
import { IMainToolbarButtonThresholds, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
@@ -189,261 +157,6 @@ export function getToolbarTimeout(state: IReduxState) {
|
||||
return toolbarConfig?.timeout || TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
interface ICustomToolbarButton {
|
||||
backgroundColor?: string;
|
||||
icon: string;
|
||||
id: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that could be rendered.
|
||||
*
|
||||
* @param {Object} _customToolbarButtons - An array containing custom buttons objects.
|
||||
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
|
||||
*/
|
||||
export function getAllToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } {
|
||||
const microphone = {
|
||||
key: 'microphone',
|
||||
Content: AudioSettingsButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const camera = {
|
||||
key: 'camera',
|
||||
Content: VideoSettingsButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const profile = {
|
||||
key: 'profile',
|
||||
Content: ProfileButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const desktop = {
|
||||
key: 'desktop',
|
||||
Content: ShareDesktopButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
// In Narrow layout and mobile web we are using drawer for popups and that is why it is better to include
|
||||
// all forms of reactions in the overflow menu. Otherwise the toolbox will be hidden and the reactions popup
|
||||
// misaligned.
|
||||
const raisehand = {
|
||||
key: 'raisehand',
|
||||
Content: RaiseHandContainerButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const reactions = {
|
||||
key: 'reactions',
|
||||
Content: ReactionsMenuButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const participants = {
|
||||
key: 'participants-pane',
|
||||
Content: ParticipantsPaneButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const invite = {
|
||||
key: 'invite',
|
||||
Content: InviteButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const tileview = {
|
||||
key: 'tileview',
|
||||
Content: TileViewButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const toggleCamera = {
|
||||
key: 'toggle-camera',
|
||||
Content: ToggleCameraButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const videoquality = {
|
||||
key: 'videoquality',
|
||||
Content: VideoQualityButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const fullscreen = {
|
||||
key: 'fullscreen',
|
||||
Content: FullscreenButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const security = {
|
||||
key: 'security',
|
||||
Content: SecurityDialogButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const closedcaptions = {
|
||||
key: 'closedcaptions',
|
||||
Content: ClosedCaptionButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const recording = {
|
||||
key: 'recording',
|
||||
Content: RecordButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const livestreaming = {
|
||||
key: 'livestreaming',
|
||||
Content: LiveStreamButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const linktosalesforce = {
|
||||
key: 'linktosalesforce',
|
||||
Content: LinkToSalesforceButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const sharedvideo = {
|
||||
key: 'sharedvideo',
|
||||
Content: SharedVideoButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const shareaudio = {
|
||||
key: 'shareaudio',
|
||||
Content: ShareAudioButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const noisesuppression = {
|
||||
key: 'noisesuppression',
|
||||
Content: NoiseSuppressionButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
|
||||
const whiteboard = {
|
||||
key: 'whiteboard',
|
||||
Content: WhiteboardButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const etherpad = {
|
||||
key: 'etherpad',
|
||||
Content: SharedDocumentButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const virtualBackground = {
|
||||
key: 'select-background',
|
||||
Content: VideoBackgroundButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const stats = {
|
||||
key: 'stats',
|
||||
Content: SpeakerStatsButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const settings = {
|
||||
key: 'settings',
|
||||
Content: SettingsButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const shortcuts = {
|
||||
key: 'shortcuts',
|
||||
Content: KeyboardShortcutsButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const embedmeeting = {
|
||||
key: 'embedmeeting',
|
||||
Content: EmbedMeetingButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const feedback = {
|
||||
key: 'feedback',
|
||||
Content: FeedbackButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const download = {
|
||||
key: 'download',
|
||||
Content: DownloadButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const help = {
|
||||
key: 'help',
|
||||
Content: HelpButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: {
|
||||
backgroundColor,
|
||||
key: id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
}
|
||||
};
|
||||
}, {});
|
||||
|
||||
return {
|
||||
microphone,
|
||||
camera,
|
||||
profile,
|
||||
desktop,
|
||||
chat,
|
||||
raisehand,
|
||||
reactions,
|
||||
'participants-pane': participants,
|
||||
invite,
|
||||
tileview,
|
||||
'toggle-camera': toggleCamera,
|
||||
videoquality,
|
||||
fullscreen,
|
||||
security,
|
||||
closedcaptions,
|
||||
recording,
|
||||
livestreaming,
|
||||
linktosalesforce,
|
||||
sharedvideo,
|
||||
shareaudio,
|
||||
noisesuppression,
|
||||
whiteboard,
|
||||
etherpad,
|
||||
'select-background': virtualBackground,
|
||||
stats,
|
||||
settings,
|
||||
shortcuts,
|
||||
embedmeeting,
|
||||
feedback,
|
||||
download,
|
||||
help,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the notify click mode for the buttons.
|
||||
*
|
||||
@@ -464,9 +177,9 @@ function setButtonsNotifyClickMode(buttons: Object, buttonsWithNotifyClick: Map<
|
||||
}
|
||||
|
||||
interface IGetVisibleButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxButton; };
|
||||
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
customToolbarButtons?: ICustomToolbarButton[];
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
@@ -479,22 +192,22 @@ interface IGetVisibleButtonsParams {
|
||||
* @returns {Object} - The visible buttons arrays .
|
||||
*/
|
||||
export function getVisibleButtons({
|
||||
customToolbarButtons,
|
||||
allButtons,
|
||||
buttonsWithNotifyClick,
|
||||
toolbarButtons,
|
||||
clientWidth,
|
||||
jwtDisabledButtons,
|
||||
mainToolbarButtonsThresholds
|
||||
}: IGetVisibleButtonsParams) {
|
||||
const buttons = getAllToolboxButtons(customToolbarButtons);
|
||||
setButtonsNotifyClickMode(allButtons, buttonsWithNotifyClick);
|
||||
|
||||
const filteredButtons = Object.keys(buttons).filter(key =>
|
||||
const filteredButtons = Object.keys(allButtons).filter(key =>
|
||||
typeof key !== 'undefined' // filter invalid buttons that may be comming from config.mainToolbarButtons
|
||||
// override
|
||||
&& !jwtDisabledButtons.includes(key)
|
||||
&& isButtonEnabled(key, toolbarButtons));
|
||||
|
||||
setButtonsNotifyClickMode(buttons, buttonsWithNotifyClick);
|
||||
|
||||
const { order } = mainToolbarButtonsThresholds.find(({ width }) => clientWidth > width)
|
||||
|| mainToolbarButtonsThresholds[mainToolbarButtonsThresholds.length - 1];
|
||||
|
||||
@@ -507,7 +220,7 @@ export function getVisibleButtons({
|
||||
const mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length);
|
||||
const overflowMenuButtons = filteredButtons.reduce((acc, key) => {
|
||||
if (!mainButtonsKeys.includes(key)) {
|
||||
acc.push(buttons[key]);
|
||||
acc.push(allButtons[key]);
|
||||
}
|
||||
|
||||
return acc;
|
||||
@@ -522,7 +235,7 @@ export function getVisibleButtons({
|
||||
}
|
||||
|
||||
return {
|
||||
mainMenuButtons: mainButtonsKeys.map(key => buttons[key]),
|
||||
mainMenuButtons: mainButtonsKeys.map(key => allButtons[key]),
|
||||
overflowMenuButtons
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,13 +5,24 @@ import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent } from '../analytics/Ana
|
||||
import { sendAnalytics } from '../analytics/functions';
|
||||
import { IReduxState } from '../app/types';
|
||||
import { toggleDialog } from '../base/dialog/actions';
|
||||
import { isIosMobileBrowser, isIpadMobileBrowser } from '../base/environment/utils';
|
||||
import { HELP_BUTTON_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { raiseHand } from '../base/participants/actions';
|
||||
import { getLocalParticipant, hasRaisedHand } from '../base/participants/functions';
|
||||
import { isToggleCameraEnabled } from '../base/tracks/functions.web';
|
||||
import { toggleChat } from '../chat/actions.web';
|
||||
import ChatButton from '../chat/components/web/ChatButton';
|
||||
import { useEmbedButton } from '../embed-meeting/hooks';
|
||||
import { useEtherpadButton } from '../etherpad/hooks';
|
||||
import { useFeedbackButton } from '../feedback/hooks.web';
|
||||
import { setGifMenuVisibility } from '../gifs/actions';
|
||||
import { isGifEnabled } from '../gifs/function.any';
|
||||
import InviteButton from '../invite/components/add-people-dialog/web/InviteButton';
|
||||
import { registerShortcut, unregisterShortcut } from '../keyboard-shortcuts/actions.any';
|
||||
import { useKeyboardShortcutsButton } from '../keyboard-shortcuts/hooks.web';
|
||||
import NoiseSuppressionButton from '../noise-suppression/components/NoiseSuppressionButton';
|
||||
import {
|
||||
close as closeParticipantsPane,
|
||||
open as openParticipantsPane
|
||||
@@ -20,20 +31,331 @@ import {
|
||||
getParticipantsPaneOpen,
|
||||
isParticipantsPaneEnabled
|
||||
} from '../participants-pane/functions';
|
||||
import { useParticipantPaneButton } from '../participants-pane/hooks.web';
|
||||
import { addReactionToBuffer } from '../reactions/actions.any';
|
||||
import { toggleReactionsMenuVisibility } from '../reactions/actions.web';
|
||||
import RaiseHandContainerButton from '../reactions/components/web/RaiseHandContainerButtons';
|
||||
import { REACTIONS } from '../reactions/constants';
|
||||
import { shouldDisplayReactionsButtons } from '../reactions/functions.any';
|
||||
import { useReactionsButton } from '../reactions/hooks';
|
||||
import { useLiveStreamingButton, useRecordingButton } from '../recording/hooks.web';
|
||||
import { isSalesforceEnabled } from '../salesforce/functions';
|
||||
import { startScreenShareFlow } from '../screen-share/actions.web';
|
||||
import { isScreenVideoShared } from '../screen-share/functions';
|
||||
import ShareAudioButton from '../screen-share/components/web/ShareAudioButton';
|
||||
import { isScreenAudioSupported, isScreenVideoShared } from '../screen-share/functions';
|
||||
import { useSecurityDialogButton } from '../security/hooks';
|
||||
import SettingsButton from '../settings/components/web/SettingsButton';
|
||||
import SharedVideoButton from '../shared-video/components/web/SharedVideoButton';
|
||||
import SpeakerStats from '../speaker-stats/components/web/SpeakerStats';
|
||||
import { isSpeakerStatsDisabled } from '../speaker-stats/functions';
|
||||
import { useSpeakerStatsButton } from '../speaker-stats/hooks.web';
|
||||
import { useClosedCaptionButton } from '../subtitles/hooks.web';
|
||||
import { toggleTileView } from '../video-layout/actions.any';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
import { useTileViewButton } from '../video-layout/hooks';
|
||||
import VideoQualityButton from '../video-quality/components/VideoQualityButton.web';
|
||||
import VideoQualityDialog from '../video-quality/components/VideoQualityDialog.web';
|
||||
import { useVirtualBackgroundButton } from '../virtual-background/hooks';
|
||||
import { useWhiteboardButton } from '../whiteboard/hooks';
|
||||
|
||||
import { setFullScreen } from './actions.web';
|
||||
import DownloadButton from './components/DownloadButton';
|
||||
import HelpButton from './components/HelpButton';
|
||||
import AudioSettingsButton from './components/web/AudioSettingsButton';
|
||||
import CustomOptionButton from './components/web/CustomOptionButton';
|
||||
import FullscreenButton from './components/web/FullscreenButton';
|
||||
import LinkToSalesforceButton from './components/web/LinkToSalesforceButton';
|
||||
import ProfileButton from './components/web/ProfileButton';
|
||||
import ShareDesktopButton from './components/web/ShareDesktopButton';
|
||||
import ToggleCameraButton from './components/web/ToggleCameraButton';
|
||||
import VideoSettingsButton from './components/web/VideoSettingsButton';
|
||||
import { isButtonEnabled, isDesktopShareButtonDisabled } from './functions.web';
|
||||
import { ICustomToolbarButton, IToolboxButton, ToolbarButton } from './types';
|
||||
|
||||
|
||||
const microphone = {
|
||||
key: 'microphone',
|
||||
Content: AudioSettingsButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const camera = {
|
||||
key: 'camera',
|
||||
Content: VideoSettingsButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const profile = {
|
||||
key: 'profile',
|
||||
Content: ProfileButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const desktop = {
|
||||
key: 'desktop',
|
||||
Content: ShareDesktopButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
// In Narrow layout and mobile web we are using drawer for popups and that is why it is better to include
|
||||
// all forms of reactions in the overflow menu. Otherwise the toolbox will be hidden and the reactions popup
|
||||
// misaligned.
|
||||
const raisehand = {
|
||||
key: 'raisehand',
|
||||
Content: RaiseHandContainerButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const invite = {
|
||||
key: 'invite',
|
||||
Content: InviteButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const toggleCamera = {
|
||||
key: 'toggle-camera',
|
||||
Content: ToggleCameraButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const videoQuality = {
|
||||
key: 'videoquality',
|
||||
Content: VideoQualityButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const fullscreen = {
|
||||
key: 'fullscreen',
|
||||
Content: FullscreenButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const linkToSalesforce = {
|
||||
key: 'linktosalesforce',
|
||||
Content: LinkToSalesforceButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const shareVideo = {
|
||||
key: 'sharedvideo',
|
||||
Content: SharedVideoButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const shareAudio = {
|
||||
key: 'shareaudio',
|
||||
Content: ShareAudioButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const noiseSuppression = {
|
||||
key: 'noisesuppression',
|
||||
Content: NoiseSuppressionButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const settings = {
|
||||
key: 'settings',
|
||||
Content: SettingsButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const download = {
|
||||
key: 'download',
|
||||
Content: DownloadButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
const help = {
|
||||
key: 'help',
|
||||
Content: HelpButton,
|
||||
group: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the toggle camera button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function useToggleCameraButton() {
|
||||
const toggleCameraEnabled = useSelector(isToggleCameraEnabled);
|
||||
|
||||
if (toggleCameraEnabled) {
|
||||
return toggleCamera;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the desktop sharing button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getDesktopSharingButton() {
|
||||
if (JitsiMeetJS.isDesktopSharingEnabled()) {
|
||||
return desktop;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the fullscreen button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getFullscreenButton() {
|
||||
if (!isIosMobileBrowser() || isIpadMobileBrowser()) {
|
||||
return fullscreen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the "link to salesforce" button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function useLinkToSalesforceButton() {
|
||||
const _isSalesforceEnabled = useSelector(isSalesforceEnabled);
|
||||
|
||||
if (_isSalesforceEnabled) {
|
||||
return linkToSalesforce;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A hook that returns the share audio button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getShareAudioButton() {
|
||||
if (JitsiMeetJS.isDesktopSharingEnabled() && isScreenAudioSupported()) {
|
||||
return shareAudio;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the download button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function useDownloadButton() {
|
||||
const visible = useSelector(
|
||||
(state: IReduxState) => typeof state['features/base/config'].deploymentUrls?.downloadAppsUrl === 'string');
|
||||
|
||||
if (visible) {
|
||||
return download;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the help button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function useHelpButton() {
|
||||
const visible = useSelector(
|
||||
(state: IReduxState) =>
|
||||
typeof state['features/base/config'].deploymentUrls?.userDocumentationURL === 'string'
|
||||
&& getFeatureFlag(state, HELP_BUTTON_ENABLED, true));
|
||||
|
||||
if (visible) {
|
||||
return help;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that could be rendered.
|
||||
*
|
||||
* @param {Object} _customToolbarButtons - An array containing custom buttons objects.
|
||||
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
|
||||
*/
|
||||
export function useToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } {
|
||||
const dekstopSharing = getDesktopSharingButton();
|
||||
const toggleCameraButton = useToggleCameraButton();
|
||||
const _fullscreen = getFullscreenButton();
|
||||
const security = useSecurityDialogButton();
|
||||
const reactions = useReactionsButton();
|
||||
const participants = useParticipantPaneButton();
|
||||
const tileview = useTileViewButton();
|
||||
const cc = useClosedCaptionButton();
|
||||
const recording = useRecordingButton();
|
||||
const liveStreaming = useLiveStreamingButton();
|
||||
const linktosalesforce = useLinkToSalesforceButton();
|
||||
const shareaudio = getShareAudioButton();
|
||||
const whiteboard = useWhiteboardButton();
|
||||
const etherpad = useEtherpadButton();
|
||||
const virtualBackground = useVirtualBackgroundButton();
|
||||
const speakerStats = useSpeakerStatsButton();
|
||||
const shortcuts = useKeyboardShortcutsButton();
|
||||
const embed = useEmbedButton();
|
||||
const feedback = useFeedbackButton();
|
||||
const _download = useDownloadButton();
|
||||
const _help = useHelpButton();
|
||||
|
||||
const buttons: { [key in ToolbarButton]?: IToolboxButton; } = {
|
||||
microphone,
|
||||
camera,
|
||||
profile,
|
||||
desktop: dekstopSharing,
|
||||
chat,
|
||||
raisehand,
|
||||
reactions,
|
||||
'participants-pane': participants,
|
||||
invite,
|
||||
tileview,
|
||||
'toggle-camera': toggleCameraButton,
|
||||
videoquality: videoQuality,
|
||||
fullscreen: _fullscreen,
|
||||
security,
|
||||
closedcaptions: cc,
|
||||
recording,
|
||||
livestreaming: liveStreaming,
|
||||
linktosalesforce,
|
||||
sharedvideo: shareVideo,
|
||||
shareaudio,
|
||||
noisesuppression: noiseSuppression,
|
||||
whiteboard,
|
||||
etherpad,
|
||||
'select-background': virtualBackground,
|
||||
stats: speakerStats,
|
||||
settings,
|
||||
shortcuts,
|
||||
embedmeeting: embed,
|
||||
feedback,
|
||||
download: _download,
|
||||
help: _help
|
||||
};
|
||||
const buttonKeys = Object.keys(buttons) as ToolbarButton[];
|
||||
|
||||
buttonKeys.forEach(
|
||||
key => typeof buttons[key] === 'undefined' && delete buttons[key]);
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
|
||||
prev[id] = {
|
||||
backgroundColor,
|
||||
key: id,
|
||||
id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
};
|
||||
|
||||
return prev;
|
||||
}, {} as { [key: string]: ICustomToolbarButton; });
|
||||
|
||||
return {
|
||||
...buttons,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { CustomOptionButton } from './components';
|
||||
|
||||
export interface IToolboxButton {
|
||||
Content: ComponentType<any>;
|
||||
group: number;
|
||||
@@ -53,3 +55,13 @@ export type IMainToolbarButtonThresholds = Array<{
|
||||
order: Array<ToolbarButton | string>;
|
||||
width: number;
|
||||
}>;
|
||||
|
||||
export interface ICustomToolbarButton {
|
||||
Content?: typeof CustomOptionButton;
|
||||
backgroundColor?: string;
|
||||
group?: number;
|
||||
icon: string;
|
||||
id: string;
|
||||
key?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { playSound } from '../base/sounds/actions';
|
||||
import { showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { RECORDING_OFF_SOUND_ID, RECORDING_ON_SOUND_ID } from '../recording/constants';
|
||||
import { isLiveStreamingRunning, isRecordingRunning } from '../recording/functions';
|
||||
|
||||
import { isRecorderTranscriptionsRunning } from './functions';
|
||||
|
||||
@@ -17,10 +18,10 @@ StateListenerRegistry.register(
|
||||
/* selector */ isRecorderTranscriptionsRunning,
|
||||
/* listener */ (isRecorderTranscriptionsRunningValue, { getState, dispatch }) => {
|
||||
if (isRecorderTranscriptionsRunningValue) {
|
||||
notifyTranscribingStatusChanged(true);
|
||||
notifyTranscribingStatusChanged(getState, true);
|
||||
maybeEmitRecordingNotification(dispatch, getState, true);
|
||||
} else {
|
||||
notifyTranscribingStatusChanged(false);
|
||||
notifyTranscribingStatusChanged(getState, false);
|
||||
maybeEmitRecordingNotification(dispatch, getState, false);
|
||||
}
|
||||
}
|
||||
@@ -58,11 +59,18 @@ function maybeEmitRecordingNotification(dispatch: IStore['dispatch'], getState:
|
||||
/**
|
||||
* Notify external application (if API is enabled) that transcribing has started or stopped.
|
||||
*
|
||||
* @param {Function} getState - The Redux state.
|
||||
* @param {boolean} on - True if transcribing is on, false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
function notifyTranscribingStatusChanged(on: boolean) {
|
||||
function notifyTranscribingStatusChanged(getState: IStore['getState'], on: boolean) {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const state = getState();
|
||||
const isRecording = isRecordingRunning(state);
|
||||
const isStreaming = isLiveStreamingRunning(state);
|
||||
const mode = isRecording ? JitsiRecordingConstants.mode.FILE : JitsiRecordingConstants.mode.STREAM;
|
||||
|
||||
APP.API.notifyRecordingStatusChanged(isRecording || isStreaming, mode, undefined, on);
|
||||
APP.API.notifyTranscribingStatusChanged(on);
|
||||
}
|
||||
}
|
||||
|
||||
26
react/features/video-layout/hooks.ts
Normal file
26
react/features/video-layout/hooks.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { TILE_VIEW_ENABLED } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
|
||||
import TileViewButton from './components/TileViewButton';
|
||||
|
||||
const tileview = {
|
||||
key: 'tileview',
|
||||
Content: TileViewButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the tile view button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useTileViewButton() {
|
||||
const tileViewEnabled = useSelector((state: IReduxState) => getFeatureFlag(state, TILE_VIEW_ENABLED, true));
|
||||
|
||||
if (tileViewEnabled) {
|
||||
return tileview;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openSheet } from '../../../base/dialog/actions';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IconInfoCircle } from '../../../base/icons/svg';
|
||||
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
|
||||
import ConnectionStatusComponent from './ConnectionStatusComponent';
|
||||
import { showConnectionStatus } from '../../../participants-pane/actions.native';
|
||||
|
||||
export interface IProps extends AbstractButtonProps {
|
||||
|
||||
@@ -31,9 +29,7 @@ class ConnectionStatusButton extends AbstractButton<IProps> {
|
||||
_handleClick() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(openSheet(ConnectionStatusComponent, {
|
||||
participantID
|
||||
}));
|
||||
dispatch(showConnectionStatus(participantID));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,13 +83,18 @@ class LocalVideoMenu extends PureComponent<IProps> {
|
||||
styles: bottomSheetStyles.buttons
|
||||
};
|
||||
|
||||
const connectionStatusButtonProps = {
|
||||
...buttonProps,
|
||||
afterClick: undefined
|
||||
};
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderHeader = { this._renderMenuHeader }
|
||||
showSlidingView = { true }>
|
||||
<ToggleSelfViewButton { ...buttonProps } />
|
||||
{ _showDemote && <DemoteToVisitorButton { ...buttonProps } /> }
|
||||
<ConnectionStatusButton { ...buttonProps } />
|
||||
<ConnectionStatusButton { ...connectionStatusButtonProps } />
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,18 @@
|
||||
*/
|
||||
export const DEFAULT_LAST_N = 20;
|
||||
|
||||
/**
|
||||
* The supported video codecs.
|
||||
*
|
||||
* @type {enum}
|
||||
*/
|
||||
export enum VIDEO_CODEC {
|
||||
AV1 = 'av1',
|
||||
H264 = 'h264',
|
||||
VP8 = 'vp8',
|
||||
VP9 = 'vp9'
|
||||
}
|
||||
|
||||
/**
|
||||
* The supported remote video resolutions. The values are currently based on
|
||||
* available simulcast layers.
|
||||
|
||||
27
react/features/virtual-background/hooks.ts
Normal file
27
react/features/virtual-background/hooks.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isScreenVideoShared } from '../screen-share/functions';
|
||||
|
||||
import VideoBackgroundButton from './components/VideoBackgroundButton';
|
||||
import { checkBlurSupport, checkVirtualBackgroundEnabled } from './functions';
|
||||
|
||||
const virtualBackground = {
|
||||
key: 'select-background',
|
||||
Content: VideoBackgroundButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the virtual background button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useVirtualBackgroundButton() {
|
||||
const _checkBlurSupport = checkBlurSupport();
|
||||
const _isScreenVideoShared = useSelector(isScreenVideoShared);
|
||||
const _checkVirtualBackgroundEnabled = useSelector(checkVirtualBackgroundEnabled);
|
||||
|
||||
if (_checkBlurSupport && !_isScreenVideoShared && _checkVirtualBackgroundEnabled) {
|
||||
return virtualBackground;
|
||||
}
|
||||
}
|
||||
23
react/features/whiteboard/hooks.ts
Normal file
23
react/features/whiteboard/hooks.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import WhiteboardButton from './components/web/WhiteboardButton';
|
||||
import { isWhiteboardButtonVisible } from './functions';
|
||||
|
||||
const whiteboard = {
|
||||
key: 'whiteboard',
|
||||
Content: WhiteboardButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the whiteboard button if it is enabled and undefined otherwise.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
export function useWhiteboardButton() {
|
||||
const _isWhiteboardButtonVisible = useSelector(isWhiteboardButtonVisible);
|
||||
|
||||
if (_isWhiteboardButtonVisible) {
|
||||
return whiteboard;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ function occupant_joined(event)
|
||||
|
||||
if participant_count > 1 then
|
||||
if room.created_timestamp == nil then
|
||||
room.created_timestamp = os.time() * 1000; -- Lua provides UTC time in seconds, so convert to milliseconds
|
||||
room.created_timestamp = string.format('%i', os.time() * 1000); -- Lua provides UTC time in seconds, so convert to milliseconds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,6 +16,7 @@ local new_id = require 'util.id'.medium;
|
||||
local filters = require 'util.filters';
|
||||
|
||||
local util = module:require 'util';
|
||||
local ends_with = util.ends_with;
|
||||
local is_vpaas = util.is_vpaas;
|
||||
local room_jid_match_rewrite = util.room_jid_match_rewrite;
|
||||
local get_room_from_jid = util.get_room_from_jid;
|
||||
@@ -246,9 +247,12 @@ module:hook('muc-broadcast-presence', function (event)
|
||||
if identity and identity.id then
|
||||
user_id = session.jitsi_meet_context_user.id;
|
||||
|
||||
-- non-vpass and having a token in correct tenant is considered a moderator
|
||||
if session.jitsi_meet_str_tenant
|
||||
and session.jitsi_web_query_prefix == string.lower(session.jitsi_meet_str_tenant) then
|
||||
if room._data.moderator_id then
|
||||
if room._data.moderator_id == user_id then
|
||||
is_moderator = true;
|
||||
end
|
||||
elseif session.auth_token then
|
||||
-- non-vpass and having a token is considered a moderator
|
||||
is_moderator = true;
|
||||
end
|
||||
end
|
||||
@@ -359,8 +363,19 @@ function process_host_module(name, callback)
|
||||
process_host(name);
|
||||
end
|
||||
end
|
||||
-- if the message received ends with the main domain, these are system messages
|
||||
-- for visitors, let's correct the room name there
|
||||
local function message_handler(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
|
||||
if ends_with(stanza.attr.from, main_domain) then
|
||||
stanza.attr.from = stanza.attr.from:sub(1, -(main_domain:len() + 1))..local_domain;
|
||||
end
|
||||
end
|
||||
|
||||
process_host_module(local_domain, function(host_module, host)
|
||||
host_module:hook('iq/host', stanza_handler, 10);
|
||||
host_module:hook('message/full', message_handler);
|
||||
end);
|
||||
|
||||
-- only live chat is supported for visitors
|
||||
@@ -528,6 +543,7 @@ local function iq_from_main_handler(event)
|
||||
-- if this is update it will either set or remove the password
|
||||
room:set_password(node.attr.password);
|
||||
room._data.meetingId = node.attr.meetingId;
|
||||
room._data.moderator_id = node.attr.moderatorId;
|
||||
local createdTimestamp = node.attr.createdTimestamp;
|
||||
room.created_timestamp = createdTimestamp and tonumber(createdTimestamp) or nil;
|
||||
|
||||
|
||||
@@ -86,7 +86,9 @@ function room_created(event)
|
||||
return ;
|
||||
end
|
||||
|
||||
room.jitsiMetadata = {};
|
||||
if not room.jitsiMetadata then
|
||||
room.jitsiMetadata = {};
|
||||
end
|
||||
end
|
||||
|
||||
function on_message(event)
|
||||
@@ -172,6 +174,10 @@ function process_main_muc_loaded(main_muc, host_module)
|
||||
|
||||
table.insert(event.form, getFormData(room));
|
||||
end);
|
||||
-- The room metadata was updated internally (from another module).
|
||||
host_module:hook("room-metadata-changed", function(event)
|
||||
broadcastMetadata(event.room);
|
||||
end);
|
||||
end
|
||||
|
||||
-- process or waits to process the main muc component
|
||||
|
||||
@@ -68,6 +68,7 @@ local function send_visitors_iq(conference_service, room, type)
|
||||
password = type ~= 'disconnect' and room:get_password() or '',
|
||||
lobby = room._data.lobbyroom and 'true' or 'false',
|
||||
meetingId = room._data.meetingId,
|
||||
moderatorId = room._data.moderator_id, -- can be used from external modules to set single moderator for meetings
|
||||
createdTimestamp = room.created_timestamp and tostring(room.created_timestamp) or nil
|
||||
}):up();
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ local function request_promotion_received(room, from_jid, from_vnode, nick, time
|
||||
-- only for raise hand, ignore lowering the hand
|
||||
if time and time > 0 and (
|
||||
auto_allow_promotion
|
||||
or (user_id and user_id == room._data.moderator_id)
|
||||
or force_promote == 'true') then
|
||||
-- we are in auto-allow mode, let's reply with accept
|
||||
-- we store where the request is coming from so we can send back the response
|
||||
@@ -231,17 +230,6 @@ local function stanza_handler(event)
|
||||
return true; -- stop processing
|
||||
end
|
||||
|
||||
local force_promote = request_promotion.attr.forcePromote;
|
||||
if force_promote == 'true' and not is_vpaas(room) then
|
||||
-- allow force promote only in case there are no moderators in the room
|
||||
for _, occupant in room:each_occupant() do
|
||||
if occupant.role == 'moderator' and not is_admin(occupant.bare_jid) then
|
||||
force_promote = false;
|
||||
break;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local display_name = visitors_iq:get_child_text('nick', 'http://jabber.org/protocol/nick');
|
||||
processed = request_promotion_received(
|
||||
room,
|
||||
@@ -250,7 +238,7 @@ local function stanza_handler(event)
|
||||
display_name,
|
||||
tonumber(request_promotion.attr.time),
|
||||
request_promotion.attr.userId,
|
||||
force_promote
|
||||
request_promotion.attr.forcePromote
|
||||
);
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user