diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b10221cda7c5146def0ffe0a71332976b2f1ef06..1673c4d21defdff88a69d355abedeed9f56db4aa 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -48,6 +48,7 @@ import { CenterViewInverseComponent } from './components/network/network-menu-le import { LicenseAgreementComponent } from './components/license-agreement/license-agreement.component'; import { QuickDrugTargetComponent } from './components/quick-drug-target/quick-drug-target.component'; import { QuickDrugComponent } from './components/quick-drug/quick-drug.component'; +import { ToastComponent } from './components/toast/toast.component'; @NgModule({ @@ -77,6 +78,7 @@ import { QuickDrugComponent } from './components/quick-drug/quick-drug.component LicenseAgreementComponent, QuickDrugTargetComponent, QuickDrugComponent, + ToastComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/analysis-panel/analysis-panel.component.html b/src/app/components/analysis-panel/analysis-panel.component.html index d79767836322e73f14181c3c5c70a79338f31661..efd5cb29f01cc5011e34746e63c99edfc9ce14c1 100644 --- a/src/app/components/analysis-panel/analysis-panel.component.html +++ b/src/app/components/analysis-panel/analysis-panel.component.html @@ -20,7 +20,7 @@ <a (click)="close()" class="card-header-icon" aria-label="close"> <span class="icon" title="Close analysis"> - <i class="fas fa-times" aria-hidden="true"></i> + <i class="fas fa-times color-danger" aria-hidden="true"></i> </span> </a> </header> @@ -132,7 +132,7 @@ ></i> <i *ngIf="!task.info.parameters.includeIndirectDrugs" - class="fa fa-times" + class="fa fa-times color-danger" ></i> </td> </tr> @@ -150,7 +150,7 @@ ></i> <i *ngIf="!task.info.parameters.includeNonApprovedDrugs" - class="fa fa-times" + class="fa fa-times color-danger" ></i> </td> </tr> @@ -190,7 +190,7 @@ <tr> <td>Include indirect drugs</td> <td> - <i class="fa fa-times"></i> + <i class="fa fa-times color-danger"></i> </td> </tr> <tr> diff --git a/src/app/components/toast/toast.component.html b/src/app/components/toast/toast.component.html new file mode 100644 index 0000000000000000000000000000000000000000..08d233ba5cb760d42af51d0be0210c9fbb82473e --- /dev/null +++ b/src/app/components/toast/toast.component.html @@ -0,0 +1,14 @@ +<div class="toast-holder"> + <div *ngFor="let toast of toasts | keyvalue" id="{{toastIdPrefix + toast.key}}"> + <div class="toast {{toast.value.type}}" [class.fade-in]="isSeen(toast.key)"> + <a (click)="close(toast.key)" aria-label="close" class="close"> + <span class="icon" title="Close analysis"> + <i class="fas fa-times" aria-hidden="true"></i> + </span> + </a> + <p>{{toast.value.message}}</p> + </div> + </div> +</div> + + diff --git a/src/app/components/toast/toast.component.scss b/src/app/components/toast/toast.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..257775c4bd06b12faad3c9df281fb7cfccc9b56f --- /dev/null +++ b/src/app/components/toast/toast.component.scss @@ -0,0 +1,43 @@ +@import "src/stylesheets/variables"; + +.toast-holder { + z-index: $toast-z; + top: 3rem; + position: fixed; + left: 0; + right: 0; +} + +.toast { + position: relative; + max-width: 60vw; + padding: 10px 15px; + border-radius: 0.25rem; + box-shadow: 0 0.5em 1em -0.125em hsla(0, 0%, 4%, 0.1), + 0 0 0 1px hsla(0, 0%, 4%, 0.02); + margin: 0 auto; + &.danger { + color: var(--drgstn-text-secondary); + background-color: var(--drgstn-danger); + } + &.success { + color: var(--drgstn-text-secondary) !important; + background-color: var(--drgstn-success); + } + &.warning { + color: var(--drgstn-text-secondary); + background-color: var(--drgstn-warning); + } + &.info { + color: var(--drgstn-text-secondary); + background-color: var(--drgstn-info); + } + .close { + display: inline; + float: right; + } +} + +.fa-times { + color: white; +} diff --git a/src/app/components/toast/toast.component.spec.ts b/src/app/components/toast/toast.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..bdabdad533d58389b5b666a473077b6f66b2944e --- /dev/null +++ b/src/app/components/toast/toast.component.spec.ts @@ -0,0 +1,25 @@ +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/src/app/components/toast/toast.component.ts b/src/app/components/toast/toast.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..53bbf9784f418a9f4881058cf1c051dfb734f83c --- /dev/null +++ b/src/app/components/toast/toast.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { LiveToasts } from 'src/app/interfaces'; +import { ToastService } from 'src/app/services/toast/toast.service'; + +@Component({ + selector: 'app-toast', + templateUrl: './toast.component.html', + styleUrls: ['./toast.component.scss'] +}) +export class ToastComponent implements OnInit { + + public toasts: LiveToasts = {}; + public seen: any = new Set(); + + public toastIdPrefix = 'drugstone-toast-id-'; + + constructor(public toast: ToastService) { } + + ngOnInit(): void { + this.toast.getToasts$.forEach(data => { + this.toasts = data; + }) + } + + public isSeen(id) { + console.log(this.seen.has(id)) + if (this.seen.has(id)) { + return true + } else { + this.seen.add(id); + return false + } + } + + public close(id: number) { + this.toast.deleteToast(id); + document.getElementById(`${this.toastIdPrefix}` + id).remove(); + } + +} diff --git a/src/app/interfaces.ts b/src/app/interfaces.ts index 215f50184303032584bced176d14727dc252a3ed..a46d4eeaec99113d5dfd0bb524263de8f55aa8e1 100644 --- a/src/app/interfaces.ts +++ b/src/app/interfaces.ts @@ -257,3 +257,12 @@ export interface Algorithm { slug: AlgorithmType | QuickAlgorithmType; name: string; } + +export interface Toast { + message: string; + type: 'success' | 'info' | 'warning' | 'danger' +} + +export interface LiveToasts { + [id: number]: Toast +} diff --git a/src/app/pages/explorer-page/explorer-page.component.html b/src/app/pages/explorer-page/explorer-page.component.html index 788dfcab121d839455b647aa0a6a35f8f2d3e509..7f918b1393ca88383c80cc7110e41213eaef8149 100644 --- a/src/app/pages/explorer-page/explorer-page.component.html +++ b/src/app/pages/explorer-page/explorer-page.component.html @@ -525,4 +525,6 @@ </div> </div> </div> + + <app-toast></app-toast> </div> diff --git a/src/app/services/analysis/analysis.service.ts b/src/app/services/analysis/analysis.service.ts index 0b7e031536f281de9d1008b814c7def57f5981ef..b0247cf101a2b3cf17de2e3327ed3f373f59aca5 100644 --- a/src/app/services/analysis/analysis.service.ts +++ b/src/app/services/analysis/analysis.service.ts @@ -7,6 +7,7 @@ import {Injectable} from '@angular/core'; import {NetexControllerService} from '../netex-controller/netex-controller.service'; import {DrugstoneConfigService} from "../drugstone-config/drugstone-config.service"; import {NetworkHandlerService} from "../network-handler/network-handler.service"; +import { ToastService } from '../toast/toast.service'; export type AlgorithmType = 'trustrank' @@ -72,7 +73,9 @@ export class AnalysisService { private tissues: Tissue[] = []; - constructor(private http: HttpClient, + constructor( + public toast: ToastService, + private http: HttpClient, public netex: NetexControllerService, public drugstoneConfig: DrugstoneConfigService, public networkHandler: NetworkHandlerService @@ -217,14 +220,9 @@ export class AnalysisService { async startQuickAnalysis(isSuper: boolean, algorithm: QuickAlgorithmType) { if (!this.canLaunchTask()) { - toast({ + this.toast.setNewToast({ message: `You can only run ${MAX_TASKS} tasks at once. Please wait for one of them to finish or delete it from the task list.`, - duration: 5000, - dismissible: true, - pauseOnHover: true, - type: 'is-danger', - position: 'top-center', - animate: {in: 'fadeIn', out: 'fadeOut'} + type: 'danger' }); return; } @@ -264,29 +262,19 @@ export class AnalysisService { localStorage.setItem(this.tokensCookieKey, JSON.stringify(this.tokens)); this.startWatching(); - toast({ + this.toast.setNewToast({ message: 'Quick analysis started. This may take a while.' + ' Once the computation finished you can view the results in the task list to the right.', - duration: 10000, - dismissible: true, - pauseOnHover: true, - type: 'is-success', - position: 'top-center', - animate: {in: 'fadeIn', out: 'fadeOut'} + type: 'success' }); return { taskId: resp.token, algorithm: algorithm, target: target, params: parameters } } async startAnalysis(algorithm, target: 'drug' | 'drug-target', parameters) { if (!this.canLaunchTask()) { - toast({ + this.toast.setNewToast({ message: `You can only run ${MAX_TASKS} tasks at once. Please wait for one of them to finish or delete it from the task list.`, - duration: 5000, - dismissible: true, - pauseOnHover: true, - type: 'is-danger', - position: 'top-center', - animate: {in: 'fadeIn', out: 'fadeOut'} + type: 'danger', }); return ''; } @@ -311,20 +299,15 @@ export class AnalysisService { let toastType; if (status === 'DONE') { toastMessage = 'Computation finished successfully. Click the task in the task list to view the results.'; - toastType = 'is-success'; + toastType = 'success'; } else if (status === 'FAILED') { toastMessage = 'Computation failed.'; - toastType = 'is-danger'; + toastType = 'danger'; } - toast({ + this.toast.setNewToast({ message: toastMessage, - duration: 5000, - dismissible: true, - pauseOnHover: true, type: toastType, - position: 'top-center', - animate: {in: 'fadeIn', out: 'fadeOut'} }); } diff --git a/src/app/services/toast/toast.service.spec.ts b/src/app/services/toast/toast.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0413db848d0436b5ed5939ae572827fbc4ed60c --- /dev/null +++ b/src/app/services/toast/toast.service.spec.ts @@ -0,0 +1,16 @@ +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/src/app/services/toast/toast.service.ts b/src/app/services/toast/toast.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..7b79b48bc98f3ca27909c707222526ed9fd766c5 --- /dev/null +++ b/src/app/services/toast/toast.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { Subject } from 'rxjs'; +import { LiveToasts, Toast } from 'src/app/interfaces'; + +@Injectable({ + providedIn: 'root' +}) +export class ToastService { + + constructor() { } + + public toasts: Toast[] = []; + + // id of toasts will be counted up + private id = 0; + public liveToasts: LiveToasts = {}; + + private getToasts = new Subject<LiveToasts>(); + + public setNewToast(toast: Toast) { + this.liveToasts[this.id] = toast; + this.getToasts.next(this.liveToasts); + this.setTimer(this.id); + this.id ++; + } + + public setTimer(id: number) { + setTimeout(() => { + this.deleteToast(id); + }, 10000); + } + + public deleteToast(id: number) { + if (this.liveToasts.hasOwnProperty(id)) { + delete this.liveToasts[id]; + } + } + + get getToasts$ () { + return this.getToasts.asObservable(); + } + +} diff --git a/src/stylesheets/theme-styles.scss b/src/stylesheets/theme-styles.scss index 6b2ffb32353348e813082e6ffdfa6a70b31192b8..62bdb4dcc8f6bac843bf55aa1f3c7a5275f0e8e2 100644 --- a/src/stylesheets/theme-styles.scss +++ b/src/stylesheets/theme-styles.scss @@ -200,7 +200,7 @@ } - .fa-exclamation-triangle, .fa-times, .help, .delete:after, .delete:before, .modal-close:after, .modal-close:before { + .fa-exclamation-triangle, .color-danger, .help, .delete:after, .delete:before, .modal-close:after, .modal-close:before { color: var(--drgstn-danger) !important; } diff --git a/src/stylesheets/variables.scss b/src/stylesheets/variables.scss index 221d033400e869870e46fcaa0fb5d0d5aa7c07a9..3995313f2f8cdcf96a9abb2fb761e98313671121 100644 --- a/src/stylesheets/variables.scss +++ b/src/stylesheets/variables.scss @@ -62,3 +62,5 @@ $b-text-small-font-size: 14px; $b-text-smaller-font-size: 12px; $text-normal-font-size: 12px; $text-small-font-size: 11px; + +$toast-z: 100;