@walkthru-earth/objex 1.3.1 → 1.5.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 (199) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +28 -20
  3. package/dist/components/browser/FileTreeSidebar.svelte +32 -17
  4. package/dist/components/layout/AboutSheet.svelte +5 -2
  5. package/dist/components/layout/ConnectionDialog.svelte +7 -2
  6. package/dist/components/layout/SettingsSheet.svelte +238 -0
  7. package/dist/components/layout/SettingsSheet.svelte.d.ts +6 -0
  8. package/dist/components/layout/Sidebar.svelte +73 -6
  9. package/dist/components/layout/Sidebar.svelte.d.ts +4 -1
  10. package/dist/components/layout/StatusBar.svelte +17 -14
  11. package/dist/components/layout/TabBar.svelte +4 -4
  12. package/dist/components/ui/context-menu/context-menu-radio-group.svelte.d.ts +1 -1
  13. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte.d.ts +1 -1
  14. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
  15. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  16. package/dist/components/ui/resizable/index.d.ts +1 -1
  17. package/dist/components/ui/resizable/index.js +2 -2
  18. package/dist/components/ui/slider/index.d.ts +3 -0
  19. package/dist/components/ui/slider/index.js +5 -0
  20. package/dist/components/ui/slider/range-slider.svelte +94 -0
  21. package/dist/components/ui/slider/range-slider.svelte.d.ts +21 -0
  22. package/dist/components/ui/slider/slider.svelte +83 -0
  23. package/dist/components/ui/slider/slider.svelte.d.ts +7 -0
  24. package/dist/components/viewers/ArchiveViewer.svelte +140 -113
  25. package/dist/components/viewers/CodeViewer.svelte +45 -48
  26. package/dist/components/viewers/CodeViewer.svelte.d.ts +1 -1
  27. package/dist/components/viewers/CogControls.svelte +338 -184
  28. package/dist/components/viewers/CogControls.svelte.d.ts +33 -10
  29. package/dist/components/viewers/CogViewer.svelte +269 -116
  30. package/dist/components/viewers/CopcViewer.svelte +8 -15
  31. package/dist/components/viewers/DatabaseViewer.svelte +22 -21
  32. package/dist/components/viewers/FileInfo.svelte +16 -16
  33. package/dist/components/viewers/FlatGeobufViewer.svelte +16 -46
  34. package/dist/components/viewers/GeoParquetMapViewer.svelte +11 -9
  35. package/dist/components/viewers/GeoParquetMapViewer.svelte.d.ts +1 -1
  36. package/dist/components/viewers/ImageViewer.svelte +12 -14
  37. package/dist/components/viewers/LoadProgress.svelte +6 -6
  38. package/dist/components/viewers/MarkdownViewer.svelte +29 -30
  39. package/dist/components/viewers/MediaViewer.svelte +13 -14
  40. package/dist/components/viewers/ModelViewer.svelte +18 -21
  41. package/dist/components/viewers/MultiCogViewer.svelte +474 -106
  42. package/dist/components/viewers/MultiCogViewer.svelte.d.ts +1 -1
  43. package/dist/components/viewers/NotebookViewer.svelte +28 -29
  44. package/dist/components/viewers/PdfViewer.svelte +24 -33
  45. package/dist/components/viewers/PmtilesViewer.svelte +13 -15
  46. package/dist/components/viewers/QueryHistoryPanel.svelte +18 -18
  47. package/dist/components/viewers/RawViewer.svelte +27 -21
  48. package/dist/components/viewers/StacMapViewer.svelte +6 -13
  49. package/dist/components/viewers/StacMosaicViewer.svelte +1764 -410
  50. package/dist/components/viewers/StacMosaicViewer.svelte.d.ts +1 -1
  51. package/dist/components/viewers/StacTabViewer.svelte +26 -15
  52. package/dist/components/viewers/StacTabViewer.svelte.d.ts +1 -1
  53. package/dist/components/viewers/TableGrid.svelte +38 -34
  54. package/dist/components/viewers/TableStatusBar.svelte +7 -7
  55. package/dist/components/viewers/TableToolbar.svelte +10 -9
  56. package/dist/components/viewers/TableViewer.svelte +47 -30
  57. package/dist/components/viewers/TableViewer.svelte.d.ts +1 -0
  58. package/dist/components/viewers/ViewerHeader.svelte +18 -0
  59. package/dist/components/viewers/ViewerHeader.svelte.d.ts +10 -0
  60. package/dist/components/viewers/ViewerRouter.svelte +16 -8
  61. package/dist/components/viewers/ViewerStatus.svelte +19 -0
  62. package/dist/components/viewers/ViewerStatus.svelte.d.ts +7 -0
  63. package/dist/components/viewers/ZarrMapViewer.svelte +24 -21
  64. package/dist/components/viewers/ZarrViewer.svelte +98 -65
  65. package/dist/components/viewers/cog/ChannelPicker.svelte +83 -0
  66. package/dist/components/viewers/cog/ChannelPicker.svelte.d.ts +13 -0
  67. package/dist/components/viewers/cog/PixelInspectorPanel.svelte +87 -0
  68. package/dist/components/viewers/cog/PixelInspectorPanel.svelte.d.ts +17 -0
  69. package/dist/components/viewers/cog/buildRgbLayer.d.ts +78 -0
  70. package/dist/components/viewers/cog/buildRgbLayer.js +176 -0
  71. package/dist/components/viewers/map/AttributeTable.svelte +7 -7
  72. package/dist/components/viewers/map/MapContainer.svelte +38 -12
  73. package/dist/components/viewers/pmtiles/PmtilesArchiveView.svelte +109 -83
  74. package/dist/components/viewers/pmtiles/PmtilesTileInspector.svelte +16 -16
  75. package/dist/components/viewers/stac/StacDatetimeBar.svelte +175 -0
  76. package/dist/components/viewers/stac/StacDatetimeBar.svelte.d.ts +10 -0
  77. package/dist/components/viewers/stac/StacFilterPanel.svelte +243 -0
  78. package/dist/components/viewers/stac/StacFilterPanel.svelte.d.ts +14 -0
  79. package/dist/components/viewers/stac/StacItemInspector.svelte +223 -0
  80. package/dist/components/viewers/stac/StacItemInspector.svelte.d.ts +10 -0
  81. package/dist/components/viewers/stac/StacItemStrip.svelte +228 -0
  82. package/dist/components/viewers/stac/StacItemStrip.svelte.d.ts +12 -0
  83. package/dist/constants.d.ts +6 -0
  84. package/dist/constants.js +8 -0
  85. package/dist/file-icons/index.d.ts +1 -1
  86. package/dist/file-icons/index.js +1 -1
  87. package/dist/i18n/ar.js +113 -2
  88. package/dist/i18n/en.js +113 -2
  89. package/dist/index.d.ts +2 -28
  90. package/dist/index.js +7 -23
  91. package/dist/query/engine.d.ts +10 -0
  92. package/dist/query/source.js +1 -1
  93. package/dist/query/stac-source-factory.d.ts +65 -0
  94. package/dist/query/stac-source-factory.js +77 -0
  95. package/dist/query/stac-source-parquet.d.ts +135 -0
  96. package/dist/query/stac-source-parquet.js +468 -0
  97. package/dist/query/wasm.d.ts +8 -0
  98. package/dist/query/wasm.js +310 -65
  99. package/dist/storage/presign.js +3 -2
  100. package/dist/storage/providers.js +7 -6
  101. package/dist/stores/config.svelte.d.ts +15 -0
  102. package/dist/stores/config.svelte.js +46 -0
  103. package/dist/stores/connections.svelte.d.ts +2 -2
  104. package/dist/stores/connections.svelte.js +1 -2
  105. package/dist/stores/files.svelte.d.ts +1 -1
  106. package/dist/stores/files.svelte.js +1 -1
  107. package/dist/stores/query-history.svelte.js +1 -1
  108. package/dist/stores/settings.svelte.d.ts +16 -1
  109. package/dist/stores/settings.svelte.js +104 -48
  110. package/dist/stores/tabs.svelte.d.ts +3 -0
  111. package/dist/stores/tabs.svelte.js +17 -0
  112. package/dist/utils/cog-histogram.d.ts +121 -0
  113. package/dist/utils/cog-histogram.js +424 -0
  114. package/dist/utils/cog.d.ts +177 -20
  115. package/dist/utils/cog.js +361 -76
  116. package/dist/utils/colormap-sprite.d.ts +0 -9
  117. package/dist/utils/colormap-sprite.js +0 -21
  118. package/dist/utils/deck.d.ts +18 -12
  119. package/dist/utils/deck.js +15 -7
  120. package/dist/utils/media-query.svelte.d.ts +14 -0
  121. package/dist/utils/media-query.svelte.js +29 -0
  122. package/dist/utils/pmtiles-tile.js +2 -2
  123. package/dist/utils/signed-url-effect.d.ts +7 -0
  124. package/dist/utils/signed-url-effect.js +19 -0
  125. package/dist/utils/{url.d.ts → signed-url.d.ts} +15 -1
  126. package/dist/utils/{url.js → signed-url.js} +32 -10
  127. package/dist/utils/url-state.d.ts +36 -0
  128. package/dist/utils/url-state.js +72 -2
  129. package/dist/utils/zarr-tab.d.ts +1 -2
  130. package/dist/utils/zarr-tab.js +1 -2
  131. package/dist/utils/zarr.d.ts +0 -17
  132. package/dist/utils/zarr.js +1 -45
  133. package/package.json +55 -84
  134. package/dist/components/browser/Breadcrumb.svelte +0 -50
  135. package/dist/components/browser/Breadcrumb.svelte.d.ts +0 -7
  136. package/dist/components/browser/CreateFolderDialog.svelte +0 -98
  137. package/dist/components/browser/CreateFolderDialog.svelte.d.ts +0 -6
  138. package/dist/components/browser/DeleteConfirmDialog.svelte +0 -90
  139. package/dist/components/browser/DeleteConfirmDialog.svelte.d.ts +0 -8
  140. package/dist/components/browser/DropZone.svelte +0 -83
  141. package/dist/components/browser/DropZone.svelte.d.ts +0 -7
  142. package/dist/components/browser/FileBrowser.svelte +0 -252
  143. package/dist/components/browser/FileBrowser.svelte.d.ts +0 -3
  144. package/dist/components/browser/FileRow.svelte +0 -117
  145. package/dist/components/browser/FileRow.svelte.d.ts +0 -9
  146. package/dist/components/browser/RenameDialog.svelte +0 -101
  147. package/dist/components/browser/RenameDialog.svelte.d.ts +0 -8
  148. package/dist/components/browser/SearchBar.svelte +0 -40
  149. package/dist/components/browser/SearchBar.svelte.d.ts +0 -6
  150. package/dist/components/browser/UploadButton.svelte +0 -65
  151. package/dist/components/browser/UploadButton.svelte.d.ts +0 -3
  152. package/dist/query/stac-geoparquet.d.ts +0 -31
  153. package/dist/query/stac-geoparquet.js +0 -136
  154. package/dist/utils/clipboard.d.ts +0 -13
  155. package/dist/utils/clipboard.js +0 -38
  156. package/dist/utils/cloud-url.d.ts +0 -27
  157. package/dist/utils/cloud-url.js +0 -61
  158. package/dist/utils/cog-pure.d.ts +0 -25
  159. package/dist/utils/cog-pure.js +0 -35
  160. package/dist/utils/column-types.d.ts +0 -5
  161. package/dist/utils/column-types.js +0 -137
  162. package/dist/utils/connection-identity.d.ts +0 -51
  163. package/dist/utils/connection-identity.js +0 -97
  164. package/dist/utils/error.d.ts +0 -8
  165. package/dist/utils/error.js +0 -12
  166. package/dist/utils/evidence-context.d.ts +0 -22
  167. package/dist/utils/evidence-context.js +0 -56
  168. package/dist/utils/export.d.ts +0 -22
  169. package/dist/utils/export.js +0 -76
  170. package/dist/utils/file-sort.d.ts +0 -20
  171. package/dist/utils/file-sort.js +0 -41
  172. package/dist/utils/format.d.ts +0 -24
  173. package/dist/utils/format.js +0 -78
  174. package/dist/utils/geoarrow.d.ts +0 -32
  175. package/dist/utils/geoarrow.js +0 -672
  176. package/dist/utils/geometry-type.d.ts +0 -52
  177. package/dist/utils/geometry-type.js +0 -76
  178. package/dist/utils/hex.d.ts +0 -10
  179. package/dist/utils/hex.js +0 -27
  180. package/dist/utils/host-detection.d.ts +0 -23
  181. package/dist/utils/host-detection.js +0 -95
  182. package/dist/utils/local-storage.d.ts +0 -16
  183. package/dist/utils/local-storage.js +0 -37
  184. package/dist/utils/markdown-sql.d.ts +0 -30
  185. package/dist/utils/markdown-sql.js +0 -72
  186. package/dist/utils/notebook.d.ts +0 -59
  187. package/dist/utils/notebook.js +0 -211
  188. package/dist/utils/parquet-metadata.d.ts +0 -64
  189. package/dist/utils/parquet-metadata.js +0 -262
  190. package/dist/utils/stac-geoparquet.d.ts +0 -90
  191. package/dist/utils/stac-geoparquet.js +0 -223
  192. package/dist/utils/stac-hydrate.d.ts +0 -38
  193. package/dist/utils/stac-hydrate.js +0 -243
  194. package/dist/utils/stac.d.ts +0 -136
  195. package/dist/utils/stac.js +0 -176
  196. package/dist/utils/storage-url.d.ts +0 -90
  197. package/dist/utils/storage-url.js +0 -568
  198. package/dist/utils/wkb.d.ts +0 -43
  199. package/dist/utils/wkb.js +0 -359
@@ -1,4 +1,12 @@
1
1
  <script lang="ts">
2
+ import {
3
+ handleLoadError,
4
+ interpolateTemplates,
5
+ MarkdownSqlContext,
6
+ markSqlBlocks,
7
+ parseMarkdownDocument,
8
+ wireCodeCopyButtons
9
+ } from '@walkthru-earth/objex-utils';
2
10
  import { onDestroy, tick } from 'svelte';
3
11
  import SqlResultBlock from '../editor/SqlResultBlock.svelte';
4
12
  import { Badge } from '../ui/badge/index.js';
@@ -7,15 +15,10 @@ import { t } from '../../i18n/index.svelte.js';
7
15
  import { getAdapter } from '../../storage/index.js';
8
16
  import { tabResources } from '../../stores/tab-resources.svelte.js';
9
17
  import type { Tab } from '../../types';
10
- import { wireCodeCopyButtons } from '../../utils/clipboard.js';
11
- import { handleLoadError } from '../../utils/error.js';
12
- import { EvidenceContext } from '../../utils/evidence-context';
13
18
  import { detectRTL, processDirection, renderMarkdown } from '../../utils/markdown';
14
- import {
15
- interpolateTemplates,
16
- markSqlBlocks,
17
- parseMarkdownDocument
18
- } from '../../utils/markdown-sql';
19
+ import { getQueryEngine } from '../../query/index.js';
20
+ import ViewerHeader from './ViewerHeader.svelte';
21
+ import ViewerStatus from './ViewerStatus.svelte';
19
22
 
