-
Notifications
You must be signed in to change notification settings - Fork 28
issue-107: Modify Searcher to keep track of when a search is being performed and update BusyIndicator component #106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
813146e
1a56318
3df804c
dbe24a3
116c8be
79160ce
df42497
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,21 +2,34 @@ | |
| import React from 'react'; | ||
|
|
||
| import DefaultImage from './DefaultImage'; | ||
| import Configurable from './Configurable'; | ||
| import spinnyImage from '../img/spinner.gif'; | ||
|
|
||
| type BusyIndicatorType = 'ellipsis' | 'spinny'; | ||
|
|
||
| type BusyIndicatorProps = { | ||
| /** | ||
| * If true, the component will be visible. | ||
| * Indicates if the component will be visible, default is true. | ||
| */ | ||
| show: boolean; | ||
| /** | ||
| * Optional path to the spinner file, defaults to 'img/spinner.gif'. | ||
| */ | ||
| spinner?: string; | ||
| /** | ||
| * Optional height for the spinner, default is the actual height of the spinner file. | ||
| */ | ||
| spinnerHeight?: string; | ||
| /** | ||
| * Optional width for the spinner, default is the actual height of the spinner file. | ||
| */ | ||
| spinnerWidth?: string; | ||
| /** | ||
| * Optional message to show before the animated part. Defaults to the empty string. | ||
| */ | ||
| message?: string; | ||
| /** | ||
| * Indicates message placement in relation to the spinner. Has no impact on ellipsis type. Defaults to false. | ||
| * Indicates message placement in relation to the spinner. Has no impact on ellipsis or spinnyCentered type. Defaults to false. | ||
| * If type is spinner, and this prop is omitted, the message will be positioned to the left of the spinner by default. | ||
| */ | ||
| positionMessageRight?: boolean; | ||
|
|
@@ -26,19 +39,24 @@ type BusyIndicatorProps = { | |
| messageStyle?: {}; | ||
| /** | ||
| * The type of busy indicator to show. Defaults to 'ellipsis' | ||
| * 'SpinnyCentered' type shows a spinner that is centered in the middle of it's container. | ||
| */ | ||
| type?: BusyIndicatorType; | ||
| /** | ||
| * An optional CSS style to show for the component. | ||
| * An optional CSS style to show for the component. Has no impact on the spinnyCentered type. | ||
| */ | ||
| style?: {}; | ||
| }; | ||
|
|
||
| type BusyIndicatorDefaultProps = { | ||
| show: boolean, | ||
| message: string; | ||
| positionMessageRight: boolean; | ||
| style: any; | ||
| type: BusyIndicatorType; | ||
| spinner: string; | ||
| spinnerHeight: string; | ||
| spinnerWidth: string; | ||
| }; | ||
|
|
||
| type BusyIndicatorState = { | ||
|
|
@@ -48,12 +66,16 @@ type BusyIndicatorState = { | |
| /** | ||
| * Component to indicate the app is busy with an animation and optional message. | ||
| */ | ||
| export default class BusyIndicator extends React.Component<BusyIndicatorDefaultProps, BusyIndicatorProps, BusyIndicatorState> { // eslint-disable-line max-len | ||
| class BusyIndicator extends React.Component<BusyIndicatorDefaultProps, BusyIndicatorProps, BusyIndicatorState> { // eslint-disable-line max-len | ||
| static defaultProps = { | ||
| show: true, | ||
| message: '', | ||
| positionMessageRight: false, | ||
| style: {}, | ||
| type: 'ellipsis', | ||
| spinner: 'img/spinner.gif', | ||
| spinnerHeight: '', | ||
| spinnerWidth: '', | ||
| }; | ||
|
|
||
| static displayName = 'BusyIndicator'; | ||
|
|
@@ -114,14 +136,23 @@ export default class BusyIndicator extends React.Component<BusyIndicatorDefaultP | |
| } | ||
|
|
||
| renderBusySymbol = () => { | ||
| const { type } = this.props; | ||
| const { type, spinner, spinnerHeight, spinnerWidth } = this.props; | ||
| const { dotCount } = this.state; | ||
|
|
||
| switch (type) { | ||
| case 'ellipsis': | ||
| return '.'.repeat(dotCount); | ||
| case 'spinny': | ||
| return <DefaultImage src={spinnyImage} style={{ height: '1em' }} />; | ||
| return <DefaultImage src={spinner} defaultSrc={spinnyImage} style={{ height: '1em' }} />; | ||
| case 'spinnyCentered': | ||
| return ( | ||
| <img | ||
| src={this.props.spinner} | ||
| alt="Spinner" | ||
| width={spinnerWidth} | ||
| height={spinnerHeight} | ||
| /> | ||
| ); | ||
| default: | ||
| return null; | ||
| } | ||
|
|
@@ -138,13 +169,6 @@ export default class BusyIndicator extends React.Component<BusyIndicatorDefaultP | |
| } = this.props; | ||
|
|
||
| if (show) { | ||
| const containerStyle = { | ||
| display: 'flex', | ||
| flexFlow: 'row nowrap', | ||
| alignItems: 'center', | ||
| ...style, | ||
| }; | ||
|
|
||
| const validRightPosition = type === 'spinny' && message && positionMessageRight; | ||
| const messageMargin = validRightPosition | ||
| ? 'marginLeft' | ||
|
|
@@ -158,6 +182,24 @@ export default class BusyIndicator extends React.Component<BusyIndicatorDefaultP | |
| ? <div style={mergedMessageStyle}>{message}</div> | ||
| : ''; | ||
|
|
||
| if (type === 'spinnyCentered') { | ||
| return ( | ||
| <div className="attivio-spinner"> | ||
| {this.renderBusySymbol()} | ||
| <br /> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there's no |
||
| <br /> | ||
| <b>{messageToShow}</b> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const containerStyle = { | ||
| display: 'flex', | ||
| flexFlow: 'row nowrap', | ||
| alignItems: 'center', | ||
| ...style, | ||
| }; | ||
|
|
||
| const leftContents = validRightPosition | ||
| ? this.renderBusySymbol() | ||
| : messageToShow; | ||
|
|
@@ -175,3 +217,5 @@ export default class BusyIndicator extends React.Component<BusyIndicatorDefaultP | |
| return null; | ||
| } | ||
| } | ||
|
|
||
| export default Configurable(BusyIndicator); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,7 @@ type SearcherProps = { | |
|
|
||
| /** | ||
| * The engine backing the SUIT application. Defaults to 'attivio'. | ||
| * Set to 'solr' or 'elastic' to use one of those engines instesd. | ||
| * Set to 'solr' or 'elastic' to use one of those engines instead. | ||
| */ | ||
| searchEngineType: 'attivio' | 'solr' | 'elastic'; | ||
| /** | ||
|
|
@@ -204,6 +204,7 @@ type SearcherState = { | |
| resultsPerPage: number; | ||
| resultsOffset: number; | ||
| debug: boolean; | ||
| isSearching: boolean; | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -391,6 +392,7 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| resultsPerPage: parseInt(this.props.resultsPerPage, 10), | ||
| resultsOffset: 0, | ||
| debug: this.props.debug, | ||
| isSearching: false, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -548,6 +550,7 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| if (state.debug !== this.props.debug) { | ||
| basicState.debug = state.debug; | ||
| } | ||
| basicState.isSearching = this.state.isSearching; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it makes sense to save the isSearching flag in the URL... |
||
|
|
||
| // See if there are any query parameters other than those set by the Searcher. If so, we want to maintain them. | ||
| if (originalQueryString) { | ||
|
|
@@ -562,6 +565,7 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| originalParsed.delete('sort'); | ||
| originalParsed.delete('relevancyModels'); | ||
| originalParsed.delete('debug'); | ||
| originalParsed.delete('isSearching'); | ||
| } | ||
| // Add any leftover fields back in to the basic state | ||
| basicState = Object.assign({}, basicState, originalParsed); | ||
|
|
@@ -571,10 +575,10 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| } | ||
|
|
||
| /** | ||
| * Given the query string from the location URL, parse it into the values of a SearcherState | ||
| * object. Values which are missing are set to their default values. Any values in the | ||
| * queryString which don't apply to the SearcherState are ignored. | ||
| */ | ||
| * Given the query string from the location URL, parse it into the values of a SearcherState | ||
| * object. Values which are missing are set to their default values. Any values in the | ||
| * queryString which don't apply to the SearcherState are ignored. | ||
| */ | ||
| parseLocationQueryStringToState(queryString: string): SearcherState { | ||
| const parsed = QueryString.parse(queryString); | ||
|
|
||
|
|
@@ -668,6 +672,11 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| debug = parsed.debug; | ||
| } | ||
|
|
||
| let isSearching = false; | ||
| if (Object.prototype.hasOwnProperty.call(parsed, 'isSearching')) { | ||
| isSearching = parsed.isSearching === 'true'; | ||
| } | ||
|
|
||
| const result: SearcherState = { | ||
| query, | ||
| queryLanguage, | ||
|
|
@@ -679,6 +688,7 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| relevancyModels, | ||
| debug, | ||
| haveSearched: this.state.haveSearched, // Make sure we don't change this | ||
| isSearching, | ||
| }; | ||
|
|
||
| return result; | ||
|
|
@@ -836,18 +846,26 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| */ | ||
| doSearch() { | ||
| const qr = this.getQueryRequest(); | ||
| this.search.search(qr, (response: QueryResponse | null, error: string | null) => { | ||
| this.updateSearchResults(response, error); | ||
|
|
||
| // potentially do window.scrollTo(0, 0)? | ||
|
|
||
| // Update the URL if needed. | ||
| const oldQueryString = this.props.location.query; | ||
| const updatedQueryString = this.generateLocationQueryStringFromState(this.state, oldQueryString); | ||
| if (oldQueryString !== updatedQueryString) { | ||
| this.props.history.push(`?${updatedQueryString}`); | ||
| } | ||
| }); | ||
| if (!this.state.isSearching) { | ||
| this.setState({ isSearching: true }, () => { | ||
| this.search.search(qr, (response: QueryResponse | null, error: string | null) => { | ||
| this.setState({ isSearching: false }, () => { | ||
| this.updateSearchResults(response, error); | ||
|
|
||
| // potentially do window.scrollTo(0, 0)? | ||
|
|
||
| // Update the URL if needed. | ||
| const oldQueryString = this.props.location.query; | ||
| const updatedQueryString = this.generateLocationQueryStringFromState(this.state, oldQueryString); | ||
| if (oldQueryString !== updatedQueryString) { | ||
| this.props.history.push(`?${updatedQueryString}`); | ||
| } | ||
| this.updateSearchResults(response, error); | ||
| }); | ||
| }); | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -899,7 +917,6 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear | |
| }); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Add multiple query filters (in AQL) to the query request. | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to add 'spinnyCentered' here...