From 00b444388bac88f299b4ff3eed5f1afc7ac774b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Mon, 19 Jan 2026 14:34:15 +0100 Subject: [PATCH 1/2] snackbar progressbar temp (progressbar in wrong place) --- .../shared/snackbar/snackbar.component.html | 9 +++- .../shared/snackbar/snackbar.component.scss | 8 ++++ .../app/shared/snackbar/snackbar.component.ts | 48 +++++++++++++++++-- .../app/shared/snackbar/snackbar.service.ts | 22 +++++---- 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.html b/frontend/Exence/src/app/shared/snackbar/snackbar.component.html index 60ce243..52a92f5 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.html +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.html @@ -1,4 +1,4 @@ -
+
@switch (data.type) { @@ -43,4 +43,11 @@ close
+ +
+
+
diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss b/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss index 6d56793..66a1d35 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss @@ -18,4 +18,12 @@ mat-icon { flex-shrink: 0; +} + +.progressbar { + height: 2px; + + div { + background-color: var(--primary-color); + } } \ No newline at end of file diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts b/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts index 03f363e..5bd5351 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts @@ -1,10 +1,10 @@ -import { Component, inject } from '@angular/core'; -import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar'; -import { SnackbarType } from './snackbar.service'; -import { SvgIcons } from '../svg-icons/svg-icons'; +import { Component, computed, DestroyRef, inject, signal } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar'; import { MatTooltipModule } from '@angular/material/tooltip'; -import { MatButtonModule } from '@angular/material/button'; +import { SvgIcons } from '../svg-icons/svg-icons'; +import { SnackbarType } from './snackbar.service'; export interface SnackbarData { message: string; @@ -17,6 +17,8 @@ export interface SnackbarData { }; } +export const SNACKBAR_DISMISS_DURATION = 5000; + @Component({ selector: 'ex-snackbar', templateUrl: './snackbar.component.html', @@ -29,13 +31,30 @@ export interface SnackbarData { }) export class SnackbarComponent { private readonly snackbarRef = inject(MatSnackBarRef); + private readonly destroyRef = inject(DestroyRef); readonly data = inject(MAT_SNACK_BAR_DATA); + private animationFrameId?: number; + private startTime?: number; + readonly errorType = SnackbarType.Error; readonly warnType = SnackbarType.Warning; readonly infoType = SnackbarType.Info; readonly successType = SnackbarType.Success; + progress = signal(0); + progressPercent = computed(() => Math.floor((this.progress() / SNACKBAR_DISMISS_DURATION) * 100)); + + + constructor() { + this.startProgressAnimation(); + this.destroyRef.onDestroy(() => { + if (this.animationFrameId) { + cancelAnimationFrame(this.animationFrameId); + } + }); + } + getCssClass(): string { switch (this.data.type) { case SnackbarType.Error: @@ -52,6 +71,25 @@ export class SnackbarComponent { onClose(): void { this.snackbarRef.dismiss(); } + + private startProgressAnimation(): void { + this.startTime = performance.now(); + + const animate = (currentTime: number): void => { + if (!this.startTime) return; + + const elapsed = currentTime - this.startTime; + const newProgress = Math.min(elapsed, SNACKBAR_DISMISS_DURATION); + + this.progress.set(newProgress); + + if (newProgress < SNACKBAR_DISMISS_DURATION) { + this.animationFrameId = requestAnimationFrame(animate); + } + }; + + this.animationFrameId = requestAnimationFrame(animate); + } } diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts b/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts index 818306b..f057121 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from '@angular/core'; import { MatSnackBar, MatSnackBarConfig, SimpleSnackBar } from '@angular/material/snack-bar'; -import { SnackbarComponent, SnackbarData } from './snackbar.component'; +import { SNACKBAR_DISMISS_DURATION, SnackbarComponent, SnackbarData } from './snackbar.component'; export const enum SnackbarType { Error, @@ -20,7 +20,11 @@ export class SnackbarService { }; showCustom(data: SnackbarData): void { - this.snackbar.openFromComponent(SnackbarComponent, { ...this.snackbarConfig, data }); + this.snackbar.openFromComponent(SnackbarComponent, { + ...this.snackbarConfig, + data, + duration: SNACKBAR_DISMISS_DURATION, + }); } showError(message: string): void { @@ -29,7 +33,8 @@ export class SnackbarService { data: { message, type: SnackbarType.Error - } satisfies SnackbarData + } satisfies SnackbarData, + duration: SNACKBAR_DISMISS_DURATION, }); } @@ -39,29 +44,30 @@ export class SnackbarService { data: { message, type: SnackbarType.Warning - } satisfies SnackbarData + } satisfies SnackbarData, + duration: SNACKBAR_DISMISS_DURATION, }); } showInfo(message: string): void { this.snackbar.openFromComponent(SnackbarComponent, { ...this.snackbarConfig, - duration: 10000, data: { message, type: SnackbarType.Info - } satisfies SnackbarData + } satisfies SnackbarData, + duration: SNACKBAR_DISMISS_DURATION, }); } showSuccess(message: string): void { this.snackbar.openFromComponent(SnackbarComponent, { ...this.snackbarConfig, - duration: 10000, data: { message, type: SnackbarType.Success - } satisfies SnackbarData + } satisfies SnackbarData, + duration: SNACKBAR_DISMISS_DURATION, }); } } \ No newline at end of file From ea7bbf21dadeb7c7b7fac541637bcadaad2a36b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20Nagy?= Date: Mon, 19 Jan 2026 18:40:09 +0100 Subject: [PATCH 2/2] #EX-218: Add progress bar and auto dismiss to snackbar --- .../shared/snackbar/snackbar.component.html | 20 +++++++++------- .../shared/snackbar/snackbar.component.scss | 24 +++++++++++++++++-- .../app/shared/snackbar/snackbar.component.ts | 21 +++++++++------- .../app/shared/snackbar/snackbar.service.ts | 9 ++++--- .../src/styles/components/snackbar.scss | 12 ++++++++-- frontend/Exence/src/styles/styles.scss | 4 ++-- 6 files changed, 62 insertions(+), 28 deletions(-) diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.html b/frontend/Exence/src/app/shared/snackbar/snackbar.component.html index 52a92f5..94d946a 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.html +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.html @@ -1,23 +1,26 @@ -
-
+
+
@switch (data.type) { @case (errorType) { - error + error } @case (warnType) { - warning + warning } @case (infoType) { - info + info } @case (successType) { - check + check } }
@if (data.action) { @@ -39,6 +42,7 @@ mat-icon-button matSnackBarAction (click)="onClose()" + class="close-button" > close @@ -47,7 +51,7 @@
diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss b/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss index 66a1d35..c259182 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.scss @@ -4,12 +4,31 @@ --mat-icon-button-icon-color: currentColor; } +section { + border-radius: 5px; + background-color: lighten(var(--app-card-color), 10%); + box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12); + + transition: opacity 300ms ease-out, box-shadow 300ms ease-out; + opacity: 1; + + overflow: hidden; + + &.fade-out { + opacity: 0; + box-shadow: unset; + } +} + .notification { &-error { color: var(--error-color); } - &-success { - color: var(--primary-color); + &-success, &-info { + color: var(--default-text-color); + .close-button, .type-icon { + color: var(--primary-color); + } } &-warning { color: var(--warn-color); @@ -24,6 +43,7 @@ mat-icon { height: 2px; div { + transition: width 0.1s; background-color: var(--primary-color); } } \ No newline at end of file diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts b/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts index 5bd5351..8a1b402 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, DestroyRef, inject, signal } from '@angular/core'; +import { Component, DestroyRef, inject, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar'; @@ -35,7 +35,6 @@ export class SnackbarComponent { readonly data = inject(MAT_SNACK_BAR_DATA); private animationFrameId?: number; - private startTime?: number; readonly errorType = SnackbarType.Error; readonly warnType = SnackbarType.Warning; @@ -43,8 +42,7 @@ export class SnackbarComponent { readonly successType = SnackbarType.Success; progress = signal(0); - progressPercent = computed(() => Math.floor((this.progress() / SNACKBAR_DISMISS_DURATION) * 100)); - + fadeOutStarted = signal(false); constructor() { this.startProgressAnimation(); @@ -73,17 +71,22 @@ export class SnackbarComponent { } private startProgressAnimation(): void { - this.startTime = performance.now(); + const startTime = performance.now(); const animate = (currentTime: number): void => { - if (!this.startTime) return; + if (!startTime) return; - const elapsed = currentTime - this.startTime; - const newProgress = Math.min(elapsed, SNACKBAR_DISMISS_DURATION); + const elapsed = currentTime - startTime; + const progressValue = Math.min(elapsed, SNACKBAR_DISMISS_DURATION); + const newProgress = Math.floor((progressValue / SNACKBAR_DISMISS_DURATION) * 100); this.progress.set(newProgress); - if (newProgress < SNACKBAR_DISMISS_DURATION) { + if (progressValue >= SNACKBAR_DISMISS_DURATION - 300) { + this.fadeOutStarted.set(true); + } + + if (progressValue < SNACKBAR_DISMISS_DURATION) { this.animationFrameId = requestAnimationFrame(animate); } }; diff --git a/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts b/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts index f057121..6165e4e 100644 --- a/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts +++ b/frontend/Exence/src/app/shared/snackbar/snackbar.service.ts @@ -16,7 +16,10 @@ export class SnackbarService { private readonly snackbar = inject(MatSnackBar); private readonly snackbarConfig: MatSnackBarConfig = { - panelClass: 'custom-snackbar' + panelClass: 'custom-snackbar', + duration: SNACKBAR_DISMISS_DURATION, + horizontalPosition: 'right', + verticalPosition: 'top', }; showCustom(data: SnackbarData): void { @@ -34,7 +37,6 @@ export class SnackbarService { message, type: SnackbarType.Error } satisfies SnackbarData, - duration: SNACKBAR_DISMISS_DURATION, }); } @@ -45,7 +47,6 @@ export class SnackbarService { message, type: SnackbarType.Warning } satisfies SnackbarData, - duration: SNACKBAR_DISMISS_DURATION, }); } @@ -56,7 +57,6 @@ export class SnackbarService { message, type: SnackbarType.Info } satisfies SnackbarData, - duration: SNACKBAR_DISMISS_DURATION, }); } @@ -67,7 +67,6 @@ export class SnackbarService { message, type: SnackbarType.Success } satisfies SnackbarData, - duration: SNACKBAR_DISMISS_DURATION, }); } } \ No newline at end of file diff --git a/frontend/Exence/src/styles/components/snackbar.scss b/frontend/Exence/src/styles/components/snackbar.scss index f295447..cef2a92 100644 --- a/frontend/Exence/src/styles/components/snackbar.scss +++ b/frontend/Exence/src/styles/components/snackbar.scss @@ -1,3 +1,5 @@ +@import "../imports.scss"; + mat-snack-bar-container.mat-mdc-snack-bar-container.custom-snackbar { .mdc-snackbar__label { --mat-snack-bar-supporting-text-size: 16px; @@ -5,6 +7,12 @@ mat-snack-bar-container.mat-mdc-snack-bar-container.custom-snackbar { } .mat-mdc-snackbar-surface { - --mat-snack-bar-container-color: var(--app-card-color); - } + background-color: unset; + box-shadow: unset; + padding: 0; + + .mat-mdc-snack-bar-label { + padding: 0; + } + } } \ No newline at end of file diff --git a/frontend/Exence/src/styles/styles.scss b/frontend/Exence/src/styles/styles.scss index 5aedb74..a8024bd 100644 --- a/frontend/Exence/src/styles/styles.scss +++ b/frontend/Exence/src/styles/styles.scss @@ -67,7 +67,7 @@ a { } hr { - background-color: lighten(--secondary-color, 45%); + background-color: lighten(var(--secondary-color), 45%); height: 1px; border: none; opacity: 0.36; @@ -122,4 +122,4 @@ hr { mat-card.mat-mdc-card.categories mat-card-header .mat-mdc-card-header-text { width: 100%; -} +} \ No newline at end of file