@walkthru-earth/objex 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/components/browser/FileBrowser.svelte +53 -41
- package/dist/components/browser/FileRow.svelte +8 -3
- package/dist/components/browser/FileTreeSidebar.svelte +2 -4
- package/dist/components/layout/AboutSheet.svelte +126 -0
- package/dist/components/layout/AboutSheet.svelte.d.ts +6 -0
- package/dist/components/layout/ConnectionDialog.svelte +186 -138
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +1 -0
- package/dist/components/layout/Sidebar.svelte +19 -3
- package/dist/components/layout/TabBar.svelte +4 -7
- package/dist/components/viewers/CodeViewer.svelte +17 -9
- package/dist/components/viewers/ImageViewer.svelte +6 -16
- package/dist/components/viewers/MarkdownViewer.svelte +8 -16
- package/dist/components/viewers/MediaViewer.svelte +6 -17
- package/dist/components/viewers/ModelViewer.svelte +4 -2
- package/dist/components/viewers/NotebookViewer.svelte +90 -40
- package/dist/components/viewers/PdfViewer.svelte +5 -3
- package/dist/components/viewers/RawViewer.svelte +4 -2
- package/dist/components/viewers/TableGrid.svelte +3 -2
- package/dist/components/viewers/ZarrMapViewer.svelte +334 -40
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +3 -8
- package/dist/components/viewers/ZarrViewer.svelte +459 -178
- package/dist/components/viewers/map/AttributeTable.svelte +1 -6
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +2 -6
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +96 -22
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +34 -0
- package/dist/file-icons/index.js +6 -0
- package/dist/i18n/ar.js +34 -0
- package/dist/i18n/en.js +34 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +16 -1
- package/dist/query/wasm.js +5 -4
- package/dist/storage/browser-cloud.d.ts +7 -0
- package/dist/storage/browser-cloud.js +74 -7
- package/dist/storage/providers.d.ts +53 -0
- package/dist/storage/providers.js +318 -0
- package/dist/stores/connections.svelte.js +8 -34
- package/dist/stores/files.svelte.d.ts +1 -6
- package/dist/stores/files.svelte.js +4 -36
- package/dist/stores/query-history.svelte.js +5 -28
- package/dist/stores/settings.svelte.d.ts +1 -0
- package/dist/stores/settings.svelte.js +11 -31
- package/dist/types.d.ts +2 -2
- package/dist/utils/clipboard.d.ts +13 -0
- package/dist/utils/clipboard.js +38 -0
- package/dist/utils/cloud-url.d.ts +27 -0
- package/dist/utils/cloud-url.js +61 -0
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.js +12 -0
- package/dist/utils/export.d.ts +22 -2
- package/dist/utils/export.js +35 -10
- package/dist/utils/file-sort.d.ts +20 -0
- package/dist/utils/file-sort.js +41 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.js +22 -0
- package/dist/utils/host-detection.js +78 -18
- package/dist/utils/local-storage.d.ts +16 -0
- package/dist/utils/local-storage.js +37 -0
- package/dist/utils/notebook.d.ts +59 -0
- package/dist/utils/notebook.js +211 -0
- package/dist/utils/parquet-metadata.js +1 -1
- package/dist/utils/pmtiles-tile.js +2 -1
- package/dist/utils/pmtiles.js +2 -1
- package/dist/utils/storage-url.d.ts +1 -1
- package/dist/utils/storage-url.js +82 -24
- package/dist/utils/url-state.js +2 -7
- package/dist/utils/url.d.ts +0 -2
- package/dist/utils/url.js +3 -29
- package/dist/utils/zarr.d.ts +60 -20
- package/dist/utils/zarr.js +450 -103
- package/package.json +66 -54
- package/dist/assets/favicon.svg +0 -17
- package/dist/components/CLAUDE.md +0 -44
- package/dist/components/viewers/CLAUDE.md +0 -60
- package/dist/file-icons/CLAUDE.md +0 -21
- package/dist/i18n/CLAUDE.md +0 -19
- package/dist/query/CLAUDE.md +0 -22
- package/dist/storage/CLAUDE.md +0 -23
- package/dist/stores/CLAUDE.md +0 -29
- package/dist/types/notebookjs.d.ts +0 -14
- package/dist/utils/CLAUDE.md +0 -54
- package/dist/utils/analytics.d.ts +0 -10
- package/dist/utils/analytics.js +0 -38
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { COPY_FEEDBACK_MS } from '../constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Copy text to clipboard and run a feedback callback for COPY_FEEDBACK_MS.
|
|
4
|
+
* Silently catches clipboard errors (e.g. insecure context).
|
|
5
|
+
*
|
|
6
|
+
* @returns true if copy succeeded, false otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export async function copyToClipboard(text, onFeedback) {
|
|
9
|
+
try {
|
|
10
|
+
await navigator.clipboard.writeText(text);
|
|
11
|
+
onFeedback?.(true);
|
|
12
|
+
setTimeout(() => onFeedback?.(false), COPY_FEEDBACK_MS);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Wire click-to-copy on all elements matching `selector` inside `root`.
|
|
21
|
+
* Each element must have `data-code` (URI-encoded) with the text to copy.
|
|
22
|
+
* Adds/removes a `copied` CSS class for visual feedback.
|
|
23
|
+
*/
|
|
24
|
+
export function wireCodeCopyButtons(root, selector) {
|
|
25
|
+
for (const btn of root.querySelectorAll(selector)) {
|
|
26
|
+
btn.addEventListener('click', async () => {
|
|
27
|
+
const code = decodeURIComponent(btn.dataset.code ?? '');
|
|
28
|
+
try {
|
|
29
|
+
await navigator.clipboard.writeText(code);
|
|
30
|
+
btn.classList.add('copied');
|
|
31
|
+
setTimeout(() => btn.classList.remove('copied'), COPY_FEEDBACK_MS);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// clipboard not available
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared error handling for async viewer load operations.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extract an error message from an unknown caught value.
|
|
6
|
+
* Returns null for AbortError (caller should silently return).
|
|
7
|
+
*/
|
|
8
|
+
export declare function handleLoadError(err: unknown): string | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared error handling for async viewer load operations.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extract an error message from an unknown caught value.
|
|
6
|
+
* Returns null for AbortError (caller should silently return).
|
|
7
|
+
*/
|
|
8
|
+
export function handleLoadError(err) {
|
|
9
|
+
if (err instanceof DOMException && err.name === 'AbortError')
|
|
10
|
+
return null;
|
|
11
|
+
return err instanceof Error ? err.message : String(err);
|
|
12
|
+
}
|
package/dist/utils/export.d.ts
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Escape a CSV field value per RFC 4180.
|
|
3
|
+
*/
|
|
4
|
+
export declare function escapeCsvField(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Serialize column/row data to a CSV string.
|
|
7
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serializeToCsv(columns: string[], rows: Record<string, unknown>[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Serialize column/row data to a formatted JSON string.
|
|
12
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
13
|
+
*/
|
|
14
|
+
export declare function serializeToJson(columns: string[], rows: Record<string, unknown>[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Export data as CSV file (triggers browser download).
|
|
17
|
+
*/
|
|
18
|
+
export declare function exportToCsv(columns: string[], rows: Record<string, unknown>[], filename: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Export data as JSON file (triggers browser download).
|
|
21
|
+
*/
|
|
22
|
+
export declare function exportToJson(columns: string[], rows: Record<string, unknown>[], filename: string): void;
|
package/dist/utils/export.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { jsonReplacerBigInt } from './format.js';
|
|
1
2
|
function triggerDownload(content, filename, mimeType) {
|
|
2
3
|
const blob = new Blob([content], { type: mimeType });
|
|
3
4
|
const url = URL.createObjectURL(blob);
|
|
@@ -9,30 +10,43 @@ function triggerDownload(content, filename, mimeType) {
|
|
|
9
10
|
document.body.removeChild(a);
|
|
10
11
|
URL.revokeObjectURL(url);
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
+
/** Format a cell value for export (empty string for null/undefined). */
|
|
14
|
+
function formatCellValue(value) {
|
|
13
15
|
if (value === null || value === undefined)
|
|
14
16
|
return '';
|
|
15
17
|
if (value instanceof Date)
|
|
16
18
|
return value.toISOString();
|
|
19
|
+
if (typeof value === 'bigint')
|
|
20
|
+
return value.toString();
|
|
17
21
|
if (typeof value === 'object')
|
|
18
|
-
return JSON.stringify(value);
|
|
22
|
+
return JSON.stringify(value, jsonReplacerBigInt);
|
|
19
23
|
return String(value);
|
|
20
24
|
}
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Escape a CSV field value per RFC 4180.
|
|
27
|
+
*/
|
|
28
|
+
export function escapeCsvField(value) {
|
|
22
29
|
if (value.includes(',') || value.includes('"') || value.includes('\n') || value.includes('\r')) {
|
|
23
30
|
return `"${value.replace(/"/g, '""')}"`;
|
|
24
31
|
}
|
|
25
32
|
return value;
|
|
26
33
|
}
|
|
27
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Serialize column/row data to a CSV string.
|
|
36
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
37
|
+
*/
|
|
38
|
+
export function serializeToCsv(columns, rows) {
|
|
28
39
|
const header = columns.map(escapeCsvField).join(',');
|
|
29
40
|
const body = rows
|
|
30
|
-
.map((row) => columns.map((col) => escapeCsvField(
|
|
41
|
+
.map((row) => columns.map((col) => escapeCsvField(formatCellValue(row[col]))).join(','))
|
|
31
42
|
.join('\n');
|
|
32
|
-
|
|
33
|
-
triggerDownload(csv, filename.endsWith('.csv') ? filename : `${filename}.csv`, 'text/csv;charset=utf-8;');
|
|
43
|
+
return `${header}\n${body}`;
|
|
34
44
|
}
|
|
35
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Serialize column/row data to a formatted JSON string.
|
|
47
|
+
* Pure function — no browser APIs, works in Node.js.
|
|
48
|
+
*/
|
|
49
|
+
export function serializeToJson(columns, rows) {
|
|
36
50
|
const data = rows.map((row) => {
|
|
37
51
|
const obj = {};
|
|
38
52
|
for (const col of columns) {
|
|
@@ -46,6 +60,17 @@ export function exportToJson(columns, rows, filename) {
|
|
|
46
60
|
}
|
|
47
61
|
return obj;
|
|
48
62
|
});
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
return JSON.stringify(data, jsonReplacerBigInt, 2);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Export data as CSV file (triggers browser download).
|
|
67
|
+
*/
|
|
68
|
+
export function exportToCsv(columns, rows, filename) {
|
|
69
|
+
triggerDownload(serializeToCsv(columns, rows), filename.endsWith('.csv') ? filename : `${filename}.csv`, 'text/csv;charset=utf-8;');
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Export data as JSON file (triggers browser download).
|
|
73
|
+
*/
|
|
74
|
+
export function exportToJson(columns, rows, filename) {
|
|
75
|
+
triggerDownload(serializeToJson(columns, rows), filename.endsWith('.json') ? filename : `${filename}.json`, 'application/json');
|
|
51
76
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure file entry sorting — framework-agnostic, works in Node.js.
|
|
3
|
+
*/
|
|
4
|
+
import type { FileEntry } from '../types.js';
|
|
5
|
+
export type SortField = 'name' | 'size' | 'modified' | 'extension';
|
|
6
|
+
export type SortDirection = 'asc' | 'desc';
|
|
7
|
+
export interface SortConfig {
|
|
8
|
+
field: SortField;
|
|
9
|
+
direction: SortDirection;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sort file entries by the given config.
|
|
13
|
+
* Directories always sort before files regardless of sort field.
|
|
14
|
+
* Returns a new array (does not mutate the input).
|
|
15
|
+
*/
|
|
16
|
+
export declare function sortFileEntries(entries: FileEntry[], config: SortConfig): FileEntry[];
|
|
17
|
+
/**
|
|
18
|
+
* Toggle sort config: same field flips direction, new field starts ascending.
|
|
19
|
+
*/
|
|
20
|
+
export declare function toggleSortField(current: SortConfig, field: SortField): SortConfig;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure file entry sorting — framework-agnostic, works in Node.js.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Sort file entries by the given config.
|
|
6
|
+
* Directories always sort before files regardless of sort field.
|
|
7
|
+
* Returns a new array (does not mutate the input).
|
|
8
|
+
*/
|
|
9
|
+
export function sortFileEntries(entries, config) {
|
|
10
|
+
const sorted = [...entries];
|
|
11
|
+
const dir = config.direction === 'asc' ? 1 : -1;
|
|
12
|
+
sorted.sort((a, b) => {
|
|
13
|
+
// Directories always come first
|
|
14
|
+
if (a.is_dir && !b.is_dir)
|
|
15
|
+
return -1;
|
|
16
|
+
if (!a.is_dir && b.is_dir)
|
|
17
|
+
return 1;
|
|
18
|
+
switch (config.field) {
|
|
19
|
+
case 'name':
|
|
20
|
+
return dir * a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
|
|
21
|
+
case 'size':
|
|
22
|
+
return dir * (a.size - b.size);
|
|
23
|
+
case 'modified':
|
|
24
|
+
return dir * (a.modified - b.modified);
|
|
25
|
+
case 'extension':
|
|
26
|
+
return dir * a.extension.localeCompare(b.extension, undefined, { sensitivity: 'base' });
|
|
27
|
+
default:
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return sorted;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Toggle sort config: same field flips direction, new field starts ascending.
|
|
35
|
+
*/
|
|
36
|
+
export function toggleSortField(current, field) {
|
|
37
|
+
if (current.field === field) {
|
|
38
|
+
return { field, direction: current.direction === 'asc' ? 'desc' : 'asc' };
|
|
39
|
+
}
|
|
40
|
+
return { field, direction: 'asc' };
|
|
41
|
+
}
|
package/dist/utils/format.d.ts
CHANGED
|
@@ -12,3 +12,13 @@ export declare function formatDate(timestamp: number): string;
|
|
|
12
12
|
* Returns an empty string if no extension is found.
|
|
13
13
|
*/
|
|
14
14
|
export declare function getFileExtension(filename: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* JSON replacer that converts BigInt values to strings.
|
|
17
|
+
* Use with `JSON.stringify(value, jsonReplacerBigInt)`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function jsonReplacerBigInt(_key: string, value: unknown): unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Format a value for display in tables, attribute panels, and exports.
|
|
22
|
+
* Handles null, undefined, Date, BigInt, and objects uniformly.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatValue(value: unknown): string;
|
package/dist/utils/format.js
CHANGED
|
@@ -54,3 +54,25 @@ export function getFileExtension(filename) {
|
|
|
54
54
|
return '';
|
|
55
55
|
return filename.slice(lastDot).toLowerCase();
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* JSON replacer that converts BigInt values to strings.
|
|
59
|
+
* Use with `JSON.stringify(value, jsonReplacerBigInt)`.
|
|
60
|
+
*/
|
|
61
|
+
export function jsonReplacerBigInt(_key, value) {
|
|
62
|
+
return typeof value === 'bigint' ? value.toString() : value;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Format a value for display in tables, attribute panels, and exports.
|
|
66
|
+
* Handles null, undefined, Date, BigInt, and objects uniformly.
|
|
67
|
+
*/
|
|
68
|
+
export function formatValue(value) {
|
|
69
|
+
if (value === null || value === undefined)
|
|
70
|
+
return 'NULL';
|
|
71
|
+
if (value instanceof Date)
|
|
72
|
+
return value.toISOString();
|
|
73
|
+
if (typeof value === 'bigint')
|
|
74
|
+
return value.toString();
|
|
75
|
+
if (typeof value === 'object')
|
|
76
|
+
return JSON.stringify(value, jsonReplacerBigInt);
|
|
77
|
+
return String(value);
|
|
78
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Also extracts `rootPrefix` when the app is hosted inside a subfolder.
|
|
9
9
|
*/
|
|
10
|
+
import { buildProviderBaseUrl } from '../storage/providers.js';
|
|
10
11
|
import { parseStorageUrl } from './storage-url.js';
|
|
11
12
|
/**
|
|
12
13
|
* Extract root prefix from pathname.
|
|
@@ -28,20 +29,8 @@ function extractRootPrefix(pathname) {
|
|
|
28
29
|
/**
|
|
29
30
|
* Build a normalized API endpoint URL for a detected provider.
|
|
30
31
|
*/
|
|
31
|
-
function buildBucketUrl(provider, endpoint, bucket) {
|
|
32
|
-
|
|
33
|
-
return `${endpoint.replace(/\/$/, '')}/${bucket}`;
|
|
34
|
-
}
|
|
35
|
-
switch (provider) {
|
|
36
|
-
case 'gcs':
|
|
37
|
-
return `https://storage.googleapis.com/${bucket}`;
|
|
38
|
-
case 'azure':
|
|
39
|
-
return `${endpoint}/${bucket}`;
|
|
40
|
-
case 'storj':
|
|
41
|
-
return `https://gateway.storjshare.io/${bucket}`;
|
|
42
|
-
default:
|
|
43
|
-
return `https://s3.us-east-1.amazonaws.com/${bucket}`;
|
|
44
|
-
}
|
|
32
|
+
function buildBucketUrl(provider, endpoint, bucket, region) {
|
|
33
|
+
return buildProviderBaseUrl((provider === 'unknown' ? 's3' : provider), endpoint, bucket, region || '');
|
|
45
34
|
}
|
|
46
35
|
/**
|
|
47
36
|
* Detect hosting bucket from current URL.
|
|
@@ -178,7 +167,7 @@ export function detectHostBucket() {
|
|
|
178
167
|
const doSpaces = host.match(/^(.+)\.([a-z0-9-]+)\.digitaloceanspaces\.com$/);
|
|
179
168
|
if (doSpaces) {
|
|
180
169
|
return {
|
|
181
|
-
provider: '
|
|
170
|
+
provider: 'digitalocean',
|
|
182
171
|
bucket: doSpaces[1],
|
|
183
172
|
region: doSpaces[2],
|
|
184
173
|
endpoint: `https://${doSpaces[2]}.digitaloceanspaces.com`,
|
|
@@ -190,7 +179,7 @@ export function detectHostBucket() {
|
|
|
190
179
|
const doCdn = host.match(/^(.+)\.([a-z0-9-]+)\.cdn\.digitaloceanspaces\.com$/);
|
|
191
180
|
if (doCdn) {
|
|
192
181
|
return {
|
|
193
|
-
provider: '
|
|
182
|
+
provider: 'digitalocean',
|
|
194
183
|
bucket: doCdn[1],
|
|
195
184
|
region: doCdn[2],
|
|
196
185
|
endpoint: `https://${doCdn[2]}.digitaloceanspaces.com`,
|
|
@@ -204,7 +193,7 @@ export function detectHostBucket() {
|
|
|
204
193
|
const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);
|
|
205
194
|
if (parts.length > 0) {
|
|
206
195
|
return {
|
|
207
|
-
provider: '
|
|
196
|
+
provider: 'wasabi',
|
|
208
197
|
bucket: parts[0],
|
|
209
198
|
region: wasabi[1],
|
|
210
199
|
endpoint: `https://${host}`,
|
|
@@ -217,7 +206,7 @@ export function detectHostBucket() {
|
|
|
217
206
|
const b2s3 = host.match(/^(.+)\.s3\.([a-z0-9-]+)\.backblazeb2\.com$/);
|
|
218
207
|
if (b2s3) {
|
|
219
208
|
return {
|
|
220
|
-
provider: '
|
|
209
|
+
provider: 'b2',
|
|
221
210
|
bucket: b2s3[1],
|
|
222
211
|
region: b2s3[2],
|
|
223
212
|
endpoint: `https://s3.${b2s3[2]}.backblazeb2.com`,
|
|
@@ -267,6 +256,77 @@ export function detectHostBucket() {
|
|
|
267
256
|
};
|
|
268
257
|
}
|
|
269
258
|
}
|
|
259
|
+
// Contabo: <region>.contabostorage.com/<bucket>
|
|
260
|
+
const contabo = host.match(/^([a-z0-9]+)\.contabostorage\.com$/);
|
|
261
|
+
if (contabo) {
|
|
262
|
+
const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);
|
|
263
|
+
if (parts.length > 0) {
|
|
264
|
+
return {
|
|
265
|
+
provider: 'contabo',
|
|
266
|
+
bucket: parts[0],
|
|
267
|
+
region: contabo[1],
|
|
268
|
+
endpoint: `${url.protocol}//${url.host}`,
|
|
269
|
+
rootPrefix: parts.length > 1 ? extractRootPrefix(`/${parts.slice(1).join('/')}`) : '',
|
|
270
|
+
bucketUrl: `${url.protocol}//${url.host}/${parts[0]}`
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Hetzner: <region>.your-objectstorage.com/<bucket>
|
|
275
|
+
const hetzner = host.match(/^([a-z0-9]+)\.your-objectstorage\.com$/);
|
|
276
|
+
if (hetzner) {
|
|
277
|
+
const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);
|
|
278
|
+
if (parts.length > 0) {
|
|
279
|
+
return {
|
|
280
|
+
provider: 'hetzner',
|
|
281
|
+
bucket: parts[0],
|
|
282
|
+
region: hetzner[1],
|
|
283
|
+
endpoint: `${url.protocol}//${url.host}`,
|
|
284
|
+
rootPrefix: parts.length > 1 ? extractRootPrefix(`/${parts.slice(1).join('/')}`) : '',
|
|
285
|
+
bucketUrl: `${url.protocol}//${url.host}/${parts[0]}`
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Linode / Akamai: <bucket>.<region>.linodeobjects.com or <region>.linodeobjects.com/<bucket>
|
|
290
|
+
const linodeVhost = host.match(/^(.+)\.([a-z0-9-]+)\.linodeobjects\.com$/);
|
|
291
|
+
if (linodeVhost) {
|
|
292
|
+
return {
|
|
293
|
+
provider: 'linode',
|
|
294
|
+
bucket: linodeVhost[1],
|
|
295
|
+
region: linodeVhost[2],
|
|
296
|
+
endpoint: `https://${linodeVhost[2]}.linodeobjects.com`,
|
|
297
|
+
rootPrefix: extractRootPrefix(pathname),
|
|
298
|
+
bucketUrl: `https://${linodeVhost[2]}.linodeobjects.com/${linodeVhost[1]}`
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
const linodePath = host.match(/^([a-z0-9-]+)\.linodeobjects\.com$/);
|
|
302
|
+
if (linodePath) {
|
|
303
|
+
const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);
|
|
304
|
+
if (parts.length > 0) {
|
|
305
|
+
return {
|
|
306
|
+
provider: 'linode',
|
|
307
|
+
bucket: parts[0],
|
|
308
|
+
region: linodePath[1],
|
|
309
|
+
endpoint: `https://${url.host}`,
|
|
310
|
+
rootPrefix: parts.length > 1 ? extractRootPrefix(`/${parts.slice(1).join('/')}`) : '',
|
|
311
|
+
bucketUrl: `https://${url.host}/${parts[0]}`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// OVHcloud: s3.<region>.io.cloud.ovh.net/<bucket>
|
|
316
|
+
const ovh = host.match(/^s3\.([a-z0-9-]+)\.io\.cloud\.ovh\.(?:net|us)$/);
|
|
317
|
+
if (ovh) {
|
|
318
|
+
const parts = pathname.replace(/^\//, '').split('/').filter(Boolean);
|
|
319
|
+
if (parts.length > 0) {
|
|
320
|
+
return {
|
|
321
|
+
provider: 'ovhcloud',
|
|
322
|
+
bucket: parts[0],
|
|
323
|
+
region: ovh[1],
|
|
324
|
+
endpoint: `https://${url.host}`,
|
|
325
|
+
rootPrefix: parts.length > 1 ? extractRootPrefix(`/${parts.slice(1).join('/')}`) : '',
|
|
326
|
+
bucketUrl: `https://${url.host}/${parts[0]}`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
270
330
|
// MinIO / localhost / private IPs
|
|
271
331
|
const isLocal = host === 'localhost' ||
|
|
272
332
|
host === '127.0.0.1' ||
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic localStorage helpers with SSR safety.
|
|
3
|
+
*
|
|
4
|
+
* Used by connection, settings, and query-history stores to avoid
|
|
5
|
+
* repeating the same load/persist/try-catch/SSR-guard pattern.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Load a JSON value from localStorage.
|
|
9
|
+
* Returns `defaultValue` on SSR, missing key, or parse error.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadFromStorage<T>(key: string, defaultValue: T): T;
|
|
12
|
+
/**
|
|
13
|
+
* Persist a JSON-serializable value to localStorage.
|
|
14
|
+
* Silently no-ops on SSR or storage errors (quota, private browsing).
|
|
15
|
+
*/
|
|
16
|
+
export declare function persistToStorage(key: string, value: unknown): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic localStorage helpers with SSR safety.
|
|
3
|
+
*
|
|
4
|
+
* Used by connection, settings, and query-history stores to avoid
|
|
5
|
+
* repeating the same load/persist/try-catch/SSR-guard pattern.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Load a JSON value from localStorage.
|
|
9
|
+
* Returns `defaultValue` on SSR, missing key, or parse error.
|
|
10
|
+
*/
|
|
11
|
+
export function loadFromStorage(key, defaultValue) {
|
|
12
|
+
if (typeof window === 'undefined')
|
|
13
|
+
return defaultValue;
|
|
14
|
+
try {
|
|
15
|
+
const raw = localStorage.getItem(key);
|
|
16
|
+
if (raw)
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// ignore parse errors
|
|
21
|
+
}
|
|
22
|
+
return defaultValue;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Persist a JSON-serializable value to localStorage.
|
|
26
|
+
* Silently no-ops on SSR or storage errors (quota, private browsing).
|
|
27
|
+
*/
|
|
28
|
+
export function persistToStorage(key, value) {
|
|
29
|
+
if (typeof window === 'undefined')
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// ignore storage errors
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight browser-native Jupyter notebook renderer.
|
|
3
|
+
* Replaces `notebookjs` which depends on jsdom/Buffer (Node.js only).
|
|
4
|
+
* Handles nbformat 2, 3, 4, and 5.
|
|
5
|
+
*/
|
|
6
|
+
export interface NotebookConfig {
|
|
7
|
+
markdown: (md: string) => string;
|
|
8
|
+
ansi: (text: string) => string;
|
|
9
|
+
highlighter: (code: string, lang: string) => string;
|
|
10
|
+
}
|
|
11
|
+
interface RawNotebook {
|
|
12
|
+
nbformat: number;
|
|
13
|
+
nbformat_minor?: number;
|
|
14
|
+
metadata?: Record<string, any>;
|
|
15
|
+
cells?: RawCell[];
|
|
16
|
+
worksheets?: {
|
|
17
|
+
cells: RawCell[];
|
|
18
|
+
}[];
|
|
19
|
+
}
|
|
20
|
+
interface RawCell {
|
|
21
|
+
cell_type: string;
|
|
22
|
+
source?: string | string[];
|
|
23
|
+
input?: string | string[];
|
|
24
|
+
outputs?: RawOutput[];
|
|
25
|
+
prompt_number?: number;
|
|
26
|
+
execution_count?: number | null;
|
|
27
|
+
level?: number;
|
|
28
|
+
language?: string;
|
|
29
|
+
}
|
|
30
|
+
interface RawOutput {
|
|
31
|
+
output_type: string;
|
|
32
|
+
data?: Record<string, string | string[]>;
|
|
33
|
+
text?: string | string[];
|
|
34
|
+
stream?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
png?: string;
|
|
37
|
+
jpeg?: string;
|
|
38
|
+
svg?: string;
|
|
39
|
+
html?: string;
|
|
40
|
+
latex?: string;
|
|
41
|
+
traceback?: string[];
|
|
42
|
+
ename?: string;
|
|
43
|
+
evalue?: string;
|
|
44
|
+
[key: string]: any;
|
|
45
|
+
}
|
|
46
|
+
export interface NotebookMeta {
|
|
47
|
+
kernelName: string;
|
|
48
|
+
language: string;
|
|
49
|
+
cellCount: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Parse and render a Jupyter notebook JSON to a DOM element.
|
|
53
|
+
* Returns the rendered element and metadata.
|
|
54
|
+
*/
|
|
55
|
+
export declare function renderNotebook(raw: RawNotebook, config: NotebookConfig): {
|
|
56
|
+
element: HTMLElement;
|
|
57
|
+
meta: NotebookMeta;
|
|
58
|
+
};
|
|
59
|
+
export {};
|