20
23
  let mermaidInitialized = false;
21
24
  const CAIRO_FONT = '"Cairo", sans-serif';
@@ -78,7 +81,9 @@ async function loadMarkdown() {
78
81
 
79
82
  if (parsed.sqlBlocks.length > 0) {
80
83
  // Execute SQL blocks in parallel
81
- const ctx = new EvidenceContext(
84
+ const engine = await getQueryEngine();
85
+ const ctx = new MarkdownSqlContext(
86
+ engine,
82
87
  tab.connectionId ?? '',
83
88
  tab.path.split('/').slice(0, -1).join('/')
84
89
  );
@@ -201,24 +206,22 @@ async function saveMarkdown(markdown: string) {
201
206
  editMode = false;
202
207
  await loadMarkdown();
203
208
  } catch (err) {
204
- error = err instanceof Error ? err.message : String(err);
209
+ error = handleLoadError(err);
205
210
  }
206
211
  }
207
212
  </script>
208
213
 
209
214
  <div class="flex h-full flex-col">
210
- <div
211
- class="flex items-center gap-1 border-b border-zinc-200 px-2 py-1.5 sm:gap-2 sm:px-4 dark:border-zinc-800"
212
- >
213
- <span class="truncate max-w-[120px] text-sm font-medium text-zinc-700 sm:max-w-none dark:text-zinc-300">{tab.name}</span>
214
- <Badge variant="secondary">{t('markdown.badge')}</Badge>
215
- {#if hasSqlBlocks}
216
- <Badge variant="outline" class="border-blue-200 text-blue-600 dark:border-blue-800 dark:text-blue-300">
217
- {t('markdown.evidence')}
218
- </Badge>
219
- {/if}
220
-
221
- <div class="ms-auto">
215
+ <ViewerHeader {tab}>
216
+ {#snippet badge()}
217
+ <Badge variant="secondary">{t('markdown.badge')}</Badge>
218
+ {#if hasSqlBlocks}
219
+ <Badge variant="outline" class="border-blue-200 text-blue-600 dark:border-blue-800 dark:text-blue-300">
220
+ {t('markdown.evidence')}
221
+ </Badge>
222
+ {/if}
223
+ {/snippet}
224
+ {#snippet actions()}
222
225
  <Button
223
226
  variant="ghost"
224
227
  size="sm"
@@ -227,18 +230,14 @@ async function saveMarkdown(markdown: string) {
227
230
  >
228
231
  {editMode ? t('markdown.view') : t('markdown.edit')}
229
232
  </Button>
230
- </div>
231
- </div>
233
+ {/snippet}
234
+ </ViewerHeader>
232
235
 
233
236
  <div class="flex-1 overflow-auto">
234
237
  {#if loading}
235
- <div class="flex h-full items-center justify-center">
236
- <p class="text-sm text-zinc-400">Loading...</p>
237
- </div>
238
+ <ViewerStatus kind="loading" />
238
239
  {:else if error}
239
- <div class="flex h-full items-center justify-center">
240
- <p class="text-sm text-red-400">{error}</p>
241
- </div>
240
+ <ViewerStatus kind="error" message={error} />
242
241
  {:else if editMode}
243
242
  {#await import('../editor/MilkdownEditor.svelte') then MilkdownEditor}
244
243
  <MilkdownEditor.default
@@ -1,11 +1,13 @@
1
1
  <script lang="ts">
2
+ import { handleLoadError } from '@walkthru-earth/objex-utils';
2
3
  import { onDestroy } from 'svelte';
3
4
  import { getMimeType } from '../../file-icons/index.js';
4
5
  import { getAdapter } from '../../storage/index.js';
5
6
  import { tabResources } from '../../stores/tab-resources.svelte.js';
6
7
  import type { Tab } from '../../types';
7
- import { handleLoadError } from '../../utils/error.js';
8
- import { buildHttpsUrl, canStreamDirectly } from '../../utils/url.js';
8
+ import { buildHttpsUrl, canStreamDirectly } from '../../utils/signed-url.js';
9
+ import ViewerHeader from './ViewerHeader.svelte';
10
+ import ViewerStatus from './ViewerStatus.svelte';
9
11
 
10
12
  let { tab }: { tab: Tab } = $props();
11
13
 
@@ -72,22 +74,19 @@ onDestroy(cleanup);
72
74
  </script>
73
75
 
74
76
  <div class="flex h-full flex-col">
75
- <div class="flex items-center gap-2 border-b border-zinc-200 px-4 py-2 dark:border-zinc-800">
76
- <span class="text-sm font-medium text-zinc-700 dark:text-zinc-300">{tab.name}</span>
77
- <span
78
- class="rounded bg-zinc-100 px-1.5 py-0.5 text-xs text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400"
79
- >
80
- {mediaType}
81
- </span>
82
- </div>
77
+ <ViewerHeader {tab}>
78
+ {#snippet badge()}
79
+ <span class="rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground">
80
+ {mediaType}
81
+ </span>
82
+ {/snippet}
83
+ </ViewerHeader>
83
84
 
84
85
  <div class="flex flex-1 items-center justify-center bg-zinc-950 p-4">
85
86
  {#if loading}
86
- <p class="text-sm text-zinc-400">Loading {mediaType}...</p>
87
+ <ViewerStatus kind="loading" />
87
88
  {:else if error}
88
- <div class="rounded-lg border border-red-300 bg-red-50 px-6 py-4 text-center dark:border-red-800 dark:bg-red-950">
89
- <p class="text-sm text-red-600 dark:text-red-400">{error}</p>
90
- </div>
89
+ <ViewerStatus kind="error" message={error} />
91
90
  {:else if mediaSrc}
92
91
  {#if mediaType === 'video'}
93
92
  <video
@@ -2,22 +2,23 @@
2
2
  import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
3
3
  import MaximizeIcon from '@lucide/svelte/icons/maximize';
4
4
  import RotateCcwIcon from '@lucide/svelte/icons/rotate-ccw';
5
+ import { handleLoadError } from '@walkthru-earth/objex-utils';
5
6
  import { onDestroy } from 'svelte';
6
7
  import { Badge } from '../ui/badge/index.js';
7
8
  import { Button } from '../ui/button/index.js';
8
9
  import * as DropdownMenu from '../ui/dropdown-menu/index.js';
9
- import { Separator } from '../ui/separator/index.js';
10
10
  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';
15
14
  import {
16
15
  createModelScene,
17
16
  disposeModelScene,
18
17
  loadModel,
19
18
  type ModelScene
20
19
  } from '../../utils/model3d';
20
+ import ViewerHeader from './ViewerHeader.svelte';
21
+ import ViewerStatus from './ViewerStatus.svelte';
21
22
 
22
23
  let { tab }: { tab: Tab } = $props();
23
24
 
@@ -101,19 +102,15 @@ onDestroy(cleanup);
101
102
  </script>
102
103
 
103
104
  <div class="flex h-full flex-col">
104
- <div
105
- class="flex items-center gap-1 border-b border-zinc-200 px-2 py-1.5 sm:gap-2 sm:px-4 dark:border-zinc-800"
106
- >
107
- <span class="truncate max-w-[120px] text-sm font-medium text-zinc-700 sm:max-w-none dark:text-zinc-300">{tab.name}</span>
108
- <Badge variant="secondary">{t('model.badge')}</Badge>
109
-
110
- {#if meshCount > 0}
111
- <span class="hidden text-xs text-zinc-400 sm:inline">
112
- {meshCount} {t('model.meshes')} &middot; {vertexCount.toLocaleString()} {t('model.vertices')}
113
- </span>
114
- {/if}
105
+ <ViewerHeader {tab}>
106
+ {#snippet badge()}<Badge variant="secondary">{t('model.badge')}</Badge>{/snippet}
107
+ {#snippet actions()}
108
+ {#if meshCount > 0}
109
+ <span class="hidden text-xs text-muted-foreground sm:inline">
110
+ {meshCount} {t('model.meshes')} &middot; {vertexCount.toLocaleString()} {t('model.vertices')}
111
+ </span>
112
+ {/if}
115
113
 
116
- <div class="ms-auto flex items-center gap-1">
117
114
  <!-- Desktop controls -->
118
115
  <div class="hidden items-center gap-1 sm:flex">
119
116
  <Button
@@ -135,7 +132,7 @@ onDestroy(cleanup);
135
132
  <!-- Mobile overflow menu -->
136
133
  <div class="flex sm:hidden">
137
134
  <DropdownMenu.Root>
138
- <DropdownMenu.Trigger class="rounded p-1 text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-800">
135
+ <DropdownMenu.Trigger class="rounded p-1 text-muted-foreground hover:bg-muted">
139
136
  <EllipsisVerticalIcon class="size-4" />
140
137
  </DropdownMenu.Trigger>
141
138
  <DropdownMenu.Content align="end" class="w-40">
@@ -147,18 +144,18 @@ onDestroy(cleanup);
147
144
  </DropdownMenu.Content>
148
145
  </DropdownMenu.Root>
149
146
  </div>
150
- </div>
151
- </div>
147
+ {/snippet}
148
+ </ViewerHeader>
152
149
 
153
150
  <div class="relative flex-1 overflow-hidden">
154
151
  {#if loading}
155
- <div class="absolute inset-0 z-10 flex items-center justify-center bg-zinc-900/80">
156
- <p class="text-sm text-zinc-400">{t('model.loading')}</p>
152
+ <div class="absolute inset-0 z-10 bg-zinc-900/80">
153
+ <ViewerStatus kind="loading" message={t('model.loading')} />
157
154
  </div>
158
155
  {/if}
159
156
  {#if error}
160
- <div class="absolute inset-0 z-10 flex items-center justify-center bg-zinc-900/80">
161
- <p class="text-sm text-red-400">{error}</p>
157
+ <div class="absolute inset-0 z-10 bg-zinc-900/80">
158
+ <ViewerStatus kind="error" message={error} />
162
159
  </div>
163
160
  {/if}
164
161
  <canvas bind:this={canvasEl} class="h-full w-full"></canvas>