matterviz 0.4.2 → 0.4.3

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 (244) hide show
  1. package/dist/FilePicker.svelte +40 -50
  2. package/dist/api/optimade.js +10 -7
  3. package/dist/app.css +15 -0
  4. package/dist/brillouin/BrillouinZone.svelte +18 -9
  5. package/dist/brillouin/BrillouinZoneScene.svelte +20 -29
  6. package/dist/brillouin/compute.js +10 -16
  7. package/dist/brillouin/geometry.js +2 -2
  8. package/dist/chempot-diagram/ChemPotDiagram.svelte +18 -15
  9. package/dist/chempot-diagram/ChemPotDiagram2D.svelte +17 -113
  10. package/dist/chempot-diagram/ChemPotDiagram3D.svelte +104 -264
  11. package/dist/chempot-diagram/async-compute.svelte.js +12 -13
  12. package/dist/chempot-diagram/color.d.ts +21 -1
  13. package/dist/chempot-diagram/color.js +70 -1
  14. package/dist/chempot-diagram/compute.d.ts +7 -0
  15. package/dist/chempot-diagram/compute.js +85 -26
  16. package/dist/chempot-diagram/export.js +1 -6
  17. package/dist/chempot-diagram/temperature.js +9 -10
  18. package/dist/composition/BarChart.svelte +7 -9
  19. package/dist/composition/Composition.svelte +3 -8
  20. package/dist/composition/Formula.svelte +2 -2
  21. package/dist/composition/FormulaFilter.svelte +27 -93
  22. package/dist/composition/format.js +6 -8
  23. package/dist/composition/parse.js +50 -105
  24. package/dist/constants.d.ts +0 -1
  25. package/dist/constants.js +6 -17
  26. package/dist/convex-hull/ConvexHull2D.svelte +0 -2
  27. package/dist/convex-hull/ConvexHull3D.svelte +19 -47
  28. package/dist/convex-hull/ConvexHull4D.svelte +65 -95
  29. package/dist/convex-hull/StructurePopup.svelte +1 -1
  30. package/dist/convex-hull/gas-thermodynamics.js +21 -53
  31. package/dist/convex-hull/helpers.js +1 -7
  32. package/dist/convex-hull/thermodynamics.js +19 -27
  33. package/dist/coordination/CoordinationBarPlot.svelte +12 -21
  34. package/dist/element/BohrAtom.svelte +0 -1
  35. package/dist/element/ElementTile.svelte +26 -44
  36. package/dist/element/data.d.ts +2 -0
  37. package/dist/element/data.js +5 -1
  38. package/dist/element/index.d.ts +1 -1
  39. package/dist/element/index.js +1 -1
  40. package/dist/fermi-surface/FermiSurface.svelte +22 -23
  41. package/dist/fermi-surface/FermiSurface.svelte.d.ts +2 -6
  42. package/dist/fermi-surface/FermiSurfaceScene.svelte +32 -77
  43. package/dist/fermi-surface/compute.js +22 -55
  44. package/dist/fermi-surface/parse.js +38 -70
  45. package/dist/file-viewer/JsonBrowser.svelte +1043 -0
  46. package/dist/file-viewer/JsonBrowser.svelte.d.ts +9 -0
  47. package/dist/file-viewer/PlotPanel.svelte +350 -0
  48. package/dist/file-viewer/PlotPanel.svelte.d.ts +9 -0
  49. package/dist/file-viewer/TrajectoryWithDos.svelte +30 -0
  50. package/dist/file-viewer/TrajectoryWithDos.svelte.d.ts +10 -0
  51. package/dist/file-viewer/detect.d.ts +11 -0
  52. package/dist/file-viewer/detect.js +381 -0
  53. package/dist/file-viewer/eligibility.d.ts +4 -0
  54. package/dist/file-viewer/eligibility.js +53 -0
  55. package/dist/file-viewer/host-protocol.d.ts +50 -0
  56. package/dist/file-viewer/host-transfer.d.ts +25 -0
  57. package/dist/file-viewer/host-transfer.js +42 -0
  58. package/dist/file-viewer/index.d.ts +5 -0
  59. package/dist/file-viewer/index.js +5 -0
  60. package/dist/file-viewer/main.d.ts +44 -0
  61. package/dist/file-viewer/main.js +535 -0
  62. package/dist/file-viewer/parse.d.ts +9 -0
  63. package/dist/file-viewer/parse.js +170 -0
  64. package/dist/file-viewer/plot-utils.d.ts +24 -0
  65. package/dist/file-viewer/plot-utils.js +191 -0
  66. package/dist/file-viewer/types.d.ts +4 -0
  67. package/dist/file-viewer/types.js +5 -0
  68. package/dist/heatmap-matrix/HeatmapMatrix.svelte +57 -84
  69. package/dist/heatmap-matrix/HeatmapMatrixControls.svelte.d.ts +1 -1
  70. package/dist/index.d.ts +2 -2
  71. package/dist/index.js +1 -1
  72. package/dist/io/ExportPane.svelte +1 -1
  73. package/dist/io/decompress.d.ts +2 -0
  74. package/dist/io/decompress.js +3 -2
  75. package/dist/io/export.d.ts +7 -4
  76. package/dist/io/export.js +70 -18
  77. package/dist/io/file-drop.js +60 -35
  78. package/dist/io/url-drop.d.ts +2 -0
  79. package/dist/io/url-drop.js +28 -16
  80. package/dist/isosurface/Isosurface.svelte +687 -196
  81. package/dist/isosurface/Isosurface.svelte.d.ts +7 -1
  82. package/dist/isosurface/IsosurfaceControls.svelte +564 -85
  83. package/dist/isosurface/IsosurfaceControls.svelte.d.ts +1 -1
  84. package/dist/isosurface/VolumeSlice.svelte +224 -0
  85. package/dist/isosurface/VolumeSlice.svelte.d.ts +28 -0
  86. package/dist/isosurface/coloring.d.ts +20 -0
  87. package/dist/isosurface/coloring.js +118 -0
  88. package/dist/isosurface/geometry-worker-types.d.ts +38 -0
  89. package/dist/isosurface/geometry.worker.d.ts +1 -0
  90. package/dist/isosurface/geometry.worker.js +59 -0
  91. package/dist/isosurface/grid.d.ts +10 -0
  92. package/dist/isosurface/grid.js +31 -0
  93. package/dist/isosurface/index.d.ts +4 -0
  94. package/dist/isosurface/index.js +4 -0
  95. package/dist/isosurface/profile.d.ts +12 -0
  96. package/dist/isosurface/profile.js +19 -0
  97. package/dist/isosurface/sampling.d.ts +34 -0
  98. package/dist/isosurface/sampling.js +408 -0
  99. package/dist/isosurface/slice-rendering.d.ts +13 -0
  100. package/dist/isosurface/slice-rendering.js +79 -0
  101. package/dist/isosurface/slice.d.ts +22 -2
  102. package/dist/isosurface/slice.js +200 -116
  103. package/dist/isosurface/types.d.ts +24 -1
  104. package/dist/isosurface/types.js +120 -8
  105. package/dist/json-path.d.ts +4 -0
  106. package/dist/json-path.js +89 -0
  107. package/dist/labels.js +1 -1
  108. package/dist/layout/InfoCard.svelte +1 -3
  109. package/dist/layout/SettingsSection.svelte +52 -61
  110. package/dist/layout/SettingsSection.svelte.d.ts +6 -7
  111. package/dist/layout/ViewerChrome.svelte +7 -1
  112. package/dist/layout/json-tree/JsonNode.svelte +14 -48
  113. package/dist/layout/json-tree/JsonTree.svelte +48 -95
  114. package/dist/layout/json-tree/JsonValue.svelte +3 -18
  115. package/dist/layout/json-tree/utils.d.ts +0 -3
  116. package/dist/layout/json-tree/utils.js +50 -179
  117. package/dist/marching-cubes.d.ts +8 -1
  118. package/dist/marching-cubes.js +115 -48
  119. package/dist/math.d.ts +3 -2
  120. package/dist/math.js +29 -38
  121. package/dist/overlays/ContextMenu.svelte +15 -32
  122. package/dist/overlays/DraggablePane.svelte +39 -58
  123. package/dist/overlays/DraggablePane.svelte.d.ts +1 -1
  124. package/dist/overlays/GlassChip.svelte +3 -1
  125. package/dist/periodic-table/PeriodicTableControls.svelte +26 -52
  126. package/dist/phase-diagram/IsobaricBinaryPhaseDiagram.svelte +18 -34
  127. package/dist/phase-diagram/build-diagram.js +1 -6
  128. package/dist/phase-diagram/svg-to-diagram.js +9 -14
  129. package/dist/plot/bar/BarPlot.svelte +38 -42
  130. package/dist/plot/bar/SpacegroupBarPlot.svelte +18 -42
  131. package/dist/plot/box/BoxPlot.svelte +27 -23
  132. package/dist/plot/core/auto-place.d.ts +1 -1
  133. package/dist/plot/core/auto-place.js +1 -1
  134. package/dist/plot/core/data-transform.d.ts +1 -2
  135. package/dist/plot/core/data-transform.js +0 -10
  136. package/dist/plot/core/layout.d.ts +12 -5
  137. package/dist/plot/core/layout.js +37 -27
  138. package/dist/plot/core/pan-zoom.svelte.d.ts +2 -4
  139. package/dist/plot/core/pan-zoom.svelte.js +34 -17
  140. package/dist/plot/core/placed-tween.svelte.d.ts +3 -0
  141. package/dist/plot/core/placed-tween.svelte.js +68 -21
  142. package/dist/plot/core/svg.js +0 -1
  143. package/dist/plot/core/utils/hierarchy-chart.d.ts +1 -0
  144. package/dist/plot/core/utils/hierarchy-chart.js +12 -0
  145. package/dist/plot/core/utils/hierarchy-labels.js +3 -2
  146. package/dist/plot/histogram/Histogram.svelte +21 -26
  147. package/dist/plot/sankey/sankey.js +13 -9
  148. package/dist/plot/scatter/BinnedScatterPlot.svelte +59 -43
  149. package/dist/plot/scatter/ScatterPlot.svelte +67 -52
  150. package/dist/plot/scatter/adaptive-density.d.ts +1 -1
  151. package/dist/plot/scatter/adaptive-density.js +36 -20
  152. package/dist/plot/sunburst/Sunburst.svelte +20 -20
  153. package/dist/plot/treemap/Treemap.svelte +175 -60
  154. package/dist/plot/treemap/Treemap.svelte.d.ts +6 -0
  155. package/dist/plot/treemap/index.d.ts +1 -0
  156. package/dist/plot/treemap/labels.d.ts +38 -0
  157. package/dist/plot/treemap/labels.js +80 -0
  158. package/dist/plot/treemap/treemap.d.ts +4 -2
  159. package/dist/plot/treemap/treemap.js +5 -2
  160. package/dist/rdf/RdfPlot.svelte +17 -35
  161. package/dist/rdf/calc-rdf.js +59 -44
  162. package/dist/rdf/index.d.ts +0 -1
  163. package/dist/sanitize.js +84 -26
  164. package/dist/scene/SceneCamera.svelte +1 -1
  165. package/dist/scene/SceneCamera.svelte.d.ts +1 -1
  166. package/dist/scene/index.d.ts +1 -2
  167. package/dist/scene/index.js +1 -1
  168. package/dist/scene/{props.js → props.svelte.js} +15 -1
  169. package/dist/settings.js +159 -482
  170. package/dist/spectral/Bands.svelte +151 -158
  171. package/dist/spectral/BrillouinBandsDos.svelte +4 -19
  172. package/dist/spectral/Dos.svelte +26 -24
  173. package/dist/spectral/helpers.js +40 -105
  174. package/dist/structure/AtomLegend.svelte +8 -13
  175. package/dist/structure/AtomLegend.svelte.d.ts +2 -3
  176. package/dist/structure/CellSelect.svelte +35 -10
  177. package/dist/structure/Structure.svelte +177 -99
  178. package/dist/structure/Structure.svelte.d.ts +1 -1
  179. package/dist/structure/StructureCarousel.svelte +10 -3
  180. package/dist/structure/StructureControls.svelte +8 -26
  181. package/dist/structure/StructureControls.svelte.d.ts +1 -1
  182. package/dist/structure/StructureInfoPane.svelte +65 -76
  183. package/dist/structure/StructureScene.svelte +75 -109
  184. package/dist/structure/StructureScene.svelte.d.ts +3 -1
  185. package/dist/structure/StructureViewport.svelte +120 -55
  186. package/dist/structure/StructureViewport.svelte.d.ts +3 -1
  187. package/dist/structure/atom-properties.d.ts +1 -1
  188. package/dist/structure/atom-properties.js +4 -3
  189. package/dist/structure/bond-order-perception.js +3 -8
  190. package/dist/structure/bonding.d.ts +0 -1
  191. package/dist/structure/bonding.js +16 -23
  192. package/dist/structure/export.js +50 -74
  193. package/dist/structure/format-detect.js +5 -7
  194. package/dist/structure/index.js +6 -12
  195. package/dist/structure/parse.js +38 -100
  196. package/dist/structure/partial-occupancy.js +7 -4
  197. package/dist/structure/pbc.js +8 -10
  198. package/dist/structure/polyhedra.js +5 -4
  199. package/dist/structure/prop-groups.d.ts +85 -0
  200. package/dist/structure/prop-groups.js +18 -0
  201. package/dist/symmetry/SymmetryStats.svelte +0 -4
  202. package/dist/symmetry/WyckoffTable.svelte +5 -4
  203. package/dist/symmetry/cell-transform.js +5 -14
  204. package/dist/symmetry/index.js +6 -9
  205. package/dist/table/HeatmapTable.svelte +61 -103
  206. package/dist/table/ToggleMenu.svelte +166 -44
  207. package/dist/table/ToggleMenu.svelte.d.ts +1 -1
  208. package/dist/theme/embedded.d.ts +18 -0
  209. package/dist/theme/embedded.js +206 -0
  210. package/dist/theme/index.d.ts +4 -0
  211. package/dist/theme/index.js +1 -2
  212. package/dist/theme/themes.mjs +31 -60
  213. package/dist/tooltip/TooltipContent.svelte +1 -1
  214. package/dist/tooltip/TooltipContent.svelte.d.ts +1 -1
  215. package/dist/tooltip/index.d.ts +8 -1
  216. package/dist/trajectory/Trajectory.svelte +140 -120
  217. package/dist/trajectory/TrajectoryError.svelte +1 -66
  218. package/dist/trajectory/TrajectoryExportPane.svelte +0 -1
  219. package/dist/trajectory/TrajectoryInfoPane.svelte +55 -80
  220. package/dist/trajectory/TrajectoryInfoPane.svelte.d.ts +2 -1
  221. package/dist/trajectory/extract.js +19 -29
  222. package/dist/trajectory/format-detect.d.ts +2 -0
  223. package/dist/trajectory/format-detect.js +7 -5
  224. package/dist/trajectory/frame-reader.js +3 -4
  225. package/dist/trajectory/helpers.js +16 -49
  226. package/dist/trajectory/index.js +2 -8
  227. package/dist/trajectory/parse/ase.js +1 -1
  228. package/dist/trajectory/parse/index.d.ts +4 -3
  229. package/dist/trajectory/parse/index.js +12 -17
  230. package/dist/trajectory/parse/vaspout-h5.d.ts +0 -1
  231. package/dist/trajectory/parse/vaspout-h5.js +1 -4
  232. package/dist/trajectory/parse/xyz.js +48 -2
  233. package/dist/trajectory/plotting.js +14 -37
  234. package/dist/xrd/XrdPlot.svelte +43 -68
  235. package/dist/xrd/calc-xrd.js +22 -58
  236. package/dist/xrd/parse.js +13 -22
  237. package/package.json +40 -8
  238. package/readme.md +6 -5
  239. package/dist/scene/types.d.ts +0 -26
  240. package/dist/tooltip/types.d.ts +0 -8
  241. package/dist/trajectory/constants.d.ts +0 -6
  242. package/dist/trajectory/constants.js +0 -11
  243. /package/dist/{scene/types.js → file-viewer/host-protocol.js} +0 -0
  244. /package/dist/{tooltip/types.js → isosurface/geometry-worker-types.js} +0 -0
