Adds copy icon next to the meeting url in info dialog.
This commit is contained in:
parent
7e70a8c1de
commit
3e1a008399
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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 |
|
@ -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';
|
||||||
|
|
|
@ -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'> </span>
|
<span className = 'spacer'> </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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue