Compare commits

...

55 Commits

Author SHA1 Message Date
Avram Tudor
baf5aa14e8 feat(devices) scroll overflow devices texts on hover (#12974) 2023-03-01 09:38:26 +02:00
Horatiu Muresan
29b6ce7721 fix(numbers-list) Re-add sip svg 2023-02-28 11:53:26 -06:00
Mihaela Dumitru
4f95c45e50 fix(ui) change streaming icon background + add tooltip (#12973) 2023-02-28 16:52:33 +02:00
Emmanuel Pelletier
72dd609247 feat(a11y) add headings across the app for easier screen reader nav (#12427)
feat(a11y): added headings across the app for easier screen reader nav
2023-02-28 16:52:06 +02:00
Horatiu Muresan
fed74afffe fix(sound-settings) Disable checkbox for disabled sounds (#12976) 2023-02-28 16:47:00 +02:00
Mihaela Dumitru
204f34cccb fix(pinning) Fix whiteboard pinning behavior when stage filmstrip is enabled (#12966) 2023-02-28 14:44:37 +02:00
Emmanuel Pelletier
c81777a475 Make (most) UI elements reachable via keyboard (#12657)
feat(a11y): make (most) UI elements reachable via keyboard
2023-02-28 12:21:15 +02:00
Horatiu Muresan
778bca3031 fix(always-on-top) Show participant`s avatar (#12967) 2023-02-27 16:20:40 +02:00
Avram Tudor
336fa304ce feat(notifications) trigger iframe api event when a notification occurs (#12952)
* feat(notifications) trigger iframe api event when a notification occurs

* remove useless comment

* fix typo
2023-02-27 15:31:54 +02:00
damencho
f14b69166c fix: Fixes visitors count when the data is missing. 2023-02-24 11:44:31 -06:00
Calin-Teodor
e405595a11 feat(notifications): moved notifications to the bottom of the screen 2023-02-24 17:22:55 +02:00
zobadaniel
cabe48d66a lang: Add lower sorbian translation (#12947)
* add lower sorbian translation

* adjustments after using update-translation.js

* adjustments after running lint

* add translation into language list
2023-02-24 09:04:06 -06:00
Duduman Bogdan Vlad
8d7f46024b feat(webhid) - add integration webhid telephony device (#12904) 2023-02-24 16:37:30 +02:00
damencho
d7f6c2bbf0 feat: Introduces iAmVisitor mode. 2023-02-24 07:51:47 -06:00
damencho
3c69645169 feat(mobile): Adds visitors count. 2023-02-24 07:51:47 -06:00
damencho
abe2fa4dd4 feat: Adds visitors count in conference info. 2023-02-24 07:51:47 -06:00
damencho
863fd12488 feat: Hide self-view and buttons in visitors mode. 2023-02-24 07:51:47 -06:00
damencho
50c4748d40 feat: Adds visitors count in participants pane. 2023-02-24 07:51:47 -06:00
damencho
f83840a3bc feat: Adds initial impl of visitors feature. 2023-02-24 07:51:47 -06:00
damencho
1466d7d149 fix: Always normalizes bosh config. 2023-02-24 07:51:47 -06:00
Calinteodor
d0fe034db5 feat(base/dialog): visibility control for PageReloadDialog (#12961)
* feat(base/dialog): visibility control for  PageReloadDialog
2023-02-24 13:51:27 +02:00
Дамян Минков
8225f5e363 feat: Drops external connect optimization. (#12958)
* feat: Drops external connect optimization.

A bosh only optimization which is no longer used and does not bring any significant value when measured.

* squash: Updates ljm.
2023-02-24 08:59:00 +01:00
Saúl Ibarra Corretgé
c641835d0f fix(virtual-background) don't disable uploads
Even when images are provided via dyamic branding.

Fixes: https://github.com/jitsi/jitsi-meet/issues/12941
2023-02-23 16:41:27 +01:00
Horatiu Muresan
35ee92869f feat(deeplinking) Refactor deeplinking (#12950)
- redesign deeplinking mobile page, desktop page and dial in number page
- now dial in number page is an entry point in app.bundle.
2023-02-23 17:01:55 +02:00
Stefan Plücken
9b7a5ffdd1 fix(reservations): fixes errors not being displayed when reservation system returns error. removed translations applied twice. (#11144) 2023-02-23 08:52:54 -06:00
Calin-Teodor
581c2e621c feat(notifications): fix width for notifications on all devices 2023-02-23 16:39:19 +02:00
Mihaela Dumitru
f3117f3037 fix(video-layout) get pinned participant from payload to determine pinning behavior (#12951) 2023-02-23 14:41:29 +02:00
Saúl Ibarra Corretgé
877ef58dfb deps(rn) react-native-webrtc@106.0.6
Fixes to SDP negotiation and transceiver lifetime.
2023-02-23 13:08:49 +01:00
Calinteodor
19e61747b8 feat(notifications): small ui updates (#12943)
* feat(notifications): small ui updates
2023-02-23 11:49:26 +02:00
Calinteodor
b4bf363237 feat(lobby): deleted knocking participant api event (#12940)
feat(lobby): make sure that that the knocking participant event is always fired
2023-02-21 13:55:03 +02:00
Calinteodor
f8af9c4fae feat(notifications): native UI updates (#12798)
* feat(notifications): native notifications UI updates
2023-02-21 11:26:04 +02:00
Nitish Kumar
9fa426d97f fix(ParticipantsCounter)fixup badge style 2023-02-21 10:17:22 +01:00
Yash-Ambekar
e3c95e376a fix(toolbar) Removed width from toolbar (#12935) 2023-02-20 20:29:57 +02:00
Calinteodor
00ed794c50 feat(mobile/navigation): reload now navigates to conference (#12919)
* feat(mobile/navigation): navigate back to same room when reload now is initiated
2023-02-17 17:40:01 +02:00
Saúl Ibarra Corretgé
b52d5629e2 chore(deps) remove unused dependencies 2023-02-17 15:03:55 +01:00
Horatiu Muresan
9d8e646d4e fix(lobby) Fix lobby inputs (#12930) 2023-02-17 15:34:45 +02:00
Horatiu Muresan
850c0b97e4 chore(recording-notif) Show sticky notif for copy recording link (#12928) 2023-02-17 14:24:39 +02:00
Robert Pintilii
87035d0812 ref(deps) Remove atlaskit/tabs (#12927) 2023-02-17 13:36:00 +02:00
Robert Pintilii
ef0168c9ff ref(dialog) Remove atlaskit dialog (#12925) 2023-02-17 12:53:14 +02:00
Robert Pintilii
df1a5a25d4 ref(desktop-picker) Replace atlaskit tabs with our component (#12910)
Fixes wrong focus on desktop picker dialog
2023-02-17 11:34:47 +02:00
Robert Pintilii
c424884201 ref(settings-dialog) Update to use new Dialog component (#12912)
* ref(settings-dialog) Update to use new Dialog component

Created new DialogWithTabs component
Refactored Dialog into Dialog and BaseDialog
Updated dialog functionality on mobile
2023-02-17 11:34:30 +02:00
Дамян Минков
0a464a5223 feat: Avoids joining participants before jicofo is in the room. (#12923)
* feat: Avoids joining participants before jicofo is in the room.

* squash: Move away from global hook to be able to use it per muc component.
2023-02-16 18:16:43 -06:00
Horatiu Muresan
8cd62bc132 fix(external-api) Unpin all participants when participant id is null (#12921) 2023-02-16 18:14:10 +02:00
Jaya Allamsetty
123a74b38b fix(video-quality) Add pinned participants to selectedSources.
When mulltiple videos are pinned to the stage filmstrip, the expectation is that the bridge will forward all the videos even if they are of lower quality. For this, the video sources need to be added to selectedSources instead of onStageSources.
2023-02-16 10:36:22 -05:00
Christoph Settgast
dbeca806bb lang: update German translation 2023-02-15 14:03:20 -06:00
Jaya Allamsetty
f790d3e3ed chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1582.0.0+829f5ac0...v1583.0.0+931ca368
2023-02-15 13:59:05 -05:00
Horatiu Muresan
a12f7fc4d2 fix(follow-me-pinning) Fix pin/unpin when follow-me (#12911) 2023-02-15 19:08:08 +02:00
Gabriel Borlea
456ce38a10 fix(context-menu): set height for context menu when it does not have enough space at top 2023-02-15 19:06:57 +02:00
Gabriel Borlea
72ef1668f2 fix(video-background): set dialog add button margin to right size 2023-02-15 19:06:57 +02:00
Jaya Allamsetty
fce8f52574 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1578.0.0+5855ca72...v1582.0.0+829f5ac0
2023-02-15 09:15:01 -05:00
Titus Moldovan
8fcfd7a308 fix(rn) makes the preferedCode vp8 and enabled p2p 2023-02-14 16:40:38 +01:00
Saúl Ibarra Corretgé
04a41395c8 fix(notifications) remove dead code 2023-02-14 11:51:47 +01:00
Robert Pintilii
18e8201167 fix(participants-counter) Style fix (#12907) 2023-02-14 12:40:29 +02:00
Robert Pintilii
27b8794d8c feat(video-picker) Redesign (#12902)
Convert some files to TS
Implement redesign
Add Virtual background and Flip video to picker menu
2023-02-14 12:15:37 +02:00
Calinteodor
3cb0df579c feat(overlay): native page reload dialog (#12667)
feat(overlay): native feature removal + replaced PageReloadOverlay with PageReloadDialog
2023-02-14 11:50:46 +02:00
231 changed files with 6841 additions and 4408 deletions

View File

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

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

View File

@@ -141,7 +141,7 @@ import {
showNotification,
showWarningNotification
} from './react/features/notifications';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
import { suspendDetected } from './react/features/power-monitor';
import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
@@ -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']);

View File

@@ -46,7 +46,7 @@ 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: 'wss://jitsi-meet.example.com/' + subdir + 'xmpp-websocket',
@@ -1146,6 +1146,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 +1372,6 @@ var config = {
dialOutRegionUrl
disableRemoteControl
displayJids
externalConnectUrl
e2eeLabels
firefox_fake_device
googleApiApplicationClientID

View File

@@ -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
});
});
}

View File

@@ -1,3 +0,0 @@
module.exports = {
'extends': '../react/.eslintrc.js'
};

View File

@@ -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();
}

View File

@@ -4,7 +4,7 @@
&-content {
position: relative;
right: auto;
margin-bottom: 8px;
margin-bottom: 4px;
max-height: 456px;
overflow: auto;
width: 300px;
@@ -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;

View File

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

View File

@@ -3,49 +3,38 @@
display: inline-block;
&-container {
max-height: 344px;
background: $menuBG;
border-radius: 3px;
max-height: 456px;
overflow: auto;
padding: 8px;
margin-bottom: 8px;
margin-bottom: 4px;
position: relative;
right: auto;
}
&-entry {
cursor: pointer;
height: 168px;
margin-bottom: 8px;
height: 138px;
width: 244px;
position: relative;
width: 284px;
margin: 0 7px 4px;
border-radius: 6px;
box-sizing: border-box;
overflow: hidden;
&:last-child {
margin-bottom: 0;
}
&--selected {
border: 3px solid #31B76A;
border-radius: 3px;
cursor: default;
height: 162px;
width: 278px;
border: 2px solid #4687ED;
}
}
&-video {
border-radius: 3px;
height: 100%;
object-fit: cover;
width: 100%;
}
&-overlay {
background: rgba(42, 58, 75, 0.6);
height: 100%;
position: absolute;
width: 100%;
z-index: 1;
}
&-error {
align-items: center;
display: flex;
@@ -56,23 +45,22 @@
}
&-label {
bottom: 8px;
color: #fff;
position: absolute;
width: 100%;
bottom: 0;
left: 0;
right: 0;
max-width: 100%;
padding: 8px;
z-index: 2;
&-container {
margin: 0 16px;
}
&-text {
background-color: #131519;
border-radius: 3px;
padding: 2px 8px;
font-size: 13px;
line-height: 20px;
margin: 0 auto;
background-color: rgba(0, 0, 0, 0.7);
border-radius: 4px;
padding: 4px 8px;
color: #fff;
font-size: 12px;
line-height: 16px;
font-weight: 600;
max-width: calc(100% - 16px);
overflow: hidden;
text-overflow: ellipsis;
@@ -80,8 +68,8 @@
white-space: nowrap;
}
}
// Override @atlaskit/InlineDialog container which is made with styled components
& > div:nth-child(2) {
padding: 0;
&-checkbox-container {
padding: 10px 14px;
}
}

View File

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

View File

@@ -63,3 +63,8 @@
.desktop-source-preview-image-container {
padding: 10px;
}
.desktop-picker-tabs-container {
width: 65%;
margin-top: 3px;
}

View File

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

View File

@@ -60,6 +60,7 @@
.prejoin-input {
margin-bottom: 16px;
width: 100%;
& input {
text-align: center;

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

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

View File

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

View File

@@ -7,6 +7,7 @@
"cs": "Čeština",
"da": "Dansk",
"de": "Deutsch",
"dsb": "Dolnoserbšćina",
"el": "Ελληνικά",
"en": "English",
"enGB": "English (United Kingdom)",

View File

@@ -916,6 +916,7 @@
"localRecordingVideoWarning": "Um Ihr eigenes Kamerabild aufzuzeichnen, müssen Sie Ihre Kamera beim Start der Aufnahme einschalten",
"localRecordingWarning": "Bitte prüfen Sie, dass das aktuelle Tab auswählen, um Bild und Ton aufzuzeichnen. Die Länge der Aufzeichnung ist aktuell auf 1GB beschränkt, was ungefähr 100 Minuten entspricht.",
"loggedIn": "Als {{userName}} angemeldet",
"noMicPermission": "Zugriff auf Mikrofon fehlgeschlagen. Bitte erlauben Sie den Zugriff auf das Mikrofon.",
"noStreams": "Kein Ton oder Video erkannt.",
"off": "Aufnahme gestoppt",
"offBy": "{{name}} stoppte die Aufnahme",
@@ -1285,6 +1286,7 @@
"grantModerator": "Moderationsrechte vergeben",
"hideSelfView": "Eigene Ansicht ausblenden",
"kick": "Hinauswerfen",
"mirrorVideo": "Mein Video spiegeln",
"moderator": "Moderation",
"mute": "Person ist stumm geschaltet",
"muted": "Stummgeschaltet",

1379
lang/main-dsb.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 dont 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,6 +211,12 @@
"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",
@@ -438,6 +452,11 @@
"veryBad": "Very Bad",
"veryGood": "Very Good"
},
"filmstrip": {
"accessibilityLabel": {
"heading": "Video thumbnails"
}
},
"giphy": {
"noResults": "No results found :(",
"search": "Search GIPHY"
@@ -751,6 +770,7 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Meeting participants ({{count}})",
"visitors": "Visitors ({{count}})",
"waitingLobby": "Waiting in lobby ({{count}})"
},
"search": "Search participants",
@@ -758,6 +778,7 @@
},
"passwordDigitsOnly": "Up to {{number}} digits",
"passwordSetRemotely": "Set by another participant",
"pinParticipant": "{{participantName}} - Pin",
"pinnedParticipant": "The participant is pinned",
"polls": {
"answer": {
@@ -1081,6 +1102,7 @@
"giphy": "Toggle GIPHY menu",
"grantModerator": "Grant Moderator Rights",
"hangup": "Leave the meeting",
"heading": "Toolbar",
"help": "Help",
"invite": "Invite people",
"kick": "Kick participant",
@@ -1234,6 +1256,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 +1295,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",
@@ -1286,6 +1311,7 @@
"grantModerator": "Grant Moderator Rights",
"hideSelfView": "Hide self view",
"kick": "Kick out",
"mirrorVideo": "Mirror my video",
"moderator": "Moderator",
"mute": "Participant is muted",
"muted": "Muted",
@@ -1323,6 +1349,7 @@
"webAssemblyWarning": "WebAssembly not supported",
"webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
},
"visitorsLabel": "Number of visitors: {{count}}",
"volumeSlider": "Volume slider",
"welcomepage": {
"accessibilityLabel": {
@@ -1374,5 +1401,10 @@
"terms": "Terms",
"title": "Secure, fully featured, and completely free video conferencing",
"upcomingMeetings": "Your upcoming meetings"
},
"whiteboard": {
"accessibilityLabel": {
"heading": "Whiteboard"
}
}
}

View File

@@ -71,8 +71,13 @@ import {
import { appendSuffix } from '../../react/features/display-name';
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/actions';
import { addStageParticipant, resizeFilmStrip, setVolume } from '../../react/features/filmstrip/actions.web';
import { isStageFilmstripAvailable } from '../../react/features/filmstrip/functions.web';
import {
addStageParticipant,
resizeFilmStrip,
setVolume,
togglePinStageParticipant
} from '../../react/features/filmstrip/actions.web';
import { getPinnedActiveParticipants, isStageFilmstripAvailable } from '../../react/features/filmstrip/functions.web';
import { invite } from '../../react/features/invite';
import {
selectParticipantInLargeVideo
@@ -241,6 +246,22 @@ function initCommands() {
logger.debug('Pin participant command received');
const state = APP.store.getState();
// if id not provided, unpin everybody.
if (!id) {
if (isStageFilmstripAvailable(state)) {
const pinnedParticipants = getPinnedActiveParticipants(state);
pinnedParticipants?.forEach(p => {
APP.store.dispatch(togglePinStageParticipant(p.participantId));
});
} else {
APP.store.dispatch(pinParticipant());
}
return;
}
const participant = videoType === VIDEO_TYPE.DESKTOP
? getVirtualScreenshareParticipantByOwnerId(state, id) : getParticipantById(state, id);
@@ -254,7 +275,7 @@ function initCommands() {
const participantId = participant.id;
if (isStageFilmstripAvailable(APP.store.getState())) {
if (isStageFilmstripAvailable(state)) {
APP.store.dispatch(addStageParticipant(participantId, true));
} else {
APP.store.dispatch(pinParticipant(participantId));
@@ -1208,6 +1229,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.
*

View File

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

407
package-lock.json generated
View File

@@ -15,10 +15,8 @@
"@atlaskit/icon": "21.2.0",
"@atlaskit/inline-dialog": "13.0.9",
"@atlaskit/inline-message": "11.0.8",
"@atlaskit/modal-dialog": "11.2.4",
"@atlaskit/multi-select": "15.0.5",
"@atlaskit/spinner": "15.0.6",
"@atlaskit/tabs": "12.1.2",
"@atlaskit/theme": "11.0.2",
"@atlaskit/tooltip": "17.1.2",
"@emotion/react": "11.10.0",
@@ -52,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",
@@ -73,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/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1585.0.0+362d1b2c/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -112,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",
@@ -140,7 +139,6 @@
"@babel/preset-env": "7.16.0",
"@babel/preset-flow": "7.16.0",
"@babel/preset-react": "7.16.0",
"@babel/runtime": "7.16.0",
"@jitsi/eslint-config": "4.1.5",
"@types/js-md5": "0.4.3",
"@types/lodash": "4.14.182",
@@ -167,13 +165,11 @@
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-native": "3.11.0",
"eslint-plugin-typescript-sort-keys": "2.1.0",
"imports-loader": "0.7.1",
"jetifier": "1.6.4",
"metro-react-native-babel-preset": "0.67.0",
"patch-package": "6.4.7",
"process": "0.11.10",
"sass": "1.26.8",
"string-replace-loader": "3.0.3",
"style-loader": "3.3.1",
"traverse": "0.6.6",
"ts-loader": "9.4.1",
@@ -336,44 +332,6 @@
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/blanket": {
"version": "11.4.1",
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-11.4.1.tgz",
"integrity": "sha512-jQor3MUcsD04/lUjdW89daYw6t0gFEuWzyqYGKgDQpVeQ3n0Lfrg5S3Eogc2cbRzgKMGaET6HMB+FD6EpE2QsQ==",
"dependencies": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/ds-lib": "^1.2.0",
"@atlaskit/theme": "^11.2.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
},
"peerDependencies": {
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/blanket/node_modules/@atlaskit/theme": {
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-11.5.2.tgz",
"integrity": "sha512-iycVcMzGaPboTVu0s+DoeV3oZZGIxXca1IjVtlxERA/78dftYoVTjiBnh6XtAQnIgB0O+SaAOQaaHJZgJ4gwpQ==",
"dependencies": {
"@atlaskit/tokens": "^0.2.0",
"@babel/runtime": "^7.0.0",
"exenv": "^1.2.2",
"prop-types": "^15.5.10"
},
"peerDependencies": {
"react": "^16.8.0",
"styled-components": "^3.2.6"
}
},
"node_modules/@atlaskit/blanket/node_modules/@atlaskit/tokens": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@atlaskit/tokens/-/tokens-0.2.1.tgz",
"integrity": "sha512-Fm/F9mOC7QI5OSwvyY5N5+cvhx0lPLPAE5sE520ztb+YOaSiFxHZ13YBJkkdVTtxbGsYayYWITinGDQcCnhLog==",
"dependencies": {
"@babel/runtime": "^7.0.0"
}
},
"node_modules/@atlaskit/button": {
"version": "15.1.4",
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-15.1.4.tgz",
@@ -663,93 +621,6 @@
"styled-components": "^3.2.6"
}
},
"node_modules/@atlaskit/modal-dialog": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/@atlaskit/modal-dialog/-/modal-dialog-11.2.4.tgz",
"integrity": "sha512-+Qe2Ai2qiBCaLm5F144Kn+M8931097mlZTbBppCrhBbanOyZJHrRkzqbCCd5NGGLpfLUZxfZaQYJssrHfGCDnA==",
"dependencies": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/blanket": "^11.0.0",
"@atlaskit/button": "^15.1.0",
"@atlaskit/icon": "^21.1.0",
"@atlaskit/portal": "^4.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.7",
"exenv": "^1.2.2",
"raf-schd": "^2.1.0",
"react-focus-lock": "^1.19.1",
"react-scrolllock": "^5.0.1",
"react-transition-group": "^4.4.1",
"react-uid": "^2.2.0",
"tiny-invariant": "^0.0.3"
},
"peerDependencies": {
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/modal-dialog/node_modules/@emotion/styled": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz",
"integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==",
"dependencies": {
"@emotion/styled-base": "^10.3.0",
"babel-plugin-emotion": "^10.0.27"
},
"peerDependencies": {
"@emotion/core": "^10.0.27",
"react": ">=16.3.0"
}
},
"node_modules/@atlaskit/modal-dialog/node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/@atlaskit/modal-dialog/node_modules/focus-lock": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.8.tgz",
"integrity": "sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og=="
},
"node_modules/@atlaskit/modal-dialog/node_modules/raf-schd": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
"integrity": "sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g=="
},
"node_modules/@atlaskit/modal-dialog/node_modules/react-focus-lock": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-1.19.1.tgz",
"integrity": "sha512-TPpfiack1/nF4uttySfpxPk4rGZTLXlaZl7ncZg/ELAk24Iq2B1UUaUioID8H8dneUXqznT83JTNDHDj+kwryw==",
"dependencies": {
"@babel/runtime": "^7.0.0",
"focus-lock": "^0.6.3",
"prop-types": "^15.6.2",
"react-clientside-effect": "^1.2.0"
},
"peerDependencies": {
"react": "^15.0.0 || ^16.0.0"
}
},
"node_modules/@atlaskit/modal-dialog/node_modules/react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/@atlaskit/motion": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/@atlaskit/motion/-/motion-0.4.8.tgz",
@@ -845,20 +716,6 @@
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/tabs": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/tabs/-/tabs-12.1.2.tgz",
"integrity": "sha512-/PbNIwXh0YMoo8pUjLwJfZEe+vtHENhBWuQc1pK4rW7JNBAD0VYrAJ+JLiZ+ZWGFCOoCSneoJguDBH0I2FMrqA==",
"dependencies": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
},
"peerDependencies": {
"react": "^16.8.0"
}
},
"node_modules/@atlaskit/tag": {
"version": "11.2.5",
"resolved": "https://registry.npmjs.org/@atlaskit/tag/-/tag-11.2.5.tgz",
@@ -6650,6 +6507,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",
@@ -12088,16 +11950,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imports-loader": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.7.1.tgz",
"integrity": "sha1-8gS180cCoywdt9SNidXoZ6BEElM=",
"dev": true,
"dependencies": {
"loader-utils": "^1.0.2",
"source-map": "^0.5.6"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -13553,8 +13405,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
"integrity": "sha512-AAEClrQNOVHNO1lKr/F1SOyiduZfI6bql3eiIxC3LZ5cBcyoRVmwI6uiAbLail0VkuKnTecEWcfYZ9lvokxrMw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1585.0.0+362d1b2c/lib-jitsi-meet.tgz",
"integrity": "sha512-g7JVvBfZixl1fKZI4ZMm3nvMasEz5sdapMzZdc76kA/eZSej2QuNK+W9cB8IypB7dqeTM4yzbfzi9rDipyWn+w==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
@@ -16545,9 +16397,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",
@@ -18207,51 +18059,6 @@
"integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==",
"dev": true
},
"node_modules/string-replace-loader": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.0.3.tgz",
"integrity": "sha512-8c26Dl6H9XmKNj3mFBvaUYR7ImOxQ4YRBFuUju78wXpa1cDpyDYvKmqGg8mfkxdYexQ/BBogB7PELlLnmR08nw==",
"dev": true,
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"peerDependencies": {
"webpack": "^5"
}
},
"node_modules/string-replace-loader/node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/string-replace-loader/node_modules/schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/string-replace-to-array": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz",
@@ -18787,11 +18594,6 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"node_modules/tiny-invariant": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-0.0.3.tgz",
"integrity": "sha512-SA2YwvDrCITM9fTvHTHRpq9W6L2fBsClbqm3maT5PZux4Z73SPPDYwJMtnoWh6WMgmCkJij/LaOlWiqJqFMK8g=="
},
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
@@ -20729,39 +20531,6 @@
"tslib": "^2.0.0"
}
},
"@atlaskit/blanket": {
"version": "11.4.1",
"resolved": "https://registry.npmjs.org/@atlaskit/blanket/-/blanket-11.4.1.tgz",
"integrity": "sha512-jQor3MUcsD04/lUjdW89daYw6t0gFEuWzyqYGKgDQpVeQ3n0Lfrg5S3Eogc2cbRzgKMGaET6HMB+FD6EpE2QsQ==",
"requires": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/ds-lib": "^1.2.0",
"@atlaskit/theme": "^11.2.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
},
"dependencies": {
"@atlaskit/theme": {
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/@atlaskit/theme/-/theme-11.5.2.tgz",
"integrity": "sha512-iycVcMzGaPboTVu0s+DoeV3oZZGIxXca1IjVtlxERA/78dftYoVTjiBnh6XtAQnIgB0O+SaAOQaaHJZgJ4gwpQ==",
"requires": {
"@atlaskit/tokens": "^0.2.0",
"@babel/runtime": "^7.0.0",
"exenv": "^1.2.2",
"prop-types": "^15.5.10"
}
},
"@atlaskit/tokens": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@atlaskit/tokens/-/tokens-0.2.1.tgz",
"integrity": "sha512-Fm/F9mOC7QI5OSwvyY5N5+cvhx0lPLPAE5sE520ztb+YOaSiFxHZ13YBJkkdVTtxbGsYayYWITinGDQcCnhLog==",
"requires": {
"@babel/runtime": "^7.0.0"
}
}
}
},
"@atlaskit/button": {
"version": "15.1.4",
"resolved": "https://registry.npmjs.org/@atlaskit/button/-/button-15.1.4.tgz",
@@ -20990,81 +20759,6 @@
"react-scrolllock": "^5.0.1"
}
},
"@atlaskit/modal-dialog": {
"version": "11.2.4",
"resolved": "https://registry.npmjs.org/@atlaskit/modal-dialog/-/modal-dialog-11.2.4.tgz",
"integrity": "sha512-+Qe2Ai2qiBCaLm5F144Kn+M8931097mlZTbBppCrhBbanOyZJHrRkzqbCCd5NGGLpfLUZxfZaQYJssrHfGCDnA==",
"requires": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/blanket": "^11.0.0",
"@atlaskit/button": "^15.1.0",
"@atlaskit/icon": "^21.1.0",
"@atlaskit/portal": "^4.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9",
"@emotion/styled": "^10.0.7",
"exenv": "^1.2.2",
"raf-schd": "^2.1.0",
"react-focus-lock": "^1.19.1",
"react-scrolllock": "^5.0.1",
"react-transition-group": "^4.4.1",
"react-uid": "^2.2.0",
"tiny-invariant": "^0.0.3"
},
"dependencies": {
"@emotion/styled": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz",
"integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==",
"requires": {
"@emotion/styled-base": "^10.3.0",
"babel-plugin-emotion": "^10.0.27"
}
},
"dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"focus-lock": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/focus-lock/-/focus-lock-0.6.8.tgz",
"integrity": "sha512-vkHTluRCoq9FcsrldC0ulQHiyBYgVJB2CX53I8r0nTC6KnEij7Of0jpBspjt3/CuNb6fyoj3aOh9J2HgQUM0og=="
},
"raf-schd": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-2.1.2.tgz",
"integrity": "sha512-Orl0IEvMtUCgPddgSxtxreK77UiQz4nPYJy9RggVzu4mKsZkQWiAaG1y9HlYWdvm9xtN348xRaT37qkvL/+A+g=="
},
"react-focus-lock": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-1.19.1.tgz",
"integrity": "sha512-TPpfiack1/nF4uttySfpxPk4rGZTLXlaZl7ncZg/ELAk24Iq2B1UUaUioID8H8dneUXqznT83JTNDHDj+kwryw==",
"requires": {
"@babel/runtime": "^7.0.0",
"focus-lock": "^0.6.3",
"prop-types": "^15.6.2",
"react-clientside-effect": "^1.2.0"
}
},
"react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
}
}
},
"@atlaskit/motion": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/@atlaskit/motion/-/motion-0.4.8.tgz",
@@ -21139,17 +20833,6 @@
"@emotion/core": "^10.0.9"
}
},
"@atlaskit/tabs": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@atlaskit/tabs/-/tabs-12.1.2.tgz",
"integrity": "sha512-/PbNIwXh0YMoo8pUjLwJfZEe+vtHENhBWuQc1pK4rW7JNBAD0VYrAJ+JLiZ+ZWGFCOoCSneoJguDBH0I2FMrqA==",
"requires": {
"@atlaskit/analytics-next": "^8.0.0",
"@atlaskit/theme": "^11.0.0",
"@babel/runtime": "^7.0.0",
"@emotion/core": "^10.0.9"
}
},
"@atlaskit/tag": {
"version": "11.2.5",
"resolved": "https://registry.npmjs.org/@atlaskit/tag/-/tag-11.2.5.tgz",
@@ -25380,6 +25063,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",
@@ -29506,16 +29194,6 @@
"resolve-cwd": "^3.0.0"
}
},
"imports-loader": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/imports-loader/-/imports-loader-0.7.1.tgz",
"integrity": "sha1-8gS180cCoywdt9SNidXoZ6BEElM=",
"dev": true,
"requires": {
"loader-utils": "^1.0.2",
"source-map": "^0.5.6"
}
},
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -30611,8 +30289,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
"integrity": "sha512-AAEClrQNOVHNO1lKr/F1SOyiduZfI6bql3eiIxC3LZ5cBcyoRVmwI6uiAbLail0VkuKnTecEWcfYZ9lvokxrMw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1585.0.0+362d1b2c/lib-jitsi-meet.tgz",
"integrity": "sha512-g7JVvBfZixl1fKZI4ZMm3nvMasEz5sdapMzZdc76kA/eZSej2QuNK+W9cB8IypB7dqeTM4yzbfzi9rDipyWn+w==",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",
@@ -32895,9 +32573,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",
@@ -34180,40 +33858,6 @@
"integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==",
"dev": true
},
"string-replace-loader": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.0.3.tgz",
"integrity": "sha512-8c26Dl6H9XmKNj3mFBvaUYR7ImOxQ4YRBFuUju78wXpa1cDpyDYvKmqGg8mfkxdYexQ/BBogB7PELlLnmR08nw==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"dependencies": {
"loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"schema-utils": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz",
"integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
}
}
}
},
"string-replace-to-array": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz",
@@ -34609,11 +34253,6 @@
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
"dev": true
},
"tiny-invariant": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-0.0.3.tgz",
"integrity": "sha512-SA2YwvDrCITM9fTvHTHRpq9W6L2fBsClbqm3maT5PZux4Z73SPPDYwJMtnoWh6WMgmCkJij/LaOlWiqJqFMK8g=="
},
"tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",

View File

@@ -20,10 +20,8 @@
"@atlaskit/icon": "21.2.0",
"@atlaskit/inline-dialog": "13.0.9",
"@atlaskit/inline-message": "11.0.8",
"@atlaskit/modal-dialog": "11.2.4",
"@atlaskit/multi-select": "15.0.5",
"@atlaskit/spinner": "15.0.6",
"@atlaskit/tabs": "12.1.2",
"@atlaskit/theme": "11.0.2",
"@atlaskit/tooltip": "17.1.2",
"@emotion/react": "11.10.0",
@@ -57,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",
@@ -78,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/v1578.0.0+5855ca72/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1585.0.0+362d1b2c/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -117,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",
@@ -145,7 +144,6 @@
"@babel/preset-env": "7.16.0",
"@babel/preset-flow": "7.16.0",
"@babel/preset-react": "7.16.0",
"@babel/runtime": "7.16.0",
"@jitsi/eslint-config": "4.1.5",
"@types/js-md5": "0.4.3",
"@types/lodash": "4.14.182",
@@ -172,13 +170,11 @@
"eslint-plugin-react": "7.26.1",
"eslint-plugin-react-native": "3.11.0",
"eslint-plugin-typescript-sort-keys": "2.1.0",
"imports-loader": "0.7.1",
"jetifier": "1.6.4",
"metro-react-native-babel-preset": "0.67.0",
"patch-package": "6.4.7",
"process": "0.11.10",
"sass": "1.26.8",
"string-replace-loader": "3.0.3",
"style-loader": "3.3.1",
"traverse": "0.6.6",
"ts-loader": "9.4.1",

View File

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

View File

@@ -30,12 +30,10 @@ import {
// @ts-ignore
import { screen } from '../mobile/navigation/routes';
import { clearNotifications } from '../notifications/actions';
// @ts-ignore
import { setFatalError } from '../overlay';
import { addTrackStateToURL, getDefaultURL } from './functions.native';
import logger from './logger';
import { IStore } from './types';
import { IReloadNowOptions, IStore } from './types';
export * from './actions.any';
@@ -46,9 +44,10 @@ export * from './actions.any';
* @param {string|undefined} uri - The URI to which to navigate. It may be a
* full URL with an HTTP(S) scheme, a full or partial URI with the app-specific
* scheme, or a mere room name.
* @param {Object} [options] - Options.
* @returns {Function}
*/
export function appNavigate(uri?: string) {
export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
logger.info(`appNavigate to ${uri}`);
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
@@ -144,7 +143,10 @@ export function appNavigate(uri?: string) {
dispatch(createDesiredLocalTracks());
dispatch(clearNotifications());
if (isPrejoinPageEnabled(getState())) {
// @ts-ignore
const { hidePrejoin } = options;
if (!hidePrejoin && isPrejoinPageEnabled(getState())) {
navigateRoot(screen.preJoin);
} else {
dispatch(connect());
@@ -177,7 +179,6 @@ export function maybeRedirectToWelcomePage(options: any) { // eslint-disable-lin
*/
export function reloadNow() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];
@@ -188,6 +189,8 @@ export function reloadNow() {
logger.info(`Reloading the conference using URL: ${locationURL}`);
dispatch(appNavigate(toURLString(newURL)));
dispatch(appNavigate(toURLString(newURL), {
hidePrejoin: true
}));
};
}

View File

@@ -20,7 +20,6 @@ import {
import { isVpaasMeeting } from '../jaas/functions';
import { clearNotifications, showNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { setFatalError } from '../overlay/actions';
import { isWelcomePageEnabled } from '../welcome/functions';
import {
@@ -222,7 +221,6 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea
*/
export function reloadNow() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
dispatch(setFatalError(undefined));
const state = getState();
const { locationURL } = state['features/base/connection'];

View File

@@ -1,10 +1,7 @@
// @flow
import React, { Fragment } from 'react';
import React from 'react';
import { BaseApp } from '../../base/app';
import { toURLString } from '../../base/util';
import { OverlayContainer } from '../../overlay';
import { appNavigate } from '../actions';
import { getDefaultURL } from '../functions';
@@ -73,23 +70,7 @@ export class AbstractApp extends BaseApp<Props, *> {
}
}
/**
* Creates an extra {@link ReactElement}s to be added (unconditionally)
* alongside the main element.
*
* @abstract
* @protected
* @returns {ReactElement}
*/
_createExtraElement() {
return (
<Fragment>
<OverlayContainer />
</Fragment>
);
}
_createMainElement: (React$Element<*>, Object) => ?React$Element<*>;
_createMainElement: (React.ReactElement, Object) => ?React.ReactElement;
/**
* Gets the default URL to be opened when this {@code App} mounts.

View File

@@ -1,12 +1,11 @@
// @flow
import { AtlasKitThemeProvider } from '@atlaskit/theme';
import React from 'react';
import React, { Fragment } from 'react';
import GlobalStyles from '../../base/ui/components/GlobalStyles.web';
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web';
import DialogContainer from '../../base/ui/components/web/DialogContainer';
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
import OverlayContainer from '../../overlay/components/web/OverlayContainer';
import { AbstractApp } from './AbstractApp';
@@ -14,12 +13,30 @@ import { AbstractApp } from './AbstractApp';
import '../middlewares';
import '../reducers';
/**
* Root app {@code Component} on Web/React.
*
* @augments AbstractApp
*/
export class App extends AbstractApp {
/**
* Creates an extra {@link ReactElement}s to be added (unconditionally)
* alongside the main element.
*
* @abstract
* @protected
* @returns {ReactElement}
*/
_createExtraElement() {
return (
<Fragment>
<OverlayContainer />
</Fragment>
);
}
/**
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
* the top most component.

View File

@@ -48,5 +48,6 @@ import '../transcribing/middleware';
import '../video-layout/middleware';
import '../video-quality/middleware';
import '../videosipgw/middleware';
import '../visitors/middleware';
import './middleware';

View File

@@ -15,12 +15,12 @@ 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';
import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware';
import '../base/dialog/middleware';
import './middlewares.any';

View File

@@ -56,3 +56,4 @@ import '../transcribing/reducer';
import '../video-layout/reducer';
import '../video-quality/reducer';
import '../videosipgw/reducer';
import '../visitors/reducer';

View File

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

View File

@@ -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,5 +165,11 @@ 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;
}
export interface IReloadNowOptions {
hidePrejoin?: boolean;
}

View File

@@ -98,7 +98,7 @@ function _upgradeRoleFinished(
name: authenticationError || connectionError,
...other
};
progress = authenticationError ? 0.5 : 0;
progress = 0;
}
return {

View File

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

View File

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

View File

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

View File

@@ -388,6 +388,11 @@ export interface IConfig {
lastNLimits?: {
[key: number]: number;
};
legalUrls?: {
helpCentre: string;
privacy: string;
terms: string;
};
liveStreaming?: {
dataPrivacyLink?: string;
enabled?: boolean;

View File

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

View File

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

View File

@@ -52,9 +52,7 @@ function _setConfig({ dispatch, getState }: IStore, next: Function, action: AnyA
const settings = state['features/base/settings'];
const config: IConfig = {};
// FIXME: P2P is currently temporality disabled on mobile.
// eslint-disable-next-line no-constant-condition
if (false && typeof settings.disableP2P !== 'undefined') {
if (typeof settings.disableP2P !== 'undefined') {
config.p2p = { enabled: !settings.disableP2P };
}

View File

@@ -57,10 +57,8 @@ const INITIAL_RN_STATE: IConfig = {
// than requiring this override here...
p2p: {
// Temporarily disable P2P on mobile while we sort out some (codec?) issues.
enabled: false,
disabledCodec: 'vp9',
preferredCodec: 'h264'
preferredCodec: 'vp8'
},
videoQuality: {

View File

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

View File

@@ -1,43 +0,0 @@
// @flow
import React, { Component } from 'react';
import { Container, Text } from '../../react';
import { type StyleType } from '../../styles';
import styles from './styles';
type Props = {
/**
* Children of the component.
*/
children: string | React$Node,
style: ?StyleType
};
/**
* Generic dialog content container to provide the same styling for all custom
* dialogs.
*/
export default class DialogContent extends Component<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
const { children, style } = this.props;
const childrenComponent = typeof children === 'string'
? <Text style = { style }>{ children }</Text>
: children;
return (
<Container style = { styles.dialogContainer }>
{ childrenComponent }
</Container>
);
}
}

View File

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

View File

@@ -48,7 +48,7 @@ type Props = {
/**
* Dialog title.
*/
title?: string,
title?: string
};
/**

View File

@@ -0,0 +1,231 @@
/* eslint-disable lines-around-comment */
// @ts-ignore
import { randomInt } from '@jitsi/js-utils/random';
import React, { Component } from 'react';
import { WithTranslation } from 'react-i18next';
import type { Dispatch } from 'redux';
import { appNavigate, reloadNow } from '../../../../app/actions.native';
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';
// @ts-ignore
import ConfirmDialog from './ConfirmDialog';
/**
* The type of the React {@code Component} props of
* {@link PageReloadDialog}.
*/
interface IPageReloadDialogProps extends WithTranslation {
dispatch: Dispatch<any>;
isNetworkFailure: boolean;
reason: string;
}
/**
* The type of the React {@code Component} state of
* {@link PageReloadDialog}.
*/
interface IPageReloadDialogState {
timeLeft: number;
}
/**
* Implements a React Component that is shown before the
* conference is reloaded.
* Shows a warning message and counts down towards the re-load.
*/
class PageReloadDialog extends Component<IPageReloadDialogProps, IPageReloadDialogState> {
// @ts-ignore
_interval: IntervalID;
_timeoutSeconds: number;
/**
* Initializes a new PageReloadOverlay instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
* @public
*/
constructor(props: IPageReloadDialogProps) {
super(props);
this._timeoutSeconds = 10 + randomInt(0, 20);
this.state = {
timeLeft: this._timeoutSeconds
};
this._onCancel = this._onCancel.bind(this);
this._onReloadNow = this._onReloadNow.bind(this);
this._onReconnecting = this._onReconnecting.bind(this);
}
/**
* React Component method that executes once component is mounted.
*
* @inheritdoc
* @returns {void}
*/
componentDidMount() {
const { timeLeft } = this.state;
logger.info(
`The conference will be reloaded after ${timeLeft} seconds.`
);
this._interval = setInterval(() =>
this._onReconnecting(), 1000);
}
/**
* Clears the timer interval.
*
* @inheritdoc
* @returns {void}
*/
componentWillUnmount() {
if (this._interval) {
clearInterval(this._interval);
this._interval = undefined;
}
}
/**
* Handle clicking of the "Cancel" button. It will navigate back to the
* welcome page.
*
* @private
* @returns {boolean}
*/
_onCancel() {
const { dispatch } = this.props;
clearInterval(this._interval);
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
* kick in.
*
* @private
* @returns {boolean}
*/
_onReloadNow() {
const { dispatch } = this.props;
clearInterval(this._interval);
dispatch(reloadNow());
return true;
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
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
cancelLabel = 'dialog.Cancel'
confirmLabel = 'dialog.rejoinNow'
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
onCancel = { this._onCancel }
onSubmit = { this._onReloadNow }
title = { title } />
);
}
}
/**
* Maps (parts of) the redux state to the associated component's props.
*
* @param {Object} state - The redux state.
* @protected
* @returns {{
* isNetworkFailure: boolean,
* reason: string
* }}
*/
function mapStateToProps(state: IReduxState) {
const { error: conferenceError } = state['features/base/conference'];
const { error: configError } = state['features/base/config'];
const { error: connectionError } = state['features/base/connection'];
const { fatalError } = state['features/overlay'];
const fatalConnectionError
// @ts-ignore
= connectionError && isFatalJitsiConnectionError(connectionError);
const fatalConfigError = fatalError === configError;
const isNetworkFailure = fatalConfigError || fatalConnectionError;
let reason;
if (conferenceError) {
reason = `error.conference.${conferenceError.name}`;
} else if (connectionError) {
reason = `error.conference.${connectionError.name}`;
} else if (configError) {
reason = `error.config.${configError.name}`;
} else {
logger.error('No reload reason defined!');
}
return {
isNetworkFailure,
reason
};
}
export default translate(connect(mapStateToProps)(PageReloadDialog));

View File

@@ -5,6 +5,7 @@ export { default as ConfirmDialog } from './ConfirmDialog';
export { default as DialogContainer } from './DialogContainer';
export { default as AlertDialog } from './AlertDialog';
export { default as InputDialog } from './InputDialog';
export { default as PageReloadDialog } from './PageReloadDialog';
// NOTE: Some dialogs reuse the style of these base classes for consistency
// and as we're in a /native namespace, it's safe to export the styles.

View File

@@ -1,14 +0,0 @@
import { BoxModel, createStyleSheet } from '../../styles';
/**
* The React {@code Component} styles of {@code Dialog}.
*/
export default createStyleSheet({
/**
* Unified container for a consistent Dialog style.
*/
dialogContainer: {
paddingHorizontal: BoxModel.padding,
paddingVertical: 1.5 * BoxModel.padding
}
});

View File

@@ -1,5 +0,0 @@
/**
* Placeholder styles for web to be able to use cross platform components
* unmodified such as {@code DialogContent}.
*/
export default {};

View File

@@ -5,11 +5,6 @@ import { Component } from 'react';
*/
export interface IProps {
/**
* Function that closes the dialog.
*/
closeDialog: Function;
/**
* Callback to invoke on change.
*/

View File

@@ -1,93 +0,0 @@
// @flow
import React from 'react';
import { connect } from '../../../redux';
import AbstractDialog from '../AbstractDialog';
import type { Props as AbstractDialogProps, State } from '../AbstractDialog';
import StatelessDialog from './StatelessDialog';
/**
* The type of the React {@code Component} props of {@link Dialog}.
*/
type Props = AbstractDialogProps & {
/**
* True if listening for the Enter key should be disabled.
*/
disableEnter: boolean,
/**
* Whether the dialog is modal. This means clicking on the blanket will
* leave the dialog open. No cancel button.
*/
isModal: boolean,
/**
* Disables rendering of the submit button.
*/
submitDisabled: boolean,
/**
* Width of the dialog, can be:
* - 'small' (400px), 'medium' (600px), 'large' (800px),
* 'x-large' (968px)
* - integer value for pixel width
* - string value for percentage.
*/
width: string
};
/**
* Web dialog that uses atlaskit modal-dialog to display dialogs.
*/
class Dialog extends AbstractDialog<Props, State> {
/**
* Initializes a new Dialog instance.
*
* @param {Object} props - The read-only properties 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._onCancel = this._onCancel.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const props = {
...this.props,
onCancel: this._onCancel,
onSubmit: this._onSubmit
};
// $FlowExpectedError
delete props.dispatch;
return <StatelessDialog { ...props } />;
}
_onCancel: () => void;
/**
* Dispatches action to hide the dialog.
*
* @returns {void}
*/
_onCancel() {
this.props.isModal || super._onCancel();
}
_onSubmit: (?string) => void;
}
export default connect()(Dialog);

View File

@@ -1,256 +0,0 @@
// @flow
import Tabs from '@atlaskit/tabs';
import React, { Component } from 'react';
import { translate } from '../../../i18n/functions';
import logger from '../../logger';
import StatelessDialog from './StatelessDialog';
/**
* The type of the React {@code Component} props of {@link DialogWithTabs}.
*/
export type Props = {
/**
* Function that closes the dialog.
*/
closeDialog: Function,
/**
* Css class name that will be added to the dialog.
*/
cssClassName: string,
/**
* Which settings tab should be initially displayed. If not defined then
* the first tab will be displayed.
*/
defaultTab: number,
/**
* Disables dismissing the dialog when the blanket is clicked. Enabled
* by default.
*/
disableBlanketClickDismiss: boolean,
/**
* Callback invoked when the Save button has been pressed.
*/
onSubmit: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* Information about the tabs that will be rendered.
*/
tabs: Array<Object>,
/**
* Key to use for showing a title.
*/
titleKey: string
};
/**
* The type of the React {@code Component} state of {@link DialogWithTabs}.
*/
type State = {
/**
* The index of the tab that should be displayed.
*/
selectedTab: number,
/**
* An array of the states of the tabs.
*/
tabStates: Array<Object>
};
/**
* A React {@code Component} for displaying a dialog with tabs.
*
* @augments Component
*/
class DialogWithTabs extends Component<Props, State> {
/**
* Initializes a new {@code DialogWithTabs} 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);
this.state = {
selectedTab: this.props.defaultTab || 0,
tabStates: this.props.tabs.map(tab => tab.props)
};
this._onSubmit = this._onSubmit.bind(this);
this._onTabSelected = this._onTabSelected.bind(this);
this._onTabStateChange = this._onTabStateChange.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const onCancel = this.props.closeDialog;
return (
<StatelessDialog
disableBlanketClickDismiss
= { this.props.disableBlanketClickDismiss }
onCancel = { onCancel }
onSubmit = { this._onSubmit }
titleKey = { this.props.titleKey } >
<div className = { this.props.cssClassName } >
{ this._renderTabs() }
</div>
</StatelessDialog>
);
}
/**
* Gets the props to pass into the tab component.
*
* @param {number} tabId - The index of the tab configuration within
* {@link this.state.tabStates}.
* @returns {Object}
*/
_getTabProps(tabId) {
const { tabs } = this.props;
const { tabStates } = this.state;
const tabConfiguration = tabs[tabId];
const currentTabState = tabStates[tabId];
if (tabConfiguration.propsUpdateFunction) {
return tabConfiguration.propsUpdateFunction(
currentTabState,
tabConfiguration.props);
}
return { ...currentTabState };
}
_onTabSelected: (Object, number) => void;
/**
* Callback invoked when the desired tab to display should be changed.
*
* @param {Object} tab - The configuration passed into atlaskit tabs to
* describe how to display the selected tab.
* @param {number} tabIndex - The index of the tab within the array of
* displayed tabs.
* @private
* @returns {void}
*/
_onTabSelected(tab, tabIndex) { // eslint-disable-line no-unused-vars
this.setState({ selectedTab: tabIndex });
}
/**
* Renders the tabs from the tab information passed on props.
*
* @returns {void}
*/
_renderTabs() {
const { t, tabs } = this.props;
if (tabs.length === 1) {
return this._renderTab({
...tabs[0],
tabId: 0
});
}
if (tabs.length > 1) {
return (
<Tabs
onSelect = { this._onTabSelected }
selected = { this.state.selectedTab }
tabs = {
tabs.map(({ component, label, styles }, idx) => {
return {
content: this._renderTab({
component,
styles,
tabId: idx
}),
label: t(label)
};
})
} />);
}
logger.warn('No settings tabs configured to display.');
return null;
}
/**
* Renders a tab from the tab information passed as parameters.
*
* @param {Object} tabInfo - Information about the tab.
* @returns {Component} - The tab.
*/
_renderTab({ component, styles, tabId }) {
const { closeDialog } = this.props;
const TabComponent = component;
return (
<div className = { styles }>
<TabComponent
closeDialog = { closeDialog }
mountCallback = { this.props.tabs[tabId].onMount }
onTabStateChange
= { this._onTabStateChange }
tabId = { tabId }
{ ...this._getTabProps(tabId) } />
</div>);
}
_onTabStateChange: (number, Object) => void;
/**
* Changes the state for a tab.
*
* @param {number} tabId - The id of the tab which state will be changed.
* @param {Object} state - The new state.
* @returns {void}
*/
_onTabStateChange(tabId, state) {
const tabStates = [ ...this.state.tabStates ];
tabStates[tabId] = state;
this.setState({ tabStates });
}
_onSubmit: () => void;
/**
* Submits the information filled in the dialog.
*
* @returns {void}
*/
_onSubmit() {
const { onSubmit, tabs } = this.props;
tabs.forEach(({ submit }, idx) => {
submit && submit(this.state.tabStates[idx]);
});
onSubmit();
}
}
export default translate(DialogWithTabs);

View File

@@ -1,172 +0,0 @@
/* eslint-disable lines-around-comment */
/* eslint-disable react/no-multi-comp */
import ErrorIcon from '@atlaskit/icon/glyph/error';
import WarningIcon from '@atlaskit/icon/glyph/warning';
import {
Header,
Title,
TitleText,
titleIconWrapperStyles
// @ts-ignore
} from '@atlaskit/modal-dialog/dist/es2019/styled/Content';
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../../i18n/functions';
import { IconCloseLarge } from '../../../icons/svg';
import { withPixelLineHeight } from '../../../styles/functions.web';
import Button from '../../../ui/components/web/Button';
import { BUTTON_TYPES } from '../../../ui/constants.web';
const TitleIcon = ({ appearance }: { appearance?: 'danger' | 'warning'; }) => {
if (!appearance) {
return null;
}
const IconSymbol = appearance === 'danger' ? ErrorIcon : WarningIcon;
return (
<span css = { titleIconWrapperStyles(appearance) }>
<IconSymbol label = { `${appearance} icon` } />
</span>
);
};
interface IProps extends WithTranslation {
appearance?: 'danger' | 'warning';
classes: any;
heading: string;
hideCloseIconButton: boolean;
id?: string;
isHeadingMultiline: boolean;
onClose: (e?: any) => void;
showKeyline: boolean;
testId?: string;
}
/**
* Creates the styles for the component.
*
* @param {Object} theme - The current UI theme.
*
* @returns {Object}
*/
const styles = (theme: Theme) => {
return {
closeButton: {
borderRadius: theme.shape.borderRadius,
cursor: 'pointer',
padding: 13,
[theme.breakpoints.down(480)]: {
background: theme.palette.action02
},
'&:hover': {
background: theme.palette.ui04
}
},
header: {
boxShadow: 'none',
'& h4': {
...withPixelLineHeight(theme.typography.heading5),
color: theme.palette.text01
}
}
};
};
/**
* A default header for modal-dialog components.
*
* @class ModalHeader
* @augments {React.Component<IProps>}
*/
class ModalHeader extends React.Component<IProps> {
static defaultProps = {
isHeadingMultiline: true
};
/**
* Initializes a new {@code ModalHeader} instance.
*
* @param {*} props - The read-only properties with which the new instance
* is to be initialized.
*/
constructor(props: IProps) {
super(props);
// Bind event handler so it is only bound once for every instance.
this._onKeyPress = this._onKeyPress.bind(this);
}
/**
* KeyPress handler for accessibility.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onKeyPress(e: React.KeyboardEvent) {
if (this.props.onClose && (e.key === ' ' || e.key === 'Enter')) {
e.preventDefault();
this.props.onClose();
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
id,
appearance,
classes,
heading,
hideCloseIconButton,
onClose,
showKeyline,
isHeadingMultiline,
testId,
t
} = this.props;
if (!heading) {
return null;
}
return (
<Header
className = { classes.header }
showKeyline = { showKeyline }>
<Title>
<TitleIcon appearance = { appearance } />
<TitleText
data-testid = { testId && `${testId}-heading` }
id = { id }
isHeadingMultiline = { isHeadingMultiline }>
{heading}
</TitleText>
</Title>
{
!hideCloseIconButton && <Button
accessibilityLabel = { t('dialog.close') }
icon = { IconCloseLarge }
id = 'modal-header-close-button'
onClick = { onClose }
size = 'large'
type = { BUTTON_TYPES.TERTIARY } />
}
</Header>
);
}
}
export default translate(withStyles(styles)(ModalHeader));

View File

@@ -1,377 +0,0 @@
/* eslint-disable lines-around-comment */
import Modal, { ModalFooter } from '@atlaskit/modal-dialog';
import { Theme } from '@mui/material';
import { withStyles } from '@mui/styles';
import React, { Component, ReactElement } from 'react';
import { WithTranslation } from 'react-i18next';
import { translate } from '../../../i18n/functions';
import Button from '../../../ui/components/web/Button';
import { BUTTON_TYPES } from '../../../ui/constants.web';
import type { DialogProps } from '../../constants';
import ModalHeader from './ModalHeader';
/**
* The ID to be used for the cancel button if enabled.
*
* @type {string}
*/
const CANCEL_BUTTON_ID = 'modal-dialog-cancel-button';
/**
* The ID to be used for the ok button if enabled.
*
* @type {string}
*/
const OK_BUTTON_ID = 'modal-dialog-ok-button';
/**
* The type of the React {@code Component} props of {@link StatelessDialog}.
*
* @static
*/
interface IProps extends DialogProps, WithTranslation {
/**
* An object containing the CSS classes.
*/
classes: any;
/**
* Custom dialog header that replaces the standard heading.
*/
customHeader?: ReactElement<any> | Function;
/**
* Disables dismissing the dialog when the blanket is clicked. Enabled
* by default.
*/
disableBlanketClickDismiss: boolean;
/*
* True if listening for the Enter key should be disabled.
*/
disableEnter: boolean;
/**
* If true, no footer will be displayed.
*/
disableFooter?: boolean;
/**
* If true, the cancel button will not display but cancel actions, like
* clicking the blanket, will cancel.
*/
hideCancelButton: boolean;
/**
* If true, the close icon button will not be displayed.
*/
hideCloseIconButton: boolean;
/**
* Whether the dialog is modal. This means clicking on the blanket will
* leave the dialog open. No cancel button.
*/
isModal: boolean;
/**
* The handler for the event when clicking the 'confirmNo' button.
* Defaults to onCancel if absent.
*/
onDecline?: () => void;
/**
* Callback invoked when setting the ref of the Dialog.
*/
onDialogRef?: Function;
/**
* Disables rendering of the submit button.
*/
submitDisabled: boolean;
/**
* Width of the dialog, can be:
* - 'small' (400px), 'medium' (600px), 'large' (800px),
* 'x-large' (968px)
* - integer value for pixel width
* - string value for percentage.
*/
width: string;
}
/**
* Creates the styles for the component.
*
* @param {Object} theme - The theme.
* @returns {Object}
*/
const styles = (theme: Theme) => {
return {
footer: {
boxShadow: 'none'
},
buttonContainer: {
display: 'flex',
'& > button:first-child': {
marginRight: theme.spacing(2)
}
}
};
};
/**
* Web dialog that uses atlaskit modal-dialog to display dialogs.
*/
class StatelessDialog extends Component<IProps> {
static defaultProps = {
hideCloseIconButton: false
};
/**
* Initializes a new {@code StatelessDialog} instance.
*
* @param {Object} props - The read-only properties with which the new
* instance is to be initialized.
*/
constructor(props: IProps) {
super(props);
// Bind event handlers so they are only bound once for every instance.
this._onCancel = this._onCancel.bind(this);
this._onDialogDismissed = this._onDialogDismissed.bind(this);
this._onKeyPress = this._onKeyPress.bind(this);
this._onSubmit = this._onSubmit.bind(this);
this._renderFooter = this._renderFooter.bind(this);
this._onDialogRef = this._onDialogRef.bind(this);
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const {
customHeader,
children,
hideCloseIconButton,
t,
titleString,
titleKey,
width
} = this.props;
return (
<Modal
autoFocus = { true }
components = {{
// @ts-ignore
Header: customHeader ? customHeader : props => (
// @ts-ignore
<ModalHeader
{ ...props }
heading = { titleString || t(titleKey ?? '') }
hideCloseIconButton = { hideCloseIconButton } />
)
}}
footer = { this._renderFooter }
i18n = { this.props.i18n }
onClose = { this._onDialogDismissed }
onDialogDismissed = { this._onDialogDismissed }
shouldCloseOnEscapePress = { true }
width = { width || 'medium' }>
<div
onKeyPress = { this._onKeyPress }
ref = { this._onDialogRef }>
<form
className = 'modal-dialog-form'
id = 'modal-dialog-form'
onSubmit = { this._onSubmit }>
{ children }
</form>
</div>
</Modal>
);
}
/**
* Returns a ReactElement to display buttons for closing the modal.
*
* @param {Object} propsFromModalFooter - The props passed in from the
* {@link ModalFooter} component.
* @private
* @returns {ReactElement}
*/
_renderFooter(propsFromModalFooter: any) {
// Filter out falsy (null) values because {@code ButtonGroup} will error
// if passed in anything but buttons with valid type props.
const buttons = [
this._renderCancelButton(),
this._renderOKButton()
].filter(Boolean);
if (this.props.disableFooter) {
return null;
}
return (
<ModalFooter
className = { this.props.classes.footer }
showKeyline = { propsFromModalFooter.showKeyline } >
{
/**
* Atlaskit has this empty span (JustifySim) so...
*/
}
<span />
<div className = { this.props.classes.buttonContainer }>
{ buttons }
</div>
</ModalFooter>
);
}
/**
* Dispatches action to hide the dialog.
*
* @returns {void}
*/
_onCancel() {
if (!this.props.isModal) {
const { onCancel } = this.props;
onCancel?.();
}
}
/**
* Handles click on the blanket area.
*
* @returns {void}
*/
_onDialogDismissed() {
if (!this.props.disableBlanketClickDismiss) {
this._onCancel();
}
}
/**
* Dispatches the action when submitting the dialog.
*
* @private
* @param {string} value - The submitted value if any.
* @returns {void}
*/
_onSubmit(value?: any) {
const { onSubmit } = this.props;
onSubmit?.(value);
}
/**
* Renders Cancel button.
*
* @private
* @returns {ReactElement|null} The Cancel button if enabled and dialog is
* not modal.
*/
_renderCancelButton() {
if (this.props.cancelDisabled
|| this.props.isModal
|| this.props.hideCancelButton) {
return null;
}
const {
t,
onDecline
} = this.props;
return (
<Button
accessibilityLabel = { t(this.props.cancelKey || 'dialog.Cancel') }
id = { CANCEL_BUTTON_ID }
key = { CANCEL_BUTTON_ID }
label = { t(this.props.cancelKey || 'dialog.Cancel') }
onClick = { onDecline || this._onCancel }
size = 'small'
type = { BUTTON_TYPES.TERTIARY } />
);
}
/**
* Renders OK button.
*
* @private
* @returns {ReactElement|null} The OK button if enabled.
*/
_renderOKButton() {
const {
submitDisabled,
t
} = this.props;
if (submitDisabled) {
return null;
}
return (
<Button
accessibilityLabel = { t(this.props.okKey || 'dialog.Ok') }
disabled = { this.props.okDisabled }
id = { OK_BUTTON_ID }
key = { OK_BUTTON_ID }
label = { t(this.props.okKey || 'dialog.Ok') }
onClick = { this._onSubmit }
size = 'small' />
);
}
/**
* Callback invoked when setting the ref of the dialog's child passing the Modal ref.
* It is done this way because we cannot directly access the ref of the Modal component.
*
* @param {HTMLElement} element - The DOM element for the dialog.
* @private
* @returns {void}
*/
_onDialogRef(element?: any) {
this.props.onDialogRef?.(element?.parentNode);
}
/**
* Handles 'Enter' key in the dialog to submit/hide dialog depending on
* the available buttons and their disabled state.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyPress(event: React.KeyboardEvent) {
// If the event coming to the dialog has been subject to preventDefault
// we don't handle it here.
if (event.defaultPrevented) {
return;
}
if (event.key === 'Enter' && !this.props.disableEnter) {
event.preventDefault();
event.stopPropagation();
if (this.props.submitDisabled && !this.props.cancelDisabled) {
this._onCancel();
} else if (!this.props.okDisabled) {
this._onSubmit();
}
}
}
}
export default translate(withStyles(styles)(StatelessDialog));

View File

@@ -1,31 +0,0 @@
// @flow
import {
Dialog,
FillScreen,
PositionerAbsolute,
PositionerRelative,
dialogHeight,
dialogWidth
} from '@atlaskit/modal-dialog/dist/es2019/styled/Modal.js';
import { DN50, N0 } from '@atlaskit/theme/colors';
import { themed } from '@atlaskit/theme/components';
import React from 'react';
type Props = {
isChromeless: boolean
}
const ThemedDialog = (props: Props) => {
const style = { backgroundColor: props.isChromeless ? 'transparent' : themed({ light: N0,
dark: DN50 })({ theme: { mode: 'dark' } }) };
return (<Dialog
{ ...props }
aria-modal = { true }
style = { style }
theme = {{ mode: 'dark' }} />);
};
export { ThemedDialog as Dialog, FillScreen, dialogWidth, dialogHeight, PositionerAbsolute, PositionerRelative };

View File

@@ -2,7 +2,4 @@
export { default as AbstractDialogTab } from './AbstractDialogTab';
export type { Props as AbstractDialogTabProps } from './AbstractDialogTab';
export { default as Dialog } from './Dialog-old';
export { default as DialogWithTabs } from './DialogWithTabs';
export { default as StatelessDialog } from './StatelessDialog';
export { default as DialogContainer } from '../../../ui/components/web/DialogContainer';

View File

@@ -1,73 +0,0 @@
/* eslint-disable lines-around-comment */
import LoginDialog from '../../authentication/components/web/LoginDialog';
import WaitForOwnerDialog from '../../authentication/components/web/WaitForOwnerDialog';
import ChatPrivacyDialog from '../../chat/components/web/ChatPrivacyDialog';
import DesktopPicker from '../../desktop-picker/components/DesktopPicker';
import DisplayNamePrompt from '../../display-name/components/web/DisplayNamePrompt';
import ParticipantVerificationDialog from '../../e2ee/components/ParticipantVerificationDialog';
import EmbedMeetingDialog from '../../embed-meeting/components/EmbedMeetingDialog';
// @ts-ignore
import FeedbackDialog from '../../feedback/components/FeedbackDialog.web';
import AddPeopleDialog from '../../invite/components/add-people-dialog/web/AddPeopleDialog';
import PremiumFeatureDialog from '../../jaas/components/web/PremiumFeatureDialog';
import KeyboardShortcutsDialog from '../../keyboard-shortcuts/components/web/KeyboardShortcutsDialog';
// @ts-ignore
import StartLiveStreamDialog from '../../recording/components/LiveStream/web/StartLiveStreamDialog';
// @ts-ignore
import StopLiveStreamDialog from '../../recording/components/LiveStream/web/StopLiveStreamDialog';
// @ts-ignore
import StartRecordingDialog from '../../recording/components/Recording/web/StartRecordingDialog';
// @ts-ignore
import StopRecordingDialog from '../../recording/components/Recording/web/StopRecordingDialog';
// @ts-ignore
import RemoteControlAuthorizationDialog from '../../remote-control/components/RemoteControlAuthorizationDialog';
import PasswordRequiredPrompt from '../../room-lock/components/PasswordRequiredPrompt.web';
import SalesforceLinkDialog from '../../salesforce/components/web/SalesforceLinkDialog';
import ShareAudioDialog from '../../screen-share/components/web/ShareAudioDialog';
import ShareScreenWarningDialog from '../../screen-share/components/web/ShareScreenWarningDialog';
import SecurityDialog from '../../security/components/security-dialog/web/SecurityDialog';
import LogoutDialog from '../../settings/components/web/LogoutDialog';
import SharedVideoDialog from '../../shared-video/components/web/SharedVideoDialog';
import SpeakerStats from '../../speaker-stats/components/web/SpeakerStats';
import LanguageSelectorDialog from '../../subtitles/components/LanguageSelectorDialog.web';
import GrantModeratorDialog from '../../video-menu/components/web/GrantModeratorDialog';
import KickRemoteParticipantDialog from '../../video-menu/components/web/KickRemoteParticipantDialog';
import MuteEveryoneDialog from '../../video-menu/components/web/MuteEveryoneDialog';
import MuteEveryonesVideoDialog from '../../video-menu/components/web/MuteEveryonesVideoDialog';
import MuteRemoteParticipantsVideoDialog from '../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
// @ts-ignore
import VideoQualityDialog from '../../video-quality/components/VideoQualityDialog.web';
import VirtualBackgroundDialog from '../../virtual-background/components/VirtualBackgroundDialog';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { OPEN_DIALOG } from './actionTypes';
// ! IMPORTANT - This whole middleware is only needed for the transition from from @atlaskit dialog to our component.
// ! It should be removed when the transition is over.
const NEW_DIALOG_LIST = [ KeyboardShortcutsDialog, ChatPrivacyDialog, DisplayNamePrompt, EmbedMeetingDialog,
FeedbackDialog, AddPeopleDialog, PremiumFeatureDialog, StartLiveStreamDialog, StopLiveStreamDialog,
StartRecordingDialog, StopRecordingDialog, ShareAudioDialog, ShareScreenWarningDialog, SecurityDialog,
SharedVideoDialog, SpeakerStats, LanguageSelectorDialog, MuteEveryoneDialog, MuteEveryonesVideoDialog,
GrantModeratorDialog, KickRemoteParticipantDialog, MuteRemoteParticipantsVideoDialog, VideoQualityDialog,
VirtualBackgroundDialog, LoginDialog, WaitForOwnerDialog, DesktopPicker, RemoteControlAuthorizationDialog,
LogoutDialog, SalesforceLinkDialog, ParticipantVerificationDialog, PasswordRequiredPrompt ];
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
const isNewDialog = (component: any) => NEW_DIALOG_LIST.some(comp => comp === component);
/**
* Implements the entry point of the middleware of the feature base/media.
*
* @param {IStore} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(() => (next: Function) => (action: any) => {
switch (action.type) {
case OPEN_DIALOG: {
action.isNewDialog = isNewDialog(action.component);
}
}
return next(action);
});

View File

@@ -13,7 +13,6 @@ import {
export interface IDialogState {
component?: ComponentType;
componentProps?: Object;
isNewDialog?: boolean;
sheet?: ComponentType;
sheetProps?: Object;
}
@@ -44,8 +43,7 @@ ReducerRegistry.register<IDialogState>('features/base/dialog', (state = {}, acti
case OPEN_DIALOG:
return assign(state, {
component: action.component,
componentProps: action.componentProps,
isNewDialog: action.isNewDialog
componentProps: action.componentProps
});
case HIDE_SHEET:

View File

@@ -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 1.5C11.5858 1.5 11.25 1.83579 11.25 2.25V3C11.25 3.01239 11.2503 3.0247 11.2509 3.03694C7.46047 3.41282 4.5 6.61068 4.5 10.5V12.0949C4.5 12.9852 4.10453 13.8296 3.42055 14.3995L3.07702 14.6858C2.55299 15.1225 2.25 15.7694 2.25 16.4515C2.25 17.7209 3.27906 18.75 4.54846 18.75H8.25C8.25 20.8211 9.92893 22.5 12 22.5C14.0711 22.5 15.75 20.8211 15.75 18.75H19.3635C20.6815 18.75 21.75 17.6815 21.75 16.3635C21.75 15.7306 21.4986 15.1236 21.051 14.676L20.3787 14.0037C19.8161 13.4411 19.5 12.678 19.5 11.8824V10.5C19.5 6.61068 16.5395 3.41282 12.7491 3.03694C12.7497 3.0247 12.75 3.01239 12.75 3V2.25C12.75 1.83579 12.4142 1.5 12 1.5ZM18 10.5C18 7.18629 15.3137 4.5 12 4.5C8.68629 4.5 6 7.18629 6 10.5V12.7974C6 13.6878 5.60453 14.5321 4.92055 15.1021L4.0373 15.8381C3.85526 15.9898 3.75 16.2146 3.75 16.4515C3.75 16.8925 4.10748 17.25 4.54846 17.25H19.3635C19.8531 17.25 20.25 16.8531 20.25 16.3635C20.25 16.1284 20.1566 15.9029 19.9904 15.7367L18.8787 14.625C18.3161 14.0624 18 13.2993 18 12.5037V10.5ZM14.25 18.75H9.75C9.75 19.9926 10.7574 21 12 21C13.2426 21 14.25 19.9926 14.25 18.75Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -8,6 +8,7 @@ export { default as IconArrowUpLarge } from './arrow-up-large.svg';
export { default as IconAudioOnly } from './visibility.svg';
export { default as IconAudioOnlyOff } from './visibility-off.svg';
export { default as IconBluetooth } from './bluetooth.svg';
export { default as IconBell } from './bell.svg';
export { default as IconCalendar } from './calendar.svg';
export { default as IconCameraRefresh } from './camera-refresh.svg';
export { default as IconCar } from './car.svg';
@@ -80,6 +81,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';
@@ -88,6 +90,7 @@ export { default as IconTileView } from './tile-view.svg';
export { default as IconTrash } from './trash.svg';
export { default as IconUserDeleted } from './user-deleted.svg';
export { default as IconUsers } from './users.svg';
export { default as IconUser } from './user.svg';
export { default as IconVideo } from './video.svg';
export { default as IconVideoOff } from './video-off.svg';
export { default as IconVolumeOff } from './volume-off.svg';

View File

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

View File

@@ -0,0 +1,4 @@
<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="M16.5 6.75C16.5 9.23528 14.4853 11.25 12 11.25C9.51472 11.25 7.5 9.23528 7.5 6.75C7.5 4.26472 9.51472 2.25 12 2.25C14.4853 2.25 16.5 4.26472 16.5 6.75ZM15 6.75C15 8.40685 13.6569 9.75 12 9.75C10.3431 9.75 9 8.40685 9 6.75C9 5.09315 10.3431 3.75 12 3.75C13.6569 3.75 15 5.09315 15 6.75Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.5 17.25C19.5 19.7353 16.1421 21.75 12 21.75C7.85786 21.75 4.5 19.7353 4.5 17.25C4.5 14.7647 7.85786 12.75 12 12.75C16.1421 12.75 19.5 14.7647 19.5 17.25ZM18 17.25C18 17.7588 17.6485 18.4756 16.5316 19.1457C15.4445 19.798 13.8459 20.25 12 20.25C10.1541 20.25 8.55549 19.798 7.46844 19.1457C6.35154 18.4756 6 17.7588 6 17.25C6 16.7412 6.35154 16.0244 7.46844 15.3543C8.55549 14.702 10.1541 14.25 12 14.25C13.8459 14.25 15.4445 14.702 16.5316 15.3543C17.6485 16.0244 18 16.7412 18 17.25Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1016 B

View File

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

View File

@@ -1,9 +1,12 @@
/* eslint-disable lines-around-comment */
import { IStateful } from '../app/types';
import { toState } from '../redux/functions';
// @ts-ignore
import JitsiMeetJS from './_';
const JitsiConferenceErrors = JitsiMeetJS.errors.conference;
const JitsiConnectionErrors = JitsiMeetJS.errors.connection;

View File

@@ -1,5 +1,4 @@
// @flow
// @ts-ignore
import Video from './web/Video';
export default Video;

View File

@@ -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 }>
{this._renderContent()}
<ReactFocusLock
lockProps = {{
role: 'dialog',
'aria-modal': true,
'aria-labelledby': headingId,
'aria-label': !headingId && headingLabel ? headingLabel : undefined
}}
returnFocus = { true }>
{this._renderContent()}
</ReactFocusLock>
</DialogPortal>
)}
{ children }

View File

@@ -1,210 +0,0 @@
// @flow
import React, { PureComponent } from 'react';
import { getFieldValue } from '../../../react';
type Props = {
/**
* If the input should be focused on display.
*/
autoFocus?: boolean,
/**
* Class name to be appended to the default class list.
*/
className?: string,
/**
* TestId of the button. Can be used to locate element when testing UI.
*/
testId?: string,
/**
* Callback for the onChange event of the field.
*/
onChange: Function,
/**
* Callback to be used when the user hits Enter in the field.
*/
onSubmit?: Function,
/**
* Placeholder text for the field.
*/
placeHolder: string,
/**
* Whether the input is read only or not.
*/
readOnly?: boolean,
/**
* The field type (e.g. Text, password...etc).
*/
type: string,
/**
* Externally provided value.
*/
value?: string,
id?: string,
autoComplete?: string
};
type State = {
/**
* True if the field is focused, false otherwise.
*/
focused: boolean,
/**
* The current value of the field.
*/
value: string
}
/**
* Implements a pre-styled input field to be used on pre-meeting screens.
*/
export default class InputField extends PureComponent<Props, State> {
static defaultProps: {
className: '',
type: 'text'
};
/**
* Instantiates a new component.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this.state = {
focused: false,
value: props.value || ''
};
this._onBlur = this._onBlur.bind(this);
this._onChange = this._onChange.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onKeyDown = this._onKeyDown.bind(this);
}
/**
* Implements {@code PureComponent.getDerivedStateFromProps}.
*
* @inheritdoc
*/
static getDerivedStateFromProps(props: Props, state: State) {
const { value } = props;
if (state.value !== value) {
return {
...state,
value
};
}
return null;
}
/**
* Implements {@code PureComponent#render}.
*
* @inheritdoc
*/
render() {
return (
<input
autoComplete = { this.props.autoComplete }
autoFocus = { this.props.autoFocus }
className = { `field ${this.state.focused ? 'focused' : ''} ${this.props.className || ''}` }
data-testid = { this.props.testId ? this.props.testId : undefined }
id = { this.props.id }
onBlur = { this._onBlur }
onChange = { this._onChange }
onFocus = { this._onFocus }
onKeyDown = { this._onKeyDown }
onKeyPress = { this._onKeyPress }
placeholder = { this.props.placeHolder }
readOnly = { this.props.readOnly }
type = { this.props.type }
value = { this.state.value } />
);
}
_onBlur: () => void;
/**
* Callback for the onBlur event of the field.
*
* @returns {void}
*/
_onBlur() {
this.setState({
focused: false
});
}
_onChange: Object => void;
/**
* Callback for the onChange event of the field.
*
* @param {Object} evt - The static event.
* @returns {void}
*/
_onChange(evt) {
const value = getFieldValue(evt);
this.setState({
value
});
const { onChange } = this.props;
onChange && onChange(value);
}
_onFocus: () => void;
/**
* Callback for the onFocus event of the field.
*
* @returns {void}
*/
_onFocus() {
this.setState({
focused: true
});
}
_onKeyDown: Object => void;
/**
* Joins the conference on 'Enter'.
*
* @param {Event} event - Key down event object.
* @returns {void}
*/
_onKeyDown(event) {
const { onSubmit } = this.props;
onSubmit && event.key === 'Enter' && onSubmit();
}
/**
* Stop event propagation on key press.
*
* @param {Event} event - Key press event object.
* @returns {void}
*/
_onKeyPress(event) {
event.stopPropagation();
}
}

View File

@@ -1,5 +1,4 @@
// @flow
export { default as ActionButton } from './ActionButton';
export { default as InputField } from './InputField';
export { default as PreMeetingScreen } from './PreMeetingScreen';

View File

@@ -115,7 +115,6 @@ class MeetingsList extends Component<Props> {
<Container
aria-label = { t('welcomepage.recentList') }
className = 'meetings-list'
role = 'menu'
tabIndex = '-1'>
{
meetings.length === 0
@@ -243,7 +242,7 @@ class MeetingsList extends Component<Props> {
key = { index }
onClick = { onPress }
onKeyPress = { onKeyPress }
role = 'menuitem'
role = 'button'
tabIndex = { 0 }>
<Container className = 'left-column'>
<Text className = 'title'>

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { GlobalStyles as MUIGlobalStyles } from 'tss-react';
import { useStyles } from 'tss-react/mui';
import { commonStyles, getGlobalStyles } from '../constants';
import { commonStyles } from '../constants';
/**
* A component generating all the global styles.
@@ -14,8 +14,7 @@ function GlobalStyles() {
return (<MUIGlobalStyles
styles = {{
...commonStyles(theme),
...getGlobalStyles(theme)
...commonStyles(theme)
}} />);
}

View File

@@ -13,10 +13,12 @@ import { IButtonProps } from '../types';
import styles from './buttonStyles';
export interface IProps extends IButtonProps {
color?: string;
color?: string | undefined;
contentStyle?: Object | undefined;
labelStyle?: Object | undefined;
mode?: any;
style?: Object | undefined;
useRippleColor?: boolean;
}
const Button: React.FC<IProps> = ({
@@ -27,31 +29,36 @@ const Button: React.FC<IProps> = ({
icon,
labelKey,
labelStyle,
mode = BUTTON_MODES.CONTAINED,
onClick: onPress,
style,
type
type,
useRippleColor = true
}: IProps) => {
const { t } = useTranslation();
const { CONTAINED } = BUTTON_MODES;
const { DESTRUCTIVE, PRIMARY, SECONDARY, TERTIARY } = BUTTON_TYPES;
const { CONTAINED, TEXT } = BUTTON_MODES;
const rippleColor
= useRippleColor ? BaseTheme.palette.action03Active : 'transparent';
let buttonLabelStyles;
let buttonStyles;
let color;
let mode;
if (type === PRIMARY) {
buttonLabelStyles = styles.buttonLabelPrimary;
color = BaseTheme.palette.action01;
mode = CONTAINED;
buttonLabelStyles = mode === TEXT
? styles.buttonLabelPrimaryText
: styles.buttonLabelPrimary;
color = mode === CONTAINED && BaseTheme.palette.action01;
} else if (type === SECONDARY) {
buttonLabelStyles = styles.buttonLabelSecondary;
color = BaseTheme.palette.action02;
mode = CONTAINED;
color = mode === CONTAINED && BaseTheme.palette.action02;
} else if (type === DESTRUCTIVE) {
color = BaseTheme.palette.actionDanger;
buttonLabelStyles = styles.buttonLabelDestructive;
mode = CONTAINED;
buttonLabelStyles = mode === TEXT
? styles.buttonLabelDestructiveText
: styles.buttonLabelDestructive;
color = mode === CONTAINED && BaseTheme.palette.actionDanger;
} else {
color = buttonColor;
buttonLabelStyles = styles.buttonLabel;
@@ -65,15 +72,17 @@ const Button: React.FC<IProps> = ({
}
if (type === TERTIARY) {
buttonLabelStyles
= disabled ? styles.buttonLabelTertiaryDisabled : styles.buttonLabelTertiary;
if (useRippleColor && disabled) {
buttonLabelStyles = styles.buttonLabelTertiaryDisabled;
}
buttonLabelStyles = styles.buttonLabelTertiary;
return (
<TouchableRipple
accessibilityLabel = { accessibilityLabel }
disabled = { disabled }
onPress = { onPress }
rippleColor = { BaseTheme.palette.action03Active }
rippleColor = { rippleColor }
style = { [
buttonStyles,
style

View File

@@ -36,7 +36,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
iconButtonContainerStyles = styles.iconButtonContainerSecondary;
rippleColor = BaseTheme.palette.action02;
} else if (type === TERTIARY) {
color = BaseTheme.palette.icon01;
color = iconColor;
iconButtonContainerStyles = styles.iconButtonContainer;
rippleColor = BaseTheme.palette.action03;
} else {

View File

@@ -42,6 +42,11 @@ export default {
color: BaseTheme.palette.text01
},
buttonLabelPrimaryText: {
...buttonLabel,
color: BaseTheme.palette.action01
},
buttonLabelSecondary: {
...buttonLabel,
color: BaseTheme.palette.text04
@@ -52,6 +57,11 @@ export default {
color: BaseTheme.palette.text01
},
buttonLabelDestructiveText: {
...buttonLabel,
color: BaseTheme.palette.actionDanger
},
buttonLabelTertiary: {
...buttonLabel,
color: BaseTheme.palette.text01,

View File

@@ -0,0 +1,201 @@
import React, { ReactNode, useCallback, useContext, useEffect } from 'react';
import FocusLock from 'react-focus-lock';
import { useTranslation } from 'react-i18next';
import { keyframes } from 'tss-react';
import { makeStyles } from 'tss-react/mui';
import { withPixelLineHeight } from '../../../styles/functions.web';
import { DialogTransitionContext } from './DialogTransition';
const useStyles = makeStyles()(theme => {
return {
container: {
width: '100%',
height: '100%',
position: 'fixed',
color: theme.palette.text01,
...withPixelLineHeight(theme.typography.bodyLongRegular),
top: 0,
left: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
zIndex: 301,
animation: `${keyframes`
0% {
opacity: 0.4;
}
100% {
opacity: 1;
}
`} 0.2s forwards ease-out`,
'&.unmount': {
animation: `${keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0.5;
}
`} 0.15s forwards ease-in`
}
},
backdrop: {
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
backgroundColor: theme.palette.ui02,
opacity: 0.75
},
modal: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
borderRadius: `${theme.shape.borderRadius}px`,
display: 'flex',
flexDirection: 'column',
height: 'auto',
minHeight: '200px',
maxHeight: '80vh',
marginTop: '64px',
animation: `${keyframes`
0% {
margin-top: 85px
}
100% {
margin-top: 64px
}
`} 0.2s forwards ease-out`,
'&.medium': {
width: '400px'
},
'&.large': {
width: '664px'
},
'&.unmount': {
animation: `${keyframes`
0% {
margin-top: 64px
}
100% {
margin-top: 40px
}
`} 0.15s forwards ease-in`
},
'@media (max-width: 448px)': {
width: '100% !important',
maxHeight: 'initial',
height: '100%',
margin: 0,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
animation: `${keyframes`
0% {
margin-top: 15px
}
100% {
margin-top: 0
}
`} 0.2s forwards ease-out`,
'&.unmount': {
animation: `${keyframes`
0% {
margin-top: 0
}
100% {
margin-top: 15px
}
`} 0.15s forwards ease-in`
}
}
},
focusLock: {
zIndex: 1
}
};
});
export interface IProps {
children?: ReactNode;
className?: string;
description?: string;
disableBackdropClose?: boolean;
disableEnter?: boolean;
onClose?: () => void;
size?: 'large' | 'medium';
submit?: () => void;
title?: string;
titleKey?: string;
}
const BaseDialog = ({
children,
className,
description,
disableBackdropClose,
disableEnter,
onClose,
size = 'medium',
submit,
title,
titleKey
}: IProps) => {
const { classes, cx } = useStyles();
const { isUnmounting } = useContext(DialogTransitionContext);
const { t } = useTranslation();
const onBackdropClick = useCallback(() => {
!disableBackdropClose && onClose?.();
}, [ disableBackdropClose, onClose ]);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose?.();
}
if (e.key === 'Enter' && !disableEnter) {
submit?.();
}
}, []);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
<div
className = { classes.backdrop }
onClick = { onBackdropClick } />
<FocusLock
className = { classes.focusLock }
returnFocus = { true }>
<div
aria-describedby = { description }
aria-labelledby = { title ?? t(titleKey ?? '') }
aria-modal = { true }
className = { cx(classes.modal, isUnmounting && 'unmount', size, className) }
role = 'dialog'>
{children}
</div>
</FocusLock>
</div>
);
};
export default BaseDialog;

