mirror of https://github.com/docker/docs.git
Merge pull request #1020 from FrenchBen/v2-pagination
Added V2 search and pagination
This commit is contained in:
commit
6c3784d06b
|
|
@ -7,9 +7,9 @@ class RepositoryActions {
|
||||||
regHubUtil.recommended();
|
regHubUtil.recommended();
|
||||||
}
|
}
|
||||||
|
|
||||||
search (query) {
|
search (query, page = 1) {
|
||||||
this.dispatch({});
|
this.dispatch({query, page});
|
||||||
regHubUtil.search(query);
|
regHubUtil.search(query, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
repos () {
|
repos () {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,11 @@ module.exports = React.createClass({
|
||||||
username: accountStore.getState().username,
|
username: accountStore.getState().username,
|
||||||
verified: accountStore.getState().verified,
|
verified: accountStore.getState().verified,
|
||||||
accountLoading: accountStore.getState().loading,
|
accountLoading: accountStore.getState().loading,
|
||||||
error: repositoryStore.getState().error
|
error: repositoryStore.getState().error,
|
||||||
|
currentPage: repositoryStore.getState().currentPage,
|
||||||
|
totalPage: repositoryStore.getState().totalPage,
|
||||||
|
previousPage: repositoryStore.getState().previousPage,
|
||||||
|
nextPage: repositoryStore.getState().nextPage
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
|
|
@ -43,7 +47,11 @@ module.exports = React.createClass({
|
||||||
update: function () {
|
update: function () {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: repositoryStore.loading(),
|
loading: repositoryStore.loading(),
|
||||||
repos: repositoryStore.all()
|
repos: repositoryStore.all(),
|
||||||
|
currentPage: repositoryStore.getState().currentPage,
|
||||||
|
totalPage: repositoryStore.getState().totalPage,
|
||||||
|
previousPage: repositoryStore.getState().previousPage,
|
||||||
|
nextPage: repositoryStore.getState().nextPage
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
updateAccount: function () {
|
updateAccount: function () {
|
||||||
|
|
@ -53,30 +61,40 @@ module.exports = React.createClass({
|
||||||
accountLoading: accountStore.getState().loading
|
accountLoading: accountStore.getState().loading
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
search: function (query) {
|
search: function (query, page = 1) {
|
||||||
if (_searchPromise) {
|
if (_searchPromise) {
|
||||||
_searchPromise.cancel();
|
_searchPromise.cancel();
|
||||||
_searchPromise = null;
|
_searchPromise = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousPage = (page - 1 < 1) ? 1 : page - 1;
|
||||||
|
let nextPage = (page + 1 > this.state.totalPage) ? this.state.totalPage : page + 1;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
query: query,
|
query: query,
|
||||||
loading: true
|
loading: true,
|
||||||
|
currentPage: page,
|
||||||
|
previousPage: previousPage,
|
||||||
|
nextPage: nextPage
|
||||||
});
|
});
|
||||||
|
|
||||||
_searchPromise = Promise.delay(200).cancellable().then(() => {
|
_searchPromise = Promise.delay(200).cancellable().then(() => {
|
||||||
metrics.track('Searched for Images');
|
metrics.track('Searched for Images');
|
||||||
_searchPromise = null;
|
_searchPromise = null;
|
||||||
repositoryActions.search(query);
|
repositoryActions.search(query, page);
|
||||||
}).catch(Promise.CancellationError, () => {});
|
}).catch(Promise.CancellationError, () => {});
|
||||||
},
|
},
|
||||||
handleChange: function (e) {
|
handleChange: function (e) {
|
||||||
var query = e.target.value;
|
let query = e.target.value;
|
||||||
if (query === this.state.query) {
|
if (query === this.state.query) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.search(query);
|
this.search(query);
|
||||||
},
|
},
|
||||||
|
handlePage: function (page) {
|
||||||
|
let query = this.state.query;
|
||||||
|
this.search(query, page);
|
||||||
|
},
|
||||||
handleFilter: function (filter) {
|
handleFilter: function (filter) {
|
||||||
|
|
||||||
// If we're clicking on the filter again - refresh
|
// If we're clicking on the filter again - refresh
|
||||||
|
|
@ -111,20 +129,76 @@ module.exports = React.createClass({
|
||||||
})
|
})
|
||||||
.filter(repo => filter === 'all' || (filter === 'recommended' && repo.is_recommended) || (filter === 'userrepos' && repo.is_user_repo));
|
.filter(repo => filter === 'all' || (filter === 'recommended' && repo.is_recommended) || (filter === 'userrepos' && repo.is_user_repo));
|
||||||
|
|
||||||
let results;
|
let results, paginateResults;
|
||||||
|
let previous = [];
|
||||||
|
let next = [];
|
||||||
|
if (this.state.previousPage) {
|
||||||
|
let previousPage = this.state.currentPage - 7;
|
||||||
|
if (previousPage < 1) {
|
||||||
|
previousPage = 1;
|
||||||
|
}
|
||||||
|
previous.push((
|
||||||
|
<li>
|
||||||
|
<a href="" onClick={this.handlePage.bind(this, 1)} aria-label="First">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
));
|
||||||
|
for (previousPage; previousPage < this.state.currentPage; previousPage++) {
|
||||||
|
previous.push((
|
||||||
|
<li><a href="" onClick={this.handlePage.bind(this, previousPage)}>{previousPage}</a></li>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.state.nextPage) {
|
||||||
|
let nextPage = this.state.currentPage + 1;
|
||||||
|
for (nextPage; nextPage < this.state.totalPage; nextPage++) {
|
||||||
|
next.push((
|
||||||
|
<li><a href="" onClick={this.handlePage.bind(this, nextPage)}>{nextPage}</a></li>
|
||||||
|
));
|
||||||
|
if (nextPage > this.state.currentPage + 7) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.push((
|
||||||
|
<li>
|
||||||
|
<a href="" onClick={this.handlePage.bind(this, this.state.totalPage)} aria-label="Last">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = (
|
||||||
|
<li className="active">
|
||||||
|
<span>{this.state.currentPage} <span className="sr-only">(current)</span></span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
|
||||||
|
paginateResults = next.length || previous.length ? (
|
||||||
|
<nav>
|
||||||
|
<ul className="pagination">
|
||||||
|
{previous}
|
||||||
|
{current}
|
||||||
|
{next}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
) : null;
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
results = (
|
results = (
|
||||||
<div className="no-results">
|
<div className="no-results">
|
||||||
<h2>There was an error contacting Docker Hub.</h2>
|
<h2>There was an error contacting Docker Hub.</h2>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
paginateResults = null;
|
||||||
} else if (filter === 'userrepos' && !accountStore.getState().username) {
|
} else if (filter === 'userrepos' && !accountStore.getState().username) {
|
||||||
results = (
|
results = (
|
||||||
<div className="no-results">
|
<div className="no-results">
|
||||||
<h2><Router.Link to="login">Log In</Router.Link> or <Router.Link to="signup">Sign Up</Router.Link> to access your Docker Hub repositories.</h2>
|
<h2><Router.Link to="login">Log In</Router.Link> or <Router.Link to="signup">Sign Up</Router.Link> to access your Docker Hub repositories.</h2>
|
||||||
<RetinaImage src="connect-art.png" checkIfRetinaImgExists={false}/>
|
<RetinaImage src="connect-art.png" checkIfRetinaImgExists={false}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
paginateResults = null;
|
||||||
} else if (filter === 'userrepos' && !accountStore.getState().verified) {
|
} else if (filter === 'userrepos' && !accountStore.getState().verified) {
|
||||||
let spinner = this.state.accountLoading ? <div className="spinner la-ball-clip-rotate la-dark"><div></div></div> : null;
|
let spinner = this.state.accountLoading ? <div className="spinner la-ball-clip-rotate la-dark"><div></div></div> : null;
|
||||||
results = (
|
results = (
|
||||||
|
|
@ -136,6 +210,7 @@ module.exports = React.createClass({
|
||||||
<RetinaImage src="inspection.png" checkIfRetinaImgExists={false}/>
|
<RetinaImage src="inspection.png" checkIfRetinaImgExists={false}/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
paginateResults = null;
|
||||||
} else if (this.state.loading) {
|
} else if (this.state.loading) {
|
||||||
results = (
|
results = (
|
||||||
<div className="no-results">
|
<div className="no-results">
|
||||||
|
|
@ -168,14 +243,20 @@ module.exports = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
let otherResults = otherItems.length ? (
|
let otherResults;
|
||||||
<div>
|
if (otherItems.length) {
|
||||||
<h4>Other Repositories</h4>
|
otherResults = (
|
||||||
<div className="result-grid">
|
<div>
|
||||||
{otherItems}
|
<h4>Other Repositories</h4>
|
||||||
|
<div className="result-grid">
|
||||||
|
{otherItems}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
) : null;
|
} else {
|
||||||
|
otherResults = null;
|
||||||
|
paginateResults = null;
|
||||||
|
}
|
||||||
|
|
||||||
results = (
|
results = (
|
||||||
<div className="result-grids">
|
<div className="result-grids">
|
||||||
|
|
@ -237,6 +318,9 @@ module.exports = React.createClass({
|
||||||
<div className="results">
|
<div className="results">
|
||||||
{results}
|
{results}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="pagination-center">
|
||||||
|
{paginateResults}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ class RepositoryStore {
|
||||||
this.results = [];
|
this.results = [];
|
||||||
this.recommended = [];
|
this.recommended = [];
|
||||||
this.repos = [];
|
this.repos = [];
|
||||||
|
this.query = null;
|
||||||
|
this.nextPage = null;
|
||||||
|
this.previousPage = null;
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.totalPage = null;
|
||||||
this.reposLoading = false;
|
this.reposLoading = false;
|
||||||
this.recommendedLoading = false;
|
this.recommendedLoading = false;
|
||||||
this.resultsLoading = false;
|
this.resultsLoading = false;
|
||||||
|
|
@ -41,12 +46,18 @@ class RepositoryStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search () {
|
search ({query, page}) {
|
||||||
this.setState({error: null, resultsLoading: true});
|
if (this.query === query) {
|
||||||
|
let previousPage = (page - 1 < 1) ? 1 : page - 1;
|
||||||
|
let nextPage = (page + 1 > this.totalPage) ? this.totalPage : page + 1;
|
||||||
|
this.setState({query: query, error: null, resultsLoading: true, currentPage: page, nextPage: nextPage, previousPage: previousPage});
|
||||||
|
} else {
|
||||||
|
this.setState({query: query, error: null, resultsLoading: true, nextPage: null, previousPage: null, currentPage: 1});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultsUpdated ({repos}) {
|
resultsUpdated ({repos, page, previous, next, total}) {
|
||||||
this.setState({results: repos, resultsLoading: false});
|
this.setState({results: repos, currentPage: page, previousPage: previous, nextPage: next, totalPage: total, resultsLoading: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
recommended () {
|
recommended () {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import tagServerActions from '../actions/TagServerActions';
|
||||||
|
|
||||||
let REGHUB2_ENDPOINT = process.env.REGHUB2_ENDPOINT || 'https://hub.docker.com/v2';
|
let REGHUB2_ENDPOINT = process.env.REGHUB2_ENDPOINT || 'https://hub.docker.com/v2';
|
||||||
let searchReq = null;
|
let searchReq = null;
|
||||||
|
let PAGING = 24;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Normalizes results from search to v2 repository results
|
// Normalizes results from search to v2 repository results
|
||||||
|
|
@ -24,7 +25,7 @@ module.exports = {
|
||||||
return obj;
|
return obj;
|
||||||
},
|
},
|
||||||
|
|
||||||
search: function (query, page) {
|
search: function (query, page, sorting = null) {
|
||||||
if (searchReq) {
|
if (searchReq) {
|
||||||
searchReq.abort();
|
searchReq.abort();
|
||||||
searchReq = null;
|
searchReq = null;
|
||||||
|
|
@ -33,10 +34,18 @@ module.exports = {
|
||||||
if (!query) {
|
if (!query) {
|
||||||
repositoryServerActions.resultsUpdated({repos: []});
|
repositoryServerActions.resultsUpdated({repos: []});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Sort:
|
||||||
|
* All - no sorting
|
||||||
|
* ordering: -start_count
|
||||||
|
* ordering: -pull_count
|
||||||
|
* is_automated: 1
|
||||||
|
* is_official: 1
|
||||||
|
*/
|
||||||
|
|
||||||
searchReq = request.get({
|
searchReq = request.get({
|
||||||
url: 'https://registry.hub.docker.com/v1/search?',
|
url: `${REGHUB2_ENDPOINT}/search/repositories/?`,
|
||||||
qs: {q: query, page}
|
qs: {query: query, page: page, page_size: PAGING, sorting}
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
repositoryServerActions.error({error});
|
repositoryServerActions.error({error});
|
||||||
|
|
@ -44,10 +53,14 @@ module.exports = {
|
||||||
|
|
||||||
let data = JSON.parse(body);
|
let data = JSON.parse(body);
|
||||||
let repos = _.map(data.results, result => {
|
let repos = _.map(data.results, result => {
|
||||||
|
result.name = result.repo_name;
|
||||||
return this.normalize(result);
|
return this.normalize(result);
|
||||||
});
|
});
|
||||||
|
let next = data.next;
|
||||||
|
let previous = data.previous;
|
||||||
|
let total = Math.floor(data.count / PAGING);
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
repositoryServerActions.resultsUpdated({repos});
|
repositoryServerActions.resultsUpdated({repos, page, previous, next, total});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -99,7 +112,8 @@ module.exports = {
|
||||||
|
|
||||||
tags: function (repo, callback) {
|
tags: function (repo, callback) {
|
||||||
hubUtil.request({
|
hubUtil.request({
|
||||||
url: `${REGHUB2_ENDPOINT}/repositories/${repo}/tags`
|
url: `${REGHUB2_ENDPOINT}/repositories/${repo}/tags`,
|
||||||
|
qs: {page: 1, page_size: 100}
|
||||||
}, (error, response, body) => {
|
}, (error, response, body) => {
|
||||||
if (response.statusCode === 200) {
|
if (response.statusCode === 200) {
|
||||||
let data = JSON.parse(body);
|
let data = JSON.parse(body);
|
||||||
|
|
@ -122,7 +136,8 @@ module.exports = {
|
||||||
let namespaces = [];
|
let namespaces = [];
|
||||||
// Get Orgs for user
|
// Get Orgs for user
|
||||||
hubUtil.request({
|
hubUtil.request({
|
||||||
url: `${REGHUB2_ENDPOINT}/user/orgs/?page_size=50`
|
url: `${REGHUB2_ENDPOINT}/user/orgs/`,
|
||||||
|
qs: { page_size: 50 }
|
||||||
}, (orgError, orgResponse, orgBody) => {
|
}, (orgError, orgResponse, orgBody) => {
|
||||||
if (orgError) {
|
if (orgError) {
|
||||||
repositoryServerActions.error({orgError});
|
repositoryServerActions.error({orgError});
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,22 @@
|
||||||
.spinner {
|
.spinner {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
.pagination-center {
|
||||||
|
flex: 1 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #f6f8fb;
|
||||||
|
.pagination {
|
||||||
|
margin: 5px 0px;
|
||||||
|
a:focus {
|
||||||
|
background-color: #fff;
|
||||||
|
color: @brand-primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue