diff --git a/booklore-api/build.gradle b/booklore-api/build.gradle index 27073af40..417726cc9 100644 --- a/booklore-api/build.gradle +++ b/booklore-api/build.gradle @@ -55,6 +55,8 @@ dependencies { // MapStruct implementation 'org.mapstruct:mapstruct:1.6.3' annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3' + + implementation 'io.documentnode:epub4j-core:4.2.2' } hibernate { diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/EpubController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/EpubController.java new file mode 100644 index 000000000..4245cb2ff --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/EpubController.java @@ -0,0 +1,29 @@ +package com.adityachandel.booklore.controller; + +import com.adityachandel.booklore.service.EpubService; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/epub") +public class EpubController { + + private final EpubService epubService; + + public EpubController(EpubService epubService) { + this.epubService = epubService; + } + + // New endpoint to serve the entire EPUB file + @GetMapping("/{bookId}/download") + public ResponseEntity downloadEpub(@PathVariable Long bookId) throws IOException { + ByteArrayResource epubFile = epubService.getEpubFile(bookId); + return ResponseEntity.ok() + .header("Content-Type", "application/epub+zip") + .header("Content-Disposition", "attachment; filename=\"book.epub\"") + .body(epubFile); + } +} \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/EpubService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/EpubService.java new file mode 100644 index 000000000..c9784c606 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/EpubService.java @@ -0,0 +1,27 @@ +package com.adityachandel.booklore.service; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +@Service +public class EpubService { + + // Method to retrieve the EPUB file as a ByteArrayResource + public ByteArrayResource getEpubFile(Long bookId) throws IOException { + String bookPath = "/Users/aditya.chandel/Downloads/Harry1.epub"; // Example path + File file = new File(bookPath); + if (!file.exists()) { + throw new IOException("EPUB file not found for book id: " + bookId); + } + + // Convert the EPUB file to a byte array + try (FileInputStream inputStream = new FileInputStream(file)) { + byte[] fileContent = inputStream.readAllBytes(); + return new ByteArrayResource(fileContent); + } + } +} \ No newline at end of file diff --git a/booklore-ui/package-lock.json b/booklore-ui/package-lock.json index 2f076e941..bd6217c00 100644 --- a/booklore-ui/package-lock.json +++ b/booklore-ui/package-lock.json @@ -20,6 +20,7 @@ "@primeng/themes": "19.0.2", "@stomp/rx-stomp": "^2.0.0", "@stomp/stompjs": "^7.0.0", + "epubjs": "^0.3.93", "ng-lazyload-image": "^9.1.3", "ngx-extended-pdf-viewer": "^22.0.0", "ngx-infinite-scroll": "^19.0.0", @@ -5473,6 +5474,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/localforage": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.34.tgz", + "integrity": "sha512-tJxahnjm9dEI1X+hQSC5f2BSd/coZaqbIl1m3TCl0q9SVuC52XcXfV0XmoCU1+PmjyucuVITwoTnN8OlTbEXXA==", + "deprecated": "This is a stub types definition for localforage (https://github.com/localForage/localForage). localforage provides its own type definitions, so you don't need @types/localforage installed!", + "license": "MIT", + "dependencies": { + "localforage": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -6402,6 +6413,16 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@xmldom/xmldom": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", + "integrity": "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g==", + "deprecated": "this version is no longer supported, please update to at least 0.8.*", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -7792,6 +7813,17 @@ "webpack": "^5.1.0" } }, + "node_modules/core-js": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.39.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", @@ -7810,7 +7842,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, "license": "MIT" }, "node_modules/cors": { @@ -7968,6 +7999,19 @@ "dev": true, "license": "MIT" }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/date-format": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", @@ -8409,6 +8453,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/epubjs": { + "version": "0.3.93", + "resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.3.93.tgz", + "integrity": "sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==", + "license": "BSD-2-Clause", + "dependencies": { + "@types/localforage": "0.0.34", + "@xmldom/xmldom": "^0.7.5", + "core-js": "^3.18.3", + "event-emitter": "^0.3.5", + "jszip": "^3.7.1", + "localforage": "^1.10.0", + "lodash": "^4.17.21", + "marks-pane": "^1.0.9", + "path-webpack": "0.0.3" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -8470,6 +8531,46 @@ "dev": true, "license": "MIT" }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", @@ -8792,6 +8893,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/espree": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", @@ -8899,6 +9015,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -9032,6 +9158,15 @@ "node": ">= 0.8" } }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -9968,6 +10103,12 @@ "node": ">=0.10.0" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immutable": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", @@ -10028,7 +10169,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -10270,7 +10410,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { @@ -10556,6 +10695,48 @@ ], "license": "MIT" }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/karma": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.4.tgz", @@ -11038,6 +11219,15 @@ } } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -11188,6 +11378,24 @@ "node": ">= 12.13.0" } }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/localforage/node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/locate-path": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", @@ -11208,7 +11416,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash-es": { @@ -11622,6 +11829,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/marks-pane": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/marks-pane/-/marks-pane-1.0.9.tgz", + "integrity": "sha512-Ahs4oeG90tbdPWwAJkAAoHg2lRR8lAs9mZXETNPO9hYg3AkjUJBKi1NQ4aaIQZVGrig7c/3NUV1jANl8rFTeMg==", + "license": "MIT" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -12169,6 +12382,12 @@ "dev": true, "license": "MIT" }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/ng-lazyload-image": { "version": "9.1.3", "resolved": "https://registry.npmjs.org/ng-lazyload-image/-/ng-lazyload-image-9.1.3.tgz", @@ -13001,6 +13220,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parchment": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", @@ -13184,6 +13409,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/path-webpack": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/path-webpack/-/path-webpack-0.0.3.tgz", + "integrity": "sha512-AmeDxedoo5svf7aB3FYqSAKqMxys014lVKBzy1o/5vv9CtU7U4wgGWL1dA2o6MOzcD53ScN4Jmiq6VbtLz1vIQ==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -13559,7 +13790,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, "license": "MIT" }, "node_modules/promise-inflight": { @@ -14480,6 +14710,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -15782,6 +16018,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/booklore-ui/package.json b/booklore-ui/package.json index 7ee8e477c..25dca039c 100644 --- a/booklore-ui/package.json +++ b/booklore-ui/package.json @@ -23,6 +23,7 @@ "@primeng/themes": "19.0.2", "@stomp/rx-stomp": "^2.0.0", "@stomp/stompjs": "^7.0.0", + "epubjs": "^0.3.93", "ng-lazyload-image": "^9.1.3", "ngx-extended-pdf-viewer": "^22.0.0", "ngx-infinite-scroll": "^19.0.0", @@ -54,4 +55,4 @@ "typescript": "~5.6.2", "typescript-eslint": "8.18.0" } -} \ No newline at end of file +} diff --git a/booklore-ui/src/app/app.routes.ts b/booklore-ui/src/app/app.routes.ts index 5946b866d..e4d002cc1 100644 --- a/booklore-ui/src/app/app.routes.ts +++ b/booklore-ui/src/app/app.routes.ts @@ -1,15 +1,14 @@ import {Routes} from '@angular/router'; import {PdfViewerComponent} from './book/components/pdf-viewer/pdf-viewer.component'; -import {MainDashboardComponent} from './dashboard/components/main-dashboard/main-dashboard.component'; import {BookBrowserComponent} from './book/components/book-browser/book-browser.component'; -import {AppLayoutComponent} from './layout/component/layout-main/app.layout.component'; +import {EpubViewerComponent} from './epub-viewer/component/epub-viewer.component'; export const routes: Routes = [ { - path: '', component: AppLayoutComponent, + path: '', component: EpubViewerComponent, children: [ { - path: '', component: MainDashboardComponent, + path: '', component: EpubViewerComponent, }, { path: 'all-books', component: BookBrowserComponent, diff --git a/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.html b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.html new file mode 100644 index 000000000..3beaa4a99 --- /dev/null +++ b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.html @@ -0,0 +1,34 @@ +
+ + + + +
    +
  • + {{ chapter.label }} +
  • +
+
+ + + +
+
+

Font Size: {{ fontSize }}%

+
+ + +
+ + + + +
+
+
+ +
+ + + +
diff --git a/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.scss b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.scss new file mode 100644 index 000000000..535026e41 --- /dev/null +++ b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.scss @@ -0,0 +1,106 @@ +.epub-viewer-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + position: relative; +} + +#epubContainer { + width: 100%; + height: 100%; + overflow: hidden; + position: relative; +} + +.menu-toggle-button, +.settings-toggle-button { + position: absolute; + top: 10px; + z-index: 1000; + margin: 0; +} + +.menu-toggle-button { + left: 10px; +} + +.settings-toggle-button { + right: 10px; +} + +.epub-controls-left, +.epub-controls-right { + position: absolute; + top: 50%; + transform: translateY(-50%); + z-index: 10; + font-size: 30px; + color: rgba(0, 0, 0, 0.5); + cursor: pointer; + background: none; + border: none; + padding: 10px; + transition: color 0.3s ease, transform 0.2s ease; +} + +.epub-controls-left { + left: 10px; +} + +.epub-controls-right { + right: 10px; +} + +.epub-controls-left:hover, +.epub-controls-right:hover { + color: rgba(0, 0, 0, 0.8); + transform: scale(1.1) translateY(-50%); +} + +.epub-controls-left:focus, +.epub-controls-right:focus { + outline: none; +} + +.chapter-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 10px; +} + +.chapter-item { + padding: 0.5px 0.5px; + font-size: 14px; + color: var(--text-color); + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; + border-radius: 5px; +} + +.chapter-item:hover { + color: greenyellow; +} + +.chapter-item:active { + background-color: var(--card-background); +} + +.settings-content { + padding: 10px; + display: flex; + flex-direction: column; + gap: 15px; +} + +.settings-content label { + font-size: 14px; +} + +.settings-content input[type="range"] { + width: 100%; + margin-top: 5px; +} diff --git a/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.ts b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.ts new file mode 100644 index 000000000..79bcc06d0 --- /dev/null +++ b/booklore-ui/src/app/epub-viewer/component/epub-viewer.component.ts @@ -0,0 +1,165 @@ +import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import ePub from 'epubjs'; +import {EpubService} from '../service/epub.service'; +import {Drawer} from 'primeng/drawer'; +import {Button} from 'primeng/button'; +import {NgForOf} from '@angular/common'; +import {FormsModule} from '@angular/forms'; +import {Divider} from 'primeng/divider'; +import {DropdownModule} from 'primeng/dropdown'; + +@Component({ + selector: 'app-epub-viewer', + templateUrl: './epub-viewer.component.html', + styleUrls: ['./epub-viewer.component.scss'], + imports: [ + Drawer, + Button, + NgForOf, + FormsModule, + Divider, + DropdownModule + ] +}) +export class EpubViewerComponent implements OnInit, OnDestroy { + + @ViewChild('epubContainer', {static: true}) epubContainer!: ElementRef; + bookId = 1; + chapters: { label: string; href: string }[] = []; + isDrawerVisible = false; + isSettingsDrawerVisible: boolean = false; + private book: any; + private rendition: any; + private keyListener: (e: KeyboardEvent) => void = () => { + }; + fontSize: number = 100; + + fontTypes: any[] = [ + {label: 'Serif', value: 'serif'}, + {label: 'Sans Serif', value: 'sans-serif'}, + {label: 'Roboto', value: 'roboto'}, + {label: 'Cursive', value: 'cursive'}, + {label: 'Monospace', value: 'monospace'} + ]; + selectedFontType: string = 'serif'; + + constructor(private epubService: EpubService) { + } + + ngOnInit(): void { + this.loadEpub(); + } + + loadEpub(): void { + this.epubService.downloadEpub(this.bookId).subscribe( + (data: Blob) => { + const fileReader = new FileReader(); + fileReader.onload = () => { + const epubData = fileReader.result as ArrayBuffer; + this.book = ePub(epubData); + + this.book.loaded.navigation.then((nav: any) => { + this.chapters = nav.toc.map((chapter: any) => ({ + label: chapter.label, + href: chapter.href, + })); + }); + + this.rendition = this.book.renderTo(this.epubContainer.nativeElement, { + flow: 'paginated', + width: '100%', + height: '100%', + allowScriptedContent: true, + }); + this.rendition.display(); + this.setupKeyListener(); + this.updateFontSize(); + }; + fileReader.readAsArrayBuffer(data); + }, + (error) => { + console.error('Failed to load the EPUB:', error); + } + ); + } + + ngOnDestroy(): void { + if (this.rendition) { + this.rendition.off('keyup', this.keyListener); + } + document.removeEventListener('keyup', this.keyListener); + } + + updateFontSize(): void { + if (this.rendition) { + this.rendition.themes.fontSize(`${this.fontSize}%`); + } + } + + increaseFontSize(): void { + this.fontSize += 10; + if (this.fontSize > 200) { + this.fontSize = 200; + } + this.updateFontSize(); + } + + decreaseFontSize(): void { + this.fontSize -= 10; + if (this.fontSize < 50) { + this.fontSize = 50; + } + this.updateFontSize(); + } + + changeFontType(event: any): void { + if (this.rendition) { + this.rendition.themes.font(this.selectedFontType); + } + } + + navigateToChapter(chapter: { label: string; href: string }): void { + if (this.book && chapter.href) { + this.book.rendition.display(chapter.href); + } + } + + nextPage(): void { + if (this.rendition) { + this.rendition.next(); + } + } + + prevPage(): void { + if (this.rendition) { + this.rendition.prev(); + } + } + + toggleDrawer(): void { + this.isDrawerVisible = !this.isDrawerVisible; + } + + toggleSettingsDrawer(): void { + this.isSettingsDrawerVisible = !this.isSettingsDrawerVisible; + } + + private setupKeyListener(): void { + this.keyListener = (e: KeyboardEvent) => { + switch (e.key) { + case 'ArrowLeft': + this.prevPage(); + break; + case 'ArrowRight': + this.nextPage(); + break; + default: + break; + } + }; + if (this.rendition) { + this.rendition.on('keyup', this.keyListener); + } + document.addEventListener('keyup', this.keyListener); + } +} diff --git a/booklore-ui/src/app/epub-viewer/service/epub.service.ts b/booklore-ui/src/app/epub-viewer/service/epub.service.ts new file mode 100644 index 000000000..56650af0a --- /dev/null +++ b/booklore-ui/src/app/epub-viewer/service/epub.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class EpubService { + private baseUrl = 'http://localhost:8080/api/epub'; + + constructor(private http: HttpClient) {} + + // New method to fetch the entire EPUB file + downloadEpub(bookId: number): Observable { + const url = `${this.baseUrl}/${bookId}/download`; + return this.http.get(url, { responseType: 'blob' }); + } +}