@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.
Files changed (84) hide show
  1. package/README.md +9 -2
  2. package/dist/components/browser/FileBrowser.svelte +53 -41
  3. package/dist/components/browser/FileRow.svelte +8 -3
  4. package/dist/components/browser/FileTreeSidebar.svelte +2 -4
  5. package/dist/components/layout/AboutSheet.svelte +126 -0
  6. package/dist/components/layout/AboutSheet.svelte.d.ts +6 -0
  7. package/dist/components/layout/ConnectionDialog.svelte +186 -138
  8. package/dist/components/layout/ConnectionDialog.svelte.d.ts +1 -0
  9. package/dist/components/layout/Sidebar.svelte +19 -3
  10. package/dist/components/layout/TabBar.svelte +4 -7
  11. package/dist/components/viewers/CodeViewer.svelte +17 -9
  12. package/dist/components/viewers/ImageViewer.svelte +6 -16
  13. package/dist/components/viewers/MarkdownViewer.svelte +8 -16
  14. package/dist/components/viewers/MediaViewer.svelte +6 -17
  15. package/dist/components/viewers/ModelViewer.svelte +4 -2
  16. package/dist/components/viewers/NotebookViewer.svelte +90 -40
  17. package/dist/components/viewers/PdfViewer.svelte +5 -3
  18. package/dist/components/viewers/RawViewer.svelte +4 -2
  19. package/dist/components/viewers/TableGrid.svelte +3 -2
  20. package/dist/components/viewers/ZarrMapViewer.svelte +334 -40
  21. package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +3 -8
  22. package/dist/components/viewers/ZarrViewer.svelte +459 -178
  23. package/dist/components/viewers/map/AttributeTable.svelte +1 -6
  24. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +2 -6
  25. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +96 -22
  26. package/dist/constants.d.ts +28 -0
  27. package/dist/constants.js +34 -0
  28. package/dist/file-icons/index.js +6 -0
  29. package/dist/i18n/ar.js +34 -0
  30. package/dist/i18n/en.js +34 -0
  31. package/dist/index.d.ts +13 -1
  32. package/dist/index.js +16 -1
  33. package/dist/query/wasm.js +5 -4
  34. package/dist/storage/browser-cloud.d.ts +7 -0
  35. package/dist/storage/browser-cloud.js +74 -7
  36. package/dist/storage/providers.d.ts +53 -0
  37. package/dist/storage/providers.js +318 -0
  38. package/dist/stores/connections.svelte.js +8 -34
  39. package/dist/stores/files.svelte.d.ts +1 -6
  40. package/dist/stores/files.svelte.js +4 -36
  41. package/dist/stores/query-history.svelte.js +5 -28
  42. package/dist/stores/settings.svelte.d.ts +1 -0
  43. package/dist/stores/settings.svelte.js +11 -31
  44. package/dist/types.d.ts +2 -2
  45. package/dist/utils/clipboard.d.ts +13 -0
  46. package/dist/utils/clipboard.js +38 -0
  47. package/dist/utils/cloud-url.d.ts +27 -0
  48. package/dist/utils/cloud-url.js +61 -0
  49. package/dist/utils/error.d.ts +8 -0
  50. package/dist/utils/error.js +12 -0
  51. package/dist/utils/export.d.ts +22 -2
  52. package/dist/utils/export.js +35 -10
  53. package/dist/utils/file-sort.d.ts +20 -0
  54. package/dist/utils/file-sort.js +41 -0
  55. package/dist/utils/format.d.ts +10 -0
  56. package/dist/utils/format.js +22 -0
  57. package/dist/utils/host-detection.js +78 -18
  58. package/dist/utils/local-storage.d.ts +16 -0
  59. package/dist/utils/local-storage.js +37 -0
  60. package/dist/utils/notebook.d.ts +59 -0
  61. package/dist/utils/notebook.js +211 -0
  62. package/dist/utils/parquet-metadata.js +1 -1
  63. package/dist/utils/pmtiles-tile.js +2 -1
  64. package/dist/utils/pmtiles.js +2 -1
  65. package/dist/utils/storage-url.d.ts +1 -1
  66. package/dist/utils/storage-url.js +82 -24
  67. package/dist/utils/url-state.js +2 -7
  68. package/dist/utils/url.d.ts +0 -2
  69. package/dist/utils/url.js +3 -29
  70. package/dist/utils/zarr.d.ts +60 -20
  71. package/dist/utils/zarr.js +450 -103
  72. package/package.json +66 -54
  73. package/dist/assets/favicon.svg +0 -17
  74. package/dist/components/CLAUDE.md +0 -44
  75. package/dist/components/viewers/CLAUDE.md +0 -60
  76. package/dist/file-icons/CLAUDE.md +0 -21
  77. package/dist/i18n/CLAUDE.md +0 -19
  78. package/dist/query/CLAUDE.md +0 -22
  79. package/dist/storage/CLAUDE.md +0 -23
  80. package/dist/stores/CLAUDE.md +0 -29
  81. package/dist/types/notebookjs.d.ts +0 -14
  82. package/dist/utils/CLAUDE.md +0 -54
  83. package/dist/utils/analytics.d.ts +0 -10
  84. package/dist/utils/analytics.js +0 -38
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Provider registry — single source of truth for all cloud storage providers.
3
+ *
4
+ * Centralizes endpoint patterns, regions, auth methods, and UI metadata.
5
+ * Used by ConnectionDialog, browser-cloud adapter, host-detection, url-state, etc.
6
+ */
7
+ export type ProviderId = 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj' | 'b2' | 'digitalocean' | 'wasabi' | 'contabo' | 'hetzner' | 'linode' | 'ovhcloud';
8
+ export interface ProviderRegion {
9
+ code: string;
10
+ label: string;
11
+ }
12
+ export interface ProviderDef {
13
+ /** Display label in the UI. */
14
+ label: string;
15
+ /** Short description shown as helper text. */
16
+ description: string;
17
+ /** Auth method used by this provider. */
18
+ authMethod: 'sigv4' | 'sas-token';
19
+ /** Whether the region field is relevant for this provider. */
20
+ needsRegion: boolean;
21
+ /** Whether the endpoint field is required. */
22
+ needsEndpoint: boolean;
23
+ /** Default region when creating a new connection. */
24
+ defaultRegion: string;
25
+ /**
26
+ * Endpoint template with `{region}` placeholder.
27
+ * If null, the user must provide a custom endpoint (e.g. MinIO).
28
+ * If a fixed string (no `{region}`), it's always the same (e.g. GCS).
29
+ */
30
+ endpointTemplate: string | null;
31
+ /** Known regions with labels. Empty = free-form region input. */
32
+ regions: ProviderRegion[];
33
+ /** Bucket label override (e.g. Azure uses "Container"). */
34
+ bucketLabel?: string;
35
+ /** Default endpoint placeholder shown in the input. */
36
+ endpointPlaceholder: string;
37
+ /** URI schemes that map to this provider (lowercase, without "://"). */
38
+ schemes: string[];
39
+ }
40
+ export declare const PROVIDERS: Record<ProviderId, ProviderDef>;
41
+ /** All provider IDs, ordered for the UI. */
42
+ export declare const PROVIDER_IDS: ProviderId[];
43
+ /** Get provider def, falling back to S3 for unknown. */
44
+ export declare function getProvider(id: string): ProviderDef;
45
+ /** Build endpoint URL from template + region. */
46
+ export declare function buildEndpointFromTemplate(id: ProviderId, region: string): string;
47
+ /**
48
+ * Build the base URL for API requests (endpoint + bucket).
49
+ * Used by browser-cloud adapter and url-state.
50
+ */
51
+ export declare function buildProviderBaseUrl(provider: ProviderId, endpoint: string, bucket: string, region: string): string;
52
+ /** Check if a provider uses the GCS JSON API (not S3 XML). */
53
+ export declare function isGcsProvider(provider: string, endpoint: string): boolean;
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Provider registry — single source of truth for all cloud storage providers.
3
+ *
4
+ * Centralizes endpoint patterns, regions, auth methods, and UI metadata.
5
+ * Used by ConnectionDialog, browser-cloud adapter, host-detection, url-state, etc.
6
+ */
7
+ // ---------------------------------------------------------------------------
8
+ // Registry
9
+ // ---------------------------------------------------------------------------
10
+ export const PROVIDERS = {
11
+ s3: {
12
+ label: 'AWS S3',
13
+ description: 'Amazon S3 or any S3-compatible service',
14
+ authMethod: 'sigv4',
15
+ needsRegion: true,
16
+ needsEndpoint: false,
17
+ defaultRegion: 'us-east-1',
18
+ endpointTemplate: null,
19
+ regions: [
20
+ { code: 'us-east-1', label: 'US East (N. Virginia)' },
21
+ { code: 'us-east-2', label: 'US East (Ohio)' },
22
+ { code: 'us-west-1', label: 'US West (N. California)' },
23
+ { code: 'us-west-2', label: 'US West (Oregon)' },
24
+ { code: 'eu-west-1', label: 'EU (Ireland)' },
25
+ { code: 'eu-west-2', label: 'EU (London)' },
26
+ { code: 'eu-west-3', label: 'EU (Paris)' },
27
+ { code: 'eu-central-1', label: 'EU (Frankfurt)' },
28
+ { code: 'eu-central-2', label: 'EU (Zurich)' },
29
+ { code: 'eu-north-1', label: 'EU (Stockholm)' },
30
+ { code: 'eu-south-1', label: 'EU (Milan)' },
31
+ { code: 'eu-south-2', label: 'EU (Spain)' },
32
+ { code: 'ap-northeast-1', label: 'Asia Pacific (Tokyo)' },
33
+ { code: 'ap-northeast-2', label: 'Asia Pacific (Seoul)' },
34
+ { code: 'ap-northeast-3', label: 'Asia Pacific (Osaka)' },
35
+ { code: 'ap-southeast-1', label: 'Asia Pacific (Singapore)' },
36
+ { code: 'ap-southeast-2', label: 'Asia Pacific (Sydney)' },
37
+ { code: 'ap-southeast-3', label: 'Asia Pacific (Jakarta)' },
38
+ { code: 'ap-south-1', label: 'Asia Pacific (Mumbai)' },
39
+ { code: 'ap-south-2', label: 'Asia Pacific (Hyderabad)' },
40
+ { code: 'ap-east-1', label: 'Asia Pacific (Hong Kong)' },
41
+ { code: 'sa-east-1', label: 'South America (São Paulo)' },
42
+ { code: 'ca-central-1', label: 'Canada (Central)' },
43
+ { code: 'ca-west-1', label: 'Canada (Calgary)' },
44
+ { code: 'me-south-1', label: 'Middle East (Bahrain)' },
45
+ { code: 'me-central-1', label: 'Middle East (UAE)' },
46
+ { code: 'af-south-1', label: 'Africa (Cape Town)' },
47
+ { code: 'il-central-1', label: 'Israel (Tel Aviv)' }
48
+ ],
49
+ endpointPlaceholder: 'Leave empty for AWS, or enter custom S3 endpoint',
50
+ schemes: ['s3', 's3a', 's3n', 'aws']
51
+ },
52
+ gcs: {
53
+ label: 'Google Cloud',
54
+ description: 'Google Cloud Storage',
55
+ authMethod: 'sigv4',
56
+ needsRegion: false,
57
+ needsEndpoint: false,
58
+ defaultRegion: 'auto',
59
+ endpointTemplate: 'https://storage.googleapis.com',
60
+ regions: [],
61
+ endpointPlaceholder: 'https://storage.googleapis.com',
62
+ schemes: ['gs', 'gcs']
63
+ },
64
+ r2: {
65
+ label: 'Cloudflare R2',
66
+ description: 'Cloudflare R2 Storage',
67
+ authMethod: 'sigv4',
68
+ needsRegion: false,
69
+ needsEndpoint: true,
70
+ defaultRegion: 'auto',
71
+ endpointTemplate: null,
72
+ regions: [],
73
+ endpointPlaceholder: 'https://<account-id>.r2.cloudflarestorage.com',
74
+ schemes: ['r2']
75
+ },
76
+ azure: {
77
+ label: 'Azure',
78
+ description: 'Azure Blob Storage',
79
+ authMethod: 'sas-token',
80
+ needsRegion: false,
81
+ needsEndpoint: true,
82
+ defaultRegion: '',
83
+ endpointTemplate: null,
84
+ regions: [],
85
+ bucketLabel: 'Container',
86
+ endpointPlaceholder: 'https://<account>.blob.core.windows.net',
87
+ schemes: ['azure', 'az', 'abfs', 'abfss', 'wasbs', 'adl']
88
+ },
89
+ minio: {
90
+ label: 'MinIO',
91
+ description: 'Self-hosted MinIO or S3-compatible',
92
+ authMethod: 'sigv4',
93
+ needsRegion: false,
94
+ needsEndpoint: true,
95
+ defaultRegion: 'us-east-1',
96
+ endpointTemplate: null,
97
+ regions: [],
98
+ endpointPlaceholder: 'https://minio.example.com or http://localhost:9000',
99
+ schemes: []
100
+ },
101
+ storj: {
102
+ label: 'Storj',
103
+ description: 'Storj Decentralized Cloud',
104
+ authMethod: 'sigv4',
105
+ needsRegion: false,
106
+ needsEndpoint: false,
107
+ defaultRegion: 'us1',
108
+ endpointTemplate: 'https://gateway.storjshare.io',
109
+ regions: [
110
+ { code: 'us1', label: 'US1' },
111
+ { code: 'eu1', label: 'EU1' },
112
+ { code: 'ap1', label: 'AP1' }
113
+ ],
114
+ endpointPlaceholder: 'https://gateway.storjshare.io',
115
+ schemes: ['storj', 'sj']
116
+ },
117
+ b2: {
118
+ label: 'Backblaze B2',
119
+ description: 'Backblaze B2 Cloud Storage',
120
+ authMethod: 'sigv4',
121
+ needsRegion: true,
122
+ needsEndpoint: false,
123
+ defaultRegion: 'us-west-004',
124
+ endpointTemplate: 'https://s3.{region}.backblazeb2.com',
125
+ regions: [
126
+ { code: 'us-west-000', label: 'US West (Sacramento)' },
127
+ { code: 'us-west-001', label: 'US West (Stockton)' },
128
+ { code: 'us-west-002', label: 'US West (Phoenix)' },
129
+ { code: 'us-west-004', label: 'US West' },
130
+ { code: 'us-east-005', label: 'US East (Reston)' },
131
+ { code: 'eu-central-003', label: 'EU Central (Amsterdam)' },
132
+ { code: 'ca-central-001', label: 'Canada (Toronto)' }
133
+ ],
134
+ endpointPlaceholder: 'https://s3.us-west-004.backblazeb2.com',
135
+ schemes: []
136
+ },
137
+ digitalocean: {
138
+ label: 'DigitalOcean',
139
+ description: 'DigitalOcean Spaces',
140
+ authMethod: 'sigv4',
141
+ needsRegion: true,
142
+ needsEndpoint: false,
143
+ defaultRegion: 'nyc3',
144
+ endpointTemplate: 'https://{region}.digitaloceanspaces.com',
145
+ regions: [
146
+ { code: 'nyc3', label: 'New York 3' },
147
+ { code: 'sfo3', label: 'San Francisco 3' },
148
+ { code: 'ams3', label: 'Amsterdam 3' },
149
+ { code: 'sgp1', label: 'Singapore 1' },
150
+ { code: 'lon1', label: 'London 1' },
151
+ { code: 'fra1', label: 'Frankfurt 1' },
152
+ { code: 'tor1', label: 'Toronto 1' },
153
+ { code: 'blr1', label: 'Bangalore 1' },
154
+ { code: 'syd1', label: 'Sydney 1' }
155
+ ],
156
+ endpointPlaceholder: 'https://nyc3.digitaloceanspaces.com',
157
+ schemes: []
158
+ },
159
+ wasabi: {
160
+ label: 'Wasabi',
161
+ description: 'Wasabi Hot Cloud Storage',
162
+ authMethod: 'sigv4',
163
+ needsRegion: true,
164
+ needsEndpoint: false,
165
+ defaultRegion: 'us-east-1',
166
+ endpointTemplate: 'https://s3.{region}.wasabisys.com',
167
+ regions: [
168
+ { code: 'us-east-1', label: 'US East 1 (Virginia)' },
169
+ { code: 'us-east-2', label: 'US East 2 (Virginia)' },
170
+ { code: 'us-central-1', label: 'US Central 1 (Texas)' },
171
+ { code: 'us-west-1', label: 'US West 1 (Oregon)' },
172
+ { code: 'eu-central-1', label: 'EU Central 1 (Amsterdam)' },
173
+ { code: 'eu-central-2', label: 'EU Central 2 (Frankfurt)' },
174
+ { code: 'eu-west-1', label: 'EU West 1 (London)' },
175
+ { code: 'eu-west-2', label: 'EU West 2 (Paris)' },
176
+ { code: 'ap-northeast-1', label: 'AP Northeast 1 (Tokyo)' },
177
+ { code: 'ap-northeast-2', label: 'AP Northeast 2 (Osaka)' },
178
+ { code: 'ap-southeast-1', label: 'AP Southeast 1 (Singapore)' },
179
+ { code: 'ap-southeast-2', label: 'AP Southeast 2 (Sydney)' },
180
+ { code: 'ca-central-1', label: 'Canada (Toronto)' }
181
+ ],
182
+ endpointPlaceholder: 'https://s3.us-east-1.wasabisys.com',
183
+ schemes: []
184
+ },
185
+ contabo: {
186
+ label: 'Contabo',
187
+ description: 'Contabo Object Storage',
188
+ authMethod: 'sigv4',
189
+ needsRegion: true,
190
+ needsEndpoint: false,
191
+ defaultRegion: 'eu2',
192
+ endpointTemplate: 'https://{region}.contabostorage.com',
193
+ regions: [
194
+ { code: 'eu2', label: 'European Union' },
195
+ { code: 'usc1', label: 'US Central' },
196
+ { code: 'sin1', label: 'Singapore' }
197
+ ],
198
+ endpointPlaceholder: 'https://eu2.contabostorage.com',
199
+ schemes: []
200
+ },
201
+ hetzner: {
202
+ label: 'Hetzner',
203
+ description: 'Hetzner Object Storage',
204
+ authMethod: 'sigv4',
205
+ needsRegion: true,
206
+ needsEndpoint: false,
207
+ defaultRegion: 'fsn1',
208
+ endpointTemplate: 'https://{region}.your-objectstorage.com',
209
+ regions: [
210
+ { code: 'fsn1', label: 'Falkenstein, DE' },
211
+ { code: 'nbg1', label: 'Nuremberg, DE' },
212
+ { code: 'hel1', label: 'Helsinki, FI' }
213
+ ],
214
+ endpointPlaceholder: 'https://fsn1.your-objectstorage.com',
215
+ schemes: []
216
+ },
217
+ linode: {
218
+ label: 'Linode / Akamai',
219
+ description: 'Akamai / Linode Object Storage',
220
+ authMethod: 'sigv4',
221
+ needsRegion: true,
222
+ needsEndpoint: false,
223
+ defaultRegion: 'us-east-1',
224
+ endpointTemplate: 'https://{region}.linodeobjects.com',
225
+ regions: [
226
+ { code: 'us-east-1', label: 'Newark, NJ' },
227
+ { code: 'us-southeast-1', label: 'Atlanta, GA' },
228
+ { code: 'us-ord-1', label: 'Chicago, IL' },
229
+ { code: 'us-iad-1', label: 'Washington, DC' },
230
+ { code: 'us-lax-1', label: 'Los Angeles, CA' },
231
+ { code: 'us-sea-1', label: 'Seattle, WA' },
232
+ { code: 'us-mia-1', label: 'Miami, FL' },
233
+ { code: 'eu-central-1', label: 'Frankfurt, DE' },
234
+ { code: 'nl-ams-1', label: 'Amsterdam, NL' },
235
+ { code: 'gb-lon-1', label: 'London, UK' },
236
+ { code: 'fr-par-1', label: 'Paris, FR' },
237
+ { code: 'ap-south-1', label: 'Singapore' },
238
+ { code: 'jp-osa-1', label: 'Osaka, JP' },
239
+ { code: 'au-mel-1', label: 'Melbourne, AU' },
240
+ { code: 'br-gru-1', label: 'São Paulo, BR' },
241
+ { code: 'in-maa-1', label: 'Chennai, IN' },
242
+ { code: 'id-cgk-1', label: 'Jakarta, ID' },
243
+ { code: 'it-mil-1', label: 'Milan, IT' },
244
+ { code: 'se-sto-1', label: 'Stockholm, SE' }
245
+ ],
246
+ endpointPlaceholder: 'https://us-east-1.linodeobjects.com',
247
+ schemes: []
248
+ },
249
+ ovhcloud: {
250
+ label: 'OVHcloud',
251
+ description: 'OVHcloud Object Storage',
252
+ authMethod: 'sigv4',
253
+ needsRegion: true,
254
+ needsEndpoint: false,
255
+ defaultRegion: 'gra',
256
+ endpointTemplate: 'https://s3.{region}.io.cloud.ovh.net',
257
+ regions: [
258
+ { code: 'gra', label: 'Gravelines, FR' },
259
+ { code: 'sbg', label: 'Strasbourg, FR' },
260
+ { code: 'bhs', label: 'Beauharnois, CA' },
261
+ { code: 'de', label: 'Frankfurt, DE' },
262
+ { code: 'uk', label: 'London, UK' },
263
+ { code: 'waw', label: 'Warsaw, PL' }
264
+ ],
265
+ endpointPlaceholder: 'https://s3.gra.io.cloud.ovh.net',
266
+ schemes: []
267
+ }
268
+ };
269
+ // ---------------------------------------------------------------------------
270
+ // Helpers
271
+ // ---------------------------------------------------------------------------
272
+ /** All provider IDs, ordered for the UI. */
273
+ export const PROVIDER_IDS = [
274
+ 's3',
275
+ 'gcs',
276
+ 'r2',
277
+ 'azure',
278
+ 'b2',
279
+ 'digitalocean',
280
+ 'wasabi',
281
+ 'storj',
282
+ 'hetzner',
283
+ 'contabo',
284
+ 'linode',
285
+ 'ovhcloud',
286
+ 'minio'
287
+ ];
288
+ /** Get provider def, falling back to S3 for unknown. */
289
+ export function getProvider(id) {
290
+ return PROVIDERS[id] ?? PROVIDERS.s3;
291
+ }
292
+ /** Build endpoint URL from template + region. */
293
+ export function buildEndpointFromTemplate(id, region) {
294
+ const def = PROVIDERS[id];
295
+ if (!def?.endpointTemplate)
296
+ return '';
297
+ return def.endpointTemplate.replace('{region}', region);
298
+ }
299
+ /**
300
+ * Build the base URL for API requests (endpoint + bucket).
301
+ * Used by browser-cloud adapter and url-state.
302
+ */
303
+ export function buildProviderBaseUrl(provider, endpoint, bucket, region) {
304
+ if (endpoint) {
305
+ return `${endpoint.replace(/\/$/, '')}/${bucket}`;
306
+ }
307
+ const def = PROVIDERS[provider];
308
+ if (def?.endpointTemplate) {
309
+ const resolved = def.endpointTemplate.replace('{region}', region || def.defaultRegion);
310
+ return `${resolved}/${bucket}`;
311
+ }
312
+ // Fallback: AWS S3 path-style
313
+ return `https://s3.${region || 'us-east-1'}.amazonaws.com/${bucket}`;
314
+ }
315
+ /** Check if a provider uses the GCS JSON API (not S3 XML). */
316
+ export function isGcsProvider(provider, endpoint) {
317
+ return provider === 'gcs' || (!!endpoint && /storage\.googleapis\.com/i.test(endpoint));
318
+ }
@@ -1,32 +1,6 @@
1
+ import { STORAGE_KEYS } from '../constants.js';
2
+ import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
1
3
  import { credentialStore, storeToNative } from './credentials.svelte.js';