@@ -5,7 +5,7 @@
5
5
  import { StatusMessage } from '../feedback'
6
6
  import Spinner from '../feedback/Spinner.svelte'
7
7
  import Icon from '../Icon.svelte'
8
- import { drag_over_handlers, handle_url_drop, load_from_url } from '../io'
8
+ import * as io from '../io'
9
9
  import { forward_window_keydown, handle_and_prevent } from '../keyboard'
10
10
  import { format_num, trajectory_property_config, type TrajPropertyConfig } from '../labels'
11
11
  import type { Vec2 } from '../math'
@@ -17,6 +17,7 @@
17
17
  import { Histogram, ScatterPlot } from '../plot'
18
18
  import { toggle_series_visibility } from '../plot/core/utils/series-visibility'
19
19
  import { DEFAULTS } from '../settings'
20
+ import type { AnyStructure } from '../structure'
20
21
  import Structure from '../structure/Structure.svelte'
21
22
  import { scaleLinear } from 'd3-scale'
22
23
  import type { ComponentProps, Snippet } from 'svelte'
@@ -233,6 +234,9 @@
233
234
  let element_size = $state({ width: 0, height: 0 })
234
235
  let filename_copied = $state(false)
235
236
  let orig_data = $state<string | ArrayBuffer | null>(null)
