@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.
Files changed (199) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +28 -20
  3. package/dist/components/browser/FileTreeSidebar.svelte +32 -17
  4. package/dist/components/layout/AboutSheet.svelte +5 -2
  5. package/dist/components/layout/ConnectionDialog.svelte +7 -2
  6. package/dist/components/layout/SettingsSheet.svelte +238 -0
  7. package/dist/components/layout/SettingsSheet.svelte.d.ts +6 -0
  8. package/dist/components/layout/Sidebar.svelte +73 -6
  9. package/dist/components/layout/Sidebar.svelte.d.ts +4 -1
  10. package/dist/components/layout/StatusBar.svelte +17 -14
  11. package/dist/components/layout/TabBar.svelte +4 -4
  12. package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
  13. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +1 -1
  14. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
  15. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  16. package/dist/components/ui/resizable/index.d.ts +1 -1
  17. package/dist/components/ui/resizable/index.js +2 -2
  18. package/dist/components/ui/slider/index.d.ts +3 -0
  19. package/dist/components/ui/slider/index.js +5 -0
  20. package/dist/components/ui/slider/range-slider.svelte +94 -0
  21. package/dist/components/ui/slider/range-slider.svelte.d.ts +21 -0
  22. package/dist/components/ui/slider/slider.svelte +83 -0
  23. package/dist/components/ui/slider/slider.svelte.d.ts +7 -0
  24. package/dist/components/viewers/ArchiveViewer.svelte +140 -113
  25. package/dist/components/viewers/CodeViewer.svelte +45 -48
  26. package/dist/components/viewers/CodeViewer.svelte.d.ts +1 -1
  27. package/dist/components/viewers/CogControls.svelte +338 -184
  28. package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
  29. package/dist/components/viewers/CogViewer.svelte +269 -116
  30. package/dist/components/viewers/CopcViewer.svelte +8 -15
  31. package/dist/components/viewers/DatabaseViewer.svelte +22 -21
  32. package/dist/components/viewers/FileInfo.svelte +16 -16
  33. package/dist/components/viewers/FlatGeobufViewer.svelte +16 -46
  34. package/dist/components/viewers/GeoParquetMapViewer.svelte +11 -9
  35. package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
  36. package/dist/components/viewers/ImageViewer.svelte +12 -14
  37. package/dist/components/viewers/LoadProgress.svelte +6 -6
  38. package/dist/components/viewers/MarkdownViewer.svelte +29 -30
  39. package/dist/components/viewers/MediaViewer.svelte +13 -14
  40. package/dist/components/viewers/ModelViewer.svelte +18 -21
  41. package/dist/components/viewers/MultiCogViewer.svelte +474 -106
  42. package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
  43. package/dist/components/viewers/NotebookViewer.svelte +28 -29
  44. package/dist/components/viewers/PdfViewer.svelte +24 -33
  45. package/dist/components/viewers/PmtilesViewer.svelte +13 -15
  46. package/dist/components/viewers/QueryHistoryPanel.svelte +18 -18
  47. package/dist/components/viewers/RawViewer.svelte +27 -21
  48. package/dist/components/viewers/StacMapViewer.svelte +6 -13
  49. package/dist/components/viewers/StacMosaicViewer.svelte +1764 -410
  50. package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
  51. package/dist/components/viewers/StacTabViewer.svelte +26 -15
  52. package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
  53. package/dist/components/viewers/TableGrid.svelte +38 -34
  54. package/dist/components/viewers/TableStatusBar.svelte +7 -7
  55. package/dist/components/viewers/TableToolbar.svelte +10 -9
  56. package/dist/components/viewers/TableViewer.svelte +47 -30
  57. package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
  58. package/dist/components/viewers/ViewerHeader.svelte +18 -0
  59. package/dist/components/viewers/ViewerHeader.svelte.d.ts +10 -0
  60. package/dist/components/viewers/ViewerRouter.svelte +16 -8
  61. package/dist/components/viewers/ViewerStatus.svelte +19 -0
  62. package/dist/components/viewers/ViewerStatus.svelte.d.ts +7 -0
  63. package/dist/components/viewers/ZarrMapViewer.svelte +24 -21
  64. package/dist/components/viewers/ZarrViewer.svelte +98 -65
  65. package/dist/components/viewers/cog/ChannelPicker.svelte +83 -0
  66. package/dist/components/viewers/cog/ChannelPicker.svelte.d.ts +13 -0
  67. package/dist/components/viewers/cog/PixelInspectorPanel.svelte +87 -0
  68. package/dist/components/viewers/cog/PixelInspectorPanel.svelte.d.ts +17 -0
  69. package/dist/components/viewers/cog/buildRgbLayer.d.ts +78 -0
  70. package/dist/components/viewers/cog/buildRgbLayer.js +176 -0
  71. package/dist/components/viewers/map/AttributeTable.svelte +7 -7
  72. package/dist/components/viewers/map/MapContainer.svelte +38 -12
  73. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +109 -83
  74. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +16 -16
  75. package/dist/components/viewers/stac/StacDatetimeBar.svelte +175 -0
  76. package/dist/components/viewers/stac/StacDatetimeBar.svelte.d.ts +10 -0
  77. package/dist/components/viewers/stac/StacFilterPanel.svelte +243 -0
  78. package/dist/components/viewers/stac/StacFilterPanel.svelte.d.ts +14 -0
  79. package/dist/components/viewers/stac/StacItemInspector.svelte +223 -0
  80. package/dist/components/viewers/stac/StacItemInspector.svelte.d.ts +10 -0
  81. package/dist/components/viewers/stac/StacItemStrip.svelte +228 -0
  82. package/dist/components/viewers/stac/StacItemStrip.svelte.d.ts +12 -0
  83. package/dist/constants.d.ts +6 -0
  84. package/dist/constants.js +8 -0
  85. package/dist/file-icons/index.d.ts +1 -1
  86. package/dist/file-icons/index.js +1 -1
  87. package/dist/i18n/ar.js +113 -2
  88. package/dist/i18n/en.js +113 -2
  89. package/dist/index.d.ts +2 -28
  90. package/dist/index.js +7 -23
  91. package/dist/query/engine.d.ts +10 -0
  92. package/dist/query/source.js +1 -1
  93. package/dist/query/stac-source-factory.d.ts +65 -0
  94. package/dist/query/stac-source-factory.js +77 -0
  95. package/dist/query/stac-source-parquet.d.ts +135 -0
  96. package/dist/query/stac-source-parquet.js +468 -0
  97. package/dist/query/wasm.d.ts +8 -0
  98. package/dist/query/wasm.js +310 -65
  99. package/dist/storage/presign.js +3 -2
  100. package/dist/storage/providers.js +7 -6
  101. package/dist/stores/config.svelte.d.ts +15 -0
  102. package/dist/stores/config.svelte.js +46 -0
  103. package/dist/stores/connections.svelte.d.ts +2 -2
  104. package/dist/stores/connections.svelte.js +1 -2
  105. package/dist/stores/files.svelte.d.ts +1 -1
  106. package/dist/stores/files.svelte.js +1 -1
  107. package/dist/stores/query-history.svelte.js +1 -1
  108. package/dist/stores/settings.svelte.d.ts +16 -1
  109. package/dist/stores/settings.svelte.js +104 -48
  110. package/dist/stores/tabs.svelte.d.ts +3 -0
  111. package/dist/stores/tabs.svelte.js +17 -0
  112. package/dist/utils/cog-histogram.d.ts +121 -0
  113. package/dist/utils/cog-histogram.js +424 -0
  114. package/dist/utils/cog.d.ts +177 -20
  115. package/dist/utils/cog.js +361 -76
  116. package/dist/utils/colormap-sprite.d.ts +0 -9
  117. package/dist/utils/colormap-sprite.js +0 -21
  118. package/dist/utils/deck.d.ts +18 -12
  119. package/dist/utils/deck.js +15 -7
  120. package/dist/utils/media-query.svelte.d.ts +14 -0
  121. package/dist/utils/media-query.svelte.js +29 -0
  122. package/dist/utils/pmtiles-tile.js +2 -2
  123. package/dist/utils/signed-url-effect.d.ts +7 -0
  124. package/dist/utils/signed-url-effect.js +19 -0
  125. package/dist/utils/{url.d.ts → signed-url.d.ts} +15 -1
  126. package/dist/utils/{url.js → signed-url.js} +32 -10
  127. package/dist/utils/url-state.d.ts +36 -0
  128. package/dist/utils/url-state.js +72 -2
  129. package/dist/utils/zarr-tab.d.ts +1 -2
  130. package/dist/utils/zarr-tab.js +1 -2
  131. package/dist/utils/zarr.d.ts +0 -17
  132. package/dist/utils/zarr.js +1 -45
  133. package/package.json +55 -84
  134. package/dist/components/browser/Breadcrumb.svelte +0 -50
  135. package/dist/components/browser/Breadcrumb.svelte.d.ts +0 -7
  136. package/dist/components/browser/CreateFolderDialog.svelte +0 -98
  137. package/dist/components/browser/CreateFolderDialog.svelte.d.ts +0 -6
  138. package/dist/components/browser/DeleteConfirmDialog.svelte +0 -90
  139. package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +0 -8
  140. package/dist/components/browser/DropZone.svelte +0 -83
  141. package/dist/components/browser/DropZone.svelte.d.ts +0 -7
  142. package/dist/components/browser/FileBrowser.svelte +0 -252
  143. package/dist/components/browser/FileBrowser.svelte.d.ts +0 -3
  144. package/dist/components/browser/FileRow.svelte +0 -117
  145. package/dist/components/browser/FileRow.svelte.d.ts +0 -9
  146. package/dist/components/browser/RenameDialog.svelte +0 -101
  147. package/dist/components/browser/RenameDialog.svelte.d.ts +0 -8
  148. package/dist/components/browser/SearchBar.svelte +0 -40
  149. package/dist/components/browser/SearchBar.svelte.d.ts +0 -6
  150. package/dist/components/browser/UploadButton.svelte +0 -65
  151. package/dist/components/browser/UploadButton.svelte.d.ts +0 -3
  152. package/dist/query/stac-geoparquet.d.ts +0 -31
  153. package/dist/query/stac-geoparquet.js +0 -136
  154. package/dist/utils/clipboard.d.ts +0 -13
  155. package/dist/utils/clipboard.js +0 -38
  156. package/dist/utils/cloud-url.d.ts +0 -27
  157. package/dist/utils/cloud-url.js +0 -61
  158. package/dist/utils/cog-pure.d.ts +0 -25
  159. package/dist/utils/cog-pure.js +0 -35
  160. package/dist/utils/column-types.d.ts +0 -5
  161. package/dist/utils/column-types.js +0 -137
  162. package/dist/utils/connection-identity.d.ts +0 -51
  163. package/dist/utils/connection-identity.js +0 -97
  164. package/dist/utils/error.d.ts +0 -8
  165. package/dist/utils/error.js +0 -12
  166. package/dist/utils/evidence-context.d.ts +0 -22
  167. package/dist/utils/evidence-context.js +0 -56
  168. package/dist/utils/export.d.ts +0 -22
  169. package/dist/utils/export.js +0 -76
  170. package/dist/utils/file-sort.d.ts +0 -20
  171. package/dist/utils/file-sort.js +0 -41
  172. package/dist/utils/format.d.ts +0 -24
  173. package/dist/utils/format.js +0 -78
  174. package/dist/utils/geoarrow.d.ts +0 -32
  175. package/dist/utils/geoarrow.js +0 -672
  176. package/dist/utils/geometry-type.d.ts +0 -52
  177. package/dist/utils/geometry-type.js +0 -76
  178. package/dist/utils/hex.d.ts +0 -10
  179. package/dist/utils/hex.js +0 -27
  180. package/dist/utils/host-detection.d.ts +0 -23
  181. package/dist/utils/host-detection.js +0 -95
  182. package/dist/utils/local-storage.d.ts +0 -16
  183. package/dist/utils/local-storage.js +0 -37
  184. package/dist/utils/markdown-sql.d.ts +0 -30
  185. package/dist/utils/markdown-sql.js +0 -72
  186. package/dist/utils/notebook.d.ts +0 -59
  187. package/dist/utils/notebook.js +0 -211
  188. package/dist/utils/parquet-metadata.d.ts +0 -64
  189. package/dist/utils/parquet-metadata.js +0 -262
  190. package/dist/utils/stac-geoparquet.d.ts +0 -90
  191. package/dist/utils/stac-geoparquet.js +0 -223
  192. package/dist/utils/stac-hydrate.d.ts +0 -38
  193. package/dist/utils/stac-hydrate.js +0 -243
  194. package/dist/utils/stac.d.ts +0 -136
  195. package/dist/utils/stac.js +0 -176
  196. package/dist/utils/storage-url.d.ts +0 -90
  197. package/dist/utils/storage-url.js +0 -568
  198. package/dist/utils/wkb.d.ts +0 -43
  199. package/dist/utils/wkb.js +0 -359
