add lazy loaded userlist to userpanel

This commit is contained in:
rubikscraft 2022-03-19 23:08:20 +01:00
parent 509dda78ea
commit 26c3918bcc
No known key found for this signature in database
GPG key ID: 1463EBE9200A5CD4
11 changed files with 611 additions and 426 deletions

View file

@ -19,19 +19,19 @@
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix" "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^8.4.0", "@nestjs/common": "^8.4.1",
"@nestjs/config": "^1.2.0", "@nestjs/config": "^2.0.0",
"@nestjs/core": "^8.4.0", "@nestjs/core": "^8.4.1",
"@nestjs/jwt": "^8.0.0", "@nestjs/jwt": "^8.0.0",
"@nestjs/passport": "^8.2.1", "@nestjs/passport": "^8.2.1",
"@nestjs/platform-fastify": "^8.4.0", "@nestjs/platform-fastify": "^8.4.1",
"@nestjs/serve-static": "^2.2.2", "@nestjs/serve-static": "^2.2.2",
"@nestjs/typeorm": "^8.0.3", "@nestjs/typeorm": "^8.0.3",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.13.2", "class-validator": "^0.13.2",
"fastify-multipart": "^5.3.1", "fastify-multipart": "^5.3.1",
"fastify-static": "^4.5.0", "fastify-static": "^4.6.1",
"file-type": "^17.1.1", "file-type": "^17.1.1",
"passport": "^0.5.2", "passport": "^0.5.2",
"passport-jwt": "^4.0.0", "passport-jwt": "^4.0.0",
@ -42,12 +42,12 @@
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^7.5.5", "rxjs": "^7.5.5",
"typeorm": "^0.2.45" "typeorm": "0.2.45"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^8.2.2", "@nestjs/cli": "^8.2.3",
"@nestjs/schematics": "^8.0.8", "@nestjs/schematics": "^8.0.8",
"@nestjs/testing": "^8.4.0", "@nestjs/testing": "^8.4.1",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
@ -55,16 +55,16 @@
"@types/passport-local": "^1.0.34", "@types/passport-local": "^1.0.34",
"@types/passport-strategy": "^0.2.35", "@types/passport-strategy": "^0.2.35",
"@types/supertest": "^2.0.11", "@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.14.0", "@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.14.0", "@typescript-eslint/parser": "^5.15.0",
"eslint": "^8.11.0", "eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"prettier": "^2.5.1", "prettier": "^2.6.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"ts-node": "^10.7.0", "ts-node": "^10.7.0",
"tsconfig-paths": "^3.13.0", "tsconfig-paths": "^3.14.0",
"typescript": "4.5.5", "typescript": "4.5.5",
"webpack": "^5.70.0" "webpack": "^5.70.0"
} }

View file

