Add bulk lock/unlock metadata functionality for books

This commit is contained in:
adityachandelgit
2025-04-18 14:27:17 -06:00
committed by Aditya Chandel
parent 5c50a157cd
commit 941206bdf3
21 changed files with 332 additions and 46 deletions

View File

@@ -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<BookMetadata> 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<List<BookMetadata>> toggleAllMetadata(@RequestBody ToggleAllLockRequest request) {
return ResponseEntity.ok(bookMetadataService.toggleAllLock(request));
}
@PostMapping("/regenerate-covers")
@PreAuthorize("@securityUtil.canEditMetadata() or @securityUtil.isAdmin()")
public void regenerateCovers() {

View File

@@ -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<Long> bookIds;
private Lock lock;
}

View File

@@ -150,4 +150,27 @@ public class BookMetadataEntity {
@OneToMany(fetch = FetchType.LAZY, mappedBy = "book")
private List<BookAwardEntity> 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;
}
}

View File

@@ -0,0 +1,5 @@
package com.adityachandel.booklore.model.enums;
public enum Lock {
LOCK, UNLOCK
}

View File

@@ -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<BookMetadata> toggleAllLock(ToggleAllLockRequest request) {
boolean lock = request.getLock() == Lock.LOCK;
List<BookEntity> 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());
}
}

View File

@@ -106,8 +106,7 @@
</div>
<div class="book-card-container" *ngIf="bookState?.books as books">
<app-book-table *ngIf="gridOrTable === 'table' && books.length > 0" [books]="books" (selectedBooksChange)="onSelectedBooksChange($event)"
[sortOption]="selectedSort"></app-book-table>
<app-book-table *ngIf="gridOrTable === 'table' && books.length > 0" [books]="books" (selectedBooksChange)="onSelectedBooksChange($event)" [sortOption]="selectedSort"></app-book-table>
<virtual-scroller *ngIf="gridOrTable === 'grid'" class="virtual-scroller" #scroll [items]="books">
<div class="grid grid-cols-12 gap-4" #container>
@@ -126,27 +125,50 @@
<div class="book-browser-footer bg-[var(--card-background)] bg-opacity-10" *ngIf="selectedBooks.size > 0" [@slideInOut]>
<div class="flex justify-between gap-8">
<ng-container *ngIf="entityType$ | async as entityType">
<p-button *ngIf="entityType === EntityType.LIBRARY || entityType === EntityType.ALL_BOOKS"
label="Assign Shelf"
<p-button
*ngIf="entityType === EntityType.LIBRARY || entityType === EntityType.ALL_BOOKS"
icon="pi pi-bookmark-fill"
outlined="true"
severity="info"
(onClick)="openShelfAssigner()">
(onClick)="openShelfAssigner()"
pTooltip="Assign to shelf"
tooltipPosition="top">
</p-button>
<p-button *ngIf="entityType === EntityType.SHELF"
label="Unshelf"
<p-button
*ngIf="entityType === EntityType.SHELF"
icon="pi pi-bookmark"
outlined="true"
severity="info"
(click)="unshelfBooks()">
(click)="unshelfBooks()"
pTooltip="Remove from shelf"
tooltipPosition="top">
</p-button>
<div *ngIf="userService.userData$ | async as userData">
<p-button *ngIf="userData.permissions.canEditMetadata"
label="Refresh Metadata"
<p-button
*ngIf="userData.permissions.canEditMetadata"
icon="pi pi-database"
outlined="true"
severity="info"
(click)="updateMetadata()">
(click)="updateMetadata()"
pTooltip="Update metadata"
tooltipPosition="top">
</p-button>
</div>
<p-button
label="Deselect All"
outlined="true"
icon="pi pi-lock"
severity="info"
(click)="lockUnlockMetadata()"
pTooltip="Lock/Unlock metadata"
tooltipPosition="top">
</p-button>
<p-button
outlined="true"
icon="pi pi-times"
severity="warn"
(click)="deselectAllBooks()">
(click)="deselectAllBooks()"
pTooltip="Deselect all books"
tooltipPosition="top">
</p-button>
</ng-container>
</div>

