From 941206bdf342cfd91b92f7df47c13aa1eab21913 Mon Sep 17 00:00:00 2001 From: adityachandelgit <> Date: Fri, 18 Apr 2025 14:27:17 -0600 Subject: [PATCH] Add bulk lock/unlock metadata functionality for books --- .../controller/MetadataController.java | 9 +++- .../dto/request/ToggleAllLockRequest.java | 12 +++++ .../model/entity/BookMetadataEntity.java | 23 ++++++++ .../booklore/model/enums/Lock.java | 5 ++ .../booklore/service/BookMetadataService.java | 13 +++++ .../book-browser/book-browser.component.html | 54 +++++++++++++------ .../book-browser/book-browser.component.scss | 4 +- .../book-browser/book-browser.component.ts | 20 ++++++- .../book-card/book-card.component.html | 13 ++++- .../book-card/book-card.component.scss | 1 - .../book-table/book-table.component.html | 12 +++++ .../book-table/book-table.component.ts | 30 ++++++++++- ...lock-unlock-metadata-dialog.component.html | 7 +++ ...lock-unlock-metadata-dialog.component.scss | 0 .../lock-unlock-metadata-dialog.component.ts | 43 +++++++++++++++ booklore-ui/src/app/book/model/book.model.ts | 1 + .../src/app/book/service/book.service.ts | 23 +++++++- .../metadata-editor.component.ts | 32 ++++++++--- .../metadata-picker.component.ts | 32 ++++++++--- .../metadata-viewer.component.html | 19 ++++++- .../metadata-viewer.component.ts | 25 ++++++++- 21 files changed, 332 insertions(+), 46 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ToggleAllLockRequest.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/enums/Lock.java create mode 100644 booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.html create mode 100644 booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.scss create mode 100644 booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.ts diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/MetadataController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/MetadataController.java index 7afa5b49..71dbae18 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/MetadataController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/MetadataController.java @@ -4,6 +4,7 @@ import com.adityachandel.booklore.mapper.BookMetadataMapper; import com.adityachandel.booklore.model.dto.BookMetadata; import com.adityachandel.booklore.model.dto.request.FieldLockRequest; import com.adityachandel.booklore.model.dto.request.MetadataRefreshRequest; +import com.adityachandel.booklore.model.dto.request.ToggleAllLockRequest; import com.adityachandel.booklore.quartz.JobSchedulerService; import com.adityachandel.booklore.service.BookMetadataService; import com.adityachandel.booklore.service.BookMetadataUpdater; @@ -57,7 +58,7 @@ public class MetadataController { return ResponseEntity.ok(updatedMetadata); } - @PutMapping("/{bookId}/metadata/lock") + @PutMapping("/{bookId}/metadata/toggle-field-lock") @PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()") public ResponseEntity updateFieldLockState(@RequestBody FieldLockRequest request) { long bookId = request.getBookId(); @@ -66,6 +67,12 @@ public class MetadataController { return ResponseEntity.ok(bookMetadataService.updateFieldLockState(bookId, field, isLocked)); } + @PutMapping("/metadata/toggle-all-lock") + @PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()") + public ResponseEntity> toggleAllMetadata(@RequestBody ToggleAllLockRequest request) { + return ResponseEntity.ok(bookMetadataService.toggleAllLock(request)); + } + @PostMapping("/regenerate-covers") @PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()") public void regenerateCovers() { diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ToggleAllLockRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ToggleAllLockRequest.java new file mode 100644 index 00000000..783edf8b --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/ToggleAllLockRequest.java @@ -0,0 +1,12 @@ +package com.adityachandel.booklore.model.dto.request; + +import com.adityachandel.booklore.model.enums.Lock; +import lombok.Data; + +import java.util.Set; + +@Data +public class ToggleAllLockRequest { + private Set bookIds; + private Lock lock; +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/BookMetadataEntity.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/BookMetadataEntity.java index 208686be..353af1c4 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/BookMetadataEntity.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/BookMetadataEntity.java @@ -150,4 +150,27 @@ public class BookMetadataEntity { @OneToMany(fetch = FetchType.LAZY, mappedBy = "book") private List awards; + + + public void applyLockToAllFields(boolean lock) { + this.allFieldsLocked = lock; + this.titleLocked = lock; + this.subtitleLocked = lock; + this.publisherLocked = lock; + this.publishedDateLocked = lock; + this.descriptionLocked = lock; + this.isbn13Locked = lock; + this.isbn10Locked = lock; + this.pageCountLocked = lock; + this.thumbnailLocked = lock; + this.languageLocked = lock; + this.ratingLocked = lock; + this.reviewCountLocked = lock; + this.coverLocked = lock; + this.seriesNameLocked = lock; + this.seriesNumberLocked = lock; + this.seriesTotalLocked = lock; + this.authorsLocked = lock; + this.categoriesLocked = lock; + } } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/Lock.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/Lock.java new file mode 100644 index 00000000..8896a4c6 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/Lock.java @@ -0,0 +1,5 @@ +package com.adityachandel.booklore.model.enums; + +public enum Lock { + LOCK, UNLOCK +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookMetadataService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookMetadataService.java index b100d230..df74c0a7 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/BookMetadataService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/BookMetadataService.java @@ -7,10 +7,12 @@ import com.adityachandel.booklore.model.dto.Book; import com.adityachandel.booklore.model.dto.BookMetadata; import com.adityachandel.booklore.model.dto.request.MetadataRefreshOptions; import com.adityachandel.booklore.model.dto.request.MetadataRefreshRequest; +import com.adityachandel.booklore.model.dto.request.ToggleAllLockRequest; import com.adityachandel.booklore.model.dto.settings.AppSettings; import com.adityachandel.booklore.model.entity.BookEntity; import com.adityachandel.booklore.model.entity.BookMetadataEntity; import com.adityachandel.booklore.model.entity.LibraryEntity; +import com.adityachandel.booklore.model.enums.Lock; import com.adityachandel.booklore.model.websocket.Topic; import com.adityachandel.booklore.repository.BookMetadataRepository; import com.adityachandel.booklore.repository.BookRepository; @@ -441,4 +443,15 @@ public class BookMetadataService { } log.info("{} Successfully regenerated cover for book ID {} ({})", progress, book.getId(), title); } + + @Transactional + public List toggleAllLock(ToggleAllLockRequest request) { + boolean lock = request.getLock() == Lock.LOCK; + List books = bookRepository.findAllByIdIn(request.getBookIds()) + .stream() + .peek(book -> book.getMetadata().applyLockToAllFields(lock)) + .toList(); + bookRepository.saveAll(books); + return books.stream().map(b ->bookMetadataMapper.toBookMetadata(b.getMetadata(), false)).collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/booklore-ui/src/app/book/components/book-browser/book-browser.component.html b/booklore-ui/src/app/book/components/book-browser/book-browser.component.html index 147488a1..acb39171 100644 --- a/booklore-ui/src/app/book/components/book-browser/book-browser.component.html +++ b/booklore-ui/src/app/book/components/book-browser/book-browser.component.html @@ -106,8 +106,7 @@
- +
@@ -126,27 +125,50 @@
-
-

