react-cheminfo 0.66.0 → 0.67.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 (291) hide show
  1. package/README.md +22 -11
  2. package/lib/download/ui/FigureDownloadPanel.d.ts +7 -1
  3. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  4. package/lib/download/ui/FigureDownloadPanel.js +2 -2
  5. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  6. package/lib/ecosystem/core/sites.js +3 -3
  7. package/lib/ecosystem/core/sites.js.map +1 -1
  8. package/lib/ecosystem/core/types.d.ts +1 -1
  9. package/lib/ecosystem/core/types.d.ts.map +1 -1
  10. package/lib/ecosystem/ui/glyphs.js +1 -1
  11. package/lib/ecosystem/ui/glyphs.js.map +1 -1
  12. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  13. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  14. package/lib/i18n/core/messageCatalog.d.ts +2 -2
  15. package/lib/i18n/vite/localeCatalog.d.ts +1 -1
  16. package/lib/ir/core/index.d.ts +2 -2
  17. package/lib/ir/core/index.d.ts.map +1 -1
  18. package/lib/ir/core/index.js.map +1 -1
  19. package/lib/ir/core/irEditorApi.d.ts +12 -0
  20. package/lib/ir/core/irEditorApi.d.ts.map +1 -1
  21. package/lib/ir/core/irReducer.d.ts +2 -2
  22. package/lib/ir/core/irReducer.d.ts.map +1 -1
  23. package/lib/ir/core/irSidePanels.d.ts +7 -1
  24. package/lib/ir/core/irSidePanels.d.ts.map +1 -1
  25. package/lib/ir/core/irState.d.ts +8 -5
  26. package/lib/ir/core/irState.d.ts.map +1 -1
  27. package/lib/ir/core/irState.js.map +1 -1
  28. package/lib/ir/ui/IrCanvas.d.ts +7 -0
  29. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  30. package/lib/ir/ui/IrCanvas.js +8 -3
  31. package/lib/ir/ui/IrCanvas.js.map +1 -1
  32. package/lib/ir/ui/IrChart.d.ts.map +1 -1
  33. package/lib/ir/ui/IrChart.js +5 -1
  34. package/lib/ir/ui/IrChart.js.map +1 -1
  35. package/lib/ir/ui/IrEditor.d.ts +22 -4
  36. package/lib/ir/ui/IrEditor.d.ts.map +1 -1
  37. package/lib/ir/ui/IrEditor.js.map +1 -1
  38. package/lib/ir/ui/IrShell.d.ts +9 -1
  39. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  40. package/lib/ir/ui/IrShell.js +4 -19
  41. package/lib/ir/ui/IrShell.js.map +1 -1
  42. package/lib/ir/ui/IrSidePanel.d.ts +11 -0
  43. package/lib/ir/ui/IrSidePanel.d.ts.map +1 -1
  44. package/lib/ir/ui/IrSidePanel.js +12 -3
  45. package/lib/ir/ui/IrSidePanel.js.map +1 -1
  46. package/lib/ir/ui/RamanEditor.d.ts +22 -4
  47. package/lib/ir/ui/RamanEditor.d.ts.map +1 -1
  48. package/lib/ir/ui/RamanEditor.js.map +1 -1
  49. package/lib/ir/ui/VibrationalEditor.d.ts +18 -4
  50. package/lib/ir/ui/VibrationalEditor.d.ts.map +1 -1
  51. package/lib/ir/ui/VibrationalEditor.js +4 -2
  52. package/lib/ir/ui/VibrationalEditor.js.map +1 -1
  53. package/lib/ir/ui/index.d.ts +1 -0
  54. package/lib/ir/ui/index.d.ts.map +1 -1
  55. package/lib/ir/ui/index.js.map +1 -1
  56. package/lib/ir/ui/irActions.d.ts +3 -3
  57. package/lib/ir/ui/irActions.d.ts.map +1 -1
  58. package/lib/ir/ui/irChartProps.d.ts +7 -0
  59. package/lib/ir/ui/irChartProps.d.ts.map +1 -1
  60. package/lib/ir/ui/irHostPanels.d.ts +53 -0
  61. package/lib/ir/ui/irHostPanels.d.ts.map +1 -0
  62. package/lib/ir/ui/irHostPanels.js +41 -0
  63. package/lib/ir/ui/irHostPanels.js.map +1 -0
  64. package/lib/ir/ui/useIrSide.d.ts +27 -0
  65. package/lib/ir/ui/useIrSide.d.ts.map +1 -0
  66. package/lib/ir/ui/useIrSide.js +42 -0
  67. package/lib/ir/ui/useIrSide.js.map +1 -0
  68. package/lib/ir/ui/useIrZoom.d.ts +8 -0
  69. package/lib/ir/ui/useIrZoom.d.ts.map +1 -1
  70. package/lib/ir/ui/useIrZoom.js +2 -1
  71. package/lib/ir/ui/useIrZoom.js.map +1 -1
  72. package/lib/locales/de.json +9 -1
  73. package/lib/locales/en.json +9 -1
  74. package/lib/locales/es.json +9 -1
  75. package/lib/locales/fr.json +9 -1
  76. package/lib/locales/it.json +9 -1
  77. package/lib/molecule3d/core/animatedPng.d.ts +25 -0
  78. package/lib/molecule3d/core/animatedPng.d.ts.map +1 -0
  79. package/lib/molecule3d/core/animatedPng.js +59 -0
  80. package/lib/molecule3d/core/animatedPng.js.map +1 -0
  81. package/lib/molecule3d/core/file.d.ts +24 -0
  82. package/lib/molecule3d/core/file.d.ts.map +1 -0
  83. package/lib/molecule3d/core/file.js +2 -0
  84. package/lib/molecule3d/core/file.js.map +1 -0
  85. package/lib/molecule3d/core/index.d.ts +4 -1
  86. package/lib/molecule3d/core/index.d.ts.map +1 -1
  87. package/lib/molecule3d/core/index.js +1 -0
  88. package/lib/molecule3d/core/index.js.map +1 -1
  89. package/lib/molecule3d/core/settings.d.ts +0 -7
  90. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  91. package/lib/molecule3d/core/settings.js.map +1 -1
  92. package/lib/molecule3d/ui/Molecule3DExport.d.ts +10 -2
  93. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  94. package/lib/molecule3d/ui/Molecule3DExport.js +15 -2
  95. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  96. package/lib/molecule3d/ui/Molecule3DToolbar.js +9 -2
  97. package/lib/molecule3d/ui/Molecule3DToolbar.js.map +1 -1
  98. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  99. package/lib/molecule3d/ui/MoleculeCanvas3D.js +23 -41
  100. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  101. package/lib/molecule3d/ui/drawScene.d.ts +2 -1
  102. package/lib/molecule3d/ui/drawScene.d.ts.map +1 -1
  103. package/lib/molecule3d/ui/drawScene.js.map +1 -1
  104. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +15 -1
  105. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  106. package/lib/molecule3d/ui/exportMoleculeImage.js +14 -2
  107. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  108. package/lib/molecule3d/ui/moleculeFrames.d.ts +47 -0
  109. package/lib/molecule3d/ui/moleculeFrames.d.ts.map +1 -0
  110. package/lib/molecule3d/ui/moleculeFrames.js +65 -0
  111. package/lib/molecule3d/ui/moleculeFrames.js.map +1 -0
  112. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +14 -1
  113. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  114. package/lib/molecule3d/ui/renderMolecule.d.ts +3 -3
  115. package/lib/molecule3d/ui/renderMolecule.d.ts.map +1 -1
  116. package/lib/molecule3d/ui/renderMolecule.js +2 -1
  117. package/lib/molecule3d/ui/renderMolecule.js.map +1 -1
  118. package/lib/molecule3d/ui/sceneDrawer.d.ts +2 -1
  119. package/lib/molecule3d/ui/sceneDrawer.d.ts.map +1 -1
  120. package/lib/molecule3d/ui/sceneDrawer.js +9 -1
  121. package/lib/molecule3d/ui/sceneDrawer.js.map +1 -1
  122. package/lib/molecule3d/ui/useImageExport.d.ts +8 -3
  123. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  124. package/lib/molecule3d/ui/useImageExport.js +13 -4
  125. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  126. package/lib/molecule3d/ui/useMoleculeScene.d.ts +2 -1
  127. package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -1
  128. package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -1
  129. package/lib/molecule3d/ui/useMoleculeViewer.d.ts +16 -0
  130. package/lib/molecule3d/ui/useMoleculeViewer.d.ts.map +1 -0
  131. package/lib/molecule3d/ui/useMoleculeViewer.js +37 -0
  132. package/lib/molecule3d/ui/useMoleculeViewer.js.map +1 -0
  133. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts +3 -3
  134. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts.map +1 -1
  135. package/lib/molecule3d/ui/usePolarSurfaceArea.js +4 -3
  136. package/lib/molecule3d/ui/usePolarSurfaceArea.js.map +1 -1
  137. package/lib/molecule3d/ui/viewer.d.ts +4 -1
  138. package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
  139. package/lib/molecule3d/ui/viewer.js +21 -19
  140. package/lib/molecule3d/ui/viewer.js.map +1 -1
  141. package/lib/molecule3d/ui/viewerTypes.d.ts +6 -0
  142. package/lib/molecule3d/ui/viewerTypes.d.ts.map +1 -1
  143. package/lib/molstar/core/captureScene.d.ts +19 -1
  144. package/lib/molstar/core/captureScene.d.ts.map +1 -1
  145. package/lib/molstar/core/captureScene.js +57 -2
  146. package/lib/molstar/core/captureScene.js.map +1 -1
  147. package/lib/molstar/core/index.d.ts +5 -2
  148. package/lib/molstar/core/index.d.ts.map +1 -1
  149. package/lib/molstar/core/index.js +4 -2
  150. package/lib/molstar/core/index.js.map +1 -1
  151. package/lib/molstar/core/trajectory.d.ts +56 -0
  152. package/lib/molstar/core/trajectory.d.ts.map +1 -0
  153. package/lib/molstar/core/trajectory.js +96 -0
  154. package/lib/molstar/core/trajectory.js.map +1 -0
  155. package/lib/panel/ui/PanelRail.d.ts.map +1 -1
  156. package/lib/panel/ui/PanelRail.js +1 -1
  157. package/lib/panel/ui/PanelRail.js.map +1 -1
  158. package/lib/raman.d.ts +2 -1
  159. package/lib/raman.d.ts.map +1 -1
  160. package/lib/raman.js.map +1 -1
  161. package/lib/router/core/basePath.d.ts +2 -2
  162. package/lib/router/core/basePath.js +2 -2
  163. package/lib/router/core/siteAddresses.d.ts +1 -1
  164. package/lib/router/core/siteAddresses.js +1 -1
  165. package/lib/seo/core/siteFiles.js +1 -1
  166. package/lib/seo/core/siteFiles.js.map +1 -1
  167. package/lib/server/answerAddresses.d.ts +3 -3
  168. package/lib/server/answerAddresses.js +3 -3
  169. package/lib/structure/core/index.d.ts +2 -0
  170. package/lib/structure/core/index.d.ts.map +1 -1
  171. package/lib/structure/core/index.js +1 -0
  172. package/lib/structure/core/index.js.map +1 -1
  173. package/lib/structure/core/structureTransfer.d.ts +29 -0
  174. package/lib/structure/core/structureTransfer.d.ts.map +1 -0
  175. package/lib/structure/core/structureTransfer.js +26 -0
  176. package/lib/structure/core/structureTransfer.js.map +1 -0
  177. package/lib/structure/ui/EditorImportButton.d.ts +20 -0
  178. package/lib/structure/ui/EditorImportButton.d.ts.map +1 -0
  179. package/lib/structure/ui/EditorImportButton.js +24 -0
  180. package/lib/structure/ui/EditorImportButton.js.map +1 -0
  181. package/lib/structure/ui/EditorImportProblem.d.ts +17 -0
  182. package/lib/structure/ui/EditorImportProblem.d.ts.map +1 -0
  183. package/lib/structure/ui/EditorImportProblem.js +32 -0
  184. package/lib/structure/ui/EditorImportProblem.js.map +1 -0
  185. package/lib/structure/ui/StructureDrop.d.ts +39 -0
  186. package/lib/structure/ui/StructureDrop.d.ts.map +1 -0
  187. package/lib/structure/ui/StructureDrop.js +19 -0
  188. package/lib/structure/ui/StructureDrop.js.map +1 -0
  189. package/lib/structure/ui/StructureDropOverlay.d.ts +16 -0
  190. package/lib/structure/ui/StructureDropOverlay.d.ts.map +1 -0
  191. package/lib/structure/ui/StructureDropOverlay.js +29 -0
  192. package/lib/structure/ui/StructureDropOverlay.js.map +1 -0
  193. package/lib/structure/ui/StructureEditor.d.ts +3 -80
  194. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  195. package/lib/structure/ui/StructureEditor.js +21 -33
  196. package/lib/structure/ui/StructureEditor.js.map +1 -1
  197. package/lib/structure/ui/importedStructure.d.ts +25 -0
  198. package/lib/structure/ui/importedStructure.d.ts.map +1 -0
  199. package/lib/structure/ui/importedStructure.js +74 -0
  200. package/lib/structure/ui/importedStructure.js.map +1 -0
  201. package/lib/structure/ui/index.d.ts +2 -0
  202. package/lib/structure/ui/index.d.ts.map +1 -1
  203. package/lib/structure/ui/index.js +1 -0
  204. package/lib/structure/ui/index.js.map +1 -1
  205. package/lib/structure/ui/structureEditorProps.d.ts +98 -0
  206. package/lib/structure/ui/structureEditorProps.d.ts.map +1 -0
  207. package/lib/structure/ui/structureEditorProps.js +2 -0
  208. package/lib/structure/ui/structureEditorProps.js.map +1 -0
  209. package/lib/structure/ui/useDebouncedChange.d.ts +16 -0
  210. package/lib/structure/ui/useDebouncedChange.d.ts.map +1 -0
  211. package/lib/structure/ui/useDebouncedChange.js +30 -0
  212. package/lib/structure/ui/useDebouncedChange.js.map +1 -0
  213. package/lib/structure/ui/useStructureDrop.d.ts +30 -0
  214. package/lib/structure/ui/useStructureDrop.d.ts.map +1 -0
  215. package/lib/structure/ui/useStructureDrop.js +49 -0
  216. package/lib/structure/ui/useStructureDrop.js.map +1 -0
  217. package/lib/structure/ui/useStructureImport.d.ts +45 -0
  218. package/lib/structure/ui/useStructureImport.d.ts.map +1 -0
  219. package/lib/structure/ui/useStructureImport.js +62 -0
  220. package/lib/structure/ui/useStructureImport.js.map +1 -0
  221. package/package.json +4 -1
  222. package/src/download/ui/FigureDownloadPanel.tsx +9 -2
  223. package/src/ecosystem/core/sites.ts +3 -3
  224. package/src/ecosystem/core/types.ts +1 -1
  225. package/src/ecosystem/ui/glyphs.tsx +1 -1
  226. package/src/i18n/core/messageCatalog.ts +2 -2
  227. package/src/i18n/vite/localeCatalog.ts +1 -1
  228. package/src/ir/core/index.ts +6 -2
  229. package/src/ir/core/irEditorApi.ts +13 -0
  230. package/src/ir/core/irReducer.ts +2 -2
  231. package/src/ir/core/irSidePanels.ts +8 -1
  232. package/src/ir/core/irState.ts +8 -5
  233. package/src/ir/ui/IrCanvas.tsx +27 -2
  234. package/src/ir/ui/IrChart.tsx +11 -0
  235. package/src/ir/ui/IrEditor.tsx +22 -4
  236. package/src/ir/ui/IrShell.tsx +21 -22
  237. package/src/ir/ui/IrSidePanel.tsx +36 -10
  238. package/src/ir/ui/RamanEditor.tsx +22 -4
  239. package/src/ir/ui/VibrationalEditor.tsx +24 -5
  240. package/src/ir/ui/index.ts +1 -0
  241. package/src/ir/ui/irActions.ts +3 -3
  242. package/src/ir/ui/irChartProps.ts +7 -0
  243. package/src/ir/ui/irHostPanels.ts +76 -0
  244. package/src/ir/ui/useIrSide.ts +60 -0
  245. package/src/ir/ui/useIrZoom.ts +10 -0
  246. package/src/locales/de.json +9 -1
  247. package/src/locales/en.json +9 -1
  248. package/src/locales/es.json +9 -1
  249. package/src/locales/fr.json +9 -1
  250. package/src/locales/it.json +9 -1
  251. package/src/molecule3d/core/animatedPng.ts +79 -0
  252. package/src/molecule3d/core/file.ts +23 -0
  253. package/src/molecule3d/core/index.ts +3 -1
  254. package/src/molecule3d/core/settings.ts +0 -8
  255. package/src/molecule3d/ui/Molecule3DExport.tsx +43 -5
  256. package/src/molecule3d/ui/Molecule3DToolbar.tsx +9 -2
  257. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +24 -38
  258. package/src/molecule3d/ui/drawScene.ts +2 -1
  259. package/src/molecule3d/ui/exportMoleculeImage.ts +28 -3
  260. package/src/molecule3d/ui/moleculeFrames.ts +88 -0
  261. package/src/molecule3d/ui/moleculeViewer3DProps.ts +14 -5
  262. package/src/molecule3d/ui/renderMolecule.ts +5 -3
  263. package/src/molecule3d/ui/sceneDrawer.ts +14 -2
  264. package/src/molecule3d/ui/useImageExport.ts +22 -5
  265. package/src/molecule3d/ui/useMoleculeScene.ts +2 -1
  266. package/src/molecule3d/ui/useMoleculeViewer.ts +46 -0
  267. package/src/molecule3d/ui/usePolarSurfaceArea.ts +6 -4
  268. package/src/molecule3d/ui/viewer.ts +34 -27
  269. package/src/molecule3d/ui/viewerTypes.ts +6 -0
  270. package/src/molstar/core/captureScene.ts +85 -2
  271. package/src/molstar/core/index.ts +13 -2
  272. package/src/molstar/core/trajectory.ts +117 -0
  273. package/src/panel/ui/PanelRail.tsx +3 -0
  274. package/src/raman.ts +2 -1
  275. package/src/router/core/basePath.ts +2 -2
  276. package/src/router/core/siteAddresses.ts +1 -1
  277. package/src/seo/core/siteFiles.ts +1 -1
  278. package/src/server/answerAddresses.ts +3 -3
  279. package/src/structure/core/index.ts +8 -0
  280. package/src/structure/core/structureTransfer.ts +37 -0
  281. package/src/structure/ui/EditorImportButton.tsx +59 -0
  282. package/src/structure/ui/EditorImportProblem.tsx +63 -0
  283. package/src/structure/ui/StructureDrop.tsx +66 -0
  284. package/src/structure/ui/StructureDropOverlay.tsx +50 -0
  285. package/src/structure/ui/StructureEditor.tsx +49 -134
  286. package/src/structure/ui/importedStructure.ts +83 -0
  287. package/src/structure/ui/index.ts +2 -0
  288. package/src/structure/ui/structureEditorProps.ts +103 -0
  289. package/src/structure/ui/useDebouncedChange.ts +41 -0
  290. package/src/structure/ui/useStructureDrop.ts +76 -0
  291. package/src/structure/ui/useStructureImport.ts +122 -0
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The frames of the file a viewer shows: how many there are, whether they
3
+ * play, and a picture of each.
4
+ *
5
+ * Kept apart from the viewer because every redraw of the scene has to go
6
+ * through it: a running animation must not step a state tree being rebuilt —
7
+ * it finds no model and stops itself — so the frames are stopped first and
8
+ * started again once the new file is drawn.
9
+ */
10
+
11
+ import type { PluginContext } from 'molstar/lib/mol-plugin/context.js';
12
+
13
+ import type { FramePlayback, MolstarPlugin } from '../../molstar/core/index.ts';
14
+ import {
15
+ captureFrames,
16
+ setTrajectoryPlayback,
17
+ stopTrajectory,
18
+ } from '../../molstar/core/index.ts';
19
+ import { DEFAULT_FRAMES_PER_SECOND } from '../core/animatedPng.ts';
20
+ import type { ImageSize } from '../core/exportImage.ts';
21
+
22
+ /** The frames of what one viewer shows. */
23
+ export class MoleculeFrames {
24
+ readonly #plugin: MolstarPlugin;
25
+ readonly #onCountChange: (frameCount: number) => void;
26
+ // Kept across redraws, so the next file plays as the last one did.
27
+ #playback: FramePlayback = {
28
+ playing: false,
29
+ framesPerSecond: DEFAULT_FRAMES_PER_SECOND,
30
+ };
31
+
32
+ /**
33
+ * Follow the frames of what `plugin` draws.
34
+ * @param plugin - The canvas.
35
+ * @param onCountChange - Called with how many frames each scene drawn holds.
36
+ */
37
+ constructor(
38
+ plugin: MolstarPlugin,
39
+ onCountChange: (frameCount: number) => void,
40
+ ) {
41
+ this.#plugin = plugin;
42
+ this.#onCountChange = onCountChange;
43
+ }
44
+
45
+ /**
46
+ * Redraw the scene with the frames stopped, then report how many the new
47
+ * scene holds and play them if playback is on.
48
+ * @param plugin - The molstar context.
49
+ * @param draw - Draws the scene, and resolves with how many frames it holds.
50
+ * @returns Nothing; resolves once the scene is drawn and playing.
51
+ */
52
+ async redraw(
53
+ plugin: PluginContext,
54
+ draw: () => Promise<number>,
55
+ ): Promise<void> {
56
+ await stopTrajectory(plugin);
57
+ this.#onCountChange(await draw());
58
+ const { playing, framesPerSecond } = this.#playback;
59
+ await setTrajectoryPlayback(plugin, playing, framesPerSecond);
60
+ }
61
+
62
+ /**
63
+ * Play the frames in a loop, or stop them, now and for every scene drawn
64
+ * afterwards. A scene of one frame stays still.
65
+ * @param playing - Whether to play.
66
+ * @param framesPerSecond - How fast.
67
+ * @returns Nothing; resolves once the animation is started or stopped.
68
+ */
69
+ play(playing: boolean, framesPerSecond: number): Promise<void> {
70
+ this.#playback = { playing, framesPerSecond };
71
+ return this.#plugin.run((plugin) =>
72
+ setTrajectoryPlayback(plugin, playing, framesPerSecond),
73
+ );
74
+ }
75
+
76
+ /**
77
+ * Render every frame off screen, as a still picture of the scene is
78
+ * rendered. Playback pauses while they are taken.
79
+ * @param size - Pixel size of each picture.
80
+ * @returns The pictures, in order; `undefined` once the canvas is disposed.
81
+ * @throws {Error} When the plugin has no screenshot helper.
82
+ */
83
+ capture(size: ImageSize): Promise<ImageData[] | undefined> {
84
+ return this.#plugin.run((plugin) =>
85
+ captureFrames(plugin, size, this.#playback),
86
+ );
87
+ }
88
+ }
@@ -2,13 +2,10 @@ import type { ReactNode } from 'react';
2
2
 