View File

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

View File

@@ -7,6 +7,7 @@ import JitsiPortal from '../../../../toolbox/components/web/JitsiPortal';
import { showOverflowDrawer } from '../../../../toolbox/functions.web';
import participantsPaneTheme from '../../../components/themes/participantsPaneTheme.json';
import { withPixelLineHeight } from '../../../styles/functions.web';
import { spacing } from '../../Tokens';
/**
* Get a style property from a style declaration as a float.
@@ -34,6 +35,9 @@ const getComputedOuterHeight = (element: HTMLElement) => {
interface IProps {
/**
* ARIA attributes.
*/
[key: `aria-${string}`]: string;
/**
@@ -106,6 +110,11 @@ interface IProps {
*/
onMouseLeave?: (e?: React.MouseEvent) => void;
/**
* Container role.
*/
role?: string;
/**
* Tab index for the menu.
*/
@@ -167,7 +176,9 @@ const ContextMenu = ({
onDrawerClose,
onMouseEnter,
onMouseLeave,
tabIndex
role,
tabIndex,
...aria
}: IProps) => {
const [ isHidden, setIsHidden ] = useState(true);
const containerRef = useRef<HTMLDivElement | null>(null);
@@ -184,9 +195,22 @@ const ContextMenu = ({
&& offsetTarget.offsetParent instanceof HTMLElement
) {
const { current: container } = containerRef;
// make sure the max height is not set
// @ts-ignore
container.style.maxHeight = null;
const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
const outerHeight = getComputedOuterHeight(container);
const height = Math.min(MAX_HEIGHT, outerHeight);
let outerHeight = getComputedOuterHeight(container);
let height = Math.min(MAX_HEIGHT, outerHeight);
if (offsetTop + height > offsetHeight + scrollTop && height > offsetTop) {
// top offset and + padding + border
container.style.maxHeight = `${offsetTop - ((spacing[2] * 2) + 2)}px`;
}
// get the height after style changes
outerHeight = getComputedOuterHeight(container);
height = Math.min(MAX_HEIGHT, outerHeight);
container.style.top = offsetTop + height > offsetHeight + scrollTop
? `${offsetTop - outerHeight}`
@@ -225,6 +249,7 @@ const ContextMenu = ({
</Drawer>
</JitsiPortal>
: <div
{ ...aria }
aria-label = { accessibilityLabel }
className = { cx(participantsPaneTheme.ignoredChildClassName,
styles.contextMenu,
@@ -237,7 +262,7 @@ const ContextMenu = ({
onMouseEnter = { onMouseEnter }
onMouseLeave = { onMouseLeave }
ref = { containerRef }
role = 'menu'
role = { role }
tabIndex = { tabIndex }>
{children}
</div>;

View File

@@ -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 {
@@ -59,6 +62,11 @@ export interface IProps {
*/
onKeyPress?: (e?: React.KeyboardEvent) => void;
/**
* Overflow type for item text.
*/
overflowType?: TEXT_OVERFLOW_TYPES;
/**
* Whether the item is marked as selected.
*/
@@ -149,6 +157,7 @@ const ContextMenuItem = ({
onClick,
onKeyDown,
onKeyPress,
overflowType,
selected,
testId,
text,
@@ -172,13 +181,22 @@ const ContextMenuItem = ({
onClick = { disabled ? undefined : onClick }
onKeyDown = { disabled ? undefined : onKeyDown }
onKeyPress = { disabled ? undefined : onKeyPress }
role = 'menuitem'>
role = 'button'
tabIndex = { disabled ? undefined : 0 }>
{customIcon ? customIcon
: icon && <Icon
className = { styles.contextMenuItemIcon }
size = { 20 }
src = { icon } />}
{text && <span className = { cx(styles.text, textClassName) }>{text}</span>}
{text && (
<TextWithOverflow
className = { cx(styles.text,
_overflowDrawer && styles.drawerText,
textClassName) }
overflowType = { overflowType } >
{text}
</TextWithOverflow>
)}
{children}
</div>
);

View File

@@ -1,134 +1,19 @@
import React, { useCallback, useContext, useEffect } from 'react';
import FocusLock from 'react-focus-lock';
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { keyframes } from 'tss-react';
import { makeStyles } from 'tss-react/mui';
import { hideDialog } from '../../../dialog/actions';
import { IconCloseLarge } from '../../../icons/svg';
import { withPixelLineHeight } from '../../../styles/functions.web';
import BaseDialog, { IProps as IBaseDialogProps } from './BaseDialog';
import Button from './Button';
import ClickableIcon from './ClickableIcon';
import { DialogTransitionContext } from './DialogTransition';
const useStyles = makeStyles()(theme => {
return {
container: {
width: '100%',
height: '100%',
position: 'fixed',
color: theme.palette.text01,
...withPixelLineHeight(theme.typography.bodyLongRegular),
top: 0,
left: 0,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
zIndex: 301,
animation: `${keyframes`
0% {
opacity: 0.4;
}
100% {
opacity: 1;
}
`} 0.2s forwards ease-out`,
'&.unmount': {
animation: `${keyframes`
0% {
opacity: 1;
}
100% {
opacity: 0.5;
}
`} 0.15s forwards ease-in`
}
},
backdrop: {
position: 'absolute',
width: '100%',
height: '100%',
top: 0,
left: 0,
backgroundColor: theme.palette.ui02,
opacity: 0.75
},
modal: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
borderRadius: `${theme.shape.borderRadius}px`,
display: 'flex',
flexDirection: 'column',
height: 'auto',
minHeight: '200px',
maxHeight: '80vh',
marginTop: '64px',
animation: `${keyframes`
0% {
margin-top: 85px
}
100% {
margin-top: 64px
}
`} 0.2s forwards ease-out`,
'&.medium': {
width: '400px'
},
'&.large': {
width: '664px'
},
'&.unmount': {
animation: `${keyframes`
0% {
margin-top: 64px
}
100% {
margin-top: 40px
}
`} 0.15s forwards ease-in`
},
'@media (max-width: 448px)': {
width: '100% !important',
maxHeight: 'initial',
height: '100%',
margin: 0,
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
animation: `${keyframes`
0% {
margin-top: 15px
}
100% {
margin-top: 0
}
`} 0.2s forwards ease-out`,
'&.unmount': {
animation: `${keyframes`
0% {
margin-top: 0
}
100% {
margin-top: 15px
}
`} 0.15s forwards ease-in`
}
}
},
header: {
width: '100%',
padding: '24px',
@@ -176,15 +61,11 @@ const useStyles = makeStyles()(theme => {
'& button:last-child': {
marginLeft: '16px'
}
},
focusLock: {
zIndex: 1
}
};
});
interface IDialogProps {
interface IDialogProps extends IBaseDialogProps {
back?: {
hidden?: boolean;
onClick?: () => void;
@@ -195,11 +76,7 @@ interface IDialogProps {
translationKey?: string;
};
children?: React.ReactNode;
className?: string;
description?: string;
disableAutoHideOnSubmit?: boolean;
disableBackdropClose?: boolean;
disableEnter?: boolean;
hideCloseButton?: boolean;
ok?: {
disabled?: boolean;
@@ -208,9 +85,6 @@ interface IDialogProps {
};
onCancel?: () => void;
onSubmit?: () => void;
size?: 'large' | 'medium';
title?: string;
titleKey?: string;
}
const Dialog = ({
@@ -226,13 +100,12 @@ const Dialog = ({
ok = { translationKey: 'dialog.Ok' },
onCancel,
onSubmit,
size = 'medium',
size,
title,
titleKey
}: IDialogProps) => {
const { classes, cx } = useStyles();
const { classes } = useStyles();
const { t } = useTranslation();
const { isUnmounting } = useContext(DialogTransitionContext);
const dispatch = useDispatch();
const onClose = useCallback(() => {
@@ -245,81 +118,59 @@ const Dialog = ({
onSubmit?.();
}, [ onSubmit ]);
const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
if (e.key === 'Enter' && !disableEnter) {
submit();
}
}, []);
const onBackdropClick = useCallback(() => {
!disableBackdropClose && onClose();
}, [ disableBackdropClose, onClose ]);
useEffect(() => {
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
<BaseDialog
className = { className }
description = { description }
disableBackdropClose = { disableBackdropClose }
disableEnter = { disableEnter }
onClose = { onClose }
size = { size }
submit = { submit }
title = { title }
titleKey = { titleKey }>
<div className = { classes.header }>
<p
className = { classes.title }
id = 'dialog-title'>
{title ?? t(titleKey ?? '')}
</p>
{!hideCloseButton && (
<ClickableIcon
accessibilityLabel = { t('dialog.close') }
className = { classes.closeIcon }
icon = { IconCloseLarge }
id = 'modal-header-close-button'
onClick = { onClose } />
)}
</div>
<div
className = { classes.backdrop }
onClick = { onBackdropClick } />
<FocusLock className = { classes.focusLock }>
<div
aria-describedby = { description }
aria-labelledby = { title ?? t(titleKey ?? '') }
aria-modal = { true }
className = { cx(classes.modal, isUnmounting && 'unmount', size, className) }
role = 'dialog'>
<div className = { classes.header }>
<p
className = { classes.title }
id = 'dialog-title'>
{title ?? t(titleKey ?? '')}
</p>
{!hideCloseButton && (
<ClickableIcon
accessibilityLabel = { t('dialog.close') }
className = { classes.closeIcon }
icon = { IconCloseLarge }
id = 'modal-header-close-button'
onClick = { onClose } />
)}
</div>
<div
className = { classes.content }
data-autofocus-inside = 'true'>
{children}
</div>
<div
className = { classes.footer }
data-autofocus-inside = 'true'>
{!back.hidden && <Button
accessibilityLabel = { t(back.translationKey ?? '') }
labelKey = { back.translationKey }
// eslint-disable-next-line react/jsx-handler-names
onClick = { back.onClick }
type = 'secondary' />}
{!cancel.hidden && <Button
accessibilityLabel = { t(cancel.translationKey ?? '') }
labelKey = { cancel.translationKey }
onClick = { onClose }
type = 'tertiary' />}
{!ok.hidden && <Button
accessibilityLabel = { t(ok.translationKey ?? '') }
disabled = { ok.disabled }
id = 'modal-dialog-ok-button'
labelKey = { ok.translationKey }
onClick = { submit } />}
</div>
</div>
</FocusLock>
</div>
className = { classes.content }
data-autofocus-inside = 'true'>
{children}
</div>
<div
className = { classes.footer }
data-autofocus-inside = 'true'>
{!back.hidden && <Button
accessibilityLabel = { t(back.translationKey ?? '') }
labelKey = { back.translationKey }
// eslint-disable-next-line react/jsx-handler-names
onClick = { back.onClick }
type = 'secondary' />}
{!cancel.hidden && <Button
accessibilityLabel = { t(cancel.translationKey ?? '') }
labelKey = { cancel.translationKey }
onClick = { onClose }
type = 'tertiary' />}
{!ok.hidden && <Button
accessibilityLabel = { t(ok.translationKey ?? '') }
disabled = { ok.disabled }
id = 'modal-dialog-ok-button'
labelKey = { ok.translationKey }
onClick = { submit } />}
</div>
</BaseDialog>
);
};

View File

@@ -1,4 +1,3 @@
import { ModalTransition } from '@atlaskit/modal-dialog';
import React, { Component, ComponentType } from 'react';
import { IReduxState } from '../../../../app/types';
@@ -21,11 +20,6 @@ interface IProps {
*/
_componentProps: Object;
/**
* Whether the dialog is using the new component.
*/
_isNewDialog: boolean;
/**
* Whether the overflow drawer should be used.
*/
@@ -74,16 +68,12 @@ class DialogContainer extends Component<IProps> {
* @returns {ReactElement}
*/
render() {
return this.props._isNewDialog ? (
return (
<DialogTransition>
{this.props._overflowDrawer
? <JitsiPortal>{this._renderDialogContent()}</JitsiPortal>
: this._renderDialogContent() }
: this._renderDialogContent()}
</DialogTransition>
) : (
<ModalTransition>
{ this._renderDialogContent() }
</ModalTransition>
);
}
}
@@ -104,7 +94,6 @@ function mapStateToProps(state: IReduxState) {
return {
_component: stateFeaturesBaseDialog.component,
_componentProps: stateFeaturesBaseDialog.componentProps,
_isNewDialog: stateFeaturesBaseDialog.isNewDialog,
_overflowDrawer: overflowDrawer,
_reducedUI: reducedUI
};

View File

@@ -0,0 +1,335 @@
import React, { ComponentType, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../../app/types';
import { hideDialog } from '../../../dialog/actions';
import { IconArrowBack, IconCloseLarge } from '../../../icons/svg';
import { withPixelLineHeight } from '../../../styles/functions.web';
import BaseDialog, { IProps as IBaseProps } from './BaseDialog';
import Button from './Button';
import ClickableIcon from './ClickableIcon';
import ContextMenuItem from './ContextMenuItem';
const MOBILE_BREAKPOINT = 607;
const useStyles = makeStyles()(theme => {
return {
dialog: {
flexDirection: 'row',
height: '560px',
'@media (min-width: 608px) and (max-width: 712px)': {
width: '560px'
},
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
width: '100%',
position: 'absolute',
top: 0,
left: 0,
bottom: 0
},
'@media (max-width: 448px)': {
height: '100%'
}
},
sidebar: {
display: 'flex',
flexDirection: 'column',
minWidth: '211px',
maxWidth: '100%',
borderRight: `1px solid ${theme.palette.ui03}`,
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
width: '100%',
borderRight: 'none'
}
},
menuItemMobile: {
paddingLeft: '24px'
},
titleContainer: {
margin: 0,
padding: '24px',
paddingRight: 0,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
padding: '16px 24px'
}
},
title: {
...withPixelLineHeight(theme.typography.heading5),
color: `${theme.palette.text01} !important`,
margin: 0,
padding: 0
},
contentContainer: {
position: 'relative',
display: 'flex',
padding: '24px',
flexDirection: 'column',
overflow: 'hidden',
width: '100%',
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
padding: '0'
}
},
buttonContainer: {
width: '100%',
boxSizing: 'border-box',
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
flexGrow: 0,
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
justifyContent: 'space-between',
padding: '16px 24px'
}
},
backContainer: {
display: 'flex',
alignItems: 'center',
'& > button': {
marginRight: '24px'
}
},
closeIcon: {
'&:focus': {
boxShadow: 'none'
}
},
content: {
flexGrow: 1,
overflowY: 'auto',
width: '100%',
boxSizing: 'border-box',
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
padding: '0 24px'
}
},
footer: {
justifyContent: 'flex-end',
'& button:last-child': {
marginLeft: '16px'
}
}
};
});
interface IObject {
[key: string]: string | string[] | boolean | number | number[] | {} | undefined;
}
export interface IDialogTab {
className?: string;
component: ComponentType<any>;
icon: Function;
labelKey: string;
name: string;
props?: IObject;
propsUpdateFunction?: (tabState: IObject, newProps: IObject) => IObject;
submit?: Function;
}
interface IProps extends IBaseProps {
defaultTab?: string;
tabs: IDialogTab[];
}
const DialogWithTabs = ({
className,
defaultTab,
titleKey,
tabs
}: IProps) => {
const { classes, cx } = useStyles();
const dispatch = useDispatch();
const { t } = useTranslation();
const [ selectedTab, setSelectedTab ] = useState<string | undefined>(defaultTab ?? tabs[0].name);
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);
useEffect(() => {
if (clientWidth <= MOBILE_BREAKPOINT) {
!isMobile && setIsMobile(true);
} else {
isMobile && setIsMobile(false);
}
}, [ clientWidth, isMobile ]);
useEffect(() => {
if (isMobile) {
setSelectedTab(undefined);
} else {
setSelectedTab(defaultTab ?? tabs[0].name);
}
}, [ isMobile ]);
const back = useCallback(() => {
setSelectedTab(undefined);
}, []);
const onClose = useCallback(() => {
dispatch(hideDialog());
}, []);
const onClick = useCallback((tabName: string) => () => {
setSelectedTab(tabName);
}, []);
const getTabProps = (tabId: number) => {
const tabConfiguration = tabs[tabId];
const currentTabState = tabStates[tabId];
if (tabConfiguration.propsUpdateFunction) {
return tabConfiguration.propsUpdateFunction(
currentTabState ?? {},
tabConfiguration.props ?? {});
}
return { ...currentTabState };
};
const onTabStateChange = useCallback((tabId: number, state: IObject) => {
const newTabStates = [ ...tabStates ];
newTabStates[tabId] = state;
setTabStates(newTabStates);
}, [ tabStates ]);
const onSubmit = useCallback(() => {
tabs.forEach(({ submit }, idx) => {
submit?.(tabStates[idx]);
});
onClose();
}, [ tabs, tabStates ]);
const selectedTabIndex = useMemo(() => {
if (selectedTab) {
return tabs.findIndex(tab => tab.name === selectedTab);
}
return null;
}, [ selectedTab ]);
const selectedTabComponent = useMemo(() => {
if (selectedTabIndex !== null) {
const TabComponent = tabs[selectedTabIndex].component;
return (
<div
className = { tabs[selectedTabIndex].className }
key = { tabs[selectedTabIndex].name }>
<TabComponent
onTabStateChange = { onTabStateChange }
tabId = { selectedTabIndex }
{ ...getTabProps(selectedTabIndex) } />
</div>
);
}
return null;
}, [ selectedTabIndex, tabStates ]);
const closeIcon = useMemo(() => (
<ClickableIcon
accessibilityLabel = { t('dialog.close') }
className = { classes.closeIcon }
icon = { IconCloseLarge }
id = 'modal-header-close-button'
onClick = { onClose } />
), [ onClose ]);
return (
<BaseDialog
className = { cx(classes.dialog, className) }
onClose = { onClose }
size = 'large'>
{(!isMobile || !selectedTab) && (
<div className = { classes.sidebar }>
<div className = { classes.titleContainer }>
<h2 className = { classes.title }>{t(titleKey ?? '')}</h2>
{isMobile && closeIcon}
</div>
{tabs.map(tab => {
const label = t(tab.labelKey);
return (
<ContextMenuItem
accessibilityLabel = { label }
className = { cx(isMobile && classes.menuItemMobile) }
icon = { tab.icon }
key = { tab.name }
onClick = { onClick(tab.name) }
selected = { tab.name === selectedTab }
text = { label } />
);
})}
</div>
)}
{(!isMobile || selectedTab) && (
<div className = { classes.contentContainer }>
<div className = { classes.buttonContainer }>
{isMobile && (
<span className = { classes.backContainer }>
<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}
</div>
<div
className = { cx(classes.buttonContainer, classes.footer) }>
<Button
accessibilityLabel = { t('dialog.Cancel') }
id = 'modal-dialog-cancel-button'
labelKey = { 'dialog.Cancel' }
onClick = { onClose }
type = 'tertiary' />
<Button
accessibilityLabel = { t('dialog.Ok') }
id = 'modal-dialog-ok-button'
labelKey = { 'dialog.Ok' }
onClick = { onSubmit } />
</div>
</div>
)}
</BaseDialog>
);
};
export default DialogWithTabs;

View File

@@ -23,6 +23,7 @@ interface IProps extends IInputProps {
onKeyPress?: (e: React.KeyboardEvent) => void;
readOnly?: boolean;
required?: boolean;
testId?: string;
textarea?: boolean;
type?: 'text' | 'email' | 'number' | 'password';
}
@@ -152,6 +153,7 @@ const Input = React.forwardRef<any, IProps>(({
placeholder,
readOnly = false,
required,
testId,
textarea = false,
type = 'text',
value
@@ -201,6 +203,7 @@ const Input = React.forwardRef<any, IProps>(({
autoFocus = { autoFocus }
className = { cx(styles.input, isMobile && 'is-mobile',
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
data-testid = { testId }
disabled = { disabled }
{ ...(id ? { id } : {}) }
maxLength = { maxLength }

View File

@@ -6,6 +6,7 @@ import { withPixelLineHeight } from '../../../styles/functions.web';
interface ITabProps {
accessibilityLabel: string;
className?: string;
onChange: (id: string) => void;
selected: string;
tabs: Array<{
@@ -78,10 +79,11 @@ const useStyles = makeStyles()(theme => {
const Tabs = ({
tabs,
accessibilityLabel,
className,
onChange,
selected,
accessibilityLabel
tabs
}: ITabProps) => {
const { classes, cx } = useStyles();
const isMobile = isMobileBrowser();
@@ -93,7 +95,7 @@ const Tabs = ({
return (
<div
aria-label = { accessibilityLabel }
className = { classes.container }
className = { cx(classes.container, className) }
role = 'tablist'>
{tabs.map(tab => (
<button

View File

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

View File

@@ -8,11 +8,21 @@ 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.
*/
export const BUTTON_MODES: {
CONTAINED: 'contained';
TEXT: 'text';
} = {
CONTAINED: 'contained'
CONTAINED: 'contained',
TEXT: 'text'
};

View File

@@ -281,22 +281,3 @@ export const commonStyles = (theme: Theme) => {
}
};
};
/**
* Returns the global styles.
*
* @param {Object} theme - The Jitsi theme.
* @returns {Object}
*/
export const getGlobalStyles = (theme: Theme) => {
return {
// @atlaskit/modal-dialog OVERRIDES
'.atlaskit-portal div[role=dialog]': {
// override dialog background
'& > div': {
background: theme.palette.ui02,
color: theme.palette.text01
}
}
};
};

View File

@@ -1,3 +1,11 @@
/* eslint-disable lines-around-comment, max-len */
import { navigate }
// @ts-ignore
from '../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
// @ts-ignore
import { screen } from '../mobile/navigation/routes';
import { OPEN_CHAT } from './actionTypes';
export * from './actions.any';
@@ -6,13 +14,19 @@ export * from './actions.any';
* Displays the chat panel.
*
* @param {Object} participant - The recipient for the private chat.
* @param {boolean} disablePolls - Checks if polls are disabled.
*
* @returns {{
* participant: Participant,
* participant: participant,
* type: OPEN_CHAT
* }}
*/
export function openChat(participant: Object) {
export function openChat(participant: Object, disablePolls: boolean) {
if (disablePolls) {
navigate(screen.conference.chat);
}
navigate(screen.conference.chatandpolls.main);
return {
participant,
type: OPEN_CHAT

View File

@@ -1,7 +1,6 @@
// @ts-expect-error
// @ts-ignore
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { IStore } from '../app/types';
import { getParticipantById } from '../base/participants/functions';
import { OPEN_CHAT } from './actionTypes';
import { closeChat } from './actions.any';
@@ -26,27 +25,6 @@ export function openChat(participant?: Object) {
};
}
/**
* Displays the chat panel for a participant identified by an id.
*
* @param {string} id - The id of the participant.
* @returns {{
* participant: Participant,
* type: OPEN_CHAT
* }}
*/
export function openChatById(id: string) {
return function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
const participant = getParticipantById(getState(), id);
return dispatch({
participant,
type: OPEN_CHAT
});
};
}
/**
* Toggles display of the chat panel.
*

View File

@@ -4,7 +4,7 @@ import { IconMessage, IconReply } from '../../../base/icons';
import { getParticipantById } from '../../../base/participants';
import { connect } from '../../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions';
import { handleLobbyChatInitialized, openChat } from '../../../chat/actions.native';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';

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