@walkthru-earth/objex 0.1.0 → 1.0.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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/components/browser/FileBrowser.svelte +37 -1
  3. package/dist/components/browser/FileRow.svelte +8 -3
  4. package/dist/components/browser/FileTreeSidebar.svelte +1 -3
  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 +4 -1
  32. package/dist/index.js +7 -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 +5 -5
  39. package/dist/stores/query-history.svelte.js +4 -5
  40. package/dist/stores/settings.svelte.js +4 -4
  41. package/dist/types.d.ts +2 -2
  42. package/dist/utils/clipboard.d.ts +13 -0
  43. package/dist/utils/clipboard.js +38 -0
  44. package/dist/utils/error.d.ts +8 -0
  45. package/dist/utils/error.js +12 -0
  46. package/dist/utils/format.d.ts +10 -0
  47. package/dist/utils/format.js +22 -0
  48. package/dist/utils/host-detection.js +78 -18
  49. package/dist/utils/notebook.d.ts +59 -0
  50. package/dist/utils/notebook.js +211 -0
  51. package/dist/utils/parquet-metadata.js +1 -1
  52. package/dist/utils/pmtiles-tile.js +2 -1
  53. package/dist/utils/pmtiles.js +2 -1
  54. package/dist/utils/storage-url.d.ts +1 -1
  55. package/dist/utils/storage-url.js +82 -24
  56. package/dist/utils/url-state.js +2 -7
  57. package/dist/utils/url.d.ts +15 -1
  58. package/dist/utils/url.js +45 -19
  59. package/dist/utils/zarr.d.ts +60 -20
  60. package/dist/utils/zarr.js +450 -103
  61. package/package.json +64 -52
  62. package/dist/assets/favicon.svg +0 -17
  63. package/dist/components/CLAUDE.md +0 -44
  64. package/dist/components/viewers/CLAUDE.md +0 -60
  65. package/dist/file-icons/CLAUDE.md +0 -21
  66. package/dist/i18n/CLAUDE.md +0 -19
  67. package/dist/query/CLAUDE.md +0 -22
  68. package/dist/storage/CLAUDE.md +0 -23
  69. package/dist/stores/CLAUDE.md +0 -29
  70. package/dist/types/notebookjs.d.ts +0 -14
  71. package/dist/utils/CLAUDE.md +0 -54
  72. package/dist/utils/analytics.d.ts +0 -10
  73. package/dist/utils/analytics.js +0 -38
package/README.md CHANGED
@@ -13,7 +13,7 @@ graph LR
13
13
 
14
14
  ## Features
15
15
 
16
- - **Browse** cloud storage (S3, GCS, Azure, R2, MinIO, Wasabi, Storj, direct URLs)
16
+ - **Browse** cloud storage (S3, GCS, Azure, R2, B2, DigitalOcean, Wasabi, Storj, Hetzner, Contabo, Linode, OVHcloud, MinIO, direct URLs)
17
17
  - **Query** Parquet, CSV, JSONL with SQL (DuckDB-WASM, cancellable queries)
18
18
  - **Visualize** GeoParquet, GeoJSON, COG, PMTiles, FlatGeobuf, Zarr on maps (MapLibre + deck.gl)
19
19
  - **View** 100+ file formats: code (30+ languages), Jupyter notebooks, PDF, 3D models, archives, media
@@ -1,12 +1,14 @@
1
1
  <script lang="ts">
2
- import { ArrowDown, ArrowUp, ArrowUpDown, FolderOpen, Loader2 } from '@lucide/svelte';
2
+ import { ArrowDown, ArrowUp, ArrowUpDown, FolderOpen, Layers, Loader2 } from '@lucide/svelte';
3
3
  import FolderPlusIcon from '@lucide/svelte/icons/folder-plus';
4
4
  import { Button } from '../ui/button/index.js';
5
5
  import { ScrollArea } from '../ui/scroll-area/index.js';
6
6
  import { t } from '../../i18n/index.svelte.js';
7
7
  import { browser } from '../../stores/browser.svelte.js';
8
8
  import { safeLock } from '../../stores/safelock.svelte.js';
9
+ import { tabs } from '../../stores/tabs.svelte.js';
9
10
  import type { FileEntry } from '../../types.js';
11
+ import { detectZarrMarkers } from '../../utils/zarr.js';
10
12
  import Breadcrumb from './Breadcrumb.svelte';
11
13
  import CreateFolderDialog from './CreateFolderDialog.svelte';
12
14
  import DeleteConfirmDialog from './DeleteConfirmDialog.svelte';
@@ -31,6 +33,22 @@ let renameTarget = $state<FileEntry | null>(null);
31
33
 
32
34
  let showWriteActions = $derived(browser.canWrite && !safeLock.locked);
33
35
 
