Skip to content

Commit 74a22f9

Browse files
committed
enh(pagination): add loading guard, total count, jump to page
Signed-off-by: Benjamin Frueh <benjamin.frueh@gmail.com>
1 parent 3b25c59 commit 74a22f9

File tree

7 files changed

+265
-22
lines changed

7 files changed

+265
-22
lines changed

lib/Controller/FolderController.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public function getFolders(bool $applicable = false, int $offset = 0, ?int $limi
169169
* 200: Groupfolder returned
170170
*/
171171
#[NoAdminRequired]
172-
#[FrontpageRoute(verb: 'GET', url: '/folders/{id}')]
172+
#[FrontpageRoute(verb: 'GET', url: '/folders/{id}', requirements: ['id' => '\d+'])]
173173
public function getFolder(int $id): DataResponse {
174174
$storageId = $this->getRootFolderStorageId();
175175
if ($storageId === null) {
@@ -555,6 +555,20 @@ public function aclMappingSearch(int $id, string $search = ''): DataResponse {
555555
]);
556556
}
557557

558+
/**
559+
* Gets the total number of Groupfolders
560+
*
561+
* @return DataResponse<Http::STATUS_OK, array{count: int}, array{}>
562+
*
563+
* 200: Groupfolder count returned
564+
*/
565+
#[RequireGroupFolderAdmin]
566+
#[NoAdminRequired]
567+
#[FrontpageRoute(verb: 'GET', url: '/folders/count')]
568+
public function getFoldersCount(): DataResponse {
569+
return new DataResponse(['count' => $this->manager->countAllFolders()]);
570+
}
571+
558572
private function compareFolderNames(string $a, string $b): int {
559573
if (($value = strnatcmp($a, $b)) === 0) {
560574
return $value;

lib/Folder/FolderManager.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,4 +1162,11 @@ public function hasFolderACLDefaultNoPermission(int $folderId): bool {
11621162

11631163
return $hasDefaultNoPermission;
11641164
}
1165+
1166+
public function countAllFolders(): int {
1167+
$query = $this->connection->getQueryBuilder();
1168+
$query->select($query->func()->count('folder_id'))
1169+
->from('group_folders');
1170+
return (int)$query->executeQuery()->fetchOne();
1171+
}
11651172
}

openapi.json

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2702,6 +2702,104 @@
27022702
}
27032703
}
27042704
}
2705+
},
2706+
"/index.php/apps/groupfolders/folders/count": {
2707+
"get": {
2708+
"operationId": "folder-get-folders-count",
2709+
"summary": "Gets the total number of Groupfolders",
2710+
"tags": [
2711+
"folder"
2712+
],
2713+
"security": [
2714+
{
2715+
"bearer_auth": []
2716+
},
2717+
{
2718+
"basic_auth": []
2719+
}
2720+
],
2721+
"parameters": [
2722+
{
2723+
"name": "OCS-APIRequest",
2724+
"in": "header",
2725+
"description": "Required to be true for the API request to pass",
2726+
"required": true,
2727+
"schema": {
2728+
"type": "boolean",
2729+
"default": true
2730+
}
2731+
}
2732+
],
2733+
"responses": {
2734+
"200": {
2735+
"description": "Groupfolder count returned",
2736+
"content": {
2737+
"application/json": {
2738+
"schema": {
2739+
"type": "object",
2740+
"required": [
2741+
"ocs"
2742+
],
2743+
"properties": {
2744+
"ocs": {
2745+
"type": "object",
2746+
"required": [
2747+
"meta",
2748+
"data"
2749+
],
2750+
"properties": {
2751+
"meta": {
2752+
"$ref": "#/components/schemas/OCSMeta"
2753+
},
2754+
"data": {
2755+
"type": "object",
2756+
"required": [
2757+
"count"
2758+
],
2759+
"properties": {
2760+
"count": {
2761+
"type": "integer",
2762+
"format": "int64"
2763+
}
2764+
}
2765+
}
2766+
}
2767+
}
2768+
}
2769+
}
2770+
}
2771+
}
2772+
},
2773+
"401": {
2774+
"description": "Current user is not logged in",
2775+
"content": {
2776+
"application/json": {
2777+
"schema": {
2778+
"type": "object",
2779+
"required": [
2780+
"ocs"
2781+
],
2782+
"properties": {
2783+
"ocs": {
2784+
"type": "object",
2785+
"required": [
2786+
"meta",
2787+
"data"
2788+
],
2789+
"properties": {
2790+
"meta": {
2791+
"$ref": "#/components/schemas/OCSMeta"
2792+
},
2793+
"data": {}
2794+
}
2795+
}
2796+
}
2797+
}
2798+
}
2799+
}
2800+
}
2801+
}
2802+
}
27052803
}
27062804
},
27072805
"tags": []

