Browse Source

Adds copy icon next to the meeting url in info dialog.

master
damencho 5 years ago
parent
commit
3e1a008399

+ 17
- 0
css/modals/invite/_info.scss View File

63
         width: -webkit-max-content;
63
         width: -webkit-max-content;
64
         word-break: break-all;
64
         word-break: break-all;
65
         max-width: 400px;
65
         max-width: 400px;
66
+        display: flex;
67
+        align-items: center;
66
     }
68
     }
67
 
69
 
68
     .info-dialog-dial-in {
70
     .info-dialog-dial-in {
86
         cursor: inherit;
88
         cursor: inherit;
87
     }
89
     }
88
 
90
 
91
+    .info-dialog-url-icon {
92
+        display: inline-block;
93
+        margin-left: 5px;
94
+
95
+        svg {
96
+            cursor: pointer;
97
+        }
98
+    }
99
+
89
     .info-dialog-title {
100
     .info-dialog-title {
90
         font-weight: bold;
101
         font-weight: bold;
91
         margin-bottom: 10px;
102
         margin-bottom: 10px;
214
         -moz-user-select: text;
225
         -moz-user-select: text;
215
         -webkit-user-select: text;
226
         -webkit-user-select: text;
216
     }
227
     }
228
+
229
+    .info-dialog-url-text-unselectable {
230
+        user-select: none;
231
+        -moz-user-select: none;
232
+        -webkit-user-select: none;
233
+    }
217
 }
234
 }

+ 7
- 0
react/features/base/icons/components/Icon.js View File

23
      */
23
      */
24
     id?: string,
24
     id?: string,
25
 
25
 
26
+    /**
27
+     * Function to invoke on click.
28
+     */
29
+    onClick?: Function,
30
+
26
     /**
31
     /**
27
      * The size of the icon (if not provided by the style object).
32
      * The size of the icon (if not provided by the style object).
28
      */
33
      */
53
         className,
58
         className,
54
         color,
59
         color,
55
         id,
60
         id,
61
+        onClick,
56
         size,
62
         size,
57
         src: IconComponent,
63
         src: IconComponent,
58
         style
64
         style