{{ book.metadata?.title }}

+
+

+ + +

+

{{ book.metadata?.title }}

+ + Title Authors @@ -34,6 +35,17 @@ + + + + Book Cover diff --git a/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts b/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts index 0bcf035c..25be1cc7 100644 --- a/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts +++ b/booklore-ui/src/app/book/components/book-browser/book-table/book-table.component.ts @@ -3,10 +3,13 @@ import {TableModule} from 'primeng/table'; import {NgIf} from '@angular/common'; import {Rating} from 'primeng/rating'; import {FormsModule} from '@angular/forms'; -import {Book} from '../../../model/book.model'; +import {Book, BookMetadata} from '../../../model/book.model'; import {SortOption} from '../../../model/sort.model'; import {MetadataDialogService} from '../../../../metadata/service/metadata-dialog.service'; import {UrlHelperService} from '../../../../utilities/service/url-helper.service'; +import {Button} from 'primeng/button'; +import {BookService} from '../../../service/book.service'; +import {MessageService} from 'primeng/api'; @Component({ selector: 'app-book-table', @@ -16,7 +19,8 @@ import {UrlHelperService} from '../../../../utilities/service/url-helper.service TableModule, NgIf, Rating, - FormsModule + FormsModule, + Button ], styleUrl: './book-table.component.scss' }) @@ -30,6 +34,8 @@ export class BookTableComponent implements OnChanges { protected urlHelper = inject(UrlHelperService); private metadataDialogService = inject(MetadataDialogService); + private bookService = inject(BookService); + private messageService = inject(MessageService); // Hack to set virtual-scroller height ngOnChanges() { @@ -90,4 +96,24 @@ export class BookTableComponent implements OnChanges { getGenres(genres: string[]) { return genres?.join(', ') || ''; } + + toggleMetadataLock(metadata: BookMetadata): void { + const lockAction = metadata.allFieldsLocked ? 'UNLOCK' : 'LOCK'; + this.bookService.toggleAllLock(new Set([metadata.bookId]), lockAction).subscribe({ + next: () => { + this.messageService.add({ + severity: 'success', + summary: `Metadata ${lockAction === 'LOCK' ? 'Locked' : 'Unlocked'}`, + detail: `Book metadata has been ${lockAction === 'LOCK' ? 'locked' : 'unlocked'} successfully.`, + }); + }, + error: () => { + this.messageService.add({ + severity: 'error', + summary: `Failed to ${lockAction === 'LOCK' ? 'Lock' : 'Unlock'}`, + detail: `An error occurred while ${lockAction === 'LOCK' ? 'locking' : 'unlocking'} the metadata.`, + }); + } + }); + } } diff --git a/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.html b/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.html new file mode 100644 index 00000000..80a81d1e --- /dev/null +++ b/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.html @@ -0,0 +1,7 @@ +
+

Lock or unlock all metadata for all the selected books

+
+ + +
+
diff --git a/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.scss b/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.ts b/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.ts new file mode 100644 index 00000000..c4dfb3de --- /dev/null +++ b/booklore-ui/src/app/book/components/book-browser/lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component.ts @@ -0,0 +1,43 @@ +import {Component, inject} from '@angular/core'; +import {Button} from 'primeng/button'; +import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {MessageService} from 'primeng/api'; +import {BookService} from '../../../service/book.service'; + +@Component({ + selector: 'app-lock-unlock-metadata-dialog', + imports: [ + Button + ], + templateUrl: './lock-unlock-metadata-dialog.component.html', + styleUrl: './lock-unlock-metadata-dialog.component.scss' +}) +export class LockUnlockMetadataDialogComponent { + private bookService = inject(BookService); + private dynamicDialogConfig = inject(DynamicDialogConfig); + private dialogRef = inject(DynamicDialogRef); + private messageService = inject(MessageService); + + bookIds: Set = this.dynamicDialogConfig.data.bookIds; + + toggleLock(action: 'LOCK' | 'UNLOCK'): void { + this.bookService.toggleAllLock(this.bookIds, action).subscribe({ + next: () => { + const isLock = action === 'LOCK'; + this.messageService.add({ + severity: 'success', + summary: `Metadata ${isLock ? 'Locked' : 'Unlocked'}`, + detail: `All selected books have been ${isLock ? 'locked' : 'unlocked'} successfully.`, + }); + this.dialogRef.close(action.toLowerCase()); + }, + error: () => { + this.messageService.add({ + severity: 'error', + summary: `Failed to ${action === 'LOCK' ? 'Lock' : 'Unlock'}`, + detail: `An error occurred while ${action === 'LOCK' ? 'locking' : 'unlocking'} metadata.`, + }); + } + }); + } +} diff --git a/booklore-ui/src/app/book/model/book.model.ts b/booklore-ui/src/app/book/model/book.model.ts index 6e6247d5..b1d313b6 100644 --- a/booklore-ui/src/app/book/model/book.model.ts +++ b/booklore-ui/src/app/book/model/book.model.ts @@ -40,6 +40,7 @@ export interface BookMetadata { providerBookId?: string; thumbnailUrl?: string | null; + allFieldsLocked?: boolean; titleLocked?: boolean; subtitleLocked?: boolean; publisherLocked?: boolean; diff --git a/booklore-ui/src/app/book/service/book.service.ts b/booklore-ui/src/app/book/service/book.service.ts index f22a9ba0..11c953c1 100644 --- a/booklore-ui/src/app/book/service/book.service.ts +++ b/booklore-ui/src/app/book/service/book.service.ts @@ -210,7 +210,7 @@ export class BookService { updateBookMetadata(bookId: number, bookMetadata: BookMetadata, mergeCategories: boolean): Observable { const params = new HttpParams().set('mergeCategories', mergeCategories.toString()); - return this.http.put(`${this.url}/${bookId}/metadata`, bookMetadata, { params }).pipe( + return this.http.put(`${this.url}/${bookId}/metadata`, bookMetadata, {params}).pipe( map(updatedMetadata => { this.handleBookMetadataUpdate(bookId, updatedMetadata); return updatedMetadata; @@ -218,6 +218,27 @@ export class BookService { ); } + toggleAllLock(bookIds: Set, lock: string): Observable { + const requestBody = { + bookIds: Array.from(bookIds), + lock: lock + }; + return this.http.put(`${this.url}/metadata/toggle-all-lock`, requestBody).pipe( + tap((updatedMetadataList) => { + const currentState = this.bookStateSubject.value; + const updatedBooks = (currentState.books || []).map(book => { + const updatedMetadata = updatedMetadataList.find(meta => meta.bookId === book.id); + return updatedMetadata ? {...book, metadata: updatedMetadata} : book; + }); + this.bookStateSubject.next({...currentState, books: updatedBooks}); + }), + map(() => void 0), + catchError((error) => { + throw error; + }) + ); + } + autoRefreshMetadata(metadataRefreshRequest: MetadataRefreshRequest): Observable { return this.http.put(`${this.url}/metadata/refresh`, metadataRefreshRequest).pipe( map(() => { diff --git a/booklore-ui/src/app/metadata/book-metadata-center/metadata-editor/metadata-editor.component.ts b/booklore-ui/src/app/metadata/book-metadata-center/metadata-editor/metadata-editor.component.ts index 122f930a..05aa6c9b 100644 --- a/booklore-ui/src/app/metadata/book-metadata-center/metadata-editor/metadata-editor.component.ts +++ b/booklore-ui/src/app/metadata/book-metadata-center/metadata-editor/metadata-editor.component.ts @@ -151,7 +151,7 @@ export class MetadataEditorComponent implements OnInit { } onSave(): void { - this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(), false).subscribe({ + this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(undefined), false).subscribe({ next: (response) => { this.messageService.add({severity: 'info', summary: 'Success', detail: 'Book metadata updated'}); this.metadataCenterService.emit(response); @@ -171,7 +171,7 @@ export class MetadataEditorComponent implements OnInit { } else { this.metadataForm.get(field)?.enable(); } - this.updateMetadata(); + this.updateMetadata(undefined); } lockAll(): void { @@ -182,7 +182,7 @@ export class MetadataEditorComponent implements OnInit { this.metadataForm.get(fieldName)?.disable(); } }); - this.updateMetadata(); + this.updateMetadata(true); } unlockAll(): void { @@ -193,10 +193,10 @@ export class MetadataEditorComponent implements OnInit { this.metadataForm.get(fieldName)?.enable(); } }); - this.updateMetadata(); + this.updateMetadata(false); } - private buildMetadata() { + private buildMetadata(shouldLockAllFields: boolean | undefined) { const updatedBookMetadata: BookMetadata = { bookId: this.currentBookId, title: this.metadataForm.get('title')?.value, @@ -233,17 +233,33 @@ export class MetadataEditorComponent implements OnInit { seriesNumberLocked: this.metadataForm.get('seriesNumberLocked')?.value, seriesTotalLocked: this.metadataForm.get('seriesTotalLocked')?.value, coverLocked: this.metadataForm.get('thumbnailUrlLocked')?.value, + + ...(shouldLockAllFields !== undefined && {allFieldsLocked: shouldLockAllFields}), }; return updatedBookMetadata; } - private updateMetadata(): void { - this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(), false).subscribe({ + private updateMetadata(shouldLockAllFields: boolean | undefined): void { + this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(shouldLockAllFields), false).subscribe({ next: (response) => { this.metadataCenterService.emit(response); + + if (shouldLockAllFields !== undefined) { + this.messageService.add({ + severity: 'success', + summary: shouldLockAllFields ? 'Metadata Locked' : 'Metadata Unlocked', + detail: shouldLockAllFields + ? 'All fields have been successfully locked.' + : 'All fields have been successfully unlocked.', + }); + } }, error: () => { - this.messageService.add({severity: 'error', summary: 'Error', detail: 'Failed to update lock state'}); + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'Failed to update lock state', + }); } }); } diff --git a/booklore-ui/src/app/metadata/book-metadata-center/metadata-picker/metadata-picker.component.ts b/booklore-ui/src/app/metadata/book-metadata-center/metadata-picker/metadata-picker.component.ts index 1490fd00..42ee95ac 100644 --- a/booklore-ui/src/app/metadata/book-metadata-center/metadata-picker/metadata-picker.component.ts +++ b/booklore-ui/src/app/metadata/book-metadata-center/metadata-picker/metadata-picker.component.ts @@ -180,7 +180,7 @@ export class MetadataPickerComponent implements OnInit { } onSave(): void { - const updatedBookMetadata = this.buildMetadata(); + const updatedBookMetadata = this.buildMetadata(undefined); this.bookService.updateBookMetadata(this.currentBookId, updatedBookMetadata, false).subscribe({ next: (bookMetadata) => { Object.keys(this.copiedFields).forEach((field) => { @@ -197,7 +197,7 @@ export class MetadataPickerComponent implements OnInit { }); } - private buildMetadata() { + private buildMetadata(shouldLockAllFields: boolean | undefined) { const updatedBookMetadata: BookMetadata = { bookId: this.currentBookId, title: this.metadataForm.get('title')?.value || this.copiedFields['title'] ? this.getValueOrCopied('title') : '', @@ -235,6 +235,8 @@ export class MetadataPickerComponent implements OnInit { seriesNumberLocked: this.metadataForm.get('seriesNumberLocked')?.value, seriesTotalLocked: this.metadataForm.get('seriesTotalLocked')?.value, coverLocked: this.metadataForm.get('thumbnailUrlLocked')?.value, + + ...(shouldLockAllFields !== undefined && {allFieldsLocked: shouldLockAllFields}), }; return updatedBookMetadata; } @@ -247,13 +249,27 @@ export class MetadataPickerComponent implements OnInit { return this.copiedFields['thumbnailUrl'] ? this.getValueOrCopied('thumbnailUrl') : null; } - private updateMetadata(): void { - this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(), false).subscribe({ + private updateMetadata(shouldLockAllFields: boolean | undefined): void { + this.bookService.updateBookMetadata(this.currentBookId, this.buildMetadata(shouldLockAllFields), false).subscribe({ next: (response) => { this.metadataCenterService.emit(response); + + if (shouldLockAllFields !== undefined) { + this.messageService.add({ + severity: 'success', + summary: shouldLockAllFields ? 'Metadata Locked' : 'Metadata Unlocked', + detail: shouldLockAllFields + ? 'All fields have been successfully locked.' + : 'All fields have been successfully unlocked.', + }); + } }, error: () => { - this.messageService.add({severity: 'error', summary: 'Error', detail: 'Failed to update lock state'}); + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'Failed to update lock state', + }); } }); } @@ -267,7 +283,7 @@ export class MetadataPickerComponent implements OnInit { } else { this.metadataForm.get(field)?.enable(); } - this.updateMetadata(); + this.updateMetadata(undefined); } copyMissing(): void { @@ -352,7 +368,7 @@ export class MetadataPickerComponent implements OnInit { this.metadataForm.get(fieldName)?.disable(); } }); - this.updateMetadata(); + this.updateMetadata(true); } unlockAll(): void { @@ -363,7 +379,7 @@ export class MetadataPickerComponent implements OnInit { this.metadataForm.get(fieldName)?.enable(); } }); - this.updateMetadata(); + this.updateMetadata(false); } highlightCopiedInput(field: string): void { diff --git a/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.html b/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.html index 29ff52d0..c52c9dad 100644 --- a/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.html +++ b/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.html @@ -13,7 +13,17 @@

{{ metadata.seriesName }} #{{ metadata.seriesNumber }}

-

{{ metadata.title }}

+
+

{{ metadata.title }}

+ + +

{{ getAuthorNames(metadata?.authors || []) }}

@@ -45,6 +55,13 @@ +
diff --git a/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts b/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts index cd317a32..e9f805a5 100644 --- a/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts +++ b/booklore-ui/src/app/metadata/book-metadata-center/metadata-viewer/metadata-viewer.component.ts @@ -1,6 +1,6 @@ import {Component, inject, Input, OnInit} from '@angular/core'; import {Button} from 'primeng/button'; -import {AsyncPipe, NgForOf, NgIf} from '@angular/common'; +import {AsyncPipe, NgClass, NgForOf, NgIf} from '@angular/common'; import {first, Observable} from 'rxjs'; import {BookService} from '../../../book/service/book.service'; import {BookMetadataCenterService} from '../book-metadata-center.service'; @@ -17,13 +17,14 @@ import {BookSenderComponent} from '../../../book/components/book-sender/book-sen import {DialogService} from 'primeng/dynamicdialog'; import {EmailService} from '../../../settings/email/email.service'; import {ShelfAssignerComponent} from '../../../book/components/shelf-assigner/shelf-assigner.component'; +import {Tooltip} from 'primeng/tooltip'; @Component({ selector: 'app-metadata-viewer', standalone: true, templateUrl: './metadata-viewer.component.html', styleUrl: './metadata-viewer.component.scss', - imports: [Button, NgForOf, NgIf, AsyncPipe, Rating, FormsModule, Tag, Divider, SplitButton] + imports: [Button, NgForOf, NgIf, AsyncPipe, Rating, FormsModule, Tag, Divider, SplitButton, NgClass, Tooltip] }) export class MetadataViewerComponent implements OnInit { @@ -119,4 +120,24 @@ export class MetadataViewerComponent implements OnInit { }, }); } + + /*toggleMetadataLock(metadata: BookMetadata): void { + const lockAction = metadata.allFieldsLocked ? 'UNLOCK' : 'LOCK'; + this.bookService.toggleAllLock(new Set([metadata.bookId]), lockAction).subscribe({ + next: () => { + this.messageService.add({ + severity: 'success', + summary: `Metadata ${lockAction === 'LOCK' ? 'Locked' : 'Unlocked'}`, + detail: `Book metadata has been ${lockAction === 'LOCK' ? 'locked' : 'unlocked'} successfully.`, + }); + }, + error: () => { + this.messageService.add({ + severity: 'error', + summary: `Failed to ${lockAction === 'LOCK' ? 'Lock' : 'Unlock'}`, + detail: `An error occurred while ${lockAction === 'LOCK' ? 'locking' : 'unlocking'} the metadata.`, + }); + } + }); + }*/ }