Parcourir la source

feat(embed) implement embed meeting feature

master
Tudor-Ovidiu Avram il y a 4 ans
Parent
révision
b1e12d33ab

+ 1
- 0
css/buttons/copy.scss Voir le fichier

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

+ 1
- 0
css/main.scss Voir le fichier

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

+ 59
- 0
css/modals/embed-meeting/_embed-meeting.scss Voir le fichier

@@ -0,0 +1,59 @@
1
+.embed-meeting {
2
+    &-dialog {
3
+        display: flex;
4
+        flex-direction: column;
5
+
6
+        &-header {
7
+            display: flex;
8
+            justify-content: space-between;
9
+            margin: 16px 16px 24px;
10
+            width: calc(100% - 32px);
11
+            color: #fff;
12
+            font-weight: 600;
13
+            font-size: 24px;
14
+            line-height: 32px;
15
+
16
+            & > div > svg {
17
+                cursor: pointer;
18
+                fill: #A4B8D1;
19
+            }
20
+        }
21
+    }
22
+
23
+    &-copy {
24
+        color: white;
25
+        font-size: 15px;
26
+        margin-left: auto;
27
+        margin-top: 16px;
28
+        width: auto;
29
+    }
30
+
31
+    &-code {
32
+        background: transparent;
33
+        border: 1px solid #A4B8D1;
34
+        color: white;
35
+        font-size: 15px;
36
+        height: 165px;
37
+        line-height: 24px;
38
+        padding: 8px;
39
+        width: 100%;
40
+        resize: vertical;
41
+    }
42
+
43
+    &-trigger {
44
+        display: flex;
45
+        align-items: center;
46
+        padding: 8px 8px 8px 16px;
47
+        margin-top: 24px;
48
+        width: calc(100% - 24px);
49
+        height: 24px;
50
+        background: #2A3A4B;
51
+        border: 1px solid #5E6D7A;
52
+        border-radius: 4px;
53
+        cursor: pointer;
54
+
55
+        .jitsi-icon {
56
+            margin-right: 20px;
57
+        }
58
+    }
59
+}

+ 0
- 4
css/modals/invite/_invite_more.scss Voir le fichier

@@ -47,10 +47,6 @@
47 47
         font-size: 15px;
48 48
         line-height: 24px;
49 49
 