36
+ const zarrDetection = $derived(detectZarrMarkers(browser.entries.map((e: FileEntry) => e.name)));
37
+
38
+ function openAsZarr() {
39
+ if (!browser.activeConnection) return;
40
+ const prefix = browser.currentPrefix.replace(/\/+$/, '');
41
+ const name = prefix.split('/').pop() || browser.activeConnection.bucket;
42
+ tabs.open({
43
+ id: `${browser.activeConnection.id}:${prefix}/`,
44
+ name,
45
+ path: `${prefix}/`,
46
+ source: 'remote',
47
+ connectionId: browser.activeConnection.id,
48
+ extension: 'zarr'
49
+ });
50
+ }
51
+
34
52
  const sortedAndFilteredEntries = $derived.by(() => {
35
53
  let result = [...browser.entries];
36
54
 
@@ -172,6 +190,24 @@ function handleRename(entry: FileEntry) {
172
190
  </button>
173
191
  </div>
174
192
 
193
+ <!-- Zarr detection banner -->
194
+ {#if zarrDetection.detected}
195
+ <div class="border-border flex items-center gap-2 border-b bg-purple-50 px-3 py-1.5 dark:bg-purple-950/30">
196
+ <Layers class="size-3.5 shrink-0 text-purple-500" />
197
+ <span class="flex-1 text-xs text-purple-700 dark:text-purple-300">
198
+ {t('fileBrowser.zarrDetected', { version: String(zarrDetection.version ?? '?') })}
199
+ </span>
200
+ <Button
201
+ variant="outline"
202
+ size="sm"
203
+ class="h-6 gap-1 px-2 text-[11px]"
204
+ onclick={openAsZarr}
205
+ >
206
+ {t('fileBrowser.openAsZarr')}
207
+ </Button>
208
+ </div>
209
+ {/if}
210
+
175
211
  <!-- File list -->
176
212
  <div class="relative min-h-0 flex-1">
177
213
  <DropZone>
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import PencilIcon from '@lucide/svelte/icons/pencil';
3
3
  import Trash2Icon from '@lucide/svelte/icons/trash-2';
4
+ import { VIEWER_DIR_EXTENSIONS } from '../../constants.js';
4
5
  import FileTypeIcon from '../../file-icons/FileTypeIcon.svelte';
5
6
  import { getFileTypeInfo } from '../../file-icons/index.js';
6
7
  import { t } from '../../i18n/index.svelte.js';
@@ -18,11 +19,15 @@ interface Props {
18
19
 
19
20
  let { entry, onDelete, onRename }: Props = $props();
20
21
 
21
- const info = $derived(getFileTypeInfo(entry.extension, entry.is_dir));
22
+ function isViewerDir(e: FileEntry): boolean {
23
+ return e.is_dir && VIEWER_DIR_EXTENSIONS.has(e.extension);
24
+ }
25
+
26
+ const info = $derived(getFileTypeInfo(entry.extension, entry.is_dir && !isViewerDir(entry)));
22
27
  let showActions = $derived(browser.canWrite && !safeLock.locked);
23
28
 
24
29
  function handleClick() {
25
- if (entry.is_dir) {
30
+ if (entry.is_dir && !isViewerDir(entry)) {
26
31
  browser.navigateTo(entry.path);
27
32
  } else {
28
33
  if (browser.activeConnection) {
@@ -68,7 +73,7 @@ function handleRenameClick(e: MouseEvent) {
68
73
  >
69
74
  <!-- Icon -->
70
75
  <div class="flex shrink-0 items-center justify-center">
71
- <FileTypeIcon extension={entry.extension} isDir={entry.is_dir} class="size-4" />
76
+ <FileTypeIcon extension={entry.extension} isDir={entry.is_dir && !isViewerDir(entry)} class="size-4" />
72
77
  </div>
73
78
 
74
79
  <!-- File name -->
@@ -10,6 +10,7 @@ import Loader2Icon from '@lucide/svelte/icons/loader-2';
10
10
  import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
11
11
  import SearchIcon from '@lucide/svelte/icons/search';
12
12
  import * as ContextMenu from '../ui/context-menu/index.js';
13
+ import { VIEWER_DIR_EXTENSIONS } from '../../constants.js';
13
14
  import FileTypeIcon from '../../file-icons/FileTypeIcon.svelte';
14
15
  import { t } from '../../i18n/index.svelte.js';
15
16
  import { getAdapter } from '../../storage/index.js';
@@ -170,9 +171,6 @@ function openFile(entry: FileEntry) {
170
171
  syncUrlParam(connection, entry.path);
171
172
  }
172
173
 
173
- /** Extensions that represent "virtual files" — directories that open as viewers. */
174
- const VIEWER_DIR_EXTENSIONS = new Set(['zarr', 'zr3']);
175
-
176
174
  function isViewerDir(entry: FileEntry): boolean {
177
175
  return entry.is_dir && VIEWER_DIR_EXTENSIONS.has(entry.extension);
178
176
  }
@@ -0,0 +1,126 @@
1
+ <script module lang="ts">
2
+ declare const __APP_VERSION__: string;
3
+ declare const __THIRD_PARTY_LICENSES__: {
4
+ license: string;
5
+ packages: { name: string; url: string }[];
6
+ }[];
7
+ </script>
8
+
9
+ <script lang="ts">
10
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
11
+ import ExternalLinkIcon from '@lucide/svelte/icons/external-link';
12
+ import GithubIcon from '@lucide/svelte/icons/github';
13
+ import {
14
+ Sheet,
15
+ SheetContent,
16
+ SheetDescription,
17
+ SheetHeader,
18
+ SheetTitle
19
+ } from '../ui/sheet/index.js';
20
+ import { t } from '../../i18n/index.svelte.js';
21
+
22
+ interface Props {
23
+ open: boolean;
24
+ }
25
+
26
+ let { open = $bindable(false) }: Props = $props();
27
+
28
+ let licensesOpen = $state(false);
29
+
30
+ const version = __APP_VERSION__;
31
+
32
+ const thirdPartyLicenses = __THIRD_PARTY_LICENSES__;
33
+
34
+ $effect(() => {
35
+ if (!open) licensesOpen = false;
36
+ });
37
+ </script>
38
+
39
+ <Sheet bind:open>
40
+ <SheetContent side="bottom" class="max-h-[85vh] sm:mx-auto sm:max-w-lg sm:rounded-t-lg">
41
+ <SheetHeader>
42
+ <SheetTitle>{t('about.title')}</SheetTitle>
43
+ <SheetDescription class="sr-only">
44
+ {t('about.version', { version })}
45
+ </SheetDescription>
46
+ </SheetHeader>
47
+
48
+ <div class="flex flex-col items-center gap-4 overflow-y-auto px-4 py-6 sm:px-6">
49
+ <!-- walkthru.earth logo/link -->
50
+ <a
51
+ href="https://walkthru.earth/links"
52
+ target="_blank"
53
+ rel="noopener noreferrer"
54
+ class="group flex flex-col items-center gap-2 transition-opacity hover:opacity-80"
55
+ >
56
+ <img src="https://walkthru.earth/icon.svg" alt="walkthru.earth" class="size-12" />
57
+ <span class="flex items-center gap-1 text-lg font-semibold text-foreground">
58
+ walkthru.earth
59
+ <ExternalLinkIcon
60
+ class="size-3.5 opacity-0 transition-opacity group-hover:opacity-100"
61
+ />
62
+ </span>
63
+ </a>
64
+
65
+ <!-- Version + License -->
66
+ <div class="flex flex-col items-center gap-1 text-sm text-muted-foreground">
67
+ <span>{t('about.version', { version })}</span>
68
+ <span>{t('about.license')}</span>
69
+ </div>
70
+
71
+ <!-- GitHub link -->
72
+ <a
73
+ href="https://github.com/walkthru-earth/objex"
74
+ target="_blank"
75
+ rel="noopener noreferrer"
76
+ class="inline-flex items-center gap-2 text-sm text-muted-foreground transition-colors hover:text-foreground"
77
+ >
78
+ <GithubIcon class="size-4" />
79
+ {t('about.sourceCode')}
80
+ </a>
81
+
82
+ <!-- Third-party licenses -->
83
+ <div class="w-full border-t pt-3">
84
+ <button
85
+ class="flex w-full items-center justify-between rounded-md px-3 py-1.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
86
+ onclick={() => {
87
+ licensesOpen = !licensesOpen;
88
+ }}
89
+ >
90
+ <span>{t('about.openSourceLicenses')}</span>
91
+ <ChevronDownIcon
92
+ class="size-3.5 transition-transform {licensesOpen ? 'rotate-180' : ''}"
93
+ />
94
+ </button>
95
+
96
+ {#if licensesOpen}
97
+ <div
98
+ class="mt-2 flex max-h-48 flex-col gap-3 overflow-y-auto rounded-lg bg-muted/40 p-3 sm:max-h-60"
99
+ >
100
+ {#each thirdPartyLicenses as group}
101
+ <div>
102
+ <span
103
+ class="inline-block rounded bg-muted px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground"
104
+ >
105
+ {group.license}
106
+ </span>
107
+ <div class="mt-1.5 flex flex-wrap gap-1">
108
+ {#each group.packages as pkg}
109
+ <a
110
+ href={pkg.url}
111
+ target="_blank"
112
+ rel="noopener noreferrer"
113
+ class="rounded-md border border-border/50 bg-background px-2 py-0.5 text-[11px] text-muted-foreground transition-colors hover:border-border hover:text-foreground"
114
+ >
115
+ {pkg.name}
116
+ </a>
117
+ {/each}
118
+ </div>
119
+ </div>
120
+ {/each}
121
+ </div>
122
+ {/if}
123
+ </div>
124
+ </div>
125
+ </SheetContent>
126
+ </Sheet>
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ open: boolean;
3
+ }
4
+ declare const AboutSheet: import("svelte").Component<Props, {}, "open">;
5
+ type AboutSheet = ReturnType<typeof AboutSheet>;
6
+ export default AboutSheet;