Compare commits

...

38 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
213 changed files with 6391 additions and 1991 deletions

View File

@@ -8,6 +8,7 @@ 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

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)
@@ -51,6 +52,8 @@ deploy-appbundle:
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(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 \
@@ -92,6 +95,11 @@ deploy-meet-models:
$(MEET_MODELS_DIR)/*.tflite \
$(DEPLOY_DIR)
deploy-facial-expressions:
cp \
$(FACIAL_MODELS_DIR)/* \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
@@ -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

@@ -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 {
@@ -1360,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());
@@ -2222,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)));
@@ -2904,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.
*
@@ -2998,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

@@ -430,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,
@@ -649,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,
@@ -826,6 +835,10 @@ var config = {
// format: 'flac'
//
// },
// e2ee: {
// labels,
// externallyManagedKey: false
// },
// Options related to end-to-end (participant to participant) ping.
@@ -896,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: '',

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

@@ -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

@@ -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

@@ -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

@@ -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.",
@@ -760,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",
@@ -772,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.",
@@ -858,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": " ",
@@ -943,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",
@@ -978,7 +989,7 @@
"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",
@@ -1136,6 +1147,12 @@
"button": "Andere einladen",
"youAreAlone": "Nur Sie sind in dieser Konferenz"
},
"termsView": {
"header": "Nutzungsbedingungen"
},
"privacyView": {
"header": "Datenschutz"
},
"helpView": {
"header": "Hilfecenter"
},

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?",
@@ -623,6 +637,7 @@
"invite": "Invite Someone",
"askUnmute": "Ask to unmute",
"moreModerationActions": "More moderation options",
"moreParticipantOptions": "More participant options",
"mute": "Mute",
"muteAll": "Mute all",
"muteEveryoneElse": "Mute everyone else",
@@ -862,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": " ",
@@ -879,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",
@@ -962,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",
@@ -1140,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

@@ -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

@@ -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',
@@ -1185,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,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

@@ -62,7 +62,7 @@ function getNewAudioInputDevice(newDevices, localAudio, newLabel) {
const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
const selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId);
const localAudioDeviceId = localAudio.getDeviceId();
const localAudioDeviceId = localAudio?.getDeviceId();
const localAudioDevice = availableAudioInputDevices.find(
d => d.deviceId === localAudioDeviceId);
@@ -114,7 +114,7 @@ function getNewVideoInputDevice(newDevices, localVideo, newLabel) {
const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
const selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId);
const localVideoDeviceId = localVideo.getDeviceId();
const localVideoDeviceId = localVideo?.getDeviceId();
const localVideoDevice = availableVideoInputDevices.find(
d => d.deviceId === localVideoDeviceId);

350
package-lock.json generated
View File

@@ -31,6 +31,7 @@
"@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",
@@ -39,6 +40,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",
@@ -50,6 +53,7 @@
"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",
@@ -60,7 +64,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c193f0d4335b6ed4810aa07d0c7d6860b814801a",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
@@ -110,7 +114,6 @@
"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": "8.3.2",
@@ -3371,6 +3374,40 @@
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
},
"node_modules/@jitsi/rtcstats": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.0.1.tgz",
"integrity": "sha512-kImbDneVzU3pBDyY0vOruG96iYnMp2aOeHxYvnHgXGCTQfqF4dcPGtWMucSb5Dz7KEY6+U6G77Kay5C9p9lFjw==",
"dependencies": {
"@jitsi/js-utils": "1.0.0",
"sdp": "^3.0.3",
"uuid": "3.1.0"
}
},
"node_modules/@jitsi/rtcstats/node_modules/@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"dependencies": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"node_modules/@jitsi/rtcstats/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/@jitsi/rtcstats/node_modules/uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/@jitsi/sdp-interop": {
"version": "1.0.5",
"resolved": "git+ssh://git@github.com/jitsi/sdp-interop.git#4669790bb9020cc8f10c1d1f3823c26b08497547",
@@ -4171,6 +4208,22 @@
"react-native": ">=0.57"
}
},
"node_modules/@react-navigation/bottom-tabs": {
"version": "5.11.15",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.15.tgz",
"integrity": "sha512-TBY419W6aN/HZg98xbVp5Bx1HEF5sXuHR5f55W6KMI4k2AvxlwelKD1wbfvEcX2iuQT0YUiiXsACRFUSECYhkw==",
"dependencies": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 0.6.0",
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
}
},
"node_modules/@react-navigation/core": {
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.16.1.tgz",
@@ -4222,6 +4275,24 @@
"node": ">=4"
}
},
"node_modules/@react-navigation/drawer": {
"version": "5.12.9",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.12.9.tgz",
"integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==",
"dependencies": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",
"react": "*",
"react-native": "*",
"react-native-gesture-handler": ">= 1.0.0",
"react-native-reanimated": ">= 1.0.0",
"react-native-safe-area-context": ">= 0.6.0",
"react-native-screens": ">= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0"
}
},
"node_modules/@react-navigation/material-top-tabs": {
"version": "5.3.19",
"resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-5.3.19.tgz",
@@ -4492,6 +4563,30 @@
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs-core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz",
"integrity": "sha512-uwQdiklNjqBnHPeseOdG0sGxrI3+d6lybaKu2+ou3ajVeKdPEwpWbgqA6iHjq1iylnOGkgkbbnQ6r2lwkiIIHw==",
"dependencies": {
"@types/offscreencanvas": "~2019.3.0",
"@types/seedrandom": "2.4.27",
"@types/webgl-ext": "0.0.30",
"@types/webgl2": "0.0.4",
"node-fetch": "~2.1.2",
"seedrandom": "2.4.3"
},
"engines": {
"yarn": ">= 1.3.2"
}
},
"node_modules/@tensorflow/tfjs-core/node_modules/node-fetch": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=",
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/@types/eslint": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz",
@@ -4572,6 +4667,11 @@
"integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==",
"dev": true
},
"node_modules/@types/offscreencanvas": {
"version": "2019.3.0",
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -4621,11 +4721,26 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
},
"node_modules/@types/seedrandom": {
"version": "2.4.27",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
},
"node_modules/@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
},
"node_modules/@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
},
"node_modules/@types/webgl2": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
},
"node_modules/@types/yargs": {
"version": "13.0.8",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
@@ -9258,6 +9373,15 @@
"node": ">=0.10.0"
}
},
"node_modules/face-api.js": {
"version": "0.22.2",
"resolved": "https://registry.npmjs.org/face-api.js/-/face-api.js-0.22.2.tgz",
"integrity": "sha512-9Bbv/yaBRTKCXjiDqzryeKhYxmgSjJ7ukvOvEBy6krA0Ah/vNBlsf7iBNfJljWiPA8Tys1/MnB3lyP2Hfmsuyw==",
"dependencies": {
"@tensorflow/tfjs-core": "1.7.0",
"tslib": "^1.11.1"
}
},
"node_modules/fancy-log": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
@@ -12134,11 +12258,6 @@
"jetify": "bin/jetify"
}
},
"node_modules/jitsi-meet-logger": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/jitsi/jitsi-meet-logger.git#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"integrity": "sha512-1WC//tMrc8lAxfldhEYFxk8EZuHwBcJgy+QDzd9E7ZJCCm5r+aUYshnx69PLwcbf7iLfKPhcuCKz8FOJRpBdXg=="
},
"node_modules/jquery": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
@@ -12442,18 +12561,18 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#c193f0d4335b6ed4810aa07d0c7d6860b814801a",
"integrity": "sha512-r2oFXPRnuS7nX0fiM1mu1CoJGZ/c99vB+AHtLye3yEedK4GHIgX3sgYuB4cJtj914UVWqT7geqbUSAJ7LHrzjQ==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"integrity": "sha512-DqdQ/b/l7gzVplV2UkdwUDwFzST6yEj0oB+MFEIQAobhj/bIm/QjLjhPnu8EBKXFtUegma/WsX4jSBK+6U3RzQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/sdp-interop": "github:jitsi/sdp-interop#4669790bb9020cc8f10c1d1f3823c26b08497547",
"@jitsi/sdp-simulcast": "0.4.0",
"async": "0.9.0",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"lodash.isequal": "4.5.0",
@@ -16228,11 +16347,6 @@
}
}
},
"node_modules/react-uid/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/react-window": {
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.6.tgz",
@@ -16653,40 +16767,6 @@
"node": "6.* || >= 7.*"
}
},
"node_modules/rtcstats": {
"version": "8.1.0",
"resolved": "git+ssh://git@github.com/jitsi/rtcstats.git#7b3a16b51c84fc860ce0af26f0d035ed5c94d4b2",
"integrity": "sha512-ndTxKslevhS5MMWks/+dESHB3GgeD6nM0Q6wJszZRHC7gsrU7Hut24B/R/+Zs9AwHYHmr20XnH5y5EBHmQ0UpA==",
"license": "MIT",
"dependencies": {
"@jitsi/js-utils": "1.0.0",
"uuid": "3.1.0"
}
},
"node_modules/rtcstats/node_modules/@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"dependencies": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"node_modules/rtcstats/node_modules/js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"node_modules/rtcstats/node_modules/uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
@@ -16830,6 +16910,11 @@
"sdp-verify": "checker.js"
}
},
"node_modules/seedrandom": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
"integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
},
"node_modules/select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -18390,9 +18475,9 @@
}
},
"node_modules/tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -22721,6 +22806,38 @@
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
},
"@jitsi/rtcstats": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.0.1.tgz",
"integrity": "sha512-kImbDneVzU3pBDyY0vOruG96iYnMp2aOeHxYvnHgXGCTQfqF4dcPGtWMucSb5Dz7KEY6+U6G77Kay5C9p9lFjw==",
"requires": {
"@jitsi/js-utils": "1.0.0",
"sdp": "^3.0.3",
"uuid": "3.1.0"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
}
}
},
"@jitsi/sdp-interop": {
"version": "git+ssh://git@github.com/jitsi/sdp-interop.git#4669790bb9020cc8f10c1d1f3823c26b08497547",
"integrity": "sha512-4nqEqJWyRFjHM/riI0DQRNx+mgx277iK0r5LhwVAHDZDBYbLN54vYcfZ6JepcmygQiixa8jet/gLJnikdH9wzQ==",
@@ -23314,6 +23431,15 @@
"resolved": "https://registry.npmjs.org/@react-native-masked-view/masked-view/-/masked-view-0.2.6.tgz",
"integrity": "sha512-303CxmetUmgiX9NSUxatZkNh9qTYYdiM8xkGf9I3Uj20U3eGY3M78ljeNQ4UVCJA+FNGS5nC1dtS9GjIqvB4dg=="
},
"@react-navigation/bottom-tabs": {
"version": "5.11.15",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.11.15.tgz",
"integrity": "sha512-TBY419W6aN/HZg98xbVp5Bx1HEF5sXuHR5f55W6KMI4k2AvxlwelKD1wbfvEcX2iuQT0YUiiXsACRFUSECYhkw==",
"requires": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
}
},
"@react-navigation/core": {
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-5.16.1.tgz",
@@ -23349,6 +23475,15 @@
}
}
},
"@react-navigation/drawer": {
"version": "5.12.9",
"resolved": "https://registry.npmjs.org/@react-navigation/drawer/-/drawer-5.12.9.tgz",
"integrity": "sha512-SYb2BCEAn+BiEwC6WBfCzs1VlWD+ZdQbxmsim6vo1o+ndPW2e+kiq7FXKRs0vUXhQRZVl2oOB3vBn0c3YCllQg==",
"requires": {
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0"
}
},
"@react-navigation/material-top-tabs": {
"version": "5.3.19",
"resolved": "https://registry.npmjs.org/@react-navigation/material-top-tabs/-/material-top-tabs-5.3.19.tgz",
@@ -23544,6 +23679,26 @@
"loader-utils": "^1.2.3"
}
},
"@tensorflow/tfjs-core": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz",
"integrity": "sha512-uwQdiklNjqBnHPeseOdG0sGxrI3+d6lybaKu2+ou3ajVeKdPEwpWbgqA6iHjq1iylnOGkgkbbnQ6r2lwkiIIHw==",
"requires": {
"@types/offscreencanvas": "~2019.3.0",
"@types/seedrandom": "2.4.27",
"@types/webgl-ext": "0.0.30",
"@types/webgl2": "0.0.4",
"node-fetch": "~2.1.2",
"seedrandom": "2.4.3"
},
"dependencies": {
"node-fetch": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
}
}
},
"@types/eslint": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.0.tgz",
@@ -23624,6 +23779,11 @@
"integrity": "sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ==",
"dev": true
},
"@types/offscreencanvas": {
"version": "2019.3.0",
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
},
"@types/parse-json": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
@@ -23675,11 +23835,26 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz",
"integrity": "sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA=="
},
"@types/seedrandom": {
"version": "2.4.27",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
},
"@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
},
"@types/webgl2": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
},
"@types/yargs": {
"version": "13.0.8",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
@@ -27348,6 +27523,15 @@
}
}
},
"face-api.js": {
"version": "0.22.2",
"resolved": "https://registry.npmjs.org/face-api.js/-/face-api.js-0.22.2.tgz",
"integrity": "sha512-9Bbv/yaBRTKCXjiDqzryeKhYxmgSjJ7ukvOvEBy6krA0Ah/vNBlsf7iBNfJljWiPA8Tys1/MnB3lyP2Hfmsuyw==",
"requires": {
"@tensorflow/tfjs-core": "1.7.0",
"tslib": "^1.11.1"
}
},
"fancy-log": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
@@ -29543,11 +29727,6 @@
"resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.4.tgz",
"integrity": "sha512-+f/4OLeqY8RAmXnonI1ffeY1DR8kMNJPhv5WMFehchf7U71cjMQVKkOz1n6asz6kfVoAqKNWJz1A/18i18AcXA=="
},
"jitsi-meet-logger": {
"version": "git+ssh://git@github.com/jitsi/jitsi-meet-logger.git#4add5bac2e4cea73a05f42b7596ee03c7f7a2567",
"integrity": "sha512-1WC//tMrc8lAxfldhEYFxk8EZuHwBcJgy+QDzd9E7ZJCCm5r+aUYshnx69PLwcbf7iLfKPhcuCKz8FOJRpBdXg==",
"from": "jitsi-meet-logger@github:jitsi/jitsi-meet-logger#v1.0.0"
},
"jquery": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
@@ -29809,17 +29988,17 @@
}
},
"lib-jitsi-meet": {
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#c193f0d4335b6ed4810aa07d0c7d6860b814801a",
"integrity": "sha512-r2oFXPRnuS7nX0fiM1mu1CoJGZ/c99vB+AHtLye3yEedK4GHIgX3sgYuB4cJtj914UVWqT7geqbUSAJ7LHrzjQ==",
"from": "lib-jitsi-meet@github:jitsi/lib-jitsi-meet#c193f0d4335b6ed4810aa07d0c7d6860b814801a",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"integrity": "sha512-DqdQ/b/l7gzVplV2UkdwUDwFzST6yEj0oB+MFEIQAobhj/bIm/QjLjhPnu8EBKXFtUegma/WsX4jSBK+6U3RzQ==",
"from": "lib-jitsi-meet@github:jitsi/lib-jitsi-meet#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
"@jitsi/sdp-interop": "github:jitsi/sdp-interop#4669790bb9020cc8f10c1d1f3823c26b08497547",
"@jitsi/sdp-simulcast": "0.4.0",
"async": "0.9.0",
"base64-js": "1.3.1",
"current-executing-script": "0.1.3",
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"lodash.isequal": "4.5.0",
@@ -32812,13 +32991,6 @@
"integrity": "sha512-6C5pwNYP1udgp5feQ9MTBZBKD4su9nhD2aYCFY1bB0Bpask8wYKYz0ZhAtAJ4lcmTDC5kY1ByGTQMFDHQW6p0w==",
"requires": {
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}
}
},
"react-window": {
@@ -33152,37 +33324,6 @@
"resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz",
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA=="
},
"rtcstats": {
"version": "git+ssh://git@github.com/jitsi/rtcstats.git#7b3a16b51c84fc860ce0af26f0d035ed5c94d4b2",
"integrity": "sha512-ndTxKslevhS5MMWks/+dESHB3GgeD6nM0Q6wJszZRHC7gsrU7Hut24B/R/+Zs9AwHYHmr20XnH5y5EBHmQ0UpA==",
"from": "rtcstats@github:jitsi/rtcstats#v8.1.0",
"requires": {
"@jitsi/js-utils": "1.0.0",
"uuid": "3.1.0"
},
"dependencies": {
"@jitsi/js-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-1.0.0.tgz",
"integrity": "sha512-at9GPMP7IL0v6QS1Gs9c5MbbiN2AT0uKzsgKM8qS2wqXxqvpfT3p4U3+LH2IUyXiHlkmvlBMcNM02MltBdyRmQ==",
"requires": {
"bowser": "2.7.0",
"js-md5": "0.7.3",
"postis": "2.2.0"
}
},
"js-md5": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
},
"uuid": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
"integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
}
}
},
"run-async": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
@@ -33290,6 +33431,11 @@
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
},
"seedrandom": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
"integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
},
"select-hose": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
@@ -34530,9 +34676,9 @@
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
"type-check": {
"version": "0.4.0",

View File

@@ -36,6 +36,7 @@
"@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",
@@ -44,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",
@@ -55,6 +58,7 @@
"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",
@@ -65,7 +69,7 @@
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c193f0d4335b6ed4810aa07d0c7d6860b814801a",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#bdfbb82087ce2a9336157da35e739fb4b250ed66",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.21",
"moment": "2.29.1",
@@ -115,7 +119,6 @@
"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": "8.3.2",

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

@@ -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

@@ -2,14 +2,67 @@
/* eslint-disable react/jsx-no-bind */
import React, { useState } from 'react';
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 = {
/**
* An object containing the CSS classes.
*/
classes: Object,
/**
* Css class to apply on container.
*/
@@ -46,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.
*
@@ -64,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);
}
}
@@ -112,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 } />
@@ -122,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 } />
</>
@@ -133,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 }
@@ -152,4 +216,4 @@ CopyButton.defaultProps = {
className: ''
};
export default CopyButton;
export default withStyles(styles)(CopyButton);

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

@@ -1,9 +1,11 @@
// @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 = {
@@ -13,6 +15,11 @@ type Props = {
*/
actions: React$Node,
/**
* List item container class name.
*/
className: string,
/**
* Icon to be displayed on the list item. (Avatar for participants).
*/
@@ -43,11 +50,21 @@ type Props = {
*/
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.
*/
@@ -72,6 +89,7 @@ const useStyles = makeStyles(theme => {
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,
@@ -161,24 +179,75 @@ const useStyles = makeStyles(theme => {
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 = { `list-item-container ${styles.container} ${isHighlighted ? styles.highlighted : ''}` }
className = { clsx('list-item-container',
styles.container,
isHighlighted && styles.highlighted,
className
) }
data-testid = { testId }
id = { id }
onClick = { onClick }
onMouseLeave = { onMouseLeave }>
{ ...(_isMobile
? {
onTouchEnd: _onTouchEnd,
onTouchMove: _onTouchMove,
onTouchStart: _onTouchStart
}
: {
onMouseLeave
}
) }>
<div> {icon} </div>
<div className = { styles.detailsContainer }>
<div className = { styles.name }>
@@ -186,17 +255,20 @@ const ListItem = ({
</div>
{indicators && (
<div
className = { `indicators ${styles.indicators} ${
isHighlighted || trigger === ACTION_TRIGGER.PERMANENT
? styles.indicatorsHidden : ''}` }>
className = { clsx('indicators',
styles.indicators,
(isHighlighted || trigger === ACTION_TRIGGER.PERMANENT) && styles.indicatorsHidden
) }>
{indicators}
</div>
)}
{!hideActions && (
<div
className = { `actions ${styles.actionsContainer} ${
trigger === ACTION_TRIGGER.PERMANENT ? styles.actionsPermanent : ''} ${
isHighlighted ? styles.actionsVisible : ''}` }>
className = { clsx('actions',
styles.actionsContainer,
trigger === ACTION_TRIGGER.PERMANENT && styles.actionsPermanent,
isHighlighted && styles.actionsVisible
) }>
{actions}
</div>
)}

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',
@@ -123,10 +124,12 @@ export default [
'doNotFlipLocalVideo',
'dropbox',
'e2eeLabels',
'e2ee',
'e2eping',
'enableDisplayNameInStats',
'enableEmailInStats',
'enableEncodedTransformSupport',
'enableFacialRecognition',
'enableIceRestart',
'enableInsecureRoomNameWarning',
'enableLayerSuspension',
@@ -156,6 +159,7 @@ export default [
'hideParticipantsStats',
'hideConferenceTimer',
'hiddenDomain',
'hideAddRoomButton',
'hideLobbyButton',
'hosts',
'iAmRecorder',

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

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path d="M 15 2 A 1 1 0 0 0 14.300781 2.2851562 L 3.3925781 11.207031 A 1 1 0 0 0 3.3554688 11.236328 L 3.3183594 11.267578 L 3.3183594 11.269531 A 1 1 0 0 0 3 12 A 1 1 0 0 0 4 13 L 5 13 L 5 24 C 5 25.105 5.895 26 7 26 L 23 26 C 24.105 26 25 25.105 25 24 L 25 13 L 26 13 A 1 1 0 0 0 27 12 A 1 1 0 0 0 26.681641 11.267578 L 26.666016 11.255859 A 1 1 0 0 0 26.597656 11.199219 L 25 9.8925781 L 25 6 C 25 5.448 24.552 5 24 5 L 23 5 C 22.448 5 22 5.448 22 6 L 22 7.4394531 L 15.677734 2.2675781 A 1 1 0 0 0 15 2 z M 18 15 L 22 15 L 22 23 L 18 23 L 18 15 z"/>
</svg>

After

Width:  |  Height:  |  Size: 677 B

View File

@@ -0,0 +1,3 @@
<svg width="16" height="15" viewBox="0 0 16 15" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 7.16667C6.85438 7.16667 5.84374 6.58873 5.24374 5.70851C4.57694 6.29711 4.09999 7.09581 3.91648 8.00102C5.71901 8.04514 7.16667 9.52018 7.16667 11.3333C7.16667 11.871 7.03938 12.3789 6.8133 12.8286C7.1894 12.9401 7.5877 13 8 13C8.4123 13 8.81061 12.9401 9.1867 12.8286C8.96062 12.3789 8.83333 11.871 8.83333 11.3333C8.83333 9.52018 10.281 8.04515 12.0835 8.00102C11.9 7.09581 11.4231 6.29711 10.7563 5.70851C10.1563 6.58873 9.14562 7.16667 8 7.16667ZM8 14.6667C8.85231 14.6667 9.66193 14.4839 10.3918 14.1554C10.9057 14.4793 11.5143 14.6667 12.1667 14.6667C14.0076 14.6667 15.5 13.1743 15.5 11.3333C15.5 10.0941 14.8238 9.01283 13.8202 8.43837C13.6984 6.61689 12.7405 5.02432 11.327 4.04114C11.3312 3.97241 11.3333 3.90312 11.3333 3.83333C11.3333 1.99238 9.84095 0.5 8 0.5C6.15905 0.5 4.66667 1.99238 4.66667 3.83333C4.66667 3.90312 4.66881 3.97241 4.67304 4.04114C3.2595 5.02432 2.30161 6.61689 2.17983 8.43837C1.17624 9.01282 0.5 10.0941 0.5 11.3333C0.5 13.1743 1.99238 14.6667 3.83333 14.6667C4.4857 14.6667 5.09428 14.4793 5.60821 14.1554C6.33807 14.4839 7.14769 14.6667 8 14.6667ZM9.66667 3.83333C9.66667 4.75381 8.92047 5.5 8 5.5C7.07952 5.5 6.33333 4.75381 6.33333 3.83333C6.33333 2.91286 7.07952 2.16667 8 2.16667C8.92047 2.16667 9.66667 2.91286 9.66667 3.83333ZM5.5 11.3333C5.5 12.2538 4.75381 13 3.83333 13C2.91286 13 2.16667 12.2538 2.16667 11.3333C2.16667 10.4129 2.91286 9.66667 3.83333 9.66667C4.75381 9.66667 5.5 10.4129 5.5 11.3333ZM13.8333 11.3333C13.8333 12.2538 13.0871 13 12.1667 13C11.2462 13 10.5 12.2538 10.5 11.3333C10.5 10.4129 11.2462 9.66667 12.1667 9.66667C13.0871 9.66667 13.8333 10.4129 13.8333 11.3333Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px">
<path d="M 15 2 A 1 1 0 0 0 14.300781 2.2851562 L 3.3925781 11.207031 A 1 1 0 0 0 3.3554688 11.236328 L 3.3183594 11.267578 L 3.3183594 11.269531 A 1 1 0 0 0 3 12 A 1 1 0 0 0 4 13 L 5 13 L 5 24 C 5 25.105 5.895 26 7 26 L 23 26 C 24.105 26 25 25.105 25 24 L 25 13 L 26 13 A 1 1 0 0 0 27 12 A 1 1 0 0 0 26.681641 11.267578 L 26.666016 11.255859 A 1 1 0 0 0 26.597656 11.199219 L 25 9.8925781 L 25 6 C 25 5.448 24.552 5 24 5 L 23 5 C 22.448 5 22 5.448 22 6 L 22 7.4394531 L 15.677734 2.2675781 A 1 1 0 0 0 15 2 z M 18 15 L 22 15 L 22 23 L 18 23 L 18 15 z"/>
</svg>

After

Width:  |  Height:  |  Size: 692 B

View File

@@ -60,6 +60,7 @@ export { default as IconFullScreen } from './full-screen.svg';
export { default as IconGoogle } from './google.svg';
export { default as IconHangup } from './hangup.svg';
export { default as IconHelp } from './help.svg';
export { default as IconHome } from './home.svg';
export { default as IconHorizontalPoints } from './horizontal-points.svg';
export { default as IconInfo } from './info.svg';
export { default as IconInviteMore } from './user-plus.svg';
@@ -100,6 +101,7 @@ export { default as IconRemoteControlStart } from './play.svg';
export { default as IconRemoteControlStop } from './stop.svg';
export { default as IconReply } from './reply.svg';
export { default as IconRestore } from './restore.svg';
export { default as IconRingGroup } from './icon-ring-group.svg';
export { default as IconRoomLock } from './security.svg';
export { default as IconRoomUnlock } from './security-locked.svg';
export { default as IconSecurityOff } from './security-off.svg';

View File

@@ -3,10 +3,8 @@
import { useHeaderHeight } from '@react-navigation/stack';
import React, { useEffect, useState } from 'react';
import {
Keyboard,
KeyboardAvoidingView,
Platform,
TouchableWithoutFeedback
Platform
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -60,22 +58,18 @@ const JitsiKeyboardAvoidingView = (
const androidVerticalOffset = headerHeight;
return (
<TouchableWithoutFeedback
/* eslint-disable-next-line react/jsx-handler-names */
onPress = { Keyboard.dismiss }>
<KeyboardAvoidingView
behavior = { Platform.OS === 'ios' ? 'padding' : 'height' }
contentContainerStyle = { contentContainerStyle }
enabled = { true }
keyboardVerticalOffset = {
Platform.OS === 'ios'
? iosVerticalOffset
: androidVerticalOffset
}
style = { style }>
{ children }
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
<KeyboardAvoidingView
behavior = { Platform.OS === 'ios' ? 'padding' : 'height' }
contentContainerStyle = { contentContainerStyle }
enabled = { true }
keyboardVerticalOffset = {
Platform.OS === 'ios'
? iosVerticalOffset
: androidVerticalOffset
}
style = { style }>
{ children }
</KeyboardAvoidingView>
);
};

View File

@@ -1,188 +0,0 @@
// @flow
import React, { PureComponent } from 'react';
import { KeyboardAvoidingView, Platform, SafeAreaView } from 'react-native';
import { ColorSchemeRegistry } from '../../color-scheme';
import { HeaderWithNavigation, SlidingView } from '../../react';
import { connect } from '../../redux';
import { StyleType } from '../../styles';
import { setActiveModalId } from '../actions';
import styles from './styles';
type Props = {
/**
* The color schemed style of the common header component.
*/
_headerStyles: StyleType,
/**
* True if the modal should be shown, false otherwise.
*/
_show: boolean,
/**
* The color schemed style of the modal.
*/
_styles: StyleType,
/**
* The children component(s) of the Modal, to be rendered.
*/
children: React$Node,
/**
* The Redux Dispatch function.
*/
dispatch: Function,
/**
* Optional function that renders a footer component, if needed.
*/
footerComponent?: Function,
/**
* Props to be passed over to the header.
*
* See {@code HeaderWithNavigation} for more details.
*/
headerProps: Object,
/**
* True if the header with navigation should be hidden, false otherwise.
*/
hideHeaderWithNavigation?: boolean,
/**
* The ID of the modal that is being rendered. This is used to show/hide the modal.
*/
modalId: string,
/**
* Callback to be invoked when the modal closes.
*/
onClose?: Function,
/**
* The position from where the modal should be opened. This is derived from the
* props of the {@code SlidingView} with the same name.
*/
position?: string,
/**
* Additional style to be appended to the View containing the content of the modal.
*/
style?: StyleType
};
/**
* Implements a custom Jitsi Modal that doesn't use the built in native
* Modal component of React Native.
*/
class JitsiModal extends PureComponent<Props> {
static defaultProps = {
position: 'bottom',
hideHeaderWithNavigation: false
};
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onRequestClose = this._onRequestClose.bind(this);
}
/**
* Implements {@code PureComponent#render}.
*
* @inheritdoc
*/
render() {
const {
_headerStyles,
_show,
_styles,
children,
footerComponent,
headerProps,
position,
hideHeaderWithNavigation,
style
} = this.props;
return (
<SlidingView
onHide = { this._onRequestClose }
position = { position }
show = { _show }>
<KeyboardAvoidingView
behavior =
{
Platform.OS === 'ios'
? 'padding' : 'height'
}
enabled = { true }
style = { [
_headerStyles.page,
_styles.page,
style
] }>
<HeaderWithNavigation
{ ...headerProps }
hideHeaderWithNavigation = { hideHeaderWithNavigation }
onPressBack = { this._onRequestClose } />
<SafeAreaView style = { styles.safeArea }>
{ children }
</SafeAreaView>
{ footerComponent && footerComponent() }
</KeyboardAvoidingView>
</SlidingView>
);
}
_onRequestClose: () => boolean;
/**
* Callback to be invoked when the SlidingView requests closing.
*
* @returns {boolean}
*/
_onRequestClose() {
const { _show, dispatch, onClose } = this.props;
let shouldCloseModal = true;
if (_show) {
if (typeof onClose === 'function') {
shouldCloseModal = onClose();
}
shouldCloseModal && dispatch(setActiveModalId());
return shouldCloseModal;
}
return false;
}
}
/**
* Maps part of the Redix state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
function _mapStateToProps(state, ownProps): $Shape<Props> {
return {
_headerStyles: ColorSchemeRegistry.get(state, 'Header'),
_show: state['features/base/modal'].activeModalId === ownProps.modalId,
_styles: ColorSchemeRegistry.get(state, 'Modal')
};
}
export default connect(_mapStateToProps)(JitsiModal);

View File

@@ -30,7 +30,7 @@ type Props = {
/**
* Is the screen rendering a tab navigator?
*/
hasTabNavigator: boolean,
hasTabNavigator?: boolean,
/**
* Additional style to be appended to the KeyboardAvoidingView containing the content of the modal.
@@ -42,7 +42,7 @@ const JitsiScreen = ({
contentContainerStyle,
children,
footerComponent,
hasTabNavigator,
hasTabNavigator = false,
style
}: Props) => (
<View

View File

@@ -0,0 +1,28 @@
// @flow
import React from 'react';
import WebView from 'react-native-webview';
import JitsiScreen from './JitsiScreen';
type Props = {
/**
* The URL to display.
*/
source: string,
/**
* The component's external style.
*/
style: Object
}
const JitsiScreenWebView = ({ source, style }: Props) => (
<JitsiScreen
style = { style }>
<WebView source = {{ uri: source }} />
</JitsiScreen>
);
export default JitsiScreenWebView;

View File

@@ -0,0 +1,79 @@
// @flow
import React, { useCallback } from 'react';
import { StatusBar } from 'react-native';
import { ColorSchemeRegistry } from '../../color-scheme';
import { connect } from '../../redux';
import { isDarkColor } from '../../styles';
// Register style
import '../../react/components/native/headerstyles';
/**
* Constants for the (currently) supported statusbar colors.
*/
const STATUSBAR_DARK = 'dark-content';
const STATUSBAR_LIGHT = 'light-content';
type Props = {
/**
* The color schemed style of the component.
*/
_styles: Object
}
const JitsiStatusBar = ({ _styles }: Props) => {
const getStatusBarContentColor = useCallback(() => {
const { statusBarContent } = _styles;
if (statusBarContent) {
// We have the possibility to define the statusbar color in the
// color scheme feature, but since mobile devices (at the moment)
// only support two colors (light and dark) we need to normalize
// the value.
if (isDarkColor(statusBarContent)) {
return STATUSBAR_DARK;
}
return STATUSBAR_LIGHT;
}
// The statusbar color is not defined, so we need to base our choice
// on the header colors
const { statusBar, screenHeader } = _styles;
if (isDarkColor(statusBar || screenHeader.backgroundColor)) {
return STATUSBAR_LIGHT;
}
return STATUSBAR_DARK;
}, [ _styles ]);
return (
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { getStatusBarContentColor() }
translucent = { false } />
);
};
/**
* Maps part of the Redux state to the props of the component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: Object
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Header')
};
}
export default connect(_mapStateToProps)(JitsiStatusBar);

View File

@@ -1,34 +1,7 @@
// @flow
import { useEffect, useState } from 'react';
import { Keyboard } from 'react-native';
import { toState } from '../../redux';
export const useKeyboardHeight = () => {
const [ keyboardHeight, setKeyboardHeight ] = useState(0);
const onKeyboardDidShow = e => {
setKeyboardHeight(e.endCoordinates.height);
};
const onKeyboardDidHide = () => {
setKeyboardHeight(0);
};
useEffect(() => {
const keyboardShow = Keyboard.addListener('keyboardDidShow', onKeyboardDidShow);
const keyboardHide = Keyboard.addListener('keyboardDidHide', onKeyboardDidHide);
return () => {
keyboardShow.remove();
keyboardHide.remove();
};
}, []);
return keyboardHeight;
};
/**
*
* Returns the client width.
@@ -39,7 +12,7 @@ export const useKeyboardHeight = () => {
* @returns {number}.
*/
export function getClientWidth(stateful: Object) {
const state = toState(stateful['features/base/responsive-ui']);
const state = toState(stateful)['features/base/responsive-ui'];
return state.clientWidth;
}
@@ -54,7 +27,7 @@ export function getClientWidth(stateful: Object) {
* @returns {number}.
*/
export function getClientHeight(stateful: Object) {
const state = toState(stateful['features/base/responsive-ui']);
const state = toState(stateful)['features/base/responsive-ui'];
return state.clientHeight;
}

View File

@@ -1,3 +0,0 @@
// @flow
export { default as JitsiModal } from './JitsiModal';

View File

@@ -1,6 +0,0 @@
// @flow
import { Component } from 'react';
export const JitsiModal = Component;

View File

@@ -2,4 +2,3 @@
export * from './actions';
export * from './actionTypes';
export * from './components';

View File

@@ -332,18 +332,23 @@ StateListenerRegistry.register(
*/
function _e2eeUpdated({ getState, dispatch }, conference, participantId, newValue) {
const e2eeEnabled = newValue === 'true';
const { maxMode } = getState()['features/e2ee'] || {};
if (maxMode !== MAX_MODE.THRESHOLD_EXCEEDED || !e2eeEnabled) {
dispatch(toggleE2EE(e2eeEnabled));
}
const { e2ee = {} } = getState()['features/base/config'];
dispatch(participantUpdated({
conference,
id: participantId,
e2eeEnabled
}));
if (e2ee.externallyManagedKey) {
return;
}
const { maxMode } = getState()['features/e2ee'] || {};
if (maxMode !== MAX_MODE.THRESHOLD_EXCEEDED || !e2eeEnabled) {
dispatch(toggleE2EE(e2eeEnabled));
}
}
/**

View File

@@ -1,8 +1,11 @@
// @flow
import { withStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React, { useCallback } from 'react';
import { Icon, IconArrowDown } from '../../../icons';
import { withPixelLineHeight } from '../../../styles/functions.web';
type Props = {
@@ -11,6 +14,11 @@ type Props = {
*/
children: React$Node,
/**
* An object containing the CSS classes.
*/
classes: Object,
/**
* Text css class of the button.
*/
@@ -78,6 +86,91 @@ type Props = {
ariaDropDownLabel?: string
};
/**
* Creates the styles for the component.
*
* @param {Object} theme - The current UI theme.
*
* @returns {Object}
*/
const styles = theme => {
return {
actionButton: {
...withPixelLineHeight(theme.typography.bodyLongBold),
borderRadius: theme.shape.borderRadius,
boxSizing: 'border-box',
color: theme.palette.text01,
cursor: 'pointer',
display: 'inline-block',
marginBottom: '16px',
padding: '7px 16px',
position: 'relative',
textAlign: 'center',
width: '100%',
'&.primary': {
background: theme.palette.action01,
border: '1px solid #0376DA',
'&:hover': {
backgroundColor: theme.palette.action01Hover
}
},
'&.secondary': {
background: theme.palette.action02,
border: '1px solid transparent'
},
'&.text': {
width: 'auto',
fontSize: '13px',
margin: '0',
padding: '0'
},
'&.disabled': {
background: theme.palette.action01Disabled,
border: '1px solid #5E6D7A',
color: '#AFB6BC',
cursor: 'initial',
'.icon': {
'& > svg': {
fill: '#AFB6BC'
}
}
},
[theme.breakpoints.down('400')]: {
fontSize: 16,
marginBottom: 8,
padding: '11px 16px'
}
},
options: {
borderRadius: theme.shape.borderRadius / 2,
alignItems: 'center',
display: 'flex',
height: '100%',
justifyContent: 'center',
position: 'absolute',
right: 0,
top: 0,
width: 36,
'&:hover': {
backgroundColor: '#0262B6'
},
'& svg': {
pointerEvents: 'none'
}
}
};
};
/**
* Button used for pre meeting actions.
*
@@ -85,6 +178,7 @@ type Props = {
*/
function ActionButton({
children,
classes,
className = '',
disabled,
hasOptions,
@@ -115,11 +209,18 @@ function ActionButton({
}
}, [ onOptionsClick, disabled ]);
const containerClasses = clsx(
classes.actionButton,
className && className,
type,
disabled && 'disabled'
);
return (
<div
aria-disabled = { disabled }
aria-label = { ariaLabel }
className = { `action-btn ${className} ${type} ${disabled ? 'disabled' : ''}` }
className = { containerClasses }
data-testid = { testId ? testId : undefined }
onClick = { disabled ? undefined : onClick }
onKeyPress = { onKeyPressHandler }
@@ -132,7 +233,7 @@ function ActionButton({
aria-haspopup = 'true'
aria-label = { ariaDropDownLabel }
aria-pressed = { ariaPressed }
className = 'options'
className = { classes.options }
data-testid = 'prejoin.joinOptions'
onClick = { disabled ? undefined : onOptionsClick }
onKeyPress = { onOptionsKeyPressHandler }
@@ -148,4 +249,4 @@ function ActionButton({
);
}
export default ActionButton;
export default withStyles(styles)(ActionButton);

View File

@@ -1,122 +0,0 @@
// @flow
import React, { PureComponent, type Node } from 'react';
import { SafeAreaView, StatusBar, View } from 'react-native';
import { ColorSchemeRegistry } from '../../../color-scheme';
import { connect } from '../../../redux';
import { isDarkColor } from '../../../styles';
// Register style
import './headerstyles';
/**
* Constanst for the (currently) supported statusbar colors.
*/
const STATUSBAR_DARK = 'dark-content';
const STATUSBAR_LIGHT = 'light-content';
/**
* The type of the React {@code Component} props of {@link Header}.
*/
type Props = {
/**
* Children component(s).
*/
children: Node,
/**
* The component's external style.
*/
style: Object,
/**
* The color schemed style of the component.
*/
_styles: Object
}
/**
* A generic screen header component.
*/
class Header extends PureComponent<Props> {
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
*/
render() {
const { _styles } = this.props;
return (
<View style = { _styles.headerOverlay }>
<StatusBar
backgroundColor = { _styles.statusBar }
barStyle = { this._getStatusBarContentColor() }
translucent = { false } />
<SafeAreaView>
<View
style = { [
_styles.screenHeader,
this.props.style
] }>
{
this.props.children
}
</View>
</SafeAreaView>
</View>
);
}
/**
* Calculates the color of the statusbar content (light or dark) based on
* certain criteria.
*
* @returns {string}
*/
_getStatusBarContentColor() {
const { _styles } = this.props;
const { statusBarContent } = _styles;
if (statusBarContent) {
// We have the possibility to define the statusbar color in the
// color scheme feature, but since mobile devices (at the moment)
// only support two colors (light and dark) we need to normalize
// the value.
if (isDarkColor(statusBarContent)) {
return STATUSBAR_DARK;
}
return STATUSBAR_LIGHT;
}
// The statusbar color is not defined, so we need to base our choice
// on the header colors
const { statusBar, screenHeader } = _styles;
if (isDarkColor(statusBar || screenHeader.backgroundColor)) {
return STATUSBAR_LIGHT;
}
return STATUSBAR_DARK;
}
}
/**
* Maps part of the Redux state to the props of the component.
*
* @param {Object} state - The Redux state.
* @returns {{
* _styles: Object
* }}
*/
function _mapStateToProps(state) {
return {
_styles: ColorSchemeRegistry.get(state, 'Header')
};
}
export default connect(_mapStateToProps)(Header);

View File

@@ -1,72 +0,0 @@
// @flow
import React, { Component } from 'react';
import { translate } from '../../../i18n';
import BackButton from './BackButton';
import ForwardButton from './ForwardButton';
import Header from './Header';
import HeaderLabel from './HeaderLabel';
type Props = {
/**
* Boolean to set the forward button disabled.
*/
forwardDisabled: boolean,
/**
* The i18n key of the the forward button label.
*/
forwardLabelKey: ?string,
/**
* The i18n key of the header label (title).
*/
headerLabelKey: ?string,
/**
* True if the header with navigation should be hidden, false otherwise.
*/
hideHeaderWithNavigation?: boolean,
/**
* Callback to be invoked on pressing the back button.
*/
onPressBack: ?Function,
/**
* Callback to be invoked on pressing the forward button.
*/
onPressForward: ?Function,
}
/**
* Implements a header with the standard navigation content.
*/
class HeaderWithNavigation extends Component<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
return (
!hideHeaderWithNavigation
&& <Header>
{ onPressBack && <BackButton onPress = { onPressBack } /> }
<HeaderLabel labelKey = { this.props.headerLabelKey } />
{ onPressForward && <ForwardButton
disabled = { this.props.forwardDisabled }
labelKey = { this.props.forwardLabelKey }
onPress = { onPressForward } /> }
</Header>
);
}
}
export default translate(HeaderWithNavigation);

View File

@@ -6,9 +6,7 @@ export { default as BaseIndicator } from './BaseIndicator';
export { default as Button } from './Button';
export { default as Container } from './Container';
export { default as ForwardButton } from './ForwardButton';
export { default as Header } from './Header';
export { default as HeaderLabel } from './HeaderLabel';
export { default as HeaderWithNavigation } from './HeaderWithNavigation';
export { default as Image } from './Image';
export { default as Link } from './Link';
export { default as Linkify } from './Linkify';

View File

@@ -117,7 +117,8 @@ const SECTION_LIST_STYLES = {
* The top level container style of the list.
*/
container: {
flex: 1
flex: 1,
width: '100%'
},
list: {

View File

@@ -18,6 +18,7 @@ export const colors = {
primary08: '#99BBF3',
primary09: '#CCDDF9',
primary10: '#17A0DB',
primary11: '#1081B2',
surface00: '#111111',
surface01: '#040404',
@@ -32,12 +33,26 @@ export const colors = {
surface10: '#E0E0E0',
surface11: '#FFF',
surface12: '#AAAAAA',
surface13: '#495258',
success04: '#189B55',
success05: '#1EC26A',
warning05: '#F8AE1A',
warning06: '#ED9E1B'
warning06: '#ED9E1B',
warning07: '#D77976',
disabled01: '#00000040',
support01: '#FF9B42',
support02: '#F96E57',
support03: '#DF486F',
support04: '#B23683',
support05: '#73348C',
support06: '#6A50D3',
support07: '#4380E2',
support08: '#00A8B3',
support09: '#2AA076'
};
// Mapping between the token used and the color
@@ -51,13 +66,18 @@ export const colorMap = {
ui03: 'surface04',
ui04: 'surface05',
ui05: 'surface06',
ui12: 'surface11',
// Primary buttons
action01: 'primary05',
action04: 'primary11',
// Screen header
screen01Header: 'primary10',
// Status bar
status01Bar: 'primary11',
// Hover state for primary buttons
action01Hover: 'primary06',
@@ -115,6 +135,9 @@ export const colorMap = {
// Disabled state for danger buttons
actionDangerDisabled: 'error03',
// Underlay color for buttons
underlay01: 'surface13',
// Bottom sheet background
bottomSheet: 'surface00',
@@ -130,6 +153,9 @@ export const colorMap = {
// Text for bottom sheet items
text04: 'surface12',
// Text for drawer menu displayed name
text05: 'surface06',
// error messages
textError: 'error06',
@@ -216,7 +242,29 @@ export const colorMap = {
warning01: 'warning05',
// Color for indicating a raised hand
warning02: 'warning06'
warning02: 'warning06',
// Color for insecure room
warning03: 'warning07',
// Color for disabled tab
tab01Disabled: 'disabled01',
// Color for disabled video switch
video01Disabled: 'disabled01',
// Backgrounds for avatars
support01: 'support01',
support02: 'support02',
support03: 'support03',
support04: 'support04',
support05: 'support05',
support06: 'support06',
support08: 'support08',
support09: 'support09',
// Used for avatars and raise hand badge
support07: 'support07'
};
@@ -353,3 +401,12 @@ export const typography = {
letterSpacing: 0
}
};
export const breakpoints = {
values: {
'0': 0,
'320': 320,
'400': 400,
'480': 480
}
};

View File

@@ -1,6 +1,6 @@
// @flow
import { font, colors, colorMap, spacing, shape, typography } from '../Tokens';
import { font, colors, colorMap, spacing, shape, typography, breakpoints } from '../Tokens';
import { createWebTheme } from '../functions';
export default createWebTheme({
@@ -10,10 +10,5 @@ export default createWebTheme({
spacing,
shape,
typography,
breakpoints: {
values: {
'0': 0,
'480': 480
}
}
breakpoints
});

View File

@@ -15,7 +15,7 @@ import { formatCommonClasses } from '../functions';
const useStyles = makeStyles(theme =>
createStyles({
'@global': {
...formatCommonClasses(commonStyles),
...formatCommonClasses(commonStyles(theme)),
...getGlobalStyles(theme)
}
})

View File

@@ -3,10 +3,17 @@
import { ThemeProvider } from '@material-ui/core/styles';
import * as React from 'react';
import { connect } from '../../../base/redux';
import BaseTheme from './BaseTheme';
type Props = {
/**
* The default theme or theme set through advanced branding.
*/
_theme: Object,
/**
* The children of the component.
*/
@@ -19,6 +26,22 @@ type Props = {
* @param {Object} props - The props of the component.
* @returns {React.ReactNode}
*/
export default function JitsiThemeProvider(props: Props) {
return <ThemeProvider theme = { BaseTheme }>{ props.children }</ThemeProvider>;
function JitsiThemeProvider(props: Props) {
return <ThemeProvider theme = { props._theme }>{ props.children }</ThemeProvider>;
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
const { muiBrandedTheme } = state['features/dynamic-branding'];
return {
_theme: muiBrandedTheme || BaseTheme
};
}
export default connect(_mapStateToProps)(JitsiThemeProvider);

View File

@@ -6,19 +6,153 @@
*
*/
export const commonClassName = {
emptyList: 'empty-list'
emptyList: 'empty-list',
overflowMenuItem: 'overflow-menu-item',
overflowMenuItemIcon: 'overflow-menu-item-icon',
toolboxIcon: 'toolbox-icon',
toolboxButton: 'toolbox-button',
toolboxContentItems: 'toolbox-content-items'
};
/**
* An object containing the declaration of the common, reusable CSS classes.
* Returns an object containing the declaration of the common, reusable CSS classes.
*
* @param {Object} theme -The theme.
*
* @returns {Object} - The common styles.
*/
export const commonStyles = {
// '.empty-list'
[commonClassName.emptyList]: {
listStyleType: 'none',
margin: 0,
padding: 0
}
export const commonStyles = (theme: Object) => {
return {
// '.empty-list'
[commonClassName.emptyList]: {
listStyleType: 'none',
margin: 0,
padding: 0
},
[commonClassName.overflowMenuItem]: {
alignItems: 'center',
color: theme.palette.text01,
cursor: 'pointer',
display: 'flex',
fontSize: 14,
fontWeight: 400,
height: 40,
lineHeight: '24px',
padding: '8px 16px',
boxSizing: 'border-box',
'& > div': {
display: 'flex',
alignItems: 'center'
},
'&.unclickable': {
cursor: 'default'
},
'&.disabled': {
cursor: 'initial',
color: theme.palette.text03,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
}
},
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
background: theme.palette.action02Hover
},
'&.unclickable:hover': {
background: 'inherit'
}
}
},
[commonClassName.overflowMenuItemIcon]: {
marginRight: '16px',
'& i': {
display: 'inline',
fontSize: 24
},
'@media (hover: hover) and (pointer: fine)': {
'&i:hover': {
backgroundColor: 'initial'
}
},
'& img': {
maxWidth: 24,
maxHeight: 24
},
'& svg': {
fill: theme.palette.text01,
height: 20,
width: 20
}
},
[commonClassName.toolboxIcon]: {
display: 'flex',
borderRadius: 3,
flexDirection: 'column',
fontSize: 24,
height: 48,
justifyContent: 'center',
width: 48,
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
background: theme.palette.action02Hover
}
},
[theme.breakpoints.down('320')]: {
height: 36,
width: 36
},
'&.toggled': {
background: theme.palette.ui02
},
'&.disabled': {
cursor: 'initial !important',
backgroundColor: `${theme.palette.action02Disabled} !important`,
'& svg': {
fill: `${theme.palette.text03} !important`
}
}
},
[commonClassName.toolboxButton]: {
color: theme.palette.text01,
cursor: 'pointer',
display: 'inline-block',
lineHeight: '48px',
textAlign: 'center'
},
[commonClassName.toolboxContentItems]: {
background: theme.palette.ui01,
borderRadius: 6,
margin: '0 auto',
padding: 6,
textAlign: 'center',
pointerEvents: 'all',
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)',
'& > div': {
marginLeft: 8,
'&:first-child': {
marginLeft: 0
}
}
}
};
};
/**

View File

@@ -10,7 +10,7 @@ import { createColorTokens } from './utils';
* @param {Object} arg - The ui tokens.
* @returns {Object}
*/
export function createWebTheme({ font, colors, colorMap, shape, spacing, typography }: Object) {
export function createWebTheme({ font, colors, colorMap, shape, spacing, typography, breakpoints }: Object) {
return createMuiTheme({
props: {
// disable ripple effect on buttons globally
@@ -27,7 +27,8 @@ export function createWebTheme({ font, colors, colorMap, shape, spacing, typogra
typography: {
font,
...typography
}
},
breakpoints
});
}
@@ -46,3 +47,4 @@ export function formatCommonClasses(stylesObj: Object) {
return formatted;
}

View File

@@ -0,0 +1,7 @@
// @flow
/**
* The type of (redux) action to update the breakout room data.
*
*/
export const UPDATE_BREAKOUT_ROOMS = 'UPDATE_BREAKOUT_ROOMS';

View File

@@ -0,0 +1,236 @@
// @flow
import i18next from 'i18next';
import _ from 'lodash';
import type { Dispatch } from 'redux';
import {
conferenceLeft,
conferenceWillLeave,
createConference,
getCurrentConference
} from '../base/conference';
import { setAudioMuted, setVideoMuted } from '../base/media';
import { getRemoteParticipants } from '../base/participants';
import { clearNotifications } from '../notifications';
import {
getBreakoutRooms,
getMainRoom
} from './functions';
import logger from './logger';
declare var APP: Object;
/**
* Action to create a breakout room.
*
* @param {string} name - Name / subject for the breakout room.
* @returns {Function}
*/
export function createBreakoutRoom(name?: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const rooms = getBreakoutRooms(getState);
// TODO: remove this once we add UI to customize the name.
const index = Object.keys(rooms).length;
const subject = name || i18next.t('breakoutRooms.defaultName', { index });
// $FlowExpectedError
getCurrentConference(getState)?.getBreakoutRooms()
?.createBreakoutRoom(subject);
};
}
/**
* Action to close a room and send participants to the main room.
*
* @param {string} roomId - The id of the room to close.
* @returns {Function}
*/
export function closeBreakoutRoom(roomId: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const rooms = getBreakoutRooms(getState);
const room = rooms[roomId];
const mainRoom = getMainRoom(getState);
if (room && mainRoom) {
Object.values(room.participants).forEach(p => {
// $FlowExpectedError
dispatch(sendParticipantToRoom(p.jid, mainRoom.id));
});
}
};
}
/**
* Action to remove a breakout room.
*
* @param {string} breakoutRoomJid - The jid of the breakout room to remove.
* @returns {Function}
*/
export function removeBreakoutRoom(breakoutRoomJid: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
// $FlowExpectedError
getCurrentConference(getState)?.getBreakoutRooms()
?.removeBreakoutRoom(breakoutRoomJid);
};
}
/**
* Action to auto-assign the participants to breakout rooms.
*
* @returns {Function}
*/
export function autoAssignToBreakoutRooms() {
return (dispatch: Dispatch<any>, getState: Function) => {
const rooms = getBreakoutRooms(getState);
const breakoutRooms = _.filter(rooms, (room: Object) => !room.isMainRoom);
if (breakoutRooms) {
const participantIds = Array.from(getRemoteParticipants(getState).keys());
const length = Math.ceil(participantIds.length / breakoutRooms.length);
_.chunk(_.shuffle(participantIds), length).forEach((group, index) =>
group.forEach(participantId => {
dispatch(sendParticipantToRoom(participantId, breakoutRooms[index].id));
})
);
}
};
}
/**
* Action to send a participant to a room.
*
* @param {string} participantId - The participant id.
* @param {string} roomId - The room id.
* @returns {Function}
*/
export function sendParticipantToRoom(participantId: string, roomId: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
const rooms = getBreakoutRooms(getState);
const room = rooms[roomId];
if (!room) {
logger.warn(`Invalid room: ${roomId}`);
return;
}
// Get the full JID of the participant. We could be getting the endpoint ID or
// a participant JID. We want to find the connection JID.
const participantJid = _findParticipantJid(getState, participantId);
if (!participantJid) {
logger.warn(`Could not find participant ${participantId}`);
return;
}
// $FlowExpectedError
getCurrentConference(getState)?.getBreakoutRooms()
?.sendParticipantToRoom(participantJid, room.jid);
};
}
/**
* Action to move to a room.
*
* @param {string} roomId - The room id to move to. If omitted move to the main room.
* @returns {Function}
*/
export function moveToRoom(roomId?: string) {
return (dispatch: Dispatch<any>, getState: Function) => {
let _roomId = roomId || getMainRoom(getState)?.id;
// Check if we got a full JID.
// $FlowExpectedError
if (_roomId?.indexOf('@') !== -1) {
// $FlowExpectedError
const [ id, ...domainParts ] = _roomId.split('@');
// On mobile we first store the room and the connection is created
// later, so let's attach the domain to the room String object as
// a little hack.
// eslint-disable-next-line no-new-wrappers
_roomId = new String(id);
// $FlowExpectedError
_roomId.domain = domainParts.join('@');
}
if (navigator.product === 'ReactNative') {
const conference = getCurrentConference(getState);
const { audio, video } = getState()['features/base/media'];
dispatch(conferenceWillLeave(conference));
conference.leave()
.catch(error => {
logger.warn(
'JitsiConference.leave() rejected with:',
error);
dispatch(conferenceLeft(conference));
});
dispatch(clearNotifications());
// dispatch(setRoom(_roomId));
dispatch(createConference(_roomId));
dispatch(setAudioMuted(audio.muted));
dispatch(setVideoMuted(video.muted));
} else {
APP.conference.leaveRoom()
.finally(() => APP.conference.joinRoom(_roomId));
}
};
}
/**
* Finds a participant's connection JID given its ID.
*
* @param {Function} getState - The redux store state getter.
* @param {string} participantId - ID of the given participant.
* @returns {string|undefined} - The participant connection JID if found.
*/
function _findParticipantJid(getState: Function, participantId: string) {
const conference = getCurrentConference(getState);
if (!conference) {
return;
}
// Get the full JID of the participant. We could be getting the endpoint ID or
// a participant JID. We want to find the connection JID.
let _participantId = participantId;
let participantJid;
if (!participantId.includes('@')) {
const p = conference.getParticipantById(participantId);
// $FlowExpectedError
_participantId = p?.getJid(); // This will be the room JID.
}
if (_participantId) {
const rooms = getBreakoutRooms(getState);
for (const room of Object.values(rooms)) {
// $FlowExpectedError
const participants = room.participants || {};
const p = participants[_participantId]
// $FlowExpectedError
|| Object.values(participants).find(item => item.jid === _participantId);
if (p) {
participantJid = p.jid;
break;
}
}
}
return participantJid;
}

View File

@@ -0,0 +1,3 @@
// @flow
export * from './web';

View File

@@ -0,0 +1,3 @@
// @flow
export * from './_';

View File

@@ -0,0 +1,31 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { createBreakoutRoom } from '../../actions';
import styles from './styles';
const AddBreakoutRoomButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const onAdd = useCallback(() =>
dispatch(createBreakoutRoom())
, [ dispatch ]);
return (
<Button
accessibilityLabel = { t('breakoutRooms.actions.add') }
children = { t('breakoutRooms.actions.add') }
labelStyle = { styles.addButtonLabel }
mode = 'contained'
onPress = { onAdd }
style = { styles.addButton } />
);
};
export default AddBreakoutRoomButton;

View File

@@ -0,0 +1,31 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { autoAssignToBreakoutRooms } from '../../actions';
import styles from './styles';
const AutoAssignButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const onAutoAssign = useCallback(() => {
dispatch(autoAssignToBreakoutRooms());
}, [ dispatch ]);
return (
<Button
accessibilityLabel = { t('breakoutRooms.actions.autoAssign') }
children = { t('breakoutRooms.actions.autoAssign') }
labelStyle = { styles.autoAssignLabel }
mode = 'contained'
onPress = { onAutoAssign }
style = { styles.transparentButton } />
);
};
export default AutoAssignButton;

View File

@@ -0,0 +1,85 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableOpacity } from 'react-native';
import { Text } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux';
import { hideDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import {
Icon,
IconClose,
IconRingGroup
} from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants';
import styles from '../../../participants-pane/components/native/styles';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
type Props = {
/**
* The room for which the menu is open.
*/
room: Object
}
const BreakoutRoomContextMenu = ({ room }: Props) => {
const dispatch = useDispatch();
const closeDialog = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const isLocalModerator = useSelector(isLocalParticipantModerator);
const { t } = useTranslation();
const onJoinRoom = useCallback(() => {
dispatch(moveToRoom(room.jid));
closeDialog();
}, [ dispatch, room ]);
const onRemoveBreakoutRoom = useCallback(() => {
dispatch(removeBreakoutRoom(room.jid));
closeDialog();
}, [ dispatch, room ]);
const onCloseBreakoutRoom = useCallback(() => {
dispatch(closeBreakoutRoom(room.id));
closeDialog();
}, [ dispatch, room ]);
return (
<BottomSheet
addScrollViewPadding = { false }
onCancel = { closeDialog }
showSlidingView = { true }>
<TouchableOpacity
onPress = { onJoinRoom }
style = { styles.contextMenuItem }>
<Icon
size = { 24 }
src = { IconRingGroup } />
<Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.join')}</Text>
</TouchableOpacity>
{!room?.isMainRoom && isLocalModerator
&& !(room?.participants && Object.keys(room.participants).length > 0)
? <TouchableOpacity
onPress = { onRemoveBreakoutRoom }
style = { styles.contextMenuItem }>
<Icon
size = { 24 }
src = { IconClose } />
<Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.remove')}</Text>
</TouchableOpacity>
: <TouchableOpacity
onPress = { onCloseBreakoutRoom }
style = { styles.contextMenuItem }>
<Icon
size = { 24 }
src = { IconClose } />
<Text style = { styles.contextMenuItemText }>{t('breakoutRooms.actions.close')}</Text>
</TouchableOpacity>
}
</BottomSheet>
);
};
export default BreakoutRoomContextMenu;

View File

@@ -0,0 +1,25 @@
// @flow
import React from 'react';
import { isParticipantModerator } from '../../../base/participants';
import ParticipantItem from '../../../participants-pane/components/native/ParticipantItem';
type Props = {
/**
* Participant to be displayed.
*/
item: Object
}
const BreakoutRoomParticipantItem = ({ item }: Props) => (
<ParticipantItem
displayName = { item.displayName }
isKnockingParticipant = { false }
isModerator = { isParticipantModerator(item) }
key = { item.jid }
participantID = { item.jid } />
);
export default BreakoutRoomParticipantItem;

View File

@@ -0,0 +1,72 @@
// @flow
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FlatList, Text, TouchableOpacity, View } from 'react-native';
import { useDispatch } from 'react-redux';
import { openDialog } from '../../../base/dialog';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import BreakoutRoomContextMenu from './BreakoutRoomContextMenu';
import BreakoutRoomParticipantItem from './BreakoutRoomParticipantItem';
import styles from './styles';
type Props = {
/**
* Room to display.
*/
room: Object
}
/**
* Returns a key for a passed item of the list.
*
* @param {Object} item - The participant.
* @returns {string} - The user ID.
*/
function _keyExtractor(item: Object) {
return item.jid;
}
export const CollapsibleRoom = ({ room }: Props) => {
const dispatch = useDispatch();
const [ collapsed, setCollapsed ] = useState(false);
const { t } = useTranslation();
const _toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);
const _openContextMenu = useCallback(() => {
dispatch(openDialog(BreakoutRoomContextMenu, { room }));
}, [ room ]);
return (
<View>
<TouchableOpacity
onLongPress = { _openContextMenu }
onPress = { _toggleCollapsed }
style = { styles.collapsibleRoom }>
<TouchableOpacity
onPress = { _toggleCollapsed }
style = { styles.arrowIcon }>
<Icon
size = { 18 }
src = { collapsed ? IconArrowDown : IconArrowUp } />
</TouchableOpacity>
<Text style = { styles.roomName }>
{`${room.name || t('breakoutRooms.mainRoom')} (${Object.values(room.participants || {}).length})`}
</Text>
</TouchableOpacity>
{!collapsed && <FlatList
bounces = { false }
data = { Object.values(room.participants || {}) }
horizontal = { false }
keyExtractor = { _keyExtractor }
renderItem = { BreakoutRoomParticipantItem }
showsHorizontalScrollIndicator = { false }
windowSize = { 2 } />}
</View>
);
};

View File

@@ -0,0 +1,31 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from 'react-native-paper';
import { useDispatch } from 'react-redux';
import { moveToRoom } from '../../actions';
import styles from './styles';
const LeaveBreakoutRoomButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const onLeave = useCallback(() =>
dispatch(moveToRoom())
, [ dispatch ]);
return (
<Button
accessibilityLabel = { t('breakoutRooms.actions.leaveBreakoutRoom') }
children = { t('breakoutRooms.actions.leaveBreakoutRoom') }
labelStyle = { styles.leaveButtonLabel }
mode = 'contained'
onPress = { onLeave }
style = { styles.transparentButton } />
);
};
export default LeaveBreakoutRoomButton;

View File

@@ -0,0 +1,5 @@
// @flow
export { default as AddBreakoutRoomButton } from './AddBreakoutRoomButton';
export { default as AutoAssignButton } from './AutoAssignButton';
export { default as LeaveBreakoutRoomButton } from './LeaveBreakoutRoomButton';

View File

@@ -0,0 +1,69 @@
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
const baseButton = {
height: BaseTheme.spacing[6],
marginTop: BaseTheme.spacing[2],
marginLeft: BaseTheme.spacing[3],
marginRight: BaseTheme.spacing[3]
};
const baseLabel = {
fontSize: 15,
lineHeight: 24,
textTransform: 'capitalize'
};
/**
* The styles of the native components of the feature {@code breakout rooms}.
*/
export default {
addButtonLabel: {
...baseLabel,
color: BaseTheme.palette.text01
},
addButton: {
...baseButton,
backgroundColor: BaseTheme.palette.ui03
},
collapsibleRoom: {
...baseButton,
display: 'flex',
flexDirection: 'row',
alignItems: 'center'
},
arrowIcon: {
backgroundColor: BaseTheme.palette.ui03,
height: BaseTheme.spacing[5],
width: BaseTheme.spacing[5],
borderRadius: 6,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
},
roomName: {
fontSize: 15,
color: BaseTheme.palette.text01,
fontWeight: 'bold',
marginLeft: BaseTheme.spacing[2]
},
transparentButton: {
...baseButton,
backgroundColor: 'transparent'
},
leaveButtonLabel: {
...baseLabel,
color: BaseTheme.palette.textError
},
autoAssignLabel: {
...baseLabel,
color: BaseTheme.palette.link01
}
};

View File

@@ -0,0 +1,36 @@
// @flow
import { makeStyles } from '@material-ui/core/styles';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { createBreakoutRoom } from '../../actions';
const useStyles = makeStyles(() => {
return {
button: {
width: '100%'
}
};
});
export const AddBreakoutRoomButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const styles = useStyles();
const onAdd = useCallback(() =>
dispatch(createBreakoutRoom())
, [ dispatch ]);
return (
<ParticipantPaneBaseButton
accessibilityLabel = { t('breakoutRooms.actions.add') }
className = { styles.button }
onClick = { onAdd }>
{t('breakoutRooms.actions.add')}
</ParticipantPaneBaseButton>
);
};

View File

@@ -0,0 +1,42 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { autoAssignToBreakoutRooms } from '../../actions';
const useStyles = makeStyles(theme => {
return {
button: {
color: theme.palette.link01,
width: '100%',
backgroundColor: 'transparent',
'&:hover': {
backgroundColor: 'transparent'
}
}
};
});
export const AutoAssignButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const styles = useStyles();
const onAutoAssign = useCallback(() => {
dispatch(autoAssignToBreakoutRooms());
}, [ dispatch ]);
return (
<ParticipantPaneBaseButton
accessibilityLabel = { t('breakoutRooms.actions.autoAssign') }
className = { styles.button }
onClick = { onAutoAssign }>
{t('breakoutRooms.actions.autoAssign')}
</ParticipantPaneBaseButton>
);
};

View File

@@ -0,0 +1,127 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ListItem } from '../../../base/components';
import { Icon, IconArrowDown, IconArrowUp } from '../../../base/icons';
import ParticipantItem from '../../../participants-pane/components/web/ParticipantItem';
import { ACTION_TRIGGER } from '../../../participants-pane/constants';
type Props = {
/**
* Type of trigger for the breakout room actions.
*/
actionsTrigger?: string,
/**
* React children.
*/
children: React$Node,
/**
* Is this item highlighted/raised.
*/
isHighlighted?: boolean,
/**
* Callback to raise menu. Used to raise menu on mobile long press.
*/
onRaiseMenu: Function,
/**
* Callback for when the mouse leaves this component.
*/
onLeave?: Function,
/**
* Room reference.
*/
room: Object,
}
const useStyles = makeStyles(theme => {
return {
container: {
boxShadow: 'none'
},
roomName: {
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
...theme.typography.labelButton,
lineHeight: `${theme.typography.labelButton.lineHeight}px`,
padding: '12px 0'
},
arrowContainer: {
backgroundColor: theme.palette.ui03,
width: '24px',
height: '24px',
borderRadius: '6px',
marginRight: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}
};
});
export const CollapsibleRoom = ({
actionsTrigger = ACTION_TRIGGER.HOVER,
children,
isHighlighted,
onRaiseMenu,
onLeave,
room
}: Props) => {
const { t } = useTranslation();
const styles = useStyles();
const [ collapsed, setCollapsed ] = useState(false);
const toggleCollapsed = useCallback(() => {
setCollapsed(!collapsed);
}, [ collapsed ]);
const raiseMenu = useCallback(target => {
onRaiseMenu(target);
}, [ onRaiseMenu ]);
const arrow = (<div className = { styles.arrowContainer }>
<Icon
size = { 14 }
src = { collapsed ? IconArrowDown : IconArrowUp } />
</div>);
const roomName = (<span className = { styles.roomName }>
{`${room.name || t('breakoutRooms.mainRoom')} (${Object.keys(room?.participants
|| {}).length})`}
</span>);
return (
<>
<ListItem
actions = { children }
className = { clsx(styles.container, 'breakout-room-container') }
icon = { arrow }
isHighlighted = { isHighlighted }
onClick = { toggleCollapsed }
onLongPress = { raiseMenu }
onMouseLeave = { onLeave }
testId = { room.id }
textChildren = { roomName }
trigger = { actionsTrigger } />
{!collapsed && room?.participants
&& Object.values(room?.participants || {}).map((p: Object) => (
<ParticipantItem
displayName = { p.displayName }
key = { p.jid }
local = { false }
participantID = { p.jid } />
))
}
</>
);
};

View File

@@ -0,0 +1,47 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { QuickActionButton } from '../../../base/components';
import { moveToRoom } from '../../actions';
type Props = {
/**
* The room to join.
*/
room: Object
}
const useStyles = makeStyles(theme => {
return {
button: {
marginRight: `${theme.spacing(2)}px`
}
};
});
const JoinActionButton = ({ room }: Props) => {
const styles = useStyles();
const { t } = useTranslation();
const dispatch = useDispatch();
const onJoinRoom = useCallback(e => {
e.stopPropagation();
dispatch(moveToRoom(room.jid));
}, [ dispatch, room ]);
return (<QuickActionButton
accessibilityLabel = { t('breakoutRooms.actions.join') }
className = { styles.button }
onClick = { onJoinRoom }
testId = { `join-room-${room.id}` }>
{t('breakoutRooms.actions.join')}
</QuickActionButton>
);
};
export default JoinActionButton;

View File

@@ -0,0 +1,42 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import ParticipantPaneBaseButton from '../../../participants-pane/components/web/ParticipantPaneBaseButton';
import { moveToRoom } from '../../actions';
const useStyles = makeStyles(theme => {
return {
button: {
color: theme.palette.textError,
backgroundColor: 'transparent',
width: '100%',
'&:hover': {
backgroundColor: 'transparent'
}
}
};
});
export const LeaveButton = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const styles = useStyles();
const onLeave = useCallback(() => {
dispatch(moveToRoom());
}, [ dispatch ]);
return (
<ParticipantPaneBaseButton
accessibilityLabel = { t('breakoutRooms.actions.leaveBreakoutRoom') }
className = { styles.button }
onClick = { onLeave }>
{t('breakoutRooms.actions.leaveBreakoutRoom')}
</ParticipantPaneBaseButton>
);
};

View File

@@ -0,0 +1,40 @@
// @flow
import { makeStyles } from '@material-ui/styles';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { QuickActionButton } from '../../../base/components';
import { Icon, IconHorizontalPoints } from '../../../base/icons';
type Props = {
/**
* Click handler function.
*/
onClick: Function
}
const useStyles = makeStyles(() => {
return {
button: {
padding: '6px'
}
};
});
const RoomActionEllipsis = ({ onClick }: Props) => {
const styles = useStyles();
const { t } = useTranslation();
return (
<QuickActionButton
accessibilityLabel = { t('breakoutRooms.actions.more') }
className = { styles.button }
onClick = { onClick }>
<Icon src = { IconHorizontalPoints } />
</QuickActionButton>
);
};
export default RoomActionEllipsis;

View File

@@ -0,0 +1,100 @@
// @flow
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { ContextMenu, ContextMenuItemGroup } from '../../../base/components';
import {
IconClose,
IconRingGroup
} from '../../../base/icons';
import { isLocalParticipantModerator } from '../../../base/participants';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { closeBreakoutRoom, moveToRoom, removeBreakoutRoom } from '../../actions';
type Props = {
/**
* Room reference.
*/
entity: Object,
/**
* Target elements against which positioning calculations are made.
*/
offsetTarget: HTMLElement,
/**
* Callback for the mouse entering the component.
*/
onEnter: Function,
/**
* Callback for the mouse leaving the component.
*/
onLeave: Function,
/**
* Callback for making a selection in the menu.
*/
onSelect: Function
};
export const RoomContextMenu = ({
entity: room,
offsetTarget,
onEnter,
onLeave,
onSelect
}: Props) => {
const dispatch = useDispatch();
const { t } = useTranslation();
const isLocalModerator = useSelector(isLocalParticipantModerator);
const _overflowDrawer = useSelector(showOverflowDrawer);
const onJoinRoom = useCallback(() => {
dispatch(moveToRoom(room.id));
}, [ dispatch, room ]);
const onRemoveBreakoutRoom = useCallback(() => {
dispatch(removeBreakoutRoom(room.jid));
}, [ dispatch, room ]);
const onCloseBreakoutRoom = useCallback(() => {
dispatch(closeBreakoutRoom(room.id));
}, [ dispatch, room ]);
const isRoomEmpty = !(room?.participants && Object.keys(room.participants).length > 0);
const actions = [
_overflowDrawer ? {
accessibilityLabel: t('breakoutRooms.actions.join'),
icon: IconRingGroup,
onClick: onJoinRoom,
text: t('breakoutRooms.actions.join')
} : null,
!room?.isMainRoom && isLocalModerator ? {
accessibilityLabel: isRoomEmpty ? t('breakoutRooms.actions.remove') : t('breakoutRooms.actions.close'),
icon: IconClose,
id: isRoomEmpty ? `remove-room-${room?.id}` : `close-room-${room?.id}`,
onClick: isRoomEmpty ? onRemoveBreakoutRoom : onCloseBreakoutRoom,
text: isRoomEmpty ? t('breakoutRooms.actions.remove') : t('breakoutRooms.actions.close')
} : null
].filter(Boolean);
const lowerMenu = useCallback(() => onSelect(true));
return (
<ContextMenu
entity = { room }
isDrawerOpen = { room }
offsetTarget = { offsetTarget }
onClick = { lowerMenu }
onDrawerClose = { onSelect }
onMouseEnter = { onEnter }
onMouseLeave = { onLeave }>
<ContextMenuItemGroup actions = { actions } />
</ContextMenu>
);
};

View File

@@ -0,0 +1,63 @@
// @flow
import React, { useCallback } from 'react';
import { useSelector } from 'react-redux';
import useContextMenu from '../../../base/components/context-menu/useContextMenu';
import { getParticipantCount, isLocalParticipantModerator } from '../../../base/participants';
import { equals } from '../../../base/redux';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { getBreakoutRooms, isInBreakoutRoom, getCurrentRoomId } from '../../functions';
import { AutoAssignButton } from './AutoAssignButton';
import { CollapsibleRoom } from './CollapsibleRoom';
import JoinActionButton from './JoinQuickActionButton';
import { LeaveButton } from './LeaveButton';
import RoomActionEllipsis from './RoomActionEllipsis';
import { RoomContextMenu } from './RoomContextMenu';
export const RoomList = () => {
const currentRoomId = useSelector(getCurrentRoomId);
const rooms = Object.values(useSelector(getBreakoutRooms, equals))
.filter((room: Object) => room.id !== currentRoomId)
.sort((p1: Object, p2: Object) => (p1?.name || '').localeCompare(p2?.name || ''));
const inBreakoutRoom = useSelector(isInBreakoutRoom);
const isLocalModerator = useSelector(isLocalParticipantModerator);
const participantsCount = useSelector(getParticipantCount);
const _overflowDrawer = useSelector(showOverflowDrawer);
const [ lowerMenu, raiseMenu, toggleMenu, menuEnter, menuLeave, raiseContext ] = useContextMenu();
const onRaiseMenu = useCallback(room => target => raiseMenu(room, target), [ raiseMenu ]);
return (
<>
{inBreakoutRoom && <LeaveButton />}
{!inBreakoutRoom
&& isLocalModerator
&& participantsCount > 2
&& rooms.length > 1
&& <AutoAssignButton />}
<div id = 'breakout-rooms-list'>
{rooms.map((room: Object) => (
<React.Fragment key = { room.id }>
<CollapsibleRoom
isHighlighted = { raiseContext.entity === room }
onLeave = { lowerMenu }
onRaiseMenu = { onRaiseMenu(room) }
room = { room }>
{!_overflowDrawer && <>
<JoinActionButton room = { room } />
{isLocalModerator && <RoomActionEllipsis onClick = { toggleMenu(room) } />}
</>}
</CollapsibleRoom>
</React.Fragment>
))}
</div>
<RoomContextMenu
onEnter = { menuEnter }
onLeave = { menuLeave }
onSelect = { lowerMenu }
{ ...raiseContext } />
</>
);
};

View File

@@ -0,0 +1,4 @@
// @flow
export * from './LeaveButton';
export * from './RoomList';

View File

@@ -0,0 +1,22 @@
// @flow
/**
* Key for this feature.
*/
export const FEATURE_KEY = 'features/breakout-rooms';
/**
* The type of json-message which indicates that json carries
* a request for a participant to move to a specified room.
*/
export const JSON_TYPE_MOVE_TO_ROOM_REQUEST = `${FEATURE_KEY}/move-to-room`;
/**
* The type of json-message which indicates that json carries a request to remove a specified breakout room.
*/
export const JSON_TYPE_REMOVE_BREAKOUT_ROOM = `${FEATURE_KEY}/remove`;
/**
* The type of json-message which indicates that json carries breakout rooms data.
*/
export const JSON_TYPE_UPDATE_BREAKOUT_ROOMS = `${FEATURE_KEY}/update`;

View File

@@ -0,0 +1,59 @@
// @flow
import _ from 'lodash';
import { getCurrentConference } from '../base/conference';
import { toState } from '../base/redux';
import { FEATURE_KEY } from './constants';
/**
* Returns the rooms object for breakout rooms.
*
* @param {Function|Object} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object} Object of rooms.
*/
export const getBreakoutRooms = (stateful: Function | Object) => toState(stateful)[FEATURE_KEY].rooms;
/**
* Returns the main room.
*
* @param {Function|Object} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {Object|undefined} The main room object, or undefined.
*/
export const getMainRoom = (stateful: Function | Object) => {
const rooms = getBreakoutRooms(stateful);
return _.find(rooms, (room: Object) => room.isMainRoom);
};
/**
* Returns the id of the current room.
*
* @param {Function|Object} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {string} Room id or undefined.
*/
export const getCurrentRoomId = (stateful: Function | Object) => {
const conference = getCurrentConference(stateful);
// $FlowExpectedError
return conference?.getName();
};
/**
* Determines whether the local participant is in a breakout room.
*
* @param {Function|Object} stateful - The redux store, the redux
* {@code getState} function, or the redux state itself.
* @returns {boolean}
*/
export const isInBreakoutRoom = (stateful: Function | Object) => {
const conference = getCurrentConference(stateful);
// $FlowExpectedError
return conference?.getBreakoutRooms()
?.isBreakoutRoom();
};

View File

@@ -0,0 +1,7 @@
// @flow
import { getLogger } from '../base/logging/functions';
import { FEATURE_KEY } from './constants';
export default getLogger(FEATURE_KEY);

View File

@@ -0,0 +1,31 @@
// @flow
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { StateListenerRegistry } from '../base/redux';
import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
import { moveToRoom } from './actions';
import logger from './logger';
/**
* Registers a change handler for state['features/base/conference'].conference to
* set the event listeners needed for the breakout rooms feature to operate.
*/
StateListenerRegistry.register(
state => state['features/base/conference'].conference,
(conference, { dispatch }, previousConference) => {
if (conference && !previousConference) {
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_MOVE_TO_ROOM, roomId => {
logger.debug(`Moving to room: ${roomId}`);
dispatch(moveToRoom(roomId));
});
conference.on(JitsiConferenceEvents.BREAKOUT_ROOMS_UPDATED, rooms => {
logger.debug('Room list updated');
dispatch({
type: UPDATE_BREAKOUT_ROOMS,
rooms
});
});
}
});

View File

@@ -0,0 +1,25 @@
// @flow
import { ReducerRegistry } from '../base/redux';
import { UPDATE_BREAKOUT_ROOMS } from './actionTypes';
import { FEATURE_KEY } from './constants';
/**
* Listen for actions for the breakout-rooms feature.
*/
ReducerRegistry.register(FEATURE_KEY, (state = { rooms: {} }, action) => {
switch (action.type) {
case UPDATE_BREAKOUT_ROOMS: {
const { nextIndex, rooms } = action;
return {
...state,
nextIndex,
rooms
};
}
}
return state;
});

View File

@@ -75,10 +75,16 @@ class CalendarList extends AbstractPage<Props> {
return (
CalendarListContent
? <CalendarListContent
disabled = { disabled }
listEmptyComponent
= { this._getRenderListEmptyComponent() } />
? <View
style = {
disabled
? styles.calendarSyncDisabled
: styles.calendarSync }>
<CalendarListContent
disabled = { disabled }
listEmptyComponent
= { this._getRenderListEmptyComponent() } />
</View>
: null
);
}

View File

@@ -1,4 +1,5 @@
import { ColorPalette, createStyleSheet } from '../../base/styles';
import BaseTheme from '../../base/ui/components/BaseTheme';
const NOTIFICATION_SIZE = 55;
@@ -163,5 +164,18 @@ export default createStyleSheet({
*/
touchableView: {
flexDirection: 'row'
},
calendarSync: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1,
overflow: 'hidden'
},
calendarSyncDisabled: {
backgroundColor: BaseTheme.palette.uiBackground,
flex: 1,
opacity: 0.8,
overflow: 'hidden'
}
});

View File

@@ -1,4 +1,3 @@
// @flow
export * from './native';
export { default as PrivateMessageButton } from './PrivateMessageButton';

View File

@@ -1,5 +1,3 @@
// @flow
export * from './web';
export { default as PrivateMessageButton } from './PrivateMessageButton';

View File

@@ -6,7 +6,6 @@ import React, { useEffect } from 'react';
import { translate } from '../../../base/i18n';
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import { connect } from '../../../base/redux';
import { screen } from '../../../conference/components/native/routes';
import { closeChat, openChat } from '../../actions.native';
import AbstractChat, {
_mapStateToProps,
@@ -71,7 +70,8 @@ export default translate(connect(_mapStateToProps)(props => {
_nbUnreadMessages,
dispatch,
navigation,
route
route,
t
} = props;
const isChatScreenFocused = useIsFocused();
const privateMessageRecipient = route.params?.privateMessageRecipient;
@@ -84,7 +84,7 @@ export default translate(connect(_mapStateToProps)(props => {
dispatch(openChat(privateMessageRecipient));
navigation.setOptions({
tabBarLabel: `${screen.conference.chatandpolls.tab.chat} ${nrUnreadMessages}`
tabBarLabel: `${t('chat.tabs.chat')} ${nrUnreadMessages}`
});
return () => dispatch(closeChat());

View File

@@ -12,8 +12,8 @@ import { type StyleType } from '../../../base/styles';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import { replaceNonUnicodeEmojis } from '../../functions';
import AbstractChatMessage, { type Props as AbstractProps } from '../AbstractChatMessage';
import PrivateMessageButton from '../PrivateMessageButton';
import PrivateMessageButton from './PrivateMessageButton';
import styles from './styles';
type Props = AbstractProps & {

View File

@@ -6,7 +6,6 @@ import { FlatList } from 'react-native';
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
import ChatMessage from './ChatMessage';
import styles from './styles';
type Props = {
@@ -43,8 +42,7 @@ export default class ChatMessageGroup extends Component<Props> {
data = { this.props.messages }
inverted = { true }
keyExtractor = { this._keyExtractor }
renderItem = { this._renderMessage }
style = { styles.messageContainer } />
renderItem = { this._renderMessage } />
);
}

View File

@@ -54,15 +54,15 @@ class MessageContainer extends AbstractMessageContainer<Props> {
return (
<FlatList
ListEmptyComponent = { this._renderListEmptyComponent }
bounces = { false }
data = { data }
// Workaround for RN bug:
// https://github.com/facebook/react-native/issues/21196
inverted = { Boolean(data.length) }
keyExtractor = { this._keyExtractor }
keyboardShouldPersistTaps = 'always'
renderItem = { this._renderMessageGroup }
style = { styles.messageContainer } />
keyboardShouldPersistTaps = 'handled'
renderItem = { this._renderMessageGroup } />
);
}

View File

@@ -1,13 +1,13 @@
// @flow
import { CHAT_ENABLED, getFeatureFlag } from '../../base/flags';
import { translate } from '../../base/i18n';
import { IconMessage, IconReply } from '../../base/icons';
import { getParticipantById } from '../../base/participants';
import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { navigate } from '../../conference/components/native/ConferenceNavigationContainerRef';
import { screen } from '../../conference/components/native/routes';
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconMessage, IconReply } from '../../../base/icons';
import { getParticipantById } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { navigate } from '../../../conference/components/native/ConferenceNavigationContainerRef';
import { screen } from '../../../conference/components/native/routes';
export type Props = AbstractButtonProps & {
@@ -26,11 +26,6 @@ export type Props = AbstractButtonProps & {
*/
t: Function,
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* True if the polls feature is disabled.
*/

View File

@@ -71,10 +71,6 @@ export default {
flexDirection: 'row'
},
messageContainer: {
flex: 1
},
/**
* Wrapper View for the entire block.
*/

View File

@@ -3,14 +3,12 @@
import React from 'react';
import { toArray } from 'react-emoji-render';
import { translate } from '../../../base/i18n';
import { Linkify } from '../../../base/react';
import { MESSAGE_TYPE_LOCAL } from '../../constants';
import AbstractChatMessage, {
type Props
} from '../AbstractChatMessage';
import PrivateMessageButton from '../PrivateMessageButton';
import AbstractChatMessage, { type Props } from '../AbstractChatMessage';
import PrivateMessageButton from './PrivateMessageButton';
/**
* Renders a single chat message.

View File

@@ -0,0 +1,90 @@
// @flow
import { CHAT_ENABLED, getFeatureFlag } from '../../../base/flags';
import { translate } from '../../../base/i18n';
import { IconMessage, IconReply } from '../../../base/icons';
import { getParticipantById } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { openChat } from '../../actions';
export type Props = AbstractButtonProps & {
/**
* The ID of the participant that the message is to be sent.
*/
participantID: string,
/**
* True if the button is rendered as a reply button.
*/
reply: boolean,
/**
* Function to be used to translate i18n labels.
*/
t: Function,
/**
* The Redux dispatch function.
*/
dispatch: Function,
/**
* The participant object retrieved from Redux.
*/
_participant: Object,
};
/**
* Class to render a button that initiates the sending of a private message through chet.
*/
class PrivateMessageButton extends AbstractButton<Props, any> {
accessibilityLabel = 'toolbar.accessibilityLabel.privateMessage';
icon = IconMessage;
label = 'toolbar.privateMessage';
toggledIcon = IconReply;
/**
* Handles clicking / pressing the button, and kicks the participant.
*
* @private
* @returns {void}
*/
_handleClick() {
const { _participant, dispatch } = this.props;
dispatch(openChat(_participant));
}
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if this button is toggled or not.
*
* @protected
* @returns {boolean}
*/
_isToggled() {
return this.props.reply;
}
}
/**
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
export function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
const enabled = getFeatureFlag(state, CHAT_ENABLED, true);
const { visible = enabled } = ownProps;
return {
_participant: getParticipantById(state, ownProps.participantID),
visible
};
}
export default translate(connect(_mapStateToProps)(PrivateMessageButton));

View File

@@ -3,6 +3,7 @@
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useSelector } from 'react-redux';
@@ -23,6 +24,7 @@ import {
conferenceScreenOptions,
inviteScreenOptions,
lobbyScreenOptions,
navigationContainerTheme,
participantsScreenOptions,
sharedDocumentScreenOptions
} from './ConferenceNavigatorScreenOptions';
@@ -30,6 +32,7 @@ import { screen } from './routes';
const ConferenceStack = createStackNavigator();
const ConferenceNavigationContainer = () => {
const isPollsDisabled = useSelector(getDisablePolls);
const ChatScreen
@@ -40,56 +43,52 @@ const ConferenceNavigationContainer = () => {
= isPollsDisabled
? screen.conference.chat
: screen.conference.chatandpolls.main;
const { t } = useTranslation();
return (
<SafeAreaProvider>
<NavigationContainer
independent = { true }
ref = { conferenceNavigationRef }
theme = {{
colors: {
background: '#fff'
}
}}>
theme = { navigationContainerTheme }>
<ConferenceStack.Navigator
initialRouteName = { screen.conference.main }
mode = 'modal'>
<ConferenceStack.Screen
component = { Conference }
name = { screen.conference.main }
options = {{
...conferenceScreenOptions
}} />
options = { conferenceScreenOptions } />
<ConferenceStack.Screen
/* eslint-disable-next-line react/jsx-no-bind */
component = { ChatScreen }
name = { chatScreenName }
options = {{
...chatScreenOptions
...chatScreenOptions,
title: t('chat.title')
}} />
<ConferenceStack.Screen
component = { ParticipantsPane }
name = { screen.conference.participants }
options = {{
...participantsScreenOptions
...participantsScreenOptions,
title: t('participantsPane.header')
}} />
<ConferenceStack.Screen
component = { LobbyScreen }
name = { screen.lobby }
options = {{
...lobbyScreenOptions
}} />
options = { lobbyScreenOptions } />
<ConferenceStack.Screen
component = { AddPeopleDialog }
name = { screen.conference.invite }
options = {{
...inviteScreenOptions
...inviteScreenOptions,
title: t('addPeople.add')
}} />
<ConferenceStack.Screen
component = { SharedDocument }
name = { screen.conference.sharedDocument }
options = {{
...sharedDocumentScreenOptions
...sharedDocumentScreenOptions,
title: t('documentSharing.title')
}} />
</ConferenceStack.Navigator>
</NavigationContainer>

View File

@@ -1,13 +1,31 @@
// @flow
/* eslint-disable react/no-multi-comp */
import { TransitionPresets } from '@react-navigation/stack';
import React from 'react';
import { Platform } from 'react-native';
import { IconClose } from '../../../base/icons';
import {
Icon,
IconClose,
IconHelp,
IconHome,
IconInfo,
IconSettings
} from '../../../base/icons';
import BaseTheme from '../../../base/ui/components/BaseTheme';
import { goBack } from './ConferenceNavigationContainerRef';
import HeaderNavigationButton from './HeaderNavigationButton';
/**
* Navigation container theme.
*/
export const navigationContainerTheme = {
colors: {
background: BaseTheme.palette.ui12
}
};
/**
* Default modal transition for the current platform.
@@ -20,24 +38,126 @@ export const conferenceModalPresentation = Platform.select({
/**
* Screen options and transition types.
*/
export const screenOptions = {
export const fullScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: false,
headerShown: false
};
/**
* Dial-IN Info screen options and transition types.
*/
export const dialInSummaryScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: true,
headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header
},
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Drawer navigator screens options and transition types.
*/
export const drawerNavigatorScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: false
};
/**
* Drawer screen options and transition types.
*/
export const drawerScreenOptions = {
...TransitionPresets.ModalTransition,
gestureEnabled: true,
headerShown: true,
headerStyle: {
backgroundColor: BaseTheme.palette.screen01Header
}
};
/**
* Screen options for welcome page.
*/
export const welcomeScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconHome } />
),
headerTitleStyle: {
color: BaseTheme.palette.screen01Header
}
};
/**
* Screen options for settings screen.
*/
export const settingsScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconSettings } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for terms/privacy screens.
*/
export const termsAndPrivacyScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconInfo } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for help screen.
*/
export const helpScreenOptions = {
...drawerScreenOptions,
drawerIcon: ({ focused }) => (
<Icon
color = { focused ? BaseTheme.palette.screen01Header : BaseTheme.palette.field01Disabled }
size = { 20 }
src = { IconHelp } />
),
headerTitleStyle: {
color: BaseTheme.palette.text01
}
};
/**
* Screen options for conference.
*/
export const conferenceScreenOptions = {
...screenOptions
...fullScreenOptions
};
/**
* Screen options for lobby modal.
*/
export const lobbyScreenOptions = {
...screenOptions
...fullScreenOptions
};
/**

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