react-cheminfo 0.65.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 (466) hide show
  1. package/README.md +33 -17
  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/analysisSpectra.d.ts +23 -6
  17. package/lib/ir/core/analysisSpectra.d.ts.map +1 -1
  18. package/lib/ir/core/analysisSpectra.js.map +1 -1
  19. package/lib/ir/core/bandLabelPlan.d.ts.map +1 -1
  20. package/lib/ir/core/bandLabelPlan.js +11 -6
  21. package/lib/ir/core/bandLabelPlan.js.map +1 -1
  22. package/lib/ir/core/index.d.ts +15 -11
  23. package/lib/ir/core/index.d.ts.map +1 -1
  24. package/lib/ir/core/index.js +9 -6
  25. package/lib/ir/core/index.js.map +1 -1
  26. package/lib/ir/core/irBand.d.ts +45 -14
  27. package/lib/ir/core/irBand.d.ts.map +1 -1
  28. package/lib/ir/core/irBand.js +28 -6
  29. package/lib/ir/core/irBand.js.map +1 -1
  30. package/lib/ir/core/irCommands.d.ts +11 -0
  31. package/lib/ir/core/irCommands.d.ts.map +1 -1
  32. package/lib/ir/core/irCommands.js +11 -1
  33. package/lib/ir/core/irCommands.js.map +1 -1
  34. package/lib/ir/core/irDomain.d.ts +4 -3
  35. package/lib/ir/core/irDomain.d.ts.map +1 -1
  36. package/lib/ir/core/irDomain.js +10 -7
  37. package/lib/ir/core/irDomain.js.map +1 -1
  38. package/lib/ir/core/irEditorApi.d.ts +12 -0
  39. package/lib/ir/core/irEditorApi.d.ts.map +1 -1
  40. package/lib/ir/core/irFormat.d.ts +14 -2
  41. package/lib/ir/core/irFormat.d.ts.map +1 -1
  42. package/lib/ir/core/irFormat.js +33 -4
  43. package/lib/ir/core/irFormat.js.map +1 -1
  44. package/lib/ir/core/irMode.d.ts +35 -8
  45. package/lib/ir/core/irMode.d.ts.map +1 -1
  46. package/lib/ir/core/irMode.js +38 -10
  47. package/lib/ir/core/irMode.js.map +1 -1
  48. package/lib/ir/core/irReducer.d.ts +2 -30
  49. package/lib/ir/core/irReducer.d.ts.map +1 -1
  50. package/lib/ir/core/irReducer.js +1 -24
  51. package/lib/ir/core/irReducer.js.map +1 -1
  52. package/lib/ir/core/irSidePanels.d.ts +7 -1
  53. package/lib/ir/core/irSidePanels.d.ts.map +1 -1
  54. package/lib/ir/core/irSpectrum.d.ts +52 -26
  55. package/lib/ir/core/irSpectrum.d.ts.map +1 -1
  56. package/lib/ir/core/irSpectrum.js +21 -15
  57. package/lib/ir/core/irSpectrum.js.map +1 -1
  58. package/lib/ir/core/irState.d.ts +46 -5
  59. package/lib/ir/core/irState.d.ts.map +1 -1
  60. package/lib/ir/core/irState.js +38 -1
  61. package/lib/ir/core/irState.js.map +1 -1
  62. package/lib/ir/core/irTechnique.d.ts +94 -0
  63. package/lib/ir/core/irTechnique.d.ts.map +1 -0
  64. package/lib/ir/core/irTechnique.js +87 -0
  65. package/lib/ir/core/irTechnique.js.map +1 -0
  66. package/lib/ir/core/irTechniqueCommands.d.ts +26 -0
  67. package/lib/ir/core/irTechniqueCommands.d.ts.map +1 -0
  68. package/lib/ir/core/irTechniqueCommands.js +68 -0
  69. package/lib/ir/core/irTechniqueCommands.js.map +1 -0
  70. package/lib/ir/core/pickBands.d.ts +6 -5
  71. package/lib/ir/core/pickBands.d.ts.map +1 -1
  72. package/lib/ir/core/pickBands.js +49 -26
  73. package/lib/ir/core/pickBands.js.map +1 -1
  74. package/lib/ir/core/ramanBands.d.ts +30 -0
  75. package/lib/ir/core/ramanBands.d.ts.map +1 -0
  76. package/lib/ir/core/ramanBands.js +83 -0
  77. package/lib/ir/core/ramanBands.js.map +1 -0
  78. package/lib/ir/core/spectrumJcamp.d.ts +10 -5
  79. package/lib/ir/core/spectrumJcamp.d.ts.map +1 -1
  80. package/lib/ir/core/spectrumJcamp.js +63 -15
  81. package/lib/ir/core/spectrumJcamp.js.map +1 -1
  82. package/lib/ir/core/toIrSpectrum.d.ts.map +1 -1
  83. package/lib/ir/core/toIrSpectrum.js +17 -2
  84. package/lib/ir/core/toIrSpectrum.js.map +1 -1
  85. package/lib/ir/ui/BandRow.d.ts +30 -0
  86. package/lib/ir/ui/BandRow.d.ts.map +1 -0
  87. package/lib/ir/ui/BandRow.js +42 -0
  88. package/lib/ir/ui/BandRow.js.map +1 -0
  89. package/lib/ir/ui/BandsPanel.d.ts +4 -0
  90. package/lib/ir/ui/BandsPanel.d.ts.map +1 -1
  91. package/lib/ir/ui/BandsPanel.js +14 -59
  92. package/lib/ir/ui/BandsPanel.js.map +1 -1
  93. package/lib/ir/ui/IrAboutDialog.d.ts +7 -0
  94. package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
  95. package/lib/ir/ui/IrAboutDialog.js +16 -3
  96. package/lib/ir/ui/IrAboutDialog.js.map +1 -1
  97. package/lib/ir/ui/IrCanvas.d.ts +7 -0
  98. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  99. package/lib/ir/ui/IrCanvas.js +9 -4
  100. package/lib/ir/ui/IrCanvas.js.map +1 -1
  101. package/lib/ir/ui/IrChart.d.ts +2 -1
  102. package/lib/ir/ui/IrChart.d.ts.map +1 -1
  103. package/lib/ir/ui/IrChart.js +22 -21
  104. package/lib/ir/ui/IrChart.js.map +1 -1
  105. package/lib/ir/ui/IrCommandProvider.d.ts +7 -0
  106. package/lib/ir/ui/IrCommandProvider.d.ts.map +1 -1
  107. package/lib/ir/ui/IrCommandProvider.js +2 -2
  108. package/lib/ir/ui/IrCommandProvider.js.map +1 -1
  109. package/lib/ir/ui/IrCommandTooltip.d.ts +7 -0
  110. package/lib/ir/ui/IrCommandTooltip.d.ts.map +1 -1
  111. package/lib/ir/ui/IrCommandTooltip.js +3 -2
  112. package/lib/ir/ui/IrCommandTooltip.js.map +1 -1
  113. package/lib/ir/ui/IrDocumentationDialog.d.ts +8 -1
  114. package/lib/ir/ui/IrDocumentationDialog.d.ts.map +1 -1
  115. package/lib/ir/ui/IrDocumentationDialog.js +30 -7
  116. package/lib/ir/ui/IrDocumentationDialog.js.map +1 -1
  117. package/lib/ir/ui/IrEditor.d.ts +25 -5
  118. package/lib/ir/ui/IrEditor.d.ts.map +1 -1
  119. package/lib/ir/ui/IrEditor.js +3 -20
  120. package/lib/ir/ui/IrEditor.js.map +1 -1
  121. package/lib/ir/ui/IrHeader.d.ts.map +1 -1
  122. package/lib/ir/ui/IrHeader.js +5 -1
  123. package/lib/ir/ui/IrHeader.js.map +1 -1
  124. package/lib/ir/ui/IrLogo.d.ts +11 -3
  125. package/lib/ir/ui/IrLogo.d.ts.map +1 -1
  126. package/lib/ir/ui/IrLogo.js +14 -5
  127. package/lib/ir/ui/IrLogo.js.map +1 -1
  128. package/lib/ir/ui/IrShell.d.ts +9 -1
  129. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  130. package/lib/ir/ui/IrShell.js +26 -74
  131. package/lib/ir/ui/IrShell.js.map +1 -1
  132. package/lib/ir/ui/IrSidePanel.d.ts +11 -0
  133. package/lib/ir/ui/IrSidePanel.d.ts.map +1 -1
  134. package/lib/ir/ui/IrSidePanel.js +12 -3
  135. package/lib/ir/ui/IrSidePanel.js.map +1 -1
  136. package/lib/ir/ui/IrStatusBar.d.ts +3 -2
  137. package/lib/ir/ui/IrStatusBar.d.ts.map +1 -1
  138. package/lib/ir/ui/IrStatusBar.js +14 -9
  139. package/lib/ir/ui/IrStatusBar.js.map +1 -1
  140. package/lib/ir/ui/IrToolbar.d.ts +3 -0
  141. package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
  142. package/lib/ir/ui/IrToolbar.js +10 -3
  143. package/lib/ir/ui/IrToolbar.js.map +1 -1
  144. package/lib/ir/ui/IrTraces.d.ts +4 -1
  145. package/lib/ir/ui/IrTraces.d.ts.map +1 -1
  146. package/lib/ir/ui/IrTraces.js +2 -0
  147. package/lib/ir/ui/IrTraces.js.map +1 -1
  148. package/lib/ir/ui/RamanEditor.d.ts +64 -0
  149. package/lib/ir/ui/RamanEditor.d.ts.map +1 -0
  150. package/lib/ir/ui/RamanEditor.js +17 -0
  151. package/lib/ir/ui/RamanEditor.js.map +1 -0
  152. package/lib/ir/ui/SpectraPanel.d.ts.map +1 -1
  153. package/lib/ir/ui/SpectraPanel.js +2 -2
  154. package/lib/ir/ui/SpectraPanel.js.map +1 -1
  155. package/lib/ir/ui/SpectrumRow.d.ts +7 -0
  156. package/lib/ir/ui/SpectrumRow.d.ts.map +1 -1
  157. package/lib/ir/ui/SpectrumRow.js +4 -5
  158. package/lib/ir/ui/SpectrumRow.js.map +1 -1
  159. package/lib/ir/ui/VibrationalEditor.d.ts +62 -0
  160. package/lib/ir/ui/VibrationalEditor.d.ts.map +1 -0
  161. package/lib/ir/ui/VibrationalEditor.js +33 -0
  162. package/lib/ir/ui/VibrationalEditor.js.map +1 -0
  163. package/lib/ir/ui/bandsTsv.d.ts +19 -0
  164. package/lib/ir/ui/bandsTsv.d.ts.map +1 -0
  165. package/lib/ir/ui/bandsTsv.js +38 -0
  166. package/lib/ir/ui/bandsTsv.js.map +1 -0
  167. package/lib/ir/ui/index.d.ts +1 -0
  168. package/lib/ir/ui/index.d.ts.map +1 -1
  169. package/lib/ir/ui/index.js.map +1 -1
  170. package/lib/ir/ui/irActions.d.ts +3 -3
  171. package/lib/ir/ui/irActions.d.ts.map +1 -1
  172. package/lib/ir/ui/irChartProps.d.ts +21 -4
  173. package/lib/ir/ui/irChartProps.d.ts.map +1 -1
  174. package/lib/ir/ui/irChartProps.js +3 -2
  175. package/lib/ir/ui/irChartProps.js.map +1 -1
  176. package/lib/ir/ui/irCommandContext.d.ts +8 -0
  177. package/lib/ir/ui/irCommandContext.d.ts.map +1 -1
  178. package/lib/ir/ui/irCommandContext.js +14 -3
  179. package/lib/ir/ui/irCommandContext.js.map +1 -1
  180. package/lib/ir/ui/irHostPanels.d.ts +53 -0
  181. package/lib/ir/ui/irHostPanels.d.ts.map +1 -0
  182. package/lib/ir/ui/irHostPanels.js +41 -0
  183. package/lib/ir/ui/irHostPanels.js.map +1 -0
  184. package/lib/ir/ui/useIrEditor.d.ts +3 -3
  185. package/lib/ir/ui/useIrEditor.d.ts.map +1 -1
  186. package/lib/ir/ui/useIrEditor.js +13 -4
  187. package/lib/ir/ui/useIrEditor.js.map +1 -1
  188. package/lib/ir/ui/useIrHandle.d.ts +14 -0
  189. package/lib/ir/ui/useIrHandle.d.ts.map +1 -0
  190. package/lib/ir/ui/useIrHandle.js +39 -0
  191. package/lib/ir/ui/useIrHandle.js.map +1 -0
  192. package/lib/ir/ui/useIrSide.d.ts +27 -0
  193. package/lib/ir/ui/useIrSide.d.ts.map +1 -0
  194. package/lib/ir/ui/useIrSide.js +42 -0
  195. package/lib/ir/ui/useIrSide.js.map +1 -0
  196. package/lib/ir/ui/useIrText.d.ts +40 -0
  197. package/lib/ir/ui/useIrText.d.ts.map +1 -0
  198. package/lib/ir/ui/useIrText.js +118 -0
  199. package/lib/ir/ui/useIrText.js.map +1 -0
  200. package/lib/ir/ui/useIrZoom.d.ts +15 -0
  201. package/lib/ir/ui/useIrZoom.d.ts.map +1 -1
  202. package/lib/ir/ui/useIrZoom.js +6 -4
  203. package/lib/ir/ui/useIrZoom.js.map +1 -1
  204. package/lib/ir/ui/useValueAxisZoom.d.ts +20 -0
  205. package/lib/ir/ui/useValueAxisZoom.d.ts.map +1 -0
  206. package/lib/ir/ui/useValueAxisZoom.js +54 -0
  207. package/lib/ir/ui/useValueAxisZoom.js.map +1 -0
  208. package/lib/ir.d.ts +3 -0
  209. package/lib/ir.d.ts.map +1 -1
  210. package/lib/ir.js +3 -0
  211. package/lib/ir.js.map +1 -1
  212. package/lib/locales/de.json +29 -2
  213. package/lib/locales/en.json +29 -2
  214. package/lib/locales/es.json +29 -2
  215. package/lib/locales/fr.json +29 -2
  216. package/lib/locales/it.json +29 -2
  217. package/lib/molecule3d/core/animatedPng.d.ts +25 -0
  218. package/lib/molecule3d/core/animatedPng.d.ts.map +1 -0
  219. package/lib/molecule3d/core/animatedPng.js +59 -0
  220. package/lib/molecule3d/core/animatedPng.js.map +1 -0
  221. package/lib/molecule3d/core/file.d.ts +24 -0
  222. package/lib/molecule3d/core/file.d.ts.map +1 -0
  223. package/lib/molecule3d/core/file.js +2 -0
  224. package/lib/molecule3d/core/file.js.map +1 -0
  225. package/lib/molecule3d/core/index.d.ts +4 -1
  226. package/lib/molecule3d/core/index.d.ts.map +1 -1
  227. package/lib/molecule3d/core/index.js +1 -0
  228. package/lib/molecule3d/core/index.js.map +1 -1
  229. package/lib/molecule3d/core/settings.d.ts +0 -7
  230. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  231. package/lib/molecule3d/core/settings.js.map +1 -1
  232. package/lib/molecule3d/ui/Molecule3DExport.d.ts +10 -2
  233. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  234. package/lib/molecule3d/ui/Molecule3DExport.js +15 -2
  235. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  236. package/lib/molecule3d/ui/Molecule3DToolbar.js +9 -2
  237. package/lib/molecule3d/ui/Molecule3DToolbar.js.map +1 -1
  238. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  239. package/lib/molecule3d/ui/MoleculeCanvas3D.js +23 -41
  240. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  241. package/lib/molecule3d/ui/drawScene.d.ts +2 -1
  242. package/lib/molecule3d/ui/drawScene.d.ts.map +1 -1
  243. package/lib/molecule3d/ui/drawScene.js.map +1 -1
  244. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +15 -1
  245. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  246. package/lib/molecule3d/ui/exportMoleculeImage.js +14 -2
  247. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  248. package/lib/molecule3d/ui/moleculeFrames.d.ts +47 -0
  249. package/lib/molecule3d/ui/moleculeFrames.d.ts.map +1 -0
  250. package/lib/molecule3d/ui/moleculeFrames.js +65 -0
  251. package/lib/molecule3d/ui/moleculeFrames.js.map +1 -0
  252. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +14 -1
  253. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  254. package/lib/molecule3d/ui/renderMolecule.d.ts +3 -3
  255. package/lib/molecule3d/ui/renderMolecule.d.ts.map +1 -1
  256. package/lib/molecule3d/ui/renderMolecule.js +2 -1
  257. package/lib/molecule3d/ui/renderMolecule.js.map +1 -1
  258. package/lib/molecule3d/ui/sceneDrawer.d.ts +2 -1
  259. package/lib/molecule3d/ui/sceneDrawer.d.ts.map +1 -1
  260. package/lib/molecule3d/ui/sceneDrawer.js +9 -1
  261. package/lib/molecule3d/ui/sceneDrawer.js.map +1 -1
  262. package/lib/molecule3d/ui/useImageExport.d.ts +8 -3
  263. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  264. package/lib/molecule3d/ui/useImageExport.js +13 -4
  265. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  266. package/lib/molecule3d/ui/useMoleculeScene.d.ts +2 -1
  267. package/lib/molecule3d/ui/useMoleculeScene.d.ts.map +1 -1
  268. package/lib/molecule3d/ui/useMoleculeScene.js.map +1 -1
  269. package/lib/molecule3d/ui/useMoleculeViewer.d.ts +16 -0
  270. package/lib/molecule3d/ui/useMoleculeViewer.d.ts.map +1 -0
  271. package/lib/molecule3d/ui/useMoleculeViewer.js +37 -0
  272. package/lib/molecule3d/ui/useMoleculeViewer.js.map +1 -0
  273. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts +3 -3
  274. package/lib/molecule3d/ui/usePolarSurfaceArea.d.ts.map +1 -1
  275. package/lib/molecule3d/ui/usePolarSurfaceArea.js +4 -3
  276. package/lib/molecule3d/ui/usePolarSurfaceArea.js.map +1 -1
  277. package/lib/molecule3d/ui/viewer.d.ts +4 -1
  278. package/lib/molecule3d/ui/viewer.d.ts.map +1 -1
  279. package/lib/molecule3d/ui/viewer.js +21 -19
  280. package/lib/molecule3d/ui/viewer.js.map +1 -1
  281. package/lib/molecule3d/ui/viewerTypes.d.ts +6 -0
  282. package/lib/molecule3d/ui/viewerTypes.d.ts.map +1 -1
  283. package/lib/molstar/core/captureScene.d.ts +19 -1
  284. package/lib/molstar/core/captureScene.d.ts.map +1 -1
  285. package/lib/molstar/core/captureScene.js +57 -2
  286. package/lib/molstar/core/captureScene.js.map +1 -1
  287. package/lib/molstar/core/index.d.ts +5 -2
  288. package/lib/molstar/core/index.d.ts.map +1 -1
  289. package/lib/molstar/core/index.js +4 -2
  290. package/lib/molstar/core/index.js.map +1 -1
  291. package/lib/molstar/core/trajectory.d.ts +56 -0
  292. package/lib/molstar/core/trajectory.d.ts.map +1 -0
  293. package/lib/molstar/core/trajectory.js +96 -0
  294. package/lib/molstar/core/trajectory.js.map +1 -0
  295. package/lib/panel/ui/PanelRail.d.ts.map +1 -1
  296. package/lib/panel/ui/PanelRail.js +1 -1
  297. package/lib/panel/ui/PanelRail.js.map +1 -1
  298. package/lib/raman.d.ts +17 -0
  299. package/lib/raman.d.ts.map +1 -0
  300. package/lib/raman.js +12 -0
  301. package/lib/raman.js.map +1 -0
  302. package/lib/router/core/basePath.d.ts +2 -2
  303. package/lib/router/core/basePath.js +2 -2
  304. package/lib/router/core/siteAddresses.d.ts +1 -1
  305. package/lib/router/core/siteAddresses.js +1 -1
  306. package/lib/seo/core/siteFiles.js +1 -1
  307. package/lib/seo/core/siteFiles.js.map +1 -1
  308. package/lib/server/answerAddresses.d.ts +3 -3
  309. package/lib/server/answerAddresses.js +3 -3
  310. package/lib/structure/core/index.d.ts +2 -0
  311. package/lib/structure/core/index.d.ts.map +1 -1
  312. package/lib/structure/core/index.js +1 -0
  313. package/lib/structure/core/index.js.map +1 -1
  314. package/lib/structure/core/structureTransfer.d.ts +29 -0
  315. package/lib/structure/core/structureTransfer.d.ts.map +1 -0
  316. package/lib/structure/core/structureTransfer.js +26 -0
  317. package/lib/structure/core/structureTransfer.js.map +1 -0
  318. package/lib/structure/ui/EditorImportButton.d.ts +20 -0
  319. package/lib/structure/ui/EditorImportButton.d.ts.map +1 -0
  320. package/lib/structure/ui/EditorImportButton.js +24 -0
  321. package/lib/structure/ui/EditorImportButton.js.map +1 -0
  322. package/lib/structure/ui/EditorImportProblem.d.ts +17 -0
  323. package/lib/structure/ui/EditorImportProblem.d.ts.map +1 -0
  324. package/lib/structure/ui/EditorImportProblem.js +32 -0
  325. package/lib/structure/ui/EditorImportProblem.js.map +1 -0
  326. package/lib/structure/ui/StructureDrop.d.ts +39 -0
  327. package/lib/structure/ui/StructureDrop.d.ts.map +1 -0
  328. package/lib/structure/ui/StructureDrop.js +19 -0
  329. package/lib/structure/ui/StructureDrop.js.map +1 -0
  330. package/lib/structure/ui/StructureDropOverlay.d.ts +16 -0
  331. package/lib/structure/ui/StructureDropOverlay.d.ts.map +1 -0
  332. package/lib/structure/ui/StructureDropOverlay.js +29 -0
  333. package/lib/structure/ui/StructureDropOverlay.js.map +1 -0
  334. package/lib/structure/ui/StructureEditor.d.ts +3 -80
  335. package/lib/structure/ui/StructureEditor.d.ts.map +1 -1
  336. package/lib/structure/ui/StructureEditor.js +21 -33
  337. package/lib/structure/ui/StructureEditor.js.map +1 -1
  338. package/lib/structure/ui/importedStructure.d.ts +25 -0
  339. package/lib/structure/ui/importedStructure.d.ts.map +1 -0
  340. package/lib/structure/ui/importedStructure.js +74 -0
  341. package/lib/structure/ui/importedStructure.js.map +1 -0
  342. package/lib/structure/ui/index.d.ts +2 -0
  343. package/lib/structure/ui/index.d.ts.map +1 -1
  344. package/lib/structure/ui/index.js +1 -0
  345. package/lib/structure/ui/index.js.map +1 -1
  346. package/lib/structure/ui/structureEditorProps.d.ts +98 -0
  347. package/lib/structure/ui/structureEditorProps.d.ts.map +1 -0
  348. package/lib/structure/ui/structureEditorProps.js +2 -0
  349. package/lib/structure/ui/structureEditorProps.js.map +1 -0
  350. package/lib/structure/ui/useDebouncedChange.d.ts +16 -0
  351. package/lib/structure/ui/useDebouncedChange.d.ts.map +1 -0
  352. package/lib/structure/ui/useDebouncedChange.js +30 -0
  353. package/lib/structure/ui/useDebouncedChange.js.map +1 -0
  354. package/lib/structure/ui/useStructureDrop.d.ts +30 -0
  355. package/lib/structure/ui/useStructureDrop.d.ts.map +1 -0
  356. package/lib/structure/ui/useStructureDrop.js +49 -0
  357. package/lib/structure/ui/useStructureDrop.js.map +1 -0
  358. package/lib/structure/ui/useStructureImport.d.ts +45 -0
  359. package/lib/structure/ui/useStructureImport.d.ts.map +1 -0
  360. package/lib/structure/ui/useStructureImport.js +62 -0
  361. package/lib/structure/ui/useStructureImport.js.map +1 -0
  362. package/package.json +5 -1
  363. package/src/download/ui/FigureDownloadPanel.tsx +9 -2
  364. package/src/ecosystem/core/sites.ts +3 -3
  365. package/src/ecosystem/core/types.ts +1 -1
  366. package/src/ecosystem/ui/glyphs.tsx +1 -1
  367. package/src/i18n/core/messageCatalog.ts +2 -2
  368. package/src/i18n/vite/localeCatalog.ts +1 -1
  369. package/src/ir/core/analysisSpectra.ts +26 -10
  370. package/src/ir/core/bandLabelPlan.ts +21 -10
  371. package/src/ir/core/index.ts +31 -7
  372. package/src/ir/core/irBand.ts +52 -14
  373. package/src/ir/core/irCommands.ts +15 -1
  374. package/src/ir/core/irDomain.ts +9 -7
  375. package/src/ir/core/irEditorApi.ts +13 -0
  376. package/src/ir/core/irFormat.ts +39 -6
  377. package/src/ir/core/irMode.ts +42 -10
  378. package/src/ir/core/irReducer.ts +3 -51
  379. package/src/ir/core/irSidePanels.ts +8 -1
  380. package/src/ir/core/irSpectrum.ts +59 -30
  381. package/src/ir/core/irState.ts +73 -5
  382. package/src/ir/core/irTechnique.ts +115 -0
  383. package/src/ir/core/irTechniqueCommands.ts +82 -0
  384. package/src/ir/core/pickBands.ts +63 -28
  385. package/src/ir/core/ramanBands.ts +105 -0
  386. package/src/ir/core/spectrumJcamp.ts +65 -16
  387. package/src/ir/core/toIrSpectrum.ts +20 -2
  388. package/src/ir/ui/BandRow.tsx +107 -0
  389. package/src/ir/ui/BandsPanel.tsx +32 -125
  390. package/src/ir/ui/IrAboutDialog.tsx +69 -28
  391. package/src/ir/ui/IrCanvas.tsx +29 -3
  392. package/src/ir/ui/IrChart.tsx +31 -24
  393. package/src/ir/ui/IrCommandProvider.tsx +10 -3
  394. package/src/ir/ui/IrCommandTooltip.tsx +11 -2
  395. package/src/ir/ui/IrDocumentationDialog.tsx +99 -55
  396. package/src/ir/ui/IrEditor.tsx +27 -40
  397. package/src/ir/ui/IrHeader.tsx +10 -3
  398. package/src/ir/ui/IrLogo.tsx +28 -9
  399. package/src/ir/ui/IrShell.tsx +47 -96
  400. package/src/ir/ui/IrSidePanel.tsx +36 -10
  401. package/src/ir/ui/IrStatusBar.tsx +17 -12
  402. package/src/ir/ui/IrToolbar.tsx +26 -15
  403. package/src/ir/ui/IrTraces.tsx +5 -1
  404. package/src/ir/ui/RamanEditor.tsx +71 -0
  405. package/src/ir/ui/SpectraPanel.tsx +2 -1
  406. package/src/ir/ui/SpectrumRow.tsx +14 -5
  407. package/src/ir/ui/VibrationalEditor.tsx +113 -0
  408. package/src/ir/ui/bandsTsv.ts +46 -0
  409. package/src/ir/ui/index.ts +1 -0
  410. package/src/ir/ui/irActions.ts +3 -3
  411. package/src/ir/ui/irChartProps.ts +21 -4
  412. package/src/ir/ui/irCommandContext.tsx +18 -3
  413. package/src/ir/ui/irHostPanels.ts +76 -0
  414. package/src/ir/ui/useIrEditor.ts +20 -8
  415. package/src/ir/ui/useIrHandle.ts +51 -0
  416. package/src/ir/ui/useIrSide.ts +60 -0
  417. package/src/ir/ui/useIrText.ts +170 -0
  418. package/src/ir/ui/useIrZoom.ts +22 -3
  419. package/src/ir/ui/useValueAxisZoom.ts +67 -0
  420. package/src/ir.ts +3 -0
  421. package/src/locales/de.json +29 -2
  422. package/src/locales/en.json +29 -2
  423. package/src/locales/es.json +29 -2
  424. package/src/locales/fr.json +29 -2
  425. package/src/locales/it.json +29 -2
  426. package/src/molecule3d/core/animatedPng.ts +79 -0
  427. package/src/molecule3d/core/file.ts +23 -0
  428. package/src/molecule3d/core/index.ts +3 -1
  429. package/src/molecule3d/core/settings.ts +0 -8
  430. package/src/molecule3d/ui/Molecule3DExport.tsx +43 -5
  431. package/src/molecule3d/ui/Molecule3DToolbar.tsx +9 -2
  432. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +24 -38
  433. package/src/molecule3d/ui/drawScene.ts +2 -1
  434. package/src/molecule3d/ui/exportMoleculeImage.ts +28 -3
  435. package/src/molecule3d/ui/moleculeFrames.ts +88 -0
  436. package/src/molecule3d/ui/moleculeViewer3DProps.ts +14 -5
  437. package/src/molecule3d/ui/renderMolecule.ts +5 -3
  438. package/src/molecule3d/ui/sceneDrawer.ts +14 -2
  439. package/src/molecule3d/ui/useImageExport.ts +22 -5
  440. package/src/molecule3d/ui/useMoleculeScene.ts +2 -1
  441. package/src/molecule3d/ui/useMoleculeViewer.ts +46 -0
  442. package/src/molecule3d/ui/usePolarSurfaceArea.ts +6 -4
  443. package/src/molecule3d/ui/viewer.ts +34 -27
  444. package/src/molecule3d/ui/viewerTypes.ts +6 -0
  445. package/src/molstar/core/captureScene.ts +85 -2
  446. package/src/molstar/core/index.ts +13 -2
  447. package/src/molstar/core/trajectory.ts +117 -0
  448. package/src/panel/ui/PanelRail.tsx +3 -0
  449. package/src/raman.ts +20 -0
  450. package/src/router/core/basePath.ts +2 -2
  451. package/src/router/core/siteAddresses.ts +1 -1
  452. package/src/seo/core/siteFiles.ts +1 -1
  453. package/src/server/answerAddresses.ts +3 -3
  454. package/src/structure/core/index.ts +8 -0
  455. package/src/structure/core/structureTransfer.ts +37 -0
  456. package/src/structure/ui/EditorImportButton.tsx +59 -0
  457. package/src/structure/ui/EditorImportProblem.tsx +63 -0
  458. package/src/structure/ui/StructureDrop.tsx +66 -0
  459. package/src/structure/ui/StructureDropOverlay.tsx +50 -0
  460. package/src/structure/ui/StructureEditor.tsx +49 -134
  461. package/src/structure/ui/importedStructure.ts +83 -0
  462. package/src/structure/ui/index.ts +2 -0
  463. package/src/structure/ui/structureEditorProps.ts +103 -0
  464. package/src/structure/ui/useDebouncedChange.ts +41 -0
  465. package/src/structure/ui/useStructureDrop.ts +76 -0
  466. package/src/structure/ui/useStructureImport.ts +122 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Frames of a scene as one animated PNG, which loops forever.
