mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-01 19:07:46 +00:00
Compare commits
38 Commits
5545
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ef97f2708 | ||
|
|
10d54498d0 | ||
|
|
cd166b5f19 | ||
|
|
b5faf9f62a | ||
|
|
d98ea3ca24 | ||
|
|
d2b7151f23 | ||
|
|
7fa72db384 | ||
|
|
d56b282c5d | ||
|
|
72e1dcb877 | ||
|
|
98b05a2529 | ||
|
|
2d0a70f2ab | ||
|
|
6243836b3e | ||
|
|
6520cc2496 | ||
|
|
61684b1071 | ||
|
|
e42db3c9c2 | ||
|
|
6f75226044 | ||
|
|
7e9d665697 | ||
|
|
9aa2bbe115 | ||
|
|
31e4a9c1a0 | ||
|
|
1106e71f03 | ||
|
|
d84ba85f58 | ||
|
|
1e706db114 | ||
|
|
c82cead5ae | ||
|
|
39d1ccff85 | ||
|
|
2e69ec71c5 | ||
|
|
be6adad61b | ||
|
|
23b8dd4860 | ||
|
|
c74bdf137c | ||
|
|
8ebca64af1 | ||
|
|
61025edb74 | ||
|
|
4e2fea1e12 | ||
|
|
f5cdd5fca1 | ||
|
|
eec8b9e58e | ||
|
|
a96a4ea336 | ||
|
|
876773ca2a | ||
|
|
bc6070b98d | ||
|
|
9ca0ee41b6 | ||
|
|
957bd8916a |
@@ -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
|
||||
|
||||
12
Makefile
12
Makefile
@@ -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:
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
|
||||
81
config.js
81
config.js
@@ -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: '',
|
||||
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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'));
|
||||
|
||||
38
modules/API/external/external_api.js
vendored
38
modules/API/external/external_api.js
vendored
@@ -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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
350
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
4
react/features/base/components/index.js
Normal file
4
react/features/base/components/index.js
Normal 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';
|
||||
@@ -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>
|
||||
)}
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
4
react/features/base/icons/svg/home.svg
Normal file
4
react/features/base/icons/svg/home.svg
Normal 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 |
3
react/features/base/icons/svg/icon-ring-group.svg
Normal file
3
react/features/base/icons/svg/icon-ring-group.svg
Normal 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 |
4
react/features/base/icons/svg/icons8-home.svg
Normal file
4
react/features/base/icons/svg/icons8-home.svg
Normal 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 |
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
28
react/features/base/modal/components/JitsiScreenWebView.js
Normal file
28
react/features/base/modal/components/JitsiScreenWebView.js
Normal 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;
|
||||
79
react/features/base/modal/components/JitsiStatusBar.js
Normal file
79
react/features/base/modal/components/JitsiStatusBar.js
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as JitsiModal } from './JitsiModal';
|
||||
@@ -1,6 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
export const JitsiModal = Component;
|
||||
|
||||
@@ -2,4 +2,3 @@
|
||||
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
|
||||
@@ -117,7 +117,8 @@ const SECTION_LIST_STYLES = {
|
||||
* The top level container style of the list.
|
||||
*/
|
||||
container: {
|
||||
flex: 1
|
||||
flex: 1,
|
||||
width: '100%'
|
||||
},
|
||||
|
||||
list: {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ import { formatCommonClasses } from '../functions';
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
'@global': {
|
||||
...formatCommonClasses(commonStyles),
|
||||
...formatCommonClasses(commonStyles(theme)),
|
||||
...getGlobalStyles(theme)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
7
react/features/breakout-rooms/actionTypes.js
Normal file
7
react/features/breakout-rooms/actionTypes.js
Normal 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';
|
||||
236
react/features/breakout-rooms/actions.js
Normal file
236
react/features/breakout-rooms/actions.js
Normal 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;
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
3
react/features/breakout-rooms/components/_.web.js
Normal file
3
react/features/breakout-rooms/components/_.web.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
3
react/features/breakout-rooms/components/index.js
Normal file
3
react/features/breakout-rooms/components/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './_';
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
5
react/features/breakout-rooms/components/native/index.js
Normal file
5
react/features/breakout-rooms/components/native/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
export { default as AddBreakoutRoomButton } from './AddBreakoutRoomButton';
|
||||
export { default as AutoAssignButton } from './AutoAssignButton';
|
||||
export { default as LeaveBreakoutRoomButton } from './LeaveBreakoutRoomButton';
|
||||
69
react/features/breakout-rooms/components/native/styles.js
Normal file
69
react/features/breakout-rooms/components/native/styles.js
Normal 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
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
127
react/features/breakout-rooms/components/web/CollapsibleRoom.js
Normal file
127
react/features/breakout-rooms/components/web/CollapsibleRoom.js
Normal 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 } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
42
react/features/breakout-rooms/components/web/LeaveButton.js
Normal file
42
react/features/breakout-rooms/components/web/LeaveButton.js
Normal 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>
|
||||
);
|
||||
};
|
||||
@@ -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;
|
||||
100
react/features/breakout-rooms/components/web/RoomContextMenu.js
Normal file
100
react/features/breakout-rooms/components/web/RoomContextMenu.js
Normal 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>
|
||||
);
|
||||
};
|
||||
63
react/features/breakout-rooms/components/web/RoomList.js
Normal file
63
react/features/breakout-rooms/components/web/RoomList.js
Normal 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 } />
|
||||
</>
|
||||
);
|
||||
};
|
||||
4
react/features/breakout-rooms/components/web/index.js
Normal file
4
react/features/breakout-rooms/components/web/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export * from './LeaveButton';
|
||||
export * from './RoomList';
|
||||
22
react/features/breakout-rooms/constants.js
Normal file
22
react/features/breakout-rooms/constants.js
Normal 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`;
|
||||
59
react/features/breakout-rooms/functions.js
Normal file
59
react/features/breakout-rooms/functions.js
Normal 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();
|
||||
};
|
||||
7
react/features/breakout-rooms/logger.js
Normal file
7
react/features/breakout-rooms/logger.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { getLogger } from '../base/logging/functions';
|
||||
|
||||
import { FEATURE_KEY } from './constants';
|
||||
|
||||
export default getLogger(FEATURE_KEY);
|
||||
31
react/features/breakout-rooms/middleware.js
Normal file
31
react/features/breakout-rooms/middleware.js
Normal 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
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
25
react/features/breakout-rooms/reducer.js
Normal file
25
react/features/breakout-rooms/reducer.js
Normal 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;
|
||||
});
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
export { default as PrivateMessageButton } from './PrivateMessageButton';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
|
||||
export { default as PrivateMessageButton } from './PrivateMessageButton';
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 & {
|
||||
|
||||
@@ -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 } />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 } />
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -71,10 +71,6 @@ export default {
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
messageContainer: {
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper View for the entire block.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
90
react/features/chat/components/web/PrivateMessageButton.js
Normal file
90
react/features/chat/components/web/PrivateMessageButton.js
Normal 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));
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user