@ -1,17 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { plainToClass } from 'class-transformer';
import Crypto from 'crypto'; import Crypto from 'crypto';
import { SupportedMime } from 'picsur-shared/dist/dto/mimes.dto';
import { import {
AsyncFailable, AsyncFailable,
Fail, Fail,
HasFailed, HasFailed,
HasSuccess, HasSuccess
} from 'picsur-shared/dist/types'; } from 'picsur-shared/dist/types';
import { SupportedMime } from 'picsur-shared/dist/dto/mimes.dto'; import { Repository } from 'typeorm';
import { GetCols } from '../collectionutils';
import { plainToClass } from 'class-transformer';
import { EImageBackend } from '../../models/entities/image.entity'; import { EImageBackend } from '../../models/entities/image.entity';
import { GetCols } from '../collectionutils';
@Injectable() @Injectable()
export class ImageDBService { export class ImageDBService {

View file

@ -1,5 +1,8 @@
{ {
"$schema": "../node_modules/@angular/cli/lib/config/schema.json", "$schema": "../node_modules/@angular/cli/lib/config/schema.json",
"cli": {
"analytics": false
},
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {

View file

@ -39,7 +39,7 @@
"@angular-devkit/build-angular": "14.0.0-next.4", "@angular-devkit/build-angular": "14.0.0-next.4",
"@angular/cli": "^14.0.0-next.4", "@angular/cli": "^14.0.0-next.4",
"@angular/compiler-cli": "^14.0.0-next.5", "@angular/compiler-cli": "^14.0.0-next.5",
"@types/jasmine": "~3.10.3", "@types/jasmine": "~4.0.0",
"@types/node": "^17.0.21", "@types/node": "^17.0.21",
"@types/validator": "^13.7.1", "@types/validator": "^13.7.1",
"jasmine-core": "~4.0.1", "jasmine-core": "~4.0.1",

View file

@ -6,7 +6,7 @@ import {
SysPrefValueType SysPrefValueType
} from 'picsur-shared/dist/dto/syspreferences.dto'; } from 'picsur-shared/dist/dto/syspreferences.dto';
import { HasFailed } from 'picsur-shared/dist/types'; import { HasFailed } from 'picsur-shared/dist/types';
import { debounceTime, Subject } from 'rxjs'; import { Subject, throttleTime } from 'rxjs';
import { SnackBarType } from 'src/app/models/snack-bar-type'; import { SnackBarType } from 'src/app/models/snack-bar-type';
import { SysprefService } from 'src/app/services/api/syspref.service'; import { SysprefService } from 'src/app/services/api/syspref.service';
import { UtilService } from 'src/app/util/util.service'; import { UtilService } from 'src/app/util/util.service';
@ -70,7 +70,7 @@ export class SettingsSysprefOptionComponent implements OnInit {
@AutoUnsubscribe() @AutoUnsubscribe()
subscribeUpdate() { subscribeUpdate() {
return this.updateSubject return this.updateSubject
.pipe(debounceTime(300)) .pipe(throttleTime(300, undefined, { leading: true, trailing: true }))
.subscribe(async (value) => { .subscribe(async (value) => {
const result = await this.sysprefService.setPreference( const result = await this.sysprefService.setPreference(
this.pref.key, this.pref.key,

View file

@ -1,2 +1,26 @@
<h1>Users</h1> <h1>Users</h1>
<table mat-table [dataSource]="dataSubject">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let user">{{ user.id }}</td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef>Username</th>
<td mat-cell *matCellDef="let user">{{ user.username }}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
<mat-paginator
color="accent"
[pageSizeOptions]="pageSizeOptions"
[pageSize]="startingPageSize"
length="Infinity"
aria-label="Select page of periodic elements"
(page)="updateSubject.next($event)"
>
</mat-paginator>

View file

@ -0,0 +1,3 @@
table {
width: 100%;
}

View file

@ -1,12 +1,60 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { HasFailed } from 'picsur-shared/dist/types';
import { BehaviorSubject, Subject, throttleTime } from 'rxjs';
import { UserManageService } from 'src/app/services/api/usermanage.service';
@Component({ @Component({
templateUrl: './settings-users.component.html', templateUrl: './settings-users.component.html',
styleUrls: ['./settings-users.component.scss'],
}) })
export class SettingsUsersComponent implements OnInit { export class SettingsUsersComponent implements OnInit {
constructor() {} public readonly displayedColumns: string[] = ['id', 'username'];
public readonly pageSizeOptions: number[] = [5, 10, 25, 100];
public readonly startingPageSize = this.pageSizeOptions[2];
ngOnInit(): void { public dataSubject = new BehaviorSubject<EUser[]>([]);
public updateSubject = new Subject<PageEvent>();
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor(private userManageService: UserManageService) {}
async ngOnInit() {
this.subscribeToUpdate();
this.fetchUsers(this.startingPageSize, 0);
} }
@AutoUnsubscribe()
private subscribeToUpdate() {
return this.updateSubject
.pipe(throttleTime(500, undefined, { leading: true, trailing: true }))
.subscribe(async (pageEvent: PageEvent) => {
let amount = await this.fetchUsers(
pageEvent.pageSize,
pageEvent.pageIndex
);
if (amount === 0) {
if ( pageEvent.previousPageIndex === pageEvent.pageIndex - 1){
this.paginator.previousPage();
} else {
this.paginator.firstPage();
}
}
});
}
private async fetchUsers(
pageSize: number,
pageIndex: number
): Promise<number> {
const result = await this.userManageService.getUsers(pageSize, pageIndex);
if (HasFailed(result)) return 0;
this.dataSubject.next(result);
return result.length;
}
} }

View file

@ -1,13 +1,16 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatTableModule } from '@angular/material/table';
import { SettingsUsersComponent } from './settings-users.component'; import { SettingsUsersComponent } from './settings-users.component';
import { SettingsUsersRoutingModule } from './settings-users.routing.module'; import { SettingsUsersRoutingModule } from './settings-users.routing.module';
@NgModule({ @NgModule({
declarations: [SettingsUsersComponent], declarations: [SettingsUsersComponent],
imports: [ imports: [
CommonModule, CommonModule,
SettingsUsersRoutingModule, SettingsUsersRoutingModule,
MatTableModule,
MatPaginatorModule,
], ],
}) })
export class SettingsUsersRouteModule {} export class SettingsUsersRouteModule {}

View file

@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import {
UserListRequest,
UserListResponse
} from 'picsur-shared/dist/dto/api/usermanage.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
import { ApiService } from './api.service';
@Injectable({
providedIn: 'root',
})
export class UserManageService {
constructor(private apiService: ApiService) {}
public async getUsers(count: number, page: number): AsyncFailable<EUser[]> {
const body = {
count,
page,
};
const result = await this.apiService.post(
UserListRequest,
UserListResponse,
'/api/user/list',
body
);
if (HasFailed(result)) {
return result;
}
return result.users;
}
}

873
yarn.lock

File diff suppressed because it is too large Load diff