Compare commits
2 Commits
f47d30a4c8
...
985924086e
| Author | SHA1 | Date | |
|---|---|---|---|
| 985924086e | |||
| bc68777229 |
26
src/main/angular/src/app/api/Notification/Notification.ts
Normal file
26
src/main/angular/src/app/api/Notification/Notification.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import {FromJson} from "../common/types";
|
||||||
|
import {validateDate, validateString} from "../common/validators";
|
||||||
|
|
||||||
|
export class Notification<T> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly uuid: string,
|
||||||
|
readonly title: string,
|
||||||
|
readonly date: Date,
|
||||||
|
readonly type: string,
|
||||||
|
readonly payload: T,
|
||||||
|
) {
|
||||||
|
// -
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJson<T>(fromJson: FromJson<T>): FromJson<Notification<T>> {
|
||||||
|
return (json: any) => new Notification<T>(
|
||||||
|
validateString(json.uuid),
|
||||||
|
validateString(json.title),
|
||||||
|
validateDate(json.date),
|
||||||
|
validateString(json.type),
|
||||||
|
fromJson(json.payload),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {ApiService} from "../common/api.service";
|
||||||
|
import {Notification} from "./Notification";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class NotificationService {
|
||||||
|
|
||||||
|
public visible: boolean = false;
|
||||||
|
|
||||||
|
public notifications: Notification<any>[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly api: ApiService,
|
||||||
|
) {
|
||||||
|
// -
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss(notification: Notification<any>) {
|
||||||
|
this.notifications.splice(this.notifications.indexOf(notification), 1);
|
||||||
|
if (this.notifications.length === 0) {
|
||||||
|
this.visible = false;
|
||||||
|
}
|
||||||
|
// this.api.postNone(['Notification', 'dismiss'], notification.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.visible = !this.visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -13,6 +13,9 @@
|
|||||||
>
|
>
|
||||||
{{ userService.user.name }}
|
{{ userService.user.name }}
|
||||||
</div>
|
</div>
|
||||||
|
<div #notificationsButton class="mainMenuItem mainMenuItemRight mainMenuNotifications" [class.mainMenuNotifications_blink]="blink" *ngIf="notificationService.notifications.length" (click)="toggleMenu()">
|
||||||
|
{{ notificationService.notifications.length }}
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="userService.user === null">
|
<ng-container *ngIf="userService.user === null">
|
||||||
@ -23,3 +26,22 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<router-outlet (activate)="onActivate($event)"/>
|
<router-outlet (activate)="onActivate($event)"/>
|
||||||
|
|
||||||
|
<ng-template #tmp>
|
||||||
|
<div class="notificationOverlayEntry" *ngFor="let notification of notificationService.notifications">
|
||||||
|
<div class="title">
|
||||||
|
{{ notification.title }}
|
||||||
|
</div>
|
||||||
|
<div class="dismiss" (click)="notificationService.dismiss(notification)">
|
||||||
|
X
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div #notificationsOverlayMobile class="notificationsOverlay notificationsOverlayMobile" *ngIf="notificationService.visible" (window:resize)="onResize()">
|
||||||
|
<ng-container *ngTemplateOutlet="tmp"></ng-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div #notificationsOverlayDesktop class="notificationsOverlay notificationsOverlayDesktop" *ngIf="notificationService.visible" (window:resize)="onResize()">
|
||||||
|
<ng-container *ngTemplateOutlet="tmp"></ng-container>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -23,4 +23,76 @@
|
|||||||
background-color: lightskyblue;
|
background-color: lightskyblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mainMenuNotifications {
|
||||||
|
background-color: lightskyblue;
|
||||||
|
min-width: 2.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mainMenuNotifications_blink {
|
||||||
|
color: white;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsOverlay {
|
||||||
|
position: fixed;
|
||||||
|
background-color: #eaeaea;
|
||||||
|
border: 1px solid #aeaeae;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 @space @space;
|
||||||
|
box-shadow: 0 0.5em 1em rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
|
.notificationOverlayEntry {
|
||||||
|
padding: @halfSpace;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dismiss {
|
||||||
|
float: right;
|
||||||
|
color: red;
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dismiss:hover {
|
||||||
|
background-color: red;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationOverlayEntry:hover:not(:has(div:hover)) {
|
||||||
|
background-color: lightskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsOverlayMobile {
|
||||||
|
width: calc(100% - 2 * @space);
|
||||||
|
margin: 0 @space;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsOverlayDesktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
|
||||||
|
.notificationsOverlayMobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificationsOverlayDesktop {
|
||||||
|
display: unset;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,50 @@
|
|||||||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
import {AfterViewInit, Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||||
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
||||||
import {NgIf} from "@angular/common";
|
import {JsonPipe, NgForOf, NgIf, NgTemplateOutlet} from "@angular/common";
|
||||||
import {UserService} from "./api/User/user.service";
|
import {UserService} from "./api/User/user.service";
|
||||||
import {Subscription} from "rxjs";
|
import {Subscription, timer} from "rxjs";
|
||||||
|
import {NotificationService} from "./api/Notification/notification.service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf],
|
imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf, NgForOf, JsonPipe, NgTemplateOutlet],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.less'
|
styleUrl: './app.component.less'
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
private readonly subs: Subscription[] = [];
|
private readonly subs: Subscription[] = [];
|
||||||
|
|
||||||
protected menuVisible = true;
|
protected menuVisible = true;
|
||||||
|
|
||||||
|
protected blink: boolean = true;
|
||||||
|
|
||||||
|
private _notificationsButton!: ElementRef;
|
||||||
|
@ViewChild("notificationsButton")
|
||||||
|
set notificationsButton(e: ElementRef) {
|
||||||
|
this._notificationsButton = e;
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notificationsOverlayMobile!: ElementRef;
|
||||||
|
@ViewChild("notificationsOverlayMobile")
|
||||||
|
set notificationsOverlayMobile(e: ElementRef) {
|
||||||
|
this._notificationsOverlayMobile = e;
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _notificationsOverlayDesktop!: ElementRef;
|
||||||
|
@ViewChild("notificationsOverlayDesktop")
|
||||||
|
set notificationsOverlayDesktop(e: ElementRef) {
|
||||||
|
this._notificationsOverlayDesktop = e;
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly router: Router,
|
protected readonly router: Router,
|
||||||
protected readonly userService: UserService,
|
protected readonly userService: UserService,
|
||||||
|
protected readonly notificationService: NotificationService,
|
||||||
) {
|
) {
|
||||||
// -
|
// -
|
||||||
}
|
}
|
||||||
@ -29,7 +54,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// -
|
this.subs.push(timer(500, 500).subscribe(() => this.blink = !this.blink))
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.onResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
@ -37,4 +66,27 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.length = 0;
|
this.subs.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleMenu() {
|
||||||
|
this.notificationService.toggle();
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
onResize() {
|
||||||
|
if (this._notificationsButton) {
|
||||||
|
const button = this._notificationsButton.nativeElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (this._notificationsOverlayMobile) {
|
||||||
|
const overlayMobile = this._notificationsOverlayMobile.nativeElement.style;
|
||||||
|
overlayMobile.top = button.bottom + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._notificationsOverlayDesktop) {
|
||||||
|
const overlayDesktopRect = this._notificationsOverlayDesktop.nativeElement.getBoundingClientRect();
|
||||||
|
const overlayDesktopStyle = this._notificationsOverlayDesktop.nativeElement.style;
|
||||||
|
overlayDesktopStyle.top = button.bottom + 'px';
|
||||||
|
overlayDesktopStyle.left = button.right - overlayDesktopRect.width + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user