Quellcode durchsuchen

feat(embed) implement embed meeting feature

master
Tudor-Ovidiu Avram vor 5 Jahren
Ursprung
Commit
b1e12d33ab

+ 1
- 0
css/buttons/copy.scss Datei anzeigen

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

+ 1
- 0
css/main.scss Datei anzeigen

37
 @import 'modals/desktop-picker/desktop-picker';
37
 @import 'modals/desktop-picker/desktop-picker';
38
 @import 'modals/device-selection/device-selection';
38
 @import 'modals/device-selection/device-selection';
39
 @import 'modals/dialog';
39
 @import 'modals/dialog';
40
+@import 'modals/embed-meeting/embed-meeting';
40
 @import 'modals/feedback/feedback';
41
 @import 'modals/feedback/feedback';
41
 @import 'modals/invite/info';
42
 @import 'modals/invite/info';
42
 @import 'modals/settings/settings';
43
 @import 'modals/settings/settings';

+ 59
- 0
css/modals/embed-meeting/_embed-meeting.scss Datei anzeigen

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 Datei anzeigen

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

+ 1
- 1
interface_config.js Datei anzeigen

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

+ 6
- 0
lang/main.json Datei anzeigen

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

+ 4
- 0
react/features/base/icons/svg/code-block.svg Datei anzeigen

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 Datei anzeigen

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

+ 69
- 0
react/features/embed-meeting/components/EmbedMeetingDialog.js Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

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 Datei anzeigen

1
+export { default as EmbedMeetingDialog } from './EmbedMeetingDialog';

+ 1
- 0
react/features/embed-meeting/index.js Datei anzeigen

1
+export * from './components';

+ 3
- 0
react/features/invite/components/add-people-dialog/web/AddPeopleDialog.js Datei anzeigen

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

+ 0
- 1
react/features/invite/components/add-people-dialog/web/InviteByEmailSection.js Datei anzeigen

146
                     {renderEmailIcons()}
146
                     {renderEmailIcons()}
147
                 </div>
147
                 </div>
148
             </div>
148
             </div>
149
-            <div className = 'invite-more-dialog separator' />
150
         </>
149
         </>
151
     );
150
     );
152
 }
151
 }

+ 35
- 0
react/features/toolbox/components/web/Toolbox.js Datei anzeigen

12
 import { translate } from '../../../base/i18n';
12
 import { translate } from '../../../base/i18n';
13
 import {
13
 import {
14
     IconChat,
14
     IconChat,
15
+    IconCodeBlock,
15
     IconExitFullScreen,
16
     IconExitFullScreen,
16
     IconFeedback,
17
     IconFeedback,
17
     IconFullScreen,
18
     IconFullScreen,
33
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
34
 import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
34
 import { VideoBlurButton } from '../../../blur';
35
 import { VideoBlurButton } from '../../../blur';
35
 import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
36
 import { CHAT_SIZE, ChatCounter, toggleChat } from '../../../chat';
37
+import { EmbedMeetingDialog } from '../../../embed-meeting';
36
 import { SharedDocumentButton } from '../../../etherpad';
38
 import { SharedDocumentButton } from '../../../etherpad';
37
 import { openFeedbackDialog } from '../../../feedback';
39
 import { openFeedbackDialog } from '../../../feedback';
38
 import { beginAddPeople } from '../../../invite';
40
 import { beginAddPeople } from '../../../invite';
237
         this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
239
         this._onToolbarOpenInvite = this._onToolbarOpenInvite.bind(this);
238
         this._onToolbarOpenKeyboardShortcuts = this._onToolbarOpenKeyboardShortcuts.bind(this);
240
         this._onToolbarOpenKeyboardShortcuts = this._onToolbarOpenKeyboardShortcuts.bind(this);
239
         this._onToolbarOpenSpeakerStats = this._onToolbarOpenSpeakerStats.bind(this);
241
         this._onToolbarOpenSpeakerStats = this._onToolbarOpenSpeakerStats.bind(this);
242
+        this._onToolbarOpenEmbedMeeting = this._onToolbarOpenEmbedMeeting.bind(this);
240
         this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
243
         this._onToolbarOpenVideoQuality = this._onToolbarOpenVideoQuality.bind(this);
241
         this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
244
         this._onToolbarToggleChat = this._onToolbarToggleChat.bind(this);
242
         this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
245
         this._onToolbarToggleFullScreen = this._onToolbarToggleFullScreen.bind(this);
376
         this.props.dispatch(openFeedbackDialog(_conference));
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
      * Dispatches an action to display {@code KeyboardShortcuts}.
393
      * Dispatches an action to display {@code KeyboardShortcuts}.
381
      *
394
      *
717
         this._doOpenKeyboardShorcuts();
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
     _onToolbarOpenSpeakerStats: () => void;
748
     _onToolbarOpenSpeakerStats: () => void;
721
 
749
 
722
     /**
750
     /**
1017
                     key = 'stats'
1045
                     key = 'stats'
1018
                     onClick = { this._onToolbarOpenSpeakerStats }
1046
                     onClick = { this._onToolbarOpenSpeakerStats }
1019
                     text = { t('toolbar.speakerStats') } />,
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
             this._shouldShowButton('feedback')
1055
             this._shouldShowButton('feedback')
1021
                 && _feedbackConfigured
1056
                 && _feedbackConfigured
1022
                 && <OverflowMenuItem
1057
                 && <OverflowMenuItem

Laden…
Abbrechen
Speichern