diff --git a/client/package.json b/client/package.json index bb80879..224dc1b 100644 --- a/client/package.json +++ b/client/package.json @@ -44,6 +44,7 @@ "react-dom": "^15.4.1", "react-router": "^3.0.2", "react-scripts": "0.7.0", + "react-waypoint": "^5.1.0", "recursive-readdir": "2.1.0", "rimraf": "2.5.4", "strip-ansi": "3.0.1", diff --git a/client/src/components/PackageSearch/Client.js b/client/src/components/PackageSearch/Client.js index 29c1ce2..d508728 100644 --- a/client/src/components/PackageSearch/Client.js +++ b/client/src/components/PackageSearch/Client.js @@ -1,20 +1,38 @@ import fetch from 'isomorphic-fetch' function search (query, range, dev, cb) { - query = encodeURIComponent(query) - range = encodeURIComponent(range) - - return fetch(`/api/query/${query}${range ? '/' : ''}${range}${dev ? '?dev=1' : ''}`, { - accept: 'application/json' - }).then(checkStatus) - .then(parseJSON) - .then(cb) - .catch(function (err) { - cb({ - error: true, - message: err.message + return (limit = 50, gt) => { + const _query = encodeURIComponent(query) + const _range = encodeURIComponent(range) + + return fetch(withParamteres(`/api/query/${_query}${_range ? '/' : ''}${_range}`, [ + { name: 'dev', value: 1, condition: dev }, + { name: 'limit', value: limit }, + { name: 'gt', value: gt } + ]), { + accept: 'application/json' + }).then(checkStatus) + .then(parseJSON) + .then(cb) + .catch(function (err) { + cb({ + error: true, + message: err.message + }) }) - }) + } +} + +function withParamteres (path, parameters) { + const condition = (val) => typeof val.condition === 'undefined' + ? typeof val.value !== 'undefined' + : val.condition + + return parameters.reduce((acc, val) => + condition(val) + ? `${acc}${(acc.indexOf('?') === -1) ? '?' : '&'}${val.name}=${val.value}` + : acc + , path) } function checkStatus (response) { diff --git a/client/src/components/PackageSearch/index.js b/client/src/components/PackageSearch/index.js index c7fedaf..1497c00 100644 --- a/client/src/components/PackageSearch/index.js +++ b/client/src/components/PackageSearch/index.js @@ -5,6 +5,8 @@ import ReactDOM from 'react-dom' import classnames from 'classnames' import SearchInfo from '../SearchInfo' +import SearchResults from '../SearchResults' +import Waypoint from 'react-waypoint' import './style.css' @@ -15,6 +17,17 @@ const resetState = { } const PackageSearch = React.createClass({ + + propTypes: { + limit: React.PropTypes.number + }, + + getDefaultProps () { + return { + limit: 50 + } + }, + getResetState (name, range, dev) { let state = Object.assign({}, resetState, {}) @@ -118,6 +131,10 @@ const PackageSearch = React.createClass({ let _name = name + const res = (result) => [...(this.state.results||[]), ...result] + + const gt = (result) => result[result.length - 1] && result[result.length - 1].name + Client.search(name, range, dev, (result) => { if (result.error) { let errorState = Object.assign({}, resetState, { @@ -127,12 +144,13 @@ const PackageSearch = React.createClass({ this.setState(errorState) } else { this.setState({ - results: result, + results: res(result), queryName: _name, - isLoading: false + isLoading: false, + gt: gt(result) }) } - }) + })(this.props.limit, this.state.gt) }, componentWillReceiveProps (nextProps) { @@ -143,6 +161,8 @@ const PackageSearch = React.createClass({ let newState = this.getResetState(packageParam, versionParam, devParam) + newState.gt = undefined + this.setState(newState, () => { this.runSearch(packageParam, versionParam, devParam) }) @@ -201,6 +221,18 @@ const PackageSearch = React.createClass({ ) }, + onEndOfPage (event) { + if (event.event == null) { + // waypoint is within viewport + return false + } + + const resultsLength = this.state.results ? this.state.results.length : 0 + if (resultsLength % this.props.limit === 0) { + this.runSearch() + } + }, + render () { const { searchValueForName, searchValueForRange } = this.state const showClearIconForName = searchValueForName.length > 0 @@ -305,126 +337,10 @@ const PackageSearch = React.createClass({ ) : null} + ) } }) -function SearchResults (props) { - const { - results, - searchValueForName, - searchValueForRange, - errorMessage - } = props - - if (errorMessage.length > 0) { - return ( -

- - Error: -

- "{ errorMessage }" -

-

- ) - } - - if (results === null) { - return ( - - Search for { searchValueForName || 'a package' } - { searchValueForRange ? ('@' + searchValueForRange) : '' } - - ) - } - - if (results.length === 0) { - return ( - - No results found - - ) - } - - return ( -
- {results.length > 0 ? : null} -
- ) -} - -const Message = ({ children }) => ( -

- - {children} -

-) - -function SearchResultsCount ({ results }) { - if (!results) { - return ( -

- { ' '.replace(/ /g, '\u00a0') } -

- ) - } else { - return ( -

- Found {results} dependents: -

- ) - } -} - -class SearchResultsModules extends React.Component { - shouldComponentUpdate (nextProps) { - return ( - nextProps.queryName !== this.props.queryName - ) - } - - render () { - const { - results, - queryName - } = this.props - - return ( -
- - - - - - - - - - - { - results.map((_package, idx) => ( - - - - - - )) - } - -
Dependent package nameLatest dependent versionRange for { queryName }
- - {_package.name} - - {_package.version}{_package.range}
-
- ) - } -} - export default PackageSearch diff --git a/client/src/components/SearchResults/index.js b/client/src/components/SearchResults/index.js new file mode 100644 index 0000000..d71331a --- /dev/null +++ b/client/src/components/SearchResults/index.js @@ -0,0 +1,120 @@ +import React from 'react' + +function SearchResults (props) { + const { + results, + searchValueForName, + searchValueForRange, + errorMessage + } = props + + if (errorMessage.length > 0) { + return ( +

+ + Error: +

+ "{ errorMessage }" +

+

+ ) + } + + if (results === null) { + return ( + + Search for { searchValueForName || 'a package' } + { searchValueForRange ? ('@' + searchValueForRange) : '' } + + ) + } + + if (results.length === 0) { + return ( + + No results found + + ) + } + + return ( +
+ {results.length > 0 ? : null} +
+ ) +} + +const Message = ({ children }) => ( +

+ + {children} +

+) + +function SearchResultsCount ({ results }) { + if (!results) { + return ( +

+ { ' '.replace(/ /g, '\u00a0') } +

+ ) + } else { + return ( +

+ Found {results} dependents. +

+ ) + } +} + +class SearchResultsModules extends React.Component { + shouldComponentUpdate (nextProps) { + return ( + nextProps.queryName !== this.props.queryName || this.props.results.length < nextProps.results.length + ) + } + + render () { + const { + results, + queryName + } = this.props + + return ( +
+ + + + + + + + + + { + results.map((_package, idx) => ( + + + + + + )) + } + +
Dependent package nameLatest dependent versionRange for { queryName }
+ + {_package.name} + + {_package.version}{_package.range}
+ +
+ ) + } +} + +export default SearchResults