mirror of
https://github.com/booklore-app/booklore.git
synced 2026-02-17 16:07:55 +01:00
chore: fix memory leaks, add aria-labels, improve type safety and logging (#2764)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user