@walkthru-earth/objex 1.3.1 → 1.5.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 +5 -0
- package/README.md +28 -20
- package/dist/components/browser/FileTreeSidebar.svelte +32 -17
- package/dist/components/layout/AboutSheet.svelte +5 -2
- package/dist/components/layout/ConnectionDialog.svelte +7 -2
- package/dist/components/layout/SettingsSheet.svelte +238 -0
- package/dist/components/layout/SettingsSheet.svelte.d.ts +6 -0
- package/dist/components/layout/Sidebar.svelte +73 -6
- package/dist/components/layout/Sidebar.svelte.d.ts +4 -1
- package/dist/components/layout/StatusBar.svelte +17 -14
- package/dist/components/layout/TabBar.svelte +4 -4
- package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/resizable/index.d.ts +1 -1
- package/dist/components/ui/resizable/index.js +2 -2
- package/dist/components/ui/slider/index.d.ts +3 -0
- package/dist/components/ui/slider/index.js +5 -0
- package/dist/components/ui/slider/range-slider.svelte +94 -0
- package/dist/components/ui/slider/range-slider.svelte.d.ts +21 -0
- package/dist/components/ui/slider/slider.svelte +83 -0
- package/dist/components/ui/slider/slider.svelte.d.ts +7 -0
- package/dist/components/viewers/ArchiveViewer.svelte +140 -113
- package/dist/components/viewers/CodeViewer.svelte +45 -48
- package/dist/components/viewers/CodeViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/CogControls.svelte +338 -184
- package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
- package/dist/components/viewers/CogViewer.svelte +269 -116
- package/dist/components/viewers/CopcViewer.svelte +8 -15
- package/dist/components/viewers/DatabaseViewer.svelte +22 -21
- package/dist/components/viewers/FileInfo.svelte +16 -16
- package/dist/components/viewers/FlatGeobufViewer.svelte +16 -46
- package/dist/components/viewers/GeoParquetMapViewer.svelte +11 -9
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/ImageViewer.svelte +12 -14
- package/dist/components/viewers/LoadProgress.svelte +6 -6
- package/dist/components/viewers/MarkdownViewer.svelte +29 -30
- package/dist/components/viewers/MediaViewer.svelte +13 -14
- package/dist/components/viewers/ModelViewer.svelte +18 -21
- package/dist/components/viewers/MultiCogViewer.svelte +474 -106
- package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/NotebookViewer.svelte +28 -29
- package/dist/components/viewers/PdfViewer.svelte +24 -33
- package/dist/components/viewers/PmtilesViewer.svelte +13 -15
- package/dist/components/viewers/QueryHistoryPanel.svelte +18 -18
- package/dist/components/viewers/RawViewer.svelte +27 -21
- package/dist/components/viewers/StacMapViewer.svelte +6 -13
- package/dist/components/viewers/StacMosaicViewer.svelte +1764 -410
- package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/StacTabViewer.svelte +26 -15
- package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/TableGrid.svelte +38 -34
- package/dist/components/viewers/TableStatusBar.svelte +7 -7
- package/dist/components/viewers/TableToolbar.svelte +10 -9
- package/dist/components/viewers/TableViewer.svelte +47 -30
- package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
- package/dist/components/viewers/ViewerHeader.svelte +18 -0
- package/dist/components/viewers/ViewerHeader.svelte.d.ts +10 -0
- package/dist/components/viewers/ViewerRouter.svelte +16 -8
- package/dist/components/viewers/ViewerStatus.svelte +19 -0
- package/dist/components/viewers/ViewerStatus.svelte.d.ts +7 -0
- package/dist/components/viewers/ZarrMapViewer.svelte +24 -21
- package/dist/components/viewers/ZarrViewer.svelte +98 -65
- package/dist/components/viewers/cog/ChannelPicker.svelte +83 -0
- package/dist/components/viewers/cog/ChannelPicker.svelte.d.ts +13 -0
- package/dist/components/viewers/cog/PixelInspectorPanel.svelte +87 -0
- package/dist/components/viewers/cog/PixelInspectorPanel.svelte.d.ts +17 -0
- package/dist/components/viewers/cog/buildRgbLayer.d.ts +78 -0
- package/dist/components/viewers/cog/buildRgbLayer.js +176 -0
- package/dist/components/viewers/map/AttributeTable.svelte +7 -7
- package/dist/components/viewers/map/MapContainer.svelte +38 -12
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +109 -83
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +16 -16
- package/dist/components/viewers/stac/StacDatetimeBar.svelte +175 -0
- package/dist/components/viewers/stac/StacDatetimeBar.svelte.d.ts +10 -0
- package/dist/components/viewers/stac/StacFilterPanel.svelte +243 -0
- package/dist/components/viewers/stac/StacFilterPanel.svelte.d.ts +14 -0
- package/dist/components/viewers/stac/StacItemInspector.svelte +223 -0
- package/dist/components/viewers/stac/StacItemInspector.svelte.d.ts +10 -0
- package/dist/components/viewers/stac/StacItemStrip.svelte +228 -0
- package/dist/components/viewers/stac/StacItemStrip.svelte.d.ts +12 -0
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +8 -0
- package/dist/file-icons/index.d.ts +1 -1
- package/dist/file-icons/index.js +1 -1
- package/dist/i18n/ar.js +113 -2
- package/dist/i18n/en.js +113 -2
- package/dist/index.d.ts +2 -28
- package/dist/index.js +7 -23
- package/dist/query/engine.d.ts +10 -0
- package/dist/query/source.js +1 -1
- package/dist/query/stac-source-factory.d.ts +65 -0
- package/dist/query/stac-source-factory.js +77 -0
- package/dist/query/stac-source-parquet.d.ts +135 -0
- package/dist/query/stac-source-parquet.js +468 -0
- package/dist/query/wasm.d.ts +8 -0
- package/dist/query/wasm.js +310 -65
- package/dist/storage/presign.js +3 -2
- package/dist/storage/providers.js +7 -6
- package/dist/stores/config.svelte.d.ts +15 -0
- package/dist/stores/config.svelte.js +46 -0
- package/dist/stores/connections.svelte.d.ts +2 -2
- package/dist/stores/connections.svelte.js +1 -2
- package/dist/stores/files.svelte.d.ts +1 -1
- package/dist/stores/files.svelte.js +1 -1
- package/dist/stores/query-history.svelte.js +1 -1
- package/dist/stores/settings.svelte.d.ts +16 -1
- package/dist/stores/settings.svelte.js +104 -48
- package/dist/stores/tabs.svelte.d.ts +3 -0
- package/dist/stores/tabs.svelte.js +17 -0
- package/dist/utils/cog-histogram.d.ts +121 -0
- package/dist/utils/cog-histogram.js +424 -0
- package/dist/utils/cog.d.ts +177 -20
- package/dist/utils/cog.js +361 -76
- package/dist/utils/colormap-sprite.d.ts +0 -9
- package/dist/utils/colormap-sprite.js +0 -21
- package/dist/utils/deck.d.ts +18 -12
- package/dist/utils/deck.js +15 -7
- package/dist/utils/media-query.svelte.d.ts +14 -0
- package/dist/utils/media-query.svelte.js +29 -0
- package/dist/utils/pmtiles-tile.js +2 -2
- package/dist/utils/signed-url-effect.d.ts +7 -0
- package/dist/utils/signed-url-effect.js +19 -0
- package/dist/utils/{url.d.ts → signed-url.d.ts} +15 -1
- package/dist/utils/{url.js → signed-url.js} +32 -10
- package/dist/utils/url-state.d.ts +36 -0
- package/dist/utils/url-state.js +72 -2
- package/dist/utils/zarr-tab.d.ts +1 -2
- package/dist/utils/zarr-tab.js +1 -2
- package/dist/utils/zarr.d.ts +0 -17
- package/dist/utils/zarr.js +1 -45
- package/package.json +55 -84
- package/dist/components/browser/Breadcrumb.svelte +0 -50
- package/dist/components/browser/Breadcrumb.svelte.d.ts +0 -7
- package/dist/components/browser/CreateFolderDialog.svelte +0 -98
- package/dist/components/browser/CreateFolderDialog.svelte.d.ts +0 -6
- package/dist/components/browser/DeleteConfirmDialog.svelte +0 -90
- package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +0 -8
- package/dist/components/browser/DropZone.svelte +0 -83
- package/dist/components/browser/DropZone.svelte.d.ts +0 -7
- package/dist/components/browser/FileBrowser.svelte +0 -252
- package/dist/components/browser/FileBrowser.svelte.d.ts +0 -3
- package/dist/components/browser/FileRow.svelte +0 -117
- package/dist/components/browser/FileRow.svelte.d.ts +0 -9
- package/dist/components/browser/RenameDialog.svelte +0 -101
- package/dist/components/browser/RenameDialog.svelte.d.ts +0 -8
- package/dist/components/browser/SearchBar.svelte +0 -40
- package/dist/components/browser/SearchBar.svelte.d.ts +0 -6
- package/dist/components/browser/UploadButton.svelte +0 -65
- package/dist/components/browser/UploadButton.svelte.d.ts +0 -3
- package/dist/query/stac-geoparquet.d.ts +0 -31
- package/dist/query/stac-geoparquet.js +0 -136
- package/dist/utils/clipboard.d.ts +0 -13
- package/dist/utils/clipboard.js +0 -38
- package/dist/utils/cloud-url.d.ts +0 -27
- package/dist/utils/cloud-url.js +0 -61
- package/dist/utils/cog-pure.d.ts +0 -25
- package/dist/utils/cog-pure.js +0 -35
- package/dist/utils/column-types.d.ts +0 -5
- package/dist/utils/column-types.js +0 -137
- package/dist/utils/connection-identity.d.ts +0 -51
- package/dist/utils/connection-identity.js +0 -97
- package/dist/utils/error.d.ts +0 -8
- package/dist/utils/error.js +0 -12
- package/dist/utils/evidence-context.d.ts +0 -22
- package/dist/utils/evidence-context.js +0 -56
- package/dist/utils/export.d.ts +0 -22
- package/dist/utils/export.js +0 -76
- package/dist/utils/file-sort.d.ts +0 -20
- package/dist/utils/file-sort.js +0 -41
- package/dist/utils/format.d.ts +0 -24
- package/dist/utils/format.js +0 -78
- package/dist/utils/geoarrow.d.ts +0 -32
- package/dist/utils/geoarrow.js +0 -672
- package/dist/utils/geometry-type.d.ts +0 -52
- package/dist/utils/geometry-type.js +0 -76
- package/dist/utils/hex.d.ts +0 -10
- package/dist/utils/hex.js +0 -27
- package/dist/utils/host-detection.d.ts +0 -23
- package/dist/utils/host-detection.js +0 -95
- package/dist/utils/local-storage.d.ts +0 -16
- package/dist/utils/local-storage.js +0 -37
- package/dist/utils/markdown-sql.d.ts +0 -30
- package/dist/utils/markdown-sql.js +0 -72
- package/dist/utils/notebook.d.ts +0 -59
- package/dist/utils/notebook.js +0 -211
- package/dist/utils/parquet-metadata.d.ts +0 -64
- package/dist/utils/parquet-metadata.js +0 -262
- package/dist/utils/stac-geoparquet.d.ts +0 -90
- package/dist/utils/stac-geoparquet.js +0 -223
- package/dist/utils/stac-hydrate.d.ts +0 -38
- package/dist/utils/stac-hydrate.js +0 -243
- package/dist/utils/stac.d.ts +0 -136
- package/dist/utils/stac.js +0 -176
- package/dist/utils/storage-url.d.ts +0 -90
- package/dist/utils/storage-url.js +0 -568
- package/dist/utils/wkb.d.ts +0 -43
- package/dist/utils/wkb.js +0 -359
|
@@ -3,12 +3,12 @@ import CodeIcon from '@lucide/svelte/icons/file-code';
|
|
|
3
3
|
import GlobeIcon from '@lucide/svelte/icons/globe';
|
|
4
4
|
import LayersIcon from '@lucide/svelte/icons/layers';
|
|
5
5
|
import MapIcon from '@lucide/svelte/icons/map';
|
|
6
|
+
import type { StacRoutableKind } from '@walkthru-earth/objex-utils';
|
|
6
7
|
import { t } from '../../i18n/index.svelte.js';
|
|
7
8
|
import { connectionStore } from '../../stores/connections.svelte.js';
|
|
8
9
|
import type { Tab } from '../../types.js';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import { getUrlView, updateUrlView } from '../../utils/url-state.js';
|
|
10
|
+
import { canStreamDirectly } from '../../utils/signed-url.js';
|
|
11
|
+
import { getUrlView, pickViewMode, updateUrlView } from '../../utils/url-state.js';
|
|
12
12
|
import { Badge } from '../ui/badge/index.js';
|
|
13
13
|
import { Button } from '../ui/button/index.js';
|
|
14
14
|
import * as Tooltip from '../ui/tooltip/index.js';
|
|
@@ -28,7 +28,11 @@ interface Props {
|
|
|
28
28
|
|
|
29
29
|
let { tab, mapKind, classified }: Props = $props();
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// 'code' is the URL token for raw-content view on JSON tabs; 'table' is the
|
|
32
|
+
// equivalent token on stac-geoparquet tabs. Both render the nested viewer
|
|
33
|
+
// (CodeViewer for JSON, TableViewer for parquet) but the URL stays
|
|
34
|
+
// semantically meaningful per filetype.
|
|
35
|
+
type ViewMode = 'map' | 'stac-map' | 'stac-browser' | 'code' | 'table';
|
|
32
36
|
|
|
33
37
|
interface CodeActions {
|
|
34
38
|
toggleFormat: () => Promise<void>;
|
|
@@ -70,13 +74,16 @@ const stacBadgeKey = $derived.by(() => {
|
|
|
70
74
|
});
|
|
71
75
|
|
|
72
76
|
function initialView(): ViewMode {
|
|
77
|
+
// `'map'` is conditional on `mapKind`, so it's not in the static vocabulary.
|
|
78
|
+
// Both `'code'` and `'table'` are accepted regardless of filetype so a URL
|
|
79
|
+
// shared from one type still resolves; the render branch dispatches to the
|
|
80
|
+
// appropriate inner viewer (`TableViewer` / `CodeViewer`).
|
|
81
|
+
const picked = pickViewMode<ViewMode>(['stac-map', 'stac-browser', 'code', 'table'], 'stac-map');
|
|
73
82
|
const urlView = getUrlView();
|
|
74
83
|
if (urlView === 'map' && mapKind) return 'map';
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
if (mapKind) return 'map';
|
|
79
|
-
return 'stac-map';
|
|
84
|
+
// Hash was unknown or empty: prefer the rich map view when available.
|
|
85
|
+
if (mapKind && !urlView) return 'map';
|
|
86
|
+
return picked;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
let viewMode = $state<ViewMode>(initialView());
|
|
@@ -86,18 +93,22 @@ let codeActions = $state<CodeActions | null>(null);
|
|
|
86
93
|
function setView(next: ViewMode) {
|
|
87
94
|
if (viewMode === next) return;
|
|
88
95
|
viewMode = next;
|
|
89
|
-
updateUrlView(next
|
|
96
|
+
updateUrlView(next);
|
|
90
97
|
}
|
|
98
|
+
|
|
99
|
+
// The "Table" / "JSON" button in the navbar writes a filetype-aware token so
|
|
100
|
+
// the URL stays semantically meaningful (parquet → `#table`, json → `#code`).
|
|
101
|
+
const rawContentMode: ViewMode = $derived(isParquet ? 'table' : 'code');
|
|
91
102
|
</script>
|
|
92
103
|
|
|
93
104
|
<Tooltip.Provider>
|
|
94
105
|
<div class="flex h-full flex-col overflow-hidden">
|
|
95
106
|
{#key tab.id}
|
|
96
107
|
<div
|
|
97
|
-
class="flex items-center gap-1 border-b border-
|
|
108
|
+
class="flex items-center gap-1 border-b border-border px-2 py-1.5 sm:gap-2 sm:px-4"
|
|
98
109
|
>
|
|
99
110
|
<span
|
|
100
|
-
class="max-w-[120px] truncate text-sm font-medium text-
|
|
111
|
+
class="max-w-[120px] truncate text-sm font-medium text-foreground sm:max-w-none"
|
|
101
112
|
>
|
|
102
113
|
{tab.name}
|
|
103
114
|
</span>
|
|
@@ -191,9 +202,9 @@ function setView(next: ViewMode) {
|
|
|
191
202
|
{/if}
|
|
192
203
|
<Button
|
|
193
204
|
size="sm"
|
|
194
|
-
variant={viewMode ===
|
|
205
|
+
variant={viewMode === rawContentMode ? 'default' : 'ghost'}
|
|
195
206
|
class="h-7 gap-1 px-2"
|
|
196
|
-
onclick={() => setView(
|
|
207
|
+
onclick={() => setView(rawContentMode)}
|
|
197
208
|
>
|
|
198
209
|
<CodeIcon class="size-3.5" />
|
|
199
210
|
{isParquet ? t('stac.viewTable') : t('stac.viewJson')}
|
|
@@ -244,7 +255,7 @@ function setView(next: ViewMode) {
|
|
|
244
255
|
{:else if viewMode === 'stac-browser'}
|
|
245
256
|
<StacMapViewer {tab} variant="stac-browser" />
|
|
246
257
|
{:else if isParquet}
|
|
247
|
-
<TableViewer {tab} />
|
|
258
|
+
<TableViewer {tab} nested />
|
|
248
259
|
{:else}
|
|
249
260
|
<CodeViewer {tab} nested bind:wordWrap bind:actions={codeActions} />
|
|
250
261
|
{/if}
|
|
@@ -7,15 +7,15 @@ import ColumnsIcon from '@lucide/svelte/icons/columns-3';
|
|
|
7
7
|
import CopyIcon from '@lucide/svelte/icons/copy';
|
|
8
8
|
import RowsIcon from '@lucide/svelte/icons/rows-3';
|
|
9
9
|
import XIcon from '@lucide/svelte/icons/x';
|
|
10
|
-
import { onDestroy } from 'svelte';
|
|
11
|
-
import { t } from '../../i18n/index.svelte.js';
|
|
12
10
|
import {
|
|
13
11
|
classifyType,
|
|
12
|
+
jsonReplacerBigInt,
|
|
14
13
|
type TypeCategory,
|
|
15
14
|
typeBadgeClass,
|
|
16
15
|
typeLabel
|
|
17
|
-
} from '
|
|
18
|
-
import {
|
|
16
|
+
} from '@walkthru-earth/objex-utils';
|
|
17
|
+
import { onDestroy } from 'svelte';
|
|
18
|
+
import { t } from '../../i18n/index.svelte.js';
|
|
19
19
|
|
|
20
20
|
const INITIAL_ROWS = 100;
|
|
21
21
|
const BATCH_SIZE = 100;
|
|
@@ -77,25 +77,27 @@ const tableWidth = $derived(
|
|
|
77
77
|
ROW_NUM_WIDTH + columns.reduce((sum, col) => sum + (columnWidths[col] || DEFAULT_WIDTH), 0)
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
function startResize(col: string, e:
|
|
80
|
+
function startResize(col: string, e: PointerEvent) {
|
|
81
81
|
e.preventDefault();
|
|
82
82
|
e.stopPropagation();
|
|
83
|
+
(e.target as HTMLElement).setPointerCapture?.(e.pointerId);
|
|
83
84
|
const startX = e.clientX;
|
|
84
85
|
const startW = columnWidths[col] || DEFAULT_WIDTH;
|
|
85
86
|
|
|
86
|
-
function onMove(ev:
|
|
87
|
+
function onMove(ev: PointerEvent) {
|
|
87
88
|
columnWidths[col] = Math.max(MIN_WIDTH, startW + (ev.clientX - startX));
|
|
88
89
|
}
|
|
89
|
-
function onUp() {
|
|
90
|
-
|
|
91
|
-
document.removeEventListener('
|
|
90
|
+
function onUp(ev: PointerEvent) {
|
|
91
|
+
(ev.target as HTMLElement).releasePointerCapture?.(ev.pointerId);
|
|
92
|
+
document.removeEventListener('pointermove', onMove);
|
|
93
|
+
document.removeEventListener('pointerup', onUp);
|
|
92
94
|
resizeCleanup = null;
|
|
93
95
|
}
|
|
94
|
-
document.addEventListener('
|
|
95
|
-
document.addEventListener('
|
|
96
|
+
document.addEventListener('pointermove', onMove);
|
|
97
|
+
document.addEventListener('pointerup', onUp);
|
|
96
98
|
resizeCleanup = () => {
|
|
97
|
-
document.removeEventListener('
|
|
98
|
-
document.removeEventListener('
|
|
99
|
+
document.removeEventListener('pointermove', onMove);
|
|
100
|
+
document.removeEventListener('pointerup', onUp);
|
|
99
101
|
};
|
|
100
102
|
}
|
|
101
103
|
|
|
@@ -221,16 +223,16 @@ function isNull(value: any): boolean {
|
|
|
221
223
|
</colgroup>
|
|
222
224
|
|
|
223
225
|
<thead class="sticky top-0 z-10">
|
|
224
|
-
<tr class="bg-
|
|
226
|
+
<tr class="bg-muted">
|
|
225
227
|
<th
|
|
226
|
-
class="border-b border-e border-
|
|
228
|
+
class="border-b border-e border-border px-2 py-2 text-start text-xs font-medium text-muted-foreground"
|
|
227
229
|
>
|
|
228
230
|
#
|
|
229
231
|
</th>
|
|
230
232
|
{#each columns as col}
|
|
231
233
|
{@const category = columnCategories[col]}
|
|
232
234
|
<th
|
|
233
|
-
class="group relative select-none border-b border-e border-
|
|
235
|
+
class="group relative select-none border-b border-e border-border px-3 py-1.5"
|
|
234
236
|
class:text-start={category !== 'number'}
|
|
235
237
|
class:text-end={category === 'number'}
|
|
236
238
|
class:cursor-pointer={!!onSort}
|
|
@@ -243,7 +245,7 @@ function isNull(value: any): boolean {
|
|
|
243
245
|
>
|
|
244
246
|
{typeLabel(category)}
|
|
245
247
|
</span>
|
|
246
|
-
<span class="truncate text-xs font-semibold text-
|
|
248
|
+
<span class="truncate text-xs font-semibold text-foreground">{col}</span>
|
|
247
249
|
{#if sortColumn === col}
|
|
248
250
|
{#if sortDirection === 'asc'}
|
|
249
251
|
<ArrowUpIcon class="size-3 shrink-0 text-blue-500" />
|
|
@@ -253,18 +255,20 @@ function isNull(value: any): boolean {
|
|
|
253
255
|
{/if}
|
|
254
256
|
</div>
|
|
255
257
|
{#if columnTypes[col]}
|
|
256
|
-
<div class="mt-0.5 truncate text-[10px] font-normal text-
|
|
258
|
+
<div class="mt-0.5 truncate text-[10px] font-normal text-muted-foreground">
|
|
257
259
|
{columnTypes[col]}
|
|
258
260
|
</div>
|
|
259
261
|
{/if}
|
|
260
262
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
261
263
|
<div
|
|
262
|
-
class="absolute end-0 top-0 h-full w-
|
|
263
|
-
|
|
264
|
+
class="absolute end-0 top-0 h-full w-4 cursor-col-resize touch-none px-1.5"
|
|
265
|
+
onpointerdown={(e) => startResize(col, e)}
|
|
264
266
|
ondblclick={(e) => { e.stopPropagation(); resetWidth(col); }}
|
|
265
267
|
role="separator"
|
|
266
268
|
aria-orientation="vertical"
|
|
267
|
-
|
|
269
|
+
>
|
|
270
|
+
<div class="h-full w-full bg-transparent transition-colors hover:bg-blue-400/60"></div>
|
|
271
|
+
</div>
|
|
268
272
|
</th>
|
|
269
273
|
{/each}
|
|
270
274
|
</tr>
|
|
@@ -274,7 +278,7 @@ function isNull(value: any): boolean {
|
|
|
274
278
|
{#each displayRows as row, i (i)}
|
|
275
279
|
<tr class="hover:bg-blue-50/50 dark:hover:bg-zinc-800/40">
|
|
276
280
|
<td
|
|
277
|
-
class="border-b border-e border-
|
|
281
|
+
class="border-b border-e border-border px-2 py-1 text-xs tabular-nums text-muted-foreground"
|
|
278
282
|
>
|
|
279
283
|
{i + 1}
|
|
280
284
|
</td>
|
|
@@ -283,14 +287,14 @@ function isNull(value: any): boolean {
|
|
|
283
287
|
{@const cellValue = row[col]}
|
|
284
288
|
{@const cellIsNull = isNull(cellValue)}
|
|
285
289
|
<td
|
|
286
|
-
class="overflow-hidden text-ellipsis whitespace-nowrap border-b border-e border-
|
|
290
|
+
class="overflow-hidden text-ellipsis whitespace-nowrap border-b border-e border-border px-3 py-1 text-[13px]"
|
|
287
291
|
class:text-end={category === 'number' && !cellIsNull}
|
|
288
292
|
class:font-mono={category === 'number' && !cellIsNull}
|
|
289
293
|
title={cellIsNull ? 'NULL' : formatCell(cellValue, category)}
|
|
290
294
|
oncontextmenu={(e) => handleContextMenu(e, cellValue, row, col)}
|
|
291
295
|
>
|
|
292
296
|
{#if cellIsNull}
|
|
293
|
-
<span class="text-[11px] italic text-
|
|
297
|
+
<span class="text-[11px] italic text-muted-foreground">null</span>
|
|
294
298
|
{:else if typeof cellValue === 'boolean'}
|
|
295
299
|
{#if cellValue}
|
|
296
300
|
<CheckIcon class="inline size-3.5 text-green-500" />
|
|
@@ -298,7 +302,7 @@ function isNull(value: any): boolean {
|
|
|
298
302
|
<XIcon class="inline size-3.5 text-red-400" />
|
|
299
303
|
{/if}
|
|
300
304
|
{:else}
|
|
301
|
-
<span class="text-
|
|
305
|
+
<span class="text-foreground">
|
|
302
306
|
{formatCell(cellValue, category)}
|
|
303
307
|
</span>
|
|
304
308
|
{/if}
|
|
@@ -310,7 +314,7 @@ function isNull(value: any): boolean {
|
|
|
310
314
|
</table>
|
|
311
315
|
|
|
312
316
|
{#if renderedCount < rows.length}
|
|
313
|
-
<div class="py-2 text-center text-xs text-
|
|
317
|
+
<div class="py-2 text-center text-xs text-muted-foreground">
|
|
314
318
|
{t('statusBar.rowsLabel')}: {renderedCount.toLocaleString()} / {rows.length.toLocaleString()} — scroll for more
|
|
315
319
|
</div>
|
|
316
320
|
{/if}
|
|
@@ -319,36 +323,36 @@ function isNull(value: any): boolean {
|
|
|
319
323
|
<!-- Context menu -->
|
|
320
324
|
{#if ctxMenu}
|
|
321
325
|
<div
|
|
322
|
-
class="fixed z-50 min-w-40 rounded-lg border border-
|
|
326
|
+
class="fixed z-50 min-w-40 rounded-lg border border-border bg-background py-1 shadow-xl"
|
|
323
327
|
style="left: {ctxMenu.x}px; top: {ctxMenu.y}px;"
|
|
324
328
|
role="menu"
|
|
325
329
|
>
|
|
326
330
|
<button
|
|
327
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-
|
|
331
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-foreground hover:bg-muted"
|
|
328
332
|
onclick={copyCell}
|
|
329
333
|
role="menuitem"
|
|
330
334
|
>
|
|
331
|
-
<ClipboardIcon class="size-3.5 text-
|
|
335
|
+
<ClipboardIcon class="size-3.5 text-muted-foreground" />
|
|
332
336
|
{t('table.copyCell')}
|
|
333
337
|
</button>
|
|
334
338
|
<button
|
|
335
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-
|
|
339
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-foreground hover:bg-muted"
|
|
336
340
|
onclick={copyRow}
|
|
337
341
|
role="menuitem"
|
|
338
342
|
>
|
|
339
|
-
<RowsIcon class="size-3.5 text-
|
|
343
|
+
<RowsIcon class="size-3.5 text-muted-foreground" />
|
|
340
344
|
{t('table.copyRow')}
|
|
341
345
|
</button>
|
|
342
346
|
<button
|
|
343
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-
|
|
347
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-start text-xs text-foreground hover:bg-muted"
|
|
344
348
|
onclick={copyColumn}
|
|
345
349
|
role="menuitem"
|
|
346
350
|
>
|
|
347
|
-
<ColumnsIcon class="size-3.5 text-
|
|
351
|
+
<ColumnsIcon class="size-3.5 text-muted-foreground" />
|
|
348
352
|
{t('table.copyColumn')}
|
|
349
353
|
</button>
|
|
350
354
|
{#if copied}
|
|
351
|
-
<div class="border-t border-
|
|
355
|
+
<div class="border-t border-border px-3 py-1 text-center text-[10px] text-green-500">
|
|
352
356
|
Copied!
|
|
353
357
|
</div>
|
|
354
358
|
{/if}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
|
3
3
|
import DownloadIcon from '@lucide/svelte/icons/download';
|
|
4
|
+
import { exportToCsv, exportToJson } from '@walkthru-earth/objex-utils';
|
|
4
5
|
import { t } from '../../i18n/index.svelte.js';
|
|
5
|
-
import { exportToCsv, exportToJson } from '../../utils/export.js';
|
|
6
6
|
|
|
7
7
|
let {
|
|
8
8
|
rowCount = 0,
|
|
@@ -39,7 +39,7 @@ function handleClickOutside(e: MouseEvent) {
|
|
|
39
39
|
|
|
40
40
|
<svelte:window onclick={() => { if (exportOpen) exportOpen = false; }} />
|
|
41
41
|
|
|
42
|
-
<div class="flex h-7 items-center justify-between border-t border-
|
|
42
|
+
<div class="flex h-7 items-center justify-between border-t border-border bg-muted px-3 text-xs text-muted-foreground">
|
|
43
43
|
<!-- Left side -->
|
|
44
44
|
<div>
|
|
45
45
|
{#if loading}
|
|
@@ -47,7 +47,7 @@ function handleClickOutside(e: MouseEvent) {
|
|
|
47
47
|
{:else if rowCount > 0}
|
|
48
48
|
<span>{rowCount.toLocaleString()} {t('statusBar.rowsLabel')}</span>
|
|
49
49
|
{#if executionTimeMs > 0}
|
|
50
|
-
<span class="text-
|
|
50
|
+
<span class="text-muted-foreground"> {t('statusBar.inTime', { time: executionTimeMs })}</span>
|
|
51
51
|
{/if}
|
|
52
52
|
{:else}
|
|
53
53
|
<span>{t('statusBar.noResults')}</span>
|
|
@@ -57,7 +57,7 @@ function handleClickOutside(e: MouseEvent) {
|
|
|
57
57
|
<!-- Right side: export dropdown -->
|
|
58
58
|
<div class="relative">
|
|
59
59
|
<button
|
|
60
|
-
class="flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-
|
|
60
|
+
class="flex items-center gap-1 rounded px-1.5 py-0.5 hover:bg-accent"
|
|
61
61
|
onclick={(e) => { e.stopPropagation(); exportOpen = !exportOpen; }}
|
|
62
62
|
disabled={rows.length === 0}
|
|
63
63
|
class:opacity-40={rows.length === 0}
|
|
@@ -69,18 +69,18 @@ function handleClickOutside(e: MouseEvent) {
|
|
|
69
69
|
|
|
70
70
|
{#if exportOpen}
|
|
71
71
|
<div
|
|
72
|
-
class="absolute bottom-full end-0 mb-1 w-32 rounded border border-
|
|
72
|
+
class="absolute bottom-full end-0 mb-1 w-32 rounded border border-border bg-background py-1 shadow-lg"
|
|
73
73
|
role="menu"
|
|
74
74
|
>
|
|
75
75
|
<button
|
|
76
|
-
class="w-full px-3 py-1.5 text-start text-xs hover:bg-
|
|
76
|
+
class="w-full px-3 py-1.5 text-start text-xs hover:bg-muted"
|
|
77
77
|
onclick={(e) => { e.stopPropagation(); handleExportCsv(); }}
|
|
78
78
|
role="menuitem"
|
|
79
79
|
>
|
|
80
80
|
{t('statusBar.exportCsv')}
|
|
81
81
|
</button>
|
|
82
82
|
<button
|
|
83
|
-
class="w-full px-3 py-1.5 text-start text-xs hover:bg-
|
|
83
|
+
class="w-full px-3 py-1.5 text-start text-xs hover:bg-muted"
|
|
84
84
|
onclick={(e) => { e.stopPropagation(); handleExportJson(); }}
|
|
85
85
|
role="menuitem"
|
|
86
86
|
>
|
|
@@ -10,13 +10,14 @@ import InfoIcon from '@lucide/svelte/icons/info';
|
|
|
10
10
|
import LinkIcon from '@lucide/svelte/icons/link';
|
|
11
11
|
import MapIcon from '@lucide/svelte/icons/map';
|
|
12
12
|
import TableIcon from '@lucide/svelte/icons/table';
|
|
13
|
+
import { COPY_FEEDBACK_MS } from '@walkthru-earth/objex-utils';
|
|
13
14
|
import { Button } from '../ui/button/index.js';
|
|
14
15
|
import * as DropdownMenu from '../ui/dropdown-menu/index.js';
|
|
15
16
|
import { Separator } from '../ui/separator/index.js';
|
|
16
17
|
import { t } from '../../i18n/index.svelte.js';
|
|
17
18
|
import { connections } from '../../stores/connections.svelte.js';
|
|
18
19
|
import type { Tab } from '../../types';
|
|
19
|
-
import { buildHttpsUrl, buildStorageUrl } from '../../utils/url.js';
|
|
20
|
+
import { buildHttpsUrl, buildStorageUrl } from '../../utils/signed-url.js';
|
|
20
21
|
|
|
21
22
|
const PROVIDER_LABELS: Record<string, string> = {
|
|
22
23
|
s3: 'S3',
|
|
@@ -89,7 +90,7 @@ async function handleCopy(type: 'https' | 'provider') {
|
|
|
89
90
|
try {
|
|
90
91
|
await navigator.clipboard.writeText(url);
|
|
91
92
|
copiedType = type;
|
|
92
|
-
setTimeout(() => (copiedType = null),
|
|
93
|
+
setTimeout(() => (copiedType = null), COPY_FEEDBACK_MS);
|
|
93
94
|
} catch {
|
|
94
95
|
// clipboard API may fail in some contexts
|
|
95
96
|
}
|
|
@@ -117,13 +118,13 @@ function handleJumpKeydown(e: KeyboardEvent) {
|
|
|
117
118
|
</script>
|
|
118
119
|
|
|
119
120
|
<div
|
|
120
|
-
class="flex items-center gap-1 border-b border-
|
|
121
|
+
class="flex items-center gap-1 border-b border-border px-2 py-1.5 sm:gap-2 sm:px-4"
|
|
121
122
|
>
|
|
122
123
|
<!-- File name — truncated on mobile -->
|
|
123
|
-
<span class="truncate max-w-[120px] text-sm font-medium text-
|
|
124
|
+
<span class="truncate max-w-[120px] text-sm font-medium text-foreground sm:max-w-none">{fileName}</span>
|
|
124
125
|
|
|
125
126
|
<!-- Row/col count — hidden on mobile -->
|
|
126
|
-
<span class="hidden text-xs text-
|
|
127
|
+
<span class="hidden text-xs text-muted-foreground sm:inline">
|
|
127
128
|
{#if rowCount > 0}
|
|
128
129
|
{rowCount.toLocaleString()} {t('toolbar.rows')} × {columnCount} cols
|
|
129
130
|
{:else if columnCount > 0}
|
|
@@ -137,7 +138,7 @@ function handleJumpKeydown(e: KeyboardEvent) {
|
|
|
137
138
|
<Button
|
|
138
139
|
variant={viewMode === 'info' ? 'default' : 'outline'}
|
|
139
140
|
size="sm"
|
|
140
|
-
class="h-7 gap-1 px-2 text-xs {viewMode !== 'info' ? 'border-
|
|
141
|
+
class="h-7 gap-1 px-2 text-xs {viewMode !== 'info' ? 'border-border text-muted-foreground hover:bg-muted hover:text-foreground' : ''}"
|
|
141
142
|
onclick={onToggleInfo}
|
|
142
143
|
>
|
|
143
144
|
<InfoIcon class="size-3" />
|
|
@@ -227,7 +228,7 @@ function handleJumpKeydown(e: KeyboardEvent) {
|
|
|
227
228
|
{#if onPageSizeChange && viewMode === 'table'}
|
|
228
229
|
<Separator orientation="vertical" class="!h-4" />
|
|
229
230
|
<select
|
|
230
|
-
class="rounded border border-
|
|
231
|
+
class="rounded border border-border bg-transparent px-1.5 py-0.5 text-xs text-muted-foreground outline-none"
|
|
231
232
|
value={pageSize}
|
|
232
233
|
onchange={(e) => onPageSizeChange?.(parseInt(e.currentTarget.value, 10))}
|
|
233
234
|
>
|
|
@@ -271,7 +272,7 @@ function handleJumpKeydown(e: KeyboardEvent) {
|
|
|
271
272
|
<!-- svelte-ignore a11y_autofocus -->
|
|
272
273
|
<input
|
|
273
274
|
type="number"
|
|
274
|
-
class="h-7 w-14 border border-
|
|
275
|
+
class="h-7 w-14 border border-border bg-transparent px-1 text-center text-xs outline-none"
|
|
275
276
|
bind:value={jumpPageValue}
|
|
276
277
|
onkeydown={handleJumpKeydown}
|
|
277
278
|
onblur={handleJumpSubmit}
|
|
@@ -320,7 +321,7 @@ function handleJumpKeydown(e: KeyboardEvent) {
|
|
|
320
321
|
<div class="flex sm:hidden">
|
|
321
322
|
<DropdownMenu.Root>
|
|
322
323
|
<DropdownMenu.Trigger
|
|
323
|
-
class="rounded p-1 text-
|
|
324
|
+
class="rounded p-1 text-muted-foreground hover:bg-muted"
|
|
324
325
|
>
|
|
325
326
|
<EllipsisVerticalIcon class="size-4" />
|
|
326
327
|
</DropdownMenu.Trigger>
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { GeoArrowGeomType } from '@walkthru-earth/objex-utils';
|
|
3
|
+
import {
|
|
4
|
+
buildTransformExpr,
|
|
5
|
+
extractBounds,
|
|
6
|
+
extractEpsgFromGeoMeta,
|
|
7
|
+
extractGeometryTypes,
|
|
8
|
+
findGeoColumn,
|
|
9
|
+
findGeoColumnFromRows,
|
|
10
|
+
handleLoadError,
|
|
11
|
+
isWgs84,
|
|
12
|
+
parseWKB,
|
|
13
|
+
readParquetMetadata,
|
|
14
|
+
toBinary,
|
|
15
|
+
wrapWkbWithCrs
|
|
16
|
+
} from '@walkthru-earth/objex-utils';
|
|
2
17
|
import { format as formatSql } from 'sql-formatter';
|
|
3
18
|
import { untrack } from 'svelte';
|
|
4
19
|
import CodeMirrorEditor from '../editor/CodeMirrorEditor.svelte';
|
|
@@ -18,22 +33,13 @@ import { queryHistory } from '../../stores/query-history.svelte.js';
|
|
|
18
33
|
import { settings } from '../../stores/settings.svelte.js';
|
|
19
34
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
20
35
|
import type { Tab } from '../../types';
|
|
21
|
-
import type { GeoArrowGeomType } from '../../utils/geoarrow.js';
|
|
22
|
-
import { buildTransformExpr, wrapWkbWithCrs } from '../../utils/geometry-type.js';
|
|
23
|
-
import {
|
|
24
|
-
extractBounds,
|
|
25
|
-
extractEpsgFromGeoMeta,
|
|
26
|
-
extractGeometryTypes,
|
|
27
|
-
readParquetMetadata
|
|
28
|
-
} from '../../utils/parquet-metadata.js';
|
|
29
36
|
import {
|
|
30
37
|
buildDuckDbUrl,
|
|
31
38
|
buildHttpsUrl,
|
|
32
39
|
buildStorageUrl,
|
|
33
40
|
canStreamDirectly
|
|
34
|
-
} from '../../utils/url.js';
|
|
35
|
-
import {
|
|
36
|
-
import { findGeoColumn, findGeoColumnFromRows, parseWKB, toBinary } from '../../utils/wkb.js';
|
|
41
|
+
} from '../../utils/signed-url.js';
|
|
42
|
+
import { pickViewMode, updateUrlView } from '../../utils/url-state.js';
|
|
37
43
|
import FileInfo from './FileInfo.svelte';
|
|
38
44
|
import LoadProgress, { type ProgressEntry } from './LoadProgress.svelte';
|
|
39
45
|
import QueryHistoryPanel from './QueryHistoryPanel.svelte';
|
|
@@ -41,7 +47,11 @@ import TableGrid from './TableGrid.svelte';
|
|
|
41
47
|
import TableStatusBar from './TableStatusBar.svelte';
|
|
42
48
|
import TableToolbar from './TableToolbar.svelte';
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
// `nested = true` when mounted inside `StacTabViewer` for a stac-geoparquet
|
|
51
|
+
// tab. The outer wrapper already exposes `stac-map` / `STAC Browser` buttons,
|
|
52
|
+
// so the inner `TableToolbar` hides its own STAC Map toggle to avoid
|
|
53
|
+
// duplicating the same `StacMapViewer` mount.
|
|
54
|
+
let { tab, nested = false }: { tab: Tab; nested?: boolean } = $props();
|
|
45
55
|
|
|
46
56
|
let pageSize = $state(settings.featureLimit);
|
|
47
57
|
|
|
@@ -56,9 +66,9 @@ let historyVisible = $state(false);
|
|
|
56
66
|
let hasGeo = $state(false);
|
|
57
67
|
let isStac = $state(false);
|
|
58
68
|
// Restore view mode from URL hash if present
|
|
59
|
-
|
|
60
|
-
let viewMode = $state<
|
|
61
|
-
|
|
69
|
+
type TableViewMode = 'table' | 'map' | 'stac' | 'info';
|
|
70
|
+
let viewMode = $state<TableViewMode>(
|
|
71
|
+
pickViewMode<TableViewMode>(['table', 'map', 'stac', 'info'], 'table')
|
|
62
72
|
);
|
|
63
73
|
let sqlQuery = $state('');
|
|
64
74
|
let customSql = $state('');
|
|
@@ -158,6 +168,9 @@ function buildDefaultSql(
|
|
|
158
168
|
}
|
|
159
169
|
|
|
160
170
|
function extractMapData(queryRows: Record<string, any>[]): MapQueryResult | null {
|
|
171
|
+
// The map attribute table is only consumed by the map view. Skip the
|
|
172
|
+
// O(rows x cols) walk entirely when the table/info/stac view is showing.
|
|
173
|
+
if (viewMode !== 'map') return null;
|
|
161
174
|
if (!geoCol || queryRows.length === 0 || !columns.includes('__wkb')) return null;
|
|
162
175
|
|
|
163
176
|
const wkbArrays: Uint8Array[] = [];
|
|
@@ -237,6 +250,14 @@ $effect(() => {
|
|
|
237
250
|
});
|
|
238
251
|
});
|
|
239
252
|
|
|
253
|
+
// When the user switches into map view and mapData is null (because extraction was
|
|
254
|
+
// skipped by the viewMode gate while in table/info/stac view), compute it now.
|
|
255
|
+
$effect(() => {
|
|
256
|
+
if (viewMode === 'map' && mapData === null && rows.length > 0) {
|
|
257
|
+
mapData = extractMapData(rows);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
240
261
|
function cancelLoad() {
|
|
241
262
|
loadGeneration++;
|
|
242
263
|
loadStage = t('table.cancellingQuery');
|
|
@@ -494,11 +515,7 @@ async function loadTable() {
|
|
|
494
515
|
const crsMatch = duckGeoField.type.match(/^GEOMETRY\('([^']+)'\)/i);
|
|
495
516
|
if (crsMatch) {
|
|
496
517
|
const crsVal = crsMatch[1];
|
|
497
|
-
|
|
498
|
-
crsVal === 'EPSG:4326' ||
|
|
499
|
-
crsVal === 'OGC:CRS84' ||
|
|
500
|
-
(crsVal.startsWith('EPSG:') && [4326, 4979].includes(Number(crsVal.split(':')[1])));
|
|
501
|
-
sourceCrs = isWgs84 ? null : crsVal;
|
|
518
|
+
sourceCrs = isWgs84(crsVal) ? null : crsVal;
|
|
502
519
|
needsDuckDbCrs = false;
|
|
503
520
|
} else if (typeStr.startsWith('GEOMETRY')) {
|
|
504
521
|
// GEOMETRY without CRS param — still need CRS from metadata
|
|
@@ -712,7 +729,7 @@ async function loadTable() {
|
|
|
712
729
|
} catch (err) {
|
|
713
730
|
if (thisGen !== loadGeneration) return;
|
|
714
731
|
console.error('[TableViewer] Error:', err);
|
|
715
|
-
error =
|
|
732
|
+
error = handleLoadError(err);
|
|
716
733
|
loading = false;
|
|
717
734
|
loadStage = '';
|
|
718
735
|
}
|
|
@@ -755,7 +772,7 @@ async function executeQuery(sql: string) {
|
|
|
755
772
|
error = t('table.queryCancelled');
|
|
756
773
|
return null;
|
|
757
774
|
}
|
|
758
|
-
error =
|
|
775
|
+
error = handleLoadError(err);
|
|
759
776
|
return null;
|
|
760
777
|
}
|
|
761
778
|
}
|
|
@@ -801,7 +818,7 @@ async function runCustomSql() {
|
|
|
801
818
|
});
|
|
802
819
|
} catch (err) {
|
|
803
820
|
executionTimeMs = Math.round(performance.now() - start);
|
|
804
|
-
error =
|
|
821
|
+
error = handleLoadError(err);
|
|
805
822
|
|
|
806
823
|
queryHistory.add({
|
|
807
824
|
sql: customSql,
|
|
@@ -908,7 +925,7 @@ function setStacView() {
|
|
|
908
925
|
{pageSize}
|
|
909
926
|
{historyVisible}
|
|
910
927
|
{hasGeo}
|
|
911
|
-
{isStac}
|
|
928
|
+
isStac={isStac && !nested}
|
|
912
929
|
{viewMode}
|
|
913
930
|
onPrevPage={prevPage}
|
|
914
931
|
onNextPage={nextPage}
|
|
@@ -916,13 +933,13 @@ function setStacView() {
|
|
|
916
933
|
onToggleInfo={toggleInfo}
|
|
917
934
|
onToggleHistory={toggleHistory}
|
|
918
935
|
onToggleView={toggleView}
|
|
919
|
-
onToggleStac={setStacView}
|
|
936
|
+
onToggleStac={nested ? undefined : setStacView}
|
|
920
937
|
onPageSizeChange={handlePageSizeChange}
|
|
921
938
|
/>
|
|
922
939
|
|
|
923
940
|
{#if viewMode === 'table'}
|
|
924
941
|
<!-- SQL Query Bar — hidden during schema/CRS detection, shown once query starts running -->
|
|
925
|
-
<div class="border-b border-
|
|
942
|
+
<div class="border-b border-border px-2 py-1.5 sm:px-4" class:hidden={loading && loadStage !== t('table.runningQuery')}>
|
|
926
943
|
<div class="flex items-start gap-1.5 sm:gap-2">
|
|
927
944
|
<div class="min-w-0 flex-1">
|
|
928
945
|
<CodeMirrorEditor
|
|
@@ -942,7 +959,7 @@ function setStacView() {
|
|
|
942
959
|
{queryRunning ? t('table.running') : t('table.run')}
|
|
943
960
|
</button>
|
|
944
961
|
<button
|
|
945
|
-
class="rounded px-2 py-1 text-xs text-
|
|
962
|
+
class="rounded px-2 py-1 text-xs text-muted-foreground hover:bg-muted sm:px-3"
|
|
946
963
|
onclick={handleFormatSql}
|
|
947
964
|
>
|
|
948
965
|
{t('table.format')}
|
|
@@ -955,9 +972,9 @@ function setStacView() {
|
|
|
955
972
|
<div
|
|
956
973
|
class="border-b border-red-200 bg-red-50 px-4 py-2 dark:border-red-800 dark:bg-red-950"
|
|
957
974
|
>
|
|
958
|
-
<p class="text-xs text-
|
|
975
|
+
<p class="text-xs text-destructive">{error}</p>
|
|
959
976
|
{#if tab.source === 'remote'}
|
|
960
|
-
<p class="mt-1 text-[10px] text-
|
|
977
|
+
<p class="mt-1 text-[10px] text-muted-foreground break-all">{buildStorageUrl(tab)}</p>
|
|
961
978
|
{/if}
|
|
962
979
|
</div>
|
|
963
980
|
{/if}
|
|
@@ -977,7 +994,7 @@ function setStacView() {
|
|
|
977
994
|
<div
|
|
978
995
|
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"
|
|
979
996
|
>
|
|
980
|
-
<p class="text-sm text-
|
|
997
|
+
<p class="text-sm text-destructive">{error}</p>
|
|
981
998
|
</div>
|
|
982
999
|
</div>
|
|
983
1000
|
{:else}
|