Compare commits
59 Commits
hmuresan/r
...
jitihouse/
Author | SHA1 | Date |
---|---|---|
xenia | 4bd73cc368 | |
Boris Grozev | e12999d44f | |
Robert Pintilii | 8982f17ce1 | |
Robert Pintilii | c8f1690057 | |
Robert Pintilii | aa57309057 | |
damencho | fb81619fc5 | |
Hristo Terezov | 5a5656020b | |
Hristo Terezov | 0ff44a2f22 | |
Hristo Terezov | 4d04ea325e | |
Hristo Terezov | 42ce6dcc58 | |
Hristo Terezov | b033d0268a | |
Hristo Terezov | 4aea40d34f | |
Hristo Terezov | e5a170fb28 | |
Hristo Terezov | d1cf5578fc | |
Hristo Terezov | 4b29af6b5f | |
bgrozev | f3481576ff | |
bgrozev | 455a91a5c6 | |
Gabriel Borlea | 297ab194a8 | |
Christoph Settgast | 077a88a803 | |
Gabriel Borlea | 02c232440e | |
Emmanuel Pelletier | f727b9295f | |
Robert Pintilii | 0d0bec3aad | |
Emmanuel Pelletier | cfb8589bef | |
japm48 | 65730e256e | |
Robert Pintilii | 7b8b911fee | |
Robert Pintilii | 036286a1d6 | |
Robert Pintilii | d550254f31 | |
Robert Pintilii | b1a71d55d7 | |
George Politis | 17ed45799c | |
Jaya Allamsetty | e5681382b0 | |
Robert Pintilii | c27cb25afe | |
Avram Tudor | baf5aa14e8 | |
Horatiu Muresan | 29b6ce7721 | |
Mihaela Dumitru | 4f95c45e50 | |
Emmanuel Pelletier | 72dd609247 | |
Horatiu Muresan | fed74afffe | |
Mihaela Dumitru | 204f34cccb | |
Emmanuel Pelletier | c81777a475 | |
Horatiu Muresan | 778bca3031 | |
Avram Tudor | 336fa304ce | |
damencho | f14b69166c | |
Calin-Teodor | e405595a11 | |
zobadaniel | cabe48d66a | |
Duduman Bogdan Vlad | 8d7f46024b | |
damencho | d7f6c2bbf0 | |
damencho | 3c69645169 | |
damencho | abe2fa4dd4 | |
damencho | 863fd12488 | |
damencho | 50c4748d40 | |
damencho | f83840a3bc | |
damencho | 1466d7d149 | |
Calinteodor | d0fe034db5 | |
Дамян Минков | 8225f5e363 | |
Saúl Ibarra Corretgé | c641835d0f | |
Horatiu Muresan | 35ee92869f | |
Stefan Plücken | 9b7a5ffdd1 | |
Calin-Teodor | 581c2e621c | |
Mihaela Dumitru | f3117f3037 | |
Saúl Ibarra Corretgé | 877ef58dfb |
7
Makefile
|
@ -44,12 +44,8 @@ deploy-appbundle:
|
|||
cp \
|
||||
$(BUILD_DIR)/app.bundle.min.js \
|
||||
$(BUILD_DIR)/app.bundle.min.js.map \
|
||||
$(BUILD_DIR)/do_external_connect.min.js \
|
||||
$(BUILD_DIR)/do_external_connect.min.js.map \
|
||||
$(BUILD_DIR)/external_api.min.js \
|
||||
$(BUILD_DIR)/external_api.min.js.map \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js \
|
||||
$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.js.map \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
|
@ -70,7 +66,6 @@ deploy-lib-jitsi-meet:
|
|||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.js \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.min.map \
|
||||
$(LIBJITSIMEET_DIR)/dist/umd/lib-jitsi-meet.e2ee-worker.js \
|
||||
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
|
||||
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
|
@ -131,7 +126,7 @@ dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-mode
|
|||
|
||||
source-package:
|
||||
mkdir -p source_package/jitsi-meet/css && \
|
||||
cp -r *.js *.html resources/*.txt connection_optimization favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp -r *.js *.html resources/*.txt favicon.ico fonts images libs static sounds LICENSE lang source_package/jitsi-meet && \
|
||||
cp css/all.css source_package/jitsi-meet/css && \
|
||||
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
|
||||
rm -rf source_package
|
||||
|
|
11
app.js
|
@ -33,17 +33,6 @@ window.APP = {
|
|||
API,
|
||||
conference,
|
||||
|
||||
// Used by do_external_connect.js if we receive the attach data after
|
||||
// connect was already executed. status property can be 'initialized',
|
||||
// 'ready', or 'connecting'. We are interested in 'ready' status only which
|
||||
// means that connect was executed but we have to wait for the attach data.
|
||||
// In status 'ready' handler property will be set to a function that will
|
||||
// finish the connect process when the attach data or error is received.
|
||||
connect: {
|
||||
handler: null,
|
||||
status: 'initialized'
|
||||
},
|
||||
|
||||
// Used for automated performance tests.
|
||||
connectionTimes: {
|
||||
'index.loaded': window.indexLoadedTime
|
||||
|
|
|
@ -153,6 +153,7 @@ import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise'
|
|||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
|
||||
import { muteLocal } from './react/features/video-menu/actions.any';
|
||||
import { setIAmVisitor } from './react/features/visitors/actions';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
@ -342,6 +343,8 @@ class ConferenceConnector {
|
|||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
generateVisitorConfig(APP.store.getState(), params);
|
||||
|
||||
APP.store.dispatch(setIAmVisitor(true));
|
||||
|
||||
connection.disconnect().then(() => {
|
||||
connect(this._conference.roomName).then(con => {
|
||||
const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
|
||||
|
|
25
config.js
|
@ -46,9 +46,9 @@ var config = {
|
|||
},
|
||||
|
||||
// BOSH URL. FIXME: use XEP-0156 to discover it.
|
||||
bosh: '//jitsi-meet.example.com/' + subdir + 'http-bind',
|
||||
bosh: 'https://jitsi-meet.example.com/' + subdir + 'http-bind',
|
||||
|
||||
// Websocket URL
|
||||
// Websocket URL (XMPP)
|
||||
// websocket: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
|
||||
|
||||
// The real JID of focus participant - can be overridden here
|
||||
|
@ -56,6 +56,19 @@ var config = {
|
|||
// https://github.com/jitsi/jitsi-meet/issues/7376
|
||||
// focusUserJid: 'focus@auth.jitsi-meet.example.com',
|
||||
|
||||
// Options related to the bridge (colibri) data channel
|
||||
bridgeChannel: {
|
||||
// If the backend advertises multiple colibri websockets, this options allows
|
||||
// to filter some of them out based on the domain name. We use the first URL
|
||||
// which does not match ignoreDomain, falling back to the first one that matches
|
||||
// ignoreDomain. Has no effect if undefined.
|
||||
// ignoreDomain: 'example.com',
|
||||
|
||||
// Prefer SCTP (WebRTC data channels over the media path) over a colibri websocket.
|
||||
// If SCTP is available in the backend it will be used instead of a WS. Defaults to
|
||||
// false (SCTP is used only if available and no WS are available).
|
||||
// preferSctp: false
|
||||
},
|
||||
|
||||
// Testing / experimental features.
|
||||
//
|
||||
|
@ -1146,6 +1159,13 @@ var config = {
|
|||
// }
|
||||
// },
|
||||
|
||||
// // The terms, privacy and help centre URL's.
|
||||
// legalUrls: {
|
||||
// helpCentre: 'https://web-cdn.jitsi.net/faq/meet-faq.html',
|
||||
// privacy: 'https://jitsi.org/meet/privacy',
|
||||
// terms: 'https://jitsi.org/meet/terms'
|
||||
// },
|
||||
|
||||
// A property to disable the right click context menu for localVideo
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false,
|
||||
|
@ -1365,7 +1385,6 @@ var config = {
|
|||
dialOutRegionUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
externalConnectUrl
|
||||
e2eeLabels
|
||||
firefox_fake_device
|
||||
googleApiApplicationClientID
|
||||
|
|
|
@ -32,54 +32,6 @@ const logger = Logger.getLogger(__filename);
|
|||
*/
|
||||
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
|
||||
|
||||
/**
|
||||
* Checks if we have data to use attach instead of connect. If we have the data
|
||||
* executes attach otherwise check if we have to wait for the data. If we have
|
||||
* to wait for the attach data we are setting handler to APP.connect.handler
|
||||
* which is going to be called when the attach data is received otherwise
|
||||
* executes connect.
|
||||
*
|
||||
* @param {string} [id] user id
|
||||
* @param {string} [password] password
|
||||
* @param {string} [roomName] the name of the conference.
|
||||
*/
|
||||
function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
if (window.XMPPAttachInfo) {
|
||||
APP.connect.status = 'connecting';
|
||||
|
||||
// When connection optimization is not deployed or enabled the default
|
||||
// value will be window.XMPPAttachInfo.status = "error"
|
||||
// If the connection optimization is deployed and enabled and there is
|
||||
// a failure the value will be window.XMPPAttachInfo.status = "error"
|
||||
if (window.XMPPAttachInfo.status === 'error') {
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const attachOptions = window.XMPPAttachInfo.data;
|
||||
|
||||
if (attachOptions) {
|
||||
connection.attach(attachOptions);
|
||||
delete window.XMPPAttachInfo.data;
|
||||
} else {
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
}
|
||||
} else {
|
||||
APP.connect.status = 'ready';
|
||||
APP.connect.handler
|
||||
= checkForAttachParametersAndConnect.bind(
|
||||
null,
|
||||
id, password, connection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to open connection using provided credentials.
|
||||
* @param {string} [id]
|
||||
|
@ -182,7 +134,10 @@ export async function connect(id, password) {
|
|||
APP.store.dispatch(setPrejoinDisplayNameRequired());
|
||||
}
|
||||
|
||||
checkForAttachParametersAndConnect(id, password, connection);
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
'extends': '../react/.eslintrc.js'
|
||||
};
|
|
@ -1,86 +0,0 @@
|
|||
/* global config, createConnectionExternally */
|
||||
|
||||
import getRoomName from '../react/features/base/config/getRoomName';
|
||||
import { parseURLParams } from '../react/features/base/util/parseURLParams';
|
||||
|
||||
/**
|
||||
* Implements external connect using createConnectionExternally function defined
|
||||
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
|
||||
* Token (JWT) from the URL and executes createConnectionExternally.
|
||||
*
|
||||
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
|
||||
* file as reference only because the implementation is Jitsi Meet-specific.
|
||||
*
|
||||
* NOTE: For optimal results this file should be included right after
|
||||
* external_connect.js.
|
||||
*/
|
||||
|
||||
if (typeof createConnectionExternally === 'function') {
|
||||
// URL params have higher priority than config params.
|
||||
// Do not use external connect if websocket is enabled.
|
||||
let url
|
||||
= parseURLParams(window.location, true, 'hash')[
|
||||
'config.externalConnectUrl']
|
||||
|| config.websocket ? undefined : config.externalConnectUrl;
|
||||
const isRecorder
|
||||
= parseURLParams(window.location, true, 'hash')['config.iAmRecorder'];
|
||||
|
||||
let roomName;
|
||||
|
||||
if (url && (roomName = getRoomName()) && !isRecorder) {
|
||||
url += `?room=${roomName}`;
|
||||
|
||||
const token = parseURLParams(window.location, true, 'search').jwt;
|
||||
|
||||
if (token) {
|
||||
url += `&token=${token}`;
|
||||
}
|
||||
|
||||
createConnectionExternally(
|
||||
url,
|
||||
connectionInfo => {
|
||||
// Sets that global variable to be used later by connect method
|
||||
// in connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'success',
|
||||
data: connectionInfo
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
},
|
||||
errorCallback);
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
} else {
|
||||
errorCallback();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connect from connection.js was executed and executes the handler
|
||||
* that is going to finish the connect work.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForConnectHandlerAndConnect() {
|
||||
window.APP
|
||||
&& window.APP.connect.status === 'ready'
|
||||
&& window.APP.connect.handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a callback to be invoked if anything goes wrong.
|
||||
*
|
||||
* @param {Error} error - The specifics of what went wrong.
|
||||
* @returns {void}
|
||||
*/
|
||||
function errorCallback(error) {
|
||||
// The value of error is undefined if external connect is disabled.
|
||||
error && console.warn(error);
|
||||
|
||||
// Sets that global variable to be used later by connect method in
|
||||
// connection.js.
|
||||
window.XMPPAttachInfo = {
|
||||
status: 'error'
|
||||
};
|
||||
checkForConnectHandlerAndConnect();
|
||||
}
|
|
@ -22,11 +22,7 @@
|
|||
}
|
||||
|
||||
&-entry-text {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 213px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
&.left-margin {
|
||||
margin-left: 36px;
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
@keyframes rotateAroundY {
|
||||
from { transform: rotateY(0deg); }
|
||||
to { transform: rotateY(360deg); }
|
||||
}
|
||||
|
||||
@keyframes rainbowRoad {
|
||||
to {
|
||||
background-position: 400% 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Comic Sans MS", "Comic Sans", sans-serif !important;
|
||||
}
|
||||
|
||||
a {
|
||||
background: linear-gradient(90deg, rgba(255,0,0,1) 0%, rgba(255,154,0,1) 10%, rgba(208,222,33,1) 20%, rgba(79,220,74,1) 30%, rgba(63,218,216,1) 40%, rgba(47,201,226,1) 50%, rgba(28,127,238,1) 60%, rgba(95,21,242,1) 70%, rgba(186,12,248,1) 80%, rgba(251,7,217,1) 90%, rgba(255,0,0,1) 100%) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
-moz-background-clip: text !important;
|
||||
-moz-text-fill-color: transparent !important;
|
||||
animation: rainbowRoad 8s linear infinite !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
|
||||
}
|
||||
|
||||
.dominant-speaker {
|
||||
box-shadow: inset 0px 0px 0px 4px rgba(255,0,255,0.33) !important;
|
||||
}
|
||||
|
||||
.display-avatar-only {
|
||||
background-image: url("");
|
||||
}
|
||||
|
||||
.videocontainer:nth-child(odd) {
|
||||
transform: rotate(1.5deg);
|
||||
}
|
||||
|
||||
.videocontainer:nth-child(even) {
|
||||
transform: rotate(-1.3deg);
|
||||
}
|
||||
|
||||
#largeVideoContainer.videocontainer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
transform: rotate(-1.1deg);
|
||||
}
|
||||
|
||||
.displayname:before {
|
||||
content: "♡︎ ";
|
||||
}
|
||||
|
||||
.displayname:after {
|
||||
content: " ♡︎";
|
||||
}
|
||||
|
||||
.avatar, .userAvatar {
|
||||
transform: rotateY(0deg);
|
||||
}
|
||||
|
||||
.avatar:hover, .userAvatar:hover {
|
||||
animation: rotateAroundY 3.6s linear infinite;
|
||||
}
|
|
@ -82,6 +82,7 @@
|
|||
}
|
||||
|
||||
.left-column {
|
||||
order: -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 0;
|
||||
|
@ -92,6 +93,7 @@
|
|||
.right-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
padding-left: 16px;
|
||||
padding-top: 13px;
|
||||
|
@ -102,7 +104,7 @@
|
|||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
padding-bottom: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
|
@ -125,8 +127,7 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.with-click-handler:hover,
|
||||
&.with-click-handler:focus {
|
||||
&.with-click-handler:hover {
|
||||
background-color: #c7ddff;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#polls-panel {
|
||||
.polls-panel {
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
|
|
|
@ -201,11 +201,6 @@ $deepLinkingDialInConferenceIdPadding: inherit;
|
|||
$deepLinkingDialInConferenceIdBackgroundColor: inherit;
|
||||
$deepLinkingDialInConferenceIdBorderRadius: inherit;
|
||||
|
||||
$deepLinkingDialInConferenceNameFontSize: inherit;
|
||||
$deepLinkingDialInConferenceNameLineHeight: inherit;
|
||||
$deepLinkingDialInConferenceNameMarginBottom: none;
|
||||
$deepLinkingDialInConferenceNameFontWeight: inherit;
|
||||
|
||||
$deepLinkingDialInConferenceDescriptionFontSize: 0.8em;
|
||||
$deepLinkingDialInConferenceDescriptionLineHeight: inherit;
|
||||
$deepLinkingDialInConferenceDescriptionMarginBottom: none;
|
||||
|
|
|
@ -90,7 +90,7 @@ body.welcome-page {
|
|||
font-size: 14px;
|
||||
padding-left: 10px;
|
||||
|
||||
&:focus {
|
||||
&.focus-visible {
|
||||
outline: auto 2px #005fcc;
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ body.welcome-page {
|
|||
margin: 4px;
|
||||
display: $welcomePageTabButtonsDisplay;
|
||||
|
||||
.tab {
|
||||
[role="tab"] {
|
||||
background-color: #c7ddff;
|
||||
border-radius: 7px;
|
||||
cursor: pointer;
|
||||
|
@ -176,8 +176,10 @@ body.welcome-page {
|
|||
margin: 2px;
|
||||
padding: 7px 0;
|
||||
text-align: center;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
|
||||
&.selected {
|
||||
&[aria-selected="true"] {
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,13 @@
|
|||
font-size: 1em;
|
||||
}
|
||||
|
||||
|
||||
.dial-in-conference-id {
|
||||
text-align: center;
|
||||
min-width: 200px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.dial-in-conference-id {
|
||||
margin: $deepLinkingDialInConferenceIdMargin;
|
||||
padding: $deepLinkingDialInConferenceIdPadding;
|
||||
|
@ -74,24 +81,12 @@
|
|||
border-radius: $deepLinkingDialInConferenceIdBorderRadius;
|
||||
}
|
||||
|
||||
.dial-in-conference-name {
|
||||
font-size: $deepLinkingDialInConferenceNameFontSize;
|
||||
line-height: $deepLinkingDialInConferenceNameLineHeight;
|
||||
margin-bottom: $deepLinkingDialInConferenceNameMarginBottom;
|
||||
font-weight: $deepLinkingDialInConferenceNameFontWeight;
|
||||
}
|
||||
|
||||
.dial-in-conference-description {
|
||||
font-size: $deepLinkingDialInConferenceDescriptionFontSize;
|
||||
line-height: $deepLinkingDialInConferenceDescriptionLineHeight;
|
||||
margin-bottom: $deepLinkingDialInConferenceDescriptionMarginBottom;
|
||||
}
|
||||
|
||||
.dial-in-conference-pin {
|
||||
font-size: $deepLinkingDialInConferencePinFontSize;
|
||||
line-height: $deepLinkingDialInConferencePinLineHeight;
|
||||
}
|
||||
|
||||
.toll-free-list {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ $flagsImagePath: "../images/";
|
|||
@import 'reload_overlay/reload_overlay';
|
||||
@import 'mini_toolbox';
|
||||
@import 'modals/desktop-picker/desktop-picker';
|
||||
@import 'modals/device-selection/device-selection';
|
||||
@import 'modals/dialog';
|
||||
@import 'modals/embed-meeting/embed-meeting';
|
||||
@import 'modals/feedback/feedback';
|
||||
|
@ -95,3 +94,9 @@ $flagsImagePath: "../images/";
|
|||
@import 'notifications';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
/* Jeet crew BEGIN */
|
||||
|
||||
@import 'jiti';
|
||||
|
||||
/* Jeet crew END */
|
||||
|
|
|
@ -1,148 +0,0 @@
|
|||
.device-selection {
|
||||
.device-selectors {
|
||||
font-size: 14px;
|
||||
|
||||
> div {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.device-selector-icon {
|
||||
align-self: center;
|
||||
color: inherit;
|
||||
font-size: 20px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.device-selector-label {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
|
||||
.device-selector-trigger {
|
||||
background-color: #0E1624;
|
||||
border: 1px solid #455166;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
height: 2.3em;
|
||||
justify-content: space-between;
|
||||
line-height: 2.3em;
|
||||
overflow: hidden;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.device-selector-trigger-disabled {
|
||||
.device-selector-trigger {
|
||||
color: #a5adba;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selector-trigger-text {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection-column {
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
|
||||
&.column-selectors {
|
||||
margin-left: 15px;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
&.column-video {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection-video-container {
|
||||
border-radius: 3px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.video-input-preview {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
|
||||
> video {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.video-input-preview-error {
|
||||
color: $participantNameColor;
|
||||
display: none;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
&.video-preview-has-error {
|
||||
background: black;
|
||||
|
||||
.video-input-preview-error {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.video-input-preview-display {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-output-preview {
|
||||
font-size: 14px;
|
||||
|
||||
a {
|
||||
color: #6FB1EA;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #B3D4FF;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-input-preview {
|
||||
background: #1B2638;
|
||||
border-radius: 5px;
|
||||
height: 8px;
|
||||
|
||||
.audio-input-preview-level {
|
||||
background: #75B1FF;
|
||||
border-radius: 5px;
|
||||
height: 100%;
|
||||
-webkit-transition: width .1s ease-in-out;
|
||||
-moz-transition: width .1s ease-in-out;
|
||||
-o-transition: width .1s ease-in-out;
|
||||
transition: width .1s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.device-selection.video-hidden {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
.column-selectors {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.column-video {
|
||||
order: 1;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
|
@ -44,61 +44,3 @@
|
|||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-timing-function: ease-in-out
|
||||
}
|
||||
|
||||
.feedback-dialog {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.details {
|
||||
textarea {
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-control {
|
||||
background-color: $feedbackInputBg;
|
||||
color: $feedbackInputTextColor;
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: $feedbackInputPlaceholderColor;
|
||||
}
|
||||
&::-moz-placeholder { /* Firefox 19+ */
|
||||
color: $feedbackInputPlaceholderColor;
|
||||
}
|
||||
&:-ms-input-placeholder {
|
||||
color: $feedbackInputPlaceholderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.rating {
|
||||
line-height: 1.2;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
|
||||
.star-label {
|
||||
font-size: 14px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.star-btn {
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 34px;
|
||||
outline: none;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
@include transition(all .2s ease);
|
||||
|
||||
&.active,
|
||||
&:hover,
|
||||
&.starHover {
|
||||
color: #36B37E;
|
||||
};
|
||||
|
||||
}
|
||||
.star-btn:focus,
|
||||
.star-btn:active {
|
||||
outline: 1px solid #B8C7E0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
}
|
||||
|
||||
.dial-in-numbers-list {
|
||||
max-width: 334px;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
|
@ -59,10 +61,6 @@
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid #d1dbe8;
|
||||
}
|
||||
|
||||
.flag-cell {
|
||||
vertical-align: top;
|
||||
width: 30px;
|
||||
|
@ -91,6 +89,7 @@
|
|||
font-weight: bold;
|
||||
list-style: none;
|
||||
vertical-align: top;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
li.toll-free:empty:before {
|
||||
|
@ -119,11 +118,6 @@
|
|||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.dial-in-conference-name,
|
||||
.dial-in-conference-pin {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.dial-in-conference-description {
|
||||
margin: 12px;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ sounds /usr/share/jitsi-meet/
|
|||
fonts /usr/share/jitsi-meet/
|
||||
images /usr/share/jitsi-meet/
|
||||
lang /usr/share/jitsi-meet/
|
||||
connection_optimization /usr/share/jitsi-meet/
|
||||
resources/robots.txt /usr/share/jitsi-meet/
|
||||
resources/*.sh /usr/share/jitsi-meet/scripts/
|
||||
pwa-worker.js /usr/share/jitsi-meet/
|
||||
|
|
|
@ -93,7 +93,7 @@ server {
|
|||
}
|
||||
|
||||
# ensure all static content can always be found first
|
||||
location ~ ^/(libs|css|static|images|fonts|lang|sounds|connection_optimization|.well-known)/(.*)$
|
||||
location ~ ^/(libs|css|static|images|fonts|lang|sounds|.well-known)/(.*)$
|
||||
{
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
alias /usr/share/jitsi-meet/$1/$2;
|
||||
|
|
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 6.3 KiB |
|
@ -182,8 +182,6 @@
|
|||
'error', loadErrHandler, true /* capture phase type of listener */);
|
||||
</script>
|
||||
<script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<!--#include virtual="connection_optimization/connection_optimization.html" -->
|
||||
<script src="libs/do_external_connect.min.js?v=1"></script>
|
||||
<script><!--#include virtual="/interface_config.js" --></script>
|
||||
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>
|
||||
<script src="libs/app.bundle.min.js?v=139"></script>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
var interfaceConfig = {
|
||||
APP_NAME: 'Jitsi Meet',
|
||||
APP_NAME: 'JitSea 🏴☠️',
|
||||
AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',
|
||||
AUDIO_LEVEL_SECONDARY_COLOR: 'rgba(255,255,255,0.2)',
|
||||
|
||||
|
|
|
@ -389,7 +389,7 @@ PODS:
|
|||
- react-native-video/Video (6.0.0-alpha.1):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (106.0.5):
|
||||
- react-native-webrtc (106.0.6):
|
||||
- JitsiWebRTC (~> 106.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
|
@ -754,7 +754,7 @@ SPEC CHECKSUMS:
|
|||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-webrtc: ef315d8adb68e78298b22100377d12ef168efdb5
|
||||
react-native-webrtc: 22ac6c64a1e38552bb173dde81ffea6979a58ef3
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
React-perflogger: 0458a87ea9a7342079e7a31b0d32b3734fb8415f
|
||||
React-RCTActionSheet: 22538001ea2926dea001111dd2846c13a0730bc9
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"cs": "Čeština",
|
||||
"da": "Dansk",
|
||||
"de": "Deutsch",
|
||||
"dsb": "Dolnoserbšćina",
|
||||
"el": "Ελληνικά",
|
||||
"en": "English",
|
||||
"enGB": "English (United Kingdom)",
|
||||
|
|
|
@ -184,13 +184,21 @@
|
|||
"deepLinking": {
|
||||
"appNotInstalled": "Sie benötigen die „{{app}}“-App, um der Konferenz auf dem Smartphone beizutreten.",
|
||||
"description": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. Versuchen Sie es erneut oder treten Sie der Konferenz in {{app}} im Web bei.",
|
||||
"descriptionNew": "Nichts passiert? Wir haben versucht, die Konferenz in {{app}} zu öffnen. <br /><br /> Versuchen Sie es erneut oder treten Sie der Konferenz im Web bei.",
|
||||
"descriptionWithoutWeb": "Ist nichts passiert? Wir haben versucht, Ihre Besprechung in der „{{app}}“-Desktop-App zu starten.",
|
||||
"downloadApp": "App herunterladen",
|
||||
"downloadMobileApp": "Aus dem App Store herunterladen",
|
||||
"ifDoNotHaveApp": "Wenn Sie die App noch nicht haben:",
|
||||
"ifHaveApp": "Wenn Sie die App bereits haben:",
|
||||
"joinInApp": "Mit der App am Meeting teilnehmen",
|
||||
"joinInAppNew": "Mit der App",
|
||||
"joinInBrowser": "Im Browser",
|
||||
"launchMeetingLabel": "Wie möchten Sie an der Konferenz teilnehmen?",
|
||||
"launchWebButton": "Im Web öffnen",
|
||||
"noMobileApp": "Sie haben die App noch nicht installiert?",
|
||||
"termsAndConditions": "Indem Sie fortfahren, stimmen Sie underen<a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>Nutzungsbedingungen</a> zu.",
|
||||
"title": "Die Konferenz wird in {{app}} geöffnet …",
|
||||
"titleNew": "Konferenz starten ...",
|
||||
"tryAgainButton": "Erneut mit der nativen Applikation versuchen",
|
||||
"unsupportedBrowser": "Sie verwenden einen Browser, der noch nicht unterstützt wird."
|
||||
},
|
||||
|
@ -203,6 +211,12 @@
|
|||
"microphonePermission": "Fehler beim Bezug der Mikrofon-Zugriffsberechtigungen"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"hid": {
|
||||
"callControl": "Anrufsteuerung",
|
||||
"connectedDevices": "Verbundene Geräte:",
|
||||
"deleteDevice": "Gerät löschen",
|
||||
"pairDevice": "Gerät verbinden"
|
||||
},
|
||||
"noPermission": "Berechtigungen nicht erteilt",
|
||||
"previewUnavailable": "Keine Vorschau verfügbar",
|
||||
"selectADevice": "Ein Gerät wählen",
|
||||
|
@ -226,7 +240,9 @@
|
|||
"WaitingForHostTitle": "Warten auf den Beginn der Konferenz …",
|
||||
"Yes": "Ja",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Livestream"
|
||||
"close": "Popup schließen",
|
||||
"liveStreaming": "Livestream",
|
||||
"sharingTabs": "Optionen zum Teilen"
|
||||
},
|
||||
"add": "Hinzufügen",
|
||||
"addMeetingNote": "Notiz zu dieser Konferenz hinzufügen",
|
||||
|
@ -438,6 +454,11 @@
|
|||
"veryBad": "Sehr schlecht",
|
||||
"veryGood": "Sehr gut"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Videominiaturen"
|
||||
}
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "Keine Ergebnisse :(",
|
||||
"search": "GIPHY durchsuchen"
|
||||
|
@ -751,6 +772,7 @@
|
|||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Anwesende ({{count}})",
|
||||
"visitors": "Gäste ({{count}})",
|
||||
"waitingLobby": "In der Lobby ({{count}})"
|
||||
},
|
||||
"search": "Suche Anwesende",
|
||||
|
@ -758,6 +780,7 @@
|
|||
},
|
||||
"passwordDigitsOnly": "Bis zu {{number}} Ziffern",
|
||||
"passwordSetRemotely": "von einer anderen Person gesetzt",
|
||||
"pinParticipant": "{{participantName}} - anheften",
|
||||
"pinnedParticipant": "Die Person ist angeheftet",
|
||||
"polls": {
|
||||
"answer": {
|
||||
|
@ -950,6 +973,7 @@
|
|||
"title": "Sicherheitsoptionen"
|
||||
},
|
||||
"settings": {
|
||||
"audio": "Audio",
|
||||
"buttonLabel": "Einstellungen",
|
||||
"calendar": {
|
||||
"about": "Die Kalenderintegration von {{appName}} wird verwendet, um ein sicheres Zugreifen auf Ihren Kalender und Auslesen der bevorstehenden Termine zu ermöglichen.",
|
||||
|
@ -970,9 +994,11 @@
|
|||
"maxStageParticipants": "Maximale Anzahl an Personen, die zur Hauptansicht angeheftet werden können",
|
||||
"microphones": "Mikrofon",
|
||||
"moderator": "Moderation",
|
||||
"moderatorOptions": "Moderationseinstellungen",
|
||||
"more": "Mehr",
|
||||
"name": "Name",
|
||||
"noDevice": "Kein",
|
||||
"notifications": "Benachrichtigungen",
|
||||
"participantJoined": "Neue Person nimmt teil",
|
||||
"participantKnocking": "Person hat Lobby betreten",
|
||||
"participantLeft": "Person verlässt die Konferenz",
|
||||
|
@ -983,13 +1009,14 @@
|
|||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofon",
|
||||
"selfView": "Eigene Ansicht",
|
||||
"sounds": "Hinweistöne",
|
||||
"shortcuts": "Tastaturkürzel",
|
||||
"speakers": "Lautsprecher",
|
||||
"startAudioMuted": "Alle Personen treten stummgeschaltet bei",
|
||||
"startReactionsMuted": "Interaktionstöne für alle deaktivieren",
|
||||
"startVideoMuted": "Alle Personen treten ohne Video bei",
|
||||
"talkWhileMuted": "Wenn bei Stummschaltung gesprochen wird",
|
||||
"title": "Einstellungen"
|
||||
"title": "Einstellungen",
|
||||
"video": "Kamera"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Erweitert",
|
||||
|
@ -1081,6 +1108,7 @@
|
|||
"giphy": "GIPHY ein-/ausschalten",
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"hangup": "Konferenz verlassen",
|
||||
"heading": "Toolbar",
|
||||
"help": "Hilfe",
|
||||
"invite": "Person einladen",
|
||||
"kick": "Person entfernen",
|
||||
|
@ -1147,6 +1175,7 @@
|
|||
"download": "Unsere Apps herunterladen",
|
||||
"e2ee": "Ende-zu-Ende-Verschlüsselung",
|
||||
"embedMeeting": "Konferenz einbetten",
|
||||
"enableNoiseSuppression": "Rauschunterdrückung einschalten",
|
||||
"endConference": "Konferenz für alle beenden",
|
||||
"enterFullScreen": "Vollbildmodus",
|
||||
"enterTileView": "Kachelansicht einschalten",
|
||||
|
@ -1234,6 +1263,7 @@
|
|||
"subtitlesOff": "Ausschalten",
|
||||
"tr": "TR"
|
||||
},
|
||||
"unpinParticipant": "{{participantName}} - Nicht mehr anheften",
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "Wählen Sie <b><i>Zulassen</i></b>, wenn der Browser um Berechtigungen bittet.",
|
||||
"chromeGrantPermissions": "Wählen Sie <b><i>Zulassen</i></b>, wenn der Browser um Berechtigungen bittet.",
|
||||
|
@ -1272,9 +1302,11 @@
|
|||
"ldTooltip": "Video wird in niedriger Auflösung angezeigt",
|
||||
"lowDefinition": "Niedrige Auflösung",
|
||||
"performanceSettings": "Qualitätseinstellungen",
|
||||
"recording": "Aufnahme läuft",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Video wird in Standardauflösung angezeigt",
|
||||
"standardDefinition": "Standardauflösung"
|
||||
"standardDefinition": "Standardauflösung",
|
||||
"streaming": "Streaming läuft"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Verbindungsinformationen",
|
||||
|
@ -1324,6 +1356,7 @@
|
|||
"webAssemblyWarning": "WebAssembly wird nicht unterstützt",
|
||||
"webAssemblyWarningDescription": "WebAssembly ist deaktiviert oder wird in diesem Browser nicht unterstützt"
|
||||
},
|
||||
"visitorsLabel": "Anzahl Gäste: {{count}}",
|
||||
"volumeSlider": "Lautstärkeregler",
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
|
@ -1356,6 +1389,7 @@
|
|||
"microsoftLogo": "Microsoft Logo",
|
||||
"policyLogo": "Richtlinienlogo"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "Konferenzen",
|
||||
"mobileDownLoadLinkAndroid": "Android App Download",
|
||||
"mobileDownLoadLinkFDroid": "F-Droid App Download",
|
||||
"mobileDownLoadLinkIos": "iOS App Download",
|
||||
|
@ -1375,5 +1409,10 @@
|
|||
"terms": "AGB",
|
||||
"title": "Sichere, voll funktionale und komplett kostenlose Videokonferenzen",
|
||||
"upcomingMeetings": "Ihre zukünftigen Konferenzen"
|
||||
},
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Whiteboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -838,7 +838,7 @@
|
|||
"selectCamera": "Cámara",
|
||||
"selectMic": "Micrófono",
|
||||
"sounds": "Sonidos",
|
||||
"speakers": "Parlantes",
|
||||
"speakers": "Altavoces",
|
||||
"startAudioMuted": "Todos inician silenciados",
|
||||
"startVideoMuted": "Todos inician con cámara desactivada",
|
||||
"talkWhileMuted": "Hablar en silencio",
|
||||
|
|
|
@ -184,13 +184,21 @@
|
|||
"deepLinking": {
|
||||
"appNotInstalled": "You need the {{app}} mobile app to join this meeting on your phone.",
|
||||
"description": "Nothing happened? We tried launching your meeting in the {{app}} desktop app. Try again or launch it in the {{app}} web app.",
|
||||
"descriptionNew": "Nothing happened? We tried launching your meeting in the {{app}} desktop app. <br /><br /> You can try again or launch it on web.",
|
||||
"descriptionWithoutWeb": "Nothing happened? We tried launching your meeting in the {{app}} desktop app.",
|
||||
"downloadApp": "Download the app",
|
||||
"downloadMobileApp": "Download from App Store",
|
||||
"ifDoNotHaveApp": "If you don't have the app yet:",
|
||||
"ifHaveApp": "If you already have the app:",
|
||||
"joinInApp": "Join this meeting using the app",
|
||||
"joinInAppNew": "Join in app",
|
||||
"joinInBrowser": "Join in browser",
|
||||
"launchMeetingLabel": "How do you want to join this meeting?",
|
||||
"launchWebButton": "Launch in web",
|
||||
"noMobileApp": "You don’t have the app?",
|
||||
"termsAndConditions": "By continuing you agree to our <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a>",
|
||||
"title": "Launching your meeting in {{app}}...",
|
||||
"titleNew": "Launching your meeting ...",
|
||||
"tryAgainButton": "Try again in desktop",
|
||||
"unsupportedBrowser": "It looks like you're using a browser we don't support."
|
||||
},
|
||||
|
@ -203,10 +211,16 @@
|
|||
"microphonePermission": "Error obtaining microphone permission"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"hid": {
|
||||
"callControl": "Call control",
|
||||
"connectedDevices": "Connected devices:",
|
||||
"deleteDevice": "Delete device",
|
||||
"pairDevice": "Pair device"
|
||||
},
|
||||
"noPermission": "Permission not granted",
|
||||
"previewUnavailable": "Preview unavailable",
|
||||
"selectADevice": "Select a device",
|
||||
"testAudio": "Play a test sound"
|
||||
"testAudio": "Test"
|
||||
},
|
||||
"dialIn": {
|
||||
"screenTitle": "Dial-in summary"
|
||||
|
@ -226,7 +240,9 @@
|
|||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
"Yes": "Yes",
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Live Stream"
|
||||
"close": "Close dialog",
|
||||
"liveStreaming": "Live Stream",
|
||||
"sharingTabs": "Sharing options"
|
||||
},
|
||||
"add": "Add",
|
||||
"addMeetingNote": "Add a note about this meeting",
|
||||
|
@ -438,6 +454,11 @@
|
|||
"veryBad": "Very Bad",
|
||||
"veryGood": "Very Good"
|
||||
},
|
||||
"filmstrip": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Video thumbnails"
|
||||
}
|
||||
},
|
||||
"giphy": {
|
||||
"noResults": "No results found :(",
|
||||
"search": "Search GIPHY"
|
||||
|
@ -751,6 +772,7 @@
|
|||
"headings": {
|
||||
"lobby": "Lobby ({{count}})",
|
||||
"participantsList": "Meeting participants ({{count}})",
|
||||
"visitors": "Visitors ({{count}})",
|
||||
"waitingLobby": "Waiting in lobby ({{count}})"
|
||||
},
|
||||
"search": "Search participants",
|
||||
|
@ -758,6 +780,7 @@
|
|||
},
|
||||
"passwordDigitsOnly": "Up to {{number}} digits",
|
||||
"passwordSetRemotely": "Set by another participant",
|
||||
"pinParticipant": "{{participantName}} - Pin",
|
||||
"pinnedParticipant": "The participant is pinned",
|
||||
"polls": {
|
||||
"answer": {
|
||||
|
@ -865,9 +888,9 @@
|
|||
},
|
||||
"profile": {
|
||||
"avatar": "avatar",
|
||||
"setDisplayNameLabel": "Set your display name",
|
||||
"setDisplayNameLabel": "Name",
|
||||
"setEmailInput": "Enter email",
|
||||
"setEmailLabel": "Set your gravatar email",
|
||||
"setEmailLabel": "Gravatar email",
|
||||
"title": "Profile"
|
||||
},
|
||||
"raisedHand": "Would like to speak",
|
||||
|
@ -950,6 +973,7 @@
|
|||
"title": "Security Options"
|
||||
},
|
||||
"settings": {
|
||||
"audio": "Audio",
|
||||
"buttonLabel": "Settings",
|
||||
"calendar": {
|
||||
"about": "The {{appName}} calendar integration is used to securely access your calendar so it can read upcoming events.",
|
||||
|
@ -970,9 +994,11 @@
|
|||
"maxStageParticipants": "Maximum number of participants who can be pinned to the main stage (EXPERIMENTAL)",
|
||||
"microphones": "Microphones",
|
||||
"moderator": "Moderator",
|
||||
"more": "More",
|
||||
"moderatorOptions": "Moderator options",
|
||||
"more": "General",
|
||||
"name": "Name",
|
||||
"noDevice": "None",
|
||||
"notifications": "Notifications",
|
||||
"participantJoined": "Participant Joined",
|
||||
"participantKnocking": "Participant entered lobby",
|
||||
"participantLeft": "Participant Left",
|
||||
|
@ -983,13 +1009,14 @@
|
|||
"selectCamera": "Camera",
|
||||
"selectMic": "Microphone",
|
||||
"selfView": "Self view",
|
||||
"sounds": "Sounds",
|
||||
"shortcuts": "Shortcuts",
|
||||
"speakers": "Speakers",
|
||||
"startAudioMuted": "Everyone starts muted",
|
||||
"startReactionsMuted": "Mute reaction sounds for everyone",
|
||||
"startVideoMuted": "Everyone starts hidden",
|
||||
"talkWhileMuted": "Talk while muted",
|
||||
"title": "Settings"
|
||||
"title": "Settings",
|
||||
"video": "Video"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Advanced",
|
||||
|
@ -1081,6 +1108,7 @@
|
|||
"giphy": "Toggle GIPHY menu",
|
||||
"grantModerator": "Grant Moderator Rights",
|
||||
"hangup": "Leave the meeting",
|
||||
"heading": "Toolbar",
|
||||
"help": "Help",
|
||||
"invite": "Invite people",
|
||||
"kick": "Kick participant",
|
||||
|
@ -1147,6 +1175,7 @@
|
|||
"download": "Download our apps",
|
||||
"e2ee": "End-to-End Encryption",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"enableNoiseSuppression": "Enable noise suppression",
|
||||
"endConference": "End meeting for all",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
|
@ -1234,6 +1263,7 @@
|
|||
"subtitlesOff": "Off",
|
||||
"tr": "TR"
|
||||
},
|
||||
"unpinParticipant": "{{participantName}} - Unpin",
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
|
||||
|
@ -1272,9 +1302,11 @@
|
|||
"ldTooltip": "Viewing low definition video",
|
||||
"lowDefinition": "Low definition",
|
||||
"performanceSettings": "Performance settings",
|
||||
"recording": "Recording in progress",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Viewing standard definition video",
|
||||
"standardDefinition": "Standard definition"
|
||||
"standardDefinition": "Standard definition",
|
||||
"streaming": "Streaming in progress"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Connection Info",
|
||||
|
@ -1318,12 +1350,13 @@
|
|||
"none": "None",
|
||||
"pleaseWait": "Please wait...",
|
||||
"removeBackground": "Remove background",
|
||||
"slightBlur": "Slight Blur",
|
||||
"slightBlur": "Half Blur",
|
||||
"title": "Virtual backgrounds",
|
||||
"uploadedImage": "Uploaded image {{index}}",
|
||||
"webAssemblyWarning": "WebAssembly not supported",
|
||||
"webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
|
||||
},
|
||||
"visitorsLabel": "Number of visitors: {{count}}",
|
||||
"volumeSlider": "Volume slider",
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
|
@ -1356,6 +1389,7 @@
|
|||
"microsoftLogo": "Microsoft logo",
|
||||
"policyLogo": "Policy logo"
|
||||
},
|
||||
"meetingsAccessibilityLabel": "Meetings",
|
||||
"mobileDownLoadLinkAndroid": "Download mobile app for Android",
|
||||
"mobileDownLoadLinkFDroid": "Download mobile app for F-Droid",
|
||||
"mobileDownLoadLinkIos": "Download mobile app for iOS",
|
||||
|
@ -1375,5 +1409,10 @@
|
|||
"terms": "Terms",
|
||||
"title": "Secure, fully featured, and completely free video conferencing",
|
||||
"upcomingMeetings": "Your upcoming meetings"
|
||||
},
|
||||
"whiteboard": {
|
||||
"accessibilityLabel": {
|
||||
"heading": "Whiteboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,6 +106,8 @@ import { startAudioScreenShareFlow, startScreenShareFlow } from '../../react/fea
|
|||
import { isScreenAudioSupported } from '../../react/features/screen-share/functions';
|
||||
import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-capture';
|
||||
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
|
||||
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
|
||||
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
|
||||
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
|
||||
|
@ -113,7 +115,6 @@ import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/function
|
|||
import { setTileView, toggleTileView } from '../../react/features/video-layout';
|
||||
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality';
|
||||
import VirtualBackgroundDialog from '../../react/features/virtual-background/components/VirtualBackgroundDialog';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
|
||||
|
@ -798,7 +799,8 @@ function initCommands() {
|
|||
APP.store.dispatch(overwriteConfig(whitelistedConfig));
|
||||
},
|
||||
'toggle-virtual-background': () => {
|
||||
APP.store.dispatch(toggleDialog(VirtualBackgroundDialog));
|
||||
APP.store.dispatch(toggleDialog(SettingsDialog, {
|
||||
defaultTab: SETTINGS_TABS.VIRTUAL_BACKGROUND }));
|
||||
},
|
||||
'end-conference': () => {
|
||||
APP.store.dispatch(endConference());
|
||||
|
@ -1229,6 +1231,22 @@ class API {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the external app that a notification has been triggered.
|
||||
*
|
||||
* @param {string} title - The notification title.
|
||||
* @param {string} description - The notification description.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyNotificationTriggered(title: string, description: string) {
|
||||
this._sendEvent({
|
||||
description,
|
||||
name: 'notification-triggered',
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application that the video quality setting has changed.
|
||||
*
|
||||
|
|
|
@ -127,6 +127,7 @@ const events = {
|
|||
'mouse-enter': 'mouseEnter',
|
||||
'mouse-leave': 'mouseLeave',
|
||||
'mouse-move': 'mouseMove',
|
||||
'notification-triggered': 'notificationTriggered',
|
||||
'outgoing-message': 'outgoingMessage',
|
||||
'participant-joined': 'participantJoined',
|
||||
'participant-kicked-out': 'participantKickedOut',
|
||||
|
|
|
@ -292,17 +292,6 @@ UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
|
|||
// Used by torture.
|
||||
UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
|
||||
|
||||
/**
|
||||
* Updates the displayed avatar for participant.
|
||||
*
|
||||
* @param {string} id - User id whose avatar should be updated.
|
||||
* @param {string} avatarURL - The URL to avatar image to display.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.refreshAvatarDisplay = function(id) {
|
||||
VideoLayout.changeUserAvatar(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that connection failed.
|
||||
* @param {string} stropheErrorMsg raw Strophe error message
|
||||
|
|
|
@ -139,12 +139,6 @@ const VideoLayout = {
|
|||
}
|
||||
},
|
||||
|
||||
changeUserAvatar(id, avatarUrl) {
|
||||
if (this.isCurrentlyOnLarge(id)) {
|
||||
largeVideo.updateAvatar(avatarUrl);
|
||||
}
|
||||
},
|
||||
|
||||
isLargeVideoVisible() {
|
||||
return this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE);
|
||||
},
|
||||
|
|
|
@ -8,10 +8,9 @@ import {
|
|||
createShortcutEvent,
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import { toggleDialog } from '../../react/features/base/dialog';
|
||||
import { clickOnVideo } from '../../react/features/filmstrip/actions';
|
||||
import { KeyboardShortcutsDialog }
|
||||
from '../../react/features/keyboard-shortcuts';
|
||||
import { openSettingsDialog } from '../../react/features/settings/actions';
|
||||
import { SETTINGS_TABS } from '../../react/features/settings/constants';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
|
@ -120,15 +119,17 @@ const KeyboardShortcut = {
|
|||
return jitsiLocalStorage.getItem(_enableShortcutsKey) === 'false' ? false : true;
|
||||
},
|
||||
|
||||
getShortcutsDescriptions() {
|
||||
return _shortcutsHelp;
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the {@KeyboardShortcutsDialog} dialog.
|
||||
* Opens the {@SettingsDialog} dialog on the Shortcuts page.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
openDialog() {
|
||||
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
||||
shortcutDescriptions: _shortcutsHelp
|
||||
}));
|
||||
APP.store.dispatch(openSettingsDialog(SETTINGS_TABS.SHORTCUTS, false));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"@types/amplitude-js": "8.16.2",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@types/w3c-web-hid": "1.0.3",
|
||||
"@vladmandic/human": "2.6.5",
|
||||
"@vladmandic/human-models": "2.5.9",
|
||||
"@xmldom/xmldom": "0.7.9",
|
||||
|
@ -71,7 +72,7 @@
|
|||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1589.0.0+d43c349d/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
@ -110,7 +111,7 @@
|
|||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "106.0.5",
|
||||
"react-native-webrtc": "106.0.6",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
|
@ -157,7 +158,7 @@
|
|||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
"css-loader": "3.6.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint": "8.35.0",
|
||||
"eslint-plugin-flowtype": "8.0.3",
|
||||
"eslint-plugin-import": "2.25.2",
|
||||
"eslint-plugin-jsdoc": "37.0.3",
|
||||
|
@ -3249,15 +3250,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
|
||||
"integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz",
|
||||
"integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^9.4.0",
|
||||
"globals": "^13.15.0",
|
||||
"globals": "^13.19.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@ -3278,9 +3279,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
|
||||
"version": "13.20.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
|
||||
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
|
@ -3332,6 +3333,15 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz",
|
||||
"integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@giphy/js-analytics": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@giphy/js-analytics/-/js-analytics-4.0.7.tgz",
|
||||
|
@ -3469,14 +3479,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.10.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
|
||||
"integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@humanwhocodes/object-schema": "^1.2.1",
|
||||
"debug": "^4.1.1",
|
||||
"minimatch": "^3.0.4"
|
||||
"minimatch": "^3.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.10.0"
|
||||
|
@ -6506,6 +6516,11 @@
|
|||
"@types/webrtc": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/w3c-web-hid": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/w3c-web-hid/-/w3c-web-hid-1.0.3.tgz",
|
||||
"integrity": "sha512-eTQRkPd2JukZfS9+kRtrBAaTCCb6waGh5X8BJHmH1MiVQPLMYwm4+EvhwFfOo9SDna15o9dFAwmWwN6r/YM53A=="
|
||||
},
|
||||
"node_modules/@types/webgl-ext": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
|
||||
|
@ -9879,14 +9894,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.25.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz",
|
||||
"integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz",
|
||||
"integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^1.3.3",
|
||||
"@humanwhocodes/config-array": "^0.10.5",
|
||||
"@eslint/eslintrc": "^2.0.0",
|
||||
"@eslint/js": "8.35.0",
|
||||
"@humanwhocodes/config-array": "^0.11.8",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.2",
|
||||
|
@ -9897,19 +9914,19 @@
|
|||
"eslint-utils": "^3.0.0",
|
||||
"eslint-visitor-keys": "^3.3.0",
|
||||
"espree": "^9.4.0",
|
||||
"esquery": "^1.4.0",
|
||||
"esquery": "^1.4.2",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"file-entry-cache": "^6.0.1",
|
||||
"find-up": "^5.0.0",
|
||||
"glob-parent": "^6.0.1",
|
||||
"globals": "^13.15.0",
|
||||
"globby": "^11.1.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"globals": "^13.19.0",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"is-path-inside": "^3.0.3",
|
||||
"js-sdsl": "^4.1.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
|
@ -10318,9 +10335,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/globals": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
|
||||
"version": "13.20.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
|
||||
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
|
@ -10448,9 +10465,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
|
||||
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
|
||||
"integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.8.0",
|
||||
|
@ -10486,9 +10503,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/esquery": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
|
||||
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz",
|
||||
"integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.1.0"
|
||||
|
@ -13399,8 +13416,8 @@
|
|||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1589.0.0+d43c349d/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-6QuR109o4sq24c9EU73NGLWAdJO+piiEylsqtmOL/B+I2GMTFeIras0tMOl6eQpncpZS5nD9gqiJmTNDnZqWbw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
|
@ -16391,9 +16408,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-native-webrtc": {
|
||||
"version": "106.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.5.tgz",
|
||||
"integrity": "sha512-EINzYpTZh6zXb2lcGH13Ieli1ur3M1FaT8R8WMqfUZEW8/y0WV6yBeQQVz55OA4LtWnBUX0RZyaYQ4aZN4e1Sw==",
|
||||
"version": "106.0.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.6.tgz",
|
||||
"integrity": "sha512-mxRqR/sNZfVnbTM8cd90Y+A23H53jvZ/j0W7MSSFAbsQrj9Jdew1+7tJVTcBieF1S4ytTLI/R95scOQ4+qeE2Q==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"adm-zip": "0.5.9",
|
||||
|
@ -22625,15 +22642,15 @@
|
|||
}
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
|
||||
"integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.0.tgz",
|
||||
"integrity": "sha512-fluIaaV+GyV24CCu/ggiHdV+j4RNh85yQnAYS/G2mZODZgGmmlrgCydjUcV3YvxCm9x8nMAfThsqTni4KiXT4A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.4",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^9.4.0",
|
||||
"globals": "^13.15.0",
|
||||
"globals": "^13.19.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
|
@ -22648,9 +22665,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
|
||||
"version": "13.20.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
|
||||
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
|
@ -22683,6 +22700,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@eslint/js": {
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.35.0.tgz",
|
||||
"integrity": "sha512-JXdzbRiWclLVoD8sNUjR443VVlYqiYmDVT6rGUEIEHU5YJW0gaVZwV2xgM7D4arkvASqD0IlLUVjHiFuxaftRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@giphy/js-analytics": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@giphy/js-analytics/-/js-analytics-4.0.7.tgz",
|
||||
|
@ -22804,14 +22827,14 @@
|
|||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.10.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz",
|
||||
"integrity": "sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==",
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
"integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@humanwhocodes/object-schema": "^1.2.1",
|
||||
"debug": "^4.1.1",
|
||||
"minimatch": "^3.0.4"
|
||||
"minimatch": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/module-importer": {
|
||||
|
@ -25057,6 +25080,11 @@
|
|||
"@types/webrtc": "*"
|
||||
}
|
||||
},
|
||||
"@types/w3c-web-hid": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/w3c-web-hid/-/w3c-web-hid-1.0.3.tgz",
|
||||
"integrity": "sha512-eTQRkPd2JukZfS9+kRtrBAaTCCb6waGh5X8BJHmH1MiVQPLMYwm4+EvhwFfOo9SDna15o9dFAwmWwN6r/YM53A=="
|
||||
},
|
||||
"@types/webgl-ext": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
|
||||
|
@ -27593,14 +27621,16 @@
|
|||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.25.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.25.0.tgz",
|
||||
"integrity": "sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==",
|
||||
"version": "8.35.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.35.0.tgz",
|
||||
"integrity": "sha512-BxAf1fVL7w+JLRQhWl2pzGeSiGqbWumV4WNvc9Rhp6tiCtm4oHnyPBSEtMGZwrQgudFQ+otqzWoPB7x+hxoWsw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint/eslintrc": "^1.3.3",
|
||||
"@humanwhocodes/config-array": "^0.10.5",
|
||||
"@eslint/eslintrc": "^2.0.0",
|
||||
"@eslint/js": "8.35.0",
|
||||
"@humanwhocodes/config-array": "^0.11.8",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
"ajv": "^6.10.0",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.2",
|
||||
|
@ -27611,19 +27641,19 @@
|
|||
"eslint-utils": "^3.0.0",
|
||||
"eslint-visitor-keys": "^3.3.0",
|
||||
"espree": "^9.4.0",
|
||||
"esquery": "^1.4.0",
|
||||
"esquery": "^1.4.2",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"file-entry-cache": "^6.0.1",
|
||||
"find-up": "^5.0.0",
|
||||
"glob-parent": "^6.0.1",
|
||||
"globals": "^13.15.0",
|
||||
"globby": "^11.1.0",
|
||||
"glob-parent": "^6.0.2",
|
||||
"globals": "^13.19.0",
|
||||
"grapheme-splitter": "^1.0.4",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-glob": "^4.0.0",
|
||||
"is-path-inside": "^3.0.3",
|
||||
"js-sdsl": "^4.1.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
|
@ -27705,9 +27735,9 @@
|
|||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
|
||||
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
|
||||
"version": "13.20.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
|
||||
"integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
|
@ -28019,9 +28049,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"espree": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
|
||||
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
|
||||
"version": "9.4.1",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
|
||||
"integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^8.8.0",
|
||||
|
@ -28043,9 +28073,9 @@
|
|||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"esquery": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
|
||||
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz",
|
||||
"integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estraverse": "^5.1.0"
|
||||
|
@ -30278,8 +30308,8 @@
|
|||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1589.0.0+d43c349d/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-6QuR109o4sq24c9EU73NGLWAdJO+piiEylsqtmOL/B+I2GMTFeIras0tMOl6eQpncpZS5nD9gqiJmTNDnZqWbw==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
|
@ -32562,9 +32592,9 @@
|
|||
}
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "106.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.5.tgz",
|
||||
"integrity": "sha512-EINzYpTZh6zXb2lcGH13Ieli1ur3M1FaT8R8WMqfUZEW8/y0WV6yBeQQVz55OA4LtWnBUX0RZyaYQ4aZN4e1Sw==",
|
||||
"version": "106.0.6",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-106.0.6.tgz",
|
||||
"integrity": "sha512-mxRqR/sNZfVnbTM8cd90Y+A23H53jvZ/j0W7MSSFAbsQrj9Jdew1+7tJVTcBieF1S4ytTLI/R95scOQ4+qeE2Q==",
|
||||
"requires": {
|
||||
"adm-zip": "0.5.9",
|
||||
"base64-js": "1.5.1",
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"@types/amplitude-js": "8.16.2",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
"@types/w3c-image-capture": "1.0.6",
|
||||
"@types/w3c-web-hid": "1.0.3",
|
||||
"@vladmandic/human": "2.6.5",
|
||||
"@vladmandic/human-models": "2.5.9",
|
||||
"@xmldom/xmldom": "0.7.9",
|
||||
|
@ -76,7 +77,7 @@
|
|||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1589.0.0+d43c349d/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
@ -115,7 +116,7 @@
|
|||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "106.0.5",
|
||||
"react-native-webrtc": "106.0.6",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
|
@ -162,7 +163,7 @@
|
|||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
"css-loader": "3.6.0",
|
||||
"eslint": "8.25.0",
|
||||
"eslint": "8.35.0",
|
||||
"eslint-plugin-flowtype": "8.0.3",
|
||||
"eslint-plugin-import": "2.25.2",
|
||||
"eslint-plugin-jsdoc": "37.0.3",
|
||||
|
|
|
@ -199,7 +199,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
color = { getAvatarColor(displayName, customAvatarBackgrounds) }
|
||||
id = 'avatar'
|
||||
initials = { getInitials(displayName) }
|
||||
url = { displayName ? null : avatarURL } />)
|
||||
url = { avatarURL } />)
|
||||
</div>
|
||||
<div
|
||||
className = 'displayname'
|
||||
|
|
|
@ -48,5 +48,6 @@ import '../transcribing/middleware';
|
|||
import '../video-layout/middleware';
|
||||
import '../video-quality/middleware';
|
||||
import '../videosipgw/middleware';
|
||||
import '../visitors/middleware';
|
||||
|
||||
import './middleware';
|
||||
|
|
|
@ -5,7 +5,6 @@ import '../base/media/middleware';
|
|||
import '../dynamic-branding/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
import '../keyboard-shortcuts/middleware';
|
||||
import '../no-audio-signal/middleware';
|
||||
import '../notifications/middleware';
|
||||
import '../noise-detection/middleware';
|
||||
|
@ -15,6 +14,7 @@ import '../prejoin/middleware';
|
|||
import '../remote-control/middleware';
|
||||
import '../screen-share/middleware';
|
||||
import '../shared-video/middleware';
|
||||
import '../web-hid/middleware';
|
||||
import '../settings/middleware';
|
||||
import '../talk-while-muted/middleware';
|
||||
import '../toolbox/middleware';
|
||||
|
|
|
@ -56,3 +56,4 @@ import '../transcribing/reducer';
|
|||
import '../video-layout/reducer';
|
||||
import '../video-quality/reducer';
|
||||
import '../videosipgw/reducer';
|
||||
import '../visitors/reducer';
|
||||
|
|
|
@ -16,5 +16,6 @@ import '../screenshot-capture/reducer';
|
|||
import '../talk-while-muted/reducer';
|
||||
import '../virtual-background/reducer';
|
||||
import '../whiteboard/reducer';
|
||||
import '../web-hid/reducer';
|
||||
|
||||
import './reducers.any';
|
||||
|
|
|
@ -76,6 +76,8 @@ import { IVideoLayoutState } from '../video-layout/reducer';
|
|||
import { IVideoQualityPersistedState, IVideoQualityState } from '../video-quality/reducer';
|
||||
import { IVideoSipGW } from '../videosipgw/reducer';
|
||||
import { IVirtualBackground } from '../virtual-background/reducer';
|
||||
import { IVisitorsState } from '../visitors/reducer';
|
||||
import { IWebHid } from '../web-hid/reducer';
|
||||
import { IWhiteboardState } from '../whiteboard/reducer';
|
||||
|
||||
export interface IStore {
|
||||
|
@ -163,6 +165,8 @@ export interface IReduxState {
|
|||
'features/video-quality-persistent-storage': IVideoQualityPersistedState;
|
||||
'features/videosipgw': IVideoSipGW;
|
||||
'features/virtual-background': IVirtualBackground;
|
||||
'features/visitors': IVisitorsState;
|
||||
'features/web-hid': IWebHid;
|
||||
'features/whiteboard': IWhiteboardState;
|
||||
}
|
||||
|
||||
|
|
|
@ -98,7 +98,7 @@ function _upgradeRoleFinished(
|
|||
name: authenticationError || connectionError,
|
||||
...other
|
||||
};
|
||||
progress = authenticationError ? 0.5 : 0;
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -207,7 +207,7 @@ class LoginDialog extends Component<IProps, IState> {
|
|||
let messageKey;
|
||||
|
||||
if (progress && progress < 1) {
|
||||
messageKey = t('connection.FETCH_SESSION_ID');
|
||||
messageKey = 'connection.FETCH_SESSION_ID';
|
||||
} else if (error) {
|
||||
const { name } = error;
|
||||
|
||||
|
@ -218,14 +218,14 @@ class LoginDialog extends Component<IProps, IState> {
|
|||
&& credentials.jid === toJid(username, configHosts ?? { authdomain: '',
|
||||
domain: '' })
|
||||
&& credentials.password === password) {
|
||||
messageKey = t('dialog.incorrectPassword');
|
||||
messageKey = 'dialog.incorrectPassword';
|
||||
}
|
||||
} else if (name) {
|
||||
messageKey = t('dialog.connectErrorWithMsg');
|
||||
messageKey = 'dialog.connectErrorWithMsg';
|
||||
messageOptions.msg = `${name} ${error.message}`;
|
||||
}
|
||||
} else if (connecting) {
|
||||
messageKey = t('connection.CONNECTING');
|
||||
messageKey = 'connection.CONNECTING';
|
||||
}
|
||||
|
||||
if (messageKey) {
|
||||
|
|
|
@ -88,7 +88,7 @@ const useStyles = makeStyles()(theme => {
|
|||
boxShadow: 'inset 0px -1px 0px rgba(255, 255, 255, 0.15)',
|
||||
minHeight: '40px',
|
||||
|
||||
'&:hover': {
|
||||
'&:hover, &:focus-within': {
|
||||
backgroundColor: theme.palette.ui02,
|
||||
|
||||
'& .indicators': {
|
||||
|
@ -97,6 +97,8 @@ const useStyles = makeStyles()(theme => {
|
|||
|
||||
'& .actions': {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
top: 'auto',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
}
|
||||
|
@ -154,7 +156,8 @@ const useStyles = makeStyles()(theme => {
|
|||
},
|
||||
|
||||
actionsContainer: {
|
||||
display: 'none',
|
||||
position: 'absolute',
|
||||
top: '-1000px',
|
||||
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
|
||||
backgroundColor: theme.palette.ui02
|
||||
},
|
||||
|
|
|
@ -98,7 +98,7 @@ export function overwriteConfig(config: Object) {
|
|||
* base/config.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setConfig(config: Object = {}) {
|
||||
export function setConfig(config: IConfig = {}) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { locationURL } = getState()['features/base/connection'];
|
||||
|
||||
|
@ -119,6 +119,26 @@ export function setConfig(config: Object = {}) {
|
|||
window.interfaceConfig,
|
||||
locationURL);
|
||||
|
||||
let { bosh } = config;
|
||||
|
||||
if (bosh) {
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL?.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL?.href);
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
config.bosh = bosh;
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_CONFIG,
|
||||
config
|
||||
|
|
|
@ -388,6 +388,11 @@ export interface IConfig {
|
|||
lastNLimits?: {
|
||||
[key: number]: number;
|
||||
};
|
||||
legalUrls?: {
|
||||
helpCentre: string;
|
||||
privacy: string;
|
||||
terms: string;
|
||||
};
|
||||
liveStreaming?: {
|
||||
dataPrivacyLink?: string;
|
||||
enabled?: boolean;
|
||||
|
|
|
@ -61,6 +61,11 @@ export const PREMEETING_BUTTONS = [ 'microphone', 'camera', 'select-background',
|
|||
*/
|
||||
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
|
||||
|
||||
/**
|
||||
* The toolbar buttons to show when in visitors mode.
|
||||
*/
|
||||
export const VISITORS_MODE_BUTTONS = [ 'hangup', 'tileview' ];
|
||||
|
||||
/**
|
||||
* The set of feature flags.
|
||||
*
|
||||
|
@ -70,3 +75,18 @@ export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-bac
|
|||
export const FEATURE_FLAGS = {
|
||||
SSRC_REWRITING: 'ssrcRewritingEnabled'
|
||||
};
|
||||
|
||||
/**
|
||||
* The URL at which the terms (of service/use) are available to the user.
|
||||
*/
|
||||
export const DEFAULT_TERMS_URL = 'https://jitsi.org/meet/terms';
|
||||
|
||||
/**
|
||||
* The URL at which the privacy policy is available to the user.
|
||||
*/
|
||||
export const DEFAULT_PRIVACY_URL = 'https://jitsi.org/meet/privacy';
|
||||
|
||||
/**
|
||||
* The URL at which the help centre is available to the user.
|
||||
*/
|
||||
export const DEFAULT_HELP_CENTRE_URL = 'https://web-cdn.jitsi.net/faq/meet-faq.html';
|
||||
|
|
|
@ -11,7 +11,13 @@ import { parseURLParams } from '../util/parseURLParams';
|
|||
|
||||
import { IConfig } from './configType';
|
||||
import CONFIG_WHITELIST from './configWhitelist';
|
||||
import { FEATURE_FLAGS, _CONFIG_STORE_PREFIX } from './constants';
|
||||
import {
|
||||
DEFAULT_HELP_CENTRE_URL,
|
||||
DEFAULT_PRIVACY_URL,
|
||||
DEFAULT_TERMS_URL,
|
||||
FEATURE_FLAGS,
|
||||
_CONFIG_STORE_PREFIX
|
||||
} from './constants';
|
||||
import INTERFACE_CONFIG_WHITELIST from './interfaceConfigWhitelist';
|
||||
import logger from './logger';
|
||||
|
||||
|
@ -326,3 +332,24 @@ export function getDialOutUrl(state: IReduxState) {
|
|||
export function getSecurityUiConfig(state: IReduxState) {
|
||||
return state['features/base/config']?.securityUi || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the terms, privacy and help centre URL's.
|
||||
*
|
||||
* @param {IReduxState} state - The state of the application.
|
||||
* @returns {{
|
||||
* privacy: string,
|
||||
* helpCentre: string,
|
||||
* terms: string
|
||||
* }}
|
||||
*/
|
||||
export function getLegalUrls(state: IReduxState) {
|
||||
const helpCentreURL = state['features/base/config']?.helpCentreURL;
|
||||
const configLegalUrls = state['features/base/config']?.legalUrls;
|
||||
|
||||
return {
|
||||
privacy: configLegalUrls?.privacy || DEFAULT_PRIVACY_URL,
|
||||
helpCentre: helpCentreURL || configLegalUrls?.helpCentre || DEFAULT_HELP_CENTRE_URL,
|
||||
terms: configLegalUrls?.terms || DEFAULT_TERMS_URL
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,8 +3,7 @@ import _ from 'lodash';
|
|||
import { IReduxState } from '../../app/types';
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName,
|
||||
parseURIString
|
||||
getBackendSafeRoomName
|
||||
} from '../util/uri';
|
||||
|
||||
import {
|
||||
|
@ -145,7 +144,8 @@ export function constructOptions(state: IReduxState) {
|
|||
// redux store.
|
||||
const options = _.cloneDeep(state['features/base/config']);
|
||||
|
||||
let { bosh, websocket } = options;
|
||||
const { bosh } = options;
|
||||
let { websocket } = options;
|
||||
|
||||
// TESTING: Only enable WebSocket for some percentage of users.
|
||||
if (websocket && navigator.product === 'ReactNative') {
|
||||
|
@ -154,25 +154,6 @@ export function constructOptions(state: IReduxState) {
|
|||
}
|
||||
}
|
||||
|
||||
// Normalize the BOSH URL.
|
||||
if (bosh && !websocket) {
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
if (bosh.startsWith('//')) {
|
||||
// By default our config.js doesn't include the protocol.
|
||||
bosh = `${locationURL?.protocol}${bosh}`;
|
||||
} else if (bosh.startsWith('/')) {
|
||||
// Handle relative URLs, which won't work on mobile.
|
||||
const {
|
||||
protocol,
|
||||
host,
|
||||
contextRoot
|
||||
} = parseURIString(locationURL?.href);
|
||||
|
||||
bosh = `${protocol}//${host}${contextRoot || '/'}${bosh.substr(1)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket is preferred over BOSH.
|
||||
const serviceUrl = websocket || bosh;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ type Props = {
|
|||
/**
|
||||
* Dialog title.
|
||||
*/
|
||||
title?: string,
|
||||
title?: string
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,6 +11,7 @@ import { IReduxState } from '../../../../app/types';
|
|||
import { translate } from '../../../i18n/functions';
|
||||
import { isFatalJitsiConnectionError } from '../../../lib-jitsi-meet/functions.native';
|
||||
import { connect } from '../../../redux/functions';
|
||||
import { hideDialog } from '../../actions';
|
||||
// @ts-ignore
|
||||
import logger from '../../logger';
|
||||
|
||||
|
@ -33,10 +34,7 @@ interface IPageReloadDialogProps extends WithTranslation {
|
|||
* {@link PageReloadDialog}.
|
||||
*/
|
||||
interface IPageReloadDialogState {
|
||||
message: string;
|
||||
timeLeft: number;
|
||||
timeoutSeconds: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -48,6 +46,7 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
|
||||
// @ts-ignore
|
||||
_interval: IntervalID;
|
||||
_timeoutSeconds: number;
|
||||
|
||||
/**
|
||||
* Initializes a new PageReloadOverlay instance.
|
||||
|
@ -59,27 +58,15 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
constructor(props: IPageReloadDialogProps) {
|
||||
super(props);
|
||||
|
||||
const timeoutSeconds = 10 + randomInt(0, 20);
|
||||
|
||||
let message, title;
|
||||
|
||||
if (this.props.isNetworkFailure) {
|
||||
title = 'dialog.conferenceDisconnectTitle';
|
||||
message = 'dialog.conferenceDisconnectMsg';
|
||||
} else {
|
||||
title = 'dialog.conferenceReloadTitle';
|
||||
message = 'dialog.conferenceReloadMsg';
|
||||
}
|
||||
this._timeoutSeconds = 10 + randomInt(0, 20);
|
||||
|
||||
this.state = {
|
||||
message,
|
||||
timeLeft: timeoutSeconds,
|
||||
timeoutSeconds,
|
||||
title
|
||||
timeLeft: this._timeoutSeconds
|
||||
};
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onReloadNow = this._onReloadNow.bind(this);
|
||||
this._onReconnecting = this._onReconnecting.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,32 +76,14 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
const { timeLeft } = this.state;
|
||||
|
||||
logger.info(
|
||||
`The conference will be reloaded after ${
|
||||
this.state.timeoutSeconds} seconds.`);
|
||||
`The conference will be reloaded after ${timeLeft} seconds.`
|
||||
);
|
||||
|
||||
this._interval
|
||||
= setInterval(
|
||||
() => {
|
||||
if (timeLeft === 0) {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
|
||||
dispatch(reloadNow());
|
||||
} else {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
timeLeft: prevState.timeLeft - 1
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
1000);
|
||||
this._interval = setInterval(() =>
|
||||
this._onReconnecting(), 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,12 +107,37 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
* @returns {boolean}
|
||||
*/
|
||||
_onCancel() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
dispatch(appNavigate(undefined));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles automatic reconnection.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onReconnecting() {
|
||||
const { dispatch } = this.props;
|
||||
const { timeLeft } = this.state;
|
||||
|
||||
if (timeLeft === 0) {
|
||||
if (this._interval) {
|
||||
dispatch(hideDialog());
|
||||
this._onReloadNow();
|
||||
this._interval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({
|
||||
timeLeft: timeLeft - 1
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clicking on the "Reload Now" button. It will navigate to the same
|
||||
* conference URL as before immediately, without waiting for the timer to
|
||||
|
@ -153,8 +147,10 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
* @returns {boolean}
|
||||
*/
|
||||
_onReloadNow() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(reloadNow());
|
||||
dispatch(reloadNow());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -166,8 +162,18 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { message, timeLeft, title } = this.state;
|
||||
const { isNetworkFailure, t } = this.props;
|
||||
const { timeLeft } = this.state;
|
||||
|
||||
let message, title;
|
||||
|
||||
if (isNetworkFailure) {
|
||||
title = 'dialog.conferenceDisconnectTitle';
|
||||
message = 'dialog.conferenceDisconnectMsg';
|
||||
} else {
|
||||
title = 'dialog.conferenceReloadTitle';
|
||||
message = 'dialog.conferenceReloadMsg';
|
||||
}
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
|
@ -187,9 +193,8 @@ class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDial
|
|||
* @param {Object} state - The redux state.
|
||||
* @protected
|
||||
* @returns {{
|
||||
* message: string,
|
||||
* reason: string,
|
||||
* title: string
|
||||
* isNetworkFailure: boolean,
|
||||
* reason: string
|
||||
* }}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.7158 3.03843C12.4964 2.33696 11.5037 2.33696 11.2842 3.03843L9.54263 8.60636C9.44381 8.92229 9.14957 9.13606 8.81858 9.13242L2.98497 9.0682C2.25003 9.06011 1.94325 10.0043 2.54258 10.4297L7.29982 13.8067C7.56974 13.9983 7.68213 14.3442 7.57638 14.6579L5.71262 20.1861C5.47782 20.8826 6.28099 21.4661 6.87081 21.0276L11.5525 17.5467C11.8182 17.3492 12.1819 17.3492 12.4475 17.5467L17.1293 21.0276C17.7191 21.4661 18.5223 20.8826 18.2875 20.1861L16.4237 14.6579C16.3179 14.3442 16.4303 13.9983 16.7003 13.8067L21.4575 10.4297C22.0568 10.0043 21.75 9.06011 21.0151 9.0682L15.1815 9.13242C14.8505 9.13606 14.5563 8.92228 14.4574 8.60636L12.7158 3.03843Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 814 B |
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5.77465L10.9742 9.05416C10.6778 10.0019 9.79505 10.6433 8.80206 10.6323L5.36607 10.5945L8.16808 12.5835C8.97785 13.1583 9.31502 14.1961 8.99778 15.1371L7.90002 18.3932L10.6576 16.343C11.4545 15.7505 12.5456 15.7505 13.3425 16.343L16.1001 18.3932L15.0023 15.1371C14.6851 14.1961 15.0222 13.1583 15.832 12.5835L18.634 10.5945L15.198 10.6323C14.205 10.6433 13.3223 10.0019 13.0258 9.05416L12 5.77465ZM12.7158 3.03843C12.4964 2.33696 11.5037 2.33696 11.2842 3.03843L9.54263 8.60636C9.44381 8.92229 9.14957 9.13606 8.81858 9.13242L2.98497 9.0682C2.25003 9.06011 1.94325 10.0043 2.54258 10.4297L7.29982 13.8067C7.56974 13.9983 7.68213 14.3442 7.57638 14.6579L5.71262 20.1861C5.47782 20.8826 6.28099 21.4661 6.87081 21.0276L11.5525 17.5467C11.8182 17.3492 12.1819 17.3492 12.4475 17.5467L17.1293 21.0276C17.7191 21.4661 18.5223 20.8826 18.2875 20.1861L16.4237 14.6579C16.3179 14.3442 16.4303 13.9983 16.7003 13.8067L21.4575 10.4297C22.0568 10.0043 21.75 9.06011 21.0151 9.0682L15.1815 9.13242C14.8505 9.13606 14.5563 8.92228 14.4574 8.60636L12.7158 3.03843Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,6 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.1602 8.24439C13.3281 8.16225 14.25 7.18879 14.25 6C14.25 4.75736 13.2426 3.75 12 3.75C10.7574 3.75 9.75 4.75736 9.75 6C9.75 7.18151 10.6607 8.15032 11.8184 8.24278C11.5197 8.21896 11.2375 8.13687 10.9831 8.00775C9.85816 9.83693 8.19346 10.3318 6.74461 10.3424C6.66364 9.17333 5.68961 8.25 4.5 8.25C3.25736 8.25 2.25 9.25736 2.25 10.5C2.25 11.7426 3.25736 12.75 4.5 12.75C5.32135 12.75 6.03995 12.31 6.43279 11.6528C6.39557 11.715 6.35542 11.7754 6.31253 11.8336C6.67901 11.8506 7.06503 11.8447 7.4618 11.805C8.64456 11.6868 9.95784 11.2623 11.0918 10.2228C11.4736 9.87283 11.8205 9.46641 12.1289 9.0006C12.4727 9.47803 12.8468 9.89069 13.2474 10.2432C14.3929 11.2513 15.6592 11.6829 16.8118 11.8042C17.1152 11.8362 17.4101 11.8467 17.6932 11.8412C17.6739 11.8152 17.6551 11.7887 17.6369 11.7619C18.0415 12.3582 18.725 12.75 19.5 12.75C20.7426 12.75 21.75 11.7426 21.75 10.5C21.75 9.25736 20.7426 8.25 19.5 8.25C18.3129 8.25 17.3405 9.16938 17.256 10.335C15.9245 10.2658 14.3912 9.7053 13.1957 7.90649C12.8918 8.09752 12.5389 8.21778 12.1602 8.24439ZM12 6.75C12.4142 6.75 12.75 6.41421 12.75 6C12.75 5.58579 12.4142 5.25 12 5.25C11.5858 5.25 11.25 5.58579 11.25 6C11.25 6.41421 11.5858 6.75 12 6.75ZM20.25 10.5C20.25 10.9142 19.9142 11.25 19.5 11.25C19.0858 11.25 18.75 10.9142 18.75 10.5C18.75 10.0858 19.0858 9.75 19.5 9.75C19.9142 9.75 20.25 10.0858 20.25 10.5ZM4.5 11.25C4.91421 11.25 5.25 10.9142 5.25 10.5C5.25 10.0858 4.91421 9.75 4.5 9.75C4.08579 9.75 3.75 10.0858 3.75 10.5C3.75 10.9142 4.08579 11.25 4.5 11.25Z" fill="white"/>
|
||||
<path d="M17.9485 12.1296C18.3135 12.4773 18.7952 12.7036 19.3289 12.7437L19.2591 12.9706C19.1623 13.2853 18.8715 13.5 18.5423 13.5C18.0377 13.5 17.677 13.0117 17.8254 12.5294L17.9485 12.1296Z" fill="white"/>
|
||||
<path d="M12.75 18.0001C13.1642 18.0001 13.5 18.3359 13.5 18.7501C13.5 19.1643 13.1642 19.5001 12.75 19.5001H7.85792C7.19941 19.5001 6.61791 19.0706 6.42425 18.4412L4.6712 12.7437C5.20487 12.7036 5.6866 12.4773 6.05164 12.1296L7.85792 18.0001H12.75Z" fill="white"/>
|
||||
<path d="M11.25 18.0002C10.8358 18.0002 10.5 18.336 10.5 18.7502C10.5 19.1644 10.8358 19.5002 11.25 19.5002H16.1421C16.8006 19.5002 17.3821 19.0707 17.5757 18.4413L19.3289 12.7437C18.7952 12.7036 18.3135 12.4773 17.9485 12.1296L16.1421 18.0002H11.25Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
|
@ -39,10 +39,13 @@ export { default as IconExclamationSolid } from './exclamation-solid.svg';
|
|||
export { default as IconExclamationTriangle } from './exclamation-triangle.svg';
|
||||
export { default as IconExitFullscreen } from './exit-fullscreen.svg';
|
||||
export { default as IconFaceSmile } from './face-smile.svg';
|
||||
export { default as IconFavorite } from './favorite.svg';
|
||||
export { default as IconFavoriteSolid } from './favorite-solid.svg';
|
||||
export { default as IconFeedback } from './feedback.svg';
|
||||
export { default as IconGear } from './gear.svg';
|
||||
export { default as IconGoogle } from './google.svg';
|
||||
export { default as IconHangup } from './hangup.svg';
|
||||
export { default as IconHost } from './host.svg';
|
||||
export { default as IconHelp } from './help.svg';
|
||||
export { default as IconHighlight } from './highlight.svg';
|
||||
export { default as IconImage } from './image.svg';
|
||||
|
@ -81,6 +84,7 @@ export { default as IconSend } from './send.svg';
|
|||
export { default as IconShare } from './share.svg';
|
||||
export { default as IconShareDoc } from './share-doc.svg';
|
||||
export { default as IconShortcuts } from './shortcuts.svg';
|
||||
export { default as IconSip } from './sip.svg';
|
||||
export { default as IconSites } from './sites.svg';
|
||||
export { default as IconStop } from './stop.svg';
|
||||
export { default as IconStopScreenshare } from './stop-screenshare.svg';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="20" height="14" viewBox="0 0 20 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.41201 1.90767C3.50689 3.37865 5.24438 7.52348 7.36096 9.64007C7.36096 9.64004 7.36175 9.63923 7.36329 9.63765C9.48 11.7541 13.6215 13.4932 15.0923 12.5882C16.1022 11.9668 16.0078 9.51337 15.2427 8.76783C14.7369 8.2749 13.1882 8.01994 12.5497 8.14762C12.3496 8.18763 11.7907 8.76515 11.4793 9.08696C11.4184 9.14994 11.3669 9.20313 11.3295 9.24058C11.1007 9.46937 9.63912 8.22168 9.20588 7.78845L7.60102 9.39838C8.10053 8.89635 9.2057 7.78701 9.2057 7.78701C8.77247 7.35377 7.53081 5.89935 7.7596 5.67056C7.79705 5.63311 7.85024 5.58164 7.91322 5.5207C8.23503 5.20928 8.81255 4.65041 8.85256 4.45033C8.98024 3.81178 8.72528 2.26311 8.23236 1.75727C7.48681 0.992193 5.03342 0.897765 4.41201 1.90767Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 868 B |
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
|
@ -92,13 +92,27 @@ const Label = ({
|
|||
}: IProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
const onKeyPress = useCallback(event => {
|
||||
if (!onClick) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
onClick();
|
||||
}
|
||||
}, [ onClick ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cx(classes.label, onClick && classes.clickable,
|
||||
color && classes[color], className
|
||||
) }
|
||||
id = { id }
|
||||
onClick = { onClick }>
|
||||
onClick = { onClick }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = { onClick ? 'button' : undefined }
|
||||
tabIndex = { onClick ? 0 : undefined }>
|
||||
{icon && <Icon
|
||||
color = { iconColor }
|
||||
size = '16'
|
||||
|
|
|
@ -88,7 +88,11 @@ const _updateLastN = debounce(({ dispatch, getState }: IStore) => {
|
|||
lastNSelected = 1;
|
||||
}
|
||||
|
||||
const { lastN } = state['features/base/lastn'];
|
||||
|
||||
if (lastN !== lastNSelected) {
|
||||
dispatch(setLastN(lastNSelected));
|
||||
}
|
||||
}, 1000); /* Don't send this more often than once a second. */
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,11 @@ export type MediaType = 'audio' | 'video' | 'screenshare';
|
|||
*
|
||||
* @enum {string}
|
||||
*/
|
||||
export const MEDIA_TYPE: { [key: string]: MediaType; } = {
|
||||
export const MEDIA_TYPE: {
|
||||
AUDIO: MediaType;
|
||||
SCREENSHARE: MediaType;
|
||||
VIDEO: MediaType;
|
||||
} = {
|
||||
AUDIO: 'audio',
|
||||
SCREENSHARE: 'screenshare',
|
||||
VIDEO: 'video'
|
||||
|
|
|
@ -601,7 +601,7 @@ export function getDominantSpeakerParticipant(stateful: IStateful) {
|
|||
export function isEveryoneModerator(stateful: IStateful) {
|
||||
const state = toState(stateful)['features/base/participants'];
|
||||
|
||||
return state.everyoneIsModerator === true;
|
||||
return state.numberOfNonModeratorParticipants === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -429,7 +429,8 @@ StateListenerRegistry.register(
|
|||
'e2ee.enabled': (participant: IJitsiParticipant, value: string) =>
|
||||
_e2eeUpdated(store, conference, participant.getId(), value),
|
||||
'features_e2ee': (participant: IJitsiParticipant, value: boolean) =>
|
||||
store.dispatch(participantUpdated({
|
||||
getParticipantById(store.getState(), participant.getId())?.e2eeSupported !== value
|
||||
&& store.dispatch(participantUpdated({
|
||||
conference,
|
||||
id: participant.getId(),
|
||||
e2eeSupported: value
|
||||
|
@ -506,7 +507,12 @@ StateListenerRegistry.register(
|
|||
function _e2eeUpdated({ getState, dispatch }: IStore, conference: IJitsiConference,
|
||||
participantId: string, newValue: string | boolean) {
|
||||
const e2eeEnabled = newValue === 'true';
|
||||
const { e2ee = {} } = getState()['features/base/config'];
|
||||
const state = getState();
|
||||
const { e2ee = {} } = state['features/base/config'];
|
||||
|
||||
if (e2eeEnabled === getParticipantById(state, participantId)?.e2eeEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(participantUpdated({
|
||||
conference,
|
||||
|
@ -641,7 +647,6 @@ function _participantJoinedOrUpdated(store: IStore, next: Function, action: any)
|
|||
// Send an external update of the local participant's raised hand state
|
||||
// if a new raised hand state is defined in the action.
|
||||
if (typeof raisedHandTimestamp !== 'undefined') {
|
||||
|
||||
if (local) {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
const rHand = parseInt(raisedHandTimestamp, 10);
|
||||
|
@ -691,14 +696,6 @@ function _participantJoinedOrUpdated(store: IStore, next: Function, action: any)
|
|||
}
|
||||
}
|
||||
|
||||
// Notify external listeners of potential avatarURL changes.
|
||||
if (typeof APP === 'object') {
|
||||
const currentKnownId = local ? APP.conference.getMyUserId() : id;
|
||||
|
||||
// Force update of local video getting a new id.
|
||||
APP.UI.refreshAvatarDisplay(currentKnownId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -63,10 +63,12 @@ const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [
|
|||
|
||||
const DEFAULT_STATE = {
|
||||
dominantSpeaker: undefined,
|
||||
everyoneIsModerator: false,
|
||||
fakeParticipants: new Map(),
|
||||
local: undefined,
|
||||
localScreenShare: undefined,
|
||||
numberOfNonModeratorParticipants: 0,
|
||||
numberOfParticipantsDisabledE2EE: 0,
|
||||
numberOfParticipantsNotSupportingE2EE: 0,
|
||||
overwrittenNameList: {},
|
||||
pinnedParticipant: undefined,
|
||||
raisedHandsQueue: [],
|
||||
|
@ -79,10 +81,12 @@ const DEFAULT_STATE = {
|
|||
|
||||
export interface IParticipantsState {
|
||||
dominantSpeaker?: string;
|
||||
everyoneIsModerator: boolean;
|
||||
fakeParticipants: Map<string, IParticipant>;
|
||||
local?: ILocalParticipant;
|
||||
localScreenShare?: IParticipant;
|
||||
numberOfNonModeratorParticipants: number;
|
||||
numberOfParticipantsDisabledE2EE: number;
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
|
||||
|
@ -200,23 +204,30 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
}
|
||||
|
||||
let newParticipant: IParticipant | null = null;
|
||||
const oldParticipant = local || state.local?.id === id ? state.local : state.remote.get(id);
|
||||
|
||||
if (state.remote.has(id)) {
|
||||
newParticipant = _participant(state.remote.get(id), action);
|
||||
newParticipant = _participant(oldParticipant, action);
|
||||
state.remote.set(id, newParticipant);
|
||||
} else if (id === state.local?.id) {
|
||||
newParticipant = state.local = _participant(state.local, action);
|
||||
}
|
||||
|
||||
if (newParticipant) {
|
||||
|
||||
// everyoneIsModerator calculation:
|
||||
if (oldParticipant && newParticipant && !newParticipant.fakeParticipant) {
|
||||
const isModerator = isParticipantModerator(newParticipant);
|
||||
|
||||
if (state.everyoneIsModerator && !isModerator) {
|
||||
state.everyoneIsModerator = false;
|
||||
} else if (!state.everyoneIsModerator && isModerator) {
|
||||
state.everyoneIsModerator = _isEveryoneModerator(state);
|
||||
if (isParticipantModerator(oldParticipant) !== isModerator) {
|
||||
state.numberOfNonModeratorParticipants += isModerator ? -1 : 1;
|
||||
}
|
||||
|
||||
const e2eeEnabled = Boolean(newParticipant.e2eeEnabled);
|
||||
const e2eeSupported = Boolean(newParticipant.e2eeSupported);
|
||||
|
||||
if (Boolean(oldParticipant.e2eeEnabled) !== e2eeEnabled) {
|
||||
state.numberOfParticipantsDisabledE2EE += e2eeEnabled ? -1 : 1;
|
||||
}
|
||||
if (!local && Boolean(oldParticipant.e2eeSupported) !== e2eeSupported) {
|
||||
state.numberOfParticipantsNotSupportingE2EE += e2eeSupported ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,13 +278,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
state.dominantSpeaker = id;
|
||||
}
|
||||
|
||||
if (!fakeParticipant) {
|
||||
const isModerator = isParticipantModerator(participant);
|
||||
const { local, remote } = state;
|
||||
|
||||
if (state.everyoneIsModerator && !isModerator) {
|
||||
state.everyoneIsModerator = false;
|
||||
} else if (!local && remote.size === 0 && isModerator) {
|
||||
state.everyoneIsModerator = true;
|
||||
if (!isModerator) {
|
||||
state.numberOfNonModeratorParticipants += 1;
|
||||
}
|
||||
|
||||
const { e2eeEnabled, e2eeSupported } = participant as IParticipant;
|
||||
|
||||
if (!e2eeEnabled) {
|
||||
state.numberOfParticipantsDisabledE2EE += 1;
|
||||
}
|
||||
|
||||
if (!participant.local && !e2eeSupported) {
|
||||
state.numberOfParticipantsNotSupportingE2EE += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (participant.local) {
|
||||
|
@ -349,6 +369,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
pinnedParticipant
|
||||
} = state;
|
||||
let oldParticipant = remote.get(id);
|
||||
let isLocalScreenShare = false;
|
||||
|
||||
if (oldParticipant?.sources?.size) {
|
||||
const videoSources: Map<string, ISourceInfo> | undefined = oldParticipant.sources.get(MEDIA_TYPE.VIDEO);
|
||||
|
@ -373,6 +394,7 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
oldParticipant = state.local;
|
||||
delete state.local;
|
||||
} else if (localScreenShare?.id === id) {
|
||||
isLocalScreenShare = true;
|
||||
oldParticipant = state.local;
|
||||
delete state.localScreenShare;
|
||||
} else {
|
||||
|
@ -383,10 +405,6 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
state.sortedRemoteParticipants.delete(id);
|
||||
state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== id);
|
||||
|
||||
if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
|
||||
state.everyoneIsModerator = _isEveryoneModerator(state);
|
||||
}
|
||||
|
||||
if (dominantSpeaker === id) {
|
||||
state.dominantSpeaker = undefined;
|
||||
}
|
||||
|
@ -407,6 +425,22 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
|||
state.sortedRemoteVirtualScreenshareParticipants = new Map(sortedRemoteVirtualScreenshareParticipants);
|
||||
}
|
||||
|
||||
if (oldParticipant && !oldParticipant.fakeParticipant && !isLocalScreenShare) {
|
||||
const { e2eeEnabled, e2eeSupported } = oldParticipant;
|
||||
|
||||
if (!isParticipantModerator(oldParticipant)) {
|
||||
state.numberOfNonModeratorParticipants -= 1;
|
||||
}
|
||||
|
||||
if (!e2eeEnabled) {
|
||||
state.numberOfParticipantsDisabledE2EE -= 1;
|
||||
}
|
||||
|
||||
if (!oldParticipant.local && !e2eeSupported) {
|
||||
state.numberOfParticipantsNotSupportingE2EE -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return { ...state };
|
||||
}
|
||||
case PARTICIPANT_SOURCES_UPDATED: {
|
||||
|
@ -465,27 +499,6 @@ function _getDisplayName(state: Object, name?: string): string {
|
|||
return name ?? (config?.defaultRemoteDisplayName || 'Fellow Jitster');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through the participants in the state in order to check if all participants are moderators.
|
||||
*
|
||||
* @param {Object} state - The local participant redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _isEveryoneModerator(state: IParticipantsState) {
|
||||
if (isParticipantModerator(state.local)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for (const [ k, p ] of state.remote) {
|
||||
if (!isParticipantModerator(p)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reducer function for a single participant.
|
||||
*
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { Component, ReactNode } from 'react';
|
||||
import ReactFocusLock from 'react-focus-lock';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import DialogPortal from '../../../toolbox/components/web/DialogPortal';
|
||||
|
@ -34,6 +35,18 @@ interface IProps {
|
|||
*/
|
||||
disablePopover?: boolean;
|
||||
|
||||
/**
|
||||
* The id of the dom element acting as the Popover label (matches aria-labelledby).
|
||||
*/
|
||||
headingId?: string;
|
||||
|
||||
/**
|
||||
* String acting as the Popover label (matches aria-label).
|
||||
*
|
||||
* If headingId is set, this will not be used.
|
||||
*/
|
||||
headingLabel?: string;
|
||||
|
||||
/**
|
||||
* An id attribute to apply to the root of the {@code Popover}
|
||||
* component.
|
||||
|
@ -186,7 +199,16 @@ class Popover extends Component<IProps, IState> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { children, className, content, id, overflowDrawer, visible, trigger } = this.props;
|
||||
const { children,
|
||||
className,
|
||||
content,
|
||||
headingId,
|
||||
headingLabel,
|
||||
id,
|
||||
overflowDrawer,
|
||||
visible,
|
||||
trigger
|
||||
} = this.props;
|
||||
|
||||
if (overflowDrawer) {
|
||||
return (
|
||||
|
@ -197,6 +219,7 @@ class Popover extends Component<IProps, IState> {
|
|||
{ children }
|
||||
<JitsiPortal>
|
||||
<Drawer
|
||||
headingId = { headingId }
|
||||
isOpen = { visible }
|
||||
onClose = { this._onHideDialog }>
|
||||
{ content }
|
||||
|
@ -214,7 +237,8 @@ class Popover extends Component<IProps, IState> {
|
|||
onKeyPress = { this._onKeyPress }
|
||||
{ ...(trigger === 'hover' ? {
|
||||
onMouseEnter: this._onShowDialog,
|
||||
onMouseLeave: this._onHideDialog
|
||||
onMouseLeave: this._onHideDialog,
|
||||
tabIndex: 0
|
||||
} : {}) }
|
||||
ref = { this._containerRef }>
|
||||
{ visible && (
|
||||
|
@ -222,7 +246,16 @@ class Popover extends Component<IProps, IState> {
|
|||
getRef = { this._setContextMenuRef }
|
||||
setSize = { this._setContextMenuStyle }
|
||||
style = { this.state.contextMenuStyle }>
|
||||
<ReactFocusLock
|
||||
lockProps = {{
|
||||
role: 'dialog',
|
||||
'aria-modal': true,
|
||||
'aria-labelledby': headingId,
|
||||
'aria-label': !headingId && headingLabel ? headingLabel : undefined
|
||||
}}
|
||||
returnFocus = { true }>
|
||||
{this._renderContent()}
|
||||
</ReactFocusLock>
|
||||
</DialogPortal>
|
||||
)}
|
||||
{ children }
|
||||
|
|
|
@ -105,18 +105,14 @@ class MeetingsList extends Component<Props> {
|
|||
* @returns {React.ReactNode}
|
||||
*/
|
||||
render() {
|
||||
const { listEmptyComponent, meetings, t } = this.props;
|
||||
const { listEmptyComponent, meetings } = this.props;
|
||||
|
||||
/**
|
||||
* If there are no recent meetings we don't want to display anything.
|
||||
*/
|
||||
if (meetings) {
|
||||
return (
|
||||
<Container
|
||||
aria-label = { t('welcomepage.recentList') }
|
||||
className = 'meetings-list'
|
||||
role = 'menu'
|
||||
tabIndex = '-1'>
|
||||
<Container className = 'meetings-list'>
|
||||
{
|
||||
meetings.length === 0
|
||||
? listEmptyComponent
|
||||
|
@ -238,23 +234,16 @@ class MeetingsList extends Component<Props> {
|
|||
|
||||
return (
|
||||
<Container
|
||||
aria-label = { title }
|
||||
className = { rootClassName }
|
||||
key = { index }
|
||||
onClick = { onPress }>
|
||||
<Container className = 'right-column'>
|
||||
<Text
|
||||
className = 'title'
|
||||
onClick = { onPress }
|
||||
onKeyPress = { onKeyPress }
|
||||
role = 'menuitem'
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'title'>
|
||||
{ _toDateString(date) }
|
||||
</Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ _toTimeString(time) }
|
||||
</Text>
|
||||
</Container>
|
||||
<Container className = 'right-column'>
|
||||
<Text className = 'title'>
|
||||
{ title }
|
||||
</Text>
|
||||
{
|
||||
|
@ -270,6 +259,14 @@ class MeetingsList extends Component<Props> {
|
|||
</Text>) : null
|
||||
}
|
||||
</Container>
|
||||
<Container className = 'left-column'>
|
||||
<Text className = 'title'>
|
||||
{ _toDateString(date) }
|
||||
</Text>
|
||||
<Text className = 'subtitle'>
|
||||
{ _toTimeString(time) }
|
||||
</Text>
|
||||
</Container>
|
||||
<Container className = 'actions'>
|
||||
{ elementAfter || null }
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IReduxState } from '../../app/types';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { IStateful } from '../app/types';
|
||||
import CONFIG_WHITELIST from '../config/configWhitelist';
|
||||
import { IConfigState } from '../config/reducer';
|
||||
|
@ -114,11 +115,12 @@ export function shouldHideShareAudioHelper(state: IReduxState): boolean | undefi
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the disable self view setting.
|
||||
* Gets the disabled self view setting.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getHideSelfView(state: IReduxState) {
|
||||
return state['features/base/config'].disableSelfView || state['features/base/settings'].disableSelfView;
|
||||
return state['features/base/config'].disableSelfView || state['features/base/settings'].disableSelfView
|
||||
|| iAmVisitor(state);
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
onKeyDown: disabled ? undefined : onKeyDown,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: showLabel ? 'menuitem' : 'button'
|
||||
role: 'button'
|
||||
};
|
||||
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
|
|
|
@ -123,7 +123,7 @@ class OverflowMenuItem extends Component<Props> {
|
|||
className = { className }
|
||||
onClick = { disabled ? null : onClick }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'menuitem'
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<span className = 'overflow-menu-item-icon'>
|
||||
<Icon
|
||||
|
|
|
@ -121,6 +121,7 @@ export default function ToolboxButtonWithIconPopup(props: Props) {
|
|||
<div className = 'settings-button-small-icon-container'>
|
||||
<Popover
|
||||
content = { popoverContent }
|
||||
headingLabel = { ariaLabel }
|
||||
onPopoverClose = { onPopoverClose }
|
||||
onPopoverOpen = { onPopoverOpen }
|
||||
position = 'top'
|
||||
|
|
|
@ -65,7 +65,7 @@ export default class ToolboxItem extends AbstractToolboxItem<Props> {
|
|||
onClick: disabled ? undefined : onClick,
|
||||
onKeyPress: this._onKeyPress,
|
||||
tabIndex: 0,
|
||||
role: showLabel ? 'menuitem' : 'button'
|
||||
role: 'button'
|
||||
};
|
||||
|
||||
const elementType = showLabel ? 'li' : 'div';
|
||||
|
|
|
@ -182,7 +182,9 @@ const BaseDialog = ({
|
|||
<div
|
||||
className = { classes.backdrop }
|
||||
onClick = { onBackdropClick } />
|
||||
<FocusLock className = { classes.focusLock }>
|
||||
<FocusLock
|
||||
className = { classes.focusLock }
|
||||
returnFocus = { true }>
|
||||
<div
|
||||
aria-describedby = { description }
|
||||
aria-labelledby = { title ?? t(titleKey ?? '') }
|
||||
|
|
|
@ -69,7 +69,7 @@ const useStyles = makeStyles()(theme => {
|
|||
backgroundColor: theme.palette.action01Active
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
'&.focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
},
|
||||
|
|
|
@ -53,6 +53,10 @@ const useStyles = makeStyles()(theme => {
|
|||
}
|
||||
},
|
||||
|
||||
disabled: {
|
||||
cursor: 'not-allowed'
|
||||
},
|
||||
|
||||
activeArea: {
|
||||
display: 'grid',
|
||||
placeContent: 'center',
|
||||
|
@ -73,7 +77,6 @@ const useStyles = makeStyles()(theme => {
|
|||
height: '18px',
|
||||
border: `2px solid ${theme.palette.icon03}`,
|
||||
borderRadius: '3px',
|
||||
cursor: 'pointer',
|
||||
|
||||
display: 'grid',
|
||||
placeContent: 'center',
|
||||
|
@ -154,7 +157,7 @@ const Checkbox = ({
|
|||
|
||||
return (
|
||||
<div className = { cx(styles.formControl, isMobile && 'is-mobile', className) }>
|
||||
<label className = { cx(styles.activeArea, isMobile && 'is-mobile') }>
|
||||
<label className = { cx(styles.activeArea, isMobile && 'is-mobile', disabled && styles.disabled) }>
|
||||
<input
|
||||
checked = { checked }
|
||||
disabled = { disabled }
|
||||
|
|
|
@ -25,7 +25,7 @@ const useStyles = makeStyles()(theme => {
|
|||
backgroundColor: theme.palette.ui02
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
'&.focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||
},
|
||||
|
|
|
@ -262,7 +262,7 @@ const ContextMenu = ({
|
|||
onMouseEnter = { onMouseEnter }
|
||||
onMouseLeave = { onMouseLeave }
|
||||
ref = { containerRef }
|
||||
role = { role ?? 'menu' }
|
||||
role = { role }
|
||||
tabIndex = { tabIndex }>
|
||||
{children}
|
||||
</div>;
|
||||
|
|
|
@ -5,6 +5,9 @@ import { makeStyles } from 'tss-react/mui';
|
|||
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
|
||||
import Icon from '../../../icons/components/Icon';
|
||||
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||
import { TEXT_OVERFLOW_TYPES } from '../../constants.any';
|
||||
|
||||
import TextWithOverflow from './TextWithOverflow';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
|
@ -23,6 +26,13 @@ export interface IProps {
|
|||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* Id of dom element controlled by this item. Matches aria-controls.
|
||||
* Useful if you need this item as a tab element.
|
||||
*
|
||||
*/
|
||||
controls?: string;
|
||||
|
||||
/**
|
||||
* Custom icon. If used, the icon prop is ignored.
|
||||
* Used to allow custom children instead of just the default icons.
|
||||
|
@ -52,13 +62,23 @@ export interface IProps {
|
|||
/**
|
||||
* Keydown handler.
|
||||
*/
|
||||
onKeyDown?: (e?: React.KeyboardEvent) => void;
|
||||
onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
|
||||
|
||||
/**
|
||||
* Keypress handler.
|
||||
*/
|
||||
onKeyPress?: (e?: React.KeyboardEvent) => void;
|
||||
|
||||
/**
|
||||
* Overflow type for item text.
|
||||
*/
|
||||
overflowType?: TEXT_OVERFLOW_TYPES;
|
||||
|
||||
/**
|
||||
* You can use this item as a tab. Defaults to button if not set.
|
||||
*/
|
||||
role?: 'tab' | 'button';
|
||||
|
||||
/**
|
||||
* Whether the item is marked as selected.
|
||||
*/
|
||||
|
@ -102,7 +122,7 @@ const useStyles = makeStyles()(theme => {
|
|||
backgroundColor: theme.palette.ui03
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
'&.focus-visible': {
|
||||
boxShadow: `inset 0 0 0 2px ${theme.palette.action01Hover}`
|
||||
}
|
||||
},
|
||||
|
@ -142,6 +162,7 @@ const ContextMenuItem = ({
|
|||
accessibilityLabel,
|
||||
children,
|
||||
className,
|
||||
controls,
|
||||
customIcon,
|
||||
disabled,
|
||||
id,
|
||||
|
@ -149,6 +170,8 @@ const ContextMenuItem = ({
|
|||
onClick,
|
||||
onKeyDown,
|
||||
onKeyPress,
|
||||
overflowType,
|
||||
role = 'button',
|
||||
selected,
|
||||
testId,
|
||||
text,
|
||||
|
@ -158,8 +181,10 @@ const ContextMenuItem = ({
|
|||
|
||||
return (
|
||||
<div
|
||||
aria-controls = { controls }
|
||||
aria-disabled = { disabled }
|
||||
aria-label = { accessibilityLabel }
|
||||
aria-selected = { role === 'tab' ? selected : undefined }
|
||||
className = { cx(styles.contextMenuItem,
|
||||
_overflowDrawer && styles.contextMenuItemDrawer,
|
||||
disabled && styles.contextMenuItemDisabled,
|
||||
|
@ -172,19 +197,24 @@ const ContextMenuItem = ({
|
|||
onClick = { disabled ? undefined : onClick }
|
||||
onKeyDown = { disabled ? undefined : onKeyDown }
|
||||
onKeyPress = { disabled ? undefined : onKeyPress }
|
||||
role = 'menuitem'>
|
||||
role = { role }
|
||||
tabIndex = { role === 'tab'
|
||||
? selected ? 0 : -1
|
||||
: disabled ? undefined : 0
|
||||
}>
|
||||
{customIcon ? customIcon
|
||||
: icon && <Icon
|
||||
className = { styles.contextMenuItemIcon }
|
||||
size = { 20 }
|
||||
src = { icon } />}
|
||||
{text && (
|
||||
<span
|
||||
<TextWithOverflow
|
||||
className = { cx(styles.text,
|
||||
_overflowDrawer && styles.drawerText,
|
||||
textClassName) }>
|
||||
textClassName) }
|
||||
overflowType = { overflowType } >
|
||||
{text}
|
||||
</span>
|
||||
</TextWithOverflow>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -23,12 +23,6 @@ const useStyles = makeStyles()(theme => {
|
|||
justifyContent: 'space-between'
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
'&:focus': {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
},
|
||||
|
||||
title: {
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
|
@ -137,8 +131,7 @@ const Dialog = ({
|
|||
</p>
|
||||
{!hideCloseButton && (
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('dialog.close') }
|
||||
className = { classes.closeIcon }
|
||||
accessibilityLabel = { t('dialog.accessibilityLabel.close') }
|
||||
icon = { IconCloseLarge }
|
||||
id = 'modal-header-close-button'
|
||||
onClick = { onClose } />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React, { ComponentType, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { MoveFocusInside } from 'react-focus-lock';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
@ -105,6 +106,7 @@ const useStyles = makeStyles()(theme => {
|
|||
|
||||
backContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row-reverse',
|
||||
alignItems: 'center',
|
||||
|
||||
'& > button': {
|
||||
|
@ -112,12 +114,6 @@ const useStyles = makeStyles()(theme => {
|
|||
}
|
||||
},
|
||||
|
||||
closeIcon: {
|
||||
'&:focus': {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
},
|
||||
|
||||
content: {
|
||||
flexGrow: 1,
|
||||
overflowY: 'auto',
|
||||
|
@ -129,8 +125,14 @@ const useStyles = makeStyles()(theme => {
|
|||
}
|
||||
},
|
||||
|
||||
header: {
|
||||
order: -1,
|
||||
paddingBottom: theme.spacing(4)
|
||||
},
|
||||
|
||||
footer: {
|
||||
justifyContent: 'flex-end',
|
||||
paddingTop: theme.spacing(4),
|
||||
|
||||
'& button:last-child': {
|
||||
marginLeft: '16px'
|
||||
|
@ -143,20 +145,21 @@ interface IObject {
|
|||
[key: string]: string | string[] | boolean | number | number[] | {} | undefined;
|
||||
}
|
||||
|
||||
export interface IDialogTab {
|
||||
export interface IDialogTab<P> {
|
||||
cancel?: Function;
|
||||
className?: string;
|
||||
component: ComponentType<any>;
|
||||
icon: Function;
|
||||
labelKey: string;
|
||||
name: string;
|
||||
props?: IObject;
|
||||
propsUpdateFunction?: (tabState: IObject, newProps: IObject) => IObject;
|
||||
propsUpdateFunction?: (tabState: IObject, newProps: P) => P;
|
||||
submit?: Function;
|
||||
}
|
||||
|
||||
interface IProps extends IBaseProps {
|
||||
defaultTab?: string;
|
||||
tabs: IDialogTab[];
|
||||
tabs: IDialogTab<any>[];
|
||||
}
|
||||
|
||||
const DialogWithTabs = ({
|
||||
|
@ -169,6 +172,7 @@ const DialogWithTabs = ({
|
|||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const [ selectedTab, setSelectedTab ] = useState<string | undefined>(defaultTab ?? tabs[0].name);
|
||||
const [ userSelected, setUserSelected ] = useState(false);
|
||||
const [ tabStates, setTabStates ] = useState(tabs.map(tab => tab.props));
|
||||
const clientWidth = useSelector((state: IReduxState) => state['features/base/responsive-ui'].clientWidth);
|
||||
const [ isMobile, setIsMobile ] = useState(false);
|
||||
|
@ -189,18 +193,63 @@ const DialogWithTabs = ({
|
|||
}
|
||||
}, [ isMobile ]);
|
||||
|
||||
const back = useCallback(() => {
|
||||
setSelectedTab(undefined);
|
||||
const onUserSelection = useCallback((tabName?: string) => {
|
||||
setUserSelected(true);
|
||||
setSelectedTab(tabName);
|
||||
}, []);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
const back = useCallback(() => {
|
||||
onUserSelection(undefined);
|
||||
}, []);
|
||||
|
||||
|
||||
// the userSelected state is used to prevent setting focus when the user
|
||||
// didn't actually interact (for the first rendering for example)
|
||||
useEffect(() => {
|
||||
if (userSelected) {
|
||||
document.querySelector<HTMLElement>(isMobile
|
||||
? `.${classes.title}`
|
||||
: `#${`dialogtab-button-${selectedTab}`}`
|
||||
)?.focus();
|
||||
setUserSelected(false);
|
||||
}
|
||||
}, [ isMobile, userSelected, selectedTab ]);
|
||||
|
||||
const onClose = useCallback((isCancel = true) => {
|
||||
if (isCancel) {
|
||||
tabs.forEach(({ cancel }) => {
|
||||
cancel && dispatch(cancel());
|
||||
});
|
||||
}
|
||||
dispatch(hideDialog());
|
||||
}, []);
|
||||
|
||||
const onClick = useCallback((tabName: string) => () => {
|
||||
setSelectedTab(tabName);
|
||||
onUserSelection(tabName);
|
||||
}, []);
|
||||
|
||||
const onTabKeyDown = useCallback((index: number) => (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
let newTab: IDialogTab<any> | null = null;
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
newTab = index === 0 ? tabs[tabs.length - 1] : tabs[index - 1];
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
newTab = index === tabs.length - 1 ? tabs[0] : tabs[index + 1];
|
||||
}
|
||||
|
||||
if (newTab !== null) {
|
||||
onUserSelection(newTab.name);
|
||||
}
|
||||
}, [ tabs.length ]);
|
||||
|
||||
const onMobileKeyDown = useCallback((tabName: string) => (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === ' ' || event.key === 'Enter') {
|
||||
onUserSelection(tabName);
|
||||
}
|
||||
}, [ classes.contentContainer ]);
|
||||
|
||||
const getTabProps = (tabId: number) => {
|
||||
const tabConfiguration = tabs[tabId];
|
||||
const currentTabState = tabStates[tabId];
|
||||
|
@ -225,7 +274,7 @@ const DialogWithTabs = ({
|
|||
tabs.forEach(({ submit }, idx) => {
|
||||
submit?.(tabStates[idx]);
|
||||
});
|
||||
onClose();
|
||||
onClose(false);
|
||||
}, [ tabs, tabStates ]);
|
||||
|
||||
const selectedTabIndex = useMemo(() => {
|
||||
|
@ -257,8 +306,7 @@ const DialogWithTabs = ({
|
|||
|
||||
const closeIcon = useMemo(() => (
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('dialog.close') }
|
||||
className = { classes.closeIcon }
|
||||
accessibilityLabel = { t('dialog.accessibilityLabel.close') }
|
||||
icon = { IconCloseLarge }
|
||||
id = 'modal-header-close-button'
|
||||
onClick = { onClose } />
|
||||
|
@ -270,21 +318,39 @@ const DialogWithTabs = ({
|
|||
onClose = { onClose }
|
||||
size = 'large'>
|
||||
{(!isMobile || !selectedTab) && (
|
||||
<div className = { classes.sidebar }>
|
||||
<div
|
||||
aria-orientation = 'vertical'
|
||||
className = { classes.sidebar }
|
||||
role = { isMobile ? undefined : 'tablist' }>
|
||||
<div className = { classes.titleContainer }>
|
||||
<h2 className = { classes.title }>{t(titleKey ?? '')}</h2>
|
||||
<MoveFocusInside>
|
||||
<h2
|
||||
className = { classes.title }
|
||||
tabIndex = { -1 }>
|
||||
{t(titleKey ?? '')}
|
||||
</h2>
|
||||
</MoveFocusInside>
|
||||
{isMobile && closeIcon}
|
||||
</div>
|
||||
{tabs.map(tab => {
|
||||
{tabs.map((tab, index) => {
|
||||
const label = t(tab.labelKey);
|
||||
|
||||
/**
|
||||
* When not on mobile, the items behave as tabs,
|
||||
* that's why we set `controls`, `role` and `selected` attributes
|
||||
* only when not on mobile, they are useful only for the tab behavior.
|
||||
*/
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { label }
|
||||
className = { cx(isMobile && classes.menuItemMobile) }
|
||||
controls = { isMobile ? undefined : `dialogtab-content-${tab.name}` }
|
||||
icon = { tab.icon }
|
||||
id = { `dialogtab-button-${tab.name}` }
|
||||
key = { tab.name }
|
||||
onClick = { onClick(tab.name) }
|
||||
onKeyDown = { isMobile ? onMobileKeyDown(tab.name) : onTabKeyDown(index) }
|
||||
role = { isMobile ? undefined : 'tab' }
|
||||
selected = { tab.name === selectedTab }
|
||||
text = { label } />
|
||||
);
|
||||
|
@ -292,26 +358,45 @@ const DialogWithTabs = ({
|
|||
</div>
|
||||
)}
|
||||
{(!isMobile || selectedTab) && (
|
||||
<div className = { classes.contentContainer }>
|
||||
<div className = { classes.buttonContainer }>
|
||||
<div
|
||||
className = { classes.contentContainer }
|
||||
tabIndex = { isMobile ? -1 : undefined }>
|
||||
{/* DOM order is important for keyboard users: show whole heading first when on mobile… */}
|
||||
{isMobile && (
|
||||
<div className = { cx(classes.buttonContainer, classes.header) }>
|
||||
<span className = { classes.backContainer }>
|
||||
<h2
|
||||
className = { classes.title }
|
||||
tabIndex = { -1 }>
|
||||
{(selectedTabIndex !== null) && t(tabs[selectedTabIndex].labelKey)}
|
||||
</h2>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('dialog.Back') }
|
||||
className = { classes.closeIcon }
|
||||
icon = { IconArrowBack }
|
||||
id = 'modal-header-back-button'
|
||||
onClick = { back } />
|
||||
<h2 className = { classes.title }>
|
||||
{(selectedTabIndex !== null) && t(tabs[selectedTabIndex].labelKey)}
|
||||
</h2>
|
||||
</span>
|
||||
)}
|
||||
{closeIcon}
|
||||
</div>
|
||||
<div className = { classes.content }>
|
||||
{selectedTabComponent}
|
||||
)}
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
aria-labelledby = { isMobile ? undefined : `${tab.name}-button` }
|
||||
className = { cx(classes.content, tab.name !== selectedTab && 'hide') }
|
||||
id = { `dialogtab-content-${tab.name}` }
|
||||
key = { tab.name }
|
||||
role = { isMobile ? undefined : 'tabpanel' }
|
||||
tabIndex = { isMobile ? -1 : 0 }>
|
||||
{ tab.name === selectedTab && selectedTabComponent }
|
||||
</div>
|
||||
))}
|
||||
{/* But show the close button *after* tab panels when not on mobile (using tabs).
|
||||
This is so that we can tab back and forth tab buttons and tab panels easily. */}
|
||||
{!isMobile && (
|
||||
<div className = { cx(classes.buttonContainer, classes.header) }>
|
||||
{closeIcon}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className = { cx(classes.buttonContainer, classes.footer) }>
|
||||
<Button
|
||||
|
|
|
@ -79,7 +79,7 @@ const useStyles = makeStyles()(theme => {
|
|||
width: '100%',
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||
color: theme.palette.text01,
|
||||
padding: '8px 16px',
|
||||
padding: '10px 16px',
|
||||
paddingRight: '42px',
|
||||
border: 0,
|
||||
appearance: 'none',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { isMobileBrowser } from '../../../environment/utils';
|
||||
|
@ -11,6 +11,7 @@ interface ITabProps {
|
|||
selected: string;
|
||||
tabs: Array<{
|
||||
accessibilityLabel: string;
|
||||
controlsId: string;
|
||||
countBadge?: number;
|
||||
disabled?: boolean;
|
||||
id: string;
|
||||
|
@ -44,7 +45,7 @@ const useStyles = makeStyles()(theme => {
|
|||
borderColor: theme.palette.ui10
|
||||
},
|
||||
|
||||
'&:focus': {
|
||||
'&.focus-visible': {
|
||||
outline: 0,
|
||||
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
|
||||
border: 0,
|
||||
|
@ -87,26 +88,52 @@ const Tabs = ({
|
|||
}: ITabProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
const isMobile = isMobileBrowser();
|
||||
|
||||
const handleChange = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
onChange(e.currentTarget.id);
|
||||
const onClick = useCallback(id => () => {
|
||||
onChange(id);
|
||||
}, []);
|
||||
const onKeyDown = useCallback((index: number) => (event: React.KeyboardEvent<HTMLButtonElement>) => {
|
||||
let newIndex: number | null = null;
|
||||
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
newIndex = index === 0 ? tabs.length - 1 : index - 1;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
newIndex = index === tabs.length - 1 ? 0 : index + 1;
|
||||
}
|
||||
|
||||
if (newIndex !== null) {
|
||||
onChange(tabs[newIndex].id);
|
||||
}
|
||||
}, [ tabs ]);
|
||||
|
||||
useEffect(() => {
|
||||
// this test is needed to make sure the effect is triggered because of user actually changing tab
|
||||
if (document.activeElement?.getAttribute('role') === 'tab') {
|
||||
document.querySelector<HTMLButtonElement>(`#${selected}`)?.focus();
|
||||
}
|
||||
}, [ selected ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
aria-label = { accessibilityLabel }
|
||||
className = { cx(classes.container, className) }
|
||||
role = 'tablist'>
|
||||
{tabs.map(tab => (
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
aria-controls = { tab.controlsId }
|
||||
aria-label = { tab.accessibilityLabel }
|
||||
aria-selected = { selected === tab.id }
|
||||
className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
|
||||
disabled = { tab.disabled }
|
||||
id = { tab.id }
|
||||
key = { tab.id }
|
||||
onClick = { handleChange }
|
||||
role = 'tab'>
|
||||
onClick = { onClick(tab.id) }
|
||||
onKeyDown = { onKeyDown(index) }
|
||||
role = 'tab'
|
||||
tabIndex = { selected === tab.id ? undefined : -1 }>
|
||||
{tab.label}
|
||||
{tab.countBadge && <span className = { classes.badge }>{tab.countBadge}</span>}
|
||||
</button>
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import React, { ReactNode, useRef } from 'react';
|
||||
import { keyframes } from 'styled-components';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { TEXT_OVERFLOW_TYPES } from '../../constants.web';
|
||||
|
||||
interface ITextWithOverflowProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
overflowType?: TEXT_OVERFLOW_TYPES;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles<{ translateDiff: number; }>()((_, { translateDiff }) => {
|
||||
return {
|
||||
animation: {
|
||||
'&:hover': {
|
||||
animation: `${keyframes`
|
||||
0%, 20% {
|
||||
transform: translateX(0%);
|
||||
left: 0%;
|
||||
}
|
||||
80%, 100% {
|
||||
transform: translateX(-${translateDiff}px);
|
||||
left: 100%;
|
||||
}
|
||||
`} ${Math.max(translateDiff * 50, 2000)}ms infinite alternate linear;`
|
||||
}
|
||||
},
|
||||
textContainer: {
|
||||
overflow: 'hidden'
|
||||
},
|
||||
[TEXT_OVERFLOW_TYPES.ELLIPSIS]: {
|
||||
display: 'block',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
[TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER]: {
|
||||
display: 'inline-block',
|
||||
overflow: 'visible',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const TextWithOverflow = ({
|
||||
className,
|
||||
overflowType = TEXT_OVERFLOW_TYPES.ELLIPSIS,
|
||||
children
|
||||
}: ITextWithOverflowProps) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const contentRef = useRef<HTMLSpanElement>(null);
|
||||
const shouldAnimateOnHover = overflowType === TEXT_OVERFLOW_TYPES.SCROLL_ON_HOVER
|
||||
&& containerRef.current
|
||||
&& contentRef.current
|
||||
&& containerRef.current.clientWidth < contentRef.current.clientWidth;
|
||||
|
||||
const translateDiff = shouldAnimateOnHover ? contentRef.current.clientWidth - containerRef.current.clientWidth : 0;
|
||||
const { classes: styles, cx } = useStyles({ translateDiff });
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cx(className, styles.textContainer) }
|
||||
ref = { containerRef }>
|
||||
<span
|
||||
className = { cx(styles[overflowType], shouldAnimateOnHover && styles.animation) }
|
||||
ref = { contentRef }>
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextWithOverflow;
|
|
@ -8,6 +8,14 @@ export enum BUTTON_TYPES {
|
|||
TERTIARY = 'tertiary'
|
||||
}
|
||||
|
||||
/**
|
||||
* Behaviour types for showing overflow text content.
|
||||
*/
|
||||
export enum TEXT_OVERFLOW_TYPES {
|
||||
ELLIPSIS = 'ellipsis',
|
||||
SCROLL_ON_HOVER = 'scroll-on-hover'
|
||||
}
|
||||
|
||||
/**
|
||||
* The modes of the buttons.
|
||||
*/
|
||||
|
|
|
@ -202,7 +202,8 @@ class CalendarList extends AbstractPage<Props> {
|
|||
className = 'meetings-list-empty-button'
|
||||
onClick = { this._onOpenSettings }
|
||||
onKeyPress = { this._onKeyPressOpenSettings }
|
||||
role = 'button'>
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
<Icon
|
||||
className = 'meetings-list-empty-icon'
|
||||
src = { IconCalendar } />
|
||||
|
|
|
@ -134,35 +134,38 @@ class Chat extends AbstractChat<Props> {
|
|||
_renderChat() {
|
||||
const { _isPollsEnabled, _isPollsTabFocused } = this.props;
|
||||
|
||||
if (_isPollsTabFocused) {
|
||||
return (
|
||||
<>
|
||||
{ _isPollsEnabled && this._renderTabs() }
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.POLLS }
|
||||
id = 'polls-panel'
|
||||
role = 'tabpanel'>
|
||||
<PollsPane />
|
||||
</div>
|
||||
<KeyboardAvoider />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ _isPollsEnabled && this._renderTabs() }
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.CHAT }
|
||||
className = { clsx('chat-panel', !_isPollsEnabled && 'chat-panel-no-tabs') }
|
||||
id = 'chat-panel'
|
||||
role = 'tabpanel'>
|
||||
className = { clsx(
|
||||
'chat-panel',
|
||||
!_isPollsEnabled && 'chat-panel-no-tabs',
|
||||
_isPollsTabFocused && 'hide'
|
||||
) }
|
||||
id = { `${CHAT_TABS.CHAT}-panel` }
|
||||
role = 'tabpanel'
|
||||
tabIndex = { 0 }>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onSend = { this._onSendMessage } />
|
||||
</div>
|
||||
{ _isPollsEnabled && (
|
||||
<>
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.POLLS }
|
||||
className = { clsx('polls-panel', !_isPollsTabFocused && 'hide') }
|
||||
id = { `${CHAT_TABS.POLLS}-panel` }
|
||||
role = 'tabpanel'
|
||||
tabIndex = { 0 }>
|
||||
<PollsPane />
|
||||
</div>
|
||||
<KeyboardAvoider />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -185,11 +188,13 @@ class Chat extends AbstractChat<Props> {
|
|||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
countBadge: _isPollsTabFocused && _nbUnreadMessages > 0 ? _nbUnreadMessages : undefined,
|
||||
id: CHAT_TABS.CHAT,
|
||||
controlsId: `${CHAT_TABS.CHAT}-panel`,
|
||||
label: t('chat.tabs.chat')
|
||||
}, {
|
||||
accessibilityLabel: t('chat.tabs.polls'),
|
||||
countBadge: !_isPollsTabFocused && _nbUnreadPolls > 0 ? _nbUnreadPolls : undefined,
|
||||
id: CHAT_TABS.POLLS,
|
||||
controlsId: `${CHAT_TABS.POLLS}-panel`,
|
||||
label: t('chat.tabs.polls')
|
||||
}
|
||||
] } />
|
||||
|
|
|
@ -46,9 +46,12 @@ function Header({ onCancel, className, isPollsEnabled, t }: Props) {
|
|||
|
||||
return (
|
||||
<div
|
||||
className = { className || 'chat-dialog-header' }
|
||||
className = { className || 'chat-dialog-header' }>
|
||||
<span
|
||||
aria-level = { 1 }
|
||||
role = 'heading'>
|
||||
{ t(isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') }
|
||||
</span>
|
||||
<Icon
|
||||
ariaLabel = { t('toolbar.closeChat') }
|
||||
onClick = { onCancel }
|
||||
|
|
|
@ -115,7 +115,6 @@ class ChatInput extends Component<IProps, IState> {
|
|||
</div>
|
||||
)}
|
||||
<Input
|
||||
autoFocus = { true }
|
||||
className = 'chat-input'
|
||||
icon = { this.props._areSmileysDisabled ? undefined : IconFaceSmile }
|
||||
iconClick = { this._toggleSmileysPanel }
|
||||
|
|
|
@ -8,6 +8,7 @@ export const CONFERENCE_INFO = {
|
|||
'e2ee',
|
||||
'transcribing',
|
||||
'video-quality',
|
||||
'visitors-count',
|
||||
'insecure-room',
|
||||
'top-panel-toggle'
|
||||
]
|
||||
|
|
|
@ -8,12 +8,14 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
|||
import { RecordingLabel } from '../../../recording';
|
||||
import { openHighlightDialog } from '../../../recording/actions.native';
|
||||
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
|
||||
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
|
||||
|
||||
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
|
||||
import {
|
||||
LABEL_ID_RAISED_HANDS_COUNT,
|
||||
LABEL_ID_RECORDING,
|
||||
LABEL_ID_STREAMING,
|
||||
LABEL_ID_VISITORS_COUNT,
|
||||
LabelHitSlop
|
||||
} from './constants';
|
||||
|
||||
|
@ -51,6 +53,11 @@ const AlwaysOnLabels = ({ createOnPress }: Props) => {
|
|||
onPress = { createOnPress(LABEL_ID_RAISED_HANDS_COUNT) } >
|
||||
<RaisedHandsCountLabel />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
hitSlop = { LabelHitSlop }
|
||||
onPress = { createOnPress(LABEL_ID_VISITORS_COUNT) } >
|
||||
<VisitorsCountLabel />
|
||||
</TouchableOpacity>
|
||||
</>);
|
||||
};
|
||||
|
||||
|
|
|
@ -436,6 +436,7 @@ class Conference extends AbstractConference<Props, State> {
|
|||
_shouldDisplayTileView
|
||||
|| <>
|
||||
<Filmstrip />
|
||||
{ this._renderNotificationsContainer() }
|
||||
<Toolbox />
|
||||
</>
|
||||
}
|
||||
|
@ -467,7 +468,6 @@ class Conference extends AbstractConference<Props, State> {
|
|||
{/* eslint-disable-next-line react/jsx-no-bind */}
|
||||
<AlwaysOnLabels createOnPress = { this._createOnPress } />
|
||||
</View>
|
||||
{ this._renderNotificationsContainer() }
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
@ -530,7 +530,8 @@ class Conference extends AbstractConference<Props, State> {
|
|||
|
||||
return super.renderNotificationsContainer(
|
||||
{
|
||||
style: notificationsStyle
|
||||
style: notificationsStyle,
|
||||
toolboxVisible: this.props._toolboxVisible
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ export const LABEL_ID_STREAMING = 'streaming';
|
|||
export const LABEL_ID_TRANSCRIBING = 'transcribing';
|
||||
export const LABEL_ID_INSECURE_ROOM_NAME = 'insecure-room-name';
|
||||
export const LABEL_ID_RAISED_HANDS_COUNT = 'raised-hands-count';
|
||||
export const LABEL_ID_VISITORS_COUNT = 'visitors-count';
|
||||
|
||||
/**
|
||||
* The {@code ExpandedLabel} components to be rendered for the individual
|
||||
|
|
|
@ -210,7 +210,8 @@ class Conference extends AbstractConference<Props, *> {
|
|||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
_showLobby,
|
||||
_showPrejoin
|
||||
_showPrejoin,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
|
@ -240,7 +241,17 @@ class Conference extends AbstractConference<Props, *> {
|
|||
}
|
||||
</div>
|
||||
|
||||
{ _showPrejoin || _showLobby || <Toolbox /> }
|
||||
{ _showPrejoin || _showLobby || (
|
||||
<>
|
||||
<span
|
||||
aria-level = { 1 }
|
||||
className = 'sr-only'
|
||||
role = 'heading'>
|
||||
{ t('toolbar.accessibilityLabel.heading') }
|
||||
</span>
|
||||
<Toolbox />
|
||||
</>
|
||||
)}
|
||||
|
||||
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
|
||||
? <JitsiPortal className = 'notification-portal'>
|
||||
|
|
|
@ -9,9 +9,11 @@ import { connect } from '../../../base/redux';
|
|||
import E2EELabel from '../../../e2ee/components/E2EELabel';
|
||||
import HighlightButton from '../../../recording/components/Recording/web/HighlightButton';
|
||||
import RecordingLabel from '../../../recording/components/web/RecordingLabel';
|
||||
import { showToolbox } from '../../../toolbox/actions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.web';
|
||||
import TranscribingLabel from '../../../transcribing/components/TranscribingLabel.web';
|
||||
import VideoQualityLabel from '../../../video-quality/components/VideoQualityLabel.web';
|
||||
import VisitorsCountLabel from '../../../visitors/components/web/VisitorsCountLabel';
|
||||
import ConferenceTimer from '../ConferenceTimer';
|
||||
import { getConferenceInfo } from '../functions';
|
||||
|
||||
|
@ -32,6 +34,11 @@ type Props = {
|
|||
*/
|
||||
_conferenceInfo: Object,
|
||||
|
||||
/**
|
||||
* Invoked to active other features of the app.
|
||||
*/
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* Indicates whether the component should be visible or not.
|
||||
*/
|
||||
|
@ -80,6 +87,10 @@ const COMPONENTS = [
|
|||
Component: VideoQualityLabel,
|
||||
id: 'video-quality'
|
||||
},
|
||||
{
|
||||
Component: VisitorsCountLabel,
|
||||
id: 'visitors-count'
|
||||
},
|
||||
{
|
||||
Component: InsecureRoomNameLabel,
|
||||
id: 'insecure-room'
|
||||
|
@ -108,6 +119,21 @@ class ConferenceInfo extends Component<Props> {
|
|||
|
||||
this._renderAutoHide = this._renderAutoHide.bind(this);
|
||||
this._renderAlwaysVisible = this._renderAlwaysVisible.bind(this);
|
||||
this._onTabIn = this._onTabIn.bind(this);
|
||||
}
|
||||
|
||||
_onTabIn: () => void;
|
||||
|
||||
/**
|
||||
* Callback invoked when the component is focused to show the conference
|
||||
* info if necessary.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTabIn() {
|
||||
if (this.props._conferenceInfo.autoHide?.length && !this.props._visible) {
|
||||
this.props.dispatch(showToolbox());
|
||||
}
|
||||
}
|
||||
|
||||
_renderAutoHide: () => void;
|
||||
|
@ -176,7 +202,9 @@ class ConferenceInfo extends Component<Props> {
|
|||
*/
|
||||
render() {
|
||||
return (
|
||||
<div className = 'details-container' >
|
||||
<div
|
||||
className = 'details-container'
|
||||
onFocus = { this._onTabIn }>
|
||||
{ this._renderAlwaysVisible() }
|
||||
{ this._renderAutoHide() }
|
||||
</div>
|
||||
|
|
|
@ -216,7 +216,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
|||
*/
|
||||
render() {
|
||||
// @ts-ignore
|
||||
const { enableStatsDisplay, participantId, statsPopoverPosition, classes } = this.props;
|
||||
const { enableStatsDisplay, participantId, statsPopoverPosition, classes, t } = this.props;
|
||||
const visibilityClass = this._getVisibilityClass();
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -233,6 +233,7 @@ class ConnectionIndicator extends AbstractConnectionIndicator<Props, IState> {
|
|||
inheritedStats = { this.state.stats }
|
||||
participantId = { participantId } /> }
|
||||
disablePopover = { !enableStatsDisplay }
|
||||
headingLabel = { t('videothumbnail.connectionInfo') }
|
||||
id = 'participant-connection-indicator'
|
||||
onPopoverClose = { this._onHidePopover }
|
||||
onPopoverOpen = { this._onShowPopover }
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// @flow
|
||||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate } from '../app/actions';
|
|
@ -1,192 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
||||
import { IDeeplinkingConfig } from '../../base/config/configType';
|
||||
import { isSupportedBrowser } from '../../base/environment';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants.web';
|
||||
import {
|
||||
openDesktopApp,
|
||||
openWebApp
|
||||
} from '../actions';
|
||||
import { _TNS } from '../constants';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link DeepLinkingDesktopPage}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The deeplinking config.
|
||||
*/
|
||||
_deeplinkingCfg: IDeeplinkingConfig,
|
||||
|
||||
/**
|
||||
* Used to dispatch actions from the buttons.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Used to obtain translations.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* React component representing the deep linking page.
|
||||
*
|
||||
* @class DeepLinkingDesktopPage
|
||||
*/
|
||||
class DeepLinkingDesktopPage<P : Props> extends Component<P> {
|
||||
/**
|
||||
* Initializes a new {@code DeepLinkingDesktopPage} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onLaunchWeb = this._onLaunchWeb.bind(this);
|
||||
this._onTryAgain = this._onTryAgain.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Component's componentDidMount method.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t, _deeplinkingCfg: { desktop = {}, hideLogo, showImage } } = this.props;
|
||||
const { appName } = desktop;
|
||||
const rightColumnStyle
|
||||
= showImage ? null : { width: '100%' };
|
||||
|
||||
return (
|
||||
|
||||
// Enabling light theme because of the color of the buttons.
|
||||
<AtlasKitThemeProvider mode = 'light'>
|
||||
<div className = 'deep-linking-desktop'>
|
||||
<div className = 'header'>
|
||||
{
|
||||
hideLogo
|
||||
? null
|
||||
: <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = 'logo'
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
</div>
|
||||
<div className = 'content'>
|
||||
{
|
||||
showImage
|
||||
? <div className = 'leftColumn'>
|
||||
<div className = 'leftColumnContent'>
|
||||
<div className = 'image' />
|
||||
</div>
|
||||
</div> : null
|
||||
}
|
||||
<div
|
||||
className = 'rightColumn'
|
||||
style = { rightColumnStyle }>
|
||||
<div className = 'rightColumnContent'>
|
||||
<h1 className = 'title'>
|
||||
{
|
||||
t(`${_TNS}.title`,
|
||||
{ app: appName })
|
||||
}
|
||||
</h1>
|
||||
<p className = 'description'>
|
||||
{
|
||||
t(
|
||||
`${_TNS}.${isSupportedBrowser()
|
||||
? 'description'
|
||||
: 'descriptionWithoutWeb'}`,
|
||||
{ app: appName }
|
||||
)
|
||||
}
|
||||
</p>
|
||||
<div className = 'buttons'>
|
||||
<Button
|
||||
label = { t(`${_TNS}.tryAgainButton`) }
|
||||
onClick = { this._onTryAgain }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
{
|
||||
isSupportedBrowser()
|
||||
&& <Button
|
||||
label = { t(`${_TNS}.launchWebButton`) }
|
||||
onClick = { this._onLaunchWeb }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
_onTryAgain: () => void;
|
||||
|
||||
/**
|
||||
* Handles try again button clicks.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTryAgain() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'tryAgainButton', { isMobileBrowser: false }));
|
||||
this.props.dispatch(openDesktopApp());
|
||||
}
|
||||
|
||||
_onLaunchWeb: () => void;
|
||||
|
||||
/**
|
||||
* Handles launch web button clicks.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLaunchWeb() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: false }));
|
||||
this.props.dispatch(openWebApp());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code DeepLinkingDesktopPage} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DeepLinkingDesktopPage));
|
|
@ -0,0 +1,159 @@
|
|||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createDeepLinkingPageEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IDeeplinkingConfig } from '../../base/config/configType';
|
||||
import { getLegalUrls } from '../../base/config/functions.any';
|
||||
import { isSupportedBrowser } from '../../base/environment/environment';
|
||||
import { translate, translateToHTML } from '../../base/i18n/functions';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../base/ui/constants.any';
|
||||
import {
|
||||
openDesktopApp,
|
||||
openWebApp
|
||||
} from '../actions';
|
||||
import { _TNS } from '../constants';
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
background: '#1E1E1E',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex'
|
||||
},
|
||||
contentPane: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: theme.palette.ui01,
|
||||
border: `1px solid ${theme.palette.ui03}`,
|
||||
padding: 40,
|
||||
borderRadius: 16,
|
||||
maxWidth: 410,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
logo: {
|
||||
marginBottom: 32
|
||||
},
|
||||
launchingMeetingLabel: {
|
||||
marginBottom: 16,
|
||||
...withPixelLineHeight(theme.typography.heading4)
|
||||
},
|
||||
roomName: {
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.heading5)
|
||||
},
|
||||
descriptionLabel: {
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegular)
|
||||
},
|
||||
buttonsContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-start',
|
||||
'& > *:not(:last-child)': {
|
||||
marginRight: 16
|
||||
}
|
||||
},
|
||||
separator: {
|
||||
marginTop: 40,
|
||||
height: 1,
|
||||
maxWidth: 390,
|
||||
background: theme.palette.ui03
|
||||
},
|
||||
label: {
|
||||
marginTop: 40,
|
||||
...withPixelLineHeight(theme.typography.labelRegular),
|
||||
color: theme.palette.text02,
|
||||
'& a': {
|
||||
color: theme.palette.link01
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const DeepLinkingDesktopPage: React.FC<WithTranslation> = ({ t }) => {
|
||||
const dispatch = useDispatch();
|
||||
const room = useSelector((state: IReduxState) => decodeURIComponent(state['features/base/conference'].room || ''));
|
||||
const deeplinkingCfg = useSelector((state: IReduxState) =>
|
||||
state['features/base/config']?.deeplinking || {} as IDeeplinkingConfig);
|
||||
|
||||
const legalUrls = useSelector(getLegalUrls);
|
||||
|
||||
const { hideLogo, desktop } = deeplinkingCfg;
|
||||
|
||||
const { classes: styles } = useStyles();
|
||||
const onLaunchWeb = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: false }));
|
||||
dispatch(openWebApp());
|
||||
}, []);
|
||||
const onTryAgain = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'tryAgainButton', { isMobileBrowser: false }));
|
||||
dispatch(openDesktopApp());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingDesktop', { isMobileBrowser: false }));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className = { styles.container }>
|
||||
<div className = { styles.contentPane }>
|
||||
<div className = 'header'>
|
||||
{
|
||||
!hideLogo
|
||||
&& <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = { styles.logo }
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.launchingMeetingLabel }>
|
||||
{
|
||||
t(`${_TNS}.titleNew`)
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.roomName }>{ room }</div>
|
||||
<div className = { styles.descriptionLabel }>
|
||||
{
|
||||
isSupportedBrowser()
|
||||
? translateToHTML(t, `${_TNS}.descriptionNew`, { app: desktop?.appName })
|
||||
: t(`${_TNS}.descriptionWithoutWeb`, { app: desktop?.appName })
|
||||
}
|
||||
</div>
|
||||
<div className = { styles.buttonsContainer }>
|
||||
<Button
|
||||
label = { t(`${_TNS}.tryAgainButton`) }
|
||||
onClick = { onTryAgain } />
|
||||
{ isSupportedBrowser() && (
|
||||
<Button
|
||||
label = { t(`${_TNS}.launchWebButton`) }
|
||||
onClick = { onLaunchWeb }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
|
||||
</div>
|
||||
<div className = { styles.separator } />
|
||||
<div className = { styles.label }> {translateToHTML(t, 'deepLinking.termsAndConditions', {
|
||||
termsAndConditionsLink: legalUrls.terms
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(DeepLinkingDesktopPage);
|
|
@ -1,299 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
|
||||
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
|
||||
import { isSupportedMobileBrowser } from '../../base/environment';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Platform } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { DialInSummary } from '../../invite';
|
||||
import { openWebApp } from '../actions';
|
||||
import { _TNS } from '../constants';
|
||||
import { generateDeepLinkingURL } from '../functions';
|
||||
import { renderPromotionalFooter } from '../renderPromotionalFooter';
|
||||
|
||||
/**
|
||||
* The namespace of the CSS styles of DeepLinkingMobilePage.
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
const _SNS = 'deep-linking-mobile';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link DeepLinkingMobilePage}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The deeplinking config.
|
||||
*/
|
||||
_deeplinkingCfg: IDeeplinkingConfig,
|
||||
|
||||
/**
|
||||
* Application mobile deeplinking config.
|
||||
*/
|
||||
_mobileConfig: IDeeplinkingMobileConfig,
|
||||
|
||||
/**
|
||||
* The deeplinking url.
|
||||
*/
|
||||
_deepLinkingUrl: string,
|
||||
|
||||
/**
|
||||
* The name of the conference attempting to being joined.
|
||||
*/
|
||||
_room: string,
|
||||
|
||||
/**
|
||||
* The page current url.
|
||||
*/
|
||||
_url: URL,
|
||||
|
||||
/**
|
||||
* Used to dispatch actions from the buttons.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* The function to translate human-readable text.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* React component representing mobile browser page.
|
||||
*
|
||||
* @class DeepLinkingMobilePage
|
||||
*/
|
||||
class DeepLinkingMobilePage extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code DeepLinkingMobilePage} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onDownloadApp = this._onDownloadApp.bind(this);
|
||||
this._onLaunchWeb = this._onLaunchWeb.bind(this);
|
||||
this._onOpenApp = this._onOpenApp.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the Component's componentDidMount method.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingMobile', { isMobileBrowser: true }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_deeplinkingCfg: { hideLogo },
|
||||
_mobileConfig: { downloadLink, appName },
|
||||
_room,
|
||||
t,
|
||||
_url,
|
||||
_deepLinkingUrl
|
||||
} = this.props;
|
||||
const downloadButtonClassName
|
||||
= `${_SNS}__button ${_SNS}__button_primary`;
|
||||
|
||||
|
||||
const onOpenLinkProperties = downloadLink
|
||||
? {
|
||||
// When opening a link to the download page, we want to let the
|
||||
// OS itself handle intercepting and opening the appropriate
|
||||
// app store. This avoids potential issues with browsers, such
|
||||
// as iOS Chrome, not opening the store properly.
|
||||
}
|
||||
: {
|
||||
// When falling back to another URL (Firebase) let the page be
|
||||
// opened in a new window. This helps prevent the user getting
|
||||
// trapped in an app-open-cycle where going back to the mobile
|
||||
// browser re-triggers the app-open behavior.
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
};
|
||||
|
||||
return (
|
||||
<div className = { _SNS }>
|
||||
<div className = 'header'>
|
||||
{
|
||||
hideLogo
|
||||
? null
|
||||
: <img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
className = 'logo'
|
||||
src = 'images/logo-deep-linking.png' />
|
||||
}
|
||||
</div>
|
||||
<div className = { `${_SNS}__body` }>
|
||||
<p className = { `${_SNS}__text` }>
|
||||
{ t(`${_TNS}.appNotInstalled`, { app: appName }) }
|
||||
</p>
|
||||
<p className = { `${_SNS}__text` }>
|
||||
{ t(`${_TNS}.ifHaveApp`) }
|
||||
</p>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { `${_SNS}__href` }
|
||||
href = { _deepLinkingUrl }
|
||||
onClick = { this._onOpenApp }
|
||||
target = '_top'>
|
||||
<button className = { `${_SNS}__button ${_SNS}__button_primary` }>
|
||||
{ t(`${_TNS}.joinInApp`) }
|
||||
</button>
|
||||
</a>
|
||||
<p className = { `${_SNS}__text` }>
|
||||
{ t(`${_TNS}.ifDoNotHaveApp`) }
|
||||
</p>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
href = { this._generateDownloadURL() }
|
||||
onClick = { this._onDownloadApp }
|
||||
target = '_top'>
|
||||
<button className = { downloadButtonClassName }>
|
||||
{ t(`${_TNS}.downloadApp`) }
|
||||
</button>
|
||||
</a>
|
||||
{
|
||||
isSupportedMobileBrowser()
|
||||
? (
|
||||
<a
|
||||
onClick = { this._onLaunchWeb }
|
||||
target = '_top'>
|
||||
<button className = { downloadButtonClassName }>
|
||||
{ t(`${_TNS}.launchWebButton`) }
|
||||
</button>
|
||||
</a>
|
||||
) : (
|
||||
<b>
|
||||
{ t(`${_TNS}.unsupportedBrowser`) }
|
||||
</b>
|
||||
)
|
||||
}
|
||||
{ renderPromotionalFooter() }
|
||||
<DialInSummary
|
||||
className = 'deep-linking-dial-in'
|
||||
clickableNumbers = { true }
|
||||
room = { _room }
|
||||
url = { _url } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the URL for downloading the app.
|
||||
*
|
||||
* @private
|
||||
* @returns {string} - The URL for downloading the app.
|
||||
*/
|
||||
_generateDownloadURL() {
|
||||
const { _mobileConfig: { downloadLink, dynamicLink, appScheme } } = this.props;
|
||||
|
||||
if (downloadLink && typeof dynamicLink === 'undefined') {
|
||||
return downloadLink;
|
||||
}
|
||||
|
||||
const {
|
||||
apn,
|
||||
appCode,
|
||||
customDomain,
|
||||
ibi,
|
||||
isi
|
||||
} = dynamicLink || {};
|
||||
|
||||
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
|
||||
|
||||
return `${domain}/?link=${
|
||||
encodeURIComponent(window.location.href)}&apn=${
|
||||
apn}&ibi=${
|
||||
ibi}&isi=${
|
||||
isi}&ius=${
|
||||
appScheme}&efr=1`;
|
||||
}
|
||||
|
||||
_onDownloadApp: () => void;
|
||||
|
||||
/**
|
||||
* Handles download app button clicks.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDownloadApp() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'downloadAppButton', { isMobileBrowser: true }));
|
||||
}
|
||||
|
||||
_onLaunchWeb: () => void;
|
||||
|
||||
/**
|
||||
* Handles launch web button clicks.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLaunchWeb() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: true }));
|
||||
this.props.dispatch(openWebApp());
|
||||
}
|
||||
|
||||
_onOpenApp: () => void;
|
||||
|
||||
/**
|
||||
* Handles open app button clicks.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onOpenApp() {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'openAppButton', { isMobileBrowser: true }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code DeepLinkingMobilePage} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { locationURL = {} } = state['features/base/connection'];
|
||||
const { deeplinking } = state['features/base/config'];
|
||||
const mobileConfig = deeplinking?.[Platform.OS] || {};
|
||||
|
||||
return {
|
||||
_deeplinkingCfg: deeplinking || {},
|
||||
_mobileConfig: mobileConfig,
|
||||
_room: decodeURIComponent(state['features/base/conference'].room),
|
||||
_url: locationURL,
|
||||
_deepLinkingUrl: generateDeepLinkingURL(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DeepLinkingMobilePage));
|
|
@ -0,0 +1,241 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import { Theme } from '@mui/material';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createDeepLinkingPageEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
|
||||
import { isSupportedMobileBrowser } from '../../base/environment/environment';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Platform from '../../base/react/Platform.web';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import Button from '../../base/ui/components/web/Button';
|
||||
// @ts-ignore
|
||||
import DialInSummary from '../../invite/components/dial-in-summary/web/DialInSummary';
|
||||
import { openWebApp } from '../actions';
|
||||
// @ts-ignore
|
||||
import { _TNS } from '../constants';
|
||||
// @ts-ignore
|
||||
import { generateDeepLinkingURL } from '../functions';
|
||||
|
||||
|
||||
const PADDINGS = {
|
||||
topBottom: 24,
|
||||
leftRight: 40
|
||||
};
|
||||
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
background: '#1E1E1E',
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'auto',
|
||||
justifyContent: 'center',
|
||||
display: 'flex',
|
||||
'& a': {
|
||||
textDecoration: 'none'
|
||||
}
|
||||
},
|
||||
contentPane: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
padding: `${PADDINGS.topBottom}px ${PADDINGS.leftRight}px`,
|
||||
maxWidth: 410,
|
||||
color: theme.palette.text01
|
||||
},
|
||||
launchingMeetingLabel: {
|
||||
marginTop: 24,
|
||||
textAlign: 'center',
|
||||
marginBottom: 32,
|
||||
...withPixelLineHeight(theme.typography.heading5)
|
||||
},
|
||||
roomNameLabel: {
|
||||
...withPixelLineHeight(theme.typography.bodyLongRegularLarge)
|
||||
},
|
||||
joinMeetWrapper: {
|
||||
marginTop: 24,
|
||||
width: '100%'
|
||||
},
|
||||
labelDescription: {
|
||||
textAlign: 'center',
|
||||
marginTop: 16,
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
},
|
||||
linkWrapper: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 8,
|
||||
width: '100%'
|
||||
},
|
||||
linkLabel: {
|
||||
color: theme.palette.link01,
|
||||
...withPixelLineHeight(theme.typography.bodyLongBoldLarge)
|
||||
},
|
||||
supportedBrowserContent: {
|
||||
marginTop: 16,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
labelOr: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
|
||||
},
|
||||
separator: {
|
||||
marginTop: '32px',
|
||||
height: 1,
|
||||
width: `calc(100% + ${2 * PADDINGS.leftRight}px)`,
|
||||
background: theme.palette.ui03
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const DeepLinkingMobilePage: React.FC<WithTranslation> = ({ t }) => {
|
||||
const deeplinkingCfg = useSelector((state: IReduxState) =>
|
||||
state['features/base/config']?.deeplinking || {} as IDeeplinkingConfig);
|
||||
const { hideLogo } = deeplinkingCfg;
|
||||
const deepLinkingUrl: string = useSelector(generateDeepLinkingURL);
|
||||
const room = useSelector((state: IReduxState) => decodeURIComponent(state['features/base/conference'].room || ''));
|
||||
const url = useSelector((state: IReduxState) => state['features/base/connection'] || {});
|
||||
const dispatch = useDispatch();
|
||||
const { classes: styles } = useStyles();
|
||||
|
||||
const generateDownloadURL = useCallback(() => {
|
||||
const { downloadLink, dynamicLink, appScheme }
|
||||
= (deeplinkingCfg?.[Platform.OS as keyof typeof deeplinkingCfg] || {}) as IDeeplinkingMobileConfig;
|
||||
|
||||
if (downloadLink && typeof dynamicLink === 'undefined') {
|
||||
return downloadLink;
|
||||
}
|
||||
|
||||
const {
|
||||
apn,
|
||||
appCode,
|
||||
customDomain,
|
||||
ibi,
|
||||
isi
|
||||
} = dynamicLink || {};
|
||||
|
||||
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
|
||||
|
||||
return `${domain}/?link=${
|
||||
encodeURIComponent(window.location.href)}&apn=${
|
||||
apn}&ibi=${
|
||||
ibi}&isi=${
|
||||
isi}&ius=${
|
||||
appScheme}&efr=1`;
|
||||
}, [ deeplinkingCfg ]);
|
||||
|
||||
const onDownloadApp = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'downloadAppButton', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
const onLaunchWeb = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'launchWebButton', { isMobileBrowser: true }));
|
||||
dispatch(openWebApp());
|
||||
}, []);
|
||||
|
||||
const onOpenApp = useCallback(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'clicked', 'openAppButton', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
const onOpenLinkProperties = useMemo(() => {
|
||||
const { downloadLink }
|
||||
= (deeplinkingCfg?.[Platform.OS as keyof typeof deeplinkingCfg] || {}) as IDeeplinkingMobileConfig;
|
||||
|
||||
if (downloadLink) {
|
||||
return {
|
||||
// When opening a link to the download page, we want to let the
|
||||
// OS itself handle intercepting and opening the appropriate
|
||||
// app store. This avoids potential issues with browsers, such
|
||||
// as iOS Chrome, not opening the store properly.
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
// When falling back to another URL (Firebase) let the page be
|
||||
// opened in a new window. This helps prevent the user getting
|
||||
// trapped in an app-open-cycle where going back to the mobile
|
||||
// browser re-triggers the app-open behavior.
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer'
|
||||
};
|
||||
}, [ deeplinkingCfg ]);
|
||||
|
||||
useEffect(() => {
|
||||
sendAnalytics(
|
||||
createDeepLinkingPageEvent(
|
||||
'displayed', 'DeepLinkingMobile', { isMobileBrowser: true }));
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className = { styles.container }>
|
||||
<div className = { styles.contentPane }>
|
||||
{!hideLogo && (<img
|
||||
alt = { t('welcomepage.logo.logoDeepLinking') }
|
||||
src = 'images/logo-deep-linking-mobile.png' />
|
||||
)}
|
||||
|
||||
<div className = { styles.launchingMeetingLabel }>{ t(`${_TNS}.launchMeetingLabel`) }</div>
|
||||
<div className = ''>{room}</div>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { styles.joinMeetWrapper }
|
||||
href = { deepLinkingUrl }
|
||||
onClick = { onOpenApp }
|
||||
target = '_top'>
|
||||
<Button
|
||||
fullWidth = { true }
|
||||
label = { t(`${_TNS}.joinInAppNew`) } />
|
||||
</a>
|
||||
<div className = { styles.labelDescription }>{ t(`${_TNS}.noMobileApp`) }</div>
|
||||
<a
|
||||
{ ...onOpenLinkProperties }
|
||||
className = { styles.linkWrapper }
|
||||
href = { generateDownloadURL() }
|
||||
onClick = { onDownloadApp }
|
||||
target = '_top'>
|
||||
<div className = { styles.linkLabel }>{ t(`${_TNS}.downloadMobileApp`) }</div>
|
||||
</a>
|
||||
{isSupportedMobileBrowser() ? (
|
||||
<div className = { styles.supportedBrowserContent }>
|
||||
<div className = { styles.labelOr }>OR</div>
|
||||
<a
|
||||
className = { styles.linkWrapper }
|
||||
onClick = { onLaunchWeb }
|
||||
target = '_top'>
|
||||
<div className = { styles.linkLabel }>{ t(`${_TNS}.joinInBrowser`) }</div>
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<div className = { styles.labelDescription }>
|
||||
{t(`${_TNS}.unsupportedBrowser`)}
|
||||
</div>
|
||||
)}
|
||||
<div className = { styles.separator } />
|
||||
<DialInSummary
|
||||
className = 'deep-linking-dial-in'
|
||||
clickableNumbers = { true }
|
||||
room = { room }
|
||||
url = { url } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(DeepLinkingMobilePage);
|