Skip to content
Closed
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
74 changes: 59 additions & 15 deletions src/components/newPdfViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useZoom, ZoomPluginPackage, ZoomMode } from '@embedpdf/plugin-
import { RenderLayer, RenderPluginPackage } from '@embedpdf/plugin-render/react';
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';

import { Download, ZoomIn, ZoomOut, Maximize2, Minimize2 } from "lucide-react";
import { Download, ZoomIn, ZoomOut, Maximize2, Minimize2, Focus } from "lucide-react";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { downloadFile } from "../lib/utils/download";
import { Button } from "./ui/button";
Expand All @@ -21,6 +21,8 @@ interface ControlProps {
documentId: string;
toggleFullscreen: () => void;
isFullscreen: boolean;
toggleZenMode: () => void;
isZenMode: boolean;
onDownload: () => Promise<void>;
forceMobile?: boolean;
isMobile: boolean;
Expand Down Expand Up @@ -57,8 +59,8 @@ function useBreakpoint() {
return {isMobile: width !== null && width < 768, isSmall: width !== null && width < 640};
}

const Controls = memo(function Controls({documentId, toggleFullscreen, isFullscreen, onDownload,
forceMobile, isMobile, isSmall, viewerRef}: ControlProps) {
const Controls = memo(function Controls({documentId, toggleFullscreen, isFullscreen, toggleZenMode,
isZenMode, onDownload, forceMobile, isMobile, isSmall, viewerRef}: ControlProps) {

const { provides: zoomProv, state: zoomState } = useZoom(documentId);
const { provides: scrollProv, state: scrollState } = useScroll(documentId);
Expand All @@ -79,6 +81,9 @@ const Controls = memo(function Controls({documentId, toggleFullscreen, isFullscr
setTotalPages(0);
};
}, [scrollCapability, documentId]);
// Zen mode isn't real browser fullscreen, but it hides everything around the
// paper the same way, so it gets the same "immersive" control layout.
const immersive = isFullscreen || isZenMode;

useEffect(() => {
if (!scrollProv) return;
Expand Down Expand Up @@ -120,7 +125,7 @@ const pageChange = useCallback(
}

const pageInput = (
<div className={(!isFullscreen && !isMobile) ? "flex flex-col items-center gap-2" : "flex flex-row items-center gap-2"}>
<div className={(!immersive && !isMobile) ? "flex flex-col items-center gap-2" : "flex flex-row items-center gap-2"}>
<input
type="text"
value={pageNo}
Expand Down Expand Up @@ -154,6 +159,16 @@ const pageChange = useCallback(
{isFullscreen ? <Minimize2 size={24} /> : <Maximize2 size={24} />}
</Button>

<Button
onClick={toggleZenMode}
className={`h-10 w-10 rounded p-0 text-white transition ${
isZenMode ? "bg-[#7d4fc7] ring-2 ring-white/50" : "bg-[#6536c1] hover:bg-[#7d4fc7]"
}`}
title={isZenMode ? "Exit zen mode (Esc)" : "Zen mode — hide everything but the paper"}
>
<Focus size={24} />
</Button>

<Button
onClick={onDownload}
className="h-10 w-10 rounded p-0 text-white bg-[#6536c1] transition hover:bg-[#7d4fc7]"
Expand Down Expand Up @@ -198,7 +213,7 @@ const pageChange = useCallback(
<div
style={{
position: "absolute",
...(isFullscreen ? {...fullScreenStyle, flexDirection: "row"} : {
...(immersive ? {...fullScreenStyle, flexDirection: "row"} : {
top: "40%",
right: 20,
transform: "translateY(-50%)",
Expand All @@ -210,7 +225,7 @@ const pageChange = useCallback(
gap: 16,
alignItems: "center",
alignSelf: "center",
width: isFullscreen ? "auto" : "96px",
width: immersive ? "auto" : "96px",
background: "#262635",
borderRadius: 8,
backdropFilter: "blur(6px)",
Expand All @@ -223,7 +238,7 @@ const pageChange = useCallback(
<div
style={{
position: "absolute",
...(isFullscreen ? fullScreenStyle : {
...(immersive ? fullScreenStyle : {
top: "40%",
right: 20,
transform: "translateY(-50%)",
Expand All @@ -235,7 +250,7 @@ const pageChange = useCallback(
gap: 16,
alignItems: "center",
alignSelf: "center",
width: isFullscreen ? "auto" : "96px",
width: immersive ? "auto" : "96px",
background: "#262635",
borderRadius: 8,
backdropFilter: "blur(6px)",
Expand All @@ -245,7 +260,7 @@ const pageChange = useCallback(
<div
style={{
display: "flex",
flexDirection: isFullscreen ? "row" as const : "column" as const,
flexDirection: immersive ? "row" as const : "column" as const,
gap: 16,
alignItems: "center",
}}
Expand Down Expand Up @@ -411,6 +426,7 @@ export default function PDFViewer({
const { isMobile, isSmall } = useBreakpoint();
const { resolvedTheme } = useTheme();
const [isFullscreen, setIsFullscreen] = useState(false);
const [isZenMode, setIsZenMode] = useState(false);
const viewerRef = useRef<HTMLDivElement>(null);
const effectiveBackgroundColor =
backgroundColor ?? (resolvedTheme === "light" ? "#F3F5FF" : "#070114");
Expand Down Expand Up @@ -441,6 +457,27 @@ export default function PDFViewer({
return () => document.removeEventListener("fullscreenchange", handleFullscreenChange);
}, []);

const toggleZenMode = useCallback(() => {
setIsZenMode((z) => !z);
}, []);

// Zen mode is a page-level "just the paper" view — no real Fullscreen API
// call (which iOS Safari barely supports), just an overlay that covers the
// navbar/title/related-papers behind it. Esc backs out of it like fullscreen does.
useEffect(() => {
if (!isZenMode) return;
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") setIsZenMode(false);
};
document.addEventListener("keydown", handleKeyDown);
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
return () => {
document.removeEventListener("keydown", handleKeyDown);
document.body.style.overflow = previousOverflow;
};
}, [isZenMode]);

const plugins = useMemo(() => [
createPluginRegistration(DocumentManagerPluginPackage, {
initialDocuments: [{ url }],
Expand Down Expand Up @@ -503,9 +540,12 @@ if (isLoading || !engine) {
data-pdf-viewer-scrollbars={hideScrollbar ? "hidden" : "visible"}
className={className}
style={{
height,
width: "100%",
position: "relative",
height: isZenMode ? "100dvh" : height,
width: isZenMode ? "100vw" : "100%",
position: isZenMode ? "fixed" : "relative",
top: isZenMode ? 0 : undefined,
left: isZenMode ? 0 : undefined,
zIndex: isZenMode ? 9999 : undefined,
backgroundColor: effectiveBackgroundColor,
display: "flex",
flexDirection: "column",
Expand All @@ -518,11 +558,13 @@ if (isLoading || !engine) {
activeDocumentId && (
<>
<WheelZoom documentId={activeDocumentId} viewerRef={viewerRef} />
{!hideControls && (isMobile && !isFullscreen) &&
{!hideControls && (isMobile && !isFullscreen && !isZenMode) &&
<Controls
documentId={activeDocumentId}
toggleFullscreen={toggleFullscreen}
isFullscreen={isFullscreen}
toggleZenMode={toggleZenMode}
isZenMode={isZenMode}
onDownload={handleDownload}
forceMobile={true}
isMobile={isMobile}
Expand Down Expand Up @@ -569,11 +611,13 @@ if (isLoading || !engine) {
)}
</DocumentContent>

{!hideControls && (!isMobile || isFullscreen) && (
{!hideControls && (!isMobile || isFullscreen || isZenMode) && (
<Controls
documentId={activeDocumentId}
toggleFullscreen={toggleFullscreen}
isFullscreen={isFullscreen}
toggleZenMode={toggleZenMode}
isZenMode={isZenMode}
onDownload={handleDownload}
forceMobile={false}
isMobile={isMobile}
Expand All @@ -588,4 +632,4 @@ if (isLoading || !engine) {
</div>
</>
);
}
}
Loading