Compare commits

...

85 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
664552bc05 chore(rn,versions) bump SDK versions 2022-03-28 11:22:36 +02:00
Saúl Ibarra Corretgé
ac35eea08e feat(ios) enable Dropbox recording 2022-03-25 17:24:06 +01:00
Saúl Ibarra Corretgé
172683d645 fix(rn,recording) fix recording dialog state not updating 2022-03-25 17:23:30 +01:00
Calinteodor
858e83b09e fix(rn,recording) fix start button not being enabled 2022-03-25 11:53:30 +02:00
Calin Chitu
e2750ee58e feat(participants/native) - updated container styles 2022-03-25 11:51:01 +02:00
Calin Chitu
fe132581d4 fix(mobile/navigation) - fixed bottom color glitch 2022-03-23 19:09:52 +02:00
Saúl Ibarra Corretgé
21083e6777 fix(mobile,navigation) remove end meeting page
It will just flash for a split second and all it does is dispatch
readyToClose, so do it directly instead.
2022-03-22 12:10:38 +01:00
Calin Chitu
a3c60d5943 fix(mobile/navigation/sdk) - simplified check for sdk 2022-03-22 12:10:38 +01:00
Robert Pintilii
28556e030c fix(settings-dialog) Fix crash (#11191)
Fixes crash when the participant becomes moderator while the dialog is open
2022-03-22 13:10:08 +02:00
Shahab
e54685a566 refactor(premeeting): remove redundant styles from prejoin.scss (#11151)
"prejoin-preview-dropdown-btn" and "prejoin-preview-dropdown-icon" styles are already declared using jss in DropdownButton component.
2022-03-22 09:29:31 +02:00
Hristo Terezov
9ffb3eddb0 fix(shared-video): Can't click controls issue
The Dominant speaker name badge was overlaping the shared video
controls  (audio level, play/pause, etc).
2022-03-21 16:00:24 -05:00
Jaya Allamsetty
d064f607ec chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1399.0.0+1a98d919...v1401.0.0+584a8680
2022-03-21 14:57:59 -04:00
Aaron van Meerten
be454137f9 task: mod_muc_password_whitelist prosody module (#11184) 2022-03-21 11:38:21 -05:00
zygisS22
530369613a fix: removed platformColor from styles 2022-03-21 16:03:59 +02:00
Avram Tudor
6b0b575bd4 fix(highlight) fix notifications not disappearing (#11183) 2022-03-21 15:14:26 +02:00
Calin Chitu
aa5dd92846 patch(react-native-dialog)- replaced PlatformColor with hardcoded colors on Android 2022-03-18 16:14:26 +01:00
Gabriel Borlea
85c505a29d add(highlights): mobile flow (#11168)
* add(highlight): mobile initial flow

* fix(hightlight): get meeting fqn on mobile

* fix(dynamic-branding): extract fqn on mobile

* fix(highlights): remove local fqn extraction and grounp dispatches in batch

* fix(dynamic-branding): check if state is defined in extract fqn
2022-03-18 16:16:56 +02:00
Avram Tudor
b8e12e581f fix(recording) fix incorrect condition for recording notification message (#11167) 2022-03-18 13:21:10 +02:00
Avram Tudor
8456a63a23 fix(highlight) display option to start recording (#11146)
Fix incorrect handling of error case when highlighting moments
Allow users to start recording when trying to highlight while recording not started
2022-03-18 10:19:20 +02:00
Mihaela Dumitru
89394e69b6 fix(config): add missing toolbar button config (#11165) 2022-03-17 18:58:25 +02:00
Saúl Ibarra Corretgé
3ae50c1701 fix(css) remove no longer used AUI classes 2022-03-17 17:28:36 +01:00
Calinteodor
5da40a5fb0 fix(mobile/navigations) added LoadConfigOverlay to RootNavigator (#11067)
Converted LoadConfigOverlay to a JitsiScreen component that right now is part of navigation as ConnectingPage, plus I
separated appNative and other actions into web and native.
2022-03-17 16:13:58 +02:00
Saúl Ibarra Corretgé
f04a01ee3a fix(ios,build) use epoch seconds for build number
Avoids conflicts when a build is rejected by Apple since Fastlane cannot
detect it.
2022-03-17 14:03:24 +01:00
Christoph Settgast
9ad57127d4 feat: Use same recommendedBrowsers page for IE and browsers marked in interface_config.js (#10923)
Reason:
- This is easier overridable than then embedded react page
- Consistenty between IE and other unsupported browsers
2022-03-17 14:56:43 +02:00
Robert Pintilii
a131644dfb fix(video-constraints) Fix calculations (#11161)
Only calculate for Large Video on the web (otherwise native breaks)
Take preferred max into calculations
2022-03-17 14:18:49 +02:00
Mihaela Dumitru
649c1b0669 fix(salesforce): send link notes and default to empty string (#11160) 2022-03-17 12:58:12 +02:00
Robert Pintilii
7bcf7bb686 fix(resziable-filmstrip) Update video constraints on filmstrip resize (#11150)
Update video quality of large video as well
2022-03-16 16:57:30 +02:00
Shahab
8e035b03b0 fix(premeeting): fix undefined breakpoint in media query (#11148) 2022-03-16 15:16:01 +02:00
Saúl Ibarra Corretgé
3b05fd70a3 Revert "deps(rn) react-native-webrtc@1.98.0"
This reverts commit 4fa377c12a.
2022-03-16 12:05:06 +01:00
Shahab
64d32a5005 fix(premeeting): call hooks before any conditional block in ConnectionStatus (#11136) 2022-03-16 10:59:24 +02:00
Aaron van Meerten
fbe15aaa47 feature: patch for muc_owner_allow_kick in prosody 0.12 (#11142) 2022-03-15 21:15:14 -05:00
Hristo Terezov
16bcb1b217 feat(filmstrip): Don't reorder in small meetings. 2022-03-15 14:56:46 -05:00
Jaya Allamsetty
d4c314deb3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1398.0.0+aed1fa44...v1399.0.0+1a98d919
2022-03-15 15:27:25 -04:00
JohannesPertl
e8e9e24f77 fix(config) add missing notify.hostAskedUnmute 2022-03-15 19:16:56 +01:00
Jaya Allamsetty
9f72c318d6 feat(multi-stream-support) Add screenshare as a second video track to the call.
* feat(multi-stream-support) Add screenshare as a second video track to the call.
This feature is behind a sendMultipleVideoStreams config.js flag. sourceNameSignaling flag also needs to enabled. Sending multiple tracks is currently supported only on endpoints running in unified plan mode. However, clients with source-name signaling enabled and running in plan-b can still receive multiple streams .

* squash: check if there is an existing track before adding camera/desktop

* squash: enable multi-stream only on unified plan endpoints.
2022-03-15 13:24:49 -04:00
Jaya Allamsetty
5f1a4f189c chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1389.0.0+313e0dd3...v1398.0.0+aed1fa44
2022-03-15 11:18:22 -04:00
Hristo Terezov
cb9511ef2c fix(resizable-filmstrip): grid view paddings. 2022-03-15 07:48:51 -05:00
Hristo Terezov
e7c4a55add feat(tile-view): Optimize grid dimnsions.
Now the algorithm that calculates the
rows/columns/thumbnail-width/thumbnail-height configuration will
go trough all possible configurations and will choose the one
that covers with thumbnails the biggest area of the window.
2022-03-15 07:48:51 -05:00
Gabriel Borlea
3884862996 add(screenshot-capture): local participants id to participants array in metadata (#11134) 2022-03-15 14:42:26 +02:00
gpatel-fr
8f1c693c3e fix(lang) update french translation 2022-03-15 13:35:45 +01:00
Gabriel Borlea
4d2000a7a4 fix(screenshot-capture): send remote participant id instead of jid (#11132)
* fix(screenshot-capture): send remote participant id instead of jid

* fix: lint errors
2022-03-15 14:05:01 +02:00
Shahab
f6a79860f2 refactor(prejoin) use jss instead of sass in DialInDialog (#11122) 2022-03-15 13:30:38 +02:00
Shahab
4ed2d55cae refactor(participnats-pane) move participant-avatar to commmonStyles (#11120) 2022-03-15 13:29:42 +02:00
Shahab
2dad8163bb refactor(premeeting): use jss instead of sass in ConnectionStatus (#11115) 2022-03-15 13:28:16 +02:00
Shahab
985b37195c refactor(prejoin) use jss instead of sass in CallingDialog (#11117) 2022-03-15 13:16:01 +02:00
Ali Alhaidary
052b588b33 fix(lang) update Arabic translation 2022-03-15 12:13:48 +01:00
Shahab
320aeaa4df refactor(speaker-stats) use jss instead of sass in SpeakerStats (#11121) 2022-03-15 13:02:50 +02:00
Shahab
8c9c1a29be refactor(prejoin) use jss instead of sass in DeviceStatus (#11116) 2022-03-15 12:48:05 +02:00
Shahab
c25fb702c2 refactor(avatar) use jss instead of scss (#11037) 2022-03-15 12:27:40 +02:00
Shahab
a6b55b676c chore: remove unused modal ids constants (#11036) 2022-03-15 11:28:25 +02:00
Shahab
0ce6fce58d refactor(keyboard-shortcuts) use jss instead of scss 2022-03-15 09:27:07 +01:00
Avram Tudor
c5fbe14de2 fix(highlight) fix allowing disabled button to execute handler (#11128) 2022-03-14 17:18:54 +02:00
Horatiu Muresan
506f72d43e feat(remote-participant-menu) Enhance remote participant menu:
- option to hide the context menu
- option to hide private chat button
2022-03-14 16:46:19 +02:00
Дамян Минков
3d5f838870 fix: Hides ask to unmute when participant is audio unmuted. 2022-03-14 08:00:29 -05:00
Дамян Минков
e18173adc9 fix: Hides ask to unmute when av mod is disabled. Fixes #11057. 2022-03-14 08:00:29 -05:00
Robert Pintilii
096ba054db fix(filmstrip) Fix resizable filmstrip with shared video (#11124)
Calculate shared video width with the filmstrip width
Make resizable filmstrip mouseup event work with shared video iframe
2022-03-14 13:11:22 +02:00
Avram Tudor
d651ecb166 feat(recording) allow highlighting meeting recording moments (#10981) 2022-03-14 10:31:08 +02:00
Avram Tudor
faac45b5bc feat(toolbar) add flag for autohiding while chat open (#11104) 2022-03-14 10:30:31 +02:00
José Luís Andrade
14ee7e247b fix(lang) update Portuguese translation 2022-03-14 09:29:52 +01:00
Randolf Richardson
8604b1f64d Fix broken link in README.md to the handbook
Fixed the broken link to the handbook for the self-hosting installation guide.
2022-03-11 18:51:59 -06:00
Robert Pintilii
190041fc5a feat(gif) Added GIF support (GIPHY integration) (#11021)
Show GIF menu in reactions menu
Search GIFs using the GIPHY API
Show GIFs as images in chat
Show GIFs on the thumbnail of the participant that sent it
Move GIF focus using up/ down arrows and send with Enter
Added analytics
2022-03-11 15:00:49 +02:00
Ali Alhaidary
b6d55571ba fix(lang) update Arabic translation 2022-03-11 13:15:32 +01:00
Oliver Schneider
ec96acac71 fix(letsencrypt) avoid using hardcoded path
This introduces a variable which holds the path to the detected `certbot`
binary and reuses that to fix the inconsistency which arose from looking for
`certbot` but then later using the hardcoded path `/usr/bin/certbot` in
invocations. This broke snap installations of certbot.

Fixes: https://github.com/jitsi/jitsi-meet/issues/11072

Co-authored-by: Oliver Schneider <oliver@assarbad.net>
2022-03-11 13:14:42 +01:00
Horatiu Muresan
26f21e5eb9 fix(prejoin) Fix layout on reduced height 2022-03-11 11:24:05 +02:00
Robert Pintilii
6820f48fd2 fix(screensharing) Fix screensharing container width (#11089)
Fix width when the filmstrip is resized
2022-03-11 09:21:56 +02:00
Дамян Минков
14cad060cb fix: Updates token verification hooks priority. (#11105)
* fix: Updates token verification hooks priority.

Set it as the highest priorities, if denying access, better do it earlier.

* squash: Updates values.
2022-03-10 14:28:58 -06:00
Avram Tudor
f0f135ac7d fix(dynamic-branding) fix permissions screen not accounting for custom backgrounds (#11097) 2022-03-10 15:31:27 +02:00
Calinteodor
c60a51e671 feat(recording) mobile and web ui updates 2022-03-08 17:08:36 +01:00
Horatiu Muresan
4b95a5d6cb fix(language) Add lang API option
- jwt from API overwrites any jwt sent as queryparam
- `defaultLanguage` from configOverwrite converts to `lang` query param
2022-03-08 16:57:53 +02:00
Saúl Ibarra Corretgé
8b149f9138 chore(rn,versions) bump app versions 2022-03-08 15:36:41 +01:00
Saúl Ibarra Corretgé
4fa377c12a deps(rn) react-native-webrtc@1.98.0 2022-03-08 15:19:53 +01:00
Calin Chitu
371f3ee199 feat(display-name) remove DisplayNameLabel web 2022-03-08 16:15:23 +02:00
Mihaela Dumitru
98256a8d5b feat(salesforce) - link resources to the current session (#10992) 2022-03-08 11:10:30 +02:00
Jaya Allamsetty
9d8ae922a9 fix(conference) Disable audio-only mode when user switches to screenshare.
Make the behavior consistent with enabling camera when the user is audio-only mode. Also fixes an issue where the screenshare preview doesn't appear if it is enabled while the user is in audio-only mode.
2022-03-07 16:31:22 -05:00
Christoph Settgast
981e96fb6a fix(lang) update German translation
Signed-off-by: Christoph Settgast <csett86@web.de>
2022-03-05 12:06:22 +01:00
Hristo Terezov
74384bfa66 fix(video-layout):Screenshares not updated on time 2022-03-04 18:13:36 -06:00
Tudor-Ovidiu Avram
d718d9d8fb feat(face-centering) implement centering of faces in a video
Config options:

faceCoordinatesSharing.enabled
faceCoordinatesSharig.threshold
faceCoordinatesSharing.captureInterval
2022-03-04 18:13:36 -06:00
Robert Pintilii
2863b05f37 fix(dominant-label) Fix dominant speaker stage view label (#11071)
Move label to LargeVideo so that's it's centred in the view instead of the whole screen
Move vertical filmstrip max width calculation to a function and use it to set the large video width
2022-03-04 15:10:57 +02:00
Saúl Ibarra Corretgé
fca9a249dc fix(rn,dialogs,auth) fix not showing authentication dialogs
1) Fix not being able to show multiple dialogs at once in iOS
2) Fix cancelling the login dialog when XMPP auth is enabled without
   guest support
2022-03-04 13:56:56 +01:00
Doganbros
7522de033a feat: Lobby chat (#10847)
* feat(lobby): lobby chat

lobby chat support
knocking participants list updates
knocking participants conditonal checks to show message button
handle lobby chat message events
lobby messages from or to moderators only

Co-authored-by: Fecri Kaan Ulubey <f.kaan93@gmail.com>

* squash: Drop typos.

Co-authored-by: Kusi Musah Hussein <kusimusah@gmail.com>
Co-authored-by: Fecri Kaan Ulubey <f.kaan93@gmail.com>
Co-authored-by: Дамян Минков <damencho@jitsi.org>
2022-03-03 11:29:38 -06:00
humanamburu
7a4a234f8e fix(premeeting): Improve pre-meeting responsiveness for screens less than 1000px
Use 720 (HD) width as breakpoint to change screen layout
Reduce join panel size to be able to see video camera pic
2022-03-03 16:35:23 +01:00
Shahab
bc5e9e2258 fix(popper): resolve @atlaskit/popper to an unbuggy version 2022-03-03 16:19:57 +01:00
Gabriel Borlea
a65ab1c029 fix(dynamic-branding): Extract fqn from public meeting 2022-03-03 10:08:08 +02:00
Дамян Минков
5d41da9358 fix: Fixes recording dialog web rendering.
[features/base/app] <APP.componentDidCatch>:  TypeError: Failed to set an indexed property on 'CSSStyleDeclaration': Indexed property setter is not supported.
2022-03-02 13:19:34 -06:00
Pawel Domas
0936a64d3f fix(receiver constraints): source name not found
...when new participant joins.

Repro steps:

1. With p2p disabled and source name signaling enabled.
2. Start a call with 2 tabs.
3. Reload the 2nd tab.
4. The receiver constraints should be updated when the 2nd
   user rejoins. They were not updated, because
   getTrackSourceNameByMediaTypeAndParticipant doesn't have
   the track yet at the time when visibleRemoteParticipants
   are updated. This is fixed by also checking on
   the remote tracks state.
2022-03-02 11:30:38 -05:00
266 changed files with 9038 additions and 1831 deletions

View File

@@ -4,6 +4,7 @@ DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet
LIBFLAC_DIR = node_modules/libflacjs/dist/min
OLM_DIR = node_modules/@matrix-org/olm
TF_WASM_DIR = node_modules/@tensorflow/tfjs-backend-wasm/dist/
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist
TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models
@@ -29,7 +30,7 @@ clean:
rm -fr $(BUILD_DIR)
.NOTPARALLEL:
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-css deploy-local deploy-facial-expressions
deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-tf-wasm deploy-css deploy-local deploy-facial-expressions
deploy-init:
rm -fr $(DEPLOY_DIR)
@@ -52,6 +53,8 @@ deploy-appbundle:
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.js.map \
$(BUILD_DIR)/face-centering-worker.min.js \
$(BUILD_DIR)/face-centering-worker.min.js.map \
$(BUILD_DIR)/facial-expressions-worker.min.js \
$(BUILD_DIR)/facial-expressions-worker.min.js.map \
$(DEPLOY_DIR)
@@ -80,6 +83,11 @@ deploy-olm:
$(OLM_DIR)/olm.wasm \
$(DEPLOY_DIR)
deploy-tf-wasm:
cp \
$(TF_WASM_DIR)/*.wasm \
$(DEPLOY_DIR)
deploy-rnnoise-binary:
cp \
$(RNNOISE_WASM_DIR)/rnnoise.wasm \
@@ -109,7 +117,7 @@ deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
.NOTPARALLEL:
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-facial-expressions
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-tf-wasm deploy-facial-expressions
$(WEBPACK_DEV_SERVER)
source-package:

View File

@@ -45,7 +45,7 @@ developed you can also sign up for our open beta testing here:
## Running your own instance
If you'd like to run your own Jitsi Meet installation head over to the [handbook](https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-start) to get started.
If you'd like to run your own Jitsi Meet installation head over to the [handbook](https://jitsi.github.io/handbook/docs/devops-guide/) to get started.
We provide Debian packages and a comprehensive Docker setup to make deployments as simple as possible.
Advanced users also have the possibility of building all the components from source.

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=22.0.0
sdkVersion=5.0.0
appVersion=22.1.0
sdkVersion=5.0.1

View File

@@ -27,6 +27,7 @@ import {
} from './react/features/app/actions';
import { showModeratedNotification } from './react/features/av-moderation/actions';
import { shouldShowModeratedNotification } from './react/features/av-moderation/functions';
import { setAudioOnly } from './react/features/base/audio-only';
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
@@ -51,7 +52,7 @@ import {
sendLocalParticipant,
nonParticipantMessageReceived
} from './react/features/base/conference';
import { getReplaceParticipant } from './react/features/base/config/functions';
import { getReplaceParticipant, getMultipleVideoSupportFeatureFlag } from './react/features/base/config/functions';
import {
checkAndNotifyForNewDevice,
getAvailableDevices,
@@ -105,6 +106,7 @@ import {
updateSettings
} from './react/features/base/settings';
import {
addLocalTrack,
createLocalPresenterTrack,
createLocalTracksF,
destroyLocalTracks,
@@ -126,6 +128,7 @@ import {
maybeOpenFeedbackDialog,
submitFeedback
} from './react/features/feedback';
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
import {
isModerationNotificationDisplayed,
showNotification,
@@ -1442,11 +1445,13 @@ export default {
* @returns {Promise}
*/
useVideoStream(newTrack) {
const state = APP.store.getState();
logger.debug(`useVideoStream: ${newTrack}`);
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
const oldTrack = getLocalJitsiVideoTrack(APP.store.getState());
const oldTrack = getLocalJitsiVideoTrack(state);
logger.debug(`useVideoStream: Replacing ${oldTrack} with ${newTrack}`);
@@ -1457,6 +1462,26 @@ export default {
return;
}
// In the multi-stream mode, add the track to the conference if there is no existing track, replace it
// otherwise.
if (getMultipleVideoSupportFeatureFlag(state)) {
const trackAction = oldTrack
? replaceLocalTrack(oldTrack, newTrack, room)
: addLocalTrack(newTrack);
APP.store.dispatch(trackAction)
.then(() => {
this.setVideoMuteStatus();
})
.then(resolve)
.catch(error => {
logger.error(`useVideoStream failed: ${error}`);
reject(error);
})
.then(onFinish);
return;
}
APP.store.dispatch(
replaceLocalTrack(oldTrack, newTrack, room))
.then(() => {
@@ -1676,6 +1701,9 @@ export default {
return Promise.reject('Cannot toggle screen sharing: not supported.');
}
if (this.isAudioOnly()) {
APP.store.dispatch(setAudioOnly(false));
}
if (toggle) {
try {
await this._switchToScreenSharing(options);
@@ -1937,11 +1965,13 @@ export default {
// api.
if (localAudio) {
this._mixerEffect = new AudioMixerEffect(this._desktopAudioStream);
logger.debug(`_switchToScreenSharing is mixing ${this._desktopAudioStream} and ${localAudio}`
+ ' as a single audio stream');
await localAudio.setEffect(this._mixerEffect);
} else {
// If no local stream is present ( i.e. no input audio devices) we use the screen share audio
// stream as we would use a regular stream.
logger.debug(`_switchToScreenSharing is using ${this._desktopAudioStream} for useAudioStream`);
await this.useAudioStream(this._desktopAudioStream);
}
@@ -2102,6 +2132,10 @@ export default {
if (this.isLocalId(id)) {
logger.info(`My role changed, new role: ${role}`);
if (role === 'moderator') {
APP.store.dispatch(maybeSetLobbyChatMessageListener());
}
APP.store.dispatch(localParticipantRoleChanged(role));
APP.API.notifyUserRoleChanged(id, role);
} else {

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-unused-vars, no-var */
var config = {
@@ -473,6 +474,9 @@ var config = {
// If Lobby is enabled starts knocking automatically.
// autoKnockLobby: false,
// Enable lobby chat.
// enableLobbyChat: true,
// DEPRECATED! Use `breakoutRooms.hideAddRoomButton` instead.
// Hides add breakout room button
// hideAddRoomButton: false,
@@ -512,7 +516,7 @@ var config = {
// Hides the dominant speaker name badge that hovers above the toolbox
// hideDominantSpeakerBadge: false,
// Default language for the user interface.
// Default language for the user interface. Cannot be overwritten.
// defaultLanguage: 'en',
// Disables profile and the edit of all fields from the profile settings (display name and email)
@@ -599,7 +603,9 @@ var config = {
// 'fullscreen',
// 'hangup',
// 'help',
// 'highlight',
// 'invite',
// 'linktosalesforce',
// 'livestreaming',
// 'microphone',
// 'mute-everyone',
@@ -631,7 +637,9 @@ var config = {
// timeout: 4000,
// // Moved from interfaceConfig.TOOLBAR_ALWAYS_VISIBLE
// // Whether toolbar should be always visible or should hide after x miliseconds.
// alwaysVisible: false
// alwaysVisible: false,
// // Indicates whether the toolbar should still autohide when chat is open
// autoHideWhileChatIsOpen: false
// },
// Toolbar buttons which have their click/tap event exposed through the API on
@@ -746,6 +754,17 @@ var config = {
// Enables displaying facial expressions in speaker stats
// enableDisplayFacialExpressions: true,
// faceCoordinatesSharing: {
// // Enables sharing your face cordinates. Used for centering faces within a video.
// enabled: false,
// // Minimum required face movement percentage threshold for sending new face coordinates data.
// threshold: 10,
// // Miliseconds for processing a new image capture in order to detect face coordinates if they exist.
// captureInterval: 100
// },
// Controls the percentage of automatic feedback shown to participants when callstats is enabled.
// The default value is 100%. If set to 0, no automatic feedback will be requested
// feedbackPercentage: 100,
@@ -980,12 +999,25 @@ var config = {
// Options related to the remote participant menu.
// remoteVideoMenu: {
// // Whether the remote video context menu to be rendered or not.
// disabled: true,
// // If set to true the 'Kick out' button will be disabled.
// disableKick: true,
// // If set to true the 'Grant moderator' button will be disabled.
// disableGrantModerator: true
// disableGrantModerator: true,
// // If set to true the 'Send private message' button will be disabled.
// disablePrivateChat: true
// },
// Endpoint that enables support for salesforce integration with in-meeting resource linking
// This is required for:
// listing the most recent records - salesforceUrl/records/recents
// searching records - salesforceUrl/records?text=${text}
// retrieving record details - salesforceUrl/records/${id}?type=${type}
// and linking the meeting - salesforceUrl/sessions/${sessionId}/records/${id}
//
// salesforceUrl: 'https://api.example.com/',
// If set to true all muting operations of remote participants will be disabled.
// disableRemoteMute: true,
@@ -1092,7 +1124,8 @@ var config = {
// 'e2ee',
// 'transcribing',
// 'video-quality',
// 'insecure-room'
// 'insecure-room',
// 'highlight-moment'
// ]
// },
@@ -1232,6 +1265,7 @@ var config = {
// 'notify.invitedThreePlusMembers', // shown when 3+ participants have been invited
// 'notify.invitedTwoMembers', // shown when 2 participants have been invited
// 'notify.kickParticipant', // shown when a participant is kicked
// 'notify.linkToSalesforce', // shown when joining a meeting with salesforce integration
// 'notify.moderationStartedTitle', // shown when AV moderation is activated
// 'notify.moderationStoppedTitle', // shown when AV moderation is deactivated
// 'notify.moderationInEffectTitle', // shown when user attempts to unmute audio during AV moderation
@@ -1247,6 +1281,7 @@ var config = {
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.startSilentTitle', // shown when user joined with no audio
// 'notify.unmute', // shown to moderator when user raises hand during AV moderation
// 'notify.hostAskedUnmute', // shown to participant when host asks them to unmute
// 'prejoin.errorDialOut',
// 'prejoin.errorDialOutDisconnected',
// 'prejoin.errorDialOutFailed',
@@ -1269,12 +1304,34 @@ var config = {
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
// disableResizable: false,
// }
// },
// Tile view related config options.
// tileView: {
// // The optimal number of tiles that are going to be shown in tile view. Depending on the screen size it may
// // not be possible to show the exact number of participants specified here.
// numberOfVisibleTiles: 25
// },
// Specifies whether the chat emoticons are disabled or not
// disableChatSmileys: false,
// Settings for the GIPHY integration.
// giphy: {
// // Whether the feature is enabled or not.
// enabled: false,
// // SDK API Key from Giphy.
// sdkKey: '',
// // Display mode can be one of:
// // - tile: show the GIF on the tile of the participant that sent it.
// // - chat: show the GIF as a message in chat
// // - all: all of the above. This is the default option
// displayMode: 'all',
// // How long the GIF should be displayed on the tile (in miliseconds).
// tileTime: 5000
// },
// Allow all above example options to include a trailing comma and
// prevent fear when commenting out the last value.
makeJsonParserHappy: 'even if last key had a trailing comma'

View File

@@ -1,50 +0,0 @@
.avatar {
background-color: #AAA;
border-radius: 50%;
color: rgba(255, 255, 255, 1);
font-weight: 100;
object-fit: cover;
&.avatar-small {
height: 28px !important;
width: 28px !important;
}
&.avatar-xsmall {
height: 16px !important;
width: 16px !important;
}
.jitsi-icon {
transform: translateY(50%);
}
}
.avatar-svg {
height: 100%;
width: 100%;
}
.avatar-badge {
position: relative;
&-available::after {
@include avatarBadge;
background-color: $presence-available;
}
&-away::after {
@include avatarBadge;
background-color: $presence-away;
}
&-busy::after {
@include avatarBadge;
background-color: $presence-busy;
}
&-idle::after {
@include avatarBadge;
background-color: $presence-idle;
}
}

View File

@@ -164,16 +164,6 @@ form {
font-size: 12px;
}
/**
* Dialogs fade
*/
.aui-blanket {
background: #000;
transition: opacity 0.2s, visibility 0.2s;
transition-delay: 0.1s;
visibility: visible;
}
#inviteLinkRef {
-webkit-user-select: text;
user-select: text;

View File

@@ -85,6 +85,10 @@
fill: white;
}
}
&.lobby-chat-recipient {
background-color: $chatLobbyMessageBackgroundColor;
}
}
@@ -455,6 +459,9 @@
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
.display-name {
@@ -494,6 +501,10 @@
justify-content: center;
padding: 5px;
&.lobbychatmessageactions {
border-left-color: $chatLobbyActionsSeparatorColor;
}
.toolbox-icon {
cursor: pointer;
}
@@ -511,6 +522,9 @@
&.privatemessage {
background-color: $chatPrivateMessageBackgroundColor;
}
&.lobbymessage {
background-color: $chatLobbyMessageBackgroundColor;
}
}
}

View File

@@ -1,10 +0,0 @@
.shortcuts-list {
list-style-type: none;
padding: 0;
&__item {
display: flex;
justify-content: space-between;
margin-bottom: em(7, 14);
}
}

View File

@@ -193,16 +193,3 @@
@mixin transparentBg($color, $alpha) {
background-color: rgba(red($color), green($color), blue($color), $alpha);
}
/**
* Avatar status badge mixin
*/
@mixin avatarBadge {
border-radius: 50%;
content: '';
display: block;
height: 35%;
position: absolute;
bottom: 0;
width: 35%;
}

View File

@@ -25,10 +25,6 @@
}
}
.participant-avatar {
margin: 8px 16px 8px 0;
}
@media (max-width: 580px) {
.participants_pane {
height: 100vh;

View File

@@ -7,7 +7,20 @@
border-radius: 3px;
padding: 16px;
&.with-gif {
width: 328px;
.reactions-row .toolbox-button:last-of-type {
top: 3px;
& .toolbox-icon.toggled {
background-color: #000000;
}
}
}
&.overflow {
width: 100%;
.toolbox-icon {
width: 48px;
@@ -27,6 +40,10 @@
.toolbox-button {
margin-right: 0;
}
.toolbox-button:last-of-type {
top: 0;
}
}
}
@@ -56,6 +73,7 @@
.toolbox-button {
margin-right: 8px;
touch-action: manipulation;
position: relative;
}
.toolbox-button:last-of-type {

View File

@@ -7,22 +7,23 @@
flex-direction: column;
.recording-header {
align-items: center;
display: flex;
flex: 0;
flex-direction: row;
justify-content: space-between;
padding-top: 32px;
.recording-title {
display: inline-flex;
align-items: center;
font-size: 16px;
font-size: 14px;
margin-left: 16px;
}
}
.recording-header-line {
border-top: 1px solid #5e6d7a;
padding-top: 32px;
}
.recording-switch-disabled {
@@ -34,10 +35,79 @@
align-items: center;
}
.file-sharing-icon-container {
background-color: #525252;
border-radius: 4px;
height: 40px;
justify-content: center;
width: 56px;
}
.cloud-content-recording-icon-container {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
justify-content: center;
width: 40px;
}
.jitsi-recording-header {
margin-bottom: 32px;
}
.jitsi-content-recording-icon-container-with-switch {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
width: 56px;
}
.jitsi-content-recording-icon-container-without-switch {
background-color: #FFFFFF;
border-radius: 4px;
height: 40px;
width: 46px;
}
.recording-icon {
width: 32px;
height: 32px;
height: 40px;
object-fit: contain;
width: 40px;
}
.content-recording-icon {
height: 18px;
margin: 10px 0 0 10px;
object-fit: contain;
width: 18px;
}
.recording-file-sharing-icon {
height: 18px;
object-fit: contain;
width: 18px;
}
.recording-info{
background-color: #FFD740;
color: black;
display: inline-flex;
margin: 32px 0;
width: 100%;
}
.recording-info-icon {
align-self: center;
height: 14px;
margin: 0 24px 0 16px;
object-fit: contain;
width: 14px;
}
.recording-info-title {
display: inline-flex;
font-size: 14px;
width: 290px
}
.recording-switch {

View File

@@ -48,6 +48,7 @@ canvas,
progress,
video {
display: inline-block;
transition: object-position 0.5s ease 0s;
vertical-align: baseline;
}
audio:not([controls]) {

View File

@@ -28,10 +28,6 @@ $defaultSemiDarkColor: #ACACAC;
$defaultDarkColor: #2b3d5c;
$defaultWarningColor: rgb(215, 121, 118);
$participantsPaneBgColor: #141414;
$presence-available: rgb(110, 176, 5);
$presence-away: rgb(250, 201, 20);
$presence-busy: rgb(233, 0, 27);
$presence-idle: rgb(172, 172, 172);
/**
* Toolbar
@@ -83,6 +79,8 @@ $modalTextColor: #333;
$chatActionsSeparatorColor: rgb(173, 105, 112);
$chatBackgroundColor: #131519;
$chatInputSeparatorColor: #A4B8D1;
$chatLobbyMessageBackgroundColor: #6A50D3;
$chatLobbyActionsSeparatorColor: #6A50D3;
$chatLocalMessageBackgroundColor: #484A4F;
$chatPrivateMessageBackgroundColor: rgb(153, 69, 77);
$chatRemoteMessageBackgroundColor: #242528;

View File

@@ -1,68 +0,0 @@
.select2-container.aui-select2-container {
background-color: transparent !important;
margin-top: 2px;
a.select2-choice {
height: 28px !important;
line-height: 18px !important;
width: 100% !important;
background-color: $selectBg !important;
border-color: $selectBg !important;
color: $selectFontColor !important;
text-shadow: none !important;
font-size: 12px !important;
margin: 0 auto !important;
&:after {
border-top-color: $selectFontColor;
}
}
&.select2-dropdown-open{
a.select2-choice {
background-color: $selectActiveBg !important;
border-color: $selectActiveBg !important;
}
}
}
.select2-drop.aui-select2-drop.aui-style-default {
z-index: $dropdownZ;
background-color: $selectActiveBg;
border-color: $selectActiveBg;
.select2-results{
background-color: $selectActiveBg;
border-color: $selectActiveBg;
&::-webkit-scrollbar {
background-color: transparent;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-track-piece {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $selectActiveItemBg;
}
.select2-result{
&.select2-highlighted{
background-color: $selectActiveItemBg;
}
.select2-result-label{
font-size: 12px;
color: $selectFontColor !important;
line-height: 20px;
}
}
}
}
.select2-drop-mask {
z-index: $dropdownMaskZ;
}

View File

@@ -2,7 +2,6 @@
* CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
*/
.tile-view {
.remote-videos {
align-items: center;
box-sizing: border-box;
@@ -74,6 +73,10 @@
display: block;
}
.filmstrip__videos.has-scroll {
padding-left: 7px;
}
.remote-videos {
box-sizing: border-box;
@@ -91,7 +94,6 @@
margin-top: auto;
margin-bottom: auto;
justify-content: center;
position: absolute;
.videocontainer {
border: 0;

View File

@@ -117,6 +117,25 @@
height: calc(100% - calc(#{$newToolbarSizeWithPadding} + #{$scrollHeight}));
}
.filmstrip__videos.vertical-view-grid#remoteVideos {
align-items: 'center';
border: 0px;
padding-right: 7px;
&.has-scroll {
padding-right: 0px;
}
.remote-videos > div {
left: 0px; // fixes an issue on FF - the div is aligned to the right by default for some reason
}
.videocontainer {
border: 0px;
margin: 2px;
}
}
.remote-videos {
display: flex;
overscroll-behavior: contain;
@@ -125,10 +144,6 @@
transition: height .3s ease-in;
}
&.vertical-grid-margin > div {
margin-right: $scrollHeight;
}
& > div {
position: absolute;
transition: opacity 1s;

View File

@@ -24,8 +24,7 @@ $flagsImagePath: "../images/";
/* Flags END */
/* Modules BEGIN */
@import 'aui_reset';
@import 'reset';
@import 'atlaskit_overrides';
@import 'base';
@import 'utils';
@@ -42,7 +41,6 @@ $flagsImagePath: "../images/";
@import 'modals/settings/settings';
@import 'modals/screen-share/share-audio';
@import 'modals/screen-share/share-screen-warning';
@import 'modals/speaker_stats/speaker_stats';
@import 'modals/virtual-background/virtual-background';
@import 'modals/local-recording/local-recording';
@import 'videolayout_default';
@@ -57,7 +55,6 @@ $flagsImagePath: "../images/";
@import 'welcome_page_content';
@import 'welcome_page_settings_toolbar';
@import 'toolbars';
@import 'keyboard-shortcuts';
@import 'redirect_page';
@import 'components/form-control';
@import 'components/link';
@@ -65,7 +62,6 @@ $flagsImagePath: "../images/";
@import 'components/input-control';
@import 'components/input-slider';
@import "connection-info";
@import 'aui-components/dropdown';
@import '404';
@import 'policy';
@import 'popover';
@@ -83,7 +79,6 @@ $flagsImagePath: "../images/";
@import 'navigate_section_list';
@import 'third-party-branding/google';
@import 'third-party-branding/microsoft';
@import 'avatar';
@import 'promotional-footer';
@import 'chrome-extension-banner';
@import 'settings-button';

View File

@@ -1,114 +1,3 @@
.dialog {
box-sizing: border-box;
height: auto;
min-height: 131px;
overflow: visible;
visibility: visible;
width: 400px;
h1, h2, h3, h4, h5, h6 {
color: $auiDialogColor;
}
.aui {
&-dialog2 {
&-header, &-footer {
background-color: $auiDialogBg;
border: none;
}
&-header {
border-bottom: 1px solid $auiBorderColor;
border-radius: 5px 5px 0 0;
box-sizing: border-box;
color: #333;
display: table;
font-weight: normal;
height: em(58, 12);
margin-top: -69px;
padding: 0 20px;
width: 100%;
h2 {
font-size: em(20, 12);
font-weight: $dialogTitleFontWeight;
color: $auiDialogColor;
}
&-main {
display: table-cell;
padding-right: 0;
max-width: 400px;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
}
&-footer {
border-top: 1px solid $auiBorderColor;
border-radius: 0 0 5px 5px;
box-sizing: border-box;
height: 51px;
overflow: hidden;
padding: 10px 20px;
width: 100%;
&:empty {
height: 5px;
padding: 0;
}
}
&-content {
background-color: $auiDialogBg;
box-sizing: border-box;
color: $auiDialogColor;
font-size: em(14, 12);
overflow: auto;
max-height: 100%;
padding: 20px;
p,span, h3 {
font-weight: $labelFontWeight;
}
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
&:first-child {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
}
}
&-hide {
display: none;
}
}
.input-control {
background-color: $auiDialogContentBg;
color: $auiDialogColor;
}
.form-control:not(:last-child) {
border-bottom: 1px solid $auiBorderColor;
}
}
@media all and (max-width: 420px) {
.aui-dialog2-small .aui-dialog2-content {
height: 100%;
}
}
.modal-dialog-form {
margin-top: 5px !important;

View File

@@ -1,30 +0,0 @@
.speaker-stats {
list-style: none;
.row{
display: flex;
align-items: center;
.avatar {
width: 32px;
margin-right: 16px;
}
.name-time {
width: calc(100% - 48px);
display: flex;
justify-content: space-between;
align-items: center;
}
.name-time_expressions-on {
width: calc(47% - 48px);
}
.expressions {
width: calc(53% - 29px);
display: flex;
justify-content: space-between;
.expression {
width: 30px;
text-align: center;
}
}
}
}

View File

@@ -1,71 +0,0 @@
.con-status {
border-radius: 6px;
color: #fff;
font-size: 12px;
letter-spacing: 0.16px;
line-height: 16px;
position: absolute;
width: 100%;
&-header {
background-color: rgba(0, 0, 0, 0.7);
align-items: center;
display: flex;
padding: 14px 16px;
}
&-circle {
border-radius: 50%;
display: inline-block;
padding: 4px;
margin-right: 16px;
}
&--good {
background: #31B76A;
}
&--poor {
background: #E12D2D;
}
&--non-optimal {
background: #E39623;
}
&-arrow {
margin-left: auto;
transition: background-color 0.16s ease-out;
&--up {
transform: rotate(180deg);
}
&>svg {
cursor: pointer;
}
&:hover {
background-color: rgba(1,1,1, 0.1);
}
}
&-text {
text-align: center;
}
&-details {
background-color: rgba(0, 0, 0, 0.7);
border-top: 1px solid #5E6D7A;
padding: 16px;
transition: opacity 0.16s ease-out;
&-visible {
opacity: 1;
}
&-hidden {
opacity: 0;
}
}
}

View File

@@ -1,38 +0,0 @@
.device {
&-status {
align-items: center;
color: #fff;
display: flex;
font-size: 14px;
line-height: 20px;
padding: 6px;
text-align: center;
&-error {
align-items: flex-start;
background-color: #F8AE1A;
border-radius: 6px;
color: #040404;
padding: 12px 16px;
text-align: left;
}
span {
margin-left: 16px;
}
}
&-icon {
background-position: center;
background-repeat: no-repeat;
display: inline-block;
height: 16px;
width: 16px;
&--ok {
svg path {
fill: #189b55;
}
}
}
}

View File

@@ -12,11 +12,29 @@
margin: 8px;
}
.lobby-chat-container {
background-color: $chatBackgroundColor;
width: 100%;
height: 314px;
display: flex;
flex-direction: column;
align-items: stretch;
margin-bottom: 16px;
border-radius: 5px;
.lobby-chat-header {
display: none;
}
}
.joining-message {
color: white;
margin: 24px auto;
text-align: center;
}
.open-chat-button {
display: none;
}
}
}
@@ -40,3 +58,149 @@
}
}
}
#notification-participant-list {
background-color: $newToolbarBackgroundColor;
border: 1px solid rgba(255, 255, 255, .4);
border-radius: 8px;
left: 0;
margin: 20px;
max-height: 600px;
overflow: hidden;
overflow-y: auto;
position: fixed;
top: 30px;
z-index: $toolbarZ + 1;
&:empty {
border: none;
}
&.toolbox-visible {
// Same as toolbox subject position
top: 120px;
}
&.avoid-chat {
left: 315px;
}
.title {
background-color: rgba(0, 0, 0, .2);
font-size: 1.2em;
padding: 15px
}
button {
align-self: stretch;
margin-bottom: 8px 0;
padding: 12px;
transition: .2s transform ease;
&:disabled {
opacity: .5;
}
&:hover {
transform: scale(1.05);
&:disabled {
transform: none;
}
}
&.borderLess {
background-color: transparent;
border-width: 0;
}
&.primary {
background-color: rgb(3, 118, 218);
border-width: 0;
}
}
}
.knocking-participants-container {
list-style-type: none;
padding: 0 15px 15px 15px;
}
.knocking-participant {
align-items: center;
display: flex;
flex-direction: row;
margin: 8px 0;
.details {
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-evenly;
margin: 0 30px 0 10px;
}
button {
align-self: unset;
margin: 0 5px;
}
}
@media (max-width: 300px) {
#knocking-participant-list {
margin: 0;
text-align: center;
width: 100%;
.avatar {
display: none;
}
}
.knocking-participant {
flex-direction: column;
.details {
margin: 0;
}
}
}
@media (max-width: 1000px) {
.lobby-screen-content {
.lobby-chat-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 255;
&.hidden {
display: none;
}
.lobby-chat-header {
display: flex;
flex-direction: row;
padding-top: 20px;
padding-left: 16px;
padding-right: 16px;
.title {
flex: 1;
color: #fff;
font-size: 20px;
font-weight: 600;
line-height: 28px;
letter-spacing: -1.2%;
}
}
}
.open-chat-button {
display: block;
}
}
}

View File

@@ -1,5 +1,3 @@
@import 'connection-status';
@import 'device-status';
@import 'lobby';
@import 'premeeting-screens';
@import 'prejoin';

View File

@@ -116,72 +116,3 @@
margin: 8px 0 16px 0;
}
}
.prejoin-dialog-dialin {
text-align: center;
&-header {
align-items: center;
margin: 16px 0 32px 16px;
display: flex;
}
&-icon {
margin-right: 16px;
}
&-num {
background: #3e474f;
border-radius: 4px;
display: inline-block;
font-size: 15px;
line-height: 24px;
margin: 4px;
padding: 8px;
&-container {
min-height: 48px;
margin: 8px 0;
}
}
&-link {
color: #6FB1EA;
cursor: pointer;
display: inline-block;
font-size: 13px;
line-height: 20px;
margin-bottom: 24px;
}
&-spaced-label {
margin-bottom: 16px;
margin-top: 28px;
}
&-btns {
&> div {
margin-bottom: 16px;
}
}
}
.prejoin-dialog-calling {
padding: 16px;
text-align: center;
&-header {
text-align: right;
}
&-label {
font-size: 15px;
margin: 8px 0 16px 0;
}
&-number {
font-size: 19px;
line-height: 28px;
margin: 16px 0;
}
}

View File

@@ -23,30 +23,6 @@
padding: 8px 0;
}
&-dropdown-btn {
align-items: center;
color: #1C2025;
cursor: pointer;
display: flex;
height: 40px;
font-size: 15px;
line-height: 24px;
padding: 0 16px;
&:hover {
background-color: #DAEBFA;
}
}
&-dropdown-icon {
display: inline-block;
margin-right: 16px;
& > svg {
fill: #1C2025;
}
}
&-dropdown-container {
position: relative;
width: 100%;

View File

@@ -82,7 +82,7 @@
flex-direction: column;
flex-shrink: 0;
height: 100%;
margin: 0 110px;
margin: 0 30px;
padding: 24px 0 16px;
position: relative;
width: $prejoinDefaultContentWidth;
@@ -154,33 +154,20 @@
}
}
@media (max-width: 1000px) {
@media (max-width: 720px) {
flex-direction: column-reverse;
.content {
height: auto;
margin: 0 auto;
}
.con-status {
margin: 24px auto;
position: fixed;
top: 0;
width: $prejoinDefaultContentWidth;
}
}
// mobile phone landscape
@media (max-height: 420px) {
flex-direction: row;
div.content {
padding: 16px 16px 0 16px;
}
.con-status {
display: none;
}
}
@media (max-width: 400px) {
@@ -200,11 +187,6 @@
}
}
.con-status {
margin: 0;
width: 100%;
}
.device-status-error {
border-radius: 0;
margin: 0 -16px;

View File

@@ -46,11 +46,6 @@ $reloadProgressBarBg: #0074E0;
/**
* Dialog colors
**/
$auiDialogColor: #eceef1;
$auiDialogBg: #253858;
$auiDialogContentBg: #344563;
$auiBorderColor: #253858;
$dialogTitleFontWeight: 400;
$dialogErrorText: #344563;
/**

BIN
images/GIPHY_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

BIN
images/GIPHY_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/icon-info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>22.0.0</string>
<string>22.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -37,13 +37,8 @@
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
[builder setFeatureFlag:@"resolution" withValue:@(360)];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
// Apple rejected our app because they claim requiring a
// Dropbox account for recording is not acceptable.
#if DEBUG
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
#endif
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>22.0.0</string>
<string>22.1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>22.0.0</string>
<string>22.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>22.0.0</string>
<string>22.1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -71,7 +71,7 @@ platform :ios do
# Inrement the build number by 1
increment_build_number(
build_number: latest_testflight_build_number + 1,
build_number: Time.now.to_i,
xcodeproj: "app/app.xcodeproj"
)

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>5.0.0</string>
<string>5.0.1</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -2,9 +2,9 @@
"addPeople": {
"add": "ادع",
"addContacts": "ادع كل جهات الاتصال لدي",
"contacts": "contacts",
"copyInvite": "انسخ دعوةً للاجتماع",
"copyLink": "انسخ رابط الاجتماع",
"contacts": "جهات اتصال",
"copyInvite": "انسخ دعوةً للمُلتقى",
"copyLink": "انسخ رابط المُلتقى",
"copyStream": "انسخ رابط البث المباشر",
"countryNotSupported": "لا ندعم هذه الوجهة حاليًا.",
"countryReminder": "أتريد الاتصال بمن هو خارج الولايات المتحدة؟ تأكد من الابتداء برمز الدولة أولًا!",
@@ -13,20 +13,20 @@
"failedToAdd": "فشل إضافة مشاركين",
"footerText": "الاتصال لدعوة الغير مُعطَّل.",
"googleEmail": "بريد غوغل",
"inviteMoreHeader": "أنت بمفردك في هذا الاجتماع",
"inviteMoreMailSubject": "ضم {{appName}} للاجتماع",
"inviteMoreHeader": "أنت بمفردك في هذا المُلتقى",
"inviteMoreMailSubject": "ضم {{appName}} للمُلتقى",
"inviteMorePrompt": "ادعُ أشخاصًا آخرين",
"linkCopied": "نُسِخ الرابط",
"noResults": "لم يُعثَر على أي نتيجة بحث متطابقة",
"outlookEmail": "بريد مايكروسوفت",
"phoneNumbers": "أضف ارقام هواتف",
"searching": "يبحث",
"shareInvite": "شارك دعوةً للاجتماع",
"shareLink": "شارك رابط الاجتماع لدعوة الأخرين",
"shareStream": "شارك رابط البث المباشر للاجتماع",
"shareInvite": "شارك دعوةً للمُلتقى",
"shareLink": "شارك رابط المُلتقى لدعوة الأخرين",
"shareStream": "شارك رابط البث المباشر للمُلتقى",
"sipAddresses": "sip عنوان",
"telephone": "رقم الهاتف: {{number}}",
"title": "ادعُ أحدًا لهذا الاجتماع",
"title": "ادعُ أحدًا لهذا المُلتقى",
"yahooEmail": "بريد ياهوو"
},
"audioDevices": {
@@ -40,7 +40,7 @@
"audioOnly": "معدل تبادل البيانات منخفض"
},
"blankPage": {
"meetingEnded": "انتهى الاجتماع."
"meetingEnded": "انتهى المُلتقى."
},
"breakoutRooms": {
"actions": {
@@ -53,7 +53,7 @@
"remove": "إزالة",
"sendToBreakoutRoom": "أرسل المشارك إلى:"
},
"defaultName": "غرفة الاجتماعات الفرعية رقم {{index}}",
"defaultName": "غرفة المُلتقيات الفرعية رقم {{index}}",
"mainRoom": "الغرفة الرئيسية",
"notifications": {
"joined": " الغرفة الجانبيةالانضمام إلى {{index}}",
@@ -62,7 +62,7 @@
}
},
"calendarSync": {
"addMeetingURL": "أضف رابطًا لاجتماع",
"addMeetingURL": "أضف رابطًا لمُلتقى",
"confirmAddLink": "هل تريد إضافة رابط جيستسي لهذا الحدث؟",
"error": {
"appConfiguration": "لم تُضبَط عملية إضافة الرزنامة ضبطًا صحيحًا.",
@@ -70,12 +70,12 @@
"notSignedIn": "حدث خطأ أثناء إجراء عملية الاستيثاق للوصول إلى تفاصيل الأحداث المسجلة في الرزنامة. تحقق رجاءً من إعدادات رزنامتك وجرب التسجيل مرة أخرى."
},
"join": "انضم",
"joinTooltip": "انضم إلى الاجتماع",
"nextMeeting": "الاجتماع التالي",
"joinTooltip": "انضم إلى المُلتقى",
"nextMeeting": "المُلتقى التالي",
"noEvents": "لا يوجد أي أحدات آتية مجدولة.",
"ongoingMeeting": "اجتماع قائم",
"ongoingMeeting": "مُلتقى قائم",
"permissionButton": "افتح الإعدادات",
"permissionMessage": "يطلب إذن الوصول إلى الرزنامة لرؤية مواعيد اجتماعاتك.",
"permissionMessage": "يطلب إذن الوصول إلى الرزنامة لرؤية مواعيد مُلتقياتك.",
"refresh": "حدِّث الرزنامة",
"today": "اليوم"
},
@@ -83,6 +83,7 @@
"enter": "أدخل الغرفة",
"error": "خطأ: لم تُرسَل رسالتك. السبب: {{error}}",
"fieldPlaceHolder": "اكتب رسالتك هنا",
"lobbyChatMessageTo": "رسالة دردشة لوبي إلى {{recipient}}",
"message": "رسالة",
"messageAccessibleTitle": "{{user}} مقولة:",
"messageAccessibleTitleMe": "أنا أقول:",
@@ -93,7 +94,7 @@
"title": "اكتب لقبًا لاعتماده في المحادثة",
"titleWithPolls": "اكتب لقبًا لاعتماده في المحادثة"
},
"noMessagesMessage": "لا يوجد أي رسالة في الاجتماع بعد. ابدأ محادثة هنا.",
"noMessagesMessage": "لا يوجد أي رسالة في المُلتقى بعد. ابدأ محادثة هنا.",
"privateNotice": "أرسل رسالة خاصة إلى {{recipient}}",
"smileysPanel": "واجهة الإيموجي",
"tabs": {
@@ -171,15 +172,15 @@
"yesterday": "البارحة"
},
"deepLinking": {
"appNotInstalled": "تحتاج إلى تطبيق الجوال {{app}} للانضمام إلى إلى هذا الاجتماع على هاتفك.",
"description": "ألم يحدث شيء؟ جربنا عقد اجتماعك على تطبيق الحاسوب {{app}}. جرب مرة أخرى أو اعقد الاجتماع على تطبيق الويب {{app}}.",
"descriptionWithoutWeb": "ألم يحدث شيء؟ جربنا عقد اجتماعك على تطبيق الحاسوب {{app}}.",
"appNotInstalled": "تحتاج إلى تطبيق الجوال {{app}} للانضمام إلى إلى هذا المُلتقى على هاتفك.",
"description": "ألم يحدث شيء؟ جربنا عقد مُلتقىك على تطبيق الحاسوب {{app}}. جرب مرة أخرى أو اعقد المُلتقى على تطبيق الويب {{app}}.",
"descriptionWithoutWeb": "ألم يحدث شيء؟ جربنا عقد مُلتقىك على تطبيق الحاسوب {{app}}.",
"downloadApp": "نزِّل التطبيق",
"ifDoNotHaveApp": "إن لم تملك التطبيق بعد:",
"ifHaveApp": "إن كان لديك التطبيق:",
"joinInApp": "انضم للاجتماع عبر تطبيق الجوال",
"joinInApp": "انضم للمُلتقى عبر تطبيق الجوال",
"launchWebButton": "افتح تطبيق الويب",
"title": "قيد عقد اجتماعك في {{app}}...",
"title": "قيد عقد مُلتقىك في {{app}}...",
"tryAgainButton": "جرب مرة أخرى في تطبيق الحاسوب",
"unsupportedBrowser": "يبدو أنك تستخدم متصفحًا لا ندعمه."
},
@@ -215,6 +216,8 @@
"liveStreaming": "بث حي مباشر"
},
"add": "أضف",
"addMeetingNote": "أضف ملاحظة حول هذا المُلتقى",
"addOptionalNote": "أضف ملاحظة (اختياري)",
"allow": "اسمح",
"alreadySharedVideoMsg": "يشارك أحد الحضور الفيديو حاليًا، ولا يسمح هذا الإجتماع سوى بمشاركة فيديو واحد في آن واحد",
"alreadySharedVideoTitle": "لا يُسمَح سوى بفيديو مشارك واحد على الأكثر في آن واحد.",
@@ -245,12 +248,12 @@
"dismiss": "تجاهل",
"displayNameRequired": "السلام عليكم! ما اسمك؟",
"done": "اُنجِز",
"e2eeDescription": "<p>عملية التعمية من طرف لطرف <strong>قيد التجريب</strong> حاليًا. زر رجاءً <a href='https://jitsi.org/blog/e2ee/' target='_blank'>هذا المنشور</a> لمزيد من التفاصيل.</p><br/><p>ضع في ذهنك أن تشغيل عملية التعمية من طرف لطرف ستعطل عمل بعض الخدمات التي يقدمها المُخدِّم مثل: التسجيل، والبث الحي، الاشتراك عبر الهاتف. أضف إلى ذلك أن الاجتماع سيعمل مع الأشخاص المنضمين من المتصفح التي تدعم قابلية الدخول إلى البث.</p>",
"e2eeDescription": "<p>عملية التعمية من طرف لطرف <strong>قيد التجريب</strong> حاليًا. زر رجاءً <a href='https://jitsi.org/blog/e2ee/' target='_blank'>هذا المنشور</a> لمزيد من التفاصيل.</p><br/><p>ضع في ذهنك أن تشغيل عملية التعمية من طرف لطرف ستعطل عمل بعض الخدمات التي يقدمها المُخدِّم مثل: التسجيل، والبث الحي، الاشتراك عبر الهاتف. أضف إلى ذلك أن المُلتقى سيعمل مع الأشخاص المنضمين من المتصفح التي تدعم قابلية الدخول إلى البث.</p>",
"e2eeDisabledDueToMaxModeDescription": "لا يمكن تمكين التشفير من طرف إلى طرف بسبب العدد الكبير من المشاركين في المؤتمر.",
"e2eeLabel": "المفتاح",
"e2eeWarning": "تحذير: لا يبدو أن جميع المشاركين في هذا الاجتماع لديهم دعم للتشفير من طرف إلى طرف. إذا قمت بتمكينه فلن يتمكنوا من رؤيتك أو سماعك.",
"e2eeWarning": "تحذير: لا يبدو أن جميع المشاركين في هذا المُلتقى لديهم دعم للتشفير من طرف إلى طرف. إذا قمت بتمكينه فلن يتمكنوا من رؤيتك أو سماعك.",
"e2eeWillDisableDueToMaxModeDescription": "تحذير: سيتم تعطيل التشفير من طرف إلى طرف تلقائيًا إذا انضم المزيد من المشاركين إلى المؤتمر.",
"embedMeeting": "تضمين الاجتماع",
"embedMeeting": "تضمين المُلتقى",
"enterDisplayName": "أدخل اسمك هنا، رجاءً",
"error": "خطأ",
"gracefulShutdown": "خدمتنا متوقفة حاليًا لعمليات الصيانة. جرب مرة أخرى في وقت لاحق.",
@@ -265,13 +268,15 @@
"kickParticipantButton": "اطرد",
"kickParticipantDialog": "أمتأكد من طرد هذا المشارك؟",
"kickParticipantTitle": "أتريد طرد هذا المشارك؟",
"kickTitle": "عذرًا! تم طردك {{participantDisplayName}} من الاجتماع",
"kickTitle": "عذرًا! تم طردك {{participantDisplayName}} من المُلتقى",
"linkMeeting": "ربط المُلتقى",
"linkMeetingTitle": "ربط المُلتقى بـ Salesforce",
"liveStreaming": "البث المباشر الحي",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "غير ممكن أثناء التسجيل",
"liveStreamingDisabledTooltip": "بدء بثٍ حيٍّ مُعطَّل",
"localUserControls": "ضوابط المستخدم المحلي",
"lockMessage": "فشل جعل المؤتمر مغلقًا.",
"lockRoom": "أضف الاجتماع $t(lockRoomPasswordUppercase)",
"lockRoom": "أضف المُلتقى $t(lockRoomPasswordUppercase)",
"lockTitle": "فشلت عملية القفل والإغلاق",
"login": "تسجيل الدخول",
"logoutQuestion": "أمتأكد من رغبتك في الخروج وإيقاف المؤتمر؟",
@@ -310,8 +315,8 @@
"muteParticipantsVideoTitle": "تعطيل الكاميرا لهذا المشارك؟",
"noDropboxToken": "لا يوجد رمز مميز صالح لـ Dropbox",
"password": "كلمه السر",
"passwordLabel": "جعل عضو ما هذا الاجتماع مغلقًا. أدخل رجاءً $t(lockRoomPassword) للإنضمام.",
"passwordNotSupported": "ضبط اجتماع $t(lockRoomPassword) غير مدعوم.",
"passwordLabel": "جعل عضو ما هذا المُلتقى مغلقًا. أدخل رجاءً $t(lockRoomPassword) للإنضمام.",
"passwordNotSupported": "ضبط مُلتقى $t(lockRoomPassword) غير مدعوم.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) غير مدعوم",
"passwordRequired": "يُطلَب $t(lockRoomPasswordUppercase)",
"permissionCameraRequiredError": "مطلوب إذن الكاميرا للمشاركة في المؤتمرات بالفيديو. يرجى منحه في الإعدادات",
@@ -320,6 +325,7 @@
"popupError": "يمنع متصفحك النوافذ المنبثقة من هذا الموقع. فعِّل رجاءً النوافذ المنبثقة في المتصفح من إعدادات الحماية وحاول مرة أخرى.",
"popupErrorTitle": "النوافذ المنبثقة محجوبة.",
"readMore": "أكثر",
"recentlyUsedObjects": "الأشياء المستخدمة مؤخرًا",
"recording": "قيد التسجيل",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "ليس بالإمكان ما دمت قيد البث المباشر",
"recordingDisabledTooltip": "عملية التسجيل معطلة.",
@@ -342,6 +348,12 @@
"screenSharingFailed": "عجبًا! حصل خطأ ما، فلن نتمكن من مشاركة الشاشة للأسف!",
"screenSharingFailedTitle": "فشلت عملية مشاركة الشاشة!",
"screenSharingPermissionDeniedError": "عجبًا! حصل خطأ ما متعلق بأذونات إضافة مشاركة الشاشة. أعد التحميل وجرِّب مرة أخرى، رجاءً.",
"searchInSalesforce": "ابحث في Salesforce",
"searchResults": "نتائج البحث ({{count}})",
"searchResultsDetailsError": "حدث خطأ ما أثناء استرداد بيانات المالك.",
"searchResultsError": "حدث خطأ ما أثناء استرداد البيانات.",
"searchResultsNotFound": "لم يتم العثور على نتائج عن البحث.",
"searchResultsTryAgain": "حاول استخدام كلمات رئيسية بديلة.",
"sendPrivateMessage": "وصلتك رسالة خاصة للتو، أتنوي الرد عليها ردًا خاصًا أم تريد إرسال رسالتك على المجموعة؟",
"sendPrivateMessageCancel": "أرسل إلى المجموعة",
"sendPrivateMessageOk": "أرسل ردًا خاصًا",
@@ -364,8 +376,9 @@
"shareVideoTitle": "شارك فيديو",
"shareYourScreen": "شارك شاشتك",
"shareYourScreenDisabled": "مشاركة الشاشة مُعطَّلة",
"sharedVideoDialogError": "",
"sharedVideoDialogError": "خطأ: رابط غير صالح",
"sharedVideoLinkPlaceholder": "رابط اليوتيوب او رابط الفيديو المباشر",
"start": "إبدأ",
"startLiveStreaming": "ابدأ بثًا حيًا",
"startRecording": "ابدأ التسجيل",
"startRemoteControlErrorMessage": "حصل خطأٌ أثناء محاولة بدء جلسة تحكم بعيد!",
@@ -379,7 +392,7 @@
"tokenAuthFailed": "عذرًا، لا يسمح لك بالإنضام إلى هذه المكالمة.",
"tokenAuthFailedTitle": "فشلت عملية الاستيثاق",
"transcribing": "يذاع",
"unlockRoom": "إزل الاجتماع $t(lockRoomPassword)",
"unlockRoom": "إزل المُلتقى $t(lockRoomPassword)",
"user": "مستخدم",
"userIdentifier": "معرف المستخدم",
"userPassword": "كلمة مرور المستخدم",
@@ -396,7 +409,7 @@
"labelToolTip": "فعَّل كل المشاركين في هذا الإجتماع عملية التعمية طرف لطرف"
},
"embedMeeting": {
"title": "ضمِّن هذا الاجتماع"
"title": "ضمِّن هذا المُلتقى"
},
"feedback": {
"average": "المتوسط",
@@ -408,6 +421,10 @@
"veryBad": "سيئة للغاية",
"veryGood": "ممتازة"
},
"giphy": {
"noResults": "لم يتم العثور على نتائج :(",
"search": "ابحث في GIPHY"
},
"helpView": {
"header": "مركز المساعدة"
},
@@ -415,7 +432,7 @@
"answer": "أجب",
"audioCallTitle": "مكالمة صوتية واردة",
"decline": "ارفض",
"productLabel": "إجتماع من جيتسي",
"productLabel": "مُلتقى جيتسي",
"videoCallTitle": "مكالمة مرئية واردة"
},
"info": {
@@ -425,25 +442,25 @@
"conferenceURL": "رابط:",
"copyNumber": "إنسخ الرقم",
"country": "البلد",
"dialANumber": "إن أردت الإنضمام إلى الإجتماع، اتصل بأحد الأرقام التالية ثم أدخل رمز المرور",
"dialANumber": "إن أردت الإنضمام إلى المُلتقى، اتصل بأحد الأرقام التالية ثم أدخل رمز المرور",
"dialInConferenceID": "رمز المرور (PIN):",
"dialInNotSupported": "عذرًا، الاتصال غير مدعوم حاليًا.",
"dialInNumber": "اتصل:",
"dialInSummaryError": "خطأ في تحصيل معلومات الاتصال. جرب مرة أخرى لاحقًا.",
"dialInTollFree": "رقم هاتف مجاني",
"genericError": "عفوًا، شيء ما لم يسر على ما يرام.",
"inviteLiveStream": "لمشاهدة البث الحي لهذا الإجتماع، اضط على هذا الرابط: {{url}}",
"inviteLiveStream": "لمشاهدة البث الحي لهذا المُلتقى، اضط على هذا الرابط: {{url}}",
"invitePhone": "للإنضمام من الهاتف، استعمل: {{number}},,{{conferenceID}}#\n",
"invitePhoneAlternatives": "أتبحث عن رقم اتصال مختلف؟\nأنظر أرقام الوصول إلى هذا الإجتماع: {{url}}\n\n\nإن كنت أيضًا تتصل عبر غرفة اتصال (room phone)، انضم دون الاتصال بالصوت: {{silentUrl}}",
"inviteSipEndpoint": "للانضمام باستخدام عنوان SIP ، أدخل هذا: {{sipUri}}",
"inviteTextiOSInviteUrl": "انقر فوق الرابط التالي للانضمام: {{inviteUrl}}.",
"inviteTextiOSJoinSilent": "إذا كنت تقوم بالاتصال من خلال هاتف الغرفة ، فاستخدم هذا الارتباط للانضمام دون الاتصال بالصوت: {{silentUrl}}.",
"inviteTextiOSPersonal": "{{name}} يدعوك إلى اجتماع.",
"inviteTextiOSPersonal": "{{name}} يدعوك إلى مُلتقى.",
"inviteTextiOSPhone": "للانضمام عبر الهاتف ، استخدم هذا الرقم: {{number}},,{{conferenceID}}#. إذا كنت تبحث عن رقم مختلف ، فهذه هي القائمة الكاملة: {{didUrl}}.",
"inviteURLFirstPartGeneral": "دُعيِت للإنضمام إلى اجتماع",
"inviteURLFirstPartPersonal": "دعاك {{name}} لاجتماع.\n",
"inviteURLSecondPart": "\nانضم للاجتماع:\n{{url}}\n",
"label": "تفاصيل الاجتماع",
"inviteURLFirstPartGeneral": "دُعيِت للإنضمام إلى مُلتقى",
"inviteURLFirstPartPersonal": "دعاك {{name}} لمُلتقى.\n",
"inviteURLSecondPart": "\nانضم للمُلتقى:\n{{url}}\n",
"label": "تفاصيل المُلتقى",
"liveStreamURL": "بث حي:",
"moreNumbers": "أرقام إضافية",
"noNumbers": "لا يوجد أرقام اتصال.",
@@ -453,7 +470,7 @@
"password": "$t(lockRoomPasswordUppercase):",
"sip": "SIP عنوان",
"title": "شارك",
"tooltip": "شارك رابط وتفاصيل الاتصال لهذا الاجتماع"
"tooltip": "شارك رابط وتفاصيل الاتصال لهذا المُلتقى"
},
"inlineDialogFailure": {
"msg": "تعثرث معنا بعض الأمور :(",
@@ -474,6 +491,7 @@
"focusLocal": "ركز على الفيديو الخاص بك",
"focusRemote": "ركز على فيديو مشارك آخر",
"fullScreen": "استعمل/اخرج من وضع الشاشة الكاملة",
"giphyMenu": "تبديل قائمة GIPHY",
"keyboardShortcuts": "اختصارات لوحة المفاتيح",
"localRecording": "اظهِر أو اخفِ التحكم بالتسجيل المحلي",
"mute": "اكتم أو ألغ كتم المجهار (المايكروفون) الخاص بك",
@@ -498,7 +516,7 @@
"errorAPI": "حصل خطأ أثناء الوصول إلى البث الخاص بك على يوتيوب. حاول تسجيل الدخول مرَّة أخرى.",
"errorLiveStreamNotEnabled": "البث الحي غير مفعَّل على على {{email}}. فعَّل البث الحي رجاءً، أو سجِّل الدخل إلى حسابٍ مُفعَّل فيه البث الحي",
"expandedOff": "أُوقِف البث الحي",
"expandedOn": "يجري بث الاجتماع على يوتيوب",
"expandedOn": "يجري بث المُلتقى على يوتيوب",
"expandedPending": "تبدأ عملية البث الحي...",
"failedToStart": "فشلت عملية بدء البث الحي",
"getStreamKeyManually": "لم نتمكن من الوصول إلى أي بث حي. جرب جلب مفتاح بث حي خاص بك من يوتيوب.",
@@ -528,33 +546,36 @@
"admitAll": "سمح للجميع بالدخول",
"allow": "اسمح",
"backToKnockModeButton": "لا يوجد كلمة مرور، اطلب الإذن بالدخول بدلًا من ذلك.",
"chat": "دردشة",
"dialogTitle": "ونضع غرفة الانتظار",
"disableDialogContent": "وضع غرفة الانتظار مفعَّل. تسمح هذه الميزة بعدم السماح للغرباء بالانضمام إلى الاجتماع، فهل تريد حقًا تعطيلها؟",
"disableDialogContent": "وضع غرفة الانتظار مفعَّل. تسمح هذه الميزة بعدم السماح للغرباء بالانضمام إلى المُلتقى، فهل تريد حقًا تعطيلها؟",
"disableDialogSubmit": "عطِّل",
"emailField": "أدخل بريدك الإلكتروني",
"enableDialogPasswordField": "حدِّد كلمة مرور (اختياري)",
"enableDialogSubmit": "فعِّل",
"enableDialogText": "يحمي وضع الانتظار غرفة الاجتماع عبر منح رئيس الجلسة إمكانية الموافقة على انضمام المشاركين.",
"enterPasswordButton": "أدخل كلمة المرور لهذا الاجتماع",
"enterPasswordTitle": "أدخل كلمة المرور للدخول للاجتماع",
"errorMissingPassword": "الرجاء إدخال كلمة مرور الاجتماع",
"enableDialogText": "يحمي وضع الانتظار غرفة المُلتقى عبر منح رئيس الجلسة إمكانية الموافقة على انضمام المشاركين.",
"enterPasswordButton": "أدخل كلمة المرور لهذا المُلتقى",
"enterPasswordTitle": "أدخل كلمة المرور للدخول للمُلتقى",
"errorMissingPassword": "الرجاء إدخال كلمة مرور المُلتقى",
"invalidPassword": "كلمة مرور غير صحيحة",
"joinRejectedMessage": "رفض رئيس الجلسة منحك الإذن بالدخول إلى الاجتماع",
"joinTitle": "انضم للاجتماع",
"joinRejectedMessage": "رفض رئيس الجلسة منحك الإذن بالدخول إلى المُلتقى",
"joinTitle": "انضم للمُلتقى",
"joinWithPasswordMessage": "الرجاء الانتظار أثناء محاولة الدخول دون كلمة مرور...",
"joiningMessage": "ستتمكن من الانضمام للاجتماع بعد الموافقة على طلبك",
"joiningMessage": "ستتمكن من الانضمام للمُلتقى بعد الموافقة على طلبك",
"joiningTitle": "يجري طلب إذنٍ للدخول...",
"joiningWithPasswordTitle": "الدخول مع كلمة مرور...",
"knockButton": "اطلب إذن الدخول",
"knockTitle": "يريد أحدٌ الدخول إلى الاجتماع",
"knockTitle": "يريد أحدٌ الدخول إلى المُلتقى",
"knockingParticipantList": "تنبيه قائمة المشاركين",
"lobbyChatStartedNotification": "بدأ {{moderator}} دردشة في الردهة مع {{attendee}}",
"lobbyChatStartedTitle": "بدأ {{moderator}} محادثة في الردهة معك.",
"nameField": "أدخل اسمك",
"notificationLobbyAccessDenied": "رفض {{originParticipantName}} مشاركة {{targetParticipantName}} للاجتماع",
"notificationLobbyAccessGranted": "سمح {{originParticipantName}} بمشاركة {{targetParticipantName}} للاجتماع",
"notificationLobbyAccessDenied": "رفض {{originParticipantName}} مشاركة {{targetParticipantName}} للمُلتقى",
"notificationLobbyAccessGranted": "سمح {{originParticipantName}} بمشاركة {{targetParticipantName}} للمُلتقى",
"notificationLobbyDisabled": "عطَّل {{originParticipantName}} وضع غرفة الانتظار",
"notificationLobbyEnabled": "فعَّل {{originParticipantName}} وضع غرفة الانتظار",
"notificationTitle": "غرفة الانتظار",
"passwordField": "أدخل كلمة الدخول إلى الاجتماع",
"passwordField": "أدخل كلمة الدخول إلى المُلتقى",
"passwordJoinButton": "انضم",
"reject": "رفض",
"rejectAll": "رفض الكل",
@@ -593,7 +614,7 @@
"lockRoomPasswordUppercase": "كلمة المرور",
"lonelyMeetingExperience": {
"button": "ادعُ آخرين",
"youAreAlone": "أنت بمفردك في الاجتماع"
"youAreAlone": "أنت بمفردك في المُلتقى"
},
"me": "أنا",
"notify": {
@@ -603,9 +624,9 @@
"audioUnmuteBlockedDescription": "تم حظر عملية إلغاء كتم صوت الميكروفون مؤقتًا بسبب قيود النظام.",
"audioUnmuteBlockedTitle": "تم حظر إعادة صوت الميكروفون!",
"chatMessages": "رسائل الدردشة",
"connectedOneMember": "انضم {{name}} للاجتماع",
"connectedThreePlusMembers": "انضم {{name}} وعدد {{count}} غيره إلى الاجتماع",
"connectedTwoMembers": "انضم {{first}} و {{second}} إلى الاجتماع",
"connectedOneMember": "انضم {{name}} للمُلتقى",
"connectedThreePlusMembers": "انضم {{name}} وعدد {{count}} غيره إلى المُلتقى",
"connectedTwoMembers": "انضم {{first}} و {{second}} إلى المُلتقى",
"disconnected": "انقطع الاتصال",
"displayNotifications": "عرض الإخطارات لـ",
"focus": "التركيز على المؤتمر",
@@ -616,9 +637,15 @@
"invitedThreePlusMembers": "دُعِي {{name}} وعدد {{count}} آخرين",
"invitedTwoMembers": "دُعِي {{first}} و {{second}}",
"kickParticipant": "طرد {{kicked}} المشارك {{kicker}}",
"leftOneMember": "{{name}} غادر الاجتماع",
"leftThreePlusMembers": "غادر {{name}} والعديد من الأشخاص الآخرين الاجتماع",
"leftTwoMembers": "غادر {{first}} و {{second}} الاجتماع",
"leftOneMember": "{{name}} غادر المُلتقى",
"leftThreePlusMembers": "غادر {{name}} والعديد من الأشخاص الآخرين المُلتقى",
"leftTwoMembers": "غادر {{first}} و {{second}} المُلتقى",
"linkToSalesforce": "ارتباط إلى Salesforce",
"linkToSalesforceDescription": "يمكنك ربط ملخص الاجتماع بكائن Salesforce.",
"linkToSalesforceError": "فشل ربط المُلتقى بـ Salesforce",
"linkToSalesforceKey": "ربط هذا المُلتقى",
"linkToSalesforceProgress": "جارٍ ربط الاجتماع بـ Salesforce ...",
"linkToSalesforceSuccess": "تم ربط الاجتماع بـ Salesforce",
"me": "أنا",
"moderationInEffectCSDescription": "يرجى رفع اليد إذا كنت تريد مشاركة شاشتك.",
"moderationInEffectCSTitle": "تم حظر مشاركة الشاشة من قبل المشرف",
@@ -633,7 +660,7 @@
"moderationToggleDescription": "من {{participantDisplayName}}",
"moderator": "مُنحَت صلاحية رئيس الجلسة!",
"muted": "بدأ المحادثة مكتوب الصوت.",
"mutedRemotelyDescription": "يمكنك إلغاء الكتم متى كنت جاهزًا للتحدث. اكتم الصوت بعد الانتهاء من التحدث لخفض الضجيج أثناء الاجتماع",
"mutedRemotelyDescription": "يمكنك إلغاء الكتم متى كنت جاهزًا للتحدث. اكتم الصوت بعد الانتهاء من التحدث لخفض الضجيج أثناء المُلتقى",
"mutedRemotelyTitle": "كتم {{participantDisplayName}} الصوت لديك!",
"mutedTitle": "مكتوم!",
"newDeviceAction": "استعمل",
@@ -642,8 +669,8 @@
"oldElectronClientDescription1": "يبدو أنَّك تستعمل إصدارًا قديمًا من جيتسي يحوي ثغرة أمنية. تأكد رجاءً من أنَّك حدَّثته إلى ",
"oldElectronClientDescription2": "أحدث إصدار",
"oldElectronClientDescription3": " الآن!",
"participantWantsToJoin": "يريد الانضمام إلى الاجتماع",
"participantsWantToJoin": "يريد الانضمام إلى الاجتماع",
"participantWantsToJoin": "يريد الانضمام إلى المُلتقى",
"participantsWantToJoin": "يريد الانضمام إلى المُلتقى",
"passwordRemovedRemotely": "أزال أحد المشاركين {{participantDisplayName}}",
"passwordSetRemotely": "ضبط أحد المشاركين $t(lockRoomPasswordUppercase)",
"raiseHandAction": "رفع اليد",
@@ -655,9 +682,9 @@
"screenShareNoAudioTitle": "تعذرت مشاركة صوت النظام!",
"selfViewTitle": "يمكنك دائمًا إلغاء إخفاء العرض الذاتي من الإعدادات",
"somebody": "شخص ما",
"startSilentDescription": "أنضم مجدَّدًا للاجتماع لتفعيل الصوت",
"startSilentDescription": "أنضم مجدَّدًا للمُلتقى لتفعيل الصوت",
"startSilentTitle": "انضممت دون مخرج للصوت!",
"suboptimalBrowserWarning": "نخشى أن لا تكون تجربة الاجتماع جيدة. نعمل على تحسين الكثير من الأمور، لكن ننصحك حتى ذلك الحين باستعمال باستعمال أحد <a href='static/recommendedBrowsers.html' target='_blank'>المتصفحات المدعومة</a>.",
"suboptimalBrowserWarning": "نخشى أن لا تكون تجربة المُلتقى جيدة. نعمل على تحسين الكثير من الأمور، لكن ننصحك حتى ذلك الحين باستعمال باستعمال أحد <a href='static/recommendedBrowsers.html' target='_blank'>المتصفحات المدعومة</a>.",
"suboptimalExperienceTitle": "تحذير من المتصفح",
"unmute": "إلغاء الكتم",
"videoMutedRemotelyDescription": "You can always turn it on again.",
@@ -690,7 +717,7 @@
"header": "مشاركون",
"headings": {
"lobby": "الردهة ({{count}})",
"participantsList": "المشاركون في الاجتماع({{count}})",
"participantsList": "المشاركون في المُلتقى({{count}})",
"waitingLobby": "منتظرون في الردهة ({{count}})"
},
"search": "بحث"
@@ -716,11 +743,11 @@
},
"notification": {
"description": "افتح علامة تبويب الاقتراع للتصويت",
"title": "تمت إضافة اقتراع جديد إلى هذا الاجتماع"
"title": "تمت إضافة اقتراع جديد إلى هذا المُلتقى"
},
"results": {
"changeVote": "تغيير التصويت",
"empty": "لا توجد استطلاعات للرأي في الاجتماع حتى الآن. ابدأ الاستطلاع هنا!",
"empty": "لا توجد استطلاعات للرأي في المُلتقى حتى الآن. ابدأ الاستطلاع هنا!",
"hideDetailedResults": "أخفِ التفاصيل",
"showDetailedResults": "اظهر التفاصيل",
"vote": "تصويت"
@@ -756,8 +783,8 @@
"videoLowQuality": "نتوقع أن تكون جودة الفيديو منخفضة من ناحية معدِّل الإطارات والدقة",
"videoTearing": "نتوقع أن تكون دقة الفيديو تعيسة"
},
"copyAndShare": "انسخ رابط الاجتماع وشاركه",
"dialInMeeting": "يجري الاتصال بالاجتماع",
"copyAndShare": "انسخ رابط المُلتقى وشاركه",
"dialInMeeting": "يجري الاتصال بالمُلتقى",
"dialInPin": "يجري الاتصال، أدخل الرمز PIN:",
"dialing": "يجري الاتصال",
"doNotShow": "لا تظهر لي هذه مرة أخرى",
@@ -765,22 +792,22 @@
"errorDialOutDisconnected": "قطع الاتصال لفشل العملية.",
"errorDialOutFailed": "فشلت عملية الاتصال، للأسف.",
"errorDialOutStatus": "خطأ في معرفة حالة الاتصال",
"errorMissingName": "أدخل اسمك للدخول للاجتماع",
"errorMissingName": "أدخل اسمك للدخول للمُلتقى",
"errorNoPermissions": "تحتاج إلى تمكين الوصول إلى الميكروفون والكاميرا",
"errorStatusCode": "فشل الاتصال برمز خطأ: {{status}}",
"errorValidation": "فشل التحقق من الرقم",
"iWantToDialIn": "أريد الاتصال",
"initiated": "بدأ الاتصال",
"joinAudioByPhone": "انضم مع صوت من الجوال",
"joinMeeting": "انضم للاجتماع",
"joinMeeting": "انضم للمُلتقى",
"joinWithoutAudio": "انضم دون صوت",
"keyboardShortcuts": "تفعيل اختصارات لوحة المفاتيح",
"linkCopied": "نُسِخ الرابط",
"lookGood": "يبدو أن المجهار لديك تعيس",
"or": "أو",
"premeeting": "ما قبل الاجتماع",
"premeeting": "ما قبل المُلتقى",
"screenSharingError": "خطأ في مشاركة الشاشة:",
"showScreen": "تفعيل واجهة ما قبل الاجتماع",
"showScreen": "تفعيل واجهة ما قبل المُلتقى",
"startWithPhone": "البدء مع جهاز الصوت من الجوال",
"videoOnlyError": "خطأ في الفيديو:",
"videoTrackError": "لم نتمكن من إنشاء ملف الفيديو",
@@ -812,6 +839,18 @@
},
"raisedHand": "يرد التحدث",
"raisedHandsLabel": "عدد الأيدي المرفوعة",
"record": {
"already": {
"linked": "السجل مرتبط بالفعل بهذه الجلسة."
},
"type": {
"account": "الحساب",
"contact": "جهات الاتصال",
"lead": "البدء",
"opportunity": "الفرصة",
"owner": "المالك"
}
},
"recording": {
"authDropboxText": "رفع إلى Dropbox",
"availableSpace": "المساحة المتاحة: {{spaceLeft}} ميغابايت (ما يقارب {{duration}} دقيقة تسجيل).",
@@ -822,10 +861,14 @@
"error": "فشل التسجيل. حاول مرة أخرى.",
"errorFetchingLink": "خطأ في جلب رابط التسجيل.",
"expandedOff": "أوقٍف التسجيل",
"expandedOn": "يُسجَّل الاجتماع الآن",
"expandedOn": "يُسجَّل المُلتقى الآن",
"expandedPending": "بدء التسجيل...",
"failedToStart": "فشل بدء التسجيل",
"fileSharingdescription": "شارك التسجيل مع المشاركين للاجتماع",
"fileSharingdescription": "شارك التسجيل مع المشاركين للمُلتقى",
"highlightMoment": "لحظة تسليط الضوء",
"highlightMomentDisabled": "يمكنك تمييز اللحظات التي يبدأ فيها التسجيل",
"highlightMomentSuccess": "تم تمييز اللحظة",
"highlightMomentSucessDescription": "ستتم إضافة اللحظة المميزة إلى ملخص المُلتقى.",
"inProgress": "التسجيل أو البث المباشر قيد التقدم",
"limitNotificationDescriptionNative": "نظرًا للضغط الكبير، سيقيَّد التسجيل إلى {{limit}} د، ولكن إن أردت التسجيل لمدة مفتوحة، جرِّب <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "نظرًا للضغط الكبير، سيقيَّد التسجيل إلى {{limit}} د، ولكن إن أردت التسجيل لمدة مفتوحة، جرِّب <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -836,10 +879,11 @@
"offBy": "أوقَف {{name}} التسجيل",
"on": "تسجيل",
"onBy": "بدأ {{name}} التسجيل",
"pending": "التحضير لتسجيل الاجتماع...",
"pending": "التحضير لتسجيل المُلتقى...",
"rec": "تسجيل",
"serviceDescription": "ستحفظ خدمة التسجيل الفيديو المستجل",
"serviceDescriptionCloud": "تسجيل سحابي",
"serviceDescriptionCloudInfo": "يتم مسح المُلتقيات المسجلة تلقائيًا بعد 24 ساعة من وقت التسجيل.",
"serviceName": "خدمة التسجيل",
"sessionAlreadyActive": "هذه الجلسة قيد التسجيل أو البث المباشر.",
"signIn": "دخول",
@@ -852,10 +896,10 @@
"pullToRefresh": "اسحب للتحديث"
},
"security": {
"about": "يمكنك إضافة $t(lockRoomPassword) إلى الاجتماع. سيتوجب على المشاركين إدخال $t(lockRoomPassword) قبل السماح لهم بالانضمام إلى الاجتماع.",
"aboutReadOnly": "المشاركون بصفة رئيس الجلسة يمكنهم إضافة $t(lockRoomPassword) إلى الاجتماع. سيتوجب على المشاركين إدخال $t(lockRoomPassword) قبل السماح لهم بالانضمام إلى الاجتماع.",
"about": "يمكنك إضافة $t(lockRoomPassword) إلى المُلتقى. سيتوجب على المشاركين إدخال $t(lockRoomPassword) قبل السماح لهم بالانضمام إلى المُلتقى.",
"aboutReadOnly": "المشاركون بصفة رئيس الجلسة يمكنهم إضافة $t(lockRoomPassword) إلى المُلتقى. سيتوجب على المشاركين إدخال $t(lockRoomPassword) قبل السماح لهم بالانضمام إلى المُلتقى.",
"header": "خيارات الأمان",
"insecureRoomNameWarning": "اسم الغرفة غير آمن، فقد ينضم عبره مشاركون غرباء إلى الاجتماع. ننصحك بتأمين الاجتماع عبر وسائل الحماية التي يوفرها زر الحماية."
"insecureRoomNameWarning": "اسم الغرفة غير آمن، فقد ينضم عبره مشاركون غرباء إلى المُلتقى. ننصحك بتأمين المُلتقى عبر وسائل الحماية التي يوفرها زر الحماية."
},
"settings": {
"calendar": {
@@ -882,7 +926,7 @@
"participantJoined": "انضم مشارك",
"participantLeft": "غادر المشارك",
"playSounds": "تشغيل الصوت عند:",
"reactions": "ردود فعل الاجتماع",
"reactions": "ردود فعل المُلتقى",
"sameAsSystem": "مثل النظام ({{label}})",
"selectAudioOutput": "خرج الصوت",
"selectCamera": "الكاميرا",
@@ -919,13 +963,14 @@
"version": "الإصدار"
},
"share": {
"dialInfoText": "\n\n=====\n\nأتريد الاتصال فقط من هاتفك؟\n\n{{defaultDialInNumber}}اضغط على هذا الرابط لرؤية أرقام الهواتف الخاصة بهذا الاجتماع\n{{dialInfoPageUrl}}",
"mainText": "اضغط على الرابط التالي للانضمام إلى الاجتماع:\n{{roomUrl}}"
"dialInfoText": "\n\n=====\n\nأتريد الاتصال فقط من هاتفك؟\n\n{{defaultDialInNumber}}اضغط على هذا الرابط لرؤية أرقام الهواتف الخاصة بهذا المُلتقى\n{{dialInfoPageUrl}}",
"mainText": "اضغط على الرابط التالي للانضمام إلى المُلتقى:\n{{roomUrl}}"
},
"speaker": "المتحدث",
"speakerStats": {
"angry": "غاضب",
"disgusted": "مشمئز",
"displayEmotions": "إظهار المشاعر",
"fearful": "خائف",
"happy": "سعيد",
"hours": "{{count}}س",
@@ -940,7 +985,7 @@
"surprised": "مندهش"
},
"startupoverlay": {
"genericTitle": "يحتاج الاجتماع إلى استخدام الميكروفون والكاميرا.",
"genericTitle": "يحتاج المُلتقى إلى استخدام الميكروفون والكاميرا.",
"policyText": " ",
"title": "يريد {{app}} استعمال المجهار والكاميرا خاصَّتك."
},
@@ -967,10 +1012,11 @@
"collapse": "قلّص",
"document": "اظهِر/اخفِ الملف المشارك",
"download": "نزِّل التطبيق",
"embedMeeting": "ضمِّن الاجتماع",
"embedMeeting": "ضمِّن المُلتقى",
"expand": "وسّع",
"feedback": "أبدِ رأيك",
"fullScreen": "استعمل/اخرج من وضع الشاشة الكاملة",
"giphy": "تبديل قائمة GIPHY",
"grantModerator": "امنح صلاحيات رئيس الجلسة",
"hangup": "أغلق المكالمة",
"help": "مساعدة",
@@ -978,9 +1024,10 @@
"kick": "اطرد مشاركًا",
"laugh": "يضحك",
"like": "رفع الإبهام متمنيا النجاح",
"linkToSalesforce": "ارتباط إلى Salesforce",
"lobbyButton": "فعِّل/عطِّل وضع غرفة الانتظار",
"localRecording": "اظهِر/اخفِ التحكم بالتسجيل المحلي",
"lockRoom": "اظهِر/اخفِ كلمة مرور الاجتماع",
"lockRoom": "اظهِر/اخفِ كلمة مرور المُلتقى",
"moreActions": "اظهِر/اخفِ قائمة المزيد من الإجراءات",
"moreActionsMenu": "قائمة المزيد من الإجراءات",
"moreOptions": "اظهر المزيد من الخيارت",
@@ -1028,17 +1075,18 @@
"clap": "تصفيق",
"closeChat": "أغلق الدردشة",
"closeReactionsMenu": "إغلاق قائمة ردود الفعل",
"disableReactionSounds": "يمكنك تعطيل أصوات ردود الفعل لهذا الاجتماع",
"disableReactionSounds": "يمكنك تعطيل أصوات ردود الفعل لهذا المُلتقى",
"documentClose": "أغلق الملف المشارك",
"documentOpen": "افتح الملف المشارك",
"download": "نزِّل التطبيق",
"e2ee": "تعمية طرف-لطرف",
"embedMeeting": "ضمِّن الاجتماع",
"embedMeeting": "ضمِّن المُلتقى",
"enterFullScreen": "تعمية طرف-لطرف",
"enterTileView": "عرض بشاشة كاملة",
"exitFullScreen": "أدخل عنوان العرض",
"exitTileView": "اخرج من وضع الشاشة الكاملة",
"feedback": "أبدِ رأيك",
"giphy": "تبديل قائمة GIPHY",
"hangup": "غادر",
"help": "مساعدة",
"invite": "ادعُ أحدًا",
@@ -1046,6 +1094,7 @@
"laugh": "يضحك",
"leaveBreakoutRoom": "اترك إلى غرفة الجانبية",
"like": "رفع الإبهام متمنيا النجاح",
"linkToSalesforce": "ارتباط إلى Salesforce",
"lobbyButtonDisable": "عطِّل وضع غرفة الانتظار",
"lobbyButtonEnable": "فعِّل وضع غرفة الانتظار",
"login": "ادخل",
@@ -1103,9 +1152,9 @@
"error": "فشلت عملية الإذاعة. حاول مرة أخرى، رجاءً.",
"expandedLabel": "عملية الإذاعة تعمل",
"failedToStart": "فشلت عملية بدء الإذاعة",
"labelToolTip": "يجري إذاعة الاجتماع",
"labelToolTip": "يجري إذاعة المُلتقى",
"off": "أوقفت الإذاعة",
"pending": "التحضير لإذاعة الاجتماع...",
"pending": "التحضير لإذاعة المُلتقى...",
"start": "بدء إظهار الترجمة",
"stop": "إيقاف عرض الترجمة",
"tr": "يذاع"
@@ -1202,7 +1251,7 @@
"join": "انقر للمشاركة",
"roomname": "أدخل اسم الغرفة"
},
"addMeetingName": "أضف اسم الاجتماع",
"addMeetingName": "أضف اسم المُلتقى",
"appDescription": "انطلق وأجر محادثة مرئية مع كامل الفريق. يمكنك أيضًا أن تدعو من تريد. {{app}} مُعمَّى بالكامل، ومفتوح المصدر بالكامل ويعد حلًا لإجراء المؤتمرات المرئية يمكنك استعماله متى تريد، مجانًا، حتى دون حساب.",
"audioVideoSwitch": {
"audio": "صوت",
@@ -1210,15 +1259,15 @@
},
"calendar": "رزنامة",
"connectCalendarButton": "أوصل رزنامتك",
"connectCalendarText": "أوصل رزنامتك لعرض كل اجتماعاتك في {{app}}. أضف إلى ذلك أنَّه يمكنك إضافة اجتماعات {{provider}} إلى رزنامتك وبدئها بضغطة واحدة.",
"enterRoomTitle": "بدء اجتماع جديد",
"connectCalendarText": "أوصل رزنامتك لعرض كل مُلتقياتك في {{app}}. أضف إلى ذلك أنَّه يمكنك إضافة مُلتقيات {{provider}} إلى رزنامتك وبدئها بضغطة واحدة.",
"enterRoomTitle": "بدء مُلتقى جديد",
"getHelp": "أريد مساعدة",
"go": "ابدأ",
"goSmall": "ابدأ",
"headerSubtitle": "آمــن وبنـوعيـة فـائقـة الجـودة",
"headerTitle": "حِـــوار جيتسي",
"headerTitle": "مُلتقى جيتسي",
"info": "معلومات",
"jitsiOnMobile": "جيتسي على الهاتف المحمول - حمّل تطبيقاتنا وابدأ اجتماعًا من أي مكان",
"jitsiOnMobile": "جيتسي على الهاتف المحمول - حمّل تطبيقاتنا وابدأ مُلتقىًا من أي مكان",
"join": "أنشئ / انضم",
"logo": {
"calendar": "شعار التقويم",
@@ -1231,18 +1280,18 @@
"mobileDownLoadLinkAndroid": "قم بتنزيل تطبيق الهاتف لنظام أندرويد",
"mobileDownLoadLinkFDroid": "قم بتنزيل تطبيق الجوال لـ F-Droid",
"mobileDownLoadLinkIos": "قم بتنزيل تطبيق الهاتف لنظام iOS",
"moderatedMessage": "أو a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">احجز رابط لاجتماع</a> إن كنت رئيس الجلسة الوحيد فقط.",
"moderatedMessage": "أو a href=\"{{url}}\" rel=\"noopener noreferrer\" target=\"_blank\">احجز رابط لمُلتقى</a> إن كنت رئيس الجلسة الوحيد فقط.",
"privacy": "الخصوصية",
"recentList": "الجديد",
"recentListDelete": "حذف",
"recentListEmpty": "قائمتك الأخيرة فارغة حاليًا. ابدأ التحدث مع الفريق وستجد كل اجتماعاتك الأخيرة هنا.",
"recentListEmpty": "قائمتك الأخيرة فارغة حاليًا. ابدأ التحدث مع الفريق وستجد كل مُلتقياتك الأخيرة هنا.",
"reducedUIText": "يا مرحبًا بك في {{app}}!",
"roomNameAllowedChars": "لا يجب أن يحوي اسم الاجتماع على: ?، &، :، '، \"، %، #.",
"roomNameAllowedChars": "لا يجب أن يحوي اسم المُلتقى على: ?، &، :، '، \"، %، #.",
"roomname": "أدخل اسم الغرفة",
"roomnameHint": "أدخل اسم أو رابط الغرفة التي تريد الانضمام إليها. يمكنك إنشاء اسم جديد لترسله إلى من تريد أن تجتمع معهم.",
"sendFeedback": "أبدِ رأيك",
"startMeeting": "إبدأ الحـِوار",
"startMeeting": "إبدأ المُلتقى",
"terms": "الشروط",
"title": "منصة عقد مؤتمرات واجتماعات آمنة وكاملة المزايا ومجانية بالمطلق"
"title": "منصة عقد مؤتمرات ومُلتقيات آمنة وكاملة المزايا ومجانية بالمطلق"
}
}

View File

@@ -83,6 +83,7 @@
"enter": "Chat-Raum betreten",
"error": "Fehler: Ihre Nachricht wurde nicht versendet. Grund: {{error}}",
"fieldPlaceHolder": "Geben Sie Ihre Nachricht hier ein",
"lobbyChatMessageTo": "Lobby-Nachricht an {{recipient}}",
"message": "Nachricht",
"messageAccessibleTitle": "{{user}} sagt:",
"messageAccessibleTitleMe": "Ich sage:",
@@ -529,6 +530,7 @@
"admitAll": "Alle zulassen",
"allow": "Annehmen",
"backToKnockModeButton": "Kein Passwort, stattdessen Beitritt anfragen",
"chat": "Chat",
"dialogTitle": "Lobbymodus",
"disableDialogContent": "Der Lobbymodus ist derzeit aktiviert. Diese Funktion stellt sicher, dass unerwünschte Personen Ihrer Konferenz nicht beitreten können. Funktion deaktivieren?",
"disableDialogSubmit": "Deaktivieren",
@@ -549,6 +551,8 @@
"knockButton": "Beitritt anfragen",
"knockTitle": "Jemand möchte der Konferenz beitreten",
"knockingParticipantList": "Liste anklopfender Personen",
"lobbyChatStartedNotification": "{{moderator}} hat einen Lobby-Chat mit {{attendee}} gestartet",
"lobbyChatStartedTitle": "{{moderator}} hat einen Lobby-Chat mit Ihnen gestartet.",
"nameField": "Geben Sie Ihren Namen ein",
"notificationLobbyAccessDenied": "{{targetParticipantName}} wurde von {{originParticipantName}} der Zutritt verwehrt",
"notificationLobbyAccessGranted": "{{targetParticipantName}} wurde von {{originParticipantName}} der Zutritt gestattet",

View File

@@ -83,6 +83,7 @@
"enter": "Entrez dans le salon",
"error": "Erreur : votre message n'a pas été envoyé. Raison : {{error}}",
"fieldPlaceHolder": "Tapez votre message ici",
"lobbyChatMessageTo": "Message de salle d'attente à {{recipient}}",
"message": "Message",
"messageAccessibleTitle": "{{user}} dit: ",
"messageAccessibleTitleMe": "Je dis: ",
@@ -215,6 +216,8 @@
"liveStreaming": "Diffusion en direct"
},
"add": "Ajouter",
"addMeetingNote": "Ajouter une note à cette conférence",
"addOptionalNote": "Ajouter une note (optionnel):",
"allow": "Autoriser",
"alreadySharedVideoMsg": "Un autre participant est en train de partager sa vidéo. Cette conférence ne permet de partager qu'une seule vidéo à la fois.",
"alreadySharedVideoTitle": "Une seule vidéo partagée est autorisée à la fois",
@@ -266,6 +269,8 @@
"kickParticipantDialog": "Êtes-vous sûr(e) de vouloir expulser ce participant ?",
"kickParticipantTitle": "Expulser ce participant ?",
"kickTitle": "Oups ! vous avez été expulsé(e) par {{participantDisplayName}}",
"linkMeeting": "Relier la conférence",
"linkMeetingTitle": "Relier la conférence à Salesforce",
"liveStreaming": "Direct",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Impossible durant l'enregistrement",
"liveStreamingDisabledTooltip": "La diffusion en direct est désactivée",
@@ -320,6 +325,7 @@
"popupError": "Votre navigateur bloque les fenêtres pop-up. Veuillez autoriser les fenêtres pop-up dans les paramètres de votre navigateur.",
"popupErrorTitle": "Pop-up bloquée",
"readMore": "plus",
"recentlyUsedObjects": "Vos objets récemment utilisés",
"recording": "Enregistrement",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Impossible durant le direct",
"recordingDisabledTooltip": "L'enregistrement est désactivé.",
@@ -342,6 +348,12 @@
"screenSharingFailed": "Houla ! Quelque chose s'est mal passé, nous n'avons pas pu démarrer le partage d'écran !",
"screenSharingFailedTitle": "Echec du partage d'écran !",
"screenSharingPermissionDeniedError": "Houla ! Un problème est survenu avec vos autorisations de partage d'écran. Veuillez réessayer.",
"searchInSalesforce": "Rechercher dans Salesforce",
"searchResults": "Résultats de recherche({{count}})",
"searchResultsDetailsError": "Un problème est survenu en récupérant les données du propriétaire.",
"searchResultsError": "Un problème est survenu en récupérant des données.",
"searchResultsNotFound": "Aucun résultat trouvé.",
"searchResultsTryAgain": "Essayer d'utiliser d'autres mots clé.",
"sendPrivateMessage": "Vous avez récemment reçu un message privé. Aviez-vous l'intention d'y répondre en privé, ou vouliez-vous envoyer votre message au groupe ?",
"sendPrivateMessageCancel": "Envoyer au groupe",
"sendPrivateMessageOk": "Envoyer en privé",
@@ -409,6 +421,10 @@
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"giphy": {
"noResults": "Aucun résultat de recherche :(",
"search": "Rechercher dans GIPHY"
},
"helpView": {
"header": "Centre d'aide"
},
@@ -475,6 +491,7 @@
"focusLocal": "Épingler ma vidéo",
"focusRemote": "Épingler la vidéo de quelqu'un d'autre",
"fullScreen": "Activer / Désactiver le mode plein écran",
"giphyMenu": "Activer/désactiver le menu GIPHY",
"keyboardShortcuts": "Raccourcis clavier",
"localRecording": "Afficher / Masquer les commandes de l'enregistrement local",
"mute": "Activer / Couper le microphone",
@@ -529,6 +546,7 @@
"admitAll": "Tout accepter",
"allow": "Autoriser",
"backToKnockModeButton": "Aucun mot de passe, demander à rejoindre plutôt",
"chat": "Chat",
"dialogTitle": "Mode salle d'attente",
"disableDialogContent": "Le mode salle d'attente est actuellement activé. Cette fonctionnalité garantit que les participants indésirables ne peuvent pas rejoindre votre réunion. Souhaitez-vous la désactiver ?",
"disableDialogSubmit": "Désactiver",
@@ -549,6 +567,8 @@
"knockButton": "Demander à rejoindre",
"knockTitle": "Quelqu'un souhaite rejoindre la réunion",
"knockingParticipantList": "Liste des participants en attente",
"lobbyChatStartedNotification": "Un modérateur dialogue en salle d'attente avec {{attendee}}",
"lobbyChatStartedTitle": "Un modérateur dialogue en salle d'attente avec vous.",
"nameField": "Saisissez votre nom",
"notificationLobbyAccessDenied": "{{targetParticipantName}} a été refusé par {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} a été accepté par {{originParticipantName}}",
@@ -620,6 +640,12 @@
"leftOneMember": "{{name}} a quitté la réunion",
"leftThreePlusMembers": "{{name}} et beaucoup d'autres ont quitté la réunion",
"leftTwoMembers": "{{first}} et {{second}} ont quitté la réunion",
"linkToSalesforce": "Lien à Salesforce",
"linkToSalesforceDescription": "Vous pouvez lier le résumé de la conférence à un objet Salesforce.",
"linkToSalesforceError": "Impossible de relier la conférence à Salesforce",
"linkToSalesforceKey": "Relier cette conférence",
"linkToSalesforceProgress": "Liaison de la conférence à Salesforce...",
"linkToSalesforceSuccess": "La conférence a été reliée à Salesforce",
"me": "Moi",
"moderationInEffectCSDescription": "Merci de lever la main si vous voulez partager votre écran.",
"moderationInEffectCSTitle": "Le partage d'écran est interdit par le modérateur",
@@ -813,6 +839,18 @@
},
"raisedHand": "Aimerait prendre la parole",
"raisedHandsLabel": "Nombre de mains levées",
"record": {
"already": {
"linked": "L'enregistrement est déjà relié à cette session."
},
"type": {
"account": "Compte",
"contact": "Contact",
"lead": "Piste",
"opportunity": "Opportunité",
"owner": "Propriétaire"
}
},
"recording": {
"authDropboxText": "Téléchargement vers Dropbox",
"availableSpace": "Espace disponible : {{spaceLeft}} Mo (approximativement {{duration}} minutes d'enregistrement)",
@@ -841,6 +879,7 @@
"rec": "REC",
"serviceDescription": "Votre enregistrement sera enregistré par le service dédié",
"serviceDescriptionCloud": "Enregistrement Cloud",
"serviceDescriptionCloudInfo": "Les conférences enregistrées sont automatiquement supprimées 24h après leur heure d'enregistrement.",
"serviceName": "Service d'enregistrement",
"sessionAlreadyActive": "Cette session est déjà en cours d'enregistrement ou de diffusion.",
"signIn": "Se connecter",
@@ -973,6 +1012,7 @@
"expand": "Développer",
"feedback": "Laisser des commentaires",
"fullScreen": "Activer / Désactiver le plein écran",
"giphy": "Activer/désactiver le menu GIPHY",
"grantModerator": "donner des droits de modérateur",
"hangup": "Quitter la conversation",
"help": "Aide",
@@ -980,6 +1020,7 @@
"kick": "Expulser le participant",
"laugh": "Rire",
"like": "Approuver",
"linkToSalesforce": "Lien à Salesforce",
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
@@ -1041,6 +1082,7 @@
"exitFullScreen": "Quitter le mode plein écran",
"exitTileView": "Quitter le mode mosaïque",
"feedback": "Laisser des commentaires",
"giphy": "Activer/désactiver le menu GIPHY",
"hangup": "Quitter",
"help": "Aide",
"invite": "Inviter des participants",
@@ -1048,6 +1090,7 @@
"laugh": "Rire",
"leaveBreakoutRoom": "Quitter salle annexe",
"like": "Approuver",
"linkToSalesforce": "Lien à Salesforce",
"lobbyButtonDisable": "Désactiver le mode salle d'attente / contrôle des participant(e)s",
"lobbyButtonEnable": "Activer le mode salle d'attente / contrôle des participant(e)s",
"login": "Connexion",

View File

@@ -83,6 +83,7 @@
"enter": "Entrar na sala",
"error": "Erro: a sua mensagem não foi enviada. Motivo: {{error}}",
"fieldPlaceHolder": "Escreva aqui a sua mensagem",
"lobbyChatMessageTo": "Mensagem de chat na sala de espera para {{recipient}}",
"message": "Mensagem",
"messageAccessibleTitle": "{{user}} disse:",
"messageAccessibleTitleMe": "Eu disse:",
@@ -126,7 +127,7 @@
"FETCH_SESSION_ID": "Obtenção da session-id...",
"GET_SESSION_ID_ERROR": "Obter erro de session-id: {{code}}",
"GOT_SESSION_ID": "Obtenção da session-id... Feito",
"LOW_BANDWIDTH": "Vídeo para {{displayName}} foi desligada para poupar largura de banda"
"LOW_BANDWIDTH": "O vídeo para {{displayName}} foi desligado para poupar largura de banda"
},
"connectionindicator": {
"address": "Endereço:",
@@ -215,6 +216,8 @@
"liveStreaming": "Transmissão em direto"
},
"add": "Adicionar",
"addMeetingNote": "Acrescentar uma nota sobre esta reunião",
"addOptionalNote": "Adicionar uma nota (opcional):",
"allow": "Permitir",
"alreadySharedVideoMsg": "Outro participante já está a partilhar um vídeo. Esta conferência permite apenas um vídeo partilhado de cada vez.",
"alreadySharedVideoTitle": "Só é permitido um vídeo partilhado de cada vez",
@@ -266,6 +269,8 @@
"kickParticipantDialog": "Tem a certeza que quer expulsar este participante?",
"kickParticipantTitle": "Expulsar este participante?",
"kickTitle": "Ai! {{participantDisplayName}} expulsou-o da reunião",
"linkMeeting": "Link da reunião",
"linkMeetingTitle": "Link da reunião à Força de Vendas",
"liveStreaming": "Transmissão em direto",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Não é possível enquanto a gravação estiver activa",
"liveStreamingDisabledTooltip": "Início de transmissão em direto desactivada",
@@ -320,6 +325,7 @@
"popupError": "O seu navegador está a bloquear janelas pop-up a partir deste site. Por favor, active os pop-ups nas definições de segurança do seu browser e tente novamente.",
"popupErrorTitle": "Pop-up bloqueado",
"readMore": "mais",
"recentlyUsedObjects": "Os seus objetos recentemente utilizados",
"recording": "A gravar",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Não possível enquanto a transmissão em direto estiver activa",
"recordingDisabledTooltip": "Início de gravação desactivada.",
@@ -342,6 +348,12 @@
"screenSharingFailed": "Oops! Algo correu mal, não fomos capazes de começar a partilhar o ecrã!",
"screenSharingFailedTitle": "A partilha de ecrã falhou!",
"screenSharingPermissionDeniedError": "Oops! Alguma coisa correu mal com as vossas permissões de partilha de ecrã. Por favor, volte a carregar e tente novamente.",
"searchInSalesforce": "Pesquisar na Força de Vendas",
"searchResults": "Resultados da pesquisa({{count}})",
"searchResultsDetailsError": "Algo correu mal enquanto se recuperava os dados do proprietário.",
"searchResultsError": "Alguma coisa correu mal durante a recuperação de dados.",
"searchResultsNotFound": "Não foram encontrados resultados de pesquisa.",
"searchResultsTryAgain": "Tente usar palavras-chave alternativas.",
"sendPrivateMessage": "Recebeu recentemente uma mensagem privada. Pretende responder a essa mensagem em privado, ou quer enviar a sua mensagem ao grupo?",
"sendPrivateMessageCancel": "Enviar para o grupo",
"sendPrivateMessageOk": "Enviar em privado",
@@ -364,7 +376,9 @@
"shareVideoTitle": "Partilhar vídeo",
"shareYourScreen": "Partilhe o seu ecrã",
"shareYourScreenDisabled": "Partilha de ecrã desactivada.",
"sharedVideoDialogError": "Erro: URL inválido",
"sharedVideoLinkPlaceholder": "Link do YouTube ou link direto do vídeo",
"start": "Iniciar ",
"startLiveStreaming": "Iniciar a transmissão em direto",
"startRecording": "Iniciar gravação",
"startRemoteControlErrorMessage": "Ocorreu um erro ao tentar iniciar a sessão de controlo remoto!",
@@ -407,6 +421,10 @@
"veryBad": "Muito má",
"veryGood": "Muito boa"
},
"giphy": {
"noResults": "Não foram encontrados resultados :(",
"search": "Procurar GIPHY"
},
"helpView": {
"header": "Centro de ajuda"
},
@@ -473,6 +491,7 @@
"focusLocal": "Focar no seu vídeo",
"focusRemote": "Focar no vídeo de outro participante",
"fullScreen": "Entrar ou sair do ecrã completo",
"giphyMenu": "Alternar o menu GIPHY",
"keyboardShortcuts": "Atalhos de teclado",
"localRecording": "Mostrar ou ocultar controlos de gravação local",
"mute": "Ligar ou desligar o seu microfone",
@@ -527,6 +546,7 @@
"admitAll": "Aceitar todos",
"allow": "Permitir",
"backToKnockModeButton": "Peça para aderir",
"chat": "Chat",
"dialogTitle": "Modo sala de espera",
"disableDialogContent": "O modo sala de espera está actualmente activada. Esta característica assegura que os participantes indesejados não possam juntar-se à sua reunião. Quer desativá-la?",
"disableDialogSubmit": "Desativar",
@@ -547,6 +567,8 @@
"knockButton": "Pedir para participar",
"knockTitle": "Alguém quer juntar-se à reunião",
"knockingParticipantList": "Lista de participantes a expulsar",
"lobbyChatStartedNotification": "{{moderator}} iniciou uma conversa na sala de espera com {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} iniciou uma conversa na sala de espera consigo.",
"nameField": "Introduza o seu nome",
"notificationLobbyAccessDenied": "{{targetParticipantName}} foi recusada a adesão por {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} foi autorizado a aderir por {{originParticipantName}}",
@@ -618,6 +640,12 @@
"leftOneMember": "{{name}} deixou a reunião",
"leftThreePlusMembers": "{{name}} e muitos outros deixaram a reunião",
"leftTwoMembers": "{{first}} e {{second}} deixaram a reunião",
"linkToSalesforce": "Link para a Força de Vendas",
"linkToSalesforceDescription": "Pode ligar o resumo da reunião a um objecto da Força de Vendas.",
"linkToSalesforceError": "Falha na ligação da reunião à Força de Vendas",
"linkToSalesforceKey": "Ligar esta reunião",
"linkToSalesforceProgress": "A ligar a reunião à Força de Vendas...",
"linkToSalesforceSuccess": "A reunião foi ligada à Força de Vendas",
"me": "Eu",
"moderationInEffectCSDescription": "Por favor, levantem a mão se quiserem partilhar o vosso ecrã.",
"moderationInEffectCSTitle": "A partilha de ecrã é bloqueada pelo moderador",
@@ -641,6 +669,8 @@
"oldElectronClientDescription1": "Parece estar a utilizar uma versão antiga do cliente Jitsi Meet que tem vulnerabilidades de segurança conhecidas. Por favor, certifique-se de que actualiza a nossa ",
"oldElectronClientDescription2": "compilação mais recente",
"oldElectronClientDescription3": " agora!",
"participantWantsToJoin": "Deseja juntar-se à reunião",
"participantsWantToJoin": "Desejam juntar-se à reunião",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
"raiseHandAction": "Levantar a mão",
@@ -648,7 +678,7 @@
"raisedHands": "{{participantName}} e mais {{raisedHands}} pessoas",
"reactionSounds": "Desactivar sons",
"reactionSoundsForAll": "Desativar sons para todos",
"screenShareNoAudio": " A caixa de compartilhar áudio não foi marcada no ecrã de seleção da janela.",
"screenShareNoAudio": "A caixa de compartilhar áudio não foi marcada no ecrã de seleção da janela.",
"screenShareNoAudioTitle": "Não foi possível partilhar o áudio do sistema!",
"selfViewTitle": "Pode sempre reexibir a autovisualização a partir das definições",
"somebody": "Alguém",
@@ -660,7 +690,9 @@
"videoMutedRemotelyDescription": "Pode sempre ligá-la novamente.",
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{participantDisplayName}}.",
"videoUnmuteBlockedDescription": "A operação de ligar a câmara e partilhar o ambiente de trabalho foi temporariamente bloqueada devido aos limites do sistema.",
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!"
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!",
"viewLobby": "Ver sala de espera",
"waitingParticipants": "{{waitingParticipants}} pessoas"
},
"participantsPane": {
"actions": {
@@ -807,6 +839,18 @@
},
"raisedHand": "Gostaria de falar",
"raisedHandsLabel": "Número de mãos levantadas",
"record": {
"already": {
"linked": "O registo já está ligado a esta sessão."
},
"type": {
"account": "Conta",
"contact": "Contacto",
"lead": "Líder",
"opportunity": "Oportunidade",
"owner": "Proprietário"
}
},
"recording": {
"authDropboxText": "Enviar para o Dropbox.",
"availableSpace": "Espaço disponível: {{spaceLeft}} MB (aproximadamente {{duration}} minutos de gravação)",
@@ -820,7 +864,7 @@
"expandedOn": "A reunião está sendo gravada.",
"expandedPending": "Iniciando gravação...",
"failedToStart": "Falha ao iniciar a gravação",
"fileSharingdescription": "Compartilhar gravação com participantes da reunião",
"fileSharingdescription": "Partilhar o link da gravação com os participantes da reunião",
"inProgress": "Gravação ou transmissão em direto em curso",
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. Para gravações ilimitadas tente <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Devido à grande procura, a sua gravação será limitada a {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -835,6 +879,7 @@
"rec": "REC",
"serviceDescription": "Sua gravação será salva pelo serviço de gravação",
"serviceDescriptionCloud": "Gravação na nuvem",
"serviceDescriptionCloudInfo": "As reuniões gravadas são automaticamente apagadas 24h após a hora de gravação.",
"serviceName": "Serviço de gravação",
"sessionAlreadyActive": "Esta sessão já está a ser gravada ou transmitida em direto.",
"signIn": "Entrar",
@@ -921,6 +966,7 @@
"speakerStats": {
"angry": "Zangado",
"disgusted": "Desgostoso",
"displayEmotions": "Mostrar emoções",
"fearful": "Temeroso",
"happy": "Feliz",
"hours": "{{count}}h",
@@ -966,6 +1012,7 @@
"expand": "Expandir",
"feedback": "Deixar comentários",
"fullScreen": "Mudar para ecrã completo",
"giphy": "Alternar o menu GIPHY",
"grantModerator": "Conceder direitos de moderador",
"hangup": "Sair da reunião",
"help": "Ajuda",
@@ -973,6 +1020,7 @@
"kick": "Remover participante",
"laugh": "Risos",
"like": "Aprovado",
"linkToSalesforce": "Link para a Força de Vendas",
"lobbyButton": "Ativar/desativar sala de espera",
"localRecording": "Mudar os controlos locais de gravação",
"lockRoom": "Mudar palavra-chave de reunião",
@@ -995,6 +1043,7 @@
"remoteVideoMute": "Desativar a câmara do participante",
"security": "Opções de segurança",
"selectBackground": "Selecionar plano de fundo",
"selfView": "Mudar a autovisualização",
"shareRoom": "Convidar alguém",
"shareYourScreen": "Iniciar / Parar partilha de ecrã",
"shareaudio": "Partilhar áudio",
@@ -1033,6 +1082,7 @@
"exitFullScreen": "Sair de ecrã completo",
"exitTileView": "Sair de quadrícula",
"feedback": "Deixar comentários",
"giphy": "Alternar o menu GIPHY",
"hangup": "Sair da reunião",
"help": "Ajuda",
"invite": "Convidar pessoas",
@@ -1040,6 +1090,7 @@
"laugh": "Risos",
"leaveBreakoutRoom": "Sair da sala",
"like": "Aprovado",
"linkToSalesforce": "Link para a Força de Vendas",
"lobbyButtonDisable": "Desativar sala de espera",
"lobbyButtonEnable": "Ativar sala de espera",
"login": "Iniciar sessão",
@@ -1160,9 +1211,11 @@
"mute": "Participante está sem som",
"muted": "Sem som",
"remoteControl": "Iniciar / Parar controlo remoto",
"screenSharing": "Participante está a partilhar o seu ecrã",
"show": "Mostrar no palco",
"showSelfView": "Mostrar autovisualização",
"videoMuted": "Câmara desativada",
"videomute": "O participante parou a câmara"
"videomute": "Participante parou a câmara"
},
"virtualBackground": {
"addBackground": "Adicionar imagem de fundo",
@@ -1194,7 +1247,7 @@
"join": "Toque para entrar",
"roomname": "Digite o nome da sala"
},
"addMeetingName": "AAdicionar nome da reunião",
"addMeetingName": "Adicionar nome da reunião",
"appDescription": "Vá em frente, converse por vídeo com toda a equipa. Na verdade, convide todos os que conhece. {{app}} é uma solução de videoconferência totalmente criptografada e 100% de código aberto que pode usar todos os dias, a cada dia, gratuitamente — sem necessidade de conta.",
"audioVideoSwitch": {
"audio": "Voz",

View File

@@ -68,6 +68,7 @@
"enter": "Odaya gir",
"error": "Hata: Mesajınız gönderilmedi. Neden: {{error}}",
"fieldPlaceHolder": "Mesajınızı buraya yazın",
"lobbyChatMessageTo": "{{recipient}} adlı kişiye lobi mesajı",
"message": "Mesaj",
"messageAccessibleTitle": "{{user}} diyor:",
"messageAccessibleTitleMe": "ben diyorum:",
@@ -526,6 +527,7 @@
"admitAll": "Hepsini kabul et",
"allow": "İzin ver",
"backToKnockModeButton": "Parola yok, bunun yerine katılmayı isteyin",
"chat": "Sohbet et",
"dialogTitle": "Lobi modu",
"disableDialogContent": "Lobi modu şu anda etkin. Bu özellik, istenmeyen katılımcıların toplantınıza katılamamasını sağlar. Devre dışı bırakmak istiyor musunuz?",
"disableDialogSubmit": "Devre Dışı",
@@ -546,6 +548,8 @@
"knockButton": "Katılmak için sor",
"knockTitle": "Birisi toplantıya katılmak istiyor",
"knockingParticipantList": "Kapıyı çalan katılımcı listesi",
"lobbyChatStartedNotification": "{{moderator}} {{attendee}} adlı kişiyle lobi mesajlaşması başlattı",
"lobbyChatStartedTitle": "{{moderator}} sizinle lobi mesajlaşması başlattı",
"nameField": "Adınızı giriniz",
"notificationLobbyAccessDenied": "{{targetParticipantName}} adlı katılımcı {{originParticipantName}} tarafından reddedildi",
"notificationLobbyAccessGranted": "{{targetParticipantName}} adlı katılımcı {{originParticipantName}} tarafından kabul edildi",

View File

@@ -39,9 +39,6 @@
"audioOnly": {
"audioOnly": "Low bandwidth"
},
"blankPage": {
"meetingEnded": "Meeting ended."
},
"breakoutRooms": {
"actions": {
"add": "Add breakout room",
@@ -83,6 +80,7 @@
"enter": "Enter room",
"error": "Error: your message was not sent. Reason: {{error}}",
"fieldPlaceHolder": "Type your message here",
"lobbyChatMessageTo": "Lobby chat message to {{recipient}}",
"message": "Message",
"messageAccessibleTitle": "{{user}} says:",
"messageAccessibleTitleMe": "me says:",
@@ -215,6 +213,8 @@
"liveStreaming": "Live Stream"
},
"add": "Add",
"addMeetingNote": "Add a note about this meeting",
"addOptionalNote": "Add a note (optional):",
"allow": "Allow",
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
@@ -266,6 +266,8 @@
"kickParticipantDialog": "Are you sure you want to kick this participant?",
"kickParticipantTitle": "Kick this participant?",
"kickTitle": "Ouch! {{participantDisplayName}} kicked you out of the meeting",
"linkMeeting": "Link meeting",
"linkMeetingTitle": "Link meeting to Salesforce",
"liveStreaming": "Live Streaming",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Not possible while recording is active",
"liveStreamingDisabledTooltip": "Start live stream disabled.",
@@ -320,6 +322,7 @@
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
"popupErrorTitle": "Pop-up blocked",
"readMore": "more",
"recentlyUsedObjects": "Your recently used objects",
"recording": "Recording",
"recordingDisabledBecauseOfActiveLiveStreamingTooltip": "Not possible while a live stream is active",
"recordingDisabledTooltip": "Start recording disabled.",
@@ -342,6 +345,12 @@
"screenSharingFailed": "Oops! Something went wrong, we werent able to start screen sharing!",
"screenSharingFailedTitle": "Screen sharing failed!",
"screenSharingPermissionDeniedError": "Oops! Something went wrong with your screen sharing permissions. Please reload and try again.",
"searchInSalesforce": "Search in Salesforce",
"searchResults": "Search results({{count}})",
"searchResultsDetailsError": "Something went wrong while retrieving owner data.",
"searchResultsError": "Something went wrong while retrieving data.",
"searchResultsNotFound": "No search results found.",
"searchResultsTryAgain": "Try using alternative keywords.",
"sendPrivateMessage": "You recently received a private message. Did you intend to reply to that privately, or you want to send your message to the group?",
"sendPrivateMessageCancel": "Send to the group",
"sendPrivateMessageOk": "Send privately",
@@ -409,6 +418,10 @@
"veryBad": "Very Bad",
"veryGood": "Very Good"
},
"giphy": {
"noResults": "No results found :(",
"search": "Search GIPHY"
},
"helpView": {
"header": "Help center"
},
@@ -475,6 +488,7 @@
"focusLocal": "Focus on your video",
"focusRemote": "Focus on another person's video",
"fullScreen": "View or exit full screen",
"giphyMenu": "Toggle GIPHY menu",
"keyboardShortcuts": "Keyboard shortcuts",
"localRecording": "Show or hide local recording controls",
"mute": "Mute or unmute your microphone",
@@ -529,6 +543,7 @@
"admitAll": "Admit all",
"allow": "Allow",
"backToKnockModeButton": "Ask to join",
"chat": "Chat",
"dialogTitle": "Lobby mode",
"disableDialogContent": "Lobby mode is currently enabled. This feature ensures that unwanted participants can't join your meeting. Do you want to disable it?",
"disableDialogSubmit": "Disable",
@@ -549,6 +564,8 @@
"knockButton": "Ask to Join",
"knockTitle": "Someone wants to join the meeting",
"knockingParticipantList": "Knocking participant list",
"lobbyChatStartedNotification": "{{moderator}} started a lobby chat with {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} has started a lobby chat with you.",
"nameField": "Enter your name",
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
@@ -620,6 +637,12 @@
"leftOneMember": "{{name}} left the meeting",
"leftThreePlusMembers": "{{name}} and many others left the meeting",
"leftTwoMembers": "{{first}} and {{second}} left the meeting",
"linkToSalesforce": "Link to Salesforce",
"linkToSalesforceDescription": "You can link the meeting summary to a Salesforce object.",
"linkToSalesforceError": "Failed to link meeting to Salesforce",
"linkToSalesforceKey": "Link this meeting",
"linkToSalesforceProgress": "Linking meeting to Salesforce...",
"linkToSalesforceSuccess": "The meeting was linked to Salesforce",
"me": "Me",
"moderationInEffectCSDescription": "Please raise hand if you want to share your screen.",
"moderationInEffectCSTitle": "Screen sharing is blocked by the moderator",
@@ -813,6 +836,18 @@
},
"raisedHand": "Would like to speak",
"raisedHandsLabel": "Number of raised hands",
"record": {
"already": {
"linked": "Record is already linked to this session."
},
"type": {
"account": "Account",
"contact": "Contact",
"lead": "Lead",
"opportunity": "Opportunity",
"owner": "Owner"
}
},
"recording": {
"authDropboxText": "Upload to Dropbox",
"availableSpace": "Available space: {{spaceLeft}} MB (approximately {{duration}} minutes of recording)",
@@ -826,7 +861,12 @@
"expandedOn": "The meeting is currently being recorded.",
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share recording with meeting participants",
"fileSharingdescription": "Share the recording link with the meeting participants",
"highlight": "Highlight",
"highlightMoment": "Highlight moment",
"highlightMomentDisabled": "You can highlight moments when the recording starts",
"highlightMomentSuccess": "Moment highlighted",
"highlightMomentSucessDescription": "Your highlighted moment will be added to the meeting summary.",
"inProgress": "Recording or live streaming in progress",
"limitNotificationDescriptionNative": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Due to high demand your recording will be limited to {{limit}} min. For unlimited recordings try <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
@@ -841,6 +881,7 @@
"rec": "REC",
"serviceDescription": "Your recording will be saved by the recording service",
"serviceDescriptionCloud": "Cloud recording",
"serviceDescriptionCloudInfo": "Recorded meetings are automatically cleared 24h after their recording time.",
"serviceName": "Recording service",
"sessionAlreadyActive": "This session is already being recorded or live streamed.",
"signIn": "Sign in",
@@ -973,6 +1014,7 @@
"expand": "Expand",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
"giphy": "Toggle GIPHY menu",
"grantModerator": "Grant Moderator Rights",
"hangup": "Leave the meeting",
"help": "Help",
@@ -980,6 +1022,7 @@
"kick": "Kick participant",
"laugh": "Laugh",
"like": "Thumbs Up",
"linkToSalesforce": "Link to Salesforce",
"lobbyButton": "Enable/disable lobby mode",
"localRecording": "Toggle local recording controls",
"lockRoom": "Toggle meeting password",
@@ -1041,6 +1084,7 @@
"exitFullScreen": "Exit full screen",
"exitTileView": "Exit tile view",
"feedback": "Leave feedback",
"giphy": "Toggle GIPHY menu",
"hangup": "Leave the meeting",
"help": "Help",
"invite": "Invite people",
@@ -1048,6 +1092,7 @@
"laugh": "Laugh",
"leaveBreakoutRoom": "Leave breakout room",
"like": "Thumbs Up",
"linkToSalesforce": "Link to Salesforce",
"lobbyButtonDisable": "Disable lobby mode",
"lobbyButtonEnable": "Enable lobby mode",
"login": "Login",

View File

@@ -170,6 +170,7 @@ function changeParticipantNumber(APIInstance, number) {
* configuration options defined in interface_config.js to be overridden.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.lang] - The meeting's default language.
* @param {string} [options.roomName] - The name of the room to join.
* @returns {string} The URL.
*/
@@ -208,7 +209,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload
onload,
lang
] = args;
return {
@@ -219,7 +221,8 @@ function parseArguments(args) {
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload
onload,
lang
};
}
case 'object': // new arguments format
@@ -280,6 +283,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* configuration options defined in interface_config.js to be overridden.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.lang] - The meeting's default language.
* @param {string} [options.onload] - The onload function that will listen
* for iframe onload event.
* @param {Array<Object>} [options.invitees] - Array of objects containing
@@ -301,6 +305,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
configOverwrite = {},
interfaceConfigOverwrite = {},
jwt = undefined,
lang = undefined,
onload = undefined,
invitees,
devices,
@@ -314,6 +319,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
configOverwrite,
interfaceConfigOverwrite,
jwt,
lang,
roomName,
devices,
userInfo,

View File

@@ -26,7 +26,7 @@ import {
isTrackStreamingStatusInactive,
isTrackStreamingStatusInterrupted
} from '../../../react/features/connection-indicator/functions';
import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../../react/features/filmstrip';
import { FILMSTRIP_BREAKPOINT, isFilmstripResizable, getVerticalViewMaxWidth } from '../../../react/features/filmstrip';
import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video/actions';
@@ -419,7 +419,7 @@ export default class LargeVideoManager {
}
if (resizableFilmstrip && visible && filmstripWidth.current >= FILMSTRIP_BREAKPOINT) {
widthToUse -= filmstripWidth.current;
widthToUse -= getVerticalViewMaxWidth(state);
}
this.width = widthToUse;

View File

@@ -6,6 +6,7 @@ import ReactDOM from 'react-dom';
import { browser } from '../../../react/features/base/lib-jitsi-meet';
import { isTestModeEnabled } from '../../../react/features/base/testing';
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip';
import { ORIENTATION, LargeVideoBackground, updateLastLargeVideoMediaEvent } from '../../../react/features/large-video';
import { LAYOUTS, getCurrentLayout } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
@@ -37,13 +38,15 @@ const containerEvents = [
* @param videoHeight the height of the video to position
* @param videoSpaceWidth the width of the available space
* @param videoSpaceHeight the height of the available space
* @param subtractFilmstrip whether to subtract the filmstrip or not
* @return an array with 2 elements, the video width and the video height
*/
function computeDesktopVideoSize( // eslint-disable-line max-params
videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
videoSpaceHeight,
subtractFilmstrip) {
if (videoWidth === 0 || videoHeight === 0 || videoSpaceWidth === 0 || videoSpaceHeight === 0) {
// Avoid NaN values caused by division by 0.
return [ 0, 0 ];
@@ -54,8 +57,10 @@ function computeDesktopVideoSize( // eslint-disable-line max-params
let availableHeight = Math.max(videoHeight, videoSpaceHeight);
if (interfaceConfig.VERTICAL_FILMSTRIP) {
// eslint-disable-next-line no-param-reassign
videoSpaceWidth -= Filmstrip.getVerticalFilmstripWidth();
if (subtractFilmstrip) {
// eslint-disable-next-line no-param-reassign
videoSpaceWidth -= Filmstrip.getVerticalFilmstripWidth();
}
} else {
// eslint-disable-next-line no-param-reassign
videoSpaceHeight -= Filmstrip.getFilmstripHeight();
@@ -307,16 +312,18 @@ export class VideoContainer extends LargeContainer {
* Calculate optimal video size for specified container size.
* @param {number} containerWidth container width
* @param {number} containerHeight container height
* @param {number} verticalFilmstripWidth current width of the vertical filmstrip
* @returns {{availableWidth, availableHeight}}
*/
_getVideoSize(containerWidth, containerHeight) {
_getVideoSize(containerWidth, containerHeight, verticalFilmstripWidth) {
const { width, height } = this.getStreamSize();
if (this.stream && this.isScreenSharing()) {
return computeDesktopVideoSize(width,
height,
containerWidth,
containerHeight);
containerHeight,
verticalFilmstripWidth < FILMSTRIP_BREAKPOINT);
}
return computeCameraVideoSize(width,
@@ -334,14 +341,15 @@ export class VideoContainer extends LargeContainer {
* @param {number} height video height
* @param {number} containerWidth container width
* @param {number} containerHeight container height
* @param {number} verticalFilmstripWidth current width of the vertical filmstrip
* @returns {{horizontalIndent, verticalIndent}}
*/
getVideoPosition(width, height, containerWidth, containerHeight) {
getVideoPosition(width, height, containerWidth, containerHeight, verticalFilmstripWidth) {
let containerWidthToUse = containerWidth;
/* eslint-enable max-params */
if (this.stream && this.isScreenSharing()) {
if (interfaceConfig.VERTICAL_FILMSTRIP) {
if (interfaceConfig.VERTICAL_FILMSTRIP && verticalFilmstripWidth < FILMSTRIP_BREAKPOINT) {
containerWidthToUse -= Filmstrip.getVerticalFilmstripWidth();
}
@@ -401,7 +409,10 @@ export class VideoContainer extends LargeContainer {
if (this.$video.length === 0) {
return;
}
const currentLayout = getCurrentLayout(APP.store.getState());
const state = APP.store.getState();
const currentLayout = getCurrentLayout(state);
const verticalFilmstripWidth = state['features/filmstrip'].width?.current;
if (currentLayout === LAYOUTS.TILE_VIEW) {
// We don't need to resize the large video since it won't be displayed and we'll resize when returning back
@@ -411,7 +422,7 @@ export class VideoContainer extends LargeContainer {
this.positionRemoteStatusMessages();
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight);
const [ width, height ] = this._getVideoSize(containerWidth, containerHeight, verticalFilmstripWidth);
if (width === 0 || height === 0) {
// We don't need to set 0 for width or height since the visibility is controlled by the visibility css prop
@@ -432,7 +443,7 @@ export class VideoContainer extends LargeContainer {
this._updateBackground();
const { horizontalIndent, verticalIndent }
= this.getVideoPosition(width, height, containerWidth, containerHeight);
= this.getVideoPosition(width, height, containerWidth, containerHeight, verticalFilmstripWidth);
this.$wrapper.animate({
width,

884
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,8 @@
"@atlaskit/theme": "11.0.2",
"@atlaskit/toggle": "12.0.3",
"@atlaskit/tooltip": "17.1.2",
"@giphy/js-fetch-api": "4.1.2",
"@giphy/react-components": "5.6.0",
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -53,6 +55,10 @@
"@react-navigation/native": "6.0.6",
"@react-navigation/stack": "6.0.11",
"@svgr/webpack": "4.3.2",
"@tensorflow-models/blazeface": "0.0.7",
"@tensorflow/tfjs-backend-wasm": "3.13.0",
"@tensorflow/tfjs-converter": "3.13.0",
"@tensorflow/tfjs-core": "3.13.0",
"@vladmandic/face-api": "1.6.4",
"@xmldom/xmldom": "0.7.5",
"amplitude-js": "8.2.1",
@@ -72,7 +78,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1389.0.0+313e0dd3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1401.0.0+584a8680/lib-jitsi-meet.tgz",
"libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",

View File

@@ -1,5 +1,40 @@
diff --git a/node_modules/react-native-dialog/lib/Button.js b/node_modules/react-native-dialog/lib/Button.js
index 19eeb22..b8a66f4 100644
--- a/node_modules/react-native-dialog/lib/Button.js
+++ b/node_modules/react-native-dialog/lib/Button.js
@@ -50,7 +50,7 @@ const buildStyles = (isDark) => StyleSheet.create({
backgroundColor: "transparent",
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "link_text_dark" : "link_text_dark_light"}`),
+ color: isDark ? '#BFC7C7C7' : '#BF727272',
textAlign: "center",
backgroundColor: "transparent",
padding: 8,
diff --git a/node_modules/react-native-dialog/lib/CodeInput.js b/node_modules/react-native-dialog/lib/CodeInput.js
index eceae56..cc4339d 100644
--- a/node_modules/react-native-dialog/lib/CodeInput.js
+++ b/node_modules/react-native-dialog/lib/CodeInput.js
@@ -97,7 +97,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 20,
},
default: {},
@@ -107,7 +107,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 14,
},
default: {},
diff --git a/node_modules/react-native-dialog/lib/Container.js b/node_modules/react-native-dialog/lib/Container.js
index 69e3764..109126f 100644
index 69e3764..d7569fb 100644
--- a/node_modules/react-native-dialog/lib/Container.js
+++ b/node_modules/react-native-dialog/lib/Container.js
@@ -82,7 +82,7 @@ DialogContainer.propTypes = {
@@ -16,7 +51,68 @@ index 69e3764..109126f 100644
},
android: {
- backgroundColor: PlatformColor("?attr/colorBackgroundFloating"),
+ backgroundColor: PlatformColor(`@android:color/${isDark ? "background_dark" : "background_light"}`),
+ backgroundColor: isDark ? '#212121' : '#FFFFFF',
flexDirection: "column",
borderRadius: 3,
padding: 16,
diff --git a/node_modules/react-native-dialog/lib/Description.js b/node_modules/react-native-dialog/lib/Description.js
index 2da9ed3..248ac2f 100644
--- a/node_modules/react-native-dialog/lib/Description.js
+++ b/node_modules/react-native-dialog/lib/Description.js
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
marginTop: 4,
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "secondary_text_dark" : "secondary_text_light"}`),
+ color: isDark ? '#C7C7C7' : '#727272',
fontSize: 16,
marginTop: 10,
},
diff --git a/node_modules/react-native-dialog/lib/Input.js b/node_modules/react-native-dialog/lib/Input.js
index b33a1a0..063d7f8 100644
--- a/node_modules/react-native-dialog/lib/Input.js
+++ b/node_modules/react-native-dialog/lib/Input.js
@@ -48,7 +48,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontSize: 14,
},
default: {},
@@ -58,7 +58,7 @@ const buildStyles = (isDark) => StyleSheet.create({
color: PlatformColor("label"),
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
marginLeft: -4,
paddingLeft: 4,
},
diff --git a/node_modules/react-native-dialog/lib/Switch.js b/node_modules/react-native-dialog/lib/Switch.js
index 26a05ca..05114fa 100644
--- a/node_modules/react-native-dialog/lib/Switch.js
+++ b/node_modules/react-native-dialog/lib/Switch.js
@@ -52,7 +52,7 @@ const buildStyles = (isDark) => StyleSheet.create({
flex: 1,
paddingRight: 8,
fontSize: 16,
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
},
default: {},
}),
diff --git a/node_modules/react-native-dialog/lib/Title.js b/node_modules/react-native-dialog/lib/Title.js
index 1c6fd87..b5511cc 100644
--- a/node_modules/react-native-dialog/lib/Title.js
+++ b/node_modules/react-native-dialog/lib/Title.js
@@ -28,7 +28,7 @@ const buildStyles = (isDark) => StyleSheet.create({
fontWeight: "600",
},
android: {
- color: PlatformColor(`@android:color/${isDark ? "primary_text_dark" : "primary_text_light"}`),
+ color: isDark ? '#FAFAFA' : '#212121',
fontWeight: "500",
fontSize: 18,
},

View File

@@ -7,6 +7,8 @@ module.exports = {
'.eslintrc-react-native.js'
],
'rules': {
'flowtype/no-types-missing-file-annotation': 0,
// XXX remove this eventually.
'react/jsx-indent-props': 0
},

View File

@@ -899,3 +899,15 @@ export function createBreakoutRoomsEvent(actionSubject) {
source: 'breakout.rooms'
};
}
/**
* Creates and event which indicates a GIF was sent.
*
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createGifSentEvent() {
return {
action: 'gif.sent'
};
}

View File

@@ -0,0 +1,88 @@
// @flow
import type { Dispatch } from 'redux';
import { getLocationContextRoot } from '../base/util';
import { addTrackStateToURL } from './functions.any';
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
*
* @param {(string)} pathname - The path to navigate to.
* @returns {Function}
*/
export function redirectWithStoredParams(pathname: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const newLocationURL = new URL(locationURL.href);
newLocationURL.pathname = pathname;
window.location.assign(newLocationURL.toString());
};
}
/**
* Assigns a specific pathname to window.location.pathname taking into account
* the context root of the Web app.
*
* @param {string} pathname - The pathname to assign to
* window.location.pathname. If the specified pathname is relative, the context
* root of the Web app will be prepended to the specified pathname before
* assigning it to window.location.pathname.
* @param {string} hashParam - Optional hash param to assign to
* window.location.hash.
* @returns {Function}
*/
export function redirectToStaticPage(pathname: string, hashParam: ?string) {
return () => {
const windowLocation = window.location;
let newPathname = pathname;
if (!newPathname.startsWith('/')) {
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
newPathname.startsWith('./')
&& (newPathname = newPathname.substring(2));
newPathname = getLocationContextRoot(windowLocation) + newPathname;
}
if (hashParam) {
windowLocation.hash = hashParam;
}
windowLocation.pathname = newPathname;
};
}
/**
* Reloads the page by restoring the original URL.
*
* @returns {Function}
*/
export function reloadWithStoredParams() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted states.
const newURL = addTrackStateToURL(locationURL, state);
const windowLocation = window.location;
const oldSearchString = windowLocation.search;
windowLocation.replace(newURL.toString());
if (newURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will
// be changed!
// location.replace will not trigger redirect/reload when
// only the hash params are changed. That's why we need to call
// reload in addition to replace.
windowLocation.reload();
}
};
}

View File

@@ -0,0 +1,154 @@
// @flow
import type { Dispatch } from 'redux';
import { setRoom } from '../base/conference';
import {
configWillLoad,
createFakeConfig,
loadConfigError,
restoreConfig,
setConfig,
storeConfig
} from '../base/config';
import { connect, disconnect, setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { createDesiredLocalTracks } from '../base/tracks';
import {
getBackendSafeRoomName,
parseURIString,
toURLString
} from '../base/util';
import { navigateRoot } from '../mobile/navigation/rootNavigationContainerRef';
import { screen } from '../mobile/navigation/routes';
import { setFatalError } from '../overlay';
import { getDefaultURL } from './functions';
import { addTrackStateToURL } from './functions.native';
import logger from './logger';
export * from './actions.any';
/**
* Triggers an in-app navigation to a specific route. Allows navigation to be
* abstracted between the mobile/React Native and Web/React applications.
*
* @param {string|undefined} uri - The URI to which to navigate. It may be a
* full URL with an HTTP(S) scheme, a full or partial URI with the app-specific
* scheme, or a mere room name.
* @returns {Function}
*/
export function appNavigate(uri: ?string) {
return async (dispatch: Dispatch<any>, getState: Function) => {
let location = parseURIString(uri);
// If the specified location (URI) does not identify a host, use the app's
// default.
if (!location || !location.host) {
const defaultLocation = parseURIString(getDefaultURL(getState));
if (location) {
location.host = defaultLocation.host;
// FIXME Turn location's host, hostname, and port properties into
// setters in order to reduce the risks of inconsistent state.
location.hostname = defaultLocation.hostname;
location.pathname
= defaultLocation.pathname + location.pathname.substr(1);
location.port = defaultLocation.port;
location.protocol = defaultLocation.protocol;
} else {
location = defaultLocation;
}
}
location.protocol || (location.protocol = 'https:');
const { contextRoot, host, room } = location;
const locationURL = new URL(location.toString());
if (room) {
navigateRoot(screen.connecting);
}
dispatch(disconnect());
dispatch(configWillLoad(locationURL, room));
let protocol = location.protocol.toLowerCase();
// The React Native app supports an app-specific scheme which is sure to not
// be supported by fetch.
protocol !== 'http:' && protocol !== 'https:' && (protocol = 'https:');
const baseURL = `${protocol}//${host}${contextRoot || '/'}`;
let url = `${baseURL}config.js`;
// XXX In order to support multiple shards, tell the room to the deployment.
room && (url += `?room=${getBackendSafeRoomName(room)}`);
let config;
// Avoid (re)loading the config when there is no room.
if (!room) {
config = restoreConfig(baseURL);
}
if (!config) {
try {
config = await loadConfig(url);
dispatch(storeConfig(baseURL, config));
} catch (error) {
config = restoreConfig(baseURL);
if (!config) {
if (room) {
dispatch(loadConfigError(error, locationURL));
return;
}
// If there is no room (we are on the welcome page), don't fail, just create a fake one.
logger.warn('Failed to load config but there is no room, applying a fake one');
config = createFakeConfig(baseURL);
}
}
}
if (getState()['features/base/config'].locationURL !== locationURL) {
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL));
return;
}
dispatch(setLocationURL(locationURL));
dispatch(setConfig(config));
dispatch(setRoom(room));
if (room) {
dispatch(createDesiredLocalTracks());
dispatch(connect());
}
};
}
/**
* Reloads the page.
*
* @protected
* @returns {Function}
*/
export function reloadNow() {
return (dispatch: Dispatch<Function>, getState: Function) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted state after the reload.
const newURL = addTrackStateToURL(locationURL, state);
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(appNavigate(toURLString(newURL)));
};
}

View File

@@ -2,7 +2,7 @@
import type { Dispatch } from 'redux';
import { API_ID } from '../../../modules/API/constants';
import { API_ID } from '../../../modules/API';
import { setRoom } from '../base/conference';
import {
configWillLoad,
@@ -12,30 +12,33 @@ import {
setConfig,
storeConfig
} from '../base/config';
import { connect, disconnect, setLocationURL } from '../base/connection';
import { setLocationURL } from '../base/connection';
import { loadConfig } from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media';
import { toState } from '../base/redux';
import { createDesiredLocalTracks, isLocalCameraTrackMuted, isLocalTrackMuted } from '../base/tracks';
import {
addHashParamsToURL,
getBackendSafeRoomName,
getLocationContextRoot,
parseURIString,
toURLString
parseURIString
} from '../base/util';
import { isVpaasMeeting } from '../jaas/functions';
import { NOTIFICATION_TIMEOUT_TYPE, clearNotifications, showNotification } from '../notifications';
import {
clearNotifications,
NOTIFICATION_TIMEOUT_TYPE,
showNotification
} from '../notifications';
import { setFatalError } from '../overlay';
import {
getDefaultURL,
getName
} from './functions';
redirectToStaticPage,
redirectWithStoredParams,
reloadWithStoredParams
} from './actions.any';
import { getDefaultURL, getName } from './functions';
import logger from './logger';
declare var interfaceConfig: Object;
export * from './actions.any';
/**
* Triggers an in-app navigation to a specific route. Allows navigation to be
@@ -74,12 +77,6 @@ export function appNavigate(uri: ?string) {
const { contextRoot, host, room } = location;
const locationURL = new URL(location.toString());
// Disconnect from any current conference.
// FIXME: unify with web.
if (navigator.product === 'ReactNative') {
dispatch(disconnect());
}
// There are notifications now that gets displayed after we technically left
// the conference, but we're still on the conference screen.
dispatch(clearNotifications());
@@ -135,137 +132,6 @@ export function appNavigate(uri: ?string) {
dispatch(setLocationURL(locationURL));
dispatch(setConfig(config));
dispatch(setRoom(room));
// FIXME: unify with web, currently the connection and track creation happens in conference.js.
if (room && navigator.product === 'ReactNative') {
dispatch(createDesiredLocalTracks());
dispatch(connect());
}
};
}
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
*
* @param {(string)} pathname - The path to navigate to.
* @returns {Function}
*/
export function redirectWithStoredParams(pathname: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const { locationURL } = getState()['features/base/connection'];
const newLocationURL = new URL(locationURL.href);
newLocationURL.pathname = pathname;
window.location.assign(newLocationURL.toString());
};
}
/**
* Assigns a specific pathname to window.location.pathname taking into account
* the context root of the Web app.
*
* @param {string} pathname - The pathname to assign to
* window.location.pathname. If the specified pathname is relative, the context
* root of the Web app will be prepended to the specified pathname before
* assigning it to window.location.pathname.
* @param {string} hashParam - Optional hash param to assign to
* window.location.hash.
* @returns {Function}
*/
export function redirectToStaticPage(pathname: string, hashParam: ?string) {
return () => {
const windowLocation = window.location;
let newPathname = pathname;
if (!newPathname.startsWith('/')) {
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
newPathname.startsWith('./')
&& (newPathname = newPathname.substring(2));
newPathname = getLocationContextRoot(windowLocation) + newPathname;
}
if (hashParam) {
windowLocation.hash = hashParam;
}
windowLocation.pathname = newPathname;
};
}
/**
* Reloads the page.
*
* @protected
* @returns {Function}
*/
export function reloadNow() {
return (dispatch: Dispatch<Function>, getState: Function) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted state after the reload.
const newURL = addTrackStateToURL(locationURL, state);
logger.info(`Reloading the conference using URL: ${locationURL}`);
if (navigator.product === 'ReactNative') {
dispatch(appNavigate(toURLString(newURL)));
} else {
dispatch(reloadWithStoredParams());
}
};
}
/**
* Adds the current track state to the passed URL.
*
* @param {URL} url - The URL that will be modified.
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @returns {URL} - Returns the modified URL.
*/
function addTrackStateToURL(url, stateful) {
const state = toState(stateful);
const tracks = state['features/base/tracks'];
const isVideoMuted = isLocalCameraTrackMuted(tracks);
const isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
return addHashParamsToURL(new URL(url), { // use new URL object in order to not pollute the passed parameter.
'config.startWithAudioMuted': isAudioMuted,
'config.startWithVideoMuted': isVideoMuted
});
}
/**
* Reloads the page by restoring the original URL.
*
* @returns {Function}
*/
export function reloadWithStoredParams() {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
const { locationURL } = state['features/base/connection'];
// Preserve the local tracks muted states.
const newURL = addTrackStateToURL(locationURL, state);
const windowLocation = window.location;
const oldSearchString = windowLocation.search;
windowLocation.replace(newURL.toString());
if (newURL.search === oldSearchString) {
// NOTE: Assuming that only the hash or search part of the URL will
// be changed!
// location.replace will not trigger redirect/reload when
// only the hash params are changed. That's why we need to call
// reload in addition to replace.
windowLocation.reload();
}
};
}
@@ -341,3 +207,22 @@ export function maybeRedirectToWelcomePage(options: Object = {}) {
}
};
}
/**
* Reloads the page.
*
* @protected
* @returns {Function}
*/
export function reloadNow() {
return (dispatch: Dispatch<Function>, getState: Function) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(reloadWithStoredParams());
};
}

View File

@@ -0,0 +1,24 @@
import { MEDIA_TYPE } from '../base/media';
import { toState } from '../base/redux';
import { isLocalCameraTrackMuted, isLocalTrackMuted } from '../base/tracks';
import { addHashParamsToURL } from '../base/util';
/**
* Adds the current track state to the passed URL.
*
* @param {URL} url - The URL that will be modified.
* @param {Function|Object} stateful - The redux store or {@code getState} function.
* @returns {URL} - Returns the modified URL.
*/
export function addTrackStateToURL(url, stateful) {
const state = toState(stateful);
const tracks = state['features/base/tracks'];
const isVideoMuted = isLocalCameraTrackMuted(tracks);
const isAudioMuted = isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO);
return addHashParamsToURL(new URL(url), { // use new URL object in order to not pollute the passed parameter.
'config.startWithAudioMuted': isAudioMuted,
'config.startWithVideoMuted': isVideoMuted
});
}

View File

@@ -5,6 +5,8 @@ import { NativeModules } from 'react-native';
import { toState } from '../base/redux';
import { getServerURL } from '../base/settings';
export * from './functions.any';
/**
* Retrieves the default URL for the app. This can either come from a prop to
* the root App component or be configured in the settings.

View File

@@ -3,6 +3,8 @@
import { toState } from '../base/redux';
import { getServerURL } from '../base/settings';
export * from './functions.any';
declare var interfaceConfig: Object;
/**

View File

@@ -30,6 +30,7 @@ import '../display-name/middleware';
import '../etherpad/middleware';
import '../filmstrip/middleware';
import '../follow-me/middleware';
import '../gifs/middleware';
import '../invite/middleware';
import '../jaas/middleware';
import '../large-video/middleware';

View File

@@ -20,6 +20,7 @@ import '../shared-video/middleware';
import '../settings/middleware';
import '../talk-while-muted/middleware';
import '../virtual-background/middleware';
import '../face-centering/middleware';
import '../facial-recognition/middleware';
import './middlewares.any';

View File

@@ -33,6 +33,7 @@ import '../dynamic-branding/reducer';
import '../etherpad/reducer';
import '../filmstrip/reducer';
import '../follow-me/reducer';
import '../gifs/reducer';
import '../google-api/reducer';
import '../invite/reducer';
import '../jaas/reducer';

View File

@@ -2,6 +2,7 @@
import '../base/devices/reducer';
import '../e2ee/reducer';
import '../face-centering/reducer';
import '../facial-recognition/reducer';
import '../feedback/reducer';
import '../local-recording/reducer';

View File

@@ -44,6 +44,11 @@ type Props = {
*/
_error: Object,
/**
* Extra handler for cancel functionality.
*/
_onCancel: Function,
/**
* The progress in the floating range between 0 and 1 of the authenticating
* and upgrading the role of the local participant/user.
@@ -63,7 +68,12 @@ type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function
t: Function,
/**
* Override the default visibility.
*/
visible: boolean
};
/**
@@ -110,6 +120,10 @@ type State = {
* of the configuration parameters.
*/
class LoginDialog extends Component<Props, State> {
static defaultProps = {
visible: true
};
/**
* Initializes a new LoginDialog instance.
*
@@ -140,13 +154,14 @@ class LoginDialog extends Component<Props, State> {
render() {
const {
_connecting: connecting,
t
t,
visible
} = this.props;
return (
<View>
<Dialog.Container
visible = { true }>
visible = { visible }>
<Dialog.Title>
{ t('dialog.login') }
</Dialog.Title>
@@ -280,7 +295,10 @@ class LoginDialog extends Component<Props, State> {
* @returns {void}
*/
_onCancel() {
this.props.dispatch(cancelLogin());
const { _onCancel, dispatch } = this.props;
_onCancel && _onCancel();
dispatch(cancelLogin());
}
_onLogin: () => void;

View File

@@ -6,7 +6,9 @@ import type { Dispatch } from 'redux';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { openLoginDialog, cancelWaitForOwner } from '../../actions.native';
import { cancelWaitForOwner } from '../../actions.native';
import LoginDialog from './LoginDialog';
/**
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
@@ -40,9 +42,14 @@ class WaitForOwnerDialog extends Component<Props> {
constructor(props) {
super(props);
this.state = {
showLoginDialog: false
};
// Bind event handlers so they are only bound once per instance.
this._onCancel = this._onCancel.bind(this);
this._onLogin = this._onLogin.bind(this);
this._onLoginDialogCancel = this._onLoginDialogCancel.bind(this);
}
/**
@@ -58,7 +65,12 @@ class WaitForOwnerDialog extends Component<Props> {
confirmLabel = 'dialog.IamHost'
descriptionKey = 'dialog.WaitForHostMsg'
onCancel = { this._onCancel }
onSubmit = { this._onLogin } />
onSubmit = { this._onLogin }>
<LoginDialog
// eslint-disable-next-line react/jsx-handler-names
_onCancel = { this._onLoginDialogCancel }
visible = { this.state.showLoginDialog } />
</ConfirmDialog>
);
}
@@ -83,7 +95,17 @@ class WaitForOwnerDialog extends Component<Props> {
* @returns {void}
*/
_onLogin() {
this.props.dispatch(openLoginDialog());
this.setState({ showLoginDialog: true });
}
/**
* Called when the nested login dialog is cancelled.
*
* @private
* @returns {void}
*/
_onLoginDialogCancel() {
this.setState({ showLoginDialog: false });
}
}

View File

@@ -62,13 +62,14 @@ MiddlewareRegistry.register(store => next => action => {
// Go back to the app's entry point.
_hideLoginDialog(store);
const { authRequired, conference } = getState()['features/base/conference'];
const state = getState();
const { authRequired, conference } = state['features/base/conference'];
const { passwordRequired } = state['features/base/connection'];
// Only end the meeting if we are not already inside and trying to upgrade.
if (authRequired && !conference) {
// FIXME Like cancelWaitForOwner, dispatch conferenceLeft to notify
// the external-api.
// NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
// connection auth error.
if ((passwordRequired || authRequired) && !conference) {
dispatch(appNavigate(undefined));
}
}

View File

@@ -26,6 +26,7 @@ export const CS_MODERATION_NOTIFICATION_ID = 'screensharing-moderation';
export const MODERATION_NOTIFICATIONS = {
[MEDIA_TYPE.AUDIO]: AUDIO_MODERATION_NOTIFICATION_ID,
[MEDIA_TYPE.SCREENSHARE]: CS_MODERATION_NOTIFICATION_ID,
[MEDIA_TYPE.VIDEO]: VIDEO_MODERATION_NOTIFICATION_ID,
[MEDIA_TYPE.PRESENTER]: CS_MODERATION_NOTIFICATION_ID
};

View File

@@ -3,6 +3,7 @@
import { StyleSheet } from 'react-native';
import { ColorPalette } from '../../../styles';
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
const DEFAULT_SIZE = 65;
@@ -34,16 +35,16 @@ export default {
switch (status) {
case 'available':
color = 'rgb(110, 176, 5)';
color = PRESENCE_AVAILABLE_COLOR;
break;
case 'away':
color = 'rgb(250, 201, 20)';
color = PRESENCE_AWAY_COLOR;
break;
case 'busy':
color = 'rgb(233, 0, 27)';
color = PRESENCE_BUSY_COLOR;
break;
case 'idle':
color = 'rgb(172, 172, 172)';
color = PRESENCE_IDLE_COLOR;
break;
}

View File

@@ -0,0 +1,5 @@
// Colors for avatar status badge
export const PRESENCE_AVAILABLE_COLOR = 'rgb(110, 176, 5)';
export const PRESENCE_AWAY_COLOR = 'rgb(250, 201, 20)';
export const PRESENCE_BUSY_COLOR = 'rgb(233, 0, 27)';
export const PRESENCE_IDLE_COLOR = 'rgb(172, 172, 172)';

View File

@@ -1,12 +1,20 @@
// @flow
import { withStyles } from '@material-ui/core/styles';
import clsx from 'clsx';
import React from 'react';
import { Icon } from '../../../icons';
import AbstractStatelessAvatar, { type Props as AbstractProps } from '../AbstractStatelessAvatar';
import { PRESENCE_AVAILABLE_COLOR, PRESENCE_AWAY_COLOR, PRESENCE_BUSY_COLOR, PRESENCE_IDLE_COLOR } from '../styles';
type Props = AbstractProps & {
/**
* An object containing the CSS classes.
*/
classes: Object,
/**
* External class name passed through props.
*/
@@ -38,11 +46,77 @@ type Props = AbstractProps & {
useCORS?: ?boolean
};
/**
* Creates the styles for the component.
*
* @returns {Object}
*/
const styles = () => {
return {
avatar: {
backgroundColor: '#AAA',
borderRadius: '50%',
color: 'rgba(255, 255, 255, 1)',
fontWeight: '100',
objectFit: 'cover',
'&.avatar-small': {
height: '28px !important',
width: '28px !important'
},
'&.avatar-xsmall': {
height: '16px !important',
width: '16px !important'
},
'& .jitsi-icon': {
transform: 'translateY(50%)'
},
'& .avatar-svg': {
height: '100%',
width: '100%'
}
},
badge: {
position: 'relative',
'&.avatar-badge:after': {
borderRadius: '50%',
content: '""',
display: 'block',
height: '35%',
position: 'absolute',
bottom: 0,
width: '35%'
},
'&.avatar-badge-available:after': {
backgroundColor: PRESENCE_AVAILABLE_COLOR
},
'&.avatar-badge-away:after': {
backgroundColor: PRESENCE_AWAY_COLOR
},
'&.avatar-badge-busy:after': {
backgroundColor: PRESENCE_BUSY_COLOR
},
'&.avatar-badge-idle:after': {
backgroundColor: PRESENCE_IDLE_COLOR
}
}
};
};
/**
* Implements a stateless avatar component that renders an avatar purely from what gets passed through
* props.
*/
export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
class StatelessAvatar extends AbstractStatelessAvatar<Props> {
/**
* Instantiates a new {@code Component}.
@@ -66,7 +140,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
if (this._isIcon(url)) {
return (
<div
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
data-testid = { this.props.testId }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
@@ -96,7 +170,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
if (initials) {
return (
<div
className = { `${this._getAvatarClassName()} ${this._getBadgeClassName()}` }
className = { clsx(this._getAvatarClassName(), this._getBadgeClassName()) }
data-testid = { this.props.testId }
id = { this.props.id }
style = { this._getAvatarStyle(this.props.color) }>
@@ -157,7 +231,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
* @returns {string}
*/
_getAvatarClassName(additional) {
return `avatar ${additional || ''} ${this.props.className || ''}`;
return clsx('avatar', additional, this.props.className, this.props.classes.avatar);
}
/**
@@ -169,7 +243,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
const { status } = this.props;
if (status) {
return `avatar-badge avatar-badge-${status}`;
return clsx('avatar-badge', `avatar-badge-${status}`, this.props.classes.badge);
}
return '';
@@ -192,3 +266,5 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
}
}
}
export default withStyles(styles)(StatelessAvatar);

View File

@@ -17,6 +17,8 @@ export default {
'Chat': {
displayName: 'rgb(94, 109, 121)',
localMsgBackground: 'rgb(215, 230, 249)',
lobbyMsgBackground: 'rgb(106, 80, 211)',
lobbyMsgNotice: 'rgb(16, 10, 41)',
privateMsgBackground: 'rgb(250, 219, 219)',
privateMsgNotice: 'rgb(186, 39, 58)',
remoteMsgBackground: 'rgb(241, 242, 246)',

View File

@@ -13,7 +13,11 @@ import {
participantLeft
} from '../participants';
import { toState } from '../redux';
import { getBackendSafePath, getJitsiMeetGlobalNS, safeDecodeURIComponent } from '../util';
import {
getBackendSafePath,
getJitsiMeetGlobalNS,
safeDecodeURIComponent
} from '../util';
import {
AVATAR_URL_COMMAND,

View File

@@ -9,8 +9,13 @@ import {
sendAnalytics
} from '../../analytics';
import { reloadNow } from '../../app/actions';
import { removeLobbyChatParticipant } from '../../chat/actions.any';
import { openDisplayNamePrompt } from '../../display-name';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification } from '../../notifications';
import {
NOTIFICATION_TIMEOUT_TYPE,
showErrorNotification
} from '../../notifications';
import { showSalesforceNotification } from '../../salesforce';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
import { validateJwt } from '../jwt';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
@@ -210,7 +215,12 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
const result = next(action);
const { conference } = action;
const { pendingSubjectChange } = getState()['features/base/conference'];
const { requireDisplayName, disableBeforeUnloadHandlers = false } = getState()['features/base/config'];
const {
disableBeforeUnloadHandlers = false,
requireDisplayName
} = getState()['features/base/config'];
dispatch(removeLobbyChatParticipant(true));
pendingSubjectChange && dispatch(setSubject(pendingSubjectChange));
@@ -230,6 +240,9 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
dispatch(openDisplayNamePrompt(undefined));
}
dispatch(showSalesforceNotification());
return result;
}

View File

@@ -1,13 +1,30 @@
// @flow
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
import { showNotification, NOTIFICATION_TIMEOUT_TYPE } from '../../notifications';
import { setSkipPrejoinOnReload } from '../../prejoin';
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share';
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
import { setAudioOnly } from '../audio-only';
import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
import { JitsiConferenceErrors, JitsiTrackErrors } from '../lib-jitsi-meet';
import { MEDIA_TYPE, setScreenshareMuted, VIDEO_TYPE } from '../media';
import { MiddlewareRegistry } from '../redux';
import {
addLocalTrack,
createLocalTracksF,
getLocalDesktopTrack,
getLocalJitsiAudioTrack,
replaceLocalTrack,
TOGGLE_SCREENSHARING
} from '../tracks';
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes';
import { getCurrentConference } from './functions';
import './middleware.any';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
MiddlewareRegistry.register(store => next => action => {
const { dispatch, getState } = store;
const { enableForcedReload } = getState()['features/base/config'];
switch (action.type) {
@@ -25,7 +42,153 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
break;
}
case TOGGLE_SCREENSHARING: {
getMultipleVideoSupportFeatureFlag(getState()) && _toggleScreenSharing(action, store);
break;
}
}
return next(action);
});
/**
* Displays a UI notification for screensharing failure based on the error passed.
*
* @private
* @param {Object} error - The error.
* @param {Object} store - The redux store.
* @returns {void}
*/
function _handleScreensharingError(error, { dispatch }) {
if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
return;
}
let descriptionKey, titleKey;
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedTitle';
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
descriptionKey = 'dialog.cameraConstraintFailedError';
titleKey = 'deviceError.cameraError';
} else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
descriptionKey = 'dialog.screenSharingFailed';
titleKey = 'dialog.screenSharingFailedTitle';
} else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
descriptionKey = 'notify.screenShareNoAudio';
titleKey = 'notify.screenShareNoAudioTitle';
}
dispatch(showNotification({
titleKey,
descriptionKey
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
/**
* Applies the AudioMixer effect on the local audio track if applicable. If there is no local audio track, the desktop
* audio track is added to the conference.
*
* @private
* @param {JitsiLocalTrack} desktopAudioTrack - The audio track to be added to the conference.
* @param {*} state - The redux state.
* @returns {void}
*/
async function _maybeApplyAudioMixerEffect(desktopAudioTrack, state) {
const localAudio = getLocalJitsiAudioTrack(state);
const conference = getCurrentConference(state);
if (localAudio) {
// If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing API.
const mixerEffect = new AudioMixerEffect(desktopAudioTrack);
await localAudio.setEffect(mixerEffect);
} else {
// If no local stream is present ( i.e. no input audio devices) we use the screen share audio
// stream as we would use a regular stream.
await conference.replaceTrack(null, desktopAudioTrack);
}
}
/**
* Toggles screen sharing.
*
* @private
* @param {boolean} enabled - The state to toggle screen sharing to.
* @param {Store} store - The redux store.
* @returns {void}
*/
async function _toggleScreenSharing({ enabled, audioOnly = false }, store) {
const { dispatch, getState } = store;
const state = getState();
const conference = getCurrentConference(state);
const localAudio = getLocalJitsiAudioTrack(state);
const localScreenshare = getLocalDesktopTrack(state['features/base/tracks']);
if (enabled) {
let tracks;
try {
tracks = await createLocalTracksF({ devices: [ VIDEO_TYPE.DESKTOP ] });
} catch (error) {
_handleScreensharingError(error, store);
return;
}
const desktopAudioTrack = tracks.find(track => track.getType() === MEDIA_TYPE.AUDIO);
const desktopVideoTrack = tracks.find(track => track.getType() === MEDIA_TYPE.VIDEO);
// Dispose the desktop track for audio-only screensharing.
if (audioOnly) {
desktopVideoTrack.dispose();
if (!desktopAudioTrack) {
_handleScreensharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, store);
return;
}
} else if (desktopVideoTrack) {
if (localScreenshare) {
await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
} else {
await dispatch(addLocalTrack(desktopVideoTrack));
}
}
// Apply the AudioMixer effect if there is a local audio track, add the desktop track to the conference
// otherwise without unmuting the microphone.
if (desktopAudioTrack) {
_maybeApplyAudioMixerEffect(desktopAudioTrack, state);
dispatch(setScreenshareAudioTrack(desktopAudioTrack));
}
// Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
// audio-only screensharing.
const { enabled: bestPerformanceMode } = state['features/base/audio-only'];
if (bestPerformanceMode && !audioOnly) {
dispatch(setAudioOnly(false));
}
} else {
const { desktopAudioTrack } = state['features/screen-share'];
// Mute the desktop track instead of removing it from the conference since we don't want the client to signal
// a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled again, the
// same sender will be re-used without the need for signaling a new ssrc through source-add.
dispatch(setScreenshareMuted(true));
if (desktopAudioTrack) {
if (localAudio) {
localAudio.setEffect(undefined);
} else {
await conference.replaceTrack(desktopAudioTrack, null);
}
desktopAudioTrack.dispose();
dispatch(setScreenshareAudioTrack(null));
}
}
if (audioOnly) {
dispatch(setScreenAudioShareState(enabled));
}
}

View File

@@ -137,7 +137,7 @@ function _authStatusChanged(state, { authEnabled, authLogin }) {
*/
function _conferenceFailed(state, { conference, error }) {
// The current (similar to getCurrentConference in
// base/conference/functions.js) conference which is joining or joined:
// base/conference/functions.any.js) conference which is joining or joined:
const conference_ = state.conference || state.joining;
if (conference_ && conference_ !== conference) {

View File

@@ -81,7 +81,6 @@ export default [
'brandingRoomAlias',
'debug',
'debugAudioLevels',
'defaultLanguage',
'defaultLocalDisplayName',
'defaultRemoteDisplayName',
'desktopSharingFrameRate',
@@ -142,6 +141,7 @@ export default [
'enableInsecureRoomNameWarning',
'enableLayerSuspension',
'enableLipSync',
'enableLobbyChat',
'enableOpusRed',
'enableRemb',
'enableSaveLogs',
@@ -152,6 +152,7 @@ export default [
'enableTcc',
'enableAutomaticUrlCopy',
'etherpad_base',
'faceCoordinatesSharing',
'failICE',
'feedbackPercentage',
'fileRecordingsEnabled',
@@ -160,6 +161,7 @@ export default [
'forceJVB121Ratio',
'forceTurnRelay',
'gatherStats',
'giphy',
'googleApiApplicationClientID',
'hiddenPremeetingButtons',
'hideConferenceSubject',
@@ -199,6 +201,7 @@ export default [
'readOnlyName',
'replaceParticipant',
'resolution',
'salesforceUrl',
'screenshotCapture',
'startAudioMuted',
'startAudioOnly',
@@ -213,6 +216,7 @@ export default [
'testing',
'toolbarButtons',
'toolbarConfig',
'tileView',
'transcribingEnabled',
'useHostPageLocalStorage',
'useTurnUdp',

View File

@@ -26,7 +26,9 @@ export const TOOLBAR_BUTTONS = [
'fullscreen',
'hangup',
'help',
'highlight',
'invite',
'linktosalesforce',
'livestreaming',
'microphone',
'mute-everyone',
@@ -65,5 +67,6 @@ export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-bac
*/
export const FEATURE_FLAGS = {
MULTIPLE_VIDEO_STREAMS_SUPPORT: 'sendMultipleVideoStreams',
SOURCE_NAME_SIGNALING: 'sourceNameSignaling'
};

View File

@@ -4,6 +4,7 @@ import Bourne from '@hapi/bourne';
import { jitsiLocalStorage } from '@jitsi/js-utils';
import _ from 'lodash';
import { browser } from '../lib-jitsi-meet';
import { parseURLParams } from '../util';
import CONFIG_WHITELIST from './configWhitelist';
@@ -12,7 +13,7 @@ import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
import logger from './logger';
// XXX The function getRoomName is split out of
// functions.js because it is bundled in both app.bundle and
// functions.any.js because it is bundled in both app.bundle and
// do_external_connect, webpack 1 does not support tree shaking, and we don't
// want all functions to be bundled in do_external_connect.
export { default as getRoomName } from './getRoomName';
@@ -49,6 +50,18 @@ export function getMeetingRegion(state: Object) {
return state['features/base/config']?.deploymentInfo?.region || '';
}
/**
* Selector used to get the sendMultipleVideoStreams feature flag.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getMultipleVideoSupportFeatureFlag(state: Object) {
return getFeatureFlag(state, FEATURE_FLAGS.MULTIPLE_VIDEO_STREAMS_SUPPORT)
&& getSourceNameSignalingFeatureFlag(state)
&& isUnifiedPlanEnabled(state);
}
/**
* Selector used to get the sourceNameSignaling feature flag.
*
@@ -196,6 +209,19 @@ export function isDisplayNameVisible(state: Object): boolean {
return !state['features/base/config'].hideDisplayName;
}
/**
* Selector for determining if Unified plan support is enabled.
*
* @param {Object} state - The state of the app.
* @returns {boolean}
*/
export function isUnifiedPlanEnabled(state: Object): boolean {
const { enableUnifiedOnChrome = true } = state['features/base/config'];
return browser.supportsUnifiedPlan()
&& (!browser.isChromiumBased() || (browser.isChromiumBased() && enableUnifiedOnChrome));
}
/**
* Restores a Jitsi Meet config.js from {@code localStorage} if it was
* previously downloaded from a specific {@code baseURL} and stored with

View File

@@ -156,7 +156,7 @@ function _connectionWillConnect(
}
/**
* The current (similar to getCurrentConference in base/conference/functions.js)
* The current (similar to getCurrentConference in base/conference/functions.any.js)
* connection which is {@code connection} or {@code connecting}.
*
* @param {Object} baseConnectionState - The current state of the

View File

@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M0 3C0 1.34315 1.34315 0 3 0H37C38.6569 0 40 1.34315 40 3V37C40 38.6569 38.6569 40 37 40H3C1.34315 40 0 38.6569 0 37V3Z" fill="#818DDB"/>
<path d="M30.9976 20.1427C31.0414 19.399 30.4727 19.1803 30.2539 19.1803H22.3798C21.6799 19.1803 21.5924 19.924 21.5924 19.9677V28.498H30.9976V20.1427ZM25.5732 26.267C25.5732 26.6607 25.267 27.0107 24.8296 27.0107H24.0859C23.6922 27.0107 23.3422 26.6607 23.3422 26.267V25.5233C23.3422 25.1296 23.6484 24.7797 24.0859 24.7797H24.8296C25.2233 24.7797 25.5732 25.1296 25.5732 25.5233V26.267ZM25.5732 22.5487C25.5732 22.9424 25.267 23.2923 24.8296 23.2923H24.0859C23.6922 23.2923 23.3422 22.9424 23.3422 22.5487V21.805C23.3422 21.4113 23.6484 21.0614 24.0859 21.0614H24.8296C25.2233 21.0614 25.5732 21.4113 25.5732 21.805V22.5487ZM29.2041 26.267C29.2041 26.6607 28.8978 27.0107 28.4604 27.0107H27.7167C27.323 27.0107 26.9731 26.6607 26.9731 26.267V25.5233C26.9731 25.1296 27.2793 24.7797 27.7167 24.7797H28.4604C28.8541 24.7797 29.2041 25.1296 29.2041 25.5233V26.267ZM29.2041 22.5487C29.2041 22.9424 28.8978 23.2923 28.4604 23.2923H27.7167C27.323 23.2923 26.9731 22.9424 26.9731 22.5487V21.805C26.9731 21.4113 27.2793 21.0614 27.7167 21.0614H28.4604C28.8541 21.0614 29.2041 21.4113 29.2041 21.805V22.5487Z" fill="white"/>
<path d="M23.7359 16.2057C23.7359 15.8994 23.7359 11.9624 23.7359 11.9624C23.7797 11.2187 23.2547 11 23.036 11H10.7874C10.0875 11 10 11.7437 10 11.7874V28.498H19.4052V17.8242C19.4052 17.8242 19.4052 16.9493 20.1926 16.9493C20.1926 16.9493 22.6423 16.9493 23.036 16.9493C23.4735 16.9493 23.7359 16.5119 23.7359 16.2057ZM13.9808 25.9171C13.9808 26.3108 13.6746 26.6607 13.2371 26.6607H12.5372C12.1435 26.6607 11.7935 26.3108 11.7935 25.9171V25.1734C11.7935 24.7797 12.0998 24.4297 12.5372 24.4297H13.2809C13.6746 24.4297 14.0245 24.7797 14.0245 25.1734V25.9171H13.9808ZM13.9808 22.155C13.9808 22.5487 13.6746 22.8986 13.2371 22.8986H12.5372C12.1435 22.8986 11.7935 22.5487 11.7935 22.155V21.4113C11.7935 21.0176 12.0998 20.6677 12.5372 20.6677H13.2809C13.6746 20.6677 14.0245 21.0176 14.0245 21.4113V22.155H13.9808ZM13.9808 18.4367C13.9808 18.8304 13.6746 19.1803 13.2371 19.1803H12.5372C12.1435 19.1803 11.7935 18.8304 11.7935 18.4367V17.693C11.7935 17.2993 12.0998 16.9493 12.5372 16.9493H13.2809C13.6746 16.9493 14.0245 17.2993 14.0245 17.693V18.4367H13.9808ZM13.9808 14.7183C13.9808 15.112 13.6746 15.462 13.2371 15.462H12.5372C12.1435 15.462 11.7935 15.112 11.7935 14.7183V13.9747C11.7935 13.581 12.0998 13.231 12.5372 13.231H13.2809C13.6746 13.231 14.0245 13.581 14.0245 13.9747V14.7183H13.9808ZM17.9616 25.9171C17.9616 26.3108 17.6554 26.6607 17.2179 26.6607H16.4743C16.0806 26.6607 15.7306 26.3108 15.7306 25.9171V25.1734C15.7306 24.7797 16.0368 24.4297 16.4743 24.4297H17.2179C17.6116 24.4297 17.9616 24.7797 17.9616 25.1734V25.9171ZM17.9616 22.155C17.9616 22.5487 17.6554 22.8986 17.2179 22.8986H16.4743C16.0806 22.8986 15.7306 22.5487 15.7306 22.155V21.4113C15.7306 21.0176 16.0368 20.6677 16.4743 20.6677H17.2179C17.6116 20.6677 17.9616 21.0176 17.9616 21.4113V22.155ZM17.9616 18.4367C17.9616 18.8304 17.6554 19.1803 17.2179 19.1803H16.4743C16.0806 19.1803 15.7306 18.8304 15.7306 18.4367V17.693C15.7306 17.2993 16.0368 16.9493 16.4743 16.9493H17.2179C17.6116 16.9493 17.9616 17.2993 17.9616 17.693V18.4367ZM17.9616 14.7183C17.9616 15.112 17.6554 15.462 17.2179 15.462H16.4743C16.0806 15.462 15.7306 15.112 15.7306 14.7183V13.9747C15.7306 13.581 16.0368 13.231 16.4743 13.231H17.2179C17.6116 13.231 17.9616 13.581 17.9616 13.9747V14.7183ZM21.9424 14.7183C21.9424 15.112 21.6362 15.462 21.1987 15.462H20.4988C20.1051 15.462 19.7551 15.112 19.7551 14.7183V13.9747C19.7551 13.581 20.0614 13.231 20.4988 13.231H21.2425C21.6362 13.231 21.9861 13.581 21.9861 13.9747V14.7183H21.9424Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,4 @@
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M0 3C0 1.34315 1.34315 0 3 0H37C38.6569 0 40 1.34315 40 3V37C40 38.6569 38.6569 40 37 40H3C1.34315 40 0 38.6569 0 37V3Z" fill="#A094ED"/>
<path d="M29.12 12.02H10.88C9.626 12.02 8.6 13.046 8.6 14.3V25.32C8.6 26.574 9.626 27.6 10.88 27.6H29.12C30.374 27.6 31.4 26.574 31.4 25.32V14.3C31.4 13.046 30.374 12.02 29.12 12.02ZM19.468 24.94H12.932C12.21 24.94 11.64 24.142 11.64 23.382C11.678 22.242 12.856 21.558 14.11 20.988C14.984 20.608 15.098 20.266 15.098 19.886C15.098 19.506 14.87 19.164 14.566 18.898C14.072 18.442 13.768 17.758 13.768 16.998C13.768 15.554 14.642 14.338 16.162 14.338C17.682 14.338 18.556 15.554 18.556 16.998C18.556 17.758 18.29 18.442 17.758 18.898C17.454 19.164 17.226 19.506 17.226 19.886C17.226 20.266 17.34 20.608 18.214 20.95C19.468 21.482 20.646 22.242 20.684 23.382C20.76 24.142 20.19 24.94 19.468 24.94ZM28.36 22.28C28.36 22.698 28.018 23.04 27.6 23.04H24.18C23.762 23.04 23.42 22.698 23.42 22.28V21.14C23.42 20.722 23.762 20.38 24.18 20.38H27.6C28.018 20.38 28.36 20.722 28.36 21.14V22.28ZM28.36 18.1C28.36 18.518 28.018 18.86 27.6 18.86H21.9C21.482 18.86 21.14 18.518 21.14 18.1V16.96C21.14 16.542 21.482 16.2 21.9 16.2H27.6C28.018 16.2 28.36 16.542 28.36 16.96V18.1Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.1966 6.05507L16.0207 5.25708L17.669 6.85306L16.8448 7.65106L15.1966 6.05507ZM11.9 6.05507L13.5483 4.45908L14.3724 3.66109C15.2827 2.77966 16.7586 2.77961 17.669 3.66109L19.3173 5.25708C20.2276 6.13855 20.2276 7.56762 19.3173 8.44905L18.4931 9.24705L16.8448 10.843L12.7242 14.833L11.0759 16.429L10.882 16.6167L7.76993 17.7444C6.91521 18.0542 5.95229 17.8519 5.30692 17.227C4.66155 16.6021 4.45265 15.6697 4.77257 14.8421L5.93715 11.8288L6.13106 11.641L7.77933 10.045L11.9 6.05507ZM15.1966 9.24705L11.0759 13.237L9.42761 11.641L13.5483 7.65106L15.1966 9.24705ZM6.95731 15.629L7.85388 13.3092L9.35306 14.7608L6.95731 15.629ZM5.16551 18.7429C4.52181 18.7429 4 19.2482 4 19.8715C4 20.4947 4.52181 21 5.16551 21H17.9861C18.6298 21 19.1516 20.4947 19.1516 19.8715C19.1516 19.2482 18.6298 18.7429 17.9861 18.7429H5.16551Z" />
</svg>

After

Width:  |  Height:  |  Size: 962 B

View File

@@ -60,6 +60,7 @@ export { default as IconFullScreen } from './full-screen.svg';
export { default as IconGoogle } from './google.svg';
export { default as IconHangup } from './hangup.svg';
export { default as IconHelp } from './help.svg';
export { default as IconHighlight } from './highlight.svg';
export { default as IconHome } from './home.svg';
export { default as IconHorizontalPoints } from './horizontal-points.svg';
export { default as IconInfo } from './info.svg';
@@ -97,6 +98,10 @@ export { default as IconPresentation } from './presentation.svg';
export { default as IconRaisedHand } from './raised-hand.svg';
export { default as IconRaisedHandHollow } from './raised-hand-hollow.svg';
export { default as IconRec } from './rec.svg';
export { default as IconRecordAccount } from './account-record.svg';
export { default as IconRecordContact } from './contact-record.svg';
export { default as IconRecordLead } from './lead-record.svg';
export { default as IconRecordOpportunity } from './opportunity-record.svg';
export { default as IconRemoteControlStart } from './play.svg';
export { default as IconRemoteControlStop } from './stop.svg';
export { default as IconReply } from './reply.svg';
@@ -104,6 +109,7 @@ export { default as IconRestore } from './restore.svg';
export { default as IconRingGroup } from './icon-ring-group.svg';
export { default as IconRoomLock } from './security.svg';
export { default as IconRoomUnlock } from './security-locked.svg';
export { default as IconSalesforce } from './salesforce.svg';
export { default as IconSecurityOff } from './security-off.svg';
export { default as IconSecurityOn } from './security-on.svg';
export { default as IconSearch } from './search.svg';

View File

@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M0 3C0 1.34315 1.34315 0 3 0H37C38.6569 0 40 1.34315 40 3V37C40 38.6569 38.6569 40 37 40H3C1.34315 40 0 38.6569 0 37V3Z" fill="#EA8F6A"/>
<path d="M20.0186 16.0059C21.6771 16.0059 23.0216 14.6614 23.0216 13.0029C23.0216 11.3445 21.6771 10 20.0186 10C18.3601 10 17.0157 11.3445 17.0157 13.0029C17.0157 14.6614 18.3601 16.0059 20.0186 16.0059Z" fill="white"/>
<path d="M29.3611 18.0079H10.6761C10.0088 18.0079 9.74188 18.842 10.3091 19.209L15.1805 22.3454C15.4141 22.5123 15.5476 22.8126 15.4475 23.0795L13.6123 29.1855C13.4121 29.8528 14.2797 30.3199 14.7802 29.8194L19.5181 24.8145C19.7851 24.5142 20.2522 24.5142 20.5191 24.8145L25.2571 29.8194C25.7242 30.3199 26.5917 29.8528 26.4249 29.1855L24.5898 23.0795C24.523 22.8126 24.6231 22.5123 24.8567 22.3454L29.7281 19.209C30.2954 18.842 30.0284 18.0079 29.3611 18.0079Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 936 B

View File

@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<path d="M0 3C0 1.34315 1.34315 0 3 0H37C38.6569 0 40 1.34315 40 3V37C40 38.6569 38.6569 40 37 40H3C1.34315 40 0 38.6569 0 37V3Z" fill="#F3BB6B"/>
<path d="M25.8802 26.355H13.2598C12.9607 26.355 12.6916 26.6242 12.6916 26.9232V26.9531C12.6916 27.94 13.499 28.7475 14.4859 28.7475H24.6541C25.641 28.7475 26.4484 27.94 26.4484 26.9531V26.9232C26.4484 26.6242 26.1793 26.355 25.8802 26.355Z" fill="white"/>
<path d="M27.3456 14.0934C26.3587 14.0934 25.5513 14.9009 25.5513 15.8878C25.5513 16.4261 25.7905 16.9345 26.1793 17.2635C25.6709 18.4298 24.5045 19.2373 23.1288 19.1775C21.5438 19.0878 20.2578 17.8018 20.1681 16.2168C20.1681 15.9476 20.1681 15.7084 20.2279 15.4691C20.8859 15.2 21.3644 14.5719 21.3644 13.7944C21.3644 12.8075 20.5569 12 19.57 12C18.5831 12 17.7756 12.8075 17.7756 13.7944C17.7756 14.542 18.2541 15.2 18.9121 15.4691C18.9719 15.7084 18.9719 15.9476 18.9719 16.2168C18.8822 17.8018 17.5962 19.0878 16.0112 19.1775C14.6355 19.2672 13.4392 18.4298 12.9607 17.2635C13.3495 16.9345 13.5887 16.4261 13.5887 15.8878C13.5887 14.9009 12.7813 14.0934 11.7944 14.0934C10.8075 14.0934 10 14.9009 10 15.8878C10 16.8747 10.8075 17.6822 11.7944 17.6822L12.6318 24.0821C12.6617 24.3513 12.9009 24.5606 13.2 24.5606H25.94C26.2092 24.5606 26.4484 24.3513 26.5082 24.0821L27.3456 17.6822C28.3325 17.6822 29.14 16.8747 29.14 15.8878C29.14 14.9009 28.3325 14.0934 27.3456 14.0934Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.833344 11.6667C0.833344 9.76981 1.88966 8.11962 3.44616 7.27229C3.93274 5.02055 5.93601 3.33337 8.33334 3.33337C10.5537 3.33337 12.436 4.78064 13.0884 6.78327L13.1803 6.75332C13.4357 6.70694 13.7965 6.66671 14.1667 6.66671C16.9281 6.66671 19.1667 8.90528 19.1667 11.6667C19.1667 14.4281 16.9281 16.6667 14.1667 16.6667H5.83334C3.07192 16.6667 0.833344 14.4281 0.833344 11.6667ZM11.9714 8.73523L11.5037 7.29952C11.0607 5.9396 9.78725 5.00004 8.33334 5.00004C6.75638 5.00004 5.40363 6.10457 5.07522 7.62432L4.91389 8.37091L4.24304 8.73611C3.17664 9.31664 2.50001 10.4313 2.50001 11.6667C2.50001 13.5077 3.99239 15 5.83334 15H14.1667C16.0076 15 17.5 13.5077 17.5 11.6667C17.5 9.82576 16.0076 8.33337 14.1667 8.33337C13.9212 8.33337 13.6803 8.35962 13.4462 8.41108L11.9714 8.73523Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 949 B

View File

@@ -40,6 +40,16 @@ export const SET_AUDIO_UNMUTE_PERMISSIONS = 'SET_AUDIO_UNMUTE_PERMISSIONS';
*/
export const SET_CAMERA_FACING_MODE = 'SET_CAMERA_FACING_MODE';
/**
* The type of (redux) action to set the muted state of the local screenshare.
*
* {
* type: SET_SCREENSHARE_MUTED,
* muted: boolean
* }
*/
export const SET_SCREENSHARE_MUTED = 'SET_SCREENSHARE_MUTED';
/**
* The type of (redux) action to adjust the availability of the local video.
*

View File

@@ -11,6 +11,7 @@ import {
SET_AUDIO_AVAILABLE,
SET_AUDIO_UNMUTE_PERMISSIONS,
SET_CAMERA_FACING_MODE,
SET_SCREENSHARE_MUTED,
SET_VIDEO_AVAILABLE,
SET_VIDEO_MUTED,
SET_VIDEO_UNMUTE_PERMISSIONS,
@@ -20,6 +21,7 @@ import {
import {
MEDIA_TYPE,
type MediaType,
SCREENSHARE_MUTISM_AUTHORITY,
VIDEO_MUTISM_AUTHORITY
} from './constants';
@@ -92,6 +94,47 @@ export function setCameraFacingMode(cameraFacingMode: string) {
};
}
/**
* Action to set the muted state of the local screenshare.
*
* @param {boolean} muted - True if the local screenshare is to be enabled or false otherwise.
* @param {MEDIA_TYPE} mediaType - The type of media.
* @param {number} authority - The {@link SCREENSHARE_MUTISM_AUTHORITY} which is muting/unmuting the local screenshare.
* @param {boolean} ensureTrack - True if we want to ensure that a new track is created if missing.
* @returns {Function}
*/
export function setScreenshareMuted(
muted: boolean,
mediaType: MediaType = MEDIA_TYPE.SCREENSHARE,
authority: number = SCREENSHARE_MUTISM_AUTHORITY.USER,
ensureTrack: boolean = false) {
return (dispatch: Dispatch<any>, getState: Function) => {
const state = getState();
// check for A/V Moderation when trying to unmute
if (!muted && shouldShowModeratedNotification(MEDIA_TYPE.SCREENSHARE, state)) {
if (!isModerationNotificationDisplayed(MEDIA_TYPE.SCREENSHARE, state)) {
ensureTrack && dispatch(showModeratedNotification(MEDIA_TYPE.SCREENSHARE));
}
return;
}
const oldValue = state['features/base/media'].screenshare.muted;
// eslint-disable-next-line no-bitwise
const newValue = muted ? oldValue | authority : oldValue & ~authority;
return dispatch({
type: SET_SCREENSHARE_MUTED,
authority,
mediaType,
ensureTrack,
muted: newValue
});
};
}
/**
* Action to adjust the availability of the local video.
*

View File

@@ -10,7 +10,7 @@ export const CAMERA_FACING_MODE = {
USER: 'user'
};
export type MediaType = 'audio' | 'video' | 'presenter';
export type MediaType = 'audio' | 'video' | 'presenter' | 'screenshare';
/**
* The set of media types.
@@ -20,12 +20,23 @@ export type MediaType = 'audio' | 'video' | 'presenter';
export const MEDIA_TYPE = {
AUDIO: 'audio',
PRESENTER: 'presenter',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
/* eslint-disable no-bitwise */
/**
* The types of authorities which may mute/unmute the local screenshare.
*
* @enum {number}
*/
export const SCREENSHARE_MUTISM_AUTHORITY = {
AUDIO_ONLY: 1 << 0,
USER: 1 << 2
};
/**
* The types of authorities which may mute/unmute the local video.
*

View File

@@ -16,6 +16,7 @@ import { isForceMuted } from '../../participants-pane/functions';
import { isScreenMediaShared } from '../../screen-share/functions';
import { SET_AUDIO_ONLY, setAudioOnly } from '../audio-only';
import { isRoomValid, SET_ROOM } from '../conference';
import { getMultipleVideoSupportFeatureFlag } from '../config';
import { getLocalParticipant } from '../participants';
import { MiddlewareRegistry } from '../redux';
import { getPropertyValue } from '../settings';
@@ -30,13 +31,20 @@ import {
import {
SET_AUDIO_MUTED,
SET_AUDIO_UNMUTE_PERMISSIONS,
SET_SCREENSHARE_MUTED,
SET_VIDEO_MUTED,
SET_VIDEO_UNMUTE_PERMISSIONS
} from './actionTypes';
import { setAudioMuted, setCameraFacingMode, setVideoMuted } from './actions';
import {
setAudioMuted,
setCameraFacingMode,
setScreenshareMuted,
setVideoMuted
} from './actions';
import {
CAMERA_FACING_MODE,
MEDIA_TYPE,
SCREENSHARE_MUTISM_AUTHORITY,
VIDEO_MUTISM_AUTHORITY
} from './constants';
import { getStartWithAudioMuted, getStartWithVideoMuted } from './functions';
@@ -100,6 +108,15 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case SET_SCREENSHARE_MUTED: {
const state = store.getState();
const participant = getLocalParticipant(state);
if (!action.muted && isForceMuted(participant, MEDIA_TYPE.SCREENSHARE, state)) {
return;
}
break;
}
case SET_VIDEO_MUTED: {
const state = store.getState();
const participant = getLocalParticipant(state);
@@ -167,15 +184,17 @@ function _appStateChanged({ dispatch, getState }, next, action) {
* @private
* @returns {Object} The value returned by {@code next(action)}.
*/
function _setAudioOnly({ dispatch }, next, action) {
function _setAudioOnly({ dispatch, getState }, next, action) {
const { audioOnly, ensureVideoTrack } = action;
const state = getState();
sendAnalytics(createTrackMutedEvent('video', 'audio-only mode', audioOnly));
// Make sure we mute both the desktop and video tracks.
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.VIDEO, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
if (navigator.product !== 'ReactNative') {
if (getMultipleVideoSupportFeatureFlag(state)) {
dispatch(setScreenshareMuted(audioOnly, MEDIA_TYPE.SCREENSHARE, SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY));
} else if (navigator.product !== 'ReactNative') {
dispatch(setVideoMuted(audioOnly, MEDIA_TYPE.PRESENTER, VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY, ensureVideoTrack));
}
@@ -292,11 +311,9 @@ function _syncTrackMutedState({ getState }, track) {
// fired before track gets to state.
if (track.muted !== muted) {
sendAnalytics(createSyncTrackStateEvent(track.mediaType, muted));
logger.log(
`Sync ${track.mediaType} track muted state to ${
muted ? 'muted' : 'unmuted'}`);
logger.log(`Sync ${track.mediaType} track muted state to ${muted ? 'muted' : 'unmuted'}`);
track.muted = muted;
setTrackMuted(track.jitsiTrack, muted);
setTrackMuted(track.jitsiTrack, muted, state);
}
}

View File

@@ -9,13 +9,14 @@ import {
SET_AUDIO_MUTED,
SET_AUDIO_UNMUTE_PERMISSIONS,
SET_CAMERA_FACING_MODE,
SET_SCREENSHARE_MUTED,
SET_VIDEO_AVAILABLE,
SET_VIDEO_MUTED,
SET_VIDEO_UNMUTE_PERMISSIONS,
STORE_VIDEO_TRANSFORM,
TOGGLE_CAMERA_FACING_MODE
} from './actionTypes';
import { CAMERA_FACING_MODE } from './constants';
import { CAMERA_FACING_MODE, SCREENSHARE_MUTISM_AUTHORITY } from './constants';
/**
* Media state object for local audio.
@@ -73,6 +74,54 @@ function _audio(state = _AUDIO_INITIAL_MEDIA_STATE, action) {
}
}
/**
* Media state object for local screenshare.
*
* @typedef {Object} ScreenshareMediaState
* @property {boolean} available=true - Screenshare available state.
* @property {boolean} muted=true - Screenshare muted state.
* @property {boolean} unmuteBlocked=false - Screenshare unmute blocked state.
*/
/**
* Initial state for video.
*
* @type {ScreenshareMediaState}
*/
export const _SCREENSHARE_INITIAL_MEDIA_STATE = {
available: true,
muted: SCREENSHARE_MUTISM_AUTHORITY.USER,
unmuteBlocked: false
};
/**
* Reducer for screenshare media state.
*
* @param {VideoMediaState} state - Media state of local screenshare.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @private
* @returns {ScreenshareMediaState}
*/
function _screenshare(state = _SCREENSHARE_INITIAL_MEDIA_STATE, action) {
switch (action.type) {
case SET_SCREENSHARE_MUTED:
return {
...state,
muted: action.muted
};
case SET_VIDEO_UNMUTE_PERMISSIONS:
return {
...state,
unmuteBlocked: action.blocked
};
default:
return state;
}
}
/**
* Media state object for local video.
*
@@ -179,6 +228,7 @@ function _video(state = _VIDEO_INITIAL_MEDIA_STATE, action) {
*/
ReducerRegistry.register('features/base/media', combineReducers({
audio: _audio,
screenshare: _screenshare,
video: _video
}));

View File

@@ -1,10 +1,12 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback, useState } from 'react';
import { translate } from '../../../i18n';
import { Icon, IconArrowDownSmall, IconWifi1Bar, IconWifi2Bars, IconWifi3Bars } from '../../../icons';
import { connect } from '../../../redux';
import { PREJOIN_DEFAULT_CONTENT_WIDTH } from '../../../ui/components/variables';
import { CONNECTION_TYPE } from '../../constants';
import { getConnectionData } from '../../functions';
@@ -26,6 +28,99 @@ type Props = {
t: Function
}
const useStyles = makeStyles(theme => {
return {
connectionStatus: {
borderRadius: '6px',
color: '#fff',
fontSize: '12px',
letterSpacing: '0.16px',
lineHeight: '16px',
position: 'absolute',
width: '100%',
[theme.breakpoints.down('400')]: {
margin: 0,
width: '100%'
},
'@media (max-width: 720px)': {
margin: `${theme.spacing(4)} auto`,
position: 'fixed',
top: 0,
width: PREJOIN_DEFAULT_CONTENT_WIDTH
},
// mobile phone landscape
'@media (max-height: 420px)': {
display: 'none'
},
'& .con-status-header': {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
alignItems: 'center',
display: 'flex',
padding: '14px 16px'
},
'& .con-status-circle': {
borderRadius: '50%',
display: 'inline-block',
padding: theme.spacing(1),
marginRight: theme.spacing(3)
},
'& .con-status--good': {
background: '#31B76A'
},
'& .con-status--poor': {
background: '#E12D2D'
},
'& .con-status--non-optimal': {
background: '#E39623'
},
'& .con-status-arrow': {
marginLeft: 'auto',
transition: 'background-color 0.16s ease-out'
},
'& .con-status-arrow--up': {
transform: 'rotate(180deg)'
},
'& .con-status-arrow > svg': {
cursor: 'pointer'
},
'& .con-status-arrow:hover': {
backgroundColor: 'rgba(1, 1, 1, 0.1)'
},
'& .con-status-text': {
textAlign: 'center'
},
'& .con-status-details': {
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderTop: '1px solid #5E6D7A',
padding: theme.spacing(3),
transition: 'opacity 0.16s ease-out'
},
'& .con-status-details-visible': {
opacity: 1
},
'& .con-status-details-hidden': {
opacity: 0
}
}
};
});
const CONNECTION_TYPE_MAP = {
[CONNECTION_TYPE.POOR]: {
connectionClass: 'con-status--poor',
@@ -51,11 +146,8 @@ const CONNECTION_TYPE_MAP = {
* @returns {ReactElement}
*/
function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
if (connectionType === CONNECTION_TYPE.NONE) {
return null;
}
const classes = useStyles();
const { connectionClass, icon, connectionText } = CONNECTION_TYPE_MAP[connectionType];
const [ showDetails, toggleDetails ] = useState(false);
const arrowClassName = showDetails
? 'con-status-arrow con-status-arrow--up'
@@ -77,8 +169,14 @@ function ConnectionStatus({ connectionDetails, t, connectionType }: Props) {
}
}, [ showDetails, toggleDetails ]);
if (connectionType === CONNECTION_TYPE.NONE) {
return null;
}
const { connectionClass, icon, connectionText } = CONNECTION_TYPE_MAP[connectionType];
return (
<div className = 'con-status'>
<div className = { classes.connectionStatus }>
<div
aria-level = { 1 }
className = 'con-status-header'

View File

@@ -2,6 +2,7 @@
import React, { PureComponent } from 'react';
import { isMobileBrowser } from '../../../environment/utils';
import { getFieldValue } from '../../../react';
type Props = {
@@ -132,6 +133,9 @@ export default class InputField extends PureComponent<Props, State> {
onKeyDown = { this._onKeyDown }
placeholder = { this.props.placeHolder }
readOnly = { this.props.readOnly }
// eslint-disable-next-line react/jsx-no-bind
ref = { inputElement => this.props.autoFocus && isMobileBrowser()
&& inputElement && inputElement.focus() }
type = { this.props.type }
value = { this.state.value } />
);

View File

@@ -3,6 +3,9 @@
import React, { Component } from 'react';
import { toArray } from 'react-emoji-render';
import GifMessage from '../../../../chat/components/web/GifMessage';
import { isGifMessage } from '../../../../gifs/functions';
import Linkify from './Linkify';
type Props = {
@@ -44,16 +47,26 @@ class Message extends Component<Props> {
const content = [];
for (const token of tokens) {
if (token.includes('://')) {
// check if the message is a GIF
if (isGifMessage(text)) {
const url = text.substring(4, text.length - 1);
// Bypass the emojification when urls are involved
content.push(token);
} else {
content.push(...toArray(token, { className: 'smiley' }));
content.push(<GifMessage
key = { url }
url = { url } />);
} else {
for (const token of tokens) {
if (token.includes('://')) {
// Bypass the emojification when urls are involved
content.push(token);
} else {
content.push(...toArray(token, { className: 'smiley' }));
}
content.push(' ');
}
content.push(' ');
}
content.forEach(token => {

View File

@@ -1,5 +1,7 @@
import React, { Component } from 'react';
import { getFixedPlatformStyle } from '../../../styles';
/**
* Implements a React/Web {@link Component} for displaying text similar to React
* Native's {@code Text} in order to facilitate cross-platform source code.
@@ -14,6 +16,12 @@ export default class Text extends Component {
* @returns {ReactElement}
*/
render() {
return React.createElement('span', this.props);
// eslint-disable-next-line react/prop-types
const _style = getFixedPlatformStyle(this.props.style);
return React.createElement('span', {
...this.props,
style: _style
});
}
}

View File

@@ -54,6 +54,18 @@ export const TRACK_CREATE_CANCELED = 'TRACK_CREATE_CANCELED';
*/
export const TRACK_CREATE_ERROR = 'TRACK_CREATE_ERROR';
/**
* The type of redux action dispatched when the track mute/unmute operation fails at the conference level. This could
* happen because of {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.
*
* {
* type: TRACK_MUTE_UNMUTE_FAILED,
* track: Track,
* wasMuting: Boolean
* }
*/
export const TRACK_MUTE_UNMUTE_FAILED = 'TRACK_MUTE_UNMUTE_FAILED';
/**
* The type of redux action dispatched when a track has triggered no data from source event.
*

View File

@@ -5,11 +5,14 @@ import {
sendAnalytics
} from '../../analytics';
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showNotification } from '../../notifications';
import { getCurrentConference } from '../conference';
import { getMultipleVideoSupportFeatureFlag } from '../config';
import { JitsiTrackErrors, JitsiTrackEvents, createLocalTrack } from '../lib-jitsi-meet';
import {
CAMERA_FACING_MODE,
MEDIA_TYPE,
setAudioMuted,
setScreenshareMuted,
setVideoMuted,
VIDEO_MUTISM_AUTHORITY,
VIDEO_TYPE
@@ -23,6 +26,7 @@ import {
TRACK_ADDED,
TRACK_CREATE_CANCELED,
TRACK_CREATE_ERROR,
TRACK_MUTE_UNMUTE_FAILED,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_STOPPED,
@@ -39,6 +43,35 @@ import {
} from './functions';
import logger from './logger';
/**
* Add a given local track to the conference.
*
* @param {JitsiLocalTrack} newTrack - The local track to be added to the conference.
* @returns {Function}
*/
export function addLocalTrack(newTrack) {
return async (dispatch, getState) => {
const conference = getCurrentConference(getState());
if (conference) {
await conference.addTrack(newTrack);
}
const setMuted = newTrack.isVideoTrack()
? getMultipleVideoSupportFeatureFlag(getState())
&& newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
? setScreenshareMuted
: setVideoMuted
: setAudioMuted;
const isMuted = newTrack.isMuted();
logger.log(`Adding ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
await dispatch(setMuted(isMuted));
return dispatch(_addTracks([ newTrack ]));
};
}
/**
* Requests the creating of the desired media type tracks. Desire is expressed
* by base/media unless the function caller specifies desired media types
@@ -320,49 +353,34 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
* @returns {Function}
*/
function replaceStoredTracks(oldTrack, newTrack) {
return dispatch => {
return async (dispatch, getState) => {
// We call dispose after doing the replace because dispose will
// try and do a new o/a after the track removes itself. Doing it
// after means the JitsiLocalTrack.conference is already
// cleared, so it won't try and do the o/a.
const disposePromise
= oldTrack
? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
: Promise.resolve();
if (oldTrack) {
await dispatch(_disposeAndRemoveTracks([ oldTrack ]));
}
return disposePromise
.then(() => {
if (newTrack) {
// The mute state of the new track should be
// reflected in the app's mute state. For example,
// if the app is currently muted and changing to a
// new track that is not muted, the app's mute
// state should be falsey. As such, emit a mute
// event here to set up the app to reflect the
// track's mute state. If this is not done, the
// current mute state of the app will be reflected
// on the track, not vice-versa.
const setMuted
= newTrack.isVideoTrack()
? setVideoMuted
: setAudioMuted;
const isMuted = newTrack.isMuted();
if (newTrack) {
// The mute state of the new track should be reflected in the app's mute state. For example, if the
// app is currently muted and changing to a new track that is not muted, the app's mute state
// should be falsey. As such, emit a mute event here to set up the app to reflect the track's mute
// state. If this is not done, the current mute state of the app will be reflected on the track,
// not vice-versa.
const setMuted = newTrack.isVideoTrack()
? getMultipleVideoSupportFeatureFlag(getState()) && newTrack.getVideoType() === VIDEO_TYPE.DESKTOP
? setScreenshareMuted
: setVideoMuted
: setAudioMuted;
const isMuted = newTrack.isMuted();
sendAnalytics(createTrackMutedEvent(
newTrack.getType(),
'track.replaced',
isMuted));
logger.log(`Replace ${newTrack.getType()} track - ${
isMuted ? 'muted' : 'unmuted'}`);
sendAnalytics(createTrackMutedEvent(newTrack.getType(), 'track.replaced', isMuted));
logger.log(`Replace ${newTrack.getType()} track - ${isMuted ? 'muted' : 'unmuted'}`);
return dispatch(setMuted(isMuted));
}
})
.then(() => {
if (newTrack) {
return dispatch(_addTracks([ newTrack ]));
}
});
await dispatch(setMuted(isMuted));
await dispatch(_addTracks([ newTrack ]));
}
};
}
@@ -384,7 +402,9 @@ export function trackAdded(track) {
// participantId
const local = track.isLocal();
const mediaType = track.getType();
const mediaType = getMultipleVideoSupportFeatureFlag(getState()) && track.getVideoType() === VIDEO_TYPE.DESKTOP
? MEDIA_TYPE.SCREENSHARE
: track.getType();
let isReceivingData, noDataFromSourceNotificationInfo, participantId;
if (local) {
@@ -471,6 +491,25 @@ export function trackMutedChanged(track) {
};
}
/**
* Create an action for when a track's muted state change action has failed. This could happen because of
* {@code getUserMedia} errors during unmute or replace track errors at the peerconnection level.
*
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
* @param {boolean} wasMuting - If the operation that failed was a mute operation or an unmute operation.
* @returns {{
* type: TRACK_MUTE_UNMUTE_FAILED,
* track: Track
* }}
*/
export function trackMuteUnmuteFailed(track, wasMuting) {
return {
type: TRACK_MUTE_UNMUTE_FAILED,
track,
wasMuting
};
}
/**
* Create an action for when a track's no data from source notification information changes.
*

View File

@@ -1,5 +1,6 @@
/* global APP */
import { getMultipleVideoSupportFeatureFlag } from '../config/functions.any';
import { isMobileBrowser } from '../environment/utils';
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
@@ -295,6 +296,33 @@ export function getLocalAudioTrack(tracks) {
return getLocalTrack(tracks, MEDIA_TYPE.AUDIO);
}
/**
* Returns the local desktop track.
*
* @param {Track[]} tracks - List of all tracks.
* @param {boolean} [includePending] - Indicates whether a local track is to be returned if it is still pending.
* A local track is pending if {@code getUserMedia} is still executing to create it and, consequently, its
* {@code jitsiTrack} property is {@code undefined}. By default a pending local track is not returned.
* @returns {(Track|undefined)}
*/
export function getLocalDesktopTrack(tracks, includePending = false) {
return (
getLocalTracks(tracks, includePending)
.find(t => t.mediaType === MEDIA_TYPE.SCREENSHARE || t.videoType === VIDEO_TYPE.DESKTOP));
}
/**
* Returns the stored local desktop jitsiLocalTrack.
*
* @param {Object} state - The redux state.
* @returns {JitsiLocalTrack|undefined}
*/
export function getLocalJitsiDesktopTrack(state) {
const track = getLocalDesktopTrack(getTrackState(state));
return track?.jitsiTrack;
}
/**
* Returns local track by media type.
*
@@ -524,20 +552,22 @@ export function isUserInteractionRequiredForUnmute(state) {
}
/**
* Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of
* the specified {@code track} is already in accord with the specified
* {@code muted} value, then does nothing.
* Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of the specified {@code track} is already in
* accord with the specified {@code muted} value, then does nothing.
*
* @param {JitsiLocalTrack} track - The {@code JitsiLocalTrack} to mute or
* unmute.
* @param {boolean} muted - If the specified {@code track} is to be muted, then
* {@code true}; otherwise, {@code false}.
* @param {JitsiLocalTrack} track - The {@code JitsiLocalTrack} to mute or unmute.
* @param {boolean} muted - If the specified {@code track} is to be muted, then {@code true}; otherwise, {@code false}.
* @param {Object} state - The redux state.
* @returns {Promise}
*/
export function setTrackMuted(track, muted) {
export function setTrackMuted(track, muted, state) {
muted = Boolean(muted); // eslint-disable-line no-param-reassign
if (track.isMuted() === muted) {
// Ignore the check for desktop track muted operation. When the screenshare is terminated by clicking on the
// browser's 'Stop sharing' button, the local stream is stopped before the inactive stream handler is fired.
// We still need to proceed here and remove the track from the peerconnection.
if (track.isMuted() === muted
&& !(track.getVideoType() === VIDEO_TYPE.DESKTOP && getMultipleVideoSupportFeatureFlag(state))) {
return Promise.resolve();
}
@@ -546,8 +576,9 @@ export function setTrackMuted(track, muted) {
return track[f]().catch(error => {
// Track might be already disposed so ignore such an error.
if (error.name !== JitsiTrackErrors.TRACK_IS_DISPOSED) {
// FIXME Emit mute failed, so that the app can show error dialog.
logger.error(`set track ${f} failed`, error);
return Promise.reject(error);
}
});
}

View File

@@ -8,6 +8,7 @@ import { shouldShowModeratedNotification } from '../../av-moderation/functions';
import { hideNotification, isModerationNotificationDisplayed } from '../../notifications';
import { isPrejoinPageVisible } from '../../prejoin/functions';
import { getCurrentConference } from '../conference/functions';
import { getMultipleVideoSupportFeatureFlag } from '../config';
import { getAvailableDevices } from '../devices/actions';
import {
CAMERA_FACING_MODE,
@@ -18,15 +19,20 @@ import {
VIDEO_MUTISM_AUTHORITY,
TOGGLE_CAMERA_FACING_MODE,
toggleCameraFacingMode,
VIDEO_TYPE
SET_SCREENSHARE_MUTED,
VIDEO_TYPE,
setScreenshareMuted,
SCREENSHARE_MUTISM_AUTHORITY
} from '../media';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import {
TRACK_ADDED,
TOGGLE_SCREENSHARING,
TRACK_ADDED,
TRACK_MUTE_UNMUTE_FAILED,
TRACK_NO_DATA_FROM_SOURCE,
TRACK_REMOVED,
TRACK_STOPPED,
TRACK_UPDATED
} from './actionTypes';
import {
@@ -34,6 +40,7 @@ import {
destroyLocalTracks,
showNoDataFromSourceVideoError,
toggleScreensharing,
trackMuteUnmuteFailed,
trackRemoved,
trackNoDataFromSourceNotificationInfoChanged
} from './actions';
@@ -107,6 +114,10 @@ MiddlewareRegistry.register(store => next => action => {
break;
}
case SET_SCREENSHARE_MUTED:
_setMuted(store, action, action.mediaType);
break;
case SET_VIDEO_MUTED:
if (!action.muted
&& isUserInteractionRequiredForUnmute(store.getState())) {
@@ -156,19 +167,54 @@ MiddlewareRegistry.register(store => next => action => {
const { enabled, audioOnly, ignoreDidHaveVideo } = action;
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, { enabled,
audioOnly,
ignoreDidHaveVideo });
if (!getMultipleVideoSupportFeatureFlag(store.getState())) {
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING,
{
enabled,
audioOnly,
ignoreDidHaveVideo
});
}
}
break;
case TRACK_MUTE_UNMUTE_FAILED: {
const { jitsiTrack } = action.track;
const muted = action.wasMuted;
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
if (typeof APP !== 'undefined') {
if (isVideoTrack && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP
&& getMultipleVideoSupportFeatureFlag(store.getState())) {
store.dispatch(setScreenshareMuted(!muted));
} else if (isVideoTrack) {
APP.conference.setVideoMuteStatus();
} else {
APP.conference.setAudioMuteStatus(!muted);
}
}
break;
}
case TRACK_STOPPED: {
const { jitsiTrack } = action.track;
if (typeof APP !== 'undefined'
&& getMultipleVideoSupportFeatureFlag(store.getState())
&& jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
store.dispatch(toggleScreensharing(false));
}
break;
}
case TRACK_UPDATED: {
// TODO Remove the following calls to APP.UI once components interested
// in track mute changes are moved into React and/or redux.
if (typeof APP !== 'undefined') {
const result = next(action);
const state = store.getState();
if (isPrejoinPageVisible(store.getState())) {
if (isPrejoinPageVisible(state)) {
return result;
}
@@ -181,10 +227,11 @@ MiddlewareRegistry.register(store => next => action => {
// Do not change the video mute state for local presenter tracks.
if (jitsiTrack.type === MEDIA_TYPE.PRESENTER) {
APP.conference.mutePresenter(muted);
} else if (jitsiTrack.isLocal() && !(jitsiTrack.videoType === VIDEO_TYPE.DESKTOP)) {
} else if (jitsiTrack.isLocal() && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
APP.conference.setVideoMuteStatus();
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.videoType === VIDEO_TYPE.DESKTOP) {
store.dispatch(toggleScreensharing(false, false, true));
} else if (jitsiTrack.isLocal() && muted && jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP) {
!getMultipleVideoSupportFeatureFlag(state)
&& store.dispatch(toggleScreensharing(false, false, true));
} else {
APP.UI.setVideoMuted(participantID);
}
@@ -335,25 +382,34 @@ function _removeNoDataFromSourceNotification({ getState, dispatch }, track) {
* @private
* @returns {void}
*/
function _setMuted(store, { ensureTrack, authority, muted }, mediaType: MEDIA_TYPE) {
const localTrack
= _getLocalTrack(store, mediaType, /* includePending */ true);
async function _setMuted(store, { ensureTrack, authority, muted }, mediaType: MEDIA_TYPE) {
const { dispatch, getState } = store;
const localTrack = _getLocalTrack(store, mediaType, /* includePending */ true);
const state = getState();
if (mediaType === MEDIA_TYPE.SCREENSHARE
&& getMultipleVideoSupportFeatureFlag(state)
&& !muted) {
return;
}
if (localTrack) {
// The `jitsiTrack` property will have a value only for a localTrack for
// which `getUserMedia` has already completed. If there's no
// `jitsiTrack`, then the `muted` state will be applied once the
// `jitsiTrack` is created.
// The `jitsiTrack` property will have a value only for a localTrack for which `getUserMedia` has already
// completed. If there's no `jitsiTrack`, then the `muted` state will be applied once the `jitsiTrack` is
// created.
const { jitsiTrack } = localTrack;
const isAudioOnly = authority === VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY;
const isAudioOnly = (mediaType === MEDIA_TYPE.VIDEO && authority === VIDEO_MUTISM_AUTHORITY.AUDIO_ONLY)
|| (mediaType === MEDIA_TYPE.SCREENSHARE && authority === SCREENSHARE_MUTISM_AUTHORITY.AUDIO_ONLY);
// screenshare cannot be muted or unmuted using the video mute button
// anymore, unless it is muted by audioOnly.
jitsiTrack && (jitsiTrack.videoType !== 'desktop' || isAudioOnly)
&& setTrackMuted(jitsiTrack, muted);
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(store.getState()))) {
// Screenshare cannot be unmuted using the video mute button unless it is muted by audioOnly in the legacy
// screensharing mode.
if (jitsiTrack
&& (jitsiTrack.videoType !== 'desktop' || isAudioOnly || getMultipleVideoSupportFeatureFlag(state))) {
setTrackMuted(jitsiTrack, muted, state).catch(() => dispatch(trackMuteUnmuteFailed(localTrack, muted)));
}
} else if (!muted && ensureTrack && (typeof APP === 'undefined' || isPrejoinPageVisible(state))) {
// FIXME: This only runs on mobile now because web has its own way of
// creating local tracks. Adjust the check once they are unified.
store.dispatch(createLocalTracksA({ devices: [ mediaType ] }));
dispatch(createLocalTracksA({ devices: [ mediaType ] }));
}
}

View File

@@ -44,6 +44,7 @@ export const colors = {
warning05: '#F8AE1A',
warning06: '#FFD600',
warning07: '#FFD740',
disabled01: '#00000040',
@@ -64,6 +65,7 @@ export const colorMap = {
uiBackground: 'surface01',
// Container background
ui00: 'surface00',
ui01: 'surface02',
ui02: 'surface03',
ui03: 'surface04',
@@ -166,6 +168,9 @@ export const colorMap = {
// Text for saved input values
text06: 'surface03',
// Text info
text07Info: 'surface02',
// error messages
textError: 'error06',
@@ -265,6 +270,9 @@ export const colorMap = {
// Color for indicating a raised hand
warning02: 'warning06',
// Color for indicating recording info
warning03: 'warning07',
// Color for disabled tab
tab01Disabled: 'disabled01',
@@ -369,6 +377,13 @@ export const typography = {
letterSpacing: 0
},
bodyLongRegularLarge: {
fontSize: 16,
lineHeight: 26,
fontWeight: font.weightRegular,
letterSpacing: 0
},
bodyLongBold: {
fontSize: 14,
lineHeight: 24,

Some files were not shown because too many files have changed in this diff Show More