Skip to content
Open
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
27 changes: 27 additions & 0 deletions app/components/common/ClientOnly.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// components/ClientOnly.js
import { lazy, Suspense, useEffect, useState } from "react";
import PropTypes from "prop-types";

const ClientOnly = ({ load, fallback = null, ...props }) => {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

if (!mounted) return fallback;

const Component = lazy(load);
return (
<Suspense fallback={fallback}>
<Component {...props} />
</Suspense>
);
};

ClientOnly.propTypes = {
load: PropTypes.func.isRequired,
fallback: PropTypes.node,
};

export default ClientOnly;
54 changes: 54 additions & 0 deletions app/hooks/useScreenDimensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// src/hooks/useBookDimensions.js
import { useState, useEffect, useCallback } from "react";

const DESKTOP_BREAKPOINT = 768; // Adjust this as needed

const useScreenDimensions = (aspectRatio = 1.3) => {
const [isMobileView, setIsMobileView] = useState(false);
const [screenWidth, setscreenWidth] = useState(600); // Initialize to 0
const [screenHeight, setscreenHeight] = useState(800); // Initialize to 0

const calculateDimensions = useCallback(() => {
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;

const newIsMobileView = windowWidth < DESKTOP_BREAKPOINT;
setIsMobileView(newIsMobileView);

let calculatedscreenWidth;
let calculatedscreenHeight;

if (newIsMobileView) {
// For mobile, make the page fill a good portion of the screen
// Subtract some padding/margin for better fit
calculatedscreenWidth = Math.min(450, windowWidth * 0.9); // Max 450px, or 90% of screen
calculatedscreenHeight = calculatedscreenWidth * aspectRatio;
} else {
// For desktop, target a comfortable reading size for a single page
// Subtracting some space for margin/buttons, and leave room for two pages
// Ensure book height doesn't exceed screen height, leaving space for UI
calculatedscreenHeight = Math.min(600, windowHeight * 0.8); // Max 600px, or 80% of screen height
calculatedscreenWidth = calculatedscreenHeight / aspectRatio;
}

setscreenWidth(calculatedscreenWidth);
setscreenHeight(calculatedscreenHeight);
}, [aspectRatio]); // useCallback dependency

useEffect(() => {
// Set initial dimensions when the component mounts
calculateDimensions();

// Add event listener for window resize
window.addEventListener("resize", calculateDimensions);

// Clean up event listener on component unmount
return () => {
window.removeEventListener("resize", calculateDimensions);
};
}, [calculateDimensions]); // useEffect dependency

return { isMobileView, screenWidth, screenHeight };
};

