@vertesia/ui 0.80.0-dev.20251121 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -0
- package/lib/esm/core/components/MenuList.js +2 -5
- package/lib/esm/core/components/MenuList.js.map +1 -1
- package/lib/esm/core/components/MessageBox.js +1 -1
- package/lib/esm/core/components/MessageBox.js.map +1 -1
- package/lib/esm/core/components/shadcn/dialog.js +16 -2
- package/lib/esm/core/components/shadcn/dialog.js.map +1 -1
- package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js +6 -9
- package/lib/esm/core/components/shadcn/filters/filter/SelectFilter.js.map +1 -1
- package/lib/esm/core/components/shadcn/filters/filterBar.js +1 -1
- package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
- package/lib/esm/core/components/shadcn/selectBox.js +23 -5
- package/lib/esm/core/components/shadcn/selectBox.js.map +1 -1
- package/lib/esm/core/components/shadcn/tabs.js +3 -3
- package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
- package/lib/esm/env/index.js +3 -0
- package/lib/esm/env/index.js.map +1 -1
- package/lib/esm/features/agent/chat/AgentChart.js +184 -0
- package/lib/esm/features/agent/chat/AgentChart.js.map +1 -0
- package/lib/esm/features/agent/chat/ModernAgentConversation.js +87 -10
- package/lib/esm/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +6 -2
- package/lib/esm/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js +4 -4
- package/lib/esm/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js +4 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js +12 -4
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js +60 -56
- package/lib/esm/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/esm/features/agent/chat/index.js +1 -0
- package/lib/esm/features/agent/chat/index.js.map +1 -1
- package/lib/esm/features/agent/createChartTool.js +354 -0
- package/lib/esm/features/agent/createChartTool.js.map +1 -0
- package/lib/esm/features/agent/examples.js +295 -0
- package/lib/esm/features/agent/examples.js.map +1 -0
- package/lib/esm/features/agent/index.js +2 -0
- package/lib/esm/features/agent/index.js.map +1 -1
- package/lib/esm/features/agent/visualization.js +165 -0
- package/lib/esm/features/agent/visualization.js.map +1 -0
- package/lib/esm/features/facets/CollectionsFacetsNav.js +5 -1
- package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -1
- package/lib/esm/features/index.js +1 -0
- package/lib/esm/features/index.js.map +1 -1
- package/lib/esm/features/layout/GenericPageNavHeader.js +14 -4
- package/lib/esm/features/layout/GenericPageNavHeader.js.map +1 -1
- package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js +268 -0
- package/lib/esm/features/magic-pdf/AnnotatedImageSlider.js.map +1 -0
- package/lib/esm/features/magic-pdf/DownloadPopover.js +11 -11
- package/lib/esm/features/magic-pdf/DownloadPopover.js.map +1 -1
- package/lib/esm/features/magic-pdf/ExtractedContentView.js +77 -0
- package/lib/esm/features/magic-pdf/ExtractedContentView.js.map +1 -0
- package/lib/esm/features/magic-pdf/MagicPdfProvider.js +242 -0
- package/lib/esm/features/magic-pdf/MagicPdfProvider.js.map +1 -0
- package/lib/esm/features/magic-pdf/MagicPdfView.js +41 -47
- package/lib/esm/features/magic-pdf/MagicPdfView.js.map +1 -1
- package/lib/esm/features/pdf-viewer/PdfPageRenderer.js +261 -0
- package/lib/esm/features/pdf-viewer/PdfPageRenderer.js.map +1 -0
- package/lib/esm/features/pdf-viewer/PdfPageSlider.js +276 -0
- package/lib/esm/features/pdf-viewer/PdfPageSlider.js.map +1 -0
- package/lib/esm/features/pdf-viewer/SimplePdfViewer.js +71 -0
- package/lib/esm/features/pdf-viewer/SimplePdfViewer.js.map +1 -0
- package/lib/esm/features/pdf-viewer/index.js +4 -0
- package/lib/esm/features/pdf-viewer/index.js.map +1 -0
- package/lib/esm/features/store/collections/EditCollectionView.js +3 -5
- package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
- package/lib/esm/features/store/collections/SharedPropsEditor.js +1 -2
- package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -1
- package/lib/esm/features/store/objects/DocumentSearchResults.js +0 -7
- package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/esm/features/store/objects/components/ContentOverview.js +273 -83
- package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/esm/features/store/objects/components/useContentPanelHooks.js +153 -0
- package/lib/esm/features/store/objects/components/useContentPanelHooks.js.map +1 -0
- package/lib/esm/features/store/objects/layout/DocumentTableColumn.js +3 -3
- package/lib/esm/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
- package/lib/esm/features/store/objects/layout/renderers.js +13 -0
- package/lib/esm/features/store/objects/layout/renderers.js.map +1 -1
- package/lib/esm/features/utils/index.js +2 -0
- package/lib/esm/features/utils/index.js.map +1 -1
- package/lib/esm/features/utils/mimeType.js +8 -0
- package/lib/esm/features/utils/mimeType.js.map +1 -1
- package/lib/esm/features/utils/print.js +181 -0
- package/lib/esm/features/utils/print.js.map +1 -0
- package/lib/esm/features/utils/workflowStatus.js +43 -0
- package/lib/esm/features/utils/workflowStatus.js.map +1 -0
- package/lib/esm/router/HistoryNavigator.js +22 -2
- package/lib/esm/router/HistoryNavigator.js.map +1 -1
- package/lib/esm/shell/login/UserInfo.js +2 -1
- package/lib/esm/shell/login/UserInfo.js.map +1 -1
- package/lib/esm/shell/login/UserSessionMenu.js +7 -1
- package/lib/esm/shell/login/UserSessionMenu.js.map +1 -1
- package/lib/esm/widgets/form/Form.js +6 -2
- package/lib/esm/widgets/form/Form.js.map +1 -1
- package/lib/esm/widgets/markdown/MarkdownRenderer.js +226 -4
- package/lib/esm/widgets/markdown/MarkdownRenderer.js.map +1 -1
- package/lib/esm/widgets/schema-editor/ManagedSchema.js +0 -3
- package/lib/esm/widgets/schema-editor/ManagedSchema.js.map +1 -1
- package/lib/esm/widgets/schema-editor/json-schema4-utils.js +1 -1
- package/lib/esm/widgets/schema-editor/json-schema4-utils.js.map +1 -1
- package/lib/esm/widgets/xml-viewer/components/XMLViewer.js +18 -9
- package/lib/esm/widgets/xml-viewer/components/XMLViewer.js.map +1 -1
- package/lib/esm/widgets/xml-viewer/constants/index.js +10 -0
- package/lib/esm/widgets/xml-viewer/constants/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/types/core/components/MessageBox.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/dialog.d.ts +2 -1
- package/lib/types/core/components/shadcn/dialog.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/selectBox.d.ts +5 -1
- package/lib/types/core/components/shadcn/selectBox.d.ts.map +1 -1
- package/lib/types/core/components/shadcn/tabs.d.ts +3 -1
- package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/types/env/index.d.ts +2 -0
- package/lib/types/env/index.d.ts.map +1 -1
- package/lib/types/features/agent/chat/AgentChart.d.ts +48 -0
- package/lib/types/features/agent/chat/AgentChart.d.ts.map +1 -0
- package/lib/types/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -2
- package/lib/types/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts +2 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts +4 -2
- package/lib/types/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +2 -4
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/types/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/types/features/agent/chat/index.d.ts +1 -0
- package/lib/types/features/agent/chat/index.d.ts.map +1 -1
- package/lib/types/features/agent/createChartTool.d.ts +178 -0
- package/lib/types/features/agent/createChartTool.d.ts.map +1 -0
- package/lib/types/features/agent/examples.d.ts +59 -0
- package/lib/types/features/agent/examples.d.ts.map +1 -0
- package/lib/types/features/agent/index.d.ts +2 -0
- package/lib/types/features/agent/index.d.ts.map +1 -1
- package/lib/types/features/agent/visualization.d.ts +95 -0
- package/lib/types/features/agent/visualization.d.ts.map +1 -0
- package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
- package/lib/types/features/index.d.ts +1 -0
- package/lib/types/features/index.d.ts.map +1 -1
- package/lib/types/features/layout/GenericPageNavHeader.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts +13 -0
- package/lib/types/features/magic-pdf/AnnotatedImageSlider.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/DownloadPopover.d.ts.map +1 -1
- package/lib/types/features/magic-pdf/ExtractedContentView.d.ts +8 -0
- package/lib/types/features/magic-pdf/ExtractedContentView.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts +58 -0
- package/lib/types/features/magic-pdf/MagicPdfProvider.d.ts.map +1 -0
- package/lib/types/features/magic-pdf/MagicPdfView.d.ts +1 -1
- package/lib/types/features/magic-pdf/MagicPdfView.d.ts.map +1 -1
- package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts +83 -0
- package/lib/types/features/pdf-viewer/PdfPageRenderer.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts +29 -0
- package/lib/types/features/pdf-viewer/PdfPageSlider.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts +19 -0
- package/lib/types/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -0
- package/lib/types/features/pdf-viewer/index.d.ts +4 -0
- package/lib/types/features/pdf-viewer/index.d.ts.map +1 -0
- package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
- package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -1
- package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
- package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
- package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts +30 -0
- package/lib/types/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -0
- package/lib/types/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/types/features/utils/index.d.ts +2 -0
- package/lib/types/features/utils/index.d.ts.map +1 -1
- package/lib/types/features/utils/mimeType.d.ts +1 -0
- package/lib/types/features/utils/mimeType.d.ts.map +1 -1
- package/lib/types/features/utils/print.d.ts +10 -0
- package/lib/types/features/utils/print.d.ts.map +1 -0
- package/lib/types/features/utils/workflowStatus.d.ts +10 -0
- package/lib/types/features/utils/workflowStatus.d.ts.map +1 -0
- package/lib/types/router/HistoryNavigator.d.ts +3 -0
- package/lib/types/router/HistoryNavigator.d.ts.map +1 -1
- package/lib/types/shell/login/UserInfo.d.ts.map +1 -1
- package/lib/types/shell/login/UserSessionMenu.d.ts.map +1 -1
- package/lib/types/widgets/form/Form.d.ts.map +1 -1
- package/lib/types/widgets/markdown/MarkdownRenderer.d.ts +5 -1
- package/lib/types/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
- package/lib/types/widgets/schema-editor/ManagedSchema.d.ts.map +1 -1
- package/lib/types/widgets/xml-viewer/components/XMLViewer.d.ts.map +1 -1
- package/lib/types/widgets/xml-viewer/constants/index.d.ts +10 -0
- package/lib/types/widgets/xml-viewer/constants/index.d.ts.map +1 -1
- package/lib/vertesia-ui-core.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +1 -1
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-layout.js +1 -1
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-router.js +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +1 -1
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/package.json +11 -8
- package/src/core/components/MenuList.tsx +3 -6
- package/src/core/components/MessageBox.tsx +7 -2
- package/src/core/components/SelectBox.tsx +1 -1
- package/src/core/components/shadcn/dialog.tsx +19 -1
- package/src/core/components/shadcn/filters/filter/SelectFilter.tsx +31 -31
- package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
- package/src/core/components/shadcn/selectBox.tsx +32 -6
- package/src/core/components/shadcn/tabs.tsx +3 -2
- package/src/env/index.ts +5 -0
- package/src/features/agent/CHART_INSTRUCTIONS.md +228 -0
- package/src/features/agent/chat/AgentChart.tsx +601 -0
- package/src/features/agent/chat/ModernAgentConversation.tsx +123 -11
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +8 -2
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +12 -2
- package/src/features/agent/chat/ModernAgentOutput/InlineSlidingPlanPanel.tsx +6 -1
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +15 -10
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +122 -87
- package/src/features/agent/chat/index.ts +1 -0
- package/src/features/agent/createChartTool.ts +364 -0
- package/src/features/agent/examples.ts +321 -0
- package/src/features/agent/index.ts +2 -0
- package/src/features/agent/visualization.ts +227 -0
- package/src/features/facets/CollectionsFacetsNav.tsx +5 -1
- package/src/features/index.ts +1 -0
- package/src/features/layout/GenericPageNavHeader.tsx +15 -4
- package/src/features/magic-pdf/AnnotatedImageSlider.tsx +482 -0
- package/src/features/magic-pdf/DownloadPopover.tsx +45 -40
- package/src/features/magic-pdf/ExtractedContentView.tsx +132 -0
- package/src/features/magic-pdf/MagicPdfProvider.tsx +297 -0
- package/src/features/magic-pdf/MagicPdfView.tsx +184 -91
- package/src/features/pdf-viewer/PdfPageRenderer.tsx +612 -0
- package/src/features/pdf-viewer/PdfPageSlider.tsx +473 -0
- package/src/features/pdf-viewer/SimplePdfViewer.tsx +142 -0
- package/src/features/pdf-viewer/index.ts +3 -0
- package/src/features/store/collections/EditCollectionView.tsx +3 -5
- package/src/features/store/collections/SharedPropsEditor.tsx +1 -2
- package/src/features/store/objects/DocumentSearchResults.tsx +0 -7
- package/src/features/store/objects/components/ContentOverview.tsx +677 -210
- package/src/features/store/objects/components/useContentPanelHooks.ts +169 -0
- package/src/features/store/objects/layout/DocumentTableColumn.tsx +3 -3
- package/src/features/store/objects/layout/knowledge.md +1 -0
- package/src/features/store/objects/layout/renderers.tsx +25 -0
- package/src/features/utils/index.ts +3 -1
- package/src/features/utils/mimeType.ts +10 -1
- package/src/features/utils/print.ts +189 -0
- package/src/features/utils/workflowStatus.ts +44 -0
- package/src/router/HistoryNavigator.ts +30 -2
- package/src/shell/login/UserInfo.tsx +2 -0
- package/src/shell/login/UserSessionMenu.tsx +12 -1
- package/src/widgets/form/Form.tsx +8 -3
- package/src/widgets/markdown/MarkdownRenderer.tsx +350 -6
- package/src/widgets/schema-editor/ManagedSchema.ts +0 -3
- package/src/widgets/schema-editor/json-schema4-utils.ts +1 -1
- package/src/widgets/xml-viewer/components/XMLViewer.tsx +22 -10
- package/src/widgets/xml-viewer/constants/index.ts +11 -0
- package/lib/esm/features/magic-pdf/PageSlider.js +0 -70
- package/lib/esm/features/magic-pdf/PageSlider.js.map +0 -1
- package/lib/esm/features/magic-pdf/PdfPageProvider.js +0 -188
- package/lib/esm/features/magic-pdf/PdfPageProvider.js.map +0 -1
- package/lib/esm/features/magic-pdf/TextPageView.js +0 -62
- package/lib/esm/features/magic-pdf/TextPageView.js.map +0 -1
- package/lib/esm/features/magic-pdf/useResizeOnDrag.js +0 -34
- package/lib/esm/features/magic-pdf/useResizeOnDrag.js.map +0 -1
- package/lib/types/features/magic-pdf/PageSlider.d.ts +0 -9
- package/lib/types/features/magic-pdf/PageSlider.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts +0 -39
- package/lib/types/features/magic-pdf/PdfPageProvider.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/TextPageView.d.ts +0 -8
- package/lib/types/features/magic-pdf/TextPageView.d.ts.map +0 -1
- package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts +0 -9
- package/lib/types/features/magic-pdf/useResizeOnDrag.d.ts.map +0 -1
- package/src/features/magic-pdf/PageSlider.tsx +0 -142
- package/src/features/magic-pdf/PdfPageProvider.tsx +0 -310
- package/src/features/magic-pdf/TextPageView.tsx +0 -91
- package/src/features/magic-pdf/useResizeOnDrag.ts +0 -42
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, createContext, useContext, useCallback } from 'react';
|
|
2
|
+
import { Document, Page, pdfjs } from 'react-pdf';
|
|
3
|
+
import { Loader2 } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
// Configure PDF.js worker - use CDN for the worker
|
|
6
|
+
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
|
|
7
|
+
|
|
8
|
+
// Loading spinner component
|
|
9
|
+
function LoadingSpinner({ className, size = 'md' }: { className?: string; size?: 'sm' | 'md' | 'lg' }) {
|
|
10
|
+
const sizeClasses = {
|
|
11
|
+
sm: 'w-4 h-4',
|
|
12
|
+
md: 'w-6 h-6',
|
|
13
|
+
lg: 'w-8 h-8'
|
|
14
|
+
};
|
|
15
|
+
return (
|
|
16
|
+
<div className={`flex items-center justify-center ${className || ''}`}>
|
|
17
|
+
<Loader2 className={`${sizeClasses[size]} animate-spin text-muted-foreground`} />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PdfPageRendererProps {
|
|
23
|
+
pdfUrl: string;
|
|
24
|
+
pageNumber: number;
|
|
25
|
+
width?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
renderTextLayer?: boolean;
|
|
28
|
+
renderAnnotationLayer?: boolean;
|
|
29
|
+
onLoadSuccess?: (numPages: number) => void;
|
|
30
|
+
onError?: (error: Error) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function PdfPageRenderer({
|
|
34
|
+
pdfUrl,
|
|
35
|
+
pageNumber,
|
|
36
|
+
width,
|
|
37
|
+
className,
|
|
38
|
+
renderTextLayer = false,
|
|
39
|
+
renderAnnotationLayer = false,
|
|
40
|
+
onLoadSuccess,
|
|
41
|
+
onError
|
|
42
|
+
}: PdfPageRendererProps) {
|
|
43
|
+
const [loading, setLoading] = useState(true);
|
|
44
|
+
const [error, setError] = useState<Error | null>(null);
|
|
45
|
+
|
|
46
|
+
const handleLoadSuccess = ({ numPages }: { numPages: number }) => {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
onLoadSuccess?.(numPages);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleError = (err: Error) => {
|
|
52
|
+
setLoading(false);
|
|
53
|
+
setError(err);
|
|
54
|
+
onError?.(err);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (error) {
|
|
58
|
+
return (
|
|
59
|
+
<div className={`flex items-center justify-center text-destructive text-sm ${className || ''}`}>
|
|
60
|
+
Failed to load PDF
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className={className}>
|
|
67
|
+
{loading && (
|
|
68
|
+
<LoadingSpinner className="py-4" size="md" />
|
|
69
|
+
)}
|
|
70
|
+
<Document
|
|
71
|
+
file={pdfUrl}
|
|
72
|
+
onLoadSuccess={handleLoadSuccess}
|
|
73
|
+
onLoadError={handleError}
|
|
74
|
+
loading={null}
|
|
75
|
+
>
|
|
76
|
+
<Page
|
|
77
|
+
pageNumber={pageNumber}
|
|
78
|
+
width={width}
|
|
79
|
+
renderTextLayer={renderTextLayer}
|
|
80
|
+
renderAnnotationLayer={renderAnnotationLayer}
|
|
81
|
+
loading={<LoadingSpinner className="py-4" size="sm" />}
|
|
82
|
+
/>
|
|
83
|
+
</Document>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Page dimensions from PDF
|
|
89
|
+
interface PageDimensions {
|
|
90
|
+
width: number;
|
|
91
|
+
height: number;
|
|
92
|
+
aspectRatio: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// PDF document proxy type
|
|
96
|
+
interface PDFDocumentProxy {
|
|
97
|
+
numPages: number;
|
|
98
|
+
getPage: (pageNum: number) => Promise<{ getViewport: (options: { scale: number }) => { width: number; height: number } }>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Context for sharing PDF state
|
|
102
|
+
interface SharedPdfContextValue {
|
|
103
|
+
pdfUrl: string;
|
|
104
|
+
numPages: number;
|
|
105
|
+
loading: boolean;
|
|
106
|
+
error: Error | null;
|
|
107
|
+
pageDimensions: PageDimensions | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SharedPdfContext = createContext<SharedPdfContextValue | null>(null);
|
|
111
|
+
|
|
112
|
+
interface SharedPdfProviderProps {
|
|
113
|
+
pdfUrl: string;
|
|
114
|
+
urlLoading?: boolean;
|
|
115
|
+
children: (renderPage: (pageNumber: number, width?: number) => React.ReactNode) => React.ReactNode;
|
|
116
|
+
onLoadSuccess?: (numPages: number) => void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Provider that loads a PDF once using a single Document component.
|
|
121
|
+
* Children receive a renderPage function to render pages inside the Document.
|
|
122
|
+
*/
|
|
123
|
+
export function SharedPdfProvider({ pdfUrl, urlLoading = false, children, onLoadSuccess }: SharedPdfProviderProps) {
|
|
124
|
+
const [numPages, setNumPages] = useState(0);
|
|
125
|
+
const [loading, setLoading] = useState(true);
|
|
126
|
+
const [error, setError] = useState<Error | null>(null);
|
|
127
|
+
const [pageDimensions, setPageDimensions] = useState<PageDimensions | null>(null);
|
|
128
|
+
|
|
129
|
+
const handleLoadSuccess = async (pdf: PDFDocumentProxy) => {
|
|
130
|
+
setNumPages(pdf.numPages);
|
|
131
|
+
onLoadSuccess?.(pdf.numPages);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const page = await pdf.getPage(1);
|
|
135
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
136
|
+
setPageDimensions({
|
|
137
|
+
width: viewport.width,
|
|
138
|
+
height: viewport.height,
|
|
139
|
+
aspectRatio: viewport.width / viewport.height
|
|
140
|
+
});
|
|
141
|
+
} catch (err) {
|
|
142
|
+
console.error('Failed to get page dimensions:', err);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setLoading(false);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const handleError = (err: Error) => {
|
|
149
|
+
setLoading(false);
|
|
150
|
+
setError(err);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const isLoading = urlLoading || (pdfUrl ? loading : true);
|
|
154
|
+
|
|
155
|
+
const value: SharedPdfContextValue = {
|
|
156
|
+
pdfUrl,
|
|
157
|
+
numPages,
|
|
158
|
+
loading: isLoading,
|
|
159
|
+
error,
|
|
160
|
+
pageDimensions
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Render function that children use to render pages
|
|
164
|
+
const renderPage = (pageNumber: number, width?: number) => (
|
|
165
|
+
<Page
|
|
166
|
+
key={pageNumber}
|
|
167
|
+
pageNumber={pageNumber}
|
|
168
|
+
width={width}
|
|
169
|
+
renderTextLayer={false}
|
|
170
|
+
renderAnnotationLayer={false}
|
|
171
|
+
loading={<LoadingSpinner className="py-4" size="sm" />}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (error) {
|
|
176
|
+
return (
|
|
177
|
+
<div className="flex items-center justify-center text-destructive text-sm py-4">
|
|
178
|
+
Failed to load PDF
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<SharedPdfContext.Provider value={value}>
|
|
185
|
+
{pdfUrl ? (
|
|
186
|
+
<Document
|
|
187
|
+
file={pdfUrl}
|
|
188
|
+
onLoadSuccess={handleLoadSuccess}
|
|
189
|
+
onLoadError={handleError}
|
|
190
|
+
loading={<LoadingSpinner className="py-4" size="md" />}
|
|
191
|
+
>
|
|
192
|
+
{children(renderPage)}
|
|
193
|
+
</Document>
|
|
194
|
+
) : (
|
|
195
|
+
<LoadingSpinner className="py-4" size="md" />
|
|
196
|
+
)}
|
|
197
|
+
</SharedPdfContext.Provider>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function useSharedPdf() {
|
|
202
|
+
return useContext(SharedPdfContext);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// A4 portrait aspect ratio
|
|
206
|
+
const A4_ASPECT_RATIO = 210 / 297;
|
|
207
|
+
|
|
208
|
+
interface SimplePdfPageProps {
|
|
209
|
+
pageNumber: number;
|
|
210
|
+
width?: number;
|
|
211
|
+
className?: string;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Simple wrapper for a PDF page that adds styling.
|
|
216
|
+
* Must be used inside SharedPdfProvider's children render function.
|
|
217
|
+
*/
|
|
218
|
+
export function SimplePdfPage({ pageNumber, width, className }: SimplePdfPageProps) {
|
|
219
|
+
const context = useSharedPdf();
|
|
220
|
+
const aspectRatio = context?.pageDimensions?.aspectRatio ?? A4_ASPECT_RATIO;
|
|
221
|
+
const placeholderHeight = width ? Math.round(width / aspectRatio) : 200;
|
|
222
|
+
|
|
223
|
+
if (context?.loading) {
|
|
224
|
+
return (
|
|
225
|
+
<div
|
|
226
|
+
className={`flex items-center justify-center bg-muted ${className || ''}`}
|
|
227
|
+
style={{ height: placeholderHeight, width: width || '100%' }}
|
|
228
|
+
>
|
|
229
|
+
<LoadingSpinner size="md" />
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className={className}>
|
|
236
|
+
<Page
|
|
237
|
+
pageNumber={pageNumber}
|
|
238
|
+
width={width}
|
|
239
|
+
renderTextLayer={false}
|
|
240
|
+
renderAnnotationLayer={false}
|
|
241
|
+
loading={<LoadingSpinner className="py-4" size="sm" />}
|
|
242
|
+
/>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
interface PdfThumbnailListProps {
|
|
248
|
+
pdfUrl: string;
|
|
249
|
+
urlLoading?: boolean;
|
|
250
|
+
pageCount: number;
|
|
251
|
+
currentPage: number;
|
|
252
|
+
thumbnailWidth?: number;
|
|
253
|
+
onPageSelect: (pageNumber: number) => void;
|
|
254
|
+
renderThumbnail: (props: {
|
|
255
|
+
pageNumber: number;
|
|
256
|
+
isSelected: boolean;
|
|
257
|
+
pageElement: React.ReactNode;
|
|
258
|
+
onSelect: () => void;
|
|
259
|
+
}) => React.ReactNode;
|
|
260
|
+
/** Optional ref to the scroll container. If not provided, will search for scrollable ancestor. */
|
|
261
|
+
scrollContainerRef?: React.RefObject<HTMLElement | null>;
|
|
262
|
+
/** Callback when aspect ratio is determined from the PDF. Useful for synchronizing placeholder sizing. */
|
|
263
|
+
onAspectRatioChange?: (aspectRatio: number) => void;
|
|
264
|
+
/** Callback when item height is calculated. Useful for scroll position calculations. */
|
|
265
|
+
onItemHeightChange?: (itemHeight: number) => void;
|
|
266
|
+
/** Custom function to calculate item height. Receives placeholder height and should return total item height. */
|
|
267
|
+
calculateItemHeight?: (placeholderHeight: number) => number;
|
|
268
|
+
/** Callback when actual page count is determined from the PDF. Useful when initial count is estimated. */
|
|
269
|
+
onPageCountChange?: (pageCount: number) => void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Virtualized PDF thumbnail that only renders the Page when visible.
|
|
274
|
+
* Uses IntersectionObserver for efficient visibility detection.
|
|
275
|
+
*/
|
|
276
|
+
function VirtualizedThumbnail({
|
|
277
|
+
pageNumber,
|
|
278
|
+
width,
|
|
279
|
+
isSelected,
|
|
280
|
+
onSelect,
|
|
281
|
+
renderThumbnail,
|
|
282
|
+
aspectRatio = A4_ASPECT_RATIO,
|
|
283
|
+
rootMargin = '200px 0px'
|
|
284
|
+
}: {
|
|
285
|
+
pageNumber: number;
|
|
286
|
+
width?: number;
|
|
287
|
+
isSelected: boolean;
|
|
288
|
+
onSelect: () => void;
|
|
289
|
+
renderThumbnail: PdfThumbnailListProps['renderThumbnail'];
|
|
290
|
+
aspectRatio?: number;
|
|
291
|
+
rootMargin?: string;
|
|
292
|
+
}) {
|
|
293
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
294
|
+
const [hasBeenVisible, setHasBeenVisible] = useState(false);
|
|
295
|
+
|
|
296
|
+
// Set up intersection observer
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
const container = containerRef.current;
|
|
299
|
+
if (!container) return;
|
|
300
|
+
|
|
301
|
+
const observer = new IntersectionObserver(
|
|
302
|
+
(entries) => {
|
|
303
|
+
const entry = entries[0];
|
|
304
|
+
if (entry?.isIntersecting) {
|
|
305
|
+
setHasBeenVisible(true);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{ rootMargin, threshold: 0 }
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
observer.observe(container);
|
|
312
|
+
return () => observer.disconnect();
|
|
313
|
+
}, [rootMargin]);
|
|
314
|
+
|
|
315
|
+
const placeholderHeight = width ? Math.round(width / aspectRatio) : 200;
|
|
316
|
+
|
|
317
|
+
// Only render the actual Page component if visible or has been visible
|
|
318
|
+
// Once rendered, keep it rendered to preserve the canvas
|
|
319
|
+
const shouldRenderPage = hasBeenVisible;
|
|
320
|
+
|
|
321
|
+
const pageElement = shouldRenderPage ? (
|
|
322
|
+
<Page
|
|
323
|
+
pageNumber={pageNumber}
|
|
324
|
+
width={width}
|
|
325
|
+
renderTextLayer={false}
|
|
326
|
+
renderAnnotationLayer={false}
|
|
327
|
+
loading={
|
|
328
|
+
<div
|
|
329
|
+
className="flex items-center justify-center bg-muted"
|
|
330
|
+
style={{ height: placeholderHeight }}
|
|
331
|
+
>
|
|
332
|
+
<LoadingSpinner size="sm" />
|
|
333
|
+
</div>
|
|
334
|
+
}
|
|
335
|
+
/>
|
|
336
|
+
) : (
|
|
337
|
+
<div
|
|
338
|
+
className="flex items-center justify-center bg-muted"
|
|
339
|
+
style={{ height: placeholderHeight, width: width || '100%' }}
|
|
340
|
+
>
|
|
341
|
+
<span className="text-muted-foreground text-xs">{pageNumber}</span>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div ref={containerRef}>
|
|
347
|
+
{renderThumbnail({
|
|
348
|
+
pageNumber,
|
|
349
|
+
isSelected,
|
|
350
|
+
pageElement,
|
|
351
|
+
onSelect
|
|
352
|
+
})}
|
|
353
|
+
</div>
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Renders a list of PDF page thumbnails using a single Document.
|
|
359
|
+
* Uses windowed virtualization for better performance with large PDFs.
|
|
360
|
+
* Only renders components for pages within a window around the current scroll position.
|
|
361
|
+
*/
|
|
362
|
+
// Helper to find the scrollable ancestor element
|
|
363
|
+
function findScrollableAncestor(element: HTMLElement | null): HTMLElement | null {
|
|
364
|
+
if (!element) return null;
|
|
365
|
+
|
|
366
|
+
let current = element.parentElement;
|
|
367
|
+
while (current) {
|
|
368
|
+
const style = window.getComputedStyle(current);
|
|
369
|
+
const overflowY = style.overflowY;
|
|
370
|
+
if (overflowY === 'auto' || overflowY === 'scroll') {
|
|
371
|
+
return current;
|
|
372
|
+
}
|
|
373
|
+
current = current.parentElement;
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function PdfThumbnailList({
|
|
379
|
+
pdfUrl,
|
|
380
|
+
urlLoading = false,
|
|
381
|
+
pageCount,
|
|
382
|
+
currentPage,
|
|
383
|
+
thumbnailWidth,
|
|
384
|
+
onPageSelect,
|
|
385
|
+
renderThumbnail,
|
|
386
|
+
scrollContainerRef,
|
|
387
|
+
onAspectRatioChange,
|
|
388
|
+
onItemHeightChange,
|
|
389
|
+
calculateItemHeight,
|
|
390
|
+
onPageCountChange
|
|
391
|
+
}: PdfThumbnailListProps) {
|
|
392
|
+
const [error, setError] = useState<Error | null>(null);
|
|
393
|
+
const [visibleRange, setVisibleRange] = useState({ start: 0, end: Math.min(15, pageCount) });
|
|
394
|
+
// Start with null to indicate we haven't loaded the PDF yet
|
|
395
|
+
const [aspectRatio, setAspectRatio] = useState<number | null>(null);
|
|
396
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
397
|
+
|
|
398
|
+
const handleError = useCallback((err: Error) => {
|
|
399
|
+
setError(err);
|
|
400
|
+
}, []);
|
|
401
|
+
|
|
402
|
+
// Get actual page dimensions and count from PDF on load
|
|
403
|
+
const handleLoadSuccess = useCallback(async (pdf: PDFDocumentProxy) => {
|
|
404
|
+
// Report actual page count from PDF
|
|
405
|
+
onPageCountChange?.(pdf.numPages);
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
const page = await pdf.getPage(1);
|
|
409
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
410
|
+
const ratio = viewport.width / viewport.height;
|
|
411
|
+
setAspectRatio(ratio);
|
|
412
|
+
onAspectRatioChange?.(ratio);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
console.error('Failed to get page dimensions:', err);
|
|
415
|
+
// Fall back to A4 if we can't get dimensions
|
|
416
|
+
setAspectRatio(A4_ASPECT_RATIO);
|
|
417
|
+
onAspectRatioChange?.(A4_ASPECT_RATIO);
|
|
418
|
+
}
|
|
419
|
+
}, [onAspectRatioChange, onPageCountChange]);
|
|
420
|
+
|
|
421
|
+
// Use A4 as fallback if aspect ratio not yet determined
|
|
422
|
+
const effectiveAspectRatio = aspectRatio ?? A4_ASPECT_RATIO;
|
|
423
|
+
|
|
424
|
+
// Calculate placeholder height using actual aspect ratio from PDF
|
|
425
|
+
const placeholderHeight = thumbnailWidth ? Math.round(thumbnailWidth / effectiveAspectRatio) : 200;
|
|
426
|
+
// Total height per item - use custom calculator if provided, otherwise default formula
|
|
427
|
+
// Default: padding (p-2 = 8px top + 8px bottom) + page number text (~24px) + gap
|
|
428
|
+
const itemHeight = calculateItemHeight
|
|
429
|
+
? calculateItemHeight(placeholderHeight)
|
|
430
|
+
: placeholderHeight + 16 + 24 + 8;
|
|
431
|
+
|
|
432
|
+
// Notify parent of item height changes for scroll calculations
|
|
433
|
+
useEffect(() => {
|
|
434
|
+
if (itemHeight > 0 && aspectRatio !== null) {
|
|
435
|
+
onItemHeightChange?.(itemHeight);
|
|
436
|
+
}
|
|
437
|
+
}, [itemHeight, aspectRatio, onItemHeightChange]);
|
|
438
|
+
|
|
439
|
+
// Window size: how many pages to render above and below visible area
|
|
440
|
+
const WINDOW_BUFFER = 5;
|
|
441
|
+
|
|
442
|
+
// Track scroll position to update visible range
|
|
443
|
+
useEffect(() => {
|
|
444
|
+
// Find the scroll container - either from prop or by searching ancestors
|
|
445
|
+
const container = scrollContainerRef?.current || findScrollableAncestor(containerRef.current);
|
|
446
|
+
if (!container) return;
|
|
447
|
+
|
|
448
|
+
const updateVisibleRange = () => {
|
|
449
|
+
const scrollTop = container.scrollTop;
|
|
450
|
+
const viewportHeight = container.clientHeight;
|
|
451
|
+
|
|
452
|
+
// Calculate which pages are visible
|
|
453
|
+
const firstVisible = Math.floor(scrollTop / itemHeight);
|
|
454
|
+
const lastVisible = Math.ceil((scrollTop + viewportHeight) / itemHeight);
|
|
455
|
+
|
|
456
|
+
// Add buffer around visible range
|
|
457
|
+
const start = Math.max(0, firstVisible - WINDOW_BUFFER);
|
|
458
|
+
const end = Math.min(pageCount, lastVisible + WINDOW_BUFFER);
|
|
459
|
+
|
|
460
|
+
setVisibleRange(prev => {
|
|
461
|
+
if (prev.start !== start || prev.end !== end) {
|
|
462
|
+
return { start, end };
|
|
463
|
+
}
|
|
464
|
+
return prev;
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// Initial calculation
|
|
469
|
+
updateVisibleRange();
|
|
470
|
+
|
|
471
|
+
// Listen to scroll events
|
|
472
|
+
container.addEventListener('scroll', updateVisibleRange, { passive: true });
|
|
473
|
+
return () => container.removeEventListener('scroll', updateVisibleRange);
|
|
474
|
+
}, [itemHeight, pageCount, scrollContainerRef]);
|
|
475
|
+
|
|
476
|
+
if (error) {
|
|
477
|
+
return (
|
|
478
|
+
<div className="flex items-center justify-center text-destructive text-sm py-4">
|
|
479
|
+
Failed to load PDF
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (urlLoading || !pdfUrl) {
|
|
485
|
+
return <LoadingSpinner className="py-4" size="md" />;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Calculate spacer heights for virtual scrolling
|
|
489
|
+
const topSpacerHeight = visibleRange.start * itemHeight;
|
|
490
|
+
const bottomSpacerHeight = (pageCount - visibleRange.end) * itemHeight;
|
|
491
|
+
|
|
492
|
+
// Only render the virtualized list once we have the actual aspect ratio
|
|
493
|
+
// This prevents the total scroll height from changing after initial render
|
|
494
|
+
const hasAspectRatio = aspectRatio !== null;
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<div ref={containerRef}>
|
|
498
|
+
<Document
|
|
499
|
+
file={pdfUrl}
|
|
500
|
+
onLoadSuccess={handleLoadSuccess}
|
|
501
|
+
onLoadError={handleError}
|
|
502
|
+
loading={<LoadingSpinner className="py-4" size="md" />}
|
|
503
|
+
>
|
|
504
|
+
{hasAspectRatio ? (
|
|
505
|
+
<>
|
|
506
|
+
{/* Top spacer for pages above visible window */}
|
|
507
|
+
{topSpacerHeight > 0 && (
|
|
508
|
+
<div style={{ height: topSpacerHeight }} />
|
|
509
|
+
)}
|
|
510
|
+
|
|
511
|
+
{/* Only render pages within the visible window */}
|
|
512
|
+
{Array.from({ length: visibleRange.end - visibleRange.start }, (_, index) => {
|
|
513
|
+
const pageNumber = visibleRange.start + index + 1;
|
|
514
|
+
return (
|
|
515
|
+
<div key={pageNumber} data-page-index={pageNumber - 1} style={{ height: itemHeight, overflow: 'hidden' }}>
|
|
516
|
+
<VirtualizedThumbnail
|
|
517
|
+
pageNumber={pageNumber}
|
|
518
|
+
width={thumbnailWidth}
|
|
519
|
+
isSelected={pageNumber === currentPage}
|
|
520
|
+
onSelect={() => onPageSelect(pageNumber)}
|
|
521
|
+
renderThumbnail={renderThumbnail}
|
|
522
|
+
aspectRatio={effectiveAspectRatio}
|
|
523
|
+
/>
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
})}
|
|
527
|
+
|
|
528
|
+
{/* Bottom spacer for pages below visible window */}
|
|
529
|
+
{bottomSpacerHeight > 0 && (
|
|
530
|
+
<div style={{ height: bottomSpacerHeight }} />
|
|
531
|
+
)}
|
|
532
|
+
</>
|
|
533
|
+
) : (
|
|
534
|
+
<LoadingSpinner className="py-4" size="md" />
|
|
535
|
+
)}
|
|
536
|
+
</Document>
|
|
537
|
+
</div>
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
interface PdfDocumentRendererProps {
|
|
542
|
+
pdfUrl: string;
|
|
543
|
+
pageNumber: number;
|
|
544
|
+
width?: number;
|
|
545
|
+
height?: number;
|
|
546
|
+
className?: string;
|
|
547
|
+
renderTextLayer?: boolean;
|
|
548
|
+
renderAnnotationLayer?: boolean;
|
|
549
|
+
onPageChange?: (pageNumber: number, totalPages: number) => void;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function PdfDocumentRenderer({
|
|
553
|
+
pdfUrl,
|
|
554
|
+
pageNumber,
|
|
555
|
+
width,
|
|
556
|
+
height,
|
|
557
|
+
className,
|
|
558
|
+
renderTextLayer = false,
|
|
559
|
+
renderAnnotationLayer = false,
|
|
560
|
+
onPageChange
|
|
561
|
+
}: PdfDocumentRendererProps) {
|
|
562
|
+
const [numPages, setNumPages] = useState<number>(0);
|
|
563
|
+
const [loading, setLoading] = useState(true);
|
|
564
|
+
const [error, setError] = useState<Error | null>(null);
|
|
565
|
+
|
|
566
|
+
useEffect(() => {
|
|
567
|
+
if (numPages > 0) {
|
|
568
|
+
onPageChange?.(pageNumber, numPages);
|
|
569
|
+
}
|
|
570
|
+
}, [pageNumber, numPages, onPageChange]);
|
|
571
|
+
|
|
572
|
+
const handleLoadSuccess = ({ numPages: pages }: { numPages: number }) => {
|
|
573
|
+
setNumPages(pages);
|
|
574
|
+
setLoading(false);
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const handleError = (err: Error) => {
|
|
578
|
+
setLoading(false);
|
|
579
|
+
setError(err);
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
if (error) {
|
|
583
|
+
return (
|
|
584
|
+
<div className={`flex items-center justify-center text-destructive ${className || ''}`}>
|
|
585
|
+
<span>Failed to load PDF: {error.message}</span>
|
|
586
|
+
</div>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
return (
|
|
591
|
+
<div className={className}>
|
|
592
|
+
{loading && (
|
|
593
|
+
<LoadingSpinner className="py-8" size="lg" />
|
|
594
|
+
)}
|
|
595
|
+
<Document
|
|
596
|
+
file={pdfUrl}
|
|
597
|
+
onLoadSuccess={handleLoadSuccess}
|
|
598
|
+
onLoadError={handleError}
|
|
599
|
+
loading={null}
|
|
600
|
+
>
|
|
601
|
+
<Page
|
|
602
|
+
pageNumber={Math.min(pageNumber, numPages || 1)}
|
|
603
|
+
width={width}
|
|
604
|
+
height={height}
|
|
605
|
+
renderTextLayer={renderTextLayer}
|
|
606
|
+
renderAnnotationLayer={renderAnnotationLayer}
|
|
607
|
+
loading={<LoadingSpinner className="py-8" size="md" />}
|
|
608
|
+
/>
|
|
609
|
+
</Document>
|
|
610
|
+
</div>
|
|
611
|
+
);
|
|
612
|
+
}
|