mirror of
https://github.com/flarum/core.git
synced 2025-08-04 23:47:32 +02:00
Create Pagination util & make DiscussionListState and DiscussionList use it
I'm 100% sure that this code can be improved by a ton. Just pushing this so we can potentially work off of it. Some stuff taken from PR #1829. Does *not* have changing URL query parameter - this code is already a disaster.
This commit is contained in:
80
js/src/common/utils/Pagination.ts
Normal file
80
js/src/common/utils/Pagination.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
export default class Pagination<T> {
|
||||
private readonly loadFunction: (page: number) => Promise<any>;
|
||||
|
||||
public loading = {
|
||||
prev: false,
|
||||
next: false,
|
||||
};
|
||||
|
||||
public page: number;
|
||||
|
||||
public data: { [page: number]: T } = {};
|
||||
|
||||
public pages: {
|
||||
first: number;
|
||||
last: number;
|
||||
};
|
||||
|
||||
constructor(load: (page: number) => Promise<any>, page: number = 1) {
|
||||
this.loadFunction = load;
|
||||
this.page = page;
|
||||
|
||||
this.pages = {
|
||||
first: page,
|
||||
last: page,
|
||||
};
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
refresh(page: number) {
|
||||
this.clear();
|
||||
|
||||
this.page = page;
|
||||
this.pages.last = page - 1;
|
||||
this.pages.first = page;
|
||||
|
||||
return this.loadNext();
|
||||
}
|
||||
|
||||
loadNext() {
|
||||
this.loading.next = true;
|
||||
const page = this.pages.last + 1;
|
||||
|
||||
return this.load(
|
||||
page,
|
||||
() => (this.loading.next = false),
|
||||
() => (this.pages.last = this.page = page)
|
||||
);
|
||||
}
|
||||
|
||||
loadPrev() {
|
||||
this.loading.prev = true;
|
||||
const page = this.pages.first - 1;
|
||||
|
||||
return this.load(
|
||||
page,
|
||||
() => (this.loading.prev = false),
|
||||
() => (this.pages.first = this.page = page)
|
||||
);
|
||||
}
|
||||
|
||||
private load(page, done, success) {
|
||||
return this.loadFunction(page)
|
||||
.then((out) => {
|
||||
done();
|
||||
success();
|
||||
|
||||
this.data[this.page] = out;
|
||||
|
||||
return out;
|
||||
})
|
||||
.catch((err) => {
|
||||
done();
|
||||
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
}
|
@@ -21,13 +21,7 @@ export default class DiscussionList extends Component {
|
||||
if (state.isLoading()) {
|
||||
loading = LoadingIndicator.component();
|
||||
} else if (state.moreResults) {
|
||||
loading = Button.component(
|
||||
{
|
||||
className: 'Button',
|
||||
onclick: state.loadMore.bind(state),
|
||||
},
|
||||
app.translator.trans('core.forum.discussion_list.load_more_button')
|
||||
);
|
||||
loading = this.getLoadButton('more', state.loadMore.bind(state));
|
||||
}
|
||||
|
||||
if (state.empty()) {
|
||||
@@ -35,8 +29,18 @@ export default class DiscussionList extends Component {
|
||||
return <div className="DiscussionList">{Placeholder.component({ text })}</div>;
|
||||
}
|
||||
|
||||
console.log(state);
|
||||
|
||||
return (
|
||||
<div className={'DiscussionList' + (state.isSearchResults() ? ' DiscussionList--searchResults' : '')}>
|
||||
{state.isLoadingPrev() ? (
|
||||
<LoadingIndicator />
|
||||
) : state.pagination.pages.first !== 1 ? (
|
||||
<div className="DiscussionList-loadMore">{this.getLoadButton('prev', state.loadPrev.bind(state))}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
<ul className="DiscussionList-discussions">
|
||||
{state.discussions.map((discussion) => {
|
||||
return (
|
||||
@@ -46,8 +50,17 @@ export default class DiscussionList extends Component {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
|
||||
<div className="DiscussionList-loadMore">{loading}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getLoadButton(key, onclick) {
|
||||
return (
|
||||
<Button className="Button" onclick={onclick}>
|
||||
{app.translator.trans(`core.forum.discussion_list.load_${key}_button`)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,8 @@
|
||||
import Pagination from '../../common/utils/Pagination';
|
||||
|
||||
export default class DiscussionListState {
|
||||
static DISCUSSIONS_PER_PAGE = 20;
|
||||
|
||||
constructor(params = {}, app = window.app) {
|
||||
this.params = params;
|
||||
|
||||
@@ -8,7 +12,7 @@ export default class DiscussionListState {
|
||||
|
||||
this.moreResults = false;
|
||||
|
||||
this.loading = false;
|
||||
this.pagination = new Pagination(this.load.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,33 +86,16 @@ export default class DiscussionListState {
|
||||
* This can be used to refresh discussions without loading animations.
|
||||
*/
|
||||
refresh({ deferClear = false } = {}) {
|
||||
this.loading = true;
|
||||
this.pagination.loading.next = true;
|
||||
|
||||
if (!deferClear) {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
return this.loadResults().then(
|
||||
(results) => {
|
||||
// This ensures that any changes made while waiting on this request
|
||||
// are ignored. Otherwise, we could get duplicate discussions.
|
||||
// We don't use `this.clear()` to avoid an unnecessary redraw.
|
||||
this.discussions = [];
|
||||
this.parseResults(results);
|
||||
},
|
||||
() => {
|
||||
this.loading = false;
|
||||
m.redraw();
|
||||
}
|
||||
);
|
||||
return this.pagination.refresh(Number(m.route.param('page')) || 1).then(this.parse.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a new page of discussion results.
|
||||
*
|
||||
* @param offset The index to start the page at.
|
||||
*/
|
||||
loadResults(offset) {
|
||||
load(page) {
|
||||
const preloadedDiscussions = this.app.preloadedApiDocument();
|
||||
|
||||
if (preloadedDiscussions) {
|
||||
@@ -116,44 +103,54 @@ export default class DiscussionListState {
|
||||
}
|
||||
|
||||
const params = this.requestParams();
|
||||
params.page = { offset };
|
||||
params.page = { offset: DiscussionListState.DISCUSSIONS_PER_PAGE * (page - 1) };
|
||||
params.include = params.include.join(',');
|
||||
|
||||
return this.app.store.find('discussions', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the next page of discussion results.
|
||||
*/
|
||||
loadMore() {
|
||||
this.loading = true;
|
||||
loadPrev() {
|
||||
return this.pagination.loadPrev().then(this.parse.bind(this));
|
||||
}
|
||||
|
||||
this.loadResults(this.discussions.length).then(this.parseResults.bind(this));
|
||||
loadMore() {
|
||||
return this.pagination.loadNext().then(this.parse.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse results and append them to the discussion list.
|
||||
*/
|
||||
parseResults(results) {
|
||||
this.discussions.push(...results);
|
||||
parse() {
|
||||
const discussions = [];
|
||||
const { first, last } = this.pagination.pages;
|
||||
|
||||
this.loading = false;
|
||||
this.moreResults = !!results.payload.links && !!results.payload.links.next;
|
||||
for (let page = first; page <= last; page++) {
|
||||
const results = this.pagination.data[page];
|
||||
|
||||
if (Array.isArray(results)) discussions.push(...results);
|
||||
}
|
||||
|
||||
this.discussions = discussions;
|
||||
|
||||
const results = this.pagination.data[last];
|
||||
this.moreResults = !!results.payload.links.next;
|
||||
|
||||
m.redraw();
|
||||
|
||||
return results;
|
||||
return discussions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a discussion from the list if it is present.
|
||||
*/
|
||||
removeDiscussion(discussion) {
|
||||
const index = this.discussions.indexOf(discussion);
|
||||
Object.keys(this.pagination.data).forEach((key) => {
|
||||
const index = this.pagination.data[key].indexOf(discussion);
|
||||
|
||||
if (index !== -1) {
|
||||
this.discussions.splice(index, 1);
|
||||
}
|
||||
this.pagination.data[key].splice(index, 1);
|
||||
});
|
||||
|
||||
this.parse();
|
||||
|
||||
m.redraw();
|
||||
}
|
||||
@@ -177,7 +174,11 @@ export default class DiscussionListState {
|
||||
* Are discussions currently being loaded?
|
||||
*/
|
||||
isLoading() {
|
||||
return this.loading;
|
||||
return this.pagination.loading.next;
|
||||
}
|
||||
|
||||
isLoadingPrev() {
|
||||
return this.pagination.loading.prev;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user