Compare commits

..

2 Commits

Author SHA1 Message Date
985924086e NotificationsOverlay UI 2024-11-08 11:11:17 +01:00
bc68777229 NotificationsOverlay WIP 2024-11-08 10:52:39 +01:00
5 changed files with 210 additions and 6 deletions

View 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),
);
}
}

View File

@ -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;
}
}

View File

@ -13,6 +13,9 @@
>
{{ userService.user.name }}
</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 *ngIf="userService.user === null">
@ -23,3 +26,22 @@
</div>
<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>

View File

@ -23,4 +23,76 @@
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;
}
}

View File

@ -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 {NgIf} from "@angular/common";
import {JsonPipe, NgForOf, NgIf, NgTemplateOutlet} from "@angular/common";
import {UserService} from "./api/User/user.service";
import {Subscription} from "rxjs";
import {Subscription, timer} from "rxjs";
import {NotificationService} from "./api/Notification/notification.service";
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf],
imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf, NgForOf, JsonPipe, NgTemplateOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.less'
})
export class AppComponent implements OnInit, OnDestroy {
export class AppComponent implements OnInit, OnDestroy, AfterViewInit {
private readonly subs: Subscription[] = [];
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(
protected readonly router: Router,
protected readonly userService: UserService,
protected readonly notificationService: NotificationService,
) {
// -
}
@ -29,7 +54,11 @@ export class AppComponent implements OnInit, OnDestroy {
}
ngOnInit(): void {
// -
this.subs.push(timer(500, 500).subscribe(() => this.blink = !this.blink))
}
ngAfterViewInit(): void {
this.onResize();
}
ngOnDestroy(): void {
@ -37,4 +66,27 @@ export class AppComponent implements OnInit, OnDestroy {
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';
}
}
}
}