@@ -1,5 +1,5 @@
1
+ import { type StacRoutableKind } from '@walkthru-earth/objex-utils';
1
2
  import type { Tab } from '../../types.js';
2
- import { type StacRoutableKind } from '../../utils/stac.js';
3
3
  type $$ComponentProps = {
4
4
  tab: Tab;
5
5
  classified?: StacRoutableKind;
@@ -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 type { StacRoutableKind } from '../../utils/stac.js';
10
- import { canStreamDirectly } from '../../utils/url.js';
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
- type ViewMode = 'map' | 'stac-map' | 'stac-browser' | 'code';
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
- if (urlView === 'stac-map') return 'stac-map';
76
- if (urlView === 'stac-browser') return 'stac-browser';
77
- if (urlView === 'code') return 'code';
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 === 'map' ? 'map' : 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-zinc-200 px-2 py-1.5 sm:gap-2 sm:px-4 dark:border-zinc-800"
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-zinc-700 sm:max-w-none dark:text-zinc-300"
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 === 'code' ? 'default' : 'ghost'}
205
+ variant={viewMode === rawContentMode ? 'default' : 'ghost'}
195
206
  class="h-7 gap-1 px-2"
196
- onclick={() => setView('code')}
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}
@@ -1,5 +1,5 @@
1
+ import type { StacRoutableKind } from '@walkthru-earth/objex-utils';
1
2
  import type { Tab } from '../../types.js';
