Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lib/Controller/FolderController.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public function getFolders(bool $applicable = false, int $offset = 0, ?int $limi
* 200: Groupfolder returned
*/
#[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/folders/{id}')]
#[FrontpageRoute(verb: 'GET', url: '/folders/{id}', requirements: ['id' => '\d+'])]
public function getFolder(int $id): DataResponse {
$storageId = $this->getRootFolderStorageId();
if ($storageId === null) {
Expand Down Expand Up @@ -555,6 +555,20 @@ public function aclMappingSearch(int $id, string $search = ''): DataResponse {
]);
}

/**
* Gets the total number of Groupfolders
*
* @return DataResponse<Http::STATUS_OK, array{count: int}, array{}>
*
* 200: Groupfolder count returned
*/
#[RequireGroupFolderAdmin]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'GET', url: '/folders/count')]
public function getFoldersCount(): DataResponse {
return new DataResponse(['count' => $this->manager->countAllFolders()]);
}

private function compareFolderNames(string $a, string $b): int {
if (($value = strnatcmp($a, $b)) === 0) {
return $value;
Expand Down
7 changes: 7 additions & 0 deletions lib/Folder/FolderManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -1162,4 +1162,11 @@ public function hasFolderACLDefaultNoPermission(int $folderId): bool {

return $hasDefaultNoPermission;
}

public function countAllFolders(): int {
$query = $this->connection->getQueryBuilder();
$query->select($query->func()->count('folder_id'))
->from('group_folders');
return (int)$query->executeQuery()->fetchOne();
}
}
98 changes: 98 additions & 0 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2702,6 +2702,104 @@
}
}
}
},
"/index.php/apps/groupfolders/folders/count": {
"get": {
"operationId": "folder-get-folders-count",
"summary": "Gets the total number of Groupfolders",
"tags": [
"folder"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Groupfolder count returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "object",
"required": [
"count"
],
"properties": {
"count": {
"type": "integer",
"format": "int64"
}
}
}
}
}
}
}
}
}
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
}
}
},
"tags": []
Expand Down
5 changes: 5 additions & 0 deletions src/settings/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,9 @@ export class Api {
}
}

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

}
33 changes: 24 additions & 9 deletions src/settings/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,30 @@
}
}

.groupfolders-pagination__list {
display: flex;
gap: var(--default-grid-baseline);
justify-content: center;
}

.groupfolders-pagination__button {
height: var(--default-clickable-area);
}
.groupfolders-pagination {
display: flex;
align-items: center;

.groupfolders-pagination__list {
display: flex;
gap: var(--default-grid-baseline);
justify-content: center;
}

.groupfolders-pagination__button {
height: var(--default-clickable-area);
line-height: var(--default-clickable-area);
}

.groupfolders-pagination__goto-page {
flex: 1;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 6px;
}

}

}

Expand Down
66 changes: 54 additions & 12 deletions src/settings/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ export interface AppState {
isAdminNextcloud: boolean;
checkAppsInstalled: boolean;
currentPage: number;
loading: boolean;
totalFolders: number;
}

export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Search.Core> {
Expand All @@ -80,6 +82,8 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
isAdminNextcloud: false,
checkAppsInstalled: false,
currentPage: 0,
loading: false,
totalFolders: 0,
}

componentDidMount() {
Expand All @@ -93,6 +97,9 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
this.api.listCircles().then((circles) => {
this.setState({ circles })
})
this.api.countFolders().then((totalFolders) => {
this.setState({ totalFolders })
})

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

async goToPage(page: number) {
if (this.state.loading) return

const loadedPage = Math.floor(this.state.folders.length / pageSize)
if (loadedPage <= page) {
const folders = await this.api.listFolders(this.state.folders.length, (page + 1) * pageSize - this.state.folders.length + 1, this.state.sort)
this.setState({
folders: [...this.state.folders, ...folders],
currentPage: page,
})
this.setState({ loading: true })
try {
const folders = await this.api.listFolders(
this.state.folders.length, (page + 1) * pageSize - this.state.folders.length + 1,
this.state.sort,
this.state.sortOrder === 1 ? 'asc' : 'desc',
)
this.setState({
folders: [...this.state.folders, ...folders],
currentPage: page,
})
} finally {
this.setState({ loading: false })
}

} else {
this.setState({
currentPage: page,
Expand Down Expand Up @@ -266,6 +285,7 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se

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

console.log('totalFolders:', this.state.totalFolders, 'lastPage:', lastPage, 'currentPage:', this.state.currentPage)

const rows
= this.state.folders
.filter(folder => {
Expand Down Expand Up @@ -416,13 +438,14 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
</tr>
</FlipMove>
</table>
<nav className="groupfolders-pagination" aria-label={t('groupfolders', 'Pagination of team folders')}>
<nav className="groupfolders-pagination" style={{ display: 'flex', alignItems: 'center' }} aria-label={t('groupfolders', 'Pagination of team folders')}>
<div style={{ flex: 1 }} />
<ul className="groupfolders-pagination__list">
<li>
<button
aria-label={t('groupfolders', 'Previous')}
className="groupfolders-pagination__button"
disabled={this.state.currentPage === 0}
disabled={this.state.currentPage === 0 || this.state.loading}
title={t('groupfolders', 'Previous')}
onClick={() => this.goToPage(this.state.currentPage - 1)}>⮜</button>
</li>
Expand All @@ -441,34 +464,53 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
<li><button aria-current="page" aria-disabled className="primary">{this.state.currentPage + 1}</button></li>
{
// show the next page if it exists (we know at least that the next exists or not)
(this.state.currentPage + 1) < (this.state.folders.length / pageSize)
(this.state.currentPage + 1) <= lastPage
&& <li>
<button onClick={() => this.goToPage(this.state.currentPage + 1)}>{this.state.currentPage + 2}</button>
</li>
}
{
// If we know more than two next pages exist we show the ellipsis for the intermediate pages
(this.state.currentPage + 3) < (this.state.folders.length / pageSize)
(this.state.currentPage + 3) <= lastPage
&& <li>
<button disabled>&#8230;</button>
</li>
}
{
// If more than one next page exist we show the last page as a button
(this.state.currentPage + 2) < (this.state.folders.length / pageSize)
(this.state.currentPage + 2) <= lastPage
&& <li>
<button onClick={() => this.goToPage(Math.floor(this.state.folders.length / pageSize))}>{Math.floor(this.state.folders.length / pageSize) + 1}</button>
<button onClick={() => this.goToPage(lastPage)}>{lastPage + 1}</button>
</li>
}
<li>
<button
aria-label={t('groupfolders', 'Next')}
className="groupfolders-pagination__button"
disabled={this.state.currentPage >= Math.floor(this.state.folders.length / pageSize)}
disabled={this.state.currentPage >= lastPage || this.state.loading}
title={t('groupfolders', 'Next')}
onClick={() => this.goToPage(this.state.currentPage + 1)}>⮞</button>
</li>
</ul>
{lastPage > 4 && (
<div className="groupfolders-pagination__goto-page">
<label style={{ whiteSpace: 'nowrap' }}>
{t('groupfolders', 'Page:')}
</label>
<input
type="number"
min={1}
max={lastPage + 1}
style={{ width: 70, textAlign: 'center' }}
value={this.state.currentPage + 1}
onChange={(e) => {
const page = Math.min(parseInt(e.target.value) - 1, lastPage)
if (page >= 0) this.goToPage(page)
}}
/>
<span style={{ whiteSpace: 'nowrap' }}>/ {lastPage + 1}</span>
</div>
)}
</nav>
</div>
}
Expand Down
Loading
Loading