View File

@@ -34,8 +34,8 @@
.book-browser-footer {
position: absolute;
bottom: 0;
left: 20%;
right: 20%;
left: 30%;
right: 30%;
display: flex;
justify-content: center;
padding: 1rem 0.5rem 0.5rem;

View File

@@ -1,10 +1,10 @@
import {AfterViewInit, Component, inject, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {MenuItem, MessageService, PrimeTemplate} from 'primeng/api';
import {ConfirmationService, MenuItem, MessageService, PrimeTemplate} from 'primeng/api';
import {LibraryService} from '../../service/library.service';
import {BookService} from '../../service/book.service';
import {map, switchMap} from 'rxjs/operators';
import {BehaviorSubject, combineLatest, Observable, of, Subject} from 'rxjs';
import {BehaviorSubject, Observable, of, Subject} from 'rxjs';
import {ShelfService} from '../../service/shelf.service';
import {ShelfAssignerComponent} from '../shelf-assigner/shelf-assigner.component';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
@@ -31,6 +31,7 @@ import {BookFilterComponent} from './book-filter/book-filter.component';
import {Tooltip} from 'primeng/tooltip';
import {Fluid} from 'primeng/fluid';
import {UserService} from '../../../user.service';
import {LockUnlockMetadataDialogComponent} from './lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component';
export enum EntityType {
LIBRARY = 'Library',
@@ -92,6 +93,7 @@ export class BookBrowserComponent implements OnInit, AfterViewInit {
private dialogService = inject(DialogService);
private sortService = inject(SortService);
private libraryShelfMenuService = inject(LibraryShelfMenuService);
private confirmationService = inject(ConfirmationService);
protected resetFilterSubject = new Subject<void>();
@@ -483,4 +485,18 @@ export class BookBrowserComponent implements OnInit, AfterViewInit {
this.selectedFilter.next(item);
});
}
lockUnlockMetadata() {
this.dynamicDialogRef = this.dialogService.open(LockUnlockMetadataDialogComponent, {
header: 'Toggle Metadata Lock',
modal: true,
closable: true,
data: {
bookIds: Array.from(this.selectedBooks)
}
});
this.dynamicDialogRef.onClose.subscribe(() => {
this.deselectAllBooks();
});
}
}

View File

@@ -26,8 +26,17 @@
</div>
<div class="book-info">
<div class="book-title-container">
<h4 class="book-title">{{ book.metadata?.title }}</h4>
<div class="book-title-container flex items-center">
<p class="m-0 pl-2">
<i
class="pi"
[ngClass]="book.metadata?.allFieldsLocked ? 'pi-lock text-red-400' : 'pi-lock-open text-green-400'"
[title]="book.metadata?.allFieldsLocked ? 'Locked' : 'Unlocked'"
style="font-size: 0.75rem;">
</i>
</p>
<h4 class="book-title m-0 pl-2">{{ book.metadata?.title }}</h4>
<p-tieredmenu #menu [model]="items" [popup]="true" appendTo="body"></p-tieredmenu>
<p-button
size="small"

View File

@@ -80,7 +80,6 @@
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
padding-left: 6px;
}
.info-btn,

View File

@@ -16,6 +16,7 @@
<th>
<p-tableHeaderCheckbox/>
</th>
<th pResizableColumn></th>
<th class="max-w-14 min-w-14"></th>
<th pResizableColumn>Title</th>
<th pResizableColumn>Authors</th>
@@ -34,6 +35,17 @@
<td class="max-w-16">
<p-tableCheckbox [value]="book"></p-tableCheckbox>
</td>
<td class="text-center">
<p-button
[icon]="metadata.allFieldsLocked ? 'pi pi-lock' : 'pi pi-lock-open'"
[severity]="metadata.allFieldsLocked ? 'danger' : 'success'"
[text]="true"
[title]="metadata.allFieldsLocked ? 'Locked' : 'Unlocked'"
size="small"
[style]="{ width: '1.5rem', height: '1.5rem', padding: '0', fontSize: '0.75rem' }"
(click)="toggleMetadataLock(metadata)">
</p-button>
</td>
<td (click)="openMetadataCenter(book.id)">
<img [attr.src]="urlHelper.getCoverUrl(metadata.bookId, metadata?.coverUpdatedOn)" alt="Book Cover" class="size-7"/>
</td>