237
+ let data_url_load_id = 0
238
+ let loaded_data_url: string | undefined
239
+ let url_owned_trajectory: TrajectoryType | undefined
236
240
 
237
241
  let controls_config = $derived(normalize_show_controls(show_controls))
238
242
 
@@ -280,6 +284,7 @@
280
284
  if (trajectory && current_step_idx >= 0 && current_step_idx < total_frames) {
281
285
  if (trajectory.frame_loader) {
282
286
  // Load frame on demand (works for both indexed files and external streaming)
287
+ current_frame = null
283
288
  schedule_frame_load_on_demand(current_step_idx)
284
289
  } else {
285
290
  clear_frame_load_timeout()
@@ -310,22 +315,28 @@
310
315
  frame_load_timeout = undefined
311
316
  }
312
317
 
313
- const merge_plot_metadata = (batch: TrajectoryMetadata[]) => {
314
- if (!trajectory || batch.length === 0) return
315
- trajectory = {
316
- ...trajectory,
317
- plot_metadata: [...(trajectory.plot_metadata ?? []), ...batch],
318
- }
318
+ const skip_stale_url_stream = () =>
319
+ Boolean(data_url && loaded_data_url && data_url !== loaded_data_url)
320
+
321
+ // Replace the trajectory with an updated copy, keeping URL ownership if it applied.
322
+ // No-ops while a data_url switch is in flight so stale streams can't mutate the old model.
323
+ const update_trajectory = (updates: Partial<TrajectoryType>) => {
324
+ if (!trajectory || skip_stale_url_stream()) return
325
+ const preserves_url_ownership = trajectory === url_owned_trajectory
326
+ trajectory = { ...trajectory, ...updates }
327
+ if (preserves_url_ownership) url_owned_trajectory = trajectory
319
328
  }
320
329
 
321
- const finish_plot_metadata_loading = () => {
322
- if (!trajectory) return
323
- trajectory = {
324
- ...trajectory,
325
- metadata: { ...trajectory.metadata, plot_metadata_loading: false },
326
- }
330
+ const merge_plot_metadata = (batch: TrajectoryMetadata[]) => {
331
+ if (batch.length === 0) return
332
+ update_trajectory({ plot_metadata: [...(trajectory?.plot_metadata ?? []), ...batch] })
327
333
  }
328
334
 
335
+ const finish_plot_metadata_loading = () =>
336
+ update_trajectory({
337
+ metadata: { ...trajectory?.metadata, plot_metadata_loading: false },
338
+ })
339
+
329
340
  onMount(() => {
330
341
  const handle_plot_metadata_stream = (event: MessageEvent<PlotMetadataStreamMessage>) => {
331
342
  // Global listener: other code posts arbitrary messages (including null data)
@@ -489,8 +500,14 @@
489
500
  }
490
501
  }
491
502
 
492
- // Current frame structure for display
493
- let current_structure = $derived(current_frame?.structure)
503
+ // Current frame structure for display. Holds the last resolved structure so the 3D
504
+ // view doesn't blank while an uncached frame loads on demand (current_frame is nulled
505
+ // during loads to keep the info pane from showing the previous frame's data).
506
+ let current_structure = $state<AnyStructure | undefined>(undefined)
507
+ $effect(() => {
508
+ if (current_frame?.structure) current_structure = current_frame.structure
509
+ else if (!trajectory) current_structure = undefined
510
+ })
494
511
 
495
512
  // Track hidden elements (persists across frame changes)
496
513
  let hidden_elements = $state(new SvelteSet<ElementSymbol>())
@@ -637,10 +654,7 @@
637
654
  display_mode !== `structure` &&
638
655
  (plot_metadata_loading || !should_hide_plot(trajectory, plot_series)),
639
656
  )
640
-
641
- // Determine what to show based on display mode
642
657
  let show_structure = $derived(![`scatter`, `histogram`].includes(display_mode))
643
- let actual_show_plot = $derived(display_mode !== `structure` && show_plot)
644
658
 
645
659
  // Check if there are any Y2 series to determine padding
646
660
  let has_y2_series = $derived(plot_series.some((srs) => srs.y_axis === `y2` && srs.visible))
@@ -659,11 +673,10 @@
659
673
  frame: current_frame || undefined,
660
674
  })
661
675
  }
