@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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { Tab } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
let { tab, badge, actions }: { tab: Tab; badge?: Snippet; actions?: Snippet } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div
|
|
9
|
+
class="flex items-center gap-1 border-b border-border px-2 py-1.5 sm:gap-2 sm:px-4"
|
|
10
|
+
>
|
|
11
|
+
<span class="max-w-[120px] truncate text-sm font-medium text-foreground sm:max-w-none">
|
|
12
|
+
{tab.name}
|
|
13
|
+
</span>
|
|
14
|
+
{#if badge}{@render badge()}{/if}
|
|
15
|
+
{#if actions}
|
|
16
|
+
<div class="ms-auto flex items-center gap-1">{@render actions()}</div>
|
|
17
|
+
{/if}
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { Tab } from '../../types.js';
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
tab: Tab;
|
|
5
|
+
badge?: Snippet;
|
|
6
|
+
actions?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const ViewerHeader: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ViewerHeader = ReturnType<typeof ViewerHeader>;
|
|
10
|
+
export default ViewerHeader;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { getViewerKind } from '../../file-icons/index.js';
|
|
3
|
-
import { getAdapter } from '../../storage/index.js';
|
|
4
|
-
import type { Tab } from '../../types.js';
|
|
5
|
-
import { readParquetMetadata } from '../../utils/parquet-metadata.js';
|
|
6
2
|
import {
|
|
7
3
|
classifyStac,
|
|
8
4
|
detectMosaicCapable,
|
|
9
5
|
detectMultiCogCapable,
|
|
6
|
+
isStacGeoparquetSchema,
|
|
7
|
+
readParquetMetadata,
|
|
8
|
+
STAC_API_PATH_RE,
|
|
10
9
|
type StacRoutableKind
|
|
11
|
-
} from '
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
10
|
+
} from '@walkthru-earth/objex-utils';
|
|
11
|
+
import { getViewerKind } from '../../file-icons/index.js';
|
|
12
|
+
import { getAdapter } from '../../storage/index.js';
|
|
13
|
+
import type { Tab } from '../../types.js';
|
|
14
|
+
import { buildHttpsUrlAsync } from '../../utils/signed-url.js';
|
|
15
15
|
import CodeViewer from './CodeViewer.svelte';
|
|
16
16
|
import ImageViewer from './ImageViewer.svelte';
|
|
17
17
|
import MediaViewer from './MediaViewer.svelte';
|
|
@@ -164,6 +164,14 @@ function pickMapKind(classified: StacRoutableKind): 'mosaic' | 'multicog' | null
|
|
|
164
164
|
|
|
165
165
|
{#if stacRoute.kind === 'stac' && viewerKind === 'table'}
|
|
166
166
|
<StacTabViewer {tab} mapKind={stacRoute.mapKind} classified={stacRoute.classified} />
|
|
167
|
+
{:else if stacRoute.kind === 'pending' && (viewerKind === 'table' || viewerKind === 'code' || viewerKind === 'raw')}
|
|
168
|
+
<!-- STAC detection (sniff parquet schema or peek 256KB JSON) is in flight.
|
|
169
|
+
Mounting TableViewer / CodeViewer here would let them read the URL hash,
|
|
170
|
+
pick a default viewMode, and potentially write back over an explicit
|
|
171
|
+
hash that StacTabViewer would otherwise own (e.g. `#map` on a STAC
|
|
172
|
+
collection JSON). The pending window is short — render an empty pane
|
|
173
|
+
until detection resolves and the right viewer takes over. -->
|
|
174
|
+
<div class="h-full"></div>
|
|
167
175
|
{:else if viewerKind === 'table'}
|
|
168
176
|
<TableViewer {tab} />
|
|
169
177
|
{:else if viewerKind === 'image'}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Loader } from '@lucide/svelte';
|
|
3
|
+
import { t } from '../../i18n/index.svelte.js';
|
|
4
|
+
|
|
5
|
+
let { kind, message }: { kind: 'loading' | 'error' | 'empty'; message?: string } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div class="flex h-full items-center justify-center p-4">
|
|
9
|
+
{#if kind === 'loading'}
|
|
10
|
+
<div class="text-muted-foreground flex items-center gap-2 text-sm">
|
|
11
|
+
<Loader class="size-4 animate-spin" />
|
|
12
|
+
<span>{message ?? t('common.loading')}</span>
|
|
13
|
+
</div>
|
|
14
|
+
{:else if kind === 'error'}
|
|
15
|
+
<p class="text-destructive text-sm">{message ?? t('common.error')}</p>
|
|
16
|
+
{:else}
|
|
17
|
+
<p class="text-muted-foreground text-sm">{message}</p>
|
|
18
|
+
{/if}
|
|
19
|
+
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { MapboxOverlay } from '@deck.gl/mapbox';
|
|
3
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
3
4
|
import type maplibregl from 'maplibre-gl';
|
|
4
5
|
import maplibreModule from 'maplibre-gl';
|
|
5
6
|
import { onDestroy, untrack } from 'svelte';
|
|
@@ -7,7 +8,7 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
7
8
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
8
9
|
import type { Tab } from '../../types.js';
|
|
9
10
|
import { createEpsgResolver } from '../../utils/cog.js';
|
|
10
|
-
import { buildHttpsUrlAsync } from '../../utils/url.js';
|
|
11
|
+
import { buildHttpsUrlAsync } from '../../utils/signed-url.js';
|
|
11
12
|
import {
|
|
12
13
|
detectGeoZarr,
|
|
13
14
|
ensureCodecsRegistered,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
type ZarrHierarchy,
|
|
18
19
|
type ZarrNode
|
|
19
20
|
} from '../../utils/zarr.js';
|
|
21
|
+
import { Slider } from '../ui/slider/index.js';
|
|
20
22
|
import MapContainer from './map/MapContainer.svelte';
|
|
21
23
|
|
|
22
24
|
/** Enriched selector dimension with coordinate metadata. */
|
|
@@ -490,7 +492,7 @@ async function addZarrLayer(map: maplibregl.Map) {
|
|
|
490
492
|
zarrLayer = new ZarrLayer(opts);
|
|
491
493
|
map.addLayer(zarrLayer);
|
|
492
494
|
} catch (err) {
|
|
493
|
-
error =
|
|
495
|
+
error = handleLoadError(err);
|
|
494
496
|
loading = false;
|
|
495
497
|
}
|
|
496
498
|
}
|
|
@@ -526,7 +528,7 @@ async function tryAddGeoZarrLayer(
|
|
|
526
528
|
const zarrInfoSnapshot = $state.snapshot(geoZarrInfo) as GeoZarrInfo;
|
|
527
529
|
const layer = new ZarrLayer({
|
|
528
530
|
id: `geozarr-${tab.id}`,
|
|
529
|
-
|
|
531
|
+
node: group,
|
|
530
532
|
variable: zarrInfoSnapshot.variantPath || undefined,
|
|
531
533
|
selection: {},
|
|
532
534
|
epsgResolver: dsZarrEpsg,
|
|
@@ -598,7 +600,7 @@ async function updateSelector() {
|
|
|
598
600
|
try {
|
|
599
601
|
await zarrLayer.setSelector(buildSelector());
|
|
600
602
|
} catch (err) {
|
|
601
|
-
error =
|
|
603
|
+
error = handleLoadError(err);
|
|
602
604
|
}
|
|
603
605
|
}
|
|
604
606
|
|
|
@@ -640,12 +642,12 @@ onDestroy(cleanup);
|
|
|
640
642
|
<div class="flex h-full w-full flex-col overflow-hidden">
|
|
641
643
|
<!-- Controls bar -->
|
|
642
644
|
<div
|
|
643
|
-
class="flex flex-wrap items-center gap-x-3 gap-y-1 border-b border-
|
|
645
|
+
class="flex flex-wrap items-center gap-x-3 gap-y-1 border-b border-border px-3 py-1.5"
|
|
644
646
|
>
|
|
645
|
-
<label class="flex items-center gap-1 text-xs text-
|
|
647
|
+
<label class="flex items-center gap-1 text-xs text-muted-foreground">
|
|
646
648
|
{t('map.variable')}
|
|
647
649
|
<select
|
|
648
|
-
class="rounded border border-
|
|
650
|
+
class="rounded border border-border bg-background px-1.5 py-0.5 text-xs text-foreground"
|
|
649
651
|
bind:value={selectedVar}
|
|
650
652
|
onchange={changeVariable}
|
|
651
653
|
>
|
|
@@ -657,24 +659,25 @@ onDestroy(cleanup);
|
|
|
657
659
|
|
|
658
660
|
{#each selectorDims as dim}
|
|
659
661
|
<label
|
|
660
|
-
class="flex shrink-0 items-center gap-1.5 rounded border border-
|
|
662
|
+
class="flex shrink-0 items-center gap-1.5 rounded border border-border px-2 py-0.5 text-xs text-muted-foreground"
|
|
661
663
|
title={dimLabel(dim)}
|
|
662
664
|
>
|
|
663
|
-
<span class="shrink-0 font-medium text-
|
|
664
|
-
<
|
|
665
|
-
type="
|
|
666
|
-
min=
|
|
665
|
+
<span class="shrink-0 font-medium text-muted-foreground">{dim.name}</span>
|
|
666
|
+
<Slider
|
|
667
|
+
type="single"
|
|
668
|
+
min={0}
|
|
667
669
|
max={dim.size - 1}
|
|
670
|
+
step={1}
|
|
668
671
|
value={selectorValues[dim.name] ?? 0}
|
|
669
|
-
|
|
670
|
-
selectorValues[dim.name] =
|
|
672
|
+
onValueChange={(v) => {
|
|
673
|
+
selectorValues[dim.name] = v as number;
|
|
671
674
|
}}
|
|
672
|
-
|
|
673
|
-
class="
|
|
675
|
+
onValueCommit={() => updateSelector()}
|
|
676
|
+
class="w-20"
|
|
674
677
|
/>
|
|
675
678
|
{#if dim.isDatetime && dim.minDate && dim.maxDate}
|
|
676
679
|
{@const dateVal = indexToDateStr(selectorValues[dim.name] ?? 0, dim)}
|
|
677
|
-
<span class="shrink-0 tabular-nums text-
|
|
680
|
+
<span class="shrink-0 tabular-nums text-muted-foreground">
|
|
678
681
|
{dateVal ? (dim.subDaily ? dateVal.replace('T', ' ') : dateVal) : (selectorValues[dim.name] ?? 0)}
|
|
679
682
|
</span>
|
|
680
683
|
<input
|
|
@@ -689,10 +692,10 @@ onDestroy(cleanup);
|
|
|
689
692
|
updateSelector();
|
|
690
693
|
}
|
|
691
694
|
}}
|
|
692
|
-
class="h-5 rounded border border-
|
|
695
|
+
class="h-5 rounded border border-border bg-background px-1 text-[10px] text-muted-foreground"
|
|
693
696
|
/>
|
|
694
697
|
{:else}
|
|
695
|
-
<span class="shrink-0 tabular-nums text-
|
|
698
|
+
<span class="shrink-0 tabular-nums text-muted-foreground">{selectorValues[dim.name] ?? 0}<span class="text-muted-foreground/60">/{dim.size - 1}</span></span>
|
|
696
699
|
{#if dim.dtype}
|
|
697
700
|
<span class="shrink-0 text-[10px] text-zinc-400/70">{dim.dtype}</span>
|
|
698
701
|
{/if}
|
|
@@ -701,7 +704,7 @@ onDestroy(cleanup);
|
|
|
701
704
|
{/each}
|
|
702
705
|
|
|
703
706
|
{#if selectedMeta?.shape}
|
|
704
|
-
<span class="ms-auto text-xs text-
|
|
707
|
+
<span class="ms-auto text-xs text-muted-foreground">
|
|
705
708
|
{selectedMeta.dtype} [{selectedMeta.shape.join(', ')}]
|
|
706
709
|
</span>
|
|
707
710
|
{/if}
|
|
@@ -711,7 +714,7 @@ onDestroy(cleanup);
|
|
|
711
714
|
<div class="relative min-h-0 flex-1">
|
|
712
715
|
{#if error && !loading}
|
|
713
716
|
<div class="flex h-full items-center justify-center">
|
|
714
|
-
<p class="max-w-md text-center text-sm text-
|
|
717
|
+
<p class="max-w-md text-center text-sm text-destructive">{error}</p>
|
|
715
718
|
</div>
|
|
716
719
|
{:else}
|
|
717
720
|
<MapContainer {onMapReady} bounds={[-130, 20, -60, 55]} />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
3
|
+
import { onDestroy, untrack } from 'svelte';
|
|
3
4
|
import { Badge } from '../ui/badge/index.js';
|
|
4
5
|
import { Button } from '../ui/button/index.js';
|
|
5
6
|
import {
|
|
@@ -8,9 +9,10 @@ import {
|
|
|
8
9
|
ResizablePaneGroup
|
|
9
10
|
} from '../ui/resizable/index.js';
|
|
10
11
|
import { t } from '../../i18n/index.svelte.js';
|
|
12
|
+
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
11
13
|
import type { Tab } from '../../types';
|
|
12
|
-
import { buildHttpsUrlAsync } from '../../utils/url.js';
|
|
13
|
-
import {
|
|
14
|
+
import { buildHttpsUrlAsync } from '../../utils/signed-url.js';
|
|
15
|
+
import { pickViewMode, updateUrlView } from '../../utils/url-state.js';
|
|
14
16
|
import {
|
|
15
17
|
computeChunkCount,
|
|
16
18
|
computeChunkSize,
|
|
@@ -25,13 +27,16 @@ import {
|
|
|
25
27
|
type ZarrHierarchy,
|
|
26
28
|
type ZarrNode
|
|
27
29
|
} from '../../utils/zarr.js';
|
|
30
|
+
import { useIsWide } from '../../utils/media-query.svelte.js';
|
|
28
31
|
|
|
29
32
|
let { tab }: { tab: Tab } = $props();
|
|
30
33
|
|
|
34
|
+
const isWide = useIsWide();
|
|
35
|
+
|
|
31
36
|
let loading = $state(true);
|
|
32
37
|
let error = $state<string | null>(null);
|
|
33
|
-
|
|
34
|
-
let viewMode = $state<
|
|
38
|
+
type ZarrViewMode = 'inspect' | 'map';
|
|
39
|
+
let viewMode = $state<ZarrViewMode>(pickViewMode<ZarrViewMode>(['inspect', 'map'], 'inspect'));
|
|
35
40
|
|
|
36
41
|
let hierarchy = $state.raw<ZarrHierarchy | null>(null);
|
|
37
42
|
let selectedNode = $state<ZarrNode | null>(null);
|
|
@@ -110,6 +115,19 @@ $effect(() => {
|
|
|
110
115
|
});
|
|
111
116
|
});
|
|
112
117
|
|
|
118
|
+
function cleanup() {
|
|
119
|
+
hierarchy = null;
|
|
120
|
+
selectedNode = null;
|
|
121
|
+
expanded = new Set();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
$effect(() => {
|
|
125
|
+
const id = tab.id;
|
|
126
|
+
const unregister = tabResources.register(id, cleanup);
|
|
127
|
+
return unregister;
|
|
128
|
+
});
|
|
129
|
+
onDestroy(cleanup);
|
|
130
|
+
|
|
113
131
|
function setViewMode(mode: 'inspect' | 'map') {
|
|
114
132
|
viewMode = mode;
|
|
115
133
|
updateUrlView(viewMode);
|
|
@@ -133,7 +151,7 @@ async function loadHierarchy() {
|
|
|
133
151
|
expanded = new Set(['/']);
|
|
134
152
|
}
|
|
135
153
|
} catch (err) {
|
|
136
|
-
error =
|
|
154
|
+
error = handleLoadError(err);
|
|
137
155
|
} finally {
|
|
138
156
|
loading = false;
|
|
139
157
|
updateUrlView(viewMode);
|
|
@@ -182,7 +200,7 @@ function selectStoreAttrs() {
|
|
|
182
200
|
{#if hasChildren}
|
|
183
201
|
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
|
184
202
|
<span
|
|
185
|
-
class="flex size-4 shrink-0 items-center justify-center rounded hover:bg-
|
|
203
|
+
class="flex size-4 shrink-0 items-center justify-center rounded hover:bg-accent"
|
|
186
204
|
role="button"
|
|
187
205
|
tabindex="-1"
|
|
188
206
|
aria-label={isExpanded ? 'Collapse' : 'Expand'}
|
|
@@ -192,7 +210,7 @@ function selectStoreAttrs() {
|
|
|
192
210
|
}}
|
|
193
211
|
>
|
|
194
212
|
<svg
|
|
195
|
-
class="size-3 text-
|
|
213
|
+
class="size-3 text-muted-foreground transition-transform"
|
|
196
214
|
class:rotate-90={isExpanded}
|
|
197
215
|
viewBox="0 0 16 16"
|
|
198
216
|
fill="currentColor"
|
|
@@ -229,10 +247,8 @@ function selectStoreAttrs() {
|
|
|
229
247
|
<span
|
|
230
248
|
class="truncate"
|
|
231
249
|
class:font-medium={node.kind === 'array'}
|
|
232
|
-
class:text-
|
|
233
|
-
class:
|
|
234
|
-
class:text-zinc-600={node.kind === 'group'}
|
|
235
|
-
class:dark:text-zinc-400={node.kind === 'group'}
|
|
250
|
+
class:text-foreground={node.kind === 'array'}
|
|
251
|
+
class:text-muted-foreground={node.kind === 'group'}
|
|
236
252
|
>
|
|
237
253
|
{node.path === '/' ? '/ (root)' : node.name}
|
|
238
254
|
</span>
|
|
@@ -259,18 +275,18 @@ function selectStoreAttrs() {
|
|
|
259
275
|
{#snippet nodeDetails()}
|
|
260
276
|
{#if showingStoreAttrs && hierarchy}
|
|
261
277
|
<div
|
|
262
|
-
class="shrink-0 border-b border-
|
|
278
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
263
279
|
>
|
|
264
280
|
{t('zarr.storeAttributes')}
|
|
265
281
|
</div>
|
|
266
282
|
<div class="flex-1 overflow-auto p-3">
|
|
267
283
|
<div
|
|
268
|
-
class="rounded border border-
|
|
284
|
+
class="rounded border border-border bg-muted p-2 text-xs"
|
|
269
285
|
>
|
|
270
286
|
{#each Object.entries(hierarchy.storeAttrs) as [key, value]}
|
|
271
287
|
<div class="flex gap-2 py-0.5">
|
|
272
288
|
<span class="shrink-0 font-medium text-muted-foreground">{key}:</span>
|
|
273
|
-
<span class="break-all text-
|
|
289
|
+
<span class="break-all text-foreground">
|
|
274
290
|
{typeof value === 'string' ? value : JSON.stringify(value)}
|
|
275
291
|
</span>
|
|
276
292
|
</div>
|
|
@@ -279,7 +295,7 @@ function selectStoreAttrs() {
|
|
|
279
295
|
</div>
|
|
280
296
|
{:else if selectedNode}
|
|
281
297
|
<div
|
|
282
|
-
class="shrink-0 border-b border-
|
|
298
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
283
299
|
>
|
|
284
300
|
{selectedNode.path}
|
|
285
301
|
</div>
|
|
@@ -389,12 +405,12 @@ function selectStoreAttrs() {
|
|
|
389
405
|
<dt class="text-muted-foreground">{t('zarr.attributes')}</dt>
|
|
390
406
|
<dd>
|
|
391
407
|
<div
|
|
392
|
-
class="mt-1 rounded border border-
|
|
408
|
+
class="mt-1 rounded border border-border bg-muted p-2"
|
|
393
409
|
>
|
|
394
410
|
{#each Object.entries(selectedNode.attributes) as [key, value]}
|
|
395
411
|
<div class="flex gap-2 py-0.5">
|
|
396
412
|
<span class="shrink-0 font-medium text-muted-foreground">{key}:</span>
|
|
397
|
-
<span class="break-all text-
|
|
413
|
+
<span class="break-all text-foreground">
|
|
398
414
|
{typeof value === 'string' ? value : JSON.stringify(value)}
|
|
399
415
|
</span>
|
|
400
416
|
</div>
|
|
@@ -414,10 +430,10 @@ function selectStoreAttrs() {
|
|
|
414
430
|
|
|
415
431
|
<div class="flex h-full flex-col">
|
|
416
432
|
<!-- Header bar -->
|
|
417
|
-
<div class="shrink-0 border-b border-
|
|
433
|
+
<div class="shrink-0 border-b border-border px-3 py-2 sm:px-4">
|
|
418
434
|
<div class="flex items-center gap-1.5 sm:gap-2">
|
|
419
435
|
<span
|
|
420
|
-
class="max-w-[140px] truncate text-sm font-medium text-
|
|
436
|
+
class="max-w-[140px] truncate text-sm font-medium text-foreground sm:max-w-none"
|
|
421
437
|
>{tab.name}</span
|
|
422
438
|
>
|
|
423
439
|
<Badge
|
|
@@ -459,11 +475,11 @@ function selectStoreAttrs() {
|
|
|
459
475
|
<!-- Content -->
|
|
460
476
|
{#if loading}
|
|
461
477
|
<div class="flex flex-1 items-center justify-center">
|
|
462
|
-
<p class="text-sm text-
|
|
478
|
+
<p class="text-sm text-muted-foreground">{t('zarr.loading')}</p>
|
|
463
479
|
</div>
|
|
464
480
|
{:else if error}
|
|
465
481
|
<div class="flex flex-1 items-center justify-center">
|
|
466
|
-
<p class="max-w-md text-center text-sm text-
|
|
482
|
+
<p class="max-w-md text-center text-sm text-destructive">{error}</p>
|
|
467
483
|
</div>
|
|
468
484
|
{:else if viewMode === 'map' && hasMapVars}
|
|
469
485
|
{#key viewMode}
|
|
@@ -480,56 +496,73 @@ function selectStoreAttrs() {
|
|
|
480
496
|
{/key}
|
|
481
497
|
{:else if hierarchy}
|
|
482
498
|
<!-- Inspect mode (tree + detail panel) -->
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
499
|
+
{#snippet zarrTree()}
|
|
500
|
+
<div class="flex h-full flex-col">
|
|
501
|
+
<div
|
|
502
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
503
|
+
>
|
|
504
|
+
{t('zarr.contents')}
|
|
505
|
+
<span class="ms-1 normal-case tracking-normal"
|
|
506
|
+
>({hierarchy!.totalNodes})</span
|
|
489
507
|
>
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
508
|
+
</div>
|
|
509
|
+
<div class="flex-1 overflow-auto">
|
|
510
|
+
{#if hasStoreAttrs}
|
|
511
|
+
<button
|
|
512
|
+
class="flex w-full items-center gap-2 border-b border-zinc-100 px-3 py-1 text-xs hover:bg-zinc-100 dark:border-zinc-800/50 dark:hover:bg-zinc-800/50"
|
|
513
|
+
class:bg-blue-50={showingStoreAttrs}
|
|
514
|
+
class:dark:bg-blue-950={showingStoreAttrs}
|
|
515
|
+
onclick={selectStoreAttrs}
|
|
493
516
|
>
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
class:bg-blue-50={showingStoreAttrs}
|
|
500
|
-
class:dark:bg-blue-950={showingStoreAttrs}
|
|
501
|
-
onclick={selectStoreAttrs}
|
|
517
|
+
<span class="size-4 shrink-0"></span>
|
|
518
|
+
<svg
|
|
519
|
+
class="size-3.5 shrink-0 text-muted-foreground"
|
|
520
|
+
viewBox="0 0 16 16"
|
|
521
|
+
fill="currentColor"
|
|
502
522
|
>
|
|
503
|
-
<
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
<span class="truncate font-medium text-muted-foreground">
|
|
516
|
-
{t('zarr.storeAttributes')}
|
|
517
|
-
</span>
|
|
518
|
-
</button>
|
|
519
|
-
{/if}
|
|
520
|
-
{@render treeNode(hierarchy.root, 0)}
|
|
521
|
-
</div>
|
|
523
|
+
<path
|
|
524
|
+
fill-rule="evenodd"
|
|
525
|
+
d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7H3a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-1.5V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z"
|
|
526
|
+
clip-rule="evenodd"
|
|
527
|
+
/>
|
|
528
|
+
</svg>
|
|
529
|
+
<span class="truncate font-medium text-muted-foreground">
|
|
530
|
+
{t('zarr.storeAttributes')}
|
|
531
|
+
</span>
|
|
532
|
+
</button>
|
|
533
|
+
{/if}
|
|
534
|
+
{@render treeNode(hierarchy!.root, 0)}
|
|
522
535
|
</div>
|
|
523
|
-
</
|
|
536
|
+
</div>
|
|
537
|
+
{/snippet}
|
|
538
|
+
|
|
539
|
+
{#if isWide.value}
|
|
540
|
+
<ResizablePaneGroup direction="horizontal" class="min-h-0 flex-1">
|
|
541
|
+
<!-- Left: Tree view -->
|
|
542
|
+
<ResizablePane defaultSize={40} minSize={20}>
|
|
543
|
+
{@render zarrTree()}
|
|
544
|
+
</ResizablePane>
|
|
524
545
|
|
|
525
|
-
|
|
546
|
+
<ResizableHandle />
|
|
526
547
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
548
|
+
<!-- Right: Detail panel -->
|
|
549
|
+
<ResizablePane defaultSize={60} minSize={30}>
|
|
550
|
+
<div class="flex h-full flex-col">
|
|
551
|
+
{@render nodeDetails()}
|
|
552
|
+
</div>
|
|
553
|
+
</ResizablePane>
|
|
554
|
+
</ResizablePaneGroup>
|
|
555
|
+
{:else}
|
|
556
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
|
557
|
+
<!-- Tree pane: fixed height so it doesn't crowd the detail section -->
|
|
558
|
+
<div class="max-h-64 shrink-0 border-b border-border">
|
|
559
|
+
{@render zarrTree()}
|
|
560
|
+
</div>
|
|
561
|
+
<!-- Detail panel: grows to fill remaining space -->
|
|
562
|
+
<div class="flex flex-1 flex-col">
|
|
530
563
|
{@render nodeDetails()}
|
|
531
564
|
</div>
|
|
532
|
-
</
|
|
533
|
-
|
|
565
|
+
</div>
|
|
566
|
+
{/if}
|
|
534
567
|
{/if}
|
|
535
568
|
</div>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ChannelRef, CogAsset } from '@walkthru-earth/objex-utils';
|
|
3
|
+
import { t } from '../../../i18n/index.svelte.js';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
channel: 'r' | 'g' | 'b' | 'a';
|
|
7
|
+
label: string;
|
|
8
|
+
colorClass: string;
|
|
9
|
+
assets: CogAsset[];
|
|
10
|
+
value: ChannelRef;
|
|
11
|
+
onChange: (next: ChannelRef) => void;
|
|
12
|
+
allowNone?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let { channel, label, colorClass, assets, value, onChange, allowNone = false }: Props = $props();
|
|
16
|
+
|
|
17
|
+
const assetByKey = $derived(new Map(assets.map((a) => [a.key, a])));
|
|
18
|
+
const currentAsset = $derived(assetByKey.get(value.assetKey) ?? null);
|
|
19
|
+
const bandCount = $derived(currentAsset?.bandCount ?? 1);
|
|
20
|
+
const bandIndices = $derived(Array.from({ length: bandCount }, (_, i) => i));
|
|
21
|
+
|
|
22
|
+
function assetLabel(a: CogAsset): string {
|
|
23
|
+
const cn = a.eoCommon[0];
|
|
24
|
+
const base = cn ? `${a.key} (${cn})` : a.key;
|
|
25
|
+
return a.bandCount > 1 ? `${base} · ${a.bandCount} bands` : base;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function bandLabel(i: number, asset: CogAsset | null): string {
|
|
29
|
+
if (!asset) return `${t('cog.band')} ${i + 1}`;
|
|
30
|
+
const cn = asset.eoCommon[i];
|
|
31
|
+
return cn ? `${t('cog.band')} ${i + 1} (${cn})` : `${t('cog.band')} ${i + 1}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function setAsset(key: string): void {
|
|
35
|
+
if (channel === 'a' && allowNone && key === '') {
|
|
36
|
+
onChange({ assetKey: '', bandIndex: 0 });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const target = assetByKey.get(key);
|
|
40
|
+
const maxIdx = Math.max(0, (target?.bandCount ?? 1) - 1);
|
|
41
|
+
const nextBand = Math.min(value.bandIndex, maxIdx);
|
|
42
|
+
onChange({ assetKey: key, bandIndex: nextBand });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setBand(idx: number): void {
|
|
46
|
+
onChange({ assetKey: value.assetKey, bandIndex: idx });
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<div class="flex items-start gap-2">
|
|
51
|
+
<span class="mt-1 w-3 shrink-0 font-bold {colorClass}">{label}</span>
|
|
52
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1 sm:flex-row sm:items-center">
|
|
53
|
+
<select
|
|
54
|
+
class="min-w-0 flex-1 truncate rounded border border-border bg-background px-1.5 py-0.5 text-xs"
|
|
55
|
+
aria-label={`${label} ${t('cog.asset')}`}
|
|
56
|
+
value={value.assetKey}
|
|
57
|
+
onchange={(e) => setAsset((e.target as HTMLSelectElement).value)}
|
|
58
|
+
>
|
|
59
|
+
{#if allowNone}
|
|
60
|
+
<option value="">{t('map.multiCogChannelNone')}</option>
|
|
61
|
+
{/if}
|
|
62
|
+
{#each assets as a (a.key)}
|
|
63
|
+
<option value={a.key}>{assetLabel(a)}</option>
|
|
64
|
+
{/each}
|
|
65
|
+
</select>
|
|
66
|
+
{#if currentAsset && bandCount > 1}
|
|
67
|
+
<select
|
|
68
|
+
class="min-w-0 flex-1 truncate rounded border border-border bg-background px-1.5 py-0.5 text-xs sm:flex-[0_1_auto] sm:min-w-24"
|
|
69
|
+
aria-label={`${label} ${t('cog.band')}`}
|
|
70
|
+
value={value.bandIndex}
|
|
71
|
+
onchange={(e) => setBand(Number((e.target as HTMLSelectElement).value))}
|
|
72
|
+
>
|
|
73
|
+
{#each bandIndices as i (i)}
|
|
74
|
+
<option value={i}>{bandLabel(i, currentAsset)}</option>
|
|
75
|
+
{/each}
|
|
76
|
+
</select>
|
|
77
|
+
{:else if currentAsset}
|
|
78
|
+
<span class="min-w-0 truncate px-1.5 py-0.5 text-[10px] text-muted-foreground sm:min-w-24">
|
|
79
|
+
{t('cog.band')} 1
|
|
80
|
+
</span>
|
|
81
|
+
{/if}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ChannelRef, CogAsset } from '@walkthru-earth/objex-utils';
|
|
2
|
+
type Props = {
|
|
3
|
+
channel: 'r' | 'g' | 'b' | 'a';
|
|
4
|
+
label: string;
|
|
5
|
+
colorClass: string;
|
|
6
|
+
assets: CogAsset[];
|
|
7
|
+
value: ChannelRef;
|
|
8
|
+
onChange: (next: ChannelRef) => void;
|
|
9
|
+
allowNone?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare const ChannelPicker: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type ChannelPicker = ReturnType<typeof ChannelPicker>;
|
|
13
|
+
export default ChannelPicker;
|