Enhance amendment wizard

- close button instead of back-button
- "are you sure" prompt if chances to the wizard were made
- edit and save events, like every other view
- enhanced next, previous, create logic that follows validation

also:
- fixed a bug with custom cancel events in the head-bar
- mobile button has the correct icon again
This commit is contained in:
Sean Engelhardt 2019-08-26 13:51:25 +02:00
parent b1c02133ee
commit af8b49450b
9 changed files with 107 additions and 34 deletions

View File

@ -15,20 +15,28 @@ export class RoutingStateService {
/**
* Hold the previous URL
*/
private previousUrl: string;
private _previousUrl: string;
/**
* Unsafe paths that the user should not go "back" to
* TODO: Might also work using Routing parameters
*/
private unsafeUrls: string[] = ['/login', '/privacypolicy', '/legalnotice'];
private unsafeUrls: string[] = ['/login', '/privacypolicy', '/legalnotice', '/new', '/create'];
/**
* Checks if the previous URL is safe to navigate to.
* If this fails, the open nav button should be shown
*/
public get isSafePrevUrl(): boolean {
return !this.previousUrl || !this.unsafeUrls.includes(this.previousUrl);
if (this._previousUrl) {
return !this.unsafeUrls.some(unsafeUrl => this._previousUrl.includes(unsafeUrl));
} else {
return false;
}
}
public get previousUrl(): string {
return this._previousUrl;
}
/**
@ -43,7 +51,7 @@ export class RoutingStateService {
pairwise()
)
.subscribe((event: any[]) => {
this.previousUrl = event[0].urlAfterRedirects;
this._previousUrl = event[0].urlAfterRedirects;
});
}

View File

@ -11,7 +11,7 @@
</button>
<!-- Cancel edit button -->
<button mat-icon-button *ngIf="editMode" (click)="cancelEditEvent ? sendCancelEditEvent() : sendMainEvent()">
<button mat-icon-button *ngIf="editMode" (click)="isCancelEditUsed ? sendCancelEditEvent() : sendMainEvent()">
<mat-icon>close</mat-icon>
</button>
@ -44,7 +44,7 @@
<!-- Save button -->
<button mat-button *ngIf="editMode" [disabled]="!isSaveButtonEnabled" (click)="save()">
<strong translate class="upper">Save</strong>
<strong translate class="upper">{{ saveText }}</strong>
</button>
<!-- Menu button slot -->
@ -61,5 +61,10 @@
(click)="sendMainEvent()"
matTooltip="{{ mainActionTooltip | translate }}"
>
<mat-icon>{{ mainButtonIcon }}</mat-icon>
<mat-icon *ngIf="mainButtonIcon === 'add_circle'; else mainIconBlock">
add
</mat-icon>
</button>
<ng-template #mainIconBlock>
{{ mainButtonIcon }}
</ng-template>

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MainMenuService } from 'app/core/core-services/main-menu.service';
@ -17,11 +17,14 @@ import { ViewportService } from 'app/core/ui-services/viewport.service';
* ```html
* <os-head-bar
* prevUrl="../.."
* saveText="Create"
* [nav]="false"
* [goBack]="true"
* [mainButton]="opCanEdit()"
* [mainButtonIcon]="edit"
* [backButtonIcon]="arrow_back"
* [editMode]="editMotion"
* [isSaveButtonEnabled]="myConditionIsTrue()"
* [multiSelectMode]="isMultiSelect"
* (mainEvent)="setEditMode(!editMotion)"
* (saveEvent)="saveMotion()">
@ -52,7 +55,7 @@ import { ViewportService } from 'app/core/ui-services/viewport.service';
templateUrl: './head-bar.component.html',
styleUrls: ['./head-bar.component.scss']
})
export class HeadBarComponent {
export class HeadBarComponent implements OnInit {
/**
* Determine if the the navigation "hamburger" icon should be displayed in mobile mode
*/
@ -65,6 +68,12 @@ export class HeadBarComponent {
@Input()
public mainButtonIcon = 'add_circle';
/**
* Custom text to show as "save"
*/
@Input()
public saveText = 'Save';
/**
* Determine edit mode
*/
@ -123,6 +132,11 @@ export class HeadBarComponent {
@Output()
public cancelEditEvent = new EventEmitter<void>();
/**
* To detect if the cancel event was used
*/
public isCancelEditUsed = false;
/**
* Sends a signal if a detail view should be saved
*/
@ -144,6 +158,13 @@ export class HeadBarComponent {
private routingState: RoutingStateService
) {}
/**
* Detect if the cancel edit event was used
*/
public ngOnInit(): void {
this.isCancelEditUsed = this.cancelEditEvent.observers.length > 0;
}
/**
* Emits a signal to the parent if
*/

View File

@ -1,15 +1,24 @@
<os-head-bar [nav]="false" [goBack]="false">
<os-head-bar
[nav]="false"
[editMode]="true"
saveText="Create"
[isSaveButtonEnabled]="matStepper.selectedIndex === 1"
(saveEvent)="saveAmendment()"
(cancelEditEvent)="cancelCreation()"
>
<!-- Title -->
<div class="title-slot"><h2 translate>New amendment</h2></div>
<div class="menu-slot">
<!-- Next-button -->
<div class="extra-controls-slot">
<div *ngIf="matStepper.selectedIndex === 0">
<button mat-button [disabled]="contentForm.value.selectedParagraph === null" (click)="matStepper.next()">
<button mat-button [disabled]="contentForm.value.selectedParagraphs.length === 0" (click)="matStepper.next()">
<span class="upper" translate>Next</span>
</button>
</div>
<div *ngIf="matStepper.selectedIndex === 1">
<button type="button" mat-button (click)="saveAmendment()">
<span class="upper" translate>Create</span>
<button type="button" mat-button (click)="matStepper.previous()">
<span class="upper" translate>Previous</span>
</button>
</div>
</div>

View File

@ -8,6 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
import { MotionRepositoryService, ParagraphToChoose } from 'app/core/repositories/motions/motion-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { BaseViewComponent } from 'app/site/base/base-view';
import { CreateMotion } from 'app/site/motions/models/create-motion';
import { ViewMotion } from 'app/site/motions/models/view-motion';
@ -54,14 +55,15 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
/**
* Constructs this component.
*
* @param {Title} titleService set the browser title
* @param {TranslateService} translate the translation service
* @param {ConfigService} configService The configuration provider
* @param {FormBuilder} formBuilder Form builder
* @param {MotionRepositoryService} repo Motion Repository
* @param {ActivatedRoute} route The activated route
* @param {Router} router The router
* @param {MatSnackBar} matSnackBar Material Design SnackBar
* @param titleService set the browser title
* @param translate the translation service
* @param configService The configuration provider
* @param formBuilder Form builder
* @param repo Motion Repository
* @param route The activated route
* @param router The router
* @param promptService Show a prompt by leaving the view
* @param matSnackBar Material Design SnackBar
*/
public constructor(
titleService: Title,
@ -71,6 +73,7 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
private repo: MotionRepositoryService,
private route: ActivatedRoute,
private router: Router,
private promptService: PromptService,
matSnackBar: MatSnackBar
) {
super(titleService, translate, matSnackBar);
@ -103,6 +106,21 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
});
}
/**
* Cancel the editing.
* Only fires when the form was dirty
*/
public async cancelCreation(): Promise<void> {
if (this.contentForm.dirty || this.contentForm.value.selectedParagraphs.length > 0) {
const title = this.translate.instant('Are you sure you want to discard this amendment?');
if (await this.promptService.open(title)) {
this.router.navigate(['..'], { relativeTo: this.route });
}
} else {
this.router.navigate(['..'], { relativeTo: this.route });
}
}
/**
* Creates the forms for the Motion and the MotionVersion
*/

View File

@ -1,8 +1,8 @@
<os-head-bar
[mainButton]="perms.isAllowed('can_create_amendments', motion)"
mainActionTooltip="New amendment"
prevUrl="../.."
[goBack]="motion && !!motion.parent_id"
[prevUrl]="getPrevUrl()"
[goBack]="routingStateService.isSafePrevUrl"
[nav]="false"
[editMode]="editMotion"
[isSaveButtonEnabled]="contentForm.valid"

View File

@ -26,6 +26,7 @@ import { DiffLinesInParagraph, DiffService, LineRange } from 'app/core/ui-servic
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
import { PersonalNoteService } from 'app/core/ui-services/personal-note.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { RoutingStateService } from 'app/core/ui-services/routing-state.service';
import { ViewportService } from 'app/core/ui-services/viewport.service';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Motion } from 'app/shared/models/motions/motion';
@ -445,7 +446,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
private blockRepo: MotionBlockRepositoryService,
private itemRepo: ItemRepositoryService,
private motionSortService: MotionSortListService,
private motionFilterService: MotionFilterListService
private motionFilterService: MotionFilterListService,
public routingStateService: RoutingStateService
) {
super(title, translate, matSnackBar);
}
@ -1548,15 +1550,17 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}
/**
* Tries to "logically" navigate back. If the motion has a parent, it will
* try to navigate to the parent
* rather than just into the list view.
* Tries to determine the previous URL if it's considered unsafe
*
* @returns the target to navigate to
*/
public getPrevUrl(): string {
if (this.motion && this.motion.parent_id) {
return `../../${this.motion.parent_id}`;
if (this.routingStateService.previousUrl && this.routingStateService.isSafePrevUrl) {
return this.routingStateService.previousUrl;
} else {
return this.motion.parent.getDetailStateURL();
}
}
return '../..';
}