662
- // Step navigation functions
676
+ // Step navigation functions (streaming frame loading is handled by the reactive effect)
663
677
  function next_step() {
664
678
  if (current_step_idx < total_frames - 1) {
665
679
  current_step_idx++
666
- // Streaming frame loading handled by reactive effect
667
680
  notify_step_change()
668
681
  }
669
682
  }
@@ -671,7 +684,6 @@
671
684
  function prev_step() {
672
685
  if (current_step_idx > 0) {
673
686
  current_step_idx--
674
- // Streaming frame loading handled by reactive effect
675
687
  notify_step_change()
676
688
  }
677
689
  }
@@ -679,8 +691,6 @@
679
691
  function go_to_step(idx: number) {
680
692
  if (idx >= 0 && idx < total_frames) {
681
693
  current_step_idx = idx
682
- // Note: streaming frame loading is handled by reactive effect
683
- // Handle callbacks for both traditional and streaming modes
684
694
  notify_step_change()
685
695
  }
686
696
  }
@@ -816,16 +826,16 @@
816
826
  }
817
827
 
818
828
  // Handle URL-based files (e.g. from FilePicker)
819
- const handled = await handle_url_drop(event, async (content, filename) => {
820
- current_filename = filename
821
- file_size =
822
- content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
823
- await load_trajectory_data(content, filename)
824
- }).catch(() => false)
825
-
826
- if (handled) {
827
- return
828
- }
829
+ const handled = await io
830
+ .handle_url_drop(event, async (content, filename) => {
831
+ current_filename = filename
832
+ file_size =
833
+ content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
834
+ await load_trajectory_data(content, filename)
835
+ })
836
+ .catch(() => false)
837
+
838
+ if (handled) return
829
839
 
