Просмотр исходного кода

Add `Install chrome extension` banner (#4996)

master
horymury 4 лет назад
Родитель
Сommit
63a411168e

+ 14
- 0
config.js Просмотреть файл

@@ -385,6 +385,20 @@ var config = {
385 385
         // userRegion: "asia"
386 386
     }
387 387
 
388
+    // Information for the chrome extension banner
389
+    // chromeExtensionBanner: {
390
+    //     // The chrome extension to be installed address
391
+    //     url: 'https://chrome.google.com/webstore/detail/jitsi-meetings/kglhbbefdnlheedjiejgomgmfplipfeb',
392
+
393
+    //     // Extensions info which allows checking if they are installed or not
394
+    //     chromeExtensionsInfo: [
395
+    //         {
396
+    //             id: 'kglhbbefdnlheedjiejgomgmfplipfeb',
397
+    //             path: 'jitsi-logo-48x48.png'
398
+    //         }
399
+    //     ]
400
+    // }
401
+
388 402
     // Local Recording
389 403
     //
390 404
 

+ 5
- 0
css/_base.scss Просмотреть файл

@@ -37,6 +37,11 @@ body {
37 37
     fill: white;
38 38
 }
39 39
 
40
+.jitsi-icon.gray svg {
41
+    fill: #5E6D7A;
42
+    cursor: pointer;
43
+}
44
+
40 45
 /**
41 46
  * AtlasKitThemeProvider sets a background color on an app-wrapping div, thereby
42 47
  * preventing transparency in filmstrip-only mode. The selector chosen to

+ 93
- 0
css/_chrome-extension-banner.scss Просмотреть файл

@@ -0,0 +1,93 @@
1
+.chrome-extension-banner {
2
+  position: fixed;
3
+  width: 406px;
4
+  height: $chromeExtensionBannerHeight;
5
+  background: #FFF;
6
+  box-shadow: 0px 2px 48px rgba(0, 0, 0, 0.25);
7
+  border-radius: 4px;
8
+  z-index: 1000;
9
+  float: right;
10
+  display: flex;
11
+  flex-direction: column;
12
+  padding: 20px 20px;
13
+  top: $chromeExtensionBannerTop;
14
+  right: $chromeExtensionBannerRight;
15
+    &__pos_in_meeting {
16
+      top: $chromeExtensionBannerTopInMeeting;
17
+      right: $chromeExtensionBannerRightInMeeeting;
18
+    }
19
+
20
+    &__container {
21
+      display: flex;
22
+      justify-content: space-between;
23
+      margin-bottom: 16px;
24
+    }
25
+
26
+    &__button-container {
27
+      display: flex;
28
+    }
29
+
30
+    &__checkbox-container {
31
+        display: $chromeExtensionBannerDontShowAgainDisplay;
32
+        margin-left: 45px;
33
+        margin-top: 16px;
34
+    }
35
+
36
+    &__checkbox-label {
37
+        font-size: 14px;
38
+        line-height: 18px;
39
+        display: flex;
40
+        align-items: center;
41
+        letter-spacing: -0.006em;
42
+        color: #1C2025;
43
+    }
44
+
45
+    &__icon-container {
46
+      display: flex;
47
+      background: url('../images/chromeLogo.svg');
48
+      background-repeat: no-repeat;
49
+      width: 27px;
50
+      height: 27px;
51
+    }
52
+
53
+    &__text-container {
54
+      font-size: 14px;
55
+      line-height: 18px;
56
+      display: flex;
57
+      align-items: center;
58
+      letter-spacing: -0.006em;
59
+      color: #151531;
60
+      width: 329px;
61
+    }
62
+
63
+    &__close-container {
64
+      display: flex;
65
+      width: 12px;
66
+      height: 12px;
67
+    }
68
+
69
+    &__gray-close-icon {
70
+      fill: #5E6D7A;
71
+      width: 12px;
72
+      height: 12px;
73
+      cursor: pointer;
74
+    }
75
+
76
+    &__button-open-url {
77
+      background: #0A57EB;
78
+      border-radius: 24px;
79
+      margin-left: 45px;
80
+      width: 236px;
81
+      height: 40px;
82
+      cursor: pointer;
83
+    }
84
+
85
+    &__button-text {
86
+      font-weight: 600;
87
+      font-size: 14px;
88
+      line-height: 40px;
89
+      text-align: center;
90
+      letter-spacing: -0.006em;
91
+      color: #FFFFFF;
92
+    }
93
+}

+ 11
- 0
css/_variables.scss Просмотреть файл

@@ -260,3 +260,14 @@ $deepLinkingMobileButtonFontWeight: bold;
260 260
 $deepLinkingMobileButtonFontSize: inherit;
261 261
 
262 262
 $primaryDeepLinkingMobileButtonBorderRadius: inherit;
263
+
264
+/**
265
+* Chrome extension banner variables.
266
+*/
267
+$chromeExtensionBannerDontShowAgainDisplay: flex;
268
+$chromeExtensionBannerHeight: 128px;
269
+$chromeExtensionBannerTop: 80px;
270
+$chromeExtensionBannerRight: 16px;
271
+$chromeExtensionBannerTopInMeeting: 10px;
272
+$chromeExtensionBannerRightInMeeeting: 10px;
273
+

+ 1
- 0
css/main.scss Просмотреть файл

@@ -85,5 +85,6 @@ $flagsImagePath: "../images/";
85 85
 @import 'third-party-branding/microsoft';
86 86
 @import 'avatar';
87 87
 @import 'promotional-footer';
88
+@import 'chrome-extension-banner';
88 89
 
89 90
 /* Modules END */

+ 9
- 0
images/chromeLogo.svg
Разница между файлами не показана из-за своего большого размера
Просмотреть файл


+ 8
- 1
interface_config.js Просмотреть файл

@@ -199,7 +199,14 @@ var interfaceConfig = {
199 199
     DISABLE_PRESENCE_STATUS: false,
200 200
 
201 201
     // If true, notifications regarding joining/leaving are no longer displayed
202
-    DISABLE_JOIN_LEAVE_NOTIFICATIONS: false
202
+    DISABLE_JOIN_LEAVE_NOTIFICATIONS: false,
203
+  
204
+    /**
205
+    * Decides whether the chrome extension banner should be rendered on the landing page and during the meeting.
206
+    * If this is set to false, the banner will not be rendered at all. If set to true, the check for extension(s)
207
+    * being already installed is done before rendering.
208
+    */
209
+    SHOW_CHROME_EXTENSION_BANNER: false
203 210
 
204 211
     /**
205 212
      * How many columns the tile view can expand to. The respected range is

+ 6
- 2
lang/main.json Просмотреть файл

@@ -59,6 +59,11 @@
59 59
         "title": "Chat",
60 60
         "you": "you"
61 61
     },
62
+    "chromeExtensionBanner": {
63
+        "installExtensionText": "Install the extension for Google Calendar and Office 365 integration",
64
+        "buttonText": "Install Chrome Extension",
65
+        "dontShowAgain": "Don’t show me this again"
66
+    },
62 67
     "connectingOverlay": {
63 68
         "joiningRoom": "Connecting you to your meeting..."
64 69
     },
@@ -277,7 +282,7 @@
277 282
     "dialOut": {
278 283
         "statusMessage": "is now {{status}}"
279 284
     },
280
-    "documentSharing" : {
285
+    "documentSharing": {
281 286
         "title": "Shared Document"
282 287
     },
283 288
     "feedback": {
@@ -630,7 +635,6 @@
630 635
         "lowerYourHand": "Lower your hand",
631 636
         "moreActions": "More actions",
632 637
         "mute": "Mute / Unmute",
633
-
634 638
         "noAudioSignalTitle": "There is no input coming from your mic!",
635 639
         "noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider changing the device.",
636 640
         "noAudioSignalDescSuggestion": "If you did not purposely mute it from system settings or hardware, consider using the following device:",

+ 7
- 0
react/features/base/app/components/BaseApp.js Просмотреть файл

@@ -18,7 +18,9 @@ import { PersistenceRegistry } from '../../storage';
18 18
 
19 19
 import { appWillMount, appWillUnmount } from '../actions';
20 20
 import logger from '../logger';
21
+import { ChromeExtensionBanner } from '../../../chrome-extension-banner';
21 22
 
23
+declare var interfaceConfig: Object;
22 24
 declare var APP: Object;
23 25
 
24 26
 /**
@@ -129,6 +131,11 @@ export default class BaseApp extends Component<*, State> {
129 131
                 <I18nextProvider i18n = { i18next }>
130 132
                     <Provider store = { store }>
131 133
                         <Fragment>
134
+                            {
135
+                                typeof interfaceConfig !== 'undefined'
136
+                                && interfaceConfig.SHOW_CHROME_EXTENSION_BANNER
137
+                                && <ChromeExtensionBanner />
138
+                            }
132 139
                             { this._createMainElement(component) }
133 140
                             <SoundCollection />
134 141
                             { this._createExtraElement() }

+ 0
- 0
react/features/chrome-extension-banner/components/ChromeExtensionBanner.native.js Просмотреть файл


+ 276
- 0
react/features/chrome-extension-banner/components/ChromeExtensionBanner.web.js Просмотреть файл

@@ -0,0 +1,276 @@
1
+// @flow
2
+import React, { PureComponent } from 'react';
3
+import { connect } from '../../base/redux';
4
+import { Icon, IconClose } from '../../base/icons';
5
+import { translate } from '../../base/i18n';
6
+import { getCurrentConference } from '../../base/conference/functions';
7
+
8
+/**
9
+ * Local storage key name for flag telling if user checked 'Don't show again' checkbox on the banner
10
+ * If the user checks this before closing the banner, next time he will access a jitsi domain
11
+ * the banner will not be shown regardless of extensions being installed or not.
12
+ */
13
+const DONT_SHOW_AGAIN_CHECKED = 'hide_chrome_extension_banner';
14
+
15
+/**
16
+ * The type of the React {@code PureComponent} props of {@link ChromeExtensionBanner}.
17
+ */
18
+type Props = {
19
+
20
+    /**
21
+     * Conference data, if any
22
+     */
23
+    conference: Object,
24
+
25
+    /**
26
+     * The url of the chrome extension
27
+     */
28
+    chromeExtensionUrl: string,
29
+
30
+    /**
31
+     * An array containing info for identifying a chrome extension
32
+     */
33
+    chromeExtensionsInfo: Array<Object>,
34
+
35
+    /**
36
+     * Invoked to obtain translated strings.
37
+     */
38
+    t: Function,
39
+};
40
+
41
+/**
42
+ * The type of the React {@link PureComponent} state of {@link ChromeExtensionBanner}.
43
+ */
44
+type State = {
45
+
46
+    /**
47
+     * Keeps the current value of dont show again checkbox
48
+     */
49
+    dontShowAgainChecked: boolean,
50
+
51
+    /**
52
+     * Tells whether user pressed install extension or close button.
53
+     */
54
+    closePressed: boolean,
55
+
56
+    /**
57
+     * Tells whether should show the banner or not based on extension being installed or not.
58
+     */
59
+    shouldShow: boolean,
60
+};
61
+
62
+/**
63
+ * Implements a React {@link PureComponent} which displays a banner having a link to the chrome extension.
64
+ * @class ChromeExtensionBanner
65
+ * @extends PureComponent
66
+ */
67
+class ChromeExtensionBanner extends PureComponent<Props, State> {
68
+    /**
69
+     * Initializes a new {@code ChromeExtensionBanner} instance.
70
+     *
71
+     * @param {Object} props - The read-only React {@code PureComponent} props with
72
+     * which the new instance is to be initialized.
73
+     */
74
+    constructor(props: Props) {
75
+        super(props);
76
+        this.state = {
77
+            dontShowAgainChecked: false,
78
+            closePressed: false,
79
+            shouldShow: false
80
+        };
81
+
82
+        this._onClosePressed = this._onClosePressed.bind(this);
83
+        this._onInstallExtensionClick = this._onInstallExtensionClick.bind(this);
84
+        this._checkExtensionsInstalled = this._checkExtensionsInstalled.bind(this);
85
+        this._shouldNotRender = this._shouldNotRender.bind(this);
86
+        this._onDontShowAgainChange = this._onDontShowAgainChange.bind(this);
87
+    }
88
+
89
+    /**
90
+     * Executed on component update.
91
+     * Checks whether any chrome extension from the config is installed.
92
+     *
93
+     * @inheritdoc
94
+     */
95
+    async componentDidUpdate() {
96
+        const hasExtensions = await this._checkExtensionsInstalled();
97
+
98
+        if (
99
+            hasExtensions
100
+            && hasExtensions.length
101
+            && hasExtensions.every(ext => !ext)
102
+            && !this.state.shouldShow
103
+        ) {
104
+            this.setState({ shouldShow: true }); // eslint-disable-line
105
+        }
106
+    }
107
+
108
+    _onClosePressed: () => void;
109
+
110
+    /**
111
+     * Closes the banner for the current session.
112
+     *
113
+     * @returns {void}
114
+     */
115
+    _onClosePressed() {
116
+        this.setState({ closePressed: true });
117
+    }
118
+
119
+    _onInstallExtensionClick: () => void;
120
+
121
+    /**
122
+     * Opens the chrome extension page.
123
+     *
124
+     * @returns {void}
125
+     */
126
+    _onInstallExtensionClick() {
127
+        window.open(this.props.chromeExtensionUrl);
128
+        this.setState({ closePressed: true });
129
+    }
130
+
131
+    _checkExtensionsInstalled: () => Promise<*>;
132
+
133
+    /**
134
+     * Checks whether the chrome extensions defined in the config file are installed or not.
135
+     *
136
+     * @returns {Promise[]}
137
+     */
138
+    _checkExtensionsInstalled() {
139
+        const isExtensionInstalled = info => new Promise(resolve => {
140
+            const img = new Image();
141
+
142
+            img.src = `chrome-extension://${info.id}/${info.path}`;
143
+            img.onload = function() {
144
+                resolve(true);
145
+            };
146
+            img.onerror = function() {
147
+                resolve(false);
148
+            };
149
+        });
150
+        const extensionInstalledFunction = info => isExtensionInstalled(info);
151
+
152
+        if (!this.props.chromeExtensionsInfo.length) {
153
+            console.warn('Further configuration needed, missing chrome extension(s) info');
154
+        }
155
+
156
+        return Promise.all(
157
+            this.props.chromeExtensionsInfo.map(info => extensionInstalledFunction(info))
158
+        );
159
+    }
160
+
161
+    _shouldNotRender: () => boolean;
162
+
163
+    /**
164
+     * Checks whether the banner should be displayed based on:
165
+     * Whether there is a configuration issue with the chrome extensions data.
166
+     * Whether the user checked don't show again checkbox in a previous session.
167
+     * Whether the user closed the banner.
168
+     * Whether the extension is already installed.
169
+     *
170
+     * @returns {boolean} whether to show the banner or not.
171
+     */
172
+    _shouldNotRender() {
173
+        if (!this.props.chromeExtensionUrl) {
174
+            console.warn('Further configuration needed, missing chrome extension URL');
175
+
176
+            return true;
177
+        }
178
+
179
+        const dontShowAgain = localStorage.getItem(DONT_SHOW_AGAIN_CHECKED) === 'true';
180
+
181
+        return dontShowAgain
182
+        || this.state.closePressed
183
+        || !this.state.shouldShow;
184
+    }
185
+
186
+    _onDontShowAgainChange: (object: Object) => void;
187
+
188
+    /**
189
+    * Handles the current `don't show again` checkbox state.
190
+    *
191
+    * @param {Object} event - Input change event.
192
+    * @returns {void}
193
+    */
194
+    _onDontShowAgainChange(event) {
195
+        this.setState({ dontShowAgainChecked: event.target.checked });
196
+    }
197
+
198
+    /**
199
+     * Implements React's {@link PureComponent#render()}.
200
+     *
201
+     * @inheritdoc
202
+     * @returns {ReactElement}
203
+     */
204
+    render() {
205
+        if (this._shouldNotRender()) {
206
+            if (this.state.dontShowAgainChecked) {
207
+                localStorage.setItem(DONT_SHOW_AGAIN_CHECKED, 'true');
208
+            }
209
+
210
+            return null;
211
+        }
212
+        const { t } = this.props;
213
+        const mainClassNames = this.props.conference
214
+            ? 'chrome-extension-banner chrome-extension-banner__pos_in_meeting'
215
+            : 'chrome-extension-banner';
216
+
217
+        return (
218
+            <div className = { mainClassNames }>
219
+                <div className = 'chrome-extension-banner__container'>
220
+                    <div
221
+                        className = 'chrome-extension-banner__icon-container' />
222
+                    <div
223
+                        className = 'chrome-extension-banner__text-container'>
224
+                        { t('chromeExtensionBanner.installExtensionText') }
225
+                    </div>
226
+                    <div
227
+                        className = 'chrome-extension-banner__close-container'
228
+                        onClick = { this._onClosePressed }>
229
+                        <Icon
230
+                            className = 'gray'
231
+                            size = { 12 }
232
+                            src = { IconClose } />
233
+                    </div>
234
+                </div>
235
+                <div
236
+                    className = 'chrome-extension-banner__button-container'>
237
+                    <div
238
+                        className = 'chrome-extension-banner__button-open-url'
239
+                        onClick = { this._onInstallExtensionClick }>
240
+                        <div
241
+                            className = 'chrome-extension-banner__button-text'>
242
+                            { t('chromeExtensionBanner.buttonText') }
243
+                        </div>
244
+                    </div>
245
+                </div>
246
+                <div className = 'chrome-extension-banner__checkbox-container'>
247
+                    <label className = 'chrome-extension-banner__checkbox-label'>
248
+                        <input
249
+                            checked = { this.state.dontShowAgainChecked }
250
+                            onChange = { this._onDontShowAgainChange }
251
+                            type = 'checkbox' />
252
+                        &nbsp;{ t('chromeExtensionBanner.dontShowAgain') }
253
+                    </label>
254
+                </div>
255
+            </div>
256
+        );
257
+    }
258
+}
259
+
260
+/**
261
+ * Function that maps parts of Redux state tree into component props.
262
+ *
263
+ * @param {Object} state - Redux state.
264
+ * @returns {Object}
265
+ */
266
+const _mapStateToProps = state => {
267
+    const bannerCfg = state['features/base/config'].chromeExtensionBanner || {};
268
+
269
+    return {
270
+        chromeExtensionUrl: bannerCfg.url,
271
+        chromeExtensionsInfo: bannerCfg.chromeExtensionsInfo || [],
272
+        conference: getCurrentConference(state)
273
+    };
274
+};
275
+
276
+export default translate(connect(_mapStateToProps)(ChromeExtensionBanner));

+ 2
- 0
react/features/chrome-extension-banner/components/index.js Просмотреть файл

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

+ 1
- 0
react/features/chrome-extension-banner/index.js Просмотреть файл

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

Загрузка…
Отмена
Сохранить