Merge pull request #4653 from GabrielInTheWorld/sorting

Save motion category weight, handle auto update in categories
This commit is contained in:
Sean 2019-04-30 16:18:48 +02:00 committed by GitHub
commit addc0cc3cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 135 additions and 9 deletions

View File

@ -2,10 +2,11 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { CategoryListComponent } from './components/category-list/category-list.component'; import { CategoryListComponent } from './components/category-list/category-list.component';
import { CategorySortComponent } from './components/category-sort/category-sort.component'; import { CategorySortComponent } from './components/category-sort/category-sort.component';
import { WatchSortingTreeGuard } from 'app/shared/utils/watch-sorting-tree.guard';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: CategoryListComponent, pathMatch: 'full' }, { path: '', component: CategoryListComponent, pathMatch: 'full' },
{ path: ':id', component: CategorySortComponent } { path: ':id', component: CategorySortComponent, canDeactivate: [WatchSortingTreeGuard] }
]; ];
@NgModule({ @NgModule({

View File

@ -1,5 +1,9 @@
<!-- TODO permission --> <!-- TODO permission -->
<os-head-bar [nav]="false"> <os-head-bar
[editMode]="hasChanged"
(saveEvent)="sendUpdate()"
(mainEvent)="onCancel()"
[nav]="false">
<!-- Title --> <!-- Title -->
<div class="title-slot"><h2 translate>Sort motions</h2></div> <div class="title-slot"><h2 translate>Sort motions</h2></div>
</os-head-bar> </os-head-bar>
@ -15,20 +19,26 @@
mat-raised-button mat-raised-button
color="primary" color="primary"
(click)="onNumberMotions()" (click)="onNumberMotions()"
class="spacer-top-10" class="spacer-top-10 spacer-bottom-10"
[disabled]="!motionsCount" [disabled]="!motionsCount || hasChanged"
> >
<span translate>Number motions</span> <span translate>Number motions</span>
</button> </button>
<os-sorting-list [input]="motionObservable" #sorter>
<os-sorting-list
(sortEvent)="onListUpdate($event)"
[input]="motionObservable"
#sorter
>
<!-- implicit motion references into the component using ng-template slot --> <!-- implicit motion references into the component using ng-template slot -->
<ng-template let-motion> <ng-template let-motion>
<div class="ellipsis-overflow small" *ngIf="motion.tags && motion.tags.length"> <span class="ellipsis-overflow small" *ngIf="motion.tags && motion.tags.length">
<span *ngFor="let tag of motion.tags"> <span *ngFor="let tag of motion.tags">
<mat-icon inline>local_offer</mat-icon> <mat-icon inline>local_offer</mat-icon>
{{ tag.getTitle() }} {{ tag.getTitle() }}
</span> </span>
</div> </span>
<mat-chip matTooltip="{{ 'Sequential number' | translate }}">{{ motion.id }}</mat-chip>
</ng-template> </ng-template>
</os-sorting-list> </os-sorting-list>
</mat-card> </mat-card>

View File

@ -12,6 +12,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
import { SortingListComponent } from 'app/shared/components/sorting-list/sorting-list.component'; import { SortingListComponent } from 'app/shared/components/sorting-list/sorting-list.component';
import { ViewCategory } from 'app/site/motions/models/view-category'; import { ViewCategory } from 'app/site/motions/models/view-category';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { CanComponentDeactivate } from 'app/shared/utils/watch-sorting-tree.guard';
/** /**
* View for rearranging and renumbering the motions of a category. The {@link onNumberMotions} * View for rearranging and renumbering the motions of a category. The {@link onNumberMotions}
@ -23,7 +24,7 @@ import { ViewMotion } from 'app/site/motions/models/view-motion';
templateUrl: './category-sort.component.html', templateUrl: './category-sort.component.html',
styleUrls: ['./category-sort.component.scss'] styleUrls: ['./category-sort.component.scss']
}) })
export class CategorySortComponent extends BaseViewComponent implements OnInit { export class CategorySortComponent extends BaseViewComponent implements OnInit, CanComponentDeactivate {
/** /**
* The current category. Determined by the route * The current category. Determined by the route
*/ */
@ -39,6 +40,22 @@ export class CategorySortComponent extends BaseViewComponent implements OnInit {
*/ */
public motionsCount = 0; public motionsCount = 0;
/**
* Flag to define if the list has changed.
*/
public hasChanged = false;
/**
* Copied array of the motions in this category
*/
private motionsCopy: ViewMotion[] = [];
/**
* Array that contains the initial list of motions.
* Necessary to reset the list.
*/
private motionsBackup: ViewMotion[] = [];
/** /**
* @returns an observable for the {@link motionsSubject} * @returns an observable for the {@link motionsSubject}
*/ */
@ -95,11 +112,27 @@ export class CategorySortComponent extends BaseViewComponent implements OnInit {
}); });
this.motionRepo.getViewModelListObservable().subscribe(motions => { this.motionRepo.getViewModelListObservable().subscribe(motions => {
const filtered = motions.filter(m => m.category_id === category_id); const filtered = motions.filter(m => m.category_id === category_id);
this.motionsBackup = [...filtered];
this.motionsCount = filtered.length; this.motionsCount = filtered.length;
this.motionsSubject.next(filtered); if (this.motionsCopy.length === 0) {
this.initializeList(filtered);
} else {
this.motionsSubject.next(this.handleMotionUpdates(filtered));
}
}); });
} }
/**
* Function to (re-)set the current list of motions.
*
* @param motions An array containing the new motions.
*/
private initializeList(motions: ViewMotion[]): void {
motions.sort((a, b) => (a.category_weight < b.category_weight ? -1 : 1));
this.motionsSubject.next(motions);
this.motionsCopy = motions;
}
/** /**
* Triggers a (re-)numbering of the motions after a configmarion dialog * Triggers a (re-)numbering of the motions after a configmarion dialog
* *
@ -117,4 +150,86 @@ export class CategorySortComponent extends BaseViewComponent implements OnInit {
} }
} }
} }
/**
* Listener for the sorting event in the `sorting-list`.
*
* @param motions ViewMotion[]: The sorted array of motions.
*/
public onListUpdate(motions: ViewMotion[]): void {
this.hasChanged = true;
this.motionsCopy = motions;
}
/**
* Resets the current list.
*/
public async onCancel(): Promise<void> {
if (await this.canDeactivate()) {
this.motionsSubject.next([]);
this.initializeList(this.motionsBackup);
this.hasChanged = false;
}
}
/**
* This function sends the changed list.
* Only an array containing ids from the motions will be sent.
*/
public async sendUpdate(): Promise<void> {
const title = this.translate.instant('Save changes');
const content = this.translate.instant('Do you really want to save your changes?');
if (await this.promptService.open(title, content)) {
const ids = this.motionsCopy.map(motion => motion.id);
this.repo.sortMotionsInCategory(this.category.category, ids);
this.hasChanged = false;
}
}
/**
* This function handles the incoming motions after the user sorted them previously.
*
* @param nextMotions are the motions that are received from the server.
*
* @returns An array containing the new motions or not the removed motions.
*/
private handleMotionUpdates(nextMotions: ViewMotion[]): ViewMotion[] {
const copy = this.motionsCopy;
if (nextMotions.length > copy.length) {
for (const motion of nextMotions) {
if (!this.motionsCopy.includes(motion)) {
copy.push(motion);
}
}
} else if (nextMotions.length < copy.length) {
for (const motion of copy) {
if (!nextMotions.includes(motion)) {
copy.splice(copy.indexOf(motion), 1);
}
}
} else {
for (const motion of copy) {
if (!nextMotions.includes(motion)) {
const updatedMotion = nextMotions.find(theMotion => theMotion.id === motion.id);
copy.splice(copy.indexOf(motion), 1, updatedMotion);
}
}
}
return copy;
}
/**
* Function to open a prompt dialog,
* so the user will be warned if he has made changes and not saved them.
*
* @returns The result from the prompt dialog.
*/
public async canDeactivate(): Promise<boolean> {
if (this.hasChanged) {
const title = this.translate.instant('You made changes.');
const content = this.translate.instant('Do you really want to exit?');
return await this.promptService.open(title, content);
}
return true;
}
} }