Skip to content

Commit

Permalink
feature(book): Save reading progression from web
Browse files Browse the repository at this point in the history
  • Loading branch information
ragusa87 committed Aug 12, 2024
1 parent 1e2d496 commit a7144ad
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 7 deletions.
49 changes: 45 additions & 4 deletions assets/ReadEbook.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,59 @@
</template>

<script setup>
const props = defineProps({'file': String, 'css': String, 'bgColor': String})
const props = defineProps({
'file': String,
'css': String,
'bgColor': String,
'percent': String,
'progressionUrl': String
})
import { VueReader } from 'vue-book-reader'
const initialCfi = new URLSearchParams(window.location.search).get('cfi');
const locationChange = (detail) => {
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
const locationChange = debounce((detail) => {
const { fraction } = detail
console.log('locationChange', fraction, detail)
history.pushState({fraction: fraction, }, document.title, "?cfi=" + encodeURIComponent(detail.cfi) + "#page="+ detail.location.current+"&percent=" + (fraction * 100).toFixed(2));
}
if(props.progressionUrl !== ""){
fetch(props.progressionUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({percent: fraction, cfi: detail.cfi}),
})
.then(response => response.json())
.catch((error) => {
console.error('Error:', error);
});
}
}, 500);
const getInitialLocation = () => {
return initialCfi ?? null
// If we have a cfi in the url we use it
if(initialCfi){
return initialCfi
}
// If we know the current progress in percent we use it
if(props.percent !== "undefined"){
const percent = parseFloat(props.percent)
if(!isNaN(percent)){
return {fraction: percent};
}
}
return null;
}
const getRendition = async (rendition) => {
Expand Down
10 changes: 9 additions & 1 deletion assets/read-ebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@ document.addEventListener('DOMContentLoaded', () => {
const file = document.getElementById(mountId).getAttribute('data-file')
const css = document.getElementById(mountId).getAttribute('data-css')
const bgColor = document.getElementById(mountId).getAttribute('data-background-color')
createApp(ReadEBook, {file: file, css: css, bgColor: bgColor}).mount(`#${mountId}`);
const percent = document.getElementById(mountId).getAttribute('data-percent')
const progressionUrl = document.getElementById(mountId).getAttribute('data-progressionUrl')
createApp(ReadEBook, {
file: file,
css: css,
bgColor: bgColor,
percent: percent,
progressionUrl: progressionUrl
}).mount(`#${mountId}`);
});
37 changes: 36 additions & 1 deletion src/Controller/BookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use App\Entity\User;
use App\Repository\BookRepository;
use App\Service\BookFileSystemManager;
use App\Service\BookProgressionService;
use App\Service\ThemeSelector;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Process\Process;
Expand All @@ -21,6 +24,10 @@
#[Route('/books')]
class BookController extends AbstractController
{
public function __construct(private readonly BookProgressionService $bookProgressionService)
{
}

#[Route('/{book}/{slug}', name: 'app_book')]
public function index(Book $book, string $slug, BookRepository $bookRepository, EntityManagerInterface $manager, BookFileSystemManager $fileSystemManager): Response
{
Expand Down Expand Up @@ -93,7 +100,8 @@ public function read(
BookFileSystemManager $fileSystemManager,
PaginatorInterface $paginator,
ThemeSelector $themeSelector,
EntityManagerInterface $manager
EntityManagerInterface $manager,
BookProgressionService $bookProgressionService
): Response {
set_time_limit(120);
if ($slug !== $book->getSlug()) {
Expand All @@ -112,8 +120,13 @@ public function read(
switch ($book->getExtension()) {
case 'epub':
case 'mobi':
if ($request->isMethod('POST') && $request->headers->get('Content-Type') === 'application/json') {
return $this->updateProgression($request, $book, $user);
}

return $this->render('book/reader-files-epub.html.twig', [
'book' => $book,
'percent' => $this->bookProgressionService->getProgression($book, $user),
'file' => $fileSystemManager->getBookPublicPath($book),
'body_class' => $themeSelector->isDark() ? 'bg-darker' : '',
'isDark' => $themeSelector->isDark(),
Expand Down Expand Up @@ -311,4 +324,26 @@ public function relocate(Book $book, BookFileSystemManager $fileSystemManager, E
'slug' => $book->getSlug(),
]);
}

private function updateProgression(Request $request, Book $book, User $user): JsonResponse
{
try {
/** @var array{percent?:string|float, cfi?:string} $json */
$json = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new BadRequestException('Invalid percent in json', 0, $e);
}
$percent = $json['percent'] ?? null;
$percent = $percent === null ? null : floatval((string) $percent);

if ($percent !== null && $percent <= 1.0 && $percent >= 0) {
$this->bookProgressionService->setProgression($book, $user, $percent)
->flush();

return new JsonResponse([
'percent' => $percent,
]);
}
throw new BadRequestException('Invalid percent in json: '.json_encode($json));
}
}
7 changes: 7 additions & 0 deletions src/Service/BookProgressionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ public function setProgression(Book $book, User $user, ?float $progress): self
return $this;
}

public function flush(): self
{
$this->em->flush();

return $this;
}

public function processPageNumber(Book $book, bool $force = false): ?int
{
// Read from book entity (null > 0 is falsy)
Expand Down
2 changes: 1 addition & 1 deletion templates/book/reader-files-epub.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{% endblock %}

{% block content %}
<div id="vue-book-reader" data-file="{{ file }}" data-css="{{ block('readercss')|e('html_attr') }}" data-background-color="{{ isDark ? '#000' : '#fff' }}">
<div id="vue-book-reader" data-progressionUrl="{{ app.request.pathInfo }}" data-percent="{{ percent??'undefined' }}" data-file="{{ file }}" data-css="{{ block('readercss')|e('html_attr') }}" data-background-color="{{ isDark ? '#000' : '#fff' }}">
<a href="{{ file }}">Loading book</a>
</div>
{% endblock %}
Expand Down

0 comments on commit a7144ad

Please sign in to comment.