src/settings/Api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,9 @@ export class Api {
148148
}
149149
}
150150

151+
async countFolders(): Promise<number> {
152+
const response = await axios.get<OCSResponse<{ count: number }>>(this.getUrl('folders/count'))
153+
return response.data.ocs.data.count
154+
}
155+
151156
}

src/settings/App.scss

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,30 @@
178178
}
179179
}
180180

181-
.groupfolders-pagination__list {
182-
display: flex;
183-
gap: var(--default-grid-baseline);
184-
justify-content: center;
185-
}
186-
187-
.groupfolders-pagination__button {
188-
height: var(--default-clickable-area);
189-
}
181+
.groupfolders-pagination {
182+
display: flex;
183+
align-items: center;
184+
185+
.groupfolders-pagination__list {
186+
display: flex;
187+
gap: var(--default-grid-baseline);
188+
justify-content: center;
189+
}
190+
191+
.groupfolders-pagination__button {
192+
height: var(--default-clickable-area);
193+
line-height: var(--default-clickable-area);
194+
}
195+
196+
.groupfolders-pagination__goto-page {
197+
flex: 1;
198+
display: flex;
199+
justify-content: flex-end;
200+
align-items: center;
201+
gap: 6px;
202+
}
203+
204+
}
190205

191206
}
192207

src/settings/App.tsx

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ export interface AppState {
5757
isAdminNextcloud: boolean;
5858
checkAppsInstalled: boolean;
5959
currentPage: number;
60+
loading: boolean;
61+
totalFolders: number;
6062
}
6163

6264
export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Search.Core> {
@@ -80,6 +82,8 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
8082
isAdminNextcloud: false,
8183
checkAppsInstalled: false,
8284
currentPage: 0,
85+
loading: false,
86+
totalFolders: 0,
8387
}
8488

8589
componentDidMount() {
@@ -93,6 +97,9 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
9397
this.api.listCircles().then((circles) => {
9498
this.setState({ circles })
9599
})
100+
this.api.countFolders().then((totalFolders) => {
101+
this.setState({ totalFolders })
102+
})
96103

97104
this.setState({ isAdminNextcloud: loadState('groupfolders', 'isAdminNextcloud') })
98105
this.setState({ checkAppsInstalled: loadState('groupfolders', 'checkAppsInstalled') })
@@ -203,13 +210,25 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
203210
}
204211

205212
async goToPage(page: number) {
213+
if (this.state.loading) return
214+
206215
const loadedPage = Math.floor(this.state.folders.length / pageSize)
207216
if (loadedPage <= page) {
208-
const folders = await this.api.listFolders(this.state.folders.length, (page + 1) * pageSize - this.state.folders.length + 1, this.state.sort)
209-
this.setState({
210-
folders: [...this.state.folders, ...folders],
211-
currentPage: page,
212-
})
217+
this.setState({ loading: true })
218+
try {
219+
const folders = await this.api.listFolders(
220+
this.state.folders.length, (page + 1) * pageSize - this.state.folders.length + 1,
221+
this.state.sort,
222+
this.state.sortOrder === 1 ? 'asc' : 'desc',
223+
)
224+
this.setState({
225+
folders: [...this.state.folders, ...folders],
226+
currentPage: page,
227+
})
228+
} finally {
229+
this.setState({ loading: false })
230+
}
231+
213232
} else {
214233
this.setState({
215234
currentPage: page,
@@ -266,6 +285,7 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
266285

267286
render() {
268287
const isCirclesEnabled = loadState('groupfolders', 'isCirclesEnabled', false)
288+
const lastPage = Math.max(0, Math.ceil(this.state.totalFolders / pageSize) - 1)
269289
const groupHeader = isCirclesEnabled
270290
? t('groupfolders', 'Group or team')
271291
: t('groupfolders', 'Group')
@@ -274,6 +294,8 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
274294
? t('groupfolders', 'Sort by number of groups or teams that have access to this folder')
275295
: t('groupfolders', 'Sort by number of groups that have access to this folder')
276296

297+
console.log('totalFolders:', this.state.totalFolders, 'lastPage:', lastPage, 'currentPage:', this.state.currentPage)
298+
277299
const rows
278300
= this.state.folders
279301
.filter(folder => {
@@ -416,13 +438,14 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
416438
</tr>
417439
</FlipMove>
418440
</table>
419-
<nav className="groupfolders-pagination" aria-label={t('groupfolders', 'Pagination of team folders')}>
441+
<nav className="groupfolders-pagination" style={{ display: 'flex', alignItems: 'center' }} aria-label={t('groupfolders', 'Pagination of team folders')}>
442+
<div style={{ flex: 1 }} />
420443
<ul className="groupfolders-pagination__list">
421444
<li>
422445
<button
423446
aria-label={t('groupfolders', 'Previous')}
424447
className="groupfolders-pagination__button"
425-
disabled={this.state.currentPage === 0}
448+
disabled={this.state.currentPage === 0 || this.state.loading}
426449
title={t('groupfolders', 'Previous')}
427450
onClick={() => this.goToPage(this.state.currentPage - 1)}></button>
428451
</li>
@@ -441,34 +464,53 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
441464
<li><button aria-current="page" aria-disabled className="primary">{this.state.currentPage + 1}</button></li>
442465
{
443466
// show the next page if it exists (we know at least that the next exists or not)
444-
(this.state.currentPage + 1) < (this.state.folders.length / pageSize)
467+
(this.state.currentPage + 1) <= lastPage
445468
&& <li>
446469
<button onClick={() => this.goToPage(this.state.currentPage + 1)}>{this.state.currentPage + 2}</button>
447470
</li>
448471
}
449472
{
450473
// If we know more than two next pages exist we show the ellipsis for the intermediate pages
451-
(this.state.currentPage + 3) < (this.state.folders.length / pageSize)
474+
(this.state.currentPage + 3) <= lastPage
452475
&& <li>
453476
<button disabled>&#8230;</button>
454477
</li>
455478
}
456479
{
457480
// If more than one next page exist we show the last page as a button
458-
(this.state.currentPage + 2) < (this.state.folders.length / pageSize)
481+
(this.state.currentPage + 2) <= lastPage
459482
&& <li>
460-
<button onClick={() => this.goToPage(Math.floor(this.state.folders.length / pageSize))}>{Math.floor(this.state.folders.length / pageSize) + 1}</button>
483+
<button onClick={() => this.goToPage(lastPage)}>{lastPage + 1}</button>
461484
</li>
462485
}
463486
<li>
464487
<button
465488
aria-label={t('groupfolders', 'Next')}
466489
className="groupfolders-pagination__button"
467-
disabled={this.state.currentPage >= Math.floor(this.state.folders.length / pageSize)}
490+
disabled={this.state.currentPage >= lastPage || this.state.loading}
468491
title={t('groupfolders', 'Next')}
469492
onClick={() => this.goToPage(this.state.currentPage + 1)}></button>
470493
</li>
471494
</ul>
495+
{lastPage > 4 && (
496+
<div className="groupfolders-pagination__goto-page">
497+
<label style={{ whiteSpace: 'nowrap' }}>
498+
{t('groupfolders', 'Page:')}
499+
</label>
500+
<input
501+
type="number"
502+
min={1}
503+
max={lastPage + 1}
504+
style={{ width: 70, textAlign: 'center' }}
505+
value={this.state.currentPage + 1}
506+
onChange={(e) => {
507+
const page = Math.min(parseInt(e.target.value) - 1, lastPage)
508+
if (page >= 0) this.goToPage(page)
509+
}}
510+
/>
511+
<span style={{ whiteSpace: 'nowrap' }}>/ {lastPage + 1}</span>
512+
</div>
513+
)}
472514
</nav>
473515
</div>
474516
}

0 commit comments

Comments
 (0)