-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pdf view polish & add tree view component
- Loading branch information
1 parent
b1cfe2c
commit fb472c3
Showing
6 changed files
with
615 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
src/app/[locale]/t/[bookId]/_components/content-tab/pdf-chapters-section.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
125 changes: 125 additions & 0 deletions
125
src/app/[locale]/t/[bookId]/_components/pdf-view/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }), | ||
})); |
Oops, something went wrong.