From 1e9249b2578e8622da1ccb41236674804120f4aa Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <> Date: Fri, 13 Dec 2024 15:42:14 -0700 Subject: [PATCH] Added late read module --- .../booklore/controller/BookController.java | 30 ++++++- .../adityachandel/booklore/dto/BookDTO.java | 2 + .../adityachandel/booklore/entity/Book.java | 4 + .../booklore/repository/BookRepository.java | 2 + .../booklore/service/BooksService.java | 29 +++++-- .../transformer/BookSettingTransformer.java | 16 ++++ .../booklore/transformer/BookTransformer.java | 1 + .../V1__Create_Library_and_Book_Tables.sql | 23 +++-- booklore-ui/src/app/app.module.ts | 4 +- .../dashboard-scroller.component.html | 20 +++++ .../dashboard-scroller.component.scss | 83 +++++++++++++++++++ .../dashboard-scroller.component.ts | 68 +++++++++++++++ .../dashboard/dashboard.component.html | 25 ++++-- .../dashboard/dashboard.component.scss | 26 +++--- .../dashboard/dashboard.component.ts | 16 ++-- .../library-browser.component.html | 7 +- .../pdf-viewer/pdf-viewer.component.ts | 30 +++++-- .../src/app/book/model/book-settings.model.ts | 6 ++ booklore-ui/src/app/book/model/book.model.ts | 8 -- .../src/app/book/service/book.service.ts | 15 ++++ 20 files changed, 346 insertions(+), 69 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookSettingTransformer.java create mode 100644 booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.html create mode 100644 booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.scss create mode 100644 booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.ts create mode 100644 booklore-ui/src/app/book/model/book-settings.model.ts diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookController.java index c9981586..32ad9b82 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookController.java @@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.List; import java.util.Map; +import java.util.Random; @RequestMapping("/v1/book") @RestController @@ -30,9 +31,13 @@ public class BookController { return ResponseEntity.ok(booksService.getBook(bookId)); } - @GetMapping() - public ResponseEntity> getBooks(@RequestParam(defaultValue = "0") @Min(0) int page, @RequestParam(defaultValue = "25") @Min(1) @Max(100) int size) { - Page books = booksService.getBooks(page, size); + @GetMapping + public ResponseEntity> getBooks( + @RequestParam(defaultValue = "0") @Min(0) int page, + @RequestParam(defaultValue = "25") @Min(1) @Max(100) int size, + @RequestParam(defaultValue = "lastReadTime") String sortBy, + @RequestParam(defaultValue = "desc") String sortDir) { + Page books = booksService.getBooks(page, size, sortBy, sortDir); return ResponseEntity.ok(books); } @@ -44,6 +49,14 @@ public class BookController { @GetMapping("/{bookId}/cover") public ResponseEntity getBookCover(@PathVariable long bookId) { + Random random = new Random(); + int delay = 250 + random.nextInt(750); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } return ResponseEntity.ok(booksService.getBookCover(bookId)); } @@ -52,9 +65,20 @@ public class BookController { return booksService.getBookData(bookId); } + @GetMapping("/{bookId}/viewer-setting") + public ResponseEntity getBookViewerSettings(@PathVariable long bookId) { + return ResponseEntity.ok(booksService.getBookViewerSetting(bookId)); + } + @PutMapping("/{bookId}/viewer-setting") public ResponseEntity updateBookViewerSettings(@RequestBody BookViewerSettingDTO bookViewerSettingDTO, @PathVariable long bookId) { booksService.saveBookViewerSetting(bookId, bookViewerSettingDTO); return ResponseEntity.noContent().build(); } + + @PutMapping("/{bookId}/update-last-read") + public ResponseEntity updateBookViewerSettings(@PathVariable long bookId) { + booksService.updateLastReadTime(bookId); + return ResponseEntity.noContent().build(); + } } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/dto/BookDTO.java b/booklore-api/src/main/java/com/adityachandel/booklore/dto/BookDTO.java index 19b07e62..e773bdb0 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/dto/BookDTO.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/dto/BookDTO.java @@ -3,6 +3,7 @@ package com.adityachandel.booklore.dto; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import java.time.Instant; import java.util.ArrayList; import java.util.List; @@ -13,5 +14,6 @@ public class BookDTO { private Long libraryId; private String fileName; private String title; + private Instant lastReadTime; private List authors = new ArrayList<>(); } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/entity/Book.java b/booklore-api/src/main/java/com/adityachandel/booklore/entity/Book.java index 5ac52fd1..47c1c2dd 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/entity/Book.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/entity/Book.java @@ -3,6 +3,7 @@ package com.adityachandel.booklore.entity; import jakarta.persistence.*; import lombok.*; +import java.time.Instant; import java.util.List; @Entity @@ -40,4 +41,7 @@ public class Book { @OneToOne(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true) private BookViewerSetting viewerSetting; + + @Column(name = "last_read_time") + private Instant lastReadTime; } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookRepository.java b/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookRepository.java index b2f33932..f3397d65 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookRepository.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookRepository.java @@ -19,5 +19,7 @@ public interface BookRepository extends JpaRepository, JpaSpecificat Optional findBookByIdAndLibraryId(long id, long libraryId); List findByTitleContainingIgnoreCase(String title); + + Page findByLastReadTimeIsNotNull(Pageable pageable); } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/BooksService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/BooksService.java index a8596a2f..32b88a82 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/BooksService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/BooksService.java @@ -10,6 +10,7 @@ import com.adityachandel.booklore.exception.ErrorCode; import com.adityachandel.booklore.repository.BookRepository; import com.adityachandel.booklore.repository.BookViewerSettingRepository; import com.adityachandel.booklore.service.parser.PdfParser; +import com.adityachandel.booklore.transformer.BookSettingTransformer; import com.adityachandel.booklore.transformer.BookTransformer; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,6 +19,7 @@ import org.springframework.core.io.UrlResource; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -28,6 +30,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -49,10 +52,13 @@ public class BooksService { return BookTransformer.convertToBookDTO(book); } - public Page getBooks(int page, int size) { - PageRequest pageRequest = PageRequest.of(page, size); - Page bookPage = bookRepository.findAll(PageRequest.of(page, size)); - List bookDTOs = bookPage.getContent().stream().map(BookTransformer::convertToBookDTO).collect(Collectors.toList()); + public Page getBooks(int page, int size, String sortBy, String sortDir) { + Sort sort = Sort.by(Sort.Direction.fromString(sortDir), sortBy); + PageRequest pageRequest = PageRequest.of(page, size, sort); + Page bookPage = bookRepository.findByLastReadTimeIsNotNull(pageRequest); + List bookDTOs = bookPage.getContent().stream() + .map(BookTransformer::convertToBookDTO) + .collect(Collectors.toList()); return new PageImpl<>(bookDTOs, pageRequest, bookPage.getTotalElements()); } @@ -102,10 +108,6 @@ public class BooksService { Book book = pdfParser.parseBook(filePath.toAbsolutePath().toString(), appProperties.getPathConfig()); book.setViewerSetting(BookViewerSetting.builder() .bookId(book.getId()) - .pageNumber(0) - .zoom("page-fit") - .spread("off") - .sidebar_visible(false) .build()); return book; } @@ -147,4 +149,15 @@ public class BooksService { List books = bookRepository.findByTitleContainingIgnoreCase(title); return books.stream().map(BookTransformer::convertToBookDTO).toList(); } + + public BookViewerSettingDTO getBookViewerSetting(long bookId) { + BookViewerSetting bookViewerSetting = bookViewerSettingRepository.findById(bookId).orElseThrow(() -> ErrorCode.BOOK_NOT_FOUND.createException(bookId)); + return BookSettingTransformer.convertToDTO(bookViewerSetting); + } + + public void updateLastReadTime(long bookId) { + Book book = bookRepository.findById(bookId).orElseThrow(() -> ErrorCode.BOOK_NOT_FOUND.createException(bookId)); + book.setLastReadTime(Instant.now()); + bookRepository.save(book); + } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookSettingTransformer.java b/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookSettingTransformer.java new file mode 100644 index 00000000..efc47423 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookSettingTransformer.java @@ -0,0 +1,16 @@ +package com.adityachandel.booklore.transformer; + +import com.adityachandel.booklore.dto.BookViewerSettingDTO; +import com.adityachandel.booklore.entity.BookViewerSetting; + +public class BookSettingTransformer { + + public static BookViewerSettingDTO convertToDTO(BookViewerSetting bookViewerSetting) { + return BookViewerSettingDTO.builder() + .zoom(bookViewerSetting.getZoom()) + .pageNumber(bookViewerSetting.getPageNumber()) + .spread(bookViewerSetting.getSpread()) + .sidebar_visible(bookViewerSetting.isSidebar_visible()) + .build(); + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookTransformer.java b/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookTransformer.java index 8cd49669..a20bf108 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookTransformer.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/transformer/BookTransformer.java @@ -13,6 +13,7 @@ public class BookTransformer { bookDTO.setLibraryId(book.getLibrary().getId()); bookDTO.setFileName(book.getFileName()); bookDTO.setTitle(book.getTitle()); + bookDTO.setLastReadTime(book.getLastReadTime()); bookDTO.setAuthors(book.getAuthors().stream().map(AuthorTransformer::toAuthorDTO).collect(Collectors.toList())); return bookDTO; } diff --git a/booklore-api/src/main/resources/db/migration/V1__Create_Library_and_Book_Tables.sql b/booklore-api/src/main/resources/db/migration/V1__Create_Library_and_Book_Tables.sql index a1d84bd7..6ba6155b 100644 --- a/booklore-api/src/main/resources/db/migration/V1__Create_Library_and_Book_Tables.sql +++ b/booklore-api/src/main/resources/db/migration/V1__Create_Library_and_Book_Tables.sql @@ -7,13 +7,16 @@ CREATE TABLE IF NOT EXISTS library CREATE TABLE IF NOT EXISTS book ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(255), - file_name VARCHAR(255) NOT NULL, - library_id BIGINT NOT NULL, - path VARCHAR(1000) NOT NULL, + id BIGINT AUTO_INCREMENT PRIMARY KEY, + title VARCHAR(255), + file_name VARCHAR(255) NOT NULL, + library_id BIGINT NOT NULL, + path VARCHAR(1000) NOT NULL, + last_read_time TIMESTAMP NULL, CONSTRAINT fk_library FOREIGN KEY (library_id) REFERENCES library (id) ON DELETE CASCADE, - CONSTRAINT unique_file_library UNIQUE (file_name, library_id) + CONSTRAINT unique_file_library UNIQUE (file_name, library_id), + INDEX idx_library_id (library_id), + INDEX idx_last_read_time (last_read_time) ); CREATE TABLE IF NOT EXISTS author @@ -29,15 +32,17 @@ CREATE TABLE IF NOT EXISTS book_author_mapping author_id BIGINT NOT NULL, CONSTRAINT fk_book_author_mapping_book FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE CASCADE, CONSTRAINT fk_book_author_mapping_author FOREIGN KEY (author_id) REFERENCES author (id), - CONSTRAINT unique_book_author UNIQUE (book_id, author_id) + CONSTRAINT unique_book_author UNIQUE (book_id, author_id), + INDEX idx_book_id (book_id), + INDEX idx_author_id (author_id) ); CREATE TABLE IF NOT EXISTS book_viewer_setting ( book_id BIGINT PRIMARY KEY, page_number INT DEFAULT 1, - zoom VARCHAR(32) DEFAULT 'page-fit', + zoom VARCHAR(16) DEFAULT 'page-fit', sidebar_visible BOOLEAN DEFAULT false, - spread VARCHAR(32) DEFAULT 'off', + spread VARCHAR(16) DEFAULT 'odd', CONSTRAINT fk_book_viewer_setting FOREIGN KEY (book_id) REFERENCES book (id) ON DELETE CASCADE ); \ No newline at end of file diff --git a/booklore-ui/src/app/app.module.ts b/booklore-ui/src/app/app.module.ts index 01407bc7..ea3279e3 100644 --- a/booklore-ui/src/app/app.module.ts +++ b/booklore-ui/src/app/app.module.ts @@ -17,9 +17,9 @@ import {InputIconModule} from 'primeng/inputicon'; import {ToggleButtonModule} from 'primeng/togglebutton'; import {PasswordModule} from 'primeng/password'; import {ToastModule} from 'primeng/toast'; -import { LibraryBrowserComponent } from './book/component/library-browser/library-browser.component'; +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 {SearchComponent} from './book/component/search/search.component'; @NgModule({ declarations: [ diff --git a/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.html b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.html new file mode 100644 index 00000000..61a10319 --- /dev/null +++ b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.html @@ -0,0 +1,20 @@ +
+

Last Read

+
+
+ Cover of {{ book.title }} +
+

{{ book.title }}

+

{{ getAuthorNames(book) }}

+ +
+
+
+
+ diff --git a/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.scss b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.scss new file mode 100644 index 00000000..304da39f --- /dev/null +++ b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.scss @@ -0,0 +1,83 @@ +.book-list-container { + margin-bottom: 20px; +} + +.last-read-title { + font-size: 1.5rem; + color: var(--text-color); + margin-bottom: 10px; + padding: 5px; + text-align: left; +} + +.book-list { + display: flex; + flex-direction: row; + overflow-x: auto; + overflow-y: hidden; + scroll-snap-type: x mandatory; + padding: 10px; + gap: 20px; + height: 100%; + box-sizing: border-box; +} + +.book-item { + overflow: hidden; + background-color: var(--surface-card); + border-radius: 8px; + flex: 0 0 12%; + scroll-snap-align: start; + position: relative; +} + +.book-cover { + height: 225px; + object-fit: cover; + background-color: #444; + transition: background-color 0.3s ease, filter 0.3s ease; + position: relative; + border: none; +} + +.book-item:hover .book-cover { + filter: blur(2px); +} + +.book-info { + padding: 5px; + text-align: center; +} + +h4 { + font-size: 0.9rem; + color: var(--text-color); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; + margin-bottom: 3px; +} + +p { + color: var(--text-color-secondary); + font-size: 0.7rem; + line-height: 1.2; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.view-btn { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: none; + z-index: 1; +} + +.book-item:hover .view-btn { + display: block; +} diff --git a/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.ts b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.ts new file mode 100644 index 00000000..8e706d40 --- /dev/null +++ b/booklore-ui/src/app/book/component/dashboard-scroller/dashboard-scroller.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { Book } from '../../model/book.model'; +import { BookService } from '../../service/book.service'; +import { Button } from 'primeng/button'; +import { InfiniteScrollDirective } from 'ngx-infinite-scroll'; +import { NgForOf } from '@angular/common'; + +@Component({ + selector: 'app-dashboard-scroller', + templateUrl: './dashboard-scroller.component.html', + imports: [ + Button, + InfiniteScrollDirective, + NgForOf + ], + styleUrls: ['./dashboard-scroller.component.scss'] +}) +export class DashboardScrollerComponent implements OnInit { + books: Book[] = []; + private currentPage: number = 0; + + @ViewChild('scrollContainer') scrollContainer!: ElementRef; + + constructor(private bookService: BookService) {} + + ngOnInit(): void { + this.loadBooks(); + } + + loadBooks(): void { + this.bookService.loadLatestBooks(this.currentPage).subscribe({ + next: (response) => { + this.books = [...this.books, ...response.content]; + this.currentPage++; + }, + error: (err) => { + console.error('Error loading books:', err); + }, + }); + } + + coverImageSrc(bookId: number): string { + return this.bookService.getBookCoverUrl(bookId); + } + + loadMore(): void { + console.log('Loading more books...'); + this.loadBooks(); + } + + getAuthorNames(book: Book): string { + return book.authors?.map((author) => author.name).join(', ') || 'No authors available'; + } + + openBook(bookId: number): void { + const url = `/pdf-viewer/book/${bookId}`; + window.open(url, '_blank'); + } + + + onScroll(event: any): void { + const container = this.scrollContainer.nativeElement; + const scrollRight = container.scrollWidth - container.scrollLeft === container.clientWidth; + if (scrollRight) { + this.loadMore(); + } + } +} diff --git a/booklore-ui/src/app/book/component/dashboard/dashboard.component.html b/booklore-ui/src/app/book/component/dashboard/dashboard.component.html index 4c7b1efe..4063ea20 100644 --- a/booklore-ui/src/app/book/component/dashboard/dashboard.component.html +++ b/booklore-ui/src/app/book/component/dashboard/dashboard.component.html @@ -1,11 +1,18 @@
-

- Looks like you haven't added a library yet. Let's add one! -

- - +
+

+ Looks like you haven't added a library yet. Let's add one! +

+ + +
+ + + + +
diff --git a/booklore-ui/src/app/book/component/dashboard/dashboard.component.scss b/booklore-ui/src/app/book/component/dashboard/dashboard.component.scss index 17dc6934..71591377 100644 --- a/booklore-ui/src/app/book/component/dashboard/dashboard.component.scss +++ b/booklore-ui/src/app/book/component/dashboard/dashboard.component.scss @@ -1,27 +1,25 @@ .dashboard { - display: flex; flex-direction: column; align-items: center; justify-content: center; - margin-top: 5rem; text-align: center; +} - .no-library-header { - font-size: 1.5rem; - font-weight: 600; - margin-bottom: 1rem; - color: var(--text-color) - } - .p-dialog { - max-width: 90%; /* Ensure the dialog doesn't overflow the viewport */ - } +.dashboard .no-library-header { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1rem; + color: var(--text-color); +} + +.dashboard .p-dialog { + max-width: 90%; } ::ng-deep .p-dialog-mask { - background-color: rgba(0, 0, 0, 0.5); /* Darken the background with 50% opacity */ + background-color: rgba(0, 0, 0, 0.5); } -/* You can also adjust the blur if you want */ ::ng-deep .p-dialog-mask { - backdrop-filter: blur(3px); /* Optional: Apply blur to the background */ + backdrop-filter: blur(3px); } diff --git a/booklore-ui/src/app/book/component/dashboard/dashboard.component.ts b/booklore-ui/src/app/book/component/dashboard/dashboard.component.ts index f62909db..078912db 100644 --- a/booklore-ui/src/app/book/component/dashboard/dashboard.component.ts +++ b/booklore-ui/src/app/book/component/dashboard/dashboard.component.ts @@ -5,16 +5,18 @@ import { Button } from 'primeng/button'; import { NgIf } from '@angular/common'; import { LibraryCreatorComponent } from '../library-creator/library-creator.component'; import { DialogService, DynamicDialogRef } from 'primeng/dynamicdialog'; +import {DashboardScrollerComponent} from '../dashboard-scroller/dashboard-scroller.component'; @Component({ selector: 'app-home-page', templateUrl: './dashboard.component.html', imports: [ Button, - NgIf + NgIf, + DashboardScrollerComponent ], styleUrls: ['./dashboard.component.scss'], - providers: [DialogService], // Ensure the DialogService is available + providers: [DialogService], }) export class DashboardComponent { private libraries: WritableSignal; @@ -25,7 +27,7 @@ export class DashboardComponent { } get isLibrariesEmpty(): boolean { - return this.libraries()?.length === 1; + return this.libraries()?.length === 0; } createNewLibrary(event: MouseEvent) { @@ -39,12 +41,12 @@ export class DashboardComponent { this.ref = this.dialogService.open(LibraryCreatorComponent, { modal: true, - width: `${dialogWidthPercentage}%`, // Dynamic width - height: 'auto', // Let height adapt to content + width: `${dialogWidthPercentage}%`, + height: 'auto', style: { position: 'absolute', - top: `${buttonRect.bottom + 10}px`, // Position below the button - left: `${Math.max(leftPosition, 0)}px`, // Ensure it stays within the viewport + top: `${buttonRect.bottom + 10}px`, + left: `${Math.max(leftPosition, 0)}px` }, }); } diff --git a/booklore-ui/src/app/book/component/library-browser/library-browser.component.html b/booklore-ui/src/app/book/component/library-browser/library-browser.component.html index b548cda7..c8b6309a 100644 --- a/booklore-ui/src/app/book/component/library-browser/library-browser.component.html +++ b/booklore-ui/src/app/book/component/library-browser/library-browser.component.html @@ -1,4 +1,9 @@ -
+
Cover of {{ book.title }}
diff --git a/booklore-ui/src/app/book/component/pdf-viewer/pdf-viewer.component.ts b/booklore-ui/src/app/book/component/pdf-viewer/pdf-viewer.component.ts index f32499af..7fecd808 100644 --- a/booklore-ui/src/app/book/component/pdf-viewer/pdf-viewer.component.ts +++ b/booklore-ui/src/app/book/component/pdf-viewer/pdf-viewer.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, NgZone, OnInit, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { NgxExtendedPdfViewerModule, ScrollModeType } from 'ngx-extended-pdf-viewer'; import { BookService } from '../../service/book.service'; @@ -9,7 +9,7 @@ import { BookService } from '../../service/book.service'; imports: [NgxExtendedPdfViewerModule], templateUrl: './pdf-viewer.component.html', }) -export class PdfViewerComponent implements OnInit { +export class PdfViewerComponent implements OnInit, OnDestroy { bookId!: number; handTool = true; page = 1; @@ -31,18 +31,25 @@ export class PdfViewerComponent implements OnInit { this.route.paramMap.subscribe((params) => { this.bookId = +params.get('bookId')!; this.loadBook(this.bookId); + this.updateLastReadTime(); }); } + ngOnDestroy(): void { + this.updateLastReadTime(); + } + private loadBook(bookId: number): void { this.bookService.getBook(bookId).subscribe((book) => { this.zone.run(() => { - const { pageNumber, zoom, sidebar_visible, spread } = book.viewerSetting; - this.page = pageNumber || 1; - this.zoom = zoom || 'page-fit'; - this.sidebarVisible = sidebar_visible || false; - this.spread = spread || 'odd'; - this.isInitialLoad = false; + this.bookService.getBookSetting(bookId).subscribe((bookSetting) => { + const { pageNumber, zoom, sidebar_visible, spread } = bookSetting; + this.page = pageNumber || 1; + this.zoom = zoom || 'page-fit'; + this.sidebarVisible = sidebar_visible || false; + this.spread = spread || 'odd'; + this.isInitialLoad = false; + }); }); }); } @@ -58,7 +65,12 @@ export class PdfViewerComponent implements OnInit { this.bookService.updateViewerSetting(updatedViewerSetting, this.bookId).subscribe(); } + private updateLastReadTime(): void { + this.bookService.updateLastReadTime(this.bookId).subscribe(); + } + onPageChange(page: number): void { + console.log('page', page); if (page !== this.page) { this.page = page; this.updateViewerSetting(); @@ -66,6 +78,7 @@ export class PdfViewerComponent implements OnInit { } onZoomChange(zoom: string | number): void { + console.log('zoom', zoom); if (zoom !== this.zoom) { this.zoom = zoom; this.updateViewerSetting(); @@ -73,6 +86,7 @@ export class PdfViewerComponent implements OnInit { } onSidebarVisibleChange(visible: boolean): void { + console.log('sidebarVisible', visible); if (visible !== this.sidebarVisible) { this.sidebarVisible = visible; this.updateViewerSetting(); diff --git a/booklore-ui/src/app/book/model/book-settings.model.ts b/booklore-ui/src/app/book/model/book-settings.model.ts new file mode 100644 index 00000000..43924963 --- /dev/null +++ b/booklore-ui/src/app/book/model/book-settings.model.ts @@ -0,0 +1,6 @@ +export interface BookSetting { + pageNumber: number; + zoom: number | string; + sidebar_visible: boolean; + spread: 'off' | 'even' | 'odd'; +} diff --git a/booklore-ui/src/app/book/model/book.model.ts b/booklore-ui/src/app/book/model/book.model.ts index b1d26a8c..f1e42214 100644 --- a/booklore-ui/src/app/book/model/book.model.ts +++ b/booklore-ui/src/app/book/model/book.model.ts @@ -5,14 +5,6 @@ export interface Book { libraryId: number; title: string; authors: Author[]; - viewerSetting: BookViewerSetting; -} - -export interface BookViewerSetting { - pageNumber: number; - zoom: number | string; - sidebar_visible: boolean; - spread: 'off' | 'even' | 'odd'; } export interface PaginatedBooksResponse { diff --git a/booklore-ui/src/app/book/service/book.service.ts b/booklore-ui/src/app/book/service/book.service.ts index 9803ad59..ace50ae5 100644 --- a/booklore-ui/src/app/book/service/book.service.ts +++ b/booklore-ui/src/app/book/service/book.service.ts @@ -2,6 +2,7 @@ import { Observable } from 'rxjs'; import { Book, 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', @@ -23,6 +24,12 @@ export class BookService { ); } + loadLatestBooks(page: number): Observable { + return this.http.get( + `${this.bookUrl}?page=${page}&size=10&sortBy=lastReadTime&sortDir=desc` + ); + } + searchBooks(query: string): Observable { if (query.length < 3) { return new Observable(); @@ -35,6 +42,10 @@ export class BookService { return this.http.put(url, viewerSetting); } + getBookSetting(bookId: number): Observable { + return this.http.get(`${this.bookUrl}/${bookId}/viewer-setting`); + } + getBookDataUrl(bookId: number): string { return `${this.bookUrl}/${bookId}/data`; } @@ -42,4 +53,8 @@ export class BookService { getBookCoverUrl(bookId: number): string { return `${this.bookUrl}/${bookId}/cover`; } + + updateLastReadTime(bookId: number): Observable { + return this.http.put(`${this.bookUrl}/${bookId}/update-last-read`, {}); + } }