From 788beaae2a995aa80e55efa2562c4f099dcd6d1d Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Fri, 2 Nov 2018 11:41:03 +0100 Subject: [PATCH] error handling --- client/src/app/core/services/http.service.ts | 64 ++++++++++++++-- .../agenda-list/agenda-list.component.ts | 7 +- .../assignment-list.component.ts | 13 +++- client/src/app/site/base/base-view.ts | 48 ++++++++++++ client/src/app/site/base/list-view-base.ts | 11 +-- .../config-field/config-field.component.ts | 10 +-- .../login-mask/login-mask.component.ts | 25 +++---- .../mediafile-list.component.ts | 10 ++- .../category-list/category-list.component.ts | 38 ++++++---- ...otion-change-recommendation.component.html | 4 +- .../motion-change-recommendation.component.ts | 30 +++++--- ...motion-comment-section-list.component.html | 2 +- .../motion-comment-section-list.component.ts | 40 ++++++---- .../motion-detail-diff.component.ts | 47 ++++++++---- .../motion-detail/motion-detail.component.ts | 57 +++++++------- .../motion-list/motion-list.component.html | 8 +- .../motion-list/motion-list.component.ts | 4 +- .../statute-paragraph-list.component.ts | 41 ++++++---- ...hange-recommendation-repository.service.ts | 11 +-- .../components/tag-list/tag-list.component.ts | 33 ++++++--- .../group-list/group-list.component.ts | 66 +++++++++++------ .../user-detail/user-detail.component.ts | 74 +++++++++++++------ .../user-list/user-list.component.ts | 8 +- 23 files changed, 445 insertions(+), 206 deletions(-) create mode 100644 client/src/app/site/base/base-view.ts diff --git a/client/src/app/core/services/http.service.ts b/client/src/app/core/services/http.service.ts index 3dfed62de..107d9140e 100644 --- a/client/src/app/core/services/http.service.ts +++ b/client/src/app/core/services/http.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { TranslateService } from '@ngx-translate/core'; /** * Enum for different HTTPMethods @@ -18,14 +19,14 @@ export enum HTTPMethod { @Injectable({ providedIn: 'root' }) - export class HttpService { /** * Construct a HttpService * * @param http The HTTP Client + * @param translate */ - public constructor(private http: HttpClient) {} + public constructor(private http: HttpClient, private translate: TranslateService) {} private async send(url: string, method: HTTPMethod, data?: any): Promise { if (!url.endsWith('/')) { @@ -33,15 +34,66 @@ export class HttpService { } const options = { - body: data, + body: data }; try { const response = await this.http.request(method, url, options).toPromise(); return response; } catch (e) { - console.log("error", e); - throw e; + throw this.handleError(e); + } + } + + /** + * Takes an error thrown by the HttpClient. Processes it to return a string that can + * be presented to the user. + * @param e The error thrown. + * @returns The prepared and translated message for the user + */ + private handleError(e: any): string { + let error = this.translate.instant('Error') + ': '; + // If the error is no HttpErrorResponse, it's not clear what is wrong. + if (!(e instanceof HttpErrorResponse)) { + console.error('Unknown error thrown by the http client: ', e); + error += this.translate.instant('An unknown error occurred.'); + return error; + } + + if (!e.error) { + error += this.translate.instant("The server didn't respond."); + } else if (typeof e.error === 'object') { + if (e.error.detail) { + error += this.processErrorTexts(e.error.detail); + } else { + error = Object.keys(e.error) + .map(key => { + const capitalizedKey = key.charAt(0).toUpperCase() + key.slice(1); + return this.translate.instant(capitalizedKey) + ': ' + this.processErrorTexts(e.error[key]); + }) + .join(', '); + } + } else if (e.status === 500) { + error += this.translate.instant('A server error occured. Please contact your system administrator.'); + } else if (e.status > 500) { + error += this.translate.instant('The server cound not be reached') + ` (${e.status})` + } else { + error += e.message; + } + + return error; + } + + /** + * Errors from the servers may be string or array of strings. This function joins the strings together, + * if an array is send. + * @param str a string or a string array to join together. + */ + private processErrorTexts(str: string | string[]): string { + if (str instanceof Array) { + return str.join(' '); + } else { + return str; } } diff --git a/client/src/app/site/agenda/agenda-list/agenda-list.component.ts b/client/src/app/site/agenda/agenda-list/agenda-list.component.ts index 75249527c..b023d6091 100644 --- a/client/src/app/site/agenda/agenda-list/agenda-list.component.ts +++ b/client/src/app/site/agenda/agenda-list/agenda-list.component.ts @@ -5,6 +5,7 @@ import { ViewItem } from '../models/view-item'; import { ListViewBaseComponent } from '../../base/list-view-base'; import { AgendaRepositoryService } from '../services/agenda-repository.service'; import { Router } from '@angular/router'; +import { MatSnackBar } from '@angular/material'; /** * List view for the agenda. @@ -21,14 +22,18 @@ export class AgendaListComponent extends ListViewBaseComponent impleme * The usual constructor for components * @param titleService * @param translate + * @param matSnackBar + * @param router + * @param repo */ public constructor( titleService: Title, translate: TranslateService, + matSnackBar: MatSnackBar, private router: Router, private repo: AgendaRepositoryService ) { - super(titleService, translate); + super(titleService, translate, matSnackBar); } /** diff --git a/client/src/app/site/assignments/assignment-list/assignment-list.component.ts b/client/src/app/site/assignments/assignment-list/assignment-list.component.ts index 9e782a449..90cfbd965 100644 --- a/client/src/app/site/assignments/assignment-list/assignment-list.component.ts +++ b/client/src/app/site/assignments/assignment-list/assignment-list.component.ts @@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser'; import { ViewAssignment } from '../models/view-assignment'; import { ListViewBaseComponent } from '../../base/list-view-base'; import { AssignmentRepositoryService } from '../services/assignment-repository.service'; +import { MatSnackBar } from '@angular/material'; /** * Listview for the assignments @@ -18,12 +19,18 @@ export class AssignmentListComponent extends ListViewBaseComponent; + + /** + * Constructor for bas elist views + * @param titleService the title serivce, passed to the base component + * @param translate the translate service, passed to the base component + * @param matSnackBar the snack bar service. Needed for showing errors. + */ + public constructor(titleService: Title, translate: TranslateService, private matSnackBar: MatSnackBar) { + super(titleService, translate); + } + + /** + * Opens an error snack bar with the given error message. + * This is implemented as an arrow function to capture the called `this`. You can use this function + * as callback (`.then(..., this.raiseError)`) instead of doing `this.raiseError.bind(this)`. + * @param message The message to show. + */ + protected raiseError = (message: string): void => { + this.errorSnackBar = this.matSnackBar.open(message, this.translate.instant('OK'), { + duration: 0 + }); + }; + + /** + * automatically dismisses the error snack bar, if the component is destroyed. + */ + public ngOnDestroy(): void { + if (this.errorSnackBar) { + this.errorSnackBar.dismiss(); + } + } +} diff --git a/client/src/app/site/base/list-view-base.ts b/client/src/app/site/base/list-view-base.ts index c20bf18d7..f392ca7b1 100644 --- a/client/src/app/site/base/list-view-base.ts +++ b/client/src/app/site/base/list-view-base.ts @@ -1,11 +1,11 @@ import { ViewChild } from '@angular/core'; -import { BaseComponent } from '../../base.component'; import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { MatTableDataSource, MatTable, MatSort, MatPaginator } from '@angular/material'; +import { MatTableDataSource, MatTable, MatSort, MatPaginator, MatSnackBar } from '@angular/material'; import { BaseViewModel } from './base-view-model'; +import { BaseViewComponent } from './base-view'; -export abstract class ListViewBaseComponent extends BaseComponent { +export abstract class ListViewBaseComponent extends BaseViewComponent { /** * The data source for a table. Requires to be initialised with a BaseViewModel */ @@ -33,9 +33,10 @@ export abstract class ListViewBaseComponent extends Bas * Constructor for list view bases * @param titleService the title serivce * @param translate the translate service + * @param matSnackBar */ - public constructor(titleService: Title, translate: TranslateService) { - super(titleService, translate); + public constructor(titleService: Title, translate: TranslateService, matSnackBar: MatSnackBar) { + super(titleService, translate, matSnackBar); } /** diff --git a/client/src/app/site/config/components/config-field/config-field.component.ts b/client/src/app/site/config/components/config-field/config-field.component.ts index 7474c61e8..23843964e 100644 --- a/client/src/app/site/config/components/config-field/config-field.component.ts +++ b/client/src/app/site/config/components/config-field/config-field.component.ts @@ -115,20 +115,18 @@ export class ConfigFieldComponent extends BaseComponent implements OnInit { /** * Updates the this config field. + * @param value The new value to set. */ - private async update(value: any): Promise { + private update(value: any): void { // TODO: Fix the Datetimepicker parser and formatter. if (this.configItem.inputType === 'datetimepicker') { value = Date.parse(value); } this.debounceTimeout = null; - try { - await this.repo.update({ value: value }, this.configItem); + this.repo.update({ value: value }, this.configItem).then(() => { this.error = null; this.showSuccessIcon(); - } catch (e) { - this.setError(e.error.detail); - } + }, this.setError.bind(this)); } /** diff --git a/client/src/app/site/login/components/login-mask/login-mask.component.ts b/client/src/app/site/login/components/login-mask/login-mask.component.ts index d545c63d2..a099173e3 100644 --- a/client/src/app/site/login/components/login-mask/login-mask.component.ts +++ b/client/src/app/site/login/components/login-mask/login-mask.component.ts @@ -89,21 +89,18 @@ export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestr * Observes the operator, if a user was already logged in, recreate to user and skip the login */ public ngOnInit(): void { - // Get the login data. Save information to the login data service - this.http.get(environment.urlPrefix + '/users/login/').then( - response => { - if (response.info_text) { - this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), { - duration: 5000 - }); - } - this.loginDataService.setPrivacyPolicy(response.privacy_policy); - this.loginDataService.setLegalNotice(response.legal_notice); - }, - () => { - // TODO: Error handling + // Get the login data. Save information to the login data service. If there is an + // error, ignore it. + // TODO: This has to be caught by the offline service + this.http.get(environment.urlPrefix + '/users/login/').then(response => { + if (response.info_text) { + this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), { + duration: 5000 + }); } - ); + this.loginDataService.setPrivacyPolicy(response.privacy_policy); + this.loginDataService.setLegalNotice(response.legal_notice); + }, () => {}); } public ngOnDestroy(): void { diff --git a/client/src/app/site/mediafiles/mediafile-list/mediafile-list.component.ts b/client/src/app/site/mediafiles/mediafile-list/mediafile-list.component.ts index e84cad0c3..1c3a21b9c 100644 --- a/client/src/app/site/mediafiles/mediafile-list/mediafile-list.component.ts +++ b/client/src/app/site/mediafiles/mediafile-list/mediafile-list.component.ts @@ -6,6 +6,7 @@ import { TranslateService } from '@ngx-translate/core'; import { ViewMediafile } from '../models/view-mediafile'; import { MediafileRepositoryService } from '../services/mediafile-repository.service'; import { ListViewBaseComponent } from '../../base/list-view-base'; +import { MatSnackBar } from '@angular/material'; /** * Lists all the uploaded files. @@ -25,11 +26,12 @@ export class MediafileListComponent extends ListViewBaseComponent * @param translate */ public constructor( - private repo: MediafileRepositoryService, - protected titleService: Title, - protected translate: TranslateService + titleService: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, + private repo: MediafileRepositoryService ) { - super(titleService, translate); + super(titleService, translate, matSnackBar); } /** diff --git a/client/src/app/site/motions/components/category-list/category-list.component.ts b/client/src/app/site/motions/components/category-list/category-list.component.ts index c278995a2..0392cba56 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.ts +++ b/client/src/app/site/motions/components/category-list/category-list.component.ts @@ -3,7 +3,6 @@ import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { BaseComponent } from '../../../../base.component'; import { Category } from '../../../../shared/models/motions/category'; import { CategoryRepositoryService } from '../../services/category-repository.service'; import { ViewCategory } from '../../models/view-category'; @@ -11,6 +10,8 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { Motion } from '../../../../shared/models/motions/motion'; import { SortingListComponent } from '../../../../shared/components/sorting-list/sorting-list.component'; import { PromptService } from 'app/core/services/prompt.service'; +import { BaseViewComponent } from '../../../base/base-view'; +import { MatSnackBar } from '@angular/material'; /** * List view for the categories. @@ -20,7 +21,7 @@ import { PromptService } from 'app/core/services/prompt.service'; templateUrl: './category-list.component.html', styleUrls: ['./category-list.component.scss'] }) -export class CategoryListComponent extends BaseComponent implements OnInit { +export class CategoryListComponent extends BaseViewComponent implements OnInit { /** * Hold the category to create */ @@ -56,17 +57,20 @@ export class CategoryListComponent extends BaseComponent implements OnInit { * The usual component constructor * @param titleService * @param translate + * @param matSnackBar * @param repo * @param formBuilder + * @param promptService */ public constructor( - protected titleService: Title, - protected translate: TranslateService, + titleService: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, private repo: CategoryRepositoryService, private formBuilder: FormBuilder, private promptService: PromptService ) { - super(titleService, translate); + super(titleService, translate, matSnackBar); this.createForm = this.formBuilder.group({ prefix: ['', Validators.required], @@ -80,11 +84,12 @@ export class CategoryListComponent extends BaseComponent implements OnInit { } /** - * Event on Key Down in form + * Event on key-down in form + * @param event + * @param viewCategory */ public keyDownFunction(event: KeyboardEvent, viewCategory?: ViewCategory): void { if (event.keyCode === 13) { - console.log('hit enter'); if (viewCategory) { this.onSaveButton(viewCategory); } else { @@ -119,11 +124,10 @@ export class CategoryListComponent extends BaseComponent implements OnInit { /** * Creates a new category. Executed after hitting save. */ - public async onCreateButton(): Promise { + public onCreateButton(): void { if (this.createForm.valid) { this.categoryToCreate.patchValues(this.createForm.value as Category); - await this.repo.create(this.categoryToCreate) - this.categoryToCreate = null; + this.repo.create(this.categoryToCreate).then(() => (this.categoryToCreate = null), this.raiseError); } } @@ -141,11 +145,17 @@ export class CategoryListComponent extends BaseComponent implements OnInit { } /** - * Saves the categories + * Saves the category + * @param viewCategory */ public async onSaveButton(viewCategory: ViewCategory): Promise { if (this.updateForm.valid) { - await this.repo.update(this.updateForm.value as Partial, viewCategory); + // TODO: Check the motion sorting code below. If it is removed, change to .then() syntax. + try { + await this.repo.update(this.updateForm.value as Partial, viewCategory); + } catch (e) { + this.raiseError(e); + } this.onCancelButton(); this.sortDataSource(); } @@ -174,12 +184,12 @@ export class CategoryListComponent extends BaseComponent implements OnInit { /** * is executed, when the delete button is pressed + * @param viewCategory The category to delete */ public async onDeleteButton(viewCategory: ViewCategory): Promise { const content = this.translate.instant('Delete') + ` ${viewCategory.name}?`; if (await this.promptService.open('Are you sure?', content)) { - await this.repo.delete(viewCategory); - this.onCancelButton(); + this.repo.delete(viewCategory).then(() => this.onCancelButton(), this.raiseError); } } diff --git a/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.html b/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.html index d2f850eec..bfd030725 100644 --- a/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.html +++ b/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.html @@ -15,7 +15,7 @@ - + - + diff --git a/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.ts b/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.ts index 46221c19f..7ac1c54e5 100644 --- a/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.ts +++ b/client/src/app/site/motions/components/motion-change-recommendation/motion-change-recommendation.component.ts @@ -1,9 +1,12 @@ import { Component, Inject } from '@angular/core'; import { LineRange, ModificationType } from '../../services/diff.service'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; +import { MAT_DIALOG_DATA, MatDialogRef, MatSnackBar } from '@angular/material'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service'; import { ViewChangeReco } from '../../models/view-change-reco'; +import { BaseViewComponent } from '../../../base/base-view'; +import { Title } from '@angular/platform-browser'; +import { TranslateService } from '@ngx-translate/core'; /** * Data that needs to be provided to the MotionChangeRecommendationComponent dialog @@ -39,7 +42,7 @@ export interface MotionChangeRecommendationComponentData { templateUrl: './motion-change-recommendation.component.html', styleUrls: ['./motion-change-recommendation.component.scss'] }) -export class MotionChangeRecommendationComponent { +export class MotionChangeRecommendationComponent extends BaseViewComponent { /** * Determine if the change recommendation is edited */ @@ -86,10 +89,15 @@ export class MotionChangeRecommendationComponent { public constructor( @Inject(MAT_DIALOG_DATA) public data: MotionChangeRecommendationComponentData, + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, private formBuilder: FormBuilder, private repo: ChangeRecommendationRepositoryService, private dialogRef: MatDialogRef ) { + super(title, translate, matSnackBar); + this.editReco = data.editChangeRecommendation; this.newReco = data.newChangeRecommendation; this.changeReco = data.changeRecommendation; @@ -116,14 +124,16 @@ export class MotionChangeRecommendationComponent { !this.contentForm.controls.public.value ); - if (this.newReco) { - await this.repo.createByViewModel(this.changeReco); - this.dialogRef.close(); - // @TODO Show an error message - } else { - await this.repo.update(this.changeReco.changeRecommendation, this.changeReco); - this.dialogRef.close(); - // @TODO Show an error message + try { + if (this.newReco) { + await this.repo.createByViewModel(this.changeReco); + this.dialogRef.close(); + } else { + await this.repo.update(this.changeReco.changeRecommendation, this.changeReco); + this.dialogRef.close(); + } + } catch (e) { + this.raiseError(e); } } } diff --git a/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.html b/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.html index 9a37db11e..eb8d04726 100644 --- a/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.html +++ b/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.html @@ -50,7 +50,7 @@
- add + edit {{ section.write_groups }} – diff --git a/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.ts b/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.ts index 48c5b7d17..2ad1d4226 100644 --- a/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.ts +++ b/client/src/app/site/motions/components/motion-comment-section-list/motion-comment-section-list.component.ts @@ -3,7 +3,6 @@ import { Title } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; -import { BaseComponent } from '../../../../base.component'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { MotionCommentSection } from '../../../../shared/models/motions/motion-comment-section'; import { ViewMotionCommentSection } from '../../models/view-motion-comment-section'; @@ -12,6 +11,8 @@ import { PromptService } from '../../../../core/services/prompt.service'; import { BehaviorSubject } from 'rxjs'; import { Group } from '../../../../shared/models/users/group'; import { DataStoreService } from '../../../../core/services/data-store.service'; +import { BaseViewComponent } from '../../../base/base-view'; +import { MatSnackBar } from '@angular/material'; /** * List view for the categories. @@ -21,7 +22,7 @@ import { DataStoreService } from '../../../../core/services/data-store.service'; templateUrl: './motion-comment-section-list.component.html', styleUrls: ['./motion-comment-section-list.component.scss'] }) -export class MotionCommentSectionListComponent extends BaseComponent implements OnInit { +export class MotionCommentSectionListComponent extends BaseViewComponent implements OnInit { public commentSectionToCreate: MotionCommentSection | null; /** @@ -45,18 +46,23 @@ export class MotionCommentSectionListComponent extends BaseComponent implements * The usual component constructor * @param titleService * @param translate + * @param matSnackBar * @param repo * @param formBuilder + * @param promptService + * @param DS */ public constructor( - protected titleService: Title, - protected translate: TranslateService, + titleService: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, private repo: MotionCommentSectionRepositoryService, private formBuilder: FormBuilder, private promptService: PromptService, private DS: DataStoreService ) { - super(titleService, translate); + super(titleService, translate, matSnackBar); + const form = { name: ['', Validators.required], read_groups_id: [[]], @@ -98,7 +104,7 @@ export class MotionCommentSectionListComponent extends BaseComponent implements } /** - * Add a new Section. + * Opens the create form. */ public onPlusButton(): void { if (!this.commentSectionToCreate) { @@ -111,11 +117,15 @@ export class MotionCommentSectionListComponent extends BaseComponent implements } } - public async create(): Promise { + /** + * Creates the comment section from the create form. + */ + public create(): void { if (this.createForm.valid) { this.commentSectionToCreate.patchValues(this.createForm.value as MotionCommentSection); - await this.repo.create(this.commentSectionToCreate); - this.commentSectionToCreate = null; + this.repo + .create(this.commentSectionToCreate) + .then(() => (this.commentSectionToCreate = null), this.raiseError); } } @@ -135,22 +145,24 @@ export class MotionCommentSectionListComponent extends BaseComponent implements /** * Saves the categories + * @param viewSection The section to save */ - public async onSaveButton(viewSection: ViewMotionCommentSection): Promise { + public onSaveButton(viewSection: ViewMotionCommentSection): void { if (this.updateForm.valid) { - await this.repo.update(this.updateForm.value as Partial, viewSection); - this.openId = this.editId = null; + this.repo.update(this.updateForm.value as Partial, viewSection).then(() => { + this.openId = this.editId = null; + }, this.raiseError); } } /** * is executed, when the delete button is pressed + * @param viewSection The section to delete */ public async onDeleteButton(viewSection: ViewMotionCommentSection): Promise { const content = this.translate.instant('Delete') + ` ${viewSection.name}?`; if (await this.promptService.open('Are you sure?', content)) { - await this.repo.delete(viewSection); - this.openId = this.editId = null; + this.repo.delete(viewSection).then(() => (this.openId = this.editId = null), this.raiseError); } } diff --git a/client/src/app/site/motions/components/motion-detail-diff/motion-detail-diff.component.ts b/client/src/app/site/motions/components/motion-detail-diff/motion-detail-diff.component.ts index cc4cde7da..0939796dc 100644 --- a/client/src/app/site/motions/components/motion-detail-diff/motion-detail-diff.component.ts +++ b/client/src/app/site/motions/components/motion-detail-diff/motion-detail-diff.component.ts @@ -1,16 +1,18 @@ import { AfterViewInit, Component, ElementRef, EventEmitter, Input, Output } from '@angular/core'; import { ViewMotion } from '../../models/view-motion'; import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../models/view-unified-change'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; import { MotionRepositoryService } from '../../services/motion-repository.service'; import { LineRange, ModificationType } from '../../services/diff.service'; import { ViewChangeReco } from '../../models/view-change-reco'; -import { MatDialog } from '@angular/material'; +import { MatDialog, MatSnackBar } from '@angular/material'; import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service'; import { MotionChangeRecommendationComponent, MotionChangeRecommendationComponentData } from '../motion-change-recommendation/motion-change-recommendation.component'; +import { BaseViewComponent } from '../../../base/base-view'; +import { TranslateService } from '@ngx-translate/core'; /** * This component displays the original motion text with the change blocks inside. @@ -36,7 +38,7 @@ import { templateUrl: './motion-detail-diff.component.html', styleUrls: ['./motion-detail-diff.component.scss'] }) -export class MotionDetailDiffComponent implements AfterViewInit { +export class MotionDetailDiffComponent extends BaseViewComponent implements AfterViewInit { @Input() public motion: ViewMotion; @Input() @@ -47,13 +49,28 @@ export class MotionDetailDiffComponent implements AfterViewInit { @Output() public createChangeRecommendation: EventEmitter = new EventEmitter(); + /** + * @param title + * @param translate + * @param matSnackBar + * @param sanitizer + * @param motionRepo + * @param recoRepo + * @param dialogService + * @param el + */ public constructor( + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, private sanitizer: DomSanitizer, private motionRepo: MotionRepositoryService, private recoRepo: ChangeRecommendationRepositoryService, private dialogService: MatDialog, private el: ElementRef - ) {} + ) { + super(title, translate, matSnackBar); + } /** * Returns the part of this motion between two change objects @@ -171,11 +188,15 @@ export class MotionDetailDiffComponent implements AfterViewInit { * @param {string} value */ public async setAcceptanceValue(change: ViewChangeReco, value: string): Promise { - if (value === 'accepted') { - await this.recoRepo.setAccepted(change); - } - if (value === 'rejected') { - await this.recoRepo.setRejected(change); + try { + if (value === 'accepted') { + await this.recoRepo.setAccepted(change); + } + if (value === 'rejected') { + await this.recoRepo.setRejected(change); + } + } catch (e) { + this.raiseError(e); } } @@ -185,8 +206,8 @@ export class MotionDetailDiffComponent implements AfterViewInit { * @param {ViewChangeReco} change * @param {boolean} internal */ - public async setInternal(change: ViewChangeReco, internal: boolean): Promise { - await this.recoRepo.setInternal(change, internal); + public setInternal(change: ViewChangeReco, internal: boolean): void { + this.recoRepo.setInternal(change, internal).then(null, this.raiseError); } /** @@ -196,10 +217,10 @@ export class MotionDetailDiffComponent implements AfterViewInit { * @param {ViewChangeReco} reco * @param {MouseEvent} $event */ - public async deleteChangeRecommendation(reco: ViewChangeReco, $event: MouseEvent): Promise { + public deleteChangeRecommendation(reco: ViewChangeReco, $event: MouseEvent): void { $event.stopPropagation(); $event.preventDefault(); - await this.recoRepo.delete(reco); + this.recoRepo.delete(reco).then(null, this.raiseError); } /** diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts index 56890c265..26f5d0978 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts @@ -1,9 +1,8 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { MatDialog, MatExpansionPanel, MatSelectChange } from '@angular/material'; +import { MatDialog, MatExpansionPanel, MatSnackBar, MatSelectChange } from '@angular/material'; -import { BaseComponent } from '../../../../base.component'; import { Category } from '../../../../shared/models/motions/category'; import { ViewportService } from '../../../../core/services/viewport.service'; import { MotionRepositoryService } from '../../services/motion-repository.service'; @@ -20,10 +19,10 @@ import { } from '../motion-change-recommendation/motion-change-recommendation.component'; import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service'; import { ViewChangeReco } from '../../models/view-change-reco'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; import { ViewUnifiedChange } from '../../models/view-unified-change'; import { OperatorService } from '../../../../core/services/operator.service'; -import { CategoryRepositoryService } from '../../services/category-repository.service'; +import { BaseViewComponent } from '../../../base/base-view'; /** * Component for the motion detail view @@ -33,7 +32,7 @@ import { CategoryRepositoryService } from '../../services/category-repository.se templateUrl: './motion-detail.component.html', styleUrls: ['./motion-detail.component.scss'] }) -export class MotionDetailComponent extends BaseComponent implements OnInit { +export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * MatExpansionPanel for the meta info * Only relevant in mobile view @@ -131,18 +130,24 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { /** * Constuct the detail view. * + * @param title + * @param translate + * @param matSnackBar * @param vp the viewport service + * @param op * @param router to navigate back to the motion list and to an existing motion * @param route determine if this is a new or an existing motion * @param formBuilder For reactive forms. Form Group and Form Control * @param dialogService For opening dialogs - * @param repo: Motion Repository - * @param changeRecoRepo: Change Recommendation Repository - * @param DS: The DataStoreService - * @param sanitizer: For making HTML SafeHTML - * @param translate: Translation Service + * @param repo Motion Repository + * @param changeRecoRepo Change Recommendation Repository + * @param DS The DataStoreService + * @param sanitizer For making HTML SafeHTML */ public constructor( + title: Title, + translate: TranslateService, + matSnackBar: MatSnackBar, public vp: ViewportService, private op: OperatorService, private router: Router, @@ -151,12 +156,10 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { private dialogService: MatDialog, private repo: MotionRepositoryService, private changeRecoRepo: ChangeRecommendationRepositoryService, - private categoryRepo: CategoryRepositoryService, private DS: DataStoreService, - private sanitizer: DomSanitizer, - protected translate: TranslateService + private sanitizer: DomSanitizer ) { - super(); + super(title, translate, matSnackBar); this.createForm(); this.getMotionByUrl(); @@ -278,14 +281,17 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { const fromForm = new Motion(); fromForm.deserialize(newMotionValues); - if (this.newMotion) { - const response = await this.repo.create(fromForm); - this.router.navigate(['./motions/' + response.id]); - } else { - await this.repo.update(fromForm, this.motionCopy); - // if the motion was successfully updated, change the edit mode. - this.editMotion = false; - // TODO: Show errors if there appear here + try { + if (this.newMotion) { + const response = await this.repo.create(fromForm); + this.router.navigate(['./motions/' + response.id]); + } else { + await this.repo.update(fromForm, this.motionCopy); + // if the motion was successfully updated, change the edit mode. + this.editMotion = false; + } + } catch (e) { + this.raiseError(e); } } @@ -319,13 +325,14 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { public deleteMotionButton(): void { this.repo.delete(this.motion).then(() => { this.router.navigate(['./motions/']); - }); - const motList = this.categoryRepo.getMotionsOfCategory(this.motion.category); + }, this.raiseError); + // TODO: this needs to be in the autoupdate code. + /*const motList = this.categoryRepo.getMotionsOfCategory(this.motion.category); const index = motList.indexOf(this.motion.motion, 0); if (index > -1) { motList.splice(index, 1); } - this.categoryRepo.updateCategoryNumbering(this.motion.category, motList); + this.categoryRepo.updateCategoryNumbering(this.motion.category, motList);*/ } /** diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index bfb369875..ccc7be4de 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -24,7 +24,7 @@ - Identifier + Identifier
{{motion.identifier}} @@ -34,7 +34,7 @@ - Title + Title
{{motion.title}} @@ -49,7 +49,7 @@ - State + State
{{getStateIcon(motion.state)}}> @@ -77,7 +77,7 @@