Merge pull request #3968 from FinnStutzenstein/ErrorHandling
error handling
This commit is contained in:
commit
080b6f52ad
@ -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<T>(url: string, method: HTTPMethod, data?: any): Promise<T> {
|
||||
if (!url.endsWith('/')) {
|
||||
@ -33,15 +34,66 @@ export class HttpService {
|
||||
}
|
||||
|
||||
const options = {
|
||||
body: data,
|
||||
body: data
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.http.request<T>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ViewItem> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<ViewAssignmen
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param repo the repository
|
||||
* @param titleService
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param repo the repository
|
||||
*/
|
||||
public constructor(private repo: AssignmentRepositoryService, titleService: Title, translate: TranslateService) {
|
||||
super(titleService, translate);
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private repo: AssignmentRepositoryService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
48
client/src/app/site/base/base-view.ts
Normal file
48
client/src/app/site/base/base-view.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { BaseComponent } from '../../base.component';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material';
|
||||
import { OnDestroy } from '@angular/core';
|
||||
|
||||
/**
|
||||
* A base class for all views. Implements a generic error handling by raising a snack bar
|
||||
* with the error. The error is dismissed, if the component is destroyed, so if the
|
||||
* view is leaved.
|
||||
*/
|
||||
export abstract class BaseViewComponent extends BaseComponent implements OnDestroy {
|
||||
/**
|
||||
* A reference to the current error snack bar.
|
||||
*/
|
||||
private errorSnackBar: MatSnackBarRef<SimpleSnackBar>;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<V extends BaseViewModel> extends BaseComponent {
|
||||
export abstract class ListViewBaseComponent<V extends BaseViewModel> extends BaseViewComponent {
|
||||
/**
|
||||
* The data source for a table. Requires to be initialised with a BaseViewModel
|
||||
*/
|
||||
@ -33,9 +33,10 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<void> {
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -90,21 +90,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<any>(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<any>(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 {
|
||||
|
@ -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<ViewMediafile>
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
if (this.updateForm.valid) {
|
||||
await this.repo.update(this.updateForm.value as Partial<Category>, 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<Category>, 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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close>Abort</button>
|
||||
<button mat-button mat-dialog-close translate>Abort</button>
|
||||
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
|
||||
<button mat-button (click)="saveChangeRecommendation()">save</button>
|
||||
<button mat-button (click)="saveChangeRecommendation()" translate>Save</button>
|
||||
</mat-dialog-actions>
|
||||
|
@ -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<MotionChangeRecommendationComponent>
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="write">
|
||||
<mat-icon>add</mat-icon>
|
||||
<mat-icon>edit</mat-icon>
|
||||
{{ section.write_groups }}
|
||||
<ng-container *ngIf="section.write_groups.length === 0">
|
||||
–
|
||||
|
@ -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<void> {
|
||||
/**
|
||||
* 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<void> {
|
||||
public onSaveButton(viewSection: ViewMotionCommentSection): void {
|
||||
if (this.updateForm.valid) {
|
||||
await this.repo.update(this.updateForm.value as Partial<MotionCommentSection>, viewSection);
|
||||
this.openId = this.editId = null;
|
||||
this.repo.update(this.updateForm.value as Partial<MotionCommentSection>, 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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<LineRange> = new EventEmitter<LineRange>();
|
||||
|
||||
/**
|
||||
* @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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
public deleteChangeRecommendation(reco: ViewChangeReco, $event: MouseEvent): void {
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
await this.recoRepo.delete(reco);
|
||||
this.recoRepo.delete(reco).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@
|
||||
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
|
||||
<!-- identifier column -->
|
||||
<ng-container matColumnDef="identifier">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Identifier </mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Identifier</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div class='innerTable'>
|
||||
{{motion.identifier}}
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
<!-- title column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Title </mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div class='innerTable'>
|
||||
<span class='motion-list-title'>{{motion.title}}</span>
|
||||
@ -49,7 +49,7 @@
|
||||
|
||||
<!-- state column -->
|
||||
<ng-container matColumnDef="state">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> State </mat-header-cell>
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>State</mat-header-cell>
|
||||
<mat-cell *matCellDef="let motion">
|
||||
<div *ngIf='isDisplayIcon(motion.state) && motion.state' class='innerTable'>
|
||||
<mat-icon>{{getStateIcon(motion.state)}}></mat-icon>
|
||||
@ -77,7 +77,7 @@
|
||||
|
||||
<button mat-menu-item routerLink="comment-section">
|
||||
<mat-icon>speaker_notes</mat-icon>
|
||||
<span translate>Comments</span>
|
||||
<span translate>Comment sections</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item routerLink="statute-paragraphs">
|
||||
|
@ -8,6 +8,7 @@ import { MotionRepositoryService } from '../../services/motion-repository.servic
|
||||
import { ViewMotion } from '../../models/view-motion';
|
||||
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
|
||||
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
/**
|
||||
* Component that displays all the motions in a Table using DataSource.
|
||||
@ -42,11 +43,12 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private repo: MotionRepositoryService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,12 +3,13 @@ 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 { PromptService } from '../../../../core/services/prompt.service';
|
||||
import { StatuteParagraph } from '../../../../shared/models/motions/statute-paragraph';
|
||||
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
|
||||
import { StatuteParagraphRepositoryService } from '../../services/statute-paragraph-repository.service';
|
||||
import { BaseViewComponent } from '../../../base/base-view';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
/**
|
||||
* List view for the statute paragraphs.
|
||||
@ -18,7 +19,7 @@ import { StatuteParagraphRepositoryService } from '../../services/statute-paragr
|
||||
templateUrl: './statute-paragraph-list.component.html',
|
||||
styleUrls: ['./statute-paragraph-list.component.scss']
|
||||
})
|
||||
export class StatuteParagraphListComponent extends BaseComponent implements OnInit {
|
||||
export class StatuteParagraphListComponent extends BaseViewComponent implements OnInit {
|
||||
public statuteParagraphToCreate: StatuteParagraph | null;
|
||||
|
||||
/**
|
||||
@ -40,17 +41,21 @@ export class StatuteParagraphListComponent extends BaseComponent implements OnIn
|
||||
* 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: StatuteParagraphRepositoryService,
|
||||
private formBuilder: FormBuilder,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
super(titleService, translate, matSnackBar);
|
||||
|
||||
const form = {
|
||||
title: ['', Validators.required],
|
||||
text: ['', Validators.required]
|
||||
@ -85,11 +90,15 @@ export class StatuteParagraphListComponent extends BaseComponent implements OnIn
|
||||
}
|
||||
}
|
||||
|
||||
public async create(): Promise<void> {
|
||||
/**
|
||||
* Handler when clicking on create to create a new statute paragraph
|
||||
*/
|
||||
public create(): void {
|
||||
if (this.createForm.valid) {
|
||||
this.statuteParagraphToCreate.patchValues(this.createForm.value as StatuteParagraph);
|
||||
await this.repo.create(this.statuteParagraphToCreate);
|
||||
this.statuteParagraphToCreate = null;
|
||||
this.repo.create(this.statuteParagraphToCreate).then(() => {
|
||||
this.statuteParagraphToCreate = null;
|
||||
}, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,23 +116,25 @@ export class StatuteParagraphListComponent extends BaseComponent implements OnIn
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the statute paragrpah
|
||||
* Saves the statute paragraph
|
||||
* @param viewStatuteParagraph The statute paragraph to save
|
||||
*/
|
||||
public async onSaveButton(viewStatuteParagraph: ViewStatuteParagraph): Promise<void> {
|
||||
public onSaveButton(viewStatuteParagraph: ViewStatuteParagraph): void {
|
||||
if (this.updateForm.valid) {
|
||||
await this.repo.update(this.updateForm.value as Partial<StatuteParagraph>, viewStatuteParagraph);
|
||||
this.openId = this.editId = null;
|
||||
this.repo.update(this.updateForm.value as Partial<StatuteParagraph>, viewStatuteParagraph).then(() => {
|
||||
this.openId = this.editId = null;
|
||||
}, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* is executed, when the delete button is pressed
|
||||
* Is executed, when the delete button is pressed
|
||||
* @param viewStatuteParagraph The statute paragraph to delete
|
||||
*/
|
||||
public async onDeleteButton(viewStatuteParagraph: ViewStatuteParagraph): Promise<void> {
|
||||
const content = this.translate.instant('Delete') + ` ${viewStatuteParagraph.title}?`;
|
||||
if (await this.promptService.open('Are you sure?', content)) {
|
||||
await this.repo.delete(viewStatuteParagraph);
|
||||
this.openId = this.editId = null;
|
||||
this.repo.delete(viewStatuteParagraph).then(() => (this.openId = this.editId = null), this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,17 +54,12 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a change recommendation view object, a entry in the backend is created and the new
|
||||
* change recommendation view object is returned (as an observable).
|
||||
*
|
||||
* @param {ViewChangeReco} view
|
||||
* @deprecated Will not work with PR #3928. There will just be the id as response to create requests.
|
||||
* Two possibilities: Make a server change to still retrieve the created object or you have to wait for the
|
||||
* correct autoupdate.
|
||||
* Given a change recommendation view object, a entry in the backend is created.
|
||||
* @param view
|
||||
* @returns The id of the created change recommendation
|
||||
*/
|
||||
public async createByViewModel(view: ViewChangeReco): Promise<Identifiable> {
|
||||
return await this.dataSend.createModel(view.changeRecommendation);
|
||||
// return new ViewChangeReco(cr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,7 @@ import { TagRepositoryService } from '../../services/tag-repository.service';
|
||||
import { ViewTag } from '../../models/view-tag';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
/**
|
||||
* Listview for the complete lsit of available Tags
|
||||
@ -32,15 +33,18 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag> implements
|
||||
* Constructor.
|
||||
* @param titleService
|
||||
* @param translate
|
||||
* @param repo the repository
|
||||
* @param matSnackBar
|
||||
* @param repo the tag repository
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private repo: TagRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,26 +74,26 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag> implements
|
||||
/**
|
||||
* Saves a newly created tag.
|
||||
*/
|
||||
public async submitNewTag(): Promise<void> {
|
||||
public submitNewTag(): void {
|
||||
if (!this.tagForm.value || !this.tagForm.valid) {
|
||||
return;
|
||||
}
|
||||
await this.repo.create(this.tagForm.value);
|
||||
this.tagForm.reset();
|
||||
this.cancelEditing();
|
||||
this.repo.create(this.tagForm.value).then(() => {
|
||||
this.tagForm.reset();
|
||||
this.cancelEditing();
|
||||
}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an edited tag.
|
||||
*/
|
||||
public async submitEditedTag(): Promise<void> {
|
||||
public submitEditedTag(): void {
|
||||
if (!this.tagForm.value || !this.tagForm.valid) {
|
||||
return;
|
||||
}
|
||||
const updateData = new Tag({ name: this.tagForm.value.name });
|
||||
|
||||
await this.repo.update(updateData, this.selectedTag);
|
||||
this.cancelEditing();
|
||||
this.repo.update(updateData, this.selectedTag).then(() => this.cancelEditing(), this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,11 +102,13 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag> implements
|
||||
public async deleteSelectedTag(): Promise<void> {
|
||||
const content = this.translate.instant('Delete') + ` ${this.selectedTag.name}?`;
|
||||
if (await this.promptService.open(this.translate.instant('Are you sure?'), content)) {
|
||||
await this.repo.delete(this.selectedTag);
|
||||
this.cancelEditing();
|
||||
this.repo.delete(this.selectedTag).then(() => this.cancelEditing(), this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Canceles the editing
|
||||
*/
|
||||
public cancelEditing(): void {
|
||||
this.newTag = false;
|
||||
this.editTag = false;
|
||||
@ -126,6 +132,11 @@ export class TagListComponent extends ListViewBaseComponent<ViewTag> implements
|
||||
this.cancelEditing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles keyboard events. On enter, the editing is canceled.
|
||||
* @param event
|
||||
*/
|
||||
public keyDownFunction(event: KeyboardEvent): void {
|
||||
if (event.keyCode === 27) {
|
||||
this.cancelEditing();
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatTableDataSource } from '@angular/material';
|
||||
import { MatTableDataSource, MatSnackBar } from '@angular/material';
|
||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
||||
|
||||
import { GroupRepositoryService } from '../../services/group-repository.service';
|
||||
import { ViewGroup } from '../../models/view-group';
|
||||
import { Group } from '../../../../shared/models/users/group';
|
||||
import { BaseComponent } from '../../../../base.component';
|
||||
import { BaseViewComponent } from '../../../base/base-view';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Component for the Group-List and permission matrix
|
||||
@ -17,7 +18,7 @@ import { BaseComponent } from '../../../../base.component';
|
||||
templateUrl: './group-list.component.html',
|
||||
styleUrls: ['./group-list.component.scss']
|
||||
})
|
||||
export class GroupListComponent extends BaseComponent implements OnInit {
|
||||
export class GroupListComponent extends BaseViewComponent implements OnInit {
|
||||
/**
|
||||
* Holds all Groups
|
||||
*/
|
||||
@ -51,22 +52,37 @@ export class GroupListComponent extends BaseComponent implements OnInit {
|
||||
*
|
||||
* @param titleService Title Service
|
||||
* @param translate Translations
|
||||
* @param DS The Data Store
|
||||
* @param constants Constants
|
||||
* @param matSnackBar
|
||||
* @param repo
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(titleService: Title, translate: TranslateService, public repo: GroupRepositoryService) {
|
||||
super(titleService, translate);
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
public repo: GroupRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
public setEditMode(mode: boolean, newGroup: boolean = true): void {
|
||||
this.editGroup = mode;
|
||||
/**
|
||||
* Set, if the view is in edit mode. If editMod eis false, the editing is canceled.
|
||||
* @param editMode
|
||||
* @param newGroup Set to true, if the edit mode is for creating instead of updating a group.
|
||||
*/
|
||||
public setEditMode(editMode: boolean, newGroup: boolean = true): void {
|
||||
this.editGroup = editMode;
|
||||
this.newGroup = newGroup;
|
||||
|
||||
if (!mode) {
|
||||
if (!editMode) {
|
||||
this.cancelEditing();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates or updates a group.
|
||||
*/
|
||||
public saveGroup(): void {
|
||||
if (this.editGroup && this.newGroup) {
|
||||
this.submitNewGroup();
|
||||
@ -86,37 +102,39 @@ export class GroupListComponent extends BaseComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* Saves a newly created group.
|
||||
* @param form form data given by the group
|
||||
*/
|
||||
public async submitNewGroup(): Promise<void> {
|
||||
public submitNewGroup(): void {
|
||||
if (!this.groupForm.value || !this.groupForm.valid) {
|
||||
return;
|
||||
}
|
||||
await this.repo.create(this.groupForm.value);
|
||||
this.groupForm.reset();
|
||||
this.cancelEditing();
|
||||
this.repo.create(this.groupForm.value).then(() => {
|
||||
this.groupForm.reset();
|
||||
this.cancelEditing();
|
||||
}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves an edited group.
|
||||
* @param form form data given by the group
|
||||
*/
|
||||
public async submitEditedGroup(): Promise<void> {
|
||||
public submitEditedGroup(): void {
|
||||
if (!this.groupForm.value || !this.groupForm.valid) {
|
||||
return;
|
||||
}
|
||||
const updateData = new Group({ name: this.groupForm.value.name });
|
||||
|
||||
await this.repo.update(updateData, this.selectedGroup);
|
||||
this.cancelEditing();
|
||||
this.repo.update(updateData, this.selectedGroup).then(() => {
|
||||
this.cancelEditing();
|
||||
}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected Group
|
||||
*/
|
||||
public async deleteSelectedGroup(): Promise<void> {
|
||||
await this.repo.delete(this.selectedGroup)
|
||||
this.cancelEditing();
|
||||
const content = this.translate.instant('Delete') + ` ${this.selectedGroup.name}?`;
|
||||
if (await this.promptService.open(this.translate.instant('Are you sure?'), content)) {
|
||||
this.repo.delete(this.selectedGroup).then(() => this.cancelEditing(), this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,12 +148,12 @@ export class GroupListComponent extends BaseComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* Triggers when a permission was toggled
|
||||
* @param group
|
||||
* @param viewGroup
|
||||
* @param perm
|
||||
*/
|
||||
public async togglePerm(viewGroup: ViewGroup, perm: string): Promise<void> {
|
||||
public togglePerm(viewGroup: ViewGroup, perm: string): void {
|
||||
const updateData = new Group({ permissions: viewGroup.getAlteredPermissions(perm) });
|
||||
await this.repo.update(updateData, viewGroup);
|
||||
this.repo.update(updateData, viewGroup).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,11 @@ import { UserRepositoryService } from '../../services/user-repository.service';
|
||||
import { Group } from '../../../../shared/models/users/group';
|
||||
import { DataStoreService } from '../../../../core/services/data-store.service';
|
||||
import { OperatorService } from '../../../../core/services/operator.service';
|
||||
import { BaseViewComponent } from '../../../base/base-view';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { PromptService } from '../../../../core/services/prompt.service';
|
||||
|
||||
/**
|
||||
* Users detail component for both new and existing users
|
||||
@ -16,7 +21,7 @@ import { OperatorService } from '../../../../core/services/operator.service';
|
||||
templateUrl: './user-detail.component.html',
|
||||
styleUrls: ['./user-detail.component.scss']
|
||||
})
|
||||
export class UserDetailComponent implements OnInit {
|
||||
export class UserDetailComponent extends BaseViewComponent implements OnInit {
|
||||
/**
|
||||
* Info form object
|
||||
*/
|
||||
@ -54,15 +59,32 @@ export class UserDetailComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* Constructor for user
|
||||
*
|
||||
* @param title Title
|
||||
* @param translate TranslateService
|
||||
* @param matSnackBar MatSnackBar
|
||||
* @param formBuilder FormBuilder
|
||||
* @param route ActivatedRoute
|
||||
* @param router Router
|
||||
* @param repo UserRepositoryService
|
||||
* @param DS DataStoreService
|
||||
* @param operator OperatorService
|
||||
* @param promptService PromptService
|
||||
*/
|
||||
public constructor(
|
||||
title: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private repo: UserRepositoryService,
|
||||
private DS: DataStoreService,
|
||||
private op: OperatorService
|
||||
private operator: OperatorService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
|
||||
this.user = new ViewUser();
|
||||
if (route.snapshot.url[0] && route.snapshot.url[0].path === 'new') {
|
||||
this.newUser = true;
|
||||
@ -75,7 +97,7 @@ export class UserDetailComponent implements OnInit {
|
||||
this.ownPage = this.opOwnsPage(Number(params.id));
|
||||
|
||||
// observe operator to find out if we see our own page or not
|
||||
this.op.getObservable().subscribe(newOp => {
|
||||
this.operator.getObservable().subscribe(newOp => {
|
||||
if (newOp) {
|
||||
this.ownPage = this.opOwnsPage(Number(params.id));
|
||||
}
|
||||
@ -86,10 +108,12 @@ export class UserDetailComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the ownPage variable if the operator owns the page
|
||||
* Checks, if the given user id matches with the operator ones.
|
||||
* @param userId The id to check, if it's the operator
|
||||
* @returns If the user is the operator
|
||||
*/
|
||||
public opOwnsPage(userId: number): boolean {
|
||||
return this.op.user && this.op.user.id === userId;
|
||||
return this.operator.user && this.operator.user.id === userId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,15 +132,15 @@ export class UserDetailComponent implements OnInit {
|
||||
public isAllowed(action: string): boolean {
|
||||
switch (action) {
|
||||
case 'manage':
|
||||
return this.op.hasPerms('users.can_manage');
|
||||
return this.operator.hasPerms('users.can_manage');
|
||||
case 'seeName':
|
||||
return this.op.hasPerms('users.can_see_name', 'users.can_manage') || this.ownPage;
|
||||
return this.operator.hasPerms('users.can_see_name', 'users.can_manage') || this.ownPage;
|
||||
case 'seeExtra':
|
||||
return this.op.hasPerms('users.can_see_extra_data', 'users.can_manage');
|
||||
return this.operator.hasPerms('users.can_see_extra_data', 'users.can_manage');
|
||||
case 'seePersonal':
|
||||
return this.op.hasPerms('users.can_see_extra_data', 'users.can_manage') || this.ownPage;
|
||||
return this.operator.hasPerms('users.can_see_extra_data', 'users.can_manage') || this.ownPage;
|
||||
case 'changePersonal':
|
||||
return this.op.hasPerms('user.cans_manage') || this.ownPage;
|
||||
return this.operator.hasPerms('user.cans_manage') || this.ownPage;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -254,16 +278,20 @@ export class UserDetailComponent implements OnInit {
|
||||
* Save / Submit a user
|
||||
*/
|
||||
public async saveUser(): Promise<void> {
|
||||
if (this.newUser) {
|
||||
const response = await this.repo.create(this.personalInfoForm.value);
|
||||
this.newUser = false;
|
||||
this.router.navigate([`./users/${response.id}`]);
|
||||
} else {
|
||||
// TODO (Issue #3962): We need a waiting-State, so if autoupdates come before the response,
|
||||
// the user is also updated.
|
||||
await this.repo.update(this.personalInfoForm.value, this.user);
|
||||
this.setEditMode(false);
|
||||
this.loadViewUser(this.user.id);
|
||||
try {
|
||||
if (this.newUser) {
|
||||
const response = await this.repo.create(this.personalInfoForm.value);
|
||||
this.newUser = false;
|
||||
this.router.navigate([`./users/${response.id}`]);
|
||||
} else {
|
||||
// TODO (Issue #3962): We need a waiting-State, so if autoupdates come before the response,
|
||||
// the user is also updated.
|
||||
await this.repo.update(this.personalInfoForm.value, this.user);
|
||||
this.setEditMode(false);
|
||||
this.loadViewUser(this.user.id);
|
||||
}
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,8 +313,10 @@ export class UserDetailComponent implements OnInit {
|
||||
* click on the delete user button
|
||||
*/
|
||||
public async deleteUserButton(): Promise<void> {
|
||||
await this.repo.delete(this.user);
|
||||
this.router.navigate(['./users/']);
|
||||
const content = this.translate.instant('Delete') + ` ${this.user.full_name}?`;
|
||||
if (await this.promptService.open(this.translate.instant('Are you sure?'), content)) {
|
||||
this.repo.delete(this.user).then(() => this.router.navigate(['./users/']), this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,6 +7,7 @@ import { ViewUser } from '../../models/view-user';
|
||||
import { UserRepositoryService } from '../../services/user-repository.service';
|
||||
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
|
||||
/**
|
||||
* Component for the user list view.
|
||||
@ -26,14 +27,15 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
* @param translate
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
private repo: UserRepositoryService,
|
||||
protected titleService: Title,
|
||||
protected translate: TranslateService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
protected csvExport: CsvExportService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user