mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-03-08 04:30:19 +00:00
For the most part the changes are taking the "static propTypes" declaration off of components and declaring them as Flow types. Sometimes to support flow some method signatures had to be added. There are some exceptions in which more had to be done to tame the beast: - AbstractVideoTrack: put in additional truthy checks for videoTrack. - Video: add truthy checks for the _videoElement ref. - shouldRenderVideoTrack function: Some component could pass null for the videoTrack argument and Flow wanted that called out explicitly. - DisplayName: Add a truthy check for the input ref before acting on it. - NumbersList: Move array checks inline for Flow to comprehend array methods could be called. Add type checks in the Object.entries loop as the value is assumed to be a mixed type by Flow. - AbstractToolbarButton: add additional truthy check for passed in type.
334 lines
8.5 KiB
JavaScript
334 lines
8.5 KiB
JavaScript
// @flow
|
|
|
|
import { MultiSelectStateless } from '@atlaskit/multi-select';
|
|
import AKInlineDialog from '@atlaskit/inline-dialog';
|
|
import _debounce from 'lodash/debounce';
|
|
import React, { Component } from 'react';
|
|
|
|
import InlineDialogFailure from './InlineDialogFailure';
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
/**
|
|
* The type of the React {@code Component} props of
|
|
* {@link MultiSelectAutocomplete}.
|
|
*/
|
|
type Props = {
|
|
|
|
/**
|
|
* The default value of the selected item.
|
|
*/
|
|
defaultValue: Array<Object>,
|
|
|
|
/**
|
|
* Optional footer to show as a last element in the results.
|
|
* Should be of type {content: <some content>}
|
|
*/
|
|
footer: Object,
|
|
|
|
/**
|
|
* Indicates if the component is disabled.
|
|
*/
|
|
isDisabled: boolean,
|
|
|
|
/**
|
|
* Text to display while a query is executing.
|
|
*/
|
|
loadingMessage: string,
|
|
|
|
/**
|
|
* The text to show when no matches are found.
|
|
*/
|
|
noMatchesFound: string,
|
|
|
|
/**
|
|
* The function called immediately before a selection has been actually
|
|
* selected. Provides an opportunity to do any formatting.
|
|
*/
|
|
onItemSelected: Function,
|
|
|
|
/**
|
|
* The function called when the selection changes.
|
|
*/
|
|
onSelectionChange: Function,
|
|
|
|
/**
|
|
* The placeholder text of the input component.
|
|
*/
|
|
placeholder: string,
|
|
|
|
/**
|
|
* The service providing the search.
|
|
*/
|
|
resourceClient: { makeQuery: Function, parseResults: Function },
|
|
|
|
/**
|
|
* Indicates if the component should fit the container.
|
|
*/
|
|
shouldFitContainer: boolean,
|
|
|
|
/**
|
|
* Indicates if we should focus.
|
|
*/
|
|
shouldFocus: boolean
|
|
};
|
|
|
|
/**
|
|
* The type of the React {@code Component} state of
|
|
* {@link MultiSelectAutocomplete}.
|
|
*/
|
|
type State = {
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
error: boolean,
|
|
|
|
/**
|
|
* The list of result items.
|
|
*/
|
|
items: Array<Object>,
|
|
|
|
/**
|
|
* The list of selected items.
|
|
*/
|
|
selectedItems: Array<Object>
|
|
};
|
|
|
|
/**
|
|
* A MultiSelect that is also auto-completing.
|
|
*/
|
|
class MultiSelectAutocomplete extends Component<Props, State> {
|
|
/**
|
|
* Initializes a new {@code MultiSelectAutocomplete} instance.
|
|
*
|
|
* @param {Object} props - The read-only properties with which the new
|
|
* instance is to be initialized.
|
|
*/
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
const defaultValue = this.props.defaultValue || [];
|
|
|
|
this.state = {
|
|
isOpen: false,
|
|
filterValue: '',
|
|
loading: false,
|
|
error: false,
|
|
items: [],
|
|
selectedItems: [ ...defaultValue ]
|
|
};
|
|
|
|
this._onFilterChange = this._onFilterChange.bind(this);
|
|
this._onRetry = this._onRetry.bind(this);
|
|
this._onSelectionChange = this._onSelectionChange.bind(this);
|
|
this._sendQuery = _debounce(this._sendQuery.bind(this), 200);
|
|
}
|
|
|
|
/**
|
|
* Sets the items to display as selected.
|
|
*
|
|
* @param {Array<Object>} selectedItems - The list of items to display as
|
|
* having been selected.
|
|
* @returns {void}
|
|
*/
|
|
setSelectedItems(selectedItems: Array<Object> = []) {
|
|
this.setState({ selectedItems });
|
|
}
|
|
|
|
/**
|
|
* Renders the content of this component.
|
|
*
|
|
* @returns {ReactElement}
|
|
*/
|
|
render() {
|
|
const shouldFitContainer = this.props.shouldFitContainer || false;
|
|
const shouldFocus = this.props.shouldFocus || false;
|
|
const isDisabled = this.props.isDisabled || false;
|
|
const placeholder = this.props.placeholder || '';
|
|
const noMatchesFound = this.props.noMatchesFound || '';
|
|
|
|
return (
|
|
<div>
|
|
<MultiSelectStateless
|
|
filterValue = { this.state.filterValue }
|
|
footer = { this.props.footer }
|
|
icon = { null }
|
|
isDisabled = { isDisabled }
|
|
isLoading = { this.state.loading }
|
|
isOpen = { this.state.isOpen }
|
|
items = { this.state.items }
|
|
loadingMessage = { this.props.loadingMessage }
|
|
noMatchesFound = { noMatchesFound }
|
|
onFilterChange = { this._onFilterChange }
|
|
onRemoved = { this._onSelectionChange }
|
|
onSelected = { this._onSelectionChange }
|
|
placeholder = { placeholder }
|
|
selectedItems = { this.state.selectedItems }
|
|
shouldFitContainer = { shouldFitContainer }
|
|
shouldFocus = { shouldFocus } />
|
|
{ this._renderError() }
|
|
</div>
|
|
);
|
|
}
|
|
|
|
_onFilterChange: (string) => void;
|
|
|
|
/**
|
|
* Sets the state and sends a query on filter change.
|
|
*
|
|
* @param {string} filterValue - The filter text value.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onFilterChange(filterValue) {
|
|
this.setState({
|
|
// Clean the error if the filterValue is empty.
|
|
error: this.state.error && Boolean(filterValue),
|
|
filterValue,
|
|
isOpen: Boolean(this.state.items.length) && Boolean(filterValue),
|
|
items: filterValue ? this.state.items : [],
|
|
loading: Boolean(filterValue)
|
|
});
|
|
if (filterValue) {
|
|
this._sendQuery(filterValue);
|
|
}
|
|
}
|
|
|
|
_onRetry: () => void;
|
|
|
|
/**
|
|
* Retries the query on retry.
|
|
*
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onRetry() {
|
|
this._sendQuery(this.state.filterValue);
|
|
}
|
|
|
|
_onSelectionChange: (Object) => void;
|
|
|
|
/**
|
|
* Updates the selected items when a selection event occurs.
|
|
*
|
|
* @param {Object} item - The selected item.
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
_onSelectionChange(item) {
|
|
const existing
|
|
= this.state.selectedItems.find(k => k.value === item.value);
|
|
let selectedItems = this.state.selectedItems;
|
|
|
|
if (existing) {
|
|
selectedItems = selectedItems.filter(k => k !== existing);
|
|
} else {
|
|
selectedItems.push(this.props.onItemSelected(item));
|
|
}
|
|
this.setState({
|
|
isOpen: false,
|
|
selectedItems
|
|
});
|
|
|
|
if (this.props.onSelectionChange) {
|
|
this.props.onSelectionChange(selectedItems);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renders the error UI.
|
|
*
|
|
* @returns {ReactElement|null}
|
|
*/
|
|
_renderError() {
|
|
if (!this.state.error) {
|
|
return null;
|
|
}
|
|
const content = (
|
|
<div className = 'autocomplete-error'>
|
|
<InlineDialogFailure
|
|
onRetry = { this._onRetry } />
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<AKInlineDialog
|
|
content = { content }
|
|
isOpen = { true } />
|
|
);
|
|
}
|
|
|
|
_sendQuery: (string) => void;
|
|
|
|
/**
|
|
* Sends a query to the resourceClient.
|
|
*
|
|
* @param {string} filterValue - The string to use for the search.
|
|
* @returns {void}
|
|
*/
|
|
_sendQuery(filterValue) {
|
|
if (!filterValue) {
|
|
return;
|
|
}
|
|
|
|
this.setState({
|
|
error: false
|
|
});
|
|
|
|
const resourceClient = this.props.resourceClient || {
|
|
makeQuery: () => Promise.resolve([]),
|
|
parseResults: results => results
|
|
};
|
|
|
|
resourceClient.makeQuery(filterValue)
|
|
.then(results => {
|
|
if (this.state.filterValue !== filterValue) {
|
|
this.setState({
|
|
error: false
|
|
});
|
|
|
|
return;
|
|
}
|
|
const itemGroups = [
|
|
{
|
|
items: resourceClient.parseResults(results)
|
|
}
|
|
];
|
|
|
|
this.setState({
|
|
items: itemGroups,
|
|
isOpen: true,
|
|
loading: false,
|
|
error: false
|
|
});
|
|
})
|
|
.catch(error => {
|
|
logger.error('MultiSelectAutocomplete error in query', error);
|
|
|
|
this.setState({
|
|
error: true,
|
|
loading: false,
|
|
isOpen: false
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export default MultiSelectAutocomplete;
|