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

This commit is contained in:
damencho 2019-10-08 13:05:41 +01:00 committed by Дамян Минков
parent 7e70a8c1de
commit 3e1a008399
5 changed files with 86 additions and 6 deletions

View File

@ -63,6 +63,8 @@
width: -webkit-max-content; width: -webkit-max-content;
word-break: break-all; word-break: break-all;
max-width: 400px; max-width: 400px;
display: flex;
align-items: center;
} }
.info-dialog-dial-in { .info-dialog-dial-in {
@ -86,6 +88,15 @@
cursor: inherit; cursor: inherit;
} }
.info-dialog-url-icon {
display: inline-block;
margin-left: 5px;
svg {
cursor: pointer;
}
}
.info-dialog-title { .info-dialog-title {
font-weight: bold; font-weight: bold;
margin-bottom: 10px; margin-bottom: 10px;
@ -214,4 +225,10 @@
-moz-user-select: text; -moz-user-select: text;
-webkit-user-select: text; -webkit-user-select: text;
} }
.info-dialog-url-text-unselectable {
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
}
} }

View File

@ -23,6 +23,11 @@ type Props = {
*/ */
id?: string, id?: string,
/**
* Function to invoke on click.
*/
onClick?: Function,
/** /**
* The size of the icon (if not provided by the style object). * The size of the icon (if not provided by the style object).
*/ */
@ -53,6 +58,7 @@ export default function Icon(props: Props) {
className, className,
color, color,
id, id,
onClick,
size, size,
src: IconComponent, src: IconComponent,
style style
@ -69,6 +75,7 @@ export default function Icon(props: Props) {
return ( return (
<Container <Container
className = { `jitsi-icon ${className}` } className = { `jitsi-icon ${className}` }
onClick = { onClick }
style = { restStyle }> style = { restStyle }>
<IconComponent <IconComponent
fill = { calculatedColor } fill = { calculatedColor }

View File

@ -0,0 +1,3 @@
<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="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"/>
</svg>

After

Width:  |  Height:  |  Size: 401 B

View File

@ -18,6 +18,7 @@ export { default as IconClose } from './close.svg';
export { default as IconClosedCaption } from './closed_caption.svg'; export { default as IconClosedCaption } from './closed_caption.svg';
export { default as IconConnectionActive } from './gsm-bars.svg'; export { default as IconConnectionActive } from './gsm-bars.svg';
export { default as IconConnectionInactive } from './ninja.svg'; export { default as IconConnectionInactive } from './ninja.svg';
export { default as IconCopy } from './copy.svg';
export { default as IconDeviceBluetooth } from './bluetooth.svg'; export { default as IconDeviceBluetooth } from './bluetooth.svg';
export { default as IconDeviceEarpiece } from './phone-talk.svg'; export { default as IconDeviceEarpiece } from './phone-talk.svg';
export { default as IconDeviceHeadphone } from './headset.svg'; export { default as IconDeviceHeadphone } from './headset.svg';

View File

@ -7,7 +7,7 @@ import { setPassword } from '../../../../base/conference';
import { getInviteURL } from '../../../../base/connection'; import { getInviteURL } from '../../../../base/connection';
import { Dialog } from '../../../../base/dialog'; import { Dialog } from '../../../../base/dialog';
import { translate } from '../../../../base/i18n'; import { translate } from '../../../../base/i18n';
import { Icon, IconInfo } from '../../../../base/icons'; import { Icon, IconInfo, IconCopy } from '../../../../base/icons';
import { connect } from '../../../../base/redux'; import { connect } from '../../../../base/redux';
import { import {
isLocalParticipantModerator, isLocalParticipantModerator,
@ -136,6 +136,7 @@ type State = {
*/ */
class InfoDialog extends Component<Props, State> { class InfoDialog extends Component<Props, State> {
_copyElement: ?Object; _copyElement: ?Object;
_copyUrlElement: ?Object;
/** /**
* Implements React's {@link Component#getDerivedStateFromProps()}. * Implements React's {@link Component#getDerivedStateFromProps()}.
@ -197,12 +198,14 @@ class InfoDialog extends Component<Props, State> {
// Bind event handlers so they are only bound once for every instance. // Bind event handlers so they are only bound once for every instance.
this._onClickURLText = this._onClickURLText.bind(this); this._onClickURLText = this._onClickURLText.bind(this);
this._onCopyInviteURL = this._onCopyInviteURL.bind(this); this._onCopyInviteInfo = this._onCopyInviteInfo.bind(this);
this._onCopyInviteUrl = this._onCopyInviteUrl.bind(this);
this._onPasswordRemove = this._onPasswordRemove.bind(this); this._onPasswordRemove = this._onPasswordRemove.bind(this);
this._onPasswordSubmit = this._onPasswordSubmit.bind(this); this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
this._onTogglePasswordEditState this._onTogglePasswordEditState
= this._onTogglePasswordEditState.bind(this); = this._onTogglePasswordEditState.bind(this);
this._setCopyElement = this._setCopyElement.bind(this); this._setCopyElement = this._setCopyElement.bind(this);
this._setCopyUrlElement = this._setCopyUrlElement.bind(this);
} }
/** /**
@ -239,12 +242,18 @@ class InfoDialog extends Component<Props, State> {
<span className = 'spacer'>&nbsp;</span> <span className = 'spacer'>&nbsp;</span>
<span className = 'info-value'> <span className = 'info-value'>
<a <a
className = 'info-dialog-url-text' className = 'info-dialog-url-text info-dialog-url-text-unselectable'
href = { this.props._inviteURL } href = { this.props._inviteURL }
onClick = { this._onClickURLText } > onClick = { this._onClickURLText } >
{ decodeURI(this._getURLToDisplay()) } { decodeURI(this._getURLToDisplay()) }
</a> </a>
</span> </span>
<span className = 'info-dialog-url-icon'>
<Icon
onClick = { this._onCopyInviteUrl }
size = { 18 }
src = { IconCopy } />
</span>
</div> </div>
<div className = 'info-dialog-dial-in'> <div className = 'info-dialog-dial-in'>
{ this._renderDialInDisplay() } { this._renderDialInDisplay() }
@ -262,7 +271,7 @@ class InfoDialog extends Component<Props, State> {
<div className = 'info-dialog-action-link'> <div className = 'info-dialog-action-link'>
<a <a
className = 'info-copy' className = 'info-copy'
onClick = { this._onCopyInviteURL }> onClick = { this._onCopyInviteInfo }>
{ t('dialog.copy') } { t('dialog.copy') }
</a> </a>
</div> </div>
@ -275,6 +284,12 @@ class InfoDialog extends Component<Props, State> {
ref = { this._setCopyElement } ref = { this._setCopyElement }
tabIndex = '-1' tabIndex = '-1'
value = { this._getTextToCopy() } /> value = { this._getTextToCopy() } />
<textarea
className = 'info-dialog-copy-element'
readOnly = { true }
ref = { this._setCopyUrlElement }
tabIndex = '-1'
value = { this.props._inviteURL } />
</div> </div>
); );
@ -365,7 +380,7 @@ class InfoDialog extends Component<Props, State> {
event.preventDefault(); event.preventDefault();
} }
_onCopyInviteURL: () => void; _onCopyInviteInfo: () => void;
/** /**
* Callback invoked to copy the contents of {@code this._copyElement} to the * Callback invoked to copy the contents of {@code this._copyElement} to the
@ -374,7 +389,7 @@ class InfoDialog extends Component<Props, State> {
* @private * @private
* @returns {void} * @returns {void}
*/ */
_onCopyInviteURL() { _onCopyInviteInfo() {
try { try {
if (!this._copyElement) { if (!this._copyElement) {
throw new Error('No element to copy from.'); throw new Error('No element to copy from.');
@ -388,6 +403,28 @@ class InfoDialog extends Component<Props, State> {
} }
} }
_onCopyInviteUrl: () => void;
/**
* Callback invoked to copy the contents of {@code this._copyUrlElement} to the clipboard.
*
* @private
* @returns {void}
*/
_onCopyInviteUrl() {
try {
if (!this._copyUrlElement) {
throw new Error('No element to copy from.');
}
this._copyUrlElement && this._copyUrlElement.select();
document.execCommand('copy');
this._copyUrlElement && this._copyUrlElement.blur();
} catch (err) {
logger.error('error when copying the text', err);
}
}
_onPasswordRemove: () => void; _onPasswordRemove: () => void;
/** /**
@ -565,6 +602,21 @@ class InfoDialog extends Component<Props, State> {
_setCopyElement(element: Object) { _setCopyElement(element: Object) {
this._copyElement = element; this._copyElement = element;
} }
_setCopyUrlElement: () => void;
/**
* Sets the internal reference to the DOM/HTML element backing the React
* {@code Component} input.
*
* @param {HTMLInputElement} element - The DOM/HTML element for this
* {@code Component}'s input.
* @private
* @returns {void}
*/
_setCopyUrlElement(element: Object) {
this._copyUrlElement = element;
}
} }
/** /**