@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Archive, ChevronRight, Download, File, Folder, Loader } from '@lucide/svelte';
|
|
3
|
+
import { formatFileSize, handleLoadError, isAbortError } from '@walkthru-earth/objex-utils';
|
|
3
4
|
import type { Entry } from '@zip.js/zip.js';
|
|
4
5
|
import { onDestroy, untrack } from 'svelte';
|
|
5
6
|
import { Badge } from '../ui/badge/index.js';
|
|
@@ -28,11 +29,13 @@ import {
|
|
|
28
29
|
streamTarGzEntriesFromUrl,
|
|
29
30
|
streamZipEntriesFromUrl
|
|
30
31
|
} from '../../utils/archive';
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
32
|
+
import { buildHttpsUrlAsync } from '../../utils/signed-url.js';
|
|
33
|
+
import { useIsWide } from '../../utils/media-query.svelte.js';
|
|
33
34
|
|
|
34
35
|
let { tab }: { tab: Tab } = $props();
|
|
35
36
|
|
|
37
|
+
const isWide = useIsWide();
|
|
38
|
+
|
|
36
39
|
const MAX_ITEMS = 500;
|
|
37
40
|
|
|
38
41
|
// ── State ──────────────────────────────────────────────────────────────
|
|
@@ -165,8 +168,8 @@ async function loadArchive() {
|
|
|
165
168
|
error = t('archive.unsupported');
|
|
166
169
|
}
|
|
167
170
|
} catch (err) {
|
|
168
|
-
if ((err
|
|
169
|
-
error =
|
|
171
|
+
if (isAbortError(err)) return;
|
|
172
|
+
error = handleLoadError(err);
|
|
170
173
|
} finally {
|
|
171
174
|
scanning = false;
|
|
172
175
|
if (initializing) initializing = false;
|
|
@@ -187,7 +190,7 @@ async function loadZip() {
|
|
|
187
190
|
loadMethod = 'range';
|
|
188
191
|
return;
|
|
189
192
|
} catch (err) {
|
|
190
|
-
if ((err
|
|
193
|
+
if (isAbortError(err)) throw err;
|
|
191
194
|
entryList = [];
|
|
192
195
|
scanCount = 0;
|
|
193
196
|
zipEntryMap.clear();
|
|
@@ -218,7 +221,7 @@ async function loadTar() {
|
|
|
218
221
|
loadMethod = 'range';
|
|
219
222
|
return;
|
|
220
223
|
} catch (err) {
|
|
221
|
-
if ((err
|
|
224
|
+
if (isAbortError(err)) throw err;
|
|
222
225
|
entryList = [];
|
|
223
226
|
scanCount = 0;
|
|
224
227
|
remoteUrl = '';
|
|
@@ -260,7 +263,7 @@ async function loadTarGz() {
|
|
|
260
263
|
loadMethod = 'full';
|
|
261
264
|
return;
|
|
262
265
|
} catch (err) {
|
|
263
|
-
if ((err
|
|
266
|
+
if (isAbortError(err)) throw err;
|
|
264
267
|
// Fall through to full-buffer approach
|
|
265
268
|
entryList = [];
|
|
266
269
|
scanCount = 0;
|
|
@@ -312,7 +315,7 @@ const totalFiles = $derived(contents.files.length);
|
|
|
312
315
|
{#if selectedFile}
|
|
313
316
|
{@const fileName = selectedFile.filename.split('/').pop()}
|
|
314
317
|
<div
|
|
315
|
-
class="shrink-0 border-b border-
|
|
318
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
316
319
|
>
|
|
317
320
|
{t('archive.fileDetails')}
|
|
318
321
|
</div>
|
|
@@ -367,10 +370,10 @@ const totalFiles = $derived(contents.files.length);
|
|
|
367
370
|
|
|
368
371
|
<div class="flex h-full flex-col">
|
|
369
372
|
<!-- Header bar -->
|
|
370
|
-
<div class="shrink-0 border-b border-
|
|
373
|
+
<div class="shrink-0 border-b border-border px-3 py-2 sm:px-4">
|
|
371
374
|
<div class="flex items-center gap-1.5 sm:gap-2">
|
|
372
375
|
<Archive class="h-4 w-4 shrink-0 text-amber-500" />
|
|
373
|
-
<span class="max-w-[140px] truncate text-sm font-medium text-
|
|
376
|
+
<span class="max-w-[140px] truncate text-sm font-medium text-foreground sm:max-w-none">
|
|
374
377
|
{tab.name}
|
|
375
378
|
</span>
|
|
376
379
|
<Badge variant="outline" class="text-[10px]">{formatLabel}</Badge>
|
|
@@ -434,60 +437,118 @@ const totalFiles = $derived(contents.files.length);
|
|
|
434
437
|
<!-- Content area -->
|
|
435
438
|
{#if initializing}
|
|
436
439
|
<div class="flex flex-1 items-center justify-center gap-2">
|
|
437
|
-
<Loader class="h-5 w-5 animate-spin text-
|
|
438
|
-
<span class="text-sm text-
|
|
440
|
+
<Loader class="h-5 w-5 animate-spin text-muted-foreground" />
|
|
441
|
+
<span class="text-sm text-muted-foreground">{t('archive.loading')}</span>
|
|
439
442
|
</div>
|
|
440
443
|
{:else if error}
|
|
441
444
|
<div class="flex flex-1 items-center justify-center px-4">
|
|
442
|
-
<p class="text-sm text-
|
|
445
|
+
<p class="text-sm text-destructive">{error}</p>
|
|
443
446
|
</div>
|
|
444
447
|
{:else}
|
|
445
448
|
<!-- Column browser (resizable) -->
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
449
|
+
{#snippet archiveContents()}
|
|
450
|
+
<div class="flex h-full flex-col">
|
|
451
|
+
<div
|
|
452
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
453
|
+
>
|
|
454
|
+
{t('archive.contents')}
|
|
455
|
+
<span class="ms-1 normal-case tracking-normal">({(totalDirs + totalFiles).toLocaleString()})</span>
|
|
456
|
+
</div>
|
|
457
|
+
<div class="flex-1 overflow-auto">
|
|
458
|
+
{#if contents.directories.length === 0 && contents.files.length === 0 && !scanning}
|
|
459
|
+
<div class="p-4 text-center text-xs text-muted-foreground">
|
|
460
|
+
{t('archive.empty')}
|
|
461
|
+
</div>
|
|
462
|
+
{/if}
|
|
463
|
+
|
|
464
|
+
{#each contents.directories as dir, i}
|
|
465
|
+
{#if i < MAX_ITEMS}
|
|
466
|
+
{@const dirName = dir.split('/').pop()}
|
|
467
|
+
<button
|
|
468
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-muted"
|
|
469
|
+
class:bg-muted={selectedDir === dir}
|
|
470
|
+
onclick={() => selectDirectory(dir)}
|
|
471
|
+
ondblclick={() => navigateIntoDir(dir)}
|
|
472
|
+
>
|
|
473
|
+
<Folder class="size-3.5 shrink-0 text-amber-500/70" />
|
|
474
|
+
<span class="truncate font-medium">{dirName}</span>
|
|
475
|
+
<ChevronRight class="ms-auto size-3 shrink-0 text-muted-foreground" />
|
|
476
|
+
</button>
|
|
477
|
+
{:else if i === MAX_ITEMS}
|
|
478
|
+
<div class="px-3 py-1.5 text-[10px] text-muted-foreground">
|
|
479
|
+
+{contents.directories.length - MAX_ITEMS} more
|
|
480
|
+
</div>
|
|
481
|
+
{/if}
|
|
482
|
+
{/each}
|
|
483
|
+
|
|
484
|
+
{#each contents.files as file, i}
|
|
485
|
+
{#if i < MAX_ITEMS}
|
|
486
|
+
{@const fileName = file.filename.split('/').pop()}
|
|
487
|
+
<button
|
|
488
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-muted"
|
|
489
|
+
class:bg-muted={selectedFile?.filename === file.filename}
|
|
490
|
+
onclick={() => selectFile(file)}
|
|
491
|
+
>
|
|
492
|
+
<File class="size-3.5 shrink-0 text-muted-foreground/70" />
|
|
493
|
+
<span class="truncate">{fileName}</span>
|
|
494
|
+
<span class="ms-auto shrink-0 text-[10px] tabular-nums text-muted-foreground">
|
|
495
|
+
{formatFileSize(file.uncompressedSize)}
|
|
496
|
+
</span>
|
|
497
|
+
</button>
|
|
498
|
+
{:else if i === MAX_ITEMS}
|
|
499
|
+
<div class="px-3 py-1.5 text-[10px] text-muted-foreground">
|
|
500
|
+
+{contents.files.length - MAX_ITEMS} more
|
|
501
|
+
</div>
|
|
502
|
+
{/if}
|
|
503
|
+
{/each}
|
|
504
|
+
|
|
505
|
+
{#if scanning}
|
|
506
|
+
<div class="flex items-center gap-2 px-3 py-2 text-[10px] text-muted-foreground">
|
|
507
|
+
<Loader class="h-3 w-3 animate-spin" />
|
|
508
|
+
<span>{t('archive.scanningProgress', { count: scanCount.toLocaleString() })}</span>
|
|
509
|
+
</div>
|
|
510
|
+
{/if}
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
{/snippet}
|
|
514
|
+
|
|
515
|
+
{#snippet archiveSelectedDir()}
|
|
516
|
+
<div class="flex h-full flex-col">
|
|
517
|
+
{#if selectedDir}
|
|
518
|
+
{@const dirName = selectedDir.split('/').pop()}
|
|
450
519
|
<div
|
|
451
|
-
class="shrink-0 border-b border-
|
|
520
|
+
class="shrink-0 border-b border-border px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground"
|
|
452
521
|
>
|
|
453
|
-
{
|
|
454
|
-
<span class="ms-1 normal-case tracking-normal">({(
|
|
522
|
+
{dirName}
|
|
523
|
+
<span class="ms-1 normal-case tracking-normal">({(selectedDirContents.directories.length + selectedDirContents.files.length).toLocaleString()})</span>
|
|
455
524
|
</div>
|
|
456
525
|
<div class="flex-1 overflow-auto">
|
|
457
|
-
{#if
|
|
526
|
+
{#if selectedDirContents.directories.length === 0 && selectedDirContents.files.length === 0}
|
|
458
527
|
<div class="p-4 text-center text-xs text-muted-foreground">
|
|
459
528
|
{t('archive.empty')}
|
|
460
529
|
</div>
|
|
461
530
|
{/if}
|
|
462
531
|
|
|
463
|
-
{#each
|
|
532
|
+
{#each selectedDirContents.directories as subDir, i}
|
|
464
533
|
{#if i < MAX_ITEMS}
|
|
465
|
-
{@const
|
|
534
|
+
{@const subDirName = subDir.split('/').pop()}
|
|
466
535
|
<button
|
|
467
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-
|
|
468
|
-
|
|
469
|
-
class:dark:bg-zinc-800={selectedDir === dir}
|
|
470
|
-
onclick={() => selectDirectory(dir)}
|
|
471
|
-
ondblclick={() => navigateIntoDir(dir)}
|
|
536
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-muted"
|
|
537
|
+
onclick={() => navigateIntoDir(subDir)}
|
|
472
538
|
>
|
|
473
539
|
<Folder class="size-3.5 shrink-0 text-amber-500/70" />
|
|
474
|
-
<span class="truncate font-medium">{
|
|
540
|
+
<span class="truncate font-medium">{subDirName}</span>
|
|
475
541
|
<ChevronRight class="ms-auto size-3 shrink-0 text-muted-foreground" />
|
|
476
542
|
</button>
|
|
477
|
-
{:else if i === MAX_ITEMS}
|
|
478
|
-
<div class="px-3 py-1.5 text-[10px] text-muted-foreground">
|
|
479
|
-
+{contents.directories.length - MAX_ITEMS} more
|
|
480
|
-
</div>
|
|
481
543
|
{/if}
|
|
482
544
|
{/each}
|
|
483
545
|
|
|
484
|
-
{#each
|
|
546
|
+
{#each selectedDirContents.files as file, i}
|
|
485
547
|
{#if i < MAX_ITEMS}
|
|
486
548
|
{@const fileName = file.filename.split('/').pop()}
|
|
487
549
|
<button
|
|
488
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-
|
|
489
|
-
class:bg-
|
|
490
|
-
class:dark:bg-zinc-800={selectedFile?.filename === file.filename}
|
|
550
|
+
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-muted"
|
|
551
|
+
class:bg-muted={selectedFile?.filename === file.filename}
|
|
491
552
|
onclick={() => selectFile(file)}
|
|
492
553
|
>
|
|
493
554
|
<File class="size-3.5 shrink-0 text-muted-foreground/70" />
|
|
@@ -496,91 +557,57 @@ const totalFiles = $derived(contents.files.length);
|
|
|
496
557
|
{formatFileSize(file.uncompressedSize)}
|
|
497
558
|
</span>
|
|
498
559
|
</button>
|
|
499
|
-
{:else if i === MAX_ITEMS}
|
|
500
|
-
<div class="px-3 py-1.5 text-[10px] text-muted-foreground">
|
|
501
|
-
+{contents.files.length - MAX_ITEMS} more
|
|
502
|
-
</div>
|
|
503
560
|
{/if}
|
|
504
561
|
{/each}
|
|
505
|
-
|
|
506
|
-
{#if scanning}
|
|
507
|
-
<div class="flex items-center gap-2 px-3 py-2 text-[10px] text-muted-foreground">
|
|
508
|
-
<Loader class="h-3 w-3 animate-spin" />
|
|
509
|
-
<span>{t('archive.scanningProgress', { count: scanCount.toLocaleString() })}</span>
|
|
510
|
-
</div>
|
|
511
|
-
{/if}
|
|
512
562
|
</div>
|
|
513
|
-
|
|
514
|
-
|
|
563
|
+
{:else}
|
|
564
|
+
<div class="flex flex-1 items-center justify-center text-xs text-muted-foreground">
|
|
565
|
+
{t('archive.selectFolder')}
|
|
566
|
+
</div>
|
|
567
|
+
{/if}
|
|
568
|
+
</div>
|
|
569
|
+
{/snippet}
|
|
515
570
|
|
|
516
|
-
|
|
571
|
+
{#if isWide.value}
|
|
572
|
+
<ResizablePaneGroup direction="horizontal" class="min-h-0 flex-1">
|
|
573
|
+
<!-- Column 1: Current path entries -->
|
|
574
|
+
<ResizablePane defaultSize={35} minSize={20}>
|
|
575
|
+
{@render archiveContents()}
|
|
576
|
+
</ResizablePane>
|
|
517
577
|
|
|
518
|
-
|
|
519
|
-
<ResizablePane defaultSize={35} minSize={20}>
|
|
520
|
-
<div class="flex h-full flex-col">
|
|
521
|
-
{#if selectedDir}
|
|
522
|
-
{@const dirName = selectedDir.split('/').pop()}
|
|
523
|
-
<div
|
|
524
|
-
class="shrink-0 border-b border-zinc-200 px-3 py-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground dark:border-zinc-800"
|
|
525
|
-
>
|
|
526
|
-
{dirName}
|
|
527
|
-
<span class="ms-1 normal-case tracking-normal">({(selectedDirContents.directories.length + selectedDirContents.files.length).toLocaleString()})</span>
|
|
528
|
-
</div>
|
|
529
|
-
<div class="flex-1 overflow-auto">
|
|
530
|
-
{#if selectedDirContents.directories.length === 0 && selectedDirContents.files.length === 0}
|
|
531
|
-
<div class="p-4 text-center text-xs text-muted-foreground">
|
|
532
|
-
{t('archive.empty')}
|
|
533
|
-
</div>
|
|
534
|
-
{/if}
|
|
578
|
+
<ResizableHandle />
|
|
535
579
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800/50"
|
|
541
|
-
onclick={() => navigateIntoDir(subDir)}
|
|
542
|
-
>
|
|
543
|
-
<Folder class="size-3.5 shrink-0 text-amber-500/70" />
|
|
544
|
-
<span class="truncate font-medium">{subDirName}</span>
|
|
545
|
-
<ChevronRight class="ms-auto size-3 shrink-0 text-muted-foreground" />
|
|
546
|
-
</button>
|
|
547
|
-
{/if}
|
|
548
|
-
{/each}
|
|
549
|
-
|
|
550
|
-
{#each selectedDirContents.files as file, i}
|
|
551
|
-
{#if i < MAX_ITEMS}
|
|
552
|
-
{@const fileName = file.filename.split('/').pop()}
|
|
553
|
-
<button
|
|
554
|
-
class="flex w-full items-center gap-2 px-3 py-1.5 text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800/50"
|
|
555
|
-
class:bg-zinc-100={selectedFile?.filename === file.filename}
|
|
556
|
-
class:dark:bg-zinc-800={selectedFile?.filename === file.filename}
|
|
557
|
-
onclick={() => selectFile(file)}
|
|
558
|
-
>
|
|
559
|
-
<File class="size-3.5 shrink-0 text-muted-foreground/70" />
|
|
560
|
-
<span class="truncate">{fileName}</span>
|
|
561
|
-
<span class="ms-auto shrink-0 text-[10px] tabular-nums text-muted-foreground">
|
|
562
|
-
{formatFileSize(file.uncompressedSize)}
|
|
563
|
-
</span>
|
|
564
|
-
</button>
|
|
565
|
-
{/if}
|
|
566
|
-
{/each}
|
|
567
|
-
</div>
|
|
568
|
-
{:else}
|
|
569
|
-
<div class="flex flex-1 items-center justify-center text-xs text-muted-foreground">
|
|
570
|
-
{t('archive.selectFolder')}
|
|
571
|
-
</div>
|
|
572
|
-
{/if}
|
|
573
|
-
</div>
|
|
574
|
-
</ResizablePane>
|
|
580
|
+
<!-- Column 2: Selected directory contents -->
|
|
581
|
+
<ResizablePane defaultSize={35} minSize={20}>
|
|
582
|
+
{@render archiveSelectedDir()}
|
|
583
|
+
</ResizablePane>
|
|
575
584
|
|
|
576
|
-
|
|
585
|
+
<ResizableHandle />
|
|
577
586
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
587
|
+
<!-- Column 3: File details -->
|
|
588
|
+
<ResizablePane defaultSize={30} minSize={15}>
|
|
589
|
+
<div class="flex h-full flex-col">
|
|
590
|
+
{@render fileDetails()}
|
|
591
|
+
</div>
|
|
592
|
+
</ResizablePane>
|
|
593
|
+
</ResizablePaneGroup>
|
|
594
|
+
{:else}
|
|
595
|
+
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto">
|
|
596
|
+
<!-- Contents: fixed height list -->
|
|
597
|
+
<div class="max-h-52 shrink-0 border-b border-border">
|
|
598
|
+
{@render archiveContents()}
|
|
599
|
+
</div>
|
|
600
|
+
<!-- Selected dir: fixed height list (hidden when empty) -->
|
|
601
|
+
{#if selectedDir}
|
|
602
|
+
<div class="max-h-52 shrink-0 border-b border-border">
|
|
603
|
+
{@render archiveSelectedDir()}
|
|
604
|
+
</div>
|
|
605
|
+
{/if}
|
|
606
|
+
<!-- File details: grows to fill remaining space -->
|
|
607
|
+
<div class="flex flex-1 flex-col">
|
|
581
608
|
{@render fileDetails()}
|
|
582
609
|
</div>
|
|
583
|
-
</
|
|
584
|
-
|
|
610
|
+
</div>
|
|
611
|
+
{/if}
|
|
585
612
|
{/if}
|
|
586
613
|
</div>
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
|
|
3
|
+
import {
|
|
4
|
+
copyToClipboard,
|
|
5
|
+
handleLoadError,
|
|
6
|
+
isStacCatalog,
|
|
7
|
+
isStacCollection,
|
|
8
|
+
isStacItem
|
|
9
|
+
} from '@walkthru-earth/objex-utils';
|
|
3
10
|
import { onDestroy } from 'svelte';
|
|
4
11
|
import { Badge } from '../ui/badge/index.js';
|
|
5
12
|
import { Button } from '../ui/button/index.js';
|
|
@@ -8,13 +15,13 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
8
15
|
import { getAdapter } from '../../storage/index.js';
|
|
9
16
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
10
17
|
import type { Tab } from '../../types';
|
|
11
|
-
import { copyToClipboard } from '../../utils/clipboard.js';
|
|
12
|
-
import { handleLoadError } from '../../utils/error.js';
|
|
13
18
|
import { extensionToShikiLang, highlightCode } from '../../utils/shiki';
|
|
14
|
-
import { buildHttpsUrl,
|
|
15
|
-
import {
|
|
19
|
+
import { buildHttpsUrl, canStreamDirectly } from '../../utils/signed-url.js';
|
|
20
|
+
import { resolveSignedTabUrl } from '../../utils/signed-url-effect.js';
|
|
21
|
+
import { getUrlView, pickViewMode, updateUrlView } from '../../utils/url-state.js';
|
|
16
22
|
import { openZarrTab } from '../../utils/zarr-tab.js';
|
|
17
|
-
import
|
|
23
|
+
import ViewerHeader from './ViewerHeader.svelte';
|
|
24
|
+
import ViewerStatus from './ViewerStatus.svelte';
|
|
18
25
|
|
|
19
26
|
interface CodeActions {
|
|
20
27
|
toggleFormat: () => Promise<void>;
|
|
@@ -44,20 +51,20 @@ let error = $state<string | null>(null);
|
|
|
44
51
|
let copied = $state(false);
|
|
45
52
|
let formatted = $state(false);
|
|
46
53
|
const urlView = getUrlView();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (urlView === 'code') return
|
|
59
|
-
|
|
60
|
-
return 'code';
|
|
54
|
+
type CodeViewMode = 'code' | 'render' | 'stac-browser' | 'kepler' | 'maputnik' | 'marimo';
|
|
55
|
+
const CODE_VIEW_MODES = [
|
|
56
|
+
'code',
|
|
57
|
+
'render',
|
|
58
|
+
'stac-browser',
|
|
59
|
+
'kepler',
|
|
60
|
+
'maputnik',
|
|
61
|
+
'marimo'
|
|
62
|
+
] as const satisfies readonly CodeViewMode[];
|
|
63
|
+
function getInitialViewMode(): CodeViewMode {
|
|
64
|
+
const explicit = pickViewMode<CodeViewMode>(CODE_VIEW_MODES, 'code');
|
|
65
|
+
if (explicit !== 'code' || urlView === 'code') return explicit;
|
|
66
|
+
// No (or unknown) hash: default to render for HTML, code otherwise.
|
|
67
|
+
return tab.extension.toLowerCase() === 'html' ? 'render' : 'code';
|
|
61
68
|
}
|
|
62
69
|
let viewMode = $state(getInitialViewMode());
|
|
63
70
|
|
|
@@ -119,17 +126,10 @@ const stacBadgeKey = $derived<Record<string, string>>({
|
|
|
119
126
|
// must wait for the presign so the iframe never loads a bare `s3://` href.
|
|
120
127
|
let styleUrl = $state('');
|
|
121
128
|
$effect(() => {
|
|
122
|
-
const id = tab.id;
|
|
123
129
|
styleUrl = canStreamDirectly(tab) ? buildHttpsUrl(tab) : '';
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (cancelled || id !== tab.id) return;
|
|
128
|
-
styleUrl = url;
|
|
129
|
-
})();
|
|
130
|
-
return () => {
|
|
131
|
-
cancelled = true;
|
|
132
|
-
};
|
|
130
|
+
return resolveSignedTabUrl(tab, (u) => {
|
|
131
|
+
styleUrl = u;
|
|
132
|
+
});
|
|
133
133
|
});
|
|
134
134
|
const stacBrowserSrc = $derived(
|
|
135
135
|
`https://radiantearth.github.io/stac-browser/#/external/${styleUrl}`
|
|
@@ -207,12 +207,17 @@ $effect(() => {
|
|
|
207
207
|
};
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
// Auto-switch to STAC Browser when STAC JSON is detected
|
|
210
|
+
// Auto-switch to STAC Browser when STAC JSON is detected and the user did NOT
|
|
211
|
+
// request a specific view via the URL hash. Any explicit hash (#map, #stac-map,
|
|
212
|
+
// #stac-browser, #code, …) MUST be honored, because while ViewerRouter's async
|
|
213
|
+
// detectStac is pending it falls back to plain CodeViewer for .json tabs;
|
|
214
|
+
// rewriting the hash here would race the eventual StacTabViewer mount and
|
|
215
|
+
// clobber the shared link the user opened.
|
|
211
216
|
// Skipped when nested in StacTabViewer since the outer wrapper owns the view toggle.
|
|
212
217
|
let stacAutoSwitched = false;
|
|
213
218
|
$effect(() => {
|
|
214
219
|
if (nested) return;
|
|
215
|
-
if (isStacJson && !stacAutoSwitched && viewMode === 'code' && urlView
|
|
220
|
+
if (isStacJson && !stacAutoSwitched && viewMode === 'code' && !urlView) {
|
|
216
221
|
stacAutoSwitched = true;
|
|
217
222
|
viewMode = 'stac-browser';
|
|
218
223
|
updateUrlView('stac-browser');
|
|
@@ -349,7 +354,7 @@ async function toggleFormat() {
|
|
|
349
354
|
formatted = true;
|
|
350
355
|
}
|
|
351
356
|
|
|
352
|
-
function setViewMode(mode:
|
|
357
|
+
function setViewMode(mode: CodeViewMode) {
|
|
353
358
|
viewMode = viewMode === mode ? (isHtml ? 'render' : 'code') : mode;
|
|
354
359
|
updateUrlView(viewMode === 'render' ? '' : viewMode);
|
|
355
360
|
}
|
|
@@ -361,13 +366,9 @@ async function copyCode() {
|
|
|
361
366
|
|
|
362
367
|
<div class="flex h-full flex-col">
|
|
363
368
|
{#if !nested}
|
|
364
|
-
<
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
<span class="truncate max-w-[120px] text-sm font-medium text-zinc-700 sm:max-w-none dark:text-zinc-300">{tab.name}</span>
|
|
368
|
-
<Badge variant="secondary">{language}</Badge>
|
|
369
|
-
|
|
370
|
-
<div class="ms-auto flex items-center gap-1 sm:gap-2">
|
|
369
|
+
<ViewerHeader {tab}>
|
|
370
|
+
{#snippet badge()}<Badge variant="secondary">{language}</Badge>{/snippet}
|
|
371
|
+
{#snippet actions()}
|
|
371
372
|
{#if jsonKind === 'maplibre-style'}
|
|
372
373
|
<Badge variant="outline" class="hidden border-blue-200 text-blue-600 sm:inline-flex dark:border-blue-800 dark:text-blue-300">
|
|
373
374
|
{t('code.maplibreStyle')}
|
|
@@ -473,7 +474,7 @@ async function copyCode() {
|
|
|
473
474
|
<!-- Mobile overflow menu -->
|
|
474
475
|
<div class="flex sm:hidden">
|
|
475
476
|
<DropdownMenu.Root>
|
|
476
|
-
<DropdownMenu.Trigger class="rounded p-1 text-
|
|
477
|
+
<DropdownMenu.Trigger class="rounded p-1 text-muted-foreground hover:bg-muted">
|
|
477
478
|
<EllipsisVerticalIcon class="size-4" />
|
|
478
479
|
</DropdownMenu.Trigger>
|
|
479
480
|
<DropdownMenu.Content align="end" class="w-44">
|
|
@@ -530,8 +531,8 @@ async function copyCode() {
|
|
|
530
531
|
</DropdownMenu.Content>
|
|
531
532
|
</DropdownMenu.Root>
|
|
532
533
|
</div>
|
|
533
|
-
|
|
534
|
-
</
|
|
534
|
+
{/snippet}
|
|
535
|
+
</ViewerHeader>
|
|
535
536
|
{/if}
|
|
536
537
|
|
|
537
538
|
{#if viewMode === 'stac-browser' && styleUrl}
|
|
@@ -587,13 +588,9 @@ async function copyCode() {
|
|
|
587
588
|
class:word-wrap={wordWrap}
|
|
588
589
|
>
|
|
589
590
|
{#if loading}
|
|
590
|
-
<
|
|
591
|
-
<p class="text-sm text-zinc-400">{t('code.loading')}</p>
|
|
592
|
-
</div>
|
|
591
|
+
<ViewerStatus kind="loading" message={t('code.loading')} />
|
|
593
592
|
{:else if error}
|
|
594
|
-
<
|
|
595
|
-
<p class="text-sm text-red-400">{error}</p>
|
|
596
|
-
</div>
|
|
593
|
+
<ViewerStatus kind="error" message={error} />
|
|
597
594
|
{:else}
|
|
598
595
|
{@html html}
|
|
599
596
|
{/if}
|
|
@@ -12,6 +12,6 @@ type $$ComponentProps = {
|
|
|
12
12
|
wordWrap?: boolean;
|
|
13
13
|
actions?: CodeActions | null;
|
|
14
14
|
};
|
|
15
|
-
declare const CodeViewer: import("svelte").Component<$$ComponentProps, {}, "
|
|
15
|
+
declare const CodeViewer: import("svelte").Component<$$ComponentProps, {}, "actions" | "wordWrap">;
|
|
16
16
|
type CodeViewer = ReturnType<typeof CodeViewer>;
|
|
17
17
|
export default CodeViewer;
|