@walkthru-earth/objex 0.1.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/components/browser/FileBrowser.svelte +53 -41
- package/dist/components/browser/FileRow.svelte +8 -3
- package/dist/components/browser/FileTreeSidebar.svelte +2 -4
- package/dist/components/layout/AboutSheet.svelte +126 -0
- package/dist/components/layout/AboutSheet.svelte.d.ts +6 -0
- package/dist/components/layout/ConnectionDialog.svelte +186 -138
- package/dist/components/layout/ConnectionDialog.svelte.d.ts +1 -0
- package/dist/components/layout/Sidebar.svelte +19 -3
- package/dist/components/layout/TabBar.svelte +4 -7
- package/dist/components/viewers/CodeViewer.svelte +17 -9
- package/dist/components/viewers/ImageViewer.svelte +6 -16
- package/dist/components/viewers/MarkdownViewer.svelte +8 -16
- package/dist/components/viewers/MediaViewer.svelte +6 -17
- package/dist/components/viewers/ModelViewer.svelte +4 -2
- package/dist/components/viewers/NotebookViewer.svelte +90 -40
- package/dist/components/viewers/PdfViewer.svelte +5 -3
- package/dist/components/viewers/RawViewer.svelte +4 -2
- package/dist/components/viewers/TableGrid.svelte +3 -2
- package/dist/components/viewers/ZarrMapViewer.svelte +334 -40
- package/dist/components/viewers/ZarrMapViewer.svelte.d.ts +3 -8
- package/dist/components/viewers/ZarrViewer.svelte +459 -178
- package/dist/components/viewers/map/AttributeTable.svelte +1 -6
- package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +2 -6
- package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +96 -22
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +34 -0
- package/dist/file-icons/index.js +6 -0
- package/dist/i18n/ar.js +34 -0
- package/dist/i18n/en.js +34 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +16 -1
- package/dist/query/wasm.js +5 -4
- package/dist/storage/browser-cloud.d.ts +7 -0
- package/dist/storage/browser-cloud.js +74 -7
- package/dist/storage/providers.d.ts +53 -0
- package/dist/storage/providers.js +318 -0
- package/dist/stores/connections.svelte.js +8 -34
- package/dist/stores/files.svelte.d.ts +1 -6
- package/dist/stores/files.svelte.js +4 -36
- package/dist/stores/query-history.svelte.js +5 -28
- package/dist/stores/settings.svelte.d.ts +1 -0
- package/dist/stores/settings.svelte.js +11 -31
- package/dist/types.d.ts +2 -2
- package/dist/utils/clipboard.d.ts +13 -0
- package/dist/utils/clipboard.js +38 -0
- package/dist/utils/cloud-url.d.ts +27 -0
- package/dist/utils/cloud-url.js +61 -0
- package/dist/utils/error.d.ts +8 -0
- package/dist/utils/error.js +12 -0
- package/dist/utils/export.d.ts +22 -2
- package/dist/utils/export.js +35 -10
- package/dist/utils/file-sort.d.ts +20 -0
- package/dist/utils/file-sort.js +41 -0
- package/dist/utils/format.d.ts +10 -0
- package/dist/utils/format.js +22 -0
- package/dist/utils/host-detection.js +78 -18
- package/dist/utils/local-storage.d.ts +16 -0
- package/dist/utils/local-storage.js +37 -0
- package/dist/utils/notebook.d.ts +59 -0
- package/dist/utils/notebook.js +211 -0
- package/dist/utils/parquet-metadata.js +1 -1
- package/dist/utils/pmtiles-tile.js +2 -1
- package/dist/utils/pmtiles.js +2 -1
- package/dist/utils/storage-url.d.ts +1 -1
- package/dist/utils/storage-url.js +82 -24
- package/dist/utils/url-state.js +2 -7
- package/dist/utils/url.d.ts +0 -2
- package/dist/utils/url.js +3 -29
- package/dist/utils/zarr.d.ts +60 -20
- package/dist/utils/zarr.js +450 -103
- package/package.json +66 -54
- package/dist/assets/favicon.svg +0 -17
- package/dist/components/CLAUDE.md +0 -44
- package/dist/components/viewers/CLAUDE.md +0 -60
- package/dist/file-icons/CLAUDE.md +0 -21
- package/dist/i18n/CLAUDE.md +0 -19
- package/dist/query/CLAUDE.md +0 -22
- package/dist/storage/CLAUDE.md +0 -23
- package/dist/stores/CLAUDE.md +0 -29
- package/dist/types/notebookjs.d.ts +0 -14
- package/dist/utils/CLAUDE.md +0 -54
- package/dist/utils/analytics.d.ts +0 -10
- package/dist/utils/analytics.js +0 -38
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte';
|
|
3
|
+
import { getMimeType } from '../../file-icons/index.js';
|
|
3
4
|
import { getAdapter } from '../../storage/index.js';
|
|
4
5
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
5
6
|
import type { Tab } from '../../types';
|
|
7
|
+
import { handleLoadError } from '../../utils/error.js';
|
|
6
8
|
import { buildHttpsUrl, canStreamDirectly } from '../../utils/url.js';
|
|
7
9
|
|
|
8
10
|
let { tab }: { tab: Tab } = $props();
|
|
@@ -10,19 +12,6 @@ let { tab }: { tab: Tab } = $props();
|
|
|
10
12
|
const videoExtensions = new Set(['mp4', 'webm', 'mov', 'avi', 'mkv']);
|
|
11
13
|
const mediaType = $derived(videoExtensions.has(tab.extension.toLowerCase()) ? 'video' : 'audio');
|
|
12
14
|
|
|
13
|
-
const mimeMap: Record<string, string> = {
|
|
14
|
-
mp4: 'video/mp4',
|
|
15
|
-
webm: 'video/webm',
|
|
16
|
-
mov: 'video/quicktime',
|
|
17
|
-
avi: 'video/x-msvideo',
|
|
18
|
-
mkv: 'video/x-matroska',
|
|
19
|
-
mp3: 'audio/mpeg',
|
|
20
|
-
wav: 'audio/wav',
|
|
21
|
-
ogg: 'audio/ogg',
|
|
22
|
-
flac: 'audio/flac',
|
|
23
|
-
aac: 'audio/aac'
|
|
24
|
-
};
|
|
25
|
-
|
|
26
15
|
let abortController: AbortController | null = null;
|
|
27
16
|
let mediaSrc = $state<string | null>(null);
|
|
28
17
|
let blobUrl = $state<string | null>(null);
|
|
@@ -51,14 +40,14 @@ async function loadMedia() {
|
|
|
51
40
|
// Authenticated S3 — download via storage adapter (blob fallback)
|
|
52
41
|
const adapter = getAdapter(tab.source, tab.connectionId);
|
|
53
42
|
const data = await adapter.read(tab.path, undefined, undefined, signal);
|
|
54
|
-
const
|
|
55
|
-
const blob = new Blob([data as unknown as BlobPart], { type: mime });
|
|
43
|
+
const blob = new Blob([data as unknown as BlobPart], { type: getMimeType(tab.extension) });
|
|
56
44
|
blobUrl = URL.createObjectURL(blob);
|
|
57
45
|
mediaSrc = blobUrl;
|
|
58
46
|
}
|
|
59
47
|
} catch (err) {
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
const msg = handleLoadError(err);
|
|
49
|
+
if (msg === null) return;
|
|
50
|
+
error = msg;
|
|
62
51
|
} finally {
|
|
63
52
|
loading = false;
|
|
64
53
|
}
|
|
@@ -11,6 +11,7 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
11
11
|
import { getAdapter } from '../../storage/index.js';
|
|
12
12
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
13
13
|
import type { Tab } from '../../types';
|
|
14
|
+
import { handleLoadError } from '../../utils/error.js';
|
|
14
15
|
import {
|
|
15
16
|
createModelScene,
|
|
16
17
|
disposeModelScene,
|
|
@@ -52,8 +53,9 @@ async function loadModelFile() {
|
|
|
52
53
|
meshCount = info.meshCount;
|
|
53
54
|
vertexCount = info.vertexCount;
|
|
54
55
|
} catch (err) {
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const msg = handleLoadError(err);
|
|
57
|
+
if (msg === null) return;
|
|
58
|
+
error = msg;
|
|
57
59
|
} finally {
|
|
58
60
|
loading = false;
|
|
59
61
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
|
|
3
|
-
import {
|
|
3
|
+
import type { BundledLanguage } from 'shiki';
|
|
4
|
+
import { onDestroy, tick } from 'svelte';
|
|
4
5
|
import { Badge } from '../ui/badge/index.js';
|
|
5
6
|
import { Button } from '../ui/button/index.js';
|
|
6
7
|
import * as DropdownMenu from '../ui/dropdown-menu/index.js';
|
|
@@ -8,7 +9,10 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
8
9
|
import { getAdapter } from '../../storage/index.js';
|
|
9
10
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
10
11
|
import type { Tab } from '../../types';
|
|
11
|
-
import {
|
|
12
|
+
import { copyToClipboard, wireCodeCopyButtons } from '../../utils/clipboard.js';
|
|
13
|
+
import { handleLoadError } from '../../utils/error.js';
|
|
14
|
+
import { renderNotebook } from '../../utils/notebook';
|
|
15
|
+
import { highlightCodeReversed } from '../../utils/shiki';
|
|
12
16
|
|
|
13
17
|
let { tab }: { tab: Tab } = $props();
|
|
14
18
|
|
|
@@ -58,60 +62,58 @@ async function loadNotebook() {
|
|
|
58
62
|
rawContent = new TextDecoder().decode(data);
|
|
59
63
|
const notebook = JSON.parse(rawContent);
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
// Accept nbformat 2–5: v4+ has top-level `cells`, v2/v3 uses `worksheets`
|
|
66
|
+
if (
|
|
67
|
+
typeof notebook.nbformat !== 'number' ||
|
|
68
|
+
(!Array.isArray(notebook.cells) && !Array.isArray(notebook.worksheets))
|
|
69
|
+
) {
|
|
62
70
|
throw new Error('Not a valid Jupyter notebook');
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
kernelName =
|
|
67
|
-
notebook.metadata?.kernelspec?.display_name ?? notebook.metadata?.language_info?.name ?? '';
|
|
68
|
-
|
|
69
|
-
await renderNotebook(notebook);
|
|
73
|
+
await renderNotebookContent(notebook);
|
|
70
74
|
} catch (err) {
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
const msg = handleLoadError(err);
|
|
76
|
+
if (msg === null) return;
|
|
77
|
+
error = msg;
|
|
73
78
|
} finally {
|
|
74
79
|
loading = false;
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
async function
|
|
83
|
+
async function renderNotebookContent(notebook: any) {
|
|
84
|
+
await tick();
|
|
79
85
|
if (!container) return;
|
|
80
86
|
|
|
81
|
-
const [
|
|
82
|
-
import('notebookjs').then((m) => m.default || m),
|
|
83
|
-
import('marked'),
|
|
84
|
-
import('ansi_up')
|
|
85
|
-
]);
|
|
87
|
+
const [{ marked }, { AnsiUp }] = await Promise.all([import('marked'), import('ansi_up')]);
|
|
86
88
|
|
|
87
|
-
// Configure notebookjs
|
|
88
|
-
nb.markdown = (md: string) => marked.parse(md, { async: false }) as string;
|
|
89
89
|
const ansiUp = new AnsiUp();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
};
|
|
90
|
+
const { element, meta } = renderNotebook(notebook, {
|
|
91
|
+
markdown: (md: string) => marked.parse(md, { async: false }) as string,
|
|
92
|
+
ansi: (text: string) => ansiUp.ansi_to_html(text),
|
|
93
|
+
highlighter: (code: string) => code
|
|
94
|
+
});
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
cellCount = meta.cellCount;
|
|
97
|
+
kernelName = meta.kernelName;
|
|
98
98
|
|
|
99
99
|
container.innerHTML = '';
|
|
100
|
-
container.appendChild(
|
|
100
|
+
container.appendChild(element);
|
|
101
101
|
|
|
102
|
-
// Apply Shiki syntax highlighting to code cells
|
|
102
|
+
// Apply Shiki syntax highlighting + copy buttons to code cells
|
|
103
103
|
const codeBlocks = container.querySelectorAll('.nb-input pre');
|
|
104
104
|
for (const block of codeBlocks) {
|
|
105
105
|
const code = block.textContent ?? '';
|
|
106
|
-
const lang =
|
|
107
|
-
notebook.metadata?.kernelspec?.language ?? notebook.metadata?.language_info?.name ?? 'python';
|
|
108
106
|
try {
|
|
109
|
-
const
|
|
110
|
-
|
|
107
|
+
const highlighted = await highlightCodeReversed(code, meta.language as BundledLanguage);
|
|
108
|
+
const copyBtn = `<button class="nb-copy-btn" data-code="${encodeURIComponent(code)}" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button>`;
|
|
109
|
+
block.outerHTML = copyBtn + highlighted;
|
|
111
110
|
} catch {
|
|
112
111
|
// Shiki doesn't support this language — keep the escaped HTML
|
|
113
112
|
}
|
|
114
113
|
}
|
|
114
|
+
|
|
115
|
+
// Wire copy button click handlers
|
|
116
|
+
wireCodeCopyButtons(container, '.nb-copy-btn');
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function toggleCode() {
|
|
@@ -125,13 +127,7 @@ function toggleCode() {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
async function copyRaw() {
|
|
128
|
-
|
|
129
|
-
await navigator.clipboard.writeText(rawContent);
|
|
130
|
-
copied = true;
|
|
131
|
-
setTimeout(() => (copied = false), 2000);
|
|
132
|
-
} catch {
|
|
133
|
-
// clipboard not available
|
|
134
|
-
}
|
|
130
|
+
await copyToClipboard(rawContent, (v) => (copied = v));
|
|
135
131
|
}
|
|
136
132
|
</script>
|
|
137
133
|
|
|
@@ -191,9 +187,8 @@ async function copyRaw() {
|
|
|
191
187
|
<div class="flex h-full items-center justify-center">
|
|
192
188
|
<p class="text-sm text-red-400">{error}</p>
|
|
193
189
|
</div>
|
|
194
|
-
{:else}
|
|
195
|
-
<div bind:this={container} class="notebook-content"></div>
|
|
196
190
|
{/if}
|
|
191
|
+
<div bind:this={container} class="notebook-content" class:hidden={loading || !!error}></div>
|
|
197
192
|
</div>
|
|
198
193
|
</div>
|
|
199
194
|
|
|
@@ -215,6 +210,7 @@ async function copyRaw() {
|
|
|
215
210
|
|
|
216
211
|
/* Code input cells */
|
|
217
212
|
.notebook-content :global(.nb-input) {
|
|
213
|
+
position: relative;
|
|
218
214
|
border: 1px solid var(--border);
|
|
219
215
|
border-radius: 0.375rem;
|
|
220
216
|
overflow: hidden;
|
|
@@ -386,4 +382,58 @@ async function copyRaw() {
|
|
|
386
382
|
font-family: monospace;
|
|
387
383
|
font-size: 0.8125rem;
|
|
388
384
|
}
|
|
385
|
+
|
|
386
|
+
/* Copy button on code cells */
|
|
387
|
+
.notebook-content :global(.nb-copy-btn) {
|
|
388
|
+
position: absolute;
|
|
389
|
+
top: 6px;
|
|
390
|
+
right: 6px;
|
|
391
|
+
z-index: 1;
|
|
392
|
+
display: flex;
|
|
393
|
+
align-items: center;
|
|
394
|
+
gap: 4px;
|
|
395
|
+
padding: 4px 6px;
|
|
396
|
+
border: none;
|
|
397
|
+
border-radius: 4px;
|
|
398
|
+
background: rgba(255, 255, 255, 0.1);
|
|
399
|
+
color: rgba(255, 255, 255, 0.6);
|
|
400
|
+
cursor: pointer;
|
|
401
|
+
font-size: 0.6875rem;
|
|
402
|
+
opacity: 0;
|
|
403
|
+
transition: opacity 0.15s;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.notebook-content :global(.nb-input:hover .nb-copy-btn) {
|
|
407
|
+
opacity: 1;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.notebook-content :global(.nb-copy-btn:hover) {
|
|
411
|
+
background: rgba(255, 255, 255, 0.2);
|
|
412
|
+
color: rgba(255, 255, 255, 0.9);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.notebook-content :global(.nb-copy-btn.copied) {
|
|
416
|
+
opacity: 1;
|
|
417
|
+
color: #4ade80;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.notebook-content :global(.nb-copy-btn.copied)::after {
|
|
421
|
+
content: '\2713';
|
|
422
|
+
margin-left: 2px;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Dark mode: reversed theme means light code bg */
|
|
426
|
+
:global(.dark) .notebook-content :global(.nb-copy-btn) {
|
|
427
|
+
background: rgba(0, 0, 0, 0.1);
|
|
428
|
+
color: rgba(0, 0, 0, 0.5);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
:global(.dark) .notebook-content :global(.nb-copy-btn:hover) {
|
|
432
|
+
background: rgba(0, 0, 0, 0.2);
|
|
433
|
+
color: rgba(0, 0, 0, 0.8);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
:global(.dark) .notebook-content :global(.nb-copy-btn.copied) {
|
|
437
|
+
color: #16a34a;
|
|
438
|
+
}
|
|
389
439
|
</style>
|
|
@@ -14,6 +14,7 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
14
14
|
import { getAdapter } from '../../storage/index.js';
|
|
15
15
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
16
16
|
import type { Tab } from '../../types';
|
|
17
|
+
import { handleLoadError } from '../../utils/error.js';
|
|
17
18
|
import { loadPdfDocument, loadPdfFromUrl } from '../../utils/pdf';
|
|
18
19
|
import { buildHttpsUrl, canStreamDirectly } from '../../utils/url.js';
|
|
19
20
|
|
|
@@ -73,10 +74,11 @@ async function loadPdf() {
|
|
|
73
74
|
totalPages = doc.numPages;
|
|
74
75
|
currentPage = 1;
|
|
75
76
|
} catch (err: any) {
|
|
76
|
-
// Ignore cancellation errors (destroyed loading task)
|
|
77
|
-
if (err instanceof DOMException && err.name === 'AbortError') return;
|
|
77
|
+
// Ignore PDF-specific cancellation errors (destroyed loading task)
|
|
78
78
|
if (err?.name === 'PasswordException' || err?.message?.includes('destroy')) return;
|
|
79
|
-
|
|
79
|
+
const msg = handleLoadError(err);
|
|
80
|
+
if (msg === null) return;
|
|
81
|
+
error = msg;
|
|
80
82
|
} finally {
|
|
81
83
|
loading = false;
|
|
82
84
|
}
|
|
@@ -5,6 +5,7 @@ import { t } from '../../i18n/index.svelte.js';
|
|
|
5
5
|
import { getAdapter } from '../../storage/index.js';
|
|
6
6
|
import { tabResources } from '../../stores/tab-resources.svelte.js';
|
|
7
7
|
import type { Tab } from '../../types';
|
|
8
|
+
import { handleLoadError } from '../../utils/error.js';
|
|
8
9
|
import { formatFileSize } from '../../utils/format';
|
|
9
10
|
import { generateHexDump, type HexRow } from '../../utils/hex';
|
|
10
11
|
|
|
@@ -55,8 +56,9 @@ async function loadHexDump() {
|
|
|
55
56
|
truncated = fileSize > MAX_BYTES;
|
|
56
57
|
rows = generateHexDump(data);
|
|
57
58
|
} catch (err) {
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const msg = handleLoadError(err);
|
|
60
|
+
if (msg === null) return;
|
|
61
|
+
error = msg;
|
|
60
62
|
} finally {
|
|
61
63
|
loading = false;
|
|
62
64
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
typeBadgeClass,
|
|
16
16
|
typeLabel
|
|
17
17
|
} from '../../utils/column-types.js';
|
|
18
|
+
import { jsonReplacerBigInt } from '../../utils/format.js';
|
|
18
19
|
|
|
19
20
|
const INITIAL_ROWS = 100;
|
|
20
21
|
const BATCH_SIZE = 100;
|
|
@@ -167,7 +168,7 @@ function copyRow() {
|
|
|
167
168
|
for (const [k, v] of Object.entries(ctxMenu.rowData)) {
|
|
168
169
|
if (!k.startsWith('__')) clean[k] = v;
|
|
169
170
|
}
|
|
170
|
-
copyToClipboard(JSON.stringify(clean,
|
|
171
|
+
copyToClipboard(JSON.stringify(clean, jsonReplacerBigInt, 2));
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
function copyColumn() {
|
|
@@ -195,7 +196,7 @@ function formatCell(value: any, category: TypeCategory): string {
|
|
|
195
196
|
}
|
|
196
197
|
if (typeof value === 'bigint') return value.toString();
|
|
197
198
|
if (typeof value === 'object') {
|
|
198
|
-
return JSON.stringify(value,
|
|
199
|
+
return JSON.stringify(value, jsonReplacerBigInt);
|
|
199
200
|
}
|
|
200
201
|
return String(value);
|
|
201
202
|
}
|