@walkthru-earth/objex 0.1.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/LICENSE +396 -0
- package/README.md +114 -0
- package/dist/assets/favicon.svg +17 -0
- package/dist/components/CLAUDE.md +44 -0
- package/dist/components/browser/Breadcrumb.svelte +50 -0
- package/dist/components/browser/Breadcrumb.svelte.d.ts +7 -0
- package/dist/components/browser/CreateFolderDialog.svelte +98 -0
- package/dist/components/browser/CreateFolderDialog.svelte.d.ts +6 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte +90 -0
- package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +8 -0
- package/dist/components/browser/DropZone.svelte +83 -0
- package/dist/components/browser/DropZone.svelte.d.ts +7 -0
- package/dist/components/browser/FileBrowser.svelte +229 -0
- package/dist/components/browser/FileBrowser.svelte.d.ts +3 -0
- package/dist/components/browser/FileRow.svelte +112 -0
- package/dist/components/browser/FileRow.svelte.d.ts +9 -0
- package/dist/components/browser/FileTreeSidebar.svelte +559 -0
- package/dist/components/browser/FileTreeSidebar.svelte.d.ts +8 -0
- package/dist/components/browser/RenameDialog.svelte +101 -0
- package/dist/components/browser/RenameDialog.svelte.d.ts +8 -0
- package/dist/components/browser/SearchBar.svelte +40 -0
- package/dist/components/browser/SearchBar.svelte.d.ts +6 -0
- package/dist/components/browser/UploadButton.svelte +65 -0
- package/dist/components/browser/UploadButton.svelte.d.ts +3 -0
- package/dist/components/editor/CodeMirrorEditor.svelte +404 -0
- package/dist/components/editor/CodeMirrorEditor.svelte.d.ts +12 -0
- package/dist/components/editor/MilkdownEditor.svelte +98 -0
- package/dist/components/editor/MilkdownEditor.svelte.d.ts +9 -0
- package/dist/components/editor/SqlEditor.svelte +173 -0
- package/dist/components/editor/SqlEditor.svelte.d.ts +7 -0
- package/dist/components/editor/SqlResultBlock.svelte +199 -0
- package/dist/components/editor/SqlResultBlock.svelte.d.ts +9 -0
- package/dist/components/layout/ConnectionDialog.svelte +439 -0
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +9 -0
- package/dist/components/layout/LocaleToggle.svelte +32 -0
- package/dist/components/layout/LocaleToggle.svelte.d.ts +3 -0
- package/dist/components/layout/SafeLockToggle.svelte +37 -0
- package/dist/components/layout/SafeLockToggle.svelte.d.ts +18 -0
- package/dist/components/layout/Sidebar.svelte +314 -0
- package/dist/components/layout/Sidebar.svelte.d.ts +3 -0
- package/dist/components/layout/StatusBar.svelte +73 -0
- package/dist/components/layout/StatusBar.svelte.d.ts +3 -0
- package/dist/components/layout/TabBar.svelte +102 -0
- package/dist/components/layout/TabBar.svelte.d.ts +7 -0
- package/dist/components/layout/ThemeToggle.svelte +52 -0
- package/dist/components/layout/ThemeToggle.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge.svelte +49 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +32 -0
- package/dist/components/ui/badge/index.d.ts +1 -0
- package/dist/components/ui/badge/index.js +1 -0
- package/dist/components/ui/button/button.svelte +82 -0
- package/dist/components/ui/button/button.svelte.d.ts +64 -0
- package/dist/components/ui/button/index.d.ts +2 -0
- package/dist/components/ui/button/index.js +4 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte +40 -0
- package/dist/components/ui/context-menu/context-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte +28 -0
- package/dist/components/ui/context-menu/context-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte +21 -0
- package/dist/components/ui/context-menu/context-menu-group-heading.svelte.d.ts +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte +27 -0
- package/dist/components/ui/context-menu/context-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte +24 -0
- package/dist/components/ui/context-menu/context-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte +16 -0
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte +33 -0
- package/dist/components/ui/context-menu/context-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte +17 -0
- package/dist/components/ui/context-menu/context-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte +20 -0
- package/dist/components/ui/context-menu/context-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/context-menu/context-menu-sub-trigger.svelte.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/context-menu/context-menu.svelte +7 -0
- package/dist/components/ui/context-menu/context-menu.svelte.d.ts +3 -0
- package/dist/components/ui/context-menu/index.d.ts +17 -0
- package/dist/components/ui/context-menu/index.js +19 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +43 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte.d.ts +9 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte.d.ts +10 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-portal.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +33 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte.d.ts +5 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +20 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte.d.ts +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-sub.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.svelte.d.ts +3 -0
- package/dist/components/ui/dropdown-menu/index.d.ts +18 -0
- package/dist/components/ui/dropdown-menu/index.js +18 -0
- package/dist/components/ui/input/index.d.ts +2 -0
- package/dist/components/ui/input/index.js +4 -0
- package/dist/components/ui/input/input.svelte +52 -0
- package/dist/components/ui/input/input.svelte.d.ts +13 -0
- package/dist/components/ui/resizable/index.d.ts +4 -0
- package/dist/components/ui/resizable/index.js +6 -0
- package/dist/components/ui/resizable/resizable-handle.svelte +30 -0
- package/dist/components/ui/resizable/resizable-handle.svelte.d.ts +8 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte +20 -0
- package/dist/components/ui/resizable/resizable-pane-group.svelte.d.ts +7 -0
- package/dist/components/ui/scroll-area/index.d.ts +3 -0
- package/dist/components/ui/scroll-area/index.js +5 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +31 -0
- package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte.d.ts +4 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte +47 -0
- package/dist/components/ui/scroll-area/scroll-area.svelte.d.ts +11 -0
- package/dist/components/ui/separator/index.d.ts +2 -0
- package/dist/components/ui/separator/index.js +4 -0
- package/dist/components/ui/separator/separator.svelte +21 -0
- package/dist/components/ui/separator/separator.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/index.d.ts +11 -0
- package/dist/components/ui/sheet/index.js +13 -0
- package/dist/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/components/ui/sheet/sheet-close.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-content.svelte +62 -0
- package/dist/components/ui/sheet/sheet-content.svelte.d.ts +37 -0
- package/dist/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/components/ui/sheet/sheet-description.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/components/ui/sheet/sheet-footer.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/components/ui/sheet/sheet-header.svelte.d.ts +5 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte +20 -0
- package/dist/components/ui/sheet/sheet-overlay.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/components/ui/sheet/sheet-portal.svelte.d.ts +3 -0
- package/dist/components/ui/sheet/sheet-title.svelte +13 -0
- package/dist/components/ui/sheet/sheet-title.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/components/ui/sheet/sheet-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/sheet/sheet.svelte +7 -0
- package/dist/components/ui/sheet/sheet.svelte.d.ts +3 -0
- package/dist/components/ui/switch/index.d.ts +2 -0
- package/dist/components/ui/switch/index.js +4 -0
- package/dist/components/ui/switch/switch.svelte +28 -0
- package/dist/components/ui/switch/switch.svelte.d.ts +8 -0
- package/dist/components/ui/tabs/index.d.ts +5 -0
- package/dist/components/ui/tabs/index.js +7 -0
- package/dist/components/ui/tabs/tabs-content.svelte +17 -0
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-list.svelte +16 -0
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte +20 -0
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tabs/tabs.svelte +19 -0
- package/dist/components/ui/tabs/tabs.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/index.d.ts +6 -0
- package/dist/components/ui/tooltip/index.js +8 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte +52 -0
- package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +11 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-portal.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-provider.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +4 -0
- package/dist/components/ui/tooltip/tooltip.svelte +7 -0
- package/dist/components/ui/tooltip/tooltip.svelte.d.ts +4 -0
- package/dist/components/viewers/ArchiveViewer.svelte +586 -0
- package/dist/components/viewers/ArchiveViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CLAUDE.md +60 -0
- package/dist/components/viewers/CodeViewer.svelte +553 -0
- package/dist/components/viewers/CodeViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CogViewer.svelte +1345 -0
- package/dist/components/viewers/CogViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/CopcViewer.svelte +25 -0
- package/dist/components/viewers/CopcViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/DatabaseViewer.svelte +169 -0
- package/dist/components/viewers/DatabaseViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/FileInfo.svelte +174 -0
- package/dist/components/viewers/FileInfo.svelte.d.ts +10 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte +755 -0
- package/dist/components/viewers/FlatGeobufViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte +278 -0
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ImageViewer.svelte +233 -0
- package/dist/components/viewers/ImageViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/LoadProgress.svelte +93 -0
- package/dist/components/viewers/LoadProgress.svelte.d.ts +15 -0
- package/dist/components/viewers/MapViewer.svelte +234 -0
- package/dist/components/viewers/MapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MarkdownViewer.svelte +478 -0
- package/dist/components/viewers/MarkdownViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/MediaViewer.svelte +121 -0
- package/dist/components/viewers/MediaViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ModelViewer.svelte +164 -0
- package/dist/components/viewers/ModelViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/NotebookViewer.svelte +389 -0
- package/dist/components/viewers/NotebookViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PdfViewer.svelte +278 -0
- package/dist/components/viewers/PdfViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/PmtilesViewer.svelte +191 -0
- package/dist/components/viewers/PmtilesViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte +159 -0
- package/dist/components/viewers/QueryHistoryPanel.svelte.d.ts +8 -0
- package/dist/components/viewers/RawViewer.svelte +117 -0
- package/dist/components/viewers/RawViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StacMapViewer.svelte +20 -0
- package/dist/components/viewers/StacMapViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte +27 -0
- package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +7 -0
- package/dist/components/viewers/TableGrid.svelte +355 -0
- package/dist/components/viewers/TableGrid.svelte.d.ts +12 -0
- package/dist/components/viewers/TableStatusBar.svelte +92 -0
- package/dist/components/viewers/TableStatusBar.svelte.d.ts +11 -0
- package/dist/components/viewers/TableToolbar.svelte +382 -0
- package/dist/components/viewers/TableToolbar.svelte.d.ts +25 -0
- package/dist/components/viewers/TableViewer.svelte +923 -0
- package/dist/components/viewers/TableViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/ViewerRouter.svelte +70 -0
- package/dist/components/viewers/ViewerRouter.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +288 -0
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +17 -0
- package/dist/components/viewers/ZarrViewer.svelte +256 -0
- package/dist/components/viewers/ZarrViewer.svelte.d.ts +7 -0
- package/dist/components/viewers/map/AttributeTable.svelte +52 -0
- package/dist/components/viewers/map/AttributeTable.svelte.d.ts +8 -0
- package/dist/components/viewers/map/MapContainer.svelte +158 -0
- package/dist/components/viewers/map/MapContainer.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +389 -0
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte.d.ts +10 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte +332 -0
- package/dist/components/viewers/pmtiles/PmtilesMapView.svelte.d.ts +11 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +373 -0
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte.d.ts +12 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte +112 -0
- package/dist/components/viewers/pmtiles/SvgTileRenderer.svelte.d.ts +10 -0
- package/dist/file-icons/CLAUDE.md +21 -0
- package/dist/file-icons/FileTypeIcon.svelte +74 -0
- package/dist/file-icons/FileTypeIcon.svelte.d.ts +9 -0
- package/dist/file-icons/index.d.ts +56 -0
- package/dist/file-icons/index.js +1070 -0
- package/dist/i18n/CLAUDE.md +19 -0
- package/dist/i18n/ar.d.ts +1 -0
- package/dist/i18n/ar.js +404 -0
- package/dist/i18n/en.d.ts +1 -0
- package/dist/i18n/en.js +404 -0
- package/dist/i18n/index.svelte.d.ts +9 -0
- package/dist/i18n/index.svelte.js +27 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +13 -0
- package/dist/query/CLAUDE.md +22 -0
- package/dist/query/engine.d.ts +56 -0
- package/dist/query/engine.js +6 -0
- package/dist/query/index.d.ts +4 -0
- package/dist/query/index.js +19 -0
- package/dist/query/wasm.d.ts +20 -0
- package/dist/query/wasm.js +890 -0
- package/dist/storage/CLAUDE.md +23 -0
- package/dist/storage/adapter.d.ts +21 -0
- package/dist/storage/adapter.js +1 -0
- package/dist/storage/browser-azure.d.ts +25 -0
- package/dist/storage/browser-azure.js +271 -0
- package/dist/storage/browser-cloud.d.ts +32 -0
- package/dist/storage/browser-cloud.js +293 -0
- package/dist/storage/index.d.ts +11 -0
- package/dist/storage/index.js +37 -0
- package/dist/storage/url-adapter.d.ts +19 -0
- package/dist/storage/url-adapter.js +51 -0
- package/dist/stores/CLAUDE.md +29 -0
- package/dist/stores/browser.svelte.d.ts +28 -0
- package/dist/stores/browser.svelte.js +160 -0
- package/dist/stores/connections.svelte.d.ts +56 -0
- package/dist/stores/connections.svelte.js +272 -0
- package/dist/stores/credentials.svelte.d.ts +56 -0
- package/dist/stores/credentials.svelte.js +79 -0
- package/dist/stores/files.svelte.d.ts +20 -0
- package/dist/stores/files.svelte.js +76 -0
- package/dist/stores/query-history.svelte.d.ts +16 -0
- package/dist/stores/query-history.svelte.js +57 -0
- package/dist/stores/safelock.svelte.d.ts +8 -0
- package/dist/stores/safelock.svelte.js +52 -0
- package/dist/stores/settings.svelte.d.ts +11 -0
- package/dist/stores/settings.svelte.js +101 -0
- package/dist/stores/tab-resources.svelte.d.ts +25 -0
- package/dist/stores/tab-resources.svelte.js +61 -0
- package/dist/stores/tabs.svelte.d.ts +17 -0
- package/dist/stores/tabs.svelte.js +110 -0
- package/dist/types/notebookjs.d.ts +14 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.js +1 -0
- package/dist/utils/CLAUDE.md +54 -0
- package/dist/utils/analytics.d.ts +10 -0
- package/dist/utils/analytics.js +38 -0
- package/dist/utils/archive.d.ts +70 -0
- package/dist/utils/archive.js +333 -0
- package/dist/utils/column-types.d.ts +5 -0
- package/dist/utils/column-types.js +137 -0
- package/dist/utils/deck.d.ts +98 -0
- package/dist/utils/deck.js +208 -0
- package/dist/utils/evidence-context.d.ts +22 -0
- package/dist/utils/evidence-context.js +56 -0
- package/dist/utils/export.d.ts +2 -0
- package/dist/utils/export.js +51 -0
- package/dist/utils/format.d.ts +14 -0
- package/dist/utils/format.js +56 -0
- package/dist/utils/geoarrow.d.ts +32 -0
- package/dist/utils/geoarrow.js +672 -0
- package/dist/utils/hex.d.ts +10 -0
- package/dist/utils/hex.js +27 -0
- package/dist/utils/host-detection.d.ts +23 -0
- package/dist/utils/host-detection.js +289 -0
- package/dist/utils/map-selection.d.ts +12 -0
- package/dist/utils/map-selection.js +45 -0
- package/dist/utils/markdown-sql.d.ts +30 -0
- package/dist/utils/markdown-sql.js +73 -0
- package/dist/utils/markdown.d.ts +18 -0
- package/dist/utils/markdown.js +146 -0
- package/dist/utils/model3d.d.ts +13 -0
- package/dist/utils/model3d.js +62 -0
- package/dist/utils/parquet-metadata.d.ts +58 -0
- package/dist/utils/parquet-metadata.js +228 -0
- package/dist/utils/pdf.d.ts +8 -0
- package/dist/utils/pdf.js +28 -0
- package/dist/utils/pmtiles-tile.d.ts +38 -0
- package/dist/utils/pmtiles-tile.js +64 -0
- package/dist/utils/pmtiles.d.ts +46 -0
- package/dist/utils/pmtiles.js +135 -0
- package/dist/utils/shiki.d.ts +8 -0
- package/dist/utils/shiki.js +98 -0
- package/dist/utils/storage-url.d.ts +64 -0
- package/dist/utils/storage-url.js +374 -0
- package/dist/utils/url-state.d.ts +40 -0
- package/dist/utils/url-state.js +113 -0
- package/dist/utils/url.d.ts +27 -0
- package/dist/utils/url.js +115 -0
- package/dist/utils/wkb.d.ts +43 -0
- package/dist/utils/wkb.js +345 -0
- package/dist/utils/zarr.d.ts +39 -0
- package/dist/utils/zarr.js +204 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +5 -0
- package/package.json +203 -0
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { format as formatSql } from 'sql-formatter';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import CodeMirrorEditor from '../editor/CodeMirrorEditor.svelte';
|
|
5
|
+
import { buildDuckDbSource, isCloudNativeFormat } from '../../file-icons/index.js';
|
|
6
|
+
import { t } from '../../i18n/index.svelte.js';
|
|
7
|
+
import type { MapQueryResult, SchemaField } from '../../query/engine';
|
|
8
|
+
import { getQueryEngine, QueryCancelledError, type QueryHandle } from '../../query/index.js';
|
|
9
|
+
import { queryHistory } from '../../stores/query-history.svelte.js';
|
|
10
|
+
import { settings } from '../../stores/settings.svelte.js';
|
|
11
|
+
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
12
|
+
import type { Tab } from '../../types';
|
|
13
|
+
import type { GeoArrowGeomType } from '../../utils/geoarrow.js';
|
|
14
|
+
import {
|
|
15
|
+
extractBounds,
|
|
16
|
+
extractEpsgFromGeoMeta,
|
|
17
|
+
extractGeometryTypes,
|
|
18
|
+
readParquetMetadata
|
|
19
|
+
} from '../../utils/parquet-metadata.js';
|
|
20
|
+
import {
|
|
21
|
+
buildDuckDbUrl,
|
|
22
|
+
buildHttpsUrl,
|
|
23
|
+
buildStorageUrl,
|
|
24
|
+
canStreamDirectly
|
|
25
|
+
} from '../../utils/url.js';
|
|
26
|
+
import { getUrlView, updateUrlView } from '../../utils/url-state.js';
|
|
27
|
+
import { findGeoColumn, findGeoColumnFromRows, parseWKB, toBinary } from '../../utils/wkb.js';
|
|
28
|
+
import FileInfo from './FileInfo.svelte';
|
|
29
|
+
import LoadProgress, { type ProgressEntry } from './LoadProgress.svelte';
|
|
30
|
+
import QueryHistoryPanel from './QueryHistoryPanel.svelte';
|
|
31
|
+
import TableGrid from './TableGrid.svelte';
|
|
32
|
+
import TableStatusBar from './TableStatusBar.svelte';
|
|
33
|
+
import TableToolbar from './TableToolbar.svelte';
|
|
34
|
+
|
|
35
|
+
let { tab }: { tab: Tab } = $props();
|
|
36
|
+
|
|
37
|
+
let pageSize = $state(settings.featureLimit);
|
|
38
|
+
|
|
39
|
+
let schema = $state<SchemaField[]>([]);
|
|
40
|
+
let columns = $state<string[]>([]);
|
|
41
|
+
let rows = $state.raw<Record<string, any>[]>([]);
|
|
42
|
+
let totalRows = $state<number | null>(null);
|
|
43
|
+
let currentPage = $state(1);
|
|
44
|
+
let loading = $state(true);
|
|
45
|
+
let error = $state<string | null>(null);
|
|
46
|
+
let historyVisible = $state(false);
|
|
47
|
+
let hasGeo = $state(false);
|
|
48
|
+
let isStac = $state(false);
|
|
49
|
+
// Restore view mode from URL hash if present
|
|
50
|
+
const urlView = getUrlView();
|
|
51
|
+
let viewMode = $state<'table' | 'map' | 'stac' | 'info'>(
|
|
52
|
+
urlView === 'map' ? 'map' : urlView === 'stac' ? 'stac' : urlView === 'info' ? 'info' : 'table'
|
|
53
|
+
);
|
|
54
|
+
let sqlQuery = $state('');
|
|
55
|
+
let customSql = $state('');
|
|
56
|
+
let queryRunning = $state(false);
|
|
57
|
+
let executionTimeMs = $state(0);
|
|
58
|
+
|
|
59
|
+
// Progress stage for user feedback
|
|
60
|
+
let loadStage = $state('');
|
|
61
|
+
let loadProgress = $state<ProgressEntry[]>([]);
|
|
62
|
+
|
|
63
|
+
// Load cancellation: incrementing ID so stale loads are ignored
|
|
64
|
+
let loadGeneration = 0;
|
|
65
|
+
let activeHandle: QueryHandle | null = null;
|
|
66
|
+
let forceCancelVisible = $state(false);
|
|
67
|
+
let forceCancelTimer: ReturnType<typeof setTimeout> | null = null;
|
|
68
|
+
|
|
69
|
+
// Sort state
|
|
70
|
+
let sortColumn = $state<string | null>(null);
|
|
71
|
+
let sortDirection = $state<'asc' | 'desc' | null>(null);
|
|
72
|
+
|
|
73
|
+
// Tracks whether the current mapData came from a user-edited query (no system limit)
|
|
74
|
+
let isCustomQuery = $state(false);
|
|
75
|
+
|
|
76
|
+
// Geo column state for unified table+map query
|
|
77
|
+
let geoCol = $state<string | null>(null);
|
|
78
|
+
let geoColType = $state<string>('');
|
|
79
|
+
let sourceCrs = $state<string | null>(null);
|
|
80
|
+
let mapData = $state.raw<MapQueryResult | null>(null);
|
|
81
|
+
let knownGeomType = $state<GeoArrowGeomType | undefined>(undefined);
|
|
82
|
+
let metadataBounds = $state<[number, number, number, number] | null>(null);
|
|
83
|
+
|
|
84
|
+
const totalPages = $derived(totalRows != null ? Math.max(1, Math.ceil(totalRows / pageSize)) : 1);
|
|
85
|
+
const connId = $derived(tab?.connectionId ?? '');
|
|
86
|
+
|
|
87
|
+
// Build column type map from schema
|
|
88
|
+
const columnTypes = $derived(Object.fromEntries(schema.map((f) => [f.name, f.type])));
|
|
89
|
+
|
|
90
|
+
// Columns for display — exclude internal __wkb helper
|
|
91
|
+
const displayColumns = $derived(columns.filter((c) => c !== '__wkb'));
|
|
92
|
+
|
|
93
|
+
function buildDefaultSql(offset = 0): string {
|
|
94
|
+
const fileUrl = buildDuckDbUrl(tab);
|
|
95
|
+
const source = buildDuckDbSource(tab.path, fileUrl);
|
|
96
|
+
|
|
97
|
+
let sql: string;
|
|
98
|
+
if (geoCol) {
|
|
99
|
+
const quoted = `"${geoCol}"`;
|
|
100
|
+
const upper = geoColType.toUpperCase();
|
|
101
|
+
// Spatial types that ST_AsWKB accepts directly (GEOMETRY, WKB_BLOB, etc.).
|
|
102
|
+
// Includes Arrow "Binary"/"LargeBinary" — DuckDB GEOMETRY columns from
|
|
103
|
+
// ST_ReadSHP/ST_Read appear as Arrow Binary but are NOT WKB blobs.
|
|
104
|
+
const isSpatialType =
|
|
105
|
+
upper === 'GEOMETRY' ||
|
|
106
|
+
upper === 'GEOGRAPHY' ||
|
|
107
|
+
upper === 'WKB_BLOB' ||
|
|
108
|
+
upper.includes('POINT') ||
|
|
109
|
+
upper.includes('LINESTRING') ||
|
|
110
|
+
upper.includes('POLYGON') ||
|
|
111
|
+
upper.includes('BINARY'); // Arrow serialization of DuckDB GEOMETRY
|
|
112
|
+
// Actual WKB BLOB columns (e.g. GeoParquet) need explicit ST_GeomFromWKB
|
|
113
|
+
// because DuckDB has no implicit BLOB→GEOMETRY cast.
|
|
114
|
+
const isWkbBlob = upper === 'BLOB' || upper === 'BYTEA';
|
|
115
|
+
|
|
116
|
+
let wkbExpr: string;
|
|
117
|
+
if (isWkbBlob && !sourceCrs) {
|
|
118
|
+
// Already WKB — use directly, no conversion needed
|
|
119
|
+
wkbExpr = `${quoted} AS __wkb`;
|
|
120
|
+
} else {
|
|
121
|
+
let geomExpr = isSpatialType
|
|
122
|
+
? quoted
|
|
123
|
+
: isWkbBlob
|
|
124
|
+
? `ST_GeomFromWKB(${quoted})`
|
|
125
|
+
: `ST_GeomFromGeoJSON(${quoted})`;
|
|
126
|
+
if (sourceCrs) {
|
|
127
|
+
geomExpr = `ST_Transform(${geomExpr}, '${sourceCrs}', 'EPSG:4326', always_xy := true)`;
|
|
128
|
+
}
|
|
129
|
+
wkbExpr = `ST_AsWKB(${geomExpr}) AS __wkb`;
|
|
130
|
+
}
|
|
131
|
+
sql = `SELECT * EXCLUDE(${quoted}), ${wkbExpr} FROM ${source}`;
|
|
132
|
+
} else {
|
|
133
|
+
sql = `SELECT * FROM ${source}`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (sortColumn && sortDirection) {
|
|
137
|
+
sql += ` ORDER BY "${sortColumn}" ${sortDirection.toUpperCase()}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
sql += ` LIMIT ${pageSize} OFFSET ${offset}`;
|
|
141
|
+
return sql;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function extractMapData(queryRows: Record<string, any>[]): MapQueryResult | null {
|
|
145
|
+
if (!geoCol || queryRows.length === 0 || !columns.includes('__wkb')) return null;
|
|
146
|
+
|
|
147
|
+
const wkbArrays: Uint8Array[] = [];
|
|
148
|
+
let geometryType = 'POINT';
|
|
149
|
+
|
|
150
|
+
for (const row of queryRows) {
|
|
151
|
+
const bin = toBinary(row.__wkb);
|
|
152
|
+
if (bin) wkbArrays.push(bin);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Detect geometry type from first WKB value
|
|
156
|
+
if (wkbArrays.length > 0) {
|
|
157
|
+
const parsed = parseWKB(wkbArrays[0]);
|
|
158
|
+
if (parsed) geometryType = parsed.type.toUpperCase();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Build attributes map (exclude __wkb)
|
|
162
|
+
const attributes = new Map<string, { values: any[]; type: string }>();
|
|
163
|
+
for (const col of columns) {
|
|
164
|
+
if (col === '__wkb') continue;
|
|
165
|
+
const fieldType = columnTypes[col] ?? 'VARCHAR';
|
|
166
|
+
const values = queryRows.map((r) => r[col]);
|
|
167
|
+
attributes.set(col, { values, type: fieldType });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { wkbArrays, geometryType, attributes, rowCount: wkbArrays.length };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Track last loaded tab to prevent duplicate loads
|
|
174
|
+
let lastLoadedTabId = '';
|
|
175
|
+
|
|
176
|
+
// Register cleanup so tab store can free heavy data on close
|
|
177
|
+
$effect(() => {
|
|
178
|
+
const id = tab.id;
|
|
179
|
+
const unregister = tabResources.register(id, () => {
|
|
180
|
+
loadGeneration++;
|
|
181
|
+
// Cancel in-flight query if any
|
|
182
|
+
if (activeHandle) {
|
|
183
|
+
activeHandle.cancel();
|
|
184
|
+
activeHandle = null;
|
|
185
|
+
}
|
|
186
|
+
if (forceCancelTimer) {
|
|
187
|
+
clearTimeout(forceCancelTimer);
|
|
188
|
+
forceCancelTimer = null;
|
|
189
|
+
}
|
|
190
|
+
forceCancelVisible = false;
|
|
191
|
+
rows = [];
|
|
192
|
+
schema = [];
|
|
193
|
+
columns = [];
|
|
194
|
+
mapData = null;
|
|
195
|
+
geoCol = null;
|
|
196
|
+
knownGeomType = undefined;
|
|
197
|
+
metadataBounds = null;
|
|
198
|
+
error = null;
|
|
199
|
+
});
|
|
200
|
+
return unregister;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
$effect(() => {
|
|
204
|
+
if (!tab) return;
|
|
205
|
+
const tabId = tab.id;
|
|
206
|
+
untrack(() => {
|
|
207
|
+
if (tabId !== lastLoadedTabId) {
|
|
208
|
+
const isInitialLoad = lastLoadedTabId === '';
|
|
209
|
+
lastLoadedTabId = tabId;
|
|
210
|
+
if (!isInitialLoad) {
|
|
211
|
+
// Reset view mode on tab switch to avoid stale iframe/map views
|
|
212
|
+
viewMode = 'table';
|
|
213
|
+
updateUrlView('');
|
|
214
|
+
}
|
|
215
|
+
loadTable();
|
|
216
|
+
} else {
|
|
217
|
+
console.warn('[TableViewer] $effect re-fired but tab unchanged, skipping loadTable', tabId);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
function cancelLoad() {
|
|
223
|
+
loadGeneration++;
|
|
224
|
+
loadStage = t('table.cancellingQuery');
|
|
225
|
+
|
|
226
|
+
// Attempt graceful cancel via conn.cancelSent()
|
|
227
|
+
if (activeHandle) {
|
|
228
|
+
activeHandle.cancel();
|
|
229
|
+
// If the query hasn't settled after 5s, show force-cancel button
|
|
230
|
+
forceCancelTimer = setTimeout(() => {
|
|
231
|
+
forceCancelVisible = true;
|
|
232
|
+
}, 5000);
|
|
233
|
+
} else {
|
|
234
|
+
loading = false;
|
|
235
|
+
queryRunning = false;
|
|
236
|
+
loadStage = '';
|
|
237
|
+
error = t('table.queryCancelled');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function forceCancel() {
|
|
242
|
+
if (forceCancelTimer) {
|
|
243
|
+
clearTimeout(forceCancelTimer);
|
|
244
|
+
forceCancelTimer = null;
|
|
245
|
+
}
|
|
246
|
+
forceCancelVisible = false;
|
|
247
|
+
loadStage = '';
|
|
248
|
+
|
|
249
|
+
const engine = await getQueryEngine();
|
|
250
|
+
if (engine.forceCancel) {
|
|
251
|
+
await engine.forceCancel();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
activeHandle = null;
|
|
255
|
+
loading = false;
|
|
256
|
+
queryRunning = false;
|
|
257
|
+
error = t('table.queryCancelled');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function loadTable() {
|
|
261
|
+
const thisGen = ++loadGeneration;
|
|
262
|
+
|
|
263
|
+
// Cancel in-flight query from a previous load to prevent duplicate concurrent queries
|
|
264
|
+
if (activeHandle) {
|
|
265
|
+
activeHandle.cancel();
|
|
266
|
+
activeHandle = null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
loading = true;
|
|
270
|
+
error = null;
|
|
271
|
+
geoCol = null;
|
|
272
|
+
geoColType = '';
|
|
273
|
+
sourceCrs = null;
|
|
274
|
+
mapData = null;
|
|
275
|
+
isCustomQuery = false;
|
|
276
|
+
knownGeomType = undefined;
|
|
277
|
+
metadataBounds = null;
|
|
278
|
+
loadStage = t('table.preparingQuery');
|
|
279
|
+
loadProgress = [];
|
|
280
|
+
|
|
281
|
+
// Set SQL eagerly so editor shows the query while loading
|
|
282
|
+
const initialSql = buildDefaultSql(0);
|
|
283
|
+
sqlQuery = initialSql;
|
|
284
|
+
customSql = initialSql;
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const fileUrl = buildDuckDbUrl(tab);
|
|
288
|
+
const httpsUrl = buildHttpsUrl(tab);
|
|
289
|
+
const cloudNative = isCloudNativeFormat(tab.path);
|
|
290
|
+
const isParquet = /\.parquet$/i.test(tab.path);
|
|
291
|
+
const streamable = canStreamDirectly(tab);
|
|
292
|
+
|
|
293
|
+
// Start DuckDB boot immediately (runs in parallel with hyparquet)
|
|
294
|
+
loadStage = t('table.initEngine');
|
|
295
|
+
const enginePromise = getQueryEngine();
|
|
296
|
+
|
|
297
|
+
// ── Fast metadata via hyparquet (runs concurrently with DuckDB boot) ──
|
|
298
|
+
let metaFromHyparquet = false;
|
|
299
|
+
let needsDuckDbCrs = false;
|
|
300
|
+
if (cloudNative && isParquet && streamable) {
|
|
301
|
+
try {
|
|
302
|
+
loadStage = t('table.readingMetadata');
|
|
303
|
+
const meta = await readParquetMetadata(httpsUrl);
|
|
304
|
+
if (thisGen !== loadGeneration) return;
|
|
305
|
+
|
|
306
|
+
// Format detection
|
|
307
|
+
const formatName = meta.geo ? 'GeoParquet' : 'Parquet';
|
|
308
|
+
loadProgress = [
|
|
309
|
+
{ label: t('progress.format'), value: formatName },
|
|
310
|
+
{ label: t('progress.source'), value: t('progress.rangeRequest') }
|
|
311
|
+
];
|
|
312
|
+
|
|
313
|
+
// Instant schema display — use hyparquet-detected types as-is.
|
|
314
|
+
// mapParquetType() returns 'GEOMETRY' for native Parquet GEOMETRY
|
|
315
|
+
// logical type (Format 2.11+) and 'BLOB' for plain BYTE_ARRAY.
|
|
316
|
+
// This matches what DuckDB reports with enable_geoparquet_conversion=false.
|
|
317
|
+
schema = meta.schema.map((s) => ({
|
|
318
|
+
name: s.name,
|
|
319
|
+
type: s.type,
|
|
320
|
+
nullable: true
|
|
321
|
+
}));
|
|
322
|
+
columns = schema.map((f) => f.name);
|
|
323
|
+
totalRows = meta.rowCount;
|
|
324
|
+
|
|
325
|
+
// Column names preview (truncated)
|
|
326
|
+
const colPreview =
|
|
327
|
+
columns.length <= 8
|
|
328
|
+
? columns.join(', ')
|
|
329
|
+
: `${columns.slice(0, 7).join(', ')}, +${columns.length - 7} more`;
|
|
330
|
+
loadProgress = [
|
|
331
|
+
...loadProgress,
|
|
332
|
+
{ label: t('progress.columns'), value: String(columns.length), detail: colPreview },
|
|
333
|
+
{ label: t('progress.rows'), value: meta.rowCount.toLocaleString() }
|
|
334
|
+
];
|
|
335
|
+
|
|
336
|
+
// Row groups & compression
|
|
337
|
+
if (meta.numRowGroups > 0) {
|
|
338
|
+
loadProgress = [
|
|
339
|
+
...loadProgress,
|
|
340
|
+
{ label: t('progress.rowGroups'), value: String(meta.numRowGroups) }
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
if (meta.compression) {
|
|
344
|
+
loadProgress = [
|
|
345
|
+
...loadProgress,
|
|
346
|
+
{ label: t('progress.compression'), value: meta.compression }
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Created by (tool)
|
|
351
|
+
if (meta.createdBy) {
|
|
352
|
+
loadProgress = [
|
|
353
|
+
...loadProgress,
|
|
354
|
+
{ label: t('progress.createdBy'), value: meta.createdBy }
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (meta.geo) {
|
|
359
|
+
geoCol = meta.geo.primaryColumn;
|
|
360
|
+
// With enable_geoparquet_conversion=false, DuckDB reads ALL
|
|
361
|
+
// GeoParquet geometry columns as BLOB regardless of Parquet
|
|
362
|
+
// logical type. Native Parquet GEOMETRY (Format 2.11+) is a
|
|
363
|
+
// DuckDB v1.5 feature not yet in WASM (v1.33).
|
|
364
|
+
geoColType = 'BLOB';
|
|
365
|
+
sourceCrs = extractEpsgFromGeoMeta(meta.geo);
|
|
366
|
+
const geomTypes = extractGeometryTypes(meta.geo);
|
|
367
|
+
if (geomTypes.length === 1) knownGeomType = geomTypes[0];
|
|
368
|
+
metadataBounds = extractBounds(meta.geo);
|
|
369
|
+
|
|
370
|
+
const geomLabel = geomTypes.join(', ') || geoColType;
|
|
371
|
+
const primaryCol = meta.geo.columns[meta.geo.primaryColumn];
|
|
372
|
+
const encodingInfo = primaryCol?.encoding ?? 'WKB';
|
|
373
|
+
loadProgress = [
|
|
374
|
+
...loadProgress,
|
|
375
|
+
{ label: t('progress.geometry'), value: `${geoCol} (${geomLabel})` },
|
|
376
|
+
{ label: t('progress.encoding'), value: encodingInfo }
|
|
377
|
+
];
|
|
378
|
+
if (sourceCrs) {
|
|
379
|
+
loadProgress = [...loadProgress, { label: t('progress.crs'), value: sourceCrs }];
|
|
380
|
+
} else {
|
|
381
|
+
loadProgress = [
|
|
382
|
+
...loadProgress,
|
|
383
|
+
{ label: t('progress.crs'), value: 'EPSG:4326 (WGS84)' }
|
|
384
|
+
];
|
|
385
|
+
}
|
|
386
|
+
if (metadataBounds) {
|
|
387
|
+
const [minX, minY, maxX, maxY] = metadataBounds;
|
|
388
|
+
loadProgress = [
|
|
389
|
+
...loadProgress,
|
|
390
|
+
{
|
|
391
|
+
label: t('progress.bounds'),
|
|
392
|
+
value: `${minX.toFixed(2)}, ${minY.toFixed(2)}, ${maxX.toFixed(2)}, ${maxY.toFixed(2)}`
|
|
393
|
+
}
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// No GeoParquet "geo" metadata — detect native Parquet GEOMETRY
|
|
399
|
+
// columns (Format 2.11+) via schema column names/types
|
|
400
|
+
if (!geoCol) {
|
|
401
|
+
const detectedGeoCol = findGeoColumn(schema);
|
|
402
|
+
if (detectedGeoCol) {
|
|
403
|
+
geoCol = detectedGeoCol;
|
|
404
|
+
// DuckDB WASM v1.33 doesn't support native Parquet GEOMETRY —
|
|
405
|
+
// columns are always BLOB with enable_geoparquet_conversion=false.
|
|
406
|
+
geoColType = 'BLOB';
|
|
407
|
+
needsDuckDbCrs = true;
|
|
408
|
+
loadProgress = [
|
|
409
|
+
...loadProgress,
|
|
410
|
+
{ label: t('progress.geometry'), value: `${geoCol} (native Parquet)` }
|
|
411
|
+
];
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
hasGeo = geoCol !== null;
|
|
416
|
+
isStac = schema.some((f) => f.name === 'stac_version');
|
|
417
|
+
if (isStac) {
|
|
418
|
+
loadProgress = [
|
|
419
|
+
...loadProgress,
|
|
420
|
+
{ label: t('progress.format'), value: t('progress.stacDetected') }
|
|
421
|
+
];
|
|
422
|
+
}
|
|
423
|
+
metaFromHyparquet = true;
|
|
424
|
+
} catch {
|
|
425
|
+
// hyparquet failed (CORS, auth, format) — fall back to DuckDB
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Wait for DuckDB engine
|
|
430
|
+
loadStage = metaFromHyparquet ? t('table.bootingEngine') : t('table.initEngine');
|
|
431
|
+
const engine = await enginePromise;
|
|
432
|
+
if (thisGen !== loadGeneration) return;
|
|
433
|
+
|
|
434
|
+
// If hyparquet detected a geo column but couldn't determine CRS
|
|
435
|
+
// (native Parquet GEOMETRY without "geo" KV metadata), use DuckDB
|
|
436
|
+
if (metaFromHyparquet && needsDuckDbCrs && geoCol) {
|
|
437
|
+
try {
|
|
438
|
+
sourceCrs = await engine.detectCrs(connId, fileUrl, geoCol);
|
|
439
|
+
if (thisGen !== loadGeneration) return;
|
|
440
|
+
if (sourceCrs) {
|
|
441
|
+
loadProgress = [...loadProgress, { label: t('progress.crs'), value: sourceCrs }];
|
|
442
|
+
} else {
|
|
443
|
+
loadProgress = [
|
|
444
|
+
...loadProgress,
|
|
445
|
+
{ label: t('progress.crs'), value: 'EPSG:4326 (WGS84)' }
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
// CRS detection failed — continue with WGS84 assumption
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (cloudNative && !metaFromHyparquet) {
|
|
454
|
+
// Fallback: DuckDB metadata queries
|
|
455
|
+
loadStage = t('table.loadingSchema');
|
|
456
|
+
loadProgress = [
|
|
457
|
+
...loadProgress,
|
|
458
|
+
{ label: t('progress.source'), value: t('progress.duckdbFallback') }
|
|
459
|
+
];
|
|
460
|
+
|
|
461
|
+
if (engine.getSchemaAndCrs) {
|
|
462
|
+
const result = await engine.getSchemaAndCrs(connId, fileUrl, findGeoColumn);
|
|
463
|
+
if (thisGen !== loadGeneration) return;
|
|
464
|
+
schema = result.schema;
|
|
465
|
+
columns = schema.map((f) => f.name);
|
|
466
|
+
const colPreview =
|
|
467
|
+
columns.length <= 8
|
|
468
|
+
? columns.join(', ')
|
|
469
|
+
: `${columns.slice(0, 7).join(', ')}, +${columns.length - 7} more`;
|
|
470
|
+
loadProgress = [
|
|
471
|
+
...loadProgress,
|
|
472
|
+
{ label: t('progress.columns'), value: String(columns.length), detail: colPreview }
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
geoCol = result.geomCol;
|
|
476
|
+
if (result.geomCol) {
|
|
477
|
+
const geoField = schema.find((f) => f.name === result.geomCol);
|
|
478
|
+
geoColType = geoField?.type ?? 'GEOMETRY';
|
|
479
|
+
loadProgress = [
|
|
480
|
+
...loadProgress,
|
|
481
|
+
{ label: t('progress.geometry'), value: `${result.geomCol} (${geoColType})` }
|
|
482
|
+
];
|
|
483
|
+
sourceCrs = result.crs;
|
|
484
|
+
if (sourceCrs) {
|
|
485
|
+
loadProgress = [...loadProgress, { label: t('progress.crs'), value: sourceCrs }];
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
schema = await engine.getSchema(connId, fileUrl);
|
|
490
|
+
if (thisGen !== loadGeneration) return;
|
|
491
|
+
columns = schema.map((f) => f.name);
|
|
492
|
+
const colPreview =
|
|
493
|
+
columns.length <= 8
|
|
494
|
+
? columns.join(', ')
|
|
495
|
+
: `${columns.slice(0, 7).join(', ')}, +${columns.length - 7} more`;
|
|
496
|
+
loadProgress = [
|
|
497
|
+
...loadProgress,
|
|
498
|
+
{ label: t('progress.columns'), value: String(columns.length), detail: colPreview }
|
|
499
|
+
];
|
|
500
|
+
|
|
501
|
+
const detectedGeoCol = findGeoColumn(schema);
|
|
502
|
+
geoCol = detectedGeoCol;
|
|
503
|
+
if (detectedGeoCol) {
|
|
504
|
+
const geoField = schema.find((f) => f.name === detectedGeoCol);
|
|
505
|
+
geoColType = geoField?.type ?? 'GEOMETRY';
|
|
506
|
+
loadProgress = [
|
|
507
|
+
...loadProgress,
|
|
508
|
+
{ label: t('progress.geometry'), value: `${detectedGeoCol} (${geoColType})` }
|
|
509
|
+
];
|
|
510
|
+
sourceCrs = await engine.detectCrs(connId, fileUrl, detectedGeoCol);
|
|
511
|
+
if (thisGen !== loadGeneration) return;
|
|
512
|
+
if (sourceCrs) {
|
|
513
|
+
loadProgress = [...loadProgress, { label: t('progress.crs'), value: sourceCrs }];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
hasGeo = geoCol !== null;
|
|
518
|
+
isStac = schema.some((f) => f.name === 'stac_version');
|
|
519
|
+
if (isStac) {
|
|
520
|
+
loadProgress = [
|
|
521
|
+
...loadProgress,
|
|
522
|
+
{ label: t('progress.format'), value: t('progress.stacDetected') }
|
|
523
|
+
];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Rebuild SQL now that we know the actual path resolution
|
|
528
|
+
sqlQuery = buildDefaultSql(0);
|
|
529
|
+
customSql = sqlQuery;
|
|
530
|
+
|
|
531
|
+
loadStage = t('table.runningQuery');
|
|
532
|
+
const start = performance.now();
|
|
533
|
+
const result = await executeQuery(sqlQuery);
|
|
534
|
+
if (thisGen !== loadGeneration) return;
|
|
535
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
536
|
+
|
|
537
|
+
if (!cloudNative && result) {
|
|
538
|
+
// Non-cloud-native (CSV, GeoJSON, etc.): derive schema from the
|
|
539
|
+
// query result so we avoid a second full-file download.
|
|
540
|
+
schema = (result.columns ?? []).map((col, i) => ({
|
|
541
|
+
name: col,
|
|
542
|
+
type: result.types?.[i] ?? 'VARCHAR',
|
|
543
|
+
nullable: true
|
|
544
|
+
}));
|
|
545
|
+
columns = schema.map((f) => f.name);
|
|
546
|
+
|
|
547
|
+
const detectedGeoCol = findGeoColumn(schema);
|
|
548
|
+
geoCol = detectedGeoCol;
|
|
549
|
+
if (detectedGeoCol) {
|
|
550
|
+
const geoField = schema.find((f) => f.name === detectedGeoCol);
|
|
551
|
+
geoColType = geoField?.type ?? 'GEOMETRY';
|
|
552
|
+
}
|
|
553
|
+
hasGeo = detectedGeoCol !== null;
|
|
554
|
+
isStac = schema.some((f) => f.name === 'stac_version');
|
|
555
|
+
|
|
556
|
+
// Re-query with geo-aware SQL if geo column was detected
|
|
557
|
+
if (hasGeo) {
|
|
558
|
+
const geoSql = buildDefaultSql(0);
|
|
559
|
+
sqlQuery = geoSql;
|
|
560
|
+
customSql = geoSql;
|
|
561
|
+
const geoStart = performance.now();
|
|
562
|
+
await executeQuery(geoSql);
|
|
563
|
+
if (thisGen !== loadGeneration) return;
|
|
564
|
+
executionTimeMs = Math.round(performance.now() - geoStart);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// If schema-only detection missed geo, try content sniffing on actual rows
|
|
569
|
+
if (!hasGeo && rows.length > 0) {
|
|
570
|
+
hasGeo = findGeoColumnFromRows(rows, schema) !== null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
mapData = extractMapData(rows);
|
|
574
|
+
|
|
575
|
+
// Auto-switch to table if URL hash requested map/stac but no geo/stac was detected
|
|
576
|
+
if (!hasGeo && viewMode === 'map') {
|
|
577
|
+
viewMode = 'table';
|
|
578
|
+
}
|
|
579
|
+
if (!isStac && viewMode === 'stac') {
|
|
580
|
+
viewMode = 'table';
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
loading = false;
|
|
584
|
+
loadStage = '';
|
|
585
|
+
updateUrlView(viewMode);
|
|
586
|
+
|
|
587
|
+
// Row count: skip if hyparquet already provided it
|
|
588
|
+
if (totalRows === null) {
|
|
589
|
+
if (!cloudNative && rows.length < pageSize) {
|
|
590
|
+
totalRows = rows.length;
|
|
591
|
+
} else {
|
|
592
|
+
loadStage = t('table.countingRows');
|
|
593
|
+
engine
|
|
594
|
+
.getRowCount(connId, fileUrl)
|
|
595
|
+
.then((count) => {
|
|
596
|
+
if (thisGen === loadGeneration) {
|
|
597
|
+
totalRows = count;
|
|
598
|
+
}
|
|
599
|
+
loadStage = '';
|
|
600
|
+
})
|
|
601
|
+
.catch(() => {
|
|
602
|
+
loadStage = '';
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
} catch (err) {
|
|
607
|
+
if (thisGen !== loadGeneration) return;
|
|
608
|
+
console.error('[TableViewer] Error:', err);
|
|
609
|
+
error = err instanceof Error ? err.message : String(err);
|
|
610
|
+
loading = false;
|
|
611
|
+
loadStage = '';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function executeQuery(sql: string) {
|
|
616
|
+
try {
|
|
617
|
+
const engine = await getQueryEngine();
|
|
618
|
+
|
|
619
|
+
if (engine.queryCancellable) {
|
|
620
|
+
const handle = engine.queryCancellable(connId, sql);
|
|
621
|
+
activeHandle = handle;
|
|
622
|
+
try {
|
|
623
|
+
const result = await handle.result;
|
|
624
|
+
columns = result.columns;
|
|
625
|
+
rows = result.rows;
|
|
626
|
+
return result;
|
|
627
|
+
} finally {
|
|
628
|
+
activeHandle = null;
|
|
629
|
+
if (forceCancelTimer) {
|
|
630
|
+
clearTimeout(forceCancelTimer);
|
|
631
|
+
forceCancelTimer = null;
|
|
632
|
+
}
|
|
633
|
+
forceCancelVisible = false;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const result = await engine.query(connId, sql);
|
|
638
|
+
columns = result.columns;
|
|
639
|
+
rows = result.rows;
|
|
640
|
+
return result;
|
|
641
|
+
} catch (err) {
|
|
642
|
+
if (err instanceof QueryCancelledError) {
|
|
643
|
+
loading = false;
|
|
644
|
+
queryRunning = false;
|
|
645
|
+
loadStage = '';
|
|
646
|
+
error = t('table.queryCancelled');
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
error = err instanceof Error ? err.message : String(err);
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function loadPage(page: number) {
|
|
655
|
+
const offset = (page - 1) * pageSize;
|
|
656
|
+
const sql = buildDefaultSql(offset);
|
|
657
|
+
sqlQuery = sql;
|
|
658
|
+
customSql = sql;
|
|
659
|
+
const start = performance.now();
|
|
660
|
+
await executeQuery(sql);
|
|
661
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
662
|
+
mapData = extractMapData(rows);
|
|
663
|
+
currentPage = page;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
async function runCustomSql() {
|
|
667
|
+
queryRunning = true;
|
|
668
|
+
error = null;
|
|
669
|
+
isCustomQuery = true;
|
|
670
|
+
loadStage = t('table.runningCustomQuery');
|
|
671
|
+
const start = performance.now();
|
|
672
|
+
try {
|
|
673
|
+
sqlQuery = customSql;
|
|
674
|
+
await executeQuery(customSql);
|
|
675
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
676
|
+
currentPage = 1;
|
|
677
|
+
totalRows = null;
|
|
678
|
+
updateUrlView('query');
|
|
679
|
+
|
|
680
|
+
// Pass custom query results to the map (respects user's LIMIT or lack thereof)
|
|
681
|
+
mapData = extractMapData(rows);
|
|
682
|
+
|
|
683
|
+
// Record in history
|
|
684
|
+
queryHistory.add({
|
|
685
|
+
sql: customSql,
|
|
686
|
+
timestamp: Date.now(),
|
|
687
|
+
durationMs: executionTimeMs,
|
|
688
|
+
rowCount: rows.length,
|
|
689
|
+
connectionId: connId || undefined
|
|
690
|
+
});
|
|
691
|
+
} catch (err) {
|
|
692
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
693
|
+
error = err instanceof Error ? err.message : String(err);
|
|
694
|
+
|
|
695
|
+
queryHistory.add({
|
|
696
|
+
sql: customSql,
|
|
697
|
+
timestamp: Date.now(),
|
|
698
|
+
durationMs: executionTimeMs,
|
|
699
|
+
rowCount: 0,
|
|
700
|
+
error: error ?? undefined,
|
|
701
|
+
connectionId: connId || undefined
|
|
702
|
+
});
|
|
703
|
+
} finally {
|
|
704
|
+
queryRunning = false;
|
|
705
|
+
loadStage = '';
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function handleSqlChange(val: string) {
|
|
710
|
+
customSql = val;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function handleFormatSql() {
|
|
714
|
+
try {
|
|
715
|
+
customSql = formatSql(customSql, { language: 'sql' });
|
|
716
|
+
} catch {
|
|
717
|
+
// ignore format errors
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function handleSort(column: string, direction: 'asc' | 'desc' | null) {
|
|
722
|
+
sortColumn = direction ? column : null;
|
|
723
|
+
sortDirection = direction;
|
|
724
|
+
// Rebuild the default sql and reload
|
|
725
|
+
const sql = buildDefaultSql(0);
|
|
726
|
+
sqlQuery = sql;
|
|
727
|
+
customSql = sql;
|
|
728
|
+
currentPage = 1;
|
|
729
|
+
const start = performance.now();
|
|
730
|
+
executeQuery(sql).then(() => {
|
|
731
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
732
|
+
mapData = extractMapData(rows);
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function handleHistorySelect(sql: string) {
|
|
737
|
+
customSql = sql;
|
|
738
|
+
runCustomSql();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function handlePageSizeChange(size: number) {
|
|
742
|
+
pageSize = size;
|
|
743
|
+
settings.setFeatureLimit(size);
|
|
744
|
+
currentPage = 1;
|
|
745
|
+
const sql = buildDefaultSql(0);
|
|
746
|
+
sqlQuery = sql;
|
|
747
|
+
customSql = sql;
|
|
748
|
+
const start = performance.now();
|
|
749
|
+
executeQuery(sql).then(() => {
|
|
750
|
+
executionTimeMs = Math.round(performance.now() - start);
|
|
751
|
+
mapData = extractMapData(rows);
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function prevPage() {
|
|
756
|
+
if (currentPage > 1) loadPage(currentPage - 1);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function nextPage() {
|
|
760
|
+
if (totalRows != null && currentPage < totalPages) loadPage(currentPage + 1);
|
|
761
|
+
else if (totalRows == null && rows.length === pageSize) loadPage(currentPage + 1);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function goToPage(page: number) {
|
|
765
|
+
if (page >= 1 && page <= totalPages) loadPage(page);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function toggleInfo() {
|
|
769
|
+
viewMode = viewMode === 'info' ? 'table' : 'info';
|
|
770
|
+
updateUrlView(viewMode);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function toggleHistory() {
|
|
774
|
+
historyVisible = !historyVisible;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
function toggleView() {
|
|
778
|
+
viewMode = viewMode === 'map' ? 'table' : 'map';
|
|
779
|
+
updateUrlView(viewMode);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function setStacView() {
|
|
783
|
+
viewMode = viewMode === 'stac' ? 'table' : 'stac';
|
|
784
|
+
updateUrlView(viewMode);
|
|
785
|
+
}
|
|
786
|
+
</script>
|
|
787
|
+
|
|
788
|
+
<div class="flex h-full flex-col">
|
|
789
|
+
<!-- Toolbar — always visible -->
|
|
790
|
+
<TableToolbar
|
|
791
|
+
{tab}
|
|
792
|
+
fileName={tab.name}
|
|
793
|
+
columnCount={displayColumns.length}
|
|
794
|
+
rowCount={totalRows ?? 0}
|
|
795
|
+
{currentPage}
|
|
796
|
+
{totalPages}
|
|
797
|
+
{pageSize}
|
|
798
|
+
{historyVisible}
|
|
799
|
+
{hasGeo}
|
|
800
|
+
{isStac}
|
|
801
|
+
{viewMode}
|
|
802
|
+
onPrevPage={prevPage}
|
|
803
|
+
onNextPage={nextPage}
|
|
804
|
+
onGoToPage={goToPage}
|
|
805
|
+
onToggleInfo={toggleInfo}
|
|
806
|
+
onToggleHistory={toggleHistory}
|
|
807
|
+
onToggleView={toggleView}
|
|
808
|
+
onToggleStac={setStacView}
|
|
809
|
+
onPageSizeChange={handlePageSizeChange}
|
|
810
|
+
/>
|
|
811
|
+
|
|
812
|
+
{#if viewMode === 'table'}
|
|
813
|
+
<!-- SQL Query Bar — hidden during schema/CRS detection, shown once query starts running -->
|
|
814
|
+
<div class="border-b border-zinc-200 px-2 py-1.5 sm:px-4 dark:border-zinc-800" class:hidden={loading && loadStage !== t('table.runningQuery')}>
|
|
815
|
+
<div class="flex items-start gap-1.5 sm:gap-2">
|
|
816
|
+
<div class="min-w-0 flex-1">
|
|
817
|
+
<CodeMirrorEditor
|
|
818
|
+
value={customSql}
|
|
819
|
+
onChange={handleSqlChange}
|
|
820
|
+
onExecute={runCustomSql}
|
|
821
|
+
placeholder={t('table.enterSql')}
|
|
822
|
+
schemaColumns={displayColumns}
|
|
823
|
+
/>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="flex shrink-0 flex-col gap-1">
|
|
826
|
+
<button
|
|
827
|
+
class="rounded bg-primary px-2 py-1 text-xs text-primary-foreground hover:bg-primary/90 disabled:opacity-50 sm:px-3"
|
|
828
|
+
onclick={runCustomSql}
|
|
829
|
+
disabled={queryRunning || loading}
|
|
830
|
+
>
|
|
831
|
+
{queryRunning ? t('table.running') : t('table.run')}
|
|
832
|
+
</button>
|
|
833
|
+
<button
|
|
834
|
+
class="rounded px-2 py-1 text-xs text-zinc-400 hover:bg-zinc-100 sm:px-3 dark:hover:bg-zinc-800"
|
|
835
|
+
onclick={handleFormatSql}
|
|
836
|
+
>
|
|
837
|
+
{t('table.format')}
|
|
838
|
+
</button>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
</div>
|
|
842
|
+
|
|
843
|
+
{#if error}
|
|
844
|
+
<div
|
|
845
|
+
class="border-b border-red-200 bg-red-50 px-4 py-2 dark:border-red-800 dark:bg-red-950"
|
|
846
|
+
>
|
|
847
|
+
<p class="text-xs text-red-600 dark:text-red-400">{error}</p>
|
|
848
|
+
{#if tab.source === 'remote'}
|
|
849
|
+
<p class="mt-1 text-[10px] text-zinc-400 break-all">{buildStorageUrl(tab)}</p>
|
|
850
|
+
{/if}
|
|
851
|
+
</div>
|
|
852
|
+
{/if}
|
|
853
|
+
|
|
854
|
+
<!-- Content area: loading / table + side panels -->
|
|
855
|
+
<div class="relative flex flex-1 overflow-hidden">
|
|
856
|
+
{#if loading || queryRunning}
|
|
857
|
+
<LoadProgress
|
|
858
|
+
stage={loadStage}
|
|
859
|
+
entries={loadProgress}
|
|
860
|
+
onCancel={cancelLoad}
|
|
861
|
+
{forceCancelVisible}
|
|
862
|
+
onForceCancel={forceCancel}
|
|
863
|
+
/>
|
|
864
|
+
{:else if error && rows.length === 0}
|
|
865
|
+
<div class="flex flex-1 items-center justify-center">
|
|
866
|
+
<div
|
|
867
|
+
class="max-w-lg rounded-lg border border-red-300 bg-red-50 px-6 py-4 text-center dark:border-red-800 dark:bg-red-950"
|
|
868
|
+
>
|
|
869
|
+
<p class="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
870
|
+
</div>
|
|
871
|
+
</div>
|
|
872
|
+
{:else}
|
|
873
|
+
<TableGrid
|
|
874
|
+
columns={displayColumns}
|
|
875
|
+
{rows}
|
|
876
|
+
totalRows={totalRows ?? rows.length}
|
|
877
|
+
{columnTypes}
|
|
878
|
+
{sortColumn}
|
|
879
|
+
{sortDirection}
|
|
880
|
+
onSort={handleSort}
|
|
881
|
+
/>
|
|
882
|
+
{/if}
|
|
883
|
+
<QueryHistoryPanel
|
|
884
|
+
visible={historyVisible}
|
|
885
|
+
onSelect={handleHistorySelect}
|
|
886
|
+
onClose={toggleHistory}
|
|
887
|
+
/>
|
|
888
|
+
</div>
|
|
889
|
+
|
|
890
|
+
<!-- Status bar — table mode only -->
|
|
891
|
+
<TableStatusBar
|
|
892
|
+
rowCount={rows.length}
|
|
893
|
+
{executionTimeMs}
|
|
894
|
+
loading={loading || queryRunning}
|
|
895
|
+
columns={displayColumns}
|
|
896
|
+
{rows}
|
|
897
|
+
fileName={tab.name}
|
|
898
|
+
/>
|
|
899
|
+
{:else if viewMode === 'info'}
|
|
900
|
+
<!-- Info mode — file metadata & schema -->
|
|
901
|
+
<div class="min-h-0 flex-1 overflow-auto">
|
|
902
|
+
<FileInfo
|
|
903
|
+
entries={loadProgress}
|
|
904
|
+
{schema}
|
|
905
|
+
parquetUrl={/\.parquet$/i.test(tab.path) ? buildHttpsUrl(tab) : ''}
|
|
906
|
+
/>
|
|
907
|
+
</div>
|
|
908
|
+
{:else if viewMode === 'stac'}
|
|
909
|
+
<!-- STAC Map mode — full size -->
|
|
910
|
+
<div class="flex-1 overflow-hidden">
|
|
911
|
+
{#await import('./StacMapViewer.svelte') then StacMapViewer}
|
|
912
|
+
<StacMapViewer.default {tab} />
|
|
913
|
+
{/await}
|
|
914
|
+
</div>
|
|
915
|
+
{:else if viewMode === 'map'}
|
|
916
|
+
<!-- Map mode — full size -->
|
|
917
|
+
<div class="flex-1 overflow-hidden">
|
|
918
|
+
{#await import('./GeoParquetMapViewer.svelte') then GeoParquetMapViewer}
|
|
919
|
+
<GeoParquetMapViewer.default {tab} {schema} {mapData} {sourceCrs} {knownGeomType} {metadataBounds} {isCustomQuery} progressEntries={loadProgress} />
|
|
920
|
+
{/await}
|
|
921
|
+
</div>
|
|
922
|
+
{/if}
|
|
923
|
+
</div>
|