69
     return (
75
     return (
70
         <Container
76
         <Container
71
             className = { `jitsi-icon ${className}` }
77
             className = { `jitsi-icon ${className}` }
78
+            onClick = { onClick }
72
             style = { restStyle }>
79
             style = { restStyle }>
73
             <IconComponent
80
             <IconComponent
74
                 fill = { calculatedColor }
81
                 fill = { calculatedColor }

+ 3
- 0
react/features/base/icons/svg/copy.svg View File

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="M4 4C4 2.89543 4.89543 2 6 2H14C15.1046 2 16 2.89543 16 4H6V18C4.89543 18 4 17.1046 4 16V4ZM10 8V20H18V8H10ZM10 6H18C19.1046 6 20 6.89543 20 8V20C20 21.1046 19.1046 22 18 22H10C8.89543 22 8 21.1046 8 20V8C8 6.89543 8.89543 6 10 6Z" fill="#5E6D7A"/>
3
+</svg>

+ 1
- 0
react/features/base/icons/svg/index.js View File

18
 export { default as IconClosedCaption } from './closed_caption.svg';
18
 export { default as IconClosedCaption } from './closed_caption.svg';
19
 export { default as IconConnectionActive } from './gsm-bars.svg';
19
 export { default as IconConnectionActive } from './gsm-bars.svg';
20
 export { default as IconConnectionInactive } from './ninja.svg';
20
 export { default as IconConnectionInactive } from './ninja.svg';
21
+export { default as IconCopy } from './copy.svg';
21
 export { default as IconDeviceBluetooth } from './bluetooth.svg';
22
 export { default as IconDeviceBluetooth } from './bluetooth.svg';
22
 export { default as IconDeviceEarpiece } from './phone-talk.svg';
23
 export { default as IconDeviceEarpiece } from './phone-talk.svg';
23
 export { default as IconDeviceHeadphone } from './headset.svg';
24
 export { default as IconDeviceHeadphone } from './headset.svg';

+ 58
- 6
react/features/invite/components/info-dialog/web/InfoDialog.js View File

7
 import { getInviteURL } from '../../../../base/connection';
7
 import { getInviteURL } from '../../../../base/connection';
8
 import { Dialog } from '../../../../base/dialog';
8
 import { Dialog } from '../../../../base/dialog';
9
 import { translate } from '../../../../base/i18n';
9
 import { translate } from '../../../../base/i18n';
10
-import { Icon, IconInfo } from '../../../../base/icons';
10
+import { Icon, IconInfo, IconCopy } from '../../../../base/icons';
11
 import { connect } from '../../../../base/redux';
11
 import { connect } from '../../../../base/redux';
12
 import {
12
 import {
13
     isLocalParticipantModerator,
13
     isLocalParticipantModerator,
136
  */
136
  */
137
 class InfoDialog extends Component<Props, State> {
137
 class InfoDialog extends Component<Props, State> {
138
     _copyElement: ?Object;
138
     _copyElement: ?Object;
139
+    _copyUrlElement: ?Object;
139
 
140
 
140
     /**
141
     /**
141
      * Implements React's {@link Component#getDerivedStateFromProps()}.
142
      * Implements React's {@link Component#getDerivedStateFromProps()}.
197
 
198
 
198
         // Bind event handlers so they are only bound once for every instance.
199
         // Bind event handlers so they are only bound once for every instance.
199
         this._onClickURLText = this._onClickURLText.bind(this);
200
         this._onClickURLText = this._onClickURLText.bind(this);
200
-        this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
201
+        this._onCopyInviteInfo = this._onCopyInviteInfo.bind(this);
202
+        this._onCopyInviteUrl = this._onCopyInviteUrl.bind(this);
201
         this._onPasswordRemove = this._onPasswordRemove.bind(this);
203
         this._onPasswordRemove = this._onPasswordRemove.bind(this);
202
         this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
204
         this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
203
         this._onTogglePasswordEditState
205
         this._onTogglePasswordEditState
204
             = this._onTogglePasswordEditState.bind(this);
206
             = this._onTogglePasswordEditState.bind(this);
205
         this._setCopyElement = this._setCopyElement.bind(this);
207
         this._setCopyElement = this._setCopyElement.bind(this);
208
+        this._setCopyUrlElement = this._setCopyUrlElement.bind(this);
206
     }
209
     }
207
 
210
 
208
     /**
211
     /**
239
                         <span className = 'spacer'>&nbsp;</span>
242
                         <span className = 'spacer'>&nbsp;</span>
240
                         <span className = 'info-value'>
243
                         <span className = 'info-value'>
241
                             <a
244
                             <a
242
-                                className = 'info-dialog-url-text'
245
+                                className = 'info-dialog-url-text info-dialog-url-text-unselectable'
243
                                 href = { this.props._inviteURL }
246
                                 href = { this.props._inviteURL }
244
                                 onClick = { this._onClickURLText } >
247
                                 onClick = { this._onClickURLText } >
245
                                 { decodeURI(this._getURLToDisplay()) }
248
                                 { decodeURI(this._getURLToDisplay()) }
246
                             </a>
249
                             </a>
247
                         </span>
250
                         </span>
251
+                        <span className = 'info-dialog-url-icon'>
252
+                            <Icon
253
+                                onClick = { this._onCopyInviteUrl }
254
+                                size = { 18 }
255
+                                src = { IconCopy } />
256
+                        </span>
248
                     </div>
257
                     </div>
249
                     <div className = 'info-dialog-dial-in'>
258
                     <div className = 'info-dialog-dial-in'>
250
                         { this._renderDialInDisplay() }
259
                         { this._renderDialInDisplay() }
262
                         <div className = 'info-dialog-action-link'>
271
                         <div className = 'info-dialog-action-link'>
263
                             <a
272
                             <a
264
                                 className = 'info-copy'
273
                                 className = 'info-copy'
265
-                                onClick = { this._onCopyInviteURL }>
274
+                                onClick = { this._onCopyInviteInfo }>
266
                                 { t('dialog.copy') }
275
                                 { t('dialog.copy') }
267
                             </a>
276
                             </a>
268
                         </div>
277
                         </div>
275
                     ref = { this._setCopyElement }
284
                     ref = { this._setCopyElement }
276
                     tabIndex = '-1'
285
                     tabIndex = '-1'
277
                     value = { this._getTextToCopy() } />
286
                     value = { this._getTextToCopy() } />
287
+                <textarea
288
+                    className = 'info-dialog-copy-element'
289
+                    readOnly = { true }
290
+                    ref = { this._setCopyUrlElement }
291
+                    tabIndex = '-1'
292
+                    value = { this.props._inviteURL } />
278
             </div>
293
             </div>
279
         );
294
         );
280
 
295
 
365
         event.preventDefault();
380
         event.preventDefault();
366
     }
381
     }
367
 
382
 
368
-    _onCopyInviteURL: () => void;
383
+    _onCopyInviteInfo: () => void;
369
 
384
 
370
     /**
385
     /**
371
      * Callback invoked to copy the contents of {@code this._copyElement} to the
386
      * Callback invoked to copy the contents of {@code this._copyElement} to the
374
      * @private
389
      * @private
375
      * @returns {void}
390
      * @returns {void}
376
      */
391
      */
377
-    _onCopyInviteURL() {
392
+    _onCopyInviteInfo() {
378
         try {
393
         try {
379
             if (!this._copyElement) {
394
             if (!this._copyElement) {
380
                 throw new Error('No element to copy from.');
395
                 throw new Error('No element to copy from.');
388
         }
403
         }
389
     }
404
     }
390
 
405
 
406
+    _onCopyInviteUrl: () => void;
407
+
408
+    /**
409
+     * Callback invoked to copy the contents of {@code this._copyUrlElement} to the clipboard.
410
+     *
411
+     * @private
412
+     * @returns {void}
413
+     */
414
+    _onCopyInviteUrl() {
415
+        try {
416
+            if (!this._copyUrlElement) {
417
+                throw new Error('No element to copy from.');
418
+            }
419
+
420
+            this._copyUrlElement && this._copyUrlElement.select();
421
+            document.execCommand('copy');
422
+            this._copyUrlElement && this._copyUrlElement.blur();
423
+        } catch (err) {
424
+            logger.error('error when copying the text', err);
425
+        }
426
+    }
427
+
391
     _onPasswordRemove: () => void;
428
     _onPasswordRemove: () => void;
392
 
429
 
393
     /**
430
     /**
565
     _setCopyElement(element: Object) {
602
     _setCopyElement(element: Object) {
566
         this._copyElement = element;
603
         this._copyElement = element;
567
     }
604
     }
605
+
606
+    _setCopyUrlElement: () => void;
607
+
608
+    /**
609
+     * Sets the internal reference to the DOM/HTML element backing the React
610
+     * {@code Component} input.
611
+     *
612
+     * @param {HTMLInputElement} element - The DOM/HTML element for this
613
+     * {@code Component}'s input.
614
+     * @private
615
+     * @returns {void}
616
+     */
617
+    _setCopyUrlElement(element: Object) {
618
+        this._copyUrlElement = element;
619
+    }
568
 }
620
 }
569
 
621
 
570
 /**
622
 /**

Loading…
Cancel
Save