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:
parent
b1c02133ee
commit
af8b49450b
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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"
|
||||
|
@ -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 '../..';
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user