mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-10-02 03:56:58 +00:00
Compare commits
21 Commits
5378
...
new-update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63754f01a5 | ||
|
|
dcf677bdf0 | ||
|
|
eaea2cdef0 | ||
|
|
bcad87e894 | ||
|
|
d500493649 | ||
|
|
8ad3ef0022 | ||
|
|
d0664d9ea0 | ||
|
|
45c40d7afc | ||
|
|
e33edfd2a5 | ||
|
|
96b2a07172 | ||
|
|
0e8c01af75 | ||
|
|
3049ad6dbf | ||
|
|
9a30ad87a7 | ||
|
|
11c05560c4 | ||
|
|
d92fa59c74 | ||
|
|
4eb1d16faa | ||
|
|
91adc70724 | ||
|
|
15b083c182 | ||
|
|
26fdaf7b8d | ||
|
|
64a9874d63 | ||
|
|
753cffc458 |
@@ -78,7 +78,7 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
@@ -109,6 +109,7 @@ gradle.projectsEvaluated {
|
||||
def dropboxActivity = """
|
||||
<activity
|
||||
android:configChanges="keyboard|orientation"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask"
|
||||
android:name="com.dropbox.core.android.AuthActivity">
|
||||
<intent-filter>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
android:installLocation="auto">
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
@@ -13,6 +14,7 @@
|
||||
android:resource="@xml/app_restrictions" />
|
||||
<activity
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:name=".MainActivity"
|
||||
|
||||
@@ -18,9 +18,9 @@ buildscript {
|
||||
|
||||
ext {
|
||||
buildToolsVersion = "30.0.3"
|
||||
compileSdkVersion = 30
|
||||
compileSdkVersion = 31
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 30
|
||||
targetSdkVersion = 31
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
|
||||
@@ -24,6 +24,7 @@ android.enableDexingArtifactTransform.desugaring=false
|
||||
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.bundle.enableUncompressedNativeLibs=false
|
||||
|
||||
appVersion=21.4.0
|
||||
sdkVersion=3.10.0
|
||||
appVersion=21.4.1
|
||||
sdkVersion=3.10.2
|
||||
|
||||
@@ -82,7 +82,7 @@ class OngoingNotification {
|
||||
}
|
||||
|
||||
Intent notificationIntent = new Intent(context, context.getClass());
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);
|
||||
|
||||
@@ -125,7 +125,7 @@ class OngoingNotification {
|
||||
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
|
||||
intent.setAction(action.getName());
|
||||
PendingIntent pendingIntent
|
||||
= PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
= PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);
|
||||
String title = context.getString(titleId);
|
||||
return new NotificationCompat.Action(0, title, pendingIntent);
|
||||
}
|
||||
|
||||
@@ -1503,14 +1503,14 @@ export default {
|
||||
*
|
||||
* @param {boolean} didHaveVideo indicates if there was a camera video being
|
||||
* used, before switching to screen sharing.
|
||||
* @param {boolean} ignoreDidHaveVideo indicates if the camera video should be
|
||||
* ignored when switching screen sharing off.
|
||||
* @param {boolean} wasVideoMuted indicates if the video was muted, before
|
||||
* switching to screen sharing.
|
||||
* @return {Promise} resolved after the screen sharing is turned off, or
|
||||
* rejected with some error (no idea what kind of error, possible GUM error)
|
||||
* in case it fails.
|
||||
* @private
|
||||
*/
|
||||
async _turnScreenSharingOff(didHaveVideo, ignoreDidHaveVideo) {
|
||||
async _turnScreenSharingOff(didHaveVideo) {
|
||||
this._untoggleScreenSharing = null;
|
||||
this.videoSwitchInProgress = true;
|
||||
|
||||
@@ -1554,7 +1554,7 @@ export default {
|
||||
|
||||
APP.store.dispatch(setScreenAudioShareState(false));
|
||||
|
||||
if (didHaveVideo && !ignoreDidHaveVideo) {
|
||||
if (didHaveVideo) {
|
||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||
.then(([ stream ]) => {
|
||||
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
||||
@@ -1605,10 +1605,9 @@ export default {
|
||||
* @param {Array<string>} [options.desktopSharingSources] - Array with the
|
||||
* sources that have to be displayed in the desktop picker window ('screen',
|
||||
* 'window', etc.).
|
||||
* @param {boolean} ignoreDidHaveVideo - if true ignore if video was on when sharing started.
|
||||
* @return {Promise.<T>}
|
||||
*/
|
||||
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}, ignoreDidHaveVideo) {
|
||||
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
|
||||
logger.debug(`toggleScreenSharing: ${toggle}`);
|
||||
if (this.videoSwitchInProgress) {
|
||||
return Promise.reject('Switch in progress.');
|
||||
@@ -1634,7 +1633,7 @@ export default {
|
||||
}
|
||||
|
||||
return this._untoggleScreenSharing
|
||||
? this._untoggleScreenSharing(ignoreDidHaveVideo)
|
||||
? this._untoggleScreenSharing()
|
||||
: Promise.resolve();
|
||||
},
|
||||
|
||||
@@ -2477,8 +2476,8 @@ export default {
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.TOGGLE_SCREENSHARING, ({ enabled, audioOnly, ignoreDidHaveVideo }) => {
|
||||
this.toggleScreenSharing(enabled, { audioOnly }, ignoreDidHaveVideo);
|
||||
UIEvents.TOGGLE_SCREENSHARING, ({ enabled, audioOnly }) => {
|
||||
this.toggleScreenSharing(enabled, { audioOnly });
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
17
config.js
17
config.js
@@ -545,19 +545,6 @@ var config = {
|
||||
// '__end'
|
||||
// ],
|
||||
|
||||
// Holds values related to toolbar visibility control.
|
||||
// toolbarConfig: {
|
||||
// // Moved from interfaceConfig.INITIAL_TOOLBAR_TIMEOUT
|
||||
// // The initial numer of miliseconds for the toolbar buttons to be visible on screen.
|
||||
// initialTimeout: 20000,
|
||||
// // Moved from interfaceConfig.TOOLBAR_TIMEOUT
|
||||
// // Number of miliseconds for the toolbar buttons to be visible on screen.
|
||||
// timeout: 4000,
|
||||
// // Moved from interfaceConfig.TOOLBAR_ALWAYS_VISIBLE
|
||||
// // Whether toolbar should be always visible or should hide after x miliseconds.
|
||||
// alwaysVisible: false
|
||||
// },
|
||||
|
||||
// Toolbar buttons which have their click event exposed through the API on
|
||||
// `toolbarButtonClicked` event instead of executing the normal click routine.
|
||||
// buttonsWithNotifyClick: [
|
||||
@@ -909,10 +896,6 @@ var config = {
|
||||
*/
|
||||
// dynamicBrandingUrl: '',
|
||||
|
||||
// When true the user cannot add more images to be used as virtual background.
|
||||
// Only the default ones from will be available.
|
||||
// disableAddingBackgroundImages: false,
|
||||
|
||||
// Sets the background transparency level. '0' is fully transparent, '1' is opaque.
|
||||
// backgroundAlpha: 1,
|
||||
|
||||
|
||||
@@ -213,7 +213,6 @@
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
-ms-overflow-style: none;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
@@ -222,13 +221,8 @@
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
scrollbar-width: none;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg:hover {
|
||||
|
||||
@@ -75,7 +75,3 @@
|
||||
top: 0;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.shift-right .details-container {
|
||||
margin-left: calc(#{$sidebarWidth} / 2);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ var interfaceConfig = {
|
||||
|
||||
CLOSE_PAGE_GUEST_HINT: false, // A html text to be shown to guests on the close page, false disables it
|
||||
|
||||
// Connection indicators (
|
||||
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
|
||||
// CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT,
|
||||
// CONNECTION_INDICATOR_DISABLED) got moved to config.js.
|
||||
|
||||
DEFAULT_BACKGROUND: '#474747',
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: 'me',
|
||||
DEFAULT_LOGO_URL: 'images/watermark.svg',
|
||||
@@ -34,6 +39,9 @@ var interfaceConfig = {
|
||||
|
||||
DISABLE_DOMINANT_SPEAKER_INDICATOR: false,
|
||||
|
||||
// Deprecated. Please use disableModeratorIndicator from config.js
|
||||
// DISABLE_FOCUS_INDICATOR: false,
|
||||
|
||||
/**
|
||||
* If true, notifications regarding joining/leaving are no longer displayed.
|
||||
*/
|
||||
@@ -89,6 +97,7 @@ var interfaceConfig = {
|
||||
*/
|
||||
HIDE_INVITE_MORE_HEADER: false,
|
||||
|
||||
INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
JITSI_WATERMARK_LINK: 'https://jitsi.org',
|
||||
|
||||
LANG_DETECTION: true, // Allow i18n to detect the system language
|
||||
@@ -174,6 +183,16 @@ var interfaceConfig = {
|
||||
*/
|
||||
SUPPORT_URL: 'https://community.jitsi.org/',
|
||||
|
||||
TOOLBAR_ALWAYS_VISIBLE: false,
|
||||
|
||||
/**
|
||||
* DEPRECATED!
|
||||
* This config was moved to config.js as `toolbarButtons`.
|
||||
*/
|
||||
// TOOLBAR_BUTTONS: [],
|
||||
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
|
||||
// Browsers, in addition to those which do not fully support WebRTC, that
|
||||
// are not supported and should show the unsupported browser page.
|
||||
UNSUPPORTED_BROWSERS: [],
|
||||
@@ -238,28 +257,6 @@ var interfaceConfig = {
|
||||
PHONE_NUMBER_REGEX
|
||||
*/
|
||||
|
||||
// -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------
|
||||
|
||||
// Connection indicators (
|
||||
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,
|
||||
// CONNECTION_INDICATOR_AUTO_HIDE_TIMEOUT,
|
||||
// CONNECTION_INDICATOR_DISABLED) got moved to config.js.
|
||||
|
||||
// Please use disableModeratorIndicator from config.js
|
||||
// DISABLE_FOCUS_INDICATOR: false,
|
||||
|
||||
// Moved to config.js as `toolbarConfig.initialTimeout`.
|
||||
// INITIAL_TOOLBAR_TIMEOUT: 20000,
|
||||
|
||||
// Moved to config.js as `toolbarConfig.alwaysVisible`.
|
||||
// TOOLBAR_ALWAYS_VISIBLE: false,
|
||||
|
||||
// This config was moved to config.js as `toolbarButtons`.
|
||||
// TOOLBAR_BUTTONS: [],
|
||||
|
||||
// Moved to config.js as `toolbarConfig.timeout`.
|
||||
// TOOLBAR_TIMEOUT: 4000,
|
||||
|
||||
// Allow all above example options to include a trailing comma and
|
||||
// prevent fear when commenting out the last value.
|
||||
// eslint-disable-next-line sort-keys
|
||||
|
||||
@@ -299,7 +299,7 @@ PODS:
|
||||
- react-native-video/Video (= 5.1.1)
|
||||
- react-native-video/Video (5.1.1):
|
||||
- React-Core
|
||||
- react-native-webrtc (1.92.1):
|
||||
- react-native-webrtc (1.92.2):
|
||||
- React-Core
|
||||
- react-native-webview (11.0.2):
|
||||
- React-Core
|
||||
@@ -584,7 +584,7 @@ SPEC CHECKSUMS:
|
||||
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
||||
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
|
||||
react-native-video: 1574074179ecaf6a9dd067116c8f31bf9fec15c8
|
||||
react-native-webrtc: 77b969fe6bc5b7c93b455f93a13698812fb8bb4e
|
||||
react-native-webrtc: 41526e4060dac373c18676f866962d4180ee47b9
|
||||
react-native-webview: b2542d6fd424bcc3e3b2ec5f854f0abb4ec86c87
|
||||
React-RCTActionSheet: bcbc311dc3b47bc8efb2737ff0940239a45789a9
|
||||
React-RCTAnimation: 65f61080ce632f6dea23d52e354ffac9948396c6
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>21.4.0</string>
|
||||
<string>21.4.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>21.4.0</string>
|
||||
<string>21.4.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>21.4.0</string>
|
||||
<string>21.4.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>21.4.0</string>
|
||||
<string>21.4.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>CLKComplicationPrincipalClass</key>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.10.0</string>
|
||||
<string>3.10.4</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
}
|
||||
|
||||
- (void)destroyReactNativeBridge {
|
||||
[_bridgeWrapper invalidate];
|
||||
_bridgeWrapper = nil;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,4 +34,6 @@
|
||||
|
||||
@property (nonatomic, readonly, strong) RCTBridge *bridge;
|
||||
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,6 +33,10 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)invalidate {
|
||||
[_bridge invalidate];
|
||||
}
|
||||
|
||||
#pragma mark helper methods for getting the packager URL
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@@ -248,8 +248,6 @@
|
||||
"micPermissionDeniedError": "Vous n'avez pas autorisé l'utilisation de votre microphone. Vous pouvez toujours participer à la conférence, mais les autres ne vont pas vous entendre. Utilisez le bouton du microphone dans la barre d'adresse pour résoudre ce problème.",
|
||||
"micTimeoutError": "Impossible de démarrer la source audio. Délai dépassé!",
|
||||
"micUnknownError": "Vous ne pouvez pas utiliser le microphone pour une raison inconnue.",
|
||||
"moderationAudioLabel": "Autoriser les participants à réactiver leur micro",
|
||||
"moderationVideoLabel": "Autoriser les participants à démarrer leur vidéo",
|
||||
"muteEveryoneElseDialog": "Une fois leur micro coupé, vous ne pourrez plus le réactiver, mais ils pourront l'activer par eux-mêmes à tout moment.",
|
||||
"muteEveryoneElseTitle": "Couper le micro de tout le monde sauf de {{whom}} ?",
|
||||
"muteEveryoneDialog": "Êtes-vous sûr de vouloir couper les micros de tout le monde ? Vous ne pourrez plus réactiver leur micro, mais ils pourront l'activer par eux-mêmes à tout moment.",
|
||||
@@ -565,21 +563,12 @@
|
||||
"close": "Fermer",
|
||||
"headings": {
|
||||
"lobby": "Salle d'attente ({{count}})",
|
||||
"participantsList": "Participants de la réunion ({{count}})",
|
||||
"waitingLobby": "Dans la salle d'attente ({{count}})"
|
||||
"participantsList": "Participants de la réunion ({{count}})"
|
||||
},
|
||||
"actions": {
|
||||
"allow": "Autoriser les participant à:",
|
||||
"blockEveryoneMicCamera": "Bloquer tous les micros et caméras",
|
||||
"invite": "Inviter quelqu'un",
|
||||
"askUnmute": "Demander de réactiver le micro",
|
||||
"mute": "Couper le micro",
|
||||
"muteAll": "Couper le micro de tout le monde",
|
||||
"muteEveryoneElse": "Couper le micro de tous les autres",
|
||||
"startModeration": "Réactiver son micro ou démarrer sa vidéo",
|
||||
"stopEveryonesVideo": "Couper toutes les caméras",
|
||||
"stopVideo": "Couper la vidéo",
|
||||
"unblockEveryoneMicCamera": "Débloquer tous les micros et caméras"
|
||||
"muteAll": "couper le micro de tout le monde",
|
||||
"stopVideo": "couper la vidéo"
|
||||
}
|
||||
},
|
||||
"passwordSetRemotely": "défini par un autre participant",
|
||||
|
||||
@@ -17,17 +17,21 @@
|
||||
"inviteMoreMailSubject": "Rejónher la conferéncia {{appName}}",
|
||||
"inviteMorePrompt": "Convidar mai de monde",
|
||||
"linkCopied": "Ligam copiat al quichapapièrs",
|
||||
"loading": "Recèrca de monde e de numèro de telefòn",
|
||||
"loadingNumber": "Validacion del numèro de telefòn",
|
||||
"loadingPeople": "Recèrca de monde de convidar",
|
||||
"noResults": "Pas cap de resultat trobat",
|
||||
"noValidNumbers": "Picatz lo numèro de telefòn",
|
||||
"outlookEmail": "Outlook Email",
|
||||
"searchNumbers": "Apondre de numèros de telefòn",
|
||||
"searchPeople": "Cercar de monde",
|
||||
"searchPeopleAndNumbers": "Cercar de monde o apondre lor numèros de telefòn",
|
||||
"shareInvite": "Partejar invitacion conferéncia",
|
||||
"shareLink": "Partejar lo ligam de la conferéncia per convidar de monde",
|
||||
"shareStream": "Partejar la ligam de la difusion en dirècte",
|
||||
"telephone": "Telefòn : {{number}}",
|
||||
"title": "Convidatz de monde a vòstra conferéncia",
|
||||
"yahooEmail": "Yahoo Email",
|
||||
"phoneNumbers": "numèros de telefòn",
|
||||
"searching": "Recèrca...",
|
||||
"sipAddresses": "adreças sip"
|
||||
"yahooEmail": "Yahoo Email"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
@@ -75,18 +79,12 @@
|
||||
"smileysPanel": "Panèl d’Emoji",
|
||||
"title": "Messatjariá",
|
||||
"titleWithPolls": "Messatjariá",
|
||||
"you": "vos",
|
||||
"enter": "Dintrar dins la sala",
|
||||
"tabs": {
|
||||
"chat": "Messatjariá",
|
||||
"polls": "Sondatges"
|
||||
}
|
||||
"you": "vos"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"buttonText": "Installar l’extension Chrome",
|
||||
"dontShowAgain": "Me mostrar pas mai aquò",
|
||||
"installExtensionText": "Installar l’extension per l’integracion de Google Calendar e Office 365",
|
||||
"close": "Tampar"
|
||||
"installExtensionText": "Installar l’extension per l’integracion de Google Calendar e Office 365"
|
||||
},
|
||||
"connectingOverlay": {
|
||||
"joiningRoom": "Connexion a vòstra reünion…"
|
||||
@@ -104,7 +102,8 @@
|
||||
"FETCH_SESSION_ID": "Obtencion de session-id...",
|
||||
"GET_SESSION_ID_ERROR": "Obténer l’error session-id : {{code}}",
|
||||
"GOT_SESSION_ID": "Obtencion de session-id... Fach",
|
||||
"LOW_BANDWIDTH": "La vidèo per {{displayName}} es estada copada per estalviar la banda passanta"
|
||||
"LOW_BANDWIDTH": "La vidèo per {{displayName}} es estada copada per estalviar la banda passanta",
|
||||
"RECONNECTING": "Un problèma ret s'es produita. Reconnexion en cors..."
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "Adreça :",
|
||||
@@ -157,6 +156,7 @@
|
||||
"ifHaveApp": "S’avètz ja l’aplicacion :",
|
||||
"joinInApp": "Rejónher la conferéncia en utilizant l’aplicacion",
|
||||
"launchWebButton": "Lançar del navigador",
|
||||
"openApp": "Telecargar l’aplicacion",
|
||||
"title": "Aviada de vòstra conferéncia dins {{app}}…",
|
||||
"tryAgainButton": "Tornar ensajar del burèu"
|
||||
},
|
||||
@@ -214,12 +214,17 @@
|
||||
"e2eeWarning": "AVERTIMENT : pas totes los participants d'aquesta conferéncia semblan poder suportar lo chiframent del cap a la fin. Se l'activatz poiràn pas vos veire nimai vos entendre.",
|
||||
"enterDisplayName": "Volgatz picar vòstre nom aquí",
|
||||
"error": "Error",
|
||||
"externalInstallationMsg": "Avètz d'installar nòstra extension de partiment d'ecran.",
|
||||
"externalInstallationTitle": "Extension requesida",
|
||||
"goToStore": "Anar al webstore",
|
||||
"gracefulShutdown": "Lo servici es actualament en mantenença. Ensajatz tornamai pus tard.",
|
||||
"grantModeratorDialog": "Volètz vertadièrament far venir aqueste participant moderator ?",
|
||||
"grantModeratorTitle": "Passar moderator",
|
||||
"IamHost": "Soi l’òste",
|
||||
"incorrectPassword": "Nom de compte o senhal incorrècte",
|
||||
"incorrectRoomLockPassword": "Senhal incorrècte",
|
||||
"inlineInstallationMsg": "Avètz d'installar nòstra extension de partiment d'ecran.",
|
||||
"inlineInstallExtension": "Installar ara",
|
||||
"internalError": "Òu ! Quicòm a pas foncionat. L'error seguenta s'es producha : {{error}}",
|
||||
"internalErrorTitle": "Error intèrna",
|
||||
"kickMessage": "Podètz contactat {{participantDisplayName}} per mai de detalhs.",
|
||||
@@ -229,6 +234,7 @@
|
||||
"kickTitle": "Ai ! {{participantDisplayName}} vos a forabandit de la conferéncia",
|
||||
"liveStreaming": "La difusion en dirècte es estada arrestada",
|
||||
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Impossible pendent un enregistrament actiu",
|
||||
"liveStreamingDisabledForGuestTooltip": "Los convidats pòdon pas aviar una difusion en dirècte.",
|
||||
"liveStreamingDisabledTooltip": "Difusion en dirècte desactivada.",
|
||||
"lockMessage": "Impossible de verrolhar la conferéncia.",
|
||||
"lockRoom": "Ajustar un $t(lockRoomPasswordUppercase) a la conferéncia",
|
||||
@@ -263,6 +269,7 @@
|
||||
"readMore": "mai",
|
||||
"recording": "Enregistrament",
|
||||
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Impossible pendent una difusion activa",
|
||||
"recordingDisabledForGuestTooltip": "Los convits pòdon pas lançar d’enregistraments.",
|
||||
"recordingDisabledTooltip": "L’enregistrament es desactivat.",
|
||||
"rejoinNow": "Participar ara",
|
||||
"remoteControlAllowedMessage": "{{user}} a acceptat vòstra demanda de contraròtle alonhat !",
|
||||
@@ -282,6 +289,10 @@
|
||||
"screenSharingAudio": "Partejar l’àudio",
|
||||
"screenSharingFailed": "Ops ! Quicòm a trucat, avèm pas pogut començar lo partiment d'ecran!",
|
||||
"screenSharingFailedTitle": "Fracàs del partiment d'ecran !",
|
||||
"screenSharingFailedToInstall": "Òu ! Fracàs de l'installacion de partatge d'ecran.",
|
||||
"screenSharingFailedToInstallTitle": "Fracàs de l'installacion de partatge d'ecran",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Quicòm a fach mèuca quand èrem a ensajar de partejar vòstre ecran. Mercés de verificar qu’avètz donat l’autorizacion de lo partejar.",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Ops ! Avèm pas pogut aviar lo partatge d’ecran.",
|
||||
"screenSharingPermissionDeniedError": "Òps ! Quicòm s'es pas ben passat amb l'autorizacion de vòstra autorizacion de partatge d'ecran. Mercés de recargar e tornar ensajar.",
|
||||
"sendPrivateMessage": "Avètz recentament recebut un messatge privat. Avètz ensajat d’i respondre en privat, o volètz enviar lo messatge al grop ?",
|
||||
"sendPrivateMessageCancel": "Enviar al grop",
|
||||
@@ -294,6 +305,7 @@
|
||||
"shareVideoTitle": "Partejar una vidèo",
|
||||
"shareYourScreen": "Partejar vòstre ecran",
|
||||
"shareYourScreenDisabled": "Lo partiment d’ecran es desactivat.",
|
||||
"shareYourScreenDisabledForGuest": "Los convits pòdon pas partejar l’ecran.",
|
||||
"startLiveStreaming": "Aviar una difusion en dirècte",
|
||||
"startRecording": "Arrestar l'enregistrament",
|
||||
"startRemoteControlErrorMessage": "Una error s'es produsida en ensajar de començar la session de contraròtle a distància !",
|
||||
@@ -313,47 +325,9 @@
|
||||
"userPassword": "senhal utilizaire",
|
||||
"WaitForHostMsg": "La conferéncia <b>{{room}}</b> a pas encara començat. Se sètz l’òst volgatz ben vos identificar. Autrament esperatz qu’arribe l’òste.",
|
||||
"WaitForHostMsgWOk": "La conferéncia <b>{{room}}</b> a pas encara començat. Se sètz l’òst volgatz ben clicar Ok per vos identificar. Autrament esperatz qu’arribe l’òste.",
|
||||
"WaitingForHost": "En espèra de l’òste...",
|
||||
"Yes": "Òc",
|
||||
"yourEntireScreen": "Vòstre ecran complet",
|
||||
"authenticationRequired": "Autentificacion requerida",
|
||||
"login": "Connexion",
|
||||
"muteEveryoneElsesVideoDialog": "Un còp la camèra desactivada, poiretz pas la reactivar, mas pòdon la reactivar quand vòlgan.",
|
||||
"WaitingForHostTitle": "En espèra de l’òste...",
|
||||
"embedMeeting": "Integrar la conferéncia",
|
||||
"hideShareAudioHelper": "Afichar pas mai aquesta fenèstra",
|
||||
"micTimeoutError": "Aviada impossibla de la font àudio. Relambi despassat !",
|
||||
"moderationAudioLabel": "Permetre als convidats de se copar lo son",
|
||||
"moderationVideoLabel": "Permetre als convidats d’aviar lor vidèo",
|
||||
"muteEveryoneDialogModerationOn": "Los participants pòdon enviar una requèsta per parlar quand vòlgan.",
|
||||
"muteEveryoneElsesVideoTitle": "Arrestar la vidèo de tot lo monde levat {{whom}} ?",
|
||||
"muteEveryonesVideoTitle": "Arrestar la vidèo de tot lo monde ?",
|
||||
"muteParticipantsVideoButton": "Arrestar la camèra",
|
||||
"muteParticipantsVideoTitle": "Desactivar la camèra d’aqueste participant ?",
|
||||
"noDropboxToken": "Cap de geton Dropbox pas valid",
|
||||
"password": "Senhal",
|
||||
"sessionRestarted": "Sonada reaviada pel pont",
|
||||
"shareAudio": "Contunhar",
|
||||
"shareAudioTitle": "Cossí partejar l’àudio",
|
||||
"shareAudioWarningTitle": "Devètz arrestar lo partiment d’ecran abans lo partiment d’àudio",
|
||||
"shareAudioWarningH1": "Se volètz partejar pas que l’àudio :",
|
||||
"shareAudioWarningD1": "devètz arrestar lo partiment d’ecran abans lo partiment d’àudio.",
|
||||
"shareMediaWarningGenericH2": "Se volètz partejar vòstre ecran e l’àudio",
|
||||
"shareScreenWarningTitle": "Devètz arrestar lo partiment àudio abans lo partiment de l’ecran",
|
||||
"shareScreenWarningH1": "Se volètz partejar pas que l’ecran :",
|
||||
"shareScreenWarningD1": "devètz arrestar lo partiment àudio abans lo partiment de l’ecran.",
|
||||
"sharedVideoLinkPlaceholder": "Ligam YouTube o ligam vidèo dirèct",
|
||||
"userIdentifier": "Identificador utilizaire",
|
||||
"cameraTimeoutError": "Aviada impossibla de la font vidèo. Relambi despassat !",
|
||||
"muteEveryonesVideoDialog": "Los participants pòdon activar lor vidèo quand vòlgan.",
|
||||
"muteEveryonesVideoDialogModerationOn": "Los participants pòdon enviar una requèsta per activar lor vidèo quand vòlgan.",
|
||||
"muteEveryonesVideoDialogOk": "Desactivar",
|
||||
"permissionErrorTitle": "Autorizacion requerida",
|
||||
"videoLink": "ligam vidèo",
|
||||
"viewUpgradeOptions": "Veire las opcions de mesa a nivèl",
|
||||
"viewUpgradeOptionsTitle": "Avètz descobèrt una foncionalitat premium !",
|
||||
"e2eeDisabledDueToMaxModeDescription": "Impossible d'activar lo chiframent del cap a la fin a causa d'un nombre tròp bèl de participants dins la conferéncia.",
|
||||
"remoteUserControls": "Contraròtle a distància de l'utilizaire {{username}}",
|
||||
"e2eeWillDisableDueToMaxModeDescription": "AVERTIMENT : lo chiframent del cap a la fin serà automaticament desactivat se mai de participants rejonhent la conferéncia."
|
||||
"yourEntireScreen": "Vòstre ecran complet"
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": "ara es {{status}}"
|
||||
@@ -374,8 +348,7 @@
|
||||
"good": "Bona",
|
||||
"rateExperience": "Mercés de donar una nòta a vòstra experiéncia",
|
||||
"veryBad": "Fòrça marrida",
|
||||
"veryGood": "Fòrça bona",
|
||||
"star": "Estela"
|
||||
"veryGood": "Fòrça bona"
|
||||
},
|
||||
"helpView": {
|
||||
"header": "Centre d’ajuda"
|
||||
@@ -415,10 +388,7 @@
|
||||
"numbers": "Sonar de numèros",
|
||||
"password": "$t(lockRoomPasswordUppercase) :",
|
||||
"title": "Partejar",
|
||||
"tooltip": "Partejar lo ligam e las informacions d’aquesta conferéncia",
|
||||
"copyNumber": "Copiar lo numèro",
|
||||
"inviteTextiOSPersonal": "{{name}} vos convida a la conferéncia.",
|
||||
"sip": "adreça SIP"
|
||||
"tooltip": "Partejar lo ligam e las informacions d’aquesta conferéncia"
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "Avèm un pauc patit a manténer la connexion.",
|
||||
@@ -449,7 +419,7 @@
|
||||
"toggleScreensharing": "Caplevar entre camèra e partatge d'ecran",
|
||||
"toggleShortcuts": "Mostrar o amagar los acorchis clavièr",
|
||||
"videoMute": "Aviar o arrestar vòstra camèra",
|
||||
"toggleParticipantsPane": "Afichar o amagar lo panèl dels participants"
|
||||
"videoQuality": "Gerir la qualitat de las sonadas"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Sèm a ensajar de liurar de ressorças flux. Mercés de tornar ensajar dins una estona.",
|
||||
@@ -514,11 +484,7 @@
|
||||
"passwordField": "Picatz lo senhal de la conferéncia",
|
||||
"passwordJoinButton": "Rejónher",
|
||||
"reject": "Regetar",
|
||||
"toggleLabel": "Activar la sala d'espèra",
|
||||
"admit": "Acceptar",
|
||||
"admitAll": "Tot acceptar",
|
||||
"rejectAll": "Tot regetar",
|
||||
"errorMissingPassword": "Mercés de picar lo senhal de la conferéncia"
|
||||
"toggleLabel": "Activar la sala d'espèra"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -563,6 +529,7 @@
|
||||
"disconnected": "desconnectat",
|
||||
"focus": "Focus de conferéncia",
|
||||
"focusFail": "{{component}} es pas disponible - ensajatz tornamai dins {{ms}} sec",
|
||||
"grantedTo": "Dreches moderator acordats a {{to}} !",
|
||||
"invitedOneMember": "{{name}} es estat convidat",
|
||||
"invitedThreePlusMembers": "{{name}} e {{count}} autres son estats convidats",
|
||||
"invitedTwoMembers": "{{first}} e {{second}} son estats convidats",
|
||||
@@ -588,22 +555,7 @@
|
||||
"startSilentTitle": "Avètz jonch sens cap de sortida àudio !",
|
||||
"suboptimalBrowserWarning": "Planhèm que vòstra experiéncia de la conferéncia siá pas de las bonas. Sèm a cercar de solucions per melhorar aquò, d’aquel temps, ensajatz un dels <a href='{{recommendedBrowserPageLink}}' target='_blank'>navegators compatibles</a>.",
|
||||
"suboptimalExperienceTitle": "Avertiment del navegador",
|
||||
"unmute": "Restablir lo son",
|
||||
"allowAction": "Autorizar",
|
||||
"videoMutedRemotelyTitle": "{{moderator}} a copat vòstra camèra",
|
||||
"moderationInEffectTitle": "Lo moderator a amudit vòstre microfòn",
|
||||
"moderationInEffectVideoTitle": "Lo moderator a blocat vòstra camèra",
|
||||
"videoMutedRemotelyDescription": "La podètz totjorn tornar activar.",
|
||||
"moderationInEffectCSTitle": "Lo moderator a blocat lo partiment d’ecran",
|
||||
"moderationRequestFromModerator": "L’òste volriá que restabliguèssetz vòstre son",
|
||||
"moderationRequestFromParticipant": "Vòl parlar",
|
||||
"moderationStartedTitle": "Moderacion començada",
|
||||
"moderationStoppedTitle": "Moderacion arrestada",
|
||||
"moderationToggleDescription": "per {{participantDisplayName}}",
|
||||
"raiseHandAction": "Levar la man",
|
||||
"reactionSounds": "Desactivar los sons",
|
||||
"groupTitle": "Notificacions",
|
||||
"hostAskedUnmute": "Lo moderator vos que parletz"
|
||||
"unmute": "Restablir lo son"
|
||||
},
|
||||
"passwordDigitsOnly": "Fins a {{number}} chifras",
|
||||
"passwordSetRemotely": "causit per qualqu'un mai",
|
||||
@@ -797,6 +749,7 @@
|
||||
"chat": "Passar a la fenèstra chat",
|
||||
"document": "Tampar los documents partejats",
|
||||
"download": "Telecargar nòstra aplicacion",
|
||||
"e2ee": "Chiframent del cap a la fin",
|
||||
"embedMeeting": "Conferéncia integrada",
|
||||
"feedback": "Daissar un comentari",
|
||||
"fullScreen": "Passar al ecran complèt",
|
||||
@@ -865,8 +818,8 @@
|
||||
"muteEveryone": "Rendre mut tot lo monde",
|
||||
"noAudioSignalDesc": "S’avètz pas volontàriament copat lo son a partir dels paramètres sistèma o material, pensatz de cambiar d’aparelh.",
|
||||
"noAudioSignalDescSuggestion": "S’avètz pas volontàriament copat lo son a partir dels paramètres sistèma o material, pensatz d’utilizar un autre aparelh suggerit.",
|
||||
"noAudioSignalDialInDesc": "Podètz tanben sonar en utilizant :",
|
||||
"noAudioSignalDialInLinkDesc": "Numèros de sonada",
|
||||
"noAudioSignalDialInDesc": "",
|
||||
"noAudioSignalDialInLinkDesc": "",
|
||||
"noAudioSignalTitle": "I a pas cap de son en entrada del microfòn !",
|
||||
"noisyAudioInputDesc": "Sembla que vòstre microfòn mene bruch, pensatz de lo copar o de lo cambiar.",
|
||||
"noisyAudioInputTitle": "Vòstre microfòn sembla brusent !",
|
||||
@@ -884,25 +837,15 @@
|
||||
"speakerStats": "Estatisticas parladors",
|
||||
"startScreenSharing": "Aviar lo partatge d’ecran",
|
||||
"startSubtitles": "Aviar los sostítols",
|
||||
"startvideoblur": "Trebolar mon rèire-plan",
|
||||
"stopScreenSharing": "Arrestar lo partatge d’ecran",
|
||||
"stopSharedVideo": "Arrestar la vidèo Youtube",
|
||||
"stopSubtitles": "Arrestar los sostítols",
|
||||
"stopvideoblur": "Desactivar lo borrolatge del rèire-plan",
|
||||
"talkWhileMutedPopup": "Ensajatz de parlar ? Vòstre microfòn es copat.",
|
||||
"tileViewToggle": "Activar/Desactivar la vista en mosaïc",
|
||||
"toggleCamera": "Passar a la camèra",
|
||||
"videomute": "Aviar / Arrestar la camèra",
|
||||
"openReactionsMenu": "Dobrir lo menú de reaccions",
|
||||
"participants": "Participants",
|
||||
"shareaudio": "Partejar l’àudio",
|
||||
"silence": "Amudir",
|
||||
"surprised": "Suspresa",
|
||||
"selectBackground": "Seleccionar un rèireplan",
|
||||
"audioSettings": "Paramètres àudio",
|
||||
"videoSettings": "Paramètres vidèo",
|
||||
"laugh": "Rire",
|
||||
"muteEveryonesVideo": "Desactivar la camèra de tot lo monde",
|
||||
"stopAudioSharing": "Arrestar lo partiment àudio",
|
||||
"closeReactionsMenu": "Tampar lo menú de reaccions"
|
||||
"videomute": "Aviar / Arrestar la camèra"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Aviar / Arrestat los sostítols",
|
||||
@@ -950,6 +893,8 @@
|
||||
"ld": "Bassa definicion",
|
||||
"ldTooltip": "Difusion vidèo en bassa definicion",
|
||||
"lowDefinition": "Bassa definicion",
|
||||
"onlyAudioAvailable": "Pas que l’àudio es disponible",
|
||||
"onlyAudioSupported": "Sèm compatibles solament amb l’àudio dins aqueste navigator.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Difusion vidèo en definicion estandard",
|
||||
"standardDefinition": "Definicion estandard"
|
||||
@@ -966,10 +911,7 @@
|
||||
"muted": "Mut",
|
||||
"remoteControl": "Contraròtle alonhat",
|
||||
"show": "Mostrar davant",
|
||||
"videomute": "Lo participant a arrestat la camèra",
|
||||
"domuteVideo": "Desactivar la camèra",
|
||||
"domuteVideoOfOthers": "Desactivar la camèra dels demai",
|
||||
"videoMuted": "Camèra desactivada"
|
||||
"videomute": "Lo participant a arrestat la camèra"
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
@@ -990,6 +932,7 @@
|
||||
"goSmall": "Crear",
|
||||
"headerTitle": "Jitsi Meet",
|
||||
"info": "Infor",
|
||||
"jitsiMeet": "Jitsi Meet",
|
||||
"jitsiOnMobile": "Jitsi sus mobil –telecargatz nòstra aplicacion e començatz de conferéncias de pertot",
|
||||
"join": "CREAR / REJÓNHER",
|
||||
"moderatedMessage": "O <a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">reservatz una URL de conferéncia</a> a l'avança ont sètz l'unic moderator.",
|
||||
@@ -1001,95 +944,10 @@
|
||||
"roomname": "Sasissètz un nom de sala",
|
||||
"roomNameAllowedChars": "Lo nom de la conferéncia deu pas conténer aqueles caractèrs : ?, &, :, ', \", %, #.",
|
||||
"roomnameHint": "Picatz lo nom o l’URL de la sala que volètz jónher. Podètz inventar un nom, cal pas que lo monde que volètz convidar lo sàpian.",
|
||||
"secureMeetings": "Conferéncias seguras e de nauta qualitat",
|
||||
"sendFeedback": "Mandar vòstra opinion",
|
||||
"startMeeting": "Començar la reünion",
|
||||
"terms": "Tèrmes",
|
||||
"title": "Conferéncias vidèo securizadas amb plen de foncionalitats e complètament gratuitas",
|
||||
"addMeetingName": "Nomenar la conferéncia",
|
||||
"mobileDownLoadLinkIos": "Telecargar l'aplicacion per iOS",
|
||||
"mobileDownLoadLinkAndroid": "Telecargar l'aplicacion per Android",
|
||||
"mobileDownLoadLinkFDroid": "Telecargar l'aplicacion per F-Droid",
|
||||
"headerSubtitle": "Conferéncias securizadas e de Nauta qualitat",
|
||||
"logo": {
|
||||
"policyLogo": "Logotipe de la politica",
|
||||
"calendar": "Logotipe Calendar",
|
||||
"microsoftLogo": "Logotipe Microsoft",
|
||||
"logoDeepLinking": "Logotipe Jitsi meet",
|
||||
"desktopPreviewThumbnail": "Vinheta d'apercebut del burèu",
|
||||
"googleLogo": "Logotipe Google"
|
||||
}
|
||||
},
|
||||
"virtualBackground": {
|
||||
"addBackground": "Apondre un rèireplan",
|
||||
"image7": "Solelh levant",
|
||||
"backgroundEffectError": "Aplicacion fracassada de l’efeich al rèireplan.",
|
||||
"apply": "Aplicar",
|
||||
"title": "Rèireplans virtuals",
|
||||
"blur": "Fosc",
|
||||
"slightBlur": "Fosc leugièr",
|
||||
"removeBackground": "Suprimir lo rèireplan",
|
||||
"pleaseWait": "Esperatz...",
|
||||
"none": "Cap",
|
||||
"uploadedImage": "Imatge enviat {{index}}",
|
||||
"deleteImage": "Suprimir l’imatge",
|
||||
"image1": "Plaja",
|
||||
"image2": "Paret blanca neutra",
|
||||
"image3": "Sala voida blanca",
|
||||
"image4": "Lampadari negre",
|
||||
"image5": "Montanha",
|
||||
"image6": "Forèst ",
|
||||
"desktopShareError": "Creacion impossibla d’un partiment de burèu",
|
||||
"desktopShare": "Partiment de burèu",
|
||||
"webAssemblyWarning": "WebAssembly pas pres en carga"
|
||||
},
|
||||
"participantsPane": {
|
||||
"headings": {
|
||||
"lobby": "Sala d’espèra ({{count}})",
|
||||
"participantsList": "Participants de la conferéncia ({{count}})",
|
||||
"waitingLobby": "Dins la sala d'espèra ({{count}})"
|
||||
},
|
||||
"close": "Tancar",
|
||||
"header": "Participants",
|
||||
"actions": {
|
||||
"allow": "Permetre als convidats de :",
|
||||
"audioModeration": "Se tornar lo son",
|
||||
"blockEveryoneMicCamera": "Blocar lo microfòn e la camèra del monde",
|
||||
"invite": "Convidar qualqu'un",
|
||||
"askUnmute": "Demandar a restablir lo son",
|
||||
"mute": "Amudir",
|
||||
"muteAll": "Amudir tot lo monde",
|
||||
"muteEveryoneElse": "Amudir tot los demai",
|
||||
"stopEveryonesVideo": "Arrestar la vidèo de tot lo monde",
|
||||
"stopVideo": "Arrestar la vidèo",
|
||||
"unblockEveryoneMicCamera": "Desblocar lo microfòn e la camèra de tot lo monde",
|
||||
"videoModeration": "Aviar lor vidèo"
|
||||
}
|
||||
},
|
||||
"jitsiHome": "{{logo}} Logotipe, mena a la pagina d'acuèlh",
|
||||
"polls": {
|
||||
"create": {
|
||||
"addOption": "Apondre una opcion",
|
||||
"answerPlaceholder": "Opcion {{index}}",
|
||||
"create": "Crear un sondatge",
|
||||
"cancel": "Anullar",
|
||||
"pollOption": "Opcion sondarge {{index}}",
|
||||
"pollQuestion": "Question del sondatge",
|
||||
"questionPlaceholder": "Pausar una question",
|
||||
"removeOption": "Suprimir l'opcion",
|
||||
"send": "Enviar"
|
||||
},
|
||||
"answer": {
|
||||
"skip": "Sautar",
|
||||
"submit": "Enviar"
|
||||
},
|
||||
"results": {
|
||||
"vote": "Votar",
|
||||
"changeVote": "Cambiar lo vote",
|
||||
"hideDetailedResults": "Rescondre los detalhs",
|
||||
"showDetailedResults": "Mostrar los detalhs"
|
||||
},
|
||||
"notification": {
|
||||
"title": "Un sondatge novèl es estat apondut a la conferéncia"
|
||||
}
|
||||
"title": "Conferéncias vidèo securizadas amb plen de foncionalitats e complètament gratuitas"
|
||||
}
|
||||
}
|
||||
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -11117,8 +11117,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
|
||||
"from": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
|
||||
"version": "github:jitsi/lib-jitsi-meet#1bef6319fbdfdfb03b51d3f7efa348a111ac5805",
|
||||
"from": "github:jitsi/lib-jitsi-meet#1bef6319fbdfdfb03b51d3f7efa348a111ac5805",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "github:jitsi/sdp-interop#4669790bb9020cc8f10c1d1f3823c26b08497547",
|
||||
@@ -15201,9 +15201,8 @@
|
||||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.92.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.92.1.tgz",
|
||||
"integrity": "sha512-cbFU1acL9aud/ohk/40jwahSNac4PwBKdmRPgXb9WqaGwxAr4CgSsrBRmruCISMdw0Pec+ZoZtYjXVuHzF51sg==",
|
||||
"version": "github:react-native-webrtc/react-native-webrtc#aeb735154c9393bbfde40bf02b797e6eeb91f63a",
|
||||
"from": "github:react-native-webrtc/react-native-webrtc#aeb735154c9393bbfde40bf02b797e6eeb91f63a",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"cross-os": "^1.3.0",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#3b8baa9d3be2839510abaa954357d0b0ab023649",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#1bef6319fbdfdfb03b51d3f7efa348a111ac5805",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
@@ -92,7 +92,7 @@
|
||||
"react-native-url-polyfill": "1.2.0",
|
||||
"react-native-video": "5.1.1",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.92.1",
|
||||
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#aeb735154c9393bbfde40bf02b797e6eeb91f63a",
|
||||
"react-native-webview": "11.0.2",
|
||||
"react-native-youtube-iframe": "2.1.1",
|
||||
"react-redux": "7.1.0",
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
diff --git a/node_modules/react-native/Libraries/WebSocket/RCTWebSocketModule.m b/node_modules/react-native/Libraries/WebSocket/RCTWebSocketModule.m
|
||||
index d9387c4..a487da0 100644
|
||||
--- a/node_modules/react-native/Libraries/WebSocket/RCTWebSocketModule.m
|
||||
+++ b/node_modules/react-native/Libraries/WebSocket/RCTWebSocketModule.m
|
||||
@@ -165,10 +165,10 @@ - (void)webSocket:(RCTSRWebSocket *)webSocket didFailWithError:(NSError *)error
|
||||
NSNumber *socketID = [webSocket reactTag];
|
||||
_contentHandlers[socketID] = nil;
|
||||
_sockets[socketID] = nil;
|
||||
- [self sendEventWithName:@"websocketFailed" body:@{
|
||||
- @"message": error.localizedDescription,
|
||||
- @"id": socketID
|
||||
- }];
|
||||
+ NSDictionary *body =
|
||||
+ @{@"message" : error.localizedDescription ?: @"Undefined, error is nil",
|
||||
+ @"id" : socketID ?: @(-1)};
|
||||
+ [self sendEventWithName:@"websocketFailed" body:body];
|
||||
}
|
||||
|
||||
- (void)webSocket:(RCTSRWebSocket *)webSocket
|
||||
diff --git a/node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm b/node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm
|
||||
index bd48f44..d243ed0 100644
|
||||
--- a/node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import '../authentication/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
import '../keyboard-shortcuts/middleware';
|
||||
|
||||
@@ -135,8 +135,11 @@ function _addConferenceListeners(conference, dispatch, state) {
|
||||
|
||||
// Remove the tracks from peerconnection as well.
|
||||
for (const track of localTracks) {
|
||||
if ((audioMuted && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)
|
||||
|| (videoMuted && track.jitsiTrack.getType() === MEDIA_TYPE.VIDEO)) {
|
||||
const trackType = track.jitsiTrack.getType();
|
||||
|
||||
// Do not remove the audio track on RN. Starting with iOS 15 it will fail to unmute otherwise.
|
||||
if ((audioMuted && trackType === MEDIA_TYPE.AUDIO && navigator.product !== 'ReactNative')
|
||||
|| (videoMuted && trackType === MEDIA_TYPE.VIDEO)) {
|
||||
dispatch(replaceLocalTrack(track.jitsiTrack, null, conference));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ export default [
|
||||
'disableAEC',
|
||||
'disableAGC',
|
||||
'disableAP',
|
||||
'disableAddingBackgroundImages',
|
||||
'disableAudioLevels',
|
||||
'disableChatSmileys',
|
||||
'disableDeepLinking',
|
||||
@@ -189,7 +188,6 @@ export default [
|
||||
'subject',
|
||||
'testing',
|
||||
'toolbarButtons',
|
||||
'toolbarConfig',
|
||||
'useHostPageLocalStorage',
|
||||
'useTurnUdp',
|
||||
'videoQuality.persist',
|
||||
|
||||
@@ -227,28 +227,6 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
|
||||
}
|
||||
|
||||
if (!oldValue.toolbarConfig) {
|
||||
oldValue.toolbarConfig = {};
|
||||
}
|
||||
|
||||
if (typeof oldValue.toolbarConfig.alwaysVisible !== 'boolean'
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE === 'boolean') {
|
||||
newValue.toolbarConfig.alwaysVisible = interfaceConfig.TOOLBAR_ALWAYS_VISIBLE;
|
||||
}
|
||||
|
||||
if (typeof oldValue.toolbarConfig.initialTimeout !== 'number'
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& typeof interfaceConfig.INITIAL_TOOLBAR_TIMEOUT === 'number') {
|
||||
newValue.toolbarConfig.initialTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (typeof oldValue.toolbarConfig.timeout !== 'number'
|
||||
&& typeof interfaceConfig === 'object'
|
||||
&& typeof interfaceConfig.TOOLBAR_TIMEOUT === 'number') {
|
||||
newValue.toolbarConfig.timeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
const filteredConferenceInfo = Object.keys(CONFERENCE_HEADER_MAPPING).filter(key => oldValue[key]);
|
||||
|
||||
if (filteredConferenceInfo.length) {
|
||||
|
||||
@@ -261,20 +261,17 @@ export function showNoDataFromSourceVideoError(jitsiTrack) {
|
||||
*
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {boolean} audioOnly - Only share system audio.
|
||||
* @param {boolean} ignoreDidHaveVideo - Wether or not to ignore if video was on when sharing started.
|
||||
* @returns {{
|
||||
* type: TOGGLE_SCREENSHARING,
|
||||
* on: boolean,
|
||||
* audioOnly: boolean,
|
||||
* ignoreDidHaveVideo: boolean
|
||||
* audioOnly: boolean
|
||||
* }}
|
||||
*/
|
||||
export function toggleScreensharing(enabled, audioOnly = false, ignoreDidHaveVideo = false) {
|
||||
export function toggleScreensharing(enabled, audioOnly = false) {
|
||||
return {
|
||||
type: TOGGLE_SCREENSHARING,
|
||||
enabled,
|
||||
audioOnly,
|
||||
ignoreDidHaveVideo
|
||||
audioOnly
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -149,11 +149,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return;
|
||||
}
|
||||
|
||||
const { enabled, audioOnly, ignoreDidHaveVideo } = action;
|
||||
const { enabled, audioOnly } = action;
|
||||
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, { enabled,
|
||||
audioOnly,
|
||||
ignoreDidHaveVideo });
|
||||
audioOnly });
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -179,7 +178,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
} else if (jitsiTrack.isLocal() && !(jitsiTrack.videoType === VIDEO_TYPE.DESKTOP)) {
|
||||
APP.conference.setVideoMuteStatus();
|
||||
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
|
||||
store.dispatch(toggleScreensharing(false, false, true));
|
||||
store.dispatch(toggleScreensharing(false));
|
||||
} else {
|
||||
APP.UI.setVideoMuted(participantID);
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
* @param {string} path - The URL path.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function extractFqnFromPath() {
|
||||
const parts = window.location.pathname.split('/');
|
||||
export function extractFqnFromPath(path: string) {
|
||||
const parts = path.split('/');
|
||||
const len = parts.length;
|
||||
|
||||
return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
|
||||
@@ -28,7 +28,7 @@ export function getDynamicBrandingUrl(state: Object) {
|
||||
}
|
||||
|
||||
const baseUrl = state['features/base/config'].brandingDataUrl;
|
||||
const fqn = extractFqnFromPath();
|
||||
const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
|
||||
if (baseUrl && fqn) {
|
||||
return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { APP_WILL_MOUNT } from '../base/app';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import { fetchCustomBrandingData } from './actions';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT: {
|
||||
|
||||
store.dispatch(fetchCustomBrandingData());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
import { type Image } from '../virtual-background/constants';
|
||||
|
||||
import {
|
||||
SET_DYNAMIC_BRANDING_DATA,
|
||||
@@ -114,15 +113,7 @@ const DEFAULT_STATE = {
|
||||
* @public
|
||||
* @type {boolean}
|
||||
*/
|
||||
useDynamicBrandingData: false,
|
||||
|
||||
/**
|
||||
* An array of images to be used as virtual backgrounds instead of the default ones.
|
||||
*
|
||||
* @public
|
||||
* @type {Array<Object>}
|
||||
*/
|
||||
virtualBackgrounds: []
|
||||
useDynamicBrandingData: false
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -140,8 +131,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
inviteDomain,
|
||||
logoClickUrl,
|
||||
logoImageUrl,
|
||||
premeetingBackground,
|
||||
virtualBackgrounds
|
||||
premeetingBackground
|
||||
} = action.value;
|
||||
|
||||
return {
|
||||
@@ -156,8 +146,7 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
premeetingBackground,
|
||||
customizationFailed: false,
|
||||
customizationReady: true,
|
||||
useDynamicBrandingData: true,
|
||||
virtualBackgrounds: formatImages(virtualBackgrounds || [])
|
||||
useDynamicBrandingData: true
|
||||
};
|
||||
}
|
||||
case SET_DYNAMIC_BRANDING_FAILED: {
|
||||
@@ -177,30 +166,3 @@ ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
/**
|
||||
* Transforms the branding images into an array of Images objects ready
|
||||
* to be used as virtual backgrounds.
|
||||
*
|
||||
* @param {Array<string>} images -
|
||||
* @private
|
||||
* @returns {{Props}}
|
||||
*/
|
||||
function formatImages(images: Array<string> | Array<Object>): Array<Image> {
|
||||
return images.map((img, i) => {
|
||||
let src;
|
||||
let tooltip;
|
||||
|
||||
if (typeof img === 'object') {
|
||||
({ src, tooltip } = img);
|
||||
} else {
|
||||
src = img;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `branding-${i}`,
|
||||
src,
|
||||
tooltip
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export function sendJaasFeedbackMetadata(conference: Object, feedback: Object) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const meetingFqn = extractFqnFromPath();
|
||||
const meetingFqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
|
||||
const feedbackData = {
|
||||
...feedback,
|
||||
sessionId: conference.sessionId,
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, { Component } from 'react';
|
||||
import { Watermarks } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { setColorAlpha } from '../../base/util';
|
||||
import { fetchCustomBrandingData } from '../../dynamic-branding';
|
||||
import { SharedVideo } from '../../shared-video/components/web';
|
||||
import { Captions } from '../../subtitles/';
|
||||
|
||||
@@ -27,6 +28,11 @@ type Props = {
|
||||
*/
|
||||
_customBackgroundImageUrl: string,
|
||||
|
||||
/**
|
||||
* Fetches the branding data.
|
||||
*/
|
||||
_fetchCustomBrandingData: Function,
|
||||
|
||||
/**
|
||||
* Prop that indicates whether the chat is open.
|
||||
*/
|
||||
@@ -46,6 +52,14 @@ type Props = {
|
||||
* @extends Component
|
||||
*/
|
||||
class LargeVideo extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.props._fetchCustomBrandingData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
@@ -153,4 +167,8 @@ function _mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(LargeVideo);
|
||||
const _mapDispatchToProps = {
|
||||
_fetchCustomBrandingData: fetchCustomBrandingData
|
||||
};
|
||||
|
||||
export default connect(_mapStateToProps, _mapDispatchToProps)(LargeVideo);
|
||||
|
||||
@@ -3,202 +3,10 @@
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
conferenceWillJoin,
|
||||
getCurrentConference,
|
||||
sendLocalParticipant,
|
||||
setPassword
|
||||
getCurrentConference
|
||||
} from '../base/conference';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT,
|
||||
SET_KNOCKING_STATE,
|
||||
SET_LOBBY_MODE_ENABLED,
|
||||
SET_PASSWORD_JOIN_FAILED,
|
||||
SET_LOBBY_VISIBILITY
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Tries to join with a preset password.
|
||||
*
|
||||
* @param {string} password - The password to join with.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinWithPassword(password: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
dispatch(setPassword(conference, conference.join, password));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be dispatched when a knocking poarticipant leaves before any response.
|
||||
*
|
||||
* @param {string} id - The ID of the participant.
|
||||
* @returns {{
|
||||
* id: string,
|
||||
* type: KNOCKING_PARTICIPANT_LEFT
|
||||
* }}
|
||||
*/
|
||||
export function knockingParticipantLeft(id: string) {
|
||||
return {
|
||||
id,
|
||||
type: KNOCKING_PARTICIPANT_LEFT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be executed when a participant starts knocking or an already knocking participant gets updated.
|
||||
*
|
||||
* @param {Object} participant - The knocking participant.
|
||||
* @returns {{
|
||||
* participant: Object,
|
||||
* type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
|
||||
* }}
|
||||
*/
|
||||
export function participantIsKnockingOrUpdated(participant: Object) {
|
||||
return {
|
||||
participant,
|
||||
type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves (lets in) or rejects a knocking participant.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approved - True if the participant is approved, false otherwise.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setKnockingParticipantApproval(id: string, approved: boolean) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference) {
|
||||
if (approved) {
|
||||
conference.lobbyApproveAccess(id);
|
||||
} else {
|
||||
conference.lobbyDenyAccess(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to admit multiple participants in the conference.
|
||||
*
|
||||
* @param {Array<Object>} participants - A list of knocking participants.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function admitMultiple(participants: Array<Object>) {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
participants.forEach(p => {
|
||||
conference.lobbyApproveAccess(p.id);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves the request of a knocking participant to join the meeting.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function approveKnockingParticipant(id: string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
conference && conference.lobbyApproveAccess(id);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Denies the request of a knocking participant to join the meeting.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function rejectKnockingParticipant(id: string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
conference && conference.lobbyDenyAccess(id);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the knocking state of the participant.
|
||||
*
|
||||
* @param {boolean} knocking - The new state.
|
||||
* @returns {{
|
||||
* state: boolean,
|
||||
* type: SET_KNOCKING_STATE
|
||||
* }}
|
||||
*/
|
||||
export function setKnockingState(knocking: boolean) {
|
||||
return {
|
||||
knocking,
|
||||
type: SET_KNOCKING_STATE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the new state of the lobby mode.
|
||||
*
|
||||
* @param {boolean} enabled - The new state to set.
|
||||
* @returns {{
|
||||
* enabled: boolean,
|
||||
* type: SET_LOBBY_MODE_ENABLED
|
||||
* }}
|
||||
*/
|
||||
export function setLobbyModeEnabled(enabled: boolean) {
|
||||
return {
|
||||
enabled,
|
||||
type: SET_LOBBY_MODE_ENABLED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be dispatched when we failed to join with a password.
|
||||
*
|
||||
* @param {boolean} failed - True of recent password join failed.
|
||||
* @returns {{
|
||||
* failed: boolean,
|
||||
* type: SET_PASSWORD_JOIN_FAILED
|
||||
* }}
|
||||
*/
|
||||
export function setPasswordJoinFailed(failed: boolean) {
|
||||
return {
|
||||
failed,
|
||||
type: SET_PASSWORD_JOIN_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts knocking and waiting for approval.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function startKnocking() {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch(conferenceWillJoin(membersOnly));
|
||||
|
||||
// We need to update the conference object with the current display name, if approved
|
||||
// we want to send that display name, it was not updated in case when pre-join is disabled
|
||||
sendLocalParticipant(state, membersOnly);
|
||||
|
||||
membersOnly.joinLobby(localParticipant.name, localParticipant.email);
|
||||
dispatch(setKnockingState(true));
|
||||
};
|
||||
}
|
||||
import { SET_LOBBY_VISIBILITY } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Action to toggle lobby mode on or off.
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate } from '../app/actions';
|
||||
import { openDialog } from '../base/dialog';
|
||||
|
||||
import { DisableLobbyModeDialog, EnableLobbyModeDialog } from './components/native';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Cancels the ongoing knocking and abandons the join flow.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancelKnocking() {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
dispatch(appNavigate(undefined));
|
||||
};
|
||||
}
|
||||
export * from './actions.web';
|
||||
|
||||
/**
|
||||
* Action to show the dialog to disable lobby mode.
|
||||
|
||||
@@ -2,22 +2,223 @@
|
||||
|
||||
import { type Dispatch } from 'redux';
|
||||
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions';
|
||||
|
||||
import { appNavigate, maybeRedirectToWelcomePage } from '../app/actions';
|
||||
import {
|
||||
conferenceWillJoin,
|
||||
getCurrentConference,
|
||||
sendLocalParticipant,
|
||||
setPassword
|
||||
} from '../base/conference';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
export * from './actions.any';
|
||||
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT,
|
||||
SET_KNOCKING_STATE,
|
||||
SET_LOBBY_MODE_ENABLED,
|
||||
SET_PASSWORD_JOIN_FAILED
|
||||
} from './actionTypes';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Cancels the ongoing knocking and abandons the join flow.
|
||||
* Cancels the ongoing knocking and abandones the join flow.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancelKnocking() {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
// when we are redirecting the library should handle any
|
||||
// unload and clean of the connection.
|
||||
APP.API.notifyReadyToClose();
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
if (typeof APP !== 'undefined') {
|
||||
// when we are redirecting the library should handle any
|
||||
// unload and clean of the connection.
|
||||
APP.API.notifyReadyToClose();
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(appNavigate(undefined));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to join with a preset password.
|
||||
*
|
||||
* @param {string} password - The password to join with.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function joinWithPassword(password: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
dispatch(setPassword(conference, conference.join, password));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be dispatched when a knocking poarticipant leaves before any response.
|
||||
*
|
||||
* @param {string} id - The ID of the participant.
|
||||
* @returns {{
|
||||
* id: string,
|
||||
* type: KNOCKING_PARTICIPANT_LEFT
|
||||
* }}
|
||||
*/
|
||||
export function knockingParticipantLeft(id: string) {
|
||||
return {
|
||||
id,
|
||||
type: KNOCKING_PARTICIPANT_LEFT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be executed when a participant starts knocking or an already knocking participant gets updated.
|
||||
*
|
||||
* @param {Object} participant - The knocking participant.
|
||||
* @returns {{
|
||||
* participant: Object,
|
||||
* type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
|
||||
* }}
|
||||
*/
|
||||
export function participantIsKnockingOrUpdated(participant: Object) {
|
||||
return {
|
||||
participant,
|
||||
type: KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves (lets in) or rejects a knocking participant.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approved - True if the participant is approved, false otherwise.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setKnockingParticipantApproval(id: string, approved: boolean) {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
if (conference) {
|
||||
if (approved) {
|
||||
conference.lobbyApproveAccess(id);
|
||||
} else {
|
||||
conference.lobbyDenyAccess(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to admit multiple participants in the conference.
|
||||
*
|
||||
* @param {Array<Object>} participants - A list of knocking participants.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function admitMultiple(participants: Array<Object>) {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
participants.forEach(p => {
|
||||
conference.lobbyApproveAccess(p.id);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approves the request of a knocking participant to join the meeting.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function approveKnockingParticipant(id: string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
conference && conference.lobbyApproveAccess(id);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Denies the request of a knocking participant to join the meeting.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function rejectKnockingParticipant(id: string) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const conference = getCurrentConference(getState);
|
||||
|
||||
conference && conference.lobbyDenyAccess(id);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the knocking state of the participant.
|
||||
*
|
||||
* @param {boolean} knocking - The new state.
|
||||
* @returns {{
|
||||
* state: boolean,
|
||||
* type: SET_KNOCKING_STATE
|
||||
* }}
|
||||
*/
|
||||
export function setKnockingState(knocking: boolean) {
|
||||
return {
|
||||
knocking,
|
||||
type: SET_KNOCKING_STATE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to set the new state of the lobby mode.
|
||||
*
|
||||
* @param {boolean} enabled - The new state to set.
|
||||
* @returns {{
|
||||
* enabled: boolean,
|
||||
* type: SET_LOBBY_MODE_ENABLED
|
||||
* }}
|
||||
*/
|
||||
export function setLobbyModeEnabled(enabled: boolean) {
|
||||
return {
|
||||
enabled,
|
||||
type: SET_LOBBY_MODE_ENABLED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to be dispatched when we failed to join with a password.
|
||||
*
|
||||
* @param {boolean} failed - True of recent password join failed.
|
||||
* @returns {{
|
||||
* failed: boolean,
|
||||
* type: SET_PASSWORD_JOIN_FAILED
|
||||
* }}
|
||||
*/
|
||||
export function setPasswordJoinFailed(failed: boolean) {
|
||||
return {
|
||||
failed,
|
||||
type: SET_PASSWORD_JOIN_FAILED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts knocking and waiting for approval.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function startKnocking() {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
dispatch(conferenceWillJoin(membersOnly));
|
||||
|
||||
// We need to update the conference object with the current display name, if approved
|
||||
// we want to send that display name, it was not updated in case when pre-join is disabled
|
||||
sendLocalParticipant(state, membersOnly);
|
||||
|
||||
membersOnly.joinLobby(localParticipant.name, localParticipant.email);
|
||||
dispatch(setKnockingState(true));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export const LobbyParticipantItem = ({
|
||||
openDrawerForParticipant
|
||||
}: Props) => {
|
||||
const { id } = p;
|
||||
const [ admit, reject ] = useLobbyActions({ participantID: id });
|
||||
const [ admit ] = useLobbyActions({ participantID: id });
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -48,11 +48,6 @@ export const LobbyParticipantItem = ({
|
||||
raisedHand = { p.raisedHand }
|
||||
videoMediaState = { MEDIA_STATE.NONE }
|
||||
youText = { t('chat.you') }>
|
||||
<ParticipantActionButton
|
||||
onClick = { reject }
|
||||
primary = { false }>
|
||||
{t('lobby.reject')}
|
||||
</ParticipantActionButton>
|
||||
<ParticipantActionButton
|
||||
onClick = { admit }
|
||||
primary = { true }>
|
||||
|
||||
@@ -55,6 +55,7 @@ export async function sendReactionsWebhook(state: Object, reactions: Array<?stri
|
||||
const { webhookProxyUrl: url } = state['features/base/config'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
const headers = {
|
||||
@@ -64,7 +65,7 @@ export async function sendReactionsWebhook(state: Object, reactions: Array<?stri
|
||||
|
||||
|
||||
const reqBody = {
|
||||
meetingFqn: extractFqnFromPath(),
|
||||
meetingFqn: extractFqnFromPath(locationURL.pathname),
|
||||
sessionId: conference.sessionId,
|
||||
submitted: Date.now(),
|
||||
reactions,
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
/**
|
||||
* Starts a search by criteria.
|
||||
*
|
||||
* @param {string | null} criteria - The search criteria.
|
||||
* @param {string} criteria - The search criteria.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function initSearch(criteria: string | null) {
|
||||
export function initSearch(criteria: string) {
|
||||
return {
|
||||
type: INIT_SEARCH,
|
||||
criteria
|
||||
|
||||
@@ -10,7 +10,7 @@ import { connect } from '../../base/redux';
|
||||
import { escapeRegexp } from '../../base/util';
|
||||
import { initUpdateStats, initSearch } from '../actions';
|
||||
import { SPEAKER_STATS_RELOAD_INTERVAL } from '../constants';
|
||||
import { getSpeakerStats } from '../functions';
|
||||
import { getSpeakerStats, getSearchCriteria } from '../functions';
|
||||
|
||||
import SpeakerStatsItem from './SpeakerStatsItem';
|
||||
import SpeakerStatsLabels from './SpeakerStatsLabels';
|
||||
@@ -36,7 +36,7 @@ type Props = {
|
||||
/**
|
||||
* The search criteria.
|
||||
*/
|
||||
_criteria: string | null,
|
||||
_criteria: string,
|
||||
|
||||
/**
|
||||
* The JitsiConference from which stats will be pulled.
|
||||
@@ -140,9 +140,24 @@ class SpeakerStats extends Component<Props> {
|
||||
const dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime();
|
||||
const hasLeft = statsModel.hasLeft();
|
||||
|
||||
let displayName;
|
||||
|
||||
if (statsModel.isLocalStats()) {
|
||||
const { t } = this.props;
|
||||
const meString = t('me');
|
||||
|
||||
displayName = this.props._localDisplayName;
|
||||
displayName
|
||||
= displayName ? `${displayName} (${meString})` : meString;
|
||||
} else {
|
||||
displayName
|
||||
= this.props._stats[userId].getDisplayName()
|
||||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
return (
|
||||
<SpeakerStatsItem
|
||||
displayName = { statsModel.getDisplayName() }
|
||||
displayName = { displayName }
|
||||
dominantSpeakerTime = { dominantSpeakerTime }
|
||||
hasLeft = { hasLeft }
|
||||
isDominantSpeaker = { isDominantSpeaker }
|
||||
@@ -172,40 +187,7 @@ class SpeakerStats extends Component<Props> {
|
||||
* @private
|
||||
*/
|
||||
_updateStats() {
|
||||
this.props.dispatch(initUpdateStats(() => this._getSpeakerStats()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the internal state with the latest speaker stats.
|
||||
*
|
||||
* @returns {Object}
|
||||
* @private
|
||||
*/
|
||||
_getSpeakerStats() {
|
||||
const stats = { ...this.props.conference.getSpeakerStats() };
|
||||
|
||||
for (const userId in stats) {
|
||||
if (stats[userId]) {
|
||||
if (stats[userId].isLocalStats()) {
|
||||
const { t } = this.props;
|
||||
const meString = t('me');
|
||||
|
||||
stats[userId].setDisplayName(
|
||||
this.props._localDisplayName
|
||||
? `${this.props._localDisplayName} (${meString})`
|
||||
: meString
|
||||
);
|
||||
}
|
||||
|
||||
if (!stats[userId].getDisplayName()) {
|
||||
stats[userId].setDisplayName(
|
||||
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
this.props.dispatch(initUpdateStats(() => this.props.conference.getSpeakerStats()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +198,8 @@ class SpeakerStats extends Component<Props> {
|
||||
* @private
|
||||
* @returns {{
|
||||
* _localDisplayName: ?string,
|
||||
* _stats: Object
|
||||
* _stats: Object,
|
||||
* _criteria: string,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
@@ -230,7 +213,8 @@ function _mapStateToProps(state) {
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
_localDisplayName: localParticipant && localParticipant.name,
|
||||
_stats: getSpeakerStats(state)
|
||||
_stats: getSpeakerStats(state),
|
||||
_criteria: getSearchCriteria(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ export function getSpeakerStats(state: Object) {
|
||||
* Gets speaker stats search criteria.
|
||||
*
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {string | null} - The search criteria.
|
||||
* @returns {string} - The search criteria.
|
||||
*/
|
||||
export function getSearchCriteria(state: Object) {
|
||||
return state['features/speaker-stats']?.criteria;
|
||||
return state['features/speaker-stats']?.criteria ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,14 +161,16 @@ export function filterBySearchCriteria(state: Object, stats: ?Object) {
|
||||
const filteredStats = _.cloneDeep(stats ?? getSpeakerStats(state));
|
||||
const criteria = getSearchCriteria(state);
|
||||
|
||||
if (criteria !== null) {
|
||||
if (criteria) {
|
||||
const searchRegex = new RegExp(criteria, 'gi');
|
||||
|
||||
for (const id in filteredStats) {
|
||||
if (filteredStats[id].hasOwnProperty('_isLocalStats')) {
|
||||
const name = filteredStats[id].getDisplayName();
|
||||
|
||||
filteredStats[id].hidden = !name || !name.match(searchRegex);
|
||||
if (!name || !name.match(searchRegex)) {
|
||||
filteredStats[id].hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
const INITIAL_STATE = {
|
||||
stats: {},
|
||||
pendingReorder: true,
|
||||
criteria: null
|
||||
criteria: ''
|
||||
};
|
||||
|
||||
ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), action) => {
|
||||
|
||||
@@ -55,6 +55,16 @@ export const SET_OVERFLOW_MENU_VISIBLE = 'SET_OVERFLOW_MENU_VISIBLE';
|
||||
*/
|
||||
export const SET_TOOLBAR_HOVERED = 'SET_TOOLBAR_HOVERED';
|
||||
|
||||
/**
|
||||
* The type of the action which sets the permanent visibility of the Toolbox.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBOX_ALWAYS_VISIBLE,
|
||||
* alwaysVisible: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBOX_ALWAYS_VISIBLE = 'SET_TOOLBOX_ALWAYS_VISIBLE';
|
||||
|
||||
/**
|
||||
* The type of the (redux) action which enables/disables the Toolbox.
|
||||
*
|
||||
@@ -77,6 +87,17 @@ export const SET_TOOLBOX_ENABLED = 'SET_TOOLBOX_ENABLED';
|
||||
*/
|
||||
export const SET_TOOLBOX_TIMEOUT = 'SET_TOOLBOX_TIMEOUT';
|
||||
|
||||
/**
|
||||
* The type of the action which sets the delay in milliseconds after which
|
||||
* the Toolbox visibility is to be changed.
|
||||
*
|
||||
* {
|
||||
* type: SET_TOOLBOX_TIMEOUT_MS,
|
||||
* timeoutMS: number
|
||||
* }
|
||||
*/
|
||||
export const SET_TOOLBOX_TIMEOUT_MS = 'SET_TOOLBOX_TIMEOUT_MS';
|
||||
|
||||
/**
|
||||
* The type of the (redux) action which shows/hides the Toolbox.
|
||||
*
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import {
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbox.
|
||||
*
|
||||
* @param {boolean} enabled - True to enable the toolbox or false to disable it.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_ENABLED,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxEnabled(enabled: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_ENABLED,
|
||||
enabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the toolbox.
|
||||
*
|
||||
* @param {boolean} visible - True to show the toolbox or false to hide it.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setToolboxVisible(visible: boolean): Object {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { toolbarConfig: { alwaysVisible } } = getState()['features/base/config'];
|
||||
|
||||
if (!visible && alwaysVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_TOOLBOX_VISIBLE,
|
||||
visible
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,28 +1,160 @@
|
||||
// @flow
|
||||
/* @flow */
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
import {
|
||||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBOX_ALWAYS_VISIBLE,
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_TIMEOUT,
|
||||
SET_TOOLBOX_TIMEOUT_MS,
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
import { TOGGLE_TOOLBOX_VISIBLE } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
/**
|
||||
* Signals that toolbox timeout should be cleared.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLEAR_TOOLBOX_TIMEOUT
|
||||
* }}
|
||||
*/
|
||||
export function clearToolboxTimeout(): Object {
|
||||
return {
|
||||
type: CLEAR_TOOLBOX_TIMEOUT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the overflow menu.
|
||||
*
|
||||
* @param {boolean} visible - True to show it or false to hide it.
|
||||
* @returns {{
|
||||
* type: SET_OVERFLOW_MENU_VISIBLE,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setOverflowMenuVisible(visible: boolean): Object {
|
||||
return {
|
||||
type: SET_OVERFLOW_MENU_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbar is hovered value should be changed.
|
||||
*
|
||||
* @param {boolean} hovered - Flag showing whether toolbar is hovered.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_HOVERED,
|
||||
* hovered: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarHovered(hovered: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_HOVERED,
|
||||
hovered
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that always visible toolbars value should be changed.
|
||||
*
|
||||
* @param {boolean} alwaysVisible - Value to be set in redux store.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_ALWAYS_VISIBLE,
|
||||
* alwaysVisible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxAlwaysVisible(alwaysVisible: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_ALWAYS_VISIBLE,
|
||||
alwaysVisible
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-disable flowtype/space-before-type-colon */
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbox.
|
||||
*
|
||||
* @param {boolean} enabled - True to enable the toolbox or false to disable it.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_ENABLED,
|
||||
* enabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxEnabled(enabled: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_ENABLED,
|
||||
enabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action which sets new timeout and clears the previous one.
|
||||
*
|
||||
* @param {Function} handler - Function to be invoked after the timeout.
|
||||
* @param {number} timeoutMS - Delay.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_TIMEOUT,
|
||||
* handler: Function,
|
||||
* timeoutMS: number
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxTimeout(handler: Function, timeoutMS: number)
|
||||
: Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_TIMEOUT,
|
||||
handler,
|
||||
timeoutMS
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-enable flowtype/space-before-type-colon */
|
||||
|
||||
/**
|
||||
* Dispatches an action which sets new toolbox timeout value.
|
||||
*
|
||||
* @param {number} timeoutMS - Delay.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_TIMEOUT_MS,
|
||||
* timeoutMS: number
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxTimeoutMS(timeoutMS: number): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_TIMEOUT_MS,
|
||||
timeoutMS
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the toolbox.
|
||||
*
|
||||
* @param {boolean} visible - True to show the toolbox or false to hide it.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_VISIBLE,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxVisible(visible: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to toggle the toolbox visibility.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @returns {{
|
||||
* type: TOGGLE_TOOLBOX_VISIBLE
|
||||
* }}
|
||||
*/
|
||||
export function toggleToolboxVisible() {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { toolbarConfig: { alwaysVisible } } = state['features/base/config'];
|
||||
const { visible } = state['features/toolbox'];
|
||||
|
||||
if (visible && alwaysVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: TOGGLE_TOOLBOX_VISIBLE
|
||||
});
|
||||
return {
|
||||
type: TOGGLE_TOOLBOX_VISIBLE
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { overwriteConfig } from '../base/config';
|
||||
import { isLayoutTileView } from '../video-layout';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
FULL_SCREEN_CHANGED,
|
||||
SET_FULL_SCREEN,
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBOX_TIMEOUT
|
||||
SET_OVERFLOW_DRAWER
|
||||
} from './actionTypes';
|
||||
import { setToolboxVisible } from './actions';
|
||||
import { getToolbarTimeout } from './functions';
|
||||
import {
|
||||
clearToolboxTimeout,
|
||||
setToolboxTimeout,
|
||||
setToolboxTimeoutMS,
|
||||
setToolboxVisible
|
||||
} from './actions.native';
|
||||
|
||||
export * from './actions.any';
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
export * from './actions.native';
|
||||
|
||||
/**
|
||||
* Docks/undocks the Toolbox.
|
||||
@@ -27,9 +28,7 @@ export * from './actions.any';
|
||||
*/
|
||||
export function dockToolbox(dock: boolean): Function {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { visible } = state['features/toolbox'];
|
||||
const toolbarTimeout = getToolbarTimeout(state);
|
||||
const { timeoutMS, visible } = getState()['features/toolbox'];
|
||||
|
||||
if (dock) {
|
||||
// First make sure the toolbox is shown.
|
||||
@@ -40,7 +39,7 @@ export function dockToolbox(dock: boolean): Function {
|
||||
dispatch(
|
||||
setToolboxTimeout(
|
||||
() => dispatch(hideToolbox()),
|
||||
toolbarTimeout));
|
||||
timeoutMS));
|
||||
} else {
|
||||
dispatch(showToolbox());
|
||||
}
|
||||
@@ -74,9 +73,11 @@ export function fullScreenChanged(fullScreen: boolean) {
|
||||
export function hideToolbox(force: boolean = false): Function {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { toolbarConfig: { alwaysVisible } } = state['features/base/config'];
|
||||
const { hovered } = state['features/toolbox'];
|
||||
const toolbarTimeout = getToolbarTimeout(state);
|
||||
const {
|
||||
alwaysVisible,
|
||||
hovered,
|
||||
timeoutMS
|
||||
} = state['features/toolbox'];
|
||||
|
||||
if (alwaysVisible) {
|
||||
return;
|
||||
@@ -94,7 +95,7 @@ export function hideToolbox(force: boolean = false): Function {
|
||||
dispatch(
|
||||
setToolboxTimeout(
|
||||
() => dispatch(hideToolbox()),
|
||||
toolbarTimeout));
|
||||
timeoutMS));
|
||||
} else {
|
||||
dispatch(setToolboxVisible(false));
|
||||
}
|
||||
@@ -127,13 +128,9 @@ export function showToolbox(timeout: number = 0): Object {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const {
|
||||
toolbarConfig: { initialTimeout, alwaysVisible },
|
||||
toolbarConfig
|
||||
} = state['features/base/config'];
|
||||
const toolbarTimeout = getToolbarTimeout(state);
|
||||
|
||||
const {
|
||||
alwaysVisible,
|
||||
enabled,
|
||||
timeoutMS,
|
||||
visible,
|
||||
overflowDrawer
|
||||
} = state['features/toolbox'];
|
||||
@@ -146,17 +143,11 @@ export function showToolbox(timeout: number = 0): Object {
|
||||
// If the Toolbox is always visible, there's no need for a timeout
|
||||
// to toggle its visibility.
|
||||
if (!alwaysVisible) {
|
||||
if (typeof initialTimeout === 'number') {
|
||||
// reset `initialTimeout` once it is consumed once
|
||||
dispatch(overwriteConfig({ toolbarConfig: {
|
||||
...toolbarConfig,
|
||||
initialTimeout: null
|
||||
} }));
|
||||
}
|
||||
dispatch(
|
||||
setToolboxTimeout(
|
||||
() => dispatch(hideToolbox()),
|
||||
timeout || initialTimeout || toolbarTimeout));
|
||||
timeout || timeoutMS));
|
||||
dispatch(setToolboxTimeoutMS(interfaceConfig.TOOLBAR_TIMEOUT));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -189,73 +180,9 @@ export function hideToolboxOnTileView() {
|
||||
const state = getState();
|
||||
const { overflowDrawer } = state['features/toolbox'];
|
||||
|
||||
|
||||
if (!overflowDrawer && isLayoutTileView(state)) {
|
||||
dispatch(hideToolbox(true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbox timeout should be cleared.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CLEAR_TOOLBOX_TIMEOUT
|
||||
* }}
|
||||
*/
|
||||
export function clearToolboxTimeout(): Object {
|
||||
return {
|
||||
type: CLEAR_TOOLBOX_TIMEOUT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the overflow menu.
|
||||
*
|
||||
* @param {boolean} visible - True to show it or false to hide it.
|
||||
* @returns {{
|
||||
* type: SET_OVERFLOW_MENU_VISIBLE,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setOverflowMenuVisible(visible: boolean): Object {
|
||||
return {
|
||||
type: SET_OVERFLOW_MENU_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that toolbar is hovered value should be changed.
|
||||
*
|
||||
* @param {boolean} hovered - Flag showing whether toolbar is hovered.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBAR_HOVERED,
|
||||
* hovered: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setToolbarHovered(hovered: boolean): Object {
|
||||
return {
|
||||
type: SET_TOOLBAR_HOVERED,
|
||||
hovered
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action which sets new timeout and clears the previous one.
|
||||
*
|
||||
* @param {Function} handler - Function to be invoked after the timeout.
|
||||
* @param {number} timeoutMS - Delay.
|
||||
* @returns {{
|
||||
* type: SET_TOOLBOX_TIMEOUT,
|
||||
* handler: Function,
|
||||
* timeoutMS: number
|
||||
* }}
|
||||
*/
|
||||
export function setToolboxTimeout(handler: Function, timeoutMS: number): Object {
|
||||
return {
|
||||
type: SET_TOOLBOX_TIMEOUT,
|
||||
handler,
|
||||
timeoutMS
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -29,5 +29,3 @@ export const THRESHOLDS = [
|
||||
];
|
||||
|
||||
export const NOT_APPLICABLE = 'N/A';
|
||||
|
||||
export const TOOLBAR_TIMEOUT = 4000;
|
||||
|
||||
@@ -60,14 +60,12 @@ export function getMovableButtons(width: number): Set<string> {
|
||||
*/
|
||||
export function isToolboxVisible(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const { toolbarConfig: { alwaysVisible } } = state['features/base/config'];
|
||||
const { enabled, visible } = state['features/toolbox'];
|
||||
const { alwaysVisible, enabled, visible } = state['features/toolbox'];
|
||||
const participantCount = getParticipantCountWithFake(state);
|
||||
const alwaysVisibleFlag = getFeatureFlag(state, TOOLBOX_ALWAYS_VISIBLE, false);
|
||||
const enabledFlag = getFeatureFlag(state, TOOLBOX_ENABLED, true);
|
||||
|
||||
return enabledFlag && enabled
|
||||
&& (alwaysVisible || visible || participantCount === 1 || alwaysVisibleFlag);
|
||||
return enabledFlag && enabled && (alwaysVisible || visible || participantCount === 1 || alwaysVisibleFlag);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { getToolbarButtons } from '../base/config';
|
||||
import { hasAvailableDevices } from '../base/devices';
|
||||
|
||||
import { TOOLBAR_TIMEOUT } from './constants';
|
||||
|
||||
/**
|
||||
* Helper for getting the height of the toolbox.
|
||||
*
|
||||
@@ -39,8 +37,9 @@ export function isButtonEnabled(name: string, state: Object) {
|
||||
* otherwise.
|
||||
*/
|
||||
export function isToolboxVisible(state: Object) {
|
||||
const { iAmSipGateway, toolbarConfig: { alwaysVisible } } = state['features/base/config'];
|
||||
const { iAmSipGateway } = state['features/base/config'];
|
||||
const {
|
||||
alwaysVisible,
|
||||
timeoutID,
|
||||
visible
|
||||
} = state['features/toolbox'];
|
||||
@@ -102,15 +101,3 @@ export function showOverflowDrawer(state: Object) {
|
||||
export function isToolboxEnabled(state: Object) {
|
||||
return state['features/toolbox'].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar timeout from config or the default value.
|
||||
*
|
||||
* @param {Object} state - The state from the Redux store.
|
||||
* @returns {number} - Toolbar timeout in miliseconds.
|
||||
*/
|
||||
export function getToolbarTimeout(state: Object) {
|
||||
const { toolbarConfig: { timeout } } = state['features/base/config'];
|
||||
|
||||
return timeout || TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
@@ -8,66 +8,121 @@ import {
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBOX_ALWAYS_VISIBLE,
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_TIMEOUT,
|
||||
SET_TOOLBOX_TIMEOUT_MS,
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* Initial state of toolbox's part of Redux store.
|
||||
* Returns initial state for toolbox's part of Redux store.
|
||||
*
|
||||
* @private
|
||||
* @returns {{
|
||||
* alwaysVisible: boolean,
|
||||
* enabled: boolean,
|
||||
* hovered: boolean,
|
||||
* overflowDrawer: boolean,
|
||||
* overflowMenuVisible: boolean,
|
||||
* timeoutID: number,
|
||||
* timeoutMS: number,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
const INITIAL_STATE = {
|
||||
function _getInitialState() {
|
||||
// Does the toolbar eventually fade out, or is it always visible?
|
||||
let alwaysVisible = false;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
enabled: true,
|
||||
// Toolbar (initial) visibility.
|
||||
let visible = false;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether a Toolbar in the Toolbox is
|
||||
* hovered.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
hovered: false,
|
||||
// Default toolbox timeout for mobile app.
|
||||
let timeoutMS = 5000;
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the overflow menu(s) are to be displayed as drawers.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
overflowDrawer: false,
|
||||
if (typeof interfaceConfig !== 'undefined') {
|
||||
if (interfaceConfig.INITIAL_TOOLBAR_TIMEOUT) {
|
||||
timeoutMS = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
}
|
||||
if (typeof interfaceConfig.TOOLBAR_ALWAYS_VISIBLE !== 'undefined') {
|
||||
alwaysVisible = interfaceConfig.TOOLBAR_ALWAYS_VISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the OverflowMenu is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
overflowMenuVisible: false,
|
||||
// When the toolbar is always visible, it must initially be visible too.
|
||||
if (alwaysVisible === true) {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A number, non-zero value which identifies the timer created by a call
|
||||
* to setTimeout().
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
timeoutID: null,
|
||||
return {
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox should always be
|
||||
* visible. When false, the toolbar will fade out after timeoutMS.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
alwaysVisible,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the Toolbox is enabled.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
enabled: true,
|
||||
|
||||
/**
|
||||
* The indicator that determines whether the Toolbox is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
visible: false
|
||||
};
|
||||
/**
|
||||
* The indicator which determines whether a Toolbar in the Toolbox is
|
||||
* hovered.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
hovered: false,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the overflow menu(s) are to be displayed as drawers.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
overflowDrawer: false,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the OverflowMenu is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
overflowMenuVisible: false,
|
||||
|
||||
/**
|
||||
* A number, non-zero value which identifies the timer created by a call
|
||||
* to setTimeout() with timeoutMS.
|
||||
*
|
||||
* @type {number|null}
|
||||
*/
|
||||
timeoutID: null,
|
||||
|
||||
/**
|
||||
* The delay in milliseconds before timeoutID executes (after its
|
||||
* initialization).
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
timeoutMS,
|
||||
|
||||
/**
|
||||
* The indicator that determines whether the Toolbox is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
ReducerRegistry.register(
|
||||
'features/toolbox',
|
||||
(state: Object = INITIAL_STATE, action: Object) => {
|
||||
(state: Object = _getInitialState(), action: Object) => {
|
||||
switch (action.type) {
|
||||
case CLEAR_TOOLBOX_TIMEOUT:
|
||||
return {
|
||||
@@ -99,6 +154,13 @@ ReducerRegistry.register(
|
||||
hovered: action.hovered
|
||||
};
|
||||
|
||||
case SET_TOOLBOX_ALWAYS_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
alwaysVisible: action.alwaysVisible,
|
||||
visible: action.alwaysVisible === true ? true : state.visible
|
||||
};
|
||||
|
||||
case SET_TOOLBOX_ENABLED:
|
||||
return {
|
||||
...state,
|
||||
@@ -108,14 +170,21 @@ ReducerRegistry.register(
|
||||
case SET_TOOLBOX_TIMEOUT:
|
||||
return {
|
||||
...state,
|
||||
timeoutID: action.timeoutID
|
||||
timeoutID: action.timeoutID,
|
||||
timeoutMS: action.timeoutMS
|
||||
};
|
||||
|
||||
case SET_TOOLBOX_TIMEOUT_MS:
|
||||
return {
|
||||
...state,
|
||||
timeoutMS: action.timeoutMS
|
||||
};
|
||||
|
||||
case SET_TOOLBOX_VISIBLE:
|
||||
return set(state, 'visible', action.visible);
|
||||
return set(state, 'visible', state.alwaysVisible || action.visible);
|
||||
|
||||
case TOGGLE_TOOLBOX_VISIBLE:
|
||||
return set(state, 'visible', !state.visible);
|
||||
return set(state, 'visible', state.alwaysVisible || !state.visible);
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
getRemoteParticipants,
|
||||
muteRemoteParticipant
|
||||
} from '../base/participants';
|
||||
import { toggleScreensharing } from '../base/tracks';
|
||||
import { isModerationNotificationDisplayed } from '../notifications';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -35,10 +34,9 @@ const logger = getLogger(__filename);
|
||||
*
|
||||
* @param {boolean} enable - Whether to mute or unmute.
|
||||
* @param {MEDIA_TYPE} mediaType - The type of the media channel to mute.
|
||||
* @param {boolean} stopScreenSharing - Whether or not to stop the screensharing.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function muteLocal(enable: boolean, mediaType: MEDIA_TYPE, stopScreenSharing: boolean = false) {
|
||||
export function muteLocal(enable: boolean, mediaType: MEDIA_TYPE) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const isAudio = mediaType === MEDIA_TYPE.AUDIO;
|
||||
|
||||
@@ -57,10 +55,6 @@ export function muteLocal(enable: boolean, mediaType: MEDIA_TYPE, stopScreenShar
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable && stopScreenSharing) {
|
||||
dispatch(toggleScreensharing(false, false, true));
|
||||
}
|
||||
|
||||
sendAnalytics(createToolbarEvent(isAudio ? AUDIO_MUTE : VIDEO_MUTE, { enable }));
|
||||
dispatch(isAudio ? setAudioMuted(enable, /* ensureTrack */ true)
|
||||
: setVideoMuted(enable, mediaType, VIDEO_MUTISM_AUTHORITY.USER, /* ensureTrack */ true));
|
||||
@@ -103,7 +97,7 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYP
|
||||
const localId = getLocalParticipant(state).id;
|
||||
|
||||
if (!exclude.includes(localId)) {
|
||||
dispatch(muteLocal(true, mediaType, true));
|
||||
dispatch(muteLocal(true, mediaType));
|
||||
}
|
||||
|
||||
getRemoteParticipants(state).forEach((p, id) => {
|
||||
|
||||
@@ -105,7 +105,7 @@ export default class AbstractMuteEveryoneDialog<P: Props> extends AbstractMuteRe
|
||||
dispatch(muteAllParticipants(exclude, MEDIA_TYPE.AUDIO));
|
||||
if (this.state.audioModerationEnabled) {
|
||||
dispatch(requestEnableAudioModeration());
|
||||
} else if (this.state.audioModerationEnabled !== undefined) {
|
||||
} else {
|
||||
dispatch(requestDisableAudioModeration());
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export default class AbstractMuteEveryonesVideoDialog<P: Props>
|
||||
dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO));
|
||||
if (this.state.moderationEnabled) {
|
||||
dispatch(requestEnableVideoModeration());
|
||||
} else if (this.state.moderationEnabled !== undefined) {
|
||||
} else {
|
||||
dispatch(requestDisableVideoModeration());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconPlusCircle } from '../../base/icons';
|
||||
import { VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
|
||||
import { resizeImage } from '../functions';
|
||||
import logger from '../logger';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Callback used to set the 'loading' state of the parent component.
|
||||
*/
|
||||
setLoading: Function,
|
||||
|
||||
/**
|
||||
* Callback used to set the options.
|
||||
*/
|
||||
setOptions: Function,
|
||||
|
||||
/**
|
||||
* Callback used to set the storedImages array.
|
||||
*/
|
||||
setStoredImages: Function,
|
||||
|
||||
/**
|
||||
* A list of images locally stored.
|
||||
*/
|
||||
storedImages: Array<Image>,
|
||||
|
||||
/**
|
||||
* If a label should be displayed alongside the button.
|
||||
*/
|
||||
showLabel: boolean,
|
||||
|
||||
/**
|
||||
* Used for translation.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Component used to upload an image.
|
||||
*
|
||||
* @param {Object} Props - The props of the component.
|
||||
* @returns {React$Node}
|
||||
*/
|
||||
function UploadImageButton({
|
||||
setLoading,
|
||||
setOptions,
|
||||
setStoredImages,
|
||||
showLabel,
|
||||
storedImages,
|
||||
t
|
||||
}: Props) {
|
||||
const uploadImageButton: Object = useRef(null);
|
||||
const uploadImageKeyPress = useCallback(e => {
|
||||
if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
uploadImageButton.current.click();
|
||||
}
|
||||
}, [ uploadImageButton.current ]);
|
||||
|
||||
|
||||
const uploadImage = useCallback(async e => {
|
||||
const reader = new FileReader();
|
||||
const imageFile = e.target.files;
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const url = await resizeImage(reader.result);
|
||||
const uuId = uuid.v4();
|
||||
|
||||
setStoredImages([
|
||||
...storedImages,
|
||||
{
|
||||
id: uuId,
|
||||
src: url
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
reader.onerror = () => {
|
||||
setLoading(false);
|
||||
logger.error('Failed to upload virtual image!');
|
||||
};
|
||||
}, [ storedImages ]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showLabel && <label
|
||||
aria-label = { t('virtualBackground.uploadImage') }
|
||||
className = 'file-upload-label'
|
||||
htmlFor = 'file-upload'
|
||||
onKeyPress = { uploadImageKeyPress }
|
||||
tabIndex = { 0 } >
|
||||
<Icon
|
||||
className = { 'add-background' }
|
||||
size = { 20 }
|
||||
src = { IconPlusCircle } />
|
||||
{t('virtualBackground.addBackground')}
|
||||
</label>}
|
||||
|
||||
<input
|
||||
accept = 'image/*'
|
||||
className = 'file-upload-btn'
|
||||
id = 'file-upload'
|
||||
onChange = { uploadImage }
|
||||
ref = { uploadImageButton }
|
||||
type = 'file' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(UploadImageButton);
|
||||
@@ -3,11 +3,12 @@
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import Bourne from '@hapi/bourne';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { Dialog, hideDialog, openDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconCloseSmall, IconShareDesktop } from '../../base/icons';
|
||||
import { Icon, IconCloseSmall, IconPlusCircle, IconShareDesktop } from '../../base/icons';
|
||||
import { browser, JitsiTrackErrors } from '../../base/lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
|
||||
import { VIDEO_TYPE } from '../../base/media';
|
||||
@@ -17,19 +18,61 @@ import { Tooltip } from '../../base/tooltip';
|
||||
import { getLocalVideoTrack } from '../../base/tracks';
|
||||
import { showErrorNotification } from '../../notifications';
|
||||
import { toggleBackgroundEffect } from '../actions';
|
||||
import { IMAGES, BACKGROUNDS_LIMIT, VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
|
||||
import { toDataURL } from '../functions';
|
||||
import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||
import { resizeImage, toDataURL } from '../functions';
|
||||
import logger from '../logger';
|
||||
|
||||
import UploadImageButton from './UploadImageButton';
|
||||
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The list of Images to choose from.
|
||||
*/
|
||||
_images: Array<Image>,
|
||||
type Image = {
|
||||
tooltip?: string,
|
||||
id: string,
|
||||
src: string
|
||||
}
|
||||
|
||||
// The limit of virtual background uploads is 24. When the number
|
||||
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
||||
// the first/oldest uploaded background.
|
||||
const backgroundsLimit = 25;
|
||||
const images: Array<Image> = [
|
||||
{
|
||||
tooltip: 'image1',
|
||||
id: '1',
|
||||
src: 'images/virtual-background/background-1.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image2',
|
||||
id: '2',
|
||||
src: 'images/virtual-background/background-2.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image3',
|
||||
id: '3',
|
||||
src: 'images/virtual-background/background-3.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image4',
|
||||
id: '4',
|
||||
src: 'images/virtual-background/background-4.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image5',
|
||||
id: '5',
|
||||
src: 'images/virtual-background/background-5.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image6',
|
||||
id: '6',
|
||||
src: 'images/virtual-background/background-6.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image7',
|
||||
id: '7',
|
||||
src: 'images/virtual-background/background-7.jpg'
|
||||
}
|
||||
];
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The current local flip x status.
|
||||
@@ -46,11 +89,6 @@ type Props = {
|
||||
*/
|
||||
_selectedThumbnail: string,
|
||||
|
||||
/**
|
||||
* If the upload button should be displayed or not.
|
||||
*/
|
||||
_showUploadButton: boolean,
|
||||
|
||||
/**
|
||||
* Returns the selected virtual background object.
|
||||
*/
|
||||
@@ -90,15 +128,11 @@ const onError = event => {
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
|
||||
const hasBrandingImages = Boolean(dynamicBrandingImages.length);
|
||||
|
||||
return {
|
||||
_localFlipX: Boolean(localFlipX),
|
||||
_images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
|
||||
_virtualBackground: state['features/virtual-background'],
|
||||
_selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
|
||||
_showUploadButton: !(hasBrandingImages || state['features/base/config'].disableAddingBackgroundImages),
|
||||
_jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
|
||||
};
|
||||
}
|
||||
@@ -111,11 +145,9 @@ const VirtualBackgroundDialog = translate(connect(_mapStateToProps)(VirtualBackg
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function VirtualBackground({
|
||||
_images,
|
||||
_jitsiTrack,
|
||||
_localFlipX,
|
||||
_jitsiTrack,
|
||||
_selectedThumbnail,
|
||||
_showUploadButton,
|
||||
_virtualBackground,
|
||||
dispatch,
|
||||
initialOptions,
|
||||
@@ -126,7 +158,7 @@ function VirtualBackground({
|
||||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
|
||||
const uploadImageButton: Object = useRef(null);
|
||||
const [ activeDesktopVideo ] = useState(_virtualBackground?.virtualSource?.videoType === VIDEO_TYPE.DESKTOP
|
||||
? _virtualBackground.virtualSource
|
||||
: null);
|
||||
@@ -154,7 +186,7 @@ function VirtualBackground({
|
||||
// Preventing localStorage QUOTA_EXCEEDED_ERR
|
||||
err && setStoredImages(storedImages.slice(1));
|
||||
}
|
||||
if (storedImages.length === BACKGROUNDS_LIMIT) {
|
||||
if (storedImages.length === backgroundsLimit) {
|
||||
setStoredImages(storedImages.slice(1));
|
||||
}
|
||||
}, [ storedImages ]);
|
||||
@@ -289,27 +321,61 @@ function VirtualBackground({
|
||||
|
||||
const setImageBackground = useCallback(async e => {
|
||||
const imageId = e.currentTarget.getAttribute('data-imageid');
|
||||
const image = _images.find(img => img.id === imageId);
|
||||
const image = images.find(img => img.id === imageId);
|
||||
|
||||
if (image) {
|
||||
try {
|
||||
const url = await toDataURL(image.src);
|
||||
const url = await toDataURL(image.src);
|
||||
|
||||
setOptions({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
});
|
||||
logger.info('Image set for virtual background preview!');
|
||||
} catch (err) {
|
||||
logger.error('Could not fetch virtual background image:', err);
|
||||
}
|
||||
setOptions({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
});
|
||||
logger.info('Image setted for virtual background preview!');
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const uploadImage = useCallback(async e => {
|
||||
const reader = new FileReader();
|
||||
const imageFile = e.target.files;
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const url = await resizeImage(reader.result);
|
||||
const uuId = uuid.v4();
|
||||
|
||||
setStoredImages([
|
||||
...storedImages,
|
||||
{
|
||||
id: uuId,
|
||||
src: url
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
reader.onerror = () => {
|
||||
setLoading(false);
|
||||
logger.error('Failed to upload virtual image!');
|
||||
};
|
||||
}, [ dispatch, storedImages ]);
|
||||
|
||||
const uploadImageKeyPress = useCallback(e => {
|
||||
if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
|
||||
e.preventDefault();
|
||||
uploadImageButton.current.click();
|
||||
}
|
||||
}, [ uploadImageButton.current ]);
|
||||
|
||||
const setImageBackgroundKeyPress = useCallback(e => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -382,13 +448,25 @@ function VirtualBackground({
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{_showUploadButton
|
||||
&& <UploadImageButton
|
||||
setLoading = { setLoading }
|
||||
setOptions = { setOptions }
|
||||
setStoredImages = { setStoredImages }
|
||||
showLabel = { previewIsLoaded }
|
||||
storedImages = { storedImages } />}
|
||||
{previewIsLoaded && <label
|
||||
aria-label = { t('virtualBackground.uploadImage') }
|
||||
className = 'file-upload-label'
|
||||
htmlFor = 'file-upload'
|
||||
onKeyPress = { uploadImageKeyPress }
|
||||
tabIndex = { 0 } >
|
||||
<Icon
|
||||
className = { 'add-background' }
|
||||
size = { 20 }
|
||||
src = { IconPlusCircle } />
|
||||
{t('virtualBackground.addBackground')}
|
||||
</label> }
|
||||
<input
|
||||
accept = 'image/*'
|
||||
className = 'file-upload-btn'
|
||||
id = 'file-upload'
|
||||
onChange = { uploadImage }
|
||||
ref = { uploadImageButton }
|
||||
type = 'file' />
|
||||
<div
|
||||
className = 'virtual-background-dialog'
|
||||
role = 'radiogroup'
|
||||
@@ -457,7 +535,7 @@ function VirtualBackground({
|
||||
src = { IconShareDesktop } />
|
||||
</div>
|
||||
</Tooltip>
|
||||
{_images.map(image => (
|
||||
{images.map(image => (
|
||||
<Tooltip
|
||||
content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
|
||||
key = { image.id }
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* An enumeration of the different virtual background types.
|
||||
*
|
||||
@@ -11,54 +9,3 @@ export const VIRTUAL_BACKGROUND_TYPE = {
|
||||
BLUR: 'blur',
|
||||
NONE: 'none'
|
||||
};
|
||||
|
||||
|
||||
export type Image = {
|
||||
tooltip?: string,
|
||||
id: string,
|
||||
src: string
|
||||
}
|
||||
|
||||
// The limit of virtual background uploads is 24. When the number
|
||||
// of uploads is 25 we trigger the deleteStoredImage function to delete
|
||||
// the first/oldest uploaded background.
|
||||
export const BACKGROUNDS_LIMIT = 25;
|
||||
|
||||
|
||||
export const IMAGES: Array<Image> = [
|
||||
{
|
||||
tooltip: 'image1',
|
||||
id: '1',
|
||||
src: 'images/virtual-background/background-1.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image2',
|
||||
id: '2',
|
||||
src: 'images/virtual-background/background-2.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image3',
|
||||
id: '3',
|
||||
src: 'images/virtual-background/background-3.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image4',
|
||||
id: '4',
|
||||
src: 'images/virtual-background/background-4.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image5',
|
||||
id: '5',
|
||||
src: 'images/virtual-background/background-5.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image6',
|
||||
id: '6',
|
||||
src: 'images/virtual-background/background-6.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'image7',
|
||||
id: '7',
|
||||
src: 'images/virtual-background/background-7.jpg'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -22,7 +22,7 @@ local function get_poll_message(stanza)
|
||||
return nil;
|
||||
end
|
||||
local data = json.decode(json_data);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
if data.type ~= "new-poll" and data.type ~= "answer-poll" then
|
||||
return nil;
|
||||
end
|
||||
return data;
|
||||
@@ -42,7 +42,7 @@ end
|
||||
module:hook("muc-room-created", function(event)
|
||||
local room = event.room;
|
||||
if is_healthcheck_room(room.jid) then return end
|
||||
module:log("debug", "setting up polls in room %s", room.jid);
|
||||
module:log("debug", "setting up polls in room "..tostring(room));
|
||||
room.polls = {
|
||||
by_id = {};
|
||||
order = {};
|
||||
|
||||
@@ -128,9 +128,9 @@ function Util:get_public_key(keyId)
|
||||
local content = self.cache:get(keyId);
|
||||
if content == nil then
|
||||
-- If the key is not found in the cache.
|
||||
module:log("debug", "Cache miss for key: %s", keyId);
|
||||
module:log("debug", "Cache miss for key: "..keyId);
|
||||
local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
|
||||
module:log("debug", "Fetching public key from: %s", keyurl);
|
||||
module:log("debug", "Fetching public key from: "..keyurl);
|
||||
content = http_get_with_retry(keyurl, nr_retries);
|
||||
if content ~= nil then
|
||||
self.cache:set(keyId, content);
|
||||
@@ -138,7 +138,7 @@ function Util:get_public_key(keyId)
|
||||
return content;
|
||||
else
|
||||
-- If the key is in the cache, use it.
|
||||
module:log("debug", "Cache hit for key: %s", keyId);
|
||||
module:log("debug", "Cache hit for key: "..keyId);
|
||||
return content;
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user