feat(embed) implement embed meeting feature
This commit is contained in:
parent
28094947a7
commit
b1e12d33ab
|
@ -21,6 +21,7 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 292px;
|
||||
margin-right: 16px;
|
||||
|
||||
&.selected {
|
||||
font-weight: 600;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -47,10 +47,6 @@
|
|||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
|
||||
& > span {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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! What’s 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",
|
||||
|
|
|
@ -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 |
|
@ -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';
|
||||
|
|
|
@ -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));
|
|
@ -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));
|
|
@ -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);
|
|
@ -0,0 +1 @@
|
|||
export { default as EmbedMeetingDialog } from './EmbedMeetingDialog';
|
|
@ -0,0 +1 @@
|
|||
export * from './components';
|
|
@ -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 } />
|
||||
|
|
|
@ -146,7 +146,6 @@ function InviteByEmailSection({ inviteSubject, inviteText, t }: Props) {
|
|||
{renderEmailIcons()}
|
||||
</div>
|
||||
</div>
|
||||
<div className = 'invite-more-dialog separator' />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue