Compare commits

...

96 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
2ef97f2708 test CI run 2021-11-19 11:29:17 +01:00
Saúl Ibarra Corretgé
10d54498d0 fix(config) fix incorrect option name and whitelist it 2021-11-19 11:00:57 +01:00
Saúl Ibarra Corretgé
cd166b5f19 fix(build) fix make dev with facial recognition worker 2021-11-19 11:00:57 +01:00
Werner Fleischer
b5faf9f62a feat(breakout-rooms) add breakout-rooms
- implement breakout-rooms
- integrated into the participants panel
- managed by moderators
- moderators can send participants to breakout-rooms
- participants can join breakout rooms by themselve
- participants can leave breakout rooms anytime

Co-authored-by: Robert Pintilii <robert.pin9@gmail.com>
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2021-11-19 10:27:34 +01:00
Saúl Ibarra Corretgé
d98ea3ca24 chore(deps) lib-jitsi-meet@latest
* feat(breakout-rooms) introduce breakout rooms

131b9458fe...bdfbb82087
2021-11-19 09:37:43 +01:00
Jaya Allamsetty
d2b7151f23 chore(deps) lib-jitsi-meet@latest
* TPC: make the comments more descriptive.
* fix(SignalingLayerImpl): Log an error when only the ssrc owner gets overwritten with a diff ep id.
* fix(TPC): Force reneg when user unmutes the first time. This ensures that the source signaling is sent before the mute state is sent in presence. Jicofo relies on mute state from presence to check if the sender limit has been reached.

6d981ebb6c...131b9458fe
2021-11-18 11:13:09 -05:00
Saúl Ibarra Corretgé
7fa72db384 fix(rn,remote-video-menu) fix import after refactor 2021-11-18 15:58:43 +01:00
Gabriel Borlea
d56b282c5d fix(facial-expressions): some markdowns in the README.md for licence 2021-11-18 11:01:19 +01:00
Gabriel Borlea
72e1dcb877 add(facial-expressions): licence info about facial models 2021-11-18 11:01:19 +01:00
hmuresan
98b05a2529 fix(notify-button-clicked) Fix crash on mobile browsers 2021-11-18 09:35:28 +02:00
Christoph Settgast
2d0a70f2ab lang: Update German translation 2021-11-18 07:57:52 +01:00
Andrei Gavrilescu
6243836b3e chore(deps) lib-jitsi-meet@latest (#10386)
* feat: add facial-expressions in speaker stats (#1724)

b337778da8...6d981ebb6c
2021-11-17 18:32:49 +02:00
Payetus
6520cc2496 feat(Lobby): Adding autoknock feature as a config (#10366)
* adding lobby localization to "ca" lang

* feat(Lobby): Adding autoknock config

feat(Lobby): Adding autoknock config

Co-authored-by: Payetus <payet91@gmail.xom>
2021-11-17 10:24:18 -06:00
Gabriel Borlea
61684b1071 feat(facial-expressions): add the facial expression feature and display them in speakerstats (#10006)
* Initial implementation; Happy flow

* Maybe revert this

* Functional prototype

* feat(facial-expressions): get stream when changing background effect and use presenter effect with camera

* add(facial-expressions): array that stores the expressions durin the meeting

* refactor(facial-expressions): capture imagebitmap from stream with imagecapture api

* add(speaker-stats): expression label

* fix(facial-expression): expression store

* revert: expression leabel on speaker stats

* add(facial-expressions): broadcast of expression when it changes

* feat: facial expression handling on prosody

* fix(facial-expressions): get the right track when opening and closing camera

* add(speaker-stats): facial expression column

* fix(facial-expressions): allow to start facial recognition only after joining conference

* fix(mod_speakerstats_component): storing last emotion in speaker stats component and sending it

* chore(facial-expressions): change detection from 2000ms to 1000ms

* add(facial-expressions): send expression to server when there is only one participant

* feat(facial-expressions): store expresions as a timeline

* feat(mod_speakerstats_component): store facial expresions as a timeline

* fix(facial-expressions): stop facial recognition only when muting video track

* fix(facial-expressions): presenter mode get right track to detect face

* add: polyfils for image capture for firefox and safari

* refactor(facial-expressions): store expressions by counting them in a map

* chore(facial-expressions): remove manually assigning the backend for tenserflowjs

* feat(facial-expressions): move face-api from main thread to web worker

* fix(facial-expressions): make feature work on firefox and safari

* feat(facial-expressions): camera time tracker

* feat(facial-expressions): camera time tracker in prosody

* add(facial-expressions): expressions time as TimeElapsed object in speaker stats

* fix(facial-expresions): lower the frequency of detection when tf uses cpu backend

* add(facial-expressions): duration to the expression and send it with durantion when it is done

* fix(facial-expressions): prosody speaker stats covert fro string to number and bool values set by xmpp

* refactor(facial-expressions): change expressions labels from text to emoji

* refactor(facial-expressions): remove camera time tracker

* add(facial-expressions): detection time interval

* chore(facial-expressions): add docs and minor refactor of the code

* refactor(facial-expressions): put timeout in worker and remove set interval in main thread

* feat(facial-expressions): disable feature in the config

* add(facial-expressions): tooltips of labels in speaker stats

* refactor(facial-expressions): send facial expressions function and remove some unused functions and console logs

* refactor(facial-expressions): rename action type when a change is done to the track by the virtual backgrounds to be used in facial expressions middleware

* chore(facial-expressions): order imports and format some code

* fix(facial-expressions): rebase issues with newer master

* fix(facial-expressions): package-lock.json

* fix(facial-expression): add commented default value of disableFacialRecognition flag and short description

* fix(facial-expressions): change disableFacialRecognition to enableFacialRecognition flag in config

* fix: resources load-test package-lock.json

* fix(facial-expressions): set and get facial expressions only if facial recognition enabled

* add: facial recognition resources folder in .eslintignore

* chore: package-lock update

* fix: package-lock.json

* fix(facial-expressions): gpu memory leak in the web worker

* fix(facial-expressions): set cpu time interval for detection to 6000ms

* chore(speaker-stats): fix indentation

* chore(facial-expressions): remove empty lines between comments and type declarations

* fix(facial-expressions): remove camera timetracker

* fix(facial-expressions): remove facialRecognitionAllowed flag

* fix(facial-expressions): remove sending interval time to worker

* refactor(facial-expression): middleware

* fix(facial-expression): end tensor scope after setting backend

* fix(facial-expressions): sending info back to worker only on facial expression message

* fix: lint errors

* refactor(facial-expressions): bundle web worker using webpack

* fix: deploy-facial-expressions command in makefile

* chore: fix load test package-lock.json and package.json

* chore: sync package-lock.json

Co-authored-by: Mihai-Andrei Uscat <mihai.uscat@8x8.com>
2021-11-17 16:33:03 +02:00
Calin Chitu
e42db3c9c2 feat(welcome) added WelcomePage React Nav bottom tabs 2021-11-17 15:42:06 +02:00
Andrei Gavrilescu
6f75226044 chore(deps): update rtcstats 9.0.1 (#10374) 2021-11-17 14:21:37 +02:00
Andrei Gavrilescu
7e9d665697 fix(toolbox): enable fullscreen button on Android mobile browsers 2021-11-17 12:55:10 +01:00
Robert Pintilii
9aa2bbe115 feat(tokens) Added more colors to theme (#10381) 2021-11-17 11:35:07 +02:00
Vlad Piersec
31e4a9c1a0 fix(responsive-ui): Make modal full screen & fix prejoin layout on mobile landscape 2021-11-17 10:44:35 +02:00
Vlad Piersec
1106e71f03 fix(speaker-stats): Fix stats search position on narrow screens 2021-11-17 10:44:17 +02:00
Horatiu Muresan
d84ba85f58 fix(dynamic-branding) Fix bogus mUI dependency on mobile (#10375) 2021-11-16 22:16:18 +01:00
robertpin
1e706db114 fix(av-moderation) Don't stop local screensharing on mute all 2021-11-16 12:47:39 -06:00
Calin Chitu
c82cead5ae feat(chat/native) removed keyboard dismiss from JitsiKeybAvView 2021-11-16 17:50:09 +02:00
Calin Chitu
39d1ccff85 feat(chat/native) fixed scroll inside chat room 2021-11-16 17:50:09 +02:00
tmoldovan8x8
2e69ec71c5 feat(e2ee) add externally managed key mode 2021-11-16 12:12:10 +01:00
Avram Tudor
be6adad61b chore(deps) lib-jitsi-meet@latest (#10368)
* fix(browser) Mark safari <14 as unsupported

51f77cbd51...b337778da8
2021-11-15 15:45:50 +02:00
Vlad Piersec
23b8dd4860 fix(Prejoin): Make prejoin name noneditable only when taken from jwt 2021-11-15 15:40:18 +02:00
Robert Pintilii
c74bdf137c fix(screenshot-capture) Update data sent to backend (#10364) 2021-11-15 14:59:06 +02:00
Horatiu Muresan
8ebca64af1 feat(dynamic-branding): Add custom theme branding (#10335)
- apply theming to various buttons
2021-11-15 10:37:54 +02:00
Saúl Ibarra Corretgé
61025edb74 chore(deps) lib-jitsi-meet@latest
* fix(e2ee) disable p2p when e2ee is enabled
* fix(e2ee) fix race condition when restarting media sessions
* fix(p2p) fix error if p2p session is stopped while accepting it
* fix(e2ee) removed no longer needed code
* feat: SourceVideoTypeMessage message
* chore(lint) tame the new linter
* chore(deps) update Babel and ESLint to the latest versions
* chore(deps) adapt to logger package rename
* fix(e2ee): fix loading web worker when using a relative path inside a blob for the E2EE context
* * fix(sdp): provide SCTP streams, because the XMPP parser expects them

c193f0d433...51f77cbd51
2021-11-11 16:52:39 +01:00
Calinteodor
4e2fea1e12 feat(rn,welcome) React Navigation drawer 2021-11-11 15:32:56 +01:00
Vlad Piersec
f5cdd5fca1 fix(Chat): Fix private message reply button not working 2021-11-11 15:21:24 +02:00
Robert Pintilii
eec8b9e58e fix(participants-list) Show participants with empty names (#10353) 2021-11-11 14:48:15 +02:00
Saúl Ibarra Corretgé
a96a4ea336 chore(deps) update rtcstats, change package name 2021-11-11 08:04:28 +01:00
Christoph Wiechert
876773ca2a fix(lang) Fix typos in german translation 2021-11-11 06:40:31 +01:00
Jaya Allamsetty
bc6070b98d fix(prejoin): Add audio tracks on Safari always.
This fixes a bug where remote audio is not being played out if the user joins audio and video muted from pre-join screen.
2021-11-10 14:12:53 -05:00
Jaya Allamsetty
9ca0ee41b6 fix(devices): Fixes a JS error when no audio/video is selected and there is a device update. 2021-11-10 14:12:53 -05:00
Saúl Ibarra Corretgé
957bd8916a chore(tech-debt) remove dead code 2021-11-10 18:29:34 +01:00
Saúl Ibarra Corretgé
8f08a54fb2 chore(deps) adapt to logger package rename 2021-11-10 14:40:17 +01:00
Saúl Ibarra Corretgé
28c793b3a9 chore(deps) adapt to eslint-config-jitsi rename 2021-11-10 13:10:15 +01:00
tmoldovan8x8
243aa20912 bugfix(rn) sends CONFERENCE_TERMINATED to native after dismissing the ReloadOverlay 2021-11-10 13:34:16 +02:00
Pavol Cvengros
2d27195652 chore(deps) update uuid package to 8.3.2
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2021-11-10 11:35:20 +01:00
Hristo Terezov
bcb0fe919c fix(recorder): "already started" notification 2021-11-09 13:25:19 -06:00
Hristo Terezov
981fc2f410 feat: Handle recording already started error 2021-11-09 12:33:43 -06:00
Hristo Terezov
e6e808c764 chore(deps) lib-jitsi-meet@latest
* feat(recording): Add unexpected-request error
* fix(xmpp): use RTX with Firefox from 96 on only
* fix(sdp): update data channel to RFC format
* sdp: switch port to 9 and rtp protocol to UDP/TLS/RTP/SAVPF (#1697)

03bc5278da...c193f0d433
2021-11-09 12:33:24 -06:00
Saúl Ibarra Corretgé
01ae4c477b fix(lint) remove warning in eslint-plugin-react
Fixes: Warning: React version not specified in eslint-plugin-react settings.
2021-11-09 15:16:01 +01:00
Robert Pintilii
e0010def14 fix(analytics) Fix analytics event names (#10332)
Fixed analytics where event names had duplicated words (eg. calendar.calendar.selected.selected)

Group reaction buttons analytics into one event

Removed unused code
2021-11-09 12:20:40 +02:00
Saúl Ibarra Corretgé
e77216f18e chore(lint) tame the linter 2021-11-09 11:05:09 +01:00
Saúl Ibarra Corretgé
ba7b5fc996 chore(deps) update eslint-config-jitsi to version 3.0.0 2021-11-09 11:05:09 +01:00
Saúl Ibarra Corretgé
162a67fe8b chore(lint) tame the (uppdated) linter 2021-11-09 09:43:55 +01:00
Saúl Ibarra Corretgé
0aba61d5c6 chore(Deps) update Babel and eslint to their latest versions 2021-11-09 09:43:55 +01:00
Vlad Piersec
5e4f09dd0a fix(config): Move 'disableDetails' flag to connectionIndicators config 2021-11-09 09:18:34 +01:00
Дамян Минков
17c0298e59 feat: Filters moderators presence in lobby room. (#10316)
* feat: Filters moderators presence in lobby room.

* squash: Print actor creating lobby.
2021-11-08 16:38:22 -07:00
Jaya Allamsetty
6348840cbd fix(large-video) Call play() whenever a new stream is attached to large-video.
This fixes an issue on Safari where black video is rendered sometimes whenever a new stream is attached to the large video container.
2021-11-08 16:49:45 -05:00
Paweł Domas
6a7842ab7e fix: incompatible effect instance (#10311)
There are hard to handle race conditions around
screensharing/presenter mode turning on/off. It's
easier to make a NO-OP for turning the camera on/off
while switching to screen sharing is in progress
than trying to handle this gracefully. It should be okay
for the user to click the button again after the switch
operation is done.

Ideally this logic will be re-implemented in redux
middlewares and moved out of the conference.js file.
2021-11-08 14:05:23 -06:00
hmuresan
b2894cc941 fix(raise-hand): Remove participant left from raised hand queue 2021-11-08 18:41:10 +02:00
Robert Pintilii
fc52105cf6 fix(reactions) Updated sounds (#10321) 2021-11-08 16:16:09 +02:00
Christoph Settgast
b47a263e5c lang: fixes for German
Signed-off-by: Christoph Settgast <csett86@web.de>
2021-11-08 05:52:04 -07:00
Vlad Piersec
15b677aa2a fix(Avatar): Display special characters in avatar initials 2021-11-08 14:32:30 +02:00
Vlad Piersec
08fb232627 fix(Avatar): Calculate avatar color based on display name 2021-11-08 14:32:16 +02:00
Vlad Piersec
3c5017a66a feat(connection-indicator): Add config option to disable indicator popover 2021-11-08 11:38:03 +02:00
Robert Pintilii
a97b700712 fix(tracks) Use duration from JitsiTrack (#10304) 2021-11-08 10:55:28 +02:00
José Luís Andrade
bf36b22bbd fix(lang) update portuguese translation of "highestQuality" (#10317)
Shorten text size for better viewing
2021-11-07 08:02:18 +01:00
Payetus
d6253c57c9 adding lobby localization to "ca" lang 2021-11-06 05:57:24 +01:00
José Luís Andrade
978586c157 fix(lang) update Portuguese Translation 2021-11-05 19:59:16 +01:00
tudordan7
a7efbfb0f8 feat(app-notifications): Remove device notifications in the prejoin screen. 2021-11-05 16:16:10 +01:00
alexbratu92
68eb0795eb feat(polls) fix spacing and send answer identifier 2021-11-05 07:34:52 -07:00
alexandrubratu8x8
4c46396e6a feat(polls): trigger events for poll created and answered (#10249)
Co-authored-by: alexbratu92 <alexbratu92@gmail.com>
2021-11-04 12:18:41 +02:00
Robert Pintilii
4767ef497f chore(deps) lib-jitsi-meet@latest (#10305) 2021-11-04 09:26:39 +02:00
Robert Pintilii
f27cb46f3c fix(reactions) Remove auth header if there's no JWT 2021-11-03 17:43:47 +02:00
Robert Pintilii
c2e55339d1 fix(av-moderation) Fix text on stop video dialog
Show correct text on stop participant's video dialog when moderation is on
2021-11-03 14:16:11 +02:00
Saúl Ibarra Corretgé
a78c8c199d feat(ci) use Node 16 / npm 8 for testing 2021-11-03 12:44:45 +01:00
Saúl Ibarra Corretgé
0d7ecfad40 chore(deps) run npm audit fix 2021-11-03 12:44:45 +01:00
Saúl Ibarra Corretgé
040a1d8add feat(misc) add .nvmrc 2021-11-03 12:44:45 +01:00
Saúl Ibarra Corretgé
3294dc882d chore(deps) sync package-lock.json 2021-11-03 12:44:45 +01:00
robertpin
21d5b7bcd6 fix(av-moderation) Fix text on stop video dialog
Show correct text on stop participant's video dialog when moderation is on
2021-11-03 12:38:12 +02:00
gpatel-fr
c4cbf83208 fix(lang) update french translation 2021-11-03 10:21:32 +01:00
Alexey Matveev
f1ae9a6697 fix(lang) update Russian translation
Co-authored-by: Alexey Matveev <malex@1forma.ru>
2021-11-02 16:48:39 +01:00
Jaya Allamsetty
c653334d69 chore(deps) lib-jitsi-meet@latest
* fix(presence) Send presence on mute state change.
* fix(TPC) change the tranceiver dir to recvonly when track is removed. This fixes occasional failures of MuteTest.MuteAfterJoinCanShareAndUnmute torture test and also the case on Safari where user stopping the screenshare doesn't stop showing the screensharing indication on the thumbnail.
* fix: Avoid sending two presences if start muted and then screen share. (#1771)
* feat: use source names in presence
* ref: move SignalingLayer to the conference
* feat: Delays deployment info stats till we get update from backend. (#1770)

e566291864...e6b330186f
2021-11-02 10:32:00 -04:00
Vlad Piersec
e51655a93a feat(Polls): Display creator name for polls 2021-11-02 15:45:46 +02:00
Vlad Piersec
8983ea41fd fix(Drawer): Close drawer on item click
Clicking on an item when the popup drawer is displayed would keep it open.
Now clicking on any item should automatically close the drawer.

Popup was also refactored and no longer uses refs.
2021-11-02 15:45:29 +02:00
Nils Ohlmeier
cd6a814978 addressed review comments 2021-11-01 11:27:22 -05:00
Nils Ohlmeier
41f5872f70 fix(conference): store user selected device from Firefox prompt
When Firefox users choose a new device via the gUM prompt store
these as the new user selected devices, so they show up in the
settings menu.
2021-11-01 11:27:22 -05:00
Gabriel Borlea
af01072827 fix(speaker-stats): calculate total dominant speaker time if user is dominant and has no previous speaker time 2021-11-01 07:53:10 -07:00
Vlad Piersec
eaa084722f fix(Chat): Place Chat pane above Participants pane on mobile web 2021-11-01 15:30:59 +02:00
Vlad Piersec
318bc26fa0 fix(Polls): Fix polls pane on Firefox
On Firefox the create button is not visible.
Having a poll with long question/options breaks the UI.
2021-11-01 14:55:18 +02:00
tudordan7
072c29c0f9 fix(connection-indicator): Hide indicator circle when ghost icon is hidden. 2021-11-01 10:02:43 +01:00
Vlad Piersec
9ab16a0a5c chore(webpack): Fix source maps on hot reloading 2021-11-01 11:01:52 +02:00
robertpin
69d2cb52fe feat(virtual-bg) Added config to disable screen sharing as virtual bg 2021-11-01 09:50:22 +02:00
Vlad Piersec
6845a759a0 fix(Slider): Fix slider appearance on Firefox 2021-11-01 08:55:09 +02:00
robertpin
7aca5e71b9 refactor(participants-pane) Refactored with reusable components
Created Reusable components for:
- ListItem - used by participants list and lobby participants list
- ContextMenu - used by participant context menu and advanced moderation context menu
- Quick action button - used by quick action buttons on participant list items

Moved participants custom theme to base/components/themes

Created reusable button component for all participants pane buttons (Invite, Mute All, More)

Moved web components to web folder

Moved all styles from Styled Components to JSS

Fixed accessibility labels for some buttons

Removed unused code

Updated all styles to use theme tokens
2021-11-01 08:54:13 +02:00
Дамян Минков
78e825de36 fix: Fixes upgrading component to muc prosody config.
Fixes #10282.
2021-10-29 13:53:01 -07:00
robertpin
829f36e886 fix(lobby) Added data-testid and aria-label used for testing 2021-10-29 14:02:06 +03:00
robertpin
afbf261f67 fix(iframe) Keep URL params on iframe reload
Don't cleanup URL params when jitsi is in iframe
Fixes an issue where the user right-clicks on the iframe and Reload frame. This causes a refresh of the iframe, not the whole page, but without the parameters (jwt, configOverwrite etc are all lost). The parameters are part of the URL, so by not cleaning them up we make sure that on reload we still have all params
2021-10-29 10:59:54 +03:00
robertpin
76fc5a0806 fix(av-moderation) Stop screensharing on video mute on native
When the moderator stops the video for the participant stop screensharing (removes the track doesn't just mute it)
2021-10-29 09:21:47 +03:00
scott boone
09d3344c39 temporarily pin luajwtjitsi because v3 will intro a breaking change (#10262) 2021-10-27 16:24:56 -05:00
642 changed files with 14694 additions and 12732 deletions

View File

@@ -8,8 +8,12 @@ libs/*
resources/*
react/features/stream-effects/virtual-background/vendor/*
load-test/*
react/features/facial-recognition/resources/*
# ESLint will by default ignore its own configuration file. However, there does
# not seem to be a reason why we will want to risk being inconsistent with our
# remaining JavaScript source code.
!.eslintrc.js
# Not worth it.
actionTypes.js

View File

@@ -1,5 +1,5 @@
module.exports = {
'extends': [
'eslint-config-jitsi'
'@jitsi/eslint-config'
]
};

View File

@@ -10,8 +10,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '14.x'
- run: npm i -g npm@7
node-version: '16.x'
- run: npm install
- name: Check git status
run: git status

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
16

View File

@@ -7,6 +7,7 @@ OLM_DIR = node_modules/@matrix-org/olm
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/
FACIAL_MODELS_DIR = react/features/facial-recognition/resources
NODE_SASS = ./node_modules/.bin/sass
NPM = npm
OUTPUT_DIR = .
@@ -28,7 +29,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: 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-init:
rm -fr $(DEPLOY_DIR)
@@ -37,24 +38,26 @@ deploy-init:
deploy-appbundle:
cp \
$(BUILD_DIR)/app.bundle.min.js \
$(BUILD_DIR)/app.bundle.min.map \
$(BUILD_DIR)/app.bundle.min.js.map \
$(BUILD_DIR)/do_external_connect.min.js \
$(BUILD_DIR)/do_external_connect.min.map \
$(BUILD_DIR)/do_external_connect.min.js.map \
$(BUILD_DIR)/external_api.min.js \
$(BUILD_DIR)/external_api.min.map \
$(BUILD_DIR)/external_api.min.js.map \
$(BUILD_DIR)/flacEncodeWorker.min.js \
$(BUILD_DIR)/flacEncodeWorker.min.map \
$(BUILD_DIR)/flacEncodeWorker.min.js.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
$(BUILD_DIR)/dial_in_info_bundle.min.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.map \
$(BUILD_DIR)/alwaysontop.min.js.map \
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.map \
$(BUILD_DIR)/analytics-ga.min.js.map \
$(BUILD_DIR)/facial-expressions-worker.min.js \
$(BUILD_DIR)/facial-expressions-worker.min.js.map \
$(DEPLOY_DIR)
cp \
$(BUILD_DIR)/close3.min.js \
$(BUILD_DIR)/close3.min.map \
$(BUILD_DIR)/close3.min.js.map \
$(DEPLOY_DIR) || true
deploy-lib-jitsi-meet:
@@ -85,12 +88,17 @@ deploy-rnnoise-binary:
deploy-tflite:
cp \
$(TFLITE_WASM)/*.wasm \
$(DEPLOY_DIR)
$(DEPLOY_DIR)
deploy-meet-models:
cp \
$(MEET_MODELS_DIR)/*.tflite \
$(DEPLOY_DIR)
$(DEPLOY_DIR)
deploy-facial-expressions:
cp \
$(FACIAL_MODELS_DIR)/* \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
@@ -101,7 +109,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
dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-facial-expressions
$(WEBPACK_DEV_SERVER)
source-package:

View File

@@ -1,6 +1,6 @@
# <p align="center">Jitsi Meet</p>
Jitsi Meet is a set of Open Source projects which empower users to use and deploy
aaaJitsi Meet is a set of Open Source projects which empower users to use and deploy
video conferencing platforms with state-of-the-art video quality and features.
<hr />

View File

@@ -71,6 +71,7 @@ dependencies {
implementation project(':react-native-community_netinfo')
implementation project(':react-native-default-preference')
implementation project(':react-native-gesture-handler')
implementation project(':react-native-get-random-values')
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-masked-view_masked-view')

View File

@@ -190,6 +190,7 @@ class ReactInstanceManagerHolder {
new com.kevinresol.react_native_default_preference.RNDefaultPreferencePackage(),
new com.learnium.RNDeviceInfo.RNDeviceInfo(),
new com.swmansion.gesturehandler.react.RNGestureHandlerPackage(),
new org.linusu.RNGetRandomValuesPackage(),
new com.rnimmersive.RNImmersivePackage(),
new com.swmansion.rnscreens.RNScreensPackage(),
new com.zmxv.RNSound.RNSoundPackage(),

View File

@@ -17,6 +17,8 @@ include ':react-native-device-info'
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
include ':react-native-get-random-values'
project(':react-native-get-random-values').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-get-random-values/android')
include ':react-native-google-signin'
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/google-signin/android')
include ':react-native-immersive'

View File

@@ -1,8 +1,8 @@
/* global APP, JitsiMeetJS, config, interfaceConfig */
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from '@jitsi/logger';
import EventEmitter from 'events';
import Logger from 'jitsi-meet-logger';
import { openConnection } from './connection';
import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
@@ -29,6 +29,7 @@ import { shouldShowModeratedNotification } from './react/features/av-moderation/
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
_conferenceWillJoin,
authStatusChanged,
commonUserJoinedHandling,
commonUserLeftHandling,
@@ -47,7 +48,7 @@ import {
onStartMutedPolicyChanged,
p2pStatusChanged,
sendLocalParticipant,
_conferenceWillJoin
nonParticipantMessageReceived
} from './react/features/base/conference';
import { getReplaceParticipant } from './react/features/base/config/functions';
import {
@@ -760,7 +761,7 @@ export default {
// XXX The API will take care of disconnecting from the XMPP
// server (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
(new ConferenceConnector(resolve, reject)).connect();
new ConferenceConnector(resolve, reject).connect();
});
},
@@ -1015,6 +1016,14 @@ export default {
* dialogs in case of media permissions error.
*/
muteVideo(mute, showUI = true) {
if (this.videoSwitchInProgress) {
// Turning the camera on while the screen sharing mode is being turned off is causing issues around
// the presenter mode handling. It should be okay for the user to click the button again once that's done.
console.warn('muteVideo - unable to perform operations while video switch is in progress');
return;
}
if (!mute
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
logger.error('Unmuting video requires user interaction');
@@ -1352,11 +1361,49 @@ export default {
}
},
_createRoom(localTracks) {
/**
* Used by the Breakout Rooms feature to join a breakout room or go back to the main room.
*/
async joinRoom(roomName, isBreakoutRoom = false) {
this.roomName = roomName;
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks();
const localTracks = await tryCreateLocalTracks;
this._displayErrorsForCreateInitialLocalTracks(errors);
localTracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
track.mute();
}
});
this._createRoom(localTracks, isBreakoutRoom);
return new Promise((resolve, reject) => {
new ConferenceConnector(resolve, reject).connect();
});
},
_createRoom(localTracks, isBreakoutRoom = false) {
const extraOptions = {};
if (isBreakoutRoom) {
// We must be in a room already.
if (!room?.xmpp?.breakoutRoomsComponentAddress) {
throw new Error('Breakout Rooms not enabled');
}
// TODO: re-evaluate this. -saghul
extraOptions.customDomain = room.xmpp.breakoutRoomsComponentAddress;
}
room
= connection.initJitsiConference(
APP.conference.roomName,
this._getConferenceOptions());
{
...this._getConferenceOptions(),
...extraOptions
});
// Filter out the tracks that are muted (except on Safari).
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
@@ -1551,7 +1598,7 @@ export default {
APP.store.dispatch(toggleScreenshotCaptureSummary(false));
}
const tracks = APP.store.getState()['features/base/tracks'];
const timestamp = getLocalVideoTrack(tracks)?.timestamp ?? 0;
const duration = getLocalVideoTrack(tracks)?.jitsiTrack.getDuration() ?? 0;
// It can happen that presenter GUM is in progress while screensharing is being turned off. Here it needs to
// wait for that GUM to be resolved in order to prevent leaking the presenter track(this.localPresenterVideo
@@ -1614,7 +1661,7 @@ export default {
() => {
this.videoSwitchInProgress = false;
sendAnalytics(createScreenSharingEvent('stopped',
timestamp === 0 ? null : (Date.now() / 1000) - timestamp));
duration === 0 ? null : duration));
logger.info('Screen sharing stopped.');
},
error => {
@@ -1644,7 +1691,7 @@ export default {
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}, ignoreDidHaveVideo) {
logger.debug(`toggleScreenSharing: ${toggle}`);
if (this.videoSwitchInProgress) {
return Promise.reject('Switch in progress.');
return Promise.reject(`toggleScreenSharing: ${toggle} aborted - video switch in progress.`);
}
if (!JitsiMeetJS.isDesktopSharingEnabled()) {
return Promise.reject('Cannot toggle screen sharing: not supported.');
@@ -2214,6 +2261,10 @@ export default {
}
});
room.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(...args) => APP.store.dispatch(nonParticipantMessageReceived(...args)));
room.on(
JitsiConferenceEvents.LOCK_STATE_CHANGED,
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
@@ -2896,6 +2947,17 @@ export default {
});
},
/**
* Leaves the room.
*
* @returns {Promise}
*/
leaveRoom() {
if (room && room.isJoined()) {
return room.leave();
}
},
/**
* Leaves the room and calls JitsiConnection.disconnect.
*
@@ -2990,6 +3052,15 @@ export default {
room.sendEndpointMessage(to, payload);
},
/**
* Sends a facial expression as a string and its duration as a number
* @param {object} payload - Object containing the {string} facialExpression
* and {number} duration
*/
sendFacialExpression(payload) {
room.sendFacialExpression(payload);
},
/**
* Adds new listener.
* @param {String} eventName the name of the event

View File

@@ -279,6 +279,7 @@ var config = {
// autoHide: true,
// autoHideTimeout: 5000,
// disabled: false,
// disableDetails: false,
// inactiveDisabled: false
// },
@@ -429,6 +430,12 @@ var config = {
// Hides lobby button
// hideLobbyButton: false,
// If Lobby is enabled starts knocking automatically.
// autoKnockLobby: false,
// Hides add breakout room button
// hideAddRoomButton: false,
// Require users to always specify a display name.
// requireDisplayName: true,
@@ -648,6 +655,9 @@ var config = {
// Enables sending participants' emails (if available) to callstats and other analytics
// enableEmailInStats: false,
// Enables detecting faces of participants and get their expression and send it to other participants
// enableFacialRecognition: true,
// 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,
@@ -825,6 +835,10 @@ var config = {
// format: 'flac'
//
// },
// e2ee: {
// labels,
// externallyManagedKey: false
// },
// Options related to end-to-end (participant to participant) ping.
@@ -895,25 +909,55 @@ var config = {
If there is no url set or there are missing fields, the defaults are applied.
The config file should be in JSON.
None of the fields are mandatory and the response must have the shape:
{
// The domain url to apply (will replace the domain in the sharing conference link/embed section)
inviteDomain: 'example-company.org,
// The hex value for the colour used as background
backgroundColor: '#fff',
// The url for the image used as background
backgroundImageUrl: 'https://example.com/background-img.png',
// The anchor url used when clicking the logo image
logoClickUrl: 'https://example-company.org',
// The url used for the image used as logo
logoImageUrl: 'https://example.com/logo-img.png',
// Overwrite for pool of background images for avatars
avatarBackgrounds: ['url(https://example.com/avatar-background-1.png)', '#FFF'],
// The lobby/prejoin screen background
premeetingBackground: 'url(https://example.com/premeeting-background.png)',
// A list of images that can be used as video backgrounds.
// When this field is present, the default images will be replaced with those provided.
virtualBackgrounds: ['https://example.com/img.jpg']
}
{
// The domain url to apply (will replace the domain in the sharing conference link/embed section)
inviteDomain: 'example-company.org,
// The hex value for the colour used as background
backgroundColor: '#fff',
// The url for the image used as background
backgroundImageUrl: 'https://example.com/background-img.png',
// The anchor url used when clicking the logo image
logoClickUrl: 'https://example-company.org',
// The url used for the image used as logo
logoImageUrl: 'https://example.com/logo-img.png',
// Overwrite for pool of background images for avatars
avatarBackgrounds: ['url(https://example.com/avatar-background-1.png)', '#FFF'],
// The lobby/prejoin screen background
premeetingBackground: 'url(https://example.com/premeeting-background.png)',
// A list of images that can be used as video backgrounds.
// When this field is present, the default images will be replaced with those provided.
virtualBackgrounds: ['https://example.com/img.jpg'],
// Object containing a theme's properties. It also supports partial overwrites of the main theme.
// For a list of all possible theme tokens and their current defaults, please check:
// https://github.com/jitsi/jitsi-meet/tree/master/resources/custom-theme/custom-theme.json
// For a short explanations on each of the tokens, please check:
// https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/ui/Tokens.js
// IMPORTANT!: This is work in progress so many of the various tokens are not yet applied in code
// or they are partially applied.
customTheme: {
palette: {
ui01: "orange !important",
ui02: "maroon",
surface02: 'darkgreen',
ui03: "violet",
ui04: "magenta",
ui05: "blueviolet",
field02Hover: 'red',
action01: 'green',
action01Hover: 'lightgreen',
action02Disabled: 'beige',
success02: 'cadetblue',
action02Hover: 'aliceblue'
},
typography: {
labelRegular: {
fontSize: 25,
lineHeight: 30,
fontWeight: 500
}
}
}
}
*/
// dynamicBrandingUrl: '',
@@ -921,6 +965,9 @@ var config = {
// Only the default ones from will be available.
// disableAddingBackgroundImages: false,
// Disables using screensharing as virtual background.
// disableScreensharingVirtualBackground: false,
// Sets the background transparency level. '0' is fully transparent, '1' is opaque.
// backgroundAlpha: 1,

View File

@@ -1,7 +1,7 @@
/* global APP, JitsiMeetJS, config */
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
import { LoginDialog } from './react/features/authentication/components';

View File

@@ -103,3 +103,20 @@ div.Tooltip {
line-height: 14px;
padding: 8px;
}
// make modal full screen on landscape orientation
@media (max-height: 420px) {
.atlaskit-portal {
.css-1oc7v0j {
height: 100%;
padding: 0;
max-width: 100%;
top: 0;
width: 100%;
&> div {
height: 100%;
}
}
}
}

View File

@@ -11,12 +11,6 @@
{
@extend %connection-info;
/**
* Apply negative margin to reduce the appearance of padding in AtlasKit
* InlineDialog.
*/
margin: -15px;
> table {
white-space: nowrap;
@extend %connection-info;

View File

@@ -56,3 +56,9 @@
border-radius: 3px;
}
}
.mobile-browser.shift-right {
.participants_pane {
z-index: -1;
}
}

View File

@@ -21,6 +21,12 @@
margin-bottom: 8px;
}
.poll-creator {
color: #C2C2C2;
font-weight: 600;
margin: 4px 0 16px 0;
}
.poll-answer-container {
background: #3D3D3D;
border-radius: 3px;
@@ -134,7 +140,7 @@ ol.poll-result-list {
.poll-question {
font-size: 16px;
font-weight: 600;
margin-bottom: 16px;
line-height: 26px;
}
.poll-answer-voters {
@@ -236,7 +242,7 @@ ol.poll-result-list {
.polls-pane-content {
height: calc(100% - 102px);
height: 100%;
position: relative;
}
@@ -308,6 +314,10 @@ ol.poll-result-list {
padding: 10px 16px;
}
#polls-panel {
height: calc(100% - 102px);
}
.poll-container {
font-size: 14px;
font-weight: 600;

View File

@@ -46,3 +46,7 @@
padding: 16px 24px;
z-index: $popoverZ;
}
.padded-content {
padding: 4px 8px;
}

View File

@@ -5,6 +5,7 @@
.popupmenu {
background-color: $menuBG;
border-radius: 3px;
list-style-type: none;
min-width: 150px;
text-align: left;
padding: 0px;
@@ -38,6 +39,11 @@
}
}
&__list {
margin: 0;
padding: 0;
}
&__text {
display: inline-block;
margin-left: 8px;

View File

@@ -77,14 +77,6 @@
}
}
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
display: inline-block;
line-height: $newToolbarSize;
text-align: center;
}
.toolbar-button-with-badge {
display: inline-block;
position: relative;
@@ -115,86 +107,6 @@
padding-bottom: env(safe-area-inset-bottom, 0);
}
.toolbox-content-items {
background: $newToolbarBackgroundColor;
border-radius: 6px;
margin: 0 auto;
padding: 6px;
text-align: center;
pointer-events: all;
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
>div {
margin-left: 8px;
&:first-child {
margin-left: 0;
}
}
}
.overflow-menu {
font-size: 14px;
list-style-type: none;
padding: 8px 0;
background-color: $menuBG;
.profile-text {
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.overflow-menu-item {
align-items: center;
color: $overflowMenuItemColor;
cursor: pointer;
display: flex;
font-size: 14px;
font-weight: 400;
height: 40px;
line-height: 24px;
padding: 8px 16px;
box-sizing: border-box;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: $overflowMenuItemBackground;
}
}
div {
display: flex;
flex-direction: row;
align-items: center;
}
&.unclickable {
cursor: default;
}
&.disabled {
cursor: initial;
color: #929292;
&:hover {
background: none;
}
svg {
fill: #929292;
}
}
@media (hover: hover) and (pointer: fine) {
&.unclickable:hover {
background: inherit;
}
}
}
.beta-tag {
background: #36383C;
border-radius: 3px;
@@ -205,73 +117,12 @@
text-transform: uppercase;
}
.overflow-menu-item-icon {
margin-right: 16px;
i {
display: inline;
font-size: 24px;
}
@media (hover: hover) and (pointer: fine) {
i:hover {
background-color: initial;
}
}
img {
max-width: 24px;
max-height: 24px;
}
svg {
fill: #fff;
height: 20px;
width: 20px;
}
}
.overflow-menu-hr {
border-top: 1px solid #4C4D50;
border-bottom: 0;
margin: 8px 0;
}
.toolbox-icon {
display: flex;
border-radius: 3px;
flex-direction: column;
font-size: 24px;
height: $newToolbarSize;
justify-content: center;
width: $newToolbarSize;
@media (hover: hover) and (pointer: fine) {
&:hover {
background: $newToolbarButtonHoverColor;
}
}
@media (max-width: 320px) {
height: 36px;
width: 36px;
}
&.toggled {
background: $newToolbarButtonToggleColor;
}
&.disabled {
cursor: initial !important;
background-color: #36383c !important;
svg {
fill: #929292 !important;
}
}
}
.hangup-button {
background-color: $hangupColor;

View File

@@ -415,6 +415,11 @@
&.status-other {
background: $connectionIndicatorBg;
}
&.status-disabled {
background: transparent;
border: none
}
}
.local-video-menu-trigger,

View File

@@ -1,38 +0,0 @@
.copy-button {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 8px 8px 16px;
margin-top: 5px;
width: calc(100% - 24px);
height: 24px;
background: #0376DA;
border-radius: 4px;
cursor: pointer;
&:hover {
background: #278ADF;
font-weight: 600;
}
&-content {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 292px;
margin-right: 16px;
&.selected {
font-weight: 600;
}
}
&.clicked {
background: #31B76A;
}
& > div > svg > path {
fill: #fff;
}
}

View File

@@ -33,7 +33,6 @@ $flagsImagePath: "../images/";
@import 'inlay';
@import 'reload_overlay/reload_overlay';
@import 'mini_toolbox';
@import 'buttons/copy.scss';
@import 'modals/desktop-picker/desktop-picker';
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';

View File

@@ -27,7 +27,10 @@
.speaker-stats-item__status,
.speaker-stats-item__name,
.speaker-stats-item__time {
.speaker-stats-item__time,
.speaker-stats-item__name_expressions_on,
.speaker-stats-item__time_expressions_on,
.speaker-stats-item__expression {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
@@ -41,9 +44,35 @@
.speaker-stats-item__time {
width: 55%;
}
.speaker-stats-item__name_expressions_on {
width: 20%;
}
.speaker-stats-item__time_expressions_on {
width: 25%;
}
.speaker-stats-item__expression {
width: 7%;
text-align: center;
}
@media(max-width: 750px) {
.speaker-stats-item__name_expressions_on {
width: 25%;
}
.speaker-stats-item__time_expressions_on {
width: 30%;
}
.speaker-stats-item__expression {
width: 10%;
}
}
.speaker-stats-item__name,
.speaker-stats-item__time {
.speaker-stats-item__time,
.speaker-stats-item__name_expressions_on,
.speaker-stats-item__time_expressions_on,
.speaker-stats-item__expression {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

View File

@@ -138,7 +138,6 @@
.toolbox-content-items {
background: transparent;
border-radius: 0;
box-shadow: none;
display: flex;
justify-content: space-evenly;
@@ -171,6 +170,19 @@
}
}
// 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) {
.content {
padding: 16px;

View File

@@ -130,6 +130,8 @@ case "$1" in
echo -e " storage = \"memory\"" >> $PROSODY_HOST_CONFIG
echo -e " modules_enabled = { \"ping\"; }" >> $PROSODY_HOST_CONFIG
echo -e " admins = { \"$JICOFO_AUTH_USER@$JICOFO_AUTH_DOMAIN\", \"jvb@$JICOFO_AUTH_DOMAIN\" }" >> $PROSODY_HOST_CONFIG
echo -e " muc_room_locking = false" >> $PROSODY_HOST_CONFIG
echo -e " muc_room_default_public_jids = true" >> $PROSODY_HOST_CONFIG
fi
# Convert the old focus component config to the new one.

View File

@@ -61,7 +61,7 @@ case "$1" in
sed -i '/^\s*--\s*"token_verification"/ s/--\s*//' $PROSODY_HOST_CONFIG
# Install luajwt
if ! luarocks install luajwtjitsi; then
if ! luarocks install luajwtjitsi 2.0-0; then
echo "Failed to install luajwtjitsi - try installing it manually"
fi

View File

@@ -52,10 +52,12 @@ VirtualHost "jitmeet.example.com"
"external_services";
"conference_duration";
"muc_lobby_rooms";
"muc_breakout_rooms";
"av_moderation";
}
c2s_require_encryption = false
lobby_muc = "lobby.jitmeet.example.com"
breakout_rooms_muc = "breakout.jitmeet.example.com"
main_muc = "conference.jitmeet.example.com"
-- muc_lobby_whitelist = { "recorder.jitmeet.example.com" } -- Here we can whitelist jibri to enter lobby enabled rooms
@@ -73,6 +75,18 @@ Component "conference.jitmeet.example.com" "muc"
muc_room_locking = false
muc_room_default_public_jids = true
Component "breakout.jitmeet.example.com" "muc"
restrict_room_creation = true
storage = "memory"
modules_enabled = {
"muc_meeting_id";
"muc_domain_mapper";
--"token_verification";
}
admins = { "focusUser@auth.jitmeet.example.com" }
muc_room_locking = false
muc_room_default_public_jids = true
-- internal muc component
Component "internal.auth.jitmeet.example.com" "muc"
storage = "memory"

View File

@@ -1,102 +0,0 @@
// flow-typed signature: 609c1622fc97de96d59519934aa5ce87
// flow-typed version: c6154227d1/uuid_v3.x.x/flow_>=v0.32.x <=v0.103.x
declare module "uuid" {
declare class uuid {
static (
options?: {|
random?: number[],
rng?: () => number[] | Buffer
|},
buffer?: number[] | Buffer,
offset?: number
): string,
static v1(
options?: {|
node?: number[],
clockseq?: number,
msecs?: number | Date,
nsecs?: number
|},
buffer?: number[] | Buffer,
offset?: number
): string,
static v4(
options?: {|
random?: number[],
rng?: () => number[] | Buffer
|},
buffer?: number[] | Buffer,
offset?: number
): string
}
declare module.exports: Class<uuid>;
}
declare module "uuid/v1" {
declare class v1 {
static (
options?: {|
node?: number[],
clockseq?: number,
msecs?: number | Date,
nsecs?: number
|},
buffer?: number[] | Buffer,
offset?: number
): string
}
declare module.exports: Class<v1>;
}
declare module "uuid/v3" {
declare class v3 {
static (
name?: string | number[],
namespace?: string | number[],
buffer?: number[] | Buffer,
offset?: number
): string,
static name: string,
static DNS: string,
static URL: string
}
declare module.exports: Class<v3>;
}
declare module "uuid/v4" {
declare class v4 {
static (
options?: {|
random?: number[],
rng?: () => number[] | Buffer
|},
buffer?: number[] | Buffer,
offset?: number
): string
}
declare module.exports: Class<v4>;
}
declare module "uuid/v5" {
declare class v5 {
static (
name?: string | number[],
namespace?: string | number[],
buffer?: number[] | Buffer,
offset?: number
): string,
static name: string,
static DNS: string,
static URL: string
}
declare module.exports: Class<v5>;
}

71
flow-typed/npm/uuid_v8.x.x.js vendored Normal file
View File

@@ -0,0 +1,71 @@
declare module 'uuid' {
// v1 (Timestamp)
declare type V1Options = {|
node?: $ReadOnlyArray<number>,
clockseq?: number,
msecs?: number,
nsecs?: number,
random?: $ReadOnlyArray<number>,
rng?: () => $ReadOnlyArray<number>,
|};
declare export function v1(options?: V1Options): string;
declare export function v1(
options: V1Options | null,
buffer: Array<number>,
offset?: number
): Array<number>;
// v3 (Namespace)
declare function v3Impl(
name: string | $ReadOnlyArray<number>,
namespace: string | $ReadOnlyArray<number>
): string;
declare function v3Impl(
name: string | $ReadOnlyArray<number>,
namespace: string | $ReadOnlyArray<number>,
buffer: Array<number>,
offset?: number
): Array<number>;
declare export var v3: {|
[[call]]: typeof v3Impl,
DNS: string,
URL: string,
|};
// v4 (Random)
declare type V4Options = {|
random?: $ReadOnlyArray<number>,
rng?: () => $ReadOnlyArray<number>,
|};
declare export function v4(options?: V4Options): string;
declare export function v4(
options: V4Options | null,
buffer: Array<number>,
offset?: number
): Array<number>;
// v5 (Namespace)
declare function v5Impl(
name: string | $ReadOnlyArray<number>,
namespace: string | $ReadOnlyArray<number>
): string;
declare function v5Impl(
name: string | $ReadOnlyArray<number>,
namespace: string | $ReadOnlyArray<number>,
buffer: Array<number>,
offset?: number
): Array<number>;
declare export var v5: {|
[[call]]: typeof v5Impl,
DNS: string,
URL: string,
|};
}

View File

@@ -57,6 +57,7 @@ target 'JitsiMeetSDK' do
pod 'amplitude-react-native', :path => '../node_modules/@amplitude/react-native'
pod 'react-native-background-timer', :path => '../node_modules/react-native-background-timer'
pod 'react-native-calendar-events', :path => '../node_modules/react-native-calendar-events'
pod 'react-native-get-random-values', :path => '../node_modules/react-native-get-random-values'
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake'
pod 'react-native-netinfo', :path => '../node_modules/@react-native-community/netinfo'
pod 'react-native-performance', :path => '../node_modules/react-native-performance/ios'

View File

@@ -284,6 +284,8 @@ PODS:
- React
- react-native-calendar-events (2.0.0):
- React
- react-native-get-random-values (1.7.0):
- React-Core
- react-native-keep-awake (4.0.0):
- React
- react-native-netinfo (4.1.5):
@@ -412,6 +414,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
- react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
- react-native-get-random-values (from `../node_modules/react-native-get-random-values`)
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-performance (from `../node_modules/react-native-performance/ios`)
@@ -502,6 +505,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-background-timer"
react-native-calendar-events:
:path: "../node_modules/react-native-calendar-events"
react-native-get-random-values:
:path: "../node_modules/react-native-get-random-values"
react-native-keep-awake:
:path: "../node_modules/react-native-keep-awake"
react-native-netinfo:
@@ -603,6 +608,7 @@ SPEC CHECKSUMS:
React-jsinspector: 92ceee6c66dc19886289b52436ade7e020b89602
react-native-background-timer: 029c606b3fd6dd476b153e177c518b6ade4c169f
react-native-calendar-events: 82dc6c915653a1a8a266e43fdbbfdb3b1022ca99
react-native-get-random-values: 237bffb1c7e05fb142092681531810a29ba53015
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
react-native-netinfo: 0e563248a4b9a99c33ec29bd03c2d50767db22a6
react-native-performance: 6bd6cfac80594775fb782405fceaaf206becf53b
@@ -635,6 +641,6 @@ SPEC CHECKSUMS:
RNWatch: a5320c959c75e72845c07985f3e935e58998f1d3
Yoga: 96b469c5e81ff51b917b92e8c3390642d4ded30c
PODFILE CHECKSUM: 42be6796ba6ac039dae5c02125677728ecd0df0d
PODFILE CHECKSUM: 836d4804218c0608e1326471ec83fe31cfa9c86d
COCOAPODS: 1.11.2

View File

@@ -236,7 +236,7 @@
"remoteControlDeniedMessage": "{{user}} ha rebutjat la petició de control remot!",
"remoteControlErrorMessage": "S'ha produït un error en intentar sol·licitar a {{user}} permisos de control remot!",
"remoteControlRequestMessage": "Voleu permetre a {{user}} de controlar remotament el vostre escriptori?",
"remoteControlShareScreenWarning": "Tingueu present que si pitgeu \"Permet\" compartireu la vostra pantalla!",
"remoteControlShareScreenWarning": "Tingueu present que si pitgeu \"Permet\"compartireu la vostra pantalla!",
"remoteControlStopMessage": "La sessió de control remot ha finalitzat!",
"remoteControlTitle": "Control d'escriptori remot",
"Remove": "Suprimeix",
@@ -577,7 +577,7 @@
},
"startupoverlay": {
"title": "{{app}} requereix usar el micròfon i la càmera.",
"policyText": " "
"policyText": ""
},
"suspendedoverlay": {
"rejoinKeyTitle": "Torna a entrar",
@@ -782,5 +782,42 @@
},
"helpView": {
"header": "Centre d'ajuda"
},
"lobby": {
"admit": "Acceptar",
"admitAll": "Acceptar Tots",
"knockingParticipantList": "Participants en espera",
"allow": "Permetre",
"backToKnockModeButton": "Sol·licitar entrada",
"dialogTitle": "Sala despera",
"disableDialogContent": "La sala despera està habilitada actualment. Aquesta funció garanteix que els participants no desitjats no puguin unir-se a la seva reunió. Vols deshabilitar-ho?",
"disableDialogSubmit": "Deshabilitat",
"emailField": "Introdueix el teu email",
"enableDialogPasswordField": "Estableix la contrasenya (opcional)",
"enableDialogSubmit": "Habilitat",
"enableDialogText": "La sala despera li permet protegir la seva reunió en permetre que les persones només entrin després duna aprovació formal per part dun moderador.",
"enterPasswordButton": "Introduir la contrasenya de la reunió",
"enterPasswordTitle": "Introdueixi la contrasenya per a unir-se a la reunió",
"errorMissingPassword": "Si us plau, Introdueixi la contrasenya de la reunió",
"invalidPassword": "Contrasenya errònia",
"joiningMessage": "Tuniràs a la reunió tan aviat com algú accepti la teva sol·licitud",
"joinWithPasswordMessage": "Intentant unir-se amb contrasenya, esperi ...",
"joinRejectedMessage": "Un moderador va rebutjar la seva sol·licitud per a unir-se.",
"joinTitle": "Unir-se a la reunió",
"joiningTitle": "Sol·licitar unir-se a la reunió...",
"joiningWithPasswordTitle": "Connectant-se a la reunió amb contrasenya...",
"knockButton": "Sol·licitar entrada a la reunió",
"knockTitle": "Algú vol unir-se a la reunió",
"nameField": "Introdueix el teu nom",
"notificationLobbyAccessDenied": "{{originParticipantName}} ha rebutjat lentrada a la reunió de {{targetParticipantName}}",
"notificationLobbyAccessGranted": "{{originParticipantName}} ha acceptat lentrada a la reunió de {{targetParticipantName}}",
"notificationLobbyDisabled": "La sala despera ha estat deshabilitada per {{originParticipantName}}",
"notificationLobbyEnabled": "La sala despera ha estat habilitada per {{originParticipantName}}",
"notificationTitle": "Sala despera",
"passwordField": "Introduir la contrasenya de la reunió",
"passwordJoinButton": "Unir-se",
"reject": "Rebutjar",
"rejectAll": "Rebutjar Tots",
"toggleLabel": "Habilitar sala despera"
}
}

View File

@@ -501,6 +501,7 @@
"expandedPending": "Livestream wird gestartet …",
"failedToStart": "Livestream konnte nicht gestartet werden",
"getStreamKeyManually": "Wir waren nicht in der Lage, Livestreams abzurufen. Versuchen Sie, Ihren Livestream-Schlüssel von YouTube zu erhalten.",
"inProgress": "Livestreaming gestartet",
"invalidStreamKey": "Der Livestream-Schlüssel ist u. U. falsch.",
"off": "Livestream gestoppt",
"offBy": "{{name}} stoppte den Livestream",
@@ -508,6 +509,7 @@
"onBy": "{{name}} startete den Livestream",
"pending": "Livestream wird gestartet …",
"serviceName": "Livestreaming-Dienst",
"sessionAlreadyActive": "Diese Konferenz wird bereits als Livestream übertragen.",
"signedInAs": "Sie sind derzeit angemeldet als:",
"signIn": "Mit Google anmelden",
"signInCTA": "Anmelden oder den Streamschlüssel des YouTube-Livestreams eingeben.",
@@ -620,6 +622,7 @@
"blockEveryoneMicCamera": "Kamera und Mikrofon von allen sperren",
"invite": "Person einladen",
"askUnmute": "Anfragen, Stummschaltung aufzuheben",
"moreModerationActions": "Weitere Moderationsoptionen",
"mute": "Stummschalten",
"muteAll": "Alle stummschalten",
"muteEveryoneElse": "Alle anderen stummschalten",
@@ -627,11 +630,13 @@
"stopVideo": "Kamera ausschalten",
"unblockEveryoneMicCamera": "Kamera und Mikrofon von allen entsperren",
"videoModeration": "Kamera einschalten"
}
},
"search": "Suche Anwesende"
},
"passwordSetRemotely": "von einer anderen Person gesetzt",
"passwordDigitsOnly": "Bis zu {{number}} Ziffern",
"polls": {
"by": "Von {{ name }}",
"create": {
"addOption": "Antwort hinzufügen",
"answerPlaceholder": "Antwort {{index}}",
@@ -757,6 +762,7 @@
"expandedPending": "Aufzeichnung wird gestartet…",
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
"fileSharingdescription": "Aufzeichnung mit den Personen der Konferenz teilen",
"inProgress": "Aufzeichnung gestartet",
"linkGenerated": "Link zur Aufzeichnung wurde generiert.",
"live": "LIVE",
"loggedIn": "Als {{userName}} angemeldet",
@@ -769,6 +775,7 @@
"serviceDescription": "Ihre Aufzeichnung wird vom Aufzeichnungsdienst gespeichert",
"serviceDescriptionCloud": "Cloud-Aufzeichnung",
"serviceName": "Aufnahmedienst",
"sessionAlreadyActive": "Diese Konferenz wird bereits aufgezeichnet.",
"signIn": "Anmelden",
"signOut": "Abmelden",
"unavailable": "Oh! Der {{serviceName}} ist aktuell nicht verfügbar. Wir arbeiten an der Behebung des Problems. Bitte versuchen Sie es später noch einmal.",
@@ -855,7 +862,14 @@
"name": "Name",
"seconds": "{{count}} Sek.",
"speakerStats": "Sprechstatistik",
"speakerTime": "Sprechzeit"
"speakerTime": "Sprechzeit",
"happy": "Fröhlich",
"neutral": "Neutral",
"sad": "Traurig",
"surprised": "Überrascht",
"angry": "Sauer",
"fearful": "Ängstlich",
"disgusted": "Angeekelt"
},
"startupoverlay": {
"policyText": " ",
@@ -903,7 +917,7 @@
"pip": "Bild-in-Bild-Modus ein-/ausschalten",
"privateMessage": "Private Nachricht senden",
"profile": "Profil bearbeiten",
"raiseHand": "Hand erheben / senken",
"raiseHand": "Hand heben",
"reactionsMenu": "Interaktionsmenü öffnen / schließen",
"recording": "Aufzeichnung ein-/ausschalten",
"remoteMute": "Personen stummschalten",
@@ -940,7 +954,7 @@
"chat": "Chat öffnen / schließen",
"clap": "Klatschen",
"closeChat": "Chat schließen",
"closeReactionsMenu": "Interationsmenü schließen",
"closeReactionsMenu": "Interaktionsmenü schließen",
"disableReactionSounds": "Sie können die Interaktionstöne für diese Konferenz deaktivieren",
"documentClose": "Geteiltes Dokument schließen",
"documentOpen": "Geteiltes Dokument öffnen",
@@ -975,12 +989,12 @@
"noisyAudioInputTitle": "Ihr Mikrofon scheint lärmintensiv zu sein!",
"noisyAudioInputDesc": "Es klingt, als ob Ihr Mikrofon Störgeräusche verursacht. Bitte überlegen Sie, ob Sie das Gerät stummschalten oder austauschen wollen.",
"openChat": "Chat öffnen",
"openReactionsMenu": "Interationsmenü öffnen",
"openReactionsMenu": "Interaktionsmenü öffnen",
"participants": "Anwesende",
"pip": "Bild-in-Bild-Modus einschalten",
"privateMessage": "Private Nachricht senden",
"profile": "Profil bearbeiten",
"raiseHand": "Hand erheben / senken",
"raiseHand": "Hand heben",
"raiseYourHand": "Melden",
"reactionBoo": "Buhen senden",
"reactionClap": "Klatschen senden",
@@ -1045,7 +1059,10 @@
"pending": "{{displayName}} wurde eingeladen"
},
"videoStatus": {
"adjustFor": "Einstellen für:",
"audioOnly": "AUD",
"bestPerformance": "Beste Leistung",
"highestQuality": "Höchste Qualität",
"audioOnlyExpanded": "Sie befinden sich im Modus „Nur Audio“. Dieser Modus benötigt weniger Bandbreite, Sie sehen jedoch nicht die Videos der anderen.",
"callQuality": "Videoqualität",
"hd": "HD",
@@ -1056,6 +1073,7 @@
"ld": "LD",
"ldTooltip": "Video wird in niedriger Auflösung angezeigt",
"lowDefinition": "Niedrige Auflösung",
"performanceSettings": "Qualitätseinstellungen",
"sd": "SD",
"sdTooltip": "Video wird in Standardauflösung angezeigt",
"standardDefinition": "Standardauflösung"
@@ -1129,6 +1147,12 @@
"button": "Andere einladen",
"youAreAlone": "Nur Sie sind in dieser Konferenz"
},
"termsView": {
"header": "Nutzungsbedingungen"
},
"privacyView": {
"header": "Datenschutz"
},
"helpView": {
"header": "Hilfecenter"
},

View File

@@ -633,6 +633,7 @@
"passwordSetRemotely": "défini par un autre participant",
"passwordDigitsOnly": "Jusqu'à {{number}} chiffres",
"polls": {
"by": "Par {{ name }}",
"create": {
"addOption": "Ajouter une option",
"answerPlaceholder": "Option {{index}}",

View File

@@ -268,12 +268,12 @@
"muteEveryoneStartMuted": "A partir de agora, toda a gente começa a ficar calada",
"muteParticipantBody": "Não poderá reativá-los, mas eles podem reativar-se a qualquer momento.",
"muteParticipantButton": "Silenciar",
"muteParticipantDialog": "Tem a certeza de que quer silenciar este participante? Não poderá reativá-los, mas eles podem reativar-se a qualquer momento.",
"muteParticipantsVideoDialog": "Tem a certeza de que quer desativar a câmara deste participante? Não poderá voltar a ativar a câmara, mas eles podem voltar a reativá-la a qualquer momento.",
"muteParticipantTitle": "Silenciar este participante?",
"muteParticipantsVideoDialogModerationOn": "Tem a certeza de que quer desligar a câmara deste participante? Não será capaz de voltar a ligar a câmara e eles também não.",
"muteParticipantsVideoButton": "Parar vídeo",
"muteParticipantsVideoTitle": "Desativar a câmara deste participante?",
"muteParticipantsVideoBody": "Não poderá voltar a ligar a câmara, mas eles podem voltar a ligá-la a qualquer momento.",
"muteParticipantsVideoBodyModerationOn": "Não será capaz de voltar a ligar a câmara e eles também não.",
"noDropboxToken": "Nenhum token do Dropbox válido",
"Ok": "OK",
"password": "Palavra-passe",
@@ -377,7 +377,7 @@
},
"virtualBackground": {
"apply": "Aplicar",
"title": "Planos de Fundo virtuais",
"title": "Planos de fundo virtuais",
"blur": "Desfocagem",
"slightBlur": "Desfocagem ligeira",
"removeBackground": "Remover imagem de fundo",
@@ -568,9 +568,9 @@
"moderator": "É agora um moderador",
"muted": "Você iniciou uma conversa com o microfone desativado.",
"mutedTitle": "Você está silenciado!",
"mutedRemotelyTitle": "Foi silenciado pelo {{moderador}}",
"mutedRemotelyTitle": "Foi silenciado pelo {{participantDisplayName}}",
"mutedRemotelyDescription": "Pode sempre voltar a ativar o microfone quando estiver pronto para falar. Silencie de volta quando estiver pronto para manter o barulho afastado da reunião.",
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{moderador}}.",
"videoMutedRemotelyTitle": "A sua câmara foi desligada pelo {{participantDisplayName}}.",
"videoMutedRemotelyDescription": "Pode sempre ligá-la novamente.",
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removido por outro participante",
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) definido por outro participante",
@@ -616,22 +616,25 @@
"actions": {
"allow": "Permitir aos participantes:",
"allowVideo": "Permitir vídeo",
"audioModeration": "Eles próprios ativam o som",
"audioModeration": "Ativarem o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"invite": "Convidar alguém",
"askUnmute": "Pedir para ligar o microfone",
"moreModerationActions": "Mais opções de moderação",
"mute": "Silenciar",
"muteAll": "Silenciar todos",
"muteEveryoneElse": "Silenciar todos os outros",
"stopEveryonesVideo": "Desligar a câmara de todos",
"stopVideo": "Desligar a câmara",
"unblockEveryoneMicCamera": "Desbloquear o microfone e a câmara de todos",
"videoModeration": "Ligar a câmara deles"
}
"videoModeration": "Ligarem a câmara deles"
},
"search": "Pesquisar participantes"
},
"passwordSetRemotely": "Definido por outro participante",
"passwordDigitsOnly": "Até {{number}} dígitos",
"polls": {
"by": "Por {{ name }}",
"create": {
"addOption": "Adicionar opção",
"answerPlaceholder": "Opção {{index}}",
@@ -1045,7 +1048,10 @@
"pending": "{{displayName}} foi convidado"
},
"videoStatus": {
"adjustFor": "Ajustar para:",
"audioOnly": "AUD",
"bestPerformance": "Melhor desempenho",
"highestQuality": "Máxima qualidade",
"audioOnlyExpanded": "Está em modo de baixa largura de banda. Neste modo, receberá apenas partilha de áudio e ecrã.",
"callQuality": "Qualidade de vídeo",
"hd": "HD",
@@ -1056,6 +1062,7 @@
"ld": "LD",
"ldTooltip": "Ver vídeo em baixa definição",
"lowDefinition": "Baixa definição (LD)",
"performanceSettings": "Definições de desempenho",
"sd": "SD",
"sdTooltip": "Ver vídeo em definição padrão",
"standardDefinition": "Definição padrão"

View File

@@ -238,10 +238,10 @@
"internalError": "Что-то пошло не так. Ошибка: {{error}}",
"internalErrorTitle": "Внутренняя ошибка",
"kickMessage": "Вы можете связаться с {{participantDisplayName}} для получения более подробной информации.",
"kickParticipantButton": "Выгнать",
"kickParticipantDialog": "Вы уверены, что хотите выгнать этого участника?",
"kickParticipantTitle": "Выгнать этого участника?",
"kickTitle": "Ай! {{participantDisplayName}} выгнал вас из конференции.",
"kickParticipantButton": "Отключить",
"kickParticipantDialog": "Вы уверены, что хотите отключить этого участника?",
"kickParticipantTitle": "Отключить этого участника?",
"kickTitle": "{{participantDisplayName}} отключил вас от конференции.",
"liveStreaming": "Трансляция",
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Невозможно пока активна запись",
"liveStreamingDisabledForGuestTooltip": "Гости не могут начать трансляцию",
@@ -482,6 +482,7 @@
"passwordField": "Введите пароль встречи",
"passwordJoinButton": "Присоединиться",
"reject": "Отказать",
"rejectAll": "Отказать всем",
"toggleLabel": "Включить лобби"
},
"localRecording": {
@@ -523,6 +524,7 @@
},
"me": "я",
"notify": {
"allowAction": "Разрешить",
"OldElectronAPPTitle": "Уязвимость в системе безопасности!",
"connectedOneMember": "{{name}} присоединился к конференции",
"connectedThreePlusMembers": "{{name}} и {{count}} других пользователей присоединились к конференции",
@@ -534,7 +536,7 @@
"invitedOneMember": "{{name}} был приглашен",
"invitedThreePlusMembers": "Приглашены {{name}} и {{count}} других пользователей(ля)",
"invitedTwoMembers": "{{first}} и {{second}} присоединились к конференции",
"kickParticipant": "{{kicker}} выгнал {{kicked}}",
"kickParticipant": "{{kicker}} отключил {{kicked}}",
"me": "Я",
"moderator": "Получены права модератора!",
"muted": "Вы начали разговор без звука.",
@@ -801,7 +803,7 @@
"moreOptions": "Больше настроек",
"mute": "Микрофон (вкл./выкл.)",
"muteEveryone": "Выкл. микрофон у всех",
"muteEveryonesVideo": "Выкл. Камеру у всех",
"muteEveryonesVideo": "Выкл. камеру у всех",
"noAudioSignalTitle": "От вашего микрофона не идет звуковой сигнал!",
"noAudioSignalDesc": "Если вы специально не отключали микрофон в системных настройках, подумайте о том, чтобы поменять его.",
"noAudioSignalDescSuggestion": "Если вы специально не отключали микрофон в системных настройках, вы можете попробовать использовать следующее устройство:",

View File

@@ -39,6 +39,20 @@
"audioOnly": {
"audioOnly": "Low bandwidth"
},
"breakoutRooms": {
"defaultName": "Breakout room #{{index}}",
"mainRoom": "Main room",
"actions": {
"add": "Add breakout room",
"autoAssign": "Auto assign to breakout rooms",
"close": "Close",
"join": "Join",
"leaveBreakoutRoom": "Leave breakout room",
"more": "More",
"remove": "Remove",
"sendToBreakoutRoom": "Send participant to:"
}
},
"calendarSync": {
"addMeetingURL": "Add a meeting link",
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
@@ -268,12 +282,12 @@
"muteEveryoneStartMuted": "Everyone starts muted from now on",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantButton": "Mute",
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantsVideoDialog": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on, but they can turn it back on at any time.",
"muteParticipantTitle": "Mute this participant?",
"muteParticipantsVideoDialogModerationOn": "Are you sure you want to turn off this participant's camera? You won't be able to turn the camera back on and neither will they.",
"muteParticipantsVideoButton": "Stop video",
"muteParticipantsVideoTitle": "Disable camera of this participant?",
"muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
"muteParticipantsVideoBodyModerationOn": "You won't be able to turn the camera back on and neither will they.",
"noDropboxToken": "No valid Dropbox token",
"Ok": "OK",
"password": "Password",
@@ -501,6 +515,7 @@
"expandedPending": "The live streaming is being started...",
"failedToStart": "Live Streaming failed to start",
"getStreamKeyManually": "We werent able to fetch any live streams. Try getting your live stream key from YouTube.",
"inProgress": "Recording or live streaming in progress",
"invalidStreamKey": "Live stream key may be incorrect.",
"off": "Live Streaming stopped",
"offBy": "{{name}} stopped the live streaming",
@@ -508,6 +523,7 @@
"onBy": "{{name}} started the live streaming",
"pending": "Starting Live Stream...",
"serviceName": "Live Streaming service",
"sessionAlreadyActive": "This session is already being recorded or live streamed.",
"signedInAs": "You are currently signed in as:",
"signIn": "Sign in with Google",
"signInCTA": "Sign in or enter your live stream key from YouTube.",
@@ -620,6 +636,8 @@
"blockEveryoneMicCamera": "Block everyone's mic and camera",
"invite": "Invite Someone",
"askUnmute": "Ask to unmute",
"moreModerationActions": "More moderation options",
"moreParticipantOptions": "More participant options",
"mute": "Mute",
"muteAll": "Mute all",
"muteEveryoneElse": "Mute everyone else",
@@ -633,6 +651,7 @@
"passwordSetRemotely": "Set by another participant",
"passwordDigitsOnly": "Up to {{number}} digits",
"polls": {
"by": "By {{ name }}",
"create": {
"addOption": "Add option",
"answerPlaceholder": "Option {{index}}",
@@ -758,6 +777,7 @@
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"fileSharingdescription": "Share recording with meeting participants",
"inProgress": "Recording or live streaming in progress",
"linkGenerated": "We have generated a link to your recording.",
"live": "LIVE",
"loggedIn": "Logged in as {{userName}}",
@@ -770,6 +790,7 @@
"serviceDescription": "Your recording will be saved by the recording service",
"serviceDescriptionCloud": "Cloud recording",
"serviceName": "Recording service",
"sessionAlreadyActive": "This session is already being recorded or live streamed.",
"signIn": "Sign in",
"signOut": "Sign out",
"unavailable": "Oops! The {{serviceName}} is currently unavailable. We're working on resolving the issue. Please try again later.",
@@ -856,7 +877,14 @@
"name": "Name",
"seconds": "{{count}}s",
"speakerStats": "Speaker Stats",
"speakerTime": "Speaker Time"
"speakerTime": "Speaker Time",
"happy": "Happy",
"neutral": "Neutral",
"sad": "Sad",
"surprised": "Surprised",
"angry": "Angry",
"fearful": "Fearful",
"disgusted": "Disgusted"
},
"startupoverlay": {
"policyText": " ",
@@ -873,6 +901,7 @@
"audioOnly": "Toggle audio only",
"audioRoute": "Select the sound device",
"boo": "Boo",
"breakoutRoom": "Join/leave breakout room",
"callQuality": "Manage video quality",
"cc": "Toggle subtitles",
"chat": "Open / Close chat",
@@ -956,7 +985,9 @@
"hangup": "Leave the meeting",
"help": "Help",
"invite": "Invite people",
"joinBreakoutRoom": "Join breakout room",
"laugh": "Laugh",
"leaveBreakoutRoom": "Leave breakout room",
"like": "Thumbs Up",
"lobbyButtonDisable": "Disable lobby mode",
"lobbyButtonEnable": "Enable lobby mode",
@@ -1134,6 +1165,12 @@
"button": "Invite others",
"youAreAlone": "You are the only one in the meeting"
},
"termsView": {
"header": "Terms"
},
"privacyView": {
"header": "Privacy"
},
"helpView": {
"header": "Help center"
},

View File

@@ -1,6 +1,6 @@
// @flow
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import {
createApiEvent,
@@ -52,7 +52,7 @@ import {
processExternalDeviceRequest
} from '../../react/features/device-selection/functions';
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
import { toggleE2EE } from '../../react/features/e2ee/actions';
import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/actions';
import { setVolume } from '../../react/features/filmstrip';
import { invite } from '../../react/features/invite';
import {
@@ -364,6 +364,9 @@ function initCommands() {
logger.debug('Toggle E2EE key command received');
APP.store.dispatch(toggleE2EE(enabled));
},
'set-media-encryption-key': keyInfo => {
APP.store.dispatch(setMediaEncryptionKey(JSON.parse(keyInfo)));
},
'set-video-quality': frameHeight => {
logger.debug('Set video quality command received');
sendAnalytics(createApiEvent('set.video.quality'));

View File

@@ -12,6 +12,6 @@ import { parseURLParams } from '../../react/features/base/util/parseURLParams';
export const API_ID = parseURLParams(window.location).jitsi_meet_external_api_id;
/**
* The payload name for the datachannel/endpoint text message event
* The payload name for the datachannel/endpoint text message event.
*/
export const ENDPOINT_TEXT_MESSAGE_NAME = 'endpoint-text-message';

View File

@@ -24,7 +24,7 @@ const ALWAYS_ON_TOP_FILENAMES = [
/**
* Maps the names of the commands expected by the API with the name of the
* commands expected by jitsi-meet
* commands expected by jitsi-meet.
*/
const commands = {
answerKnockingParticipant: 'answer-knocking-participant',
@@ -50,6 +50,7 @@ const commands = {
sendTones: 'send-tones',
setFollowMe: 'set-follow-me',
setLargeVideoParticipant: 'set-large-video-participant',
setMediaEncryptionKey: 'set-media-encryption-key',
setParticipantVolume: 'set-participant-volume',
setTileView: 'set-tile-view',
setVideoQuality: 'set-video-quality',
@@ -63,6 +64,7 @@ const commands = {
toggleCamera: 'toggle-camera',
toggleCameraMirror: 'toggle-camera-mirror',
toggleChat: 'toggle-chat',
toggleE2EE: 'toggle-e2ee',
toggleFilmStrip: 'toggle-film-strip',
toggleModeration: 'toggle-moderation',
toggleRaiseHand: 'toggle-raise-hand',
@@ -75,7 +77,7 @@ const commands = {
/**
* Maps the names of the events expected by the API with the name of the
* events expected by jitsi-meet
* events expected by jitsi-meet.
*/
const events = {
'avatar-changed': 'avatarChanged',
@@ -129,7 +131,8 @@ const events = {
};
/**
* Last id of api object
* Last id of api object.
*
* @type {number}
*/
let id = 0;
@@ -387,7 +390,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
return ALWAYS_ON_TOP_FILENAMES.map(
filename => (new URL(filename, baseURL)).href
filename => new URL(filename, baseURL).href
);
}
@@ -1184,6 +1187,40 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @returns {void}
*/
stopRecording(mode) {
this.executeCommand('startRecording', mode);
this.executeCommand('stopRecording', mode);
}
/**
* Sets e2ee enabled/disabled.
*
* @param {boolean} enabled - The new value for e2ee enabled.
* @returns {void}
*/
toggleE2EE(enabled) {
this.executeCommand('toggleE2EE', enabled);
}
/**
* Sets the key and keyIndex for e2ee.
*
* @param {Object} keyInfo - Json containing key information.
* @param {CryptoKey} [keyInfo.encryptionKey] - The encryption key.
* @param {number} [keyInfo.index] - The index of the encryption key.
* @returns {void}
*/
async setMediaEncryptionKey(keyInfo) {
const { key, index } = keyInfo;
if (key) {
const exportedKey = await crypto.subtle.exportKey('raw', key);
this.executeCommand('setMediaEncryptionKey', JSON.stringify({
exportedKey: Array.from(new Uint8Array(exportedKey)),
index }));
} else {
this.executeCommand('setMediaEncryptionKey', JSON.stringify({
exportedKey: false,
index }));
}
}
}

View File

@@ -1,6 +1,6 @@
// @flow
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
const logger = Logger.getLogger(__filename);

View File

@@ -3,8 +3,8 @@
const UI = {};
import Logger from '@jitsi/logger';
import EventEmitter from 'events';
import Logger from 'jitsi-meet-logger';
import { isMobileBrowser } from '../../react/features/base/environment/utils';
import { setColorAlpha } from '../../react/features/base/util';

View File

@@ -1,6 +1,6 @@
// @flow
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import { openConnection } from '../../../connection';
import {

View File

@@ -1,4 +1,4 @@
/* global $, APP */
/* global APP */
import {
NOTIFICATION_TIMEOUT,
@@ -8,44 +8,6 @@ import {
} from '../../../react/features/notifications';
const messageHandler = {
OK: 'dialog.OK',
CANCEL: 'dialog.Cancel',
/**
* Returns the formatted title string.
*
* @return the title string formatted as a div.
*/
_getFormattedTitleString(titleKey) {
const $titleString = $('<h2>');
$titleString.addClass('aui-dialog2-header-main');
$titleString.attr('data-i18n', titleKey);
return $('<div>').append($titleString)
.html();
},
/**
* Returns the dialog css classes.
*
* @return the dialog css classes
*/
_getDialogClasses(size = 'small') {
return {
box: '',
form: '',
prompt: `dialog aui-layer aui-dialog2 aui-dialog2-${size}`,
close: 'aui-hide',
fade: 'aui-blanket',
button: 'button-control',
message: 'aui-dialog2-content',
buttons: 'aui-dialog2-footer',
defaultButton: 'button-control_primary',
title: 'aui-dialog2-header'
};
},
/**
* Opens new popup window for given <tt>url</tt> centered over current
* window.

View File

@@ -1,6 +1,6 @@
/* global $, APP */
/* eslint-disable no-unused-vars */
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import React from 'react';
import ReactDOM from 'react-dom';
import { I18nextProvider } from 'react-i18next';
@@ -8,6 +8,7 @@ import { Provider } from 'react-redux';
import { createScreenSharingIssueEvent, sendAnalytics } from '../../../react/features/analytics';
import { Avatar } from '../../../react/features/base/avatar';
import theme from '../../../react/features/base/components/themes/participantsPaneTheme.json';
import { i18next } from '../../../react/features/base/i18n';
import {
JitsiParticipantConnectionStatus
@@ -20,7 +21,6 @@ import {
updateKnownLargeVideoResolution
} from '../../../react/features/large-video/actions';
import { getParticipantsPaneOpen } from '../../../react/features/participants-pane/functions';
import theme from '../../../react/features/participants-pane/theme.json';
import { PresenceLabel } from '../../../react/features/presence-status';
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */

View File

@@ -495,6 +495,10 @@ export class VideoContainer extends LargeContainer {
stream.attach(this.$video[0]);
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
browser.isWebKitBased() && this.$video[0].play();
const flipX = stream.isLocal() && this.localFlipX && !this.isScreenSharing();
this.$video.css({

View File

@@ -1,6 +1,6 @@
/* global APP */
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
import {

View File

@@ -8,7 +8,8 @@ import {
import {
getUserSelectedCameraDeviceId,
getUserSelectedMicDeviceId,
getUserSelectedOutputDeviceId
getUserSelectedOutputDeviceId,
updateSettings
} from '../../react/features/base/settings';
/**
@@ -61,6 +62,9 @@ function getNewAudioInputDevice(newDevices, localAudio, newLabel) {
const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
const selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId);
const localAudioDeviceId = localAudio?.getDeviceId();
const localAudioDevice = availableAudioInputDevices.find(
d => d.deviceId === localAudioDeviceId);
// Here we handle case when no device was initially plugged, but
// then it's connected OR new device was connected when previous
@@ -76,13 +80,22 @@ function getNewAudioInputDevice(newDevices, localAudio, newLabel) {
return availableAudioInputDevices[0].deviceId;
}
} else if (selectedAudioInputDevice
&& selectedAudioInputDeviceId !== localAudio.getDeviceId()
&& !newLabel) {
&& selectedAudioInputDeviceId !== localAudioDeviceId) {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
return selectedAudioInputDeviceId;
if (newLabel) {
// If a Firefox user with manual permission prompt chose a different
// device from what we have stored as the preferred device we accept
// and store that as the new preferred device.
APP.store.dispatch(updateSettings({
userSelectedMicDeviceId: localAudioDeviceId,
userSelectedMicDeviceLabel: localAudioDevice.label
}));
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
return selectedAudioInputDeviceId;
}
}
}
@@ -101,6 +114,9 @@ function getNewVideoInputDevice(newDevices, localVideo, newLabel) {
const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
const selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId);
const localVideoDeviceId = localVideo?.getDeviceId();
const localVideoDevice = availableVideoInputDevices.find(
d => d.deviceId === localVideoDeviceId);
// Here we handle case when no video input device was initially plugged,
// but then device is connected OR new device was connected when
@@ -116,12 +132,22 @@ function getNewVideoInputDevice(newDevices, localVideo, newLabel) {
return availableVideoInputDevices[0].deviceId;
}
} else if (selectedVideoInputDevice
&& selectedVideoInputDeviceId !== localVideo.getDeviceId()
&& !newLabel) {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
return selectedVideoInputDeviceId;
&& selectedVideoInputDeviceId !== localVideoDeviceId) {
if (newLabel) {
// If a Firefox user with manual permission prompt chose a different
// device from what we have stored as the preferred device we accept
// and store that as the new preferred device.
APP.store.dispatch(updateSettings({
userSelectedCameraDeviceId: localVideoDeviceId,
userSelectedCameraDeviceLabel: localVideoDevice.label
}));
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
return selectedVideoInputDeviceId;
}
}
}

View File

@@ -1,6 +1,6 @@
/* global APP */
import { jitsiLocalStorage } from '@jitsi/js-utils';
import Logger from 'jitsi-meet-logger';
import Logger from '@jitsi/logger';
import {
ACTION_SHORTCUT_PRESSED as PRESSED,

View File

@@ -1,4 +1,4 @@
const logger = require('jitsi-meet-logger').getLogger(__filename);
const logger = require('@jitsi/logger').getLogger(__filename);
/**
* Manages a queue of functions where the current function in progress will

13494
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,8 @@
"@atlaskit/tooltip": "17.1.2",
"@hapi/bourne": "2.0.0",
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/rtcstats": "9.0.1",
"@material-ui/core": "4.11.3",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "1.1.0",
@@ -43,6 +45,8 @@
"@react-native-community/netinfo": "4.1.5",
"@react-native-community/slider": "3.0.3",
"@react-native-masked-view/masked-view": "0.2.6",
"@react-navigation/bottom-tabs": "5.11.15",
"@react-navigation/drawer": "5.12.9",
"@react-navigation/material-top-tabs": "5.3.19",
"@react-navigation/native": "5.9.8",
"@react-navigation/stack": "5.14.9",
@@ -54,18 +58,18 @@
"clipboard-copy": "4.0.1",
"clsx": "1.1.1",
"dropbox": "10.7.0",
"face-api.js": "0.22.2",
"focus-visible": "5.1.0",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-browser-languagedetector": "3.0.1",
"i18next-xhr-backend": "3.0.0",
"image-capture": "0.4.0",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"jquery": "3.5.1",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#e566291864046cadba2ef0af7fc2e31b513e3b4b",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
@@ -87,6 +91,7 @@
"react-native-default-preference": "1.4.2",
"react-native-device-info": "8.0.0",
"react-native-gesture-handler": "1.10.3",
"react-native-get-random-values": "1.7.0",
"react-native-immersive": "2.0.0",
"react-native-keep-awake": "4.0.0",
"react-native-paper": "4.8.1",
@@ -114,39 +119,33 @@
"redux-thunk": "2.2.0",
"resemblejs": "4.0.0",
"rnnoise-wasm": "github:jitsi/rnnoise-wasm#566a16885897704d6e6d67a1d5ac5d39781db2af",
"rtcstats": "github:jitsi/rtcstats#v8.1.0",
"styled-components": "3.4.9",
"util": "0.12.1",
"uuid": "3.1.0",
"uuid": "8.3.2",
"wasm-check": "2.0.1",
"windows-iana": "^3.1.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/plugin-proposal-class-properties": "7.1.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.4.4",
"@babel/plugin-proposal-optional-chaining": "7.2.0",
"@babel/plugin-transform-flow-strip-types": "7.0.0",
"@babel/preset-env": "7.1.0",
"@babel/preset-flow": "7.0.0",
"@babel/preset-react": "7.0.0",
"@babel/runtime": "7.15.3",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"@babel/core": "7.16.0",
"@babel/eslint-parser": "7.16.0",
"@babel/plugin-proposal-export-default-from": "7.16.0",
"@babel/preset-env": "7.16.0",
"@babel/preset-flow": "7.16.0",
"@babel/preset-react": "7.16.0",
"@babel/runtime": "7.16.0",
"@jitsi/eslint-config": "4.0.0",
"babel-loader": "8.2.3",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
"clean-css-cli": "4.3.0",
"css-loader": "3.6.0",
"eslint": "5.6.1",
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#2.0.0",
"eslint-plugin-flowtype": "2.50.3",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-jsdoc": "3.8.0",
"eslint-plugin-react": "7.11.1",
"eslint-plugin-react-native": "3.3.0",
"eslint": "8.1.0",
"eslint-plugin-flowtype": "8.0.3",
"eslint-plugin-import": "2.25.2",
"eslint-plugin-jsdoc": "37.0.3",
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-native": "3.11.0",
"expose-loader": "3.0.0",
"flow-bin": "0.104.0",
"imports-loader": "0.7.1",

View File

@@ -0,0 +1,11 @@
diff --git a/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json b/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
index 90a0d69..2ad7d68 100644
--- a/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
+++ b/node_modules/eslint-plugin-flowtype/dist/configs/recommended.json
@@ -1,5 +1,5 @@
{
- "parser": "@babel/eslint",
+ "parser": "@babel/eslint-parser",
"parserOptions": {
"babelOptions": {
"plugins": [

View File

@@ -1,9 +1,18 @@
module.exports = {
'extends': [
'../.eslintrc.js',
'eslint-config-jitsi/flow',
'eslint-config-jitsi/jsdoc',
'eslint-config-jitsi/react',
'@jitsi/eslint-config/flow',
'@jitsi/eslint-config/jsdoc',
'@jitsi/eslint-config/react',
'.eslintrc-react-native.js'
]
],
'rules': {
// XXX remove this eventually.
'react/jsx-indent-props': 0
},
'settings': {
'react': {
'version': 'detect'
}
}
};

View File

@@ -33,7 +33,7 @@ type State = {
* Represents the always on top page.
*
* @class AlwaysOnTop
* @extends Component
* @augments Component
*/
export default class AlwaysOnTop extends Component<*, State> {
_hovered: boolean;

View File

@@ -30,7 +30,7 @@ type Props = {
/**
* Represents the toolbar in the Always On Top window.
*
* @extends Component
* @augments Component
*/
export default class Toolbar extends Component<Props> {
/**

View File

@@ -2,6 +2,7 @@
* The constant for the event type 'track'.
* TODO: keep these constants in a single place. Can we import them from
* lib-jitsi-meet's AnalyticsEvents somehow?
*
* @type {string}
*/
const TYPE_TRACK = 'track';
@@ -10,6 +11,7 @@ const TYPE_TRACK = 'track';
* The constant for the event type 'UI' (User Interaction).
* TODO: keep these constants in a single place. Can we import them from
* lib-jitsi-meet's AnalyticsEvents somehow?
*
* @type {string}
*/
const TYPE_UI = 'ui';
@@ -143,7 +145,6 @@ export function createCalendarClickedEvent(eventName, attributes = {}) {
export function createCalendarSelectedEvent(attributes = {}) {
return {
action: 'selected',
actionSubject: 'calendar.selected',
attributes,
source: 'calendar',
type: TYPE_UI
@@ -159,8 +160,8 @@ export function createCalendarSelectedEvent(attributes = {}) {
*/
export function createCalendarConnectedEvent(attributes = {}) {
return {
action: 'calendar.connected',
actionSubject: 'calendar.connected',
action: 'connected',
actionSubject: 'calendar',
attributes
};
}
@@ -212,7 +213,6 @@ export function createChromeExtensionBannerEvent(installPressed, attributes = {}
export function createRecentSelectedEvent(attributes = {}) {
return {
action: 'selected',
actionSubject: 'recent.list.selected',
attributes,
source: 'recent.list',
type: TYPE_UI
@@ -533,12 +533,11 @@ export function createRejoinedEvent({ url, lastConferenceDuration, timeSinceLeft
export function createRemoteMuteConfirmedEvent(participantId, mediaType) {
return {
action: 'clicked',
actionSubject: 'remote.mute.dialog.confirm.button',
attributes: {
'participant_id': participantId,
'media_type': mediaType
},
source: 'remote.mute.dialog',
source: 'remote.mute.button',
type: TYPE_UI
};
}
@@ -582,21 +581,6 @@ export function createRTCStatsTraceCloseEvent(closeEvent) {
return event;
}
/**
* Creates an event indicating that an action related to video blur
* occurred (e.g. It was started or stopped).
*
* @param {string} action - The action which occurred.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createVideoBlurEvent(action) {
return {
action,
actionSubject: 'video.blur'
};
}
/**
* Creates an event indicating that an action related to screen sharing
* occurred (e.g. It was started or stopped).
@@ -629,26 +613,6 @@ export function createScreenSharingIssueEvent(attributes) {
};
}
/**
* The local participant failed to send a "selected endpoint" message to the
* bridge.
*
* @param {Error} error - The error which caused the failure.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
export function createSelectParticipantFailedEvent(error) {
const event = {
action: 'select.participant.failed'
};
if (error) {
event.error = error.toString();
}
return event;
}
/**
* Creates an event associated with the "shared video" feature.
*
@@ -686,7 +650,6 @@ export function createShortcutEvent(
attributes = {}) {
return {
action,
actionSubject: 'keyboard.shortcut',
actionSubjectId: shortcut,
attributes,
source: 'keyboard.shortcut',
@@ -821,24 +784,6 @@ export function createToolbarEvent(buttonName, attributes = {}) {
};
}
/**
* Creates an event associated with reactions.
*
* @param {string} action - Event action.
* @param {string} name - Event name.
* @param {string} source - Event source.
* @returns {Object} The event in a format suitable for sending via
* sendAnalytics.
*/
function createReactionEvent(action, name, source) {
return {
action,
actionSubject: name,
source: `reaction.${source}`,
type: TYPE_UI
};
}
/**
* Creates an event associated with a reaction button being clicked/pressed.
*
@@ -848,7 +793,13 @@ function createReactionEvent(action, name, source) {
* sendAnalytics.
*/
export function createReactionMenuEvent(buttonName) {
return createReactionEvent('clicked', buttonName, 'button');
return {
action: 'clicked',
actionSubject: 'button',
source: 'reaction',
buttonName,
type: TYPE_UI
};
}
/**
@@ -858,7 +809,12 @@ export function createReactionMenuEvent(buttonName) {
* sendAnalytics.
*/
export function createReactionSoundsDisabledEvent() {
return createReactionEvent('disabled', 'sounds', 'settings');
return {
action: 'disabled',
actionSubject: 'sounds',
source: 'reaction.settings',
type: TYPE_UI
};
}
/**
@@ -925,6 +881,6 @@ export function createWelcomePageEvent(action, actionSubject, attributes = {}) {
*/
export function createScreensharingCaptureTakenEvent() {
return {
action: 'screen.sharing.capture'
action: 'screen.sharing.capture.taken'
};
}

View File

@@ -187,7 +187,7 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
permanentProperties.externalApi = typeof API_ID === 'number';
// Report if we are loaded in iframe
permanentProperties.inIframe = _inIframe();
permanentProperties.inIframe = inIframe();
// Report the tenant from the URL.
permanentProperties.tenant = tenant || '/';
@@ -227,7 +227,7 @@ export function initAnalytics({ getState }: { getState: Function }, handlers: Ar
* @returns {boolean} Returns {@code true} if loaded in iframe.
* @private
*/
function _inIframe() {
export function inIframe() {
if (navigator.product === 'ReactNative') {
return false;
}

View File

@@ -1,5 +1,5 @@
/**
* Abstract implementation of analytics handler
* Abstract implementation of analytics handler.
*/
export default class AbstractHandler {
/**

View File

@@ -51,7 +51,7 @@ type Props = AbstractAppProps & {
/**
* Root app {@code Component} on mobile/React Native.
*
* @extends AbstractApp
* @augments AbstractApp
*/
export class App extends AbstractApp {
_init: Promise<*>;

View File

@@ -17,7 +17,7 @@ import '../reducers';
/**
* Root app {@code Component} on Web/React.
*
* @extends AbstractApp
* @augments AbstractApp
*/
export class App extends AbstractApp {
/**

View File

@@ -2,8 +2,7 @@
import { isRoomValid } from '../base/conference';
import { toState } from '../base/redux';
import { ConferenceNavigationContainer } from '../conference';
import { isWelcomePageAppEnabled } from '../welcome';
import { BlankPage, WelcomePage } from '../welcome/components';
import RootNavigationContainer from '../welcome/components/RootNavigationContainer';
/**
* Determines which route is to be rendered in order to depict a specific Redux
@@ -26,27 +25,17 @@ export function _getRouteToRender(stateful) {
* @returns {Promise}
*/
function _getMobileRoute(state) {
const route = _getEmptyRoute();
const route = {
component: null,
href: undefined
};
if (isRoomValid(state['features/base/conference'].room)) {
route.component = ConferenceNavigationContainer;
} else if (isWelcomePageAppEnabled(state)) {
route.component = WelcomePage;
} else {
route.component = BlankPage;
route.component = RootNavigationContainer;
}
return Promise.resolve(route);
}
/**
* Returns the default {@code Route}.
*
* @returns {Object}
*/
function _getEmptyRoute() {
return {
component: BlankPage,
href: undefined
};
}

View File

@@ -7,9 +7,7 @@ import { toState } from '../base/redux';
import { Conference } from '../conference';
import { getDeepLinkingPage } from '../deep-linking';
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
import { isWelcomePageUserEnabled } from '../welcome';
import { BlankPage, WelcomePage } from '../welcome/components';
import { BlankPage, isWelcomePageUserEnabled, WelcomePage } from '../welcome';
/**
* Determines which route is to be rendered in order to depict a specific Redux

View File

@@ -2,6 +2,7 @@
import {
createConnectionEvent,
inIframe,
sendAnalytics
} from '../analytics';
import { SET_ROOM } from '../base/conference';
@@ -53,6 +54,10 @@ function _connectionEstablished(store, next, action) {
// determined by when no one needs them anymore.
const { history, location } = window;
if (inIframe()) {
return;
}
if (history
&& location
&& history.length

View File

@@ -19,6 +19,7 @@ import '../base/sounds/middleware';
import '../base/testing/middleware';
import '../base/tracks/middleware';
import '../base/user-interaction/middleware';
import '../breakout-rooms/middleware';
import '../calendar-sync/middleware';
import '../chat/middleware';
import '../conference/middleware';

View File

@@ -17,5 +17,6 @@ import '../screen-share/middleware';
import '../shared-video/middleware';
import '../talk-while-muted/middleware';
import '../virtual-background/middleware';
import '../facial-recognition/middleware';
import './middlewares.any';

View File

@@ -26,6 +26,7 @@ import '../base/sounds/reducer';
import '../base/testing/reducer';
import '../base/tracks/reducer';
import '../base/user-interaction/reducer';
import '../breakout-rooms/reducer';
import '../calendar-sync/reducer';
import '../chat/reducer';
import '../deep-linking/reducer';
@@ -54,4 +55,3 @@ import '../toolbox/reducer';
import '../transcribing/reducer';
import '../video-layout/reducer';
import '../videosipgw/reducer';
import '../welcome/reducer';

View File

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

View File

@@ -29,7 +29,7 @@ type Props = {
/**
* Creates a ReactElement responsible for drawing audio levels.
*
* @extends {Component}
* @augments {Component}
*/
class AudioLevelIndicator extends Component<Props> {
/**

View File

@@ -29,7 +29,7 @@ import './styles';
type Props = {
/**
* {@link JitsiConference} that needs authentication - will hold a valid
* {@link JitsiConference} That needs authentication - will hold a valid
* value in XMPP login + guest access mode.
*/
_conference: Object,

View File

@@ -21,7 +21,7 @@ import {
type Props = {
/**
* {@link JitsiConference} that needs authentication - will hold a valid
* {@link JitsiConference} That needs authentication - will hold a valid
* value in XMPP login + guest access mode.
*/
_conference: Object,

View File

@@ -259,5 +259,5 @@ export default class BaseApp extends Component<*, State> {
*
* @returns {React$Element}
*/
_renderDialogContainer: () => React$Element<*>
_renderDialogContainer: () => React$Element<*>;
}

View File

@@ -12,13 +12,6 @@ ReducerRegistry.register('features/base/app', (state = {}, action) => {
if (state.app !== app) {
return {
...state,
/**
* The one and only (i.e. singleton) {@link BaseApp} instance
* which is currently mounted.
*
* @type {BaseApp}
*/
app
};
}

View File

@@ -38,12 +38,12 @@ export type Props = {
/**
* Display name of the entity to render an avatar for (if any). This is handy when we need
* an avatar for a non-participasnt entity (e.g. a recent list item).
* an avatar for a non-participasnt entity (e.g. A recent list item).
*/
displayName?: string,
/**
* Whether or not to update the background color of the avatar
* Whether or not to update the background color of the avatar.
*/
dynamicColor?: Boolean,
@@ -220,7 +220,7 @@ export function _mapStateToProps(state: Object, ownProps: Props) {
_customAvatarBackgrounds: state['features/dynamic-branding'].avatarBackgrounds,
_initialsBase,
_loadableAvatarUrl: _participant?.loadableAvatarUrl,
colorBase: !colorBase && _participant ? _participant.id : colorBase
colorBase
};
}

View File

@@ -63,7 +63,7 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
);
}
_isIcon: (?string | ?Object) => boolean
_isIcon: (?string | ?Object) => boolean;
/**
* Renders a badge representing the avatar status.

View File

@@ -13,7 +13,7 @@ type Props = AbstractProps & {
className?: string,
/**
* The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop)
* The default avatar URL if we want to override the app bundled one (e.g. AlwaysOnTop).
*/
defaultAvatar?: string,
@@ -157,5 +157,5 @@ export default class StatelessAvatar extends AbstractStatelessAvatar<Props> {
return '';
}
_isIcon: (?string | ?Object) => boolean
_isIcon: (?string | ?Object) => boolean;
}

View File

@@ -53,7 +53,7 @@ export function getInitials(s: ?string) {
let initials = '';
for (const w of words) {
(initials.length < 2) && (initials += w.substr(0, 1).toUpperCase());
(initials.length < 2) && (initials += String.fromCodePoint(w.codePointAt(0)).toUpperCase());
}
return initials;

View File

@@ -1,40 +1,95 @@
// @flow
import React, { useState } from 'react';
/* eslint-disable react/jsx-no-bind */
import { withStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React, { useEffect, useState } from 'react';
import { Icon, IconCheck, IconCopy } from '../../base/icons';
import { withPixelLineHeight } from '../styles/functions.web';
import { copyText } from '../util';
const styles = theme => {
return {
copyButton: {
...withPixelLineHeight(theme.typography.bodyLongRegular),
borderRadius: theme.shape.borderRadius,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '8px 8px 8px 16px',
marginTop: 5,
width: 'calc(100% - 24px)',
height: 24,
background: theme.palette.action01,
cursor: 'pointer',
'&:hover': {
backgroundColor: theme.palette.action01Hover,
fontWeight: 600
},
'&.clicked': {
background: theme.palette.success02
},
'& > div > svg > path': {
fill: theme.palette.text01
}
},
content: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
maxWidth: 292,
marginRight: 16,
'&.selected': {
fontWeight: 600
}
}
};
};
let mounted;
type Props = {
/**
* Css class to apply on container
* An object containing the CSS classes.
*/
classes: Object,
/**
* Css class to apply on container.
*/
className: string,
/**
* The displayed text
* The displayed text.
*/
displayedText: string,
/**
* The text that needs to be copied (might differ from the displayedText)
* The text that needs to be copied (might differ from the displayedText).
*/
textToCopy: string,
/**
* The text displayed on mouse hover
* The text displayed on mouse hover.
*/
textOnHover: string,
/**
* The text displayed on copy success
* The text displayed on copy success.
*/
textOnCopySuccess: string,
/**
* The id of the button
* The id of the button.
*/
id?: string,
};
@@ -44,10 +99,18 @@ type Props = {
*
* @returns {React$Element<any>}
*/
function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnCopySuccess, id }: Props) {
function CopyButton({ classes, className, displayedText, textToCopy, textOnHover, textOnCopySuccess, id }: Props) {
const [ isClicked, setIsClicked ] = useState(false);
const [ isHovered, setIsHovered ] = useState(false);
useEffect(() => {
mounted = true;
return () => {
mounted = false;
};
}, []);
/**
* Click handler for the element.
*
@@ -62,7 +125,10 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
setIsClicked(true);
setTimeout(() => {
setIsClicked(false);
// avoid: Can't perform a React state update on an unmounted component
if (mounted) {
setIsClicked(false);
}
}, 2500);
}
}
@@ -110,7 +176,7 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
if (isClicked) {
return (
<>
<div className = 'copy-button-content selected'>
<div className = { clsx(classes.content, 'selected') }>
<span role = { 'alert' }>{ textOnCopySuccess }</span>
</div>
<Icon src = { IconCheck } />
@@ -120,8 +186,8 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
return (
<>
<div className = 'copy-button-content'>
{isHovered ? textOnHover : displayedText}
<div className = { `${classes.copyButton}-content` }>
{ isHovered ? textOnHover : displayedText }
</div>
<Icon src = { IconCopy } />
</>
@@ -131,7 +197,7 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
return (
<div
aria-label = { textOnHover }
className = { `${className} copy-button${isClicked ? ' clicked' : ''}` }
className = { clsx(className, classes.copyButton, isClicked ? ' clicked' : '') }
id = { id }
onBlur = { onHoverOut }
onClick = { onClick }
@@ -150,4 +216,4 @@ CopyButton.defaultProps = {
className: ''
};
export default CopyButton;
export default withStyles(styles)(CopyButton);

View File

@@ -0,0 +1,67 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React from 'react';
type Props = {
/**
* Label used for accessibility.
*/
accessibilityLabel: string,
/**
* Additional class name for custom styles.
*/
className: string,
/**
* Children of the component.
*/
children: string | React$Node,
/**
* Click handler.
*/
onClick: Function,
/**
* Data test id.
*/
testId?: string
}
const useStyles = makeStyles(theme => {
return {
button: {
backgroundColor: theme.palette.action01,
color: theme.palette.text01,
borderRadius: `${theme.shape.borderRadius}px`,
...theme.typography.labelBold,
lineHeight: `${theme.typography.labelBold.lineHeight}px`,
padding: '8px 12px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
border: 0,
'&:hover': {
backgroundColor: theme.palette.action01Hover
}
}
};
});
const QuickActionButton = ({ accessibilityLabel, className, children, onClick, testId }: Props) => {
const styles = useStyles();
return (<button
aria-label = { accessibilityLabel }
className = { `${styles.button} ${className}` }
data-testid = { testId }
onClick = { onClick }>
{children}
</button>);
};
export default QuickActionButton;

View File

@@ -0,0 +1,177 @@
// @flow
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { getComputedOuterHeight } from '../../../participants-pane/functions';
import { Drawer, JitsiPortal } from '../../../toolbox/components/web';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import participantsPaneTheme from '../themes/participantsPaneTheme.json';
type Props = {
/**
* Children of the context menu.
*/
children: React$Node,
/**
* Class name for context menu. Used to overwrite default styles.
*/
className?: string,
/**
* The entity for which the context menu is displayed.
*/
entity?: Object,
/**
* Whether or not the menu is hidden. Used to overwrite the internal isHidden.
*/
hidden?: boolean,
/**
* Whether or not drawer should be open.
*/
isDrawerOpen: boolean,
/**
* Target elements against which positioning calculations are made.
*/
offsetTarget?: HTMLElement,
/**
* Callback for click on an item in the menu.
*/
onClick?: Function,
/**
* Callback for drawer close.
*/
onDrawerClose: Function,
/**
* Callback for the mouse entering the component.
*/
onMouseEnter?: Function,
/**
* Callback for the mouse leaving the component.
*/
onMouseLeave: Function
};
const useStyles = makeStyles(theme => {
return {
contextMenu: {
backgroundColor: theme.palette.ui02,
borderRadius: `${theme.shape.borderRadius / 2}px`,
boxShadow: '0px 3px 16px rgba(0, 0, 0, 0.6), 0px 0px 4px 1px rgba(0, 0, 0, 0.25)',
color: theme.palette.text01,
...theme.typography.bodyShortRegular,
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`,
marginTop: `${(participantsPaneTheme.panePadding * 2) + theme.typography.bodyShortRegular.fontSize}px`,
position: 'absolute',
right: `${participantsPaneTheme.panePadding}px`,
top: 0,
zIndex: 2
},
contextMenuHidden: {
pointerEvents: 'none',
visibility: 'hidden'
},
drawer: {
'& > div': {
...theme.typography.bodyShortRegularLarge,
lineHeight: `${theme.typography.bodyShortRegularLarge.lineHeight}px`,
'& svg': {
fill: theme.palette.icon01
}
},
'& > *:first-child': {
paddingTop: '15px!important'
}
}
};
});
const ContextMenu = ({
children,
className,
entity,
hidden,
isDrawerOpen,
offsetTarget,
onClick,
onDrawerClose,
onMouseEnter,
onMouseLeave
}: Props) => {
const [ isHidden, setIsHidden ] = useState(true);
const containerRef = useRef<HTMLDivElement | null>(null);
const styles = useStyles();
const _overflowDrawer = useSelector(showOverflowDrawer);
useLayoutEffect(() => {
if (_overflowDrawer) {
return;
}
if (entity && offsetTarget
&& containerRef.current
&& offsetTarget?.offsetParent
&& offsetTarget.offsetParent instanceof HTMLElement
) {
const { current: container } = containerRef;
const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
const outerHeight = getComputedOuterHeight(container);
container.style.top = offsetTop + outerHeight > offsetHeight + scrollTop
? `${offsetTop - outerHeight}`
: `${offsetTop}`;
setIsHidden(false);
} else {
setIsHidden(true);
}
}, [ entity, offsetTarget, _overflowDrawer ]);
useEffect(() => {
if (hidden !== undefined) {
setIsHidden(hidden);
}
}, [ hidden ]);
return _overflowDrawer
? <JitsiPortal>
<Drawer
isOpen = { isDrawerOpen && _overflowDrawer }
onClose = { onDrawerClose }>
<div
className = { styles.drawer }
onClick = { onDrawerClose }>
{children}
</div>
</Drawer>
</JitsiPortal>
: <div
className = { clsx(participantsPaneTheme.ignoredChildClassName,
styles.contextMenu,
isHidden && styles.contextMenuHidden,
className
) }
onClick = { onClick }
onMouseEnter = { onMouseEnter }
onMouseLeave = { onMouseLeave }
ref = { containerRef }>
{children}
</div>
;
};
export default ContextMenu;

View File

@@ -0,0 +1,136 @@
// @flow
import { makeStyles } from '@material-ui/core';
import clsx from 'clsx';
import React from 'react';
import { useSelector } from 'react-redux';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { Icon } from '../../icons';
export type Action = {
/**
* Label used for accessibility.
*/
accessibilityLabel: string,
/**
* CSS class name used for custom styles.
*/
className?: string,
/**
* Custom icon. If used, the icon prop is ignored.
* Used to allow custom children instead of just the default icons.
*/
customIcon?: React$Node,
/**
* Id of the action container.
*/
id?: string,
/**
* Default icon for action.
*/
icon?: Function,
/**
* Click handler.
*/
onClick?: Function,
/**
* Action text.
*/
text: string
}
type Props = {
/**
* List of actions in this group.
*/
actions?: Array<Action>,
/**
* The children of the component.
*/
children?: React$Node,
};
const useStyles = makeStyles(theme => {
return {
contextMenuItemGroup: {
'&:not(:empty)': {
padding: `${theme.spacing(2)}px 0`
},
'& + &:not(:empty)': {
borderTop: `1px solid ${theme.palette.ui04}`
}
},
contextMenuItem: {
alignItems: 'center',
cursor: 'pointer',
display: 'flex',
minHeight: '40px',
padding: '10px 16px',
boxSizing: 'border-box',
'& > *:not(:last-child)': {
marginRight: `${theme.spacing(3)}px`
},
'&:hover': {
backgroundColor: theme.palette.ui04
}
},
contextMenuItemDrawer: {
padding: '12px 16px'
},
contextMenuItemIcon: {
'& svg': {
fill: theme.palette.icon01
}
}
};
});
const ContextMenuItemGroup = ({
actions,
children
}: Props) => {
const styles = useStyles();
const _overflowDrawer = useSelector(showOverflowDrawer);
return (
<div className = { styles.contextMenuItemGroup }>
{children}
{actions && actions.map(({ accessibilityLabel, className, customIcon, id, icon, onClick, text }) => (
<div
aria-label = { accessibilityLabel }
className = { clsx(styles.contextMenuItem,
_overflowDrawer && styles.contextMenuItemDrawer,
className
) }
id = { id }
key = { text }
onClick = { onClick }>
{customIcon ? customIcon
: icon && <Icon
className = { styles.contextMenuItemIcon }
size = { 20 }
src = { icon } />}
<span>{text}</span>
</div>
))}
</div>
);
};
export default ContextMenuItemGroup;

View File

@@ -0,0 +1,79 @@
// @flow
import { useCallback, useRef, useState } from 'react';
import { findAncestorByClass } from '../../../participants-pane/functions';
type NullProto = {
[key: string]: any,
__proto__: null
};
type RaiseContext = NullProto | {|
/**
* Target elements against which positioning calculations are made.
*/
offsetTarget?: HTMLElement,
/**
* The entity for which the menu is context menu is raised.
*/
entity?: string | Object,
|};
const initialState = Object.freeze(Object.create(null));
const useContextMenu = () => {
const [ raiseContext, setRaiseContext ] = useState < RaiseContext >(initialState);
const isMouseOverMenu = useRef(false);
const lowerMenu = useCallback((force: boolean | Object = false) => {
/**
* We are tracking mouse movement over the active participant item and
* the context menu. Due to the order of enter/leave events, we need to
* defer checking if the mouse is over the context menu with
* queueMicrotask.
*/
window.queueMicrotask(() => {
if (isMouseOverMenu.current && !(force === true)) {
return;
}
if (raiseContext !== initialState) {
setRaiseContext(initialState);
}
});
}, [ raiseContext ]);
const raiseMenu = useCallback((entity: string | Object, target: EventTarget) => {
setRaiseContext({
entity,
offsetTarget: findAncestorByClass(target, 'list-item-container')
});
}, [ raiseContext ]);
const toggleMenu = useCallback((entity: string | Object) => (e: MouseEvent) => {
e.stopPropagation();
const { entity: raisedEntity } = raiseContext;
if (raisedEntity && raisedEntity === entity) {
lowerMenu();
} else {
raiseMenu(entity, e.target);
}
}, [ raiseContext ]);
const menuEnter = useCallback(() => {
isMouseOverMenu.current = true;
}, []);
const menuLeave = useCallback(() => {
isMouseOverMenu.current = false;
lowerMenu();
}, [ lowerMenu ]);
return [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ];
};
export default useContextMenu;

View File

@@ -0,0 +1,4 @@
export { default as ContextMenu } from './context-menu/ContextMenu';
export { default as ContextMenuItemGroup } from './context-menu/ContextMenuItemGroup';
export { default as ListItem } from './participants-pane-list/ListItem';
export { default as QuickActionButton } from './buttons/QuickActionButton';

View File

@@ -0,0 +1,280 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React from 'react';
import { ACTION_TRIGGER } from '../../../participants-pane/constants';
import { isMobileBrowser } from '../../environment/utils';
import participantsPaneTheme from '../themes/participantsPaneTheme.json';
type Props = {
/**
* List item actions.
*/
actions: React$Node,
/**
* List item container class name.
*/
className: string,
/**
* Icon to be displayed on the list item. (Avatar for participants).
*/
icon: React$Node,
/**
* Id of the container.
*/
id: string,
/**
* Whether or not the actions should be hidden.
*/
hideActions?: Boolean,
/**
* Indicators to be displayed on the list item.
*/
indicators?: React$Node,
/**
* Whether or not the item is highlighted.
*/
isHighlighted?: boolean,
/**
* Click handler.
*/
onClick: Function,
/**
* Long press handler.
*/
onLongPress: Function,
/**
* Mouse leave handler.
*/
onMouseLeave: Function,
/**
* Data test id.
*/
testId?: string,
/**
* Text children to be displayed on the list item.
*/
textChildren: React$Node | string,
/**
* The actions trigger. Can be Hover or Permanent.
*/
trigger: string
}
const useStyles = makeStyles(theme => {
return {
container: {
alignItems: 'center',
color: theme.palette.text01,
display: 'flex',
...theme.typography.bodyShortRegular,
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`,
margin: `0 -${participantsPaneTheme.panePadding}px`,
padding: `0 ${participantsPaneTheme.panePadding}px`,
position: 'relative',
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)',
minHeight: '40px',
'&:hover': {
backgroundColor: theme.palette.action02Active,
'& .indicators': {
display: 'none'
},
'& .actions': {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`,
backgroundColor: theme.palette.action02Active
}
},
[`@media(max-width: ${participantsPaneTheme.MD_BREAKPOINT})`]: {
...theme.typography.bodyShortRegularLarge,
lineHeight: `${theme.typography.bodyShortRegularLarge.lineHeight}px`,
padding: `${theme.spacing(2)}px ${participantsPaneTheme.panePadding}px`
}
},
highlighted: {
backgroundColor: theme.palette.action02Active
},
detailsContainer: {
display: 'flex',
alignItems: 'center',
flex: 1,
height: '100%',
overflow: 'hidden',
position: 'relative'
},
name: {
display: 'flex',
flex: 1,
marginRight: `${theme.spacing(2)}px`,
overflow: 'hidden',
flexDirection: 'column',
justifyContent: 'flex-start'
},
indicators: {
display: 'flex',
justifyContent: 'flex-end',
'& > *': {
alignItems: 'center',
display: 'flex',
justifyContent: 'center'
},
'& > *:not(:last-child)': {
marginRight: `${theme.spacing(2)}px`
},
'& .jitsi-icon': {
padding: '3px'
}
},
indicatorsHidden: {
display: 'none'
},
actionsContainer: {
display: 'none',
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`,
backgroundColor: theme.palette.action02Active
},
actionsPermanent: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
backgroundColor: theme.palette.ui01
},
actionsVisible: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.action02Active}`,
backgroundColor: theme.palette.action02Active
}
};
});
const ListItem = ({
actions,
className,
icon,
id,
hideActions = false,
indicators,
isHighlighted,
onClick,
onLongPress,
onMouseLeave,
testId,
textChildren,
trigger
}: Props) => {
const styles = useStyles();
const _isMobile = isMobileBrowser();
let timeoutHandler;
/**
* Set calling long press handler after x milliseconds.
*
* @param {TouchEvent} e - Touch start event.
* @returns {void}
*/
function _onTouchStart(e) {
const target = e.touches[0].target;
timeoutHandler = setTimeout(() => onLongPress(target), 600);
}
/**
* Cancel calling on long press after x milliseconds if the number of milliseconds is not reached
* before a touch move(drag), or just clears the timeout.
*
* @returns {void}
*/
function _onTouchMove() {
clearTimeout(timeoutHandler);
}
/**
* Cancel calling on long press after x milliseconds if the number of milliseconds is not reached yet,
* or just clears the timeout.
*
* @returns {void}
*/
function _onTouchEnd() {
clearTimeout(timeoutHandler);
}
return (
<div
className = { clsx('list-item-container',
styles.container,
isHighlighted && styles.highlighted,
className
) }
data-testid = { testId }
id = { id }
onClick = { onClick }
{ ...(_isMobile
? {
onTouchEnd: _onTouchEnd,
onTouchMove: _onTouchMove,
onTouchStart: _onTouchStart
}
: {
onMouseLeave
}
) }>
<div> {icon} </div>
<div className = { styles.detailsContainer }>
<div className = { styles.name }>
{textChildren}
</div>
{indicators && (
<div
className = { clsx('indicators',
styles.indicators,
(isHighlighted || trigger === ACTION_TRIGGER.PERMANENT) && styles.indicatorsHidden
) }>
{indicators}
</div>
)}
{!hideActions && (
<div
className = { clsx('actions',
styles.actionsContainer,
trigger === ACTION_TRIGGER.PERMANENT && styles.actionsPermanent,
isHighlighted && styles.actionsVisible
) }>
{actions}
</div>
)}
</div>
</div>
);
};
export default ListItem;

View File

@@ -0,0 +1,10 @@
{
"colors": {
"moderationDisabled": "#E54B4B"
},
"headerSize": 60,
"ignoredChildClassName": "ignore-child",
"panePadding": 16,
"participantsPaneWidth": 315,
"MD_BREAKPOINT": "580px"
}

View File

@@ -127,6 +127,17 @@ export const KICKED_OUT = 'KICKED_OUT';
*/
export const LOCK_STATE_CHANGED = 'LOCK_STATE_CHANGED';
/**
* The type of (redux) action which signals that a system (non-participant) message has been received.
*
* {
* type: NON_PARTICIPANT_MESSAGE_RECEIVED,
* id: String,
* json: Object
* }
*/
export const NON_PARTICIPANT_MESSAGE_RECEIVED = 'NON_PARTICIPANT_MESSAGE_RECEIVED';
/**
* The type of (redux) action which sets the peer2peer flag for the current
* conference.

View File

@@ -37,6 +37,7 @@ import {
DATA_CHANNEL_OPENED,
KICKED_OUT,
LOCK_STATE_CHANGED,
NON_PARTICIPANT_MESSAGE_RECEIVED,
P2P_STATUS_CHANGED,
SEND_TONES,
SET_FOLLOW_ME,
@@ -179,6 +180,10 @@ function _addConferenceListeners(conference, dispatch, state) {
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args) => dispatch(endpointMessageReceived(...args)));
conference.on(
JitsiConferenceEvents.NON_PARTICIPANT_MESSAGE_RECEIVED,
(...args) => dispatch(nonParticipantMessageReceived(...args)));
conference.on(
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
(...args) => dispatch(participantConnectionStatusChanged(...args)));
@@ -415,9 +420,11 @@ export function conferenceWillLeave(conference: Object) {
/**
* Initializes a new conference.
*
* @param {string} overrideRoom - Override the room to join, instead of taking it
* from Redux.
* @returns {Function}
*/
export function createConference() {
export function createConference(overrideRoom?: string) {
return (dispatch: Function, getState: Function) => {
const state = getState();
const { connection, locationURL } = state['features/base/connection'];
@@ -432,7 +439,20 @@ export function createConference() {
throw new Error('Cannot join a conference without a room name!');
}
const conference = connection.initJitsiConference(getBackendSafeRoomName(room), getConferenceOptions(state));
// XXX: revisit this.
// Hide the custom domain in the room name.
const tmp = overrideRoom || room;
let _room = getBackendSafeRoomName(tmp);
if (tmp.domain) {
// eslint-disable-next-line no-new-wrappers
_room = new String(tmp);
// $FlowExpectedError
_room.domain = tmp.domain;
}
const conference = connection.initJitsiConference(_room, getConferenceOptions(state));
connection[JITSI_CONNECTION_CONFERENCE_KEY] = conference;
@@ -525,6 +545,25 @@ export function lockStateChanged(conference: Object, locked: boolean) {
};
}
/**
* Signals that a non participant endpoint message has been received.
*
* @param {string} id - The resource id of the sender.
* @param {Object} json - The json carried by the endpoint message.
* @returns {{
* type: NON_PARTICIPANT_MESSAGE_RECEIVED,
* id: Object,
* json: Object
* }}
*/
export function nonParticipantMessageReceived(id: String, json: Object) {
return {
type: NON_PARTICIPANT_MESSAGE_RECEIVED,
id,
json
};
}
/**
* Updates the known state of start muted policies.
*

View File

@@ -18,6 +18,7 @@ export default [
'analytics.rtcstatsEnabled',
'audioLevelsInterval',
'audioQuality',
'autoKnockLobby',
'apiLogLevels',
'avgRtpStatsN',
'backgroundAlpha',
@@ -109,6 +110,7 @@ export default [
'disableRemoteMute',
'disableResponsiveTiles',
'disableRtx',
'disableScreensharingVirtualBackground',
'disableShortcuts',
'disableShowMoreStats',
'disableRemoveRaisedHandOnFocus',
@@ -122,10 +124,12 @@ export default [
'doNotFlipLocalVideo',
'dropbox',
'e2eeLabels',
'e2ee',
'e2eping',
'enableDisplayNameInStats',
'enableEmailInStats',
'enableEncodedTransformSupport',
'enableFacialRecognition',
'enableIceRestart',
'enableInsecureRoomNameWarning',
'enableLayerSuspension',
@@ -155,6 +159,7 @@ export default [
'hideParticipantsStats',
'hideConferenceTimer',
'hiddenDomain',
'hideAddRoomButton',
'hideLobbyButton',
'hosts',
'iAmRecorder',

View File

@@ -1,4 +1,4 @@
/**
* Deploy-specific configuration whitelists
* Deploy-specific configuration whitelists.
*/
export default [];

View File

@@ -314,6 +314,12 @@ function _translateLegacyConfig(oldValue: Object) {
newValue.disableModeratorIndicator = interfaceConfig.DISABLE_FOCUS_INDICATOR;
}
newValue.e2ee = newValue.e2ee || {};
if (oldValue.e2eeLabels) {
newValue.e2ee.e2eeLabels = oldValue.e2eeLabels;
}
return newValue;
}

View File

@@ -292,13 +292,14 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
break;
}
}
dispatch(showNotification({
description,
titleKey,
customActionNameKey: 'notify.newDeviceAction',
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
}));
if (!isPrejoinPageVisible(store.getState())) {
dispatch(showNotification({
description,
titleKey,
customActionNameKey: 'notify.newDeviceAction',
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
}));
}
});
}

View File

@@ -45,7 +45,7 @@ class AlertDialog extends BaseSubmitDialog<Props, *> {
);
}
_renderHTML: string => Object | string
_renderHTML: string => Object | string;
}
export default translate(connect(_abstractMapStateToProps)(AlertDialog));

View File

@@ -93,7 +93,7 @@ class BaseDialog<P: Props, S: State> extends AbstractDialog<P, S> {
*
* @returns {ReactElement}
*/
_renderContent: () => Object
_renderContent: () => Object;
/**
* Renders a specific {@code string} which may contain HTML.

View File

@@ -21,7 +21,7 @@ type Props = BaseProps & {
}
/**
* Abstract dialog to submit something. E.g. a confirmation or a form.
* Abstract dialog to submit something. E.g. A confirmation or a form.
*/
class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
/**
@@ -84,16 +84,16 @@ class BaseSubmitDialog<P: Props, S: *> extends BaseDialog<P, S> {
_onSubmit: () => boolean;
_renderHTML: string => Object | string
_renderHTML: string => Object | string;
/**
/** .
* Renders the actual content of the dialog defining what is about to be
* submitted. E.g. a simple confirmation (text, properly wrapped) or a
* submitted. E.g. A simple confirmation (text, properly wrapped) or a
* complex form.
*
* @returns {Object}
*/
_renderSubmittable: () => Object
_renderSubmittable: () => Object;
}
export default BaseSubmitDialog;

View File

@@ -72,7 +72,7 @@ type Props = {
showSlidingView?: boolean,
/**
* The component's external style
* The component's external style.
*/
style: Object
};

View File

@@ -101,7 +101,7 @@ class ConfirmDialog extends BaseSubmitDialog<Props, *> {
);
}
_renderHTML: string => Object | string
_renderHTML: string => Object | string;
}
export default translate(connect(_abstractMapStateToProps)(ConfirmDialog));

View File

@@ -12,7 +12,7 @@ import AbstractDialogContainer, {
* need a separate container so we can handle multiple dialogs by showing them
* simultaneously or queueing them.
*
* @extends AbstractDialogContainer
* @augments AbstractDialogContainer
*/
class DialogContainer extends AbstractDialogContainer {

View File

@@ -34,7 +34,7 @@ type Props = BaseProps & {
initialValue?: ?string,
/**
* A message key to be shown for the user (e.g. an error that is defined after submitting the form).
* A message key to be shown for the user (e.g. An error that is defined after submitting the form).
*/
messageKey?: string,

View File

@@ -27,7 +27,7 @@ export type Props = {
/**
* Abstract React {@code Component} for tabs of the DialogWithTabs component.
*
* @extends Component
* @augments Component
*/
class AbstractDialogTab<P: Props, S: *> extends Component<P, S> {
/**

View File

@@ -34,7 +34,7 @@ type Props = AbstractDialogProps & {
* - 'small' (400px), 'medium' (600px), 'large' (800px),
* 'x-large' (968px)
* - integer value for pixel width
* - string value for percentage
* - string value for percentage.
*/
width: string
};

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