3
3
  import type { ViewerCapability } from '../../orbital/ui/capability.ts';
4
4
  import type { Molecule3DCamera } from '../core/camera.ts';
5
+ import type { Molecule3DFile } from '../core/file.ts';
5
6
  import type { Molecule3DHighlight } from '../core/highlight.ts';
6
7
  import type { Measurement } from '../core/measurement.ts';
7
- import type {
8
- Molecule3DFile,
9
- Molecule3DSettings,
10
- Molecule3DTools,
11
- } from '../core/settings.ts';
8
+ import type { Molecule3DSettings, Molecule3DTools } from '../core/settings.ts';
12
9
 
13
10
  /**
14
11
  * Props of `MoleculeViewer3D`.
@@ -69,6 +66,18 @@ export interface MoleculeViewer3DProps {
69
66
  * @default 1 / 3
70
67
  */
71
68
  spinSpeed?: number;
69
+ /**
70
+ * Whether the frames of a file holding several are played in a loop. A file
71
+ * of one model stays still whatever this says.
72
+ * @default false
73
+ */
74
+ playing?: boolean;
75
+ /**
76
+ * How many frames are shown per second while playing, and in an animated
77
+ * PNG; molstar plays between 5 and 60.
78
+ * @default 25
79
+ */
80
+ framesPerSecond?: number;
72
81
  /**
73
82
  * The measurements drawn over the model, when the site owns them. They name
74
83
  * atoms by position, so they carry over to another conformer of the same
@@ -15,7 +15,8 @@ import { StructureFromModel } from 'molstar/lib/mol-plugin-state/transforms/mode
15
15
  import { StructureRepresentation3D } from 'molstar/lib/mol-plugin-state/transforms/representation.js';
16
16
  import type { StateTransformer } from 'molstar/lib/mol-state/index.js';
17
17
 
18
- import type { Molecule3DFile, RepresentationId } from '../core/settings.ts';
18
+ import type { Molecule3DFile } from '../core/file.ts';
19
+ import type { RepresentationId } from '../core/settings.ts';
19
20
 
20
21
  import type { MoleculeStyle } from './viewerTypes.ts';
21
22
 
@@ -48,7 +49,7 @@ const STICK_SCALE = 1.2;
48
49
  * @param plugin - The molstar context.
49
50
  * @param molfile - Molfile text plus the format id.
50
51
  * @param style - See {@link MoleculeStyle}.
51
- * @returns Nothing; resolves once the model is on screen.
52
+ * @returns How many frames the file holds, the first of which is on screen.
52
53
  * @throws {Error} When the file parses to zero atoms. molstar's `mol` parser returns
53
54
  * exactly that on a V3000 file, with no error, so without this check the viewer
54
55
  * would show an empty scene and report nothing.
@@ -57,7 +58,7 @@ export async function renderMolecule(
57
58
  plugin: PluginContext,
58
59
  molfile: Molecule3DFile,
59
60
  style: MoleculeStyle = {},
60
- ): Promise<void> {
61
+ ): Promise<number> {
61
62
  const { representation = 'ball-and-stick', sizeFactor = 1 } = style;
62
63
  await clearMolecule(plugin);
63
64
  const data = await plugin.builders.data.rawData(
@@ -99,6 +100,7 @@ export async function renderMolecule(
99
100
  ),
100
101
  )
101
102
  .commit();
103
+ return trajectory.data?.frameCount ?? 1;
102
104
  }
103
105
 
104
106
  /**
@@ -11,9 +11,10 @@
11
11
  */
