diff options
author | Martin Stockhammer <martin_s@apache.org> | 2020-12-23 22:43:04 +0100 |
---|---|---|
committer | Martin Stockhammer <martin_s@apache.org> | 2020-12-23 22:43:04 +0100 |
commit | 7744b086d7f71b67f436c0f617d262b2ae91c8f6 (patch) | |
tree | 5508bb31ac71d475862e2bb1c5d441a3b0768810 /archiva-modules/archiva-web/archiva-webapp | |
parent | 3012e2f76ff672dccefa306585391b21bfe24f8a (diff) | |
download | archiva-7744b086d7f71b67f436c0f617d262b2ae91c8f6.tar.gz archiva-7744b086d7f71b67f436c0f617d262b2ae91c8f6.zip |
Adding toast notification
Diffstat (limited to 'archiva-modules/archiva-web/archiva-webapp')
12 files changed, 343 insertions, 18 deletions
diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html index e1d9bb0c9..c49a00ec8 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/app.component.html @@ -35,6 +35,7 @@ <button type="button" class="btn btn-danger" (click)="modal.close('Save click')">{{'modal.close'|translate}}</button> </div> </ng-template> +<app-toasts aria-live="polite" aria-atomic="true"></app-toasts> <div class="app d-flex flex-column"> <header> <nav class="navbar navbar-expand-md fixed-top navbar-light " style="background-color: #c6cbd2;"> diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts new file mode 100644 index 000000000..787762b0a --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/app-notification.ts @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {TemplateRef} from "@angular/core"; + +export class AppNotification { + origin: string; + header: string; + body: string | TemplateRef<any>; + timestamp: Date; + classname: string=''; + delay:number=5000; + contextData:any; + type:string='normal' + + constructor(origin: string, body: string|TemplateRef<any>, header:string="", options: any = {}, timestamp:Date = new Date()) { + this.origin = origin + this.header = header; + this.body = body; + this.timestamp = timestamp; + console.log("Options " + JSON.stringify(options)); + if (options.classname) { + this.classname = options.classname; + } + if (options.delay) { + this.delay = options.delay; + } + if (options.contextData) { + this.contextData = options.contextData; + } + if (options.type) { + this.type = options.type; + } + } + + public toString(): string { + return this.origin + ',classname:' + this.classname + ", delay:" + this.delay +", context: "+JSON.stringify(this.contextData); + } + +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts index 479f9492c..ff3f16ce3 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/error-message.ts @@ -20,4 +20,14 @@ export class ErrorMessage { error_key: string; args: string[]; message: string; + + static of(messageString:string): ErrorMessage { + const msg = new ErrorMessage() + msg.message = messageString; + return msg; + } + + public toString() { + return this.message; + } } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html index 1f5e246fc..915c81261 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.html @@ -54,6 +54,9 @@ <input type="password" class="form-control" formControlName="password" id="password" [ngClass]="valid('password')" placeholder="{{'users.input.password'|translate}}"> + <div *ngFor="let error of getErrorsFor('password')" class="invalid-feedback"> + {{error}} + </div> </div> <div class="form-group col-md-8"> <label for="confirm_password">{{'users.attributes.confirm_password' |translate}}</label> @@ -87,16 +90,21 @@ <button class="btn btn-primary" type="submit" [attr.disabled]="userForm.valid?null:true">{{'users.add.submit'|translate}}</button> </div> - <div *ngIf="success" class="alert alert-success" role="alert"> - User <a [routerLink]="['/security','users','edit',result?.user_id]">{{result?.user_id}}</a> was added to the list. + <div class="form-group col-md-8"> + <button class="btn btn-primary" (click)="showMessage()">Show Message</button> </div> - <div *ngIf="error" class="alert alert-danger" role="alert" > - <h4 class="alert-heading">{{'users.add.errortitle'|translate}}</h4> - <ng-container *ngFor="let message of errorResult?.error_messages; first as isFirst" > - <hr *ngIf="!isFirst"> + + <ng-template #successTmpl let-userId="user_id"> + User <a [routerLink]="['/security','users','edit',userId]">{{userId}}</a> was added to the list. + </ng-template> + <ng-template #errorTmpl let-messages="error_messages"> + <h4 class="alert-heading">{{'users.add.errortitle1'|translate}}</h4> + <p>{{'users.add.errortitle2'|translate}}</p> + <ng-container *ngFor="let message of messages; first as isFirst" > + <hr> <p>{{message.message}}</p> </ng-container> - </div> + </ng-template> </form> diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts index 1cd5c3671..227e7d9a0 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-add/manage-users-add.component.ts @@ -16,13 +16,15 @@ * under the License. */ -import {Component, OnInit} from '@angular/core'; -import {AbstractControl, FormBuilder, FormControl, FormGroup, ValidationErrors, ValidatorFn} from '@angular/forms'; -import {UserService} from "../../../../services/user.service"; -import {ErrorResult} from "../../../../model/error-result"; +import {Component, OnInit, TemplateRef, ViewChild} from '@angular/core'; +import {FormBuilder} from '@angular/forms'; +import {UserService} from "@app/services/user.service"; +import {ErrorResult} from "@app/model/error-result"; import {catchError} from "rxjs/operators"; -import {UserInfo} from "../../../../model/user-info"; +import {UserInfo} from "@app/model/user-info"; import {ManageUsersBaseComponent} from "../manage-users-base.component"; +import {ToastService} from "@app/services/toast.service"; +import {ErrorMessage} from "@app/model/error-message"; @Component({ selector: 'app-manage-users-add', @@ -31,7 +33,10 @@ import {ManageUsersBaseComponent} from "../manage-users-base.component"; }) export class ManageUsersAddComponent extends ManageUsersBaseComponent implements OnInit { - constructor(userService: UserService, fb: FormBuilder) { + @ViewChild('errorTmpl') public errorTmpl: TemplateRef<any>; + @ViewChild('successTmpl') public successTmpl: TemplateRef<any>; + + constructor(userService: UserService, fb: FormBuilder, private toastService: ToastService) { super(userService, fb); } @@ -61,12 +66,15 @@ export class ManageUsersAddComponent extends ManageUsersBaseComponent implements this.errorResult = error; this.success = false; this.error = true; + this.toastService.showError('manage-users-add',this.errorTmpl,{contextData:this.errorResult}) + return []; // return throwError(error); })).subscribe((user: UserInfo) => { this.result = user; this.success = true; this.error = false; + this.toastService.showSuccess('manage-users-add',this.successTmpl,{contextData:this.result}) this.userForm.reset(this.formInitialValues); }); } @@ -74,7 +82,18 @@ export class ManageUsersAddComponent extends ManageUsersBaseComponent implements - + showMessage() { + this.result=new UserInfo() + this.result.user_id='XXXXX' + const errorResult : ErrorResult = new ErrorResult([ + ErrorMessage.of('Not so good'), + ErrorMessage.of('Completely crap') + ]); + console.log(JSON.stringify(errorResult)); + errorResult.status=422; + this.toastService.showSuccess('manage-users-add',this.successTmpl,{contextData:this.result,delay:1000}) + this.toastService.showError('manage-users-add',this.errorTmpl,{contextData:errorResult,delay:10000}) + } } diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts index 012d70444..708c8e70c 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/security/users/manage-users-base.component.ts @@ -96,6 +96,16 @@ export class ManageUsersBaseComponent { } } + public getErrorsFor(formField:string) : string[] { + let field=this.userForm.get(formField) + if (field) { + if (field.errors) { + return Object.values(field.errors); + } + } + return [] + } + /** * Async validator with debounce time * @constructor diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts index 76bd9845f..a6bb5475b 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/shared.module.ts @@ -26,7 +26,8 @@ import { NgbModalModule, NgbPaginationModule, NgbTooltipModule, - NgbTypeaheadModule + NgbTypeaheadModule, + NgbToastModule } from "@ng-bootstrap/ng-bootstrap"; import {TranslateCompiler, TranslateLoader, TranslateModule} from "@ngx-translate/core"; import {TranslateMessageFormatCompiler} from "ngx-translate-messageformat-compiler"; @@ -35,6 +36,7 @@ import {TranslateHttpLoader} from "@ngx-translate/http-loader"; import {RouterModule} from "@angular/router"; import { WithLoadingPipe } from './with-loading.pipe'; import { StripLoadingPipe } from './strip-loading.pipe'; +import { ToastComponent } from './toast/toast.component'; export { LoadingValue } from './model/loading-value'; export { PageQuery } from './model/page-query'; @@ -45,7 +47,8 @@ export { PageQuery } from './model/page-query'; SortedTableHeaderComponent, SortedTableHeaderRowComponent, WithLoadingPipe, - StripLoadingPipe + StripLoadingPipe, + ToastComponent ], exports: [ CommonModule, @@ -56,17 +59,20 @@ export { PageQuery } from './model/page-query'; NgbAccordionModule, NgbModalModule, NgbTypeaheadModule, + NgbToastModule, PaginatedEntitiesComponent, SortedTableHeaderComponent, SortedTableHeaderRowComponent, WithLoadingPipe, - StripLoadingPipe + StripLoadingPipe, + ToastComponent ], imports: [ CommonModule, RouterModule, NgbPaginationModule, NgbTooltipModule, + NgbToastModule, TranslateModule.forChild({ compiler: { provide: TranslateCompiler, diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts new file mode 100644 index 000000000..1ccb4d7aa --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.spec.ts @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ToastComponent } from './toast.component'; + +describe('ToastComponent', () => { + let component: ToastComponent; + let fixture: ComponentFixture<ToastComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ToastComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ToastComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts new file mode 100644 index 000000000..0813f6c7b --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/modules/shared/toast/toast.component.ts @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Component, OnInit } from '@angular/core'; +import {ToastService} from "@app/services/toast.service"; +import {TemplateRef} from "@angular/core"; +import {AppNotification} from "@app/model/app-notification"; + +@Component({ + selector: 'app-toasts', + template: ` + <ngb-toast + *ngFor="let toast of toastService.toasts" + [class]="toast.classname" + [autohide]="autohide" + [delay]="toast.delay || 5000" + (hidden)="toastService.remove(toast); autohide=true;" + (mouseenter)="autohide = false" + (mouseleave)="autohide = true" + > + <i *ngIf="toast.type=='error'" class="fas fa-exclamation-triangle"></i> + <ng-template [ngIf]="isTemplate(toast)" [ngIfElse]="text"> + <ng-template [ngTemplateOutlet]="toast.body" [ngTemplateOutletContext]="toast.contextData" ></ng-template> + </ng-template> + + <ng-template #text>{{ toast.body }}</ng-template> + </ngb-toast> + `, + styles: [".ngb-toasts{margin:.5em;padding:0.5em;position:fixed;right:2px;top:2px;z-index:1200}" + ], + host: {'[class.ngb-toasts]': 'true'} +}) +export class ToastComponent implements OnInit { + + autohide:boolean=true; + + constructor(public toastService:ToastService) { } + + ngOnInit(): void { + } + + isTemplate(toast:AppNotification) { + console.log("Context data: "+JSON.stringify(toast.contextData)) + return toast.body instanceof TemplateRef; } + +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts new file mode 100644 index 000000000..38c4a5ecd --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.spec.ts @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TestBed } from '@angular/core/testing'; + +import { ToastService } from './toast.service'; + +describe('ToastService', () => { + let service: ToastService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ToastService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts new file mode 100644 index 000000000..b168a0f51 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/app/services/toast.service.ts @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Injectable, TemplateRef } from '@angular/core'; +import {AppNotification} from "@app/model/app-notification"; +import {not} from "rxjs/internal-compatibility"; + +@Injectable({ + providedIn: 'root' +}) +export class ToastService { + + maxNotifications:number=10 + maxHistory:number=100 + toasts:AppNotification[]=[] + toastHistory:AppNotification[]=[] + + constructor() { } + + show(origin:string, textOrTpl: string | TemplateRef<any>, options: any = {}) { + let notification = new AppNotification(origin, textOrTpl, "", options); + this.toasts.push(notification); + this.toastHistory.push(notification); + if (this.toasts.length>this.maxNotifications) { + this.toasts.splice(0, 1); + } + if (this.toastHistory.length>this.maxHistory) { + this.toastHistory.splice(0, 1); + } + console.log("Notification " + notification); + } + + showStandard(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) { + options.classname='bg-primary' + if (!options.delay) { + options.delay=8000 + } + this.show(origin,textOrTpl,options) + } + + showError(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) { + options.classname='bg-warning' + options.type='error' + if (!options.delay) { + options.delay=10000 + } + this.show(origin,textOrTpl,options) + } + + showSuccess(origin:string,textOrTpl:string|TemplateRef<any>, options:any={}) { + options.classname='bg-info' + options.type='success' + if (!options.delay) { + options.delay=8000 + } + this.show(origin,textOrTpl,options) + } + + remove(toast) { + this.toasts = this.toasts.filter(t => t != toast); + } +} diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json index 297c9f21d..0be67d397 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/archiva-web/src/assets/i18n/en.json @@ -99,7 +99,8 @@ "add": { "head": "Add User", "submit": "Add User", - "errortitle": "Could not add the user. Please check the following error messages." + "errortitle1": "Could not add the user!", + "errortitle2": "Please check the following error messages:" }, "edit": { "submit": "Save Changes", |