@walkthru-earth/objex 1.0.0 → 1.2.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 +11 -2
- package/dist/components/browser/FileBrowser.svelte +41 -54
- package/dist/components/browser/FileTreeSidebar.svelte +43 -7
- package/dist/components/layout/ConnectionDialog.svelte +100 -1
- package/dist/components/layout/Sidebar.svelte +43 -25
- package/dist/components/viewers/CodeViewer.svelte +23 -0
- package/dist/components/viewers/CogControls.svelte +208 -0
- package/dist/components/viewers/CogControls.svelte.d.ts +12 -0
- package/dist/components/viewers/CogViewer.svelte +353 -1160
- package/dist/components/viewers/CogViewer.svelte.d.ts +1 -1
- package/dist/components/viewers/DatabaseViewer.svelte +345 -37
- package/dist/components/viewers/MarkdownViewer.svelte +1 -1
- package/dist/components/viewers/TableViewer.svelte +123 -41
- package/dist/components/viewers/ZarrMapViewer.svelte +29 -0
- package/dist/components/viewers/ZarrViewer.svelte +1 -4
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +6 -2
- package/dist/file-icons/index.d.ts +1 -1
- package/dist/file-icons/index.js +12 -2
- package/dist/i18n/ar.js +24 -0
- package/dist/i18n/en.js +24 -0
- package/dist/i18n/index.svelte.d.ts +0 -1
- package/dist/i18n/index.svelte.js +0 -3
- package/dist/index.d.ts +11 -0
- package/dist/index.js +10 -0
- package/dist/query/engine.d.ts +20 -4
- package/dist/query/index.d.ts +2 -1
- package/dist/query/index.js +1 -0
- package/dist/query/source.d.ts +30 -0
- package/dist/query/source.js +37 -0
- package/dist/query/wasm.d.ts +7 -5
- package/dist/query/wasm.js +138 -85
- package/dist/storage/providers.d.ts +47 -0
- package/dist/storage/providers.js +160 -0
- package/dist/stores/connections.svelte.js +5 -31
- package/dist/stores/files.svelte.d.ts +2 -8
- package/dist/stores/files.svelte.js +5 -38
- package/dist/stores/query-history.svelte.js +3 -25
- package/dist/stores/settings.svelte.d.ts +1 -0
- package/dist/stores/settings.svelte.js +10 -30
- package/dist/stores/tabs.svelte.d.ts +9 -2
- package/dist/stores/tabs.svelte.js +11 -2
- package/dist/types.d.ts +11 -0
- package/dist/utils/cloud-url.d.ts +27 -0
- package/dist/utils/cloud-url.js +61 -0
- package/dist/utils/cog.d.ts +244 -0
- package/dist/utils/cog.js +1039 -0
- package/dist/utils/deck.d.ts +0 -18
- package/dist/utils/deck.js +0 -36
- 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/geometry-type.d.ts +52 -0
- package/dist/utils/geometry-type.js +76 -0
- package/dist/utils/local-storage.d.ts +16 -0
- package/dist/utils/local-storage.js +37 -0
- package/dist/utils/markdown-sql.d.ts +1 -1
- package/dist/utils/markdown-sql.js +3 -4
- package/dist/utils/pmtiles-tile.d.ts +0 -2
- package/dist/utils/pmtiles-tile.js +0 -8
- package/dist/utils/url-state.d.ts +6 -0
- package/dist/utils/url-state.js +34 -26
- package/dist/utils/url.d.ts +13 -25
- package/dist/utils/url.js +17 -78
- package/dist/utils/zarr-tab.d.ts +22 -0
- package/dist/utils/zarr-tab.js +30 -0
- package/dist/utils/zarr.d.ts +0 -2
- package/dist/utils/zarr.js +73 -44
- package/package.json +50 -46
- package/dist/components/ui/tabs/index.d.ts +0 -5
- package/dist/components/ui/tabs/index.js +0 -7
- package/dist/components/ui/tabs/tabs-content.svelte +0 -17
- package/dist/components/ui/tabs/tabs-content.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-list.svelte +0 -16
- package/dist/components/ui/tabs/tabs-list.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs-trigger.svelte +0 -20
- package/dist/components/ui/tabs/tabs-trigger.svelte.d.ts +0 -4
- package/dist/components/ui/tabs/tabs.svelte +0 -19
- package/dist/components/ui/tabs/tabs.svelte.d.ts +0 -4
- package/dist/components/viewers/MapViewer.svelte +0 -234
- package/dist/components/viewers/MapViewer.svelte.d.ts +0 -7
- package/dist/components/viewers/StyleEditorOverlay.svelte +0 -27
- package/dist/components/viewers/StyleEditorOverlay.svelte.d.ts +0 -7
|
@@ -266,6 +266,148 @@ export const PROVIDERS = {
|
|
|
266
266
|
schemes: []
|
|
267
267
|
}
|
|
268
268
|
};
|
|
269
|
+
export const CORS_HELP = {
|
|
270
|
+
s3: {
|
|
271
|
+
defaultEnabled: false,
|
|
272
|
+
docsUrl: 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html',
|
|
273
|
+
note: 'Enable via S3 Console: Bucket > Permissions > CORS, or use the AWS CLI.'
|
|
274
|
+
},
|
|
275
|
+
gcs: {
|
|
276
|
+
defaultEnabled: false,
|
|
277
|
+
docsUrl: 'https://cloud.google.com/storage/docs/using-cors',
|
|
278
|
+
note: 'CORS cannot be configured via the Cloud Console. Use the gcloud CLI.',
|
|
279
|
+
cliSteps: [
|
|
280
|
+
'Create a cors.json file:\n[\n {\n "origin": ["*"],\n "method": ["GET", "HEAD"],\n "responseHeader": [\n "Content-Type",\n "Content-Length",\n "Content-Range",\n "Accept-Ranges",\n "ETag"\n ],\n "maxAgeSeconds": 3600\n }\n]',
|
|
281
|
+
'gcloud storage buckets update gs://BUCKET --cors-file=cors.json'
|
|
282
|
+
]
|
|
283
|
+
},
|
|
284
|
+
r2: {
|
|
285
|
+
defaultEnabled: false,
|
|
286
|
+
docsUrl: 'https://developers.cloudflare.com/r2/buckets/cors/',
|
|
287
|
+
note: 'Enable via R2 Dashboard: Bucket > Settings > CORS Policy.'
|
|
288
|
+
},
|
|
289
|
+
azure: {
|
|
290
|
+
defaultEnabled: false,
|
|
291
|
+
docsUrl: 'https://learn.microsoft.com/en-us/rest/api/storageservices/cross-origin-resource-sharing--cors--support-for-the-azure-storage-services',
|
|
292
|
+
note: 'Enable via Azure Portal: Storage Account > Blob Service > CORS, or use the Azure CLI.',
|
|
293
|
+
cliSteps: [
|
|
294
|
+
'az storage cors add --services b --methods GET HEAD \\\n --origins "*" --allowed-headers "*" \\\n --exposed-headers "*" --max-age 3600 \\\n --account-name ACCOUNT'
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
minio: {
|
|
298
|
+
defaultEnabled: true,
|
|
299
|
+
docsUrl: 'https://docs.min.io/enterprise/aistor-object-store/reference/cli/mc-cors/',
|
|
300
|
+
note: 'MinIO allows all origins by default. For custom rules, use mc cors set.'
|
|
301
|
+
},
|
|
302
|
+
storj: {
|
|
303
|
+
defaultEnabled: true,
|
|
304
|
+
note: 'Storj S3 gateway returns CORS headers by default.'
|
|
305
|
+
},
|
|
306
|
+
b2: {
|
|
307
|
+
defaultEnabled: false,
|
|
308
|
+
docsUrl: 'https://www.backblaze.com/docs/cloud-storage-cross-origin-resource-sharing-rules',
|
|
309
|
+
note: 'Enable via B2 Console: Bucket Settings > CORS Rules, or use the B2 CLI.',
|
|
310
|
+
cliSteps: [
|
|
311
|
+
'b2 bucket update --cors-rules \'[{\n "corsRuleName": "allow-all",\n "allowedOrigins": ["*"],\n "allowedOperations": ["s3_head", "s3_get"],\n "allowedHeaders": ["*"],\n "maxAgeSeconds": 3600\n}]\' BUCKET allPublic'
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
digitalocean: {
|
|
315
|
+
defaultEnabled: false,
|
|
316
|
+
docsUrl: 'https://docs.digitalocean.com/products/spaces/how-to/configure-cors/',
|
|
317
|
+
note: 'Enable via Control Panel: Space > Settings > CORS Configurations.'
|
|
318
|
+
},
|
|
319
|
+
wasabi: {
|
|
320
|
+
defaultEnabled: true,
|
|
321
|
+
docsUrl: 'https://docs.wasabi.com/docs/bucket-policy',
|
|
322
|
+
note: 'Wasabi returns CORS headers by default for all buckets.'
|
|
323
|
+
},
|
|
324
|
+
contabo: {
|
|
325
|
+
defaultEnabled: false,
|
|
326
|
+
note: 'S3-compatible CORS via the AWS CLI.',
|
|
327
|
+
cliSteps: [
|
|
328
|
+
'Create a cors.json file:\n{\n "CORSRules": [{\n "AllowedOrigins": ["*"],\n "AllowedMethods": ["GET", "HEAD"],\n "AllowedHeaders": ["*"],\n "ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Content-Range", "Accept-Ranges"],\n "MaxAgeSeconds": 3600\n }]\n}',
|
|
329
|
+
'aws s3api put-bucket-cors --bucket BUCKET \\\n --cors-configuration file://cors.json \\\n --endpoint-url https://REGION.contaboobj.com'
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
hetzner: {
|
|
333
|
+
defaultEnabled: false,
|
|
334
|
+
docsUrl: 'https://docs.hetzner.com/storage/object-storage/howto-protect-objects/cors/',
|
|
335
|
+
note: 'S3-compatible CORS via the AWS CLI.',
|
|
336
|
+
cliSteps: [
|
|
337
|
+
'Create a cors.json file:\n{\n "CORSRules": [{\n "AllowedOrigins": ["*"],\n "AllowedMethods": ["GET", "HEAD"],\n "AllowedHeaders": ["*"],\n "ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Content-Range", "Accept-Ranges"],\n "MaxAgeSeconds": 3600\n }]\n}',
|
|
338
|
+
'aws s3api put-bucket-cors --bucket BUCKET \\\n --cors-configuration file://cors.json \\\n --endpoint-url https://REGION.your-objectstorage.com \\\n --region REGION'
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
linode: {
|
|
342
|
+
defaultEnabled: false,
|
|
343
|
+
docsUrl: 'https://www.linode.com/docs/guides/working-with-cors-linode-object-storage/',
|
|
344
|
+
note: 'S3-compatible CORS via the AWS CLI.',
|
|
345
|
+
cliSteps: [
|
|
346
|
+
'Create a cors.json file:\n{\n "CORSRules": [{\n "AllowedOrigins": ["*"],\n "AllowedMethods": ["GET", "HEAD"],\n "AllowedHeaders": ["*"],\n "ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Content-Range", "Accept-Ranges"],\n "MaxAgeSeconds": 3600\n }]\n}',
|
|
347
|
+
'aws s3api put-bucket-cors --bucket BUCKET \\\n --cors-configuration file://cors.json \\\n --endpoint-url https://REGION.linodeobjects.com'
|
|
348
|
+
]
|
|
349
|
+
},
|
|
350
|
+
ovhcloud: {
|
|
351
|
+
defaultEnabled: false,
|
|
352
|
+
docsUrl: 'https://help.ovhcloud.com/csm/en-public-cloud-storage-s3-cors?id=kb_article_view&sysparm_article=KB0058291',
|
|
353
|
+
note: 'S3-compatible CORS via the AWS CLI.',
|
|
354
|
+
cliSteps: [
|
|
355
|
+
'Create a cors.json file:\n{\n "CORSRules": [{\n "AllowedOrigins": ["*"],\n "AllowedMethods": ["GET", "HEAD"],\n "AllowedHeaders": ["*"],\n "ExposeHeaders": ["ETag", "Content-Length", "Content-Type", "Content-Range", "Accept-Ranges"],\n "MaxAgeSeconds": 3600\n }]\n}',
|
|
356
|
+
'aws s3api put-bucket-cors --bucket BUCKET \\\n --cors-configuration file://cors.json \\\n --endpoint-url https://s3.REGION.io.cloud.ovh.net'
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
export const READ_ONLY_HELP = {
|
|
361
|
+
s3: {
|
|
362
|
+
note: 'Use IAM policies to create a read-only user, or apply a bucket policy that allows only s3:GetObject and s3:ListBucket.',
|
|
363
|
+
docsUrl: 'https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-policies-s3.html'
|
|
364
|
+
},
|
|
365
|
+
gcs: {
|
|
366
|
+
note: 'Assign the Storage Object Viewer role (roles/storage.objectViewer) to the service account.',
|
|
367
|
+
docsUrl: 'https://cloud.google.com/storage/docs/access-control/iam-roles'
|
|
368
|
+
},
|
|
369
|
+
r2: {
|
|
370
|
+
note: 'Create an API token with Object Read permissions in the R2 dashboard.',
|
|
371
|
+
docsUrl: 'https://developers.cloudflare.com/r2/api/tokens/'
|
|
372
|
+
},
|
|
373
|
+
azure: {
|
|
374
|
+
note: 'Generate a SAS token with Read and List permissions only. Avoid granting Write or Delete.',
|
|
375
|
+
docsUrl: 'https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview'
|
|
376
|
+
},
|
|
377
|
+
b2: {
|
|
378
|
+
note: 'Create an application key with readFiles and listBuckets capabilities only.',
|
|
379
|
+
docsUrl: 'https://www.backblaze.com/docs/cloud-storage-application-keys'
|
|
380
|
+
},
|
|
381
|
+
hetzner: {
|
|
382
|
+
note: 'Keys have full read/write by default. Use a bucket policy with the correct ARN format to deny write and policy actions. To undo, generate a new admin key from the Hetzner Console.',
|
|
383
|
+
docsUrl: 'https://docs.hetzner.com/storage/object-storage/faq/s3-credentials/#how-do-i-restrict-access-per-key',
|
|
384
|
+
cliSteps: [
|
|
385
|
+
'Find your project ID from the Hetzner Console URL:\nhttps://console.hetzner.com/projects/<PROJECT_ID>/servers',
|
|
386
|
+
'Create a policy.json file:\n{\n "Version": "2012-10-17",\n "Statement": [\n {\n "Sid": "DenyWrites",\n "Effect": "Deny",\n "Principal": {\n "AWS": "arn:aws:iam:::user/p<PROJECT_ID>:<ACCESS_KEY>"\n },\n "Action": [\n "s3:PutObject",\n "s3:DeleteObject",\n "s3:AbortMultipartUpload",\n "s3:PutBucketPolicy",\n "s3:DeleteBucketPolicy"\n ],\n "Resource": [\n "arn:aws:s3:::BUCKET",\n "arn:aws:s3:::BUCKET/*"\n ]\n }\n ]\n}',
|
|
387
|
+
'aws s3api put-bucket-policy --bucket BUCKET \\\n --policy file://policy.json \\\n --endpoint-url https://REGION.your-objectstorage.com \\\n --region REGION',
|
|
388
|
+
'Note: This key can no longer modify the policy.\nTo restore write access, generate a new key in the\nHetzner Console and use it to delete the policy.'
|
|
389
|
+
]
|
|
390
|
+
},
|
|
391
|
+
minio: {
|
|
392
|
+
note: 'Create a read-only policy with mc admin policy, or use the built-in readonly canned policy.',
|
|
393
|
+
docsUrl: 'https://docs.min.io/enterprise/aistor-object-store/administration/iam/access/'
|
|
394
|
+
},
|
|
395
|
+
digitalocean: {
|
|
396
|
+
note: 'Spaces keys are project-wide. Use a bucket policy to restrict write actions for a specific key.',
|
|
397
|
+
docsUrl: 'https://docs.digitalocean.com/products/spaces/how-to/manage-access/'
|
|
398
|
+
},
|
|
399
|
+
wasabi: {
|
|
400
|
+
note: 'Create a sub-user with a read-only policy in the Wasabi Console.',
|
|
401
|
+
docsUrl: 'https://docs.wasabi.com/docs/creating-a-user-account-and-access-key'
|
|
402
|
+
},
|
|
403
|
+
contabo: {
|
|
404
|
+
note: 'S3-compatible bucket policies. Use a Deny policy for write actions with the key ARN.',
|
|
405
|
+
cliSteps: [
|
|
406
|
+
'Create a policy.json with a Deny statement for s3:PutObject and s3:DeleteObject.',
|
|
407
|
+
'aws s3api put-bucket-policy --bucket BUCKET \\\n --policy file://policy.json \\\n --endpoint-url https://REGION.contaboobj.com'
|
|
408
|
+
]
|
|
409
|
+
}
|
|
410
|
+
};
|
|
269
411
|
// ---------------------------------------------------------------------------
|
|
270
412
|
// Helpers
|
|
271
413
|
// ---------------------------------------------------------------------------
|
|
@@ -316,3 +458,21 @@ export function buildProviderBaseUrl(provider, endpoint, bucket, region) {
|
|
|
316
458
|
export function isGcsProvider(provider, endpoint) {
|
|
317
459
|
return provider === 'gcs' || (!!endpoint && /storage\.googleapis\.com/i.test(endpoint));
|
|
318
460
|
}
|
|
461
|
+
export function getAccessMode(conn) {
|
|
462
|
+
if (conn.provider === 'azure')
|
|
463
|
+
return 'sas-https';
|
|
464
|
+
// Anonymous buckets: every provider serves files over plain HTTPS without
|
|
465
|
+
// signing (AWS path/vhost, GCS, R2 public, Storj, Wasabi, DO, etc.).
|
|
466
|
+
if (conn.anonymous)
|
|
467
|
+
return 'public-https';
|
|
468
|
+
// Authenticated: needs SigV4 signing.
|
|
469
|
+
return 'signed-s3';
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* True when the connection's files can be fetched by any HTTP client
|
|
473
|
+
* (fetch/img/video/DuckDB httpfs/COG/Zarr/etc.) without the storage adapter.
|
|
474
|
+
*/
|
|
475
|
+
export function isPubliclyStreamable(conn) {
|
|
476
|
+
const mode = getAccessMode(conn);
|
|
477
|
+
return mode === 'public-https' || mode === 'sas-https';
|
|
478
|
+
}
|
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
import { STORAGE_KEYS } from '../constants.js';
|
|
2
|
+
import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
|
|
2
3
|
import { credentialStore, storeToNative } from './credentials.svelte.js';
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
|
-
// localStorage helpers
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
function loadFromLocalStorage() {
|
|
7
|
-
if (typeof window === 'undefined')
|
|
8
|
-
return [];
|
|
9
|
-
try {
|
|
10
|
-
const raw = localStorage.getItem(STORAGE_KEYS.CONNECTIONS);
|
|
11
|
-
if (raw) {
|
|
12
|
-
return JSON.parse(raw);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
// ignore parse errors
|
|
17
|
-
}
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
function persistToLocalStorage(connections) {
|
|
21
|
-
if (typeof window === 'undefined')
|
|
22
|
-
return;
|
|
23
|
-
try {
|
|
24
|
-
localStorage.setItem(STORAGE_KEYS.CONNECTIONS, JSON.stringify(connections));
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
// ignore storage errors
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
5
|
// Store
|
|
32
6
|
// ---------------------------------------------------------------------------
|
|
33
7
|
function createConnectionsStore() {
|
|
@@ -48,7 +22,7 @@ function createConnectionsStore() {
|
|
|
48
22
|
async load() {
|
|
49
23
|
if (loaded)
|
|
50
24
|
return;
|
|
51
|
-
connections =
|
|
25
|
+
connections = loadFromStorage(STORAGE_KEYS.CONNECTIONS, []);
|
|
52
26
|
loaded = true;
|
|
53
27
|
},
|
|
54
28
|
/**
|
|
@@ -75,7 +49,7 @@ function createConnectionsStore() {
|
|
|
75
49
|
rootPrefix: config.rootPrefix
|
|
76
50
|
};
|
|
77
51
|
connections = [...connections, conn];
|
|
78
|
-
|
|
52
|
+
persistToStorage(STORAGE_KEYS.CONNECTIONS, connections);
|
|
79
53
|
// Store credentials in memory (never persisted to localStorage).
|
|
80
54
|
if (!config.anonymous) {
|
|
81
55
|
if (config.sas_token) {
|
|
@@ -114,7 +88,7 @@ function createConnectionsStore() {
|
|
|
114
88
|
rootPrefix: config.rootPrefix
|
|
115
89
|
};
|
|
116
90
|
connections = [...connections];
|
|
117
|
-
|
|
91
|
+
persistToStorage(STORAGE_KEYS.CONNECTIONS, connections);
|
|
118
92
|
// Invalidate cached adapter for this connection
|
|
119
93
|
import('../storage/index.js').then(({ clearAdapterCache }) => clearAdapterCache(id));
|
|
120
94
|
// Update in-memory credentials.
|
|
@@ -148,7 +122,7 @@ function createConnectionsStore() {
|
|
|
148
122
|
async remove(id) {
|
|
149
123
|
const before = connections.length;
|
|
150
124
|
connections = connections.filter((c) => c.id !== id);
|
|
151
|
-
|
|
125
|
+
persistToStorage(STORAGE_KEYS.CONNECTIONS, connections);
|
|
152
126
|
credentialStore.remove(id);
|
|
153
127
|
// Invalidate cached adapter for this connection
|
|
154
128
|
import('../storage/index.js').then(({ clearAdapterCache }) => clearAdapterCache(id));
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import type { FileEntry } from '../types.js';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export interface SortConfig {
|
|
5
|
-
field: SortField;
|
|
6
|
-
direction: SortDirection;
|
|
7
|
-
}
|
|
8
|
-
export declare const fileStore: {
|
|
2
|
+
import { type SortConfig, type SortField } from '../utils/file-sort.js';
|
|
3
|
+
export declare const files: {
|
|
9
4
|
readonly entries: FileEntry[];
|
|
10
5
|
readonly currentPath: string;
|
|
11
6
|
readonly loading: boolean;
|
|
@@ -17,4 +12,3 @@ export declare const fileStore: {
|
|
|
17
12
|
setError(message: string | null): void;
|
|
18
13
|
sort(field: SortField): void;
|
|
19
14
|
};
|
|
20
|
-
export { fileStore as files };
|
|
@@ -1,27 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const sorted = [...entries];
|
|
3
|
-
const dir = config.direction === 'asc' ? 1 : -1;
|
|
4
|
-
sorted.sort((a, b) => {
|
|
5
|
-
// Directories always come first
|
|
6
|
-
if (a.is_dir && !b.is_dir)
|
|
7
|
-
return -1;
|
|
8
|
-
if (!a.is_dir && b.is_dir)
|
|
9
|
-
return 1;
|
|
10
|
-
switch (config.field) {
|
|
11
|
-
case 'name':
|
|
12
|
-
return dir * a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
|
13
|
-
case 'size':
|
|
14
|
-
return dir * (a.size - b.size);
|
|
15
|
-
case 'modified':
|
|
16
|
-
return dir * (a.modified - b.modified);
|
|
17
|
-
case 'extension':
|
|
18
|
-
return dir * a.extension.localeCompare(b.extension, undefined, { sensitivity: 'base' });
|
|
19
|
-
default:
|
|
20
|
-
return 0;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
return sorted;
|
|
24
|
-
}
|
|
1
|
+
import { sortFileEntries, toggleSortField } from '../utils/file-sort.js';
|
|
25
2
|
function createFilesStore() {
|
|
26
3
|
let files = $state([]);
|
|
27
4
|
let currentPath = $state('');
|
|
@@ -45,7 +22,7 @@ function createFilesStore() {
|
|
|
45
22
|
return sortConfig;
|
|
46
23
|
},
|
|
47
24
|
setFiles(entries) {
|
|
48
|
-
files =
|
|
25
|
+
files = sortFileEntries(entries, sortConfig);
|
|
49
26
|
error = null;
|
|
50
27
|
},
|
|
51
28
|
setPath(path) {
|
|
@@ -58,19 +35,9 @@ function createFilesStore() {
|
|
|
58
35
|
error = message;
|
|
59
36
|
},
|
|
60
37
|
sort(field) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
sortConfig = {
|
|
64
|
-
field,
|
|
65
|
-
direction: sortConfig.direction === 'asc' ? 'desc' : 'asc'
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
sortConfig = { field, direction: 'asc' };
|
|
70
|
-
}
|
|
71
|
-
files = sortEntries(files, sortConfig);
|
|
38
|
+
sortConfig = toggleSortField(sortConfig, field);
|
|
39
|
+
files = sortFileEntries(files, sortConfig);
|
|
72
40
|
}
|
|
73
41
|
};
|
|
74
42
|
}
|
|
75
|
-
export const
|
|
76
|
-
export { fileStore as files };
|
|
43
|
+
export const files = createFilesStore();
|
|
@@ -1,31 +1,9 @@
|
|
|
1
1
|
import { MAX_QUERY_HISTORY_ENTRIES, STORAGE_KEYS } from '../constants.js';
|
|
2
|
-
|
|
3
|
-
if (typeof window === 'undefined')
|
|
4
|
-
return [];
|
|
5
|
-
try {
|
|
6
|
-
const raw = localStorage.getItem(STORAGE_KEYS.QUERY_HISTORY);
|
|
7
|
-
if (raw)
|
|
8
|
-
return JSON.parse(raw);
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
// ignore parse errors
|
|
12
|
-
}
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
function persistEntries(entries) {
|
|
16
|
-
if (typeof window === 'undefined')
|
|
17
|
-
return;
|
|
18
|
-
try {
|
|
19
|
-
localStorage.setItem(STORAGE_KEYS.QUERY_HISTORY, JSON.stringify(entries));
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// ignore storage errors
|
|
23
|
-
}
|
|
24
|
-
}
|
|
2
|
+
import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
|
|
25
3
|
function createQueryHistoryStore() {
|
|
26
|
-
let entries = $state(
|
|
4
|
+
let entries = $state(loadFromStorage(STORAGE_KEYS.QUERY_HISTORY, []));
|
|
27
5
|
function save() {
|
|
28
|
-
|
|
6
|
+
persistToStorage(STORAGE_KEYS.QUERY_HISTORY, entries);
|
|
29
7
|
}
|
|
30
8
|
return {
|
|
31
9
|
get entries() {
|
|
@@ -1,36 +1,16 @@
|
|
|
1
1
|
import { STORAGE_KEYS } from '../constants.js';
|
|
2
2
|
import { setLocale } from '../i18n/index.svelte.js';
|
|
3
|
+
import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
|
|
4
|
+
const SETTINGS_DEFAULTS = { theme: 'system', locale: 'en', featureLimit: 1000 };
|
|
3
5
|
function loadSettings() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const parsed = JSON.parse(raw);
|
|
11
|
-
return {
|
|
12
|
-
theme: parsed.theme ?? 'system',
|
|
13
|
-
locale: parsed.locale ?? 'en',
|
|
14
|
-
featureLimit: parsed.featureLimit ?? 1000
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
// ignore parse errors
|
|
20
|
-
}
|
|
21
|
-
return { theme: 'system', locale: 'en', featureLimit: 1000 };
|
|
22
|
-
}
|
|
23
|
-
function persistSettings(settings) {
|
|
24
|
-
if (typeof window === 'undefined')
|
|
25
|
-
return;
|
|
26
|
-
try {
|
|
27
|
-
localStorage.setItem(STORAGE_KEYS.SETTINGS, JSON.stringify(settings));
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
// ignore storage errors
|
|
31
|
-
}
|
|
6
|
+
const stored = loadFromStorage(STORAGE_KEYS.SETTINGS, {});
|
|
7
|
+
return {
|
|
8
|
+
theme: stored.theme ?? SETTINGS_DEFAULTS.theme,
|
|
9
|
+
locale: stored.locale ?? SETTINGS_DEFAULTS.locale,
|
|
10
|
+
featureLimit: stored.featureLimit ?? SETTINGS_DEFAULTS.featureLimit
|
|
11
|
+
};
|
|
32
12
|
}
|
|
33
|
-
function resolveTheme(theme) {
|
|
13
|
+
export function resolveTheme(theme) {
|
|
34
14
|
if (theme !== 'system')
|
|
35
15
|
return theme;
|
|
36
16
|
if (typeof window === 'undefined')
|
|
@@ -51,7 +31,7 @@ function createSettingsStore() {
|
|
|
51
31
|
document.documentElement.lang = initial.locale;
|
|
52
32
|
}
|
|
53
33
|
function persist() {
|
|
54
|
-
|
|
34
|
+
persistToStorage(STORAGE_KEYS.SETTINGS, { theme, locale, featureLimit });
|
|
55
35
|
}
|
|
56
36
|
function applyTheme(t) {
|
|
57
37
|
theme = t;
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { Tab } from '../types.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Tab-id for eagerly-opened direct-URL tabs (`source: 'url'`).
|
|
4
|
+
* The Sidebar's host-detection auto-migration closes an eager tab by its id
|
|
5
|
+
* and re-opens as a remote tab once a connection is available; the eager id
|
|
6
|
+
* is built in `+page.svelte::openUrlTab` and matched in `Sidebar.svelte::
|
|
7
|
+
* handleAutoDetection`, so both sides must agree on the format.
|
|
8
|
+
*/
|
|
9
|
+
export declare function eagerUrlTabId(url: string): string;
|
|
10
|
+
export declare const tabs: {
|
|
3
11
|
readonly items: Tab[];
|
|
4
12
|
readonly activeTabId: string | null;
|
|
5
13
|
readonly active: Tab | undefined;
|
|
@@ -14,4 +22,3 @@ export declare const tabStore: {
|
|
|
14
22
|
setActive(id: string): void;
|
|
15
23
|
update(id: string, partial: Partial<Omit<Tab, "id">>): void;
|
|
16
24
|
};
|
|
17
|
-
export { tabStore as tabs };
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { tabResources } from './tab-resources.svelte.js';
|
|
2
2
|
/** Maximum number of viewer instances kept alive (mounted but hidden). */
|
|
3
3
|
const MAX_ALIVE = 5;
|
|
4
|
+
/**
|
|
5
|
+
* Tab-id for eagerly-opened direct-URL tabs (`source: 'url'`).
|
|
6
|
+
* The Sidebar's host-detection auto-migration closes an eager tab by its id
|
|
7
|
+
* and re-opens as a remote tab once a connection is available; the eager id
|
|
8
|
+
* is built in `+page.svelte::openUrlTab` and matched in `Sidebar.svelte::
|
|
9
|
+
* handleAutoDetection`, so both sides must agree on the format.
|
|
10
|
+
*/
|
|
11
|
+
export function eagerUrlTabId(url) {
|
|
12
|
+
return `url:${url}`;
|
|
13
|
+
}
|
|
4
14
|
function releaseDuckDbMemory() {
|
|
5
15
|
import('../query/index.js')
|
|
6
16
|
.then(({ getQueryEngine }) => getQueryEngine().then((engine) => engine.releaseMemory()))
|
|
@@ -106,5 +116,4 @@ function createTabsStore() {
|
|
|
106
116
|
}
|
|
107
117
|
};
|
|
108
118
|
}
|
|
109
|
-
export const
|
|
110
|
-
export { tabStore as tabs };
|
|
119
|
+
export const tabs = createTabsStore();
|
package/dist/types.d.ts
CHANGED
|
@@ -38,6 +38,17 @@ export interface Tab {
|
|
|
38
38
|
connectionId?: string;
|
|
39
39
|
extension: string;
|
|
40
40
|
size?: number;
|
|
41
|
+
/**
|
|
42
|
+
* When set, the tab reads data from a SQL FROM-clause target (e.g. an
|
|
43
|
+
* attached DuckLake/DuckDB/SQLite table) rather than a file URL. The ref
|
|
44
|
+
* is inserted directly into generated SQL, so it must be fully-qualified
|
|
45
|
+
* and pre-quoted, e.g. `__objex_db__."main"."air_quality"`.
|
|
46
|
+
*
|
|
47
|
+
* When `sourceRef` is set, file-specific loading paths (hyparquet
|
|
48
|
+
* metadata, `parquet_kv_metadata`, etc.) are skipped, and schema / CRS /
|
|
49
|
+
* row count are derived from the SQL source directly via DuckDB.
|
|
50
|
+
*/
|
|
51
|
+
sourceRef?: string;
|
|
41
52
|
}
|
|
42
53
|
export interface WriteResult {
|
|
43
54
|
key: string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud storage protocol URL utilities — pure TS, no Svelte dependency.
|
|
3
|
+
*
|
|
4
|
+
* Converts cloud protocol URLs (s3://, gs://) to HTTPS URLs for browser access.
|
|
5
|
+
* Provider-aware native scheme lookup.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Map provider to its native URI scheme prefix.
|
|
9
|
+
* Derived from the registry's `schemes` array (first entry is the primary scheme).
|
|
10
|
+
* Falls back to 's3' for providers without a scheme (S3-compatible).
|
|
11
|
+
*/
|
|
12
|
+
export declare function getNativeScheme(provider: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Safely decode a percent-encoded URI component.
|
|
15
|
+
* Returns the original string if decoding fails (malformed sequences).
|
|
16
|
+
*/
|
|
17
|
+
export declare function safeDecodeURIComponent(s: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a cloud storage protocol URL (s3://, gs://) to an HTTPS URL
|
|
20
|
+
* for browser access. Returns the original URL if already HTTP(S) or unknown.
|
|
21
|
+
*
|
|
22
|
+
* Supported:
|
|
23
|
+
* - `s3://bucket/key` → `https://s3.{region}.amazonaws.com/{bucket}/{key}`
|
|
24
|
+
* (region auto-detected from bucket name when possible)
|
|
25
|
+
* - `gs://bucket/key` → `https://storage.googleapis.com/{bucket}/{key}`
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveCloudUrl(url: string): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud storage protocol URL utilities — pure TS, no Svelte dependency.
|
|
3
|
+
*
|
|
4
|
+
* Converts cloud protocol URLs (s3://, gs://) to HTTPS URLs for browser access.
|
|
5
|
+
* Provider-aware native scheme lookup.
|
|
6
|
+
*/
|
|
7
|
+
import { buildProviderBaseUrl, PROVIDERS } from '../storage/providers.js';
|
|
8
|
+
/** AWS region pattern — matches prefixes like "us-west-2", "eu-central-1", etc. */
|
|
9
|
+
const AWS_REGION_RE = /^(us|eu|ap|sa|ca|me|af|il)-(north|south|east|west|central|northeast|southeast|northwest|southwest)-\d+/;
|
|
10
|
+
/**
|
|
11
|
+
* Map provider to its native URI scheme prefix.
|
|
12
|
+
* Derived from the registry's `schemes` array (first entry is the primary scheme).
|
|
13
|
+
* Falls back to 's3' for providers without a scheme (S3-compatible).
|
|
14
|
+
*/
|
|
15
|
+
export function getNativeScheme(provider) {
|
|
16
|
+
const def = PROVIDERS[provider];
|
|
17
|
+
if (def?.schemes.length)
|
|
18
|
+
return def.schemes[0];
|
|
19
|
+
return 's3';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Safely decode a percent-encoded URI component.
|
|
23
|
+
* Returns the original string if decoding fails (malformed sequences).
|
|
24
|
+
*/
|
|
25
|
+
export function safeDecodeURIComponent(s) {
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(s);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Convert a cloud storage protocol URL (s3://, gs://) to an HTTPS URL
|
|
35
|
+
* for browser access. Returns the original URL if already HTTP(S) or unknown.
|
|
36
|
+
*
|
|
37
|
+
* Supported:
|
|
38
|
+
* - `s3://bucket/key` → `https://s3.{region}.amazonaws.com/{bucket}/{key}`
|
|
39
|
+
* (region auto-detected from bucket name when possible)
|
|
40
|
+
* - `gs://bucket/key` → `https://storage.googleapis.com/{bucket}/{key}`
|
|
41
|
+
*/
|
|
42
|
+
export function resolveCloudUrl(url) {
|
|
43
|
+
// S3 / S3-compatible: s3://, s3a://, s3n://
|
|
44
|
+
const s3Match = url.match(/^s3[an]?:\/\/([^/]+)\/?(.*)$/);
|
|
45
|
+
if (s3Match) {
|
|
46
|
+
const [, bucket, key] = s3Match;
|
|
47
|
+
// Detect region from bucket name (e.g. "us-west-2.opendata.source.coop")
|
|
48
|
+
const regionMatch = bucket.match(AWS_REGION_RE);
|
|
49
|
+
const region = regionMatch ? regionMatch[0] : 'us-east-1';
|
|
50
|
+
const base = buildProviderBaseUrl('s3', '', bucket, region);
|
|
51
|
+
return key ? `${base}/${key}` : base;
|
|
52
|
+
}
|
|
53
|
+
// Google Cloud Storage: gs://, gcs://
|
|
54
|
+
const gcsMatch = url.match(/^gcs?:\/\/([^/]+)\/?(.*)$/);
|
|
55
|
+
if (gcsMatch) {
|
|
56
|
+
const [, bucket, key] = gcsMatch;
|
|
57
|
+
const base = buildProviderBaseUrl('gcs', '', bucket, '');
|
|
58
|
+
return key ? `${base}/${key}` : base;
|
|
59
|
+
}
|
|
60
|
+
return url;
|
|
61
|
+
}
|