Add virtual scrolling for categories

StorageService is now required in BaseListView.
Adds a dialog to create categories
This commit is contained in:
jsangmeister 2019-08-13 15:43:54 +02:00 committed by Sean Engelhardt
parent 385e5bc7ab
commit d65751c5da
5 changed files with 97 additions and 116 deletions

View File

@ -57,7 +57,7 @@ export abstract class BaseListViewComponent<V extends BaseViewModel> extends Bas
titleService: Title,
translate: TranslateService,
matSnackBar: MatSnackBar,
protected storage?: StorageService
protected storage: StorageService
) {
super(titleService, translate, matSnackBar);
this.selectedRows = [];

View File

@ -20,6 +20,7 @@ import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { OperatorService } from 'app/core/core-services/operator.service';
import { StorageService } from 'app/core/core-services/storage.service';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { MediaManageService } from 'app/core/ui-services/media-manage.service';
@ -185,6 +186,7 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
titleService: Title,
protected translate: TranslateService,
matSnackBar: MatSnackBar,
storage: StorageService,
private route: ActivatedRoute,
private router: Router,
public repo: MediafileRepositoryService,
@ -199,7 +201,7 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
private groupRepo: GroupRepositoryService,
private cd: ChangeDetectorRef
) {
super(titleService, translate, matSnackBar);
super(titleService, translate, matSnackBar, storage);
this.canMultiSelect = true;
this.newDirectoryForm = this.formBuilder.group({

View File

@ -12,10 +12,43 @@
</div>
</os-head-bar>
<!-- Creating a new category -->
<mat-card class="os-card" *ngIf="isCreatingNewCategory">
<mat-card-title>New category</mat-card-title>
<mat-card-content>
<os-list-view-table
[repo]="repo"
[allowProjector]="false"
[columns]="tableColumnDefinition"
[filterProps]="filterProps"
listStorageKey="category"
(dataSourceChange)="onDataSourceChange($event)"
>
<!-- Title -->
<div *pblNgridCellDef="'title'; row as category; rowContext as rowContext" class="cell-slot fill">
<a
class="detail-link"
[routerLink]="category.id"
(click)="saveScrollIndex('category', rowContext.identity)"
></a>
<div [style.margin-left]="getMargin(category)">{{ category.prefixedName }}</div>
</div>
<!-- Amount -->
<div *pblNgridCellDef="'amount'; row as category" class="cell-slot fill">
<span class="os-amount-chip">{{ category.motions.length }}</span>
</div>
</os-list-view-table>
<mat-menu #categoryMenu="matMenu">
<button mat-menu-item [routerLink]="'./sort'">
<mat-icon>sort</mat-icon>
<span translate>Sort categories</span>
</button>
</mat-menu>
<!-- Template for new motion block dialog -->
<ng-template #newCategoryDialog>
<h1 mat-dialog-title>
<span translate>New category</span>
</h1>
<div class="os-form-card-mobile" mat-dialog-content>
<form [formGroup]="createForm" (keydown)="onKeyDown($event)">
<!-- Prefix -->
<p>
@ -28,64 +61,19 @@
<p>
<mat-form-field>
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
<mat-error *ngIf="!createForm.controls.name.valuid" translate>
<mat-error *ngIf="!createForm.controls.name.valid" translate>
A name is required
</mat-error>
</mat-form-field>
</p>
</form>
</mat-card-content>
<!-- Save and Cancel buttons -->
<mat-card-actions>
<button mat-button [disabled]="!createForm.valid" (click)="onCreate()">
</div>
<div mat-dialog-actions>
<button mat-button [disabled]="!createForm.valid" [mat-dialog-close]="true">
<span translate>Save</span>
</button>
<button mat-button (click)="onCancel()">
<button mat-button [mat-dialog-close]="false">
<span translate>Cancel</span>
</button>
</mat-card-actions>
</mat-card>
<!-- Table -->
<mat-card class="os-card">
<table class="os-headed-listview-table" mat-table [dataSource]="dataSource">
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef>
<span translate>Title</span>
</mat-header-cell>
<mat-cell *matCellDef="let category">
<div [style.margin-left]="getMargin(category)">{{ category.prefixedName }}</div>
</mat-cell>
</ng-container>
<!-- amount column -->
<ng-container matColumnDef="amount">
<mat-header-cell *matHeaderCellDef>
<span translate>Motions</span>
</mat-header-cell>
<mat-cell *matCellDef="let category">
<span class="os-amount-chip">{{ category.motions.length }}</span>
</mat-cell>
</ng-container>
<!-- Anchor column to open the separate tab -->
<ng-container matColumnDef="anchor">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let category">
<a [routerLink]="category.id"></a>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="getColumnDefinition()"></mat-header-row>
<mat-row *matRowDef="let row; columns: getColumnDefinition()"> </mat-row>
</table>
</mat-card>
<mat-menu #categoryMenu="matMenu">
<button mat-menu-item [routerLink]="'./sort'">
<mat-icon>sort</mat-icon>
<span translate>Sort categories</span>
</button>
</mat-menu>
</div>
</ng-template>

View File

@ -1,14 +1,17 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog } from '@angular/material';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatTableDataSource } from '@angular/material/table';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { PblColumnDefinition } from '@pebula/ngrid';
import { OperatorService } from 'app/core/core-services/operator.service';
import { StorageService } from 'app/core/core-services/storage.service';
import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service';
import { BaseViewComponent } from 'app/site/base/base-view';
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
import { BaseListViewComponent } from 'app/site/base/base-list-view';
import { ViewCategory } from 'app/site/motions/models/view-category';
/**
@ -19,21 +22,33 @@ import { ViewCategory } from 'app/site/motions/models/view-category';
templateUrl: './category-list.component.html',
styleUrls: ['./category-list.component.scss']
})
export class CategoryListComponent extends BaseViewComponent implements OnInit {
export class CategoryListComponent extends BaseListViewComponent<ViewCategory> implements OnInit {
@ViewChild('newCategoryDialog', { static: true })
private newCategoryDialog: TemplateRef<string>;
/**
* Holds the create form
*/
public createForm: FormGroup;
/**
* Table data Source
* Define the columns to show
*/
public dataSource: MatTableDataSource<ViewCategory>;
public tableColumnDefinition: PblColumnDefinition[] = [
{
prop: 'title',
width: 'auto'
},
{
prop: 'amount',
width: this.singleButtonWidth
}
];
/**
* Flag, if the creation panel is open
* Define extra filter properties
*/
public isCreatingNewCategory = false;
public filterProps = ['prefixedName'];
/**
* helper for permission checks
@ -59,11 +74,13 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
titleService: Title,
translate: TranslateService,
matSnackBar: MatSnackBar,
private repo: CategoryRepositoryService,
storage: StorageService,
public repo: CategoryRepositoryService,
private formBuilder: FormBuilder,
private dialog: MatDialog,
private operator: OperatorService
) {
super(titleService, translate, matSnackBar);
super(titleService, translate, matSnackBar, storage);
this.createForm = this.formBuilder.group({
prefix: [''],
@ -77,74 +94,46 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
*/
public ngOnInit(): void {
super.setTitle('Categories');
this.dataSource = new MatTableDataSource();
this.repo.getViewModelListObservable().subscribe(viewCategories => {
if (viewCategories && this.dataSource) {
this.dataSource.data = viewCategories;
}
});
}
/**
* Returns the columns that should be shown in the table
*
* @returns an array of strings building the column definition
*/
public getColumnDefinition(): string[] {
return ['title', 'amount', 'anchor'];
}
/**
* Click handler for the plus button
*/
public onPlusButton(): void {
if (!this.isCreatingNewCategory) {
this.createForm.reset();
this.isCreatingNewCategory = true;
}
}
/**
* Click handler for the save button.
* Sends the category to create to the repository and resets the form.
*/
public onCreate(): void {
if (this.createForm.valid) {
try {
this.repo.create(this.createForm.value);
this.createForm.reset();
this.isCreatingNewCategory = false;
} catch (e) {
this.raiseError(e);
this.createForm.reset();
const dialogRef = this.dialog.open(this.newCategoryDialog, infoDialogSettings);
dialogRef.afterClosed().subscribe(res => {
if (res) {
this.save();
}
}
// set a form control as "touched" to trigger potential error messages
this.createForm.get('name').markAsTouched();
});
}
/**
* clicking Shift and Enter will save automatically
* Sends the category to create to the repository.
*/
private save(): void {
if (this.createForm.valid) {
this.repo.create(this.createForm.value).catch(this.raiseError);
}
}
/**
* clicking Enter will save automatically
* clicking Escape will cancel the process
*
* @param event has the code
*/
public onKeyDown(event: KeyboardEvent): void {
if (event.key === 'Enter') {
this.onCreate();
this.save();
this.dialog.closeAll();
}
if (event.key === 'Escape') {
this.onCancel();
this.dialog.closeAll();
}
}
/**
* Cancels the current form action
*/
public onCancel(): void {
this.isCreatingNewCategory = false;
}
public getMargin(category: ViewCategory): string {
return `${category.level * 20}px`;
}

View File

@ -7,6 +7,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { PblColumnDefinition } from '@pebula/ngrid';
import { StorageService } from 'app/core/core-services/storage.service';
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { Tag } from 'app/shared/models/core/tag';
@ -68,13 +69,14 @@ export class TagListComponent extends BaseListViewComponent<ViewTag> implements
public constructor(
titleService: Title,
matSnackBar: MatSnackBar,
storage: StorageService,
public repo: TagRepositoryService,
protected translate: TranslateService, // protected required for ng-translate-extract
private promptService: PromptService,
private dialog: MatDialog,
private formBuilder: FormBuilder
) {
super(titleService, translate, matSnackBar);
super(titleService, translate, matSnackBar, storage);
}
/**