Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions documentation/components/BusyIndicator.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,14 @@ __6:__ Another that can be shown and hidden...
{button}
</div>
```

__7:__ Example of type 'spinnyCentered' with custom height and width.

```jsx
<BusyIndicator
type="spinnyCentered"
message="Loading Results..."
spinnerHeight= '100px',
spinnerWidth= '100px',
/>
```
70 changes: 57 additions & 13 deletions src/components/BusyIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Copy link
Copy Markdown
Collaborator

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...


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;
Expand All @@ -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 = {
Expand All @@ -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';
Expand Down Expand Up @@ -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;
}
Expand All @@ -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'
Expand All @@ -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 />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's no messageToShow, you probably don't want to add these <br>s and the message at all.

<br />
<b>{messageToShow}</b>
</div>
);
}

const containerStyle = {
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
...style,
};

const leftContents = validRightPosition
? this.renderBusySymbol()
: messageToShow;
Expand All @@ -175,3 +217,5 @@ export default class BusyIndicator extends React.Component<BusyIndicatorDefaultP
return null;
}
}

export default Configurable(BusyIndicator);
51 changes: 34 additions & 17 deletions src/components/Searcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
/**
Expand Down Expand Up @@ -204,6 +204,7 @@ type SearcherState = {
resultsPerPage: number;
resultsOffset: number;
debug: boolean;
isSearching: boolean;
};

/**
Expand Down Expand Up @@ -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,
};
}

Expand Down Expand Up @@ -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;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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) {
Expand All @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
});
});
});
}
}

/**
Expand Down Expand Up @@ -899,7 +917,6 @@ class Searcher extends React.Component<SearcherDefaultProps, SearcherProps, Sear
});
}


/**
* Add multiple query filters (in AQL) to the query request.
*/
Expand Down