3
+ *
4
+ * An APNG is a PNG whose frames every browser plays, and whose first frame
5
+ * any program that does not play them still shows. It keeps every colour of a
6
+ * shaded molecule, which a GIF's 256 cannot.
7
+ */
8
+
9
+ import type * as Upng from '@pdf-lib/upng';
10
+
11
+ /** How many frames a second are played, and saved, when nobody says. */
12
+ export const DEFAULT_FRAMES_PER_SECOND = 25;
13
+
14
+ /** One frame: its pixels as RGBA bytes, row by row from the top. */
15
+ export interface RgbaFrame {
16
+ width: number;
17
+ height: number;
18
+ data: Uint8ClampedArray | Uint8Array;
19
+ }
20
+
21
+ /**
22
+ * Encode frames as one animated PNG, every frame shown for the same time, the
23
+ * whole looping forever. The encoder is loaded on first use.
24
+ * @param frames - The frames, in order, all the same size.
25
+ * @param framesPerSecond - How many are shown per second.
26
+ * @returns The PNG file.
27
+ * @throws {Error} When there is no frame, or the frames differ in size.
28
+ */
29
+ export async function encodeAnimatedPng(
30
+ frames: readonly RgbaFrame[],
31
+ framesPerSecond: number,
32
+ ): Promise<Uint8Array<ArrayBuffer>> {
33
+ const first = frames[0];
34
+ if (first === undefined) throw new Error('There is no frame to encode.');
35
+ const { width, height } = first;
36
+ const buffers: ArrayBuffer[] = [];
37
+ const delays: number[] = [];
38
+ const delay = Math.max(1, Math.round(1000 / framesPerSecond));
39
+ for (const frame of frames) {
40
+ if (frame.width !== width || frame.height !== height) {
41
+ throw new Error('Every frame of an animated PNG must be the same size.');
42
+ }
43
+ buffers.push(rgbaBuffer(frame));
44
+ delays.push(delay);
45
+ }
46
+ const upng = await loadEncoder();
47
+ // 0 colours: lossless.
48
+ return new Uint8Array(upng.encode(buffers, width, height, 0, delays));
49
+ }
50
+
51
+ /**
52
+ * The bytes of a frame in an ArrayBuffer of their own size, which the encoder
53
+ * reads whole.
54
+ * @param frame - The frame.
55
+ * @returns Its RGBA bytes.
56
+ */
57
+ function rgbaBuffer(frame: RgbaFrame): ArrayBuffer {
58
+ const { data } = frame;
59
+ const { buffer, byteOffset, byteLength } = data;
60
+ if (
61
+ buffer instanceof ArrayBuffer &&
62
+ byteOffset === 0 &&
63
+ byteLength === buffer.byteLength
64
+ ) {
65
+ return buffer;
66
+ }
67
+ return new Uint8Array(data).buffer;
68
+ }
69
+
70
+ type Encoder = Pick<typeof Upng, 'encode'>;
71
+
72
+ async function loadEncoder(): Promise<Encoder> {
73
+ const { default: loaded } = (await import('@pdf-lib/upng')) as {
74
+ default: Encoder | { default: Encoder };
75
+ };
76
+ // A bundler hands the ES build's one default object; Node's own import hands
77
+ // the exports of the CommonJS build, whose default is that object.
78
+ return 'encode' in loaded ? loaded : loaded.default;
79
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A molfile for the viewer, with the parser molstar has to read it with.
3
+ *
4
+ * A file of several models — an SDF of several records, an XYZ of several
5
+ * frames — is a series of frames of one molecule, which the viewer can play
6
+ * and export as an animated PNG.
7
+ */
8
+ export interface Molecule3DFile {
9
+ /**
10
+ * molstar's format id: `mol` reads V2000, `sdf` reads V2000 and V3000, `xyz`
11
+ * reads bare coordinates and infers the bonds from the distances.
12
+ */
13
+ format: 'mol' | 'sdf' | 'xyz';
14
+ /** The file text. It must carry 3D coordinates. */
15
+ data: string;
16
+ /**
17
+ * Names the molecule the file draws, when several files draw one — one per
18
+ * normal mode, say. Replacing a file by another of the same name keeps the
19
+ * camera where the reader left it, instead of framing a new molecule.
20
+ * @default undefined — every other file is another molecule
21
+ */
22
+ moleculeId?: string;
23
+ }
@@ -1,3 +1,5 @@
1
+ export type { RgbaFrame } from './animatedPng.ts';
2
+ export { DEFAULT_FRAMES_PER_SECOND, encodeAnimatedPng } from './animatedPng.ts';
1
3
  export type {
2
4
  Molecule3DCamera,
3
5
  Molecule3DRotation,
@@ -33,8 +35,8 @@ export {
33
35
  MEASUREMENT_LABELS,
34
36
  sameMeasurements,
35
37
  } from './measurement.ts';
38
+ export type { Molecule3DFile } from './file.ts';
36
39
  export type {
37
- Molecule3DFile,
38
40
  Molecule3DSettings,
39
41
  Molecule3DTools,
40
42
  RepresentationId,
@@ -3,14 +3,6 @@
3
3
  * free of React and molstar so a site can persist and validate them.
4
4
  */
5
5
 
6
- /** A molfile for the viewer, with the parser molstar has to read it with. */
7
- export interface Molecule3DFile {
8
- /** molstar's format id: `mol` reads V2000, `sdf` reads V2000 and V3000. */
9
- format: 'mol' | 'sdf';
10
- /** The molfile text. It must carry 3D coordinates. */
11
- data: string;
12
- }
13
-
14
6
  /** Ways the model can be drawn, in the order the picker offers them. */
15
7
  export const REPRESENTATIONS = [
16
8
  'ball-and-stick',
@@ -16,15 +16,28 @@ import {
16
16
  import { FigureDownloadPanel } from '../../download/ui/FigureDownloadPanel.tsx';
17
17
  import { useFigureTask } from '../../download/ui/useFigureTask.ts';
18
18
  import { useChromeT } from '../../i18n/ui/useT.ts';
19
+ import { OverlaySegmented } from '../../overlay/ui/OverlaySegmented.tsx';
19
20
 
20
21
  /** Props of {@link Molecule3DExport}. */
21
22
  export interface Molecule3DExportProps {
22
23
  /** Reads the size of the canvas on screen, when the popover opens. */
23
24
  getSize: () => FigurePixels | null;
24
- /** Writes the file. */
25
- onExport: (format: FigureFormat, scale: number) => Promise<void>;
25
+ /**
26
+ * Writes the file: every frame, as an animated PNG, when `animated` is set.
27
+ */
28
+ onExport: (
29
+ format: FigureFormat,
30
+ scale: number,
31
+ animated: boolean,
32
+ ) => Promise<void>;
26
33
  /** Puts a PNG at that scale on the clipboard, and says what came of it. */
27
34
  onCopy: (scale: number) => Promise<FigureNotice>;
35
+ /**
36
+ * How many frames the scene holds. Above one, a PNG may hold them all, as an
37
+ * animation.
38
+ * @default 1
39
+ */
40
+ frameCount?: number;
28
41
  }
29
42
 
30
43
  /**
@@ -33,11 +46,14 @@ export interface Molecule3DExportProps {
33
46
  * @returns The panel.
34
47
  */
35
48
  export function Molecule3DExport(props: Molecule3DExportProps): ReactElement {
36
- const { getSize, onExport, onCopy } = props;
49
+ const { getSize, onExport, onCopy, frameCount = 1 } = props;
37
50
  const t = useChromeT();
38
51
  const [size] = useState(getSize);
39
52
  const [format, setFormat] = useState<FigureFormat>('png');
40
53
  const [scale, setScale] = useState(DEFAULT_FIGURE_SCALE);
54
+ const [frames, setFrames] = useState<FrameChoice>('all');
55
+ const offerFrames = frameCount > 1 && format === 'png';
56
+ const animated = offerFrames && frames === 'all';
41
57
  const { run, busy, failure, notice, clear } = useFigureTask();
42
58
 
43
59
  // What the last copy came to only describes the choices it was made with.
@@ -61,8 +77,30 @@ export function Molecule3DExport(props: Molecule3DExportProps): ReactElement {
61
77
  rasterSvg
62
78
  onFormatChange={changed(setFormat)}
63
79
  onScaleChange={changed(setScale)}
64
- onSave={() => run(onExport(format, scale).then(() => null))}
80
+ onSave={() => run(onExport(format, scale, animated).then(() => null))}
65
81
  onCopy={() => run(onCopy(scale))}
66
- />
82
+ >
83
+ {offerFrames && (
84
+ <OverlaySegmented<FrameChoice>
85
+ label={t('molecule3d.frames')}
86
+ help={{
87
+ title: t('molecule3d.frames'),
88
+ body: t('molecule3d.framesHelp'),
89
+ }}
90
+ value={frames}
91
+ options={[
92
+ { value: 'one', label: t('molecule3d.frames.one') },
93
+ {
94
+ value: 'all',
95
+ label: t('molecule3d.frames.all', { count: frameCount }),
96
+ },
97
+ ]}
98
+ onChange={changed(setFrames)}
99
+ />
100
+ )}
101
+ </FigureDownloadPanel>
67
102
  );
68
103
  }
104
+
105
+ /** The frame on screen, or every frame as an animated PNG. */
106
+ type FrameChoice = 'one' | 'all';
@@ -225,11 +225,18 @@ export function Molecule3DToolbar(props: Molecule3DToolbarProps): ReactElement {
225
225
 
226
226
  const TOOLTIP_PROPS = { placement: 'right' } as const;
227
227
 
228
+ // A canvas too short for both cards puts the second one beside the first,
229
+ // rather than cutting it off; the column itself lets clicks through to the
230
+ // canvas between and below the cards.
228
231
  const COLUMN_STYLE: CSSProperties = {
229
232
  display: 'flex',
230
233
  flexDirection: 'column',
234
+ flexWrap: 'wrap',
235
+ alignContent: 'flex-start',
236
+ alignItems: 'flex-start',
231
237
  gap: 6,
232
- pointerEvents: 'auto',
238
+ height: '100%',
239
+ pointerEvents: 'none',
233
240
  };
234
241
 
235
- const CARD_STYLE: CSSProperties = { padding: 0 };
242
+ const CARD_STYLE: CSSProperties = { padding: 0, pointerEvents: 'auto' };
@@ -10,6 +10,7 @@ import type { CSSProperties, ReactElement } from 'react';
10
10
  import { useEffect, useRef, useState } from 'react';
11
11
 
12
12
  import { DEFAULT_SPIN_SPEED } from '../../molstar/core/index.ts';
13
+ import { DEFAULT_FRAMES_PER_SECOND } from '../core/animatedPng.ts';
13
14
  import type { Molecule3DHighlight } from '../core/highlight.ts';
14
15
  import type { Measurement, MeasurementKind } from '../core/measurement.ts';
15
16
  import {
@@ -21,14 +22,13 @@ import { Molecule3DExport } from './Molecule3DExport.tsx';
21
22
  import { Molecule3DOptions } from './Molecule3DOptions.tsx';
22
23
  import { Molecule3DStage } from './Molecule3DStage.tsx';
23
24
  import { Molecule3DToolbar } from './Molecule3DToolbar.tsx';
24
- import { createMolecule3DViewer } from './createViewer.ts';
25
25
  import { highlightColorOf } from './highlightColor.ts';
26
26
  import type { MoleculeViewer3DProps } from './moleculeViewer3DProps.ts';
27
27
  import { useControlledState } from './useControlledState.ts';
28
28
  import { useImageExport } from './useImageExport.ts';
29
29
  import { useMoleculeScene } from './useMoleculeScene.ts';
30
+ import { useMoleculeViewer } from './useMoleculeViewer.ts';
30
31
  import { usePolarSurfaceArea } from './usePolarSurfaceArea.ts';
31
- import type { Molecule3DViewer } from './viewer.ts';
32
32
 
33
33
  /** Props of {@link MoleculeCanvas3D}. */
34
34
  export interface MoleculeCanvas3DProps extends Omit<
@@ -56,6 +56,8 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
56
56
  defaultSpinning = false,
57
57
  onSpinningChange,
58
58
  spinSpeed = DEFAULT_SPIN_SPEED,
59
+ playing = false,
60
+ framesPerSecond = DEFAULT_FRAMES_PER_SECOND,
59
61
  measurements: measurementsProp,
60
62
  onMeasurementsChange,
61
63
  highlights = NO_HIGHLIGHTS,
@@ -82,6 +84,7 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
82
84
  readonly Measurement[]
83
85
  >(measurementsProp, NO_MEASUREMENTS, onMeasurementsChange);
84
86
  const [measureKind, setMeasureKind] = useState<MeasurementKind | null>(null);
87
+ const [frameCount, setFrameCount] = useState(0);
85
88
  const polarSurfaceArea = usePolarSurfaceArea(
86
89
  molfile,
87
90
  settings.surfaceColoring === 'polarity',
@@ -90,51 +93,31 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
90
93
  // A callback ref: the element arriving is what creates the viewer, and every
91
94
  // effect keyed on it runs again, in order, after the viewer exists.
92
95
  const [container, setContainer] = useState<HTMLDivElement | null>(null);
93
- const viewerRef = useRef<Molecule3DViewer | null>(null);
94
96
  // The viewer is created once, so what its callbacks read is kept fresh here.
95
- const latest = useRef({
96
- measurements,
97
- setMeasurements,
98
- onFailureChange,
99
- background,
100
- });
97
+ const latest = useRef({ measurements, setMeasurements });
101
98
  useEffect(() => {
102
- latest.current = {
103
- measurements,
104
- setMeasurements,
105
- onFailureChange,
106
- background,
107
- };
99
+ latest.current = { measurements, setMeasurements };
100
+ });
101
+ const viewerRef = useMoleculeViewer(container, {
102
+ background,
103
+ onMeasure: (measurement) => {
104
+ const current = latest.current;
105
+ current.setMeasurements([...current.measurements, measurement]);
106
+ },
107
+ onFrameCountChange: setFrameCount,
108
108
  });
109
109
 
110
110
  useEffect(() => {
111
- if (container === null) return;
112
- const viewer = createMolecule3DViewer(container, {
113
- background: latest.current.background,
114
- onMeasure: (measurement) => {
115
- const current = latest.current;
116
- current.setMeasurements([...current.measurements, measurement]);
117
- },
118
- });
119
- viewerRef.current = viewer;
120
- const observer = new ResizeObserver(() => {
121
- viewer.handleResize();
122
- });
123
- observer.observe(container);
124
- return () => {
125
- observer.disconnect();
126
- viewerRef.current = null;
127
- viewer.dispose();
128
- };
129
- }, [container]);
111
+ void viewerRef.current?.setSpin(spinning, spinSpeed);
112
+ }, [container, viewerRef, spinning, spinSpeed]);
130
113
 
131
114
  useEffect(() => {
132
- void viewerRef.current?.setSpin(spinning, spinSpeed);
133
- }, [container, spinning, spinSpeed]);
115
+ void viewerRef.current?.frames.play(playing, framesPerSecond);
116
+ }, [container, viewerRef, playing, framesPerSecond]);
134
117
 
135
118
  useEffect(() => {
136
119
  void viewerRef.current?.setMeasureKind(measureKind);
137
- }, [container, measureKind]);
120
+ }, [container, viewerRef, measureKind]);
138
121
 
139
122
  // Kept out of the scene: a new highlight repaints the model, never redraws it.
140
123
  useEffect(() => {
@@ -142,7 +125,7 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
142
125
  highlights,
143
126
  highlightColorOf(container),
144
127
  );
145
- }, [container, highlights]);
128
+ }, [container, viewerRef, highlights]);
146
129
 
