@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,482 @@
|
|
|
1
|
+
import { Button, Center, VTooltip } from "@vertesia/ui/core";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { ChevronsDown, ChevronsUp, Image, Loader2, Maximize, Minus, Plus, ScanSearch } from "lucide-react";
|
|
4
|
+
import { useRef, KeyboardEvent, useState, useEffect, useCallback } from "react";
|
|
5
|
+
import { ImageType, useMagicPdfContext } from "./MagicPdfProvider";
|
|
6
|
+
|
|
7
|
+
// Zoom levels as percentages (100 = fit to width)
|
|
8
|
+
const ZOOM_LEVELS = [50, 75, 100, 125, 150, 200, 300];
|
|
9
|
+
const DEFAULT_ZOOM = 100;
|
|
10
|
+
|
|
11
|
+
// Default aspect ratio (letter size) used before first image loads
|
|
12
|
+
const DEFAULT_ASPECT_RATIO = 11 / 8.5; // height / width
|
|
13
|
+
|
|
14
|
+
// Generate page order radiating outward from current page
|
|
15
|
+
// e.g., if current=5 and total=10: [5, 6, 4, 7, 3, 8, 2, 9, 1, 10]
|
|
16
|
+
function getPageLoadOrder(currentPage: number, totalPages: number): number[] {
|
|
17
|
+
const order: number[] = [currentPage];
|
|
18
|
+
let offset = 1;
|
|
19
|
+
|
|
20
|
+
while (order.length < totalPages) {
|
|
21
|
+
const next = currentPage + offset;
|
|
22
|
+
const prev = currentPage - offset;
|
|
23
|
+
|
|
24
|
+
if (next <= totalPages) order.push(next);
|
|
25
|
+
if (prev >= 1) order.push(prev);
|
|
26
|
+
|
|
27
|
+
offset++;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return order;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface AnnotatedImageSliderProps {
|
|
34
|
+
currentPage: number;
|
|
35
|
+
onChange: (pageNumber: number) => void;
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Image-based page slider that displays annotated/instrumented page images.
|
|
41
|
+
* Progressively loads images starting from current page and radiating outward.
|
|
42
|
+
* Loads first image immediately to determine aspect ratio for stable layout.
|
|
43
|
+
*/
|
|
44
|
+
export function AnnotatedImageSlider({ className, currentPage, onChange }: AnnotatedImageSliderProps) {
|
|
45
|
+
const [imageType, setImageType] = useState<ImageType>(ImageType.instrumented);
|
|
46
|
+
const [aspectRatio, setAspectRatio] = useState<number>(DEFAULT_ASPECT_RATIO);
|
|
47
|
+
const [loadedUrls, setLoadedUrls] = useState<Map<number, string>>(new Map());
|
|
48
|
+
const [zoom, setZoom] = useState<number>(DEFAULT_ZOOM);
|
|
49
|
+
const loadedPagesRef = useRef<Set<number>>(new Set());
|
|
50
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
51
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
const isProgrammaticScrollRef = useRef(false);
|
|
53
|
+
const { imageProvider, count } = useMagicPdfContext();
|
|
54
|
+
|
|
55
|
+
const zoomIn = useCallback(() => {
|
|
56
|
+
let currentIndex = ZOOM_LEVELS.findIndex(level => level >= zoom);
|
|
57
|
+
if (currentIndex === -1) {
|
|
58
|
+
currentIndex = ZOOM_LEVELS.length - 1;
|
|
59
|
+
}
|
|
60
|
+
const nextIndex = Math.min(currentIndex + 1, ZOOM_LEVELS.length - 1);
|
|
61
|
+
setZoom(ZOOM_LEVELS[nextIndex]);
|
|
62
|
+
}, [zoom]);
|
|
63
|
+
|
|
64
|
+
const zoomOut = useCallback(() => {
|
|
65
|
+
let currentIndex = ZOOM_LEVELS.findIndex(level => level >= zoom);
|
|
66
|
+
if (currentIndex === -1) {
|
|
67
|
+
currentIndex = ZOOM_LEVELS.length - 1;
|
|
68
|
+
}
|
|
69
|
+
const prevIndex = Math.max(currentIndex - 1, 0);
|
|
70
|
+
setZoom(ZOOM_LEVELS[prevIndex]);
|
|
71
|
+
}, [zoom]);
|
|
72
|
+
|
|
73
|
+
const fitToView = useCallback(() => {
|
|
74
|
+
setZoom(DEFAULT_ZOOM);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Load first image to determine aspect ratio
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
imageProvider.getPageImageUrl(1, imageType)
|
|
80
|
+
.then((url) => {
|
|
81
|
+
const img = new window.Image();
|
|
82
|
+
img.onload = () => {
|
|
83
|
+
if (img.width > 0 && img.height > 0) {
|
|
84
|
+
setAspectRatio(img.height / img.width);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
img.src = url;
|
|
88
|
+
})
|
|
89
|
+
.catch(() => {
|
|
90
|
+
// Keep default aspect ratio on error
|
|
91
|
+
});
|
|
92
|
+
}, [imageProvider, imageType]);
|
|
93
|
+
|
|
94
|
+
// Track the current imageType for change detection
|
|
95
|
+
const prevImageTypeRef = useRef(imageType);
|
|
96
|
+
|
|
97
|
+
// Progressive loading: load pages in parallel, prioritized from current page outward
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
let cancelled = false;
|
|
100
|
+
|
|
101
|
+
// Check if imageType changed - if so, reset and reload all pages
|
|
102
|
+
const imageTypeChanged = prevImageTypeRef.current !== imageType;
|
|
103
|
+
if (imageTypeChanged) {
|
|
104
|
+
prevImageTypeRef.current = imageType;
|
|
105
|
+
loadedPagesRef.current = new Set();
|
|
106
|
+
setLoadedUrls(new Map());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const loadOrder = getPageLoadOrder(currentPage, count);
|
|
110
|
+
|
|
111
|
+
// Load all pages in parallel, but they're already prioritized by loadOrder
|
|
112
|
+
// The imageProvider handles deduplication via its pending map
|
|
113
|
+
const loadPage = async (page: number) => {
|
|
114
|
+
if (cancelled || loadedPagesRef.current.has(page)) return;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const url = await imageProvider.getPageImageUrl(page, imageType);
|
|
118
|
+
if (!cancelled) {
|
|
119
|
+
loadedPagesRef.current.add(page);
|
|
120
|
+
setLoadedUrls(prev => new Map(prev).set(page, url));
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
// Skip failed pages
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Start all loads in parallel - prioritized pages will update state first
|
|
128
|
+
// since they're fetched first in the loadOrder
|
|
129
|
+
loadOrder.forEach(page => loadPage(page));
|
|
130
|
+
|
|
131
|
+
return () => {
|
|
132
|
+
cancelled = true;
|
|
133
|
+
};
|
|
134
|
+
}, [currentPage, count, imageType, imageProvider]);
|
|
135
|
+
|
|
136
|
+
// Scroll to current page when zoom changes to preserve position
|
|
137
|
+
const prevZoomRef = useRef(zoom);
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (prevZoomRef.current !== zoom && scrollContainerRef.current) {
|
|
140
|
+
prevZoomRef.current = zoom;
|
|
141
|
+
|
|
142
|
+
// Mark as programmatic scroll to avoid triggering onChange
|
|
143
|
+
isProgrammaticScrollRef.current = true;
|
|
144
|
+
|
|
145
|
+
const thumbnail = scrollContainerRef.current.querySelector(`[data-page="${currentPage}"]`);
|
|
146
|
+
if (thumbnail) {
|
|
147
|
+
// Use requestAnimationFrame to wait for DOM to update with new sizes
|
|
148
|
+
requestAnimationFrame(() => {
|
|
149
|
+
thumbnail.scrollIntoView({
|
|
150
|
+
behavior: 'instant',
|
|
151
|
+
block: 'center',
|
|
152
|
+
});
|
|
153
|
+
// Reset after scroll completes
|
|
154
|
+
requestAnimationFrame(() => {
|
|
155
|
+
isProgrammaticScrollRef.current = false;
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
isProgrammaticScrollRef.current = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}, [zoom, currentPage]);
|
|
163
|
+
|
|
164
|
+
// Jump to current page when it changes (user navigation via buttons/input)
|
|
165
|
+
const prevPageRef = useRef(currentPage);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (prevPageRef.current !== currentPage && scrollContainerRef.current) {
|
|
168
|
+
prevPageRef.current = currentPage;
|
|
169
|
+
|
|
170
|
+
// Mark as programmatic scroll to avoid triggering onChange
|
|
171
|
+
isProgrammaticScrollRef.current = true;
|
|
172
|
+
|
|
173
|
+
const thumbnail = scrollContainerRef.current.querySelector(`[data-page="${currentPage}"]`);
|
|
174
|
+
if (thumbnail) {
|
|
175
|
+
thumbnail.scrollIntoView({
|
|
176
|
+
behavior: 'instant',
|
|
177
|
+
block: 'nearest',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Reset after a short delay to allow scroll event to fire
|
|
182
|
+
requestAnimationFrame(() => {
|
|
183
|
+
isProgrammaticScrollRef.current = false;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}, [currentPage]);
|
|
187
|
+
|
|
188
|
+
// Update current page based on scroll position (when user scrolls manually)
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const container = scrollContainerRef.current;
|
|
191
|
+
if (!container) return;
|
|
192
|
+
|
|
193
|
+
let scrollDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
194
|
+
|
|
195
|
+
const handleScroll = () => {
|
|
196
|
+
// Skip if this is a programmatic scroll
|
|
197
|
+
if (isProgrammaticScrollRef.current) return;
|
|
198
|
+
|
|
199
|
+
// Debounce scroll updates
|
|
200
|
+
if (scrollDebounceTimer) clearTimeout(scrollDebounceTimer);
|
|
201
|
+
scrollDebounceTimer = setTimeout(() => {
|
|
202
|
+
// Find the page element closest to the center of the viewport
|
|
203
|
+
const containerRect = container.getBoundingClientRect();
|
|
204
|
+
const containerCenter = containerRect.top + containerRect.height / 2;
|
|
205
|
+
|
|
206
|
+
let closestPage = currentPage;
|
|
207
|
+
let closestDistance = Infinity;
|
|
208
|
+
|
|
209
|
+
for (let i = 1; i <= count; i++) {
|
|
210
|
+
const pageEl = container.querySelector(`[data-page="${i}"]`);
|
|
211
|
+
if (pageEl) {
|
|
212
|
+
const pageRect = pageEl.getBoundingClientRect();
|
|
213
|
+
const pageCenter = pageRect.top + pageRect.height / 2;
|
|
214
|
+
const distance = Math.abs(pageCenter - containerCenter);
|
|
215
|
+
|
|
216
|
+
if (distance < closestDistance) {
|
|
217
|
+
closestDistance = distance;
|
|
218
|
+
closestPage = i;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (closestPage !== currentPage) {
|
|
224
|
+
prevPageRef.current = closestPage;
|
|
225
|
+
onChange(closestPage);
|
|
226
|
+
}
|
|
227
|
+
}, 50);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
if (scrollDebounceTimer) clearTimeout(scrollDebounceTimer);
|
|
234
|
+
container.removeEventListener('scroll', handleScroll);
|
|
235
|
+
};
|
|
236
|
+
}, [count, currentPage, onChange]);
|
|
237
|
+
|
|
238
|
+
const goPrev = () => {
|
|
239
|
+
if (currentPage > 1) {
|
|
240
|
+
onChange(currentPage - 1);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const goNext = () => {
|
|
245
|
+
if (currentPage < count) {
|
|
246
|
+
onChange(currentPage + 1);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div ref={ref} className={clsx('flex flex-col items-stretch gap-y-2', className)}>
|
|
252
|
+
<div className="relative flex items-center justify-center px-2 h-9">
|
|
253
|
+
<Button variant="ghost" size="xs" onClick={goPrev} alt="Previous page">
|
|
254
|
+
<ChevronsUp className='size-4' />
|
|
255
|
+
</Button>
|
|
256
|
+
<div className="absolute left-2 flex items-center gap-x-1">
|
|
257
|
+
<ImageTypeButton
|
|
258
|
+
type={ImageType.original}
|
|
259
|
+
currentType={imageType}
|
|
260
|
+
onClick={() => setImageType(ImageType.original)}
|
|
261
|
+
icon={<Image className="size-4" />}
|
|
262
|
+
tooltip="Original images"
|
|
263
|
+
/>
|
|
264
|
+
<ImageTypeButton
|
|
265
|
+
type={ImageType.instrumented}
|
|
266
|
+
currentType={imageType}
|
|
267
|
+
onClick={() => setImageType(ImageType.instrumented)}
|
|
268
|
+
icon={<ScanSearch className="size-4" />}
|
|
269
|
+
tooltip="Instrumented images"
|
|
270
|
+
/>
|
|
271
|
+
<div className="w-px h-4 bg-border mx-1" />
|
|
272
|
+
<ZoomControls
|
|
273
|
+
zoom={zoom}
|
|
274
|
+
onZoomIn={zoomIn}
|
|
275
|
+
onZoomOut={zoomOut}
|
|
276
|
+
onFitToView={fitToView}
|
|
277
|
+
canZoomIn={zoom < ZOOM_LEVELS[ZOOM_LEVELS.length - 1]}
|
|
278
|
+
canZoomOut={zoom > ZOOM_LEVELS[0]}
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
<div className="absolute right-2">
|
|
282
|
+
<PageNavigator currentPage={currentPage} totalPages={count} onChange={onChange} />
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div ref={scrollContainerRef} className='flex flex-col items-center gap-2 flex-1 overflow-y-auto px-2'>
|
|
286
|
+
{Array.from({ length: count }, (_, index) => (
|
|
287
|
+
<PageThumbnail
|
|
288
|
+
key={index}
|
|
289
|
+
currentPage={currentPage}
|
|
290
|
+
pageNumber={index + 1}
|
|
291
|
+
aspectRatio={aspectRatio}
|
|
292
|
+
zoom={zoom}
|
|
293
|
+
url={loadedUrls.get(index + 1)}
|
|
294
|
+
onSelect={() => onChange(index + 1)}
|
|
295
|
+
/>
|
|
296
|
+
))}
|
|
297
|
+
</div>
|
|
298
|
+
<div className="flex items-center justify-center h-9">
|
|
299
|
+
<Button variant="ghost" size="xs" onClick={goNext} alt="Next page">
|
|
300
|
+
<ChevronsDown className='size-4' />
|
|
301
|
+
</Button>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
interface ImageTypeButtonProps {
|
|
308
|
+
type: ImageType;
|
|
309
|
+
currentType: ImageType;
|
|
310
|
+
onClick: () => void;
|
|
311
|
+
icon: React.ReactNode;
|
|
312
|
+
tooltip: string;
|
|
313
|
+
}
|
|
314
|
+
function ImageTypeButton({ type, currentType, onClick, icon, tooltip }: ImageTypeButtonProps) {
|
|
315
|
+
const isSelected = type === currentType;
|
|
316
|
+
return (
|
|
317
|
+
<VTooltip description={tooltip} placement="bottom" size="xs">
|
|
318
|
+
<button
|
|
319
|
+
className={clsx(
|
|
320
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
321
|
+
isSelected
|
|
322
|
+
? "text-primary bg-primary/10"
|
|
323
|
+
: "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
324
|
+
)}
|
|
325
|
+
onClick={onClick}
|
|
326
|
+
>
|
|
327
|
+
{icon}
|
|
328
|
+
</button>
|
|
329
|
+
</VTooltip>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
interface ZoomControlsProps {
|
|
334
|
+
zoom: number;
|
|
335
|
+
onZoomIn: () => void;
|
|
336
|
+
onZoomOut: () => void;
|
|
337
|
+
onFitToView: () => void;
|
|
338
|
+
canZoomIn: boolean;
|
|
339
|
+
canZoomOut: boolean;
|
|
340
|
+
}
|
|
341
|
+
function ZoomControls({ zoom, onZoomIn, onZoomOut, onFitToView, canZoomIn, canZoomOut }: ZoomControlsProps) {
|
|
342
|
+
return (
|
|
343
|
+
<div className="flex items-center gap-x-0.5">
|
|
344
|
+
<VTooltip description="Zoom out" placement="bottom" size="xs">
|
|
345
|
+
<button
|
|
346
|
+
className={clsx(
|
|
347
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
348
|
+
canZoomOut
|
|
349
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
350
|
+
: "text-muted-foreground/40 cursor-not-allowed"
|
|
351
|
+
)}
|
|
352
|
+
onClick={onZoomOut}
|
|
353
|
+
disabled={!canZoomOut}
|
|
354
|
+
>
|
|
355
|
+
<Minus className="size-4" />
|
|
356
|
+
</button>
|
|
357
|
+
</VTooltip>
|
|
358
|
+
<span className="text-xs text-muted-foreground min-w-[32px] text-center">
|
|
359
|
+
{zoom}%
|
|
360
|
+
</span>
|
|
361
|
+
<VTooltip description="Zoom in" placement="bottom" size="xs">
|
|
362
|
+
<button
|
|
363
|
+
className={clsx(
|
|
364
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
365
|
+
canZoomIn
|
|
366
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
367
|
+
: "text-muted-foreground/40 cursor-not-allowed"
|
|
368
|
+
)}
|
|
369
|
+
onClick={onZoomIn}
|
|
370
|
+
disabled={!canZoomIn}
|
|
371
|
+
>
|
|
372
|
+
<Plus className="size-4" />
|
|
373
|
+
</button>
|
|
374
|
+
</VTooltip>
|
|
375
|
+
<VTooltip description="Fit to width" placement="bottom" size="xs">
|
|
376
|
+
<button
|
|
377
|
+
className={clsx(
|
|
378
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
379
|
+
zoom !== DEFAULT_ZOOM
|
|
380
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
381
|
+
: "text-muted-foreground/40"
|
|
382
|
+
)}
|
|
383
|
+
onClick={onFitToView}
|
|
384
|
+
>
|
|
385
|
+
<Maximize className="size-4" />
|
|
386
|
+
</button>
|
|
387
|
+
</VTooltip>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
interface PageThumbnailProps {
|
|
393
|
+
pageNumber: number;
|
|
394
|
+
currentPage: number;
|
|
395
|
+
aspectRatio: number;
|
|
396
|
+
zoom: number;
|
|
397
|
+
url?: string;
|
|
398
|
+
onSelect: () => void;
|
|
399
|
+
}
|
|
400
|
+
function PageThumbnail({ pageNumber, currentPage, aspectRatio, zoom, url, onSelect }: PageThumbnailProps) {
|
|
401
|
+
const isSelected = pageNumber === currentPage;
|
|
402
|
+
const widthPercent = zoom;
|
|
403
|
+
|
|
404
|
+
return (
|
|
405
|
+
<div
|
|
406
|
+
className="p-2 hover:bg-muted rounded-md flex flex-col items-center"
|
|
407
|
+
data-page={pageNumber}
|
|
408
|
+
style={{ width: `${widthPercent}%` }}
|
|
409
|
+
>
|
|
410
|
+
<div
|
|
411
|
+
className={clsx(
|
|
412
|
+
'relative border-[2px] cursor-pointer overflow-hidden flex items-center justify-center bg-muted/50 w-full',
|
|
413
|
+
isSelected ? "border-primary" : "border-border"
|
|
414
|
+
)}
|
|
415
|
+
style={{ aspectRatio: `1 / ${aspectRatio}` }}
|
|
416
|
+
onClick={onSelect}
|
|
417
|
+
>
|
|
418
|
+
{url ? (
|
|
419
|
+
<img src={url} alt={`Page ${pageNumber}`} className="w-full" />
|
|
420
|
+
) : (
|
|
421
|
+
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
<Center className="text-sm text-muted-foreground pt-1 font-semibold">{pageNumber}</Center>
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
interface PageNavigatorProps {
|
|
430
|
+
currentPage: number;
|
|
431
|
+
totalPages: number;
|
|
432
|
+
onChange: (page: number) => void;
|
|
433
|
+
}
|
|
434
|
+
function PageNavigator({ currentPage, totalPages, onChange }: PageNavigatorProps) {
|
|
435
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
436
|
+
const [inputValue, setInputValue] = useState(String(currentPage));
|
|
437
|
+
|
|
438
|
+
// Sync input value when currentPage changes externally
|
|
439
|
+
useEffect(() => {
|
|
440
|
+
setInputValue(String(currentPage));
|
|
441
|
+
}, [currentPage]);
|
|
442
|
+
|
|
443
|
+
const handleSubmit = () => {
|
|
444
|
+
const page = parseInt(inputValue, 10);
|
|
445
|
+
if (!isNaN(page) && page >= 1 && page <= totalPages) {
|
|
446
|
+
onChange(page);
|
|
447
|
+
} else {
|
|
448
|
+
// Reset to current page if invalid
|
|
449
|
+
setInputValue(String(currentPage));
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
454
|
+
if (e.key === 'Enter') {
|
|
455
|
+
handleSubmit();
|
|
456
|
+
inputRef.current?.blur();
|
|
457
|
+
} else if (e.key === 'Escape') {
|
|
458
|
+
setInputValue(String(currentPage));
|
|
459
|
+
inputRef.current?.blur();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
const handleBlur = () => {
|
|
464
|
+
handleSubmit();
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
469
|
+
<span>Page</span>
|
|
470
|
+
<input
|
|
471
|
+
ref={inputRef}
|
|
472
|
+
type="text"
|
|
473
|
+
value={inputValue}
|
|
474
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
475
|
+
onKeyDown={handleKeyDown}
|
|
476
|
+
onBlur={handleBlur}
|
|
477
|
+
className="w-8 h-5 text-center text-xs px-1 py-0 bg-background border border-border rounded focus:outline-none focus:border-primary"
|
|
478
|
+
/>
|
|
479
|
+
<span>/ {totalPages}</span>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ContentObject, DocumentMetadata } from "@vertesia/common";
|
|
2
|
+
import { Button } from "@vertesia/ui/core";
|
|
2
3
|
import { useUserSession } from "@vertesia/ui/session";
|
|
3
4
|
import { Popover } from "@vertesia/ui/widgets";
|
|
4
|
-
import {
|
|
5
|
-
import { getResourceUrl } from "./
|
|
5
|
+
import { Download } from "lucide-react";
|
|
6
|
+
import { getResourceUrl } from "./MagicPdfProvider";
|
|
6
7
|
|
|
7
8
|
interface DownloadPopoverProps {
|
|
8
9
|
object: ContentObject;
|
|
@@ -12,7 +13,7 @@ export function DownloadPopover({ object }: DownloadPopoverProps) {
|
|
|
12
13
|
const onDownload = (name: string) => {
|
|
13
14
|
getResourceUrl(client, object.id, name).then(url => window.open(url, '_blank'));
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
|
|
16
17
|
const getProcessorType = (): string => {
|
|
17
18
|
if (object.metadata?.type === "document") {
|
|
18
19
|
const docMetadata = object.metadata as DocumentMetadata;
|
|
@@ -20,46 +21,50 @@ export function DownloadPopover({ object }: DownloadPopoverProps) {
|
|
|
20
21
|
}
|
|
21
22
|
return "xml"; // default
|
|
22
23
|
};
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
const processorType = getProcessorType();
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
document.md
|
|
31
|
-
</button>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Default XML processor options
|
|
26
|
+
|
|
27
|
+
const buttonClass = "p-2 cursor-pointer hover:bg-muted text-left text-sm";
|
|
28
|
+
|
|
29
|
+
// For markdown processor, only one download option - render simple button
|
|
30
|
+
if (processorType === "markdown") {
|
|
36
31
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
analyzed-pages.json
|
|
46
|
-
</button>
|
|
47
|
-
</>
|
|
32
|
+
<Button
|
|
33
|
+
variant="ghost"
|
|
34
|
+
size="xs"
|
|
35
|
+
onClick={() => onDownload("document.md")}
|
|
36
|
+
alt="Download"
|
|
37
|
+
>
|
|
38
|
+
<Download className='size-4' />
|
|
39
|
+
</Button>
|
|
48
40
|
);
|
|
49
|
-
}
|
|
50
|
-
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Default XML processor - multiple options, use popover
|
|
51
44
|
return (
|
|
52
|
-
<
|
|
53
|
-
<Popover
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
45
|
+
<Popover strategy='absolute' placement='bottom-start' zIndex={100} offset={8}>
|
|
46
|
+
<Popover.Trigger click>
|
|
47
|
+
<Button
|
|
48
|
+
variant="ghost"
|
|
49
|
+
size="xs"
|
|
50
|
+
alt="Download"
|
|
51
|
+
>
|
|
52
|
+
<Download className='size-4' />
|
|
53
|
+
</Button>
|
|
54
|
+
</Popover.Trigger>
|
|
55
|
+
<Popover.Content>
|
|
56
|
+
<div className="rounded-md shadow-md border border-border bg-popover min-w-[200px] flex flex-col divide-y divide-border">
|
|
57
|
+
<button className={buttonClass} onClick={() => onDownload("annotated.pdf")}>
|
|
58
|
+
annotated.pdf
|
|
59
|
+
</button>
|
|
60
|
+
<button className={buttonClass} onClick={() => onDownload("document.xml")}>
|
|
61
|
+
document.xml
|
|
62
|
+
</button>
|
|
63
|
+
<button className={buttonClass} onClick={() => onDownload("analyzed-pages.json")}>
|
|
64
|
+
analyzed-pages.json
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</Popover.Content>
|
|
68
|
+
</Popover>
|
|
64
69
|
)
|
|
65
70
|
}
|