feat(new-toolbars): initial implementation
This commit is contained in:
parent
962df14382
commit
d93782af8a
|
@ -43,7 +43,8 @@ import {
|
||||||
lockStateChanged,
|
lockStateChanged,
|
||||||
onStartMutedPolicyChanged,
|
onStartMutedPolicyChanged,
|
||||||
p2pStatusChanged,
|
p2pStatusChanged,
|
||||||
sendLocalParticipant
|
sendLocalParticipant,
|
||||||
|
setDesktopSharingEnabled
|
||||||
} from './react/features/base/conference';
|
} from './react/features/base/conference';
|
||||||
import { updateDeviceList } from './react/features/base/devices';
|
import { updateDeviceList } from './react/features/base/devices';
|
||||||
import {
|
import {
|
||||||
|
@ -104,6 +105,7 @@ import {
|
||||||
mediaPermissionPromptVisibilityChanged,
|
mediaPermissionPromptVisibilityChanged,
|
||||||
suspendDetected
|
suspendDetected
|
||||||
} from './react/features/overlay';
|
} from './react/features/overlay';
|
||||||
|
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||||
import {
|
import {
|
||||||
isButtonEnabled,
|
isButtonEnabled,
|
||||||
showDesktopSharingButton
|
showDesktopSharingButton
|
||||||
|
@ -505,16 +507,6 @@ export default {
|
||||||
*/
|
*/
|
||||||
desktopSharingDisabledTooltip: null,
|
desktopSharingDisabledTooltip: null,
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the local "raisedHand" flag is on.
|
|
||||||
*/
|
|
||||||
isHandRaised: false,
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Whether the local participant is the dominant speaker in the conference.
|
|
||||||
*/
|
|
||||||
isDominantSpeaker: false,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The local audio track (if any).
|
* The local audio track (if any).
|
||||||
* FIXME tracks from redux store should be the single source of truth
|
* FIXME tracks from redux store should be the single source of truth
|
||||||
|
@ -773,6 +765,8 @@ export default {
|
||||||
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
|
||||||
this.isDesktopSharingEnabled);
|
this.isDesktopSharingEnabled);
|
||||||
|
|
||||||
|
APP.store.dispatch(
|
||||||
|
setDesktopSharingEnabled(this.isDesktopSharingEnabled));
|
||||||
APP.store.dispatch(showDesktopSharingButton());
|
APP.store.dispatch(showDesktopSharingButton());
|
||||||
|
|
||||||
this._createRoom(tracks);
|
this._createRoom(tracks);
|
||||||
|
@ -1896,19 +1890,6 @@ export default {
|
||||||
});
|
});
|
||||||
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
|
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
|
||||||
APP.store.dispatch(dominantSpeakerChanged(id));
|
APP.store.dispatch(dominantSpeakerChanged(id));
|
||||||
|
|
||||||
if (this.isLocalId(id)) {
|
|
||||||
this.isDominantSpeaker = true;
|
|
||||||
this.setRaisedHand(false);
|
|
||||||
} else {
|
|
||||||
this.isDominantSpeaker = false;
|
|
||||||
const participant = room.getParticipantById(id);
|
|
||||||
|
|
||||||
if (participant) {
|
|
||||||
APP.UI.setRaisedHandStatus(participant, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
APP.UI.markDominantSpeaker(id);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!interfaceConfig.filmStripOnly) {
|
if (!interfaceConfig.filmStripOnly) {
|
||||||
|
@ -2022,7 +2003,10 @@ export default {
|
||||||
(participant, name, oldValue, newValue) => {
|
(participant, name, oldValue, newValue) => {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'raisedHand':
|
case 'raisedHand':
|
||||||
APP.UI.setRaisedHandStatus(participant, newValue);
|
APP.store.dispatch(participantUpdated({
|
||||||
|
id: participant.getId(),
|
||||||
|
raisedHand: newValue === 'true'
|
||||||
|
}));
|
||||||
break;
|
break;
|
||||||
case 'remoteControlSessionStatus':
|
case 'remoteControlSessionStatus':
|
||||||
APP.UI.setRemoteControlActiveStatus(
|
APP.UI.setRemoteControlActiveStatus(
|
||||||
|
@ -2361,6 +2345,8 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
APP.store.dispatch(setSharedVideoStatus(state));
|
||||||
});
|
});
|
||||||
room.addCommandListener(
|
room.addCommandListener(
|
||||||
this.commands.defaults.SHARED_VIDEO,
|
this.commands.defaults.SHARED_VIDEO,
|
||||||
|
@ -2623,30 +2609,6 @@ export default {
|
||||||
APP.API.notifyVideoAvailabilityChanged(available);
|
APP.API.notifyVideoAvailabilityChanged(available);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the local "raised hand" status.
|
|
||||||
*/
|
|
||||||
maybeToggleRaisedHand() {
|
|
||||||
this.setRaisedHand(!this.isHandRaised);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the local "raised hand" status to a particular value.
|
|
||||||
*/
|
|
||||||
setRaisedHand(raisedHand) {
|
|
||||||
if (raisedHand !== this.isHandRaised) {
|
|
||||||
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
|
||||||
|
|
||||||
this.isHandRaised = raisedHand;
|
|
||||||
|
|
||||||
// Advertise the updated status
|
|
||||||
room.setLocalParticipantProperty('raisedHand', raisedHand);
|
|
||||||
|
|
||||||
// Update the view
|
|
||||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from the conference and optionally request user feedback.
|
* Disconnect from the conference and optionally request user feedback.
|
||||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||||
|
|
|
@ -5,6 +5,20 @@
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.use-new-toolbox {
|
||||||
|
.filmstrip.reduce-height {
|
||||||
|
bottom: $newToolbarSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filmstrip {
|
||||||
|
transition: bottom .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filmstrip__videos.hidden {
|
||||||
|
bottom: calc(-196px - #{$newToolbarSize});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.filmstrip {
|
.filmstrip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
@ -180,3 +180,6 @@
|
||||||
.icon-gsm-bars:before {
|
.icon-gsm-bars:before {
|
||||||
content: "\e926";
|
content: "\e926";
|
||||||
}
|
}
|
||||||
|
.icon-open_in_new:before {
|
||||||
|
content: "\e89e";
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,37 @@
|
||||||
/**
|
/**
|
||||||
* Toolbar side panel main container element.
|
* Toolbar side panel main container element.
|
||||||
*/
|
*/
|
||||||
|
.use-new-toolbox #sideToolbarContainer {
|
||||||
|
background-color: rgba(40, 52, 71, 0.5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||||
|
* the toolbar, plus its padding, and subtract from 100%.
|
||||||
|
*/
|
||||||
|
height: calc(100% - #{$newToolbarSize} - 10px);
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
.side-toolbar-close {
|
||||||
|
background: gray;
|
||||||
|
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 100%;
|
||||||
|
color: white;
|
||||||
|
cursor:pointer;
|
||||||
|
height: 10px;
|
||||||
|
line-height: 10px;
|
||||||
|
padding: 4px;
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
text-align: center;
|
||||||
|
top: 5px;
|
||||||
|
width: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chatconversation {
|
||||||
|
top: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
#sideToolbarContainer {
|
#sideToolbarContainer {
|
||||||
background-color: $sideToolbarContainerBg;
|
background-color: $sideToolbarContainerBg;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -261,6 +261,217 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
|
||||||
|
*/
|
||||||
|
.new-toolbox {
|
||||||
|
background-color: rgba(40, 52, 71, 0.5);
|
||||||
|
bottom: calc((#{$newToolbarSize} * 2) * -1);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 5px 20px;
|
||||||
|
position: absolute;
|
||||||
|
transition: bottom .3s ease-in;
|
||||||
|
width: 100%;
|
||||||
|
z-index: $toolbarZ;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-buttons {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group-center,
|
||||||
|
.button-group-left,
|
||||||
|
.button-group-right {
|
||||||
|
display: flex;
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite font-awesome styling to match jitsi-icon styling.
|
||||||
|
*/
|
||||||
|
.fa {
|
||||||
|
font-size: 1.22em;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
height: 100%;
|
||||||
|
line-height: inherit;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
background-color: rgba(40, 52, 71, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.toggled {
|
||||||
|
background: rgba(40, 52, 71, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.toggled:hover {
|
||||||
|
background-color: rgba(40, 52, 71, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.disabled {
|
||||||
|
cursor: initial
|
||||||
|
}
|
||||||
|
|
||||||
|
i.disabled:hover {
|
||||||
|
background-color: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-hangup {
|
||||||
|
color: $hangupColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-menu {
|
||||||
|
font-size: 1.2em;
|
||||||
|
list-style-type: none;
|
||||||
|
/**
|
||||||
|
* Undo atlaskit padding by reducing margins.
|
||||||
|
*/
|
||||||
|
margin: -15px -24px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.overflow-menu-item {
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unclickable {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
&.unclickable:hover {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-menu-item-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
background-color: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 18px;
|
||||||
|
max-height: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-text {
|
||||||
|
max-width: 150px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbox-button {
|
||||||
|
color: $toolbarButtonColor;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: $newToolbarFontSize;
|
||||||
|
line-height: $newToolbarSize;
|
||||||
|
margin: 0 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-button-with-badge {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.badge-round {
|
||||||
|
bottom: 9px;
|
||||||
|
position: absolute;
|
||||||
|
right: 9px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbox-button-wth-dialog {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbox-icon {
|
||||||
|
height: $newToolbarSize;
|
||||||
|
width: $newToolbarSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filmstrip-toolbox {
|
||||||
|
background-color: rgba(40, 52, 71, 0.5);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: $toolbarZ;
|
||||||
|
|
||||||
|
i {
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
font-size: $newToolbarFontSize;
|
||||||
|
height: 37px;
|
||||||
|
line-height: 37px;
|
||||||
|
width: 37px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i:hover {
|
||||||
|
background-color: rgba(40, 52, 71, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.toggled {
|
||||||
|
background: rgba(40, 52, 71, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
i.toggled:hover {
|
||||||
|
background-color: rgba(40, 52, 71, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-hangup {
|
||||||
|
color: $hangupColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbox-button {
|
||||||
|
color: $toolbarButtonColor;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
.toolbox-button:first-child i {
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbox-button:last-child i {
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
border-bottom-right-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.filmstrip-only {
|
.filmstrip-only {
|
||||||
.toolbox,
|
.toolbox,
|
||||||
.toolbox-toolbars {
|
.toolbox-toolbars {
|
||||||
|
|
|
@ -36,6 +36,8 @@ $alwaysOnTopToolbarFontSize: 1em;
|
||||||
$alwaysOnTopToolbarSize: 30px;
|
$alwaysOnTopToolbarSize: 30px;
|
||||||
$defaultToolbarSize: 50px;
|
$defaultToolbarSize: 50px;
|
||||||
$defaultFilmStripOnlyToolbarSize: 37px;
|
$defaultFilmStripOnlyToolbarSize: 37px;
|
||||||
|
$newToolbarSize: 50px;
|
||||||
|
$newToolbarFontSize: 1.9em;
|
||||||
$secToolbarFontSize: 1.9em;
|
$secToolbarFontSize: 1.9em;
|
||||||
$secToolbarLineHeight: 45px;
|
$secToolbarLineHeight: 45px;
|
||||||
$toolbarAvatarPadding: 10px;
|
$toolbarAvatarPadding: 10px;
|
||||||
|
|
|
@ -19,6 +19,20 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.use-new-toolbox {
|
||||||
|
/**
|
||||||
|
* Adjust the height of the filmstrip as the toolbar is displayed.
|
||||||
|
*/
|
||||||
|
.filmstrip {
|
||||||
|
top: 0;
|
||||||
|
transition: height .3s ease-in;
|
||||||
|
|
||||||
|
&.reduce-height {
|
||||||
|
height: calc(100% - #{$newToolbarSize});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.filmstrip {
|
.filmstrip {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -32,14 +46,7 @@
|
||||||
* any parent is also fixed.
|
* any parent is also fixed.
|
||||||
*/
|
*/
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
z-index: $filmstripVideosZ;
|
||||||
/**
|
|
||||||
* z-index adjusting is needed because the video state indicator has to
|
|
||||||
* display over the filmstrip when no videos are displayed but still be
|
|
||||||
* clickable but its inline dialogs must display over the video state
|
|
||||||
* indicator when videos are displayed.
|
|
||||||
*/
|
|
||||||
z-index: #{$tooltipsZ + 1};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide videos by making them slight to the right.
|
* Hide videos by making them slight to the right.
|
||||||
|
|
|
@ -135,6 +135,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-dialog-form {
|
||||||
|
.video-quality-dialog-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.video-state-indicator {
|
.video-state-indicator {
|
||||||
background: $videoStateIndicatorBackground;
|
background: $videoStateIndicatorBackground;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
@ -162,11 +168,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.centeredVideoLabel.moveToCorner {
|
.centeredVideoLabel.moveToCorner {
|
||||||
z-index: $tooltipsZ;
|
z-index: $zindex3;
|
||||||
}
|
}
|
||||||
|
|
||||||
#videoResolutionLabel {
|
#videoResolutionLabel {
|
||||||
z-index: #{$tooltipsZ + 1};
|
z-index: $zindex3 + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.centeredVideoLabel {
|
.centeredVideoLabel {
|
||||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -23,6 +23,7 @@
|
||||||
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
<glyph unicode="" glyph-name="event_note" d="M598 426v-84h-300v84h300zM810 214v468h-596v-468h596zM810 896c46 0 86-40 86-86v-596c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h42v86h86v-86h340v86h86v-86h42zM726 598v-86h-428v86h428z" />
|
||||||
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
<glyph unicode="" glyph-name="phone-talk" d="M640 512c0 70-58 128-128 128v86c118 0 214-96 214-214h-86zM810 512c0 166-132 298-298 298v86c212 0 384-172 384-384h-86zM854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24z" />
|
||||||
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
<glyph unicode="" glyph-name="public" d="M764 282c56 60 90 142 90 230 0 142-88 266-214 316v-18c0-46-40-84-86-84h-84v-86c0-24-20-42-44-42h-84v-86h256c24 0 42-18 42-42v-128h42c38 0 70-26 82-60zM470 174v82c-46 0-86 40-86 86v42l-204 204c-6-24-10-50-10-76 0-174 132-318 300-338zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||||
|
<glyph unicode="" glyph-name="open_in_new" d="M598 896h298v-298h-86v152l-418-418-60 60 418 418h-152v86zM810 214v298h86v-298c0-46-40-86-86-86h-596c-48 0-86 40-86 86v596c0 46 38 86 86 86h298v-86h-298v-596h596z" />
|
||||||
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
<glyph unicode="" glyph-name="restore" d="M512 682h64v-180l150-90-32-52-182 110v212zM554 896c212 0 384-172 384-384s-172-384-384-384c-106 0-200 42-270 112l60 62c54-54 128-88 210-88 166 0 300 132 300 298s-134 298-300 298-298-132-298-298h128l-172-172-4 6-166 166h128c0 212 172 384 384 384z" />
|
||||||
<glyph unicode="" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
<glyph unicode="" glyph-name="avatar" d="M512 204c106 0 200 56 256 138-2 84-172 132-256 132-86 0-254-48-256-132 56-82 150-138 256-138zM512 810c-70 0-128-58-128-128s58-128 128-128 128 58 128 128-58 128-128 128zM512 938c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
|
||||||
<glyph unicode="" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
|
<glyph unicode="" glyph-name="download" d="M726 470h-128v170h-172v-170h-128l214-214zM826 596c110-8 198-100 198-212 0-118-96-214-214-214h-554c-142 0-256 114-256 256 0 132 100 240 228 254 54 102 160 174 284 174 156 0 284-110 314-258z" />
|
||||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -44,7 +44,10 @@ var interfaceConfig = {
|
||||||
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
|
'microphone', 'camera', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup',
|
||||||
|
|
||||||
// extended toolbar
|
// extended toolbar
|
||||||
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip' ],
|
'profile', 'contacts', 'info', 'chat', 'recording', 'etherpad',
|
||||||
|
'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip',
|
||||||
|
'invite', 'feedback', 'stats', 'shortcuts'
|
||||||
|
],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Toolbar Buttons
|
* Main Toolbar Buttons
|
||||||
|
@ -150,7 +153,18 @@ var interfaceConfig = {
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
VIDEO_QUALITY_LABEL_DISABLED: false
|
VIDEO_QUALITY_LABEL_DISABLED: false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a temporary feature flag used to gate access to the toolbox so it
|
||||||
|
* can be developed through smaller changesets. This feature flag will be
|
||||||
|
* removed at some point, as well as the old toolbox. This new toolbox will
|
||||||
|
* be horizontal and support for horizontal filmstrip will be removed,
|
||||||
|
* except in the case of interfaceConfig.filmStripOnly being true.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
_USE_NEW_TOOLBOX: false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify custom URL for downloading android mobile app.
|
* Specify custom URL for downloading android mobile app.
|
||||||
|
|
|
@ -73,14 +73,23 @@
|
||||||
"toolbar": {
|
"toolbar": {
|
||||||
"addPeople": "Add people to your call",
|
"addPeople": "Add people to your call",
|
||||||
"audioonly": "Enable / Disable audio only mode (saves bandwidth)",
|
"audioonly": "Enable / Disable audio only mode (saves bandwidth)",
|
||||||
|
"callQuality": "Manage call quality",
|
||||||
|
"enterFullScreen": "View full screen",
|
||||||
|
"exitFullScreen": "Exit full screen",
|
||||||
|
"feedback": "Leave feedback",
|
||||||
|
"moreActions": "More actions",
|
||||||
"mute": "Mute / Unmute",
|
"mute": "Mute / Unmute",
|
||||||
"videomute": "Start / Stop camera",
|
"videomute": "Start / Stop camera",
|
||||||
"authenticate": "Authenticate",
|
"authenticate": "Authenticate",
|
||||||
"lock": "Lock / Unlock room",
|
"lock": "Lock / Unlock room",
|
||||||
"chat": "Open / Close chat",
|
"chat": "Open / Close chat",
|
||||||
"etherpad": "Open / Close shared document",
|
"etherpad": "Open / Close shared document",
|
||||||
|
"documentOpen": "Open shared document",
|
||||||
|
"documentClose": "Close shared document",
|
||||||
"sharedvideo": "Share a YouTube video",
|
"sharedvideo": "Share a YouTube video",
|
||||||
"sharescreen": "Start / Stop screen sharing",
|
"sharescreen": "Screen share",
|
||||||
|
"sharescreenDisabled": "Screen share disabled",
|
||||||
|
"stopSharedVideo": "Stop YouTube video",
|
||||||
"fullscreen": "View / Exit full screen",
|
"fullscreen": "View / Exit full screen",
|
||||||
"sip": "Call SIP number",
|
"sip": "Call SIP number",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
|
@ -96,7 +105,9 @@
|
||||||
"micDisabled": "Microphone is not available",
|
"micDisabled": "Microphone is not available",
|
||||||
"filmstrip": "Show / Hide videos",
|
"filmstrip": "Show / Hide videos",
|
||||||
"profile": "Edit your profile",
|
"profile": "Edit your profile",
|
||||||
"raiseHand": "Raise / Lower your hand"
|
"raiseHand": "Raise / Lower your hand",
|
||||||
|
"shortcuts": "View shortcuts",
|
||||||
|
"speakerStats": "Speaker stats"
|
||||||
},
|
},
|
||||||
"unsupportedBrowser": {
|
"unsupportedBrowser": {
|
||||||
"appNotInstalled": "Join this meeting with __app__ on your phone.",
|
"appNotInstalled": "Join this meeting with __app__ on your phone.",
|
||||||
|
@ -285,6 +296,7 @@
|
||||||
"liveStreaming": "Live Streaming",
|
"liveStreaming": "Live Streaming",
|
||||||
"streamKey": "Live stream key",
|
"streamKey": "Live stream key",
|
||||||
"startLiveStreaming": "Go live now",
|
"startLiveStreaming": "Go live now",
|
||||||
|
"startRecording": "Start recording",
|
||||||
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
|
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
|
||||||
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
|
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
|
||||||
"stopLiveStreaming": "Stop live streaming",
|
"stopLiveStreaming": "Stop live streaming",
|
||||||
|
@ -473,6 +485,7 @@
|
||||||
"loadingPeople": "Searching for people to invite",
|
"loadingPeople": "Searching for people to invite",
|
||||||
"noResults": "No matching search results",
|
"noResults": "No matching search results",
|
||||||
"noValidNumbers": "Please enter a phone number",
|
"noValidNumbers": "Please enter a phone number",
|
||||||
|
"notAvailable": "You can't invite people.",
|
||||||
"searchNumbers": "Enter a phone number to invite",
|
"searchNumbers": "Enter a phone number to invite",
|
||||||
"searchPeople": "Enter a name to invite",
|
"searchPeople": "Enter a name to invite",
|
||||||
"searchPeopleAndNumbers": "Enter a name or phone number to invite",
|
"searchPeopleAndNumbers": "Enter a name or phone number to invite",
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
} from '../../react/features/base/participants';
|
} from '../../react/features/base/participants';
|
||||||
import { destroyLocalTracks } from '../../react/features/base/tracks';
|
import { destroyLocalTracks } from '../../react/features/base/tracks';
|
||||||
import { openDisplayNamePrompt } from '../../react/features/display-name';
|
import { openDisplayNamePrompt } from '../../react/features/display-name';
|
||||||
|
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
|
||||||
import {
|
import {
|
||||||
setNotificationsEnabled,
|
setNotificationsEnabled,
|
||||||
showWarningNotification
|
showWarningNotification
|
||||||
|
@ -100,9 +101,6 @@ const UIListeners = new Map([
|
||||||
], [
|
], [
|
||||||
UIEvents.SHARED_VIDEO_CLICKED,
|
UIEvents.SHARED_VIDEO_CLICKED,
|
||||||
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
|
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
|
||||||
], [
|
|
||||||
UIEvents.TOGGLE_FULLSCREEN,
|
|
||||||
() => UI.toggleFullScreen()
|
|
||||||
], [
|
], [
|
||||||
UIEvents.TOGGLE_CHAT,
|
UIEvents.TOGGLE_CHAT,
|
||||||
() => UI.toggleChat()
|
() => UI.toggleChat()
|
||||||
|
@ -135,14 +133,6 @@ const UIListeners = new Map([
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the application in and out of full screen mode
|
|
||||||
* (a.k.a. presentation mode in Chrome).
|
|
||||||
*/
|
|
||||||
UI.toggleFullScreen = function() {
|
|
||||||
UIUtil.isFullScreen() ? UIUtil.exitFullScreen() : UIUtil.enterFullScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if we're currently in full screen mode.
|
* Indicates if we're currently in full screen mode.
|
||||||
*
|
*
|
||||||
|
@ -255,12 +245,20 @@ UI.showLocalConnectionInterrupted = function(isInterrupted) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the "raised hand" status for a participant.
|
* Sets the "raised hand" status for a participant.
|
||||||
|
*
|
||||||
|
* @param {string} id - The id of the participant whose raised hand UI should
|
||||||
|
* be updated.
|
||||||
|
* @param {string} name - The name of the participant with the raised hand
|
||||||
|
* update.
|
||||||
|
* @param {boolean} raisedHandStatus - Whether the participant's hand is raised
|
||||||
|
* or not.
|
||||||
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
|
UI.setRaisedHandStatus = (id, name, raisedHandStatus) => {
|
||||||
VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
|
VideoLayout.setRaisedHandStatus(id, raisedHandStatus);
|
||||||
if (raisedHandStatus) {
|
if (raisedHandStatus) {
|
||||||
messageHandler.participantNotification(
|
messageHandler.participantNotification(
|
||||||
participant.getDisplayName(),
|
name,
|
||||||
'notify.somebody',
|
'notify.somebody',
|
||||||
'connected',
|
'connected',
|
||||||
'notify.raisedHand');
|
'notify.raisedHand');
|
||||||
|
@ -374,6 +372,14 @@ UI.start = function() {
|
||||||
$('body').addClass('vertical-filmstrip');
|
$('body').addClass('vertical-filmstrip');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove this class once the old toolbar has been removed. This class
|
||||||
|
// is set so that any CSS changes needed to adjust elements outside of the
|
||||||
|
// new toolbar can be scoped to just the app with the new toolbar enabled.
|
||||||
|
if (interfaceConfig._USE_NEW_TOOLBOX && !interfaceConfig.filmStripOnly) {
|
||||||
|
$('body').addClass('use-new-toolbox');
|
||||||
|
}
|
||||||
|
|
||||||
document.title = interfaceConfig.APP_NAME;
|
document.title = interfaceConfig.APP_NAME;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -404,12 +410,7 @@ UI.bindEvents = () => {
|
||||||
// Resize and reposition videos in full screen mode.
|
// Resize and reposition videos in full screen mode.
|
||||||
$(document).on(
|
$(document).on(
|
||||||
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
|
'webkitfullscreenchange mozfullscreenchange fullscreenchange',
|
||||||
() => {
|
onResize);
|
||||||
eventEmitter.emit(
|
|
||||||
UIEvents.FULLSCREEN_TOGGLED,
|
|
||||||
UIUtil.isFullScreen());
|
|
||||||
onResize();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(window).resize(onResize);
|
$(window).resize(onResize);
|
||||||
};
|
};
|
||||||
|
@ -474,6 +475,7 @@ UI.initEtherpad = name => {
|
||||||
etherpadManager
|
etherpadManager
|
||||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||||
|
|
||||||
|
APP.store.dispatch(setEtherpadHasInitialzied());
|
||||||
APP.store.dispatch(showEtherpadButton());
|
APP.store.dispatch(showEtherpadButton());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
/* global $, interfaceConfig */
|
/* global $, APP, interfaceConfig */
|
||||||
|
|
||||||
|
import { setDocumentEditingState } from '../../../react/features/etherpad';
|
||||||
|
import { getToolboxHeight } from '../../../react/features/toolbox';
|
||||||
|
|
||||||
import VideoLayout from '../videolayout/VideoLayout';
|
import VideoLayout from '../videolayout/VideoLayout';
|
||||||
import LargeContainer from '../videolayout/LargeContainer';
|
import LargeContainer from '../videolayout/LargeContainer';
|
||||||
|
@ -126,7 +129,8 @@ class Etherpad extends LargeContainer {
|
||||||
let height, width;
|
let height, width;
|
||||||
|
|
||||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||||
height = containerHeight;
|
height = interfaceConfig._USE_NEW_TOOLBOX
|
||||||
|
? containerHeight - getToolboxHeight() : containerHeight;
|
||||||
width = containerWidth - Filmstrip.getFilmstripWidth();
|
width = containerWidth - Filmstrip.getFilmstripWidth();
|
||||||
} else {
|
} else {
|
||||||
height = containerHeight - Filmstrip.getFilmstripHeight();
|
height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||||
|
@ -242,5 +246,7 @@ export default class EtherpadManager {
|
||||||
|
|
||||||
this.eventEmitter
|
this.eventEmitter
|
||||||
.emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
|
.emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
|
||||||
|
|
||||||
|
APP.store.dispatch(setDocumentEditingState(!isVisible));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import {
|
||||||
StartLiveStreamDialog,
|
StartLiveStreamDialog,
|
||||||
StopLiveStreamDialog,
|
StopLiveStreamDialog,
|
||||||
hideRecordingLabel,
|
hideRecordingLabel,
|
||||||
|
setRecordingType,
|
||||||
updateRecordingState
|
updateRecordingState
|
||||||
} from '../../../react/features/recording';
|
} from '../../../react/features/recording';
|
||||||
|
|
||||||
|
@ -202,6 +203,8 @@ const Recording = {
|
||||||
this.eventEmitter = eventEmitter;
|
this.eventEmitter = eventEmitter;
|
||||||
this.recordingType = recordingType;
|
this.recordingType = recordingType;
|
||||||
|
|
||||||
|
APP.store.dispatch(setRecordingType(recordingType));
|
||||||
|
|
||||||
this.updateRecordingState(APP.conference.getRecordingState());
|
this.updateRecordingState(APP.conference.getRecordingState());
|
||||||
|
|
||||||
if (recordingType === 'jibri') {
|
if (recordingType === 'jibri') {
|
||||||
|
@ -219,6 +222,9 @@ const Recording = {
|
||||||
'#toolbar_button_record',
|
'#toolbar_button_record',
|
||||||
ev => this._onToolbarButtonClick(ev));
|
ev => this._onToolbarButtonClick(ev));
|
||||||
|
|
||||||
|
this.eventEmitter.on(UIEvents.TOGGLE_RECORDING,
|
||||||
|
() => this._onToolbarButtonClick());
|
||||||
|
|
||||||
// If I am a recorder then I publish my recorder custom role to notify
|
// If I am a recorder then I publish my recorder custom role to notify
|
||||||
// everyone.
|
// everyone.
|
||||||
if (config.iAmRecorder) {
|
if (config.iAmRecorder) {
|
||||||
|
@ -287,6 +293,7 @@ const Recording = {
|
||||||
this.currentState = recordingState;
|
this.currentState = recordingState;
|
||||||
|
|
||||||
let labelDisplayConfiguration;
|
let labelDisplayConfiguration;
|
||||||
|
let isRecording = false;
|
||||||
|
|
||||||
switch (recordingState) {
|
switch (recordingState) {
|
||||||
case JitsiRecordingStatus.ON:
|
case JitsiRecordingStatus.ON:
|
||||||
|
@ -298,6 +305,7 @@ const Recording = {
|
||||||
};
|
};
|
||||||
|
|
||||||
this._setToolbarButtonToggled(true);
|
this._setToolbarButtonToggled(true);
|
||||||
|
isRecording = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -362,6 +370,7 @@ const Recording = {
|
||||||
}
|
}
|
||||||
|
|
||||||
APP.store.dispatch(updateRecordingState({
|
APP.store.dispatch(updateRecordingState({
|
||||||
|
isRecording,
|
||||||
labelDisplayConfiguration,
|
labelDisplayConfiguration,
|
||||||
recordingState
|
recordingState
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -18,7 +18,11 @@ import {
|
||||||
participantJoined,
|
participantJoined,
|
||||||
participantLeft
|
participantLeft
|
||||||
} from '../../../react/features/base/participants';
|
} from '../../../react/features/base/participants';
|
||||||
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
|
import {
|
||||||
|
dockToolbox,
|
||||||
|
getToolboxHeight,
|
||||||
|
showToolbox
|
||||||
|
} from '../../../react/features/toolbox';
|
||||||
|
|
||||||
import SharedVideoThumb from './SharedVideoThumb';
|
import SharedVideoThumb from './SharedVideoThumb';
|
||||||
|
|
||||||
|
@ -695,7 +699,8 @@ class SharedVideoContainer extends LargeContainer {
|
||||||
let height, width;
|
let height, width;
|
||||||
|
|
||||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||||
height = containerHeight;
|
height = interfaceConfig._USE_NEW_TOOLBOX
|
||||||
|
? containerHeight - getToolboxHeight() : containerHeight;
|
||||||
width = containerWidth - Filmstrip.getFilmstripWidth();
|
width = containerWidth - Filmstrip.getFilmstripWidth();
|
||||||
} else {
|
} else {
|
||||||
height = containerHeight - Filmstrip.getFilmstripHeight();
|
height = containerHeight - Filmstrip.getFilmstripHeight();
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
/* global $ */
|
/* global $, APP */
|
||||||
import UIEvents from '../../../service/UI/UIEvents';
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
import { setVisiblePanel } from '../../../react/features/side-panel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles open and close of the extended toolbar side panel
|
* Handles open and close of the extended toolbar side panel
|
||||||
|
@ -57,6 +58,7 @@ const SideContainerToggler = {
|
||||||
|
|
||||||
if (isSelectorVisible) {
|
if (isSelectorVisible) {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
APP.store.dispatch(setVisiblePanel(null));
|
||||||
} else {
|
} else {
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
$('#sideToolbarContainer').children()
|
$('#sideToolbarContainer').children()
|
||||||
|
@ -74,6 +76,7 @@ const SideContainerToggler = {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showInnerContainer(elementSelector);
|
this.showInnerContainer(elementSelector);
|
||||||
|
APP.store.dispatch(setVisiblePanel(elementId));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global APP, $ */
|
/* global APP, $, interfaceConfig */
|
||||||
|
|
||||||
import { processReplacements, linkify } from './Replacement';
|
import { processReplacements, linkify } from './Replacement';
|
||||||
import CommandsProcessor from './Commands';
|
import CommandsProcessor from './Commands';
|
||||||
|
@ -9,7 +9,12 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
import { smileys } from './smileys';
|
import { smileys } from './smileys';
|
||||||
|
|
||||||
import { dockToolbox, setSubject } from '../../../../react/features/toolbox';
|
import { addMessage, markAllRead } from '../../../../react/features/chat';
|
||||||
|
import {
|
||||||
|
dockToolbox,
|
||||||
|
getToolboxHeight,
|
||||||
|
setSubject
|
||||||
|
} from '../../../../react/features/toolbox';
|
||||||
|
|
||||||
let unreadMessages = 0;
|
let unreadMessages = 0;
|
||||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||||
|
@ -163,6 +168,8 @@ function addSmileys() {
|
||||||
* Resizes the chat conversation.
|
* Resizes the chat conversation.
|
||||||
*/
|
*/
|
||||||
function resizeChatConversation() {
|
function resizeChatConversation() {
|
||||||
|
// FIXME: this function can all be done with CSS. If Chat is ever rewritten,
|
||||||
|
// do not copy over this logic.
|
||||||
const msgareaHeight = $('#usermsg').outerHeight();
|
const msgareaHeight = $('#usermsg').outerHeight();
|
||||||
const chatspace = $(`#${CHAT_CONTAINER_ID}`);
|
const chatspace = $(`#${CHAT_CONTAINER_ID}`);
|
||||||
const width = chatspace.width();
|
const width = chatspace.width();
|
||||||
|
@ -173,8 +180,17 @@ function resizeChatConversation() {
|
||||||
$('#smileys').css('bottom', (msgareaHeight - 26) / 2);
|
$('#smileys').css('bottom', (msgareaHeight - 26) / 2);
|
||||||
$('#smileysContainer').css('bottom', msgareaHeight);
|
$('#smileysContainer').css('bottom', msgareaHeight);
|
||||||
chat.width(width - 10);
|
chat.width(width - 10);
|
||||||
|
|
||||||
|
if (interfaceConfig._USE_NEW_TOOLBOX) {
|
||||||
|
const maybeAMagicNumberForPaddingAndMargin = 100;
|
||||||
|
const offset = maybeAMagicNumberForPaddingAndMargin
|
||||||
|
+ msgareaHeight + getToolboxHeight();
|
||||||
|
|
||||||
|
chat.height(window.innerHeight - offset);
|
||||||
|
} else {
|
||||||
chat.height(window.innerHeight - 15 - msgareaHeight);
|
chat.height(window.innerHeight - 15 - msgareaHeight);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Focus input after 400 ms
|
* Focus input after 400 ms
|
||||||
|
@ -249,6 +265,7 @@ const Chat = {
|
||||||
}
|
}
|
||||||
|
|
||||||
unreadMessages = 0;
|
unreadMessages = 0;
|
||||||
|
APP.store.dispatch(markAllRead());
|
||||||
updateVisualNotification();
|
updateVisualNotification();
|
||||||
|
|
||||||
// Undock the toolbar when the chat is shown and if we're in a
|
// Undock the toolbar when the chat is shown and if we're in a
|
||||||
|
@ -274,9 +291,10 @@ const Chat = {
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line max-params
|
// eslint-disable-next-line max-params
|
||||||
updateChatConversation(id, displayName, message, stamp) {
|
updateChatConversation(id, displayName, message, stamp) {
|
||||||
|
const isFromLocalParticipant = APP.conference.isLocalId(id);
|
||||||
let divClassName = '';
|
let divClassName = '';
|
||||||
|
|
||||||
if (APP.conference.isLocalId(id)) {
|
if (isFromLocalParticipant) {
|
||||||
divClassName = 'localuser';
|
divClassName = 'localuser';
|
||||||
} else {
|
} else {
|
||||||
divClassName = 'remoteuser';
|
divClassName = 'remoteuser';
|
||||||
|
@ -294,6 +312,7 @@ const Chat = {
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/\n/g, '<br/>');
|
.replace(/\n/g, '<br/>');
|
||||||
const escDisplayName = UIUtil.escapeHtml(displayName);
|
const escDisplayName = UIUtil.escapeHtml(displayName);
|
||||||
|
const timestamp = getCurrentTime(stamp);
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
message = processReplacements(escMessage);
|
message = processReplacements(escMessage);
|
||||||
|
@ -302,13 +321,18 @@ const Chat = {
|
||||||
= `${'<div class="chatmessage">'
|
= `${'<div class="chatmessage">'
|
||||||
+ '<img src="images/chatArrow.svg" class="chatArrow">'
|
+ '<img src="images/chatArrow.svg" class="chatArrow">'
|
||||||
+ '<div class="username '}${divClassName}">${escDisplayName
|
+ '<div class="username '}${divClassName}">${escDisplayName
|
||||||
}</div><div class="timestamp">${getCurrentTime(stamp)
|
}</div><div class="timestamp">${timestamp
|
||||||
}</div><div class="usermessage">${message}</div>`
|
}</div><div class="usermessage">${message}</div>`
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
|
|
||||||
$('#chatconversation').append(messageContainer);
|
$('#chatconversation').append(messageContainer);
|
||||||
$('#chatconversation').animate(
|
$('#chatconversation').animate(
|
||||||
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
|
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
|
||||||
|
|
||||||
|
const markAsRead = Chat.isVisible() || isFromLocalParticipant;
|
||||||
|
|
||||||
|
APP.store.dispatch(addMessage(
|
||||||
|
escDisplayName, message, timestamp, markAsRead));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -227,43 +227,10 @@ const UIUtil = {
|
||||||
* mode, {false} otherwise
|
* mode, {false} otherwise
|
||||||
*/
|
*/
|
||||||
isFullScreen() {
|
isFullScreen() {
|
||||||
return document.fullscreenElement
|
return Boolean(document.fullscreenElement
|
||||||
|| document.mozFullScreenElement
|
|| document.mozFullScreenElement
|
||||||
|| document.webkitFullscreenElement
|
|| document.webkitFullscreenElement
|
||||||
|| document.msFullscreenElement;
|
|| document.msFullscreenElement);
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exits full screen mode.
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
|
|
||||||
*/
|
|
||||||
exitFullScreen() {
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else if (document.msExitFullscreen) {
|
|
||||||
document.msExitFullscreen();
|
|
||||||
} else if (document.mozCancelFullScreen) {
|
|
||||||
document.mozCancelFullScreen();
|
|
||||||
} else if (document.webkitExitFullscreen) {
|
|
||||||
document.webkitExitFullscreen();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enter full screen mode.
|
|
||||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
|
|
||||||
*/
|
|
||||||
enterFullScreen() {
|
|
||||||
if (document.documentElement.requestFullscreen) {
|
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
} else if (document.documentElement.msRequestFullscreen) {
|
|
||||||
document.documentElement.msRequestFullscreen();
|
|
||||||
} else if (document.documentElement.mozRequestFullScreen) {
|
|
||||||
document.documentElement.mozRequestFullScreen();
|
|
||||||
} else if (document.documentElement.webkitRequestFullscreen) {
|
|
||||||
document.documentElement
|
|
||||||
.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -90,6 +90,17 @@ const KeyboardShortcut = {
|
||||||
enabled = value;
|
enabled = value;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the {@KeyboardShortcutsDialog} dialog.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
openDialog() {
|
||||||
|
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
||||||
|
shortcutDescriptions: _shortcutsHelp
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a new shortcut.
|
* Registers a new shortcut.
|
||||||
*
|
*
|
||||||
|
@ -177,9 +188,7 @@ const KeyboardShortcut = {
|
||||||
_initGlobalShortcuts() {
|
_initGlobalShortcuts() {
|
||||||
this.registerShortcut('?', null, () => {
|
this.registerShortcut('?', null, () => {
|
||||||
sendAnalytics(createShortcutEvent('help'));
|
sendAnalytics(createShortcutEvent('help'));
|
||||||
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
this.openDialog();
|
||||||
shortcutDescriptions: _shortcutsHelp
|
|
||||||
}));
|
|
||||||
}, 'keyboardShortcuts.toggleShortcuts');
|
}, 'keyboardShortcuts.toggleShortcuts');
|
||||||
|
|
||||||
// register SPACE shortcut in two steps to insure visibility of help
|
// register SPACE shortcut in two steps to insure visibility of help
|
||||||
|
|
|
@ -96,6 +96,18 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
|
||||||
*/
|
*/
|
||||||
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
|
export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of (redux) action which sets the desktop sharing enabled flag for
|
||||||
|
* the current conference.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||||
|
* desktopSharingEnabled: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_DESKTOP_SHARING_ENABLED
|
||||||
|
= Symbol('SET_DESKTOP_SHARING_ENABLED');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of (redux) action which updates the current known status of the
|
* The type of (redux) action which updates the current known status of the
|
||||||
* Follow Me feature.
|
* Follow Me feature.
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
LOCK_STATE_CHANGED,
|
LOCK_STATE_CHANGED,
|
||||||
P2P_STATUS_CHANGED,
|
P2P_STATUS_CHANGED,
|
||||||
SET_AUDIO_ONLY,
|
SET_AUDIO_ONLY,
|
||||||
|
SET_DESKTOP_SHARING_ENABLED,
|
||||||
SET_FOLLOW_ME,
|
SET_FOLLOW_ME,
|
||||||
SET_LASTN,
|
SET_LASTN,
|
||||||
SET_PASSWORD,
|
SET_PASSWORD,
|
||||||
|
@ -433,6 +434,22 @@ export function setAudioOnly(audioOnly: boolean) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the flag for indicating if desktop sharing is enabled.
|
||||||
|
*
|
||||||
|
* @param {boolean} desktopSharingEnabled - True if desktop sharing is enabled.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_DESKTOP_SHARING_ENABLED,
|
||||||
|
* desktopSharingEnabled: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setDesktopSharingEnabled(desktopSharingEnabled: boolean) {
|
||||||
|
return {
|
||||||
|
type: SET_DESKTOP_SHARING_ENABLED,
|
||||||
|
desktopSharingEnabled
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables or disables the Follow Me feature.
|
* Enables or disables the Follow Me feature.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
LOCK_STATE_CHANGED,
|
LOCK_STATE_CHANGED,
|
||||||
P2P_STATUS_CHANGED,
|
P2P_STATUS_CHANGED,
|
||||||
SET_AUDIO_ONLY,
|
SET_AUDIO_ONLY,
|
||||||
|
SET_DESKTOP_SHARING_ENABLED,
|
||||||
SET_FOLLOW_ME,
|
SET_FOLLOW_ME,
|
||||||
SET_PASSWORD,
|
SET_PASSWORD,
|
||||||
SET_RECEIVE_VIDEO_QUALITY,
|
SET_RECEIVE_VIDEO_QUALITY,
|
||||||
|
@ -57,6 +58,9 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
|
||||||
case SET_AUDIO_ONLY:
|
case SET_AUDIO_ONLY:
|
||||||
return _setAudioOnly(state, action);
|
return _setAudioOnly(state, action);
|
||||||
|
|
||||||
|
case SET_DESKTOP_SHARING_ENABLED:
|
||||||
|
return _setDesktopSharingEnabled(state, action);
|
||||||
|
|
||||||
case SET_FOLLOW_ME:
|
case SET_FOLLOW_ME:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -329,6 +333,21 @@ function _setAudioOnly(state, action) {
|
||||||
return set(state, 'audioOnly', action.audioOnly);
|
return set(state, 'audioOnly', action.audioOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces a specific Redux action SET_DESKTOP_SHARING_ENABLED of the feature
|
||||||
|
* base/conference.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state of the feature base/conference.
|
||||||
|
* @param {Action} action - The Redux action SET_DESKTOP_SHARING_ENABLED to
|
||||||
|
* reduce.
|
||||||
|
* @private
|
||||||
|
* @returns {Object} The new state of the feature base/conference after the
|
||||||
|
* reduction of the specified action.
|
||||||
|
*/
|
||||||
|
function _setDesktopSharingEnabled(state, action) {
|
||||||
|
return set(state, 'desktopSharingEnabled', action.desktopSharingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* @flow */
|
// @flow
|
||||||
|
|
||||||
import type { Dispatch } from 'redux';
|
import type { Dispatch } from 'redux';
|
||||||
|
|
||||||
|
@ -54,10 +54,12 @@ export function connect() {
|
||||||
/**
|
/**
|
||||||
* Closes connection.
|
* Closes connection.
|
||||||
*
|
*
|
||||||
|
* @param {boolean} [requestFeedback] - Whether or not to attempt showing a
|
||||||
|
* request for call feedback.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function disconnect() {
|
export function disconnect(requestFeedback: boolean = false) {
|
||||||
// XXX For web based version we use conference hanging up logic from the old
|
// XXX For web based version we use conference hanging up logic from the old
|
||||||
// app.
|
// app.
|
||||||
return () => APP.conference.hangup();
|
return () => APP.conference.hangup(requestFeedback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
disableBlanketClickDismiss: boolean,
|
disableBlanketClickDismiss: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the cancel button will not display but cancel actions, like
|
||||||
|
* clicking the blanket, will cancel.
|
||||||
|
*/
|
||||||
|
hideCancelButton: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the dialog is modal. This means clicking on the blanket will
|
* Whether the dialog is modal. This means clicking on the blanket will
|
||||||
* leave the dialog open. No cancel button.
|
* leave the dialog open. No cancel button.
|
||||||
|
@ -263,7 +269,9 @@ class StatelessDialog extends Component<Props> {
|
||||||
* not modal.
|
* not modal.
|
||||||
*/
|
*/
|
||||||
_renderCancelButton(options = {}) {
|
_renderCancelButton(options = {}) {
|
||||||
if (options.cancelDisabled || options.isModal) {
|
if (options.cancelDisabled
|
||||||
|
|| options.isModal
|
||||||
|
|| options.hideCancelButton) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,4 @@
|
||||||
/* @flow */
|
// @flow
|
||||||
|
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
@ -10,8 +10,12 @@ import {
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
import { playSound, registerSound, unregisterSound } from '../sounds';
|
import { playSound, registerSound, unregisterSound } from '../sounds';
|
||||||
|
|
||||||
import { localParticipantIdChanged } from './actions';
|
|
||||||
import {
|
import {
|
||||||
|
localParticipantIdChanged,
|
||||||
|
participantUpdated
|
||||||
|
} from './actions';
|
||||||
|
import {
|
||||||
|
DOMINANT_SPEAKER_CHANGED,
|
||||||
KICK_PARTICIPANT,
|
KICK_PARTICIPANT,
|
||||||
MUTE_REMOTE_PARTICIPANT,
|
MUTE_REMOTE_PARTICIPANT,
|
||||||
PARTICIPANT_DISPLAY_NAME_CHANGED,
|
PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||||
|
@ -27,6 +31,7 @@ import {
|
||||||
import {
|
import {
|
||||||
getAvatarURLByParticipantId,
|
getAvatarURLByParticipantId,
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
|
getParticipantById,
|
||||||
getParticipantCount
|
getParticipantCount
|
||||||
} from './functions';
|
} from './functions';
|
||||||
import {
|
import {
|
||||||
|
@ -66,6 +71,27 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
|
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DOMINANT_SPEAKER_CHANGED: {
|
||||||
|
// Ensure the raised hand state is cleared for the dominant speaker.
|
||||||
|
const participant = getLocalParticipant(store.getState());
|
||||||
|
|
||||||
|
if (participant) {
|
||||||
|
const local = participant.id === action.participant.id;
|
||||||
|
|
||||||
|
store.dispatch(participantUpdated({
|
||||||
|
id: action.participant.id,
|
||||||
|
local,
|
||||||
|
raisedHand: false
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
APP.UI.markDominantSpeaker(action.participant.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case KICK_PARTICIPANT:
|
case KICK_PARTICIPANT:
|
||||||
conference.kickParticipant(action.id);
|
conference.kickParticipant(action.id);
|
||||||
break;
|
break;
|
||||||
|
@ -90,10 +116,37 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
|
|
||||||
case PARTICIPANT_JOINED:
|
case PARTICIPANT_JOINED:
|
||||||
case PARTICIPANT_UPDATED: {
|
case PARTICIPANT_UPDATED: {
|
||||||
if (typeof APP !== 'undefined') {
|
const { participant } = action;
|
||||||
const participant = action.participant;
|
const { id, local, raisedHand } = participant;
|
||||||
const { id, local } = participant;
|
|
||||||
|
|
||||||
|
// Send an external update of the local participant's raised hand state
|
||||||
|
// if a new raised hand state is defined in the action.
|
||||||
|
if (typeof raisedHand !== 'undefined') {
|
||||||
|
if (local) {
|
||||||
|
conference.setLocalParticipantProperty(
|
||||||
|
'raisedHand',
|
||||||
|
raisedHand);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
if (local) {
|
||||||
|
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||||
|
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||||
|
} else {
|
||||||
|
const remoteParticipant
|
||||||
|
= getParticipantById(store.getState(), id);
|
||||||
|
|
||||||
|
remoteParticipant
|
||||||
|
&& APP.UI.setRaisedHandStatus(
|
||||||
|
remoteParticipant.id,
|
||||||
|
remoteParticipant.name,
|
||||||
|
raisedHand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify external listeners of potential avatarURL changes.
|
||||||
|
if (typeof APP === 'object') {
|
||||||
const preUpdateAvatarURL
|
const preUpdateAvatarURL
|
||||||
= getAvatarURLByParticipantId(store.getState(), id);
|
= getAvatarURLByParticipantId(store.getState(), id);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
/**
|
||||||
|
* The type of redux action dispatched to disable screensharing or to start the
|
||||||
|
* flow for enabling screenshare.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_SCREENSHARING
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_SCREENSHARING = Symbol('TOGGLE_SCREENSHARING');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of redux action dispatched when a track has been (locally or
|
* The type of redux action dispatched when a track has been (locally or
|
||||||
* remotely) added to the conference.
|
* remotely) added to the conference.
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
import { getLocalParticipant } from '../participants';
|
import { getLocalParticipant } from '../participants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
TOGGLE_SCREENSHARING,
|
||||||
TRACK_ADDED,
|
TRACK_ADDED,
|
||||||
TRACK_CREATE_CANCELED,
|
TRACK_CREATE_CANCELED,
|
||||||
TRACK_CREATE_ERROR,
|
TRACK_CREATE_ERROR,
|
||||||
|
@ -172,6 +173,20 @@ export function destroyLocalTracks() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the local participant is ending screensharing or beginning the
|
||||||
|
* screensharing flow.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_SCREENSHARING,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleScreensharing() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_SCREENSHARING
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces one track with another for one renegotiation instead of invoking
|
* Replaces one track with another for one renegotiation instead of invoking
|
||||||
* two renegotiations with a separate removeTrack and addTrack. Disposes the
|
* two renegotiations with a separate removeTrack and addTrack. Disposes the
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* @flow */
|
// @flow
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CAMERA_FACING_MODE,
|
CAMERA_FACING_MODE,
|
||||||
|
@ -10,9 +10,15 @@ import {
|
||||||
toggleCameraFacingMode
|
toggleCameraFacingMode
|
||||||
} from '../media';
|
} from '../media';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
import { createLocalTracksA } from './actions';
|
import { createLocalTracksA } from './actions';
|
||||||
import { TRACK_ADDED, TRACK_REMOVED, TRACK_UPDATED } from './actionTypes';
|
import {
|
||||||
|
TOGGLE_SCREENSHARING,
|
||||||
|
TRACK_ADDED,
|
||||||
|
TRACK_REMOVED,
|
||||||
|
TRACK_UPDATED
|
||||||
|
} from './actionTypes';
|
||||||
import { getLocalTrack, setTrackMuted } from './functions';
|
import { getLocalTrack, setTrackMuted } from './functions';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
@ -81,6 +87,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case TOGGLE_SCREENSHARING:
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case TRACK_ADDED:
|
case TRACK_ADDED:
|
||||||
// TODO Remove this middleware case once all UI interested in new tracks
|
// TODO Remove this middleware case once all UI interested in new tracks
|
||||||
// being added are converted to react and listening for store changes.
|
// being added are converted to react and listening for store changes.
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to add a new chat message.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: ADD_MESSAGE,
|
||||||
|
* hasRead: boolean,
|
||||||
|
* message: string,
|
||||||
|
* timestamp: string,
|
||||||
|
* userName: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const ADD_MESSAGE = Symbol('ADD_MESSAGE');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which updates which is the most recent message that
|
||||||
|
* has been seen by the local participant.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_LAST_READ_MESSAGE,
|
||||||
|
* message: Object
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_LAST_READ_MESSAGE = Symbol('SET_LAST_READ_MESSAGE');
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { ADD_MESSAGE, SET_LAST_READ_MESSAGE } from './actionTypes';
|
||||||
|
|
||||||
|
/* eslint-disable max-params */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a chat message to the collection of messages.
|
||||||
|
*
|
||||||
|
* @param {string} userName - The username to display of the participant that
|
||||||
|
* authored the message.
|
||||||
|
* @param {string} message - The received message to display.
|
||||||
|
* @param {string} timestamp - A timestamp to display for when the message was
|
||||||
|
* received.
|
||||||
|
* @param {boolean} hasRead - Whether or not to immediately mark the message as
|
||||||
|
* read.
|
||||||
|
* @returns {{
|
||||||
|
* type: ADD_MESSAGE,
|
||||||
|
* hasRead: boolean,
|
||||||
|
* message: string,
|
||||||
|
* timestamp: string,
|
||||||
|
* userName: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function addMessage(userName, message, timestamp, hasRead) {
|
||||||
|
return {
|
||||||
|
type: ADD_MESSAGE,
|
||||||
|
hasRead,
|
||||||
|
message,
|
||||||
|
timestamp,
|
||||||
|
userName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable max-params */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the last read message cursor to the latest message.
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function markAllRead() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { messages } = getState()['features/chat'];
|
||||||
|
|
||||||
|
dispatch(setLastReadMessage(messages[messages.length - 1]));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last read message cursor to be set at the passed in message. The
|
||||||
|
* assumption is that messages will be ordered chronologically.
|
||||||
|
*
|
||||||
|
* @param {Object} message - The message from the redux state.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_LAST_READ_MESSAGE,
|
||||||
|
* message: Object
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setLastReadMessage(message) {
|
||||||
|
return {
|
||||||
|
type: SET_LAST_READ_MESSAGE,
|
||||||
|
message
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { getUnreadCount } from '../functions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME: Move this UI logic to a generic component that can be used for
|
||||||
|
* {@code ParticipantCounter} as well.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a React {@link Component} which displays a count of the number of
|
||||||
|
* unread chat messages.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class ChatCounter extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The number of unread chat messages in the conference.
|
||||||
|
*/
|
||||||
|
_count: PropTypes.number
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<span className = 'badge-round'>
|
||||||
|
<span>
|
||||||
|
{ this.props._count || null }
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated {@code ChatCounter}'s
|
||||||
|
* props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _count: number
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
_count: getUnreadCount(state)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(ChatCounter);
|
|
@ -0,0 +1 @@
|
||||||
|
export ChatCounter from './ChatCounter';
|
|
@ -0,0 +1,20 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selector for calculating the number of unread chat messages.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @returns {number} The number of unread messages.
|
||||||
|
*/
|
||||||
|
export function getUnreadCount(state: Object) {
|
||||||
|
const { lastReadMessage, messages } = state['features/chat'];
|
||||||
|
const messagesCount = messages.length;
|
||||||
|
|
||||||
|
if (!messagesCount) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastReadIndex = messages.lastIndexOf(lastReadMessage);
|
||||||
|
|
||||||
|
return messagesCount - (lastReadIndex + 1);
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
|
export * from './components';
|
||||||
export * from './constants';
|
export * from './constants';
|
||||||
|
|
||||||
import './middleware';
|
import './middleware';
|
||||||
|
import './reducer';
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ADD_MESSAGE,
|
||||||
|
SET_LAST_READ_MESSAGE
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
const DEFAULT_STATE = {
|
||||||
|
open: false,
|
||||||
|
messages: [],
|
||||||
|
lastReadMessage: null
|
||||||
|
};
|
||||||
|
|
||||||
|
ReducerRegistry.register('features/chat', (state = DEFAULT_STATE, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ADD_MESSAGE: {
|
||||||
|
const newMessage = {
|
||||||
|
message: action.message,
|
||||||
|
timestamp: action.timestamp,
|
||||||
|
userName: action.userName
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastReadMessage:
|
||||||
|
action.hasRead ? newMessage : state.lastReadMessage,
|
||||||
|
messages: [
|
||||||
|
...state.messages,
|
||||||
|
newMessage
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_LAST_READ_MESSAGE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastReadMessage: action.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
|
@ -11,7 +11,14 @@ import { CalleeInfoContainer } from '../../base/jwt';
|
||||||
import { Filmstrip } from '../../filmstrip';
|
import { Filmstrip } from '../../filmstrip';
|
||||||
import { LargeVideo } from '../../large-video';
|
import { LargeVideo } from '../../large-video';
|
||||||
import { NotificationsContainer } from '../../notifications';
|
import { NotificationsContainer } from '../../notifications';
|
||||||
import { showToolbox, Toolbox } from '../../toolbox';
|
import { SidePanel } from '../../side-panel';
|
||||||
|
import {
|
||||||
|
Toolbox,
|
||||||
|
ToolboxV2,
|
||||||
|
fullScreenChanged,
|
||||||
|
setToolboxAlwaysVisible,
|
||||||
|
showToolbox
|
||||||
|
} from '../../toolbox';
|
||||||
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
import { HideNotificationBarStyle } from '../../unsupported-browser';
|
||||||
|
|
||||||
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
||||||
|
@ -19,11 +26,29 @@ import { maybeShowSuboptimalExperienceNotification } from '../functions';
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
declare var interfaceConfig: Object;
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DOM events for when full screen mode has changed. Different browsers need
|
||||||
|
* different vendor prefixes.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Array<string>}
|
||||||
|
*/
|
||||||
|
const FULL_SCREEN_EVENTS = [
|
||||||
|
'webkitfullscreenchange',
|
||||||
|
'mozfullscreenchange',
|
||||||
|
'fullscreenchange'
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link Conference}.
|
* The type of the React {@code Component} props of {@link Conference}.
|
||||||
*/
|
*/
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the toolbar should stay visible or be able to autohide.
|
||||||
|
*/
|
||||||
|
_alwaysVisibleToolbar: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the local participant is recording the conference.
|
* Whether the local participant is recording the conference.
|
||||||
*/
|
*/
|
||||||
|
@ -37,6 +62,7 @@ type Props = {
|
||||||
* The conference page of the Web application.
|
* The conference page of the Web application.
|
||||||
*/
|
*/
|
||||||
class Conference extends Component<Props> {
|
class Conference extends Component<Props> {
|
||||||
|
_onFullScreenChange: Function;
|
||||||
_onShowToolbar: Function;
|
_onShowToolbar: Function;
|
||||||
_originalOnShowToolbar: Function;
|
_originalOnShowToolbar: Function;
|
||||||
|
|
||||||
|
@ -59,6 +85,9 @@ class Conference extends Component<Props> {
|
||||||
leading: true,
|
leading: true,
|
||||||
trailing: false
|
trailing: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once for every instance.
|
||||||
|
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,10 +103,16 @@ class Conference extends Component<Props> {
|
||||||
APP.UI.registerListeners();
|
APP.UI.registerListeners();
|
||||||
APP.UI.bindEvents();
|
APP.UI.bindEvents();
|
||||||
|
|
||||||
const { dispatch, t } = this.props;
|
FULL_SCREEN_EVENTS.forEach(name =>
|
||||||
|
document.addEventListener(name, this._onFullScreenChange));
|
||||||
|
|
||||||
|
const { _alwaysVisibleToolbar, dispatch, t } = this.props;
|
||||||
|
|
||||||
dispatch(connect());
|
dispatch(connect());
|
||||||
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
||||||
|
|
||||||
|
dispatch(setToolboxAlwaysVisible(
|
||||||
|
_alwaysVisibleToolbar || interfaceConfig.filmStripOnly));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -90,6 +125,9 @@ class Conference extends Component<Props> {
|
||||||
APP.UI.unregisterListeners();
|
APP.UI.unregisterListeners();
|
||||||
APP.UI.unbindEvents();
|
APP.UI.unbindEvents();
|
||||||
|
|
||||||
|
FULL_SCREEN_EVENTS.forEach(name =>
|
||||||
|
document.removeEventListener(name, this._onFullScreenChange));
|
||||||
|
|
||||||
APP.conference.isJoined() && this.props.dispatch(disconnect());
|
APP.conference.isJoined() && this.props.dispatch(disconnect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,12 +138,26 @@ class Conference extends Component<Props> {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { filmStripOnly, VIDEO_QUALITY_LABEL_DISABLED } = interfaceConfig;
|
const {
|
||||||
|
_USE_NEW_TOOLBOX,
|
||||||
|
VIDEO_QUALITY_LABEL_DISABLED,
|
||||||
|
filmStripOnly
|
||||||
|
} = interfaceConfig;
|
||||||
const hideVideoQualityLabel
|
const hideVideoQualityLabel
|
||||||
= filmStripOnly
|
= filmStripOnly
|
||||||
|| VIDEO_QUALITY_LABEL_DISABLED
|
|| VIDEO_QUALITY_LABEL_DISABLED
|
||||||
|| this.props._iAmRecorder;
|
|| this.props._iAmRecorder;
|
||||||
|
|
||||||
|
let ToolboxToUse;
|
||||||
|
|
||||||
|
if (filmStripOnly) {
|
||||||
|
ToolboxToUse = null;
|
||||||
|
} else if (interfaceConfig._USE_NEW_TOOLBOX) {
|
||||||
|
ToolboxToUse = ToolboxV2;
|
||||||
|
} else {
|
||||||
|
ToolboxToUse = Toolbox;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id = 'videoconference_page'
|
id = 'videoconference_page'
|
||||||
|
@ -116,7 +168,10 @@ class Conference extends Component<Props> {
|
||||||
<Filmstrip filmstripOnly = { filmStripOnly } />
|
<Filmstrip filmstripOnly = { filmStripOnly } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ filmStripOnly ? null : <Toolbox /> }
|
{ ToolboxToUse && <ToolboxToUse /> }
|
||||||
|
|
||||||
|
{ _USE_NEW_TOOLBOX && !filmStripOnly
|
||||||
|
&& <SidePanel /> }
|
||||||
|
|
||||||
<DialogContainer />
|
<DialogContainer />
|
||||||
<NotificationsContainer />
|
<NotificationsContainer />
|
||||||
|
@ -135,6 +190,17 @@ class Conference extends Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the Redux state when full screen mode has been enabled or
|
||||||
|
* disabled.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onFullScreenChange() {
|
||||||
|
this.props.dispatch(fullScreenChanged(APP.UI.isFullScreen()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the toolbar.
|
* Displays the toolbar.
|
||||||
*
|
*
|
||||||
|
@ -153,17 +219,30 @@ class Conference extends Component<Props> {
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
|
* _alwaysVisibleToolbar: boolean,
|
||||||
* _iAmRecorder: boolean
|
* _iAmRecorder: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
|
const {
|
||||||
|
alwaysVisibleToolbar,
|
||||||
|
iAmRecorder
|
||||||
|
} = state['features/base/config'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
/**
|
||||||
|
* Whether the toolbar should stay visible or be able to autohide.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_alwaysVisibleToolbar: alwaysVisibleToolbar,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the local participant is recording the conference.
|
* Whether the local participant is recording the conference.
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_iAmRecorder: state['features/base/config'].iAmRecorder
|
_iAmRecorder: iAmRecorder
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* The type of the action which signals document editing has been enabled.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: ETHERPAD_INITIALIZED
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const ETHERPAD_INITIALIZED = Symbol('ETHERPAD_INITIALIZED');
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals document editing has stopped or started.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_DOCUMENT_EDITING_STATUS
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_DOCUMENT_EDITING_STATUS
|
||||||
|
= Symbol('SET_DOCUMENT_EDITING_STATUS');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to start or stop editing a shared
|
||||||
|
* document.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_DOCUMENT_EDITING
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_DOCUMENT_EDITING = Symbol('TOGGLE_DOCUMENT_EDITING');
|
|
@ -0,0 +1,50 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import {
|
||||||
|
ETHERPAD_INITIALIZED,
|
||||||
|
SET_DOCUMENT_EDITING_STATUS,
|
||||||
|
TOGGLE_DOCUMENT_EDITING
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to set whether document editing has started or stopped.
|
||||||
|
*
|
||||||
|
* @param {boolean} editing - Whether or not a document is currently being
|
||||||
|
* edited.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_DOCUMENT_EDITING_STATUS,
|
||||||
|
* editing: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setDocumentEditingState(editing: boolean) {
|
||||||
|
return {
|
||||||
|
type: SET_DOCUMENT_EDITING_STATUS,
|
||||||
|
editing
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to set Etherpad as having been initialized.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: ETHERPAD_INITIALIZED
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setEtherpadHasInitialzied() {
|
||||||
|
return {
|
||||||
|
type: ETHERPAD_INITIALIZED
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to show or hide Etherpad.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_DOCUMENT_EDITING
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleDocument() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_DOCUMENT_EDITING
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { TOGGLE_DOCUMENT_EDITING } from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that captures actions related to collaborative document editing
|
||||||
|
* and notifies components not hooked into redux.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
if (typeof APP === 'undefined') {
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_DOCUMENT_EDITING:
|
||||||
|
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ETHERPAD_INITIALIZED,
|
||||||
|
SET_DOCUMENT_EDITING_STATUS
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the Redux actions of the feature features/etherpad.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/etherpad', (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case ETHERPAD_INITIALIZED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
initialized: true
|
||||||
|
};
|
||||||
|
|
||||||
|
case SET_DOCUMENT_EDITING_STATUS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editing: action.editing
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
|
@ -7,11 +7,13 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
|
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
|
||||||
import { InviteButton } from '../../invite';
|
import { InviteButton } from '../../invite';
|
||||||
import { Toolbox } from '../../toolbox';
|
import { Toolbox, ToolboxFilmstrip, dockToolbox } from '../../toolbox';
|
||||||
|
|
||||||
import { setFilmstripHovered } from '../actions';
|
import { setFilmstripHovered } from '../actions';
|
||||||
import { shouldRemoteVideosBeVisible } from '../functions';
|
import { shouldRemoteVideosBeVisible } from '../functions';
|
||||||
|
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a React {@link Component} which represents the filmstrip on
|
* Implements a React {@link Component} which represents the filmstrip on
|
||||||
* Web/React.
|
* Web/React.
|
||||||
|
@ -62,6 +64,12 @@ class Filmstrip extends Component<*> {
|
||||||
*/
|
*/
|
||||||
_remoteVideosVisible: PropTypes.bool,
|
_remoteVideosVisible: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the toolbox is visible. The height of the vertical
|
||||||
|
* filmstrip needs to adjust to accommodate the horizontal toolbox.
|
||||||
|
*/
|
||||||
|
_toolboxVisible: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the redux store with filmstrip hover changes.
|
* Updates the redux store with filmstrip hover changes.
|
||||||
*/
|
*/
|
||||||
|
@ -111,6 +119,7 @@ class Filmstrip extends Component<*> {
|
||||||
_isAddToCallAvailable,
|
_isAddToCallAvailable,
|
||||||
_isDialOutAvailable,
|
_isDialOutAvailable,
|
||||||
_remoteVideosVisible,
|
_remoteVideosVisible,
|
||||||
|
_toolboxVisible,
|
||||||
filmstripOnly
|
filmstripOnly
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -122,13 +131,17 @@ class Filmstrip extends Component<*> {
|
||||||
* will get updated without replacing the DOM. If the known DOM gets
|
* will get updated without replacing the DOM. If the known DOM gets
|
||||||
* modified, then the views will get blown away.
|
* modified, then the views will get blown away.
|
||||||
*/
|
*/
|
||||||
|
const reduceHeight
|
||||||
|
= _toolboxVisible && interfaceConfig.TOOLBAR_BUTTONS.length;
|
||||||
|
const filmstripClassNames = `filmstrip ${_remoteVideosVisible
|
||||||
|
? '' : 'hide-videos'} ${reduceHeight ? 'reduce-height' : ''}`;
|
||||||
|
|
||||||
const filmstripClassNames = `filmstrip ${_remoteVideosVisible ? ''
|
const ToolboxToUse = interfaceConfig._USE_NEW_TOOLBOX
|
||||||
: 'hide-videos'}`;
|
? ToolboxFilmstrip : Toolbox;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = { filmstripClassNames }>
|
<div className = { filmstripClassNames }>
|
||||||
{ filmstripOnly ? <Toolbox /> : null }
|
{ filmstripOnly ? <ToolboxToUse /> : null }
|
||||||
<div
|
<div
|
||||||
className = 'filmstrip__videos'
|
className = 'filmstrip__videos'
|
||||||
id = 'remoteVideos'>
|
id = 'remoteVideos'>
|
||||||
|
@ -172,6 +185,9 @@ class Filmstrip extends Component<*> {
|
||||||
*/
|
*/
|
||||||
_notifyOfHoveredStateUpdate() {
|
_notifyOfHoveredStateUpdate() {
|
||||||
if (this.props._hovered !== this._isHovered) {
|
if (this.props._hovered !== this._isHovered) {
|
||||||
|
if (interfaceConfig._USE_NEW_TOOLBOX) {
|
||||||
|
this.props.dispatch(dockToolbox(this._isHovered));
|
||||||
|
}
|
||||||
this.props.dispatch(setFilmstripHovered(this._isHovered));
|
this.props.dispatch(setFilmstripHovered(this._isHovered));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +227,8 @@ class Filmstrip extends Component<*> {
|
||||||
* _hovered: boolean,
|
* _hovered: boolean,
|
||||||
* _isAddToCallAvailable: boolean,
|
* _isAddToCallAvailable: boolean,
|
||||||
* _isDialOutAvailable: boolean,
|
* _isDialOutAvailable: boolean,
|
||||||
* _remoteVideosVisible: boolean
|
* _remoteVideosVisible: boolean,
|
||||||
|
* _toolboxVisible: boolean
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
|
@ -231,11 +248,13 @@ function _mapStateToProps(state) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_hideInviteButton: iAmRecorder
|
_hideInviteButton: iAmRecorder
|
||||||
|| (!isAddToCallAvailable && !isDialOutAvailable),
|
|| (!isAddToCallAvailable && !isDialOutAvailable)
|
||||||
|
|| interfaceConfig._USE_NEW_TOOLBOX,
|
||||||
_hovered: hovered,
|
_hovered: hovered,
|
||||||
_isAddToCallAvailable: isAddToCallAvailable,
|
_isAddToCallAvailable: isAddToCallAvailable,
|
||||||
_isDialOutAvailable: isDialOutAvailable,
|
_isDialOutAvailable: isDialOutAvailable,
|
||||||
_remoteVideosVisible: shouldRemoteVideosBeVisible(state)
|
_remoteVideosVisible: shouldRemoteVideosBeVisible(state),
|
||||||
|
_toolboxVisible: state['features/toolbox'].visible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,15 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { ToolbarButton, TOOLTIP_TO_POPUP_POSITION } from '../../toolbox';
|
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
import {
|
||||||
|
ToolbarButton,
|
||||||
|
ToolbarButtonV2,
|
||||||
|
TOOLTIP_TO_POPUP_POSITION
|
||||||
|
} from '../../toolbox';
|
||||||
|
|
||||||
import { setInfoDialogVisibility } from '../actions';
|
import { setInfoDialogVisibility, updateDialInNumbers } from '../actions';
|
||||||
import { InfoDialog } from './info-dialog';
|
import { InfoDialog } from './info-dialog';
|
||||||
|
|
||||||
const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig;
|
const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig;
|
||||||
|
@ -39,6 +45,15 @@ class InfoDialogButton extends Component {
|
||||||
* @static
|
* @static
|
||||||
*/
|
*/
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Phone numbers for dialing into the conference.
|
||||||
|
*/
|
||||||
|
_dialInNumbers: PropTypes.oneOfType([
|
||||||
|
PropTypes.object,
|
||||||
|
PropTypes.array
|
||||||
|
]),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the {@code InfoDialog} should close by itself after a
|
* Whether or not the {@code InfoDialog} should close by itself after a
|
||||||
* a timeout.
|
* a timeout.
|
||||||
|
@ -61,6 +76,11 @@ class InfoDialogButton extends Component {
|
||||||
*/
|
*/
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* From which side tooltips should display. Will be re-used for
|
* From which side tooltips should display. Will be re-used for
|
||||||
* displaying the inline dialog for video quality adjustment.
|
* displaying the inline dialog for video quality adjustment.
|
||||||
|
@ -100,6 +120,10 @@ class InfoDialogButton extends Component {
|
||||||
if (this.props._shouldAutoClose) {
|
if (this.props._shouldAutoClose) {
|
||||||
this._setAutoCloseTimeout();
|
this._setAutoCloseTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props._dialInNumbers) {
|
||||||
|
this.props.dispatch(updateDialInNumbers());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,29 +169,9 @@ class InfoDialogButton extends Component {
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
|
return interfaceConfig._USE_NEW_TOOLBOX
|
||||||
const buttonConfiguration = {
|
? this._renderNewToolbarButton()
|
||||||
...DEFAULT_BUTTON_CONFIGURATION,
|
: this._renderOldToolbarButton();
|
||||||
classNames: [
|
|
||||||
...DEFAULT_BUTTON_CONFIGURATION.classNames,
|
|
||||||
_showDialog ? 'toggled button-active' : ''
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InlineDialog
|
|
||||||
content = { <InfoDialog
|
|
||||||
onClose = { this._onDialogClose }
|
|
||||||
onMouseOver = { this._onDialogMouseOver } /> }
|
|
||||||
isOpen = { _toolboxVisible && _showDialog }
|
|
||||||
onClose = { this._onDialogClose }
|
|
||||||
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
|
|
||||||
<ToolbarButton
|
|
||||||
button = { buttonConfiguration }
|
|
||||||
onClick = { this._onDialogToggle }
|
|
||||||
tooltipPosition = { tooltipPosition } />
|
|
||||||
</InlineDialog>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,9 +212,75 @@ class InfoDialogButton extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onDialogToggle() {
|
_onDialogToggle() {
|
||||||
|
sendAnalytics(createToolbarEvent('info'));
|
||||||
|
|
||||||
this.props.dispatch(setInfoDialogVisibility(!this.props._showDialog));
|
this.props.dispatch(setInfoDialogVisibility(!this.props._showDialog));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a React Element for the {@code InfoDialog} using legacy
|
||||||
|
* {@code ToolbarButton}.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderOldToolbarButton() {
|
||||||
|
const { _showDialog, _toolboxVisible, tooltipPosition } = this.props;
|
||||||
|
const buttonConfiguration = {
|
||||||
|
...DEFAULT_BUTTON_CONFIGURATION,
|
||||||
|
classNames: [
|
||||||
|
...DEFAULT_BUTTON_CONFIGURATION.classNames,
|
||||||
|
_showDialog ? 'toggled button-active' : ''
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineDialog
|
||||||
|
content = { <InfoDialog
|
||||||
|
autoUpdateNumbers = { false }
|
||||||
|
onClose = { this._onDialogClose }
|
||||||
|
onMouseOver = { this._onDialogMouseOver } /> }
|
||||||
|
isOpen = { _toolboxVisible && _showDialog }
|
||||||
|
onClose = { this._onDialogClose }
|
||||||
|
position = { TOOLTIP_TO_POPUP_POSITION[tooltipPosition] }>
|
||||||
|
<ToolbarButton
|
||||||
|
button = { buttonConfiguration }
|
||||||
|
onClick = { this._onDialogToggle }
|
||||||
|
tooltipPosition = { tooltipPosition } />
|
||||||
|
</InlineDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a React Element for the {@code InfoDialog} using the newer
|
||||||
|
* {@code ToolbarButtonV2}.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
_renderNewToolbarButton() {
|
||||||
|
const { _showDialog, _toolboxVisible, t } = this.props;
|
||||||
|
const iconClass = `icon-info ${_showDialog ? 'toggled' : ''}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'toolbox-button-wth-dialog'>
|
||||||
|
<InlineDialog
|
||||||
|
content = { <InfoDialog
|
||||||
|
autoUpdateNumbers = { false }
|
||||||
|
onClose = { this._onDialogClose }
|
||||||
|
onMouseOver = { this._onDialogMouseOver } /> }
|
||||||
|
isOpen = { _toolboxVisible && _showDialog }
|
||||||
|
onClose = { this._onDialogClose }
|
||||||
|
position = { 'top right' }>
|
||||||
|
<ToolbarButtonV2
|
||||||
|
iconName = { iconClass }
|
||||||
|
onClick = { this._onDialogToggle }
|
||||||
|
tooltip = { t('info.tooltip') } />
|
||||||
|
</InlineDialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a timeout to automatically hide the {@code InfoDialog}.
|
* Set a timeout to automatically hide the {@code InfoDialog}.
|
||||||
*
|
*
|
||||||
|
@ -235,6 +305,7 @@ class InfoDialogButton extends Component {
|
||||||
* @param {Object} state - The Redux state.
|
* @param {Object} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {{
|
* @returns {{
|
||||||
|
* _dialInNumbers: Array,
|
||||||
* _shouldAutoClose: boolean,
|
* _shouldAutoClose: boolean,
|
||||||
* _showDialog: boolean,
|
* _showDialog: boolean,
|
||||||
* _toolboxVisible: boolean
|
* _toolboxVisible: boolean
|
||||||
|
@ -243,14 +314,16 @@ class InfoDialogButton extends Component {
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state) {
|
||||||
const {
|
const {
|
||||||
infoDialogVisible,
|
infoDialogVisible,
|
||||||
infoDialogWillAutoClose
|
infoDialogWillAutoClose,
|
||||||
|
numbers
|
||||||
} = state['features/invite'];
|
} = state['features/invite'];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
_dialInNumbers: numbers,
|
||||||
_shouldAutoClose: infoDialogWillAutoClose,
|
_shouldAutoClose: infoDialogWillAutoClose,
|
||||||
_showDialog: infoDialogVisible,
|
_showDialog: infoDialogVisible,
|
||||||
_toolboxVisible: state['features/toolbox'].visible
|
_toolboxVisible: state['features/toolbox'].visible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(_mapStateToProps)(InfoDialogButton);
|
export default translate(connect(_mapStateToProps)(InfoDialogButton));
|
||||||
|
|
|
@ -24,6 +24,15 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||||
* @extends Component
|
* @extends Component
|
||||||
*/
|
*/
|
||||||
class InfoDialog extends Component {
|
class InfoDialog extends Component {
|
||||||
|
/**
|
||||||
|
* Default values for {@code InfoDialog} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
autoUpdateNumbers: true
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@code InfoDialog} component's property types.
|
* {@code InfoDialog} component's property types.
|
||||||
*
|
*
|
||||||
|
@ -69,6 +78,13 @@ class InfoDialog extends Component {
|
||||||
*/
|
*/
|
||||||
_password: PropTypes.string,
|
_password: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this component should make a request for dial-in
|
||||||
|
* numbers. If false, this component will rely on an outside source
|
||||||
|
* updating and passing in numbers through the _dialIn prop.
|
||||||
|
*/
|
||||||
|
autoUpdateNumbers: PropTypes.bool,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked to open a dialog for adding participants to the conference.
|
* Invoked to open a dialog for adding participants to the conference.
|
||||||
*/
|
*/
|
||||||
|
@ -148,7 +164,7 @@ class InfoDialog extends Component {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.state.phoneNumber) {
|
if (!this.state.phoneNumber && this.props.autoUpdateNumbers) {
|
||||||
this.props.dispatch(updateDialInNumbers());
|
this.props.dispatch(updateDialInNumbers());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* The type of the action which signals the keyboard shortcuts dialog should
|
||||||
|
* be displayed.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const OPEN_KEYBOARD_SHORTCUTS_DIALOG
|
||||||
|
= Symbol('OPEN_KEYBOARD_SHORTCUTS_DIALOG');
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the dialog showing available keyboard shortcuts.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function openKeyboardShortcutsDialog() {
|
||||||
|
return {
|
||||||
|
type: OPEN_KEYBOARD_SHORTCUTS_DIALOG
|
||||||
|
};
|
||||||
|
}
|
|
@ -1 +1,4 @@
|
||||||
|
export * from './actions';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { OPEN_KEYBOARD_SHORTCUTS_DIALOG } from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the middleware of the feature keyboard-shortcuts.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case OPEN_KEYBOARD_SHORTCUTS_DIALOG:
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
APP.keyboardshortcut.openDialog();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -20,3 +20,25 @@ export const HIDE_RECORDING_LABEL = Symbol('HIDE_RECORDING_LABEL');
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export const RECORDING_STATE_UPDATED = Symbol('RECORDING_STATE_UPDATED');
|
export const RECORDING_STATE_UPDATED = Symbol('RECORDING_STATE_UPDATED');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of Redux action which updates the current known type of configured
|
||||||
|
* recording. For example, type "jibri" is used for live streaming.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: RECORDING_STATE_UPDATED,
|
||||||
|
* recordingType: string
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const SET_RECORDING_TYPE = Symbol('SET_RECORDING_TYPE');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of Redux action triggers the flow to start or stop recording.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_RECORDING
|
||||||
|
* }
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export const TOGGLE_RECORDING = Symbol('TOGGLE_RECORDING');
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
|
import {
|
||||||
|
HIDE_RECORDING_LABEL,
|
||||||
|
RECORDING_STATE_UPDATED,
|
||||||
|
SET_RECORDING_TYPE,
|
||||||
|
TOGGLE_RECORDING
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides any displayed recording label, regardless of current recording state.
|
* Hides any displayed recording label, regardless of current recording state.
|
||||||
|
@ -13,6 +18,36 @@ export function hideRecordingLabel() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets what type of recording service will be used.
|
||||||
|
*
|
||||||
|
* @param {string} recordingType - The type of recording service to be used.
|
||||||
|
* Should be one of the enumerated types in {@link RECORDING_TYPES}.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_RECORDING_TYPE,
|
||||||
|
* recordingType: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setRecordingType(recordingType) {
|
||||||
|
return {
|
||||||
|
type: SET_RECORDING_TYPE,
|
||||||
|
recordingType
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start or stop recording.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_RECORDING
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleRecording() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_RECORDING
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the redux state for the recording feature.
|
* Updates the redux state for the recording feature.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected supported recording types. JIBRI is known to support live streaming
|
||||||
|
* whereas JIRECON is for recording.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
export const RECORDING_TYPES = {
|
||||||
|
JIBRI: 'jibri',
|
||||||
|
JIRECON: 'jirecon'
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
export * from './actions';
|
export * from './actions';
|
||||||
export * from './components';
|
export * from './components';
|
||||||
|
export * from './constants';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
import './reducer';
|
import './reducer';
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { TOGGLE_RECORDING } from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the middleware of the feature recording.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_RECORDING:
|
||||||
|
if (typeof APP === 'object') {
|
||||||
|
APP.UI.emitEvent(UIEvents.TOGGLE_RECORDING);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -1,5 +1,9 @@
|
||||||
import { ReducerRegistry } from '../base/redux';
|
import { ReducerRegistry } from '../base/redux';
|
||||||
import { HIDE_RECORDING_LABEL, RECORDING_STATE_UPDATED } from './actionTypes';
|
import {
|
||||||
|
HIDE_RECORDING_LABEL,
|
||||||
|
RECORDING_STATE_UPDATED,
|
||||||
|
SET_RECORDING_TYPE
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reduces the Redux actions of the feature features/recording.
|
* Reduces the Redux actions of the feature features/recording.
|
||||||
|
@ -18,6 +22,12 @@ ReducerRegistry.register('features/recording', (state = {}, action) => {
|
||||||
...action.recordingState
|
...action.recordingState
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case SET_RECORDING_TYPE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
recordingType: action.recordingType
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to update the current known state of the
|
||||||
|
* shared YouTube video.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_SHARED_VIDEO_STATUS,
|
||||||
|
* status: string
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_SHARED_VIDEO_STATUS = Symbol('SET_SHARED_VIDEO_STATUS');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to start the flow for starting or
|
||||||
|
* stopping a shared YouTube video.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_SHARED_VIDEO
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_SHARED_VIDEO = Symbol('TOGGLE_SHARED_VIDEO');
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { SET_SHARED_VIDEO_STATUS, TOGGLE_SHARED_VIDEO } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current known status of the shared YouTube video.
|
||||||
|
*
|
||||||
|
* @param {string} status - The current status of the YouTube video being
|
||||||
|
* shared.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_SHARED_VIDEO_STATUS,
|
||||||
|
* status: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setSharedVideoStatus(status) {
|
||||||
|
return {
|
||||||
|
type: SET_SHARED_VIDEO_STATUS,
|
||||||
|
status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the flow for starting or stopping a shared YouTube video.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_SHARED_VIDEO
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleSharedVideo() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_SHARED_VIDEO
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,31 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { TOGGLE_SHARED_VIDEO } from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that captures actions related to YouTube video sharing and updates
|
||||||
|
* components not hooked into redux.
|
||||||
|
*
|
||||||
|
* @param {Store} store - The redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
if (typeof APP === 'undefined') {
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case TOGGLE_SHARED_VIDEO:
|
||||||
|
APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { SET_SHARED_VIDEO_STATUS } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the Redux actions of the feature features/shared-video.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/shared-video', (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_SHARED_VIDEO_STATUS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
status: action.status
|
||||||
|
};
|
||||||
|
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to close the side panel.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: CLOSE_PANEL,
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const CLOSE_PANEL = Symbol('CLOSE_PANEL');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which to set the name of the current panel being
|
||||||
|
* displayed in the side panel.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_VISIBLE_PANEL,
|
||||||
|
* current: string|null
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_VISIBLE_PANEL = Symbol('SET_VISIBLE_PANEL');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to toggle the display of chat in the
|
||||||
|
* side panel.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_CHAT
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_CHAT = Symbol('TOGGLE_CHAT');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to toggle the display of profile editing
|
||||||
|
* in the side panel.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_PROFILE
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_PROFILE = Symbol('TOGGLE_PROFILE');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the action which signals to toggle the display of settings in the
|
||||||
|
* side panel.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: TOGGLE_SETTINGS
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const TOGGLE_SETTINGS = Symbol('TOGGLE_SETTINGS');
|
|
@ -0,0 +1,77 @@
|
||||||
|
import {
|
||||||
|
CLOSE_PANEL,
|
||||||
|
SET_VISIBLE_PANEL,
|
||||||
|
TOGGLE_CHAT,
|
||||||
|
TOGGLE_PROFILE,
|
||||||
|
TOGGLE_SETTINGS
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to close the currently displayed side panel.
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function closePanel() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch({
|
||||||
|
type: CLOSE_PANEL,
|
||||||
|
current: getState()['features/side-panel'].current
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the redux store with the currently displayed side panel.
|
||||||
|
*
|
||||||
|
* @param {string|null} name - The name of the side panel being displayed. Null
|
||||||
|
* (or falsy) should be set if no side panel is being displayed.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_VISIBLE_PANEL,
|
||||||
|
* current: string
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setVisiblePanel(name = null) {
|
||||||
|
return {
|
||||||
|
type: SET_VISIBLE_PANEL,
|
||||||
|
current: name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles display of the chat side panel.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_CHAT
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleChat() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_CHAT
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles display of the profile side panel.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_PROFILE
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleProfile() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_PROFILE
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles display of the settings side panel.
|
||||||
|
*
|
||||||
|
* @returns {{
|
||||||
|
* type: TOGGLE_SETTINGS
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function toggleSettings() {
|
||||||
|
return {
|
||||||
|
type: TOGGLE_SETTINGS
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { closePanel } from '../actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React Component for holding features in a side panel that slides in and out.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class SidePanel extends Component {
|
||||||
|
/**
|
||||||
|
* {@code SidePanel} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code SidePanel} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._onCloseClick = this._onCloseClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div id = 'sideToolbarContainer'>
|
||||||
|
<div
|
||||||
|
className = 'side-toolbar-close'
|
||||||
|
onClick = { this._onCloseClick }>
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked to hide {@code SidePanel}.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onCloseClick() {
|
||||||
|
this.props.dispatch(closePanel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect()(SidePanel);
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as SidePanel } from './SidePanel';
|
|
@ -0,0 +1,6 @@
|
||||||
|
export * from './actions';
|
||||||
|
export * from './actionTypes';
|
||||||
|
export * from './components';
|
||||||
|
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
|
@ -0,0 +1,45 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CLOSE_PANEL,
|
||||||
|
TOGGLE_CHAT,
|
||||||
|
TOGGLE_PROFILE,
|
||||||
|
TOGGLE_SETTINGS
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Middleware that catches actions related to the non-reactified web side panel.
|
||||||
|
*
|
||||||
|
* @param {Store} store - Redux store.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
MiddlewareRegistry.register(store => next => action => {
|
||||||
|
if (typeof APP !== 'object') {
|
||||||
|
return next(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case CLOSE_PANEL:
|
||||||
|
APP.UI.toggleSidePanel(action.current);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOGGLE_CHAT:
|
||||||
|
APP.UI.toggleChat();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOGGLE_PROFILE:
|
||||||
|
APP.UI.toggleSidePanel('profile_container');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOGGLE_SETTINGS:
|
||||||
|
APP.UI.toggleSidePanel('settings_container');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(action);
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
|
import { SET_VISIBLE_PANEL } from './actionTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduces the Redux actions of the feature features/side-panel.
|
||||||
|
*/
|
||||||
|
ReducerRegistry.register('features/side-panel', (state = {}, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_VISIBLE_PANEL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
current: action.current
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
});
|
|
@ -7,6 +7,17 @@
|
||||||
*/
|
*/
|
||||||
export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
|
export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of (redux) action which updates whether the conference is or is not
|
||||||
|
* currently in full screen view.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: FULL_SCREEN_CHANGED,
|
||||||
|
* fullScreen: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const FULL_SCREEN_CHANGED = Symbol('FULL_SCREEN_CHANGED');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the action which sets the default toolbar buttons of the Toolbox.
|
* The type of the action which sets the default toolbar buttons of the Toolbox.
|
||||||
*
|
*
|
||||||
|
@ -19,6 +30,17 @@ export const CLEAR_TOOLBOX_TIMEOUT = Symbol('CLEAR_TOOLBOX_TIMEOUT');
|
||||||
export const SET_DEFAULT_TOOLBOX_BUTTONS
|
export const SET_DEFAULT_TOOLBOX_BUTTONS
|
||||||
= Symbol('SET_DEFAULT_TOOLBOX_BUTTONS');
|
= Symbol('SET_DEFAULT_TOOLBOX_BUTTONS');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of (redux) action which requests full screen mode be entered or
|
||||||
|
* exited.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: SET_FULL_SCREEN,
|
||||||
|
* fullScreen: boolean
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const SET_FULL_SCREEN = Symbol('SET_FULL_SCREEN');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the action which sets the conference subject.
|
* The type of the action which sets the conference subject.
|
||||||
*
|
*
|
||||||
|
|
|
@ -229,9 +229,10 @@ export function toggleFullScreen(isFullScreen: boolean): Function {
|
||||||
const buttonName = 'fullscreen';
|
const buttonName = 'fullscreen';
|
||||||
const button = getButton(buttonName, getState());
|
const button = getButton(buttonName, getState());
|
||||||
|
|
||||||
|
if (button) {
|
||||||
button.toggled = isFullScreen;
|
button.toggled = isFullScreen;
|
||||||
|
|
||||||
dispatch(setToolbarButton(buttonName, button));
|
dispatch(setToolbarButton(buttonName, button));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,13 @@ import {
|
||||||
setToolboxTimeout,
|
setToolboxTimeout,
|
||||||
setToolboxTimeoutMS,
|
setToolboxTimeoutMS,
|
||||||
setToolboxVisible,
|
setToolboxVisible,
|
||||||
toggleFullScreen,
|
|
||||||
toggleToolbarButton
|
toggleToolbarButton
|
||||||
} from './actions.native';
|
} from './actions.native';
|
||||||
import { SET_DEFAULT_TOOLBOX_BUTTONS } from './actionTypes';
|
import {
|
||||||
|
FULL_SCREEN_CHANGED,
|
||||||
|
SET_DEFAULT_TOOLBOX_BUTTONS,
|
||||||
|
SET_FULL_SCREEN
|
||||||
|
} from './actionTypes';
|
||||||
import {
|
import {
|
||||||
getButton,
|
getButton,
|
||||||
getDefaultToolboxButtons,
|
getDefaultToolboxButtons,
|
||||||
|
@ -95,6 +98,23 @@ export function dockToolbox(dock: boolean): Function {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that full screen mode has been entered or exited.
|
||||||
|
*
|
||||||
|
* @param {boolean} fullScreen - Whether or not full screen mode is currently
|
||||||
|
* enabled.
|
||||||
|
* @returns {{
|
||||||
|
* type: FULL_SCREEN_CHANGED,
|
||||||
|
* fullScreen: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function fullScreenChanged(fullScreen: boolean) {
|
||||||
|
return {
|
||||||
|
type: FULL_SCREEN_CHANGED,
|
||||||
|
fullScreen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns button on mount/unmount handlers with dispatch function stored in
|
* Returns button on mount/unmount handlers with dispatch function stored in
|
||||||
* closure.
|
* closure.
|
||||||
|
@ -106,8 +126,6 @@ export function dockToolbox(dock: boolean): Function {
|
||||||
function _getButtonHandlers(dispatch) {
|
function _getButtonHandlers(dispatch) {
|
||||||
const localRaiseHandHandler
|
const localRaiseHandHandler
|
||||||
= (...args) => dispatch(changeLocalRaiseHand(...args));
|
= (...args) => dispatch(changeLocalRaiseHand(...args));
|
||||||
const toggleFullScreenHandler
|
|
||||||
= (...args) => dispatch(toggleFullScreen(...args));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
|
@ -119,22 +137,6 @@ function _getButtonHandlers(dispatch) {
|
||||||
onMount: () => dispatch(showDesktopSharingButton())
|
onMount: () => dispatch(showDesktopSharingButton())
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount/Unmount handler for toggling fullscreen button.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
fullscreen: {
|
|
||||||
onMount: () =>
|
|
||||||
APP.UI.addListener(
|
|
||||||
UIEvents.FULLSCREEN_TOGGLED,
|
|
||||||
toggleFullScreenHandler),
|
|
||||||
onUnmount: () =>
|
|
||||||
APP.UI.removeListener(
|
|
||||||
UIEvents.FULLSCREEN_TOGGLED,
|
|
||||||
toggleFullScreenHandler)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mount/Unmount handlers for raisehand button.
|
* Mount/Unmount handlers for raisehand button.
|
||||||
*
|
*
|
||||||
|
@ -291,6 +293,22 @@ export function showDialPadButton(show: boolean): Function {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals a request to enter or exit full screen mode.
|
||||||
|
*
|
||||||
|
* @param {boolean} fullScreen - True to enter full screen mode, false to exit.
|
||||||
|
* @returns {{
|
||||||
|
* type: SET_FULL_SCREEN,
|
||||||
|
* fullScreen: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function setFullScreen(fullScreen: boolean) {
|
||||||
|
return {
|
||||||
|
type: SET_FULL_SCREEN,
|
||||||
|
fullScreen
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows recording button.
|
* Shows recording button.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
import InlineDialog from '@atlaskit/inline-dialog';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
|
||||||
|
import ToolbarButtonV2 from './ToolbarButtonV2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React {@code Component} for opening or closing the {@code OverflowMenu}.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class OverflowMenuButton extends Component {
|
||||||
|
/**
|
||||||
|
* {@code OverflowMenuButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* A child React Element to display within {@code InlineDialog}.
|
||||||
|
*/
|
||||||
|
children: PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the OverflowMenu popover should display.
|
||||||
|
*/
|
||||||
|
isOpen: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calback to change the visiblility of the overflow menu.
|
||||||
|
*/
|
||||||
|
onVisibilityChange: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code OverflowMenuButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once per instance.
|
||||||
|
this._onCloseDialog = this._onCloseDialog.bind(this);
|
||||||
|
this._onToggleDialogVisibility
|
||||||
|
= this._onToggleDialogVisibility.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { children, isOpen, t } = this.props;
|
||||||
|
const iconClasses = `icon-thumb-menu ${isOpen ? 'toggled' : ''}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'toolbox-button-wth-dialog'>
|
||||||
|
<InlineDialog
|
||||||
|
content = { children }
|
||||||
|
isOpen = { isOpen }
|
||||||
|
onClose = { this._onCloseDialog }
|
||||||
|
position = { 'top right' }>
|
||||||
|
<ToolbarButtonV2
|
||||||
|
iconName = { iconClasses }
|
||||||
|
onClick = { this._onToggleDialogVisibility }
|
||||||
|
tooltip = { t('toolbar.moreActions') } />
|
||||||
|
</InlineDialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked when {@code InlineDialog} signals that it should be
|
||||||
|
* close.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onCloseDialog() {
|
||||||
|
this.props.onVisibilityChange(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback invoked to signal that an event has occurred that should change
|
||||||
|
* the visibility of the {@code InlineDialog} component.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToggleDialogVisibility() {
|
||||||
|
this.props.onVisibilityChange(!this.props.isOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(OverflowMenuButton);
|
|
@ -0,0 +1,53 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React {@code Component} for displaying a link to interact with other
|
||||||
|
* features of the application.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class OverflowMenuItem extends Component {
|
||||||
|
/**
|
||||||
|
* {@code OverflowMenuItem} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The icon class to use for displaying an icon before the link text.
|
||||||
|
*/
|
||||||
|
icon: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback to invoke when {@code OverflowMenuItem} is clicked.
|
||||||
|
*/
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text to display in the {@code OverflowMenuItem}.
|
||||||
|
*/
|
||||||
|
text: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className = 'overflow-menu-item'
|
||||||
|
onClick = { this.props.onClick }>
|
||||||
|
<span className = 'overflow-menu-item-icon'>
|
||||||
|
<i className = { this.props.icon } />
|
||||||
|
</span>
|
||||||
|
{ this.props.text }
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OverflowMenuItem;
|
|
@ -0,0 +1,119 @@
|
||||||
|
/* globals interfaceConfig */
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
getAvatarURL,
|
||||||
|
getLocalParticipant
|
||||||
|
} from '../../base/participants';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React {@code Component} for displaying a link with a profile avatar as an
|
||||||
|
* icon.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class OverflowMenuProfileItem extends Component {
|
||||||
|
/**
|
||||||
|
* {@code OverflowMenuProfileItem}'s property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* The redux representation of the local participant.
|
||||||
|
*/
|
||||||
|
_localParticipant: PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the button support clicking or not.
|
||||||
|
*/
|
||||||
|
_unclickable: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The callback to invoke when {@code OverflowMenuProfileItem} is
|
||||||
|
* clicked.
|
||||||
|
*/
|
||||||
|
onClick: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code OverflowMenuProfileItem} instance.
|
||||||
|
*
|
||||||
|
* @param {Object} props - The read-only properties with which the new
|
||||||
|
* instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once for every instance.
|
||||||
|
this._onClick = this._onClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _localParticipant, _unclickable } = this.props;
|
||||||
|
const classNames = `overflow-menu-item ${
|
||||||
|
_unclickable ? 'unclickable' : ''}`;
|
||||||
|
const avatarURL = getAvatarURL(_localParticipant);
|
||||||
|
let displayName;
|
||||||
|
|
||||||
|
if (_localParticipant && _localParticipant.name) {
|
||||||
|
displayName = _localParticipant.name.split(' ')[0];
|
||||||
|
} else {
|
||||||
|
displayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
className = { classNames }
|
||||||
|
onClick = { this._onClick }>
|
||||||
|
<span className = 'overflow-menu-item-icon'>
|
||||||
|
<Avatar uri = { avatarURL } />
|
||||||
|
</span>
|
||||||
|
<span className = 'profile-text'>
|
||||||
|
{ displayName }
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes an on click callback if clicking is allowed.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onClick() {
|
||||||
|
if (!this.props._unclickable) {
|
||||||
|
this.props.onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated
|
||||||
|
* {@code OverflowMenuProfileItem} component's props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _localParticipant: Object,
|
||||||
|
* _unclickable: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
_localParticipant: getLocalParticipant(state),
|
||||||
|
_unclickable: !state['features/base/jwt'].isGuest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(OverflowMenuProfileItem);
|
|
@ -0,0 +1,78 @@
|
||||||
|
import Tooltip from '@atlaskit/tooltip';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import AbstractToolbarButton from './AbstractToolbarButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a button in the toolbar.
|
||||||
|
*
|
||||||
|
* @extends AbstractToolbarButton
|
||||||
|
*/
|
||||||
|
class ToolbarButtonV2 extends AbstractToolbarButton {
|
||||||
|
/**
|
||||||
|
* Default values for {@code ToolbarButtonV2} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
tooltipPosition: 'top'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code ToolbarButtonV2} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractToolbarButton.propTypes,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text to display in the tooltip.
|
||||||
|
*/
|
||||||
|
tooltip: PropTypes.string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From which direction the tooltip should appear, relative to the
|
||||||
|
* button.
|
||||||
|
*/
|
||||||
|
tooltipPosition: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the button of this {@code ToolbarButton}.
|
||||||
|
*
|
||||||
|
* @param {Object} children - The children, if any, to be rendered inside
|
||||||
|
* the button. Presumably, contains the icon of this {@code ToolbarButton}.
|
||||||
|
* @protected
|
||||||
|
* @returns {ReactElement} The button of this {@code ToolbarButton}.
|
||||||
|
*/
|
||||||
|
_renderButton(children) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className = 'toolbox-button'
|
||||||
|
onClick = { this.props.onClick }>
|
||||||
|
<Tooltip
|
||||||
|
description = { this.props.tooltip }
|
||||||
|
position = { this.props.tooltipPosition }>
|
||||||
|
{ children }
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the icon of this {@code ToolbarButton}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
_renderIcon() {
|
||||||
|
return (
|
||||||
|
<div className = 'toolbox-icon'>
|
||||||
|
<i className = { this.props.iconName } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolbarButtonV2;
|
|
@ -4,26 +4,16 @@ import React, { Component } from 'react';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import {
|
|
||||||
AUDIO_MUTE,
|
|
||||||
VIDEO_MUTE,
|
|
||||||
createToolbarEvent,
|
|
||||||
sendAnalytics
|
|
||||||
} from '../../analytics';
|
|
||||||
import { toggleAudioOnly } from '../../base/conference';
|
import { toggleAudioOnly } from '../../base/conference';
|
||||||
import {
|
import {
|
||||||
MEDIA_TYPE,
|
MEDIA_TYPE,
|
||||||
setAudioMuted,
|
toggleCameraFacingMode
|
||||||
setVideoMuted,
|
|
||||||
toggleCameraFacingMode,
|
|
||||||
VIDEO_MUTISM_AUTHORITY
|
|
||||||
} from '../../base/media';
|
} from '../../base/media';
|
||||||
import { Container } from '../../base/react';
|
import { Container } from '../../base/react';
|
||||||
import {
|
import {
|
||||||
isNarrowAspectRatio,
|
isNarrowAspectRatio,
|
||||||
makeAspectRatioAware
|
makeAspectRatioAware
|
||||||
} from '../../base/responsive-ui';
|
} from '../../base/responsive-ui';
|
||||||
import { ColorPalette } from '../../base/styles';
|
|
||||||
import {
|
import {
|
||||||
EnterPictureInPictureToolbarButton
|
EnterPictureInPictureToolbarButton
|
||||||
} from '../../mobile/picture-in-picture';
|
} from '../../mobile/picture-in-picture';
|
||||||
|
@ -39,6 +29,8 @@ import AudioRouteButton from './AudioRouteButton';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import ToolbarButton from './ToolbarButton';
|
import ToolbarButton from './ToolbarButton';
|
||||||
|
|
||||||
|
import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The indicator which determines (at bundle time) whether there should be a
|
* The indicator which determines (at bundle time) whether there should be a
|
||||||
* {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
|
* {@code ToolbarButton} in {@code Toolbox} to expose the functionality of the
|
||||||
|
@ -118,20 +110,6 @@ type Props = {
|
||||||
* Implements the conference toolbox on React Native.
|
* Implements the conference toolbox on React Native.
|
||||||
*/
|
*/
|
||||||
class Toolbox extends Component<Props> {
|
class Toolbox extends Component<Props> {
|
||||||
/**
|
|
||||||
* Initializes a new {@code Toolbox} instance.
|
|
||||||
*
|
|
||||||
* @param {Props} props - The read-only React {@code Component} props with
|
|
||||||
* which the new instance is to be initialized.
|
|
||||||
*/
|
|
||||||
constructor(props: Props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once per instance.
|
|
||||||
this._onToggleAudio = this._onToggleAudio.bind(this);
|
|
||||||
this._onToggleVideo = this._onToggleVideo.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements React's {@link Component#render()}.
|
* Implements React's {@link Component#render()}.
|
||||||
*
|
*
|
||||||
|
@ -194,64 +172,6 @@ class Toolbox extends Component<Props> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_onToggleAudio: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches an action to toggle the mute state of the audio/microphone.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onToggleAudio() {
|
|
||||||
const mute = !this.props._audioMuted;
|
|
||||||
|
|
||||||
sendAnalytics(createToolbarEvent(
|
|
||||||
AUDIO_MUTE,
|
|
||||||
{
|
|
||||||
enable: mute
|
|
||||||
}));
|
|
||||||
|
|
||||||
// The user sees the reality i.e. the state of base/tracks and intends
|
|
||||||
// to change reality by tapping on the respective button i.e. the user
|
|
||||||
// sets the state of base/media. Whether the user's intention will turn
|
|
||||||
// into reality is a whole different story which is of no concern to the
|
|
||||||
// tapping.
|
|
||||||
this.props.dispatch(
|
|
||||||
setAudioMuted(
|
|
||||||
mute,
|
|
||||||
VIDEO_MUTISM_AUTHORITY.USER,
|
|
||||||
/* ensureTrack */ true));
|
|
||||||
}
|
|
||||||
|
|
||||||
_onToggleVideo: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches an action to toggle the mute state of the video/camera.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
_onToggleVideo() {
|
|
||||||
const mute = !this.props._videoMuted;
|
|
||||||
|
|
||||||
sendAnalytics(createToolbarEvent(
|
|
||||||
VIDEO_MUTE,
|
|
||||||
{
|
|
||||||
enable: mute
|
|
||||||
}));
|
|
||||||
|
|
||||||
// The user sees the reality i.e. the state of base/tracks and intends
|
|
||||||
// to change reality by tapping on the respective button i.e. the user
|
|
||||||
// sets the state of base/media. Whether the user's intention will turn
|
|
||||||
// into reality is a whole different story which is of no concern to the
|
|
||||||
// tapping.
|
|
||||||
this.props.dispatch(
|
|
||||||
setVideoMuted(
|
|
||||||
!this.props._videoMuted,
|
|
||||||
VIDEO_MUTISM_AUTHORITY.USER,
|
|
||||||
/* ensureTrack */ true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the toolbar which contains the primary buttons such as hangup,
|
* Renders the toolbar which contains the primary buttons such as hangup,
|
||||||
* audio and video mute.
|
* audio and video mute.
|
||||||
|
@ -269,24 +189,9 @@ class Toolbox extends Component<Props> {
|
||||||
<View
|
<View
|
||||||
key = 'primaryToolbar'
|
key = 'primaryToolbar'
|
||||||
style = { styles.primaryToolbar }>
|
style = { styles.primaryToolbar }>
|
||||||
<ToolbarButton
|
<AudioMuteButton buttonStyles = { audioButtonStyles } />
|
||||||
iconName = { audioButtonStyles.iconName }
|
<HangupButton />
|
||||||
iconStyle = { audioButtonStyles.iconStyle }
|
<VideoMuteButton buttonStyles = { videoButtonStyles } />
|
||||||
onClick = { this._onToggleAudio }
|
|
||||||
style = { audioButtonStyles.style } />
|
|
||||||
<ToolbarButton
|
|
||||||
accessibilityLabel = 'Hangup'
|
|
||||||
iconName = 'hangup'
|
|
||||||
iconStyle = { styles.whitePrimaryToolbarButtonIcon }
|
|
||||||
onClick = { this.props._onHangup }
|
|
||||||
style = { styles.hangup }
|
|
||||||
underlayColor = { ColorPalette.buttonUnderlay } />
|
|
||||||
<ToolbarButton
|
|
||||||
disabled = { this.props._audioOnly }
|
|
||||||
iconName = { videoButtonStyles.iconName }
|
|
||||||
iconStyle = { videoButtonStyles.iconStyle }
|
|
||||||
onClick = { this._onToggleVideo }
|
|
||||||
style = { videoButtonStyles.style } />
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setDefaultToolboxButtons,
|
setDefaultToolboxButtons
|
||||||
setToolboxAlwaysVisible
|
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
import {
|
import {
|
||||||
abstractMapStateToProps
|
abstractMapStateToProps
|
||||||
|
@ -39,11 +38,6 @@ class Toolbox extends Component<*> {
|
||||||
*/
|
*/
|
||||||
_setDefaultToolboxButtons: PropTypes.func,
|
_setDefaultToolboxButtons: PropTypes.func,
|
||||||
|
|
||||||
/**
|
|
||||||
* Handler dispatching reset always visible toolbox action.
|
|
||||||
*/
|
|
||||||
_setToolboxAlwaysVisible: PropTypes.func,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents conference subject.
|
* Represents conference subject.
|
||||||
*/
|
*/
|
||||||
|
@ -67,8 +61,6 @@ class Toolbox extends Component<*> {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.props._setToolboxAlwaysVisible();
|
|
||||||
|
|
||||||
// FIXME The redux action SET_DEFAULT_TOOLBOX_BUTTONS and related source
|
// FIXME The redux action SET_DEFAULT_TOOLBOX_BUTTONS and related source
|
||||||
// code such as the redux action creator setDefaultToolboxButtons and
|
// code such as the redux action creator setDefaultToolboxButtons and
|
||||||
// _setDefaultToolboxButtons were introduced to solve the following bug
|
// _setDefaultToolboxButtons were introduced to solve the following bug
|
||||||
|
@ -168,8 +160,7 @@ class Toolbox extends Component<*> {
|
||||||
*
|
*
|
||||||
* @param {Function} dispatch - Redux action dispatcher.
|
* @param {Function} dispatch - Redux action dispatcher.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _setDefaultToolboxButtons: Function,
|
* _setDefaultToolboxButtons: Function
|
||||||
* _setToolboxAlwaysVisible: Function
|
|
||||||
* }}
|
* }}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
@ -182,18 +173,6 @@ function _mapDispatchToProps(dispatch: Function): Object {
|
||||||
*/
|
*/
|
||||||
_setDefaultToolboxButtons() {
|
_setDefaultToolboxButtons() {
|
||||||
dispatch(setDefaultToolboxButtons());
|
dispatch(setDefaultToolboxButtons());
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches a (redux) action to reset the permanent visibility of
|
|
||||||
* the Toolbox.
|
|
||||||
*
|
|
||||||
* @returns {Object} Dispatched action.
|
|
||||||
*/
|
|
||||||
_setToolboxAlwaysVisible() {
|
|
||||||
dispatch(setToolboxAlwaysVisible(
|
|
||||||
config.alwaysVisibleToolbar === true
|
|
||||||
|| interfaceConfig.filmStripOnly));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||||
|
import { translate } from '../../base/i18n';
|
||||||
|
import { openDeviceSelectionDialog } from '../../device-selection';
|
||||||
|
|
||||||
|
import ToolbarButtonV2 from './ToolbarButtonV2';
|
||||||
|
import { AudioMuteButton, HangupButton, VideoMuteButton } from './buttons';
|
||||||
|
|
||||||
|
declare var interfaceConfig: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the conference toolbox on React/Web for filmstrip only mode.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class ToolboxFilmstrip extends Component<*> {
|
||||||
|
_visibleButtons: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code ToolboxFilmstrip} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._visibleButtons = new Set(interfaceConfig.TOOLBAR_BUTTONS);
|
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once per instance.
|
||||||
|
this._onToolbarOpenSettings = this._onToolbarOpenSettings.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = 'filmstrip-toolbox'>
|
||||||
|
{ this._shouldShowButton('microphone')
|
||||||
|
&& <AudioMuteButton tooltipPosition = 'left' /> }
|
||||||
|
{ this._shouldShowButton('camera')
|
||||||
|
&& <VideoMuteButton tooltipPosition = 'left' /> }
|
||||||
|
{ this._shouldShowButton('fodeviceselection')
|
||||||
|
&& <ToolbarButtonV2
|
||||||
|
iconName = 'icon-settings'
|
||||||
|
onClick = { this._onToolbarOpenSettings }
|
||||||
|
tooltip = { t('toolbar.Settings') }
|
||||||
|
tooltipPosition = 'left' /> }
|
||||||
|
{ this._shouldShowButton('hangup')
|
||||||
|
&& <HangupButton tooltipPosition = 'left' /> }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarOpenSettings: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics toolbar event for and dispatches an action to open
|
||||||
|
* the device selection popup dialog.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToolbarOpenSettings() {
|
||||||
|
sendAnalytics(createToolbarEvent('filmstrip.only.device.selection'));
|
||||||
|
|
||||||
|
this.props.dispatch(openDeviceSelectionDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
_shouldShowButton: (string) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if a button name has been explicitly configured to be displayed.
|
||||||
|
*
|
||||||
|
* @param {string} buttonName - The name of the button, as expected in
|
||||||
|
* {@link intefaceConfig}.
|
||||||
|
* @private
|
||||||
|
* @returns {boolean} True if the button should be displayed.
|
||||||
|
*/
|
||||||
|
_shouldShowButton(buttonName) {
|
||||||
|
return this._visibleButtons.has(buttonName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect()(ToolboxFilmstrip));
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,87 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AUDIO_MUTE,
|
||||||
|
createToolbarEvent,
|
||||||
|
sendAnalytics
|
||||||
|
} from '../../../analytics';
|
||||||
|
import {
|
||||||
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
|
setAudioMuted
|
||||||
|
} from '../../../base/media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract implementation of a button for toggling audio mute.
|
||||||
|
*/
|
||||||
|
export default class AbstractAudioMuteButton extends Component<*> {
|
||||||
|
/**
|
||||||
|
* {@code AbstractAudioMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Whether or not the local microphone is muted.
|
||||||
|
*/
|
||||||
|
_audioMuted: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to toggle audio mute.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code AbstractAudioMuteButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once per instance.
|
||||||
|
this._onToolbarToggleAudio = this._onToolbarToggleAudio.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to toggle audio mute.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_doToggleAudio() {
|
||||||
|
// The user sees the reality i.e. the state of base/tracks and intends
|
||||||
|
// to change reality by tapping on the respective button i.e. the user
|
||||||
|
// sets the state of base/media. Whether the user's intention will turn
|
||||||
|
// into reality is a whole different story which is of no concern to the
|
||||||
|
// tapping.
|
||||||
|
this.props.dispatch(
|
||||||
|
setAudioMuted(
|
||||||
|
!this.props._audioMuted,
|
||||||
|
VIDEO_MUTISM_AUTHORITY.USER,
|
||||||
|
/* ensureTrack */ true));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarToggleAudio: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics toolbar event and dispatches an action for toggling
|
||||||
|
* audio mute.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToolbarToggleAudio() {
|
||||||
|
sendAnalytics(createToolbarEvent(
|
||||||
|
AUDIO_MUTE,
|
||||||
|
{
|
||||||
|
enable: !this.props._audioMuted
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._doToggleAudio();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createToolbarEvent,
|
||||||
|
sendAnalytics
|
||||||
|
} from '../../../analytics';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract implementation of a button for leaving the conference.
|
||||||
|
*/
|
||||||
|
export default class AbstractHangupButton extends Component<*> {
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code AbstractHangupButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once per instance.
|
||||||
|
this._onToolbarHangup = this._onToolbarHangup.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action for leaving the current conference.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_doHangup() {
|
||||||
|
/* to be implemented by descendants */
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarHangup: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics toolbar event and dispatches an action for leaving
|
||||||
|
* the current conference.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToolbarHangup() {
|
||||||
|
sendAnalytics(createToolbarEvent('hangup'));
|
||||||
|
|
||||||
|
this._doHangup();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Component } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
VIDEO_MUTE,
|
||||||
|
createToolbarEvent,
|
||||||
|
sendAnalytics
|
||||||
|
} from '../../../analytics';
|
||||||
|
import {
|
||||||
|
VIDEO_MUTISM_AUTHORITY,
|
||||||
|
setVideoMuted
|
||||||
|
} from '../../../base/media';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract implementation of a button for toggling video mute.
|
||||||
|
*/
|
||||||
|
export default class AbstractVideoMuteButton extends Component<*> {
|
||||||
|
/**
|
||||||
|
* {@code AbstractVideoMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Whether or not the local camera is muted.
|
||||||
|
*/
|
||||||
|
_videoMuted: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to toggle video mute.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code AbstractVideoMuteButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handler so it is only bound once per instance.
|
||||||
|
this._onToolbarToggleVideo = this._onToolbarToggleVideo.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action to toggle the mute state of the video/camera.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_doToggleVideo() {
|
||||||
|
// The user sees the reality i.e. the state of base/tracks and intends
|
||||||
|
// to change reality by tapping on the respective button i.e. the user
|
||||||
|
// sets the state of base/media. Whether the user's intention will turn
|
||||||
|
// into reality is a whole different story which is of no concern to the
|
||||||
|
// tapping.
|
||||||
|
this.props.dispatch(
|
||||||
|
setVideoMuted(
|
||||||
|
!this.props._videoMuted,
|
||||||
|
VIDEO_MUTISM_AUTHORITY.USER,
|
||||||
|
/* ensureTrack */ true));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_onToolbarToggleVideo: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics toolbar event and dispatches an action for toggling
|
||||||
|
* video mute.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onToolbarToggleVideo() {
|
||||||
|
sendAnalytics(createToolbarEvent(
|
||||||
|
VIDEO_MUTE,
|
||||||
|
{
|
||||||
|
enable: !this.props._videoMuted
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._doToggleVideo();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||||
|
|
||||||
|
import AbstractAudioMuteButton from './AbstractAudioMuteButton';
|
||||||
|
import ToolbarButton from '../ToolbarButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for toggling audio mute.
|
||||||
|
*
|
||||||
|
* @extends AbstractAudioMuteButton
|
||||||
|
*/
|
||||||
|
export class AudioMuteButton extends AbstractAudioMuteButton {
|
||||||
|
/**
|
||||||
|
* {@code AbstractAudioMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractAudioMuteButton.propTypes,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles to be applied to the button and the icon to show.
|
||||||
|
*/
|
||||||
|
buttonStyles: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { buttonStyles } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
iconName = { buttonStyles.iconName }
|
||||||
|
iconStyle = { buttonStyles.iconStyle }
|
||||||
|
onClick = { this._onToolbarToggleAudio }
|
||||||
|
style = { buttonStyles.style } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarToggleAudio: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated props for the
|
||||||
|
* {@code AudioMuteButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _audioMuted: boolean,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(AudioMuteButton);
|
|
@ -0,0 +1,166 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ACTION_SHORTCUT_TRIGGERED,
|
||||||
|
AUDIO_MUTE,
|
||||||
|
createShortcutEvent,
|
||||||
|
sendAnalytics
|
||||||
|
} from '../../../analytics';
|
||||||
|
import { translate } from '../../../base/i18n';
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||||
|
|
||||||
|
import AbstractAudioMuteButton from './AbstractAudioMuteButton';
|
||||||
|
import ToolbarButtonV2 from '../ToolbarButtonV2';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for toggling audio mute.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
export class AudioMuteButton extends AbstractAudioMuteButton {
|
||||||
|
/**
|
||||||
|
* Default values for {@code AudioMuteButton} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
tooltipPosition: 'top'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code AudioMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractAudioMuteButton.propTypes,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code JitsiConference} for the current conference.
|
||||||
|
*/
|
||||||
|
_conference: PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to update the audio mute status.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the tooltip should display, relative to the button.
|
||||||
|
*/
|
||||||
|
tooltipPosition: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code AudioMuteButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handlers so it is only bound once per instance.
|
||||||
|
this._onShortcutToggleAudio = this._onShortcutToggleAudio.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a keyboard shortcuts for toggling audio mute.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
APP.keyboardshortcut.registerShortcut(
|
||||||
|
'M',
|
||||||
|
null,
|
||||||
|
this._onShortcutToggleAudio,
|
||||||
|
'keyboardShortcuts.mute');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the registered keyboard shortcut handler.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
APP.keyboardshortcut.unregisterShortcut('M');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _audioMuted, _conference, t, tooltipPosition } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButtonV2
|
||||||
|
iconName = { _audioMuted && _conference
|
||||||
|
? 'icon-mic-disabled toggled'
|
||||||
|
: 'icon-microphone' }
|
||||||
|
onClick = { this._onToolbarToggleAudio }
|
||||||
|
tooltip = { t('toolbar.mute') }
|
||||||
|
tooltipPosition = { tooltipPosition } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_doToggleAudio: () => void;
|
||||||
|
|
||||||
|
_onShortcutToggleAudio: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics keyboard shortcut event and dispatches an action for
|
||||||
|
* toggling audio mute.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onShortcutToggleAudio() {
|
||||||
|
sendAnalytics(createShortcutEvent(
|
||||||
|
AUDIO_MUTE,
|
||||||
|
ACTION_SHORTCUT_TRIGGERED,
|
||||||
|
{ enable: !this.props._audioMuted }));
|
||||||
|
|
||||||
|
this._doToggleAudio();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarToggleAudio: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated props for the
|
||||||
|
* {@code AudioMuteButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _audioMuted: boolean,
|
||||||
|
* _conference: Object,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_audioMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.AUDIO),
|
||||||
|
_conference: state['features/base/conference'].conference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(AudioMuteButton));
|
|
@ -0,0 +1,63 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { appNavigate } from '../../../app';
|
||||||
|
import { ColorPalette } from '../../../base/styles';
|
||||||
|
|
||||||
|
import AbstractHangupButton from './AbstractHangupButton';
|
||||||
|
import ToolbarButton from '../ToolbarButton';
|
||||||
|
import styles from '../styles';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for leaving the current conference.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
class HangupButton extends AbstractHangupButton {
|
||||||
|
/**
|
||||||
|
* {@code HangupButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Invoked to leave the conference.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
accessibilityLabel = 'Hangup'
|
||||||
|
iconName = 'hangup'
|
||||||
|
iconStyle = { styles.whitePrimaryToolbarButtonIcon }
|
||||||
|
onClick = { this._onToolbarHangup }
|
||||||
|
style = { styles.hangup }
|
||||||
|
underlayColor = { ColorPalette.buttonUnderlay } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action for leaving the current conference.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_doHangup() {
|
||||||
|
this.props.dispatch(appNavigate(undefined));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarHangup: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect()(HangupButton);
|
|
@ -0,0 +1,82 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { disconnect } from '../../../base/connection';
|
||||||
|
import { translate } from '../../../base/i18n';
|
||||||
|
|
||||||
|
import AbstractHangupButton from './AbstractHangupButton';
|
||||||
|
import ToolbarButtonV2 from '../ToolbarButtonV2';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for leaving the current conference.
|
||||||
|
*
|
||||||
|
* @extends Component
|
||||||
|
*/
|
||||||
|
export class HangupButton extends AbstractHangupButton {
|
||||||
|
/**
|
||||||
|
* Default values for {@code HangupButton} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
tooltipPosition: 'top'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code HangupButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
/**
|
||||||
|
* Invoked to trigger conference leave.
|
||||||
|
*/
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the tooltip should display, relative to the button.
|
||||||
|
*/
|
||||||
|
tooltipPosition: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { t, tooltipPosition } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButtonV2
|
||||||
|
iconName = 'icon-hangup'
|
||||||
|
onClick = { this._onToolbarHangup }
|
||||||
|
tooltip = { t('toolbar.hangup') }
|
||||||
|
tooltipPosition = { tooltipPosition } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarHangup: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an action for leaving the current conference.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_doHangup() {
|
||||||
|
this.props.dispatch(disconnect(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect()(HangupButton));
|
|
@ -0,0 +1,82 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||||
|
|
||||||
|
import AbstractVideoMuteButton from './AbstractVideoMuteButton';
|
||||||
|
import ToolbarButton from '../ToolbarButton';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for toggling video mute.
|
||||||
|
*
|
||||||
|
* @extends AbstractVideoMuteButton
|
||||||
|
*/
|
||||||
|
class VideoMuteButton extends AbstractVideoMuteButton {
|
||||||
|
/**
|
||||||
|
* {@code VideoMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractVideoMuteButton.propTypes,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the local participant is current in audio only mode.
|
||||||
|
* Video mute toggling is disabled in audio only mode.
|
||||||
|
*/
|
||||||
|
_audioOnly: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles to be applied to the button and the icon to show.
|
||||||
|
*/
|
||||||
|
buttonStyles: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _audioOnly, buttonStyles } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButton
|
||||||
|
disabled = { _audioOnly }
|
||||||
|
iconName = { buttonStyles.iconName }
|
||||||
|
iconStyle = { buttonStyles.iconStyle }
|
||||||
|
onClick = { this._onToolbarToggleVideo }
|
||||||
|
style = { buttonStyles.style } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarToggleVideo: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated props for the
|
||||||
|
* {@code VideoMuteButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _audioOnly: boolean,
|
||||||
|
* _videoMuted: boolean
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const conference = state['features/base/conference'];
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_audioOnly: Boolean(conference.audioOnly),
|
||||||
|
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(VideoMuteButton);
|
|
@ -0,0 +1,161 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ACTION_SHORTCUT_TRIGGERED,
|
||||||
|
VIDEO_MUTE,
|
||||||
|
createShortcutEvent,
|
||||||
|
sendAnalytics
|
||||||
|
} from '../../../analytics';
|
||||||
|
import { translate } from '../../../base/i18n';
|
||||||
|
import { MEDIA_TYPE } from '../../../base/media';
|
||||||
|
import { isLocalTrackMuted } from '../../../base/tracks';
|
||||||
|
|
||||||
|
import AbstractVideoMuteButton from './AbstractVideoMuteButton';
|
||||||
|
import ToolbarButtonV2 from '../ToolbarButtonV2';
|
||||||
|
|
||||||
|
declare var APP: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that renders a toolbar button for toggling video mute.
|
||||||
|
*
|
||||||
|
* @extends AbstractVideoMuteButton
|
||||||
|
*/
|
||||||
|
export class VideoMuteButton extends AbstractVideoMuteButton {
|
||||||
|
/**
|
||||||
|
* Default values for {@code VideoMuteButton} component's properties.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static defaultProps = {
|
||||||
|
tooltipPosition: 'top'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code VideoMuteButton} component's property types.
|
||||||
|
*
|
||||||
|
* @static
|
||||||
|
*/
|
||||||
|
static propTypes = {
|
||||||
|
...AbstractVideoMuteButton.propTypes,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@code JitsiConference} for the current conference.
|
||||||
|
*/
|
||||||
|
_conference: PropTypes.object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked to obtain translated strings.
|
||||||
|
*/
|
||||||
|
t: PropTypes.func,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where the tooltip should display, relative to the button.
|
||||||
|
*/
|
||||||
|
tooltipPosition: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code VideoMuteButton} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The read-only React {@code Component} props with
|
||||||
|
* which the new instance is to be initialized.
|
||||||
|
*/
|
||||||
|
constructor(props: Object) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
// Bind event handlers so they are only bound once per instance.
|
||||||
|
this._onShortcutToggleVideo = this._onShortcutToggleVideo.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a keyboard shortcuts for toggling video mute.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidMount() {
|
||||||
|
APP.keyboardshortcut.registerShortcut(
|
||||||
|
'V',
|
||||||
|
null,
|
||||||
|
this._onShortcutToggleVideo,
|
||||||
|
'keyboardShortcuts.videoMute');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the registered keyboard shortcut handler.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentWillUnmount() {
|
||||||
|
APP.keyboardshortcut.unregisterShortcut('V');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const { _conference, _videoMuted, t, tooltipPosition } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolbarButtonV2
|
||||||
|
iconName = { _videoMuted && _conference
|
||||||
|
? 'icon-camera-disabled toggled'
|
||||||
|
: 'icon-camera' }
|
||||||
|
onClick = { this._onToolbarToggleVideo }
|
||||||
|
tooltip = { t('toolbar.videomute') }
|
||||||
|
tooltipPosition = { tooltipPosition } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_doToggleVideo: () => void;
|
||||||
|
|
||||||
|
_onShortcutToggleVideo: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an analytics keyboard shortcut event for and dispatches an action
|
||||||
|
* for toggling video mute.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onShortcutToggleVideo() {
|
||||||
|
sendAnalytics(createShortcutEvent(
|
||||||
|
VIDEO_MUTE,
|
||||||
|
ACTION_SHORTCUT_TRIGGERED,
|
||||||
|
{ enable: !this.props._videoMuted }));
|
||||||
|
|
||||||
|
this._doToggleVideo();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onToolbarToggleVideo: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the Redux state to the associated props for the
|
||||||
|
* {@code AudioMuteButton} component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {{
|
||||||
|
* _conference: Object,
|
||||||
|
* _videoMuted: boolean,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state) {
|
||||||
|
const tracks = state['features/base/tracks'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_conference: state['features/base/conference'].conference,
|
||||||
|
_videoMuted: isLocalTrackMuted(tracks, MEDIA_TYPE.VIDEO)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default translate(connect(_mapStateToProps)(VideoMuteButton));
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as AudioMuteButton } from './AudioMuteButton';
|
||||||
|
export { default as HangupButton } from './HangupButton';
|
||||||
|
export { default as VideoMuteButton } from './VideoMuteButton';
|
|
@ -1,4 +1,7 @@
|
||||||
export { default as ToolbarButton } from './ToolbarButton';
|
export { default as ToolbarButton } from './ToolbarButton';
|
||||||
|
export { default as ToolbarButtonV2 } from './ToolbarButtonV2';
|
||||||
export { default as ToolbarButtonWithDialog }
|
export { default as ToolbarButtonWithDialog }
|
||||||
from './ToolbarButtonWithDialog';
|
from './ToolbarButtonWithDialog';
|
||||||
export { default as Toolbox } from './Toolbox';
|
export { default as Toolbox } from './Toolbox';
|
||||||
|
export { default as ToolboxFilmstrip } from './ToolboxFilmstrip';
|
||||||
|
export { default as ToolboxV2 } from './ToolboxV2';
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { setFullScreen } from '../toolbox';
|
||||||
import {
|
import {
|
||||||
ACTION_SHORTCUT_TRIGGERED as TRIGGERED,
|
ACTION_SHORTCUT_TRIGGERED as TRIGGERED,
|
||||||
AUDIO_MUTE,
|
AUDIO_MUTE,
|
||||||
|
@ -10,6 +11,10 @@ import {
|
||||||
createToolbarEvent,
|
createToolbarEvent,
|
||||||
sendAnalytics
|
sendAnalytics
|
||||||
} from '../analytics';
|
} from '../analytics';
|
||||||
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
|
participantUpdated
|
||||||
|
} from '../base/participants';
|
||||||
import { ParticipantCounter } from '../contact-list';
|
import { ParticipantCounter } from '../contact-list';
|
||||||
import { openDeviceSelectionDialog } from '../device-selection';
|
import { openDeviceSelectionDialog } from '../device-selection';
|
||||||
import { InfoDialogButton } from '../invite';
|
import { InfoDialogButton } from '../invite';
|
||||||
|
@ -252,33 +257,37 @@ export default function getDefaultButtons() {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
id: 'toolbar_button_fullScreen',
|
id: 'toolbar_button_fullScreen',
|
||||||
onClick() {
|
onClick() {
|
||||||
// TODO: why is the fullscreen button handled differently than
|
const state = APP.store.getState();
|
||||||
// the fullscreen keyboard shortcut (one results in a direct
|
const isFullScreen = Boolean(
|
||||||
// call to toggleFullScreen, while the other fires an
|
state['features/toolbox'].fullScreen);
|
||||||
// UIEvents.TOGGLE_FULLSCREEN event)?
|
|
||||||
|
|
||||||
// The 'enable' attribute is set to true if the action resulted
|
// The 'enable' attribute is set to true if the action resulted
|
||||||
// in fullscreen mode being enabled.
|
// in fullscreen mode being enabled.
|
||||||
sendAnalytics(createToolbarEvent(
|
sendAnalytics(createToolbarEvent(
|
||||||
'toggle.fullscreen',
|
'toggle.fullscreen',
|
||||||
{
|
{
|
||||||
enable: !APP.UI.isFullScreen()
|
enable: !isFullScreen
|
||||||
}));
|
}));
|
||||||
|
|
||||||
APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
|
APP.store.dispatch(setFullScreen(!isFullScreen));
|
||||||
},
|
},
|
||||||
shortcut: 'S',
|
shortcut: 'S',
|
||||||
shortcutAttr: 'toggleFullscreenPopover',
|
shortcutAttr: 'toggleFullscreenPopover',
|
||||||
shortcutDescription: 'keyboardShortcuts.fullScreen',
|
shortcutDescription: 'keyboardShortcuts.fullScreen',
|
||||||
shortcutFunc() {
|
shortcutFunc() {
|
||||||
|
const state = APP.store.getState();
|
||||||
|
const isFullScreen = Boolean(
|
||||||
|
state['features/toolbox'].fullScreen);
|
||||||
|
|
||||||
// The 'enable' attribute is set to true if the action resulted
|
// The 'enable' attribute is set to true if the action resulted
|
||||||
// in fullscreen mode being enabled.
|
// in fullscreen mode being enabled.
|
||||||
sendAnalytics(createShortcutEvent(
|
sendAnalytics(createShortcutEvent(
|
||||||
'toggle.fullscreen',
|
'toggle.fullscreen',
|
||||||
{
|
{
|
||||||
enable: !APP.UI.isFullScreen()
|
enable: !isFullScreen
|
||||||
}));
|
}));
|
||||||
APP.UI.toggleFullScreen();
|
|
||||||
|
APP.store.dispatch(setFullScreen(!isFullScreen));
|
||||||
},
|
},
|
||||||
tooltipKey: 'toolbar.fullscreen'
|
tooltipKey: 'toolbar.fullscreen'
|
||||||
},
|
},
|
||||||
|
@ -394,27 +403,44 @@ export default function getDefaultButtons() {
|
||||||
id: 'toolbar_button_raisehand',
|
id: 'toolbar_button_raisehand',
|
||||||
onClick() {
|
onClick() {
|
||||||
// TODO: reduce duplication with shortcutFunc below.
|
// TODO: reduce duplication with shortcutFunc below.
|
||||||
|
const localParticipant
|
||||||
|
= getLocalParticipant(APP.store.getState());
|
||||||
|
const currentRaisedHand = localParticipant.raisedHand;
|
||||||
|
|
||||||
// The 'enable' attribute is set to true if the pressing of the
|
// The 'enable' attribute is set to true if the pressing of the
|
||||||
// shortcut resulted in the hand being raised, and to false
|
// shortcut resulted in the hand being raised, and to false
|
||||||
// if it resulted in the hand being 'lowered'.
|
// if it resulted in the hand being 'lowered'.
|
||||||
sendAnalytics(createToolbarEvent(
|
sendAnalytics(createToolbarEvent(
|
||||||
'raise.hand',
|
'raise.hand',
|
||||||
{ enable: !APP.conference.isHandRaised }));
|
{ enable: !currentRaisedHand }));
|
||||||
APP.conference.maybeToggleRaisedHand();
|
|
||||||
|
APP.store.dispatch(participantUpdated({
|
||||||
|
id: localParticipant.id,
|
||||||
|
local: true,
|
||||||
|
raisedHand: !currentRaisedHand
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
shortcut: 'R',
|
shortcut: 'R',
|
||||||
shortcutAttr: 'raiseHandPopover',
|
shortcutAttr: 'raiseHandPopover',
|
||||||
shortcutDescription: 'keyboardShortcuts.raiseHand',
|
shortcutDescription: 'keyboardShortcuts.raiseHand',
|
||||||
shortcutFunc() {
|
shortcutFunc() {
|
||||||
|
const localParticipant
|
||||||
|
= getLocalParticipant(APP.store.getState());
|
||||||
|
const currentRaisedHand = localParticipant.raisedHand;
|
||||||
|
|
||||||
// The 'enable' attribute is set to true if the pressing of the
|
// The 'enable' attribute is set to true if the pressing of the
|
||||||
// shortcut resulted in the hand being raised, and to false
|
// shortcut resulted in the hand being raised, and to false
|
||||||
// if it resulted in the hand being 'lowered'.
|
// if it resulted in the hand being 'lowered'.
|
||||||
sendAnalytics(createShortcutEvent(
|
sendAnalytics(createShortcutEvent(
|
||||||
'toggle.raise.hand',
|
'toggle.raise.hand',
|
||||||
TRIGGERED,
|
TRIGGERED,
|
||||||
{ enable: !APP.conference.isHandRaised }));
|
{ enable: !currentRaisedHand }));
|
||||||
APP.conference.maybeToggleRaisedHand();
|
|
||||||
|
APP.store.dispatch(participantUpdated({
|
||||||
|
id: localParticipant.id,
|
||||||
|
local: true,
|
||||||
|
raisedHand: !currentRaisedHand
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
tooltipKey: 'toolbar.raiseHand'
|
tooltipKey: 'toolbar.raiseHand'
|
||||||
},
|
},
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue