Skip to content

Commit 383a9c8

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 383a9c8

File tree

5 files changed

+98
-22
lines changed

5 files changed

+98
-22
lines changed

lib/Controller/FolderController.php

Lines changed: 8 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,13 @@ public function aclMappingSearch(int $id, string $search = ''): DataResponse {
555555
]);
556556
}
557557

558+
#[RequireGroupFolderAdmin]
559+
#[NoAdminRequired]
560+
#[FrontpageRoute(verb: 'GET', url: '/folders/count')]
561+
public function getFoldersCount(): DataResponse {
562+
return new DataResponse(['count' => $this->manager->countAllFolders()]);
563+
}
564+
558565
private function compareFolderNames(string $a, string $b): int {
559566
if (($value = strnatcmp($a, $b)) === 0) {
560567
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
}

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)