2
- const CONNECTIONS_KEY = 'obstore-explore-connections';
3
- // ---------------------------------------------------------------------------
4
- // localStorage helpers
5
- // ---------------------------------------------------------------------------
6
- function loadFromLocalStorage() {
7
- if (typeof window === 'undefined')
8
- return [];
9
- try {
10
- const raw = localStorage.getItem(CONNECTIONS_KEY);
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(CONNECTIONS_KEY, JSON.stringify(connections));
25
- }
26
- catch {
27
- // ignore storage errors
28
- }
29
- }
30
4
  // ---------------------------------------------------------------------------
31
5
  // Store
32
6
  // ---------------------------------------------------------------------------
@@ -48,7 +22,7 @@ function createConnectionsStore() {
48
22
  async load() {
49
23
  if (loaded)
50
24
  return;
51
- connections = loadFromLocalStorage();
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
- persistToLocalStorage(connections);
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
- persistToLocalStorage(connections);
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
- persistToLocalStorage(connections);
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));
@@ -158,7 +132,7 @@ function createConnectionsStore() {
158
132
  * Test whether a connection is reachable via a lightweight list.
159
133
  */
160
134
  async test(id) {
161
- const { getAdapter } = await import(/* @vite-ignore */ '$lib/storage/index.js');
135
+ const { getAdapter } = await import('../storage/index.js');
162
136
  const adapter = getAdapter('remote', id);
163
137
  await adapter.list('');
164
138
  return true;
@@ -200,7 +174,7 @@ function createConnectionsStore() {
200
174
  }
201
175
  }
202
176
  try {
203
- const { getAdapter } = await import(/* @vite-ignore */ '$lib/storage/index.js');
177
+ const { getAdapter } = await import('../storage/index.js');
204
178
  const adapter = getAdapter('remote', tempId);
205
179
  await adapter.list(config.rootPrefix || '');
206
180
  return true;
@@ -1,10 +1,5 @@
1
1
  import type { FileEntry } from '../types.js';
2
- export type SortField = 'name' | 'size' | 'modified' | 'extension';
3
- export type SortDirection = 'asc' | 'desc';
4
- export interface SortConfig {
5
- field: SortField;
6
- direction: SortDirection;
7
- }
2
+ import { type SortConfig, type SortField } from '../utils/file-sort.js';
8
3
  export declare const fileStore: {
9
4
  readonly entries: FileEntry[];
10
5
  readonly currentPath: string;
@@ -1,27 +1,4 @@
1
- function sortEntries(entries, config) {
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 = sortEntries(entries, sortConfig);
25
+ files = sortFileEntries(entries, sortConfig);
49
26
  error = null;
50
27
  },
51
28
  setPath(path) {
@@ -58,17 +35,8 @@ function createFilesStore() {
58
35
  error = message;
59
36
  },
60
37
  sort(field) {
61
- if (sortConfig.field === field) {
62
- // Toggle direction if clicking the same field
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
  }
@@ -1,32 +1,9 @@
1
- const STORAGE_KEY = 'obstore-explore-query-history';
2
- const MAX_ENTRIES = 200;
3
- function loadEntries() {
4
- if (typeof window === 'undefined')
5
- return [];
6
- try {
7
- const raw = localStorage.getItem(STORAGE_KEY);
8
- if (raw)
9
- return JSON.parse(raw);
10
- }
11
- catch {
12
- // ignore parse errors
13
- }
14
- return [];
15
- }
16
- function persistEntries(entries) {
17
- if (typeof window === 'undefined')
18
- return;
19
- try {
20
- localStorage.setItem(STORAGE_KEY, JSON.stringify(entries));
21
- }
22
- catch {
23
- // ignore storage errors
24
- }
25
- }
1
+ import { MAX_QUERY_HISTORY_ENTRIES, STORAGE_KEYS } from '../constants.js';
2
+ import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
26
3
  function createQueryHistoryStore() {
27
- let entries = $state(loadEntries());
4
+ let entries = $state(loadFromStorage(STORAGE_KEYS.QUERY_HISTORY, []));
28
5
  function save() {
29
- persistEntries(entries);
6
+ persistToStorage(STORAGE_KEYS.QUERY_HISTORY, entries);
30
7
  }
31
8
  return {
32
9
  get entries() {
@@ -37,7 +14,7 @@ function createQueryHistoryStore() {
37
14
  ...entry,
38
15
  id: crypto.randomUUID()
39
16
  };
40
- entries = [newEntry, ...entries].slice(0, MAX_ENTRIES);
17
+ entries = [newEntry, ...entries].slice(0, MAX_QUERY_HISTORY_ENTRIES);
41
18
  save();
42
19
  },
43
20
  remove(id) {
@@ -1,5 +1,6 @@
1
1
  import { type Locale } from '../i18n/index.svelte.js';
2
2
  import type { Theme } from '../types.js';
3
+ export declare function resolveTheme(theme: Theme): 'light' | 'dark';
3
4
  export declare const settings: {
4
5
  readonly theme: Theme;
5
6
  readonly resolved: "light" | "dark";
@@ -1,36 +1,16 @@
1
+ import { STORAGE_KEYS } from '../constants.js';
1
2
  import { setLocale } from '../i18n/index.svelte.js';
2
- const SETTINGS_KEY = 'obstore-explore-settings';
3
+ import { loadFromStorage, persistToStorage } from '../utils/local-storage.js';
4
+ const SETTINGS_DEFAULTS = { theme: 'system', locale: 'en', featureLimit: 1000 };
3
5
  function loadSettings() {
4
- if (typeof window === 'undefined') {
5
- return { theme: 'system', locale: 'en', featureLimit: 1000 };
6
- }
7
- try {
8
- const raw = localStorage.getItem(SETTINGS_KEY);
9
- if (raw) {
10
- const parsed = JSON.parse(raw);
11
- return {
12
- theme: parsed.theme ?? 'system',
13
- locale: parsed.locale ?? 'en',
14
- featureLimit: parsed.featureLimit ?? 100
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(SETTINGS_KEY, 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
- persistSettings({ theme, locale, featureLimit });
34
+ persistToStorage(STORAGE_KEYS.SETTINGS, { theme, locale, featureLimit });
55
35
  }
56
36
  function applyTheme(t) {
57
37
  theme = t;
package/dist/types.d.ts CHANGED
@@ -9,7 +9,7 @@ export interface FileEntry {
9
9
  export interface Connection {
10
10
  id: string;
11
11
  name: string;
12
- provider: 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj';
12
+ provider: string;
13
13
  endpoint: string;
14
14
  bucket: string;
15
15
  region: string;
@@ -19,7 +19,7 @@ export interface Connection {
19
19
  }
20
20
  export interface ConnectionConfig {
21
21
  name: string;
22
- provider: 's3' | 'gcs' | 'r2' | 'minio' | 'azure' | 'storj';
22
+ provider: string;
23
23
  endpoint: string;
24
24
  bucket: string;
25
25
  region: string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Copy text to clipboard and run a feedback callback for COPY_FEEDBACK_MS.
3
+ * Silently catches clipboard errors (e.g. insecure context).
4
+ *
5
+ * @returns true if copy succeeded, false otherwise.
6
+ */
7
+ export declare function copyToClipboard(text: string, onFeedback?: (copied: boolean) => void): Promise<boolean>;
8
+ /**
9
+ * Wire click-to-copy on all elements matching `selector` inside `root`.
10
+ * Each element must have `data-code` (URI-encoded) with the text to copy.
11
+ * Adds/removes a `copied` CSS class for visual feedback.
12
+ */
13
+ export declare function wireCodeCopyButtons(root: Element, selector: string): void;