830
840
  // Handle file system drops with optimized large file support
831
841
  const file = event.dataTransfer?.files[0]
@@ -857,33 +867,58 @@
857
867
  }
858
868
  }
859
869
 
870
+ // Load trajectory from URL when data_url is provided. Track the model produced by
871
+ // this effect so caller-owned trajectory props keep precedence while URL-owned
872
+ // models can reload when data_url changes.
860
873
  $effect(() => {
861
- // Load trajectory from URL when data_url is provided
862
- if (data_url && !trajectory) {
863
- loading = true
864
- error_msg = null
865
-
866
- load_from_url(data_url, async (content, filename) => {
867
- current_filename = filename
868
- file_size =
869
- content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
870
- await load_trajectory_data(content, filename)
874
+ const requested_url = data_url
875
+ const current_trajectory = trajectory
876
+ const caller_owns_trajectory = Boolean(
877
+ current_trajectory && current_trajectory !== url_owned_trajectory,
878
+ )
879
+ if (!requested_url || caller_owns_trajectory) {
880
+ loaded_data_url = undefined
881
+ url_owned_trajectory = undefined
882
+ return
883
+ }
884
+ if (loaded_data_url === requested_url) return
885
+
886
+ const load_id = ++data_url_load_id
887
+ const is_current = () => load_id === data_url_load_id
888
+ loading = true
889
+ error_msg = null
890
+
891
+ io.load_from_url(requested_url, async (content, filename) => {
892
+ if (!is_current()) return
893
+ current_filename = filename
894
+ file_size =
895
+ content instanceof ArrayBuffer ? content.byteLength : new Blob([content]).size
896
+ await load_trajectory_data(content, filename, {
897
+ on_trajectory_loaded: (loaded_trajectory) => {
898
+ if (!is_current()) return
899
+ url_owned_trajectory = loaded_trajectory
900
+ loaded_data_url = requested_url
901
+ },
902
+ should_commit: is_current,
871
903
  })
872
- .then(() => {
873
- loading = false
874
- })
875
- .catch((err: Error) => {
876
- console.error(`Failed to load trajectory from URL:`, err)
877
- error_msg = `Failed to load trajectory: ${err.message}`
878
- current_filename = undefined
879
- file_size = undefined
880
- loading = false
881
- on_error?.({
882
- error_msg,
883
- filename: current_filename || undefined,
884
- file_size: file_size || undefined,
885
- })
886
- })
904
+ })
905
+ .catch((err: Error) => {
906
+ if (!is_current()) return
907
+ console.error(`Failed to load trajectory from URL:`, err)
908
+ error_msg = `Failed to load trajectory: ${err.message}`
909
+ current_filename = undefined
910
+ file_size = undefined
911
+ on_error?.({ error_msg, filename: io.basename_from_url(requested_url) })
912
+ })
913
+ .finally(() => {
914
+ if (is_current()) loading = false
915
+ })
916
+
917
+ return () => {
918
+ if (is_current()) {
919
+ data_url_load_id += 1
920
+ loading = false
921
+ }
887
922
  }
888
923
  })
889
924
 
@@ -892,13 +927,23 @@
892
927
  on_frame_rate_change?.({ trajectory, fps })
893
928
  })
894
929
 
895
- async function load_trajectory_data(data: string | ArrayBuffer, filename: string) {
930
+ async function load_trajectory_data(
931
+ data: string | ArrayBuffer,
932
+ filename: string,
933
+ options: {
934
+ on_trajectory_loaded?: (loaded_trajectory: TrajectoryType) => void
935
+ should_commit?: () => boolean
936
+ } = {},
937
+ ) {
938
+ const { on_trajectory_loaded, should_commit = () => true } = options
896
939
  loading = true
897
940
  error_msg = null
898
941
  parsing_progress = null
899
942
 
900
943
  // Reset previous loading state
901
944
  orig_data = null
945
+ const file_size_bytes =
946
+ data instanceof ArrayBuffer ? data.byteLength : new Blob([data]).size
902
947
 
903
948
  try {
904
949
  const data_size = data instanceof ArrayBuffer ? data.byteLength : data.length
@@ -906,84 +951,58 @@
906
951
  // Determine loading strategy based on file size
907
952
  const bin_file_threshold = loading_options.bin_file_threshold ?? MAX_BIN_FILE_SIZE
908
953
  const text_file_threshold = loading_options.text_file_threshold ?? MAX_TEXT_FILE_SIZE
909
- if (
954
+ const is_large_file =
910
955
  (data instanceof ArrayBuffer && data_size > bin_file_threshold) ||
911
956
  (typeof data === `string` && data_size > text_file_threshold)
912
- ) {
913
- // Large files: Use indexed loading
914
- await load_with_indexing(data, filename)
915
- } else {
916
- // Small files: Use regular loading
917
- const merged_options = { ...loading_options, atom_type_mapping }
918
- trajectory = await parse_trajectory_async(
919
- data,
920
- filename,
921
- (progress) => {
922
- parsing_progress = progress
923
- },
924
- merged_options,
925
- )
926
- }
957
+
958
+ // Large files get indexed loading by default (loading_options can override)
959
+ const parsed_trajectory = await parse_trajectory_async(
960
+ data,
961
+ filename,
962
+ (progress) => {
963
+ if (should_commit()) parsing_progress = progress
964
+ },
965
+ {
966
+ ...(is_large_file ? { use_indexing: true } : {}),
967
+ ...loading_options,
968
+ atom_type_mapping,
969
+ },
970
+ )
971
+ if (!should_commit()) return
972
+ trajectory = parsed_trajectory
973
+ if (trajectory) on_trajectory_loaded?.(trajectory)
974
+ // Keep original data only when parsing attached a frame_loader for on-demand loads.
975
+ // Direct-parse fallbacks load all frames upfront, so retaining a duplicate wastes memory.
976
+ orig_data = trajectory?.frame_loader ? data : null
927
977
 
928
978
  current_step_idx = 0
929
979
  current_filename = filename
980
+ file_size = file_size_bytes
930
981
 
931
- const file_size_bytes =
932
- data instanceof ArrayBuffer ? data.byteLength : new Blob([data]).size
982
+ const loaded_trajectory = trajectory
933
983
  on_file_load?.({
934
984
  // emit file load event
935
- trajectory,
936
- frame_count: trajectory?.frames.length ?? 0,
937
- total_atoms: trajectory?.frames[0]?.structure.sites.length ?? 0,
985
+ trajectory: loaded_trajectory,
986
+ frame_count: loaded_trajectory?.frames.length ?? 0,
987
+ total_atoms: loaded_trajectory?.frames[0]?.structure.sites.length ?? 0,
938
988
  filename,
939
989
  file_size: file_size_bytes,
940
990
  })
941
991
  } catch (err) {
992
+ if (!should_commit()) return
942
993
  const unsupported_message = get_unsupported_format_message(
943
994
  filename,
944
995
  typeof data === `string` ? data : ``,
945
996
  )
946
997
  error_msg = unsupported_message || `Failed to parse trajectory: ${err}`
998
+ on_error?.({ error_msg, filename, file_size: file_size_bytes })
947
999
  current_filename = undefined
948
1000
  file_size = undefined
949
-
950
- on_error?.({
951
- // emit error event
952
- error_msg,
953
- filename: current_filename || undefined,
954
- file_size: file_size || undefined,
955
- })
956
1001
  } finally {
957
- parsing_progress = null
958
- loading = false
959
- }
960
- }
961
-
962
- // Load using indexed parsing for large files
963
- async function load_with_indexing(data: string | ArrayBuffer, filename: string) {
964
- try {
965
- // Use indexed parsing for efficient large file handling
966
- const merged_options = {
967
- use_indexing: true,
968
- ...loading_options,
969
- atom_type_mapping,
1002
+ if (should_commit()) {
1003
+ parsing_progress = null
1004
+ loading = false
970
1005
  }
971
- trajectory = await parse_trajectory_async(
972
- data,
973
- filename,
974
- (progress) => {
975
- parsing_progress = progress
976
- },
977
- merged_options,
978
- )
979
-
980
- // Keep original data for on-demand frame loads only when indexed parsing attached a
981
- // frame_loader. Direct-parse fallbacks (e.g. large JSON or extensionless blob:
982
- // filenames) load all frames upfront, so retaining a full duplicate payload wastes memory.
983
- orig_data = trajectory?.frame_loader ? data : null
984
- } catch (error) {
985
- console.error(`Indexed loading failed:`, error)
986
- throw error
987
1006
  }
988
1007
  }
989
1008
 
@@ -1113,7 +1132,7 @@
1113
1132
  onmouseenter={() => (hovered = true)}
1114
1133
  onmouseleave={() => (hovered = false)}
1115
1134
  ondrop={handle_file_drop}
1116
- {...drag_over_handlers({
1135
+ {...io.drag_over_handlers({
1117
1136
  allow: () => allow_file_drop,
1118
1137
  set_dragover: (over) => (dragover = over),
1119
1138
  })}
@@ -1122,7 +1141,7 @@
1122
1141
  {...rest}
1123
1142
  class={[`trajectory`, actual_layout, rest.class]}
1124
1143
  class:show-both-views={[`structure+scatter`, `structure+histogram`].includes(display_mode) &&
1125
- actual_show_plot &&
1144
+ show_plot &&
1126
1145
  show_structure}
1127
1146
  >
1128
1147
  {#if loading}
@@ -1283,6 +1302,7 @@
1283
1302
  {#if trajectory && controls_config.visible(`info-pane`)}
1284
1303
  <TrajectoryInfoPane
1285
1304
  {trajectory}
1305
+ {current_frame}
1286
1306
  {current_step_idx}
1287
1307
  {current_filename}
1288
1308
  {current_file_path}
@@ -1363,7 +1383,7 @@
1363
1383
 
1364
1384
  <div
1365
1385
  class="content-area"
1366
- class:hide-plot={!actual_show_plot}
1386
+ class:hide-plot={!show_plot}
1367
1387
  class:hide-structure={!show_structure}
1368
1388
  class:show-both={[`structure+scatter`, `structure+histogram`].includes(display_mode)}
1369
1389
  class:show-structure-only={display_mode === `structure`}
@@ -1384,7 +1404,7 @@
1384
1404
  />
1385
1405
  {/if}
1386
1406
 
1387
- {#if actual_show_plot}
1407
+ {#if show_plot}
1388
1408
  {#if plot_metadata_loading}
1389
1409
  <div class="plot-metadata-loading plot">
1390
1410
  <Spinner text="Sampling trajectory plot data..." style="--spinner-size: 1.4em" />
@@ -20,7 +20,7 @@
20
20
  {#if error_snippet}
21
21
  {@render error_snippet({ error_msg, on_dismiss })}
22
22
  {:else if error_msg.startsWith(`<`)}
23
- <!-- Render HTML content for unsupported format messages -->
23
+ <!-- Render HTML error messages (sanitized) -->
24
24
  {@html sanitize_html(error_msg)}
25
25
  <button onclick={on_dismiss}>Dismiss</button>
26
26
  {:else}
@@ -63,69 +63,4 @@
63
63
  div button:hover {
64
64
  background: var(--error-btn-bg-hover);
65
65
  }
66
- /* Styles for unsupported format messages */
67
- div :global(.unsupported-format) {
68
- text-align: left;
69
- max-width: 90%;
70
- max-height: 70vh;
71
- margin: 0 auto;
72
- overflow-y: auto;
73
- overflow-x: hidden;
74
- }
75
- div :global(.unsupported-format h4) {
76
- color: var(--error-color);
77
- margin: 0 0 1rem 0;
78
- font-size: 1.1rem;
79
- display: flex;
80
- align-items: center;
81
- gap: 0.5rem;
82
- }
83
- div :global(.unsupported-format h5) {
84
- margin: 0.75rem 0 0.25rem 0;
85
- font-size: 0.9rem;
86
- font-weight: 600;
87
- }
88
- div :global(.unsupported-format p) {
89
- margin: 0.25rem 0;
90
- text-align: left;
91
- font-size: 0.85rem;
92
- }
93
- div :global(.unsupported-format ul) {
94
- text-align: left;
95
- margin: 0.5rem 0;
96
- padding-left: 1.5rem;
97
- }
98
- div :global(.unsupported-format li) {
99
- margin: 0.25rem 0;
100
- }
101
- div :global(.unsupported-format .code-options) {
102
- margin: 1rem 0 0 0;
103
- display: grid;
104
- grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
105
- gap: 1.5rem;
106
- }
107
- div :global(.unsupported-format .code-options > div) {
108
- margin: 0;
109
- }
110
- div :global(.unsupported-format .code-options strong) {
111
- display: block;
112
- margin-bottom: 0.25rem;
113
- font-size: 0.85rem;
114
- font-weight: 600;
115
- }
116
- div :global(.unsupported-format pre) {
117
- padding: 0.5rem;
118
- margin: 0;
119
- overflow-x: auto;
120
- font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace;
121
- font-size: 0.75rem;
122
- line-height: 1.2;
123
- max-height: 150px;
124
- overflow-y: auto;
125
- }
126
- div :global(.unsupported-format p code) {
127
- padding: 0.2em 0.4em;
128
- border-radius: 3px;
129
- font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace;
130
- }
131
66
  </style>
@@ -9,7 +9,6 @@
9
9
  import ExportPane from '../io/ExportPane.svelte'
10
10
  import SettingsSection from '../layout/SettingsSection.svelte'
11
11
  import type { TrajectoryType } from './'
12
- import type { ComponentProps } from 'svelte'
13
12
  import { tooltip } from 'svelte-multiselect/attachments'
14
13
  import { to_error } from '../utils'
15
14