147
130
  const { resetView } = useMoleculeScene({
148
131
  container,
@@ -160,6 +143,7 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
160
143
  container,
161
144
  viewerRef,
162
145
  fileName,
146
+ framesPerSecond,
163
147
  );
164
148
 
165
149
  return (
@@ -202,6 +186,7 @@ export function MoleculeCanvas3D(props: MoleculeCanvas3DProps): ReactElement {
202
186
  getSize={canvasSize}
203
187
  onExport={exportImage}
204
188
  onCopy={copyImage}
189
+ frameCount={frameCount}
205
190
  />
206
191
  }
207
192
  />
@@ -231,6 +216,7 @@ const CANVAS_STYLE: CSSProperties = { position: 'absolute', inset: 0 };
231
216
  const OVERLAY_STYLE: CSSProperties = {
232
217
  position: 'absolute',
233
218
  top: 8,
219
+ bottom: 8,
234
220
  left: 8,
235
221
  zIndex: 2,
236
222
  display: 'flex',
@@ -1,5 +1,6 @@
1
+ import type { Molecule3DFile } from '../core/file.ts';
1
2
  import type { Measurement } from '../core/measurement.ts';
2
- import type { Molecule3DFile, Molecule3DSettings } from '../core/settings.ts';
3
+ import type { Molecule3DSettings } from '../core/settings.ts';
3
4
  import { normalizeMolecule3DSettings } from '../core/settings.ts';
4
5
 
5
6
  import type { Molecule3DViewer } from './viewer.ts';
@@ -4,13 +4,22 @@ import { FIGURE_PNG_TYPE } from '../../download/core/figurePng.ts';
4
4
  import { figurePixels } from '../../download/core/figureScale.ts';
5
5
  import { FIGURE_SVG_TYPE } from '../../download/core/figureSvg.ts';
6
6
  import { sanitizeFileName } from '../../download/core/sanitizeFileName.ts';
7
+ import { encodeAnimatedPng } from '../core/animatedPng.ts';
7
8
  import type { ImageSize } from '../core/exportImage.ts';
8
9
  import { dataUriBytes, rasterSvgMarkup } from '../core/exportImage.ts';
9
10
 
10
- /** The one thing an export asks of a viewer: a picture of its scene. */
11
+ /** What an export asks of a viewer: a picture of its scene. */
11
12
  export interface SceneCapture {
12
13
  /** Render the scene at that pixel size, as a `data:` URI. */
13
14
  captureImage: (size: ImageSize) => Promise<string | undefined>;
15
+ /**
16
+ * The frames of the scene, each rendered at that pixel size, for an animated
17
+ * PNG.
18
+ * @default undefined — the viewer has a single frame to give
19
+ */
20
+ frames?: {
21
+ capture: (size: ImageSize) => Promise<ImageData[] | undefined>;
22
+ };
14
23
  }
15
24
 
16
25
  /** What {@link exportMoleculeImage} writes. */
@@ -22,6 +31,12 @@ export interface MoleculeImageRequest {
22
31
  size: ImageSize;
23
32
  /** File name without its extension. */
24
33
  fileName: string;
34
+ /**
35
+ * Every frame of the scene as one animated PNG, played at this many frames a
36
+ * second, rather than the frame on screen. Ignored for an SVG.
37
+ * @default undefined — the frame on screen
38
+ */
39
+ framesPerSecond?: number;
25
40
  }
26
41
 
27
42
  /**
@@ -54,11 +69,21 @@ export async function exportMoleculeImage(
54
69
  viewer: SceneCapture,
55
70
  request: MoleculeImageRequest,
56
71
  ): Promise<void> {
57
- const { format, scale, size, fileName } = request;
72
+ const { format, scale, size, fileName, framesPerSecond } = request;
58
73
  const pixels = figurePixels(size, scale);
74
+ const base = sanitizeFileName(fileName, 'molecule');
75
+ if (format === 'png' && framesPerSecond !== undefined) {
76
+ if (viewer.frames === undefined) {
77
+ throw new Error('This viewer has no frames to animate.');
78
+ }
79
+ const frames = await viewer.frames.capture(pixels);
80
+ if (frames === undefined) return;
81
+ const bytes = await encodeAnimatedPng(frames, framesPerSecond);
82
+ downloadBlob(new Blob([bytes], { type: FIGURE_PNG_TYPE }), `${base}.png`);
83
+ return;
84
+ }
59
85
  const dataUri = await viewer.captureImage(pixels);
60
86
  if (dataUri === undefined) return;
61
- const base = sanitizeFileName(fileName, 'molecule');
62
87
  if (format === 'png') {
63
88
  downloadBlob(
64
89
  new Blob([dataUriBytes(dataUri)], { type: FIGURE_PNG_TYPE }),
@@ -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,