export default useScreenDimensions;
1 change: 1 addition & 0 deletions app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default [
// user dash
...prefix("dash", [
route("compras/:assetId", "routes/purchase_detail.tsx"),
route("ebook/:assetId", "routes/assets/EbookReaderPage.tsx"),
route("compras/:assetSlug/review", "routes/assets/ReviewAsset.tsx"),
layout("components/DashLayout/DashLayout.tsx", [
index("routes/start.tsx"),
Expand Down
107 changes: 107 additions & 0 deletions app/routes/assets/EbookReader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useEffect, useRef, useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import FlipBook from "./EbookReader/FlipBook";
import "react-pdf/dist/Page/AnnotationLayer.css";
import "react-pdf/dist/Page/TextLayer.css";
import useScreenDimensions from "~/hooks/useScreenDimensions";
import { ReactReader } from "react-reader";

pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;

export default function EbookReader() {
const [numPages, setNumPages] = useState(null);
const flipBookRef = useRef(null);
const [file, setFile] = useState(null);
const [fileType, setFileType] = useState(null);
const [fileUrl, setFileUrl] = useState(null);
// Responsive sizing
const { isMobileView, screenWidth, screenHeight } = useScreenDimensions();

const [location, setLocation] = useState<string | number>(0);

const onDocumentLoadSuccess = ({ numPages }) => {
setNumPages(numPages);
};

const processFile = async (e) => {
const file = e.target.files[0];
const type = file.name.split(".").pop().toLowerCase();
console.log(file);
setFileType(type);
switch (type) {
case "pdf":
setFile(file);
break;
case "epub":
const url = URL.createObjectURL(file);
setFileUrl(url);
break;
case "mobi":
case "azw3":
default:
throw new Error("Unsupported file type");
}
};

const asset = {
title: "Book sample",
};
console.log({ fileType, fileUrl });
return (
<div className="h-screen w-full bg-brand-500 md:p-10">
<input
type="file"
id="ebook-upload"
accept=".pdf,.epub"
onChange={processFile}
/>
<div
//className="w-full flex flex-col items-center justify-center"
>
<p className="mb-3 text-3xl font-semibold text-center">{asset.title}</p>
{file && fileType === "pdf" && (
<>
<Document
file={file}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={console.error}
loading="Loading PDF..."
noData="No PDF file specified."
>
{numPages && (
<FlipBook
title={asset.title}
numPages={numPages}
width={screenWidth}
height={screenHeight}
>
{Array.from(new Array(numPages), (el, index) => (
<div className="bg-white">
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
width={screenWidth}
height={screenHeight}
/>
</div>
))}
</FlipBook>
)}
</Document>
</>
)}

{fileUrl && fileType === "epub" && (
<>
<ReactReader
url="https://react-reader.metabits.no/files/alice.epub"
title={asset.title}
location={location}
locationChanged={(loc: string) => setLocation(loc)}
/>
</>
)}
</div>
</div>
);
}
65 changes: 65 additions & 0 deletions app/routes/assets/EbookReader/FlipBook.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useCallback, useEffect, useRef, useState } from "react";
import HTMLFlipBook from "react-pageflip";
import useScreenDimensions from "~/hooks/useScreenDimensions";

export default function FlipBook({ numPages, children, title }) {
const flipBookRef = useRef(null);

// Optional: Add navigation handlers if you want external buttons
const goToPrevPage = useCallback(() => {
if (flipBookRef.current) {
flipBookRef.current.pageFlip().flipPrev();
}
}, []);

const goToNextPage = useCallback(() => {
if (flipBookRef.current) {
flipBookRef.current.pageFlip().flipNext();
}
}, []);

const onFlip = useCallback((e) => {
console.log("Current page:", e.data);
}, []);

const { isMobileView, screenWidth, screenHeight } = useScreenDimensions();

return (
<div>
<HTMLFlipBook
className="shadow-xl" // Added shadow and border for better visual separation
width={screenWidth}
height={screenHeight}
showCover={true}
flippingTime={1000}
onFlip={onFlip}
ref={flipBookRef}
usePortrait={isMobileView}
drawShadow={true}
maxShadowOpacity={0.5}
mobileScrollSupport={true}
// onChangeOrientation={this.onChangeOrientation}
// onChangeState={this.onChangeState}
// startPage={0}
// minWidth={screenWidth}
// maxWidth={screenWidth}
// minHeight={screenHeight}
// maxHeight={screenHeight}
// flippingTime={1000}
// usePortrait={false}
// startZIndex={0}
// autoSize={true}
// clickEventForward={false}
// useMouseEvents={false}
// swipeDistance={0}
// showPageCorners={true}
// disableFlipByClick={false}
>
{children}
</HTMLFlipBook>
{/* add buttons */}
{/* current page and store progress */}
{/* */}
</div>
);
}
17 changes: 17 additions & 0 deletions app/routes/assets/EbookReaderPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ClientOnly from "~/components/common/ClientOnly";
import Spinner from "~/components/common/Spinner";

export default function EbookReaderPage() {
return (
<div>
<ClientOnly
load={() => import("./EbookReader")}
fallback={
<div className="w-full h-screen flex items-center justify-center">
<Spinner />
</div>
}
/>
</div>
);
}
Loading