mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
ref(invite): add people form (#13207)
This commit is contained in:
@@ -60,7 +60,6 @@ $flagsImagePath: "../images/";
|
|||||||
@import 'filmstrip/vertical_filmstrip';
|
@import 'filmstrip/vertical_filmstrip';
|
||||||
@import 'filmstrip/vertical_filmstrip_overrides';
|
@import 'filmstrip/vertical_filmstrip_overrides';
|
||||||
@import 'unsupported-browser/main';
|
@import 'unsupported-browser/main';
|
||||||
@import 'modals/invite/add-people';
|
|
||||||
@import 'deep-linking/main';
|
@import 'deep-linking/main';
|
||||||
@import 'transcription-subtitles';
|
@import 'transcription-subtitles';
|
||||||
@import '_meetings_list.scss';
|
@import '_meetings_list.scss';
|
||||||
|
|||||||
@@ -12,24 +12,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Styling inline dialog errors.
|
|
||||||
*/
|
|
||||||
.inline-dialog-error {
|
|
||||||
margin-top: 16px;
|
|
||||||
|
|
||||||
&-text {
|
|
||||||
color: $dialogErrorText;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-button {
|
|
||||||
display: block;
|
|
||||||
margin: 16px auto 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Styling shared video dialog errors.
|
* Styling shared video dialog errors.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -41,10 +41,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Styles errors in the MultiSelectAutocomplete.
|
|
||||||
*/
|
|
||||||
.autocomplete-error {
|
|
||||||
min-width: 260px;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,57 +1,4 @@
|
|||||||
.invite-more {
|
.invite-more {
|
||||||
&-container {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
transition: margin-bottom 0.3s;
|
|
||||||
|
|
||||||
&.elevated {
|
|
||||||
margin-bottom: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-header {
|
|
||||||
max-width: 100%;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-button {
|
|
||||||
display: flex;
|
|
||||||
max-width: 100%;
|
|
||||||
height: 40px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: #0376DA;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@media (hover: hover) and (pointer: fine) {
|
|
||||||
&:hover {
|
|
||||||
background: #278ADF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-text {
|
|
||||||
margin-left: 8px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&-dialog {
|
&-dialog {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -65,59 +12,6 @@
|
|||||||
background: #5E6D7A;
|
background: #5E6D7A;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.email-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 8px 8px 16px;
|
|
||||||
margin-top: 24px;
|
|
||||||
width: calc(100% - 26px);
|
|
||||||
height: 22px;
|
|
||||||
|
|
||||||
background: #2A3A4B;
|
|
||||||
border: 1px solid #5E6D7A;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.invite-buttons {
|
|
||||||
width: 100%;
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
& > a {
|
|
||||||
display: inline-block;
|
|
||||||
height: 24px;
|
|
||||||
min-width: 48px;
|
|
||||||
border-radius: 3px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-cancel {
|
|
||||||
margin-right: 16px;
|
|
||||||
padding: 7px 15px;
|
|
||||||
background: #2A3A4B;
|
|
||||||
border: 1px solid #5E6D7A;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-add {
|
|
||||||
padding: 8px 16px;
|
|
||||||
background: #0376DA;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
& > a {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.stream {
|
&.stream {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -158,14 +52,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-browser {
|
|
||||||
.invite-more-content {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.invite-more-button {
|
|
||||||
height: 48px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { WithTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
import { translate } from '../../../i18n/functions';
|
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||||
import Button from '../../../ui/components/web/Button';
|
import Button from '../../../ui/components/web/Button';
|
||||||
|
|
||||||
|
const useStyles = makeStyles()(theme => {
|
||||||
|
return {
|
||||||
|
dialog: {
|
||||||
|
backgroundColor: theme.palette.ui01,
|
||||||
|
border: `1px solid ${theme.palette.ui04}`,
|
||||||
|
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||||
|
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
|
||||||
|
color: theme.palette.text01,
|
||||||
|
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||||
|
padding: `${theme.spacing(3)} 10`,
|
||||||
|
'& .retry-button': {
|
||||||
|
margin: '16px auto 0 auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link InlineDialogFailure}.
|
* The type of the React {@code Component} props of {@link InlineDialogFailure}.
|
||||||
*/
|
*/
|
||||||
interface IProps extends WithTranslation {
|
interface IProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to retry the call that previously didn't succeed.
|
* Allows to retry the call that previously didn't succeed.
|
||||||
@@ -22,50 +40,48 @@ interface IProps extends WithTranslation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inline dialog that represents a failure and allows a retry.
|
* Inline dialog that represents a failure and allows a retry.
|
||||||
|
*
|
||||||
|
* @returns {Element}
|
||||||
*/
|
*/
|
||||||
class InlineDialogFailure extends Component<IProps> {
|
const InlineDialogFailure = ({
|
||||||
/**
|
onRetry,
|
||||||
* Renders the content of this component.
|
showSupportLink
|
||||||
*
|
}: IProps) => {
|
||||||
* @returns {ReactElement}
|
const { t } = useTranslation();
|
||||||
*/
|
const { classes } = useStyles();
|
||||||
render() {
|
|
||||||
const { t, showSupportLink } = this.props;
|
|
||||||
|
|
||||||
const supportLink = interfaceConfig.SUPPORT_URL;
|
const supportLink = interfaceConfig.SUPPORT_URL;
|
||||||
const supportString = t('inlineDialogFailure.supportMsg');
|
const supportString = t('inlineDialogFailure.supportMsg');
|
||||||
const supportLinkElem
|
const supportLinkElem = supportLink && showSupportLink
|
||||||
= supportLink && showSupportLink
|
? (
|
||||||
? (
|
<div>
|
||||||
<div className = 'inline-dialog-error-text'>
|
<span>{ supportString.padEnd(supportString.length + 1) }
|
||||||
<span>{ supportString.padEnd(supportString.length + 1) }
|
</span>
|
||||||
</span>
|
<span>
|
||||||
<span>
|
<a
|
||||||
<a
|
href = { supportLink }
|
||||||
href = { supportLink }
|
rel = 'noopener noreferrer'
|
||||||
rel = 'noopener noreferrer'
|
target = '_blank'>
|
||||||
target = '_blank'>
|
{ t('inlineDialogFailure.support') }
|
||||||
{ t('inlineDialogFailure.support') }
|
</a>
|
||||||
</a>
|
</span>
|
||||||
</span>
|
<span>.</span>
|
||||||
<span>.</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className = 'inline-dialog-error'>
|
|
||||||
<div className = 'inline-dialog-error-text'>
|
|
||||||
{ t('inlineDialogFailure.msg') }
|
|
||||||
</div>
|
|
||||||
{ supportLinkElem }
|
|
||||||
<Button
|
|
||||||
className = 'inline-dialog-error-button'
|
|
||||||
label = { t('inlineDialogFailure.retry') }
|
|
||||||
onClick = { this.props.onRetry } />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
}
|
: null;
|
||||||
}
|
|
||||||
|
|
||||||
export default translate(InlineDialogFailure);
|
return (
|
||||||
|
<div className = { classes.dialog }>
|
||||||
|
<div>
|
||||||
|
{ t('inlineDialogFailure.msg') }
|
||||||
|
</div>
|
||||||
|
{ supportLinkElem }
|
||||||
|
<Button
|
||||||
|
className = 'retry-button'
|
||||||
|
label = { t('inlineDialogFailure.retry') }
|
||||||
|
onClick = { onRetry } />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineDialogFailure;
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
import AKInlineDialog from '@atlaskit/inline-dialog';
|
|
||||||
import { MultiSelectStateless } from '@atlaskit/multi-select';
|
|
||||||
import _debounce from 'lodash/debounce';
|
import _debounce from 'lodash/debounce';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { MultiSelectItem } from '../../../ui/components/types';
|
||||||
|
import MultiSelect from '../../../ui/components/web/MultiSelect';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
|
|
||||||
import InlineDialogFailure from './InlineDialogFailure';
|
import InlineDialogFailure from './InlineDialogFailure';
|
||||||
@@ -13,119 +11,119 @@ import InlineDialogFailure from './InlineDialogFailure';
|
|||||||
* The type of the React {@code Component} props of
|
* The type of the React {@code Component} props of
|
||||||
* {@link MultiSelectAutocomplete}.
|
* {@link MultiSelectAutocomplete}.
|
||||||
*/
|
*/
|
||||||
type Props = {
|
interface IProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default value of the selected item.
|
* The default value of the selected item.
|
||||||
*/
|
*/
|
||||||
defaultValue: Array<Object>,
|
defaultValue?: Array<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional footer to show as a last element in the results.
|
* Optional footer to show as a last element in the results.
|
||||||
* Should be of type {content: <some content>}.
|
* Should be of type {content: <some content>}.
|
||||||
*/
|
*/
|
||||||
footer: Object,
|
footer?: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the component is disabled.
|
* Indicates if the component is disabled.
|
||||||
*/
|
*/
|
||||||
isDisabled: boolean,
|
isDisabled: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text to display while a query is executing.
|
* Text to display while a query is executing.
|
||||||
*/
|
*/
|
||||||
loadingMessage: string,
|
loadingMessage: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text to show when no matches are found.
|
* The text to show when no matches are found.
|
||||||
*/
|
*/
|
||||||
noMatchesFound: string,
|
noMatchesFound: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function called immediately before a selection has been actually
|
* The function called immediately before a selection has been actually
|
||||||
* selected. Provides an opportunity to do any formatting.
|
* selected. Provides an opportunity to do any formatting.
|
||||||
*/
|
*/
|
||||||
onItemSelected: Function,
|
onItemSelected: Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function called when the selection changes.
|
* The function called when the selection changes.
|
||||||
*/
|
*/
|
||||||
onSelectionChange: Function,
|
onSelectionChange: Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The placeholder text of the input component.
|
* The placeholder text of the input component.
|
||||||
*/
|
*/
|
||||||
placeholder: string,
|
placeholder: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service providing the search.
|
* The service providing the search.
|
||||||
*/
|
*/
|
||||||
resourceClient: { makeQuery: Function, parseResults: Function },
|
resourceClient: { makeQuery: Function; parseResults: Function; };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the component should fit the container.
|
* Indicates if the component should fit the container.
|
||||||
*/
|
*/
|
||||||
shouldFitContainer: boolean,
|
shouldFitContainer: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if we should focus.
|
* Indicates if we should focus.
|
||||||
*/
|
*/
|
||||||
shouldFocus: boolean,
|
shouldFocus: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether the support link should be shown in case of an error.
|
* Indicates whether the support link should be shown in case of an error.
|
||||||
*/
|
*/
|
||||||
showSupportLink: Boolean,
|
showSupportLink: Boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} state of
|
* The type of the React {@code Component} state of
|
||||||
* {@link MultiSelectAutocomplete}.
|
* {@link MultiSelectAutocomplete}.
|
||||||
*/
|
*/
|
||||||
type State = {
|
interface IState {
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the dropdown is open.
|
|
||||||
*/
|
|
||||||
isOpen: boolean,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The text that filters the query result of the search.
|
|
||||||
*/
|
|
||||||
filterValue: string,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the component is currently loading results.
|
|
||||||
*/
|
|
||||||
loading: boolean,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if there was an error.
|
* Indicates if there was an error.
|
||||||
*/
|
*/
|
||||||
error: boolean,
|
error: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The text that filters the query result of the search.
|
||||||
|
*/
|
||||||
|
filterValue: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the dropdown is open.
|
||||||
|
*/
|
||||||
|
isOpen: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of result items.
|
* The list of result items.
|
||||||
*/
|
*/
|
||||||
items: Array<Object>,
|
items: Array<MultiSelectItem>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the component is currently loading results.
|
||||||
|
*/
|
||||||
|
loading: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of selected items.
|
* The list of selected items.
|
||||||
*/
|
*/
|
||||||
selectedItems: Array<Object>
|
selectedItems: Array<MultiSelectItem>;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A MultiSelect that is also auto-completing.
|
* A MultiSelect that is also auto-completing.
|
||||||
*/
|
*/
|
||||||
class MultiSelectAutocomplete extends Component<Props, State> {
|
class MultiSelectAutocomplete extends Component<IProps, IState> {
|
||||||
/**
|
/**
|
||||||
* Initializes a new {@code MultiSelectAutocomplete} instance.
|
* Initializes a new {@code MultiSelectAutocomplete} instance.
|
||||||
*
|
*
|
||||||
* @param {Object} props - The read-only properties with which the new
|
* @param {Object} props - The read-only properties with which the new
|
||||||
* instance is to be initialized.
|
* instance is to be initialized.
|
||||||
*/
|
*/
|
||||||
constructor(props: Props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const defaultValue = this.props.defaultValue || [];
|
const defaultValue = this.props.defaultValue || [];
|
||||||
@@ -148,11 +146,11 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
/**
|
/**
|
||||||
* Sets the items to display as selected.
|
* Sets the items to display as selected.
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} selectedItems - The list of items to display as
|
* @param {Array<MultiSelectItem>} selectedItems - The list of items to display as
|
||||||
* having been selected.
|
* having been selected.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
setSelectedItems(selectedItems: Array<Object> = []) {
|
setSelectedItems(selectedItems: Array<MultiSelectItem> = []) {
|
||||||
this.setState({ selectedItems });
|
this.setState({ selectedItems });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,37 +160,32 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const shouldFitContainer = this.props.shouldFitContainer || false;
|
const autoFocus = this.props.shouldFocus || false;
|
||||||
const shouldFocus = this.props.shouldFocus || false;
|
const disabled = this.props.isDisabled || false;
|
||||||
const isDisabled = this.props.isDisabled || false;
|
|
||||||
const placeholder = this.props.placeholder || '';
|
const placeholder = this.props.placeholder || '';
|
||||||
const noMatchesFound = this.props.noMatchesFound || '';
|
const noMatchesFound = this.props.noMatchesFound || '';
|
||||||
|
const errorDialog = this._renderError();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<MultiSelectStateless
|
<MultiSelect
|
||||||
|
autoFocus = { autoFocus }
|
||||||
|
disabled = { disabled }
|
||||||
|
error = { this.state.error }
|
||||||
|
errorDialog = { errorDialog }
|
||||||
filterValue = { this.state.filterValue }
|
filterValue = { this.state.filterValue }
|
||||||
footer = { this.props.footer }
|
|
||||||
icon = { null }
|
|
||||||
isDisabled = { isDisabled }
|
|
||||||
isLoading = { this.state.loading }
|
|
||||||
isOpen = { this.state.isOpen }
|
isOpen = { this.state.isOpen }
|
||||||
items = { this.state.items }
|
items = { this.state.items }
|
||||||
loadingMessage = { this.props.loadingMessage }
|
noMatchesText = { noMatchesFound }
|
||||||
noMatchesFound = { noMatchesFound }
|
|
||||||
onFilterChange = { this._onFilterChange }
|
onFilterChange = { this._onFilterChange }
|
||||||
onRemoved = { this._onSelectionChange }
|
onRemoved = { this._onSelectionChange }
|
||||||
onSelected = { this._onSelectionChange }
|
onSelected = { this._onSelectionChange }
|
||||||
placeholder = { placeholder }
|
placeholder = { placeholder }
|
||||||
selectedItems = { this.state.selectedItems }
|
selectedItems = { this.state.selectedItems } />
|
||||||
shouldFitContainer = { shouldFitContainer }
|
|
||||||
shouldFocus = { shouldFocus } />
|
|
||||||
{ this._renderError() }
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onFilterChange: (string) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the state and sends a query on filter change.
|
* Sets the state and sends a query on filter change.
|
||||||
@@ -201,7 +194,7 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onFilterChange(filterValue) {
|
_onFilterChange(filterValue: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
// Clean the error if the filterValue is empty.
|
// Clean the error if the filterValue is empty.
|
||||||
error: this.state.error && Boolean(filterValue),
|
error: this.state.error && Boolean(filterValue),
|
||||||
@@ -215,8 +208,6 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRetry: () => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retries the query on retry.
|
* Retries the query on retry.
|
||||||
*
|
*
|
||||||
@@ -227,18 +218,16 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
this._sendQuery(this.state.filterValue);
|
this._sendQuery(this.state.filterValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSelectionChange: (Object) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the selected items when a selection event occurs.
|
* Updates the selected items when a selection event occurs.
|
||||||
*
|
*
|
||||||
* @param {Object} item - The selected item.
|
* @param {any} item - The selected item.
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onSelectionChange(item) {
|
_onSelectionChange(item: any) {
|
||||||
const existing
|
const existing
|
||||||
= this.state.selectedItems.find(k => k.value === item.value);
|
= this.state.selectedItems.find((k: any) => k.value === item.value);
|
||||||
let selectedItems = this.state.selectedItems;
|
let selectedItems = this.state.selectedItems;
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -265,30 +254,22 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
if (!this.state.error) {
|
if (!this.state.error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const content = (
|
|
||||||
<div className = 'autocomplete-error'>
|
|
||||||
<InlineDialogFailure
|
|
||||||
onRetry = { this._onRetry }
|
|
||||||
showSupportLink = { this.props.showSupportLink } />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AKInlineDialog
|
|
||||||
content = { content }
|
<InlineDialogFailure
|
||||||
isOpen = { true } />
|
onRetry = { this._onRetry }
|
||||||
|
showSupportLink = { this.props.showSupportLink } />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendQuery: (string) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a query to the resourceClient.
|
* Sends a query to the resourceClient.
|
||||||
*
|
*
|
||||||
* @param {string} filterValue - The string to use for the search.
|
* @param {string} filterValue - The string to use for the search.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_sendQuery(filterValue) {
|
_sendQuery(filterValue: string) {
|
||||||
if (!filterValue) {
|
if (!filterValue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -299,11 +280,11 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
|
|
||||||
const resourceClient = this.props.resourceClient || {
|
const resourceClient = this.props.resourceClient || {
|
||||||
makeQuery: () => Promise.resolve([]),
|
makeQuery: () => Promise.resolve([]),
|
||||||
parseResults: results => results
|
parseResults: (results: any) => results
|
||||||
};
|
};
|
||||||
|
|
||||||
resourceClient.makeQuery(filterValue)
|
resourceClient.makeQuery(filterValue)
|
||||||
.then(results => {
|
.then((results: any) => {
|
||||||
if (this.state.filterValue !== filterValue) {
|
if (this.state.filterValue !== filterValue) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: false
|
error: false
|
||||||
@@ -311,20 +292,15 @@ class MultiSelectAutocomplete extends Component<Props, State> {
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const itemGroups = [
|
|
||||||
{
|
|
||||||
items: resourceClient.parseResults(results)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
items: itemGroups,
|
items: resourceClient.parseResults(results),
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: false
|
error: false
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error: Error) => {
|
||||||
logger.error('MultiSelectAutocomplete error in query', error);
|
logger.error('MultiSelectAutocomplete error in query', error);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -105,3 +105,11 @@ export interface ISwitchProps {
|
|||||||
*/
|
*/
|
||||||
onChange: (on?: boolean) => void;
|
onChange: (on?: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MultiSelectItem = {
|
||||||
|
content: string;
|
||||||
|
description?: string;
|
||||||
|
elemBefore?: Element;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ interface IProps extends IInputProps {
|
|||||||
maxRows?: number;
|
maxRows?: number;
|
||||||
minRows?: number;
|
minRows?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
onBlur?: (e: any) => void;
|
||||||
|
onFocus?: (event: React.FocusEvent) => void;
|
||||||
onKeyPress?: (e: React.KeyboardEvent) => void;
|
onKeyPress?: (e: React.KeyboardEvent) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
@@ -148,7 +150,9 @@ const Input = React.forwardRef<any, IProps>(({
|
|||||||
maxRows,
|
maxRows,
|
||||||
minRows,
|
minRows,
|
||||||
name,
|
name,
|
||||||
|
onBlur,
|
||||||
onChange,
|
onChange,
|
||||||
|
onFocus,
|
||||||
onKeyPress,
|
onKeyPress,
|
||||||
placeholder,
|
placeholder,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
@@ -208,7 +212,9 @@ const Input = React.forwardRef<any, IProps>(({
|
|||||||
{ ...(id ? { id } : {}) }
|
{ ...(id ? { id } : {}) }
|
||||||
maxLength = { maxLength }
|
maxLength = { maxLength }
|
||||||
name = { name }
|
name = { name }
|
||||||
|
onBlur = { onBlur }
|
||||||
onChange = { handleChange }
|
onChange = { handleChange }
|
||||||
|
onFocus = { onFocus }
|
||||||
onKeyPress = { onKeyPress }
|
onKeyPress = { onKeyPress }
|
||||||
placeholder = { placeholder }
|
placeholder = { placeholder }
|
||||||
readOnly = { readOnly }
|
readOnly = { readOnly }
|
||||||
|
|||||||
175
react/features/base/ui/components/web/MultiSelect.tsx
Normal file
175
react/features/base/ui/components/web/MultiSelect.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
import { IconCloseLarge } from '../../../icons/svg';
|
||||||
|
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||||
|
import { MultiSelectItem } from '../types';
|
||||||
|
|
||||||
|
import ClickableIcon from './ClickableIcon';
|
||||||
|
import Input from './Input';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
autoFocus?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
error?: boolean;
|
||||||
|
errorDialog?: JSX.Element | null;
|
||||||
|
filterValue?: string;
|
||||||
|
isOpen?: boolean;
|
||||||
|
items: MultiSelectItem[];
|
||||||
|
noMatchesText?: string;
|
||||||
|
onFilterChange?: (value: string) => void;
|
||||||
|
onRemoved: (item: any) => void;
|
||||||
|
onSelected: (item: any) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
selectedItems?: MultiSelectItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles()(theme => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
position: 'relative'
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
'&.found': {
|
||||||
|
position: 'absolute',
|
||||||
|
boxShadow: '0px 5px 10px rgba(0, 0, 0, 0.75)'
|
||||||
|
},
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
width: '100%',
|
||||||
|
backgroundColor: theme.palette.ui01,
|
||||||
|
border: `1px solid ${theme.palette.ui04}`,
|
||||||
|
borderRadius: `${Number(theme.shape.borderRadius)}px`,
|
||||||
|
...withPixelLineHeight(theme.typography.bodyShortRegular),
|
||||||
|
zIndex: 2,
|
||||||
|
maxHeight: '400px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: '0'
|
||||||
|
},
|
||||||
|
listItem: {
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
padding: `${theme.spacing(2)} ${theme.spacing(3)}`,
|
||||||
|
alignItems: 'center',
|
||||||
|
'& .content': {
|
||||||
|
// 38px because of the icon before the content
|
||||||
|
inlineSize: 'calc(100% - 38px)',
|
||||||
|
overflowWrap: 'break-word',
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
color: theme.palette.text01,
|
||||||
|
'&.with-remove': {
|
||||||
|
// 60px because of the icon before the content and the remove button
|
||||||
|
inlineSize: 'calc(100% - 60px)',
|
||||||
|
marginRight: theme.spacing(2),
|
||||||
|
'&.without-before': {
|
||||||
|
marginLeft: 0,
|
||||||
|
inlineSize: 'calc(100% - 38px)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'&.without-before': {
|
||||||
|
marginLeft: 0,
|
||||||
|
inlineSize: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'&.found': {
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: `10px ${theme.spacing(3)}`,
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.ui02
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'&.disabled': {
|
||||||
|
cursor: 'not-allowed',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.ui01
|
||||||
|
},
|
||||||
|
color: theme.palette.text03
|
||||||
|
}
|
||||||
|
},
|
||||||
|
errorMessage: {
|
||||||
|
position: 'absolute',
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const MultiSelect = ({
|
||||||
|
autoFocus,
|
||||||
|
disabled,
|
||||||
|
error,
|
||||||
|
errorDialog,
|
||||||
|
placeholder,
|
||||||
|
items,
|
||||||
|
filterValue,
|
||||||
|
onFilterChange,
|
||||||
|
isOpen,
|
||||||
|
noMatchesText,
|
||||||
|
onSelected,
|
||||||
|
selectedItems,
|
||||||
|
onRemoved
|
||||||
|
}: IProps) => {
|
||||||
|
const { classes } = useStyles();
|
||||||
|
const inputRef = useRef();
|
||||||
|
const selectItem = useCallback(item => () => onSelected(item), [ onSelected ]);
|
||||||
|
const removeItem = useCallback(item => () => onRemoved(item), [ onRemoved ]);
|
||||||
|
const foundItems = useMemo(() => (
|
||||||
|
<div className = { `${classes.items} found` }>
|
||||||
|
{
|
||||||
|
items.length > 0
|
||||||
|
? items.map(item => (
|
||||||
|
<div
|
||||||
|
className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''} found` }
|
||||||
|
key = { item.value }
|
||||||
|
onClick = { item.isDisabled ? undefined : selectItem(item) }>
|
||||||
|
{item.elemBefore}
|
||||||
|
<div className = { `content ${item.elemBefore ? '' : 'without-before'}` }>
|
||||||
|
{item.content}
|
||||||
|
{item.description && <p>{item.description}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
: <div>{noMatchesText}</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
), [ items ]);
|
||||||
|
|
||||||
|
const errorMessageDialog = useMemo(() =>
|
||||||
|
error && <div className = { classes.errorMessage }>
|
||||||
|
{ errorDialog }
|
||||||
|
</div>, [ error ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = { classes.container }>
|
||||||
|
<Input
|
||||||
|
autoFocus = { autoFocus }
|
||||||
|
disabled = { disabled }
|
||||||
|
onChange = { onFilterChange }
|
||||||
|
placeholder = { placeholder }
|
||||||
|
ref = { inputRef }
|
||||||
|
value = { filterValue ?? '' } />
|
||||||
|
{isOpen && foundItems}
|
||||||
|
{ errorMessageDialog }
|
||||||
|
{ selectedItems && selectedItems?.length > 0 && (
|
||||||
|
<div className = { classes.items }>
|
||||||
|
{ selectedItems.map(item => (
|
||||||
|
<div
|
||||||
|
className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''}` }
|
||||||
|
key = { item.value }>
|
||||||
|
{item.elemBefore}
|
||||||
|
<div className = { `content with-remove ${item.elemBefore ? '' : 'without-before'}` }>
|
||||||
|
<p>{item.content}</p>
|
||||||
|
</div>
|
||||||
|
<ClickableIcon
|
||||||
|
accessibilityLabel = { 'multi-select-unselect' }
|
||||||
|
icon = { IconCloseLarge }
|
||||||
|
id = 'modal-header-close-button'
|
||||||
|
onClick = { removeItem(item) } />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MultiSelect;
|
||||||
@@ -3,13 +3,12 @@ import { Component } from 'react';
|
|||||||
import { createInviteDialogEvent } from '../../../analytics/AnalyticsEvents';
|
import { createInviteDialogEvent } from '../../../analytics/AnalyticsEvents';
|
||||||
import { sendAnalytics } from '../../../analytics/functions';
|
import { sendAnalytics } from '../../../analytics/functions';
|
||||||
import { IReduxState } from '../../../app/types';
|
import { IReduxState } from '../../../app/types';
|
||||||
import { showNotification } from '../../../notifications/actions';
|
import { showErrorNotification, showNotification } from '../../../notifications/actions';
|
||||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
|
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
|
||||||
import { INotificationProps } from '../../../notifications/types';
|
import { INotificationProps } from '../../../notifications/types';
|
||||||
import { invite } from '../../actions';
|
import { invite } from '../../actions';
|
||||||
import { INVITE_TYPES } from '../../constants';
|
import { INVITE_TYPES } from '../../constants';
|
||||||
import {
|
import {
|
||||||
GetInviteResultsOptions,
|
|
||||||
getInviteResultsForQuery,
|
getInviteResultsForQuery,
|
||||||
getInviteTypeCounts,
|
getInviteTypeCounts,
|
||||||
isAddPeopleEnabled,
|
isAddPeopleEnabled,
|
||||||
@@ -17,7 +16,7 @@ import {
|
|||||||
isSipInviteEnabled
|
isSipInviteEnabled
|
||||||
} from '../../functions';
|
} from '../../functions';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import { IInvitee } from '../../types';
|
import { IInviteSelectItem, IInvitee } from '../../types';
|
||||||
|
|
||||||
export interface IProps {
|
export interface IProps {
|
||||||
|
|
||||||
@@ -26,6 +25,9 @@ export interface IProps {
|
|||||||
*/
|
*/
|
||||||
_addPeopleEnabled: boolean;
|
_addPeopleEnabled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The app id of the user.
|
||||||
|
*/
|
||||||
_appId: string;
|
_appId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,17 +50,17 @@ export interface IProps {
|
|||||||
*/
|
*/
|
||||||
_dialOutRegionUrl: string;
|
_dialOutRegionUrl: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The JWT token.
|
* The JWT token.
|
||||||
*/
|
*/
|
||||||
_jwt: string;
|
_jwt: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The query types used when searching people.
|
* The query types used when searching people.
|
||||||
*/
|
*/
|
||||||
_peopleSearchQueryTypes: Array<string>;
|
_peopleSearchQueryTypes: Array<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL pointing to the service allowing for people search.
|
* The URL pointing to the service allowing for people search.
|
||||||
*/
|
*/
|
||||||
_peopleSearchUrl: string;
|
_peopleSearchUrl: string;
|
||||||
@@ -66,7 +68,7 @@ export interface IProps {
|
|||||||
/**
|
/**
|
||||||
* Whether or not to allow sip invites.
|
* Whether or not to allow sip invites.
|
||||||
*/
|
*/
|
||||||
_sipInviteEnabled: boolean;
|
_sipInviteEnabled: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Redux dispatch function.
|
* The Redux dispatch function.
|
||||||
@@ -90,14 +92,13 @@ export interface IState {
|
|||||||
/**
|
/**
|
||||||
* The list of invite items.
|
* The list of invite items.
|
||||||
*/
|
*/
|
||||||
inviteItems: Array<Object>;
|
inviteItems: Array<IInviteSelectItem>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements an abstract dialog to invite people to the conference.
|
* Implements an abstract dialog to invite people to the conference.
|
||||||
*/
|
*/
|
||||||
export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
export default class AbstractAddPeopleDialog<P extends IProps, S extends IState> extends Component<P, S> {
|
||||||
extends Component<P, S> {
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the component.
|
* Constructor of the component.
|
||||||
*
|
*
|
||||||
@@ -112,7 +113,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
/**
|
/**
|
||||||
* Retrieves the notification display name for the invitee.
|
* Retrieves the notification display name for the invitee.
|
||||||
*
|
*
|
||||||
* @param {Object} invitee - The invitee object.
|
* @param {IInvitee} invitee - The invitee object.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
_getDisplayName(invitee: IInvitee) {
|
_getDisplayName(invitee: IInvitee) {
|
||||||
@@ -124,7 +125,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
return invitee.address;
|
return invitee.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
return invitee.name;
|
return invitee.name ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,8 +136,8 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
* no invites left to send. If any are left, that means an invite failed
|
* no invites left to send. If any are left, that means an invite failed
|
||||||
* and an error state should display.
|
* and an error state should display.
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} invitees - The items to be invited.
|
* @param {Array<IInvitee>} invitees - The items to be invited.
|
||||||
* @returns {Promise<Array<Object>>}
|
* @returns {Promise<Array<any>>}
|
||||||
*/
|
*/
|
||||||
_invite(invitees: IInvitee[]) {
|
_invite(invitees: IInvitee[]) {
|
||||||
const inviteTypeCounts = getInviteTypeCounts(invitees);
|
const inviteTypeCounts = getInviteTypeCounts(invitees);
|
||||||
@@ -176,10 +177,9 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
'error', 'invite', {
|
'error', 'invite', {
|
||||||
...erroredInviteTypeCounts
|
...erroredInviteTypeCounts
|
||||||
}));
|
}));
|
||||||
|
dispatch(showErrorNotification({
|
||||||
this.setState({
|
titleKey: 'addPeople.failedToAdd'
|
||||||
addToCallError: true
|
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||||
});
|
|
||||||
} else if (!_callFlowsEnabled) {
|
} else if (!_callFlowsEnabled) {
|
||||||
const invitedCount = invitees.length;
|
const invitedCount = invitees.length;
|
||||||
let notificationProps: INotificationProps | undefined;
|
let notificationProps: INotificationProps | undefined;
|
||||||
@@ -188,7 +188,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
notificationProps = {
|
notificationProps = {
|
||||||
titleArguments: {
|
titleArguments: {
|
||||||
name: this._getDisplayName(invitees[0]),
|
name: this._getDisplayName(invitees[0]),
|
||||||
count: invitedCount - 1
|
count: `${invitedCount - 1}`
|
||||||
},
|
},
|
||||||
titleKey: 'notify.invitedThreePlusMembers'
|
titleKey: 'notify.invitedThreePlusMembers'
|
||||||
};
|
};
|
||||||
@@ -250,7 +250,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
|||||||
_peopleSearchUrl: peopleSearchUrl,
|
_peopleSearchUrl: peopleSearchUrl,
|
||||||
_sipInviteEnabled: sipInviteEnabled
|
_sipInviteEnabled: sipInviteEnabled
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const options: GetInviteResultsOptions = {
|
const options = {
|
||||||
addPeopleEnabled,
|
addPeopleEnabled,
|
||||||
appId,
|
appId,
|
||||||
dialOutAuthUrl,
|
dialOutAuthUrl,
|
||||||
@@ -292,14 +292,14 @@ export function _mapStateToProps(state: IReduxState) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
_addPeopleEnabled: isAddPeopleEnabled(state),
|
_addPeopleEnabled: isAddPeopleEnabled(state),
|
||||||
_appId: state['features/base/jwt']?.tenant,
|
_appId: state['features/base/jwt']?.tenant ?? '',
|
||||||
_callFlowsEnabled: callFlowsEnabled,
|
_callFlowsEnabled: callFlowsEnabled ?? false,
|
||||||
_dialOutAuthUrl: dialOutAuthUrl,
|
_dialOutAuthUrl: dialOutAuthUrl ?? '',
|
||||||
_dialOutRegionUrl: dialOutRegionUrl,
|
_dialOutRegionUrl: dialOutRegionUrl ?? '',
|
||||||
_dialOutEnabled: isDialOutEnabled(state),
|
_dialOutEnabled: isDialOutEnabled(state),
|
||||||
_jwt: state['features/base/jwt'].jwt,
|
_jwt: state['features/base/jwt'].jwt ?? '',
|
||||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
_peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
|
||||||
_peopleSearchUrl: peopleSearchUrl,
|
_peopleSearchUrl: peopleSearchUrl ?? '',
|
||||||
_sipInviteEnabled: isSipInviteEnabled(state)
|
_sipInviteEnabled: isSipInviteEnabled(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,6 @@ import CopyMeetingLinkSection from './CopyMeetingLinkSection';
|
|||||||
import DialInLimit from './DialInLimit';
|
import DialInLimit from './DialInLimit';
|
||||||
import DialInSection from './DialInSection';
|
import DialInSection from './DialInSection';
|
||||||
import InviteByEmailSection from './InviteByEmailSection';
|
import InviteByEmailSection from './InviteByEmailSection';
|
||||||
// eslint-disable-next-line lines-around-comment
|
|
||||||
// @ts-ignore
|
|
||||||
import InviteContactsSection from './InviteContactsSection';
|
import InviteContactsSection from './InviteContactsSection';
|
||||||
import LiveStreamSection from './LiveStreamSection';
|
import LiveStreamSection from './LiveStreamSection';
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +1,93 @@
|
|||||||
// @flow
|
import { Theme } from '@mui/material';
|
||||||
|
import { withStyles } from '@mui/styles';
|
||||||
import InlineMessage from '@atlaskit/inline-message';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import type { Dispatch } from 'redux';
|
|
||||||
|
|
||||||
|
import { IReduxState, IStore } from '../../../../app/types';
|
||||||
import Avatar from '../../../../base/avatar/components/Avatar';
|
import Avatar from '../../../../base/avatar/components/Avatar';
|
||||||
import { translate } from '../../../../base/i18n/functions';
|
import { translate } from '../../../../base/i18n/functions';
|
||||||
import Icon from '../../../../base/icons/components/Icon';
|
import Icon from '../../../../base/icons/components/Icon';
|
||||||
import { IconPhoneRinging } from '../../../../base/icons/svg';
|
import { IconPhoneRinging } from '../../../../base/icons/svg';
|
||||||
import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
|
import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
|
||||||
|
import Button from '../../../../base/ui/components/web/Button';
|
||||||
|
import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
|
||||||
import { isVpaasMeeting } from '../../../../jaas/functions';
|
import { isVpaasMeeting } from '../../../../jaas/functions';
|
||||||
import { hideAddPeopleDialog } from '../../../actions';
|
import { hideAddPeopleDialog } from '../../../actions.web';
|
||||||
import { INVITE_TYPES } from '../../../constants';
|
import { INVITE_TYPES } from '../../../constants';
|
||||||
|
import { IInviteSelectItem, IInvitee } from '../../../types';
|
||||||
import AbstractAddPeopleDialog, {
|
import AbstractAddPeopleDialog, {
|
||||||
type Props as AbstractProps,
|
IProps as AbstractProps,
|
||||||
type State,
|
IState,
|
||||||
_mapStateToProps as _abstractMapStateToProps
|
_mapStateToProps as _abstractMapStateToProps
|
||||||
} from '../AbstractAddPeopleDialog';
|
} from '../AbstractAddPeopleDialog';
|
||||||
|
|
||||||
declare var interfaceConfig: Object;
|
const styles = (theme: Theme) => {
|
||||||
|
return {
|
||||||
|
formWrap: {
|
||||||
|
marginTop: theme.spacing(2)
|
||||||
|
},
|
||||||
|
inviteButtons: {
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'end',
|
||||||
|
marginTop: theme.spacing(2),
|
||||||
|
'& .invite-button': {
|
||||||
|
marginLeft: theme.spacing(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
type Props = AbstractProps & {
|
|
||||||
|
interface IProps extends AbstractProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link JitsiMeetConference} which will be used to invite "room" participants.
|
* The {@link JitsiMeetConference} which will be used to invite "room" participants.
|
||||||
*/
|
*/
|
||||||
_conference: Object,
|
_conference?: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the meeting belongs to JaaS user.
|
* Whether the meeting belongs to JaaS user.
|
||||||
*/
|
*/
|
||||||
_isVpaas: boolean,
|
_isVpaas?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Css classes.
|
||||||
|
*/
|
||||||
|
classes: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The redux {@code dispatch} function.
|
* The redux {@code dispatch} function.
|
||||||
*/
|
*/
|
||||||
dispatch: Dispatch<any>,
|
dispatch: IStore['dispatch'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked to obtain translated strings.
|
* Invoked to obtain translated strings.
|
||||||
*/
|
*/
|
||||||
t: Function,
|
t: Function;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Form that enables inviting others to the call.
|
* Form that enables inviting others to the call.
|
||||||
*/
|
*/
|
||||||
class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
|
||||||
_multiselect = null;
|
_multiselect: MultiSelectAutocomplete | null = null;
|
||||||
|
|
||||||
_resourceClient: Object;
|
_resourceClient: {
|
||||||
|
makeQuery: (query: string) => Promise<Array<any>>;
|
||||||
|
parseResults: Function;
|
||||||
|
};
|
||||||
|
|
||||||
_translations: Object;
|
_translations: {
|
||||||
|
[key: string]: string;
|
||||||
|
_addPeopleEnabled: string;
|
||||||
|
_dialOutEnabled: string;
|
||||||
|
_sipInviteEnabled: string;
|
||||||
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
addToCallError: false,
|
addToCallError: false,
|
||||||
addToCallInProgress: false,
|
addToCallInProgress: false,
|
||||||
inviteItems: []
|
inviteItems: [] as IInviteSelectItem[]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +96,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
* @param {Object} props - The read-only properties with which the new
|
* @param {Object} props - The read-only properties with which the new
|
||||||
* instance is to be initialized.
|
* instance is to be initialized.
|
||||||
*/
|
*/
|
||||||
constructor(props: Props) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once per instance.
|
// Bind event handlers so they are only bound once per instance.
|
||||||
@@ -99,11 +129,11 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
/**
|
/**
|
||||||
* React Component method that executes once component is updated.
|
* React Component method that executes once component is updated.
|
||||||
*
|
*
|
||||||
* @param {Object} prevProps - The state object before the update.
|
* @param {Props} prevProps - The props object before the update.
|
||||||
* @param {Object} prevState - The state object before the update.
|
* @param {State} prevState - The state object before the update.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
componentDidUpdate(prevProps, prevState) {
|
componentDidUpdate(prevProps: IProps, prevState: IState) {
|
||||||
/**
|
/**
|
||||||
* Clears selected items from the multi select component on successful
|
* Clears selected items from the multi select component on successful
|
||||||
* invite.
|
* invite.
|
||||||
@@ -133,7 +163,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
const loadingMessage = 'addPeople.searching';
|
const loadingMessage = 'addPeople.searching';
|
||||||
const noMatches = 'addPeople.noResults';
|
const noMatches = 'addPeople.noResults';
|
||||||
|
|
||||||
const features = {
|
const features: { [key: string]: boolean; } = {
|
||||||
_dialOutEnabled,
|
_dialOutEnabled,
|
||||||
_addPeopleEnabled,
|
_addPeopleEnabled,
|
||||||
_sipInviteEnabled
|
_sipInviteEnabled
|
||||||
@@ -152,9 +182,8 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className = 'add-people-form-wrap'
|
className = { this.props.classes.formWrap }
|
||||||
onKeyDown = { this._onKeyDown }>
|
onKeyDown = { this._onKeyDown }>
|
||||||
{ this._renderErrorMessage() }
|
|
||||||
<MultiSelectAutocomplete
|
<MultiSelectAutocomplete
|
||||||
isDisabled = { isMultiSelectDisabled }
|
isDisabled = { isMultiSelectDisabled }
|
||||||
loadingMessage = { t(loadingMessage) }
|
loadingMessage = { t(loadingMessage) }
|
||||||
@@ -172,21 +201,17 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_invite: Array<Object> => Promise<*>;
|
|
||||||
|
|
||||||
_isAddDisabled: () => boolean;
|
_isAddDisabled: () => boolean;
|
||||||
|
|
||||||
_onItemSelected: (Object) => Object;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback invoked when a selection has been made but before it has been
|
* Callback invoked when a selection has been made but before it has been
|
||||||
* set as selected.
|
* set as selected.
|
||||||
*
|
*
|
||||||
* @param {Object} item - The item that has just been selected.
|
* @param {IInviteSelectItem} item - The item that has just been selected.
|
||||||
* @private
|
* @private
|
||||||
* @returns {Object} The item to display as selected in the input.
|
* @returns {Object} The item to display as selected in the input.
|
||||||
*/
|
*/
|
||||||
_onItemSelected(item) {
|
_onItemSelected(item: IInviteSelectItem) {
|
||||||
if (item.item.type === INVITE_TYPES.PHONE) {
|
if (item.item.type === INVITE_TYPES.PHONE) {
|
||||||
item.content = item.item.number;
|
item.content = item.item.number;
|
||||||
}
|
}
|
||||||
@@ -194,22 +219,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSelectionChange: (Map<*, *>) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a selection change.
|
* Handles a selection change.
|
||||||
*
|
*
|
||||||
* @param {Array} selectedItems - The list of selected items.
|
* @param {Array<IInviteSelectItem>} selectedItems - The list of selected items.
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onSelectionChange(selectedItems) {
|
_onSelectionChange(selectedItems: IInviteSelectItem[]) {
|
||||||
this.setState({
|
this.setState({
|
||||||
inviteItems: selectedItems
|
inviteItems: selectedItems
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSubmit: () => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the selection for inviting.
|
* Submits the selection for inviting.
|
||||||
@@ -222,49 +244,43 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
const invitees = inviteItems.map(({ item }) => item);
|
const invitees = inviteItems.map(({ item }) => item);
|
||||||
|
|
||||||
this._invite(invitees)
|
this._invite(invitees)
|
||||||
.then(invitesLeftToSend => {
|
.then((invitesLeftToSend: IInvitee[]) => {
|
||||||
if (invitesLeftToSend.length) {
|
if (invitesLeftToSend.length) {
|
||||||
const unsentInviteIDs
|
const unsentInviteIDs
|
||||||
= invitesLeftToSend.map(invitee =>
|
= invitesLeftToSend.map(invitee =>
|
||||||
invitee.id || invitee.user_id || invitee.number);
|
invitee.id || invitee.user_id || invitee.number);
|
||||||
const itemsToSelect
|
const itemsToSelect = inviteItems.filter(({ item }) =>
|
||||||
= inviteItems.filter(({ item }) =>
|
unsentInviteIDs.includes(item.id || item.user_id || item.number));
|
||||||
unsentInviteIDs.includes(item.id || item.user_id || item.number));
|
|
||||||
|
|
||||||
if (this._multiselect) {
|
if (this._multiselect) {
|
||||||
this._multiselect.setSelectedItems(itemsToSelect);
|
this._multiselect.setSelectedItems(itemsToSelect);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.props.dispatch(hideAddPeopleDialog());
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.finally(() => this.props.dispatch(hideAddPeopleDialog()));
|
||||||
}
|
}
|
||||||
|
|
||||||
_onSubmitKeyPress: (Object) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KeyPress handler for accessibility.
|
* KeyPress handler for accessibility.
|
||||||
*
|
*
|
||||||
* @param {Object} e - The key event to handle.
|
* @param {KeyboardEvent} e - The key event to handle.
|
||||||
*
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onSubmitKeyPress(e) {
|
_onSubmitKeyPress(e: React.KeyboardEvent) {
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._onSubmit();
|
this._onSubmit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onKeyDown: (Object) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles 'Enter' key in the form to trigger the invite.
|
* Handles 'Enter' key in the form to trigger the invite.
|
||||||
*
|
*
|
||||||
* @param {Object} event - The key event.
|
* @param {KeyboardEvent} event - The key event.
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onKeyDown(event) {
|
_onKeyDown(event: React.KeyboardEvent) {
|
||||||
const { inviteItems } = this.state;
|
const { inviteItems } = this.state;
|
||||||
|
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
@@ -275,20 +291,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseQueryResults: (?Array<Object>) => Array<Object>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the avatar component for a user.
|
* Returns the avatar component for a user.
|
||||||
*
|
*
|
||||||
* @param {Object} user - The user.
|
* @param {any} user - The user.
|
||||||
* @param {string} className - The CSS class for the avatar component.
|
* @param {string} className - The CSS class for the avatar component.
|
||||||
* @private
|
* @private
|
||||||
* @returns {ReactElement}
|
* @returns {ReactElement}
|
||||||
*/
|
*/
|
||||||
_getAvatar(user, className = 'avatar-small') {
|
_getAvatar(user: any, className = 'avatar-small') {
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
className = { className }
|
className = { className }
|
||||||
|
size = { 32 }
|
||||||
status = { user.status }
|
status = { user.status }
|
||||||
url = { user.avatar } />
|
url = { user.avatar } />
|
||||||
);
|
);
|
||||||
@@ -305,7 +320,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
* @returns {Object[]} Configuration objects for items to display in the
|
* @returns {Object[]} Configuration objects for items to display in the
|
||||||
* search autocomplete.
|
* search autocomplete.
|
||||||
*/
|
*/
|
||||||
_parseQueryResults(response = []) {
|
_parseQueryResults(response: IInvitee[] = []) {
|
||||||
const { t, _dialOutEnabled } = this.props;
|
const { t, _dialOutEnabled } = this.props;
|
||||||
|
|
||||||
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
|
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
|
||||||
@@ -394,10 +409,6 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
_query: (string) => Promise<Array<Object>>;
|
|
||||||
|
|
||||||
_onClearItems: () => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the selected items from state and form.
|
* Clears the selected items from state and form.
|
||||||
*
|
*
|
||||||
@@ -410,16 +421,14 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
this.setState({ inviteItems: [] });
|
this.setState({ inviteItems: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
_onClearItemsKeyPress: () => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the selected items from state and form.
|
* Clears the selected items from state and form.
|
||||||
*
|
*
|
||||||
* @param {Object} e - The key event to handle.
|
* @param {KeyboardEvent} e - The key event to handle.
|
||||||
*
|
*
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_onClearItemsKeyPress(e) {
|
_onClearItemsKeyPress(e: KeyboardEvent) {
|
||||||
if (e.key === ' ' || e.key === 'Enter') {
|
if (e.key === ' ' || e.key === 'Enter') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this._onClearItems();
|
this._onClearItems();
|
||||||
@@ -433,76 +442,30 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
*/
|
*/
|
||||||
_renderFormActions() {
|
_renderFormActions() {
|
||||||
const { inviteItems } = this.state;
|
const { inviteItems } = this.state;
|
||||||
const { t } = this.props;
|
const { t, classes } = this.props;
|
||||||
|
|
||||||
if (!inviteItems.length) {
|
if (!inviteItems.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className = { `invite-more-dialog invite-buttons${this._isAddDisabled() ? ' disabled' : ''}` }>
|
<div className = { classes.inviteButtons }>
|
||||||
<a
|
<Button
|
||||||
aria-label = { t('dialog.Cancel') }
|
aria-label = { t('dialog.Cancel') }
|
||||||
className = 'invite-more-dialog invite-buttons-cancel'
|
className = 'invite-button'
|
||||||
|
label = { t('dialog.Cancel') }
|
||||||
onClick = { this._onClearItems }
|
onClick = { this._onClearItems }
|
||||||
onKeyPress = { this._onClearItemsKeyPress }
|
onKeyPress = { this._onClearItemsKeyPress }
|
||||||
role = 'button'
|
role = 'button'
|
||||||
tabIndex = { 0 }>
|
type = { BUTTON_TYPES.SECONDARY } />
|
||||||
{t('dialog.Cancel')}
|
<Button
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
aria-label = { t('addPeople.add') }
|
aria-label = { t('addPeople.add') }
|
||||||
className = 'invite-more-dialog invite-buttons-add'
|
className = 'invite-button'
|
||||||
|
disabled = { this._isAddDisabled() }
|
||||||
|
label = { t('addPeople.add') }
|
||||||
onClick = { this._onSubmit }
|
onClick = { this._onSubmit }
|
||||||
onKeyPress = { this._onSubmitKeyPress }
|
onKeyPress = { this._onSubmitKeyPress }
|
||||||
role = 'button'
|
role = 'button' />
|
||||||
tabIndex = { 0 }>
|
|
||||||
{t('addPeople.add')}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the error message if the add doesn't succeed.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @returns {ReactElement|null}
|
|
||||||
*/
|
|
||||||
_renderErrorMessage() {
|
|
||||||
if (!this.state.addToCallError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { t } = this.props;
|
|
||||||
const supportString = t('inlineDialogFailure.supportMsg');
|
|
||||||
const supportLink = interfaceConfig.SUPPORT_URL;
|
|
||||||
|
|
||||||
const supportLinkContent = supportLink ? (
|
|
||||||
<span>
|
|
||||||
<span>
|
|
||||||
{ supportString.padEnd(supportString.length + 1) }
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<a
|
|
||||||
aria-label = { supportLink }
|
|
||||||
href = { supportLink }
|
|
||||||
rel = 'noopener noreferrer'
|
|
||||||
target = '_blank'>
|
|
||||||
{ t('inlineDialogFailure.support') }
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span>.</span>
|
|
||||||
</span>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className = 'modal-dialog-form-error'>
|
|
||||||
<InlineMessage
|
|
||||||
title = { t('addPeople.failedToAdd') }
|
|
||||||
type = 'error'>
|
|
||||||
{ supportLinkContent }
|
|
||||||
</InlineMessage>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -515,23 +478,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
*/
|
*/
|
||||||
_renderTelephoneIcon() {
|
_renderTelephoneIcon() {
|
||||||
return (
|
return (
|
||||||
<span className = 'add-telephone-icon'>
|
<Icon src = { IconPhoneRinging } />
|
||||||
<Icon src = { IconPhoneRinging } />
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setMultiSelectElement: (React$ElementRef<*> | null) => void;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the instance variable for the multi select component
|
* Sets the instance variable for the multi select component
|
||||||
* element so it can be accessed directly.
|
* element so it can be accessed directly.
|
||||||
*
|
*
|
||||||
* @param {Object} element - The DOM element for the component's dialog.
|
* @param {MultiSelectAutocomplete} element - The DOM element for the component's dialog.
|
||||||
* @private
|
* @private
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
_setMultiSelectElement(element) {
|
_setMultiSelectElement(element: MultiSelectAutocomplete) {
|
||||||
this._multiselect = element;
|
this._multiselect = element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,15 +499,15 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
|
|||||||
* Maps (parts of) the Redux state to the associated
|
* Maps (parts of) the Redux state to the associated
|
||||||
* {@code AddPeopleDialog}'s props.
|
* {@code AddPeopleDialog}'s props.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {IReduxState} state - The Redux state.
|
||||||
* @private
|
* @private
|
||||||
* @returns {Props}
|
* @returns {Props}
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state) {
|
function _mapStateToProps(state: IReduxState) {
|
||||||
return {
|
return {
|
||||||
..._abstractMapStateToProps(state),
|
..._abstractMapStateToProps(state),
|
||||||
_isVpaas: isVpaasMeeting(state)
|
_isVpaas: isVpaasMeeting(state)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(InviteContactsForm));
|
export default translate(connect(_mapStateToProps)(withStyles(styles)(InviteContactsForm)));
|
||||||
@@ -1,25 +1,16 @@
|
|||||||
// @flow
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { translate } from '../../../../base/i18n/functions';
|
|
||||||
|
|
||||||
import InviteContactsForm from './InviteContactsForm';
|
import InviteContactsForm from './InviteContactsForm';
|
||||||
|
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked to obtain translated strings.
|
|
||||||
*/
|
|
||||||
t: Function
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that represents the invitation section of the {@code AddPeopleDialog}.
|
* Component that represents the invitation section of the {@code AddPeopleDialog}.
|
||||||
*
|
*
|
||||||
* @returns {ReactElement$<any>}
|
* @returns {ReactElement$<any>}
|
||||||
*/
|
*/
|
||||||
function InviteContactsSection({ t }: Props) {
|
function InviteContactsSection() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span>{t('addPeople.addContacts')}</span>
|
<span>{t('addPeople.addContacts')}</span>
|
||||||
@@ -29,4 +20,4 @@ function InviteContactsSection({ t }: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(InviteContactsSection);
|
export default InviteContactsSection;
|
||||||
@@ -1,6 +1,20 @@
|
|||||||
|
import { MultiSelectItem } from '../base/ui/components/types';
|
||||||
|
|
||||||
export interface IInvitee {
|
export interface IInvitee {
|
||||||
address: string;
|
address: string;
|
||||||
name: string;
|
allowed?: boolean;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
number: string;
|
number: string;
|
||||||
|
originalEntry?: string;
|
||||||
|
phone?: string;
|
||||||
|
showCountryCodeReminder?: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
|
user_id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInviteSelectItem extends MultiSelectItem {
|
||||||
|
filterValues?: string[];
|
||||||
|
item: IInvitee;
|
||||||
|
tag?: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,4 +40,4 @@ export const NOTIFY_CLICK_MODE = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Around 300 to be displayed above components like chat
|
// Around 300 to be displayed above components like chat
|
||||||
export const ZINDEX_DIALOG_PORTAL = 300;
|
export const ZINDEX_DIALOG_PORTAL = 302;
|
||||||
|
|||||||
Reference in New Issue
Block a user