feat(embed) implement embed meeting feature

This commit is contained in:
Tudor-Ovidiu Avram 2020-07-23 11:16:40 +03:00 committed by Saúl Ibarra Corretgé
parent 28094947a7
commit b1e12d33ab
16 changed files with 270 additions and 6 deletions

View File

@ -21,6 +21,7 @@
text-overflow: ellipsis;
white-space: nowrap;
max-width: 292px;
margin-right: 16px;
&.selected {
font-weight: 600;

View File

@ -37,6 +37,7 @@ $flagsImagePath: "../images/";
@import 'modals/desktop-picker/desktop-picker';
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';
@import 'modals/embed-meeting/embed-meeting';
@import 'modals/feedback/feedback';
@import 'modals/invite/info';
@import 'modals/settings/settings';

View File

@ -0,0 +1,59 @@
.embed-meeting {
&-dialog {
display: flex;
flex-direction: column;
&-header {
display: flex;
justify-content: space-between;
margin: 16px 16px 24px;
width: calc(100% - 32px);
color: #fff;
font-weight: 600;
font-size: 24px;
line-height: 32px;
& > div > svg {
cursor: pointer;
fill: #A4B8D1;
}
}
}
&-copy {
color: white;
font-size: 15px;
margin-left: auto;
margin-top: 16px;
width: auto;
}
&-code {
background: transparent;
border: 1px solid #A4B8D1;
color: white;
font-size: 15px;
height: 165px;
line-height: 24px;
padding: 8px;
width: 100%;
resize: vertical;
}
&-trigger {
display: flex;
align-items: center;
padding: 8px 8px 8px 16px;
margin-top: 24px;
width: calc(100% - 24px);
height: 24px;
background: #2A3A4B;
border: 1px solid #5E6D7A;
border-radius: 4px;
cursor: pointer;
.jitsi-icon {
margin-right: 20px;
}
}
}

View File

@ -47,10 +47,6 @@
font-size: 15px;
line-height: 24px;
& > span {
font-weight: 600;
}
&.header {
display: flex;
justify-content: space-between;

View File

@ -185,7 +185,7 @@ var interfaceConfig = {
* - 'desktop' controls the "Share your screen" button
*/
TOOLBAR_BUTTONS: [
'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen',
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',

View File

@ -190,6 +190,7 @@
"connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: {{msg}}",
"connecting": "Connecting",
"contactSupport": "Contact support",
"copied": "Copied",
"copy": "Copy",
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
@ -316,6 +317,9 @@
"e2ee": {
"labelToolTip": "Audio and Video Communication on this call is end-to-end encrypted"
},
"embedMeeting": {
"title": "Embed this meeting"
},
"feedback": {
"average": "Average",
"bad": "Bad",
@ -670,6 +674,7 @@
"chat": "Toggle chat window",
"document": "Toggle shared document",
"download": "Download our apps",
"embedMeeting": "Embed meeting",
"e2ee": "End-to-End Encryption",
"feedback": "Leave feedback",
"fullScreen": "Toggle full screen",
@ -718,6 +723,7 @@
"documentOpen": "Open shared document",
"download": "Download our apps",
"e2ee": "End-to-End Encryption",
"embedMeeting": "Embed meeting",
"enterFullScreen": "View full screen",
"enterTileView": "Enter tile view",
"exitFullScreen": "Exit full screen",

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.8716 7.70278C15.4611 7.33332 15.4278 6.70103 15.7972 6.29052C16.1667 5.88001 16.799 5.84673 17.2095 6.21619L22.4652 10.9212C22.9066 11.3184 22.9066 12.0105 22.4652 12.4077L17.2095 17.0394C16.799 17.4089 16.1667 17.3756 15.7972 16.9651C15.4278 16.5546 15.4611 15.9223 15.8716 15.5528L20.3014 11.6645L15.8716 7.70278Z" fill="#A4B8D1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.4651 15.5531C8.87561 15.9225 8.90889 16.5548 8.53943 16.9653C8.16997 17.3759 7.53768 17.4091 7.12717 17.0397L1.87145 12.3347C1.43007 11.9375 1.43007 11.2454 1.87145 10.8481L7.12717 6.21644C7.53768 5.84698 8.16997 5.88026 8.53943 6.29077C8.90889 6.70128 8.87561 7.33357 8.4651 7.70302L4.03526 11.5914L8.4651 15.5531Z" fill="#A4B8D1"/>
</svg>

After

Width:  |  Height:  |  Size: 876 B

View File

@ -20,6 +20,7 @@ export { default as IconCheck } from './check.svg';
export { default as IconClose } from './close.svg';
export { default as IconCloseX } from './close-x.svg';
export { default as IconClosedCaption } from './closed_caption.svg';
export { default as IconCodeBlock } from './code-block.svg';
export { default as IconConnectionActive } from './gsm-bars.svg';
export { default as IconConnectionInactive } from './ninja.svg';
export { default as IconCopy } from './copy.svg';

View File

@ -0,0 +1,69 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import CopyButton from '../../base/buttons/CopyButton';
import { getInviteURL } from '../../base/connection';
import { Dialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import Header from './Header';
type Props = {
/**
* Invoked to obtain translated strings.
*/
t: Function,
/**
* The URL of the conference.
*/
url: string
};
/**
* Allow users to embed a jitsi meeting in an iframe.
*
* @returns {React$Element<any>}
*/
function EmbedMeeting({ t, url }: Props) {
/**
* Get the embed code for a jitsi meeting.
*
* @returns {string} The iframe embed code.
*/
const getEmbedCode = () =>
`<iframe allow="camera; microphone; display-capture" src="${url}`
+ 'allowfullscreen="true" style="height: 100%; width: 100%; border: 0px;"></iframe>';
return (
<Dialog
customHeader = { Header }
hideCancelButton = { true }
submitDisabled = { true }
width = 'small'>
<div className = 'embed-meeting-dialog'>
<textarea
className = 'embed-meeting-code'
readOnly = { true }
value = { getEmbedCode() } />
<CopyButton
className = 'embed-meeting-copy'
displayedText = { t('dialog.copy') }
textOnCopySuccess = { t('dialog.copied') }
textOnHover = { t('dialog.copy') }
textToCopy = { getEmbedCode() } />
</div>
</Dialog>
);
}
const mapStateToProps = state => {
return {
url: getInviteURL(state)
};
};
export default translate(connect(mapStateToProps)(EmbedMeeting));

View File

@ -0,0 +1,50 @@
// @flow
import React from 'react';
import { connect } from 'react-redux';
import { openDialog } from '../../base/dialog';
import { translate } from '../../base/i18n';
import EmbedMeetingDialog from './EmbedMeetingDialog';
type Props = {
/**
* Open the embed meeting dialog
*/
openEmbedDialog: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function,
};
/**
* Component meant to trigger showing the EmbedMeetingDialog.
*
* @returns {React$Element<any>}
*/
function EmbedMeetingTrigger({ t, openEmbedDialog }: Props) {
/**
* Handles opeming the embed dialog.
*
* @returns {void}
*/
function onClick() {
openEmbedDialog(EmbedMeetingDialog);
}
return (
<div
className = 'embed-meeting-trigger'
onClick = { onClick }>
{t('embedMeeting.title')}
</div>
);
}
const mapDispatchToProps = { openEmbedDialog: openDialog };
export default translate(connect(null, mapDispatchToProps)(EmbedMeetingTrigger));

View File

@ -0,0 +1,38 @@
// @flow
import React from 'react';
import { translate } from '../../base/i18n';
import { Icon, IconClose } from '../../base/icons';
type Props = {
/**
* The {@link ModalDialog} closing function.
*/
onClose: Function,
/**
* Invoked to obtain translated strings.
*/
t: Function
};
/**
* Custom header of the {@code EmbedMeetingDialog}.
*
* @returns {React$Element<any>}
*/
function Header({ onClose, t }: Props) {
return (
<div
className = 'embed-meeting-dialog-header'>
{ t('embedMeeting.title') }
<Icon
onClick = { onClose }
src = { IconClose } />
</div>
);
}
export default translate(Header);

View File

@ -0,0 +1 @@
export { default as EmbedMeetingDialog } from './EmbedMeetingDialog';

View File

@ -0,0 +1 @@
export * from './components';

View File

@ -10,6 +10,7 @@ import { translate } from '../../../../base/i18n';
import { JitsiRecordingConstants } from '../../../../base/lib-jitsi-meet';
import { getLocalParticipant } from '../../../../base/participants';
import { connect } from '../../../../base/redux';
import EmbedMeetingTrigger from '../../../../embed-meeting/components/EmbedMeetingTrigger';
import { getActiveSession } from '../../../../recording';
import { updateDialInNumbers } from '../../../actions';
import { _getDefaultPhoneNumber, getInviteText, isAddPeopleEnabled, isDialOutEnabled } from '../../../functions';
@ -151,6 +152,8 @@ function AddPeopleDialog({
<InviteByEmailSection
inviteSubject = { inviteSubject }
inviteText = { invite } />
<EmbedMeetingTrigger />
<div className = 'invite-more-dialog separator' />
{
_liveStreamViewURL
&& <LiveStreamSection liveStreamViewURL = { _liveStreamViewURL } />

View File

@ -146,7 +146,6 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
{renderEmailIcons()}
</div>
</div>
<div className = 'invite-more-dialog separator' />
</>
);
}

View File

@ -12,6 +12,7 @@ import { openDialog, toggleDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import {
IconChat,
IconCodeBlock,
IconExitFullScreen,
IconFeedback,
IconFullScreen,
@ -33,6 +34,7 @@ import { OverflowMenuItem } from '../../../base/toolbox';
import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
import { VideoBlurButton } from '../../../blur';
import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
import { EmbedMeetingDialog } from '../../../embed-meeting';
import { SharedDocumentButton } from '../../../etherpad';
import { openFeedbackDialog } from '../../../feedback';
import { beginAddPeople } from '../../../invite';
@ -237,6 +239,7 @@ class Toolbox extends Component<Props, State> {
this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
this._onToolbarOpenKeyboardShortcuts = this._onToolbarOpenKeyboardShortcuts.bind(this);
this._onToolbarOpenSpeakerStats = this._onToolbarOpenSpeakerStats.bind(this);
this._onToolbarOpenEmbedMeeting = this._onToolbarOpenEmbedMeeting.bind(this);
this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
@ -376,6 +379,16 @@ class Toolbox extends Component<Props, State> {
this.props.dispatch(openFeedbackDialog(_conference));
}
/**
* Callback invoked to display {@code FeedbackDialog}.
*
* @private
* @returns {void}
*/
_doOpenEmbedMeeting() {
this.props.dispatch(openDialog(EmbedMeetingDialog));
}
/**
* Dispatches an action to display {@code KeyboardShortcuts}.
*
@ -717,6 +730,21 @@ class Toolbox extends Component<Props, State> {
this._doOpenKeyboardShorcuts();
}
_onToolbarOpenEmbedMeeting: () => void;
/**
* Creates an analytics toolbar event and dispatches an action for opening
* the embed meeting modal.
*
* @private
* @returns {void}
*/
_onToolbarOpenEmbedMeeting() {
sendAnalytics(createToolbarEvent('embed.meeting'));
this._doOpenEmbedMeeting();
}
_onToolbarOpenSpeakerStats: () => void;
/**
@ -1017,6 +1045,13 @@ class Toolbox extends Component<Props, State> {
key = 'stats'
onClick = { this._onToolbarOpenSpeakerStats }
text = { t('toolbar.speakerStats') } />,
this._shouldShowButton('embedmeeting')
&& <OverflowMenuItem
accessibilityLabel = { t('toolbar.accessibilityLabel.embedMeeting') }
icon = { IconCodeBlock }
key = 'embed'
onClick = { this._onToolbarOpenEmbedMeeting }
text = { t('toolbar.embedMeeting') } />,
this._shouldShowButton('feedback')
&& _feedbackConfigured
&& <OverflowMenuItem