@walkthru-earth/objex 1.3.0 → 1.4.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 +20 -12
- package/dist/components/browser/FileTreeSidebar.svelte +32 -17
- package/dist/components/layout/AboutSheet.svelte +5 -2
- package/dist/components/layout/ConnectionDialog.svelte +1 -1
- package/dist/components/layout/SettingsSheet.svelte +237 -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 +1 -1
- package/dist/components/layout/TabBar.svelte +2 -2
- 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 +2 -2
- package/dist/components/viewers/CodeViewer.svelte +31 -22
- package/dist/components/viewers/CogControls.svelte +338 -184
- package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
- package/dist/components/viewers/CogViewer.svelte +320 -119
- package/dist/components/viewers/CopcViewer.svelte +1 -1
- package/dist/components/viewers/FlatGeobufViewer.svelte +1 -1
- package/dist/components/viewers/GeoParquetMapViewer.svelte +6 -6
- package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/ImageViewer.svelte +2 -2
- package/dist/components/viewers/MarkdownViewer.svelte +12 -9
- package/dist/components/viewers/MediaViewer.svelte +2 -2
- package/dist/components/viewers/ModelViewer.svelte +1 -1
- package/dist/components/viewers/MultiCogViewer.svelte +467 -102
- package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/NotebookViewer.svelte +6 -3
- package/dist/components/viewers/PdfViewer.svelte +2 -2
- package/dist/components/viewers/PmtilesViewer.svelte +3 -6
- package/dist/components/viewers/RawViewer.svelte +6 -3
- package/dist/components/viewers/StacMapViewer.svelte +10 -2
- package/dist/components/viewers/StacMosaicViewer.svelte +1800 -362
- package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/StacTabViewer.svelte +24 -13
- package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/TableGrid.svelte +4 -4
- package/dist/components/viewers/TableStatusBar.svelte +1 -1
- package/dist/components/viewers/TableToolbar.svelte +1 -1
- package/dist/components/viewers/TableViewer.svelte +25 -17
- package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
- package/dist/components/viewers/ViewerRouter.svelte +16 -8
- package/dist/components/viewers/ZarrMapViewer.svelte +11 -9
- package/dist/components/viewers/ZarrViewer.svelte +4 -4
- 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 +1 -1
- package/dist/components/viewers/map/MapContainer.svelte +37 -11
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +1 -1
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +1 -1
- 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/file-icons/index.d.ts +1 -1
- package/dist/file-icons/index.js +1 -1
- package/dist/i18n/ar.js +110 -2
- package/dist/i18n/en.js +110 -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 +465 -0
- package/dist/query/wasm.d.ts +8 -0
- package/dist/query/wasm.js +304 -2
- package/dist/storage/presign.js +1 -1
- package/dist/storage/providers.js +5 -5
- 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 +200 -60
- package/dist/utils/cog.js +377 -114
- package/dist/utils/colormap-sprite.d.ts +0 -9
- package/dist/utils/colormap-sprite.js +0 -21
- package/dist/utils/deck.d.ts +16 -12
- package/dist/utils/deck.js +10 -4
- package/dist/utils/pmtiles-tile.js +2 -2
- 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/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
package/LICENSE
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
Copyright (c) 2026 walkthru.earth <hi@walkthru.earth>
|
|
2
|
+
|
|
3
|
+
This work is licensed under the Creative Commons Attribution 4.0 International License.
|
|
4
|
+
To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/
|
|
5
|
+
|
|
1
6
|
Attribution 4.0 International
|
|
2
7
|
|
|
3
8
|
=======================================================================
|
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ graph LR
|
|
|
23
23
|
- **Visualize** GeoParquet, GeoJSON, COG, PMTiles, FlatGeobuf, Zarr (incl. GeoZarr), STAC catalogs, and stac-geoparquet on maps (MapLibre + deck.gl)
|
|
24
24
|
- **View** 100+ file formats: code (30+ languages), Jupyter notebooks, PDF, 3D models, archives, media
|
|
25
25
|
- **Share** via URL -- `?url=<storage-url>#<view>` encodes full viewer state
|
|
26
|
+
- **Configure** without a rebuild -- bundled `config.json` (or remote `?config=<url>`) sets defaults, basemaps, and seed connections, with an in-app settings panel
|
|
26
27
|
- **i18n** -- English + Arabic with automatic RTL layout
|
|
27
28
|
- **Zero backend** -- everything runs client-side
|
|
28
29
|
|
|
@@ -58,9 +59,6 @@ npm install @walkthru-earth/objex
|
|
|
58
59
|
```ts
|
|
59
60
|
import { parseStorageUrl, formatFileSize } from '@walkthru-earth/objex';
|
|
60
61
|
import { UrlAdapter } from '@walkthru-earth/objex/storage';
|
|
61
|
-
import { parseWKB } from '@walkthru-earth/objex/utils/wkb';
|
|
62
|
-
import { buildGeoArrowTables } from '@walkthru-earth/objex/utils/geoarrow';
|
|
63
|
-
import { readParquetMetadata } from '@walkthru-earth/objex/utils/parquet-metadata';
|
|
64
62
|
import { getFileTypeInfo } from '@walkthru-earth/objex/file-icons';
|
|
65
63
|
```
|
|
66
64
|
|
|
@@ -95,20 +93,30 @@ Full per-module reference docs: [`packages/objex-utils/docs/`](packages/objex-ut
|
|
|
95
93
|
|
|
96
94
|
| Export path | What |
|
|
97
95
|
|-------------|------|
|
|
98
|
-
| `@walkthru-earth/objex` | All types, utils, storage, query engine |
|
|
96
|
+
| `@walkthru-earth/objex` | All types, pure utils, storage, query engine |
|
|
99
97
|
| `./storage` | `StorageAdapter`, `UrlAdapter` |
|
|
100
98
|
| `./query` | `QueryEngine`, `QueryCancelledError` |
|
|
101
|
-
| `./utils/wkb` | `parseWKB`, `toBinary`, `findGeoColumn` |
|
|
102
|
-
| `./utils/geoarrow` | `buildGeoArrowTables`, `normalizeGeomType` |
|
|
103
|
-
| `./utils/storage-url` | `parseStorageUrl`, `looksLikeUrl` |
|
|
104
|
-
| `./utils/parquet-metadata` | `readParquetMetadata`, `extractEpsgFromGeoMeta` |
|
|
105
|
-
| `./utils/format` | `formatFileSize`, `formatDate`, `formatValue`, `getFileExtension`, `jsonReplacerBigInt` |
|
|
106
|
-
| `./utils/hex` | `generateHexDump` |
|
|
107
|
-
| `./utils/column-types` | `classifyType`, `typeColor`, `typeBadgeClass` |
|
|
108
99
|
| `./file-icons` | `getFileTypeInfo`, `getDuckDbReadFn`, `getViewerKind` |
|
|
109
100
|
| `./types` | `FileEntry`, `Connection`, `Tab`, `WriteResult`, `Theme` |
|
|
110
101
|
|
|
111
|
-
The
|
|
102
|
+
The pure utilities live in `@walkthru-earth/objex-utils` and are also re-exported from the package root, so `parseStorageUrl`, `parseWKB`, `buildGeoArrowTables`, `readParquetMetadata`, `formatFileSize`, `generateHexDump`, `classifyType`, and the rest are importable straight from `@walkthru-earth/objex`. The root export also includes `copyToClipboard`, `handleLoadError`, the stac-geoparquet helpers (`isStacGeoparquetSchema`, `stacRowToItem`, `flattenStacBbox`, `pickStacPrimaryAsset`, `resolveStacAssetHref`), and shared constants (`WGS84_CODES`, `STORAGE_KEYS`, `DEFAULT_TARGET_CRS`, etc.).
|
|
103
|
+
|
|
104
|
+
## Configuration
|
|
105
|
+
|
|
106
|
+
objex reads a bundled `static/config.json` at startup, so a host can customize the app without rebuilding. Pass `?config=<url>` to load a remote config that overrides the bundled one.
|
|
107
|
+
|
|
108
|
+
`config.json` sets the default theme and language, query and mosaic row limits, the basemap list with a per-theme default basemap, and seed connections that load on first visit. The `ui` block toggles whether the connection rail, file tree, and settings panel are shown.
|
|
109
|
+
|
|
110
|
+
Several of these are also reachable as query params, handy for embedding and deep links.
|
|
111
|
+
|
|
112
|
+
| Param | Effect |
|
|
113
|
+
|-------|--------|
|
|
114
|
+
| `?config=<url>` | Load a remote `config.json` instead of the bundled one |
|
|
115
|
+
| `?panel=settings` | Open the settings panel on load |
|
|
116
|
+
| `?rail=hide` / `?rail=show` | Hide or show the connection rail |
|
|
117
|
+
| `?tree=hide` / `?tree=show` | Hide or show the file tree |
|
|
118
|
+
|
|
119
|
+
Users can change theme, language, query limit, and basemap from the in-app settings panel (gear icon). Their changes persist locally and take precedence over config defaults, while settings they never touched keep following the config.
|
|
112
120
|
|
|
113
121
|
## Quick Start (Development)
|
|
114
122
|
|
|
@@ -9,6 +9,7 @@ import LinkIcon from '@lucide/svelte/icons/link';
|
|
|
9
9
|
import Loader2Icon from '@lucide/svelte/icons/loader-2';
|
|
10
10
|
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
|
|
11
11
|
import SearchIcon from '@lucide/svelte/icons/search';
|
|
12
|
+
import { getNativeScheme } from '@walkthru-earth/objex-utils';
|
|
12
13
|
import * as ContextMenu from '../ui/context-menu/index.js';
|
|
13
14
|
import { VIEWER_DIR_EXTENSIONS } from '../../constants.js';
|
|
14
15
|
import FileTypeIcon from '../../file-icons/FileTypeIcon.svelte';
|
|
@@ -17,7 +18,7 @@ import { getAdapter } from '../../storage/index.js';
|
|
|
17
18
|
import { browser } from '../../stores/browser.svelte.js';
|
|
18
19
|
import { tabs } from '../../stores/tabs.svelte.js';
|
|
19
20
|
import type { Connection, FileEntry } from '../../types.js';
|
|
20
|
-
import {
|
|
21
|
+
import { buildHttpsUrlForConnection } from '../../utils/signed-url.js';
|
|
21
22
|
import { syncUrlParam } from '../../utils/url-state.js';
|
|
22
23
|
import { detectZarrMarkers } from '../../utils/zarr.js';
|
|
23
24
|
import { openZarrTab } from '../../utils/zarr-tab.js';
|
|
@@ -48,6 +49,8 @@ let rootLoading = $state(true);
|
|
|
48
49
|
let rootLoadingMore = $state(false);
|
|
49
50
|
let rootContinuationToken = $state<string | undefined>();
|
|
50
51
|
let rootHasMore = $state(false);
|
|
52
|
+
/** Set when the root listing fails: 'cors' for a blocked fetch, 'generic' otherwise. */
|
|
53
|
+
let rootError = $state<'cors' | 'generic' | null>(null);
|
|
51
54
|
let filterQuery = $state('');
|
|
52
55
|
let scrollEl = $state<HTMLElement>();
|
|
53
56
|
/** Paths of directories detected as Zarr stores (by marker files in children). */
|
|
@@ -222,15 +225,9 @@ function handleChevronClick(e: MouseEvent, node: TreeNode) {
|
|
|
222
225
|
|
|
223
226
|
// ---------- URL builders ----------
|
|
224
227
|
|
|
225
|
-
/** Build HTTPS URL for a file path. */
|
|
228
|
+
/** Build a provider-aware HTTPS URL for a file path (percent-encoded for copy). */
|
|
226
229
|
function buildHttpUrl(path: string): string {
|
|
227
|
-
|
|
228
|
-
if (conn.endpoint) {
|
|
229
|
-
const base = conn.endpoint.replace(/\/$/, '');
|
|
230
|
-
return `${base}/${conn.bucket}/${encodeKeyPath(path)}`;
|
|
231
|
-
}
|
|
232
|
-
// Default AWS S3
|
|
233
|
-
return `https://s3.${conn.region}.amazonaws.com/${conn.bucket}/${encodeKeyPath(path)}`;
|
|
230
|
+
return buildHttpsUrlForConnection(connection, path, { encode: true });
|
|
234
231
|
}
|
|
235
232
|
|
|
236
233
|
/** Build provider-native URI (s3://, gs://, r2://, az://). */
|
|
@@ -240,14 +237,8 @@ function buildNativeUri(path: string): string {
|
|
|
240
237
|
return `${scheme}://${conn.bucket}/${path}`;
|
|
241
238
|
}
|
|
242
239
|
|
|
243
|
-
// getNativeScheme imported from
|
|
244
|
-
|
|
245
|
-
function encodeKeyPath(key: string): string {
|
|
246
|
-
return key
|
|
247
|
-
.split('/')
|
|
248
|
-
.map((s) => encodeURIComponent(s))
|
|
249
|
-
.join('/');
|
|
250
|
-
}
|
|
240
|
+
// getNativeScheme imported from @walkthru-earth/objex-utils (cloud-url.ts)
|
|
241
|
+
// buildHttpsUrlForConnection imported from utils/signed-url (provider-aware base)
|
|
251
242
|
|
|
252
243
|
// ---------- Clipboard ----------
|
|
253
244
|
|
|
@@ -368,6 +359,19 @@ async function expandToPath(path: string) {
|
|
|
368
359
|
|
|
369
360
|
// ---------- Root loading ----------
|
|
370
361
|
|
|
362
|
+
/**
|
|
363
|
+
* Browsers collapse a CORS block and a true network failure into the same
|
|
364
|
+
* opaque `TypeError` ('Failed to fetch' in Chrome, 'NetworkError when
|
|
365
|
+
* attempting to fetch resource' in Firefox, 'Load failed' in Safari), so this
|
|
366
|
+
* can't tell them apart. For a cloud bucket the dominant cause is a missing
|
|
367
|
+
* browser CORS policy, so we lead the message with that.
|
|
368
|
+
*/
|
|
369
|
+
function isLikelyCorsError(err: unknown): boolean {
|
|
370
|
+
const e = err as { name?: string; message?: string } | null;
|
|
371
|
+
if (!e || e.name !== 'TypeError' || typeof e.message !== 'string') return false;
|
|
372
|
+
return /failed to fetch|networkerror|load failed/i.test(e.message);
|
|
373
|
+
}
|
|
374
|
+
|
|
371
375
|
// Load root entries when connection changes
|
|
372
376
|
$effect(() => {
|
|
373
377
|
const _connId = connection.id;
|
|
@@ -376,6 +380,7 @@ $effect(() => {
|
|
|
376
380
|
|
|
377
381
|
async function loadRoot() {
|
|
378
382
|
rootLoading = true;
|
|
383
|
+
rootError = null;
|
|
379
384
|
rootContinuationToken = undefined;
|
|
380
385
|
rootHasMore = false;
|
|
381
386
|
detectedZarrPaths = new Set();
|
|
@@ -399,6 +404,7 @@ async function loadRoot() {
|
|
|
399
404
|
}
|
|
400
405
|
} catch (err) {
|
|
401
406
|
console.error('[FileTree] Error loading root:', err);
|
|
407
|
+
rootError = isLikelyCorsError(err) ? 'cors' : 'generic';
|
|
402
408
|
} finally {
|
|
403
409
|
rootLoading = false;
|
|
404
410
|
}
|
|
@@ -456,6 +462,15 @@ async function loadMoreRoot() {
|
|
|
456
462
|
<div class="flex items-center justify-center py-8">
|
|
457
463
|
<Loader2Icon class="size-4 animate-spin text-muted-foreground" />
|
|
458
464
|
</div>
|
|
465
|
+
{:else if rootError}
|
|
466
|
+
<div class="space-y-1.5 px-3 py-6 text-center text-xs">
|
|
467
|
+
<p class="font-medium text-foreground">
|
|
468
|
+
{rootError === 'cors' ? t('fileTree.corsError') : t('fileTree.loadError')}
|
|
469
|
+
</p>
|
|
470
|
+
<p class="text-muted-foreground">
|
|
471
|
+
{rootError === 'cors' ? t('fileTree.corsHint') : t('fileTree.loadErrorHint')}
|
|
472
|
+
</p>
|
|
473
|
+
</div>
|
|
459
474
|
{:else if filteredNodes.length === 0}
|
|
460
475
|
<div class="px-3 py-6 text-center text-xs text-muted-foreground">
|
|
461
476
|
{filterQuery ? t('fileTree.noMatch') : t('fileTree.emptyBucket')}
|
|
@@ -9,7 +9,6 @@ declare const __THIRD_PARTY_LICENSES__: {
|
|
|
9
9
|
<script lang="ts">
|
|
10
10
|
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
|
11
11
|
import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
|
|
12
|
-
import GithubIcon from '@lucide/svelte/icons/github';
|
|
13
12
|
import {
|
|
14
13
|
Sheet,
|
|
15
14
|
SheetContent,
|
|
@@ -75,7 +74,11 @@ $effect(() => {
|
|
|
75
74
|
rel="noopener noreferrer"
|
|
76
75
|
class="inline-flex items-center gap-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
|
77
76
|
>
|
|
78
|
-
<
|
|
77
|
+
<svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
78
|
+
<path
|
|
79
|
+
d="M12 .5C5.37.5 0 5.78 0 12.292c0 5.211 3.438 9.63 8.205 11.188.6.111.82-.254.82-.567 0-.279-.01-1.02-.015-2.002-3.338.711-4.042-1.582-4.042-1.582-.546-1.361-1.333-1.724-1.333-1.724-1.089-.731.083-.716.083-.716 1.205.082 1.84 1.215 1.84 1.215 1.07 1.797 2.807 1.278 3.492.977.108-.76.42-1.279.762-1.573-2.665-.295-5.466-1.309-5.466-5.827 0-1.287.465-2.339 1.235-3.164-.135-.295-.54-1.494.105-3.116 0 0 1.005-.31 3.3 1.209.957-.262 1.98-.392 3-.397 1.02.005 2.04.135 3 .397 2.28-1.519 3.285-1.209 3.285-1.209.645 1.622.24 2.821.12 3.116.765.825 1.23 1.877 1.23 3.164 0 4.53-2.805 5.527-5.475 5.817.42.354.81 1.077.81 2.182 0 1.578-.015 2.846-.015 3.229 0 .309.21.678.825.561C20.565 21.917 24 17.495 24 12.292 24 5.78 18.63.5 12 .5z"
|
|
80
|
+
/>
|
|
81
|
+
</svg>
|
|
79
82
|
{t('about.sourceCode')}
|
|
80
83
|
</a>
|
|
81
84
|
|
|
@@ -10,6 +10,7 @@ import LockIcon from '@lucide/svelte/icons/lock';
|
|
|
10
10
|
import PlugZapIcon from '@lucide/svelte/icons/plug-zap';
|
|
11
11
|
import ShieldIcon from '@lucide/svelte/icons/shield';
|
|
12
12
|
import XIcon from '@lucide/svelte/icons/x';
|
|
13
|
+
import { describeParseResult, looksLikeUrl, parseStorageUrl } from '@walkthru-earth/objex-utils';
|
|
13
14
|
import { Button } from '../ui/button/index.js';
|
|
14
15
|
import { Input } from '../ui/input/index.js';
|
|
15
16
|
import {
|
|
@@ -33,7 +34,6 @@ import {
|
|
|
33
34
|
} from '../../storage/providers.js';
|
|
34
35
|
import { connections, DuplicateConnectionError } from '../../stores/connections.svelte.js';
|
|
35
36
|
import type { Connection, ConnectionConfig } from '../../types.js';
|
|
36
|
-
import { describeParseResult, looksLikeUrl, parseStorageUrl } from '../../utils/storage-url.js';
|
|
37
37
|
|
|
38
38
|
interface Props {
|
|
39
39
|
open: boolean;
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import CheckIcon from '@lucide/svelte/icons/check';
|
|
3
|
+
import CopyIcon from '@lucide/svelte/icons/copy';
|
|
4
|
+
import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
|
|
5
|
+
import {
|
|
6
|
+
Sheet,
|
|
7
|
+
SheetContent,
|
|
8
|
+
SheetDescription,
|
|
9
|
+
SheetHeader,
|
|
10
|
+
SheetTitle
|
|
11
|
+
} from '../ui/sheet/index.js';
|
|
12
|
+
import { t } from '../../i18n/index.svelte.js';
|
|
13
|
+
import { appConfig } from '../../stores/config.svelte.js';
|
|
14
|
+
import { settings } from '../../stores/settings.svelte.js';
|
|
15
|
+
import type { Theme } from '../../types.js';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
open: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let { open = $bindable(false) }: Props = $props();
|
|
22
|
+
|
|
23
|
+
const themes: Theme[] = ['light', 'dark', 'system'];
|
|
24
|
+
|
|
25
|
+
let copied = $state(false);
|
|
26
|
+
|
|
27
|
+
function buildExportConfig(): string {
|
|
28
|
+
const cfg = appConfig.value;
|
|
29
|
+
const exported = {
|
|
30
|
+
defaults: {
|
|
31
|
+
theme: settings.theme,
|
|
32
|
+
locale: settings.locale,
|
|
33
|
+
featureLimit: settings.featureLimit,
|
|
34
|
+
mosaicItemLimit: settings.mosaicItemLimit
|
|
35
|
+
},
|
|
36
|
+
ui: {
|
|
37
|
+
showConnectionRail: settings.showConnectionRail,
|
|
38
|
+
showFileTree: settings.showFileTree,
|
|
39
|
+
showSettings: cfg.ui.showSettings
|
|
40
|
+
},
|
|
41
|
+
basemaps: cfg.basemaps,
|
|
42
|
+
defaultBasemap: cfg.defaultBasemap,
|
|
43
|
+
connections: cfg.connections
|
|
44
|
+
};
|
|
45
|
+
return JSON.stringify(exported, null, 2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function copyConfig() {
|
|
49
|
+
await navigator.clipboard.writeText(buildExportConfig());
|
|
50
|
+
copied = true;
|
|
51
|
+
setTimeout(() => (copied = false), 1500);
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<Sheet bind:open>
|
|
56
|
+
<SheetContent side="bottom" class="max-h-[85vh] sm:mx-auto sm:max-w-lg sm:rounded-t-lg">
|
|
57
|
+
<SheetHeader>
|
|
58
|
+
<SheetTitle>{t('settings.title')}</SheetTitle>
|
|
59
|
+
<SheetDescription class="sr-only">{t('settings.title')}</SheetDescription>
|
|
60
|
+
</SheetHeader>
|
|
61
|
+
|
|
62
|
+
<div class="flex flex-col gap-6 overflow-y-auto px-4 py-6 sm:px-6">
|
|
63
|
+
{#if appConfig.status === 'custom'}
|
|
64
|
+
<div class="rounded-md bg-primary/10 px-3 py-1.5 text-xs text-primary">
|
|
65
|
+
{t('settings.customConfig')}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
|
|
69
|
+
<!-- Appearance -->
|
|
70
|
+
<section class="flex flex-col gap-2">
|
|
71
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
72
|
+
{t('settings.appearance')}
|
|
73
|
+
</h3>
|
|
74
|
+
<div class="flex gap-2">
|
|
75
|
+
{#each themes as th}
|
|
76
|
+
<button
|
|
77
|
+
class="flex-1 rounded-md border px-3 py-1.5 text-sm transition-colors {settings.theme ===
|
|
78
|
+
th
|
|
79
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
80
|
+
: 'border-border text-muted-foreground hover:text-foreground'}"
|
|
81
|
+
onclick={() => settings.setTheme(th)}
|
|
82
|
+
>
|
|
83
|
+
{t(`theme.${th}`)}
|
|
84
|
+
</button>
|
|
85
|
+
{/each}
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<!-- Language -->
|
|
90
|
+
<section class="flex flex-col gap-2">
|
|
91
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
92
|
+
{t('settings.language')}
|
|
93
|
+
</h3>
|
|
94
|
+
<div class="flex gap-2">
|
|
95
|
+
<button
|
|
96
|
+
class="flex-1 rounded-md border px-3 py-1.5 text-sm transition-colors {settings.locale ===
|
|
97
|
+
'en'
|
|
98
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
99
|
+
: 'border-border text-muted-foreground hover:text-foreground'}"
|
|
100
|
+
onclick={() => settings.setLocale('en')}
|
|
101
|
+
>
|
|
102
|
+
English
|
|
103
|
+
</button>
|
|
104
|
+
<button
|
|
105
|
+
class="flex-1 rounded-md border px-3 py-1.5 text-sm transition-colors {settings.locale ===
|
|
106
|
+
'ar'
|
|
107
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
108
|
+
: 'border-border text-muted-foreground hover:text-foreground'}"
|
|
109
|
+
onclick={() => settings.setLocale('ar')}
|
|
110
|
+
>
|
|
111
|
+
العربية
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
<!-- Map -->
|
|
117
|
+
{#if appConfig.value.basemaps.length > 0}
|
|
118
|
+
<section class="flex flex-col gap-2">
|
|
119
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
120
|
+
{t('settings.map')}
|
|
121
|
+
</h3>
|
|
122
|
+
<div class="flex flex-wrap gap-2">
|
|
123
|
+
<button
|
|
124
|
+
class="rounded-md border px-3 py-1.5 text-sm transition-colors {settings.basemapId ===
|
|
125
|
+
undefined
|
|
126
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
127
|
+
: 'border-border text-muted-foreground hover:text-foreground'}"
|
|
128
|
+
onclick={() => settings.setBasemap(undefined)}
|
|
129
|
+
>
|
|
130
|
+
{t('settings.basemapAuto')}
|
|
131
|
+
</button>
|
|
132
|
+
{#each appConfig.value.basemaps as bm (bm.id)}
|
|
133
|
+
<button
|
|
134
|
+
class="rounded-md border px-3 py-1.5 text-sm transition-colors {settings.basemapId ===
|
|
135
|
+
bm.id
|
|
136
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
137
|
+
: 'border-border text-muted-foreground hover:text-foreground'}"
|
|
138
|
+
onclick={() => settings.setBasemap(bm.id)}
|
|
139
|
+
>
|
|
140
|
+
{bm.label}
|
|
141
|
+
</button>
|
|
142
|
+
{/each}
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
{/if}
|
|
146
|
+
|
|
147
|
+
<!-- Data -->
|
|
148
|
+
<section class="flex flex-col gap-3">
|
|
149
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
150
|
+
{t('settings.data')}
|
|
151
|
+
</h3>
|
|
152
|
+
<label class="flex flex-col gap-1 text-sm">
|
|
153
|
+
<span>{t('settings.rowLimit')}</span>
|
|
154
|
+
<input
|
|
155
|
+
type="number"
|
|
156
|
+
min="1"
|
|
157
|
+
class="rounded-md border border-border bg-background px-2 py-1 text-sm"
|
|
158
|
+
value={settings.featureLimit}
|
|
159
|
+
onchange={(e) => settings.setFeatureLimit(Number(e.currentTarget.value))}
|
|
160
|
+
/>
|
|
161
|
+
<span class="text-xs text-muted-foreground">{t('settings.rowLimitHelp')}</span>
|
|
162
|
+
</label>
|
|
163
|
+
<label class="flex flex-col gap-1 text-sm">
|
|
164
|
+
<span>{t('settings.mosaicLimit')}</span>
|
|
165
|
+
<input
|
|
166
|
+
type="number"
|
|
167
|
+
min="1"
|
|
168
|
+
class="rounded-md border border-border bg-background px-2 py-1 text-sm"
|
|
169
|
+
value={settings.mosaicItemLimit}
|
|
170
|
+
onchange={(e) => settings.setMosaicItemLimit(Number(e.currentTarget.value))}
|
|
171
|
+
/>
|
|
172
|
+
<span class="text-xs text-muted-foreground">{t('settings.mosaicLimitHelp')}</span>
|
|
173
|
+
</label>
|
|
174
|
+
</section>
|
|
175
|
+
|
|
176
|
+
<!-- Interface -->
|
|
177
|
+
<section class="flex flex-col gap-3">
|
|
178
|
+
<h3 class="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
|
|
179
|
+
{t('settings.interface')}
|
|
180
|
+
</h3>
|
|
181
|
+
<label class="flex items-center justify-between gap-2 text-sm">
|
|
182
|
+
<span class="flex flex-col">
|
|
183
|
+
<span>{t('settings.showConnectionRail')}</span>
|
|
184
|
+
{#if settings.railLockedByParam}
|
|
185
|
+
<span class="text-xs text-muted-foreground">{t('settings.lockedByLink')}</span>
|
|
186
|
+
{/if}
|
|
187
|
+
</span>
|
|
188
|
+
<input
|
|
189
|
+
type="checkbox"
|
|
190
|
+
class="size-4"
|
|
191
|
+
disabled={settings.railLockedByParam}
|
|
192
|
+
checked={settings.showConnectionRail}
|
|
193
|
+
onchange={(e) => settings.setShowConnectionRail(e.currentTarget.checked)}
|
|
194
|
+
/>
|
|
195
|
+
</label>
|
|
196
|
+
<label class="flex items-center justify-between gap-2 text-sm">
|
|
197
|
+
<span class="flex flex-col">
|
|
198
|
+
<span>{t('settings.showFileTree')}</span>
|
|
199
|
+
{#if settings.treeLockedByParam}
|
|
200
|
+
<span class="text-xs text-muted-foreground">{t('settings.lockedByLink')}</span>
|
|
201
|
+
{/if}
|
|
202
|
+
</span>
|
|
203
|
+
<input
|
|
204
|
+
type="checkbox"
|
|
205
|
+
class="size-4"
|
|
206
|
+
disabled={settings.treeLockedByParam}
|
|
207
|
+
checked={settings.showFileTree}
|
|
208
|
+
onchange={(e) => settings.setShowFileTree(e.currentTarget.checked)}
|
|
209
|
+
/>
|
|
210
|
+
</label>
|
|
211
|
+
</section>
|
|
212
|
+
|
|
213
|
+
<!-- Footer actions -->
|
|
214
|
+
<div class="flex items-center justify-between gap-2 border-t pt-4">
|
|
215
|
+
<button
|
|
216
|
+
class="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
|
217
|
+
onclick={() => settings.reset()}
|
|
218
|
+
>
|
|
219
|
+
<RotateCcwIcon class="size-3.5" />
|
|
220
|
+
{t('settings.reset')}
|
|
221
|
+
</button>
|
|
222
|
+
<button
|
|
223
|
+
class="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
|
224
|
+
onclick={copyConfig}
|
|
225
|
+
>
|
|
226
|
+
{#if copied}
|
|
227
|
+
<CheckIcon class="size-3.5" />
|
|
228
|
+
{t('settings.copied')}
|
|
229
|
+
{:else}
|
|
230
|
+
<CopyIcon class="size-3.5" />
|
|
231
|
+
{t('settings.copyConfig')}
|
|
232
|
+
{/if}
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
</SheetContent>
|
|
237
|
+
</Sheet>
|
|
@@ -4,7 +4,9 @@ import DatabaseIcon from '@lucide/svelte/icons/database';
|
|
|
4
4
|
import GlobeIcon from '@lucide/svelte/icons/globe';
|
|
5
5
|
import PencilIcon from '@lucide/svelte/icons/pencil';
|
|
6
6
|
import PlusIcon from '@lucide/svelte/icons/plus';
|
|
7
|
+
import SettingsIcon from '@lucide/svelte/icons/settings';
|
|
7
8
|
import TrashIcon from '@lucide/svelte/icons/trash-2';
|
|
9
|
+
import { type DetectedHost, detectHostBucket, parseStorageUrl } from '@walkthru-earth/objex-utils';
|
|
8
10
|
import {
|
|
9
11
|
ContextMenu,
|
|
10
12
|
ContextMenuContent,
|
|
@@ -21,18 +23,21 @@ import {
|
|
|
21
23
|
} from '../ui/tooltip/index.js';
|
|
22
24
|
import { t } from '../../i18n/index.svelte.js';
|
|
23
25
|
import { browser } from '../../stores/browser.svelte.js';
|
|
26
|
+
import { appConfig } from '../../stores/config.svelte.js';
|
|
24
27
|
import { connections } from '../../stores/connections.svelte.js';
|
|
25
28
|
import { credentialStore, loadFromNative } from '../../stores/credentials.svelte.js';
|
|
26
29
|
import { eagerUrlTabId, tabs } from '../../stores/tabs.svelte.js';
|
|
27
30
|
import type { Connection } from '../../types.js';
|
|
28
|
-
import { type DetectedHost, detectHostBucket } from '../../utils/host-detection.js';
|
|
29
|
-
import { parseStorageUrl } from '../../utils/storage-url.js';
|
|
30
31
|
import { clearUrlState, syncUrlParam } from '../../utils/url-state.js';
|
|
31
32
|
import AboutSheet from './AboutSheet.svelte';
|
|
32
33
|
import ConnectionDialog from './ConnectionDialog.svelte';
|
|
33
34
|
import LocaleToggle from './LocaleToggle.svelte';
|
|
34
35
|
import ThemeToggle from './ThemeToggle.svelte';
|
|
35
36
|
|
|
37
|
+
// Settings panel is owned by +page.svelte so it stays reachable even when the
|
|
38
|
+
// connection rail is hidden; the gear button just requests it be opened.
|
|
39
|
+
let { onOpenSettings }: { onOpenSettings?: () => void } = $props();
|
|
40
|
+
|
|
36
41
|
let aboutOpen = $state(false);
|
|
37
42
|
let dialogOpen = $state(false);
|
|
38
43
|
let editingConnection = $state<Connection | null>(null);
|
|
@@ -42,9 +47,9 @@ let autoConnecting = $state(false);
|
|
|
42
47
|
$effect(() => {
|
|
43
48
|
connections.load().then(async () => {
|
|
44
49
|
await handleAutoDetection();
|
|
45
|
-
// On first visit (no connections, no URL params),
|
|
50
|
+
// On first visit (no connections, no URL params), seed connections from config
|
|
46
51
|
if (connections.items.length === 0 && !new URL(window.location.href).searchParams.has('url')) {
|
|
47
|
-
await
|
|
52
|
+
await loadConfigConnections();
|
|
48
53
|
}
|
|
49
54
|
});
|
|
50
55
|
});
|
|
@@ -94,9 +99,18 @@ async function handleAutoDetection() {
|
|
|
94
99
|
return;
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
const hasUrlParam = url.searchParams.has('url');
|
|
103
|
+
|
|
97
104
|
// A recognizable storage provider was detected. Close the eagerly-opened
|
|
98
105
|
// URL tab (if any) so we can re-open it with a proper connectionId that
|
|
99
106
|
// provides S3 credentials and endpoint config for DuckDB httpfs.
|
|
107
|
+
// Mark the close + reopen as a migration so the tab-sync effect in
|
|
108
|
+
// +page.svelte doesn't clear `?url=` / `#hash` during the empty-tabs
|
|
109
|
+
// window between close and open. We end migration in `finally` so an
|
|
110
|
+
// abandoned credential prompt or thrown error still resets the flag.
|
|
111
|
+
const isMigrating = hasUrlParam;
|
|
112
|
+
if (isMigrating) tabs.beginMigration();
|
|
113
|
+
|
|
100
114
|
if (rawUrl) {
|
|
101
115
|
const eagerTabId = eagerUrlTabId(rawUrl);
|
|
102
116
|
const eagerTab = tabs.items.find((t) => t.id === eagerTabId);
|
|
@@ -105,12 +119,17 @@ async function handleAutoDetection() {
|
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
121
|
|
|
108
|
-
const hasUrlParam = url.searchParams.has('url');
|
|
109
|
-
|
|
110
122
|
if (hasUrlParam) {
|
|
111
123
|
// Auto-connect immediately for ?url= param (zero-friction)
|
|
112
124
|
autoConnecting = true;
|
|
113
125
|
try {
|
|
126
|
+
// TODO(stac-storage-ext): when `rawUrl` resolves to STAC content,
|
|
127
|
+
// peek-fetch the JSON, classify with `classifyStac`, pick the first
|
|
128
|
+
// item with non-empty hints, and pass `detected` through
|
|
129
|
+
// `applyStacItemStorageHints(detected, item)` BEFORE
|
|
130
|
+
// `saveHostConnection` so `storage:region` / `storage:platform` /
|
|
131
|
+
// `storage:requester_pays` flow into the auto-created connection.
|
|
132
|
+
// Helper lives in `utils/host-detection.ts` -- modular, callers opt in.
|
|
114
133
|
const connId = await connections.saveHostConnection(detected);
|
|
115
134
|
const conn = connections.getById(connId);
|
|
116
135
|
if (!conn) return;
|
|
@@ -160,10 +179,44 @@ async function handleAutoDetection() {
|
|
|
160
179
|
syncUrlParam(conn, prefixParam || undefined);
|
|
161
180
|
} finally {
|
|
162
181
|
autoConnecting = false;
|
|
182
|
+
if (isMigrating) tabs.endMigration();
|
|
163
183
|
}
|
|
164
184
|
} else {
|
|
165
185
|
// Show indicator for hostname-detected bucket
|
|
166
186
|
detectedHost = detected;
|
|
187
|
+
if (isMigrating) tabs.endMigration();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function loadConfigConnections() {
|
|
192
|
+
const seeds = appConfig.value.connections;
|
|
193
|
+
if (seeds.length === 0) {
|
|
194
|
+
// No configured connections (e.g. config failed to load): preserve the
|
|
195
|
+
// historic first-run demo bucket so the empty app is never a dead end.
|
|
196
|
+
await loadDemoConnection();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
let firstAnon: Connection | null = null;
|
|
200
|
+
for (const seed of seeds) {
|
|
201
|
+
const { id } = await connections.save({
|
|
202
|
+
name: seed.name,
|
|
203
|
+
provider: seed.provider,
|
|
204
|
+
endpoint: seed.endpoint ?? '',
|
|
205
|
+
bucket: seed.bucket,
|
|
206
|
+
region: seed.region ?? '',
|
|
207
|
+
anonymous: seed.anonymous ?? false,
|
|
208
|
+
...(seed.authMethod ? { authMethod: seed.authMethod } : {}),
|
|
209
|
+
...(seed.rootPrefix ? { rootPrefix: seed.rootPrefix } : {})
|
|
210
|
+
});
|
|
211
|
+
const conn = connections.getById(id);
|
|
212
|
+
if (conn?.anonymous && !firstAnon) firstAnon = conn;
|
|
213
|
+
}
|
|
214
|
+
// Auto-open the first public bucket so the demo flow stays zero-click.
|
|
215
|
+
// Private seeds remain as un-browsed rows; clicking one runs the normal
|
|
216
|
+
// ensureCredentials prompt via handleBrowseConnection.
|
|
217
|
+
if (firstAnon) {
|
|
218
|
+
browser.browse(firstAnon);
|
|
219
|
+
syncUrlParam(firstAnon);
|
|
167
220
|
}
|
|
168
221
|
}
|
|
169
222
|
|
|
@@ -344,6 +397,20 @@ async function handleBrowseConnection(connection: Connection) {
|
|
|
344
397
|
|
|
345
398
|
<!-- Bottom actions -->
|
|
346
399
|
<div class="mt-auto flex flex-col items-center gap-1 pt-2">
|
|
400
|
+
{#if appConfig.value.ui.showSettings}
|
|
401
|
+
<Tooltip>
|
|
402
|
+
<TooltipTrigger>
|
|
403
|
+
<button
|
|
404
|
+
class="flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent/50 hover:text-foreground"
|
|
405
|
+
onclick={() => onOpenSettings?.()}
|
|
406
|
+
aria-label={t('settings.tooltip')}
|
|
407
|
+
>
|
|
408
|
+
<SettingsIcon class="size-4" />
|
|
409
|
+
</button>
|
|
410
|
+
</TooltipTrigger>
|
|
411
|
+
<TooltipContent side="right">{t('settings.tooltip')}</TooltipContent>
|
|
412
|
+
</Tooltip>
|
|
413
|
+
{/if}
|
|
347
414
|
<LocaleToggle />
|
|
348
415
|
<ThemeToggle />
|
|
349
416
|
</div>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
onOpenSettings?: () => void;
|
|
3
|
+
};
|
|
4
|
+
declare const Sidebar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
5
|
type Sidebar = ReturnType<typeof Sidebar>;
|
|
3
6
|
export default Sidebar;
|
|
@@ -4,13 +4,13 @@ import FileTextIcon from '@lucide/svelte/icons/file-text';
|
|
|
4
4
|
import FolderIcon from '@lucide/svelte/icons/folder';
|
|
5
5
|
import GlobeIcon from '@lucide/svelte/icons/globe';
|
|
6
6
|
import InfoIcon from '@lucide/svelte/icons/info';
|
|
7
|
+
import { formatFileSize } from '@walkthru-earth/objex-utils';
|
|
7
8
|
import { Separator } from '../ui/separator/index.js';
|
|
8
9
|
import { getFileTypeInfo } from '../../file-icons/index.js';
|
|
9
10
|
import { t } from '../../i18n/index.svelte.js';
|
|
10
11
|
import { browser } from '../../stores/browser.svelte.js';
|
|
11
12
|
import { files } from '../../stores/files.svelte.js';
|
|
12
13
|
import { tabs } from '../../stores/tabs.svelte.js';
|
|
13
|
-
import { formatFileSize } from '../../utils/format.js';
|
|
14
14
|
import SafeLockToggle from './SafeLockToggle.svelte';
|
|
15
15
|
|
|
16
16
|
let isBrowsingRemote = $derived(browser.activeConnection !== null);
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import DatabaseIcon from '@lucide/svelte/icons/database';
|
|
3
3
|
import FileTextIcon from '@lucide/svelte/icons/file-text';
|
|
4
4
|
import XIcon from '@lucide/svelte/icons/x';
|
|
5
|
+
import { copyToClipboard } from '@walkthru-earth/objex-utils';
|
|
5
6
|
import type { Snippet } from 'svelte';
|
|
6
7
|
import { Button } from '../ui/button/index.js';
|
|
7
8
|
import * as ContextMenu from '../ui/context-menu/index.js';
|
|
8
9
|
import { ScrollArea } from '../ui/scroll-area/index.js';
|
|
9
10
|
import { t } from '../../i18n/index.svelte.js';
|
|
10
11
|
import { tabs } from '../../stores/tabs.svelte.js';
|
|
11
|
-
import {
|
|
12
|
-
import { buildHttpsUrl, buildStorageUrl } from '../../utils/url.js';
|
|
12
|
+
import { buildHttpsUrl, buildStorageUrl } from '../../utils/signed-url.js';
|
|
13
13
|
|
|
14
14
|
let { leading }: { leading?: Snippet } = $props();
|
|
15
15
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ContextMenu as ContextMenuPrimitive } from 'bits-ui';
|
|
2
|
-
declare const ContextMenuRadioGroup: import("svelte").Component<ContextMenuPrimitive.RadioGroupProps, {}, "
|
|
2
|
+
declare const ContextMenuRadioGroup: import("svelte").Component<ContextMenuPrimitive.RadioGroupProps, {}, "ref" | "value">;
|
|
3
3
|
type ContextMenuRadioGroup = ReturnType<typeof ContextMenuRadioGroup>;
|
|
4
4
|
export default ContextMenuRadioGroup;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
2
|
-
declare const DropdownMenuCheckboxGroup: import("svelte").Component<DropdownMenuPrimitive.CheckboxGroupProps, {}, "
|
|
2
|
+
declare const DropdownMenuCheckboxGroup: import("svelte").Component<DropdownMenuPrimitive.CheckboxGroupProps, {}, "ref" | "value">;
|
|
3
3
|
type DropdownMenuCheckboxGroup = ReturnType<typeof DropdownMenuCheckboxGroup>;
|
|
4
4
|
export default DropdownMenuCheckboxGroup;
|