mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-09 00:52:38 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6890414bad | ||
|
|
23550d377e | ||
|
|
205822ac31 | ||
|
|
7f7200b599 | ||
|
|
ade5290013 | ||
|
|
beffdb1e9b | ||
|
|
cd68a97b95 | ||
|
|
56887eb2fa | ||
|
|
47d72c64c1 | ||
|
|
e09949be9f | ||
|
|
d3e8856896 | ||
|
|
96efa7759b | ||
|
|
20d140a3ce | ||
|
|
c7b0028652 | ||
|
|
40377634f2 | ||
|
|
b869e53713 | ||
|
|
6f0fe06ba6 | ||
|
|
4f2346aac9 |
@@ -1416,7 +1416,12 @@ export default {
|
||||
_turnScreenSharingOff(didHaveVideo, wasVideoMuted) {
|
||||
this._untoggleScreenSharing = null;
|
||||
this.videoSwitchInProgress = true;
|
||||
APP.remoteControl.receiver.stop();
|
||||
const { receiver } = APP.remoteControl;
|
||||
|
||||
if (receiver) {
|
||||
receiver.stop();
|
||||
}
|
||||
|
||||
let promise = null;
|
||||
|
||||
if (didHaveVideo) {
|
||||
@@ -2171,6 +2176,8 @@ export default {
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
cameraDeviceId => {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
sendAnalyticsEvent('settings.changeDevice.video');
|
||||
createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
@@ -2178,7 +2185,9 @@ export default {
|
||||
micDeviceId: null
|
||||
})
|
||||
.then(([ stream ]) => {
|
||||
if (this.isAudioOnly()) {
|
||||
// if we are in audio only mode or video was muted before
|
||||
// changing device, then mute
|
||||
if (this.isAudioOnly() || videoWasMuted) {
|
||||
return stream.mute()
|
||||
.then(() => stream);
|
||||
}
|
||||
@@ -2186,7 +2195,14 @@ export default {
|
||||
return stream;
|
||||
})
|
||||
.then(stream => {
|
||||
this.useVideoStream(stream);
|
||||
// if we are screen sharing we do not want to stop it
|
||||
if (this.isSharingScreen) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.then(() => {
|
||||
logger.log('switched local video device');
|
||||
APP.settings.setCameraDeviceId(cameraDeviceId, true);
|
||||
})
|
||||
@@ -2199,6 +2215,8 @@ export default {
|
||||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_DEVICE_CHANGED,
|
||||
micDeviceId => {
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
|
||||
sendAnalyticsEvent(
|
||||
'settings.changeDevice.audioIn');
|
||||
createLocalTracksF({
|
||||
@@ -2207,6 +2225,16 @@ export default {
|
||||
micDeviceId
|
||||
})
|
||||
.then(([ stream ]) => {
|
||||
// if audio was muted before changing the device, mute
|
||||
// with the new device
|
||||
if (audioWasMuted) {
|
||||
return stream.mute()
|
||||
.then(() => stream);
|
||||
}
|
||||
|
||||
return stream;
|
||||
})
|
||||
.then(stream => {
|
||||
this.useAudioStream(stream);
|
||||
logger.log('switched local audio device');
|
||||
APP.settings.setMicDeviceId(micDeviceId, true);
|
||||
@@ -2403,10 +2431,6 @@ export default {
|
||||
const promises = [];
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
const availableAudioInputDevices
|
||||
= mediaDeviceHelper.getDevicesFromListByKind(devices, 'audioinput');
|
||||
const availableVideoInputDevices
|
||||
= mediaDeviceHelper.getDevicesFromListByKind(devices, 'videoinput');
|
||||
|
||||
if (typeof newDevices.audiooutput !== 'undefined') {
|
||||
// Just ignore any errors in catch block.
|
||||
@@ -2425,9 +2449,7 @@ export default {
|
||||
.then(() => {
|
||||
// If audio was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new audio track.
|
||||
if (audioWasMuted
|
||||
|| currentDevices.audioinput.length
|
||||
> availableAudioInputDevices.length) {
|
||||
if (audioWasMuted) {
|
||||
sendAnalyticsEvent('deviceListChanged.audio.muted');
|
||||
logger.log('Audio mute: device list changed');
|
||||
muteLocalAudio(true);
|
||||
@@ -2435,10 +2457,7 @@ export default {
|
||||
|
||||
// If video was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new video track.
|
||||
if (!this.isSharingScreen
|
||||
&& (videoWasMuted
|
||||
|| currentDevices.videoinput.length
|
||||
> availableVideoInputDevices.length)) {
|
||||
if (!this.isSharingScreen && videoWasMuted) {
|
||||
sendAnalyticsEvent('deviceListChanged.video.muted');
|
||||
logger.log('Video mute: device list changed');
|
||||
muteLocalVideo(true);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
%connection-info {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: $modalTextColor;
|
||||
|
||||
td {
|
||||
padding: 2px 0;
|
||||
@@ -34,7 +33,6 @@
|
||||
&__download
|
||||
{
|
||||
@extend .connection-info__icon;
|
||||
color: $downloadConnectionIconColor;
|
||||
}
|
||||
|
||||
&__status
|
||||
@@ -45,7 +43,6 @@
|
||||
&__upload
|
||||
{
|
||||
@extend .connection-info__icon;
|
||||
color: $uploadConnectionIconColor;
|
||||
}
|
||||
|
||||
.showmore {
|
||||
|
||||
@@ -86,26 +86,16 @@
|
||||
width: 100%;
|
||||
|
||||
& button {
|
||||
background: $toolbarBackground;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
& > * {
|
||||
color: $toolbarButtonColor;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Making sure any svg-s in an invite button group will be
|
||||
* colored the way we want.
|
||||
*/
|
||||
& path {
|
||||
fill: $toolbarButtonColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
// Link Appearance
|
||||
&__link,
|
||||
&__contents {
|
||||
color: $modalTextColor;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
@@ -30,7 +29,6 @@
|
||||
padding: 0 5px;
|
||||
|
||||
&.disabled {
|
||||
color: gray !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
* Common toolbar styles.
|
||||
*/
|
||||
.toolbar {
|
||||
color: $modalTextColor;
|
||||
height: 100%;
|
||||
pointer-events: auto;
|
||||
position: relative;
|
||||
@@ -82,20 +81,21 @@
|
||||
vertical-align: middle;
|
||||
width: $defaultToolbarSize;
|
||||
|
||||
&_hangup {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&_hangup, &_hangup:hover {
|
||||
color: $hangupColor;
|
||||
font-size: $hangupFontSize;
|
||||
}
|
||||
|
||||
&:not(.toggled) {
|
||||
&:hover, &:active {
|
||||
// sum opacity with background layer should give us 0.8
|
||||
|
||||
@@ -64,6 +64,7 @@ $connectionIndicatorBg: #165ecc;
|
||||
$audioLevelShadow: rgba(9, 36, 77, 0.9);
|
||||
$videoStateIndicatorColor: $defaultColor;
|
||||
$videoStateIndicatorBackground: $toolbarBackground;
|
||||
$videoStateIndicatorSize: 40px;
|
||||
|
||||
/**
|
||||
* Feedback Modal
|
||||
|
||||
@@ -65,6 +65,15 @@
|
||||
float: left;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to overwrite the background for the top toolbar dark theme div
|
||||
* wrapper needed before we're able to move all top toolbar indicators
|
||||
* creation to react.
|
||||
*/
|
||||
.ckAJgx {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
|
||||
@@ -26,6 +26,13 @@
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
&-spinner {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-picker-source {
|
||||
|
||||
@@ -26,17 +26,6 @@
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.dial-in-numbers-trigger {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
.dial-in-numbers-trigger-icon {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.is-disabled,
|
||||
.is-loading {
|
||||
.dial-in-numbers-trigger-icon {
|
||||
@@ -62,6 +51,10 @@
|
||||
&__input-container {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
.dropdown-button-trigger {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
.video-quality-dialog {
|
||||
color: $modalTextColor;
|
||||
|
||||
.hide-warning {
|
||||
height: 0;
|
||||
@@ -12,7 +11,6 @@
|
||||
|
||||
.video-quality-dialog-contents {
|
||||
align-items: center;
|
||||
color: $modalTextColor;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
@@ -29,7 +27,7 @@
|
||||
@mixin sliderTrackStyles() {
|
||||
height: 15px;
|
||||
border-radius: 10px;
|
||||
background: black;
|
||||
background: #0E1624;
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
@@ -79,7 +77,6 @@
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
&::before {
|
||||
background: rgb(140, 156, 189);
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
left: 0;
|
||||
@@ -94,10 +91,10 @@
|
||||
}
|
||||
|
||||
.video-quality-dialog-label-container.active {
|
||||
color: $toolbarToggleBackground;
|
||||
color: $videoQualityActive;
|
||||
|
||||
&::before {
|
||||
background: $toolbarToggleBackground;
|
||||
background: $videoQualityActive;
|
||||
height: 12px;
|
||||
top: -19px;
|
||||
width: 12px;
|
||||
@@ -117,26 +114,26 @@
|
||||
|
||||
.video-state-indicator {
|
||||
background: $videoStateIndicatorBackground;
|
||||
color: $videoStateIndicatorColor;
|
||||
cursor: default;
|
||||
font-size: 13px;
|
||||
height: 40px;
|
||||
height: $videoStateIndicatorSize;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
min-width: 40px;
|
||||
min-width: $videoStateIndicatorSize;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
|
||||
i {
|
||||
cursor: pointer;
|
||||
line-height: $videoStateIndicatorSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the label padding so it has more volume and can be easily clicked.
|
||||
*/
|
||||
.video-quality-label-status {
|
||||
padding: 10px 5px;
|
||||
line-height: $videoStateIndicatorSize;
|
||||
min-width: $videoStateIndicatorSize;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,12 +44,6 @@ $defaultBackground: #474747;
|
||||
$filmstripOnlyOverlayBg: #000;
|
||||
$reloadProgressBarBg: #0074E0;
|
||||
|
||||
/**
|
||||
* Connection indicator
|
||||
**/
|
||||
$downloadConnectionIconColor: #4abd04;
|
||||
$uploadConnectionIconColor: #ffa800;
|
||||
|
||||
/**
|
||||
* Dialog colors
|
||||
**/
|
||||
@@ -111,3 +105,8 @@ $selectFontColor: $controlColor;
|
||||
$selectBg: $controlBackground;
|
||||
$selectActiveBg: darken($controlBackground, 5%);
|
||||
$selectActiveItemBg: darken($controlBackground, 20%);
|
||||
|
||||
/**
|
||||
* TODO: Replace by themed component.
|
||||
*/
|
||||
$videoQualityActive: #4C9AFF;
|
||||
|
||||
@@ -128,7 +128,7 @@ var interfaceConfig = {
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
CONNECTION_INDICATOR_AUTO_HIDE_ENABLED: false,
|
||||
CONNECTION_INDICATOR_AUTO_HIDE_ENABLED: true,
|
||||
|
||||
/**
|
||||
* How long the connection indicator should remain displayed before hiding.
|
||||
|
||||
@@ -61,29 +61,44 @@ function LoginDialog(successCallback, cancelCallback) {
|
||||
finishedButtons.push(cancelButton());
|
||||
}
|
||||
|
||||
const connDialog = APP.UI.messageHandler.openDialogWithStates(
|
||||
states, // eslint-disable-line no-use-before-define
|
||||
{
|
||||
persistent: true,
|
||||
closeText: ''
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
const states = {
|
||||
connecting: {
|
||||
buttons: [],
|
||||
defaultButton: 0,
|
||||
html: '<div id="connectionStatus"></div>',
|
||||
titleKey: 'dialog.connecting'
|
||||
},
|
||||
finished: {
|
||||
buttons: finishedButtons,
|
||||
defaultButton: 0,
|
||||
html: '<div id="errorMessage"></div>',
|
||||
titleKey: 'dialog.error',
|
||||
|
||||
submit(e, v) {
|
||||
e.preventDefault();
|
||||
if (v === 'retry') {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
connDialog.goToState('login');
|
||||
} else {
|
||||
// User cancelled
|
||||
cancelCallback();
|
||||
}
|
||||
}
|
||||
},
|
||||
login: {
|
||||
titleKey: 'dialog.passwordRequired',
|
||||
html: getPasswordInputHtml(),
|
||||
buttons: loginButtons,
|
||||
focus: ':input:first',
|
||||
// eslint-disable-next-line max-params
|
||||
submit(e, v, m, f) {
|
||||
html: getPasswordInputHtml(),
|
||||
titleKey: 'dialog.passwordRequired',
|
||||
|
||||
submit(e, v, m, f) { // eslint-disable-line max-params
|
||||
e.preventDefault();
|
||||
if (v) {
|
||||
const jid = f.username;
|
||||
const password = f.password;
|
||||
|
||||
if (jid && password) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
connDialog.goToState('connecting');
|
||||
successCallback(toJid(jid, config.hosts), password);
|
||||
}
|
||||
@@ -92,29 +107,16 @@ function LoginDialog(successCallback, cancelCallback) {
|
||||
cancelCallback();
|
||||
}
|
||||
}
|
||||
},
|
||||
connecting: {
|
||||
titleKey: 'dialog.connecting',
|
||||
html: '<div id="connectionStatus"></div>',
|
||||
buttons: [],
|
||||
defaultButton: 0
|
||||
},
|
||||
finished: {
|
||||
titleKey: 'dialog.error',
|
||||
html: '<div id="errorMessage"></div>',
|
||||
buttons: finishedButtons,
|
||||
defaultButton: 0,
|
||||
submit(e, v) {
|
||||
e.preventDefault();
|
||||
if (v === 'retry') {
|
||||
connDialog.goToState('login');
|
||||
} else {
|
||||
// User cancelled
|
||||
cancelCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const connDialog = APP.UI.messageHandler.openDialogWithStates(
|
||||
states,
|
||||
{
|
||||
closeText: '',
|
||||
persistent: true
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
/**
|
||||
* Displays error message in 'finished' state which allows either to cancel
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import {
|
||||
@@ -167,15 +168,18 @@ RemoteVideo.prototype._generatePopupContent = function() {
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<RemoteVideoMenuTriggerButton
|
||||
initialVolumeValue = { initialVolumeValue }
|
||||
isAudioMuted = { this.isAudioMuted }
|
||||
isModerator = { isModerator }
|
||||
onMenuDisplay = {this._onRemoteVideoMenuDisplay.bind(this)}
|
||||
onRemoteControlToggle = { onRemoteControlToggle }
|
||||
onVolumeChange = { onVolumeChange }
|
||||
participantID = { participantID }
|
||||
remoteControlState = { remoteControlState } />
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<RemoteVideoMenuTriggerButton
|
||||
initialVolumeValue = { initialVolumeValue }
|
||||
isAudioMuted = { this.isAudioMuted }
|
||||
isModerator = { isModerator }
|
||||
onMenuDisplay
|
||||
= {this._onRemoteVideoMenuDisplay.bind(this)}
|
||||
onRemoteControlToggle = { onRemoteControlToggle }
|
||||
onVolumeChange = { onVolumeChange }
|
||||
participantID = { participantID }
|
||||
remoteControlState = { remoteControlState } />
|
||||
</AtlasKitThemeProvider>
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
remoteVideoMenuContainer);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
@@ -780,30 +781,34 @@ SmallVideo.prototype.updateIndicators = function() {
|
||||
const tooltipPosition = interfaceConfig.VERTICAL_FILMSTRIP ? 'left' : 'top';
|
||||
|
||||
ReactDOM.render(
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<div>
|
||||
{ this._showConnectionIndicator
|
||||
? <ConnectionIndicator
|
||||
alwaysVisible = { showConnectionIndicator }
|
||||
connectionStatus = { this._connectionStatus }
|
||||
iconSize = { iconSize }
|
||||
isLocalVideo = { this.isLocal }
|
||||
enableStatsDisplay = { !interfaceConfig.filmStripOnly }
|
||||
statsPopoverPosition = { this.statsPopoverLocation }
|
||||
userID = { this.id } />
|
||||
: null }
|
||||
{ this._showRaisedHand
|
||||
? <RaisedHandIndicator
|
||||
iconSize = { iconSize }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
: null }
|
||||
{ this._showDominantSpeaker
|
||||
? <DominantSpeakerIndicator
|
||||
iconSize = { iconSize }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
: null }
|
||||
</div>
|
||||
</I18nextProvider>,
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<div>
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
{ this._showConnectionIndicator
|
||||
? <ConnectionIndicator
|
||||
alwaysVisible = { showConnectionIndicator }
|
||||
connectionStatus = { this._connectionStatus }
|
||||
iconSize = { iconSize }
|
||||
isLocalVideo = { this.isLocal }
|
||||
enableStatsDisplay
|
||||
= { !interfaceConfig.filmStripOnly }
|
||||
statsPopoverPosition
|
||||
= { this.statsPopoverLocation }
|
||||
userID = { this.id } />
|
||||
: null }
|
||||
{ this._showRaisedHand
|
||||
? <RaisedHandIndicator
|
||||
iconSize = { iconSize }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
: null }
|
||||
{ this._showDominantSpeaker
|
||||
? <DominantSpeakerIndicator
|
||||
iconSize = { iconSize }
|
||||
tooltipPosition = { tooltipPosition } />
|
||||
: null }
|
||||
</AtlasKitThemeProvider>
|
||||
</div>
|
||||
</I18nextProvider>,
|
||||
indicatorToolbar
|
||||
);
|
||||
};
|
||||
|
||||
31
package.json
31
package.json
@@ -15,20 +15,23 @@
|
||||
"author": "",
|
||||
"readmeFilename": "README.md",
|
||||
"dependencies": {
|
||||
"@atlaskit/avatar": "4.0.5",
|
||||
"@atlaskit/button": "3.0.0",
|
||||
"@atlaskit/button-group": "1.1.3",
|
||||
"@atlaskit/dropdown-menu": "1.4.0",
|
||||
"@atlaskit/field-text": "2.7.0",
|
||||
"@atlaskit/flag": "3.0.0",
|
||||
"@atlaskit/icon": "7.0.0",
|
||||
"@atlaskit/inline-dialog": "3.2.0",
|
||||
"@atlaskit/inline-message": "2.1.1",
|
||||
"@atlaskit/modal-dialog": "2.1.2",
|
||||
"@atlaskit/multi-select": "6.2.0",
|
||||
"@atlaskit/spinner": "2.2.3",
|
||||
"@atlaskit/tabs": "2.0.0",
|
||||
"@atlaskit/toggle": "2.0.4",
|
||||
"@atlaskit/avatar": "8.0.5",
|
||||
"@atlaskit/button": "5.4.2",
|
||||
"@atlaskit/button-group": "1.5.3",
|
||||
"@atlaskit/dropdown-menu": "3.10.2",
|
||||
"@atlaskit/droplist": "4.11.1",
|
||||
"@atlaskit/field-text": "4.0.1",
|
||||
"@atlaskit/flag": "6.1.0",
|
||||
"@atlaskit/icon": "10.0.0",
|
||||
"@atlaskit/inline-dialog": "5.0.2",
|
||||
"@atlaskit/inline-message": "3.0.1",
|
||||
"@atlaskit/modal-dialog": "2.6.0",
|
||||
"@atlaskit/multi-select": "7.1.3",
|
||||
"@atlaskit/spinner": "4.0.0",
|
||||
"@atlaskit/tabs": "4.0.1",
|
||||
"@atlaskit/theme": "2.2.0",
|
||||
"@atlaskit/toggle": "2.6.1",
|
||||
"@atlaskit/tooltip": "6.0.0",
|
||||
"@atlassian/aui": "6.0.6",
|
||||
"autosize": "1.18.13",
|
||||
"es6-iterator": "2.0.1",
|
||||
|
||||
@@ -8,7 +8,14 @@ import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
export const sendAnalyticsEvent = analytics.sendEvent.bind(analytics);
|
||||
/**
|
||||
* Sends an analytics event.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
export function sendAnalyticsEvent(...args: Array<any>) {
|
||||
analytics.sendEvent(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the analytics scripts and inits JitsiMeetJS.analytics by setting
|
||||
|
||||
@@ -201,9 +201,7 @@ export class AbstractApp extends Component {
|
||||
return (
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<Provider store = { this._getStore() }>
|
||||
{
|
||||
this._createElement(component)
|
||||
}
|
||||
{ this._createElement(component) }
|
||||
</Provider>
|
||||
</I18nextProvider>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import React from 'react';
|
||||
|
||||
import { getLocationContextRoot } from '../../base/util';
|
||||
import '../../room-lock';
|
||||
|
||||
@@ -37,6 +40,20 @@ export class App extends AbstractApp {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent method to inject {@link AtlasKitThemeProvider} as
|
||||
* the top most component.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_createElement(component, props) {
|
||||
return (
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
{ super._createElement(component, props) }
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a Location object from the window with information about the current
|
||||
* location of the document.
|
||||
|
||||
@@ -64,14 +64,27 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_FAILED:
|
||||
if (action.error.name
|
||||
=== JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
|
||||
case CONFERENCE_FAILED: {
|
||||
const { error } = action;
|
||||
|
||||
// XXX The feature authentication affords recovery from
|
||||
// CONFERENCE_FAILED caused by
|
||||
// JitsiConferenceErrors.AUTHENTICATION_REQUIRED.
|
||||
let recoverable;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
recoverable = error.recoverable;
|
||||
}
|
||||
if (recoverable) {
|
||||
store.dispatch(waitForOwner());
|
||||
} else {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
if (_isWaitingForOwner(store)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import AKButton from '@atlaskit/button';
|
||||
import AKButtonGroup from '@atlaskit/button-group';
|
||||
import ModalDialog from '@atlaskit/modal-dialog';
|
||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
@@ -114,25 +115,32 @@ class StatelessDialog extends Component {
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onKeyDown = { this._onKeyDown }
|
||||
ref = { this._setDialogElement }>
|
||||
<ModalDialog
|
||||
footer = { this._renderFooter() }
|
||||
header = { this._renderHeader() }
|
||||
isOpen = { true }
|
||||
onDialogDismissed = { this._onDialogDismissed }
|
||||
width = { this.props.width || 'medium' }>
|
||||
<div>
|
||||
<form
|
||||
className = 'modal-dialog-form'
|
||||
id = 'modal-dialog-form'
|
||||
onSubmit = { this._onSubmit }>
|
||||
{ this.props.children }
|
||||
</form>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
|
||||
/**
|
||||
* Enabled light theme for dialogs until all in-dialog components
|
||||
* support dark theme.
|
||||
*/
|
||||
<AtlasKitThemeProvider mode = 'light'>
|
||||
<div
|
||||
onKeyDown = { this._onKeyDown }
|
||||
ref = { this._setDialogElement }>
|
||||
<ModalDialog
|
||||
footer = { this._renderFooter() }
|
||||
header = { this._renderHeader() }
|
||||
isOpen = { true }
|
||||
onDialogDismissed = { this._onDialogDismissed }
|
||||
width = { this.props.width || 'medium' }>
|
||||
<div>
|
||||
<form
|
||||
className = 'modal-dialog-form'
|
||||
id = 'modal-dialog-form'
|
||||
onSubmit = { this._onSubmit }>
|
||||
{ this.props.children }
|
||||
</form>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</AtlasKitThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${
|
||||
isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
return dispatch(setMuted());
|
||||
return dispatch(setMuted(isMuted));
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* Action to remove known DesktopCapturerSources.
|
||||
*
|
||||
* {
|
||||
* type: RESET_DESKTOP_SOURCES,
|
||||
* }
|
||||
*/
|
||||
export const RESET_DESKTOP_SOURCES = Symbol('RESET_DESKTOP_SOURCES');
|
||||
|
||||
/**
|
||||
* Action to replace stored DesktopCapturerSources with new sources.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_DESKTOP_SOURCES,
|
||||
* sources: {Array}
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_DESKTOP_SOURCES = Symbol('UPDATE_DESKTOP_SOURCES');
|
||||
@@ -1,64 +1,7 @@
|
||||
import { openDialog } from '../base/dialog';
|
||||
|
||||
import {
|
||||
RESET_DESKTOP_SOURCES,
|
||||
UPDATE_DESKTOP_SOURCES
|
||||
} from './actionTypes';
|
||||
import { DesktopPicker } from './components';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Begins a request to get available DesktopCapturerSources.
|
||||
*
|
||||
* @param {Array} types - An array with DesktopCapturerSource type strings.
|
||||
* @param {Object} options - Additional configuration for getting a list of
|
||||
* sources.
|
||||
* @param {Object} options.thumbnailSize - The desired height and width of the
|
||||
* return native image object used for the preview image of the source.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function obtainDesktopSources(types, options = {}) {
|
||||
const capturerOptions = {
|
||||
types
|
||||
};
|
||||
|
||||
if (options.thumbnailSize) {
|
||||
capturerOptions.thumbnailSize = options.thumbnailSize;
|
||||
}
|
||||
|
||||
return dispatch => {
|
||||
const { JitsiMeetElectron } = window;
|
||||
|
||||
if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) {
|
||||
JitsiMeetElectron.obtainDesktopStreams(
|
||||
sources => dispatch(updateDesktopSources(sources)),
|
||||
error =>
|
||||
logger.error(
|
||||
`Error while obtaining desktop sources: ${error}`),
|
||||
capturerOptions
|
||||
);
|
||||
} else {
|
||||
logger.error(
|
||||
'Called JitsiMeetElectron.obtainDesktopStreams'
|
||||
+ ' but it is not defined');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to remove all stored DesktopCapturerSources.
|
||||
*
|
||||
* @returns {{
|
||||
* type: RESET_DESKTOP_SOURCES
|
||||
* }}
|
||||
*/
|
||||
export function resetDesktopSources() {
|
||||
return {
|
||||
type: RESET_DESKTOP_SOURCES
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to open a dialog with the DesktopPicker component.
|
||||
*
|
||||
@@ -67,25 +10,11 @@ export function resetDesktopSources() {
|
||||
* a DesktopCapturerSource has been chosen.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showDesktopPicker(options, onSourceChoose) {
|
||||
export function showDesktopPicker(options = {}, onSourceChoose) {
|
||||
const { desktopSharingSources } = options;
|
||||
|
||||
return openDialog(DesktopPicker, {
|
||||
options,
|
||||
desktopSharingSources,
|
||||
onSourceChoose
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals new DesktopCapturerSources have been received.
|
||||
*
|
||||
* @param {Object} sources - Arrays with DesktopCapturerSources.
|
||||
* @returns {{
|
||||
* type: UPDATE_DESKTOP_SOURCES,
|
||||
* sources: Array
|
||||
* }}
|
||||
*/
|
||||
export function updateDesktopSources(sources) {
|
||||
return {
|
||||
type: UPDATE_DESKTOP_SOURCES,
|
||||
sources
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,22 +8,23 @@ import { connect } from 'react-redux';
|
||||
import { Dialog, hideDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { obtainDesktopSources, resetDesktopSources } from '../actions';
|
||||
import DesktopPickerPane from './DesktopPickerPane';
|
||||
import { obtainDesktopSources } from '../functions';
|
||||
|
||||
const THUMBNAIL_SIZE = {
|
||||
height: 300,
|
||||
width: 300
|
||||
};
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const UPDATE_INTERVAL = 2000;
|
||||
|
||||
type TabConfiguration = {
|
||||
defaultSelected?: boolean,
|
||||
label: string,
|
||||
type: string
|
||||
label: string
|
||||
};
|
||||
const TAB_CONFIGURATIONS: Array<TabConfiguration> = [
|
||||
{
|
||||
|
||||
const TAB_CONFIGURATIONS: { [type: string]: TabConfiguration} = {
|
||||
screen: {
|
||||
/**
|
||||
* The indicator which determines whether this tab configuration is
|
||||
* selected by default.
|
||||
@@ -31,15 +32,14 @@ const TAB_CONFIGURATIONS: Array<TabConfiguration> = [
|
||||
* @type {boolean}
|
||||
*/
|
||||
defaultSelected: true,
|
||||
label: 'dialog.yourEntireScreen',
|
||||
type: 'screen'
|
||||
label: 'dialog.yourEntireScreen'
|
||||
},
|
||||
{
|
||||
label: 'dialog.applicationWindow',
|
||||
type: 'window'
|
||||
window: {
|
||||
label: 'dialog.applicationWindow'
|
||||
}
|
||||
];
|
||||
const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type);
|
||||
};
|
||||
|
||||
const VALID_TYPES = Object.keys(TAB_CONFIGURATIONS);
|
||||
|
||||
/**
|
||||
* React component for DesktopPicker.
|
||||
@@ -47,21 +47,18 @@ const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type);
|
||||
* @extends Component
|
||||
*/
|
||||
class DesktopPicker extends Component {
|
||||
/**
|
||||
* Default values for DesktopPicker component's properties.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
options: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* DesktopPicker component's property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* An array with desktop sharing sources to be displayed.
|
||||
*/
|
||||
desktopSharingSources: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
/**
|
||||
* Used to request DesktopCapturerSources.
|
||||
*/
|
||||
@@ -73,17 +70,6 @@ class DesktopPicker extends Component {
|
||||
*/
|
||||
onSourceChoose: PropTypes.func,
|
||||
|
||||
/**
|
||||
* An object with options related to desktop sharing.
|
||||
*/
|
||||
options: PropTypes.object,
|
||||
|
||||
/**
|
||||
* An object with arrays of DesktopCapturerSources. The key should be
|
||||
* the source type.
|
||||
*/
|
||||
sources: PropTypes.object,
|
||||
|
||||
/**
|
||||
* Used to obtain translations.
|
||||
*/
|
||||
@@ -94,8 +80,8 @@ class DesktopPicker extends Component {
|
||||
|
||||
state = {
|
||||
selectedSource: {},
|
||||
tabsToPopulate: [],
|
||||
typesToFetch: []
|
||||
sources: {},
|
||||
types: []
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,20 +98,18 @@ class DesktopPicker extends Component {
|
||||
this._onPreviewClick = this._onPreviewClick.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._updateSources = this._updateSources.bind(this);
|
||||
|
||||
this.state.types
|
||||
= this._getValidTypes(this.props.desktopSharingSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an immediate update request for DesktopCapturerSources and begin
|
||||
* requesting updates at an interval.
|
||||
* Starts polling.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillMount() {
|
||||
const { desktopSharingSources } = this.props.options;
|
||||
|
||||
this._onSourceTypesConfigChanged(
|
||||
desktopSharingSources);
|
||||
this._updateSources();
|
||||
componentDidMount() {
|
||||
this._startPolling();
|
||||
}
|
||||
|
||||
@@ -139,20 +123,19 @@ class DesktopPicker extends Component {
|
||||
* @returns {void}
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.state.selectedSource.id
|
||||
&& nextProps.sources.screen.length) {
|
||||
const { desktopSharingSources } = nextProps;
|
||||
|
||||
/**
|
||||
* Do only reference check in order to not calculate the types on every
|
||||
* update. This is enough for our use case and we don't need value
|
||||
* checking because if the value is the same we won't change the
|
||||
* reference for the desktopSharingSources array.
|
||||
*/
|
||||
if (desktopSharingSources !== this.props.desktopSharingSources) {
|
||||
this.setState({
|
||||
selectedSource: {
|
||||
id: nextProps.sources.screen[0].id,
|
||||
type: 'screen'
|
||||
}
|
||||
types: this._getValidTypes(desktopSharingSources)
|
||||
});
|
||||
}
|
||||
|
||||
const { desktopSharingSources } = this.props.options;
|
||||
|
||||
this._onSourceTypesConfigChanged(
|
||||
desktopSharingSources);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +145,6 @@ class DesktopPicker extends Component {
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
this._stopPolling();
|
||||
this.props.dispatch(resetDesktopSources());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,6 +156,7 @@ class DesktopPicker extends Component {
|
||||
return (
|
||||
<Dialog
|
||||
isModal = { false }
|
||||
okDisabled = { Boolean(!this.state.selectedSource.id) }
|
||||
okTitleKey = 'dialog.Share'
|
||||
onCancel = { this._onCloseModal }
|
||||
onSubmit = { this._onSubmit }
|
||||
@@ -220,22 +203,14 @@ class DesktopPicker extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changing of allowed desktop sharing source types.
|
||||
* Extracts only the valid types from the passed {@code types}.
|
||||
*
|
||||
* @param {Array<string>} desktopSharingSourceTypes - The types that will be
|
||||
* fetched and displayed.
|
||||
* @returns {void}
|
||||
* @param {Array<string>} types - The types to filter.
|
||||
* @returns {Array<string>} The filtered types.
|
||||
*/
|
||||
_onSourceTypesConfigChanged(desktopSharingSourceTypes = []) {
|
||||
const tabsToPopulate
|
||||
= TAB_CONFIGURATIONS.filter(({ type }) =>
|
||||
desktopSharingSourceTypes.includes(type)
|
||||
&& VALID_TYPES.includes(type));
|
||||
|
||||
this.setState({
|
||||
tabsToPopulate,
|
||||
typesToFetch: tabsToPopulate.map(c => c.type)
|
||||
});
|
||||
_getValidTypes(types = []) {
|
||||
return types.filter(
|
||||
type => VALID_TYPES.includes(type));
|
||||
}
|
||||
|
||||
_onSubmit: () => void;
|
||||
@@ -259,18 +234,20 @@ class DesktopPicker extends Component {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderTabs() {
|
||||
const { selectedSource } = this.state;
|
||||
const { sources, t } = this.props;
|
||||
const { selectedSource, sources, types } = this.state;
|
||||
const { t } = this.props;
|
||||
const tabs
|
||||
= this.state.tabsToPopulate.map(
|
||||
({ defaultSelected, label, type }) => {
|
||||
= types.map(
|
||||
type => {
|
||||
const { defaultSelected, label } = TAB_CONFIGURATIONS[type];
|
||||
|
||||
return {
|
||||
content: <DesktopPickerPane
|
||||
key = { type }
|
||||
onClick = { this._onPreviewClick }
|
||||
onDoubleClick = { this._onCloseModal }
|
||||
selectedSourceId = { selectedSource.id }
|
||||
sources = { sources[type] || [] }
|
||||
sources = { sources[type] }
|
||||
type = { type } />,
|
||||
defaultSelected,
|
||||
label: t(label)
|
||||
@@ -288,6 +265,7 @@ class DesktopPicker extends Component {
|
||||
*/
|
||||
_startPolling() {
|
||||
this._stopPolling();
|
||||
this._updateSources();
|
||||
this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
@@ -311,28 +289,35 @@ class DesktopPicker extends Component {
|
||||
* @returns {void}
|
||||
*/
|
||||
_updateSources() {
|
||||
this.props.dispatch(obtainDesktopSources(
|
||||
this.state.typesToFetch,
|
||||
{
|
||||
THUMBNAIL_SIZE
|
||||
}
|
||||
));
|
||||
const { types } = this.state;
|
||||
|
||||
if (types.length > 0) {
|
||||
obtainDesktopSources(
|
||||
this.state.types,
|
||||
{ thumbnailSize: THUMBNAIL_SIZE }
|
||||
)
|
||||
.then(sources => {
|
||||
const nextState: Object = {
|
||||
sources
|
||||
};
|
||||
|
||||
// FIXME: selectedSource when screen is disabled, when the
|
||||
// source has been removed or when the selectedTab is changed!!!
|
||||
if (!this.state.selectedSource.id
|
||||
&& sources.screen.length > 0) {
|
||||
nextState.selectedSource = {
|
||||
id: sources.screen[0].id,
|
||||
type: 'screen'
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Maybe check if we have stopped the timer and unmounted
|
||||
// the component.
|
||||
this.setState(nextState);
|
||||
})
|
||||
.catch(() => { /* ignore */ });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated DesktopPicker's props.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* sources: Object
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
return {
|
||||
sources: state['features/desktop-picker']
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(DesktopPicker));
|
||||
export default translate(connect()(DesktopPicker));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
@@ -60,7 +61,7 @@ class DesktopPickerPane extends Component {
|
||||
const classNames
|
||||
= `desktop-picker-pane default-scrollbar source-type-${type}`;
|
||||
const previews
|
||||
= sources.map(
|
||||
= sources ? sources.map(
|
||||
source =>
|
||||
|
||||
// eslint-disable-next-line react/jsx-wrap-multilines
|
||||
@@ -70,7 +71,14 @@ class DesktopPickerPane extends Component {
|
||||
onDoubleClick = { onDoubleClick }
|
||||
selected = { source.id === selectedSourceId }
|
||||
source = { source }
|
||||
type = { type } />);
|
||||
type = { type } />)
|
||||
: ( // eslint-disable-line no-extra-parens
|
||||
<div className = 'desktop-picker-pane-spinner'>
|
||||
<Spinner
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className = { classNames }>
|
||||
|
||||
73
react/features/desktop-picker/functions.js
Normal file
73
react/features/desktop-picker/functions.js
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Begins a request to get available DesktopCapturerSources.
|
||||
*
|
||||
* @param {Array} types - An array with DesktopCapturerSource type strings.
|
||||
* @param {Object} options - Additional configuration for getting a list of
|
||||
* sources.
|
||||
* @param {Object} options.thumbnailSize - The desired height and width of the
|
||||
* return native image object used for the preview image of the source.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function obtainDesktopSources(types, options = {}) {
|
||||
const capturerOptions = {
|
||||
types
|
||||
};
|
||||
|
||||
if (options.thumbnailSize) {
|
||||
capturerOptions.thumbnailSize = options.thumbnailSize;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const { JitsiMeetElectron } = window;
|
||||
|
||||
if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) {
|
||||
JitsiMeetElectron.obtainDesktopStreams(
|
||||
sources => resolve(_seperateSourcesByType(sources)),
|
||||
error => {
|
||||
logger.error(
|
||||
`Error while obtaining desktop sources: ${error}`);
|
||||
reject(error);
|
||||
},
|
||||
capturerOptions
|
||||
);
|
||||
} else {
|
||||
const reason = 'Called JitsiMeetElectron.obtainDesktopStreams'
|
||||
+ ' but it is not defined';
|
||||
|
||||
logger.error(reason);
|
||||
|
||||
return Promise.reject(new Error(reason));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts an array of DesktopCapturerSources to an object with types for keys
|
||||
* and values being an array with sources of the key's type.
|
||||
*
|
||||
* @param {Array} sources - DesktopCapturerSources.
|
||||
* @private
|
||||
* @returns {Object} An object with the sources split into seperate arrays based
|
||||
* on source type.
|
||||
*/
|
||||
function _seperateSourcesByType(sources = []) {
|
||||
const sourcesByType = {
|
||||
screen: [],
|
||||
window: []
|
||||
};
|
||||
|
||||
sources.forEach(source => {
|
||||
const idParts = source.id.split(':');
|
||||
const type = idParts[0];
|
||||
|
||||
if (sourcesByType[type]) {
|
||||
sourcesByType[type].push(source);
|
||||
}
|
||||
});
|
||||
|
||||
return sourcesByType;
|
||||
}
|
||||
@@ -1,5 +1,2 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
|
||||
import './reducer';
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
RESET_DESKTOP_SOURCES,
|
||||
UPDATE_DESKTOP_SOURCES
|
||||
} from './actionTypes';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
screen: [],
|
||||
window: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions that mutate the known available DesktopCapturerSources.
|
||||
*
|
||||
* @param {Object[]} state - Current state.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @param {Array} action.sources - DesktopCapturerSources.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register(
|
||||
'features/desktop-picker',
|
||||
(state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case RESET_DESKTOP_SOURCES:
|
||||
return { ...DEFAULT_STATE };
|
||||
|
||||
case UPDATE_DESKTOP_SOURCES:
|
||||
return _seperateSourcesByType(action.sources);
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Converts an array of DesktopCapturerSources to an object with types for keys
|
||||
* and values being an array with sources of the key's type.
|
||||
*
|
||||
* @param {Array} sources - DesktopCapturerSources.
|
||||
* @private
|
||||
* @returns {Object} An object with the sources split into seperate arrays based
|
||||
* on source type.
|
||||
*/
|
||||
function _seperateSourcesByType(sources = []) {
|
||||
const sourcesByType = {
|
||||
screen: [],
|
||||
window: []
|
||||
};
|
||||
|
||||
sources.forEach(source => {
|
||||
const idParts = source.id.split(':');
|
||||
const type = idParts[0];
|
||||
|
||||
if (sourcesByType[type]) {
|
||||
sourcesByType[type].push(source);
|
||||
}
|
||||
});
|
||||
|
||||
return sourcesByType;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import AKDropdownMenu from '@atlaskit/dropdown-menu';
|
||||
import ExpandIcon from '@atlaskit/icon/glyph/expand';
|
||||
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
@@ -116,7 +116,7 @@ class DeviceSelector extends Component {
|
||||
<span className = 'device-selector-trigger-text'>
|
||||
{ triggerText }
|
||||
</span>
|
||||
<ExpandIcon
|
||||
<ChevronDownIcon
|
||||
label = 'expand'
|
||||
size = 'large' />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { StatelessDropdownMenu } from '@atlaskit/dropdown-menu';
|
||||
import AKFieldText, { FieldText } from '@atlaskit/field-text';
|
||||
import ExpandIcon from '@atlaskit/icon/glyph/expand';
|
||||
import { DropdownMenuStateless as DropdownMenu } from '@atlaskit/dropdown-menu';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -149,7 +149,7 @@ class DialOutNumbersForm extends Component {
|
||||
{ _dialOutCodes ? this._createDropdownMenu(
|
||||
this._formatCountryCodes(_dialOutCodes)) : null }
|
||||
<div className = 'dial-out-input'>
|
||||
<AKFieldText
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
isLabelHidden = { true }
|
||||
label = { 'dial-out-input-field' }
|
||||
@@ -164,7 +164,7 @@ class DialOutNumbersForm extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code StatelessDropdownMenu} instance.
|
||||
* Creates a {@code DropdownMenu} instance.
|
||||
*
|
||||
* @param {Array} items - The content to display within the dropdown.
|
||||
* @returns {ReactElement}
|
||||
@@ -174,14 +174,14 @@ class DialOutNumbersForm extends Component {
|
||||
|
||||
return (
|
||||
<div className = 'dropdown-container'>
|
||||
<StatelessDropdownMenu
|
||||
<DropdownMenu
|
||||
isOpen = { this.state.isDropdownOpen }
|
||||
items = { [ { items } ] }
|
||||
onItemActivated = { this._onSelect }
|
||||
onOpenChange = { this._onOpenChange }
|
||||
shouldFitContainer = { false }>
|
||||
{ this._createDropdownTrigger(dialCode, code) }
|
||||
</StatelessDropdownMenu>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -203,10 +203,10 @@ class DialOutNumbersForm extends Component {
|
||||
className = 'dial-out-flag-icon'
|
||||
countryCode = { `${countryCode}` } />
|
||||
{ /**
|
||||
* FIXME Replace FieldText with AtlasKit Button when an issue
|
||||
* FIXME Replace TextField with AtlasKit Button when an issue
|
||||
* with icons shrinking due to button text is fixed.
|
||||
*/ }
|
||||
<FieldText
|
||||
<TextField
|
||||
className = 'input-control dial-out-code'
|
||||
isLabelHidden = { true }
|
||||
isReadOnly = { true }
|
||||
@@ -215,9 +215,9 @@ class DialOutNumbersForm extends Component {
|
||||
type = 'text'
|
||||
value = { dialCode || '' } />
|
||||
<span className = 'dropdown-trigger-icon'>
|
||||
<ExpandIcon
|
||||
<ChevronDownIcon
|
||||
label = 'expand'
|
||||
size = 'medium' />
|
||||
size = 'small' />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -225,7 +225,7 @@ class DialOutNumbersForm extends Component {
|
||||
|
||||
/**
|
||||
* Transforms the passed in numbers object into an array of objects that can
|
||||
* be parsed by {@code StatelessDropdownMenu}.
|
||||
* be parsed by {@code DropdownMenu}.
|
||||
*
|
||||
* @param {Object} countryCodes - The list of country codes.
|
||||
* @private
|
||||
@@ -259,8 +259,8 @@ class DialOutNumbersForm extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op function used to stub out FieldText's onChange in order
|
||||
* to prevent FieldText from printing prop type validation errors. FieldText
|
||||
* This is a no-op function used to stub out TextField's onChange in order
|
||||
* to prevent TextField from printing prop type validation errors. TextField
|
||||
* is used as a trigger for the dropdown in {@code DialOutNumbersForm} to
|
||||
* get the desired AtlasKit input look for the UI.
|
||||
*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AKFieldText from '@atlaskit/field-text';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
@@ -76,7 +76,7 @@ class DisplayNamePrompt extends Component {
|
||||
onSubmit = { this._onSubmit }
|
||||
titleKey = 'dialog.displayNameRequired'
|
||||
width = 'small'>
|
||||
<AKFieldText
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
label = { this.props.t('dialog.enterDisplayName') }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Button from '@atlaskit/button';
|
||||
import { FieldText } from '@atlaskit/field-text';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -106,7 +106,7 @@ class AddPasswordForm extends Component {
|
||||
onSubmit = { this._onSubmit } >
|
||||
<div className = 'form-control__container'>
|
||||
<div className = 'form-control__input-container'>
|
||||
<FieldText
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
id = 'newPasswordInput'
|
||||
@@ -123,7 +123,6 @@ class AddPasswordForm extends Component {
|
||||
id = 'addPasswordBtn'
|
||||
isDisabled = { !this.state.password }
|
||||
onClick = { this._onSubmit }
|
||||
shouldFitContainer = { true }
|
||||
type = 'button'>
|
||||
{ t('dialog.add') }
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Button from '@atlaskit/button';
|
||||
import { StatelessDropdownMenu } from '@atlaskit/dropdown-menu';
|
||||
import { FieldText } from '@atlaskit/field-text';
|
||||
import ExpandIcon from '@atlaskit/icon/glyph/expand';
|
||||
import DropdownMenu, {
|
||||
DropdownItem, DropdownItemGroup } from '@atlaskit/dropdown-menu';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -95,8 +94,6 @@ class DialInNumbersForm extends Component {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onCopyClick = this._onCopyClick.bind(this);
|
||||
this._onDropdownTriggerInputChange
|
||||
= this._onDropdownTriggerInputChange.bind(this);
|
||||
this._onOpenChange = this._onOpenChange.bind(this);
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._setCopyElement = this._setCopyElement.bind(this);
|
||||
@@ -147,7 +144,7 @@ class DialInNumbersForm extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const items = this._formatNumbers(numbers);
|
||||
const items = this._renderDropdownItems(numbers);
|
||||
|
||||
return (
|
||||
<div className = 'form-control dial-in-numbers'>
|
||||
@@ -158,11 +155,12 @@ class DialInNumbersForm extends Component {
|
||||
</span>
|
||||
</label>
|
||||
<div className = 'form-control__container'>
|
||||
{ this._createDropdownMenu(items, selectedNumber.content) }
|
||||
<div className = 'form-control__input-container'>
|
||||
{ this._createDropdownMenu(items, selectedNumber) }
|
||||
</div>
|
||||
<Button
|
||||
appearance = 'default'
|
||||
onClick = { this._onCopyClick }
|
||||
shouldFitContainer = { true }
|
||||
type = 'button'>
|
||||
{ t('dialog.copy') }
|
||||
</Button>
|
||||
@@ -178,7 +176,7 @@ class DialInNumbersForm extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code StatelessDropdownMenu} instance.
|
||||
* Creates a {@code DropdownMenu} instance.
|
||||
*
|
||||
* @param {Array} items - The content to display within the dropdown.
|
||||
* @param {string} triggerText - The text to display within the
|
||||
@@ -187,116 +185,32 @@ class DialInNumbersForm extends Component {
|
||||
*/
|
||||
_createDropdownMenu(items, triggerText) {
|
||||
return (
|
||||
<StatelessDropdownMenu
|
||||
<DropdownMenu
|
||||
isOpen = { this.state.isDropdownOpen }
|
||||
items = { [ { items } ] }
|
||||
onItemActivated = { this._onSelect }
|
||||
onOpenChange = { this._onOpenChange }
|
||||
shouldFitContainer = { true }>
|
||||
{ this._createDropdownTrigger(triggerText) }
|
||||
</StatelessDropdownMenu>
|
||||
shouldFitContainer = { true }
|
||||
trigger = { triggerText || '' }
|
||||
triggerButtonProps = {{
|
||||
className: 'dropdown-button-trigger',
|
||||
shouldFitContainer: true }}
|
||||
triggerType = 'button'>
|
||||
<DropdownItemGroup>
|
||||
{ items }
|
||||
</DropdownItemGroup>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a React {@code Component} with a readonly HTMLInputElement as a
|
||||
* trigger for displaying the dropdown menu. The {@code Component} will also
|
||||
* display the currently selected number.
|
||||
* Formats the region and number string.
|
||||
*
|
||||
* @param {string} triggerText - Text to display in the HTMLInputElement.
|
||||
* @param {string} region - The region string.
|
||||
* @param {string} number - The number string.
|
||||
* @returns {string} - The new formatted string.
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_createDropdownTrigger(triggerText) {
|
||||
return (
|
||||
<div className = 'dial-in-numbers-trigger'>
|
||||
<div className = 'form-control__input-container'>
|
||||
<FieldText
|
||||
compact = { true }
|
||||
isLabelHidden = { true }
|
||||
isReadOnly = { true }
|
||||
label = 'Select Dial-In Number'
|
||||
onChange = { this._onDropdownTriggerInputChange }
|
||||
ref = { this._setInput }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { triggerText || '' } />
|
||||
</div>
|
||||
<span className = 'dial-in-numbers-trigger-icon'>
|
||||
<ExpandIcon
|
||||
label = 'expand'
|
||||
size = 'medium' />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether the response from dialInNumbersUrl returned an array or
|
||||
* an object with dial-in numbers and calls the appropriate method to
|
||||
* transform the numbers into the format expected by
|
||||
* {@code StatelessDropdownMenu}.
|
||||
*
|
||||
* @param {Array<string>|Object} dialInNumbers - The numbers returned from
|
||||
* requesting dialInNumbersUrl.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_formatNumbers(dialInNumbers) {
|
||||
if (Array.isArray(dialInNumbers)) {
|
||||
return this._formatNumbersArray(dialInNumbers);
|
||||
}
|
||||
|
||||
return this._formatNumbersObject(dialInNumbers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the passed in numbers array into an array of objects that can
|
||||
* be parsed by {@code StatelessDropdownMenu}.
|
||||
*
|
||||
* @param {Array<string>} dialInNumbers - An array with dial-in numbers to
|
||||
* display and copy.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_formatNumbersArray(dialInNumbers) {
|
||||
return dialInNumbers.map(number => {
|
||||
return {
|
||||
content: number,
|
||||
number
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the passed in numbers object into an array of objects that can
|
||||
* be parsed by {@code StatelessDropdownMenu}.
|
||||
*
|
||||
* @param {Object} dialInNumbers - The numbers object to parse. The
|
||||
* expected format is an object with keys being the name of the country
|
||||
* and the values being an array of numbers as strings.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_formatNumbersObject(dialInNumbers) {
|
||||
const phoneRegions = Object.keys(dialInNumbers);
|
||||
|
||||
if (!phoneRegions.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const formattedNumbers = phoneRegions.map(region => {
|
||||
const numbers = dialInNumbers[region];
|
||||
|
||||
return numbers.map(number => {
|
||||
return {
|
||||
content: `${region}: ${number}`,
|
||||
number
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Array.prototype.concat(...formattedNumbers);
|
||||
_formatRegionNumber(region, number) {
|
||||
return `${region}: ${number}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,7 +227,7 @@ class DialInNumbersForm extends Component {
|
||||
});
|
||||
|
||||
const callNumber = t('invite.callNumber', {
|
||||
number: this.state.selectedNumber.number
|
||||
number: this.state.selectedNumber
|
||||
});
|
||||
const stepOne = `1) ${callNumber}`;
|
||||
|
||||
@@ -343,18 +257,6 @@ class DialInNumbersForm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op function used to stub out FieldText's onChange in order
|
||||
* to prevent FieldText from printing prop type validation errors. FieldText
|
||||
* is used as a trigger for the dropdown in {@code DialInNumbersForm} to
|
||||
* get the desired AtlasKit input look for the UI.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onDropdownTriggerInputChange() {
|
||||
// Intentionally left empty.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal state to either open or close the dropdown. If the
|
||||
* dropdown is disabled, the state will always be set to false.
|
||||
@@ -380,10 +282,71 @@ class DialInNumbersForm extends Component {
|
||||
_onSelect(selection) {
|
||||
this.setState({
|
||||
isDropdownOpen: false,
|
||||
selectedNumber: selection.item
|
||||
selectedNumber: selection
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a DropDownItem for the given id and text.
|
||||
*
|
||||
* @param {string} id - The key identifier of the DropdownItem.
|
||||
* @param {string} text - The text to display in the dropdown item.
|
||||
* @returns {React.Component}
|
||||
* @private
|
||||
*/
|
||||
_renderDropDownItem(id, text) {
|
||||
return (
|
||||
|
||||
/**
|
||||
* Arrow functions are not allowed in props, but I leave this until
|
||||
* I figure a better way to implement the same thing.
|
||||
*/
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
<DropdownItem
|
||||
key = { id }
|
||||
onClick = { () => this._onSelect(text || id) }>
|
||||
{ text }
|
||||
</DropdownItem>
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether the response from dialInNumbersUrl returned an array or
|
||||
* an object with dial-in numbers and calls the appropriate method to
|
||||
* transform the numbers into the format expected by
|
||||
* {@code DropdownMenu}.
|
||||
*
|
||||
* @param {Array<string>|Object} dialInNumbers - The numbers returned from
|
||||
* requesting dialInNumbersUrl.
|
||||
* @private
|
||||
* @returns {Array<Object>}
|
||||
*/
|
||||
_renderDropdownItems(dialInNumbers) {
|
||||
if (Array.isArray(dialInNumbers)) {
|
||||
return dialInNumbers.map(number =>
|
||||
this._renderDropDownItem(number)
|
||||
);
|
||||
}
|
||||
|
||||
const phoneRegions = Object.keys(dialInNumbers);
|
||||
|
||||
if (!phoneRegions.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dropdownItems = phoneRegions.map(region => {
|
||||
const numbers = dialInNumbers[region];
|
||||
|
||||
return numbers.map(number =>
|
||||
this._renderDropDownItem(number,
|
||||
this._formatRegionNumber(region, number))
|
||||
);
|
||||
});
|
||||
|
||||
return Array.prototype.concat(...dropdownItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the DOM/HTML element backing the React
|
||||
* {@code Component} text area.
|
||||
@@ -406,10 +369,18 @@ class DialInNumbersForm extends Component {
|
||||
* @returns {void}
|
||||
*/
|
||||
_setDefaultNumber(dialInNumbers) {
|
||||
const numbers = this._formatNumbers(dialInNumbers);
|
||||
let number = '';
|
||||
|
||||
if (Array.isArray(dialInNumbers)) {
|
||||
number = dialInNumbers[0];
|
||||
} else if (Object.keys(dialInNumbers).length > 0) {
|
||||
const region = Object.keys(dialInNumbers)[0];
|
||||
|
||||
number = this._formatRegionNumber(region, dialInNumbers[region]);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedNumber: numbers[0]
|
||||
selectedNumber: number
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Button from '@atlaskit/button';
|
||||
import { FieldText } from '@atlaskit/field-text';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
@@ -71,8 +71,7 @@ class ShareLinkForm extends Component {
|
||||
</label>
|
||||
<div className = 'form-control__container'>
|
||||
<div className = 'form-control__input-container'>
|
||||
<FieldText
|
||||
compact = { true }
|
||||
<TextField
|
||||
id = 'inviteLinkRef'
|
||||
isLabelHidden = { true }
|
||||
isReadOnly = { true }
|
||||
@@ -86,7 +85,6 @@ class ShareLinkForm extends Component {
|
||||
<Button
|
||||
appearance = 'default'
|
||||
onClick = { this._onClick }
|
||||
shouldFitContainer = { true }
|
||||
type = 'button'>
|
||||
{ t('dialog.copy') }
|
||||
</Button>
|
||||
@@ -114,8 +112,8 @@ class ShareLinkForm extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a no-op function used to stub out FieldText's onChange in order
|
||||
* to prevent FieldText from printing prop type validation errors. FieldText
|
||||
* This is a no-op function used to stub out TextField's onChange in order
|
||||
* to prevent TextField from printing prop type validation errors. TextField
|
||||
* is used as a trigger for the dropdown in {@code ShareLinkForm} to get the
|
||||
* desired AtlasKit input look for the UI.
|
||||
*
|
||||
|
||||
@@ -135,10 +135,15 @@ function _appWillMount({ dispatch, getState }, next, action) {
|
||||
function _conferenceFailed(store, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
const { callUUID } = action.conference;
|
||||
// XXX Certain CONFERENCE_FAILED errors are recoverable i.e. they have
|
||||
// prevented the user from joining a specific conference but the app may be
|
||||
// able to eventually join the conference.
|
||||
if (!action.error.recoverable) {
|
||||
const { callUUID } = action.conference;
|
||||
|
||||
if (callUUID) {
|
||||
CallKit.reportCallFailed(callUUID);
|
||||
if (callUUID) {
|
||||
CallKit.reportCallFailed(callUUID);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// @flow
|
||||
|
||||
import AKFieldText from '@atlaskit/field-text';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
|
||||
import { setPassword } from '../../base/conference';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
@@ -75,7 +74,7 @@ class PasswordRequiredPrompt extends Component {
|
||||
_renderBody() {
|
||||
return (
|
||||
<div>
|
||||
<AKFieldText
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
compact = { true }
|
||||
label = { this.props.t('dialog.passwordLabel') }
|
||||
|
||||
@@ -29,6 +29,9 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
if (conference
|
||||
&& error.name === JitsiConferenceErrors.PASSWORD_REQUIRED) {
|
||||
// XXX The feature room-lock affords recovery after
|
||||
// CONFERENCE_FAILED caused by
|
||||
// JitsiConferenceErrors.PASSWORD_REQUIRED.
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/* @flow */
|
||||
|
||||
import AKInlineDialog from '@atlaskit/inline-dialog';
|
||||
import { Tooltip } from '@atlaskit/tooltip';
|
||||
import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import Tooltip from '@atlaskit/tooltip';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
import { TOOLTIP_TO_POPUP_POSITION } from '../constants';
|
||||
import { isButtonEnabled } from '../functions';
|
||||
import StatelessToolbarButton from './StatelessToolbarButton';
|
||||
|
||||
declare var APP: Object;
|
||||
@@ -24,20 +23,6 @@ class ToolbarButton extends Component {
|
||||
|
||||
_onClick: Function;
|
||||
|
||||
_onMouseOut: Function;
|
||||
|
||||
_onMouseOver: Function;
|
||||
|
||||
state: {
|
||||
|
||||
/**
|
||||
* Whether or not the tooltip for the button should be displayed.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
showTooltip: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar button component's property types.
|
||||
*
|
||||
@@ -81,14 +66,8 @@ class ToolbarButton extends Component {
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showTooltip: false
|
||||
};
|
||||
|
||||
// Bind methods to save the context
|
||||
this._onClick = this._onClick.bind(this);
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onMouseOver = this._onMouseOver.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,10 +113,7 @@ class ToolbarButton extends Component {
|
||||
const buttonComponent = ( // eslint-disable-line no-extra-parens
|
||||
<Tooltip
|
||||
description = { button.tooltipText || t(button.tooltipKey) }
|
||||
onMouseOut = { this._onMouseOut }
|
||||
onMouseOver = { this._onMouseOver }
|
||||
position = { tooltipPosition }
|
||||
visible = { this.state.showTooltip }>
|
||||
position = { tooltipPosition }>
|
||||
<StatelessToolbarButton { ...props }>
|
||||
{ this.props.children }
|
||||
</StatelessToolbarButton>
|
||||
@@ -151,12 +127,12 @@ class ToolbarButton extends Component {
|
||||
const { dataAttr, dataInterpolate, position } = popupConfig;
|
||||
|
||||
children = ( // eslint-disable-line no-extra-parens
|
||||
<AKInlineDialog
|
||||
<InlineDialog
|
||||
content = { t(dataAttr, dataInterpolate) }
|
||||
isOpen = { Boolean(popupConfig) }
|
||||
position = { position }>
|
||||
{ buttonComponent }
|
||||
</AKInlineDialog>
|
||||
</InlineDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,7 +152,6 @@ class ToolbarButton extends Component {
|
||||
*/
|
||||
_onClick(event) {
|
||||
this.props.onClick(event);
|
||||
this.setState({ showTooltip: false });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,31 +180,6 @@ class ToolbarButton extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides any displayed tooltip.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMouseOut(): void {
|
||||
this.setState({ showTooltip: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides any displayed tooltip.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMouseOver(): void {
|
||||
const { button } = this.props;
|
||||
|
||||
this.setState({
|
||||
showTooltip: isButtonEnabled(button.buttonName)
|
||||
&& !button.unclickable
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shortcut and tooltip for current toolbar button.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user