1
0
mirror of https://github.com/flarum/core.git synced 2025-08-05 07:57:46 +02:00

feat: improved page navigation for users list (#3741)

* feat: add first and last page buttons

* feat: add textbox-based page navigation

* feat: add query parameter page navigation
This commit is contained in:
David Wheatley
2023-02-21 13:04:30 +00:00
committed by GitHub
parent 0da069ba9f
commit 408a92b4ea
3 changed files with 117 additions and 8 deletions

View File

@@ -10,6 +10,7 @@ import icon from '../../common/helpers/icon';
import listItems from '../../common/helpers/listItems'; import listItems from '../../common/helpers/listItems';
import type User from '../../common/models/User'; import type User from '../../common/models/User';
import type { IPageAttrs } from '../../common/components/Page';
import ItemList from '../../common/utils/ItemList'; import ItemList from '../../common/utils/ItemList';
import classList from '../../common/utils/classList'; import classList from '../../common/utils/classList';
@@ -45,6 +46,11 @@ export default class UserListPage extends AdminPage {
*/ */
private pageNumber: number = 0; private pageNumber: number = 0;
/**
* Page number being loaded. Zero-indexed.
*/
private loadingPageNumber: number = 0;
/** /**
* Total number of forum users. * Total number of forum users.
* *
@@ -77,12 +83,28 @@ export default class UserListPage extends AdminPage {
private isLoadingPage: boolean = false; private isLoadingPage: boolean = false;
oninit(vnode: Mithril.Vnode<IPageAttrs, this>) {
super.oninit(vnode);
// Get page query value from URL
const page = parseInt(m.route.param('page'));
if (isNaN(page) || page < 1) {
this.setPageNumberInUrl(1);
this.pageNumber = 0;
} else {
this.pageNumber = page - 1;
}
this.loadingPageNumber = this.pageNumber;
}
/** /**
* Component to render. * Component to render.
*/ */
content() { content() {
if (typeof this.pageData === 'undefined') { if (typeof this.pageData === 'undefined') {
this.loadPage(0); this.loadPage(this.pageNumber);
return [ return [
<section class="UserListPage-grid UserListPage-grid--loading"> <section class="UserListPage-grid UserListPage-grid--loading">
@@ -149,6 +171,13 @@ export default class UserListPage extends AdminPage {
{this.isLoadingPage && <LoadingIndicator size="large" />} {this.isLoadingPage && <LoadingIndicator size="large" />}
</section>, </section>,
<nav class="UserListPage-gridPagination"> <nav class="UserListPage-gridPagination">
<Button
disabled={this.pageNumber === 0}
title={app.translator.trans('core.admin.users.pagination.first_page_button')}
onclick={this.goToPage.bind(this, 1)}
icon="fas fa-step-backward"
className="Button Button--icon UserListPage-firstPageBtn"
/>
<Button <Button
disabled={this.pageNumber === 0} disabled={this.pageNumber === 0}
title={app.translator.trans('core.admin.users.pagination.back_button')} title={app.translator.trans('core.admin.users.pagination.back_button')}
@@ -158,7 +187,38 @@ export default class UserListPage extends AdminPage {
/> />
<span class="UserListPage-pageNumber"> <span class="UserListPage-pageNumber">
{app.translator.trans('core.admin.users.pagination.page_counter', { {app.translator.trans('core.admin.users.pagination.page_counter', {
current: this.pageNumber + 1, current: (
<input
type="text"
value={this.loadingPageNumber + 1}
aria-label={extractText(app.translator.trans('core.admin.users.pagination.go_to_page_textbox_a11y_label'))}
autocomplete="off"
className="FormControl UserListPage-pageNumberInput"
onchange={(e: InputEvent) => {
const target = e.target as HTMLInputElement;
let pageNumber = parseInt(target.value);
if (isNaN(pageNumber)) {
// Invalid value, reset to current page
target.value = (this.pageNumber + 1).toString();
return;
}
if (pageNumber < 1) {
// Lower constraint
pageNumber = 1;
} else if (pageNumber > this.getTotalPageCount()) {
// Upper constraint
pageNumber = this.getTotalPageCount();
}
target.value = pageNumber.toString();
this.goToPage(pageNumber);
}}
/>
),
currentNum: this.pageNumber + 1,
total: this.getTotalPageCount(), total: this.getTotalPageCount(),
})} })}
</span> </span>
@@ -169,6 +229,13 @@ export default class UserListPage extends AdminPage {
icon="fas fa-chevron-right" icon="fas fa-chevron-right"
className="Button Button--icon UserListPage-nextBtn" className="Button Button--icon UserListPage-nextBtn"
/> />
<Button
disabled={!this.moreData}
title={app.translator.trans('core.admin.users.pagination.last_page_button')}
onclick={this.goToPage.bind(this, this.getTotalPageCount())}
icon="fas fa-step-forward"
className="Button Button--icon UserListPage-lastPageBtn"
/>
</nav>, </nav>,
]; ];
} }
@@ -347,11 +414,14 @@ export default class UserListPage extends AdminPage {
* *
* Uses the `this.numPerPage` as the response limit, and automatically calculates the offset required from `pageNumber`. * Uses the `this.numPerPage` as the response limit, and automatically calculates the offset required from `pageNumber`.
* *
* @param pageNumber The page number to load and display * @param pageNumber The **zero-based** page number to load and display
*/ */
async loadPage(pageNumber: number) { async loadPage(pageNumber: number) {
if (pageNumber < 0) pageNumber = 0; if (pageNumber < 0) pageNumber = 0;
this.loadingPageNumber = pageNumber;
this.setPageNumberInUrl(pageNumber + 1);
app.store app.store
.find<User[]>('users', { .find<User[]>('users', {
filter: { q: this.query }, filter: { q: this.query },
@@ -369,9 +439,16 @@ export default class UserListPage extends AdminPage {
// @ts-ignore // @ts-ignore
delete data.payload; delete data.payload;
const lastPage = this.getTotalPageCount();
if (pageNumber > lastPage) {
this.loadPage(lastPage - 1);
} else {
this.pageData = data; this.pageData = data;
this.pageNumber = pageNumber; this.pageNumber = pageNumber;
this.loadingPageNumber = pageNumber;
this.isLoadingPage = false; this.isLoadingPage = false;
}
m.redraw(); m.redraw();
}) })
@@ -390,4 +467,20 @@ export default class UserListPage extends AdminPage {
this.isLoadingPage = true; this.isLoadingPage = true;
this.loadPage(this.pageNumber - 1); this.loadPage(this.pageNumber - 1);
} }
/**
* @param page The **1-based** page number
*/
goToPage(page: number) {
this.isLoadingPage = true;
this.loadPage(page - 1);
}
private setPageNumberInUrl(pageNumber: number) {
const search = window.location.hash.split('?', 2);
const params = new URLSearchParams(search?.[1] ?? '');
params.set('page', `${pageNumber}`);
window.location.hash = search?.[0] + '?' + params.toString();
}
} }

View File

@@ -69,11 +69,24 @@
} }
&-gridPagination { &-gridPagination {
display: flex; display: grid;
grid-template-columns: auto auto 1fr auto auto;
gap: 8px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
margin-top: 16px; margin-top: 16px;
} }
&-pageNumber {
text-align: center;
}
&-pageNumberInput {
display: inline-block;
margin: 0 8px;
width: auto;
max-width: 80px;
}
} }
// Handles styling of default UserList columns // Handles styling of default UserList columns

View File

@@ -273,6 +273,9 @@ core:
pagination: pagination:
back_button: Previous page back_button: Previous page
first_button: Go to first page
go_to_page_textbox_a11y_label: Go directly to page number
last_button: Go to last page
next_button: Next page next_button: Next page
page_counter: Page {current} of {total} page_counter: Page {current} of {total}