mirror of https://github.com/jitsi/jitsi-meet
ref(invite): remove InviteDialog (#2483)
* ref(invite): remove InviteDialog InviteDialog functionality has been moved into InfoDialog. The InviteButton has been temporarily hacked to show one of its dropdown options instead as the button. Future work will bring in a redesigned InviteModal that the button will open. * squash: filter invalid options and map valid options * squash: update stringspull/2497/head jitsi-meet_2807
parent
e2cf7a788d
commit
e47802538e
@ -1,96 +0,0 @@ |
||||
/* |
||||
* Sets the default cursor the remove password link. The link doesn't use |
||||
* the href attribute, so we need to set the cursor manually. |
||||
*/ |
||||
#inviteDialogRemovePassword { |
||||
cursor: hand; |
||||
} |
||||
|
||||
.invite-dialog { |
||||
.dial-in-numbers { |
||||
.dial-in-numbers-conference-id { |
||||
color: orange; |
||||
margin-left: 3px; |
||||
} |
||||
|
||||
/* |
||||
* dial-in-numbers-copy styling is needed for the feature of copying |
||||
* text to the clipboard. The styling keeps the element invisible |
||||
* to the user but still programmatically selectable for copying. |
||||
*/ |
||||
.dial-in-numbers-copy { |
||||
opacity: 0; |
||||
pointer-events: none; |
||||
position: fixed; |
||||
-webkit-user-select: text; |
||||
user-select: text; |
||||
} |
||||
|
||||
.is-disabled, |
||||
.is-loading { |
||||
.dial-in-numbers-trigger-icon { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.form-control { |
||||
padding: 0; |
||||
|
||||
&__container { |
||||
/** |
||||
* Ensure contents display in a line and vertically centered. |
||||
*/ |
||||
align-items: center; |
||||
|
||||
button { |
||||
font-size: $modalButtonFontSize; |
||||
} |
||||
} |
||||
|
||||
&__input-container { |
||||
flex: 1; |
||||
margin-right: 10px; |
||||
|
||||
.dropdown-button-trigger { |
||||
text-align: left; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.inviteLink { |
||||
color: $readOnlyInputColor; |
||||
} |
||||
|
||||
.lock-state { |
||||
display: flex; |
||||
} |
||||
|
||||
.password-overview { |
||||
margin-top: 10px; |
||||
|
||||
.form-control { |
||||
margin-top: 10px; |
||||
} |
||||
|
||||
.password-overview-status, |
||||
.remove-password { |
||||
display: flex; |
||||
justify-content: space-between; |
||||
} |
||||
|
||||
.password-overview-toggle-edit, |
||||
.remove-password-link { |
||||
cursor: pointer; |
||||
text-decoration: none; |
||||
} |
||||
|
||||
.remove-password { |
||||
margin-top: 15px; |
||||
} |
||||
} |
||||
|
||||
.remove-password-current { |
||||
color: $inputControlEmColor; |
||||
} |
||||
} |
@ -1,199 +0,0 @@ |
||||
import Button from '@atlaskit/button'; |
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text'; |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { setPassword } from '../../base/conference'; |
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
/** |
||||
* A React {@code Component} for locking a JitsiConference with a password. |
||||
*/ |
||||
class AddPasswordForm extends Component { |
||||
/** |
||||
* {@code AddPasswordForm}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The JitsiConference on which to lock and set a password. |
||||
* |
||||
* @type {JitsiConference} |
||||
*/ |
||||
conference: PropTypes.object, |
||||
|
||||
/** |
||||
* Invoked to set a password on the conference. |
||||
*/ |
||||
dispatch: PropTypes.func, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code AddPasswordForm} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
/** |
||||
* The current value to display in {@code AddPasswordForm} |
||||
* component's input field. The value is also used as the desired |
||||
* new password when creating a {@code setPassword} action. |
||||
* |
||||
* @type {string} |
||||
*/ |
||||
password: '' |
||||
}; |
||||
|
||||
/** |
||||
* The internal reference to the React {@code component} for entering a |
||||
* password. |
||||
* |
||||
* @private |
||||
* @type {ReactComponent} |
||||
*/ |
||||
this._inputComponent = null; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onKeyDown = this._onKeyDown.bind(this); |
||||
this._onPasswordChange = this._onPasswordChange.bind(this); |
||||
this._onSubmit = this._onSubmit.bind(this); |
||||
this._setInput = this._setInput.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Directly bind a handler to the input element. This is done in order to |
||||
* intercept enter presses so any outer forms do not become submitted. |
||||
* Atlaskit Button does not expose a way to hook onto keydown events. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
componentDidMount() { |
||||
this._inputComponent.input.onkeydown = this._onKeyDown; |
||||
} |
||||
|
||||
/** |
||||
* Remove any handlers set directly on DOM elements. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
componentWillUnmount() { |
||||
this._inputComponent.input.onkeydown = null; |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<div |
||||
className = 'form-control' |
||||
onSubmit = { this._onSubmit } > |
||||
<div className = 'form-control__container'> |
||||
<div className = 'form-control__input-container'> |
||||
<TextField |
||||
autoFocus = { true } |
||||
compact = { true } |
||||
id = 'newPasswordInput' |
||||
isLabelHidden = { true } |
||||
label = 'Enter Password' |
||||
onChange = { this._onPasswordChange } |
||||
onKeyDown = { this._onKeyDown } |
||||
placeholder = { t('dialog.createPassword') } |
||||
ref = { this._setInput } |
||||
shouldFitContainer = { true } |
||||
type = 'text' /> |
||||
</div> |
||||
<Button |
||||
id = 'addPasswordBtn' |
||||
isDisabled = { !this.state.password } |
||||
onClick = { this._onSubmit } |
||||
type = 'button'> |
||||
{ t('dialog.add') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Mimics form behavior by listening for enter key press and submitting the |
||||
* entered password. |
||||
* |
||||
* @param {Object} event - DOM Event for keydown. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onKeyDown(event) { |
||||
event.stopPropagation(); |
||||
|
||||
if (event.keyCode === /* Enter */ 13) { |
||||
this._onSubmit(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Updates the internal state of the entered password. |
||||
* |
||||
* @param {Object} event - DOM Event for value change. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onPasswordChange(event) { |
||||
this.setState({ password: event.target.value }); |
||||
} |
||||
|
||||
/** |
||||
* Dispatches a request to lock the conference with a password. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onSubmit() { |
||||
if (!this.state.password) { |
||||
return; |
||||
} |
||||
|
||||
const { conference } = this.props; |
||||
|
||||
this.props.dispatch(setPassword( |
||||
conference, |
||||
conference.lock, |
||||
this.state.password |
||||
)); |
||||
|
||||
this.setState({ password: '' }); |
||||
} |
||||
|
||||
/** |
||||
* Sets the instance variable for the React Component used for entering a |
||||
* password. |
||||
* |
||||
* @param {Object} inputComponent - The React Component for the input |
||||
* field. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setInput(inputComponent) { |
||||
if (inputComponent !== this._inputComponent) { |
||||
this._inputComponent = inputComponent; |
||||
} |
||||
} |
||||
} |
||||
|
||||
export default translate(connect()(AddPasswordForm)); |
@ -1,406 +0,0 @@ |
||||
import Button from '@atlaskit/button'; |
||||
import DropdownMenu, { |
||||
DropdownItem, DropdownItemGroup } from '@atlaskit/dropdown-menu'; |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { getLocalParticipant } from '../../base/participants'; |
||||
|
||||
import { updateDialInNumbers } from '../actions'; |
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename); |
||||
|
||||
/** |
||||
* React {@code Component} responsible for fetching and displaying telephone |
||||
* numbers for dialing into a conference. Also supports copying a selected |
||||
* dial-in number to the clipboard. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class DialInNumbersForm extends Component { |
||||
/** |
||||
* {@code DialInNumbersForm}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The redux state representing the dial-in numbers feature. |
||||
*/ |
||||
_dialIn: PropTypes.object, |
||||
|
||||
/** |
||||
* The display name of the local user. |
||||
*/ |
||||
_localUserDisplayName: PropTypes.string, |
||||
|
||||
/** |
||||
* Invoked to send an ajax request for dial-in numbers. |
||||
*/ |
||||
dispatch: PropTypes.func, |
||||
|
||||
/** |
||||
* The URL of the conference into which this {@code DialInNumbersForm} |
||||
* is inviting the local participant. |
||||
*/ |
||||
inviteURL: PropTypes.string, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code DialInNumbersForm} 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 dropdown should be open. |
||||
* |
||||
* @type {boolean} |
||||
*/ |
||||
isDropdownOpen: false, |
||||
|
||||
/** |
||||
* The dial-in number to display as currently selected in the |
||||
* dropdown. The value should be an object which has two key/value |
||||
* pairs, content and number. The value of "content" will display in |
||||
* the dropdown while the value of "number" is a substring of |
||||
* "content" which will be copied to clipboard. |
||||
* |
||||
* @type {object} |
||||
*/ |
||||
selectedNumber: null |
||||
}; |
||||
|
||||
/** |
||||
* The internal reference to the DOM/HTML element backing the React |
||||
* {@code Component} text area. It is necessary for the implementation |
||||
* of copying to the clipboard. |
||||
* |
||||
* @private |
||||
* @type {HTMLTextAreaElement} |
||||
*/ |
||||
this._copyElement = null; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onCopyClick = this._onCopyClick.bind(this); |
||||
this._onOpenChange = this._onOpenChange.bind(this); |
||||
this._onSelect = this._onSelect.bind(this); |
||||
this._setCopyElement = this._setCopyElement.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Sets a default number to display in the dropdown trigger. |
||||
* |
||||
* @inheritdoc |
||||
* returns {void} |
||||
*/ |
||||
componentWillMount() { |
||||
const { numbers } = this.props._dialIn; |
||||
|
||||
if (numbers) { |
||||
this._setDefaultNumber(numbers); |
||||
} else { |
||||
this.props.dispatch(updateDialInNumbers()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Monitors for number updates and sets a default number to display in the |
||||
* dropdown trigger if not already set. |
||||
* |
||||
* @inheritdoc |
||||
* returns {void} |
||||
*/ |
||||
componentWillReceiveProps(nextProps) { |
||||
if (!this.state.selectedNumber && nextProps._dialIn.numbers) { |
||||
this._setDefaultNumber(nextProps._dialIn.numbers); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. Returns null if the |
||||
* component is not ready for display. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement|null} |
||||
*/ |
||||
render() { |
||||
const { _dialIn, t } = this.props; |
||||
const { conferenceID, numbers, numbersEnabled } = _dialIn; |
||||
const { selectedNumber } = this.state; |
||||
|
||||
if (!conferenceID || !numbers || !numbersEnabled || !selectedNumber) { |
||||
return null; |
||||
} |
||||
|
||||
const items = this._renderDropdownItems(numbers); |
||||
|
||||
return ( |
||||
<div className = 'form-control dial-in-numbers'> |
||||
<label className = 'form-control__label'> |
||||
{ t('invite.howToDialIn') } |
||||
<span className = 'dial-in-numbers-conference-id'> |
||||
{ conferenceID } |
||||
</span> |
||||
</label> |
||||
<div className = 'form-control__container'> |
||||
<div className = 'form-control__input-container'> |
||||
{ this._createDropdownMenu(items, selectedNumber) } |
||||
</div> |
||||
<Button |
||||
appearance = 'default' |
||||
onClick = { this._onCopyClick } |
||||
type = 'button'> |
||||
{ t('dialog.copy') } |
||||
</Button> |
||||
</div> |
||||
<textarea |
||||
className = 'dial-in-numbers-copy' |
||||
readOnly = { true } |
||||
ref = { this._setCopyElement } |
||||
tabIndex = '-1' |
||||
value = { this._generateCopyText() } /> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@code DropdownMenu} instance. |
||||
* |
||||
* @param {Array} items - The content to display within the dropdown. |
||||
* @param {string} triggerText - The text to display within the |
||||
* trigger element. |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_createDropdownMenu(items, triggerText) { |
||||
return ( |
||||
<DropdownMenu |
||||
isOpen = { this.state.isDropdownOpen } |
||||
onOpenChange = { this._onOpenChange } |
||||
shouldFitContainer = { true } |
||||
trigger = { triggerText || '' } |
||||
triggerButtonProps = {{ |
||||
className: 'dropdown-button-trigger', |
||||
shouldFitContainer: true }} |
||||
triggerType = 'button'> |
||||
<DropdownItemGroup> |
||||
{ items } |
||||
</DropdownItemGroup> |
||||
</DropdownMenu> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Formats the region and number string. |
||||
* |
||||
* @param {string} region - The region string. |
||||
* @param {string} number - The number string. |
||||
* @returns {string} - The new formatted string. |
||||
* @private |
||||
*/ |
||||
_formatRegionNumber(region, number) { |
||||
return `${region}: ${number}`; |
||||
} |
||||
|
||||
/** |
||||
* Creates a message describing how to dial in to the conference. |
||||
* |
||||
* @private |
||||
* @returns {string} |
||||
*/ |
||||
_generateCopyText() { |
||||
const { t } = this.props; |
||||
const welcome = t('invite.invitedYouTo', { |
||||
inviteURL: this.props.inviteURL, |
||||
userName: this.props._localUserDisplayName |
||||
}); |
||||
|
||||
const callNumber = t('invite.callNumber', { |
||||
number: this.state.selectedNumber |
||||
}); |
||||
const stepOne = `1) ${callNumber}`; |
||||
|
||||
const enterID = t('invite.enterID', { |
||||
conferenceID: this.props._dialIn.conferenceID |
||||
}); |
||||
const stepTwo = `2) ${enterID}`; |
||||
|
||||
return `${welcome}\n${stepOne}\n${stepTwo}`; |
||||
} |
||||
|
||||
/** |
||||
* Copies part of the number displayed in the dropdown trigger into the |
||||
* clipboard. Only the value specified in selectedNumber.number, which |
||||
* should be a substring of the displayed value, will be copied. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onCopyClick() { |
||||
try { |
||||
this._copyElement.select(); |
||||
document.execCommand('copy'); |
||||
this._copyElement.blur(); |
||||
} catch (err) { |
||||
logger.error('error when copying the text', err); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the internal state to either open or close the dropdown. If the |
||||
* dropdown is disabled, the state will always be set to false. |
||||
* |
||||
* @param {Object} dropdownEvent - The even returned from clicking on the |
||||
* dropdown trigger. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onOpenChange(dropdownEvent) { |
||||
this.setState({ |
||||
isDropdownOpen: dropdownEvent.isOpen |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Updates the internal state of the currently selected number. |
||||
* |
||||
* @param {Object} selection - Event from choosing an dropdown option. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onSelect(selection) { |
||||
this.setState({ |
||||
isDropdownOpen: false, |
||||
selectedNumber: selection |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Renders a DropDownItem for the given id and text. |
||||
* |
||||
* @param {string} id - The key identifier of the DropdownItem. |
||||
* @param {string} text - The text to display in the dropdown item. |
||||
* @returns {React.Component} |
||||
* @private |
||||
*/ |
||||
_renderDropDownItem(id, text) { |
||||
return ( |
||||
|
||||
/** |
||||
* Arrow functions are not allowed in props, but I leave this until |
||||
* I figure a better way to implement the same thing. |
||||
*/ |
||||
/* eslint-disable react/jsx-no-bind */ |
||||
<DropdownItem |
||||
key = { id } |
||||
onClick = { () => this._onSelect(text || id) }> |
||||
{ text } |
||||
</DropdownItem> |
||||
/* eslint-disable react/jsx-no-bind */ |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Detects whether the response from dialInNumbersUrl returned an array or |
||||
* an object with dial-in numbers and calls the appropriate method to |
||||
* transform the numbers into the format expected by |
||||
* {@code DropdownMenu}. |
||||
* |
||||
* @param {Array<string>|Object} dialInNumbers - The numbers returned from |
||||
* requesting dialInNumbersUrl. |
||||
* @private |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
_renderDropdownItems(dialInNumbers) { |
||||
if (Array.isArray(dialInNumbers)) { |
||||
return dialInNumbers.map(number => |
||||
this._renderDropDownItem(number) |
||||
); |
||||
} |
||||
|
||||
const phoneRegions = Object.keys(dialInNumbers); |
||||
|
||||
if (!phoneRegions.length) { |
||||
return []; |
||||
} |
||||
|
||||
const dropdownItems = phoneRegions.map(region => { |
||||
const numbers = dialInNumbers[region]; |
||||
|
||||
return numbers.map(number => |
||||
this._renderDropDownItem(number, |
||||
this._formatRegionNumber(region, number)) |
||||
); |
||||
}); |
||||
|
||||
return Array.prototype.concat(...dropdownItems); |
||||
} |
||||
|
||||
/** |
||||
* Sets the internal reference to the DOM/HTML element backing the React |
||||
* {@code Component} text area. |
||||
* |
||||
* @param {HTMLTextAreaElement} element - The DOM/HTML element for this |
||||
* {@code Component}'s text area. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setCopyElement(element) { |
||||
this._copyElement = element; |
||||
} |
||||
|
||||
/** |
||||
* Updates the internal state of the currently selected number by defaulting |
||||
* to the first available number. |
||||
* |
||||
* @param {Object} dialInNumbers - The array or object of numbers to parse. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setDefaultNumber(dialInNumbers) { |
||||
let number = ''; |
||||
|
||||
if (Array.isArray(dialInNumbers)) { |
||||
number = dialInNumbers[0]; |
||||
} else if (Object.keys(dialInNumbers).length > 0) { |
||||
const region = Object.keys(dialInNumbers)[0]; |
||||
|
||||
number = this._formatRegionNumber(region, dialInNumbers[region]); |
||||
} |
||||
|
||||
this.setState({ |
||||
selectedNumber: number |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code DialInNumbersForm}'s props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _dialIn: Object, |
||||
* _localUserDisplayName: string |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
return { |
||||
_localUserDisplayName: getLocalParticipant(state).name, |
||||
_dialIn: state['features/invite'] |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(DialInNumbersForm)); |
@ -1,120 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { |
||||
createInviteDialogClosedEvent, |
||||
sendAnalytics |
||||
} from '../../analytics'; |
||||
import { getInviteURL } from '../../base/connection'; |
||||
import { Dialog } from '../../base/dialog'; |
||||
import { translate } from '../../base/i18n'; |
||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants'; |
||||
|
||||
import DialInNumbersForm from './DialInNumbersForm'; |
||||
import PasswordContainer from './PasswordContainer'; |
||||
import ShareLinkForm from './ShareLinkForm'; |
||||
|
||||
/** |
||||
* A React {@code Component} for displaying other components responsible for |
||||
* copying the current conference url and for setting or removing a conference |
||||
* password. |
||||
*/ |
||||
class InviteDialog extends Component { |
||||
/** |
||||
* {@code InviteDialog} component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* Whether or not the current user can modify the current password. |
||||
*/ |
||||
_canEditPassword: PropTypes.bool, |
||||
|
||||
/** |
||||
* The redux store representation of the JitsiConference. |
||||
*/ |
||||
_conference: PropTypes.object, |
||||
|
||||
/** |
||||
* The url for the JitsiConference. |
||||
*/ |
||||
_inviteURL: PropTypes.string, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Reports an analytics event for the invite modal being closed. |
||||
* |
||||
* @inheritdoc |
||||
*/ |
||||
componentWillUnmount() { |
||||
sendAnalytics(createInviteDialogClosedEvent()); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { _canEditPassword, _conference, _inviteURL, t } = this.props; |
||||
const titleString |
||||
= t('invite.inviteTo', { conferenceName: _conference.room }); |
||||
|
||||
return ( |
||||
<Dialog |
||||
cancelDisabled = { true } |
||||
okTitleKey = 'dialog.done' |
||||
titleString = { titleString }> |
||||
<div className = 'invite-dialog'> |
||||
<ShareLinkForm toCopy = { _inviteURL } /> |
||||
<DialInNumbersForm inviteURL = { _inviteURL } /> |
||||
<PasswordContainer |
||||
conference = { _conference.conference } |
||||
locked = { _conference.locked } |
||||
password = { _conference.password } |
||||
showPasswordEdit = { _canEditPassword } /> |
||||
</div> |
||||
</Dialog> |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated {@code InviteDialog}'s |
||||
* props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _canEditPassword: boolean, |
||||
* _conference: Object, |
||||
* _inviteURL: string |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const isModerator |
||||
= getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR; |
||||
let canEditPassword; |
||||
|
||||
if (state['features/base/config'].enableUserRolesBasedOnToken) { |
||||
canEditPassword = isModerator && !state['features/base/jwt'].isGuest; |
||||
} else { |
||||
canEditPassword = isModerator; |
||||
} |
||||
|
||||
return { |
||||
_canEditPassword: canEditPassword, |
||||
_conference: state['features/base/conference'], |
||||
_inviteURL: getInviteURL(state) |
||||
}; |
||||
} |
||||
|
||||
export default translate(connect(_mapStateToProps)(InviteDialog)); |
@ -1,59 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
/** |
||||
* A React Component for displaying the conference lock state. |
||||
*/ |
||||
class LockStatePanel extends Component { |
||||
/** |
||||
* {@code LockStatePanel}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* Whether or not the conference is currently locked. |
||||
*/ |
||||
locked: PropTypes.bool, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
let iconClass; |
||||
let stateClass; |
||||
let textKey; |
||||
|
||||
if (this.props.locked) { |
||||
iconClass = 'icon-security-locked'; |
||||
stateClass = 'is-locked'; |
||||
textKey = 'invite.locked'; |
||||
} else { |
||||
iconClass = 'icon-security'; |
||||
stateClass = 'is-unlocked'; |
||||
textKey = 'invite.unlocked'; |
||||
} |
||||
|
||||
return ( |
||||
<div className = { `lock-state ${stateClass}` }> |
||||
<span className = { iconClass } /> |
||||
<span> |
||||
{ this.props.t(textKey) } |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(LockStatePanel); |
@ -1,157 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { LOCKED_LOCALLY } from '../../room-lock'; |
||||
|
||||
import AddPasswordForm from './AddPasswordForm'; |
||||
import LockStatePanel from './LockStatePanel'; |
||||
import RemovePasswordForm from './RemovePasswordForm'; |
||||
|
||||
/** |
||||
* React {@code Component} for displaying the current room lock state as well as |
||||
* exposing features to modify the room lock. |
||||
*/ |
||||
class PasswordContainer extends Component { |
||||
/** |
||||
* {@code PasswordContainer}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The JitsiConference for which to display a lock state and change the |
||||
* password. |
||||
* |
||||
* @type {JitsiConference} |
||||
*/ |
||||
conference: PropTypes.object, |
||||
|
||||
/** |
||||
* The value for how the conference is locked (or undefined if not |
||||
* locked) as defined by room-lock constants. |
||||
*/ |
||||
locked: PropTypes.string, |
||||
|
||||
/** |
||||
* The current known password for the JitsiConference. |
||||
*/ |
||||
password: PropTypes.string, |
||||
|
||||
/** |
||||
* Whether or not the password editing components should be displayed. |
||||
*/ |
||||
showPasswordEdit: PropTypes.bool, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code PasswordContainer} 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 form to edit the password should display. If |
||||
* true, the form should display. |
||||
* |
||||
* @type {boolean} |
||||
*/ |
||||
isEditingPassword: false |
||||
}; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onTogglePasswordEdit = this._onTogglePasswordEdit.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<div className = 'password-overview'> |
||||
<div className = 'password-overview-status'> |
||||
<LockStatePanel locked = { Boolean(this.props.locked) } /> |
||||
{ this._renderShowPasswordLink() } |
||||
</div> |
||||
{ this._renderPasswordEdit() } |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Toggles the display of the ReactElements used to edit the password. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onTogglePasswordEdit() { |
||||
this.setState({ |
||||
isEditingPassword: !this.state.isEditingPassword |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Creates a ReactElement used for setting or removing a password. |
||||
* |
||||
* @private |
||||
* @returns {ReactElement|null} |
||||
*/ |
||||
_renderPasswordEdit() { |
||||
if (!this.state.isEditingPassword) { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
this.props.locked |
||||
? <RemovePasswordForm |
||||
conference = { this.props.conference } |
||||
lockedLocally = { this.props.locked === LOCKED_LOCALLY } |
||||
password = { this.props.password } /> |
||||
: <AddPasswordForm conference = { this.props.conference } /> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates a ReactElement that toggles displaying password edit components. |
||||
* |
||||
* @private |
||||
* @returns {ReactElement|null} |
||||
*/ |
||||
_renderShowPasswordLink() { |
||||
if (!this.props.showPasswordEdit) { |
||||
return null; |
||||
} |
||||
|
||||
let toggleStatusKey; |
||||
|
||||
if (this.state.isEditingPassword) { |
||||
toggleStatusKey = 'invite.hidePassword'; |
||||
} else if (this.props.locked) { |
||||
toggleStatusKey = 'invite.showPassword'; |
||||
} else { |
||||
toggleStatusKey = 'invite.addPassword'; |
||||
} |
||||
|
||||
return ( |
||||
<a |
||||
className = 'password-overview-toggle-edit' |
||||
onClick = { this._onTogglePasswordEdit }> |
||||
{ this.props.t(toggleStatusKey) } |
||||
</a> |
||||
); |
||||
} |
||||
} |
||||
|
||||
export default translate(PasswordContainer); |
@ -1,119 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { setPassword } from '../../base/conference'; |
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
/** |
||||
* A React {@code Component} for removing a lock from a JitsiConference. |
||||
*/ |
||||
class RemovePasswordForm extends Component { |
||||
/** |
||||
* {@code RemovePasswordForm}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The JitsiConference on which remove a lock. |
||||
* |
||||
* @type {JitsiConference} |
||||
*/ |
||||
conference: PropTypes.object, |
||||
|
||||
/** |
||||
* Invoked to send a password removal request. |
||||
*/ |
||||
dispatch: PropTypes.func, |
||||
|
||||
/** |
||||
* Whether or not the room lock, if any, was set by the local user. |
||||
*/ |
||||
lockedLocally: PropTypes.bool, |
||||
|
||||
/** |
||||
* The current known password for the JitsiConference. |
||||
*/ |
||||
password: PropTypes.string, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code RemovePasswordForm} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @private |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<div className = 'remove-password'> |
||||
<div className = 'remove-password-description'> |
||||
{ this._getPasswordPreviewText() } |
||||
</div> |
||||
<a |
||||
className = 'remove-password-link' |
||||
id = 'inviteDialogRemovePassword' |
||||
onClick = { this._onClick }> |
||||
{ this.props.t('dialog.removePassword') } |
||||
</a> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates a ReactElement for displaying the current password. |
||||
* |
||||
* @private |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_getPasswordPreviewText() { |
||||
const { lockedLocally, password, t } = this.props; |
||||
|
||||
return ( |
||||
<span> |
||||
<span> |
||||
{ `${t('dialog.currentPassword')} ` } |
||||
</span> |
||||
<span className = 'remove-password-current'> |
||||
{ lockedLocally ? password : t('passwordSetRemotely') } |
||||
</span> |
||||
</span> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Dispatches a request to remove any set password on the JitsiConference. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onClick() { |
||||
const { conference } = this.props; |
||||
|
||||
this.props.dispatch(setPassword( |
||||
conference, |
||||
conference.lock, |
||||
'' |
||||
)); |
||||
} |
||||
} |
||||
|
||||
export default translate(connect()(RemovePasswordForm)); |
@ -1,140 +0,0 @@ |
||||
import Button from '@atlaskit/button'; |
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text'; |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename); |
||||
|
||||
/** |
||||
* A React {@code Component} for displaying a value with a copy button to copy |
||||
* the value into the clipboard. |
||||
*/ |
||||
class ShareLinkForm extends Component { |
||||
/** |
||||
* {@code ShareLinkForm}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func, |
||||
|
||||
/** |
||||
* The value to be displayed and copied into the clipboard. |
||||
*/ |
||||
toCopy: PropTypes.string |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code ShareLinkForm} 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 React {@code component} for display |
||||
* the meeting link in an input element. |
||||
* |
||||
* @private |
||||
* @type {ReactComponent} |
||||
*/ |
||||
this._inputComponent = null; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onClick = this._onClick.bind(this); |
||||
this._onDropdownTriggerInputChange |
||||
= this._onDropdownTriggerInputChange.bind(this); |
||||
this._setInput = this._setInput.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t } = this.props; |
||||
const inputValue = this.props.toCopy || t('inviteUrlDefaultMsg'); |
||||
|
||||
return ( |
||||
<div className = 'form-control'> |
||||
<label className = 'form-control__label'> |
||||
{ t('dialog.shareLink') } |
||||
</label> |
||||
<div className = 'form-control__container'> |
||||
<div className = 'form-control__input-container'> |
||||
<TextField |
||||
id = 'inviteLinkRef' |
||||
isLabelHidden = { true } |
||||
isReadOnly = { true } |
||||
label = 'invite link' |
||||
onChange = { this._onDropdownTriggerInputChange } |
||||
ref = { this._setInput } |
||||
shouldFitContainer = { true } |
||||
type = 'text' |
||||
value = { inputValue } /> |
||||
</div> |
||||
<Button |
||||
appearance = 'default' |
||||
onClick = { this._onClick } |
||||
type = 'button'> |
||||
{ t('dialog.copy') } |
||||
</Button> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Copies the passed in value to the clipboard. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onClick() { |
||||
try { |
||||
const { input } = this._inputComponent; |
||||
|
||||
input.select(); |
||||
document.execCommand('copy'); |
||||
input.blur(); |
||||
} catch (err) { |
||||
logger.error('error when copying the text', err); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This is a no-op function used to stub out TextField's onChange in order |
||||
* to prevent TextField from printing prop type validation errors. TextField |
||||
* is used as a trigger for the dropdown in {@code ShareLinkForm} to get the |
||||
* desired AtlasKit input look for the UI. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_onDropdownTriggerInputChange() { |
||||
// Intentionally left empty.
|
||||
} |
||||
|
||||
/** |
||||
* Sets the internal reference to the React Component wrapping the input |
||||
* with id {@code inviteLinkRef}. |
||||
* |
||||
* @param {ReactComponent} inputComponent - React Component for displaying |
||||
* an input for displaying the meeting link. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setInput(inputComponent) { |
||||
this._inputComponent = inputComponent; |
||||
} |
||||
} |
||||
|
||||
export default translate(ShareLinkForm); |
Loading…
Reference in new issue