@walkthru-earth/objex 0.1.0 → 1.1.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/README.md +9 -2
- package/dist/components/browser/FileBrowser.svelte +53 -41
- package/dist/components/browser/FileRow.svelte +8 -3
- package/dist/components/browser/FileTreeSidebar.svelte +2 -4
- package/dist/components/layout/AboutSheet.svelte +126 -0
- package/dist/components/layout/AboutSheet.svelte.d.ts +6 -0
- package/dist/components/layout/ConnectionDialog.svelte +186 -138
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +1 -0
- package/dist/components/layout/Sidebar.svelte +19 -3
- package/dist/components/layout/TabBar.svelte +4 -7
- package/dist/components/viewers/CodeViewer.svelte +17 -9
- package/dist/components/viewers/ImageViewer.svelte +6 -16
- package/dist/components/viewers/MarkdownViewer.svelte +8 -16
- package/dist/components/viewers/MediaViewer.svelte +6 -17
- package/dist/components/viewers/ModelViewer.svelte +4 -2
- package/dist/components/viewers/NotebookViewer.svelte +90 -40
- package/dist/components/viewers/PdfViewer.svelte +5 -3
- package/dist/components/viewers/RawViewer.svelte +4 -2
- package/dist/components/viewers/TableGrid.svelte +3 -2
- package/dist/components/viewers/ZarrMapViewer.svelte +334 -40
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +3 -8
- package/dist/components/viewers/ZarrViewer.svelte +459 -178
- package/dist/components/viewers/map/AttributeTable.svelte +1 -6
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +2 -6
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +96 -22
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +34 -0
- package/dist/file-icons/index.js +6 -0
- package/dist/i18n/ar.js +34 -0
- package/dist/i18n/en.js +34 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +16 -1
- package/dist/query/wasm.js +5 -4
- package/dist/storage/browser-cloud.d.ts +7 -0
- package/dist/storage/browser-cloud.js +74 -7
- package/dist/storage/providers.d.ts +53 -0
- package/dist/storage/providers.js +318 -0
- package/dist/stores/connections.svelte.js +8 -34
- package/dist/stores/files.svelte.d.ts +1 -6
- package/dist/stores/files.svelte.js +4 -36
- package/dist/stores/query-history.svelte.js +5 -28
- package/dist/stores/settings.svelte.d.ts +1 -0
- package/dist/stores/settings.svelte.js +11 -31
- package/dist/types.d.ts +2 -2
- package/dist/utils/clipboard.d.ts +13 -0
- package/dist/utils/clipboard.js +38 -0
- package/dist/utils/cloud-url.d.ts +27 -0
- package/dist/utils/cloud-url.js +61 -0
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.js +12 -0
- package/dist/utils/export.d.ts +22 -2
- package/dist/utils/export.js +35 -10
- package/dist/utils/file-sort.d.ts +20 -0
- package/dist/utils/file-sort.js +41 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.js +22 -0
- package/dist/utils/host-detection.js +78 -18
- package/dist/utils/local-storage.d.ts +16 -0
- package/dist/utils/local-storage.js +37 -0
- package/dist/utils/notebook.d.ts +59 -0
- package/dist/utils/notebook.js +211 -0
- package/dist/utils/parquet-metadata.js +1 -1
- package/dist/utils/pmtiles-tile.js +2 -1
- package/dist/utils/pmtiles.js +2 -1
- package/dist/utils/storage-url.d.ts +1 -1
- package/dist/utils/storage-url.js +82 -24
- package/dist/utils/url-state.js +2 -7
- package/dist/utils/url.d.ts +0 -2
- package/dist/utils/url.js +3 -29
- package/dist/utils/zarr.d.ts +60 -20
- package/dist/utils/zarr.js +450 -103
- package/package.json +66 -54
- package/dist/assets/favicon.svg +0 -17
- package/dist/components/CLAUDE.md +0 -44
- package/dist/components/viewers/CLAUDE.md +0 -60
- package/dist/file-icons/CLAUDE.md +0 -21
- package/dist/i18n/CLAUDE.md +0 -19
- package/dist/query/CLAUDE.md +0 -22
- package/dist/storage/CLAUDE.md +0 -23
- package/dist/stores/CLAUDE.md +0 -29
- package/dist/types/notebookjs.d.ts +0 -14
- package/dist/utils/CLAUDE.md +0 -54
- package/dist/utils/analytics.d.ts +0 -10
- package/dist/utils/analytics.js +0 -38
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import XIcon from '@lucide/svelte/icons/x';
|
|
3
|
+
import { formatValue } from '../../../utils/format.js';
|
|
3
4
|
|
|
4
5
|
let {
|
|
5
6
|
feature = null,
|
|
@@ -10,12 +11,6 @@ let {
|
|
|
10
11
|
visible?: boolean;
|
|
11
12
|
onClose?: () => void;
|
|
12
13
|
} = $props();
|
|
13
|
-
|
|
14
|
-
function formatValue(value: any): string {
|
|
15
|
-
if (value === null || value === undefined) return 'NULL';
|
|
16
|
-
if (typeof value === 'object') return JSON.stringify(value);
|
|
17
|
-
return String(value);
|
|
18
|
-
}
|
|
19
14
|
</script>
|
|
20
15
|
|
|
21
16
|
{#if visible && feature}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
ResizablePaneGroup
|
|
9
9
|
} from '../../ui/resizable/index.js';
|
|
10
10
|
import { t } from '../../../i18n/index.svelte.js';
|
|
11
|
+
import { formatFileSize } from '../../../utils/format.js';
|
|
11
12
|
import type { PmtilesMetadata } from '../../../utils/pmtiles';
|
|
12
13
|
import { highlightCode } from '../../../utils/shiki';
|
|
13
14
|
|
|
@@ -151,12 +152,7 @@ async function selectZoom(zoom: number) {
|
|
|
151
152
|
loadingEntries = false;
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
if (bytes === 0) return '0 B';
|
|
156
|
-
const units = ['B', 'KB', 'MB', 'GB'];
|
|
157
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
158
|
-
return `${(bytes / 1024 ** i).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
|
|
159
|
-
}
|
|
155
|
+
const formatBytes = formatFileSize;
|
|
160
156
|
|
|
161
157
|
const maxCount = $derived(Math.max(1, ...zoomSummaries.map((s) => s.count)));
|
|
162
158
|
const dedupRatio = $derived(
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
|
3
3
|
import XIcon from '@lucide/svelte/icons/x';
|
|
4
4
|
import type { PMTiles } from 'pmtiles';
|
|
5
|
+
import { onDestroy } from 'svelte';
|
|
5
6
|
import { t } from '../../../i18n/index.svelte.js';
|
|
7
|
+
import { formatFileSize } from '../../../utils/format.js';
|
|
6
8
|
import type { PmtilesMetadata } from '../../../utils/pmtiles';
|
|
7
9
|
import {
|
|
8
10
|
type DecodedTile,
|
|
9
11
|
decodeMvtTile,
|
|
10
12
|
layerHue,
|
|
11
|
-
tileMimeType
|
|
12
|
-
tileToImageUrl
|
|
13
|
+
tileMimeType
|
|
13
14
|
} from '../../../utils/pmtiles-tile.js';
|
|
14
15
|
import SvgTileRenderer from './SvgTileRenderer.svelte';
|
|
15
16
|
|
|
@@ -49,9 +50,11 @@ $effect(() => {
|
|
|
49
50
|
|
|
50
51
|
let tile = $state<DecodedTile | null>(null);
|
|
51
52
|
let rasterUrl = $state<string | null>(null);
|
|
53
|
+
let rasterDims = $state<{ width: number; height: number } | null>(null);
|
|
52
54
|
let loading = $state(false);
|
|
53
55
|
let error = $state<string | null>(null);
|
|
54
56
|
let tileSize = $state(0);
|
|
57
|
+
let rasterZoom = $state(1);
|
|
55
58
|
|
|
56
59
|
let selectedLayerName = $state<string | null>(null);
|
|
57
60
|
let selectedFeatureIdx = $state<number | null>(null);
|
|
@@ -82,7 +85,13 @@ async function fetchTile() {
|
|
|
82
85
|
loading = true;
|
|
83
86
|
error = null;
|
|
84
87
|
tile = null;
|
|
85
|
-
|
|
88
|
+
// Revoke previous blob URL to prevent memory leak
|
|
89
|
+
if (rasterUrl) {
|
|
90
|
+
URL.revokeObjectURL(rasterUrl);
|
|
91
|
+
rasterUrl = null;
|
|
92
|
+
}
|
|
93
|
+
rasterDims = null;
|
|
94
|
+
rasterZoom = 1;
|
|
86
95
|
selectedLayerName = null;
|
|
87
96
|
selectedFeatureIdx = null;
|
|
88
97
|
|
|
@@ -100,14 +109,15 @@ async function fetchTile() {
|
|
|
100
109
|
layerVisibility = vis;
|
|
101
110
|
}
|
|
102
111
|
} else {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
if (!url) {
|
|
112
|
+
const resp = await pmtiles.getZxy(inputZ, inputX, inputY);
|
|
113
|
+
if (!resp) {
|
|
106
114
|
error = t('pmtiles.tileNotFound');
|
|
107
115
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
const bytes = new Uint8Array(resp.data);
|
|
117
|
+
tileSize = bytes.length;
|
|
118
|
+
const mime = tileMimeType(metadata.format);
|
|
119
|
+
const blob = new Blob([bytes], { type: mime });
|
|
120
|
+
rasterUrl = URL.createObjectURL(blob);
|
|
111
121
|
}
|
|
112
122
|
}
|
|
113
123
|
} catch (e) {
|
|
@@ -154,12 +164,11 @@ const selectedFeatureKey = $derived(
|
|
|
154
164
|
: null
|
|
155
165
|
);
|
|
156
166
|
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
167
|
+
onDestroy(() => {
|
|
168
|
+
if (rasterUrl) URL.revokeObjectURL(rasterUrl);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const formatBytes = formatFileSize;
|
|
163
172
|
|
|
164
173
|
function formatValue(v: unknown): string {
|
|
165
174
|
if (v === null || v === undefined) return 'NULL';
|
|
@@ -354,13 +363,78 @@ function formatValue(v: unknown): string {
|
|
|
354
363
|
</div>
|
|
355
364
|
{:else if rasterUrl}
|
|
356
365
|
<!-- Raster tile preview -->
|
|
357
|
-
<div class="
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
<div class="relative min-w-0 flex-1 overflow-hidden bg-zinc-950">
|
|
367
|
+
<!-- Checkerboard background for transparency -->
|
|
368
|
+
<div
|
|
369
|
+
class="absolute inset-0 flex items-center justify-center overflow-auto p-4"
|
|
370
|
+
style="background-image: repeating-conic-gradient(#1a1a2e 0% 25%, #16162a 0% 50%); background-size: 16px 16px;"
|
|
371
|
+
>
|
|
372
|
+
<img
|
|
373
|
+
src={rasterUrl}
|
|
374
|
+
alt="Tile {inputZ}/{inputX}/{inputY}"
|
|
375
|
+
class="rounded border border-zinc-800"
|
|
376
|
+
style="image-rendering: pixelated; width: {(rasterDims?.width ?? 256) * rasterZoom}px; height: {(rasterDims?.height ?? 256) * rasterZoom}px;"
|
|
377
|
+
onload={(e) => {
|
|
378
|
+
const img = e.currentTarget as HTMLImageElement;
|
|
379
|
+
rasterDims = { width: img.naturalWidth, height: img.naturalHeight };
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
382
|
+
</div>
|
|
383
|
+
<!-- Zoom controls -->
|
|
384
|
+
<div class="absolute bottom-3 right-3 z-10 flex items-center gap-1 rounded bg-card/90 px-1.5 py-1 text-xs shadow backdrop-blur-sm">
|
|
385
|
+
<button
|
|
386
|
+
class="rounded px-1.5 py-0.5 text-card-foreground hover:bg-zinc-700/50 disabled:opacity-30"
|
|
387
|
+
onclick={() => (rasterZoom = Math.max(0.25, rasterZoom / 2))}
|
|
388
|
+
disabled={rasterZoom <= 0.25}
|
|
389
|
+
>−</button>
|
|
390
|
+
<span class="w-10 text-center tabular-nums text-muted-foreground">{Math.round(rasterZoom * 100)}%</span>
|
|
391
|
+
<button
|
|
392
|
+
class="rounded px-1.5 py-0.5 text-card-foreground hover:bg-zinc-700/50 disabled:opacity-30"
|
|
393
|
+
onclick={() => (rasterZoom = Math.min(8, rasterZoom * 2))}
|
|
394
|
+
disabled={rasterZoom >= 8}
|
|
395
|
+
>+</button>
|
|
396
|
+
<button
|
|
397
|
+
class="ms-1 rounded px-1.5 py-0.5 text-[10px] text-muted-foreground hover:bg-zinc-700/50 hover:text-card-foreground"
|
|
398
|
+
onclick={() => (rasterZoom = 1)}
|
|
399
|
+
>1:1</button>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
<!-- Raster tile info panel -->
|
|
404
|
+
<div
|
|
405
|
+
class="flex w-56 shrink-0 flex-col border-s border-zinc-200 lg:w-64 dark:border-zinc-800"
|
|
406
|
+
>
|
|
407
|
+
<div
|
|
408
|
+
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"
|
|
409
|
+
>
|
|
410
|
+
{t('pmtiles.tileInfo')}
|
|
411
|
+
</div>
|
|
412
|
+
<div class="flex-1 overflow-auto p-3">
|
|
413
|
+
<dl class="space-y-2 text-xs">
|
|
414
|
+
<div>
|
|
415
|
+
<dt class="text-muted-foreground">{t('pmtiles.tileCoordinates')}</dt>
|
|
416
|
+
<dd class="font-mono">{inputZ}/{inputX}/{inputY}</dd>
|
|
417
|
+
</div>
|
|
418
|
+
<div>
|
|
419
|
+
<dt class="text-muted-foreground">{t('mapInfo.tileFormat')}</dt>
|
|
420
|
+
<dd class="font-medium">{metadata.formatLabel}</dd>
|
|
421
|
+
</div>
|
|
422
|
+
{#if rasterDims}
|
|
423
|
+
<div>
|
|
424
|
+
<dt class="text-muted-foreground">{t('pmtiles.dimensions')}</dt>
|
|
425
|
+
<dd class="font-mono">{rasterDims.width} × {rasterDims.height}px</dd>
|
|
426
|
+
</div>
|
|
427
|
+
{/if}
|
|
428
|
+
<div>
|
|
429
|
+
<dt class="text-muted-foreground">{t('pmtiles.compressedSize')}</dt>
|
|
430
|
+
<dd>{formatBytes(tileSize)}</dd>
|
|
431
|
+
</div>
|
|
432
|
+
<div>
|
|
433
|
+
<dt class="text-muted-foreground">{t('mapInfo.tileCompression')}</dt>
|
|
434
|
+
<dd>{metadata.tileCompression}</dd>
|
|
435
|
+
</div>
|
|
436
|
+
</dl>
|
|
437
|
+
</div>
|
|
364
438
|
</div>
|
|
365
439
|
{:else}
|
|
366
440
|
<div
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants used across the application.
|
|
3
|
+
* Centralizes magic strings, numbers, and sets to prevent duplication.
|
|
4
|
+
*/
|
|
5
|
+
export declare const STORAGE_KEYS: {
|
|
6
|
+
readonly SETTINGS: "obstore-explore-settings";
|
|
7
|
+
readonly CONNECTIONS: "obstore-explore-connections";
|
|
8
|
+
readonly QUERY_HISTORY: "obstore-explore-query-history";
|
|
9
|
+
};
|
|
10
|
+
/** EPSG codes considered WGS84 (no reprojection needed). */
|
|
11
|
+
export declare const WGS84_CODES: Set<number>;
|
|
12
|
+
/** Default target CRS for ST_Transform. */
|
|
13
|
+
export declare const DEFAULT_TARGET_CRS = "EPSG:4326";
|
|
14
|
+
/** DuckDB-WASM initialization timeout in ms. */
|
|
15
|
+
export declare const DUCKDB_INIT_TIMEOUT_MS = 30000;
|
|
16
|
+
/** Maximum entries kept in query history. */
|
|
17
|
+
export declare const MAX_QUERY_HISTORY_ENTRIES = 200;
|
|
18
|
+
/** SQL preview truncation length (characters). */
|
|
19
|
+
export declare const SQL_PREVIEW_LENGTH = 120;
|
|
20
|
+
/** Extensions that represent "virtual files" — directories that open as viewers. */
|
|
21
|
+
export declare const VIEWER_DIR_EXTENSIONS: Set<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Golden-angle-based hue multiplier for evenly distributing layer colors.
|
|
24
|
+
* 137 ≈ 360 × (1 − 1/φ) where φ is the golden ratio.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LAYER_HUE_MULTIPLIER = 137;
|
|
27
|
+
/** Duration (ms) to show "Copied!" feedback before resetting. */
|
|
28
|
+
export declare const COPY_FEEDBACK_MS = 2000;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants used across the application.
|
|
3
|
+
* Centralizes magic strings, numbers, and sets to prevent duplication.
|
|
4
|
+
*/
|
|
5
|
+
// ── localStorage keys ──
|
|
6
|
+
export const STORAGE_KEYS = {
|
|
7
|
+
SETTINGS: 'obstore-explore-settings',
|
|
8
|
+
CONNECTIONS: 'obstore-explore-connections',
|
|
9
|
+
QUERY_HISTORY: 'obstore-explore-query-history'
|
|
10
|
+
};
|
|
11
|
+
// ── Geo / CRS constants ──
|
|
12
|
+
/** EPSG codes considered WGS84 (no reprojection needed). */
|
|
13
|
+
export const WGS84_CODES = new Set([4326, 4979]);
|
|
14
|
+
/** Default target CRS for ST_Transform. */
|
|
15
|
+
export const DEFAULT_TARGET_CRS = 'EPSG:4326';
|
|
16
|
+
// ── Query engine constants ──
|
|
17
|
+
/** DuckDB-WASM initialization timeout in ms. */
|
|
18
|
+
export const DUCKDB_INIT_TIMEOUT_MS = 30_000;
|
|
19
|
+
/** Maximum entries kept in query history. */
|
|
20
|
+
export const MAX_QUERY_HISTORY_ENTRIES = 200;
|
|
21
|
+
/** SQL preview truncation length (characters). */
|
|
22
|
+
export const SQL_PREVIEW_LENGTH = 120;
|
|
23
|
+
// ── File browser constants ──
|
|
24
|
+
/** Extensions that represent "virtual files" — directories that open as viewers. */
|
|
25
|
+
export const VIEWER_DIR_EXTENSIONS = new Set(['zarr', 'zr3']);
|
|
26
|
+
// ── PMTiles ──
|
|
27
|
+
/**
|
|
28
|
+
* Golden-angle-based hue multiplier for evenly distributing layer colors.
|
|
29
|
+
* 137 ≈ 360 × (1 − 1/φ) where φ is the golden ratio.
|
|
30
|
+
*/
|
|
31
|
+
export const LAYER_HUE_MULTIPLIER = 137;
|
|
32
|
+
// ── Clipboard ──
|
|
33
|
+
/** Duration (ms) to show "Copied!" feedback before resetting. */
|
|
34
|
+
export const COPY_FEEDBACK_MS = 2000;
|
package/dist/file-icons/index.js
CHANGED
|
@@ -1016,6 +1016,12 @@ const EXTENSIONS = {
|
|
|
1016
1016
|
* Extension may be with or without leading dot: ".parquet" or "parquet".
|
|
1017
1017
|
*/
|
|
1018
1018
|
export function getFileTypeInfo(extension, isDir = false) {
|
|
1019
|
+
if (isDir && extension) {
|
|
1020
|
+
const ext = extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
|
|
1021
|
+
const found = EXTENSIONS[ext];
|
|
1022
|
+
if (found)
|
|
1023
|
+
return found;
|
|
1024
|
+
}
|
|
1019
1025
|
if (isDir)
|
|
1020
1026
|
return FOLDER_INFO;
|
|
1021
1027
|
const ext = extension.startsWith('.') ? extension.toLowerCase() : `.${extension.toLowerCase()}`;
|
package/dist/i18n/ar.js
CHANGED
|
@@ -94,6 +94,8 @@ export const ar = {
|
|
|
94
94
|
'fileBrowser.modified': 'التعديل',
|
|
95
95
|
'fileBrowser.noMatch': 'لا توجد ملفات مطابقة لـ "{query}"',
|
|
96
96
|
'fileBrowser.empty': 'هذا المجلد فارغ',
|
|
97
|
+
'fileBrowser.zarrDetected': 'تم اكتشاف مخزن Zarr الإصدار {version}',
|
|
98
|
+
'fileBrowser.openAsZarr': 'فتح كـ Zarr',
|
|
97
99
|
// Search Bar
|
|
98
100
|
'searchBar.label': 'تصفية الملفات',
|
|
99
101
|
'searchBar.placeholder': 'تصفية الملفات...',
|
|
@@ -226,6 +228,27 @@ export const ar = {
|
|
|
226
228
|
'zarr.inspect': 'فحص',
|
|
227
229
|
'zarr.map': 'خريطة',
|
|
228
230
|
'zarr.loading': 'جارٍ تحميل بيانات Zarr...',
|
|
231
|
+
'zarr.nodes': 'عقدة',
|
|
232
|
+
'zarr.group': 'مجموعة',
|
|
233
|
+
'zarr.array': 'مصفوفة',
|
|
234
|
+
'zarr.selectGroup': 'اختر مجموعة',
|
|
235
|
+
'zarr.selectNode': 'اختر عقدة',
|
|
236
|
+
'zarr.storeAttributes': 'سمات المخزن',
|
|
237
|
+
'zarr.nodeType': 'النوع',
|
|
238
|
+
'zarr.shape': 'الشكل',
|
|
239
|
+
'zarr.dimensions': 'الأبعاد',
|
|
240
|
+
'zarr.dtype': 'نوع البيانات',
|
|
241
|
+
'zarr.fillValue': 'قيمة التعبئة',
|
|
242
|
+
'zarr.chunks': 'الأجزاء',
|
|
243
|
+
'zarr.chunkCount': 'عدد الأجزاء',
|
|
244
|
+
'zarr.chunkSize': 'حجم الجزء',
|
|
245
|
+
'zarr.uncompressed': 'غير مضغوط',
|
|
246
|
+
'zarr.codecs': 'المرمّزات',
|
|
247
|
+
'zarr.chunkKeys': 'مفاتيح الأجزاء',
|
|
248
|
+
'zarr.children': 'العناصر الفرعية',
|
|
249
|
+
'zarr.attributes': 'السمات',
|
|
250
|
+
'zarr.format': 'صيغة Zarr',
|
|
251
|
+
'zarr.contents': 'المحتويات',
|
|
229
252
|
// Archive Viewer
|
|
230
253
|
'archive.badge': 'أرشيف',
|
|
231
254
|
'archive.entries': 'عناصر',
|
|
@@ -314,6 +337,7 @@ export const ar = {
|
|
|
314
337
|
'map.layersPlural': 'طبقات',
|
|
315
338
|
'map.tiles': 'بلاطات',
|
|
316
339
|
'map.variable': 'المتغير:',
|
|
340
|
+
'map.noValue': 'لا توجد قيمة',
|
|
317
341
|
// Map info panel labels
|
|
318
342
|
'mapInfo.name': 'الاسم',
|
|
319
343
|
'mapInfo.description': 'الوصف',
|
|
@@ -376,6 +400,10 @@ export const ar = {
|
|
|
376
400
|
'pmtiles.featureProperties': 'خصائص المعلم',
|
|
377
401
|
'pmtiles.selectFeature': 'انقر على معلم لفحصه',
|
|
378
402
|
'pmtiles.rasterPreview': 'معاينة نقطية',
|
|
403
|
+
'pmtiles.tileInfo': 'معلومات البلاطة',
|
|
404
|
+
'pmtiles.tileCoordinates': 'الإحداثيات',
|
|
405
|
+
'pmtiles.dimensions': 'الأبعاد',
|
|
406
|
+
'pmtiles.compressedSize': 'الحجم المضغوط',
|
|
379
407
|
'pmtiles.noTileLoaded': 'أدخل Z/X/Y واجلب بلاطة',
|
|
380
408
|
'pmtiles.parent': 'الأب',
|
|
381
409
|
'pmtiles.featureCount': 'معالم',
|
|
@@ -399,6 +427,12 @@ export const ar = {
|
|
|
399
427
|
'notebook.loading': 'جارٍ تحميل الدفتر...',
|
|
400
428
|
'notebook.hideCode': 'إخفاء الكود',
|
|
401
429
|
'notebook.showCode': 'إظهار الكود',
|
|
430
|
+
// About
|
|
431
|
+
'about.title': 'حول objex',
|
|
432
|
+
'about.version': 'الإصدار {version}',
|
|
433
|
+
'about.license': 'الرخصة: CC BY 4.0',
|
|
434
|
+
'about.sourceCode': 'الكود المصدري',
|
|
435
|
+
'about.openSourceLicenses': 'تراخيص المصادر المفتوحة',
|
|
402
436
|
// Locale
|
|
403
437
|
'locale.toggle': 'اللغة'
|
|
404
438
|
};
|
package/dist/i18n/en.js
CHANGED
|
@@ -94,6 +94,8 @@ export const en = {
|
|
|
94
94
|
'fileBrowser.modified': 'Modified',
|
|
95
95
|
'fileBrowser.noMatch': 'No files matching "{query}"',
|
|
96
96
|
'fileBrowser.empty': 'This folder is empty',
|
|
97
|
+
'fileBrowser.zarrDetected': 'Zarr v{version} store detected',
|
|
98
|
+
'fileBrowser.openAsZarr': 'Open as Zarr',
|
|
97
99
|
// Search Bar
|
|
98
100
|
'searchBar.label': 'Filter files',
|
|
99
101
|
'searchBar.placeholder': 'Filter files...',
|
|
@@ -226,6 +228,27 @@ export const en = {
|
|
|
226
228
|
'zarr.inspect': 'Inspect',
|
|
227
229
|
'zarr.map': 'Map',
|
|
228
230
|
'zarr.loading': 'Loading Zarr metadata...',
|
|
231
|
+
'zarr.nodes': 'nodes',
|
|
232
|
+
'zarr.group': 'group',
|
|
233
|
+
'zarr.array': 'array',
|
|
234
|
+
'zarr.selectGroup': 'Select a group',
|
|
235
|
+
'zarr.selectNode': 'Select a node',
|
|
236
|
+
'zarr.storeAttributes': 'Store Attributes',
|
|
237
|
+
'zarr.nodeType': 'Type',
|
|
238
|
+
'zarr.shape': 'Shape',
|
|
239
|
+
'zarr.dimensions': 'Dimensions',
|
|
240
|
+
'zarr.dtype': 'Data Type',
|
|
241
|
+
'zarr.fillValue': 'Fill Value',
|
|
242
|
+
'zarr.chunks': 'Chunks',
|
|
243
|
+
'zarr.chunkCount': 'Chunk Count',
|
|
244
|
+
'zarr.chunkSize': 'Chunk Size',
|
|
245
|
+
'zarr.uncompressed': 'Uncompressed',
|
|
246
|
+
'zarr.codecs': 'Codecs',
|
|
247
|
+
'zarr.chunkKeys': 'Chunk Keys',
|
|
248
|
+
'zarr.children': 'Children',
|
|
249
|
+
'zarr.attributes': 'Attributes',
|
|
250
|
+
'zarr.format': 'Zarr Format',
|
|
251
|
+
'zarr.contents': 'Contents',
|
|
229
252
|
// Archive Viewer
|
|
230
253
|
'archive.badge': 'Archive',
|
|
231
254
|
'archive.entries': 'entries',
|
|
@@ -314,6 +337,7 @@ export const en = {
|
|
|
314
337
|
'map.layersPlural': 'layers',
|
|
315
338
|
'map.tiles': 'tiles',
|
|
316
339
|
'map.variable': 'Variable:',
|
|
340
|
+
'map.noValue': 'No data',
|
|
317
341
|
// Map info panel labels
|
|
318
342
|
'mapInfo.name': 'Name',
|
|
319
343
|
'mapInfo.description': 'Description',
|
|
@@ -376,6 +400,10 @@ export const en = {
|
|
|
376
400
|
'pmtiles.featureProperties': 'Feature Properties',
|
|
377
401
|
'pmtiles.selectFeature': 'Click a feature to inspect',
|
|
378
402
|
'pmtiles.rasterPreview': 'Raster preview',
|
|
403
|
+
'pmtiles.tileInfo': 'Tile Info',
|
|
404
|
+
'pmtiles.tileCoordinates': 'Coordinates',
|
|
405
|
+
'pmtiles.dimensions': 'Dimensions',
|
|
406
|
+
'pmtiles.compressedSize': 'Compressed Size',
|
|
379
407
|
'pmtiles.noTileLoaded': 'Enter Z/X/Y and fetch a tile',
|
|
380
408
|
'pmtiles.parent': 'Parent',
|
|
381
409
|
'pmtiles.featureCount': 'features',
|
|
@@ -399,6 +427,12 @@ export const en = {
|
|
|
399
427
|
'notebook.loading': 'Loading notebook...',
|
|
400
428
|
'notebook.hideCode': 'Hide Code',
|
|
401
429
|
'notebook.showCode': 'Show Code',
|
|
430
|
+
// About
|
|
431
|
+
'about.title': 'About objex',
|
|
432
|
+
'about.version': 'Version {version}',
|
|
433
|
+
'about.license': 'License: CC BY 4.0',
|
|
434
|
+
'about.sourceCode': 'Source Code',
|
|
435
|
+
'about.openSourceLicenses': 'Open Source Licenses',
|
|
402
436
|
// Locale
|
|
403
437
|
'locale.toggle': 'Language'
|
|
404
438
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
export { COPY_FEEDBACK_MS, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, MAX_QUERY_HISTORY_ENTRIES, SQL_PREVIEW_LENGTH, STORAGE_KEYS, VIEWER_DIR_EXTENSIONS, WGS84_CODES } from './constants.js';
|
|
1
2
|
export type { DuckDbReadFn, FileCategory, FileTypeInfo, ViewerKind } from './file-icons/index.js';
|
|
2
3
|
export { buildDuckDbSource, getDuckDbReadFn, getFileTypeInfo, getMimeType, getViewerKind, isCloudNativeFormat, isQueryable } from './file-icons/index.js';
|
|
3
4
|
export type { MapQueryHandle, MapQueryResult, QueryEngine, QueryHandle, QueryResult, SchemaField } from './query/engine.js';
|
|
4
5
|
export { QueryCancelledError } from './query/engine.js';
|
|
5
6
|
export type { ListPage, StorageAdapter } from './storage/adapter.js';
|
|
7
|
+
export type { ProviderDef, ProviderId, ProviderRegion } from './storage/providers.js';
|
|
8
|
+
export { buildEndpointFromTemplate, buildProviderBaseUrl, getProvider, isGcsProvider, PROVIDER_IDS, PROVIDERS } from './storage/providers.js';
|
|
6
9
|
export { UrlAdapter } from './storage/url-adapter.js';
|
|
7
10
|
export type { Connection, ConnectionConfig, FileEntry, Tab, Theme, WriteResult } from './types.js';
|
|
11
|
+
export { copyToClipboard, wireCodeCopyButtons } from './utils/clipboard.js';
|
|
12
|
+
export { getNativeScheme, resolveCloudUrl, safeDecodeURIComponent } from './utils/cloud-url.js';
|
|
8
13
|
export type { TypeCategory } from './utils/column-types.js';
|
|
9
14
|
export { classifyType, typeBadgeClass, typeColor, typeLabel } from './utils/column-types.js';
|
|
10
|
-
export {
|
|
15
|
+
export { handleLoadError } from './utils/error.js';
|
|
16
|
+
export { escapeCsvField, serializeToCsv, serializeToJson } from './utils/export.js';
|
|
17
|
+
export type { SortConfig, SortDirection, SortField } from './utils/file-sort.js';
|
|
18
|
+
export { sortFileEntries, toggleSortField } from './utils/file-sort.js';
|
|
19
|
+
export { formatDate, formatFileSize, formatValue, getFileExtension, jsonReplacerBigInt } from './utils/format.js';
|
|
11
20
|
export type { GeoArrowGeomType, GeoArrowResult } from './utils/geoarrow.js';
|
|
12
21
|
export { buildGeoArrowTables, normalizeGeomType } from './utils/geoarrow.js';
|
|
13
22
|
export type { HexRow } from './utils/hex.js';
|
|
14
23
|
export { generateHexDump } from './utils/hex.js';
|
|
24
|
+
export { loadFromStorage, persistToStorage } from './utils/local-storage.js';
|
|
25
|
+
export type { ParsedMarkdownDocument, SqlBlock } from './utils/markdown-sql.js';
|
|
26
|
+
export { interpolateTemplates, markSqlBlocks, parseMarkdownDocument } from './utils/markdown-sql.js';
|
|
15
27
|
export type { GeoColumnMeta, GeoParquetMeta, ParquetFileMetadata } from './utils/parquet-metadata.js';
|
|
16
28
|
export { extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, readParquetMetadata } from './utils/parquet-metadata.js';
|
|
17
29
|
export type { Defaults, ParsedStorageUrl, StorageProvider } from './utils/storage-url.js';
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
// Core types
|
|
2
|
+
// Constants
|
|
3
|
+
export { COPY_FEEDBACK_MS, DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, LAYER_HUE_MULTIPLIER, MAX_QUERY_HISTORY_ENTRIES, SQL_PREVIEW_LENGTH, STORAGE_KEYS, VIEWER_DIR_EXTENSIONS, WGS84_CODES } from './constants.js';
|
|
2
4
|
// File icons registry
|
|
3
5
|
export { buildDuckDbSource, getDuckDbReadFn, getFileTypeInfo, getMimeType, getViewerKind, isCloudNativeFormat, isQueryable } from './file-icons/index.js';
|
|
4
6
|
export { QueryCancelledError } from './query/engine.js';
|
|
7
|
+
export { buildEndpointFromTemplate, buildProviderBaseUrl, getProvider, isGcsProvider, PROVIDER_IDS, PROVIDERS } from './storage/providers.js';
|
|
5
8
|
export { UrlAdapter } from './storage/url-adapter.js';
|
|
9
|
+
// Clipboard
|
|
10
|
+
export { copyToClipboard, wireCodeCopyButtons } from './utils/clipboard.js';
|
|
11
|
+
// Cloud URL resolution
|
|
12
|
+
export { getNativeScheme, resolveCloudUrl, safeDecodeURIComponent } from './utils/cloud-url.js';
|
|
6
13
|
export { classifyType, typeBadgeClass, typeColor, typeLabel } from './utils/column-types.js';
|
|
7
|
-
|
|
14
|
+
// Error handling
|
|
15
|
+
export { handleLoadError } from './utils/error.js';
|
|
16
|
+
// Data export / serialization
|
|
17
|
+
export { escapeCsvField, serializeToCsv, serializeToJson } from './utils/export.js';
|
|
18
|
+
export { sortFileEntries, toggleSortField } from './utils/file-sort.js';
|
|
19
|
+
export { formatDate, formatFileSize, formatValue, getFileExtension, jsonReplacerBigInt } from './utils/format.js';
|
|
8
20
|
export { buildGeoArrowTables, normalizeGeomType } from './utils/geoarrow.js';
|
|
9
21
|
export { generateHexDump } from './utils/hex.js';
|
|
22
|
+
// localStorage helpers
|
|
23
|
+
export { loadFromStorage, persistToStorage } from './utils/local-storage.js';
|
|
24
|
+
export { interpolateTemplates, markSqlBlocks, parseMarkdownDocument } from './utils/markdown-sql.js';
|
|
10
25
|
export { extractBounds, extractEpsgFromGeoMeta, extractGeometryTypes, readParquetMetadata } from './utils/parquet-metadata.js';
|
|
11
26
|
export { describeParseResult, looksLikeUrl, parseStorageUrl } from './utils/storage-url.js';
|
|
12
27
|
// Utilities
|
package/dist/query/wasm.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_TARGET_CRS, DUCKDB_INIT_TIMEOUT_MS, WGS84_CODES } from '../constants.js';
|
|
1
2
|
import { buildDuckDbSource } from '../file-icons/index.js';
|
|
2
3
|
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
3
4
|
import { QueryCancelledError } from './engine';
|
|
@@ -7,7 +8,7 @@ const duckdb_wasm = `${CDN_BASE}/duckdb-mvp.wasm`;
|
|
|
7
8
|
const mvp_worker = `${CDN_BASE}/duckdb-browser-mvp.worker.js`;
|
|
8
9
|
const duckdb_wasm_eh = `${CDN_BASE}/duckdb-eh.wasm`;
|
|
9
10
|
const eh_worker = `${CDN_BASE}/duckdb-browser-eh.worker.js`;
|
|
10
|
-
const INIT_TIMEOUT_MS =
|
|
11
|
+
const INIT_TIMEOUT_MS = DUCKDB_INIT_TIMEOUT_MS; // Centralized in constants.ts
|
|
11
12
|
// ─── Performance & diagnostic logging ────────────────────────────────
|
|
12
13
|
const LOG_PREFIX = '[DuckDB]';
|
|
13
14
|
function log(...args) {
|
|
@@ -107,7 +108,7 @@ async function ensureGeoConversionDisabled(conn) {
|
|
|
107
108
|
await conn.query('SET enable_geoparquet_conversion = false');
|
|
108
109
|
}
|
|
109
110
|
// ─── CRS detection helpers ───────────────────────────────────────────
|
|
110
|
-
|
|
111
|
+
// WGS84_CODES imported from constants.ts
|
|
111
112
|
/** Extract EPSG code from a PROJJSON object. Returns null for WGS84/CRS84. */
|
|
112
113
|
function extractEpsgFromProjjson(crs) {
|
|
113
114
|
if (!crs)
|
|
@@ -358,7 +359,7 @@ export class WasmQueryEngine {
|
|
|
358
359
|
// always_xy := true forces lon/lat (x/y) axis order for both source and
|
|
359
360
|
// target, matching the GeoParquet convention regardless of CRS authority.
|
|
360
361
|
if (sourceCrs) {
|
|
361
|
-
geomExpr = `ST_Transform(${geomExpr}, '${sourceCrs}', '
|
|
362
|
+
geomExpr = `ST_Transform(${geomExpr}, '${sourceCrs}', '${DEFAULT_TARGET_CRS}', always_xy := true)`;
|
|
362
363
|
}
|
|
363
364
|
// ST_AsWKB needed — DuckDB GEOMETRY columns (from ST_ReadSHP, ST_Read)
|
|
364
365
|
// use an internal binary format, not WKB, even though Arrow reports Binary type.
|
|
@@ -761,7 +762,7 @@ export class WasmQueryEngine {
|
|
|
761
762
|
? `ST_GeomFromWKB(${quoted})`
|
|
762
763
|
: `ST_GeomFromGeoJSON(${quoted})`;
|
|
763
764
|
if (sourceCrs) {
|
|
764
|
-
geomExpr = `ST_Transform(${geomExpr}, '${sourceCrs}', '
|
|
765
|
+
geomExpr = `ST_Transform(${geomExpr}, '${sourceCrs}', '${DEFAULT_TARGET_CRS}', always_xy := true)`;
|
|
765
766
|
}
|
|
766
767
|
wkbExpr = `ST_AsWKB(${geomExpr})`;
|
|
767
768
|
}
|
|
@@ -20,6 +20,13 @@ export declare class BrowserCloudAdapter implements StorageAdapter {
|
|
|
20
20
|
get supportsWrite(): boolean;
|
|
21
21
|
private getConnection;
|
|
22
22
|
listPage(path: string, continuationToken?: string, pageSize?: number, signal?: AbortSignal): Promise<ListPage>;
|
|
23
|
+
/**
|
|
24
|
+
* List via GCS JSON API — works for public buckets without CORS configuration.
|
|
25
|
+
* Endpoint: `storage.googleapis.com/storage/v1/b/{bucket}/o`
|
|
26
|
+
*/
|
|
27
|
+
private listPageGcs;
|
|
28
|
+
/** List via S3-compatible XML API (AWS, R2, MinIO, Storj, etc.). */
|
|
29
|
+
private listPageS3;
|
|
23
30
|
list(path: string): Promise<FileEntry[]>;
|
|
24
31
|
read(path: string, offset?: number, length?: number, signal?: AbortSignal): Promise<Uint8Array>;
|
|
25
32
|
head(path: string, signal?: AbortSignal): Promise<FileEntry>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AwsClient } from 'aws4fetch';
|
|
2
2
|
import { connectionStore } from '../stores/connections.svelte.js';
|
|
3
3
|
import { credentialStore } from '../stores/credentials.svelte.js';
|
|
4
|
+
import { buildProviderBaseUrl, isGcsProvider } from './providers.js';
|
|
4
5
|
// --- Helpers ---
|
|
5
6
|
/** Extract the last path segment from an object key. */
|
|
6
7
|
function nameFromKey(key) {
|
|
@@ -17,15 +18,18 @@ function extensionFromName(name) {
|
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Build the base URL for S3-compatible API requests.
|
|
20
|
-
*
|
|
21
|
+
* Delegates to the provider registry for endpoint resolution.
|
|
21
22
|
*/
|
|
22
23
|
function buildBaseUrl(conn) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
return buildProviderBaseUrl(conn.provider, conn.endpoint, conn.bucket, conn.region);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build the GCS JSON API base URL for a bucket.
|
|
28
|
+
* JSON API includes CORS headers (`Access-Control-Allow-Origin: *`) for public data,
|
|
29
|
+
* unlike the S3-compatible XML API which requires bucket-level CORS configuration.
|
|
30
|
+
*/
|
|
31
|
+
function gcsJsonApiBase(bucket) {
|
|
32
|
+
return `https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o`;
|
|
29
33
|
}
|
|
30
34
|
/** Decode a possibly percent-encoded S3 key (some providers URL-encode non-ASCII in XML). */
|
|
31
35
|
function decodeKey(key) {
|
|
@@ -103,6 +107,69 @@ export class BrowserCloudAdapter {
|
|
|
103
107
|
}
|
|
104
108
|
async listPage(path, continuationToken, pageSize, signal) {
|
|
105
109
|
const conn = this.getConnection();
|
|
110
|
+
if (isGcsProvider(conn.provider, conn.endpoint) && conn.anonymous) {
|
|
111
|
+
return this.listPageGcs(conn, path, continuationToken, pageSize, signal);
|
|
112
|
+
}
|
|
113
|
+
return this.listPageS3(conn, path, continuationToken, pageSize, signal);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* List via GCS JSON API — works for public buckets without CORS configuration.
|
|
117
|
+
* Endpoint: `storage.googleapis.com/storage/v1/b/{bucket}/o`
|
|
118
|
+
*/
|
|
119
|
+
async listPageGcs(conn, path, pageToken, pageSize, signal) {
|
|
120
|
+
const url = gcsJsonApiBase(conn.bucket);
|
|
121
|
+
const params = new URLSearchParams({ delimiter: '/' });
|
|
122
|
+
if (path)
|
|
123
|
+
params.set('prefix', path);
|
|
124
|
+
if (pageToken)
|
|
125
|
+
params.set('pageToken', pageToken);
|
|
126
|
+
if (pageSize)
|
|
127
|
+
params.set('maxResults', String(pageSize));
|
|
128
|
+
const res = await fetch(`${url}?${params}`, { signal });
|
|
129
|
+
if (!res.ok) {
|
|
130
|
+
const body = await res.text().catch(() => '');
|
|
131
|
+
throw new Error(`GCS list failed (${res.status}): ${body || res.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
const json = await res.json();
|
|
134
|
+
const entries = [];
|
|
135
|
+
// Directories (prefixes)
|
|
136
|
+
if (Array.isArray(json.prefixes)) {
|
|
137
|
+
for (const prefix of json.prefixes) {
|
|
138
|
+
const dirName = nameFromKey(prefix);
|
|
139
|
+
entries.push({
|
|
140
|
+
name: decodeKey(dirName),
|
|
141
|
+
path: prefix,
|
|
142
|
+
is_dir: true,
|
|
143
|
+
size: 0,
|
|
144
|
+
modified: 0,
|
|
145
|
+
extension: dirName.endsWith('.zarr') || dirName.endsWith('.zr3') ? 'zarr' : ''
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Files (items)
|
|
150
|
+
if (Array.isArray(json.items)) {
|
|
151
|
+
for (const item of json.items) {
|
|
152
|
+
const key = item.name ?? '';
|
|
153
|
+
if (!key || key === path || key.endsWith('/'))
|
|
154
|
+
continue;
|
|
155
|
+
const name = decodeKey(nameFromKey(key));
|
|
156
|
+
const size = parseInt(item.size ?? '0', 10);
|
|
157
|
+
const lastMod = item.updated ?? '';
|
|
158
|
+
entries.push({
|
|
159
|
+
name,
|
|
160
|
+
path: key,
|
|
161
|
+
is_dir: false,
|
|
162
|
+
size,
|
|
163
|
+
modified: lastMod ? Date.parse(lastMod) || 0 : 0,
|
|
164
|
+
extension: extensionFromName(name)
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const nextToken = json.nextPageToken ?? undefined;
|
|
169
|
+
return { entries, continuationToken: nextToken, hasMore: !!nextToken };
|
|
170
|
+
}
|
|
171
|
+
/** List via S3-compatible XML API (AWS, R2, MinIO, Storj, etc.). */
|
|
172
|
+
async listPageS3(conn, path, continuationToken, pageSize, signal) {
|
|
106
173
|
const baseUrl = buildBaseUrl(conn);
|
|
107
174
|
const cloudFetch = this.getFetcher();
|
|
108
175
|
const params = new URLSearchParams({
|