50
-        & > span {
51
-            font-weight: 600;
52
-        }
53
-
54 50
         &.header {
55 51
             display: flex;
56 52
             justify-content: space-between;

+ 1
- 1
interface_config.js Voir le fichier

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

+ 6
- 0
lang/main.json Voir le fichier

@@ -190,6 +190,7 @@
190 190
         "connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: {{msg}}",
191 191
         "connecting": "Connecting",
192 192
         "contactSupport": "Contact support",
193
+        "copied": "Copied",
193 194
         "copy": "Copy",
194 195
         "dismiss": "Dismiss",
195 196
         "displayNameRequired": "Hi! What’s your name?",
@@ -316,6 +317,9 @@
316 317
     "e2ee": {
317 318
         "labelToolTip": "Audio and Video Communication on this call is end-to-end encrypted"
318 319
     },
320
+    "embedMeeting": {
321
+        "title": "Embed this meeting"
322
+    },
319 323
     "feedback": {
320 324
         "average": "Average",
321 325
         "bad": "Bad",
@@ -670,6 +674,7 @@
670 674
             "chat": "Toggle chat window",
671 675
             "document": "Toggle shared document",
672 676
             "download": "Download our apps",
677
+            "embedMeeting": "Embed meeting",
673 678
             "e2ee": "End-to-End Encryption",
674 679
             "feedback": "Leave feedback",
675 680
             "fullScreen": "Toggle full screen",
@@ -718,6 +723,7 @@
718 723
         "documentOpen": "Open shared document",
719 724
         "download": "Download our apps",
720 725
         "e2ee": "End-to-End Encryption",
726
+        "embedMeeting": "Embed meeting",
721 727
         "enterFullScreen": "View full screen",
722 728
         "enterTileView": "Enter tile view",
723 729
         "exitFullScreen": "Exit full screen",

+ 4
- 0
react/features/base/icons/svg/code-block.svg Voir le fichier

@@ -0,0 +1,4 @@
1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<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"/>
3
+<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"/>
4
+</svg>

+ 1
- 0
react/features/base/icons/svg/index.js Voir le fichier

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

+ 69
- 0
react/features/embed-meeting/components/EmbedMeetingDialog.js Voir le fichier

@@ -0,0 +1,69 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import CopyButton from '../../base/buttons/CopyButton';
7
+import { getInviteURL } from '../../base/connection';
8
+import { Dialog } from '../../base/dialog';
9
+import { translate } from '../../base/i18n';
10
+
11
+import Header from './Header';
12
+
13
+type Props = {
14
+
15
+    /**
16
+     * Invoked to obtain translated strings.
17
+     */
18
+    t: Function,
19
+
20
+    /**
21
+     * The URL of the conference.
22
+     */
23
+    url: string
24
+};
25
+
26
+/**
27
+ * Allow users to embed a jitsi meeting in an iframe.
28
+ *
29
+ * @returns {React$Element<any>}
30
+ */
31
+function EmbedMeeting({ t, url }: Props) {
32
+    /**
33
+     * Get the embed code for a jitsi meeting.
34
+     *
35
+     * @returns {string} The iframe embed code.
36
+     */
37
+    const getEmbedCode = () =>
38
+        `<iframe allow="camera; microphone; display-capture" src="${url}`
39
+        + 'allowfullscreen="true" style="height: 100%; width: 100%; border: 0px;"></iframe>';
40
+
41
+    return (
42
+        <Dialog
43
+            customHeader = { Header }
44
+            hideCancelButton = { true }
45
+            submitDisabled = { true }
46
+            width = 'small'>
47
+            <div className = 'embed-meeting-dialog'>
48
+                <textarea
49
+                    className = 'embed-meeting-code'
50
+                    readOnly = { true }
51
+                    value = { getEmbedCode() } />
52
+                <CopyButton
53
+                    className = 'embed-meeting-copy'
54
+                    displayedText = { t('dialog.copy') }
55
+                    textOnCopySuccess = { t('dialog.copied') }
56
+                    textOnHover = { t('dialog.copy') }
57
+                    textToCopy = { getEmbedCode() } />
58
+            </div>
59
+        </Dialog>
60
+    );
61
+}
62
+
63
+const mapStateToProps = state => {
64
+    return {
65
+        url: getInviteURL(state)
66
+    };
67
+};
68
+
69
+export default translate(connect(mapStateToProps)(EmbedMeeting));

+ 50
- 0
react/features/embed-meeting/components/EmbedMeetingTrigger.js Voir le fichier

@@ -0,0 +1,50 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { openDialog } from '../../base/dialog';
7
+import { translate } from '../../base/i18n';
8
+
9
+import EmbedMeetingDialog from './EmbedMeetingDialog';
10
+
11
+type Props = {
12
+
13
+    /**
14
+     * Open the embed meeting dialog
15
+     */
16
+    openEmbedDialog: Function,
17
+
18
+    /**
19
+     * Invoked to obtain translated strings.
20
+     */
21
+    t: Function,
22
+};
23
+
24
+/**
25
+ * Component meant to trigger showing the EmbedMeetingDialog.
26
+ *
27
+ * @returns {React$Element<any>}
28
+ */
29
+function EmbedMeetingTrigger({ t, openEmbedDialog }: Props) {
30
+    /**
31
+     * Handles opeming the embed dialog.
32
+     *
33
+     * @returns {void}
34
+     */
35
+    function onClick() {
36
+        openEmbedDialog(EmbedMeetingDialog);
37
+    }
38
+
39
+    return (
40
+        <div
41
+            className = 'embed-meeting-trigger'
42
+            onClick = { onClick }>
43
+            {t('embedMeeting.title')}
44
+        </div>
45
+    );
46
+}
47
+
48
+const mapDispatchToProps = { openEmbedDialog: openDialog };
49
+
50
+export default translate(connect(null, mapDispatchToProps)(EmbedMeetingTrigger));

+ 38
- 0
react/features/embed-meeting/components/Header.js Voir le fichier

@@ -0,0 +1,38 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { translate } from '../../base/i18n';
6
+import { Icon, IconClose } from '../../base/icons';
7
+
8
+type Props = {
9
+
10
+    /**
11
+     * The {@link ModalDialog} closing function.
12
+     */
13
+    onClose: Function,
14
+
15
+    /**
16
+     * Invoked to obtain translated strings.
17
+     */
18
+    t: Function
19
+};
20
+
21
+/**
22
+ * Custom header of the {@code EmbedMeetingDialog}.
23
+ *
24
+ * @returns {React$Element<any>}
25
+ */
26
+function Header({ onClose, t }: Props) {
27
+    return (
28
+        <div
29
+            className = 'embed-meeting-dialog-header'>
30
+            { t('embedMeeting.title') }
31
+            <Icon
32
+                onClick = { onClose }
33
+                src = { IconClose } />
34
+        </div>
35
+    );
36
+}
37
+
38
+export default translate(Header);

+ 1
- 0
react/features/embed-meeting/components/index.js Voir le fichier

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

+ 1
- 0
react/features/embed-meeting/index.js Voir le fichier

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

+ 3
- 0
react/features/invite/components/add-people-dialog/web/AddPeopleDialog.js Voir le fichier

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

+ 0
- 1
react/features/invite/components/add-people-dialog/web/InviteByEmailSection.js Voir le fichier

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

+ 35
- 0
react/features/toolbox/components/web/Toolbox.js Voir le fichier

@@ -12,6 +12,7 @@ import { openDialog, toggleDialog } from '../../../base/dialog';
12 12
 import { translate } from '../../../base/i18n';
13 13
 import {
14 14
     IconChat,
15
+    IconCodeBlock,
15 16
     IconExitFullScreen,
16 17
     IconFeedback,
17 18
     IconFullScreen,
@@ -33,6 +34,7 @@ import { OverflowMenuItem } from '../../../base/toolbox';
33 34
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
34 35
 import { VideoBlurButton } from '../../../blur';
35 36
 import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
37
+import { EmbedMeetingDialog } from '../../../embed-meeting';
36 38
 import { SharedDocumentButton } from '../../../etherpad';
37 39
 import { openFeedbackDialog } from '../../../feedback';
38 40
 import { beginAddPeople } from '../../../invite';
@@ -237,6 +239,7 @@ class Toolbox extends Component<Props, State> {
237 239
         this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
238 240
         this._onToolbarOpenKeyboardShortcuts = this._onToolbarOpenKeyboardShortcuts.bind(this);
239 241
         this._onToolbarOpenSpeakerStats = this._onToolbarOpenSpeakerStats.bind(this);
242
+        this._onToolbarOpenEmbedMeeting = this._onToolbarOpenEmbedMeeting.bind(this);
240 243
         this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
241 244
         this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
242 245
         this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
@@ -376,6 +379,16 @@ class Toolbox extends Component<Props, State> {
376 379
         this.props.dispatch(openFeedbackDialog(_conference));
377 380
     }
378 381
 
382
+    /**
383
+     * Callback invoked to display {@code FeedbackDialog}.
384
+     *
385
+     * @private
386
+     * @returns {void}
387
+     */
388
+    _doOpenEmbedMeeting() {
389
+        this.props.dispatch(openDialog(EmbedMeetingDialog));
390
+    }
391
+
379 392
     /**
380 393
      * Dispatches an action to display {@code KeyboardShortcuts}.
381 394
      *
@@ -717,6 +730,21 @@ class Toolbox extends Component<Props, State> {
717 730
         this._doOpenKeyboardShorcuts();
718 731
     }
719 732
 
733
+    _onToolbarOpenEmbedMeeting: () => void;
734
+
735
+    /**
736
+     * Creates an analytics toolbar event and dispatches an action for opening
737
+     * the embed meeting modal.
738
+     *
739
+     * @private
740
+     * @returns {void}
741
+     */
742
+    _onToolbarOpenEmbedMeeting() {
743
+        sendAnalytics(createToolbarEvent('embed.meeting'));
744
+
745
+        this._doOpenEmbedMeeting();
746
+    }
747
+
720 748
     _onToolbarOpenSpeakerStats: () => void;
721 749
 
722 750
     /**
@@ -1017,6 +1045,13 @@ class Toolbox extends Component<Props, State> {
1017 1045
                     key = 'stats'
1018 1046
                     onClick = { this._onToolbarOpenSpeakerStats }
1019 1047
                     text = { t('toolbar.speakerStats') } />,
1048
+            this._shouldShowButton('embedmeeting')
1049
+                && <OverflowMenuItem
1050
+                    accessibilityLabel = { t('toolbar.accessibilityLabel.embedMeeting') }
1051
+                    icon = { IconCodeBlock }
1052
+                    key = 'embed'
1053
+                    onClick = { this._onToolbarOpenEmbedMeeting }
1054
+                    text = { t('toolbar.embedMeeting') } />,
1020 1055
             this._shouldShowButton('feedback')
1021 1056
                 && _feedbackConfigured
1022 1057
                 && <OverflowMenuItem

Chargement…
Annuler
Enregistrer