Skip to content

Commit

Permalink
pdf view polish & add tree view component
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedriad1 committed Sep 16, 2024
1 parent b1cfe2c commit fb472c3
Show file tree
Hide file tree
Showing 6 changed files with 615 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { useLocale, useTranslations } from "next-intl";
import { usePageNavigation } from "../usePageNavigation";
import { CheckIcon, XIcon } from "lucide-react";
import { useSearchParams } from "next/navigation";
import PdfChaptersList from "./pdf-chapters-section";

// const breadcrumbs = [
// "كتب الأخلاق والسلوك",
Expand Down Expand Up @@ -335,7 +336,7 @@ export default function ContentTab({ bookResponse }: TabProps) {
<>
<Separator className="my-4" />
<SidebarContainer className="flex flex-col gap-3">
sorry, pdf view doesnt support navigation
<PdfChaptersList />
</SidebarContainer>
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client";

import React, { useMemo } from "react";
import { PdfChapter, usePdfChapterStore } from "../pdf-view/store";
import { TreeDataItem, TreeView } from "@/components/tree-view";

const prepareChapter = (
chapter: PdfChapter,
level: number,
levelLimit: number,
): TreeDataItem => {
const id = chapter.id.toString();

return {
id,
name: chapter.title,
...(chapter.page ? { page: chapter.page } : {}),
...(chapter.children && chapter.children.length > 0 && level < levelLimit
? {
children: chapter.children.map((child) =>
prepareChapter(child, level + 1, levelLimit),
),
}
: {}),
};
};

export default function PdfChaptersList() {
const chapters = usePdfChapterStore((s) => s.chapters);
const navigateToPage = usePdfChapterStore((s) => s.navigateToPage);

const preparedChapters = useMemo(() => {
return chapters.map((chapter) => prepareChapter(chapter, 0, 5));
}, [chapters]);

if (!preparedChapters || preparedChapters.length === 0) return null;

console.log(preparedChapters);

return (
<div dir="rtl">
<TreeView
dir="rtl"
onSelectChange={(item) => {
if (!item || item?.children || !item.page) return;

// navigate when it's a chapter
navigateToPage(item.page);
}}
data={
(preparedChapters.length === 1
? preparedChapters[0]!.children
: preparedChapters) as any
}
/>
</div>
);
}
48 changes: 0 additions & 48 deletions src/app/[locale]/t/[bookId]/_components/pdf-view.tsx

This file was deleted.

125 changes: 125 additions & 0 deletions src/app/[locale]/t/[bookId]/_components/pdf-view/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use client";

import type { TurathBookResponse } from "@/server/services/books";
import type {
Core,
WebViewerInstance,
WebViewerOptions,
} from "@pdftron/webviewer";
// @ts-ignore
import WebViewer from "@pdftron/pdfjs-express-viewer";
import { useEffect, useRef } from "react";
import { PdfChapter, usePdfChapterStore } from "./store";
import { useTheme } from "next-themes";

const isInitializedByUrl = new Map<string, boolean>();

export default function PdfView({
pdf: pdfSource,
}: {
pdf: TurathBookResponse["turathResponse"]["pdf"];
}) {
const { resolvedTheme = "light" } = useTheme();
const viewerRef = useRef<HTMLDivElement>(null);
const instanceRef = useRef<WebViewerInstance | null>(null);

const setChapters = usePdfChapterStore((s) => s.setChapters);
const setIsLoading = usePdfChapterStore((s) => s.setIsLoading);
const setNavigateToPage = usePdfChapterStore((s) => s.setNavigateToPage);

useEffect(() => {
const initialize = async () => {
if (isInitializedByUrl.get(pdfSource.finalUrl!)) return;

isInitializedByUrl.set(pdfSource.finalUrl!, true);
setIsLoading(true);
setChapters([]);

const instance: WebViewerInstance = await WebViewer(
{
path: "/pdf-express", // point to where the files you copied are served from
initialDoc: pdfSource.finalUrl!, // path to your document
enableAnnotations: false,
disabledElements: [
"leftPanel",
"leftPanelButton",
"selectToolButton",
"themeChangeButton",
"languageButton",
],
licenseKey: "X83MG7YBqueJXUngqbHh",
// set the theme to dark
} satisfies WebViewerOptions,
viewerRef.current!,
);

instanceRef.current = instance;

instance.UI.setTheme(
resolvedTheme === "dark"
? instance.UI.Theme.DARK
: instance.UI.Theme.LIGHT,
);

instance.Core.documentViewer.addEventListener(
"documentLoaded",
async () => {
const chapters = await getDocumentChapters(instance!);

setChapters(chapters);
setIsLoading(false);
setNavigateToPage((page: number) => {
instance.Core.documentViewer.setCurrentPage(page, false);
});
},
);
};

if (typeof window !== "undefined") {
initialize();
}
}, [pdfSource.finalUrl]);

// when theme changes, update the theme of the viewer
useEffect(() => {
if (instanceRef.current) {
instanceRef.current.UI.setTheme(
resolvedTheme === "dark"
? instanceRef.current.UI.Theme.DARK
: instanceRef.current.UI.Theme.LIGHT,
);
}
}, [resolvedTheme]);

return (
<div className="mx-auto w-full min-w-0 flex-auto pt-28 lg:pt-20">
<div
ref={viewerRef}
className="h-[calc(100vh-7rem)] w-full border-t border-border lg:h-[calc(100vh-5rem)] lg:border-none [&_iframe]:h-[calc(100vh-7rem)] lg:[&_iframe]:h-[calc(100vh-5rem)]"
/>
</div>
);
}

async function getDocumentChapters(instance: WebViewerInstance) {
const { documentViewer } = instance.Core;
const pdfDoc = documentViewer.getDocument();

try {
const bookmarks = await pdfDoc.getBookmarks();
return parseBookmarks(bookmarks);
} catch (error) {
return [];
}
}

function parseBookmarks(bookmarks: Core.Bookmark[]): PdfChapter[] {
if (bookmarks.length === 0) return [];

return bookmarks.map((b) => ({
id: b.getIndex(),
title: b.getName(),
page: b.getPageNumber(),
children: parseBookmarks(b.getChildren()),
})) as PdfChapter[];
}
27 changes: 27 additions & 0 deletions src/app/[locale]/t/[bookId]/_components/pdf-view/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { create } from "zustand";

export type PdfChapter = {
id: number;
title: string;
page: number;
children: PdfChapter[];
};

interface PdfChapterStore {
chapters: PdfChapter[];
setChapters: (chapters: PdfChapter[]) => void;
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
navigateToPage: (page: number) => void;
setNavigateToPage: (navigateToPage: (page: number) => void) => void;
}

export const usePdfChapterStore = create<PdfChapterStore>((set) => ({
chapters: [],
setChapters: (chapters: PdfChapter[]) => set({ chapters }),
isLoading: false,
setIsLoading: (isLoading: boolean) => set({ isLoading }),
navigateToPage: (page: number) => {},
setNavigateToPage: (navigateToPage: (page: number) => void) =>
set({ navigateToPage }),
}));
Loading

0 comments on commit fb472c3

Please sign in to comment.