chore: fix memory leaks, add aria-labels, improve type safety and logging (#2764)

This commit is contained in:
ACX
2026-02-15 09:42:43 -07:00
committed by GitHub
parent 0358667323
commit 2c391cb1f4
9 changed files with 36 additions and 24 deletions

View File

@@ -16,6 +16,7 @@ import org.booklore.service.metadata.BookReviewUpdateService;
import org.booklore.service.metadata.MetadataRefreshService;
import jakarta.persistence.EntityNotFoundException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -24,6 +25,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Service
@AllArgsConstructor
public class BookReviewService {
@@ -67,7 +69,8 @@ public class BookReviewService {
.map(mapper::toDto)
.collect(Collectors.toList());
}
} catch (Exception ignored) {
} catch (Exception e) {
log.warn("Failed to auto-fetch reviews for book {}: {}", bookId, e.getMessage());
}
return existingReviews;

View File

@@ -32,14 +32,14 @@
</div>
@if (_isSeriesViewActive) {
<p-button [rounded]="true" icon="pi pi-info" class="info-btn" (click)="openSeriesInfo()"></p-button>
<p-button [rounded]="true" icon="pi pi-info" class="info-btn" (click)="openSeriesInfo()" [ariaLabel]="'book.card.alt.viewSeriesInfo' | transloco"></p-button>
} @else {
<a [routerLink]="urlHelper.getBookUrl(book)"><p-button [rounded]="true" icon="pi pi-info" class="info-btn">
</p-button></a>
<a [routerLink]="urlHelper.getBookUrl(book)"><p-button [rounded]="true" icon="pi pi-info" class="info-btn" [ariaLabel]="'book.card.alt.viewDetails' | transloco">
</p-button></a>
}
@if (!_isSeriesViewActive && hasDigitalFile()) {
<p-button [rounded]="true" [icon]="_isAudiobook ? 'pi pi-play' : 'pi pi-book'" class="read-btn" (click)="readBook(book)"></p-button>
<p-button [rounded]="true" [icon]="_isAudiobook ? 'pi pi-play' : 'pi pi-book'" class="read-btn" (click)="readBook(book)" [ariaLabel]="_isAudiobook ? ('book.card.alt.playAudiobook' | transloco) : ('book.card.alt.readBook' | transloco)"></p-button>
}
@if (isCheckboxEnabled) {
@@ -103,7 +103,8 @@
size="small"
[text]="true"
(click)="onMenuToggle($event, menu)"
icon="pi pi-ellipsis-v">
icon="pi pi-ellipsis-v"
[ariaLabel]="'book.card.alt.bookMenu' | transloco">
</p-button>
</div>
}

View File

@@ -13,7 +13,7 @@ import {MetadataRefreshType} from '../../../../metadata/model/request/metadata-r
import {UrlHelperService} from '../../../../../shared/service/url-helper.service';
import {NgClass} from '@angular/common';
import {User, UserService} from '../../../../settings/user-management/user.service';
import {filter, Subject} from 'rxjs';
import {filter, Subject, Subscription} from 'rxjs';
import {EmailService} from '../../../../settings/email-v2/email.service';
import {TieredMenu} from 'primeng/tieredmenu';
import {Router} from '@angular/router';
@@ -101,7 +101,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy {
showBookTypePill = true;
private overlayPrefSub?: any;
private overlayPrefSub?: Subscription;
ngOnInit(): void {
this.computeAllMemoizedValues();

View File

@@ -1,4 +1,4 @@
import {Component, inject, OnInit, ViewChild} from '@angular/core';
import {Component, DestroyRef, inject, OnInit, ViewChild} from '@angular/core';
import {FileSelectEvent, FileUpload, FileUploadHandlerEvent} from 'primeng/fileupload';
import {Button} from 'primeng/button';
import {AsyncPipe} from '@angular/common';
@@ -16,6 +16,7 @@ import {HttpClient, HttpEventType, HttpRequest} from '@angular/common/http';
import {Tooltip} from 'primeng/tooltip';
import {AppSettingsService} from '../../service/app-settings.service';
import {filter, take} from 'rxjs/operators';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {AppSettings} from '../../model/app-settings.model';
import {SelectButton} from 'primeng/selectbutton';
import {DynamicDialogRef} from 'primeng/dynamicdialog';
@@ -62,6 +63,7 @@ export class BookUploaderComponent implements OnInit {
private readonly http = inject(HttpClient);
private readonly ref = inject(DynamicDialogRef);
private readonly t = inject(TranslocoService);
private readonly destroyRef = inject(DestroyRef);
readonly libraryState$: Observable<LibraryState> = this.libraryService.libraryState$;
appSettings$: Observable<AppSettings | null> = this.appSettingsService.appSettings$;
@@ -85,7 +87,7 @@ export class BookUploaderComponent implements OnInit {
this.maxFileSizeDisplay = `${maxSizeMb} MB`;
});
this.libraryState$.subscribe(state => {
this.libraryState$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(state => {
if (state?.libraries?.length !== 1 || this.selectedLibrary) {
return;
}

View File

@@ -60,6 +60,7 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
return this.router.url.split('?')[0] === this.item.routerLink[0];
}
private userStateSubscription: Subscription;
menuSourceSubscription: Subscription;
menuResetSubscription: Subscription;
private routerSubscription: Subscription;
@@ -71,7 +72,7 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
private dialogLauncher: DialogLauncherService,
private bookDialogHelperService: BookDialogHelperService
) {
this.userService.userState$.subscribe(userState => {
this.userStateSubscription = this.userService.userState$.subscribe(userState => {
if (userState?.user) {
this.canManipulateLibrary = userState.user.permissions.canManageLibrary;
this.admin = userState.user.permissions.admin;
@@ -112,15 +113,10 @@ export class AppMenuitemComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
if (this.menuSourceSubscription) {
this.menuSourceSubscription.unsubscribe();
}
if (this.menuResetSubscription) {
this.menuResetSubscription.unsubscribe();
}
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
this.userStateSubscription?.unsubscribe();
this.menuSourceSubscription?.unsubscribe();
this.menuResetSubscription?.unsubscribe();
this.routerSubscription?.unsubscribe();
}
toggleExpand(key: string) {

View File

@@ -57,7 +57,7 @@
<li>
<button
class="topbar-item"
(click)="shouldShowStatsMenu ? statsMenu.toggle($event) : handleStatsButtonClick($event)"
(click)="shouldShowStatsMenu ? statsMenu?.toggle($event) : handleStatsButtonClick($event)"
[pTooltip]="statsTooltip"
tooltipPosition="bottom">
<svg class="multi-color-chart-icon" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">

View File

@@ -61,7 +61,7 @@ export class AppTopBarComponent implements OnDestroy {
@ViewChild('menubutton') menuButton!: ElementRef;
@ViewChild('topbarmenubutton') topbarMenuButton!: ElementRef;
@ViewChild('topbarmenu') menu!: ElementRef;
@ViewChild('statsMenu') statsMenu: any;
@ViewChild('statsMenu') statsMenu: Menu | undefined;
isMenuVisible = true;
progressHighlight = false;

View File

@@ -128,7 +128,12 @@
"alt": {
"cover": "Cover of {{ title }}",
"titleTooltip": "Title: {{ title }}",
"seriesCollapsed": "Series collapsed: {{ count }} books"
"seriesCollapsed": "Series collapsed: {{ count }} books",
"viewSeriesInfo": "View series information",
"viewDetails": "View book details",
"readBook": "Read book",
"playAudiobook": "Play audiobook",
"bookMenu": "Book actions menu"
}
},
"notes": {

View File

@@ -128,7 +128,12 @@
"alt": {
"cover": "Portada de {{ title }}",
"titleTooltip": "Título: {{ title }}",
"seriesCollapsed": "Series contraídas: {{ count }} libros"
"seriesCollapsed": "Series contraídas: {{ count }} libros",
"viewSeriesInfo": "Ver información de la serie",
"viewDetails": "Ver detalles del libro",
"readBook": "Leer libro",
"playAudiobook": "Reproducir audiolibro",
"bookMenu": "Menú de acciones del libro"
}
},
"notes": {