diff --git a/client/src/components/PackageSearch/Client.js b/client/src/components/PackageSearch/Client.js index 29c1ce2..cdb76c2 100644 --- a/client/src/components/PackageSearch/Client.js +++ b/client/src/components/PackageSearch/Client.js @@ -1,10 +1,26 @@ import fetch from 'isomorphic-fetch' -function search (query, range, dev, cb) { +function serialize (obj) { + let str = [] + for (let property in obj) { + if (obj.hasOwnProperty(property)) { + str.push(encodeURIComponent(property) + '=' + encodeURIComponent(obj[property])) + } + } + return str.join('&') +} + +function search (query, range, dev, limit, gt, cb) { query = encodeURIComponent(query) range = encodeURIComponent(range) - return fetch(`/api/query/${query}${range ? '/' : ''}${range}${dev ? '?dev=1' : ''}`, { + const options = Object.assign({}, { limit: -1 }, {limit, gt}) + + if (dev) { + options.dev = 1 + } + + return fetch(`/api/query/${query}${range ? '/' : ''}${range}?${serialize(options)}`, { accept: 'application/json' }).then(checkStatus) .then(parseJSON) diff --git a/client/src/components/PackageSearch/index.js b/client/src/components/PackageSearch/index.js index c7fedaf..f944e79 100644 --- a/client/src/components/PackageSearch/index.js +++ b/client/src/components/PackageSearch/index.js @@ -11,9 +11,13 @@ import './style.css' const resetState = { results: null, isLoading: false, - errorMessage: '' + isLoadingMore: false, + errorMessage: '', + lastPackage: '' } +const resultsLimit = 50 + const PackageSearch = React.createClass({ getResetState (name, range, dev) { let state = Object.assign({}, resetState, {}) @@ -97,7 +101,8 @@ const PackageSearch = React.createClass({ runSearch ( name = this.state.searchValueForName, range = this.state.searchValueForRange, - dev = this.state.searchDev + dev = this.state.searchDev, + lastPackage = this.state.lastPackage ) { if (name === '') { return false @@ -118,7 +123,50 @@ const PackageSearch = React.createClass({ let _name = name - Client.search(name, range, dev, (result) => { + Client.search(name, range, dev, resultsLimit, lastPackage, (result) => { + if (result.error) { + let errorState = Object.assign({}, resetState, { + errorMessage: result.message + }) + + this.setState(errorState) + } else { + this.setState({ + results: lastPackage ? this.state.results.concat(result) : result, + queryName: _name, + isLoading: false, + lastPackage: result.length === resultsLimit ? result[result.length - 1].name : '' + }) + } + }) + }, + + runSearchForMore ( + name = this.state.searchValueForName, + range = this.state.searchValueForRange, + dev = this.state.searchDev, + lastPackage = this.state.lastPackage + ) { + if (name === '') { + return false + } + + if (range === '') { + this.setState({ + searchValueForRange: '*' + }) + } + + dev = Boolean(dev) + + this.setState({ + errorMessage: '', + isLoadingMore: true + }) + + let _name = name + + Client.search(name, range, dev, resultsLimit, lastPackage, (result) => { if (result.error) { let errorState = Object.assign({}, resetState, { errorMessage: result.message @@ -127,14 +175,19 @@ const PackageSearch = React.createClass({ this.setState(errorState) } else { this.setState({ - results: result, + results: lastPackage ? this.state.results.concat(result) : result, queryName: _name, - isLoading: false + isLoadingMore: false, + lastPackage: result.length === resultsLimit ? result[result.length - 1].name : '' }) } }) }, + loadMore () { + this.runSearchForMore() + }, + componentWillReceiveProps (nextProps) { // Run search if new params are different from current ones let packageParam = nextProps.params.package @@ -148,6 +201,26 @@ const PackageSearch = React.createClass({ }) }, + componentDidMount () { + window.addEventListener('scroll', this.handleScroll) + }, + + componentWillUnmount () { + window.removeEventListener('scroll', this.handleScroll) + }, + + handleScroll () { + // @see http://stackoverflow.com/a/22394544/1955940 + const scrollTop = (document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop + const scrollHeight = (document.documentElement && document.documentElement.scrollHeight) || document.body.scrollHeight + + if ((scrollTop + window.innerHeight) >= scrollHeight) { + if (this.state.lastPackage) { + this.loadMore() + } + } + }, + componentWillMount () { // Run search when mounting component // eg. when hitting a deep link @@ -293,17 +366,29 @@ const PackageSearch = React.createClass({
- { this.state.isLoading ? ( + {this.state.isLoading ? (
Loading

Searching for popular modules may take a while. -

+

) : null} + {this.state.lastPackage && +
+ +
+ }
) @@ -380,7 +465,7 @@ function SearchResultsCount ({ results }) { class SearchResultsModules extends React.Component { shouldComponentUpdate (nextProps) { return ( - nextProps.queryName !== this.props.queryName + nextProps.queryName !== this.props.queryName || nextProps.lastPackage !== this.props.lastPackage ) } diff --git a/client/src/components/PackageSearch/style.css b/client/src/components/PackageSearch/style.css index 5336905..1081b97 100644 --- a/client/src/components/PackageSearch/style.css +++ b/client/src/components/PackageSearch/style.css @@ -86,3 +86,8 @@ .SearchForm .ui.large.toggle.checkbox input:checked~label:before { background-color: #00b5ad!important; } + +.ResultsPagination { + margin-top: 25px; + text-align: center; +}