mirror of https://github.com/jitsi/jitsi-meet
parent
266d8f72c5
commit
46b75e5178
@ -0,0 +1,59 @@ |
||||
.info-dialog { |
||||
display: flex; |
||||
|
||||
.info-dialog-action-link { |
||||
display: inline-block; |
||||
|
||||
a { |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
|
||||
.info-dialog-action-link:before { |
||||
color: $linkFontColor; |
||||
content: '\2022'; |
||||
padding: 0 10px; |
||||
} |
||||
|
||||
.info-dialog-action-link:first-child:before { |
||||
content: ''; |
||||
padding: 0; |
||||
} |
||||
|
||||
.info-dialog-action-links { |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.info-dialog-action-separator { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.info-dialog-copy-element { |
||||
opacity: 0; |
||||
pointer-events: none; |
||||
position: fixed; |
||||
-webkit-user-select: text; |
||||
user-select: text; |
||||
} |
||||
|
||||
.info-dialog-column { |
||||
margin-right: 10px; |
||||
} |
||||
|
||||
.info-dialog-conference-url { |
||||
margin: 10px 0; |
||||
max-width: 250px; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.info-dialog-icon { |
||||
color: #6453C0; |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.info-dialog-title { |
||||
font-weight: bold; |
||||
} |
||||
} |
Binary file not shown.
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,191 @@ |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
import PropTypes from 'prop-types'; |
||||
|
||||
import { getInviteURL } from '../../base/connection'; |
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
import { openAddPeopleDialog } from '../actions'; |
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename); |
||||
|
||||
declare var interfaceConfig: Object; |
||||
|
||||
/** |
||||
* A React Component with the contents for a dialog that shows information about |
||||
* the current conference and provides ways to invite other participants. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class InfoDialog extends Component { |
||||
/** |
||||
* {@code InfoDialog} component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The current url of the conference to be copied onto the clipboard. |
||||
*/ |
||||
_inviteURL: PropTypes.string, |
||||
|
||||
/** |
||||
* Whether or not the link to open the {@code AddPeopleDialog} should be |
||||
* displayed. |
||||
*/ |
||||
_showAddPeople: PropTypes.bool, |
||||
|
||||
/** |
||||
* Invoked to open a dialog for adding participants to the conference. |
||||
*/ |
||||
dispatch: PropTypes.func, |
||||
|
||||
/** |
||||
* Callback invoked when the dialog should be closed. |
||||
*/ |
||||
onClose: PropTypes.func, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes new {@code InfoDialog} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
/** |
||||
* The internal reference to the DOM/HTML element backing the React |
||||
* {@code Component} input. It is necessary for the implementation |
||||
* of copying to the clipboard. |
||||
* |
||||
* @private |
||||
* @type {HTMLInputElement} |
||||
*/ |
||||
this._copyElement = null; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onCopyInviteURL = this._onCopyInviteURL.bind(this); |
||||
this._onOpenInviteDialog = this._onOpenInviteDialog.bind(this); |
||||
this._setCopyElement = this._setCopyElement.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<div className = 'info-dialog'> |
||||
<div className = 'info-dialog-column'> |
||||
<h4 className = 'info-dialog-icon'> |
||||
<i className = 'icon-info' /> |
||||
</h4> |
||||
</div> |
||||
<div className = 'info-dialog-column'> |
||||
<div className = 'info-dialog-title'> |
||||
{ this.props.t('info.title') } |
||||
</div> |
||||
<div |
||||
className = 'info-dialog-conference-url' |
||||
ref = { this._inviteUrlElement }> |
||||
{ this.props._inviteURL } |
||||
<input |
||||
className = 'info-dialog-copy-element' |
||||
readOnly = { true } |
||||
ref = { this._setCopyElement } |
||||
tabIndex = '-1' |
||||
value = { this.props._inviteURL } /> |
||||
</div> |
||||
<div className = 'info-dialog-action-links'> |
||||
<div className = 'info-dialog-action-link'> |
||||
<a onClick = { this._onCopyInviteURL }> |
||||
{ this.props.t('info.copy') } |
||||
</a> |
||||
</div> |
||||
{ this.props._showAddPeople |
||||
? <div className = 'info-dialog-action-link'> |
||||
<a onClick = { this._onOpenInviteDialog }> |
||||
{ this.props.t('info.invite', { |
||||
app: interfaceConfig.ADD_PEOPLE_APP_NAME |
||||
}) } |
||||
</a> |
||||
</div> |
||||
: null } |
||||
</div> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Callback invoked to copy the contents of {@code this._copyElement} to the |
||||
* clipboard. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onCopyInviteURL() { |
||||
try { |
||||
this._copyElement.select(); |
||||
document.execCommand('copy'); |
||||
this._copyElement.blur(); |
||||
} catch (err) { |
||||
logger.error('error when copying the text', err); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Callback invoked to open the {@code AddPeople} dialog. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onOpenInviteDialog() { |
||||
this.props.dispatch(openAddPeopleDialog()); |
||||
|
||||
if (this.props.onClose) { |
||||
this.props.onClose(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 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} |
||||
*/ |
||||
_setCopyElement(element) { |
||||
this._copyElement = element; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated props for the |
||||
* {@code InfoDialog} component. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _inviteURL: string |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
_inviteURL: getInviteURL(state), |
||||
_showAddPeople: !state['features/jwt'].isGuest |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(InfoDialog)); |
@ -0,0 +1,44 @@ |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { ToolbarButtonWithDialog } from '../../toolbox'; |
||||
|
||||
import InfoDialog from './InfoDialog'; |
||||
|
||||
/** |
||||
* A configuration object to describe how {@code ToolbarButton} should render |
||||
* the button. |
||||
* |
||||
* @type {object} |
||||
*/ |
||||
const DEFAULT_BUTTON_CONFIGURATION = { |
||||
buttonName: 'info', |
||||
classNames: [ 'button', 'icon-info' ], |
||||
enabled: true, |
||||
id: 'toolbar_button_info', |
||||
tooltipKey: 'info.tooltip' |
||||
}; |
||||
|
||||
/** |
||||
* A React Component for displaying a button which opens a dialog with |
||||
* information about the conference and with ways to invite people. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class InfoDialogButton extends Component { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<ToolbarButtonWithDialog |
||||
{ ...this.props } |
||||
button = { DEFAULT_BUTTON_CONFIGURATION } |
||||
content = { InfoDialog } /> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default InfoDialogButton; |
@ -1,2 +1,3 @@ |
||||
export { default as InviteDialog } from './InviteDialog'; |
||||
export { default as AddPeopleDialog } from './AddPeopleDialog'; |
||||
export { default as InfoDialogButton } from './InfoDialogButton'; |
||||
export { default as InviteDialog } from './InviteDialog'; |
||||
|
@ -0,0 +1,164 @@ |
||||
import InlineDialog from '@atlaskit/inline-dialog'; |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import ToolbarButton from './ToolbarButton'; |
||||
|
||||
/** |
||||
* Maps AtlasKit {@code Tooltip} positions to equivalent {@code InlineDialog} |
||||
* positions. The {@code InlineDialog} will appear from the the same side of |
||||
* the button as the tooltip. |
||||
* |
||||
*/ |
||||
const TOOLTIP_TO_DIALOG_POSITION = { |
||||
bottom: 'bottom center', |
||||
left: 'left middle', |
||||
right: 'right middle', |
||||
top: 'top center' |
||||
}; |
||||
|
||||
/** |
||||
* React {@code Component} for displaying a button which will open an inline |
||||
* dialog. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class ToolbarButtonWithDialog extends Component { |
||||
/** |
||||
* {@code ToolbarButtonWithDialog}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* Whether or not the button is visible, based on the visibility of the |
||||
* toolbar. Used to automatically hide {@code InlineDialog} if not |
||||
* visible. |
||||
*/ |
||||
_visible: PropTypes.bool, |
||||
|
||||
/** |
||||
* A configuration object to describe how {@code ToolbarButton} should |
||||
* render. |
||||
* |
||||
*/ |
||||
button: PropTypes.object, |
||||
|
||||
/** |
||||
* The React Component to show within {@code InlineDialog}. |
||||
*/ |
||||
content: PropTypes.object, |
||||
|
||||
/** |
||||
* From which side tooltips should display. Will be re-used for |
||||
* displaying the inline dialog for video quality adjustment. |
||||
*/ |
||||
tooltipPosition: PropTypes.string |
||||
}; |
||||
|
||||
/** |
||||
* Initializes new {@code ToolbarButtonWithDialog} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
/** |
||||
* Whether or not the inline dialog should be displayed. |
||||
*/ |
||||
showDialog: false |
||||
}; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDialogClose = this._onDialogClose.bind(this); |
||||
this._onDialogToggle = this._onDialogToggle.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Automatically close the inline dialog if the button will not be visible. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentWillReceiveProps(nextProps) { |
||||
if (!nextProps._visible) { |
||||
this._onDialogClose(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { _visible, content, tooltipPosition } = this.props; |
||||
const buttonConfiguration = { |
||||
...this.props.button, |
||||
classNames: [ |
||||
...this.props.button.classNames, |
||||
this.state.showDialog ? 'toggled button-active' : '' |
||||
] |
||||
}; |
||||
|
||||
const Content = content; |
||||
|
||||
return ( |
||||
<InlineDialog |
||||
content = { <Content onClose = { this._onDialogClose } /> } |
||||
isOpen = { _visible && this.state.showDialog } |
||||
onClose = { this._onDialogClose } |
||||
position = { TOOLTIP_TO_DIALOG_POSITION[tooltipPosition] }> |
||||
<ToolbarButton |
||||
button = { buttonConfiguration } |
||||
onClick = { this._onDialogToggle } |
||||
tooltipPosition = { tooltipPosition } /> |
||||
</InlineDialog> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Hides the attached inline dialog. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onDialogClose() { |
||||
this.setState({ showDialog: false }); |
||||
} |
||||
|
||||
/** |
||||
* Toggles the display of the dialog. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onDialogToggle() { |
||||
this.setState({ |
||||
showDialog: !this.state.showDialog |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code ToolbarButtonWithDialog} component's props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _visible: boolean |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
_visible: state['features/toolbox'].visible |
||||
}; |
||||
} |
||||
|
||||
export default connect(_mapStateToProps)(ToolbarButtonWithDialog); |
@ -1,2 +1,4 @@ |
||||
export { default as ToolbarButton } from './ToolbarButton'; |
||||
export { default as ToolbarButtonWithDialog } |
||||
from './ToolbarButtonWithDialog'; |
||||
export { default as Toolbox } from './Toolbox'; |
||||
|
Loading…
Reference in new issue