2
- import type { StacRoutableKind } from '../../utils/stac.js';
3
3
  type MapKind = 'mosaic' | 'multicog' | null;
4
4
  interface Props {
5
5
  tab: Tab;
@@ -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 '../../utils/column-types.js';
18
- import { jsonReplacerBigInt } from '../../utils/format.js';
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: MouseEvent) {
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: MouseEvent) {
87
+ function onMove(ev: PointerEvent) {
87
88
  columnWidths[col] = Math.max(MIN_WIDTH, startW + (ev.clientX - startX));
88
89
  }
89
- function onUp() {
90
- document.removeEventListener('mousemove', onMove);
91
- document.removeEventListener('mouseup', onUp);
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('mousemove', onMove);
95
- document.addEventListener('mouseup', onUp);
96
+ document.addEventListener('pointermove', onMove);
97
+ document.addEventListener('pointerup', onUp);
96
98
  resizeCleanup = () => {
97
- document.removeEventListener('mousemove', onMove);
98
- document.removeEventListener('mouseup', onUp);
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-zinc-100 dark:bg-zinc-900">
226
+ <tr class="bg-muted">
225
227
  <th
226
- class="border-b border-e border-zinc-200 px-2 py-2 text-start text-xs font-medium text-zinc-400 dark:border-zinc-700 dark:text-zinc-500"
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-zinc-200 px-3 py-1.5 dark:border-zinc-700"
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-zinc-700 dark:text-zinc-300">{col}</span>
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-zinc-400 dark:text-zinc-500">
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-1.5 cursor-col-resize bg-transparent transition-colors hover:bg-blue-400/60"
263
- onmousedown={(e) => startResize(col, e)}
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
- ></div>
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-zinc-100 px-2 py-1 text-xs tabular-nums text-zinc-400 dark:border-zinc-800 dark:text-zinc-600"
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-zinc-100 px-3 py-1 text-[13px] dark:border-zinc-800"
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-zinc-400 dark:text-zinc-600">null</span>
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-zinc-800 dark:text-zinc-200">
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-zinc-400 dark:text-zinc-600">
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-zinc-200 bg-white py-1 shadow-xl dark:border-zinc-700 dark:bg-zinc-800"
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-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700"
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-zinc-400" />
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-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700"
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-zinc-400" />
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-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-700"
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-zinc-400" />
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-zinc-200 px-3 py-1 text-center text-[10px] text-green-500 dark:border-zinc-700">
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-zinc-200 bg-zinc-50 px-3 text-xs text-zinc-500 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-400">
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-zinc-400 dark:text-zinc-500"> {t('statusBar.inTime', { time: executionTimeMs })}</span>
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-zinc-200 dark:hover:bg-zinc-800"
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-zinc-200 bg-white py-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-800"
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-zinc-100 dark:hover:bg-zinc-700"
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-zinc-100 dark:hover:bg-zinc-700"
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), 2000);
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-zinc-200 px-2 py-1.5 sm:gap-2 sm:px-4 dark:border-zinc-800"
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-zinc-700 sm:max-w-none dark:text-zinc-300">{fileName}</span>
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-zinc-400 sm:inline dark:text-zinc-500">
127
+ <span class="hidden text-xs text-muted-foreground sm:inline">
127
128
  {#if rowCount > 0}
128
129
  {rowCount.toLocaleString()} {t('toolbar.rows')} &times; {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-zinc-300 text-zinc-600 hover:bg-zinc-50 hover:text-zinc-700 dark:border-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-900' : ''}"
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-zinc-200 bg-transparent px-1.5 py-0.5 text-xs text-zinc-500 outline-none dark:border-zinc-700 dark:text-zinc-400"
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-zinc-200 bg-transparent px-1 text-center text-xs outline-none dark:border-zinc-700 dark:bg-zinc-900"
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-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800"
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 { getUrlView, updateUrlView } from '../../utils/url-state.js';
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
- let { tab }: { tab: Tab } = $props();
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
- const urlView = getUrlView();
60
- let viewMode = $state<'table' | 'map' | 'stac' | 'info'>(
61
- urlView === 'map' ? 'map' : urlView === 'stac' ? 'stac' : urlView === 'info' ? 'info' : 'table'
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
- const isWgs84 =
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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 = err instanceof Error ? err.message : String(err);
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-zinc-200 px-2 py-1.5 sm:px-4 dark:border-zinc-800" class:hidden={loading && loadStage !== t('table.runningQuery')}>
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-zinc-400 hover:bg-zinc-100 sm:px-3 dark:hover:bg-zinc-800"
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-red-600 dark:text-red-400">{error}</p>
975
+ <p class="text-xs text-destructive">{error}</p>
959
976
  {#if tab.source === 'remote'}
960
- <p class="mt-1 text-[10px] text-zinc-400 break-all">{buildStorageUrl(tab)}</p>
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-red-600 dark:text-red-400">{error}</p>
997
+ <p class="text-sm text-destructive">{error}</p>
981
998
  </div>
982
999
  </div>
983
1000
  {:else}
@@ -1,6 +1,7 @@
1
1
  import type { Tab } from '../../types';
2
2
  type $$ComponentProps = {
3
3
  tab: Tab;
4
+ nested?: boolean;
4
5
  };
5
6
  declare const TableViewer: import("svelte").Component<$$ComponentProps, {}, "">;
6
7
  type TableViewer = ReturnType<typeof TableViewer>;