12
12
 
13
13
  import type { Molecule3DCamera } from '../core/camera.ts';
14
+ import type { Molecule3DFile } from '../core/file.ts';
14
15
  import type { Measurement } from '../core/measurement.ts';
15
16
  import { sameMeasurements } from '../core/measurement.ts';
16
- import type { Molecule3DFile, Molecule3DSettings } from '../core/settings.ts';
17
+ import type { Molecule3DSettings } from '../core/settings.ts';
17
18
  import { sameMolecule3DSettings } from '../core/settings.ts';
18
19
 
19
20
  import type { SceneViewer } from './drawScene.ts';
@@ -103,7 +104,7 @@ export class SceneDrawer {
103
104
  const { molfile, settings, measurements, frameNewMolecule } = request;
104
105
  const isNew =
105
106
  molfile !== null &&
106
- (this.#framed === null || !sameFile(this.#framed, molfile));
107
+ (this.#framed === null || !sameMolecule(this.#framed, molfile));
107
108
  this.#framed = molfile;
108
109
  // A link's camera is spent on the first molecule framed; afterwards the
109
110
  // camera is the reader's and a new molecule is framed as usual.
@@ -119,6 +120,17 @@ export class SceneDrawer {
119
120
  }
120
121
  }
121
122
 
123
+ // Two files a site names alike draw one molecule, whatever their text.
124
+ function sameMolecule(first: Molecule3DFile, second: Molecule3DFile): boolean {
125
+ if (
126
+ first.moleculeId !== undefined &&
127
+ first.moleculeId === second.moleculeId
128
+ ) {
129
+ return true;
130
+ }
131
+ return sameFile(first, second);
132
+ }
133
+
122
134
  function sameFile(
123
135
  first: Molecule3DFile | null,
124
136
  second: Molecule3DFile | null,
@@ -5,6 +5,7 @@ import type { FigureNotice } from '../../download/core/copyFigure.ts';
5
5
  import { copyFigure } from '../../download/core/copyFigure.ts';
6
6
  import type { FigureFormat } from '../../download/core/downloadFigure.ts';
7
7
  import type { FigurePixels } from '../../download/core/figureScale.ts';
8
+ import { DEFAULT_FRAMES_PER_SECOND } from '../core/animatedPng.ts';
8
9
 
9
10
  import type { SceneCapture } from './exportMoleculeImage.ts';
10
11
  import {
@@ -16,8 +17,15 @@ import {
16
17
  export interface ImageExport {
17
18
  /** Size of the canvas on screen, or `null` before it is laid out. */
18
19
  canvasSize: () => FigurePixels | null;
19
- /** Render the scene at `scale` and download it as `format`. */
20
- exportImage: (format: FigureFormat, scale: number) => Promise<void>;
20
+ /**
21
+ * Render the scene at `scale` and download it as `format` — every frame of
22
+ * it, as an animated PNG, when `animated` is set and the format is PNG.
23
+ */
24
+ exportImage: (
25
+ format: FigureFormat,
26
+ scale: number,
27
+ animated?: boolean,
28
+ ) => Promise<void>;
21
29
  /**
22
30
  * Render the scene at `scale` and put it on the clipboard as a PNG. Call it
23
31
  * from the click itself: Safari only allows a write the click started.
@@ -30,12 +38,15 @@ export interface ImageExport {
30
38
  * @param container - The element molstar draws into.
31
39
  * @param viewerRef - The viewer, once created.
32
40
  * @param fileName - File name without its extension.
41
+ * @param framesPerSecond - How fast an animated PNG plays its frames.
42
+ * @default 25
33
43
  * @returns See {@link ImageExport}.
34
44
  */
35
45
  export function useImageExport(
36
46
  container: HTMLDivElement | null,
37
47
  viewerRef: RefObject<SceneCapture | null>,
38
48
  fileName: string,
49
+ framesPerSecond = DEFAULT_FRAMES_PER_SECOND,
39
50
  ): ImageExport {
40
51
  const canvasSize = useCallback((): FigurePixels | null => {
41
52
  if (container === null) return null;
@@ -45,15 +56,21 @@ export function useImageExport(
45
56
  }, [container]);
46
57
 
47
58
  const exportImage = useCallback(
48
- async (format: FigureFormat, scale: number) => {
59
+ async (format: FigureFormat, scale: number, animated = false) => {
49
60
  const viewer = viewerRef.current;
50
61
  const size = canvasSize();
51
62
  if (viewer === null || size === null) {
52
63
  throw new Error('There is no picture to export yet.');
53
64
  }
54
- await exportMoleculeImage(viewer, { format, scale, size, fileName });
65
+ await exportMoleculeImage(viewer, {
66
+ format,
67
+ scale,
68
+ size,
69
+ fileName,
70
+ framesPerSecond: animated ? framesPerSecond : undefined,
71
+ });
55
72
  },
56
- [canvasSize, viewerRef, fileName],
73
+ [canvasSize, viewerRef, fileName, framesPerSecond],
57
74
  );
58
75
 
59
76
  const copyImage = useCallback(
@@ -12,8 +12,9 @@
12
12
  import type { RefObject } from 'react';
13
13
  import { useCallback, useEffect, useRef } from 'react';
14
14
 
15
+ import type { Molecule3DFile } from '../core/file.ts';
15
16
  import type { Measurement } from '../core/measurement.ts';
16
- import type { Molecule3DFile, Molecule3DSettings } from '../core/settings.ts';
17
+ import type { Molecule3DSettings } from '../core/settings.ts';
17
18
 
18
19
  import type { MoleculeViewer3DProps } from './moleculeViewer3DProps.ts';
19
20
  import { SceneDrawer } from './sceneDrawer.ts';
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The molstar viewer of one canvas: created once its element exists, kept to
3
+ * the element's size, and disposed with it.
4
+ */
5
+
6
+ import type { RefObject } from 'react';
7
+ import { useEffect, useRef } from 'react';
8
+
9
+ import { createMolecule3DViewer } from './createViewer.ts';
10
+ import type { Molecule3DViewer } from './viewer.ts';
11
+ import type { Molecule3DViewerOptions } from './viewerTypes.ts';
12
+
13
+ /**
14
+ * Mount a viewer in `container` for as long as it exists.
15
+ * @param container - The element molstar draws in, or `null` before it exists.
16
+ * @param options - Read when the viewer is created; a later change is ignored,
17
+ * so a callback reads what it needs from a ref of its own.
18
+ * @returns The viewer, holding `null` while there is none.
19
+ */
20
+ export function useMoleculeViewer(
21
+ container: HTMLElement | null,
22
+ options: Molecule3DViewerOptions,
23
+ ): RefObject<Molecule3DViewer | null> {
24
+ const viewerRef = useRef<Molecule3DViewer | null>(null);
25
+ const latestOptions = useRef(options);
26
+ useEffect(() => {
27
+ latestOptions.current = options;
28
+ });
29
+
30
+ useEffect(() => {
31
+ if (container === null) return;
32
+ const viewer = createMolecule3DViewer(container, latestOptions.current);
33
+ viewerRef.current = viewer;
34
+ const observer = new ResizeObserver(() => {
35
+ viewer.handleResize();
36
+ });
37
+ observer.observe(container);
38
+ return () => {
39
+ observer.disconnect();
40
+ viewerRef.current = null;
41
+ viewer.dispose();
42
+ };
43
+ }, [container]);
44
+
45
+ return viewerRef;
46
+ }
@@ -1,14 +1,14 @@
1
1
  import { useEffect, useState } from 'react';
2
2
 
3
- import type { Molecule3DFile } from '../core/settings.ts';
3
+ import type { Molecule3DFile } from '../core/file.ts';
4
4
 
5
5
  /**
6
6
  * Ertl's topological polar surface area of the molecule, from openchemlib,
7
7
  * loaded only once a caller asks for it.
8
8
  * @param molfile - The molecule, or `null`.
9
9
  * @param enabled - Whether to compute it at all.
10
- * @returns The area in Ų, or `null` while unknown, disabled, or when
11
- * openchemlib is not installed or cannot read the molfile.
10
+ * @returns The area in Ų, or `null` while unknown, disabled, for an XYZ
11
+ * file, or when openchemlib is not installed or cannot read the molfile.
12
12
  */
13
13
  export function usePolarSurfaceArea(
14
14
  molfile: Molecule3DFile | null,
@@ -18,7 +18,9 @@ export function usePolarSurfaceArea(
18
18
  data: string;
19
19
  area: number | null;
20
20
  } | null>(null);
21
- const data = enabled ? (molfile?.data ?? null) : null;
21
+ // An XYZ file has no bonds for openchemlib to find the polar atoms by.
22
+ const data =
23
+ enabled && molfile?.format !== 'xyz' ? (molfile?.data ?? null) : null;
22
24
 
23
25
  useEffect(() => {
24
26
  if (data === null) return;
@@ -8,7 +8,6 @@
8
8
  * is the component's.
9
9
  */
10
10
 
11
- import type { PluginContext } from 'molstar/lib/mol-plugin/context.js';
12
11
  import { Color } from 'molstar/lib/mol-util/color/color.js';
13
12
 
14
13
  import {
@@ -24,11 +23,12 @@ import {
24
23
  } from '../../molstar/core/index.ts';
25
24
  import type { Molecule3DCamera } from '../core/camera.ts';
26
25
  import type { ImageSize } from '../core/exportImage.ts';
26
+ import type { Molecule3DFile } from '../core/file.ts';
27
27
  import type { Molecule3DHighlight } from '../core/highlight.ts';
28
28
  import type { Measurement, MeasurementKind } from '../core/measurement.ts';
29
- import type { Molecule3DFile } from '../core/settings.ts';
30
29
 
31
30
  import { applyCamera, resetCamera, watchCamera } from './camera.ts';
31
+ import { MoleculeFrames } from './moleculeFrames.ts';
32
32
  import {
33
33
  MODEL_REPRESENTATION_REF,
34
34
  clearMolecule,
@@ -54,16 +54,24 @@ export class Molecule3DViewer {
54
54
  /** Resolves once the canvas exists; every method awaits it internally. */
55
55
  readonly ready: Promise<void>;
56
56
 
57
+ /** The frames of the file on screen: playing them, and their pictures. */
58
+ readonly frames: MoleculeFrames;
59
+
57
60
  /**
58
61
  * Mount the canvas and start initialising the plugin.
59
62
  * @param container - A positioned element.
60
63
  * @param options - See {@link Molecule3DViewerOptions}.
61
64
  */
62
65
  constructor(container: HTMLElement, options: Molecule3DViewerOptions = {}) {
63
- const { background, onMeasure = ignore } = options;
66
+ const {
67
+ background,
68
+ onMeasure = ignore,
69
+ onFrameCountChange = ignore,
70
+ } = options;
64
71
  this.#plugin = new MolstarPlugin(container, { background });
65
72
  this.ready = this.#plugin.ready;
66
- // Registered before any `#run`, so the picker exists by the time one runs.
73
+ this.frames = new MoleculeFrames(this.#plugin, onFrameCountChange);
74
+ // Registered before any other `run`, so the picker exists by the time one runs.
67
75
  void this.#plugin
68
76
  .run((plugin) => {
69
77
  this.#picker = new MeasurementPicker(
@@ -84,10 +92,12 @@ export class Molecule3DViewer {
84
92
  * @throws {Error} When the file parses to zero atoms.
85
93
  */
86
94
  showMolecule(molfile: Molecule3DFile, style?: MoleculeStyle): Promise<void> {
87
- return this.#run(async (plugin) => {
88
- await clearMeasurements(plugin);
89
- await renderMolecule(plugin, molfile, style);
90
- });
95
+ return this.#plugin.run((plugin) =>
96
+ this.frames.redraw(plugin, async () => {
97
+ await clearMeasurements(plugin);
98
+ return renderMolecule(plugin, molfile, style);
99
+ }),
100
+ );
91
101
  }
92
102
 
93
103
  /**
@@ -95,10 +105,13 @@ export class Molecule3DViewer {
95
105
  * @returns Nothing; resolves once the scene is empty.
96
106
  */
97
107
  hideMolecule(): Promise<void> {
98
- return this.#run(async (plugin) => {
99
- await clearMeasurements(plugin);
100
- await clearMolecule(plugin);
101
- });
108
+ return this.#plugin.run((plugin) =>
109
+ this.frames.redraw(plugin, async () => {
110
+ await clearMeasurements(plugin);
111
+ await clearMolecule(plugin);
112
+ return 0;
113
+ }),
114
+ );
102
115
  }
103
116
 
104
117
  /**
@@ -107,7 +120,7 @@ export class Molecule3DViewer {
107
120
  * @returns Nothing; resolves once the surface is on screen.
108
121
  */
109
122
  showSurface(style?: SurfaceStyle): Promise<void> {
110
- return this.#run((plugin) => renderSurface(plugin, style));
123
+ return this.#plugin.run((plugin) => renderSurface(plugin, style));
111
124
  }
112
125
 
113
126
  /**
@@ -115,7 +128,7 @@ export class Molecule3DViewer {
115
128
  * @returns Nothing; resolves once the surface is gone.
116
129
  */
117
130
  hideSurface(): Promise<void> {
118
- return this.#run((plugin) => clearSurface(plugin));
131
+ return this.#plugin.run((plugin) => clearSurface(plugin));
119
132
  }
120
133
 
121
134
  /**
@@ -124,7 +137,7 @@ export class Molecule3DViewer {
124
137
  * @returns Nothing; resolves once the labels are on screen.
125
138
  */
126
139
  showMeasurements(measurements: readonly Measurement[]): Promise<void> {
127
- return this.#run((plugin) =>
140
+ return this.#plugin.run((plugin) =>
128
141
  renderMeasurements(plugin, measurements, moleculeStructure),
129
142
  );
130
143
  }
@@ -140,7 +153,7 @@ export class Molecule3DViewer {
140
153
  highlights: readonly Molecule3DHighlight[],
141
154
  color: string,
142
155
  ): Promise<void> {
143
- return this.#run(() => {
156
+ return this.#plugin.run(() => {
144
157
  this.#painter?.set(highlights, Color.fromHexStyle(color));
145
158
  });
146
159
  }
@@ -151,7 +164,7 @@ export class Molecule3DViewer {
151
164
  * @returns Nothing; resolves once the plugin has switched mode.
152
165
  */
153
166
  setMeasureKind(kind: MeasurementKind | null): Promise<void> {
154
- return this.#run(() => {
167
+ return this.#plugin.run(() => {
155
168
  this.#picker?.setKind(kind);
156
169
  });
157
170
  }
@@ -164,7 +177,7 @@ export class Molecule3DViewer {
164
177
  * @throws {Error} When the plugin has no screenshot helper.
165
178
  */
166
179
  captureImage(size: ImageSize): Promise<string | undefined> {
167
- return this.#run((plugin) => captureScene(plugin, size));
180
+ return this.#plugin.run((plugin) => captureScene(plugin, size));
168
181
  }
169
182
 
170
183
  /**
@@ -177,7 +190,7 @@ export class Molecule3DViewer {
177
190
  durationMilliseconds = DEFAULT_CAMERA_DURATION,
178
191
  fromFront = false,
179
192
  ): Promise<void> {
180
- return this.#run((plugin) => {
193
+ return this.#plugin.run((plugin) => {
181
194
  resetCamera(plugin, durationMilliseconds, fromFront);
182
195
  });
183
196
  }
@@ -189,7 +202,7 @@ export class Molecule3DViewer {
189
202
  * @returns Nothing; resolves once the move has been ordered.
190
203
  */
191
204
  setCamera(camera: Molecule3DCamera, durationMilliseconds = 0): Promise<void> {
192
- return this.#run((plugin) => {
205
+ return this.#plugin.run((plugin) => {
193
206
  applyCamera(plugin, camera, durationMilliseconds);
194
207
  });
195
208
  }
@@ -211,7 +224,7 @@ export class Molecule3DViewer {
211
224
  * @returns Nothing; resolves once the trackball has been reconfigured.
212
225
  */
213
226
  setSpin(spinning: boolean, speed = DEFAULT_SPIN_SPEED): Promise<void> {
214
- return this.#run((plugin) => {
227
+ return this.#plugin.run((plugin) => {
215
228
  setSpin(plugin, spinning, speed);
216
229
  });
217
230
  }
@@ -227,12 +240,6 @@ export class Molecule3DViewer {
227
240
  this.#painter?.dispose();
228
241
  this.#plugin.dispose();
229
242
  }
230
-
231
- #run<Result>(
232
- action: (plugin: PluginContext) => Result | Promise<Result>,
233
- ): Promise<Result | undefined> {
234
- return this.#plugin.run(action);
235
- }
236
243
  }
237
244
 
238
245
  function ignore(): void {
@@ -19,6 +19,12 @@ export interface Molecule3DViewerOptions {
19
19
  * @default undefined
20
20
  */
21
21
  onMeasure?: (measurement: Measurement) => void;
22
+ /**
23
+ * Called with how many frames the file on screen holds, each time a file is
24
+ * drawn, and with 0 once the scene is emptied.
25
+ * @default undefined
26
+ */
27
+ onFrameCountChange?: (frameCount: number) => void;
22
28
  }
23
29
 
24
30
  /** How to draw the model. */
@@ -1,11 +1,22 @@
1
1
  /**
2
- * A picture of the scene, rendered off screen at a size of its own.
2
+ * A picture of the scene, rendered off screen at a size of its own — one
3
+ * frame, or every frame of the trajectory on screen.
3
4
  */
4
5
 
5
6
  import type { PluginContext } from 'molstar/lib/mol-plugin/context.js';
7
+ import type { ViewportScreenshotHelper } from 'molstar/lib/mol-plugin/util/viewport-screenshot.js';
8
+ import { Task } from 'molstar/lib/mol-task/index.js';
6
9
 
7
10
  import type { ImageSize } from '../../molecule3d/core/exportImage.ts';
8
11
 
12
+ import {
13
+ setTrajectoryPlayback,
14
+ showTrajectoryFrame,
15
+ shownTrajectoryFrame,
16
+ stopTrajectory,
17
+ trajectoryFrameCount,
18
+ } from './trajectory.ts';
19
+
9
20
  /**
10
21
  * Render the scene without its selection highlights or its axes.
11
22
  * @param plugin - The molstar context.
@@ -17,6 +28,78 @@ export async function captureScene(
17
28
  plugin: PluginContext,
18
29
  size: ImageSize,
19
30
  ): Promise<string> {
31
+ return pictureHelper(plugin, size).getImageDataUri();
32
+ }
33
+
34
+ /** How a series of frames is played, so it can be resumed once captured. */
35
+ export interface FramePlayback {
36
+ playing: boolean;
37
+ framesPerSecond: number;
38
+ }
39
+
40
+ /**
41
+ * Render every frame of the trajectory on screen, in order, as the scene shows
42
+ * it: the model, its surface and the measurements over it, each at that frame.
43
+ * The animation is stopped while the frames are taken and resumed afterwards,
44
+ * and the scene is left on the frame it showed.
45
+ * @param plugin - The molstar context.
46
+ * @param size - Pixel size of each picture.
47
+ * @param playback - What to resume once the frames are taken.
48
+ * @returns One picture per frame; a scene of one model gives one.
49
+ * @throws {Error} When the plugin has no screenshot helper.
50
+ */
51
+ export async function captureFrames(
52
+ plugin: PluginContext,
53
+ size: ImageSize,
54
+ playback: FramePlayback,
55
+ ): Promise<ImageData[]> {
56
+ const pass = pictureHelper(plugin, size).imagePass;
57
+ const loop = plugin.animationLoop;
58
+ const shown = shownTrajectoryFrame(plugin);
59
+ const count = Math.max(1, trajectoryFrameCount(plugin));
60
+ await stopTrajectory(plugin);
61
+ loop.stop({ noDraw: true });
62
+ try {
63
+ await pass.updateBackground();
64
+ const frames: ImageData[] = [];
65
+ for (let index = 0; index < count; index++) {
66
+ // eslint-disable-next-line no-await-in-loop -- each frame is a commit of the state tree, and commits must not interleave
67
+ if (count > 1) await showTrajectoryFrame(plugin, index);
68
+ // The scene is otherwise committed on the next animation frame, after
69
+ // the picture has been taken of the previous one.
70
+ plugin.canvas3d?.commit(true);
71
+ // eslint-disable-next-line no-await-in-loop -- one render at a time on the one image pass
72
+ const image = await plugin.runTask(
73
+ Task.create('Render a frame', (runtime) =>
74
+ pass.getImageData(runtime, size.width, size.height),
75
+ ),
76
+ );
77
+ frames.push(image);
78
+ }
79
+ return frames;
80
+ } finally {
81
+ if (count > 1) await showTrajectoryFrame(plugin, shown);
82
+ loop.start({ immediate: true });
83
+ await setTrajectoryPlayback(
84
+ plugin,
85
+ playback.playing,
86
+ playback.framesPerSecond,
87
+ );
88
+ }
89
+ }
90
+
91
+ /**
92
+ * The screenshot helper, set to draw a picture of `size` with no axes and an
93
+ * opaque background.
94
+ * @param plugin - The molstar context.
95
+ * @param size - Pixel size of the picture.
96
+ * @returns The helper.
97
+ * @throws {Error} When the plugin has no screenshot helper.
98
+ */
99
+ function pictureHelper(
100
+ plugin: PluginContext,
101
+ size: ImageSize,
102
+ ): ViewportScreenshotHelper {
20
103
  const helper = plugin.helpers.viewportScreenshot;
21
104
  if (helper === undefined) {
22
105
  throw new Error('This viewer cannot take a picture of its scene.');
@@ -27,5 +110,5 @@ export async function captureScene(
27
110
  axes: { name: 'off', params: {} },
28
111
  transparent: false,
29
112
  });
30
- return helper.getImageDataUri();
113
+ return helper;
31
114
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * The molstar layer under the 3D views: the plugin lifecycle, the camera, the
3
- * hover labels, the measurements, the highlights and the picture of a scene.
3
+ * hover labels, the measurements, the highlights, the frames of a trajectory
4
+ * and the picture of a scene.
4
5
  *
5
6
  * Three of our sites mount a headless molstar canvas — the molecule viewer
6
7
  * here, lcao's orbitals and symmetry's crystals — and each of them had its own
@@ -49,7 +50,8 @@ export {
49
50
  } from './hover.ts';
50
51
  export type { MolstarPluginOptions } from './plugin.ts';
51
52
  export { MolstarPlugin } from './plugin.ts';
52
- export { captureScene } from './captureScene.ts';
53
+ export type { FramePlayback } from './captureScene.ts';
54
+ export { captureFrames, captureScene } from './captureScene.ts';
53
55
  export type { MoleculeStructureSource } from './measurements.ts';
54
56
  export {
55
57
  MeasurementPicker,
@@ -57,3 +59,12 @@ export {
57
59
  clearMeasurements,
58
60
  renderMeasurements,
59
61
  } from './measurements.ts';
62
+ export {
63
+ PLAYBACK_FPS_RANGE,
64
+ clampFramesPerSecond,
65
+ setTrajectoryPlayback,
66
+ showTrajectoryFrame,
67
+ shownTrajectoryFrame,
68
+ stopTrajectory,
69
+ trajectoryFrameCount,
70
+ } from './trajectory.ts';