View File

@@ -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.`,
});
}
});
}
}

View File

@@ -0,0 +1,7 @@
<div class="flex flex-col items-center text-center space-y-6">
<p class="pt-6 px-4">Lock or unlock all metadata for all the selected books</p>
<div class="flex gap-4 justify-center">
<p-button icon="pi pi-lock" label="Lock" outlined="true" severity="danger" (onClick)="toggleLock('LOCK')"></p-button>
<p-button icon="pi pi-lock-open" label="Unlock" outlined="true" severity="success" (onClick)="toggleLock('UNLOCK')"></p-button>
</div>
</div>

View File

@@ -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<number> = 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.`,
});
}
});
}
}

View File

@@ -40,6 +40,7 @@ export interface BookMetadata {
providerBookId?: string;
thumbnailUrl?: string | null;
allFieldsLocked?: boolean;
titleLocked?: boolean;
subtitleLocked?: boolean;
publisherLocked?: boolean;

View File

@@ -210,7 +210,7 @@ export class BookService {
updateBookMetadata(bookId: number, bookMetadata: BookMetadata, mergeCategories: boolean): Observable<BookMetadata> {
const params = new HttpParams().set('mergeCategories', mergeCategories.toString());
return this.http.put<BookMetadata>(`${this.url}/${bookId}/metadata`, bookMetadata, { params }).pipe(
return this.http.put<BookMetadata>(`${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<number>, lock: string): Observable<void> {
const requestBody = {
bookIds: Array.from(bookIds),
lock: lock
};
return this.http.put<BookMetadata[]>(`${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<any> {
return this.http.put<void>(`${this.url}/metadata/refresh`, metadataRefreshRequest).pipe(
map(() => {

View File

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

View File

@@ -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 {

View File

@@ -13,7 +13,17 @@
<div class="flex flex-row items-center justify-between">
<div>
<p *ngIf="metadata.seriesName" class="italic">{{ metadata.seriesName }} #{{ metadata.seriesNumber }} </p>
<p class="text-2xl font-bold">{{ metadata.title }}</p>
<div class="flex items-center gap-2">
<p class="text-2xl font-bold m-0">{{ metadata.title }}</p>
<i
class="pi"
[ngClass]="metadata.allFieldsLocked ? 'pi-lock text-red-500' : 'pi-lock-open text-green-500'"
[title]="metadata.allFieldsLocked ? 'Metadata is locked' : 'Metadata is unlocked'"
style="font-size: 1.25rem;"
pTooltip="{{ metadata.allFieldsLocked ? 'This book metadata is locked.' : 'This book metadata is unlocked.' }}"
tooltipPosition="top">
</i>
</div>
<p class="text-xl">{{ getAuthorNames(metadata?.authors || []) }}</p>
</div>
</div>
@@ -45,6 +55,13 @@
<p-button label="Assign Shelf" icon="pi pi-folder" severity="info" outlined (onClick)="assignShelf(metadata.bookId)"></p-button>
<p-button *ngIf="userData.permissions.canDownload" label="Download" icon="pi pi-download" severity="info" outlined (onClick)="download(metadata.bookId)"></p-button>
<p-splitbutton *ngIf="userData.permissions.canEmailBook" label="Quick Send" icon="pi pi-send" [model]="items" (onClick)="quickSend(metadata.bookId)" outlined severity="info"/>
<!--<p-button
outlined
[label]="metadata.allFieldsLocked ? 'Unlock Metadata' : 'Lock Metadata'"
[icon]="metadata.allFieldsLocked ? 'pi pi-lock' : 'pi pi-lock-open'"
[severity]="metadata.allFieldsLocked ? 'danger' : 'success'"
(onClick)="toggleMetadataLock(metadata)">
</p-button>-->
</div>
</div>

View File

@@ -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.`,
});
}
});
}*/
}