View File

@ -214,7 +214,7 @@
<mat-menu #motionListMenu="matMenu">
<div *ngIf="!isMultiSelect">
<div *ngIf="perms.isAllowed('change_metadata')">
<div *ngIf="perms.isAllowed('change_metadata') && selectedView === 'list'">
<button mat-menu-item (click)="toggleMultiSelect()">
<mat-icon>library_add</mat-icon>
<span translate>Multiselect</span>
@ -265,7 +265,7 @@
</button>
</div>
<button mat-menu-item (click)="openExportDialog()">
<button mat-menu-item *ngIf="selectedView === 'list'" (click)="openExportDialog()">
<mat-icon>archive</mat-icon>
<span translate>Export</span>
</button>

View File

@ -31,6 +31,14 @@ import { MotionSortListService } from 'app/site/motions/services/motion-sort-lis
import { ViewTag } from 'app/site/tags/models/view-tag';
import { MotionExportDialogComponent } from '../../../shared-motion/motion-export-dialog/motion-export-dialog.component';
/**
* Determine the types of the motionList
*/
type MotionListviewType = 'tiles' | 'list';
/**
* Tile information
*/
interface TileCategoryInformation {
filter: string;
name: string;
@ -88,7 +96,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
/**
* String to define the current selected view.
*/
public selectedView: string;
public selectedView: MotionListviewType;
/**
* Columns to display in table when desktop view is available
@ -236,7 +244,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
this.categoryRepo.getViewModelListObservable().subscribe(cats => {
this.categories = cats;
if (cats.length > 0) {
this.storage.get<string>('motionListView').then(savedView => {
this.storage.get<string>('motionListView').then((savedView: MotionListviewType) => {
this.selectedView = savedView ? savedView : 'tiles';
});
} else {
@ -381,7 +389,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
*
* @param value is the new view the user has selected.
*/
public onChangeView(value: string): void {
public onChangeView(value: MotionListviewType): void {
this.selectedView = value;
this.storage.set('motionListView', value);
}