@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,26 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Tab } from '../../types';
|
|
3
|
-
import {
|
|
3
|
+
import { resolveSignedTabUrl } from '../../utils/signed-url-effect.js';
|
|
4
4
|
|
|
5
5
|
let { tab }: { tab: Tab } = $props();
|
|
6
6
|
|
|
7
7
|
let fileUrl = $state('');
|
|
8
8
|
|
|
9
9
|
$effect(() => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const url = await buildHttpsUrlAsync(tab);
|
|
18
|
-
if (cancelled || id !== tab.id) return;
|
|
19
|
-
fileUrl = url;
|
|
20
|
-
})();
|
|
21
|
-
return () => {
|
|
22
|
-
cancelled = true;
|
|
23
|
-
};
|
|
10
|
+
if (tab.source === 'url') {
|
|
11
|
+
fileUrl = tab.path;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
return resolveSignedTabUrl(tab, (u) => {
|
|
15
|
+
fileUrl = u;
|
|
16
|
+
});
|
|
24
17
|
});
|
|
25
18
|
|
|
26
19
|
const viewerUrl = $derived(
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import ClockIcon from '@lucide/svelte/icons/clock';
|
|
3
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
3
4
|
import { onDestroy } from 'svelte';
|
|
4
5
|
import SqlEditor from '../editor/SqlEditor.svelte';
|
|
5
6
|
import { Badge } from '../ui/badge/index.js';
|
|
@@ -127,7 +128,7 @@ async function loadDatabase() {
|
|
|
127
128
|
tables = (result.rows ?? []).map((row) => row.name).filter((name): name is string => !!name);
|
|
128
129
|
}
|
|
129
130
|
} catch (err) {
|
|
130
|
-
error =
|
|
131
|
+
error = handleLoadError(err);
|
|
131
132
|
} finally {
|
|
132
133
|
loading = false;
|
|
133
134
|
}
|
|
@@ -254,7 +255,7 @@ async function switchSnapshot(id: number) {
|
|
|
254
255
|
const connId = tab.connectionId ?? '';
|
|
255
256
|
await loadDuckLake(engine, connId, id);
|
|
256
257
|
} catch (err) {
|
|
257
|
-
error =
|
|
258
|
+
error = handleLoadError(err);
|
|
258
259
|
} finally {
|
|
259
260
|
switchingSnapshot = false;
|
|
260
261
|
}
|
|
@@ -318,7 +319,7 @@ async function switchSchema(schema: string) {
|
|
|
318
319
|
const connId = tab.connectionId ?? '';
|
|
319
320
|
await loadDuckLakeTables(engine, connId);
|
|
320
321
|
} catch (err) {
|
|
321
|
-
error =
|
|
322
|
+
error = handleLoadError(err);
|
|
322
323
|
}
|
|
323
324
|
}
|
|
324
325
|
|
|
@@ -357,23 +358,23 @@ function selectTable(tableName: string) {
|
|
|
357
358
|
|
|
358
359
|
<div class="flex h-full flex-col">
|
|
359
360
|
<div
|
|
360
|
-
class="flex items-center gap-1 border-b border-
|
|
361
|
+
class="flex items-center gap-1 border-b border-border px-2 py-1.5 sm:gap-2 sm:px-4"
|
|
361
362
|
>
|
|
362
|
-
<span class="truncate max-w-[120px] text-sm font-medium text-
|
|
363
|
+
<span class="truncate max-w-[120px] text-sm font-medium text-foreground sm:max-w-none">{tab.name}</span>
|
|
363
364
|
{#if isDuckLake}
|
|
364
365
|
<Badge variant="secondary" class="bg-teal-100 text-teal-800 dark:bg-teal-900 dark:text-teal-200">DuckLake</Badge>
|
|
365
366
|
{:else}
|
|
366
367
|
<Badge variant="secondary">{t('database.badge')}</Badge>
|
|
367
368
|
{/if}
|
|
368
369
|
{#if tables.length > 0}
|
|
369
|
-
<span class="hidden text-xs text-
|
|
370
|
+
<span class="hidden text-xs text-muted-foreground sm:inline">{tables.length} {t('database.tables')}</span>
|
|
370
371
|
{/if}
|
|
371
372
|
{#if isDuckLake && snapshotVersion !== null}
|
|
372
|
-
<div class="hidden items-center gap-1 text-xs text-
|
|
373
|
+
<div class="hidden items-center gap-1 text-xs text-muted-foreground sm:inline-flex">
|
|
373
374
|
<ClockIcon class="h-3 w-3" />
|
|
374
375
|
{#if snapshots.length > 1}
|
|
375
376
|
<select
|
|
376
|
-
class="rounded bg-
|
|
377
|
+
class="rounded bg-background px-1.5 py-0.5 text-xs text-foreground disabled:opacity-60"
|
|
377
378
|
disabled={switchingSnapshot}
|
|
378
379
|
title={t('ducklake.snapshot')}
|
|
379
380
|
value={snapshotVersion}
|
|
@@ -383,7 +384,7 @@ function selectTable(tableName: string) {
|
|
|
383
384
|
<option value={snap.id}>{formatSnapshotLabel(snap)}</option>
|
|
384
385
|
{/each}
|
|
385
386
|
</select>
|
|
386
|
-
<span class="text-
|
|
387
|
+
<span class="text-muted-foreground">({snapshots.length} {t('ducklake.snapshots')})</span>
|
|
387
388
|
{:else}
|
|
388
389
|
{@const formatted = formatSnapshotTime(snapshotTimeMs)}
|
|
389
390
|
<span>v{snapshotVersion}{#if formatted} ({formatted}){/if}</span>
|
|
@@ -406,26 +407,26 @@ function selectTable(tableName: string) {
|
|
|
406
407
|
<div class="flex flex-1 overflow-hidden">
|
|
407
408
|
{#if loading}
|
|
408
409
|
<div class="flex flex-1 items-center justify-center">
|
|
409
|
-
<p class="text-sm text-
|
|
410
|
+
<p class="text-sm text-muted-foreground">{isDuckLake ? t('ducklake.loading') : t('database.loading')}</p>
|
|
410
411
|
</div>
|
|
411
412
|
{:else if error}
|
|
412
413
|
<div class="flex flex-1 items-center justify-center p-4">
|
|
413
414
|
<div class="max-w-md text-center">
|
|
414
|
-
<p class="text-sm text-
|
|
415
|
+
<p class="text-sm text-destructive">{error}</p>
|
|
415
416
|
{#if isDuckLake && error.includes('ducklake')}
|
|
416
|
-
<p class="mt-2 text-xs text-
|
|
417
|
+
<p class="mt-2 text-xs text-muted-foreground">{t('ducklake.extensionHint')}</p>
|
|
417
418
|
{/if}
|
|
418
419
|
</div>
|
|
419
420
|
</div>
|
|
420
421
|
{:else}
|
|
421
422
|
<!-- Table list sidebar -->
|
|
422
423
|
<div
|
|
423
|
-
class="w-56 shrink-0 overflow-auto border-e border-
|
|
424
|
+
class="w-56 shrink-0 overflow-auto border-e border-border bg-muted"
|
|
424
425
|
>
|
|
425
426
|
{#if isDuckLake && schemas.length > 1}
|
|
426
|
-
<div class="border-b border-
|
|
427
|
+
<div class="border-b border-border px-3 py-2">
|
|
427
428
|
<select
|
|
428
|
-
class="w-full rounded bg-
|
|
429
|
+
class="w-full rounded bg-background px-2 py-1 text-xs text-foreground"
|
|
429
430
|
onchange={(e) => switchSchema(e.currentTarget.value)}
|
|
430
431
|
>
|
|
431
432
|
{#each schemas as schema}
|
|
@@ -434,21 +435,21 @@ function selectTable(tableName: string) {
|
|
|
434
435
|
</select>
|
|
435
436
|
</div>
|
|
436
437
|
{/if}
|
|
437
|
-
<div class="border-b border-
|
|
438
|
-
<h3 class="text-xs font-medium text-
|
|
438
|
+
<div class="border-b border-border px-3 py-2">
|
|
439
|
+
<h3 class="text-xs font-medium text-muted-foreground">{t('database.tablesHeader')}</h3>
|
|
439
440
|
</div>
|
|
440
441
|
{#each tables as tableName}
|
|
441
442
|
<button
|
|
442
|
-
class="flex w-full items-center px-3 py-1.5 text-start text-xs hover:bg-
|
|
443
|
+
class="flex w-full items-center px-3 py-1.5 text-start text-xs hover:bg-accent"
|
|
443
444
|
class:bg-blue-50={selectedTable === tableName}
|
|
444
445
|
class:dark:bg-blue-950={selectedTable === tableName}
|
|
445
446
|
onclick={() => selectTable(tableName)}
|
|
446
447
|
>
|
|
447
|
-
<span class="text-
|
|
448
|
+
<span class="text-foreground">{tableName}</span>
|
|
448
449
|
</button>
|
|
449
450
|
{/each}
|
|
450
451
|
{#if tables.length === 0}
|
|
451
|
-
<div class="px-3 py-4 text-center text-xs text-
|
|
452
|
+
<div class="px-3 py-4 text-center text-xs text-muted-foreground">
|
|
452
453
|
{isDuckLake ? t('ducklake.noTables') : t('database.selectTable')}
|
|
453
454
|
</div>
|
|
454
455
|
{/if}
|
|
@@ -468,7 +469,7 @@ function selectTable(tableName: string) {
|
|
|
468
469
|
{/key}
|
|
469
470
|
{:else}
|
|
470
471
|
<div class="flex flex-1 items-center justify-center">
|
|
471
|
-
<p class="text-sm text-
|
|
472
|
+
<p class="text-sm text-muted-foreground">{t('database.selectTable')}</p>
|
|
472
473
|
</div>
|
|
473
474
|
{/if}
|
|
474
475
|
</div>
|
|
@@ -32,7 +32,7 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
32
32
|
<!-- File Metadata -->
|
|
33
33
|
{#if fileEntries.length > 0}
|
|
34
34
|
<section class="mx-auto max-w-2xl">
|
|
35
|
-
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-
|
|
35
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
36
36
|
{t('fileInfo.fileMetadata')}
|
|
37
37
|
</h3>
|
|
38
38
|
<div
|
|
@@ -42,17 +42,17 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
42
42
|
{#each fileEntries as entry}
|
|
43
43
|
<div class="flex items-start gap-3 px-3.5 py-2">
|
|
44
44
|
<span
|
|
45
|
-
class="w-[5.5rem] shrink-0 text-xs text-
|
|
45
|
+
class="w-[5.5rem] shrink-0 text-xs text-muted-foreground"
|
|
46
46
|
>
|
|
47
47
|
{entry.label}
|
|
48
48
|
</span>
|
|
49
49
|
<div class="min-w-0 flex-1">
|
|
50
|
-
<span class="text-xs font-medium text-
|
|
50
|
+
<span class="text-xs font-medium text-foreground">
|
|
51
51
|
{entry.value}
|
|
52
52
|
</span>
|
|
53
53
|
{#if entry.detail}
|
|
54
54
|
<p
|
|
55
|
-
class="mt-0.5 truncate font-mono text-[10px] leading-tight text-
|
|
55
|
+
class="mt-0.5 truncate font-mono text-[10px] leading-tight text-muted-foreground"
|
|
56
56
|
>
|
|
57
57
|
{entry.detail}
|
|
58
58
|
</p>
|
|
@@ -68,7 +68,7 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
68
68
|
<!-- Geometry -->
|
|
69
69
|
{#if geoEntries.length > 0}
|
|
70
70
|
<section class="mx-auto max-w-2xl">
|
|
71
|
-
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-
|
|
71
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
72
72
|
{t('fileInfo.geometry')}
|
|
73
73
|
</h3>
|
|
74
74
|
<div
|
|
@@ -78,12 +78,12 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
78
78
|
{#each geoEntries as entry}
|
|
79
79
|
<div class="flex items-start gap-3 px-3.5 py-2">
|
|
80
80
|
<span
|
|
81
|
-
class="w-[5.5rem] shrink-0 text-xs text-
|
|
81
|
+
class="w-[5.5rem] shrink-0 text-xs text-muted-foreground"
|
|
82
82
|
>
|
|
83
83
|
{entry.label}
|
|
84
84
|
</span>
|
|
85
85
|
<div class="min-w-0 flex-1">
|
|
86
|
-
<span class="text-xs font-medium text-
|
|
86
|
+
<span class="text-xs font-medium text-foreground">
|
|
87
87
|
{entry.value}
|
|
88
88
|
</span>
|
|
89
89
|
</div>
|
|
@@ -97,7 +97,7 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
97
97
|
<!-- Schema -->
|
|
98
98
|
{#if schema.length > 0}
|
|
99
99
|
<section class="mx-auto max-w-2xl">
|
|
100
|
-
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-
|
|
100
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
101
101
|
{t('fileInfo.schema')} ({schema.length})
|
|
102
102
|
</h3>
|
|
103
103
|
<div
|
|
@@ -107,17 +107,17 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
107
107
|
<thead>
|
|
108
108
|
<tr class="bg-zinc-100/80 dark:bg-zinc-800/50">
|
|
109
109
|
<th
|
|
110
|
-
class="w-10 px-3 py-1.5 text-left font-medium text-
|
|
110
|
+
class="w-10 px-3 py-1.5 text-left font-medium text-muted-foreground"
|
|
111
111
|
>
|
|
112
112
|
#
|
|
113
113
|
</th>
|
|
114
114
|
<th
|
|
115
|
-
class="px-3 py-1.5 text-left font-medium text-
|
|
115
|
+
class="px-3 py-1.5 text-left font-medium text-muted-foreground"
|
|
116
116
|
>
|
|
117
117
|
{t('fileInfo.column')}
|
|
118
118
|
</th>
|
|
119
119
|
<th
|
|
120
|
-
class="px-3 py-1.5 text-left font-medium text-
|
|
120
|
+
class="px-3 py-1.5 text-left font-medium text-muted-foreground"
|
|
121
121
|
>
|
|
122
122
|
{t('fileInfo.type')}
|
|
123
123
|
</th>
|
|
@@ -128,13 +128,13 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
128
128
|
<tr
|
|
129
129
|
class="border-t border-zinc-100/80 dark:border-zinc-800/50 {i % 2 === 0 ? 'bg-zinc-50/50 dark:bg-zinc-900/20' : ''}"
|
|
130
130
|
>
|
|
131
|
-
<td class="px-3 py-1.5 tabular-nums text-
|
|
131
|
+
<td class="px-3 py-1.5 tabular-nums text-muted-foreground">
|
|
132
132
|
{i + 1}
|
|
133
133
|
</td>
|
|
134
|
-
<td class="px-3 py-1.5 font-mono text-
|
|
134
|
+
<td class="px-3 py-1.5 font-mono text-foreground">
|
|
135
135
|
{field.name}
|
|
136
136
|
</td>
|
|
137
|
-
<td class="px-3 py-1.5 font-mono text-
|
|
137
|
+
<td class="px-3 py-1.5 font-mono text-muted-foreground">
|
|
138
138
|
{field.type}
|
|
139
139
|
</td>
|
|
140
140
|
</tr>
|
|
@@ -148,7 +148,7 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
148
148
|
<!-- Parquet Table Explorer -->
|
|
149
149
|
{#if parquetTableSrc}
|
|
150
150
|
<section>
|
|
151
|
-
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-
|
|
151
|
+
<h3 class="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
152
152
|
{t('fileInfo.parquetExplorer')}
|
|
153
153
|
</h3>
|
|
154
154
|
<div
|
|
@@ -167,7 +167,7 @@ const geoEntries = $derived(entries.filter((e) => geoLabels.has(e.label)));
|
|
|
167
167
|
|
|
168
168
|
{#if entries.length === 0 && schema.length === 0}
|
|
169
169
|
<div class="flex flex-1 items-center justify-center py-16">
|
|
170
|
-
<p class="text-sm text-
|
|
170
|
+
<p class="text-sm text-muted-foreground">{t('fileInfo.noMetadata')}</p>
|
|
171
171
|
</div>
|
|
172
172
|
{/if}
|
|
173
173
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import LocateIcon from '@lucide/svelte/icons/locate';
|
|
3
|
+
import { FIRST_FEATURE_FLY_ZOOM, handleLoadError, isAbortError } from '@walkthru-earth/objex-utils';
|
|
3
4
|
import { geojson as fgbGeojson } from 'flatgeobuf';
|
|
4
5
|
import { magicbytes } from 'flatgeobuf/lib/mjs/constants.js';
|
|
5
6
|
import { buildHeader as fgbBuildHeader } from 'flatgeobuf/lib/mjs/generic/featurecollection.js';
|
|
@@ -16,10 +17,11 @@ import {
|
|
|
16
17
|
buildSelectionLayer,
|
|
17
18
|
geojsonFillColor,
|
|
18
19
|
geojsonLineColor,
|
|
20
|
+
HIGHLIGHT_COLOR,
|
|
19
21
|
hoverCursor,
|
|
20
22
|
loadDeckModules
|
|
21
23
|
} from '../../utils/deck.js';
|
|
22
|
-
import { buildHttpsUrlAsync } from '../../utils/url.js';
|
|
24
|
+
import { buildHttpsUrlAsync } from '../../utils/signed-url.js';
|
|
23
25
|
import AttributeTable from './map/AttributeTable.svelte';
|
|
24
26
|
import MapContainer from './map/MapContainer.svelte';
|
|
25
27
|
|
|
@@ -71,6 +73,10 @@ let headerInfo = $state<{
|
|
|
71
73
|
hasIndex: boolean;
|
|
72
74
|
} | null>(null);
|
|
73
75
|
|
|
76
|
+
// NOTE: intentionally broader than the canonical WGS84_CODES in constants.ts.
|
|
77
|
+
// 4267 (NAD27) / 4269 (NAD83) are treated as lon/lat here because FGB files
|
|
78
|
+
// commonly ship NAD-coded data that needs no reprojection for display.
|
|
79
|
+
// Do not narrow to [4326, 4979] without a NAD-coded FGB test file.
|
|
74
80
|
const WGS84_CODES = new Set([4326, 4979, 4267, 4269]);
|
|
75
81
|
const CRS84_NAMES = ['CRS84', 'CRS 84', 'OGC:CRS84'];
|
|
76
82
|
|
|
@@ -190,7 +196,7 @@ function extractFirstCoord(coords: any): [number, number] | null {
|
|
|
190
196
|
|
|
191
197
|
function flyToFirstFeature() {
|
|
192
198
|
if (!mapRef || !firstFeatureCoord) return;
|
|
193
|
-
mapRef.flyTo({ center: firstFeatureCoord, zoom:
|
|
199
|
+
mapRef.flyTo({ center: firstFeatureCoord, zoom: FIRST_FEATURE_FLY_ZOOM });
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
function cleanup() {
|
|
@@ -238,7 +244,6 @@ $effect(() => {
|
|
|
238
244
|
onDestroy(cleanup);
|
|
239
245
|
|
|
240
246
|
async function loadFlatGeobuf() {
|
|
241
|
-
console.log('[FGB]', 'loadFlatGeobuf() start');
|
|
242
247
|
cleanup();
|
|
243
248
|
|
|
244
249
|
loading = true;
|
|
@@ -280,13 +285,11 @@ async function loadFlatGeobuf() {
|
|
|
280
285
|
storedHeader.crs.org && code
|
|
281
286
|
? `${storedHeader.crs.org}:${code}`
|
|
282
287
|
: (storedHeader.crs.name ?? 'unknown');
|
|
283
|
-
console.log('[FGB]', 'Projected CRS detected:', crsLabel, '→ fetching proj4 definition');
|
|
284
288
|
|
|
285
289
|
try {
|
|
286
290
|
const proj4Def = await fetchProj4Def(code);
|
|
287
291
|
const converter = proj4(proj4Def, 'EPSG:4326') as any;
|
|
288
292
|
proj4Forward = (coord) => converter.forward(coord);
|
|
289
|
-
console.log('[FGB]', 'Reprojection ready:', crsLabel, '→ WGS84');
|
|
290
293
|
|
|
291
294
|
// Reproject envelope bounds so fitBounds works
|
|
292
295
|
if (storedHeader.envelope && storedHeader.envelope.length >= 4) {
|
|
@@ -298,7 +301,7 @@ async function loadFlatGeobuf() {
|
|
|
298
301
|
}
|
|
299
302
|
} catch (err) {
|
|
300
303
|
console.error('[FGB]', 'Failed to set up reprojection:', err);
|
|
301
|
-
error = `Cannot reproject CRS ${crsLabel} → WGS84: ${err
|
|
304
|
+
error = `Cannot reproject CRS ${crsLabel} → WGS84: ${handleLoadError(err) ?? String(err)}`;
|
|
302
305
|
streaming = false;
|
|
303
306
|
return;
|
|
304
307
|
}
|
|
@@ -308,11 +311,10 @@ async function loadFlatGeobuf() {
|
|
|
308
311
|
await streamFeatures(url, settings.featureLimit);
|
|
309
312
|
} catch (err) {
|
|
310
313
|
console.error('[FGB]', 'loadFlatGeobuf error:', err);
|
|
311
|
-
if (err
|
|
312
|
-
error =
|
|
314
|
+
if (isAbortError(err)) return;
|
|
315
|
+
error = handleLoadError(err);
|
|
313
316
|
loading = false;
|
|
314
317
|
} finally {
|
|
315
|
-
console.log('[FGB]', 'done, features:', features.length);
|
|
316
318
|
streaming = false;
|
|
317
319
|
}
|
|
318
320
|
}
|
|
@@ -331,25 +333,10 @@ async function readHeaderWithRangeRequests(url: string): Promise<boolean> {
|
|
|
331
333
|
}
|
|
332
334
|
|
|
333
335
|
const header = reader.header;
|
|
334
|
-
console.log('[FGB]', 'header:', {
|
|
335
|
-
geometryType: header.geometryType,
|
|
336
|
-
featuresCount: header.featuresCount,
|
|
337
|
-
indexNodeSize: header.indexNodeSize,
|
|
338
|
-
envelope: header.envelope ? Array.from(header.envelope) : null,
|
|
339
|
-
columns: header.columns?.length
|
|
340
|
-
});
|
|
341
336
|
populateHeaderInfo(header);
|
|
342
337
|
|
|
343
338
|
storedHeader = header;
|
|
344
339
|
storedFeatureOffset = reader.lengthBeforeFeatures();
|
|
345
|
-
console.log(
|
|
346
|
-
'[FGB]',
|
|
347
|
-
'featureOffset:',
|
|
348
|
-
storedFeatureOffset,
|
|
349
|
-
'(index ~',
|
|
350
|
-
((storedFeatureOffset - 12) / 1024 / 1024).toFixed(1),
|
|
351
|
-
'MB skipped)'
|
|
352
|
-
);
|
|
353
340
|
return true;
|
|
354
341
|
}
|
|
355
342
|
|
|
@@ -367,10 +354,9 @@ async function loadAllFeatures() {
|
|
|
367
354
|
await streamFeatures(url);
|
|
368
355
|
} catch (err) {
|
|
369
356
|
console.error('[FGB]', 'loadAllFeatures error:', err);
|
|
370
|
-
if (err
|
|
371
|
-
error =
|
|
357
|
+
if (isAbortError(err)) return;
|
|
358
|
+
error = handleLoadError(err);
|
|
372
359
|
} finally {
|
|
373
|
-
console.log('[FGB]', 'loadAllFeatures done, features:', features.length);
|
|
374
360
|
streaming = false;
|
|
375
361
|
}
|
|
376
362
|
}
|
|
@@ -382,7 +368,6 @@ async function loadAllFeatures() {
|
|
|
382
368
|
async function streamFeatures(url: string, limit?: number) {
|
|
383
369
|
const ac = new AbortController();
|
|
384
370
|
abortController = ac;
|
|
385
|
-
const t0 = performance.now();
|
|
386
371
|
|
|
387
372
|
let iter: AsyncGenerator;
|
|
388
373
|
activeStreamCancel = null;
|
|
@@ -398,12 +383,6 @@ async function streamFeatures(url: string, limit?: number) {
|
|
|
398
383
|
headers: { Range: `bytes=${storedFeatureOffset}-` },
|
|
399
384
|
signal: ac.signal
|
|
400
385
|
});
|
|
401
|
-
console.log(
|
|
402
|
-
'[FGB]',
|
|
403
|
-
'Range fetch:',
|
|
404
|
-
featureResp.status,
|
|
405
|
-
featureResp.headers.get('content-range')
|
|
406
|
-
);
|
|
407
386
|
if (!featureResp.ok && featureResp.status !== 206)
|
|
408
387
|
throw new Error(`HTTP ${featureResp.status}: ${featureResp.statusText}`);
|
|
409
388
|
if (!featureResp.body) throw new Error('No response body');
|
|
@@ -475,7 +454,6 @@ async function streamFeatures(url: string, limit?: number) {
|
|
|
475
454
|
activeStreamCancel?.();
|
|
476
455
|
activeStreamCancel = null;
|
|
477
456
|
ac.abort();
|
|
478
|
-
console.log('[FGB]', 'force-closed connection after', features.length, 'features');
|
|
479
457
|
}
|
|
480
458
|
}
|
|
481
459
|
|
|
@@ -487,14 +465,6 @@ async function streamFeatures(url: string, limit?: number) {
|
|
|
487
465
|
featureCount = features.length;
|
|
488
466
|
updateLayer();
|
|
489
467
|
if (!flewToFeatures) flyToFeaturesBounds();
|
|
490
|
-
console.log(
|
|
491
|
-
'[FGB]',
|
|
492
|
-
'stream done:',
|
|
493
|
-
features.length,
|
|
494
|
-
'features in',
|
|
495
|
-
(performance.now() - t0).toFixed(0),
|
|
496
|
-
'ms'
|
|
497
|
-
);
|
|
498
468
|
|
|
499
469
|
if (hitLimit) {
|
|
500
470
|
hasMore = totalFeatures != null && totalFeatures > features.length;
|
|
@@ -601,7 +571,7 @@ function updateLayer() {
|
|
|
601
571
|
pointRadiusMinPixels: 4,
|
|
602
572
|
pointRadiusMaxPixels: 12,
|
|
603
573
|
autoHighlight: true,
|
|
604
|
-
highlightColor:
|
|
574
|
+
highlightColor: HIGHLIGHT_COLOR,
|
|
605
575
|
onHover: mapRef ? hoverCursor(mapRef) : undefined,
|
|
606
576
|
onClick: (info: any) => {
|
|
607
577
|
if (info.object?.properties) {
|
|
@@ -638,11 +608,11 @@ function onMapReady(map: maplibregl.Map) {
|
|
|
638
608
|
<div class="relative flex h-full overflow-hidden">
|
|
639
609
|
{#if loading}
|
|
640
610
|
<div class="flex flex-1 items-center justify-center">
|
|
641
|
-
<p class="text-sm text-
|
|
611
|
+
<p class="text-sm text-muted-foreground">{t('map.loadingFgb')}</p>
|
|
642
612
|
</div>
|
|
643
613
|
{:else if error && featureCount === 0}
|
|
644
614
|
<div class="flex flex-1 items-center justify-center">
|
|
645
|
-
<p class="text-sm text-
|
|
615
|
+
<p class="text-sm text-destructive">{error}</p>
|
|
646
616
|
</div>
|
|
647
617
|
{:else}
|
|
648
618
|
<div class="flex-1">
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import LocateIcon from '@lucide/svelte/icons/locate';
|
|
3
|
+
import {
|
|
4
|
+
buildGeoArrowTables,
|
|
5
|
+
FIRST_FEATURE_FLY_ZOOM,
|
|
6
|
+
type GeoArrowGeomType,
|
|
7
|
+
type GeoArrowResult,
|
|
8
|
+
handleLoadError,
|
|
9
|
+
parseWKB
|
|
10
|
+
} from '@walkthru-earth/objex-utils';
|
|
3
11
|
import type maplibregl from 'maplibre-gl';
|
|
4
12
|
import { onDestroy } from 'svelte';
|
|
5
13
|
import { t } from '../../i18n/index.svelte.js';
|
|
@@ -14,12 +22,6 @@ import {
|
|
|
14
22
|
hoverCursor,
|
|
15
23
|
loadGeoArrowModules
|
|
16
24
|
} from '../../utils/deck.js';
|
|
17
|
-
import {
|
|
18
|
-
buildGeoArrowTables,
|
|
19
|
-
type GeoArrowGeomType,
|
|
20
|
-
type GeoArrowResult
|
|
21
|
-
} from '../../utils/geoarrow.js';
|
|
22
|
-
import { parseWKB } from '../../utils/wkb.js';
|
|
23
25
|
import LoadProgress, { type ProgressEntry } from './LoadProgress.svelte';
|
|
24
26
|
import AttributeTable from './map/AttributeTable.svelte';
|
|
25
27
|
import MapContainer from './map/MapContainer.svelte';
|
|
@@ -73,7 +75,7 @@ function extractFirstCoord(coords: any): [number, number] | null {
|
|
|
73
75
|
|
|
74
76
|
function flyToFirstFeature() {
|
|
75
77
|
if (!mapRef || !firstFeatureCoord) return;
|
|
76
|
-
mapRef.flyTo({ center: firstFeatureCoord, zoom:
|
|
78
|
+
mapRef.flyTo({ center: firstFeatureCoord, zoom: FIRST_FEATURE_FLY_ZOOM });
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
// mapData is read synchronously in loadGeoData (before any await),
|
|
@@ -173,7 +175,7 @@ async function loadGeoData() {
|
|
|
173
175
|
loading = false;
|
|
174
176
|
} catch (err) {
|
|
175
177
|
if (gen !== loadGen) return;
|
|
176
|
-
error =
|
|
178
|
+
error = handleLoadError(err);
|
|
177
179
|
loading = false;
|
|
178
180
|
}
|
|
179
181
|
}
|
|
@@ -225,7 +227,7 @@ function onMapReady(map: maplibregl.Map) {
|
|
|
225
227
|
<LoadProgress stage={t('map.loadingGeometry')} entries={progressEntries} />
|
|
226
228
|
{:else if error}
|
|
227
229
|
<div class="flex flex-1 items-center justify-center">
|
|
228
|
-
<p class="text-sm text-
|
|
230
|
+
<p class="text-sm text-destructive">{error}</p>
|
|
229
231
|
</div>
|
|
230
232
|
{:else}
|
|
231
233
|
<div class="flex-1">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type GeoArrowGeomType } from '@walkthru-earth/objex-utils';
|
|
1
2
|
import type { MapQueryResult, SchemaField } from '../../query/engine';
|
|
2
3
|
import type { Tab } from '../../types';
|
|
3
|
-
import { type GeoArrowGeomType } from '../../utils/geoarrow.js';
|
|
4
4
|
import { type ProgressEntry } from './LoadProgress.svelte';
|
|
5
5
|
type $$ComponentProps = {
|
|
6
6
|
tab: Tab;
|
|
@@ -5,6 +5,7 @@ import MinusIcon from '@lucide/svelte/icons/minus';
|
|
|
5
5
|
import PlusIcon from '@lucide/svelte/icons/plus';
|
|
6
6
|
import RotateCwIcon from '@lucide/svelte/icons/rotate-cw';
|
|
7
7
|
import ScanIcon from '@lucide/svelte/icons/scan';
|
|
8
|
+
import { handleLoadError } from '@walkthru-earth/objex-utils';
|
|
8
9
|
import { onDestroy } from 'svelte';
|
|
9
10
|
import { Badge } from '../ui/badge/index.js';
|
|
10
11
|
import { Button } from '../ui/button/index.js';
|
|
@@ -14,8 +15,9 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
14
15
|
import { getAdapter } from '../../storage/index.js';
|
|
15
16
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
16
17
|
import type { Tab } from '../../types';
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
18
|
+
import { buildHttpsUrl, canStreamDirectly } from '../../utils/signed-url.js';
|
|
19
|
+
import ViewerHeader from './ViewerHeader.svelte';
|
|
20
|
+
import ViewerStatus from './ViewerStatus.svelte';
|
|
19
21
|
|
|
20
22
|
let { tab }: { tab: Tab } = $props();
|
|
21
23
|
|
|
@@ -149,13 +151,9 @@ onDestroy(cleanup);
|
|
|
149
151
|
</script>
|
|
150
152
|
|
|
151
153
|
<div class="flex h-full flex-col">
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<span class="truncate max-w-[120px] text-sm font-medium text-zinc-700 sm:max-w-none dark:text-zinc-300">{tab.name}</span>
|
|
156
|
-
<Badge variant="secondary">{tab.extension.toUpperCase()}</Badge>
|
|
157
|
-
|
|
158
|
-
<div class="ms-auto flex items-center gap-1">
|
|
154
|
+
<ViewerHeader {tab}>
|
|
155
|
+
{#snippet badge()}<Badge variant="secondary">{tab.extension.toUpperCase()}</Badge>{/snippet}
|
|
156
|
+
{#snippet actions()}
|
|
159
157
|
<!-- Desktop controls -->
|
|
160
158
|
<div class="hidden items-center gap-1 sm:flex">
|
|
161
159
|
<Button variant="ghost" size="sm" class="h-7 px-1.5" onclick={zoomIn} title={t('image.zoomIn')}>
|
|
@@ -178,7 +176,7 @@ onDestroy(cleanup);
|
|
|
178
176
|
<!-- Mobile overflow menu -->
|
|
179
177
|
<div class="flex sm:hidden">
|
|
180
178
|
<DropdownMenu.Root>
|
|
181
|
-
<DropdownMenu.Trigger class="rounded p-1 text-
|
|
179
|
+
<DropdownMenu.Trigger class="rounded p-1 text-muted-foreground hover:bg-muted">
|
|
182
180
|
<EllipsisVerticalIcon class="size-4" />
|
|
183
181
|
</DropdownMenu.Trigger>
|
|
184
182
|
<DropdownMenu.Content align="end" class="w-40">
|
|
@@ -190,8 +188,8 @@ onDestroy(cleanup);
|
|
|
190
188
|
</DropdownMenu.Content>
|
|
191
189
|
</DropdownMenu.Root>
|
|
192
190
|
</div>
|
|
193
|
-
|
|
194
|
-
</
|
|
191
|
+
{/snippet}
|
|
192
|
+
</ViewerHeader>
|
|
195
193
|
|
|
196
194
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
197
195
|
<div
|
|
@@ -203,9 +201,9 @@ onDestroy(cleanup);
|
|
|
203
201
|
ondblclick={handleDblClick}
|
|
204
202
|
>
|
|
205
203
|
{#if loading}
|
|
206
|
-
<
|
|
204
|
+
<ViewerStatus kind="loading" message={t('image.loading')} />
|
|
207
205
|
{:else if error}
|
|
208
|
-
<
|
|
206
|
+
<ViewerStatus kind="error" message={error} />
|
|
209
207
|
{:else if imgSrc}
|
|
210
208
|
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
211
209
|
<img
|
|
@@ -29,7 +29,7 @@ let {
|
|
|
29
29
|
<!-- Active step -->
|
|
30
30
|
<div class="flex items-center gap-2 text-center">
|
|
31
31
|
<Loader2Icon class="size-4 shrink-0 animate-spin text-primary" />
|
|
32
|
-
<p class="text-sm text-
|
|
32
|
+
<p class="text-sm text-muted-foreground">{stage || t('table.loading')}</p>
|
|
33
33
|
</div>
|
|
34
34
|
|
|
35
35
|
<!-- Discovered metadata -->
|
|
@@ -42,17 +42,17 @@ let {
|
|
|
42
42
|
<div class="flex items-start gap-1.5 sm:gap-2">
|
|
43
43
|
<CheckCircleIcon class="mt-0.5 size-3 shrink-0 text-green-500/80" />
|
|
44
44
|
<span
|
|
45
|
-
class="w-14 shrink-0 text-[11px] leading-4 text-
|
|
45
|
+
class="w-14 shrink-0 text-[11px] leading-4 text-muted-foreground sm:w-[4.5rem] sm:text-xs"
|
|
46
46
|
>
|
|
47
47
|
{entry.label}
|
|
48
48
|
</span>
|
|
49
49
|
<div class="min-w-0 flex-1">
|
|
50
|
-
<span class="break-all text-[11px] font-medium leading-4 text-
|
|
50
|
+
<span class="break-all text-[11px] font-medium leading-4 text-foreground sm:text-xs">
|
|
51
51
|
{entry.value}
|
|
52
52
|
</span>
|
|
53
53
|
{#if entry.detail}
|
|
54
54
|
<p
|
|
55
|
-
class="mt-0.5 truncate font-mono text-[10px] leading-tight text-
|
|
55
|
+
class="mt-0.5 truncate font-mono text-[10px] leading-tight text-muted-foreground"
|
|
56
56
|
>
|
|
57
57
|
{entry.detail}
|
|
58
58
|
</p>
|
|
@@ -68,7 +68,7 @@ let {
|
|
|
68
68
|
{#if onCancel}
|
|
69
69
|
<div class="flex flex-col items-center gap-2">
|
|
70
70
|
<button
|
|
71
|
-
class="flex items-center gap-1 rounded border border-
|
|
71
|
+
class="flex items-center gap-1 rounded border border-border px-3 py-1 text-xs text-muted-foreground hover:bg-muted"
|
|
72
72
|
onclick={onCancel}
|
|
73
73
|
>
|
|
74
74
|
<XCircleIcon class="size-3" />
|
|
@@ -83,7 +83,7 @@ let {
|
|
|
83
83
|
<XCircleIcon class="size-3" />
|
|
84
84
|
{t('table.forceStop')}
|
|
85
85
|
</button>
|
|
86
|
-
<p class="max-w-xs text-center text-[10px] text-
|
|
86
|
+
<p class="max-w-xs text-center text-[10px] text-muted-foreground">
|
|
87
87
|
{t('table.forceStopWarning')}
|
|
88
88
|
</p>
|
|
89
89
|
</div>
|