mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-31 03:42:29 +00:00
Compare commits
30 Commits
rm-dead-co
...
7007
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
581c2e621c | ||
|
|
f3117f3037 | ||
|
|
877ef58dfb | ||
|
|
19e61747b8 | ||
|
|
b4bf363237 | ||
|
|
f8af9c4fae | ||
|
|
9fa426d97f | ||
|
|
e3c95e376a | ||
|
|
00ed794c50 | ||
|
|
b52d5629e2 | ||
|
|
9d8e646d4e | ||
|
|
850c0b97e4 | ||
|
|
87035d0812 | ||
|
|
ef0168c9ff | ||
|
|
df1a5a25d4 | ||
|
|
c424884201 | ||
|
|
0a464a5223 | ||
|
|
8cd62bc132 | ||
|
|
123a74b38b | ||
|
|
dbeca806bb | ||
|
|
f790d3e3ed | ||
|
|
a12f7fc4d2 | ||
|
|
456ce38a10 | ||
|
|
72ef1668f2 | ||
|
|
fce8f52574 | ||
|
|
8fcfd7a308 | ||
|
|
04a41395c8 | ||
|
|
18e8201167 | ||
|
|
27b8794d8c | ||
|
|
3cb0df579c |
@@ -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';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
&-content {
|
||||
position: relative;
|
||||
right: auto;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 4px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,3 +63,8 @@
|
||||
.desktop-source-preview-image-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.desktop-picker-tabs-container {
|
||||
width: 65%;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
|
||||
.prejoin-input {
|
||||
margin-bottom: 16px;
|
||||
width: 100%;
|
||||
|
||||
& input {
|
||||
text-align: center;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1286,6 +1286,7 @@
|
||||
"grantModerator": "Grant Moderator Rights",
|
||||
"hideSelfView": "Hide self view",
|
||||
"kick": "Kick out",
|
||||
"mirrorVideo": "Mirror my video",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Participant is muted",
|
||||
"muted": "Muted",
|
||||
|
||||
@@ -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));
|
||||
|
||||
396
package-lock.json
generated
396
package-lock.json
generated
@@ -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",
|
||||
@@ -73,7 +71,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/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -112,7 +110,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 +138,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 +164,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 +331,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 +620,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 +715,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",
|
||||
@@ -12088,16 +11944,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 +13399,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/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
@@ -16545,9 +16391,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 +18053,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 +18588,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 +20525,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 +20753,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 +20827,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",
|
||||
@@ -29506,16 +29183,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 +30278,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/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-lzgRkJtdlZ7bfq2seBuONRM6UND8NJVjMOZPlVoq7uP4UuxffBztsoHGc0g5Y5zEqi1AnfYLwVZZvXkpd82iew==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -32895,9 +32562,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 +33847,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 +34242,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",
|
||||
|
||||
@@ -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",
|
||||
@@ -78,7 +76,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/v1583.0.0+931ca368/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -117,7 +115,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 +143,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 +169,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",
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -21,6 +21,5 @@ import '../toolbox/middleware';
|
||||
import '../face-landmarks/middleware';
|
||||
import '../gifs/middleware';
|
||||
import '../whiteboard/middleware';
|
||||
import '../base/dialog/middleware';
|
||||
|
||||
import './middlewares.any';
|
||||
|
||||
@@ -165,3 +165,7 @@ export interface IReduxState {
|
||||
'features/virtual-background': IVirtualBackground;
|
||||
'features/whiteboard': IWhiteboardState;
|
||||
}
|
||||
|
||||
export interface IReloadNowOptions {
|
||||
hidePrejoin?: boolean;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// @flow
|
||||
|
||||
export * from './_';
|
||||
|
||||
export { default as DialogContent } from './DialogContent';
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
/* 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';
|
||||
// @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 {
|
||||
message: string;
|
||||
timeLeft: number;
|
||||
timeoutSeconds: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
const timeoutSeconds = 10 + randomInt(0, 20);
|
||||
|
||||
let message, title;
|
||||
|
||||
if (this.props.isNetworkFailure) {
|
||||
title = 'dialog.conferenceDisconnectTitle';
|
||||
message = 'dialog.conferenceDisconnectMsg';
|
||||
} else {
|
||||
title = 'dialog.conferenceReloadTitle';
|
||||
message = 'dialog.conferenceReloadMsg';
|
||||
}
|
||||
|
||||
this.state = {
|
||||
message,
|
||||
timeLeft: timeoutSeconds,
|
||||
timeoutSeconds,
|
||||
title
|
||||
};
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onReloadNow = this._onReloadNow.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* React Component method that executes once component is mounted.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props;
|
||||
const { timeLeft } = this.state;
|
||||
|
||||
logger.info(
|
||||
`The conference will be reloaded after ${
|
||||
this.state.timeoutSeconds} seconds.`);
|
||||
|
||||
this._interval
|
||||
= setInterval(
|
||||
() => {
|
||||
if (timeLeft === 0) {
|
||||
if (this._interval) {
|
||||
clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
|
||||
dispatch(reloadNow());
|
||||
} else {
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
timeLeft: prevState.timeLeft - 1
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(reloadNow());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { message, timeLeft, title } = this.state;
|
||||
|
||||
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 {{
|
||||
* message: string,
|
||||
* reason: string,
|
||||
* title: 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));
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Placeholder styles for web to be able to use cross platform components
|
||||
* unmodified such as {@code DialogContent}.
|
||||
*/
|
||||
export default {};
|
||||
@@ -5,11 +5,6 @@ import { Component } from 'react';
|
||||
*/
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* Function that closes the dialog.
|
||||
*/
|
||||
closeDialog: Function;
|
||||
|
||||
/**
|
||||
* Callback to invoke on change.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
@@ -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));
|
||||
@@ -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 };
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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:
|
||||
|
||||
3
react/features/base/icons/svg/bell.svg
Normal file
3
react/features/base/icons/svg/bell.svg
Normal 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 |
@@ -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';
|
||||
@@ -88,6 +89,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';
|
||||
|
||||
4
react/features/base/icons/svg/user.svg
Normal file
4
react/features/base/icons/svg/user.svg
Normal 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 |
@@ -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;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
|
||||
// @ts-ignore
|
||||
import Video from './web/Video';
|
||||
|
||||
export default Video;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as ActionButton } from './ActionButton';
|
||||
export { default as InputField } from './InputField';
|
||||
export { default as PreMeetingScreen } from './PreMeetingScreen';
|
||||
|
||||
@@ -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)
|
||||
}} />);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
199
react/features/base/ui/components/web/BaseDialog.tsx
Normal file
199
react/features/base/ui/components/web/BaseDialog.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
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 }>
|
||||
<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;
|
||||
@@ -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 ?? 'menu' }
|
||||
tabIndex = { tabIndex }>
|
||||
{children}
|
||||
</div>;
|
||||
|
||||
@@ -178,7 +178,14 @@ const ContextMenuItem = ({
|
||||
className = { styles.contextMenuItemIcon }
|
||||
size = { 20 }
|
||||
src = { icon } />}
|
||||
{text && <span className = { cx(styles.text, textClassName) }>{text}</span>}
|
||||
{text && (
|
||||
<span
|
||||
className = { cx(styles.text,
|
||||
_overflowDrawer && styles.drawerText,
|
||||
textClassName) }>
|
||||
{text}
|
||||
</span>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
335
react/features/base/ui/components/web/DialogWithTabs.tsx
Normal file
335
react/features/base/ui/components/web/DialogWithTabs.tsx
Normal 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;
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,6 +13,8 @@ export enum BUTTON_TYPES {
|
||||
*/
|
||||
export const BUTTON_MODES: {
|
||||
CONTAINED: 'contained';
|
||||
TEXT: 'text';
|
||||
} = {
|
||||
CONTAINED: 'contained'
|
||||
CONTAINED: 'contained',
|
||||
TEXT: 'text'
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { startKnocking } from '../../../lobby/actions.any';
|
||||
import { KnockingParticipantList } from '../../../lobby/components/native';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { navigate }
|
||||
from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
@@ -433,7 +432,13 @@ class Conference extends AbstractConference<Props, State> {
|
||||
|
||||
<LonelyMeetingExperience />
|
||||
|
||||
{ _shouldDisplayTileView || <><Filmstrip /><Toolbox /></> }
|
||||
{
|
||||
_shouldDisplayTileView
|
||||
|| <>
|
||||
<Filmstrip />
|
||||
<Toolbox />
|
||||
</>
|
||||
}
|
||||
</View>
|
||||
|
||||
<SafeAreaView
|
||||
@@ -463,10 +468,10 @@ class Conference extends AbstractConference<Props, State> {
|
||||
<AlwaysOnLabels createOnPress = { this._createOnPress } />
|
||||
</View>
|
||||
{ this._renderNotificationsContainer() }
|
||||
<KnockingParticipantList />
|
||||
</SafeAreaView>
|
||||
|
||||
<TestConnectionInfo />
|
||||
|
||||
{ this._renderConferenceNotification() }
|
||||
|
||||
{_shouldDisplayTileView && <Toolbox />}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CalleeInfoContainer } from '../../../invite';
|
||||
import { LargeVideo } from '../../../large-video';
|
||||
import { LobbyScreen } from '../../../lobby';
|
||||
import { getIsLobbyVisible } from '../../../lobby/functions';
|
||||
import { getOverlayToRender } from '../../../overlay/functions.web';
|
||||
import { ParticipantsPane } from '../../../participants-pane/components/web';
|
||||
import Prejoin from '../../../prejoin/components/web/Prejoin';
|
||||
import { isPrejoinPageVisible } from '../../../prejoin/functions';
|
||||
@@ -33,6 +34,7 @@ import type { AbstractProps } from '../AbstractConference';
|
||||
import ConferenceInfo from './ConferenceInfo';
|
||||
import { default as Notice } from './Notice';
|
||||
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
@@ -59,6 +61,11 @@ type Props = AbstractProps & {
|
||||
*/
|
||||
_backgroundAlpha: number,
|
||||
|
||||
/**
|
||||
* Are any overlays visible?
|
||||
*/
|
||||
_isAnyOverlayVisible: boolean,
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the root of {@link Conference} to modify the
|
||||
* application layout.
|
||||
@@ -198,6 +205,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_isAnyOverlayVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
@@ -234,7 +242,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
|
||||
{ _showPrejoin || _showLobby || <Toolbox /> }
|
||||
|
||||
{_notificationsVisible && (_overflowDrawer
|
||||
{_notificationsVisible && !_isAnyOverlayVisible && (_overflowDrawer
|
||||
? <JitsiPortal className = 'notification-portal'>
|
||||
{this.renderNotificationsContainer({ portal: true })}
|
||||
</JitsiPortal>
|
||||
@@ -383,6 +391,7 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
_overflowDrawer: overflowDrawer,
|
||||
|
||||
@@ -9,7 +9,7 @@ import Label from '../../../base/label/components/web/Label';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { Tooltip } from '../../../base/tooltip';
|
||||
import { open as openParticipantsPane } from '../../../participants-pane/actions';
|
||||
import { open as openParticipantsPane } from '../../../participants-pane/actions.web';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { areThereNotifications } from '../notifications/functions';
|
||||
import { getOverlayToRender } from '../overlay/functions';
|
||||
|
||||
|
||||
/**
|
||||
* Tells whether or not the notifications should be displayed within
|
||||
@@ -12,10 +12,7 @@ import { getOverlayToRender } from '../overlay/functions';
|
||||
*/
|
||||
export function shouldDisplayNotifications(stateful: IStateful) {
|
||||
const state = toState(stateful);
|
||||
const isAnyOverlayVisible = Boolean(getOverlayToRender(state));
|
||||
const { calleeInfoVisible } = state['features/invite'];
|
||||
|
||||
return areThereNotifications(state)
|
||||
&& !isAnyOverlayVisible
|
||||
&& !calleeInfoVisible;
|
||||
return areThereNotifications(state) && !calleeInfoVisible;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import Tabs from '@atlaskit/tabs';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
@@ -7,6 +6,7 @@ import { hideDialog } from '../../base/dialog/actions';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { connect } from '../../base/redux/functions';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
import Tabs from '../../base/ui/components/web/Tabs';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { obtainDesktopSources } from '../functions';
|
||||
@@ -85,7 +85,7 @@ interface IState {
|
||||
/**
|
||||
* The desktop source type currently being displayed.
|
||||
*/
|
||||
selectedTab: number;
|
||||
selectedTab: string;
|
||||
|
||||
/**
|
||||
* An object containing all the DesktopCapturerSources.
|
||||
@@ -133,7 +133,7 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
state: IState = {
|
||||
screenShareAudio: false,
|
||||
selectedSource: {},
|
||||
selectedTab: 0,
|
||||
selectedTab: DEFAULT_TAB_TYPE,
|
||||
sources: {},
|
||||
types: []
|
||||
};
|
||||
@@ -191,6 +191,8 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { selectedTab, selectedSource, sources } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{
|
||||
@@ -202,6 +204,14 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
size = 'large'
|
||||
titleKey = 'dialog.shareYourScreen'>
|
||||
{ this._renderTabs() }
|
||||
<DesktopPickerPane
|
||||
key = { selectedTab }
|
||||
onClick = { this._onPreviewClick }
|
||||
onDoubleClick = { this._onSubmit }
|
||||
onShareAudioChecked = { this._onShareAudioChecked }
|
||||
selectedSourceId = { selectedSource.id }
|
||||
sources = { sources[selectedTab as keyof typeof sources] }
|
||||
type = { selectedTab } />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -296,23 +306,20 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
* Stores the selected tab and updates the selected source via
|
||||
* {@code _getSelectedSource}.
|
||||
*
|
||||
* @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.
|
||||
* @param {string} id - The id of the newly selected tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTabSelected(_tab: Object, tabIndex: number) {
|
||||
const { types, sources } = this.state;
|
||||
_onTabSelected(id: string) {
|
||||
const { sources } = this.state;
|
||||
|
||||
this._selectedTabType = types[tabIndex];
|
||||
this._selectedTabType = id;
|
||||
|
||||
// When we change tabs also reset the screenShareAudio state so we don't
|
||||
// use the option from one tab when sharing from another.
|
||||
this.setState({
|
||||
screenShareAudio: false,
|
||||
selectedSource: this._getSelectedSource(sources),
|
||||
selectedTab: tabIndex
|
||||
selectedTab: id
|
||||
});
|
||||
}
|
||||
|
||||
@@ -334,27 +341,23 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderTabs() {
|
||||
const { selectedSource, sources, types } = this.state;
|
||||
const { types } = this.state;
|
||||
const { t } = this.props;
|
||||
const tabs
|
||||
= types.map(
|
||||
type => {
|
||||
return {
|
||||
content: <DesktopPickerPane
|
||||
key = { type }
|
||||
onClick = { this._onPreviewClick }
|
||||
onDoubleClick = { this._onSubmit }
|
||||
onShareAudioChecked = { this._onShareAudioChecked }
|
||||
selectedSourceId = { selectedSource.id }
|
||||
sources = { sources[type as keyof typeof sources] }
|
||||
type = { type } />,
|
||||
accessibilityLabel: t(TAB_LABELS[type as keyof typeof TAB_LABELS]),
|
||||
id: type,
|
||||
label: t(TAB_LABELS[type as keyof typeof TAB_LABELS])
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
onSelect = { this._onTabSelected }
|
||||
accessibilityLabel = ''
|
||||
className = 'desktop-picker-tabs-container'
|
||||
onChange = { this._onTabSelected }
|
||||
selected = { this.state.selectedTab }
|
||||
tabs = { tabs } />);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { getAvailableDevices } from '../../base/devices/actions.web';
|
||||
import AbstractDialogTab, {
|
||||
type Props as AbstractDialogTabProps
|
||||
} from '../../base/dialog/components/web/AbstractDialogTab';
|
||||
@@ -81,12 +82,6 @@ export type Props = {
|
||||
*/
|
||||
hideVideoInputPreview: boolean,
|
||||
|
||||
/**
|
||||
* An optional callback to invoke after the component has completed its
|
||||
* mount logic.
|
||||
*/
|
||||
mountCallback?: Function,
|
||||
|
||||
/**
|
||||
* The id of the audio input device to preview.
|
||||
*/
|
||||
@@ -176,7 +171,7 @@ class DeviceSelection extends AbstractDialogTab<Props, State> {
|
||||
this._createVideoInputTrack(this.props.selectedVideoInputId)
|
||||
])
|
||||
.catch(err => logger.warn('Failed to initialize preview tracks', err))
|
||||
.then(() => this.props.mountCallback && this.props.mountCallback());
|
||||
.then(() => getAvailableDevices());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -760,7 +760,7 @@ export function isStageFilmstripTopPanel(state: IReduxState, minParticipantCount
|
||||
export function isStageFilmstripEnabled(state: IReduxState) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return !filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP;
|
||||
return Boolean(!filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { addStageParticipant, setFilmstripVisible } from '../filmstrip/actions';
|
||||
import { addStageParticipant, removeStageParticipant, setFilmstripVisible } from '../filmstrip/actions';
|
||||
import { setTileView } from '../video-layout/actions.any';
|
||||
|
||||
import {
|
||||
@@ -180,9 +180,19 @@ function _onFollowMeCommand(attributes: any = {}, id: string, store: IStore) {
|
||||
|
||||
if (attributes.pinnedStageParticipants !== undefined) {
|
||||
const stageParticipants = JSON.parse(attributes.pinnedStageParticipants);
|
||||
let oldStageParticipants = [];
|
||||
|
||||
if (!_.isEqual(stageParticipants, oldState.pinnedStageParticipants)) {
|
||||
stageParticipants.forEach((p: { participantId: string; }) =>
|
||||
if (oldState.pinnedStageParticipants !== undefined) {
|
||||
oldStageParticipants = JSON.parse(oldState.pinnedStageParticipants);
|
||||
}
|
||||
|
||||
if (!_.isEqual(stageParticipants, oldStageParticipants)) {
|
||||
const toRemove = _.differenceWith(oldStageParticipants, stageParticipants, _.isEqual);
|
||||
const toAdd = _.differenceWith(stageParticipants, oldStageParticipants, _.isEqual);
|
||||
|
||||
toRemove.forEach((p: { participantId: string; }) =>
|
||||
store.dispatch(removeStageParticipant(p.participantId)));
|
||||
toAdd.forEach((p: { participantId: string; }) =>
|
||||
store.dispatch(addStageParticipant(p.participantId, true)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { isLocalParticipantModerator } from '../../../base/participants/functions';
|
||||
import { connect } from '../../../base/redux';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { handleLobbyChatInitialized } from '../../../chat/actions.native';
|
||||
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
|
||||
import { screen } from '../../../mobile/navigation/routes';
|
||||
import ParticipantItem
|
||||
from '../../../participants-pane/components/native/ParticipantItem';
|
||||
import { setKnockingParticipantApproval } from '../../actions.native';
|
||||
import { getKnockingParticipants, getLobbyEnabled, showLobbyChatButton } from '../../functions';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Props type of the component.
|
||||
*/
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
* The list of participants.
|
||||
*/
|
||||
_participants: Array<Object>,
|
||||
|
||||
/**
|
||||
* True if the list should be rendered.
|
||||
*/
|
||||
_visible: boolean,
|
||||
|
||||
/**
|
||||
* True if the polls feature is disabled.
|
||||
*/
|
||||
_isPollsDisabled: boolean,
|
||||
|
||||
/**
|
||||
* Returns true if the lobby chat button should be shown.
|
||||
*/
|
||||
_showChatButton: Function,
|
||||
|
||||
/**
|
||||
* The Redux Dispatch function.
|
||||
*/
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* Component to render a list for the actively knocking participants.
|
||||
*/
|
||||
class KnockingParticipantList extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onRespondToParticipant = this._onRespondToParticipant.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@code PureComponent#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { _participants, _visible, _showChatButton } = this.props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ _participants.map(p => (
|
||||
<View
|
||||
key = { p.id }
|
||||
style = { styles.knockingParticipantListEntry }>
|
||||
<ParticipantItem
|
||||
displayName = { p.name }
|
||||
isKnockingParticipant = { true }
|
||||
key = { p.id }
|
||||
participantID = { p.id }>
|
||||
<Button
|
||||
labelKey = { 'lobby.admit' }
|
||||
onClick = { this._onRespondToParticipant(p.id, true) }
|
||||
style = { styles.lobbyButtonAdmit }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
{
|
||||
_showChatButton(p)
|
||||
? (
|
||||
<Button
|
||||
labelKey = { 'lobby.chat' }
|
||||
onClick = { this._onInitializeLobbyChat(p.id) }
|
||||
style = { styles.lobbyButtonChat }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
) : null
|
||||
}
|
||||
<Button
|
||||
labelKey = { 'lobby.reject' }
|
||||
onClick = { this._onRespondToParticipant(p.id, false) }
|
||||
style = { styles.lobbyButtonReject }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
</ParticipantItem>
|
||||
</View>
|
||||
)) }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
_onRespondToParticipant: (string, boolean) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the response handler button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @param {boolean} approve - The response for the knocking.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onRespondToParticipant(id, approve) {
|
||||
return () => {
|
||||
this.props.dispatch(setKnockingParticipantApproval(id, approve));
|
||||
};
|
||||
}
|
||||
|
||||
_onInitializeLobbyChat: (string) => Function;
|
||||
|
||||
/**
|
||||
* Function that constructs a callback for the lobby chat button.
|
||||
*
|
||||
* @param {string} id - The id of the knocking participant.
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onInitializeLobbyChat(id) {
|
||||
return () => {
|
||||
this.props.dispatch(handleLobbyChatInitialized(id));
|
||||
if (this.props._isPollsDisabled) {
|
||||
return navigate(screen.conference.chat);
|
||||
}
|
||||
|
||||
navigate(screen.conference.chatandpolls.main);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const lobbyEnabled = getLobbyEnabled(state);
|
||||
const knockingParticipants = getKnockingParticipants(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_visible: lobbyEnabled && isLocalParticipantModerator(state),
|
||||
_showChatButton: participant => showLobbyChatButton(participant)(state),
|
||||
_isPollsDisabled: disablePolls,
|
||||
|
||||
// On mobile we only show a portion of the list for screen real estate reasons
|
||||
_participants: knockingParticipants.slice(0, 2)
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(KnockingParticipantList));
|
||||
@@ -1,5 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as KnockingParticipantList } from './KnockingParticipantList';
|
||||
export { default as LobbyScreen } from './LobbyScreen';
|
||||
export { default as LobbyChatScreen } from './LobbyChatScreen';
|
||||
|
||||
@@ -4,10 +4,11 @@ import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCloseLarge } from '../../../base/icons';
|
||||
import { InputField, PreMeetingScreen } from '../../../base/premeeting';
|
||||
import { PreMeetingScreen } from '../../../base/premeeting';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
import { connect } from '../../../base/redux';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
import ChatInput from '../../../chat/components/web/ChatInput';
|
||||
import MessageContainer from '../../../chat/components/web/MessageContainer';
|
||||
import AbstractLobbyScreen, {
|
||||
@@ -184,9 +185,10 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<InputField
|
||||
<Input
|
||||
className = 'prejoin-input'
|
||||
onChange = { this._onChangeDisplayName }
|
||||
placeHolder = { t('lobby.nameField') }
|
||||
placeholder = { t('lobby.nameField') }
|
||||
testId = 'lobby.nameField'
|
||||
value = { displayName } />
|
||||
);
|
||||
@@ -202,10 +204,10 @@ class LobbyScreen extends AbstractLobbyScreen<Props> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputField
|
||||
className = { _passwordJoinFailed ? 'error' : '' }
|
||||
<Input
|
||||
className = { `prejoin-input ${_passwordJoinFailed ? 'error' : ''}` }
|
||||
onChange = { this._onChangePassword }
|
||||
placeHolder = { t('lobby.passwordField') }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
testId = 'lobby.password'
|
||||
type = 'password'
|
||||
value = { this.state.password } />
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import i18n from 'i18next';
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED
|
||||
} from '../base/conference/actionTypes';
|
||||
import { conferenceWillJoin } from '../base/conference/actions';
|
||||
import { JitsiConferenceErrors, JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getFirstLoadableAvatarUrl, getParticipantDisplayName } from '../base/participants/functions';
|
||||
import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import {
|
||||
getFirstLoadableAvatarUrl,
|
||||
getParticipantDisplayName
|
||||
} from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
|
||||
import { isTestModeEnabled } from '../base/testing/functions';
|
||||
import { handleLobbyChatInitialized, removeLobbyChatParticipant } from '../chat/actions.any';
|
||||
import {
|
||||
hideNotification,
|
||||
showNotification
|
||||
} from '../notifications/actions';
|
||||
playSound,
|
||||
registerSound,
|
||||
unregisterSound
|
||||
} from '../base/sounds/actions';
|
||||
import { isTestModeEnabled } from '../base/testing/functions';
|
||||
import { BUTTON_TYPES } from '../base/ui/constants.any';
|
||||
// @ts-ignore
|
||||
import { openChat } from '../chat/actions';
|
||||
import {
|
||||
handleLobbyChatInitialized,
|
||||
removeLobbyChatParticipant
|
||||
} from '../chat/actions.any';
|
||||
import { hideNotification, showNotification } from '../notifications/actions';
|
||||
import {
|
||||
LOBBY_NOTIFICATION_ID,
|
||||
NOTIFICATION_ICON,
|
||||
@@ -28,7 +46,10 @@ import { open as openParticipantsPane } from '../participants-pane/actions';
|
||||
import { getParticipantsPaneOpen } from '../participants-pane/functions';
|
||||
import { shouldAutoKnock } from '../prejoin/functions';
|
||||
|
||||
import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED, KNOCKING_PARTICIPANT_LEFT } from './actionTypes';
|
||||
import {
|
||||
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
|
||||
KNOCKING_PARTICIPANT_LEFT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
approveKnockingParticipant,
|
||||
hideLobbyScreen,
|
||||
@@ -47,6 +68,7 @@ import { getKnockingParticipants, showLobbyChatButton } from './functions';
|
||||
import { KNOCKING_PARTICIPANT_FILE } from './sounds';
|
||||
import { IKnockingParticipant } from './types';
|
||||
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case APP_WILL_MOUNT:
|
||||
@@ -112,7 +134,14 @@ StateListenerRegistry.register(
|
||||
|
||||
const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
|
||||
|
||||
if (navigator.product === 'ReactNative' || isParticipantsPaneVisible) {
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyKnockingParticipant({
|
||||
id,
|
||||
name
|
||||
});
|
||||
}
|
||||
|
||||
if (isParticipantsPaneVisible || navigator.product === 'ReactNative') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,64 +149,6 @@ StateListenerRegistry.register(
|
||||
dispatch,
|
||||
getState
|
||||
});
|
||||
|
||||
let notificationTitle;
|
||||
let customActionNameKey;
|
||||
let customActionHandler;
|
||||
let descriptionKey;
|
||||
let icon;
|
||||
|
||||
const knockingParticipants = getKnockingParticipants(getState());
|
||||
const firstParticipant = knockingParticipants[0];
|
||||
const showChat = showLobbyChatButton(firstParticipant)(getState());
|
||||
|
||||
if (knockingParticipants.length > 1) {
|
||||
descriptionKey = 'notify.participantsWantToJoin';
|
||||
notificationTitle = i18n.t('notify.waitingParticipants', {
|
||||
waitingParticipants: knockingParticipants.length
|
||||
});
|
||||
icon = NOTIFICATION_ICON.PARTICIPANTS;
|
||||
customActionNameKey = [ 'notify.viewLobby' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(openParticipantsPane());
|
||||
}) ];
|
||||
} else {
|
||||
descriptionKey = 'notify.participantWantsToJoin';
|
||||
notificationTitle = firstParticipant.name;
|
||||
icon = NOTIFICATION_ICON.PARTICIPANT;
|
||||
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(approveKnockingParticipant(firstParticipant.id));
|
||||
}),
|
||||
() => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(rejectKnockingParticipant(firstParticipant.id));
|
||||
}) ];
|
||||
if (showChat) {
|
||||
customActionNameKey.splice(1, 0, 'lobby.chat');
|
||||
customActionHandler.splice(1, 0, () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(handleLobbyChatInitialized(firstParticipant.id));
|
||||
}));
|
||||
}
|
||||
}
|
||||
dispatch(showNotification({
|
||||
title: notificationTitle,
|
||||
descriptionKey,
|
||||
uid: LOBBY_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionHandler,
|
||||
icon
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyKnockingParticipant({
|
||||
id,
|
||||
name
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -227,16 +198,20 @@ function _handleLobbyNotification(store: IStore) {
|
||||
let notificationTitle;
|
||||
let customActionNameKey;
|
||||
let customActionHandler;
|
||||
let customActionType;
|
||||
let descriptionKey;
|
||||
let icon;
|
||||
|
||||
if (knockingParticipants.length === 1) {
|
||||
const firstParticipant = knockingParticipants[0];
|
||||
const { disablePolls } = getState()['features/base/config'];
|
||||
const showChat = showLobbyChatButton(firstParticipant)(getState());
|
||||
|
||||
descriptionKey = 'notify.participantWantsToJoin';
|
||||
notificationTitle = firstParticipant.name;
|
||||
icon = NOTIFICATION_ICON.PARTICIPANT;
|
||||
customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
|
||||
customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(approveKnockingParticipant(firstParticipant.id));
|
||||
@@ -245,6 +220,18 @@ function _handleLobbyNotification(store: IStore) {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(rejectKnockingParticipant(firstParticipant.id));
|
||||
}) ];
|
||||
|
||||
// This checks if lobby chat button is available
|
||||
// and, if so, it adds it to the customActionNameKey array
|
||||
if (showChat) {
|
||||
customActionNameKey.splice(1, 0, 'lobby.chat');
|
||||
customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
|
||||
customActionHandler.splice(1, 0, () => batch(() => {
|
||||
dispatch(handleLobbyChatInitialized(firstParticipant.id));
|
||||
// @ts-ignore
|
||||
dispatch(openChat(disablePolls));
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
descriptionKey = 'notify.participantsWantToJoin';
|
||||
notificationTitle = i18n.t('notify.waitingParticipants', {
|
||||
@@ -252,16 +239,19 @@ function _handleLobbyNotification(store: IStore) {
|
||||
});
|
||||
icon = NOTIFICATION_ICON.PARTICIPANTS;
|
||||
customActionNameKey = [ 'notify.viewLobby' ];
|
||||
customActionType = [ BUTTON_TYPES.PRIMARY ];
|
||||
customActionHandler = [ () => batch(() => {
|
||||
dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
|
||||
dispatch(openParticipantsPane());
|
||||
}) ];
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
title: notificationTitle,
|
||||
descriptionKey,
|
||||
uid: LOBBY_NOTIFICATION_ID,
|
||||
customActionNameKey,
|
||||
customActionType,
|
||||
customActionHandler,
|
||||
icon
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
@@ -40,7 +40,6 @@ import { getLocalTracks, isLocalTrackMuted, toggleScreensharing } from '../../ba
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat';
|
||||
import { openChat } from '../../chat/actions';
|
||||
import { closeChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.any';
|
||||
import { SET_PAGE_RELOAD_OVERLAY_CANCELED } from '../../overlay/actionTypes';
|
||||
import { setRequestingSubtitles } from '../../subtitles';
|
||||
import { muteLocal } from '../../video-menu/actions';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture';
|
||||
@@ -214,16 +213,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case SET_PAGE_RELOAD_OVERLAY_CANCELED:
|
||||
sendEvent(
|
||||
store,
|
||||
CONFERENCE_TERMINATED,
|
||||
/* data */ {
|
||||
error: _toErrorString(action.error),
|
||||
url: _normalizeUrl(store.getState()['features/base/connection'].locationURL)
|
||||
});
|
||||
|
||||
break;
|
||||
case SET_VIDEO_MUTED:
|
||||
sendEvent(
|
||||
store,
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// @flow
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SafeAreaView, Text, View } from 'react-native';
|
||||
|
||||
// @ts-ignore
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { LoadingIndicator } from '../../../base/react';
|
||||
// @ts-ignore
|
||||
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
|
||||
|
||||
// @ts-ignore
|
||||
import { TEXT_COLOR, navigationStyles } from './styles';
|
||||
|
||||
|
||||
const ConnectingPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React, { useCallback } from 'react';
|
||||
import { StatusBar } from 'react-native';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { DialInSummary } from '../../../invite';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
// @ts-ignore
|
||||
import DialInSummary from '../../../invite/components/dial-in-summary/native/DialInSummary';
|
||||
import Prejoin from '../../../prejoin/components/native/Prejoin';
|
||||
// @ts-ignore
|
||||
import WelcomePage from '../../../welcome/components/WelcomePage';
|
||||
import { isWelcomePageEnabled } from '../../../welcome/functions';
|
||||
// @ts-ignore
|
||||
import { _ROOT_NAVIGATION_READY } from '../actionTypes';
|
||||
// @ts-ignore
|
||||
import { rootNavigationRef } from '../rootNavigationContainerRef';
|
||||
// @ts-ignore
|
||||
import { screen } from '../routes';
|
||||
// @ts-ignore
|
||||
import {
|
||||
conferenceNavigationContainerScreenOptions,
|
||||
connectingScreenOptions,
|
||||
@@ -18,6 +27,7 @@ import {
|
||||
navigationContainerTheme,
|
||||
preJoinScreenOptions,
|
||||
welcomeScreenOptions
|
||||
// @ts-ignore
|
||||
} from '../screenOptions';
|
||||
|
||||
import ConnectingPage from './ConnectingPage';
|
||||
@@ -32,13 +42,13 @@ type Props = {
|
||||
/**
|
||||
* Redux dispatch function.
|
||||
*/
|
||||
dispatch: Function,
|
||||
dispatch: Function;
|
||||
|
||||
/**
|
||||
* Is welcome page available?
|
||||
*/
|
||||
isWelcomePageAvailable: boolean
|
||||
}
|
||||
isWelcomePageAvailable: boolean;
|
||||
};
|
||||
|
||||
|
||||
const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) => {
|
||||
@@ -100,7 +110,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: Props) =>
|
||||
* @param {Object} state - The Redux state.
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state: Object) {
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
isWelcomePageAvailable: isWelcomePageEnabled(state)
|
||||
};
|
||||
@@ -27,6 +27,11 @@ export type Props = {
|
||||
*/
|
||||
customActionNameKey: string[],
|
||||
|
||||
/**
|
||||
* The type of button.
|
||||
*/
|
||||
customActionType: ?string[],
|
||||
|
||||
/**
|
||||
* The text to display in the body of the notification. If not passed
|
||||
* in, the passed in descriptionKey will be used.
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { Icon, IconCloseLarge } from '../../../base/icons';
|
||||
import { replaceNonUnicodeEmojis } from '../../../chat/functions';
|
||||
import AbstractNotification, {
|
||||
type Props
|
||||
} from '../AbstractNotification';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* Default value for the maxLines prop.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
const DEFAULT_MAX_LINES = 2;
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notification }>
|
||||
<View style = { styles.contentColumn }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.notificationContent }>
|
||||
{
|
||||
this._renderContent()
|
||||
}
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity onPress = { this._onDismissed }>
|
||||
<Icon
|
||||
src = { IconCloseLarge }
|
||||
style = { styles.dismissIcon } />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notification's content. If the title or title key is present
|
||||
* it will be just the title. Otherwise it will fallback to description.
|
||||
*
|
||||
* @returns {Array<ReactElement>}
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
const { maxLines = DEFAULT_MAX_LINES, t, title, titleArguments, titleKey, concatText } = this.props;
|
||||
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||
const description = this._getDescription();
|
||||
const titleConcat = [];
|
||||
|
||||
if (concatText) {
|
||||
titleConcat.push(titleText);
|
||||
}
|
||||
|
||||
if (description && description.length) {
|
||||
return [ ...titleConcat, ...description ].map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText }>
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
numberOfLines = { maxLines }
|
||||
style = { styles.contentText } >
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>;
|
||||
|
||||
_onDismissed: () => void;
|
||||
}
|
||||
|
||||
export default translate(Notification);
|
||||
273
react/features/notifications/components/native/Notification.tsx
Normal file
273
react/features/notifications/components/native/Notification.tsx
Normal file
@@ -0,0 +1,273 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Animated, Text, View } from 'react-native';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import {
|
||||
Icon,
|
||||
IconCloseLarge,
|
||||
IconInfoCircle,
|
||||
IconUsers,
|
||||
IconWarning
|
||||
// @ts-ignore
|
||||
} from '../../../base/icons';
|
||||
import { colors } from '../../../base/ui/Tokens';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { replaceNonUnicodeEmojis } from '../../../chat/functions';
|
||||
import { NOTIFICATION_ICON } from '../../constants';
|
||||
import AbstractNotification, {
|
||||
type Props as AbstractNotificationProps
|
||||
// @ts-ignore
|
||||
} from '../AbstractNotification';
|
||||
|
||||
// @ts-ignore
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* Secondary colors for notification icons.
|
||||
*
|
||||
* @type {{error, info, normal, success, warning}}
|
||||
*/
|
||||
|
||||
const ICON_COLOR = {
|
||||
error: colors.error06,
|
||||
normal: colors.primary06,
|
||||
success: colors.success05,
|
||||
warning: colors.warning05
|
||||
};
|
||||
|
||||
|
||||
export type Props = AbstractNotificationProps & WithTranslation & {
|
||||
_participants: ArrayLike<any>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} to display a notification.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class Notification extends AbstractNotification<Props> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Notification} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// @ts-ignore
|
||||
this.state = {
|
||||
notificationContainerAnimation: new Animated.Value(0)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#componentDidMount()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
Animated.timing(
|
||||
// @ts-ignore
|
||||
this.state.notificationContainerAnimation,
|
||||
{
|
||||
toValue: 1,
|
||||
duration: 500,
|
||||
useNativeDriver: true
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates action button configurations for the notification based on
|
||||
* notification appearance.
|
||||
*
|
||||
* @private
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
_mapAppearanceToButtons() {
|
||||
const {
|
||||
customActionHandler,
|
||||
customActionNameKey,
|
||||
customActionType
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
if (customActionNameKey?.length && customActionHandler?.length && customActionType?.length) {
|
||||
return customActionNameKey?.map((customAction: string, index: number) => (
|
||||
<Button
|
||||
accessibilityLabel = { customAction }
|
||||
key = { index }
|
||||
labelKey = { customAction }
|
||||
mode = { BUTTON_MODES.TEXT }
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick = { () => {
|
||||
if (customActionHandler[index]()) {
|
||||
this._onDismissed();
|
||||
}
|
||||
} }
|
||||
style = { styles.btn }
|
||||
type = { customActionType[index] } />
|
||||
));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Icon type component to be used, based on icon or appearance.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getIcon() {
|
||||
const {
|
||||
appearance,
|
||||
icon
|
||||
// @ts-ignore
|
||||
} = this.props;
|
||||
|
||||
let src;
|
||||
|
||||
switch (icon || appearance) {
|
||||
case NOTIFICATION_ICON.PARTICIPANT:
|
||||
src = IconInfoCircle;
|
||||
break;
|
||||
case NOTIFICATION_ICON.PARTICIPANTS:
|
||||
src = IconUsers;
|
||||
break;
|
||||
case NOTIFICATION_ICON.WARNING:
|
||||
src = IconWarning;
|
||||
break;
|
||||
default:
|
||||
src = IconInfoCircle;
|
||||
break;
|
||||
}
|
||||
|
||||
return src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an icon component depending on the configured notification
|
||||
* appearance.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_mapAppearanceToIcon() {
|
||||
// @ts-ignore
|
||||
const { appearance } = this.props;
|
||||
// @ts-ignore
|
||||
const color = ICON_COLOR[appearance];
|
||||
|
||||
return (
|
||||
<View style = { styles.iconContainer }>
|
||||
<Icon
|
||||
color = { color }
|
||||
size = { 24 }
|
||||
src = { this._getIcon() } />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
// @ts-ignore
|
||||
const { icon } = this.props;
|
||||
const contentColumnStyles = icon === NOTIFICATION_ICON.PARTICIPANTS
|
||||
? styles.contentColumn : styles.interactiveContentColumn;
|
||||
const description = this._getDescription();
|
||||
|
||||
const notificationStyles = description?.length
|
||||
? styles.notificationWithDescription
|
||||
: styles.notification;
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
notificationStyles,
|
||||
{
|
||||
// @ts-ignore
|
||||
opacity: this.state.notificationContainerAnimation
|
||||
}
|
||||
] }>
|
||||
<View style = { contentColumnStyles }>
|
||||
{ this._mapAppearanceToIcon() }
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.contentContainer }>
|
||||
{ this._renderContent() }
|
||||
</View>
|
||||
<View style = { styles.btnContainer }>
|
||||
{ this._mapAppearanceToButtons() }
|
||||
</View>
|
||||
</View>
|
||||
<IconButton
|
||||
color = { BaseTheme.palette.icon04 }
|
||||
onPress = { this._onDismissed }
|
||||
src = { IconCloseLarge }
|
||||
type = { BUTTON_TYPES.TERTIARY } />
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the notification's content. If the title or title key is present
|
||||
* it will be just the title. Otherwise it will fallback to description.
|
||||
*
|
||||
* @returns {Array<ReactElement>}
|
||||
* @private
|
||||
*/
|
||||
_renderContent() {
|
||||
// @ts-ignore
|
||||
const { t, title, titleArguments, titleKey } = this.props;
|
||||
const titleText = title || (titleKey && t(titleKey, titleArguments));
|
||||
const description = this._getDescription();
|
||||
|
||||
if (description?.length) {
|
||||
return (
|
||||
<>
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
{
|
||||
description.map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
style = { styles.contentText }>
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
))
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.contentTextTitle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
_getDescription: () => Array<string>;
|
||||
|
||||
_onDismissed: () => void;
|
||||
}
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
export default translate(Notification);
|
||||
@@ -1,14 +1,13 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { hideNotification } from '../../actions';
|
||||
import { areThereNotifications } from '../../functions';
|
||||
|
||||
import Notification from './Notification';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -21,12 +20,7 @@ type Props = {
|
||||
/**
|
||||
* Invoked to update the redux store in order to remove notifications.
|
||||
*/
|
||||
dispatch: Function,
|
||||
|
||||
/**
|
||||
* Any custom styling applied to the notifications container.
|
||||
*/
|
||||
style: Object
|
||||
dispatch: Function
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -65,7 +59,7 @@ class NotificationsContainer extends Component<Props> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a timeout for the first notification (if applicable).
|
||||
* Sets a timeout (if applicable).
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
@@ -173,25 +167,22 @@ class NotificationsContainer extends Component<Props> {
|
||||
render() {
|
||||
const { _notifications } = this.props;
|
||||
|
||||
// Currently the native container displays only the topmost notification
|
||||
const theNotification = _notifications[0];
|
||||
|
||||
if (!theNotification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { [
|
||||
styles.notificationContainer,
|
||||
this.props.style
|
||||
] } >
|
||||
<Notification
|
||||
{ ...theNotification.props }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { theNotification.uid } />
|
||||
</View>
|
||||
<>
|
||||
{
|
||||
_notifications.map((notification, index) => {
|
||||
const { props, uid } = notification;
|
||||
|
||||
return (
|
||||
<Notification
|
||||
{ ...props }
|
||||
key = { index }
|
||||
onDismissed = { this._onDismissed }
|
||||
uid = { uid } />
|
||||
);
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
// @flow
|
||||
|
||||
import { BoxModel, ColorPalette } from '../../../base/styles';
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
const contentColumn = {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
marginLeft: BaseTheme.spacing[2]
|
||||
};
|
||||
|
||||
const notification = {
|
||||
display: 'flex',
|
||||
backgroundColor: BaseTheme.palette.ui12,
|
||||
borderRadius: BaseTheme.shape.borderRadius,
|
||||
borderLeftColor: BaseTheme.palette.link01Active,
|
||||
borderLeftWidth: 4,
|
||||
flexDirection: 'row',
|
||||
maxHeight: 104,
|
||||
height: 'auto',
|
||||
marginBottom: BaseTheme.spacing[3],
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
maxWidth: 400,
|
||||
width: 'auto'
|
||||
};
|
||||
|
||||
/**
|
||||
* The styles of the React {@code Components} of the feature notifications.
|
||||
@@ -10,52 +31,79 @@ export default {
|
||||
/**
|
||||
* The content (left) column of the notification.
|
||||
*/
|
||||
interactiveContentColumn: {
|
||||
...contentColumn
|
||||
},
|
||||
|
||||
contentColumn: {
|
||||
justifyContent: 'center',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
paddingLeft: 1.5 * BoxModel.padding
|
||||
...contentColumn,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
|
||||
/**
|
||||
* Test style of the notification.
|
||||
*/
|
||||
|
||||
contentContainer: {
|
||||
marginTop: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
contentText: {
|
||||
alignSelf: 'flex-start',
|
||||
color: ColorPalette.white
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[6],
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
contentTextTitle: {
|
||||
color: BaseTheme.palette.text04,
|
||||
marginLeft: BaseTheme.spacing[6],
|
||||
fontWeight: 'bold',
|
||||
marginTop: BaseTheme.spacing[1]
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismiss icon style.
|
||||
*/
|
||||
dismissIcon: {
|
||||
color: ColorPalette.white,
|
||||
fontSize: 20,
|
||||
padding: 1.5 * BoxModel.padding
|
||||
color: BaseTheme.palette.icon04,
|
||||
fontSize: 20
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost view of a single notification.
|
||||
*/
|
||||
notification: {
|
||||
backgroundColor: '#768898',
|
||||
flexDirection: 'row',
|
||||
minHeight: 48,
|
||||
marginTop: 0.5 * BoxModel.margin
|
||||
...notification
|
||||
},
|
||||
|
||||
/**
|
||||
* Outermost container of a list of notifications.
|
||||
*/
|
||||
notificationContainer: {
|
||||
flexGrow: 0,
|
||||
justifyContent: 'flex-end'
|
||||
notificationWithDescription: {
|
||||
...notification,
|
||||
paddingBottom: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper for the message.
|
||||
*/
|
||||
notificationContent: {
|
||||
flexDirection: 'column'
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row'
|
||||
},
|
||||
|
||||
participantName: {
|
||||
color: BaseTheme.palette.text04,
|
||||
overflow: 'hidden'
|
||||
},
|
||||
|
||||
iconContainer: {
|
||||
left: BaseTheme.spacing[1],
|
||||
position: 'absolute',
|
||||
top: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
btn: {
|
||||
marginLeft: BaseTheme.spacing[4]
|
||||
},
|
||||
|
||||
btnContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginLeft: BaseTheme.spacing[1]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -124,7 +124,6 @@ const useStyles = (theme: Theme) => {
|
||||
*/
|
||||
class NotificationsContainer extends Component<IProps> {
|
||||
_api: Object;
|
||||
_timeouts: Map<string, number>;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code NotificationsContainer} instance.
|
||||
@@ -134,8 +133,6 @@ class NotificationsContainer extends Component<IProps> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._timeouts = new Map();
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDismissed = this._onDismissed.bind(this);
|
||||
|
||||
@@ -186,13 +183,6 @@ class NotificationsContainer extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDismissed(uid: string) {
|
||||
const timeout = this._timeouts.get(`${uid}`);
|
||||
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
this._timeouts.delete(`${uid}`);
|
||||
}
|
||||
|
||||
this.props.dispatch(hideNotification(uid));
|
||||
}
|
||||
|
||||
|
||||
@@ -89,41 +89,35 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
switch (action.type) {
|
||||
case CLEAR_NOTIFICATIONS: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
const _notifications = getNotifications(state);
|
||||
const _notifications = getNotifications(state);
|
||||
|
||||
for (const notification of _notifications) {
|
||||
if (timers.has(notification.uid)) {
|
||||
const timeout = timers.get(notification.uid);
|
||||
for (const notification of _notifications) {
|
||||
if (timers.has(notification.uid)) {
|
||||
const timeout = timers.get(notification.uid);
|
||||
|
||||
clearTimeout(timeout);
|
||||
timers.delete(notification.uid);
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
timers.delete(notification.uid);
|
||||
}
|
||||
timers.clear();
|
||||
}
|
||||
timers.clear();
|
||||
break;
|
||||
}
|
||||
case SHOW_NOTIFICATION: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
if (timers.has(action.uid)) {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
}
|
||||
|
||||
createTimeoutId(action, dispatch);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HIDE_NOTIFICATION: {
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
if (timers.has(action.uid)) {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
}
|
||||
|
||||
createTimeoutId(action, dispatch);
|
||||
break;
|
||||
}
|
||||
case HIDE_NOTIFICATION: {
|
||||
const timer = timers.get(action.uid);
|
||||
|
||||
clearTimeout(timer);
|
||||
timers.delete(action.uid);
|
||||
break;
|
||||
}
|
||||
case PARTICIPANT_JOINED: {
|
||||
|
||||
@@ -90,12 +90,25 @@ ReducerRegistry.register<INotificationsState>('features/notifications',
|
||||
* queue.
|
||||
*/
|
||||
function _insertNotificationByPriority(notifications: INotification[], notification: INotification) {
|
||||
|
||||
// Create a copy to avoid mutation.
|
||||
const copyOfNotifications = notifications.slice();
|
||||
|
||||
// Get the index of any queued notification that has the same id as the new notification
|
||||
let insertAtLocation = copyOfNotifications.findIndex(
|
||||
(queuedNotification: INotification) =>
|
||||
queuedNotification?.uid === notification?.uid
|
||||
);
|
||||
|
||||
if (insertAtLocation !== -1) {
|
||||
copyOfNotifications.splice(insertAtLocation, 1, notification);
|
||||
|
||||
return copyOfNotifications;
|
||||
}
|
||||
|
||||
const newNotificationPriority
|
||||
= NOTIFICATION_TYPE_PRIORITIES[notification.props.appearance ?? ''] || 0;
|
||||
|
||||
// Default to putting the new notification at the end of the queue.
|
||||
let insertAtLocation = notifications.length;
|
||||
|
||||
// Find where to insert the new notification based on priority. Do not
|
||||
// insert at the front of the queue so that the user can finish acting on
|
||||
// any notification currently being read.
|
||||
@@ -103,7 +116,7 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
|
||||
const queuedNotification = notifications[i];
|
||||
const queuedNotificationPriority
|
||||
= NOTIFICATION_TYPE_PRIORITIES[queuedNotification.props.appearance ?? '']
|
||||
|| 0;
|
||||
|| 0;
|
||||
|
||||
if (queuedNotificationPriority < newNotificationPriority) {
|
||||
insertAtLocation = i;
|
||||
@@ -111,9 +124,6 @@ function _insertNotificationByPriority(notifications: INotification[], notificat
|
||||
}
|
||||
}
|
||||
|
||||
// Create a copy to avoid mutation and insert the notification.
|
||||
const copyOfNotifications = notifications.slice();
|
||||
|
||||
copyOfNotifications.splice(insertAtLocation, 0, notification);
|
||||
|
||||
return copyOfNotifications;
|
||||
|
||||
@@ -5,6 +5,7 @@ export interface INotificationProps {
|
||||
concatText?: boolean;
|
||||
customActionHandler?: Function[];
|
||||
customActionNameKey?: string[];
|
||||
customActionType?: string[];
|
||||
description?: string | React.ReactNode;
|
||||
descriptionArguments?: Object;
|
||||
descriptionKey?: string;
|
||||
|
||||
@@ -13,26 +13,3 @@
|
||||
*/
|
||||
export const MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED
|
||||
= 'MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED';
|
||||
|
||||
/**
|
||||
* Adjust the state of the fatal error which shows/hides the reload screen. See
|
||||
* action methods's description for more info about each of the fields.
|
||||
*
|
||||
* {
|
||||
* type: SET_FATAL_ERROR,
|
||||
* fatalError: ?Object
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_FATAL_ERROR = 'SET_FATAL_ERROR';
|
||||
|
||||
/**
|
||||
* The type of the Redux action which signals that the overlay was canceled.
|
||||
*
|
||||
* {
|
||||
* type: export const SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
* }
|
||||
* @public
|
||||
*/
|
||||
export const SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
= 'SET_PAGE_RELOAD_OVERLAY_CANCELED';
|
||||
|
||||
31
react/features/overlay/actions.native.ts
Normal file
31
react/features/overlay/actions.native.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/* eslint-disable max-len */
|
||||
|
||||
// @ts-ignore
|
||||
import { PageReloadDialog, openDialog } from '../base/dialog';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} _isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} _browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(_isVisible: boolean, _browser: string) {
|
||||
// Dummy.
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openPageReloadDialog() {
|
||||
return openDialog(PageReloadDialog);
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import {
|
||||
MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
SET_FATAL_ERROR,
|
||||
SET_PAGE_RELOAD_OVERLAY_CANCELED
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that an unrecoverable error has occurred and the reload
|
||||
* screen will be displayed or hidden.
|
||||
*
|
||||
* @param {Object} fatalError - A critical error which was not claimed by any
|
||||
* feature for error recovery (the recoverable flag was not set). If
|
||||
* {@code undefined} then any fatal error currently stored will be discarded.
|
||||
* @returns {{
|
||||
* type: SET_FATAL_ERROR,
|
||||
* fatalError: ?Error
|
||||
* }}
|
||||
*/
|
||||
export function setFatalError(fatalError?: Object) {
|
||||
return {
|
||||
type: SET_FATAL_ERROR,
|
||||
fatalError
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The action indicates that the overlay was canceled.
|
||||
*
|
||||
* @param {Object} error - The error that caused the display of the overlay.
|
||||
*
|
||||
* @returns {{
|
||||
* type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
|
||||
* error: ?Error
|
||||
* }}
|
||||
*/
|
||||
export function setPageReloadOverlayCanceled(error: Object) {
|
||||
return {
|
||||
type: SET_PAGE_RELOAD_OVERLAY_CANCELED,
|
||||
error
|
||||
};
|
||||
}
|
||||
32
react/features/overlay/actions.web.ts
Normal file
32
react/features/overlay/actions.web.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED } from './actionTypes';
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the prompt for media permission is visible or not.
|
||||
*
|
||||
* @param {boolean} isVisible - If the value is true - the prompt for media
|
||||
* permission is visible otherwise the value is false/undefined.
|
||||
* @param {string} browser - The name of the current browser.
|
||||
* @public
|
||||
* @returns {{
|
||||
* type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
* browser: {string},
|
||||
* isVisible: {boolean}
|
||||
* }}
|
||||
*/
|
||||
export function mediaPermissionPromptVisibilityChanged(isVisible: boolean, browser: string) {
|
||||
return {
|
||||
type: MEDIA_PERMISSION_PROMPT_VISIBILITY_CHANGED,
|
||||
browser,
|
||||
isVisible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link PageReloadDialog}.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openPageReloadDialog() {
|
||||
// Dummy
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export * from './native';
|
||||
@@ -1,3 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export * from './web';
|
||||
@@ -1,4 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayContainer } from './OverlayContainer';
|
||||
export * from './_';
|
||||
@@ -1,38 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component, type Node } from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code OverlayFrame}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The children components to be displayed into the overlay frame.
|
||||
*/
|
||||
children: Node,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React component to act as the frame for overlays.
|
||||
*/
|
||||
export default class OverlayFrame extends Component<Props> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<View style = { styles.container }>
|
||||
<SafeAreaView style = { styles.safeContainer } >
|
||||
{ this.props.children }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { appNavigate, reloadNow } from '../../../app/actions';
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { setFatalError, setPageReloadOverlayCanceled } from '../../actions';
|
||||
import AbstractPageReloadOverlay, {
|
||||
type Props,
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractPageReloadOverlay';
|
||||
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
|
||||
/**
|
||||
* Implements a React Component for page reload overlay. Shown before the
|
||||
* conference is reloaded. Shows a warning message and counts down towards the
|
||||
* reload.
|
||||
*/
|
||||
class PageReloadOverlay extends AbstractPageReloadOverlay<Props> {
|
||||
_interval: IntervalID;
|
||||
|
||||
/**
|
||||
* Initializes a new PageReloadOverlay instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @public
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onReloadNow = this._onReloadNow.bind(this);
|
||||
}
|
||||
|
||||
_onCancel: () => void;
|
||||
|
||||
/**
|
||||
* Handle clicking of the "Cancel" button. It will navigate back to the
|
||||
* welcome page.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancel() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(setPageReloadOverlayCanceled(this.props.error));
|
||||
this.props.dispatch(setFatalError(undefined));
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
_onReloadNow: () => void;
|
||||
|
||||
/**
|
||||
* 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 {void}
|
||||
*/
|
||||
_onReloadNow() {
|
||||
clearInterval(this._interval);
|
||||
this.props.dispatch(reloadNow());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { message, timeLeft, title } = this.state;
|
||||
|
||||
return (
|
||||
<OverlayFrame>
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.Cancel'
|
||||
confirmLabel = 'dialog.rejoinNow'
|
||||
descriptionKey = { `${t(message, { seconds: timeLeft })}` }
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onReloadNow }
|
||||
title = { title } />
|
||||
</OverlayFrame>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(PageReloadOverlay));
|
||||
@@ -1,4 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayFrame } from './OverlayFrame';
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
@@ -1,24 +0,0 @@
|
||||
// @flow
|
||||
|
||||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
|
||||
|
||||
/**
|
||||
* The React {@code Component} styles of the overlay feature.
|
||||
*/
|
||||
export default {
|
||||
/**
|
||||
* Style for a backdrop overlay covering the screen the the overlay is
|
||||
* rendered.
|
||||
*/
|
||||
container: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: BaseTheme.palette.ui00
|
||||
},
|
||||
|
||||
safeContainer: {
|
||||
flex: 1
|
||||
}
|
||||
};
|
||||
@@ -7,15 +7,15 @@ import type { Dispatch } from 'redux';
|
||||
import {
|
||||
createPageReloadScheduledEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { reloadNow } from '../../app/actions';
|
||||
} from '../../../analytics';
|
||||
import { reloadNow } from '../../../app/actions';
|
||||
import {
|
||||
isFatalJitsiConferenceError,
|
||||
isFatalJitsiConnectionError
|
||||
} from '../../base/lib-jitsi-meet/functions';
|
||||
import logger from '../logger';
|
||||
} from '../../../base/lib-jitsi-meet/functions';
|
||||
import logger from '../../logger';
|
||||
|
||||
import ReloadButton from './web/ReloadButton';
|
||||
import ReloadButton from './ReloadButton';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
@@ -91,6 +91,7 @@ type State = {
|
||||
*/
|
||||
export default class AbstractPageReloadOverlay<P: Props>
|
||||
extends Component<P, State> {
|
||||
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
@@ -100,34 +101,18 @@ export default class AbstractPageReloadOverlay<P: Props>
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
// FIXME web does not rely on the 'recoverable' flag set on an error
|
||||
// action, but on a predefined list of fatal errors. Because of that
|
||||
// the value of 'fatalError' which relies on the flag should not be used
|
||||
// on web yet (until conference/connection and their errors handling is
|
||||
// not unified).
|
||||
return typeof APP === 'undefined'
|
||||
? Boolean(state['features/overlay'].fatalError)
|
||||
: this.needsRenderWeb(state);
|
||||
}
|
||||
const { error: conferenceError } = state['features/base/conference'];
|
||||
const { error: configError } = state['features/base/config'];
|
||||
const { error: connectionError } = state['features/base/connection'];
|
||||
|
||||
/**
|
||||
* Determines whether this overlay needs to be rendered (according to a
|
||||
* specific redux state). Called by {@link OverlayContainer}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {boolean} - If this overlay needs to be rendered, {@code true};
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRenderWeb(state: Object) {
|
||||
const conferenceError = state['features/base/conference'].error;
|
||||
const configError = state['features/base/config'].error;
|
||||
const connectionError = state['features/base/connection'].error;
|
||||
const jitsiConnectionError
|
||||
|
||||
return (
|
||||
(connectionError && isFatalJitsiConnectionError(connectionError))
|
||||
|| (conferenceError
|
||||
&& isFatalJitsiConferenceError(conferenceError))
|
||||
|| configError);
|
||||
// @ts-ignore
|
||||
= connectionError && isFatalJitsiConnectionError(connectionError);
|
||||
const jitsiConferenceError
|
||||
= conferenceError && isFatalJitsiConferenceError(conferenceError);
|
||||
|
||||
return jitsiConnectionError || jitsiConferenceError || configError;
|
||||
}
|
||||
|
||||
_interval: ?IntervalID;
|
||||
@@ -28,6 +28,6 @@ export default class AbstractSuspendedOverlay extends Component<Props> {
|
||||
* {@code false}, otherwise.
|
||||
*/
|
||||
static needsRender(state: Object) {
|
||||
return state['features/power-monitor'].suspendDetected;
|
||||
return state['features/power-monitor']?.suspendDetected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { connect } from '../../base/redux';
|
||||
import { getOverlayToRender } from '../functions';
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
import { getOverlayToRender } from '../../functions.web';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The type of the React {@link Component} props of {@code OverlayContainer}.
|
||||
@@ -16,8 +14,8 @@ type Props = {
|
||||
* The React {@link Component} type of overlay to be rendered by the
|
||||
* associated {@code OverlayContainer}.
|
||||
*/
|
||||
overlay: ?React$ComponentType<*>
|
||||
}
|
||||
overlay: any;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} that will display the correct overlay
|
||||
@@ -48,7 +46,7 @@ class OverlayContainer extends Component<Props> {
|
||||
* overlay: ?Object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
/**
|
||||
* The React {@link Component} type of overlay to be rendered by the
|
||||
@@ -1,8 +1,4 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
import React, { Component, ReactChildren } from 'react';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link OverlayFrame}.
|
||||
@@ -12,18 +8,18 @@ type Props = {
|
||||
/**
|
||||
* The children components to be displayed into the overlay frame.
|
||||
*/
|
||||
children: React$Node,
|
||||
children: ReactChildren;
|
||||
|
||||
/**
|
||||
* Indicates the css style of the overlay. If true, then lighter; darker,
|
||||
* otherwise.
|
||||
*/
|
||||
isLightOverlay?: boolean,
|
||||
isLightOverlay?: boolean;
|
||||
|
||||
/**
|
||||
* The style property.
|
||||
*/
|
||||
style: Object
|
||||
style: Object;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { connect } from '../../../base/redux/functions';
|
||||
|
||||
import AbstractPageReloadOverlay, {
|
||||
type Props,
|
||||
abstractMapStateToProps
|
||||
} from '../AbstractPageReloadOverlay';
|
||||
|
||||
} from './AbstractPageReloadOverlay';
|
||||
import OverlayFrame from './OverlayFrame';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// @flow
|
||||
|
||||
export { default as OverlayFrame } from './OverlayFrame';
|
||||
|
||||
export { default as PageReloadOverlay } from './PageReloadOverlay';
|
||||
export { default as SuspendedOverlay } from './SuspendedOverlay';
|
||||
export { default as UserMediaPermissionsOverlay } from './UserMediaPermissionsOverlay';
|
||||
@@ -1,7 +1,13 @@
|
||||
/* eslint-disable lines-around-comment */
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
|
||||
import { getOverlays } from './overlays';
|
||||
|
||||
// @ts-ignore
|
||||
import PageReloadOverlay from './components/web/PageReloadOverlay';
|
||||
// @ts-ignore
|
||||
import SuspendedOverlay from './components/web/SuspendedOverlay';
|
||||
// @ts-ignore
|
||||
import UserMediaPermissionsOverlay from './components/web/UserMediaPermissionsOverlay';
|
||||
/**
|
||||
* Returns the overlay to be currently rendered.
|
||||
*
|
||||
@@ -9,7 +15,13 @@ import { getOverlays } from './overlays';
|
||||
* @returns {?React$ComponentType<*>}
|
||||
*/
|
||||
export function getOverlayToRender(state: IReduxState) {
|
||||
for (const overlay of getOverlays()) {
|
||||
const overlays = [
|
||||
PageReloadOverlay,
|
||||
SuspendedOverlay,
|
||||
UserMediaPermissionsOverlay
|
||||
];
|
||||
|
||||
for (const overlay of overlays) {
|
||||
// react-i18n / react-redux wrap components and thus we cannot access
|
||||
// the wrapped component's static methods directly.
|
||||
// @ts-ignore
|
||||
@@ -22,13 +34,3 @@ export function getOverlayToRender(state: IReduxState) {
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility of the media permissions prompt.
|
||||
*
|
||||
* @param {IReduxState} state - The Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getMediaPermissionPromptVisibility(state: IReduxState) {
|
||||
return state['features/overlay'].isMediaPermissionPromptVisible;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user