Checkpoint

This commit is contained in:
aditya.chandel
2024-12-15 22:41:13 -07:00
parent 0aa1c1b923
commit 767c6604e8
51 changed files with 297 additions and 212 deletions

View File

@@ -2,6 +2,7 @@ package com.adityachandel.booklore.controller;
import com.adityachandel.booklore.model.dto.BookDTO;
import com.adityachandel.booklore.model.dto.BookViewerSettingDTO;
import com.adityachandel.booklore.model.dto.BookWithNeighborsDTO;
import com.adityachandel.booklore.model.dto.response.GoogleBooksMetadata;
import com.adityachandel.booklore.model.dto.request.SetMetadataRequest;
import com.adityachandel.booklore.service.BooksService;

View File

@@ -1,8 +1,10 @@
package com.adityachandel.booklore.controller;
import com.adityachandel.booklore.model.dto.BookDTO;
import com.adityachandel.booklore.model.dto.BookWithNeighborsDTO;
import com.adityachandel.booklore.model.dto.LibraryDTO;
import com.adityachandel.booklore.model.dto.request.CreateLibraryRequest;
import com.adityachandel.booklore.service.BooksService;
import com.adityachandel.booklore.service.LibraryService;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
@@ -19,6 +21,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
public class LibraryController {
private LibraryService libraryService;
private BooksService booksService;
@PostMapping
public ResponseEntity<LibraryDTO> createLibraryNew(@RequestBody CreateLibraryRequest request) {
@@ -46,6 +49,11 @@ public class LibraryController {
return ResponseEntity.ok(libraryService.getLibraries(page, size));
}
@GetMapping("/{libraryId}/book/{bookId}/withNeighbors")
public ResponseEntity<BookWithNeighborsDTO> getBookWithNeighbours(@PathVariable long libraryId, @PathVariable long bookId) {
return ResponseEntity.ok(booksService.getBookWithNeighbours(libraryId, bookId));
}
@GetMapping("/{libraryId}/book/{bookId}")
public ResponseEntity<BookDTO> getBook(@PathVariable long libraryId, @PathVariable long bookId) {
return ResponseEntity.ok(libraryService.getBook(libraryId, bookId));

View File

@@ -0,0 +1,12 @@
package com.adityachandel.booklore.model.dto;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class BookWithNeighborsDTO {
private BookDTO currentBook;
private Long previousBookId;
private Long nextBookId;
}

View File

@@ -27,5 +27,9 @@ public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificat
Page<Book> findByLastReadTimeIsNotNull(Pageable pageable);
Page<Book> findByAddedOnIsNotNull(Pageable pageable);
Optional<Book> findFirstByLibraryIdAndIdLessThanOrderByIdDesc(Long libraryId, Long currentBookId);
Optional<Book> findFirstByLibraryIdAndIdGreaterThanOrderByIdAsc(Long libraryId, Long currentBookId);
}

View File

@@ -2,6 +2,7 @@ package com.adityachandel.booklore.service;
import com.adityachandel.booklore.config.AppProperties;
import com.adityachandel.booklore.model.dto.BookDTO;
import com.adityachandel.booklore.model.dto.BookWithNeighborsDTO;
import com.adityachandel.booklore.model.dto.response.GoogleBooksMetadata;
import com.adityachandel.booklore.model.dto.BookViewerSettingDTO;
import com.adityachandel.booklore.model.dto.request.SetMetadataRequest;
@@ -47,6 +48,7 @@ public class BooksService {
private final BookMetadataRepository metadataRepository;
private final AuthorRepository authorRepository;
private final CategoryRepository categoryRepository;
private final LibraryRepository libraryRepository;
public BookDTO getBook(long bookId) {
@@ -210,4 +212,17 @@ public class BooksService {
return fileName.substring(0, dotIndex);
}
}
public BookWithNeighborsDTO getBookWithNeighbours(long libraryId, long bookId) {
libraryRepository.findById(libraryId).orElseThrow(() -> ErrorCode.LIBRARY_NOT_FOUND.createException(libraryId));
Book book = bookRepository.findById(bookId).orElseThrow(() -> ErrorCode.BOOK_NOT_FOUND.createException(bookId));
Book previousBook = bookRepository.findFirstByLibraryIdAndIdLessThanOrderByIdDesc(libraryId, bookId).orElse(null);
Book nextBook = bookRepository.findFirstByLibraryIdAndIdGreaterThanOrderByIdAsc(libraryId, bookId).orElse(null);
return BookWithNeighborsDTO.builder()
.currentBook(BookTransformer.convertToBookDTO(book))
.previousBookId(previousBook != null ? previousBook.getId() : null)
.nextBookId(nextBook != null ? nextBook.getId() : null)
.build();
}
}

View File

@@ -73,7 +73,7 @@ public class PdfFileProcessor implements FileProcessor {
}
}
generateCoverImage(bookFile, new File(appProperties.getPathConfig() + "/thumbs"), document);
} catch (IOException e) {
} catch (Exception e) {
log.error("Error while processing file {}", libraryFile.getFilePath(), e);
return FileProcessResult.builder()
.libraryFile(libraryFile)

View File

@@ -1,10 +1,10 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {AppLayoutComponent} from './layout/app.layout.component';
import {AppLayoutComponent} from './book/component/layout/app.layout.component';
import {LibraryBrowserComponent} from './book/component/library-browser/library-browser.component';
import {PdfViewerComponent} from './book/component/pdf-viewer/pdf-viewer.component';
import {DashboardComponent} from './book/component/dashboard/dashboard.component';
import {BookMetadataComponent} from './book-metadata/book-metadata.component';
import {BookMetadataComponent} from './book/component/book-metadata/book-metadata.component';
const routes: Routes = [
{
@@ -17,7 +17,7 @@ const routes: Routes = [
path: 'library/:libraryId/books', component: LibraryBrowserComponent,
},
{
path: 'book/:bookId/info', component: BookMetadataComponent,
path: 'library/:libraryId/book/:bookId/info', component: BookMetadataComponent,
}
]
},

View File

@@ -3,7 +3,7 @@ import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {AppLayoutModule} from './layout/app.layout.module';
import {AppLayoutModule} from './book/component/layout/app.layout.module';
import {FormsModule} from '@angular/forms';
import {DialogService} from 'primeng/dynamicdialog';
import {DirectoryPickerComponent} from './book/component/directory-picker/directory-picker.component';
@@ -20,10 +20,10 @@ import {ToastModule} from 'primeng/toast';
import {LibraryBrowserComponent} from './book/component/library-browser/library-browser.component';
import {InfiniteScrollDirective} from 'ngx-infinite-scroll';
import {SearchComponent} from './book/component/search/search.component';
import { BookMetadataComponent } from './book-metadata/book-metadata.component';
import { BooksMetadataDialogComponent } from './books-metadata-dialog/books-metadata-dialog.component';
import { BookMetadataComponent } from './book/component/book-metadata/book-metadata.component';
import { BooksMetadataDialogComponent } from './book/component/books-metadata-dialog/books-metadata-dialog.component';
import {MessageService} from 'primeng/api';
import { NotificationComponent } from './notification/notification.component';
import { NotificationComponent } from './book/component/notification/notification.component';
@NgModule({
declarations: [

View File

@@ -1,91 +0,0 @@
import {Component, OnInit, OnDestroy} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {BookService} from '../book/service/book.service';
import {Book} from '../book/model/book.model';
import {Button} from 'primeng/button';
import {NgForOf} from '@angular/common';
import {BooksMetadataDialogComponent} from '../books-metadata-dialog/books-metadata-dialog.component';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
import {Subscription} from 'rxjs';
import {TagModule} from 'primeng/tag';
@Component({
selector: 'app-book-metadata',
templateUrl: './book-metadata.component.html',
styleUrls: ['./book-metadata.component.scss'],
imports: [
Button,
NgForOf,
TagModule
]
})
export class BookMetadataComponent implements OnInit, OnDestroy {
book: Book | null = null;
private routeSubscription!: Subscription;
ref: DynamicDialogRef | undefined;
constructor(
private bookService: BookService,
private activatedRoute: ActivatedRoute,
private dialogService: DialogService) {
}
ngOnInit(): void {
this.routeSubscription = this.activatedRoute.paramMap.subscribe((paramMap) => {
const bookId = +paramMap.get('bookId')!;
if (bookId) {
this.loadBook(bookId);
}
});
}
private loadBook(bookId: number): void {
this.bookService.getBook(bookId).subscribe((book) => {
this.book = book;
});
}
getAuthorNames(book: Book | null): string {
if (book && book.metadata && book.metadata.authors) {
return book.metadata.authors.map((author) => author.name).join(', ');
}
return 'No authors available';
}
openEditDialog(id: number | undefined) {
this.ref = this.dialogService.open(BooksMetadataDialogComponent, {
header: 'Fetch Metadata (from Google Books)',
modal: false,
width: '65%',
height: '85%',
data: {
bookId: id,
bookTitle: this.book?.metadata.title,
},
});
this.ref.onClose.subscribe(() => {
// @ts-ignore
this.loadBook(this.book?.id);
});
}
coverImageSrc(bookId: any): string {
if (bookId === null) {
return 'assets/placeholder.png';
}
return this.bookService.getBookCoverUrl(bookId);
}
ngOnDestroy(): void {
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
}
}
readBook(id: number | undefined) {
const url = `/pdf-viewer/book/${id}`;
window.open(url, '_blank');
}
}

View File

@@ -2,17 +2,38 @@
<div class="book-metadata">
<div class="book-header">
<div class="cover-container">
<img [src]="coverImageSrc(book?.id)" class="book-cover placeholder" alt="Cover of {{ book?.metadata?.title }}"
loading="lazy"/>
<img [src]="coverImageSrc(book?.id)" class="book-cover placeholder" alt="Cover of {{ book?.metadata?.title }}" loading="lazy"/>
<div>
<div class="book-navigation">
<p-button
icon="pi pi-arrow-left"
class="navigation-button"
severity="help"
(click)="navigateToBook(previousBookId, book?.libraryId!)"
[rounded]="true"
[outlined]="true"
[disabled]="!canGoPrevious()">
</p-button>
<p-button
icon="pi pi-arrow-right"
class="navigation-button"
severity="help"
(click)="navigateToBook(nextBookId, book?.libraryId!)"
[rounded]="true"
[outlined]="true"
[disabled]="!canGoNext()">
</p-button>
</div>
</div>
</div>
<div class="metadata">
<div class="title-row">
<h2 class="book-title">{{ book?.metadata?.title || 'Untitled' }}</h2>
<h1 class="book-title">
{{ book?.metadata?.title }}<span *ngIf="book?.metadata?.subtitle">: {{ book?.metadata?.subtitle }}</span>
</h1>
<div class="button-group">
<p-button [rounded]="true" [raised]="true" [outlined]="true" (onClick)="openEditDialog(book?.id)"
icon="pi pi-pencil"></p-button>
<p-button [rounded]="true" [raised]="true" [outlined]="true" (onClick)="readBook(book?.id)"
icon="pi pi-eye"></p-button>
<p-button [rounded]="true" [raised]="true" [outlined]="true" (onClick)="openEditDialog(book?.id, book?.libraryId)" icon="pi pi-pencil"></p-button>
<p-button [rounded]="true" [raised]="true" [outlined]="true" (onClick)="readBook(book?.id)" icon="pi pi-eye"></p-button>
<p-button [rounded]="true" [raised]="true" [outlined]="true" icon="pi pi-download"></p-button>
</div>
</div>

View File

@@ -39,7 +39,7 @@
}
.book-title {
font-size: 1.65rem;
font-size: 1.25rem;
font-weight: bold;
margin: 0;
}
@@ -86,3 +86,16 @@
.book-description {
margin-top: 16px;
}
.book-navigation {
display: flex;
justify-content: space-between;
margin: 0.8rem 1rem 0;
}
.book-subtitle {
font-size: 1rem;
color: var(--text-color-secondary);
margin-top: 0.25rem;
font-weight: normal;
}

View File

@@ -0,0 +1,116 @@
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {BookService} from '../../service/book.service';
import {Book} from '../../model/book.model';
import {Button} from 'primeng/button';
import {NgForOf, NgIf} from '@angular/common';
import {BooksMetadataDialogComponent} from '../books-metadata-dialog/books-metadata-dialog.component';
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
import {Subscription} from 'rxjs';
import {TagModule} from 'primeng/tag';
@Component({
selector: 'app-book-metadata',
templateUrl: './book-metadata.component.html',
styleUrls: ['./book-metadata.component.scss'],
imports: [
Button,
NgForOf,
TagModule,
NgIf
]
})
export class BookMetadataComponent implements OnInit, OnDestroy {
book: Book | null = null;
nextBookId: number | null = null;
previousBookId: number | null = null;
private routeSubscription!: Subscription;
private dialogRef: DynamicDialogRef | undefined;
private dialogSubscription?: Subscription;
constructor(
private bookService: BookService, private activatedRoute: ActivatedRoute,
private dialogService: DialogService, private router: Router) {
}
ngOnInit(): void {
this.routeSubscription = this.activatedRoute.paramMap.subscribe((paramMap) => {
const bookId = +paramMap.get('bookId')!;
const libraryId = +paramMap.get('libraryId')!;
if (bookId && libraryId) {
this.loadBookWithNeighbors(bookId, libraryId);
}
});
}
private loadBookWithNeighbors(libraryId: number, bookId: number): void {
this.bookService.getBookWithNeighbours(bookId, libraryId).subscribe((response) => {
this.book = response.currentBook;
this.nextBookId = response.nextBookId;
this.previousBookId = response.previousBookId;
});
}
navigateToBook(bookId: number | null, libraryId: number | null): void {
if (bookId && libraryId) {
this.router.navigate(['/library', libraryId, 'book', bookId, 'info']);
}
}
canGoNext(): boolean {
return this.nextBookId !== null;
}
canGoPrevious(): boolean {
return this.previousBookId !== null;
}
getAuthorNames(book: Book | null): string {
if (book && book.metadata && book.metadata.authors) {
return book.metadata.authors.map((author) => author.name).join(', ');
}
return 'No authors available';
}
openEditDialog(bookId: number | undefined, libraryId: number | undefined) {
this.dialogRef = this.dialogService.open(BooksMetadataDialogComponent, {
header: 'Metadata: Google Books',
modal: false,
width: '65%',
height: '85%',
data: {
bookId: bookId,
libraryId: libraryId,
bookTitle: this.book?.metadata.title,
},
});
this.dialogSubscription = this.dialogRef.onClose.subscribe(() => {
if (this.book?.id && this.book.libraryId) {
this.loadBookWithNeighbors(this.book.id, this.book.libraryId);
}
});
}
coverImageSrc(bookId: number | undefined): string {
if (bookId === null) {
return 'assets/book-cover-metadata.png';
}
return this.bookService.getBookCoverUrl(bookId!);
}
ngOnDestroy(): void {
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
}
}
readBook(id: number | undefined) {
const url = `/pdf-viewer/book/${id}`;
window.open(url, '_blank');
this.routeSubscription?.unsubscribe();
this.dialogSubscription?.unsubscribe();
this.dialogRef?.close();
}
}

View File

@@ -1,18 +1,3 @@
<div *ngIf="!isLoading && bookMetadataList.length > 0" class="search-container">
<div class="input-group">
<input type="text" [(ngModel)]="searchText" placeholder="Enter book title" />
<p-button severity="success" (click)="populateTitle()">Search</p-button>
</div>
</div>
<div *ngIf="isLoading" class="loading">
Loading metadata...
</div>
<div *ngIf="errorMessage" class="error">
{{ errorMessage }}
</div>
<div *ngIf="!isLoading && bookMetadataList.length > 0" class="metadata-list">
<div *ngFor="let metadata of bookMetadataList" class="book-metadata">
<div class="metadata-header">
@@ -21,8 +6,10 @@
<p-button class="select-button" (click)="selectMetadata(metadata)">Select</p-button>
</div>
<div>
<h3>{{ metadata.title }}</h3>
<p><strong>Authors:</strong> {{ metadata.authors?.join(', ') || 'No authors available' }}</p>
<h3>
{{ metadata.title }}<span *ngIf="metadata.subtitle">: {{ metadata.subtitle }}</span>
</h3>
<p><strong>Authors:</strong> {{ metadata.authors.join(', ') || 'No authors available' }}</p>
<p><strong>Publisher:</strong> {{ metadata.publisher }}</p>
<p><strong>Published Date:</strong> {{ metadata.publishedDate }}</p>
<p><strong>ISBN:</strong> {{ metadata.isbn10 }}</p>
@@ -32,9 +19,3 @@
</div>
</div>
</div>
<div *ngIf="!isLoading && bookMetadataList.length === 0" class="no-metadata">
No metadata found for this book.
</div>

View File

@@ -1,6 +1,6 @@
import {Component, OnInit} from '@angular/core';
import {BookMetadata} from '../book/model/book.model';
import {BookService} from '../book/service/book.service';
import {BookMetadata} from '../../model/book.model';
import {BookService} from '../../service/book.service';
import {NgForOf, NgIf} from '@angular/common';
import {FormBuilder, FormsModule} from '@angular/forms';
import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog';
@@ -24,11 +24,12 @@ export class BooksMetadataDialogComponent implements OnInit {
errorMessage: string | null = null;
searchText: string = '';
bookId: number = 0;
libraryId: number = 0;
constructor(private bookService: BookService, public dynamicDialogConfig: DynamicDialogConfig,
private dynamicDialogRef: DynamicDialogRef, private router: Router) {
constructor(private bookService: BookService, public dynamicDialogConfig: DynamicDialogConfig, private dynamicDialogRef: DynamicDialogRef, private router: Router) {
this.searchText = dynamicDialogConfig.data.bookTitle;
this.bookId = this.dynamicDialogConfig.data.bookId;
this.libraryId = this.dynamicDialogConfig.data.libraryId;
}
ngOnInit(): void {
@@ -48,7 +49,7 @@ export class BooksMetadataDialogComponent implements OnInit {
this.bookService.setBookMetadata(metadata.googleBookId, this.bookId).subscribe({
next: () => {
this.dynamicDialogRef.close();
this.router.navigate(['/book', this.bookId, 'info']);
this.router.navigate(['/library', this.libraryId, 'book', this.bookId, 'info']);
},
error: (error) => {
this.errorMessage = 'Failed to save book metadata';

View File

@@ -14,7 +14,7 @@
<h4>{{ book.metadata.title }}</h4>
<p>{{ getAuthorNames(book) }}</p>
<p-button [rounded]="true" icon="pi pi-eye" class="view-btn" (click)="openBook(book.id)"></p-button>
<p-button [rounded]="true" icon="pi pi-info" class="read-btn" (click)="openBookInfo(book.id)"></p-button>
<p-button [rounded]="true" icon="pi pi-info" class="read-btn" (click)="openBookInfo(book.id, book.libraryId)"></p-button>
</div>
</div>
</div>

View File

@@ -85,7 +85,7 @@ export class DashboardScrollerComponent implements OnInit {
}
}
openBookInfo(bookId: number) {
this.router.navigate(['/book', bookId, 'info']);
openBookInfo(bookId: number, libraryId: number) {
this.router.navigate(['/library', libraryId, 'book', bookId, 'info']);
}
}

View File

@@ -1,5 +1,5 @@
<div class="layout-footer">
<img src="assets/layout/images/{{layoutService.config().colorScheme === 'light' ? 'logo-dark' : 'logo-white'}}.svg" alt="Logo" height="20" class="mr-2"/>
by
<span class="font-medium ml-2">PrimeNG</span>
<span class="font-medium ml-2">Aditya C.</span>
</div>

View File

@@ -2,8 +2,8 @@ import {Component, computed, OnInit} from '@angular/core';
import {AppMenuitemComponent} from './app.menuitem.component';
import {NgForOf, NgIf} from '@angular/common';
import {MenuModule} from 'primeng/menu';
import {LibraryService} from '../book/service/library.service';
import {Library} from '../book/model/library.model';
import {LibraryService} from '../../service/library.service';
import {Library} from '../../model/library.model';
@Component({
selector: 'app-menu',

View File

@@ -3,11 +3,11 @@ import { MenuItem } from 'primeng/api';
import { LayoutService } from './service/app.layout.service';
import { RouterLink } from '@angular/router';
import { DialogService as PrimeDialogService, DynamicDialogRef } from 'primeng/dynamicdialog';
import { LibraryCreatorComponent } from '../book/component/library-creator/library-creator.component';
import { LibraryCreatorComponent } from '../library-creator/library-creator.component';
import { TooltipModule } from 'primeng/tooltip';
import { FormsModule } from '@angular/forms';
import { InputTextModule } from 'primeng/inputtext';
import { SearchComponent } from '../book/component/search/search.component';
import { SearchComponent } from '../search/search.component';
import { NotificationComponent } from '../notification/notification.component'; // Import the NotificationComponent
@Component({

View File

@@ -46,22 +46,22 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('bootstrap4-light-blue', 'light')">
<img src="assets/layout/images/themes/bootstrap4-light-blue.svg" class="w-2rem h-2rem" alt="Bootstrap Light Blue">
<img src="../../../../../assets/layout/images/themes/bootstrap4-light-blue.svg" class="w-2rem h-2rem" alt="Bootstrap Light Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('bootstrap4-light-purple', 'light')">
<img src="assets/layout/images/themes/bootstrap4-light-purple.svg" class="w-2rem h-2rem" alt="Bootstrap Light Purple">
<img src="../../../../../assets/layout/images/themes/bootstrap4-light-purple.svg" class="w-2rem h-2rem" alt="Bootstrap Light Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('bootstrap4-dark-blue', 'dark')">
<img src="assets/layout/images/themes/bootstrap4-dark-blue.svg" class="w-2rem h-2rem" alt="Bootstrap Dark Blue">
<img src="../../../../../assets/layout/images/themes/bootstrap4-dark-blue.svg" class="w-2rem h-2rem" alt="Bootstrap Dark Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('bootstrap4-dark-purple', 'dark')">
<img src="assets/layout/images/themes/bootstrap4-dark-purple.svg" class="w-2rem h-2rem" alt="Bootstrap Dark Purple">
<img src="../../../../../assets/layout/images/themes/bootstrap4-dark-purple.svg" class="w-2rem h-2rem" alt="Bootstrap Dark Purple">
</button>
</div>
</div>
@@ -70,22 +70,22 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('md-light-indigo', 'light')">
<img src="assets/layout/images/themes/md-light-indigo.svg" class="w-2rem h-2rem" alt="Material Light Indigo">
<img src="../../../../../assets/layout/images/themes/md-light-indigo.svg" class="w-2rem h-2rem" alt="Material Light Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('md-light-deeppurple', 'light')">
<img src="assets/layout/images/themes/md-light-deeppurple.svg" class="w-2rem h-2rem" alt="Material Light DeepPurple">
<img src="../../../../../assets/layout/images/themes/md-light-deeppurple.svg" class="w-2rem h-2rem" alt="Material Light DeepPurple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('md-dark-indigo', 'dark')">
<img src="assets/layout/images/themes/md-dark-indigo.svg" class="w-2rem h-2rem" alt="Material Dark Indigo">
<img src="../../../../../assets/layout/images/themes/md-dark-indigo.svg" class="w-2rem h-2rem" alt="Material Dark Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('md-dark-deeppurple', 'dark')">
<img src="assets/layout/images/themes/md-dark-deeppurple.svg" class="w-2rem h-2rem" alt="Material Dark DeepPurple">
<img src="../../../../../assets/layout/images/themes/md-dark-deeppurple.svg" class="w-2rem h-2rem" alt="Material Dark DeepPurple">
</button>
</div>
</div>
@@ -94,22 +94,22 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('mdc-light-indigo', 'light')">
<img src="assets/layout/images/themes/md-light-indigo.svg" class="w-2rem h-2rem" alt="Material Light Indigo">
<img src="../../../../../assets/layout/images/themes/md-light-indigo.svg" class="w-2rem h-2rem" alt="Material Light Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('mdc-light-deeppurple', 'light')">
<img src="assets/layout/images/themes/md-light-deeppurple.svg" class="w-2rem h-2rem" alt="Material Light Deep Purple">
<img src="../../../../../assets/layout/images/themes/md-light-deeppurple.svg" class="w-2rem h-2rem" alt="Material Light Deep Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('mdc-dark-indigo', 'dark')">
<img src="assets/layout/images/themes/md-dark-indigo.svg" class="w-2rem h-2rem" alt="Material Dark Indigo">
<img src="../../../../../assets/layout/images/themes/md-dark-indigo.svg" class="w-2rem h-2rem" alt="Material Dark Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('mdc-dark-deeppurple', 'dark')">
<img src="assets/layout/images/themes/md-dark-deeppurple.svg" class="w-2rem h-2rem" alt="Material Dark Deep Purple">
<img src="../../../../../assets/layout/images/themes/md-dark-deeppurple.svg" class="w-2rem h-2rem" alt="Material Dark Deep Purple">
</button>
</div>
</div>
@@ -118,7 +118,7 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('tailwind-light', 'light')">
<img src="assets/layout/images/themes/tailwind-light.png" class="w-2rem h-2rem" alt="Tailwind Light">
<img src="../../../../../assets/layout/images/themes/tailwind-light.png" class="w-2rem h-2rem" alt="Tailwind Light">
</button>
</div>
</div>
@@ -127,7 +127,7 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('fluent-light', 'light')">
<img src="assets/layout/images/themes/fluent-light.png" class="w-2rem h-2rem" alt="Fluent Light">
<img src="../../../../../assets/layout/images/themes/fluent-light.png" class="w-2rem h-2rem" alt="Fluent Light">
</button>
</div>
</div>
@@ -136,42 +136,42 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-light-indigo', 'light')">
<img src="assets/layout/images/themes/lara-light-indigo.png" class="w-2rem h-2rem" alt="Lara Light Indigo">
<img src="../../../../../assets/layout/images/themes/lara-light-indigo.png" class="w-2rem h-2rem" alt="Lara Light Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-light-blue', 'light')">
<img src="assets/layout/images/themes/lara-light-blue.png" class="w-2rem h-2rem" alt="Lara Light Blue">
<img src="../../../../../assets/layout/images/themes/lara-light-blue.png" class="w-2rem h-2rem" alt="Lara Light Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-light-purple', 'light')">
<img src="assets/layout/images/themes/lara-light-purple.png" class="w-2rem h-2rem" alt="Lara Light Purple">
<img src="../../../../../assets/layout/images/themes/lara-light-purple.png" class="w-2rem h-2rem" alt="Lara Light Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-light-teal', 'light')">
<img src="assets/layout/images/themes/lara-light-teal.png" class="w-2rem h-2rem" alt="Lara Light Teal">
<img src="../../../../../assets/layout/images/themes/lara-light-teal.png" class="w-2rem h-2rem" alt="Lara Light Teal">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-dark-indigo', 'dark')">
<img src="assets/layout/images/themes/lara-dark-indigo.png" class="w-2rem h-2rem" alt="Lara Dark Indigo">
<img src="../../../../../assets/layout/images/themes/lara-dark-indigo.png" class="w-2rem h-2rem" alt="Lara Dark Indigo">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-dark-blue', 'dark')">
<img src="assets/layout/images/themes/lara-dark-blue.png" class="w-2rem h-2rem" alt="Lara Dark Blue">
<img src="../../../../../assets/layout/images/themes/lara-dark-blue.png" class="w-2rem h-2rem" alt="Lara Dark Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-dark-purple', 'dark')">
<img src="assets/layout/images/themes/lara-dark-purple.png" class="w-2rem h-2rem" alt="Lara Dark Purple">
<img src="../../../../../assets/layout/images/themes/lara-dark-purple.png" class="w-2rem h-2rem" alt="Lara Dark Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('lara-dark-teal', 'dark')">
<img src="assets/layout/images/themes/lara-dark-teal.png" class="w-2rem h-2rem" alt="Lara Dark Teal">
<img src="../../../../../assets/layout/images/themes/lara-dark-teal.png" class="w-2rem h-2rem" alt="Lara Dark Teal">
</button>
</div>
</div>
@@ -180,62 +180,62 @@
<div class="grid">
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('saga-blue', 'light')">
<img src="assets/layout/images/themes/saga-blue.png" class="w-2rem h-2rem" alt="Saga Blue">
<img src="../../../../../assets/layout/images/themes/saga-blue.png" class="w-2rem h-2rem" alt="Saga Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('saga-green', 'light')">
<img src="assets/layout/images/themes/saga-green.png" class="w-2rem h-2rem" alt="Saga Green">
<img src="../../../../../assets/layout/images/themes/saga-green.png" class="w-2rem h-2rem" alt="Saga Green">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('saga-orange', 'light')">
<img src="assets/layout/images/themes/saga-orange.png" class="w-2rem h-2rem" alt="Saga Orange">
<img src="../../../../../assets/layout/images/themes/saga-orange.png" class="w-2rem h-2rem" alt="Saga Orange">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('saga-purple', 'light')">
<img src="assets/layout/images/themes/saga-purple.png" class="w-2rem h-2rem" alt="Saga Purple">
<img src="../../../../../assets/layout/images/themes/saga-purple.png" class="w-2rem h-2rem" alt="Saga Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('vela-blue', 'dark')">
<img src="assets/layout/images/themes/vela-blue.png" class="w-2rem h-2rem" alt="Vela Blue">
<img src="../../../../../assets/layout/images/themes/vela-blue.png" class="w-2rem h-2rem" alt="Vela Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('vela-green', 'dark')">
<img src="assets/layout/images/themes/vela-green.png" class="w-2rem h-2rem" alt="Vela Green">
<img src="../../../../../assets/layout/images/themes/vela-green.png" class="w-2rem h-2rem" alt="Vela Green">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('vela-orange', 'dark')">
<img src="assets/layout/images/themes/vela-orange.png" class="w-2rem h-2rem" alt="Vela Orange">
<img src="../../../../../assets/layout/images/themes/vela-orange.png" class="w-2rem h-2rem" alt="Vela Orange">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('vela-purple', 'dark')">
<img src="assets/layout/images/themes/vela-purple.png" class="w-2rem h-2rem" alt="Vela Purple">
<img src="../../../../../assets/layout/images/themes/vela-purple.png" class="w-2rem h-2rem" alt="Vela Purple">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('arya-blue', 'dark')">
<img src="assets/layout/images/themes/arya-blue.png" class="w-2rem h-2rem" alt="Arya Blue">
<img src="../../../../../assets/layout/images/themes/arya-blue.png" class="w-2rem h-2rem" alt="Arya Blue">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('arya-green', 'dark')">
<img src="assets/layout/images/themes/arya-green.png" class="w-2rem h-2rem" alt="Arya Green">
<img src="../../../../../assets/layout/images/themes/arya-green.png" class="w-2rem h-2rem" alt="Arya Green">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('arya-orange', 'dark')">
<img src="assets/layout/images/themes/arya-orange.png" class="w-2rem h-2rem" alt="Arya Orange">
<img src="../../../../../assets/layout/images/themes/arya-orange.png" class="w-2rem h-2rem" alt="Arya Orange">
</button>
</div>
<div class="col-3">
<button class="p-link w-2rem h-2rem" (click)="changeTheme('arya-purple', 'dark')">
<img src="assets/layout/images/themes/arya-purple.png" class="w-2rem h-2rem" alt="Arya Purple">
<img src="../../../../../assets/layout/images/themes/arya-purple.png" class="w-2rem h-2rem" alt="Arya Purple">
</button>
</div>
</div>

View File

@@ -9,7 +9,7 @@
<h4>{{ book.metadata.title }}</h4>
<p>{{ getAuthorNames(book) }}</p>
<p-button [rounded]="true" icon="pi pi-eye" class="view-btn" (click)="readBook(book.id)"></p-button>
<p-button [rounded]="true" icon="pi pi-info" class="read-btn" (click)="openBookInfo(book.id)"></p-button>
<p-button [rounded]="true" icon="pi pi-info" class="read-btn" (click)="openBookInfo(book.id, book.libraryId)"></p-button>
</div>
</div>
</div>

View File

@@ -1,9 +1,8 @@
import { Component, OnInit, NgZone } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { BookProgressService } from '../../service/book-progress-service';
import { BookUpdateEvent } from '../../model/book-update-event.model';
import { BookService } from '../../service/book.service';
import { Book } from '../../model/book.model';
import {Book, BookUpdateEvent} from '../../model/book.model';
import { combineLatest } from 'rxjs';
import {InfiniteScrollDirective} from 'ngx-infinite-scroll';
import {Button} from 'primeng/button';
@@ -105,7 +104,7 @@ export class LibraryBrowserComponent implements OnInit {
window.open(url, '_blank');
}
openBookInfo(bookId: number) {
this.router.navigate(['/book', bookId, 'info']);
openBookInfo(bookId: number, libraryId: number) {
this.router.navigate(['/library', libraryId, 'book', bookId, 'info']);
}
}

View File

@@ -1,8 +1,8 @@
import {Component, OnInit, OnDestroy} from '@angular/core';
import {BookProgressService} from '../book/service/book-progress-service';
import {BookUpdateEvent} from '../book/model/book-update-event.model';
import {BookProgressService} from '../../service/book-progress-service';
import {Subscription} from 'rxjs';
import {NgClass} from '@angular/common';
import {BookUpdateEvent} from '../../model/book.model';
@Component({
selector: 'app-notification',

View File

@@ -12,7 +12,7 @@
<button *ngIf="searchQuery" pButton icon="pi pi-times" (click)="clearSearch()" class="clear-btn" type="button" aria-label="Clear Search"></button>
</span>
<div class="search-dropdown layout-menu" [class.show]="(books.length > 0 || (searchQuery?.length ?? 0) > 0)">
<div class="search-dropdown layout-menu" [class.show]="(books.length > 0)">
<ng-container *ngIf="books.length > 0; else noResults">
<div class="search-dropdown-item" *ngFor="let book of books" (click)="onBookClick(book)">
<img [src]="getBookCoverUrl(book.id)" alt="Book Cover" class="search-book-cover" />

View File

@@ -1,4 +0,0 @@
export interface Author {
id: number;
name: string;
}

View File

@@ -1,6 +0,0 @@
export interface BookSetting {
pageNumber: number;
zoom: number | string;
sidebar_visible: boolean;
spread: 'off' | 'even' | 'odd';
}

View File

@@ -1,14 +0,0 @@
import {BookMetadata} from './book.model';
export interface BookUpdateEvent {
libraryId: number;
filename: string;
book: {
id: number;
libraryId: number;
fileName: string;
addedOn: string;
metadata: BookMetadata;
};
parsingStatus: string;
}

View File

@@ -17,6 +17,7 @@ export interface PaginatedBooksResponse {
export interface BookMetadata {
thumbnail: string;
title: string;
subtitle?: string;
authors: Author[];
categories: Category[];
publisher: string;
@@ -38,4 +39,29 @@ export interface Category {
name: string;
}
export interface BookWithNeighborsDTO {
currentBook: Book;
previousBookId: number | null;
nextBookId: number | null;
}
export interface BookUpdateEvent {
libraryId: number;
filename: string;
book: {
id: number;
libraryId: number;
fileName: string;
addedOn: string;
metadata: BookMetadata;
};
parsingStatus: string;
}
export interface BookSetting {
pageNumber: number;
zoom: number | string;
sidebar_visible: boolean;
spread: 'off' | 'even' | 'odd';
}

View File

@@ -1,6 +1,6 @@
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {BookUpdateEvent} from '../model/book-update-event.model';
import {BookUpdateEvent} from '../model/book.model';
@Injectable({
providedIn: 'root',

View File

@@ -1,8 +1,7 @@
import {Observable} from 'rxjs';
import {Book, BookMetadata, PaginatedBooksResponse} from '../model/book.model';
import {Book, BookMetadata, BookSetting, BookWithNeighborsDTO, PaginatedBooksResponse} from '../model/book.model';
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {BookSetting} from '../model/book-settings.model';
@Injectable({
providedIn: 'root',
@@ -19,6 +18,10 @@ export class BookService {
return this.http.get<Book>(`${this.bookUrl}/${bookId}`);
}
getBookWithNeighbours(libraryId: number, bookId: number): Observable<BookWithNeighborsDTO> {
return this.http.get<BookWithNeighborsDTO>(`${this.libraryUrl}/${libraryId}/book/${bookId}/withNeighbors`);
}
loadBooks(libraryId: number, page: number): Observable<PaginatedBooksResponse> {
return this.http.get<PaginatedBooksResponse>(
`${this.libraryUrl}/${libraryId}/book?page=${page}&size=${this.pageSize}`

View File

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB