Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add epub reader and continue reading features; #45

Merged
merged 3 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-scroll-area": "^1.2.0-rc.7",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
Expand Down
95 changes: 95 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/api/backend/search/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,18 @@ export const useGetBooksQueryWithExternalDownloads = (params: SearchParams) => {
enabled: params.query !== "",
});
};

export const useGetBooksByMd5sQuery = (md5s: string[]) => {
return useQuery({
queryKey: ["search", md5s],
queryFn: async () => {
const books: BookItem[] = [];
for (const md5 of md5s) {
const response = await getBooks({ query: md5, lang: "all", limit: 1 });
books.push(response.results[0]);
}
return books;
},
enabled: md5s.length > 0,
});
};
14 changes: 10 additions & 4 deletions src/api/backend/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { ExternalDownloadLink } from "./downloads/types";

export interface BookItem {
authors: string;
book_content: string;
author: string;
book_filetype: string;
book_image: string;
book_lang: string;
book_length: string;
book_size: string;
book_source: string;
cid: string;
description: string;
external_cover_url: string;
id: number;
isbn: string;
link: string;
md5: string;
publication: string[];
other_titles: string;
publisher: string;
series: string;
title: string;
year: string;
}

export interface BookItemWithExternalDownloads extends BookItem {
Expand Down
38 changes: 30 additions & 8 deletions src/components/books/book-item.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useState, useMemo } from "react";
import { BookItem, BookItemWithExternalDownloads } from "@/api/backend/types";
import { Card, CardContent } from "../ui/card";
import PlaceholderImage from "@/assets/placeholder.png";
Expand All @@ -9,13 +9,25 @@ import { BookmarkButton } from "./bookmark";
import { BookDownloadButton } from "./download-button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "../ui/dialog";
import { ScrollArea } from "../ui/scroll-area";
import { Progress } from "../ui/progress";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../ui/tooltip";
import { useReadingProgressStore } from "@/stores/progress";

type BookItemProps = BookItemWithExternalDownloads | BookItem;

export function BookItemCard(props: BookItemProps) {
const [isReaderOpen, setIsReaderOpen] = useState(false);
const findReadingProgress = useReadingProgressStore((state) => state.findReadingProgress);

const isEpub = Boolean(props.link?.toLowerCase().endsWith(".epub"));

const progress = useMemo(() => {
const progress = findReadingProgress(props.md5);
if (progress && progress.totalPages > 0) {
return (progress.currentPage / progress.totalPages) * 100;
}
}, [props.md5, findReadingProgress]);

return (
<Card className="shadow-md transition-shadow duration-300 hover:shadow-lg">
<CardContent className="relative flex h-full w-full items-center p-4 md:p-6">
Expand All @@ -24,7 +36,7 @@ export function BookItemCard(props: BookItemProps) {
</div>

<div className="flex w-full flex-col gap-4 pt-12 sm:pt-0 md:flex-row md:gap-6">
<div className="mx-2 flex w-full max-w-[200px] items-center justify-center md:w-1/4">
<div className="mx-2 flex w-full max-w-[200px] flex-col items-center justify-center md:w-1/4">
<AspectRatio ratio={5 / 8} className="flex items-center">
<img
src={props.book_image ?? PlaceholderImage}
Expand All @@ -36,22 +48,33 @@ export function BookItemCard(props: BookItemProps) {
onClick={() => setIsReaderOpen(true)}
/>
</AspectRatio>
{progress != null && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Progress value={progress} className="mt-2" />
</TooltipTrigger>
<TooltipContent>
<p className="text-xs dark:text-gray-400">Progress: {progress!.toFixed(2)}%</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
<div className="flex flex-1 flex-col justify-between">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<h2 className="max-w-[90%] text-2xl font-bold">{props.title}</h2>
<p className="text-md dark:text-gray-400">By {props.authors}</p>
<p className="text-md dark:text-gray-400">By {props.author}</p>
</div>
<p className="line-clamp-3 break-all text-sm dark:text-gray-400">{props.description}</p>
<p className="text-sm dark:text-gray-400">{props.book_content}</p>
<p className="text-sm dark:text-gray-400">File size: {props.book_size}</p>
<p className="text-sm dark:text-gray-400">File type: {props.book_filetype}</p>
<p className="text-sm dark:text-gray-400">MD5: {props.md5}</p>
</div>
<div className="mt-4 flex flex-wrap gap-5">
{"externalDownloads" in props && <BookDownloadButton title={props.title} extension={props.book_filetype} externalDownloads={props.externalDownloads} primaryLink={props.link} />}
{isEpub && <EpubReader title={props.title} link={props.link} open={isReaderOpen} setIsOpen={setIsReaderOpen} />}
{isEpub && <EpubReader title={props.title} md5={props.md5} link={props.link} open={isReaderOpen} setIsOpen={setIsReaderOpen} />}
</div>
</div>
</div>
Expand Down Expand Up @@ -118,11 +141,10 @@ export function BookItemDialog(props: BookItemProps) {
<DialogContent>
<DialogHeader>
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>By {props.authors}</DialogDescription>
<DialogDescription>By {props.author}</DialogDescription>
</DialogHeader>
<ScrollArea className="max-h-[80vh]">
<div className="flex flex-col gap-4">
<p>{props.book_content}</p>
<p>File size: {props.book_size}</p>
<p>File type: {props.book_filetype}</p>
<p>MD5: {props.md5}</p>
Expand All @@ -132,7 +154,7 @@ export function BookItemDialog(props: BookItemProps) {
<DialogFooter className="flex flex-row justify-between md:justify-end">
{"externalDownloads" in props && <BookDownloadButton title={props.title} extension={props.book_filetype} externalDownloads={props.externalDownloads} primaryLink={props.link} />}

{isEpub && <EpubReader title={props.title} link={props.link} open={isReaderOpen} setIsOpen={setIsReaderOpen} />}
{isEpub && <EpubReader title={props.title} md5={props.md5} link={props.link} open={isReaderOpen} setIsOpen={setIsReaderOpen} />}
</DialogFooter>
</DialogContent>
</Dialog>
Expand Down
Loading
Loading