@vertesia/ui 0.80.0-dev-20251118 → 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/TagsInput.js +194 -0
- package/lib/esm/core/components/TagsInput.js.map +1 -0
- package/lib/esm/core/components/index.js +2 -1
- package/lib/esm/core/components/index.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/popover.js +1 -1
- package/lib/esm/core/components/shadcn/popover.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 +4 -1
- 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 +19 -10
- package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
- package/lib/esm/features/store/collections/SelectCollection.js +104 -39
- package/lib/esm/features/store/collections/SelectCollection.js.map +1 -1
- package/lib/esm/features/store/collections/SharedPropsEditor.js +39 -0
- package/lib/esm/features/store/collections/SharedPropsEditor.js.map +1 -0
- package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js +35 -0
- package/lib/esm/features/store/collections/SyncMemberHeadsToggle.js.map +1 -0
- package/lib/esm/features/store/collections/index.js +2 -0
- package/lib/esm/features/store/collections/index.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/store/objects/upload/useSmartFileUploadProcessing.js +10 -9
- package/lib/esm/features/store/objects/upload/useSmartFileUploadProcessing.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/session/UserSessionProvider.js +2 -2
- package/lib/esm/session/UserSessionProvider.js.map +1 -1
- package/lib/esm/shell/apps/AppProjectSelector.js +2 -2
- package/lib/esm/shell/apps/AppProjectSelector.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/TagsInput.d.ts +16 -0
- package/lib/types/core/components/TagsInput.d.ts.map +1 -0
- package/lib/types/core/components/index.d.ts +2 -1
- package/lib/types/core/components/index.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/popover.d.ts +7 -0
- package/lib/types/core/components/shadcn/popover.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/SelectCollection.d.ts +2 -1
- package/lib/types/features/store/collections/SelectCollection.d.ts.map +1 -1
- package/lib/types/features/store/collections/SharedPropsEditor.d.ts +7 -0
- package/lib/types/features/store/collections/SharedPropsEditor.d.ts.map +1 -0
- package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts +7 -0
- package/lib/types/features/store/collections/SyncMemberHeadsToggle.d.ts.map +1 -0
- package/lib/types/features/store/collections/index.d.ts +2 -0
- package/lib/types/features/store/collections/index.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/store/objects/upload/useSmartFileUploadProcessing.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 +173 -170
- 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 +6 -1
- 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/store/objects/upload/useSmartFileUploadProcessing.ts +1 -3
- 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/session/UserSessionProvider.tsx +2 -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,473 @@
|
|
|
1
|
+
import { Button, Center, VTooltip } from "@vertesia/ui/core";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { ChevronsDown, ChevronsUp, Maximize, Minus, Plus } from "lucide-react";
|
|
4
|
+
import { ReactNode, useRef, useEffect, useState, useCallback, KeyboardEvent } from "react";
|
|
5
|
+
import { PdfThumbnailList } from "./PdfPageRenderer";
|
|
6
|
+
|
|
7
|
+
// A4 portrait aspect ratio - used as fallback
|
|
8
|
+
const A4_ASPECT_RATIO = 210 / 297;
|
|
9
|
+
|
|
10
|
+
// Zoom levels as percentages (100 = fit to width)
|
|
11
|
+
const ZOOM_LEVELS = [50, 75, 100, 125, 150, 200, 300];
|
|
12
|
+
const DEFAULT_ZOOM = 100;
|
|
13
|
+
|
|
14
|
+
interface PdfPageSliderProps {
|
|
15
|
+
/** URL to the PDF file */
|
|
16
|
+
pdfUrl: string;
|
|
17
|
+
/** Whether the PDF URL is still loading */
|
|
18
|
+
pdfUrlLoading?: boolean;
|
|
19
|
+
/** Total number of pages in the PDF (initial estimate, will be updated when PDF loads) */
|
|
20
|
+
pageCount: number;
|
|
21
|
+
/** Currently selected page number (1-indexed) */
|
|
22
|
+
currentPage: number;
|
|
23
|
+
/** Callback when page selection changes */
|
|
24
|
+
onChange: (pageNumber: number) => void;
|
|
25
|
+
/** Callback when actual page count is determined from the PDF */
|
|
26
|
+
onPageCountChange?: (pageCount: number) => void;
|
|
27
|
+
/** Additional CSS class names */
|
|
28
|
+
className?: string;
|
|
29
|
+
/** Compact mode reduces padding and navigation bar heights */
|
|
30
|
+
compact?: boolean;
|
|
31
|
+
/** Extra content to render in the header (e.g., fullscreen button) */
|
|
32
|
+
headerExtra?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Standalone PDF thumbnail slider component.
|
|
37
|
+
* Displays a vertical list of PDF page thumbnails with navigation controls.
|
|
38
|
+
* Does not depend on any context - all data is passed via props.
|
|
39
|
+
*/
|
|
40
|
+
export function PdfPageSlider({
|
|
41
|
+
pdfUrl,
|
|
42
|
+
pdfUrlLoading = false,
|
|
43
|
+
pageCount,
|
|
44
|
+
currentPage,
|
|
45
|
+
onChange,
|
|
46
|
+
onPageCountChange,
|
|
47
|
+
className,
|
|
48
|
+
compact = false,
|
|
49
|
+
headerExtra
|
|
50
|
+
}: PdfPageSliderProps) {
|
|
51
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
52
|
+
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
53
|
+
const [baseWidth, setBaseWidth] = useState<number | undefined>(undefined);
|
|
54
|
+
const [zoom, setZoom] = useState<number>(DEFAULT_ZOOM);
|
|
55
|
+
const [aspectRatio, setAspectRatio] = useState<number>(A4_ASPECT_RATIO);
|
|
56
|
+
// Track the actual item height from PdfThumbnailList for accurate scroll calculations
|
|
57
|
+
const [itemHeight, setItemHeight] = useState<number | null>(null);
|
|
58
|
+
|
|
59
|
+
// Track the previous item height to preserve scroll position during resize
|
|
60
|
+
const prevItemHeightRef = useRef<number | null>(null);
|
|
61
|
+
|
|
62
|
+
// Calculate thumbnail width based on zoom level
|
|
63
|
+
const thumbnailWidth = baseWidth ? Math.round(baseWidth * zoom / 100) : undefined;
|
|
64
|
+
|
|
65
|
+
const zoomIn = useCallback(() => {
|
|
66
|
+
let currentIndex = ZOOM_LEVELS.findIndex(level => level >= zoom);
|
|
67
|
+
if (currentIndex === -1) {
|
|
68
|
+
currentIndex = ZOOM_LEVELS.length - 1;
|
|
69
|
+
}
|
|
70
|
+
const nextIndex = Math.min(currentIndex + 1, ZOOM_LEVELS.length - 1);
|
|
71
|
+
setZoom(ZOOM_LEVELS[nextIndex]);
|
|
72
|
+
}, [zoom]);
|
|
73
|
+
|
|
74
|
+
const zoomOut = useCallback(() => {
|
|
75
|
+
let currentIndex = ZOOM_LEVELS.findIndex(level => level >= zoom);
|
|
76
|
+
if (currentIndex === -1) {
|
|
77
|
+
currentIndex = ZOOM_LEVELS.length - 1;
|
|
78
|
+
}
|
|
79
|
+
const prevIndex = Math.max(currentIndex - 1, 0);
|
|
80
|
+
setZoom(ZOOM_LEVELS[prevIndex]);
|
|
81
|
+
}, [zoom]);
|
|
82
|
+
|
|
83
|
+
const fitToView = useCallback(() => {
|
|
84
|
+
setZoom(DEFAULT_ZOOM);
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
// Calculate item height based on placeholder height - this must match the renderThumbnail layout
|
|
88
|
+
// padding (p-1=8 or p-2=16) + text height (~16-20 for compact, ~24 for normal) + gap (gap-1=4 or gap-2=8)
|
|
89
|
+
const calculateItemHeight = useCallback((placeholderHeight: number) => {
|
|
90
|
+
const extraHeight = compact ? 8 + 16 + 4 : 16 + 24 + 8;
|
|
91
|
+
return placeholderHeight + extraHeight;
|
|
92
|
+
}, [compact]);
|
|
93
|
+
|
|
94
|
+
// Legacy function for resize preservation - kept for backwards compatibility
|
|
95
|
+
const getItemHeight = (width: number | undefined, ratio: number) => {
|
|
96
|
+
const placeholderHeight = width ? Math.round(width / ratio) : 200;
|
|
97
|
+
return calculateItemHeight(placeholderHeight);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Single ResizeObserver at parent level to measure thumbnail width
|
|
101
|
+
// Debounced to avoid excessive re-renders during resize
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const container = scrollContainerRef.current;
|
|
104
|
+
if (!container) return;
|
|
105
|
+
|
|
106
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
107
|
+
|
|
108
|
+
const getAvailableWidth = () => {
|
|
109
|
+
// Container width minus padding (px-2 = 8px each side) minus thumbnail padding (p-2 = 8px each side) minus border (2px each side)
|
|
110
|
+
return container.clientWidth - 16 - 16 - 4;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const updateWidth = () => {
|
|
114
|
+
const newWidth = getAvailableWidth();
|
|
115
|
+
if (newWidth <= 0) return;
|
|
116
|
+
|
|
117
|
+
// Before updating width, preserve scroll position by calculating which page is at top
|
|
118
|
+
const oldItemHeight = prevItemHeightRef.current;
|
|
119
|
+
if (oldItemHeight && oldItemHeight > 0) {
|
|
120
|
+
const currentScrollTop = container.scrollTop;
|
|
121
|
+
const currentTopPage = Math.round(currentScrollTop / oldItemHeight);
|
|
122
|
+
|
|
123
|
+
// Calculate new item height and adjust scroll position
|
|
124
|
+
const newItemHeight = getItemHeight(newWidth, aspectRatio);
|
|
125
|
+
const newScrollTop = currentTopPage * newItemHeight;
|
|
126
|
+
|
|
127
|
+
// Update width first, then scroll
|
|
128
|
+
setBaseWidth(newWidth);
|
|
129
|
+
|
|
130
|
+
// Use requestAnimationFrame to scroll after the DOM updates
|
|
131
|
+
requestAnimationFrame(() => {
|
|
132
|
+
container.scrollTo({ top: newScrollTop, behavior: 'instant' });
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
prevItemHeightRef.current = newItemHeight;
|
|
136
|
+
} else {
|
|
137
|
+
setBaseWidth(newWidth);
|
|
138
|
+
prevItemHeightRef.current = getItemHeight(newWidth, aspectRatio);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// Initial width update
|
|
143
|
+
const initialWidth = getAvailableWidth();
|
|
144
|
+
if (initialWidth > 0) {
|
|
145
|
+
setBaseWidth(initialWidth);
|
|
146
|
+
prevItemHeightRef.current = getItemHeight(initialWidth, aspectRatio);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const handleResize = () => {
|
|
150
|
+
// Debounce width updates to avoid re-rendering PDFs during resize
|
|
151
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
152
|
+
debounceTimer = setTimeout(updateWidth, 150);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const resizeObserver = new ResizeObserver(handleResize);
|
|
156
|
+
resizeObserver.observe(container);
|
|
157
|
+
|
|
158
|
+
return () => {
|
|
159
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
160
|
+
resizeObserver.disconnect();
|
|
161
|
+
};
|
|
162
|
+
}, [aspectRatio]);
|
|
163
|
+
|
|
164
|
+
// Track whether we're programmatically scrolling to avoid feedback loops
|
|
165
|
+
const isProgrammaticScrollRef = useRef(false);
|
|
166
|
+
|
|
167
|
+
// Track pending zoom scroll - we need to wait for itemHeight to update after zoom change
|
|
168
|
+
const pendingZoomScrollRef = useRef<{ targetPage: number } | null>(null);
|
|
169
|
+
|
|
170
|
+
// When zoom changes, mark that we need to scroll after itemHeight updates
|
|
171
|
+
const prevZoomRef = useRef(zoom);
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
if (prevZoomRef.current !== zoom) {
|
|
174
|
+
prevZoomRef.current = zoom;
|
|
175
|
+
// Mark that we need to scroll to current page after itemHeight updates
|
|
176
|
+
pendingZoomScrollRef.current = { targetPage: currentPage };
|
|
177
|
+
isProgrammaticScrollRef.current = true;
|
|
178
|
+
}
|
|
179
|
+
}, [zoom, currentPage]);
|
|
180
|
+
|
|
181
|
+
// When itemHeight changes (after zoom), perform the pending scroll
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
const container = scrollContainerRef.current;
|
|
184
|
+
const pendingScroll = pendingZoomScrollRef.current;
|
|
185
|
+
|
|
186
|
+
if (pendingScroll && container && itemHeight) {
|
|
187
|
+
pendingZoomScrollRef.current = null;
|
|
188
|
+
|
|
189
|
+
// Calculate scroll position using the NEW itemHeight
|
|
190
|
+
const targetScrollTop = (pendingScroll.targetPage - 1) * itemHeight;
|
|
191
|
+
|
|
192
|
+
// Use requestAnimationFrame to ensure DOM is updated
|
|
193
|
+
requestAnimationFrame(() => {
|
|
194
|
+
container.scrollTo({ top: targetScrollTop, behavior: 'instant' });
|
|
195
|
+
// Reset after scroll completes
|
|
196
|
+
requestAnimationFrame(() => {
|
|
197
|
+
isProgrammaticScrollRef.current = false;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}, [itemHeight]);
|
|
202
|
+
|
|
203
|
+
// Track if we've done the initial scroll on mount
|
|
204
|
+
const hasInitialScrolledRef = useRef(false);
|
|
205
|
+
|
|
206
|
+
// Initial scroll to current page when component mounts and itemHeight becomes available
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
const container = scrollContainerRef.current;
|
|
209
|
+
if (!container || !itemHeight || hasInitialScrolledRef.current) return;
|
|
210
|
+
|
|
211
|
+
// Only do initial scroll if not on page 1
|
|
212
|
+
if (currentPage > 1) {
|
|
213
|
+
hasInitialScrolledRef.current = true;
|
|
214
|
+
isProgrammaticScrollRef.current = true;
|
|
215
|
+
|
|
216
|
+
const targetScrollTop = (currentPage - 1) * itemHeight;
|
|
217
|
+
container.scrollTo({ top: targetScrollTop, behavior: 'instant' });
|
|
218
|
+
|
|
219
|
+
requestAnimationFrame(() => {
|
|
220
|
+
isProgrammaticScrollRef.current = false;
|
|
221
|
+
});
|
|
222
|
+
} else {
|
|
223
|
+
hasInitialScrolledRef.current = true;
|
|
224
|
+
}
|
|
225
|
+
}, [itemHeight, currentPage]);
|
|
226
|
+
|
|
227
|
+
// Jump to current page when it changes (user navigation)
|
|
228
|
+
// Use a ref to track the previous page to avoid scrolling on resize
|
|
229
|
+
const prevPageRef = useRef(currentPage);
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
const container = scrollContainerRef.current;
|
|
232
|
+
if (!container || !itemHeight) return;
|
|
233
|
+
|
|
234
|
+
// Only scroll if the page actually changed (user navigation)
|
|
235
|
+
if (prevPageRef.current !== currentPage) {
|
|
236
|
+
prevPageRef.current = currentPage;
|
|
237
|
+
|
|
238
|
+
// Mark as programmatic scroll to avoid triggering onChange
|
|
239
|
+
isProgrammaticScrollRef.current = true;
|
|
240
|
+
|
|
241
|
+
// Calculate scroll position directly using itemHeight
|
|
242
|
+
// This is more reliable than DOM queries since virtualization uses spacers
|
|
243
|
+
const targetScrollTop = (currentPage - 1) * itemHeight;
|
|
244
|
+
container.scrollTo({ top: targetScrollTop, behavior: 'instant' });
|
|
245
|
+
|
|
246
|
+
// Reset after a short delay to allow scroll event to fire
|
|
247
|
+
requestAnimationFrame(() => {
|
|
248
|
+
isProgrammaticScrollRef.current = false;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}, [currentPage, itemHeight]);
|
|
252
|
+
|
|
253
|
+
// Update current page based on scroll position
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
const container = scrollContainerRef.current;
|
|
256
|
+
if (!container || !itemHeight) return;
|
|
257
|
+
|
|
258
|
+
let scrollDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
259
|
+
|
|
260
|
+
const handleScroll = () => {
|
|
261
|
+
// Skip if this is a programmatic scroll
|
|
262
|
+
if (isProgrammaticScrollRef.current) return;
|
|
263
|
+
|
|
264
|
+
// Debounce scroll updates
|
|
265
|
+
if (scrollDebounceTimer) clearTimeout(scrollDebounceTimer);
|
|
266
|
+
scrollDebounceTimer = setTimeout(() => {
|
|
267
|
+
// Calculate current page from scroll position using itemHeight
|
|
268
|
+
// This is more reliable than DOM queries since virtualization uses spacers
|
|
269
|
+
const scrollTop = container.scrollTop;
|
|
270
|
+
const calculatedPage = Math.round(scrollTop / itemHeight) + 1;
|
|
271
|
+
|
|
272
|
+
// Clamp to valid range and update if different
|
|
273
|
+
const clampedPage = Math.max(1, Math.min(calculatedPage, pageCount));
|
|
274
|
+
if (clampedPage !== currentPage) {
|
|
275
|
+
prevPageRef.current = clampedPage;
|
|
276
|
+
onChange(clampedPage);
|
|
277
|
+
}
|
|
278
|
+
}, 50);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
282
|
+
|
|
283
|
+
return () => {
|
|
284
|
+
if (scrollDebounceTimer) clearTimeout(scrollDebounceTimer);
|
|
285
|
+
container.removeEventListener('scroll', handleScroll);
|
|
286
|
+
};
|
|
287
|
+
}, [itemHeight, pageCount, currentPage, onChange]);
|
|
288
|
+
|
|
289
|
+
const goPrev = () => {
|
|
290
|
+
if (currentPage > 1) {
|
|
291
|
+
onChange(currentPage - 1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const goNext = () => {
|
|
295
|
+
if (currentPage < pageCount) {
|
|
296
|
+
onChange(currentPage + 1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div ref={ref} className={clsx('flex flex-col items-stretch', compact ? 'gap-y-1' : 'gap-y-2', className)}>
|
|
302
|
+
<div className={clsx("relative flex items-center justify-center px-2", compact ? "h-6" : "h-9")}>
|
|
303
|
+
<Button variant="ghost" size="xs" onClick={goPrev} alt="Previous page">
|
|
304
|
+
<ChevronsUp className='size-4' />
|
|
305
|
+
</Button>
|
|
306
|
+
<div className="absolute left-2 flex items-center gap-x-1">
|
|
307
|
+
<ZoomControls
|
|
308
|
+
zoom={zoom}
|
|
309
|
+
onZoomIn={zoomIn}
|
|
310
|
+
onZoomOut={zoomOut}
|
|
311
|
+
onFitToView={fitToView}
|
|
312
|
+
canZoomIn={zoom < ZOOM_LEVELS[ZOOM_LEVELS.length - 1]}
|
|
313
|
+
canZoomOut={zoom > ZOOM_LEVELS[0]}
|
|
314
|
+
/>
|
|
315
|
+
{headerExtra && (
|
|
316
|
+
<>
|
|
317
|
+
<div className="w-px h-4 bg-border mx-1" />
|
|
318
|
+
{headerExtra}
|
|
319
|
+
</>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
<div className="absolute right-2">
|
|
323
|
+
<PageNavigator currentPage={currentPage} totalPages={pageCount} onChange={onChange} />
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
<div ref={scrollContainerRef} className={clsx('flex flex-col items-center flex-1 overflow-y-auto px-2', compact ? 'gap-1' : 'gap-2')}>
|
|
327
|
+
<PdfThumbnailList
|
|
328
|
+
pdfUrl={pdfUrl}
|
|
329
|
+
urlLoading={pdfUrlLoading}
|
|
330
|
+
pageCount={pageCount}
|
|
331
|
+
currentPage={currentPage}
|
|
332
|
+
thumbnailWidth={thumbnailWidth}
|
|
333
|
+
onPageSelect={onChange}
|
|
334
|
+
onPageCountChange={onPageCountChange}
|
|
335
|
+
scrollContainerRef={scrollContainerRef}
|
|
336
|
+
onAspectRatioChange={setAspectRatio}
|
|
337
|
+
onItemHeightChange={setItemHeight}
|
|
338
|
+
calculateItemHeight={calculateItemHeight}
|
|
339
|
+
renderThumbnail={({ pageNumber, isSelected, pageElement, onSelect }) => (
|
|
340
|
+
<div key={pageNumber} className={clsx("hover:bg-muted rounded-md w-full", compact ? "p-1" : "p-2")}>
|
|
341
|
+
<div
|
|
342
|
+
className={clsx('relative border-[2px] cursor-pointer overflow-hidden', isSelected ? "border-primary" : "border-border")}
|
|
343
|
+
onClick={onSelect}
|
|
344
|
+
>
|
|
345
|
+
{pageElement}
|
|
346
|
+
</div>
|
|
347
|
+
<Center className={clsx("text-muted-foreground font-semibold", compact ? "text-xs pt-0.5" : "text-sm pt-1")}>{pageNumber}</Center>
|
|
348
|
+
</div>
|
|
349
|
+
)}
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
<div className={clsx("flex items-center justify-center", compact ? "h-6" : "h-9")}>
|
|
353
|
+
<Button variant="ghost" size="xs" onClick={goNext} alt="Next page">
|
|
354
|
+
<ChevronsDown className='size-4' />
|
|
355
|
+
</Button>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
interface PageNavigatorProps {
|
|
362
|
+
currentPage: number;
|
|
363
|
+
totalPages: number;
|
|
364
|
+
onChange: (page: number) => void;
|
|
365
|
+
}
|
|
366
|
+
function PageNavigator({ currentPage, totalPages, onChange }: PageNavigatorProps) {
|
|
367
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
368
|
+
const [inputValue, setInputValue] = useState(String(currentPage));
|
|
369
|
+
|
|
370
|
+
// Sync input value when currentPage changes externally
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
setInputValue(String(currentPage));
|
|
373
|
+
}, [currentPage]);
|
|
374
|
+
|
|
375
|
+
const handleSubmit = () => {
|
|
376
|
+
const page = parseInt(inputValue, 10);
|
|
377
|
+
if (!isNaN(page) && page >= 1 && page <= totalPages) {
|
|
378
|
+
onChange(page);
|
|
379
|
+
} else {
|
|
380
|
+
// Reset to current page if invalid
|
|
381
|
+
setInputValue(String(currentPage));
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
|
|
386
|
+
if (e.key === 'Enter') {
|
|
387
|
+
handleSubmit();
|
|
388
|
+
inputRef.current?.blur();
|
|
389
|
+
} else if (e.key === 'Escape') {
|
|
390
|
+
setInputValue(String(currentPage));
|
|
391
|
+
inputRef.current?.blur();
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const handleBlur = () => {
|
|
396
|
+
handleSubmit();
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
401
|
+
<span>Page</span>
|
|
402
|
+
<input
|
|
403
|
+
ref={inputRef}
|
|
404
|
+
type="text"
|
|
405
|
+
value={inputValue}
|
|
406
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
407
|
+
onKeyDown={handleKeyDown}
|
|
408
|
+
onBlur={handleBlur}
|
|
409
|
+
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"
|
|
410
|
+
/>
|
|
411
|
+
<span>/ {totalPages}</span>
|
|
412
|
+
</div>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
interface ZoomControlsProps {
|
|
417
|
+
zoom: number;
|
|
418
|
+
onZoomIn: () => void;
|
|
419
|
+
onZoomOut: () => void;
|
|
420
|
+
onFitToView: () => void;
|
|
421
|
+
canZoomIn: boolean;
|
|
422
|
+
canZoomOut: boolean;
|
|
423
|
+
}
|
|
424
|
+
function ZoomControls({ zoom, onZoomIn, onZoomOut, onFitToView, canZoomIn, canZoomOut }: ZoomControlsProps) {
|
|
425
|
+
return (
|
|
426
|
+
<div className="flex items-center gap-x-0.5">
|
|
427
|
+
<VTooltip description="Zoom out" placement="bottom" size="xs">
|
|
428
|
+
<button
|
|
429
|
+
className={clsx(
|
|
430
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
431
|
+
canZoomOut
|
|
432
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
433
|
+
: "text-muted-foreground/40 cursor-not-allowed"
|
|
434
|
+
)}
|
|
435
|
+
onClick={onZoomOut}
|
|
436
|
+
disabled={!canZoomOut}
|
|
437
|
+
>
|
|
438
|
+
<Minus className="size-4" />
|
|
439
|
+
</button>
|
|
440
|
+
</VTooltip>
|
|
441
|
+
<span className="text-xs text-muted-foreground min-w-[32px] text-center">
|
|
442
|
+
{zoom}%
|
|
443
|
+
</span>
|
|
444
|
+
<VTooltip description="Zoom in" placement="bottom" size="xs">
|
|
445
|
+
<button
|
|
446
|
+
className={clsx(
|
|
447
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
448
|
+
canZoomIn
|
|
449
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
450
|
+
: "text-muted-foreground/40 cursor-not-allowed"
|
|
451
|
+
)}
|
|
452
|
+
onClick={onZoomIn}
|
|
453
|
+
disabled={!canZoomIn}
|
|
454
|
+
>
|
|
455
|
+
<Plus className="size-4" />
|
|
456
|
+
</button>
|
|
457
|
+
</VTooltip>
|
|
458
|
+
<VTooltip description="Fit to width" placement="bottom" size="xs">
|
|
459
|
+
<button
|
|
460
|
+
className={clsx(
|
|
461
|
+
"p-1 rounded cursor-pointer transition-colors",
|
|
462
|
+
zoom !== DEFAULT_ZOOM
|
|
463
|
+
? "text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
464
|
+
: "text-muted-foreground/40"
|
|
465
|
+
)}
|
|
466
|
+
onClick={onFitToView}
|
|
467
|
+
>
|
|
468
|
+
<Maximize className="size-4" />
|
|
469
|
+
</button>
|
|
470
|
+
</VTooltip>
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { ContentObject } from "@vertesia/common";
|
|
2
|
+
import { Button, Spinner, VTooltip } from "@vertesia/ui/core";
|
|
3
|
+
import { useUserSession } from "@vertesia/ui/session";
|
|
4
|
+
import { Maximize2, X } from "lucide-react";
|
|
5
|
+
import { useEffect, useState } from "react";
|
|
6
|
+
import { PdfPageSlider } from "./PdfPageSlider";
|
|
7
|
+
|
|
8
|
+
interface SimplePdfViewerProps {
|
|
9
|
+
/** The content object containing the PDF (optional if url or source is provided) */
|
|
10
|
+
object?: ContentObject;
|
|
11
|
+
/** Direct signed URL to the PDF (ready to use, no resolution needed) */
|
|
12
|
+
url?: string;
|
|
13
|
+
/** Storage source path that needs to be resolved to a download URL */
|
|
14
|
+
source?: string;
|
|
15
|
+
/** Additional CSS class names */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A standalone PDF viewer component that displays PDF thumbnails with navigation.
|
|
21
|
+
* Fetches the PDF URL from the content object and displays it using PdfThumbnailSlider.
|
|
22
|
+
* Does not depend on any magic-pdf context.
|
|
23
|
+
*/
|
|
24
|
+
export function SimplePdfViewer({ object, url, source, className }: SimplePdfViewerProps) {
|
|
25
|
+
const { client } = useUserSession();
|
|
26
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
27
|
+
const [pdfUrl, setPdfUrl] = useState<string>(url || "");
|
|
28
|
+
const [pdfUrlLoading, setPdfUrlLoading] = useState(!url);
|
|
29
|
+
const [pageCount, setPageCount] = useState(0);
|
|
30
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
31
|
+
|
|
32
|
+
// Fetch the PDF URL - priority: url > source > object.content.source
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
// If url prop is provided, use it directly (already signed)
|
|
35
|
+
if (url) {
|
|
36
|
+
setPdfUrl(url);
|
|
37
|
+
setPdfUrlLoading(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Determine the source to resolve
|
|
42
|
+
const sourceToResolve = source || object?.content?.source;
|
|
43
|
+
if (!sourceToResolve) {
|
|
44
|
+
setPdfUrlLoading(false);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setPdfUrlLoading(true);
|
|
49
|
+
client.files.getDownloadUrl(sourceToResolve)
|
|
50
|
+
.then((result) => {
|
|
51
|
+
setPdfUrl(result.url);
|
|
52
|
+
})
|
|
53
|
+
.catch((err) => {
|
|
54
|
+
console.error("Failed to get PDF URL:", err);
|
|
55
|
+
})
|
|
56
|
+
.finally(() => {
|
|
57
|
+
setPdfUrlLoading(false);
|
|
58
|
+
});
|
|
59
|
+
}, [url, source, object?.content?.source, client]);
|
|
60
|
+
|
|
61
|
+
// Get initial page count from metadata, but actual count will be updated when PDF loads
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
// Try to get page count from metadata as initial estimate
|
|
64
|
+
const metadata = object?.metadata as { pages?: number; page_count?: number } | undefined;
|
|
65
|
+
const count = metadata?.pages || metadata?.page_count || 0;
|
|
66
|
+
|
|
67
|
+
if (count > 0) {
|
|
68
|
+
setPageCount(count);
|
|
69
|
+
}
|
|
70
|
+
// If no metadata, keep pageCount at 0 - it will be set when PDF loads
|
|
71
|
+
}, [object?.metadata]);
|
|
72
|
+
|
|
73
|
+
// Callback to update page count when PDF loads
|
|
74
|
+
const handlePageCountChange = (count: number) => {
|
|
75
|
+
setPageCount(count);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
if (pdfUrlLoading) {
|
|
79
|
+
return (
|
|
80
|
+
<div className="flex items-center justify-center h-full">
|
|
81
|
+
<Spinner size="lg" />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!pdfUrl) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex items-center justify-center h-full text-muted">
|
|
89
|
+
No PDF available
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Fullscreen overlay
|
|
95
|
+
if (isFullscreen) {
|
|
96
|
+
return (
|
|
97
|
+
<div className="fixed inset-0 bg-background z-50 flex flex-col overflow-hidden">
|
|
98
|
+
{/* Header with close button */}
|
|
99
|
+
<div className="flex h-9 items-center justify-end shrink-0 bg-sidebar px-2 border-b border-sidebar-border">
|
|
100
|
+
<Button variant="ghost" size="xs" onClick={() => setIsFullscreen(false)} alt="Close fullscreen">
|
|
101
|
+
<X className="size-4" />
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
{/* PDF viewer - min-h-0 allows flex child to shrink below content size */}
|
|
105
|
+
<PdfPageSlider
|
|
106
|
+
pdfUrl={pdfUrl}
|
|
107
|
+
pdfUrlLoading={pdfUrlLoading}
|
|
108
|
+
pageCount={pageCount || 100}
|
|
109
|
+
currentPage={currentPage}
|
|
110
|
+
onChange={setCurrentPage}
|
|
111
|
+
onPageCountChange={handlePageCountChange}
|
|
112
|
+
className="flex-1 min-h-0"
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<div className="relative h-full flex flex-col">
|
|
120
|
+
<PdfPageSlider
|
|
121
|
+
pdfUrl={pdfUrl}
|
|
122
|
+
pdfUrlLoading={pdfUrlLoading}
|
|
123
|
+
pageCount={pageCount || 100}
|
|
124
|
+
currentPage={currentPage}
|
|
125
|
+
onChange={setCurrentPage}
|
|
126
|
+
onPageCountChange={handlePageCountChange}
|
|
127
|
+
className={className}
|
|
128
|
+
compact
|
|
129
|
+
headerExtra={
|
|
130
|
+
<VTooltip description="Fullscreen" placement="bottom" size="xs">
|
|
131
|
+
<button
|
|
132
|
+
className="p-1 rounded cursor-pointer transition-colors text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
133
|
+
onClick={() => setIsFullscreen(true)}
|
|
134
|
+
>
|
|
135
|
+
<Maximize2 className="size-4" />
|
|
136
|
+
</button>
|
|
137
|
+
</VTooltip>
|
|
138
|
+
}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { json } from "@codemirror/lang-json";
|
|
2
2
|
import { Collection, CreateCollectionPayload, JSONSchemaObject } from "@vertesia/common";
|
|
3
3
|
import { Button, ErrorBox, FormItem, Input, Panel, Styles, Textarea, useFetch, useToast } from "@vertesia/ui/core";
|
|
4
|
-
import { UserInfo } from "@vertesia/ui/features";
|
|
5
|
-
import { SharedPropsEditor } from "@vertesia/ui/features";
|
|
6
|
-
import { SyncMemberHeadsToggle } from "@vertesia/ui/features";
|
|
4
|
+
import { SharedPropsEditor, SyncMemberHeadsToggle, UserInfo } from "@vertesia/ui/features";
|
|
7
5
|
import { useUserSession } from "@vertesia/ui/session";
|
|
8
6
|
import { CodeMirrorEditor, EditorApi, GeneratedForm, ManagedObject, Node } from "@vertesia/ui/widgets";
|
|
9
7
|
import { basicSetup } from "codemirror";
|
|
@@ -163,7 +161,7 @@ export function EditCollectionView({ refetch, collection }: EditCollectionViewPr
|
|
|
163
161
|
<FormItem label="Description">
|
|
164
162
|
<Textarea
|
|
165
163
|
value={metadata.description}
|
|
166
|
-
onChange={(e) => setField("description", e)}
|
|
164
|
+
onChange={(e) => setField("description", e.target.value)}
|
|
167
165
|
/>
|
|
168
166
|
</FormItem>
|
|
169
167
|
{
|
|
@@ -188,7 +186,7 @@ export function EditCollectionView({ refetch, collection }: EditCollectionViewPr
|
|
|
188
186
|
<Textarea
|
|
189
187
|
className={Styles.INPUT}
|
|
190
188
|
value={metadata.query}
|
|
191
|
-
onChange={(e) => setField("query", e)}
|
|
189
|
+
onChange={(e) => setField("query", e.target.value)}
|
|
192
190
|
/>
|
|
193
191
|
</FormItem>
|
|
194
192
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Collection, ContentObjectType } from "@vertesia/common";
|
|
2
|
-
import { Button, Panel, useToast } from "@vertesia/ui/core";
|
|
3
|
-
import { TagsInput } from "@vertesia/ui/core";
|
|
2
|
+
import { Button, Panel, TagsInput, useToast } from "@vertesia/ui/core";
|
|
4
3
|
import { useUserSession } from "@vertesia/ui/session";
|
|
5
4
|
import { useEffect, useState } from "react";
|
|
6
5
|
|
|
@@ -27,12 +27,7 @@ const defaultLayout: ColumnLayout[] = [
|
|
|
27
27
|
|
|
28
28
|
function getTableLayout(registry: TypeRegistry, type: string | undefined): ColumnLayout[] {
|
|
29
29
|
const layout = type ? registry.getTypeLayout(type) : defaultLayout;
|
|
30
|
-
console.log('[DEBUG] getTableLayout called with type:', type);
|
|
31
|
-
console.log('[DEBUG] Layout from registry:', layout);
|
|
32
|
-
console.log('[DEBUG] Using defaultLayout?', layout === defaultLayout);
|
|
33
30
|
const result = layout ?? defaultLayout;
|
|
34
|
-
console.log('[DEBUG] Final layout:', result);
|
|
35
|
-
console.log('[DEBUG] Has Status field?', result.some(col => col.field === 'status'));
|
|
36
31
|
return result;
|
|
37
32
|
}
|
|
38
33
|
|
|
@@ -101,8 +96,6 @@ export function DocumentSearchResults({ layout, onUpload, allowFilter = true, al
|
|
|
101
96
|
typeRegistry ? layout || getTableLayout(typeRegistry, search.query.type) : defaultLayout,
|
|
102
97
|
);
|
|
103
98
|
|
|
104
|
-
console.log('[DEBUG] DocumentSearchResults - actualLayout:', actualLayout);
|
|
105
|
-
console.log('[DEBUG] DocumentSearchResults - actualLayout has Status?', actualLayout.some(col => col.field === 'status'));
|
|
106
99
|
//TODO _setRefreshTrigger state not used
|
|
107
100
|
const [refreshTrigger, _setRefreshTrigger] = useState(0);
|
|
108
101
|
const [loaded, setLoaded] = useState(0);
|