Merge pull request #4889 from jsangmeister/categories-vscroll
Implemented Virtual Scrolling for Categories
This commit is contained in:
commit
bcf495e49f
@ -57,7 +57,7 @@ export abstract class BaseListViewComponent<V extends BaseViewModel> extends Bas
|
|||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
protected storage?: StorageService
|
protected storage: StorageService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
this.selectedRows = [];
|
this.selectedRows = [];
|
||||||
|
@ -20,6 +20,7 @@ import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
|
|||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
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 { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { MediaManageService } from 'app/core/ui-services/media-manage.service';
|
import { MediaManageService } from 'app/core/ui-services/media-manage.service';
|
||||||
@ -185,6 +186,7 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
|
|||||||
titleService: Title,
|
titleService: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
|
storage: StorageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public repo: MediafileRepositoryService,
|
public repo: MediafileRepositoryService,
|
||||||
@ -199,7 +201,7 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
|
|||||||
private groupRepo: GroupRepositoryService,
|
private groupRepo: GroupRepositoryService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar, storage);
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
|
|
||||||
this.newDirectoryForm = this.formBuilder.group({
|
this.newDirectoryForm = this.formBuilder.group({
|
||||||
|
@ -12,10 +12,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<!-- Creating a new category -->
|
<os-list-view-table
|
||||||
<mat-card class="os-card" *ngIf="isCreatingNewCategory">
|
[repo]="repo"
|
||||||
<mat-card-title>New category</mat-card-title>
|
[allowProjector]="false"
|
||||||
<mat-card-content>
|
[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)">
|
<form [formGroup]="createForm" (keydown)="onKeyDown($event)">
|
||||||
<!-- Prefix -->
|
<!-- Prefix -->
|
||||||
<p>
|
<p>
|
||||||
@ -28,64 +61,19 @@
|
|||||||
<p>
|
<p>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input formControlName="name" matInput placeholder="{{ 'Name' | translate }}" required />
|
<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
|
A name is required
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
<!-- Save and Cancel buttons -->
|
<button mat-button [disabled]="!createForm.valid" [mat-dialog-close]="true">
|
||||||
<mat-card-actions>
|
|
||||||
<button mat-button [disabled]="!createForm.valid" (click)="onCreate()">
|
|
||||||
<span translate>Save</span>
|
<span translate>Save</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-button (click)="onCancel()">
|
<button mat-button [mat-dialog-close]="false">
|
||||||
<span translate>Cancel</span>
|
<span translate>Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
</mat-card-actions>
|
</div>
|
||||||
</mat-card>
|
</ng-template>
|
||||||
|
|
||||||
<!-- 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>
|
|
||||||
|
@ -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 { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
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 { 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';
|
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',
|
templateUrl: './category-list.component.html',
|
||||||
styleUrls: ['./category-list.component.scss']
|
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
|
* Holds the create form
|
||||||
*/
|
*/
|
||||||
public createForm: FormGroup;
|
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
|
* helper for permission checks
|
||||||
@ -59,11 +74,13 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
private repo: CategoryRepositoryService,
|
storage: StorageService,
|
||||||
|
public repo: CategoryRepositoryService,
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
|
private dialog: MatDialog,
|
||||||
private operator: OperatorService
|
private operator: OperatorService
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar, storage);
|
||||||
|
|
||||||
this.createForm = this.formBuilder.group({
|
this.createForm = this.formBuilder.group({
|
||||||
prefix: [''],
|
prefix: [''],
|
||||||
@ -77,74 +94,46 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
super.setTitle('Categories');
|
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
|
* Click handler for the plus button
|
||||||
*/
|
*/
|
||||||
public onPlusButton(): void {
|
public onPlusButton(): void {
|
||||||
if (!this.isCreatingNewCategory) {
|
this.createForm.reset();
|
||||||
this.createForm.reset();
|
const dialogRef = this.dialog.open(this.newCategoryDialog, infoDialogSettings);
|
||||||
this.isCreatingNewCategory = true;
|
dialogRef.afterClosed().subscribe(res => {
|
||||||
}
|
if (res) {
|
||||||
}
|
this.save();
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
// 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
|
* clicking Escape will cancel the process
|
||||||
*
|
*
|
||||||
* @param event has the code
|
* @param event has the code
|
||||||
*/
|
*/
|
||||||
public onKeyDown(event: KeyboardEvent): void {
|
public onKeyDown(event: KeyboardEvent): void {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
this.onCreate();
|
this.save();
|
||||||
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
if (event.key === 'Escape') {
|
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 {
|
public getMargin(category: ViewCategory): string {
|
||||||
return `${category.level * 20}px`;
|
return `${category.level * 20}px`;
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
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 { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { Tag } from 'app/shared/models/core/tag';
|
import { Tag } from 'app/shared/models/core/tag';
|
||||||
@ -68,13 +69,14 @@ export class TagListComponent extends BaseListViewComponent<ViewTag> implements
|
|||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
matSnackBar: MatSnackBar,
|
matSnackBar: MatSnackBar,
|
||||||
|
storage: StorageService,
|
||||||
public repo: TagRepositoryService,
|
public repo: TagRepositoryService,
|
||||||
protected translate: TranslateService, // protected required for ng-translate-extract
|
protected translate: TranslateService, // protected required for ng-translate-extract
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
private formBuilder: FormBuilder
|
private formBuilder: FormBuilder
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user