react-cheminfo 0.50.0 → 0.52.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 (314) hide show
  1. package/README.md +71 -16
  2. package/bin/check-seo.mjs +0 -0
  3. package/lib/clipboard/core/index.d.ts +0 -1
  4. package/lib/clipboard/core/index.d.ts.map +1 -1
  5. package/lib/clipboard/core/index.js +0 -1
  6. package/lib/clipboard/core/index.js.map +1 -1
  7. package/lib/clipboard/ui/CopyButton.d.ts +6 -0
  8. package/lib/clipboard/ui/CopyButton.d.ts.map +1 -1
  9. package/lib/clipboard/ui/CopyButton.js +5 -2
  10. package/lib/clipboard/ui/CopyButton.js.map +1 -1
  11. package/lib/color/core/index.d.ts +3 -0
  12. package/lib/color/core/index.d.ts.map +1 -1
  13. package/lib/color/core/index.js +2 -0
  14. package/lib/color/core/index.js.map +1 -1
  15. package/lib/color/core/interpolate.d.ts +4 -2
  16. package/lib/color/core/interpolate.d.ts.map +1 -1
  17. package/lib/color/core/interpolate.js +34 -12
  18. package/lib/color/core/interpolate.js.map +1 -1
  19. package/lib/color/core/stops.d.ts +43 -0
  20. package/lib/color/core/stops.d.ts.map +1 -0
  21. package/lib/color/core/stops.js +66 -0
  22. package/lib/color/core/stops.js.map +1 -0
  23. package/lib/color/core/tone.d.ts +25 -0
  24. package/lib/color/core/tone.d.ts.map +1 -0
  25. package/lib/color/core/tone.js +67 -0
  26. package/lib/color/core/tone.js.map +1 -0
  27. package/lib/color/core/uniform.d.ts +36 -0
  28. package/lib/color/core/uniform.d.ts.map +1 -0
  29. package/lib/color/core/uniform.js +61 -0
  30. package/lib/color/core/uniform.js.map +1 -0
  31. package/lib/color/ui/ColorScaleEditor.d.ts +5 -6
  32. package/lib/color/ui/ColorScaleEditor.d.ts.map +1 -1
  33. package/lib/color/ui/ColorScaleEditor.js +74 -84
  34. package/lib/color/ui/ColorScaleEditor.js.map +1 -1
  35. package/lib/color/ui/ColorScaleSelect.d.ts +16 -0
  36. package/lib/color/ui/ColorScaleSelect.d.ts.map +1 -1
  37. package/lib/color/ui/ColorScaleSelect.js +69 -17
  38. package/lib/color/ui/ColorScaleSelect.js.map +1 -1
  39. package/lib/color/ui/ColorScaleTone.d.ts +17 -0
  40. package/lib/color/ui/ColorScaleTone.d.ts.map +1 -0
  41. package/lib/color/ui/ColorScaleTone.js +55 -0
  42. package/lib/color/ui/ColorScaleTone.js.map +1 -0
  43. package/lib/color/ui/ColorScaleTrack.d.ts +29 -0
  44. package/lib/color/ui/ColorScaleTrack.d.ts.map +1 -0
  45. package/lib/color/ui/ColorScaleTrack.js +143 -0
  46. package/lib/color/ui/ColorScaleTrack.js.map +1 -0
  47. package/lib/download/core/copyFigure.d.ts +14 -0
  48. package/lib/download/core/copyFigure.d.ts.map +1 -0
  49. package/lib/download/core/copyFigure.js +18 -0
  50. package/lib/download/core/copyFigure.js.map +1 -0
  51. package/lib/download/core/figureHtml.js +1 -0
  52. package/lib/download/core/figureHtml.js.map +1 -1
  53. package/lib/download/core/figureHtmlMarkup.d.ts +10 -0
  54. package/lib/download/core/figureHtmlMarkup.d.ts.map +1 -1
  55. package/lib/download/core/figureHtmlMarkup.js +7 -1
  56. package/lib/download/core/figureHtmlMarkup.js.map +1 -1
  57. package/lib/download/core/index.d.ts +2 -0
  58. package/lib/download/core/index.d.ts.map +1 -1
  59. package/lib/download/core/index.js +1 -0
  60. package/lib/download/core/index.js.map +1 -1
  61. package/lib/download/ui/FigureDownloadPanel.d.ts +2 -3
  62. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  63. package/lib/download/ui/FigureDownloadPanel.js +1 -1
  64. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  65. package/lib/download/ui/figureDownloadHint.d.ts +1 -1
  66. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -1
  67. package/lib/download/ui/figureDownloadHint.js.map +1 -1
  68. package/lib/download/ui/index.d.ts +0 -1
  69. package/lib/download/ui/index.d.ts.map +1 -1
  70. package/lib/download/ui/useFigureActions.d.ts +2 -11
  71. package/lib/download/ui/useFigureActions.d.ts.map +1 -1
  72. package/lib/download/ui/useFigureActions.js +4 -23
  73. package/lib/download/ui/useFigureActions.js.map +1 -1
  74. package/lib/download/ui/useFigureTask.d.ts +21 -0
  75. package/lib/download/ui/useFigureTask.d.ts.map +1 -0
  76. package/lib/download/ui/useFigureTask.js +27 -0
  77. package/lib/download/ui/useFigureTask.js.map +1 -0
  78. package/lib/host/core/connectHost.d.ts +75 -0
  79. package/lib/host/core/connectHost.d.ts.map +1 -0
  80. package/lib/host/core/connectHost.js +171 -0
  81. package/lib/host/core/connectHost.js.map +1 -0
  82. package/lib/host/core/hostStructure.d.ts +52 -0
  83. package/lib/host/core/hostStructure.d.ts.map +1 -0
  84. package/lib/host/core/hostStructure.js +106 -0
  85. package/lib/host/core/hostStructure.js.map +1 -0
  86. package/lib/host/core/index.d.ts +9 -0
  87. package/lib/host/core/index.d.ts.map +1 -0
  88. package/lib/host/core/index.js +5 -0
  89. package/lib/host/core/index.js.map +1 -0
  90. package/lib/host/core/kinds.d.ts +73 -0
  91. package/lib/host/core/kinds.d.ts.map +1 -0
  92. package/lib/host/core/kinds.js +88 -0
  93. package/lib/host/core/kinds.js.map +1 -0
  94. package/lib/host/core/protocol.d.ts +134 -0
  95. package/lib/host/core/protocol.d.ts.map +1 -0
  96. package/lib/host/core/protocol.js +92 -0
  97. package/lib/host/core/protocol.js.map +1 -0
  98. package/lib/host/testing/sciPeaks.d.ts +21 -0
  99. package/lib/host/testing/sciPeaks.d.ts.map +1 -0
  100. package/lib/host/testing/sciPeaks.js +62 -0
  101. package/lib/host/testing/sciPeaks.js.map +1 -0
  102. package/lib/host/testing/standInHost.d.ts +47 -0
  103. package/lib/host/testing/standInHost.d.ts.map +1 -0
  104. package/lib/host/testing/standInHost.js +85 -0
  105. package/lib/host/testing/standInHost.js.map +1 -0
  106. package/lib/host/ui/index.d.ts +3 -0
  107. package/lib/host/ui/index.d.ts.map +1 -0
  108. package/lib/host/ui/index.js +2 -0
  109. package/lib/host/ui/index.js.map +1 -0
  110. package/lib/host/ui/useHostConnection.d.ts +36 -0
  111. package/lib/host/ui/useHostConnection.d.ts.map +1 -0
  112. package/lib/host/ui/useHostConnection.js +62 -0
  113. package/lib/host/ui/useHostConnection.js.map +1 -0
  114. package/lib/host-testing.d.ts +8 -0
  115. package/lib/host-testing.d.ts.map +1 -0
  116. package/lib/host-testing.js +7 -0
  117. package/lib/host-testing.js.map +1 -0
  118. package/lib/host.d.ts +11 -0
  119. package/lib/host.d.ts.map +1 -0
  120. package/lib/host.js +11 -0
  121. package/lib/host.js.map +1 -0
  122. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  123. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  124. package/lib/locales/de.json +28 -2
  125. package/lib/locales/en.json +28 -2
  126. package/lib/locales/es.json +28 -2
  127. package/lib/locales/fr.json +28 -2
  128. package/lib/locales/it.json +28 -2
  129. package/lib/molecule3d/core/index.d.ts +1 -1
  130. package/lib/molecule3d/core/index.d.ts.map +1 -1
  131. package/lib/molecule3d/core/index.js +1 -1
  132. package/lib/molecule3d/core/index.js.map +1 -1
  133. package/lib/molecule3d/core/settings.d.ts +15 -0
  134. package/lib/molecule3d/core/settings.d.ts.map +1 -1
  135. package/lib/molecule3d/core/settings.js +25 -0
  136. package/lib/molecule3d/core/settings.js.map +1 -1
  137. package/lib/molecule3d/ui/Molecule3DExport.d.ts +3 -0
  138. package/lib/molecule3d/ui/Molecule3DExport.d.ts.map +1 -1
  139. package/lib/molecule3d/ui/Molecule3DExport.js +10 -16
  140. package/lib/molecule3d/ui/Molecule3DExport.js.map +1 -1
  141. package/lib/molecule3d/ui/Molecule3DOptions.d.ts +3 -1
  142. package/lib/molecule3d/ui/Molecule3DOptions.d.ts.map +1 -1
  143. package/lib/molecule3d/ui/Molecule3DOptions.js +22 -6
  144. package/lib/molecule3d/ui/Molecule3DOptions.js.map +1 -1
  145. package/lib/molecule3d/ui/MoleculeCanvas3D.d.ts.map +1 -1
  146. package/lib/molecule3d/ui/MoleculeCanvas3D.js +4 -3
  147. package/lib/molecule3d/ui/MoleculeCanvas3D.js.map +1 -1
  148. package/lib/molecule3d/ui/exportMoleculeImage.d.ts +9 -0
  149. package/lib/molecule3d/ui/exportMoleculeImage.d.ts.map +1 -1
  150. package/lib/molecule3d/ui/exportMoleculeImage.js +15 -0
  151. package/lib/molecule3d/ui/exportMoleculeImage.js.map +1 -1
  152. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts +2 -1
  153. package/lib/molecule3d/ui/moleculeViewer3DProps.d.ts.map +1 -1
  154. package/lib/molecule3d/ui/useImageExport.d.ts +7 -1
  155. package/lib/molecule3d/ui/useImageExport.d.ts.map +1 -1
  156. package/lib/molecule3d/ui/useImageExport.js +12 -3
  157. package/lib/molecule3d/ui/useImageExport.js.map +1 -1
  158. package/lib/overlay/ui/FigureBar.d.ts +41 -0
  159. package/lib/overlay/ui/FigureBar.d.ts.map +1 -0
  160. package/lib/overlay/ui/FigureBar.js +43 -0
  161. package/lib/overlay/ui/FigureBar.js.map +1 -0
  162. package/lib/overlay/ui/OverlayBar.d.ts +13 -4
  163. package/lib/overlay/ui/OverlayBar.d.ts.map +1 -1
  164. package/lib/overlay/ui/OverlayBar.js +2 -2
  165. package/lib/overlay/ui/OverlayBar.js.map +1 -1
  166. package/lib/overlay/ui/OverlayBarStrip.d.ts +2 -1
  167. package/lib/overlay/ui/OverlayBarStrip.d.ts.map +1 -1
  168. package/lib/overlay/ui/OverlayBarStrip.js +9 -5
  169. package/lib/overlay/ui/OverlayBarStrip.js.map +1 -1
  170. package/lib/overlay/ui/OverlayIconButton.d.ts +8 -2
  171. package/lib/overlay/ui/OverlayIconButton.d.ts.map +1 -1
  172. package/lib/overlay/ui/OverlayIconButton.js +2 -1
  173. package/lib/overlay/ui/OverlayIconButton.js.map +1 -1
  174. package/lib/overlay/ui/index.d.ts +4 -0
  175. package/lib/overlay/ui/index.d.ts.map +1 -1
  176. package/lib/overlay/ui/index.js +2 -0
  177. package/lib/overlay/ui/index.js.map +1 -1
  178. package/lib/overlay/ui/overlayBarShape.d.ts +18 -0
  179. package/lib/overlay/ui/overlayBarShape.d.ts.map +1 -1
  180. package/lib/overlay/ui/overlayStyles.d.ts +30 -0
  181. package/lib/overlay/ui/overlayStyles.d.ts.map +1 -1
  182. package/lib/overlay/ui/overlayStyles.js +52 -0
  183. package/lib/overlay/ui/overlayStyles.js.map +1 -1
  184. package/lib/overlay/ui/useFigureBarAwake.d.ts +12 -0
  185. package/lib/overlay/ui/useFigureBarAwake.d.ts.map +1 -0
  186. package/lib/overlay/ui/useFigureBarAwake.js +51 -0
  187. package/lib/overlay/ui/useFigureBarAwake.js.map +1 -0
  188. package/lib/periodic/core/elements.d.ts +6 -3
  189. package/lib/periodic/core/elements.d.ts.map +1 -1
  190. package/lib/periodic/core/elements.js +9 -3
  191. package/lib/periodic/core/elements.js.map +1 -1
  192. package/lib/periodic/core/index.d.ts +2 -2
  193. package/lib/periodic/core/index.d.ts.map +1 -1
  194. package/lib/periodic/core/index.js +1 -1
  195. package/lib/periodic/core/index.js.map +1 -1
  196. package/lib/periodic/core/layout.d.ts +33 -3
  197. package/lib/periodic/core/layout.d.ts.map +1 -1
  198. package/lib/periodic/core/layout.js +14 -0
  199. package/lib/periodic/core/layout.js.map +1 -1
  200. package/lib/periodic/ui/BlockStrip.d.ts +20 -0
  201. package/lib/periodic/ui/BlockStrip.d.ts.map +1 -0
  202. package/lib/periodic/ui/BlockStrip.js +48 -0
  203. package/lib/periodic/ui/BlockStrip.js.map +1 -0
  204. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  205. package/lib/periodic/ui/ElementCell.js +9 -8
  206. package/lib/periodic/ui/ElementCell.js.map +1 -1
  207. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  208. package/lib/periodic/ui/PeriodicTable.js +3 -1
  209. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  210. package/lib/periodic/ui/PeriodicTableHelp.d.ts +33 -0
  211. package/lib/periodic/ui/PeriodicTableHelp.d.ts.map +1 -0
  212. package/lib/periodic/ui/PeriodicTableHelp.js +107 -0
  213. package/lib/periodic/ui/PeriodicTableHelp.js.map +1 -0
  214. package/lib/periodic/ui/cellStyles.d.ts +3 -2
  215. package/lib/periodic/ui/cellStyles.d.ts.map +1 -1
  216. package/lib/periodic/ui/cellStyles.js +3 -2
  217. package/lib/periodic/ui/cellStyles.js.map +1 -1
  218. package/lib/periodic/ui/index.d.ts +2 -0
  219. package/lib/periodic/ui/index.d.ts.map +1 -1
  220. package/lib/periodic/ui/index.js +1 -0
  221. package/lib/periodic/ui/index.js.map +1 -1
  222. package/lib/periodic/ui/periodicTableProps.d.ts +5 -4
  223. package/lib/periodic/ui/periodicTableProps.d.ts.map +1 -1
  224. package/lib/periodic/ui/tableLayout.d.ts +3 -1
  225. package/lib/periodic/ui/tableLayout.d.ts.map +1 -1
  226. package/lib/periodic/ui/tableLayout.js +8 -3
  227. package/lib/periodic/ui/tableLayout.js.map +1 -1
  228. package/lib/share/ui/ShareDialogContent.d.ts +3 -2
  229. package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
  230. package/lib/share/ui/ShareDialogContent.js +5 -6
  231. package/lib/share/ui/ShareDialogContent.js.map +1 -1
  232. package/lib/share/ui/ShareLinkBar.d.ts +8 -2
  233. package/lib/share/ui/ShareLinkBar.d.ts.map +1 -1
  234. package/lib/share/ui/ShareLinkBar.js +6 -5
  235. package/lib/share/ui/ShareLinkBar.js.map +1 -1
  236. package/lib/share/ui/SharePreview.d.ts.map +1 -1
  237. package/lib/share/ui/SharePreview.js +2 -2
  238. package/lib/share/ui/SharePreview.js.map +1 -1
  239. package/lib/structure/ui/StructurePicturePane.js +3 -3
  240. package/lib/structure/ui/StructurePicturePane.js.map +1 -1
  241. package/package.json +4 -1
  242. package/src/clipboard/core/index.ts +0 -1
  243. package/src/clipboard/ui/CopyButton.tsx +12 -1
  244. package/src/color/core/index.ts +9 -0
  245. package/src/color/core/interpolate.ts +51 -22
  246. package/src/color/core/stops.ts +98 -0
  247. package/src/color/core/tone.ts +79 -0
  248. package/src/color/core/uniform.ts +67 -0
  249. package/src/color/ui/ColorScaleEditor.tsx +115 -141
  250. package/src/color/ui/ColorScaleSelect.tsx +160 -36
  251. package/src/color/ui/ColorScaleTone.tsx +103 -0
  252. package/src/color/ui/ColorScaleTrack.tsx +239 -0
  253. package/src/download/core/copyFigure.ts +20 -0
  254. package/src/download/core/figureHtml.ts +1 -0
  255. package/src/download/core/figureHtmlMarkup.ts +14 -1
  256. package/src/download/core/index.ts +2 -0
  257. package/src/download/ui/FigureDownloadPanel.tsx +8 -11
  258. package/src/download/ui/figureDownloadHint.ts +1 -2
  259. package/src/download/ui/index.ts +0 -1
  260. package/src/download/ui/useFigureActions.ts +6 -42
  261. package/src/download/ui/useFigureTask.ts +46 -0
  262. package/src/host/core/connectHost.ts +240 -0
  263. package/src/host/core/hostStructure.ts +129 -0
  264. package/src/host/core/index.ts +46 -0
  265. package/src/host/core/kinds.ts +128 -0
  266. package/src/host/core/protocol.ts +197 -0
  267. package/src/host/testing/sciPeaks.ts +66 -0
  268. package/src/host/testing/standInHost.ts +116 -0
  269. package/src/host/ui/index.ts +2 -0
  270. package/src/host/ui/useHostConnection.ts +85 -0
  271. package/src/host-testing.ts +18 -0
  272. package/src/host.ts +11 -0
  273. package/src/locales/de.json +28 -2
  274. package/src/locales/en.json +28 -2
  275. package/src/locales/es.json +28 -2
  276. package/src/locales/fr.json +28 -2
  277. package/src/locales/it.json +28 -2
  278. package/src/molecule3d/core/index.ts +2 -0
  279. package/src/molecule3d/core/settings.ts +35 -0
  280. package/src/molecule3d/ui/Molecule3DExport.tsx +18 -17
  281. package/src/molecule3d/ui/Molecule3DOptions.tsx +51 -3
  282. package/src/molecule3d/ui/MoleculeCanvas3D.tsx +11 -3
  283. package/src/molecule3d/ui/exportMoleculeImage.ts +20 -0
  284. package/src/molecule3d/ui/moleculeViewer3DProps.ts +2 -1
  285. package/src/molecule3d/ui/useImageExport.ts +25 -3
  286. package/src/overlay/ui/FigureBar.tsx +96 -0
  287. package/src/overlay/ui/OverlayBar.tsx +15 -5
  288. package/src/overlay/ui/OverlayBarStrip.tsx +34 -22
  289. package/src/overlay/ui/OverlayIconButton.tsx +10 -2
  290. package/src/overlay/ui/index.ts +4 -0
  291. package/src/overlay/ui/overlayBarShape.ts +19 -0
  292. package/src/overlay/ui/overlayStyles.ts +61 -0
  293. package/src/overlay/ui/useFigureBarAwake.ts +56 -0
  294. package/src/periodic/core/elements.ts +8 -3
  295. package/src/periodic/core/index.ts +8 -1
  296. package/src/periodic/core/layout.ts +45 -3
  297. package/src/periodic/ui/BlockStrip.tsx +81 -0
  298. package/src/periodic/ui/ElementCell.tsx +9 -8
  299. package/src/periodic/ui/PeriodicTable.tsx +4 -1
  300. package/src/periodic/ui/PeriodicTableHelp.tsx +205 -0
  301. package/src/periodic/ui/cellStyles.ts +3 -2
  302. package/src/periodic/ui/index.ts +5 -0
  303. package/src/periodic/ui/periodicTableProps.ts +5 -4
  304. package/src/periodic/ui/tableLayout.ts +9 -3
  305. package/src/share/ui/ShareDialogContent.tsx +9 -13
  306. package/src/share/ui/ShareLinkBar.tsx +16 -4
  307. package/src/share/ui/SharePreview.tsx +6 -2
  308. package/src/structure/ui/StructurePicturePane.tsx +3 -3
  309. package/styles/chrome.css +43 -32
  310. package/lib/clipboard/core/copyPng.d.ts +0 -13
  311. package/lib/clipboard/core/copyPng.d.ts.map +0 -1
  312. package/lib/clipboard/core/copyPng.js +0 -19
  313. package/lib/clipboard/core/copyPng.js.map +0 -1
  314. package/src/clipboard/core/copyPng.ts +0 -20
@@ -0,0 +1,128 @@
1
+ /**
2
+ * The kinds of data a tool takes from a host, checked the way
3
+ * pipeline.cheminfo.org checks what its workers pass to each other: a dotted
4
+ * name, a JSON Schema, and ajv.
5
+ *
6
+ * The names are pipeline's (`chem.molfile.2d`, `chem.ocl.id`…), plus
7
+ * `chem.smiles`, which pipeline lists among its future schemas. A name with
8
+ * children is a category whose schema every child satisfies, so `chem.molfile`
9
+ * reads `chem.molfile.2d` and `chem.molfile.3d.h.energy` alike; the fields a
10
+ * tool does not read are stripped rather than refused, so a host sending more
11
+ * than it needs to is never turned away.
12
+ */
13
+
14
+ import type { JSONSchemaType, ValidateFunction } from 'ajv';
15
+ import { Ajv } from 'ajv';
16
+
17
+ /** A molfile, V2000 or V3000. */
18
+ export interface MolfileValue {
19
+ molfile: string;
20
+ }
21
+
22
+ /** An OpenChemLib idcode and its 2D coordinates. */
23
+ export interface OclIdValue {
24
+ idCode: string;
25
+ coordinates: string;
26
+ }
27
+
28
+ /** A SMILES. */
29
+ export interface SmilesValue {
30
+ smiles: string;
31
+ }
32
+
33
+ /** A value that passed its kind's schema, tagged with that kind. */
34
+ export type CheckedValue =
35
+ | { kind: 'chem.molfile'; value: MolfileValue }
36
+ | { kind: 'chem.ocl.id'; value: OclIdValue }
37
+ | { kind: 'chem.smiles'; value: SmilesValue };
38
+
39
+ /** The name of a kind a tool can read. */
40
+ export type KnownKind = CheckedValue['kind'];
41
+
42
+ /** The value a kind a tool can read carries. */
43
+ type ValueOf<K extends KnownKind> = Extract<CheckedValue, { kind: K }>['value'];
44
+
45
+ /** What checking a value gave. */
46
+ export type CheckOutcome =
47
+ { valid: true; checked: CheckedValue } | { valid: false; errors: string[] };
48
+
49
+ /**
50
+ * Whether a kind falls under another: the same name, or a name under it on a
51
+ * segment boundary. A list (`…[]`) never stands for one value, nor one value
52
+ * for a list.
53
+ * @param accepted - The kind a tool declares, e.g. `'chem.molfile'`.
54
+ * @param offered - The kind an item carries, e.g. `'chem.molfile.2d'`.
55
+ * @returns `true` when an item of `offered` can be read as `accepted`.
56
+ */
57
+ export function kindAccepts(accepted: string, offered: string): boolean {
58
+ const list = accepted.endsWith('[]');
59
+ if (offered.endsWith('[]') !== list) return false;
60
+ const base = list ? accepted.slice(0, -2) : accepted;
61
+ const name = list ? offered.slice(0, -2) : offered;
62
+ return name === base || name.startsWith(`${base}.`);
63
+ }
64
+
65
+ /**
66
+ * Whether there is a schema for a kind.
67
+ * @param kind - A kind name.
68
+ * @returns `true` for a {@link KnownKind}.
69
+ */
70
+ export function isKnownKind(kind: string): kind is KnownKind {
71
+ return Object.hasOwn(VALIDATORS, kind);
72
+ }
73
+
74
+ /**
75
+ * Check a value against a kind's schema.
76
+ *
77
+ * Fields the schema does not name are removed from `value` itself, so what the
78
+ * site reads afterwards is exactly what the kind promises.
79
+ * @param kind - The kind to read the value as.
80
+ * @param value - The value, as received.
81
+ * @returns The value tagged with its kind, or the errors as pipeline writes
82
+ * them, `'/idCode: must be string'`.
83
+ */
84
+ export function checkValue(kind: KnownKind, value: unknown): CheckOutcome {
85
+ const validate: ValidateFunction = VALIDATORS[kind];
86
+ if (validate(value)) {
87
+ // The validator of `kind` just vouched for the shape of `value`.
88
+ return { valid: true, checked: { kind, value } as CheckedValue };
89
+ }
90
+ const errors: string[] = [];
91
+ for (const error of validate.errors ?? []) {
92
+ errors.push(`${error.instancePath || '/'}: ${error.message ?? 'invalid'}`);
93
+ }
94
+ return { valid: false, errors };
95
+ }
96
+
97
+ const MOLFILE_SCHEMA: JSONSchemaType<MolfileValue> = {
98
+ type: 'object',
99
+ properties: { molfile: { type: 'string' } },
100
+ required: ['molfile'],
101
+ additionalProperties: false,
102
+ };
103
+
104
+ const OCL_ID_SCHEMA: JSONSchemaType<OclIdValue> = {
105
+ type: 'object',
106
+ properties: {
107
+ idCode: { type: 'string' },
108
+ coordinates: { type: 'string' },
109
+ },
110
+ required: ['idCode', 'coordinates'],
111
+ additionalProperties: false,
112
+ };
113
+
114
+ const SMILES_SCHEMA: JSONSchemaType<SmilesValue> = {
115
+ type: 'object',
116
+ properties: { smiles: { type: 'string' } },
117
+ required: ['smiles'],
118
+ additionalProperties: false,
119
+ };
120
+
121
+ // `removeAdditional` strips what `additionalProperties: false` would refuse.
122
+ const ajv = new Ajv({ removeAdditional: true });
123
+
124
+ const VALIDATORS: { [K in KnownKind]: ValidateFunction<ValueOf<K>> } = {
125
+ 'chem.molfile': ajv.compile(MOLFILE_SCHEMA),
126
+ 'chem.ocl.id': ajv.compile(OCL_ID_SCHEMA),
127
+ 'chem.smiles': ajv.compile(SMILES_SCHEMA),
128
+ };
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The cheminfo host protocol, version 1: what a page that frames a tool — an
3
+ * electronic lab notebook, a course — and the tool say to each other.
4
+ *
5
+ * Two messages travel between the windows — `hello`, from the tool, and
6
+ * `connect`, the host's answer carrying a private `MessagePort` — and everything
7
+ * else goes through that port: the host's requests (`load`, `get`) and the
8
+ * tool's `result` for each. This module only spells the messages, and reads
9
+ * what arrives as the untrusted input it is.
10
+ */
11
+
12
+ /** The version a tool speaks; a `connect` naming another is ignored. */
13
+ export const PROTOCOL_VERSION = 1;
14
+
15
+ /** Posted to the parent frame or the opener at start-up: who the tool is. */
16
+ export const HELLO_TYPE = 'cheminfo:hello';
17
+
18
+ /** The host's answer to `hello`, carrying the port the rest goes through. */
19
+ export const CONNECT_TYPE = 'cheminfo:connect';
20
+
21
+ /** How many items one request may carry; the rest are dropped. */
22
+ export const MAX_ITEMS = 64;
23
+
24
+ /** How long a label may be, in characters; a longer one is cut. */
25
+ export const MAX_LABEL_LENGTH = 80;
26
+
27
+ /** One typed value crossing the frame boundary. */
28
+ export interface HostItem {
29
+ /** A kind name, e.g. `'chem.molfile.2d'`. */
30
+ kind: string;
31
+ /** Checked against the kind before anything reads it. */
32
+ value: unknown;
33
+ /**
34
+ * The host's name for it, opaque here, e.g. an attachment.
35
+ * @default undefined
36
+ */
37
+ id?: string;
38
+ /**
39
+ * The tool input it is meant for.
40
+ * @default undefined — any input that accepts its kind
41
+ */
42
+ key?: string;
43
+ /**
44
+ * What a notice calls it, e.g. `'Ethylbenzene'`.
45
+ * @default undefined
46
+ */
47
+ label?: string;
48
+ }
49
+
50
+ /** One value the tool takes. */
51
+ export interface ToolInput {
52
+ key: string;
53
+ /** Kinds in order of preference; a category accepts every kind under it. */
54
+ kinds: readonly string[];
55
+ }
56
+
57
+ /** One value the tool can hand back. */
58
+ export interface ToolOutput {
59
+ key: string;
60
+ kind: string;
61
+ }
62
+
63
+ /** What the tool is and takes, as `hello` announces it. */
64
+ export interface ToolInterface {
65
+ name: string;
66
+ inputs: readonly ToolInput[];
67
+ outputs: readonly ToolOutput[];
68
+ }
69
+
70
+ /** What `hello` carries: public, readable by any origin. */
71
+ export interface HelloMessage {
72
+ type: typeof HELLO_TYPE;
73
+ version: typeof PROTOCOL_VERSION;
74
+ tool: ToolInterface & {
75
+ /** The tool's origin, e.g. `'https://3d.cheminfo.org'`. */
76
+ origin: string;
77
+ /** The build that is running, as the About states it, or `null`. */
78
+ build: unknown;
79
+ };
80
+ }
81
+
82
+ /** `load`: the host's data, for one document. */
83
+ export interface LoadRequest {
84
+ type: 'load';
85
+ id: string;
86
+ /** What the data belongs to; the same document twice is applied once. */
87
+ document: string;
88
+ items: HostItem[];
89
+ }
90
+
91
+ /** `get`: the current value of some outputs. */
92
+ export interface GetRequest {
93
+ type: 'get';
94
+ id: string;
95
+ keys: string[];
96
+ }
97
+
98
+ /** A request the host sends through the port. */
99
+ export type HostRequest = LoadRequest | GetRequest;
100
+
101
+ /** Why a request could not be served. */
102
+ export type HostErrorCode = 'invalid' | 'unreadable' | 'no-input' | 'no-output';
103
+
104
+ /** What a failed request is answered with. */
105
+ export interface HostError {
106
+ code: HostErrorCode;
107
+ /** One sentence, in English. */
108
+ message: string;
109
+ /**
110
+ * What the schema found, as `'chem.ocl.id /idCode: must be string'`.
111
+ * @default undefined
112
+ */
113
+ errors?: string[];
114
+ }
115
+
116
+ /** Which item an input took. */
117
+ export interface UsedItem {
118
+ key: string;
119
+ kind: string;
120
+ }
121
+
122
+ /** The answer to one request, carrying its `id`. */
123
+ export type ResultMessage =
124
+ | { type: 'result'; id: string; ok: true; used: UsedItem[] }
125
+ | { type: 'result'; id: string; ok: false; error: HostError };
126
+
127
+ /**
128
+ * Read a message that arrived through the port.
129
+ *
130
+ * Anything that is not a well-formed `load` or `get` is `null`, and goes
131
+ * unanswered: an unknown request is a newer host talking, not an error. An item
132
+ * without a string `kind` is dropped, and a label is cut to
133
+ * {@link MAX_LABEL_LENGTH}.
134
+ * @param data - What the port delivered.
135
+ * @returns The request, or `null`.
136
+ */
137
+ export function parseRequest(data: unknown): HostRequest | null {
138
+ if (!isRecord(data) || typeof data.id !== 'string') return null;
139
+ if (data.type === 'load') {
140
+ if (typeof data.document !== 'string' || !Array.isArray(data.items)) {
141
+ return null;
142
+ }
143
+ return {
144
+ type: 'load',
145
+ id: data.id,
146
+ document: data.document,
147
+ items: readItems(data.items),
148
+ };
149
+ }
150
+ if (data.type === 'get') {
151
+ return { type: 'get', id: data.id, keys: readStrings(data.keys) };
152
+ }
153
+ return null;
154
+ }
155
+
156
+ /**
157
+ * Whether a window message is the host's answer to `hello`, in this version.
158
+ * @param data - What the window delivered.
159
+ * @returns `true` for a `connect` of {@link PROTOCOL_VERSION}.
160
+ */
161
+ export function isConnect(data: unknown): boolean {
162
+ return (
163
+ isRecord(data) &&
164
+ data.type === CONNECT_TYPE &&
165
+ data.version === PROTOCOL_VERSION
166
+ );
167
+ }
168
+
169
+ function readItems(values: readonly unknown[]): HostItem[] {
170
+ const items: HostItem[] = [];
171
+ const count = Math.min(values.length, MAX_ITEMS);
172
+ for (let index = 0; index < count; index++) {
173
+ const value = values[index];
174
+ if (!isRecord(value) || typeof value.kind !== 'string') continue;
175
+ const item: HostItem = { kind: value.kind, value: value.value };
176
+ if (typeof value.id === 'string') item.id = value.id;
177
+ if (typeof value.key === 'string') item.key = value.key;
178
+ if (typeof value.label === 'string') {
179
+ item.label = value.label.slice(0, MAX_LABEL_LENGTH);
180
+ }
181
+ items.push(item);
182
+ }
183
+ return items;
184
+ }
185
+
186
+ function readStrings(value: unknown): string[] {
187
+ if (!Array.isArray(value)) return [];
188
+ const strings: string[] = [];
189
+ for (const entry of value) {
190
+ if (typeof entry === 'string') strings.push(entry);
191
+ }
192
+ return strings;
193
+ }
194
+
195
+ function isRecord(value: unknown): value is Record<string, unknown> {
196
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
197
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * What SciPeaks sends a tool, byte for byte: the `load` its tool tab —
3
+ * visualizer-helper's `eln/toolTab` — writes for the ethylbenzene sample its own
4
+ * tests read (`eln/__tests__/data/sample.json`). Both repositories pin this
5
+ * message, so a change on either side that the other does not expect fails a
6
+ * test rather than a notebook.
7
+ *
8
+ * The sample is a real one, and so are its quirks: its molfile is V2000 with
9
+ * every atom at the origin, its title is blank so the label is its first name,
10
+ * and its formula travels as a kind no structure input reads.
11
+ */
12
+
13
+ import type { LoadRequest } from '../core/protocol.ts';
14
+
15
+ /** How SciPeaks' tool tab introduces itself in `connect`. */
16
+ export const SCIPEAKS_HOST_NAME = 'SciPeaks';
17
+
18
+ /** The molfile SciPeaks stores for ethylbenzene: every atom at the origin. */
19
+ export const SCIPEAKS_ETHYLBENZENE_MOLFILE = [
20
+ '',
21
+ 'Actelion Java MolfileCreator 1.0',
22
+ '',
23
+ ' 8 8 0 0 0 0 0 0 0 0999 V2000',
24
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
25
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
26
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
27
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
28
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
29
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
30
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
31
+ ' 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0',
32
+ ' 1 2 1 0 0 0 0',
33
+ ' 1 3 1 0 0 0 0',
34
+ ' 2 4 2 0 0 0 0',
35
+ ' 2 5 1 0 0 0 0',
36
+ ' 4 6 1 0 0 0 0',
37
+ ' 5 7 2 0 0 0 0',
38
+ ' 6 8 2 0 0 0 0',
39
+ ' 7 8 1 0 0 0 0',
40
+ 'M END',
41
+ '',
42
+ ].join('\n');
43
+
44
+ /** The first `load` the tool tab sends for that sample, with `accept`. */
45
+ export const SCIPEAKS_ETHYLBENZENE_LOAD: LoadRequest & { accept: string[] } = {
46
+ type: 'load',
47
+ id: 'load-1',
48
+ document: 'eln:1315a6d895f431497538cb6d834feab5',
49
+ items: [
50
+ {
51
+ kind: 'chem.molfile.2d',
52
+ value: { molfile: SCIPEAKS_ETHYLBENZENE_MOLFILE },
53
+ label: 'Ethylbenzene',
54
+ },
55
+ {
56
+ kind: 'chem.ocl.id',
57
+ value: {
58
+ idCode: 'daD@@DjUZxHH@@',
59
+ coordinates: String.raw`!BbOvw?_x@?g?~_{\Bm?t`,
60
+ },
61
+ label: 'Ethylbenzene',
62
+ },
63
+ { kind: 'chem.mf', value: { mf: 'C8H10' } },
64
+ ],
65
+ accept: [],
66
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * A whole host, for a tool's end-to-end tests: a page on another origin that
3
+ * frames the tool, answers its `hello` with a private port, sends it requests,
4
+ * and records what comes back.
5
+ *
6
+ * It is written the way an electronic lab notebook would write one: it checks
7
+ * that `hello` came from its frame and from the tool's origin, and connects
8
+ * only then. A real server serves it on `127.0.0.1` while the tool runs on
9
+ * `localhost`, so every message crosses a real origin boundary — and it has to
10
+ * be a real server: Chrome takes a page that Playwright answers itself for a
11
+ * public one, and a public page may not frame a local site.
12
+ */
13
+
14
+ import type { Server } from 'node:http';
15
+ import { createServer } from 'node:http';
16
+ import type { AddressInfo } from 'node:net';
17
+
18
+ import type { HelloMessage } from '../core/protocol.ts';
19
+
20
+ /** A running stand-in host. */
21
+ export interface StandInHost {
22
+ /** Where the host page is served, e.g. `http://127.0.0.1:53211`. */
23
+ origin: string;
24
+ /**
25
+ * The address of a host page that frames `toolUrl` in `#tool` and, once the
26
+ * tool has said `hello`, sends `requests` through the port it hands over.
27
+ * @param toolUrl - The tool's address, `?embed=1` included.
28
+ * @param requests - What to send on the port, in order.
29
+ * @returns The address to open.
30
+ */
31
+ pageUrl: (toolUrl: string, requests: readonly unknown[]) => string;
32
+ /** Stop serving. */
33
+ close: () => Promise<void>;
34
+ }
35
+
36
+ /** What the host page recorded, read with {@link readStandInHostLog}. */
37
+ export interface StandInHostLog {
38
+ /** The tool's `hello`, or `null` before it came. */
39
+ hello: HelloMessage | null;
40
+ /** Every message the tool sent on the port, in order. */
41
+ results: unknown[];
42
+ }
43
+
44
+ /**
45
+ * Serve the host page on a free port of `127.0.0.1`.
46
+ * @returns The host; close it when the tests are done.
47
+ */
48
+ export async function startStandInHost(): Promise<StandInHost> {
49
+ const server: Server = createServer((_request, response) => {
50
+ response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
51
+ response.end(HOST_PAGE);
52
+ });
53
+ await new Promise<void>((resolve) => {
54
+ server.listen(0, '127.0.0.1', resolve);
55
+ });
56
+ const { port } = server.address() as AddressInfo;
57
+ const origin = `http://127.0.0.1:${port}`;
58
+
59
+ return {
60
+ origin,
61
+ pageUrl: (toolUrl, requests) => {
62
+ const setup = encodeURIComponent(JSON.stringify({ toolUrl, requests }));
63
+ return `${origin}/?setup=${setup}`;
64
+ },
65
+ close: () =>
66
+ new Promise<void>((resolve, reject) => {
67
+ server.close((error) => {
68
+ if (error) reject(error);
69
+ else resolve();
70
+ });
71
+ }),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Read what the host page recorded. Runs in the page: hand it to Playwright's
77
+ * `page.evaluate`, as `page.evaluate(readStandInHostLog)`.
78
+ * @returns The log.
79
+ */
80
+ export function readStandInHostLog(): StandInHostLog {
81
+ return (globalThis as unknown as { hostLog: StandInHostLog }).hostLog;
82
+ }
83
+
84
+ /**
85
+ * The page. Its setup is its own `?setup=`, so one page serves every test, and
86
+ * it listens before it frames, so the tool's `hello` cannot be missed.
87
+ */
88
+ const HOST_PAGE = `<!doctype html>
89
+ <html lang="en">
90
+ <head><meta charset="utf-8"><title>Stand-in host</title></head>
91
+ <body>
92
+ <iframe id="tool" title="The framed tool" style="width: 1280px; height: 800px"></iframe>
93
+ <script>
94
+ const { toolUrl, requests } = JSON.parse(new URLSearchParams(location.search).get('setup'));
95
+ const toolOrigin = new URL(toolUrl).origin;
96
+ const frame = document.querySelector('#tool');
97
+ globalThis.hostLog = { hello: null, results: [] };
98
+ globalThis.addEventListener('message', (event) => {
99
+ if (event.source !== frame.contentWindow || event.origin !== toolOrigin) return;
100
+ if (event.data?.type !== 'cheminfo:hello') return;
101
+ globalThis.hostLog.hello = event.data;
102
+ const channel = new MessageChannel();
103
+ channel.port1.onmessage = (answer) => {
104
+ globalThis.hostLog.results.push(answer.data);
105
+ };
106
+ frame.contentWindow.postMessage(
107
+ { type: 'cheminfo:connect', version: 1, host: { name: 'Stand-in host' } },
108
+ toolOrigin,
109
+ [channel.port2],
110
+ );
111
+ for (const request of requests) channel.port1.postMessage(request);
112
+ });
113
+ frame.src = toolUrl;
114
+ </script>
115
+ </body>
116
+ </html>`;
@@ -0,0 +1,2 @@
1
+ export type { HostHandlers } from './useHostConnection.ts';
2
+ export { useHostConnection } from './useHostConnection.ts';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Connect a tool to the page that frames it, when there is one.
3
+ *
4
+ * The protocol and its schemas are fetched only then, so a visitor on the site
5
+ * itself never downloads them.
6
+ */
7
+
8
+ import { useEffect, useRef } from 'react';
9
+
10
+ import type { LoadedInput } from '../core/connectHost.ts';
11
+ import type { ToolInterface } from '../core/protocol.ts';
12
+
13
+ /** What a tool does with what its host sends, and what it says about itself. */
14
+ export interface HostHandlers {
15
+ /**
16
+ * Show the inputs of a new document. Throwing means they could not be read:
17
+ * the error's message goes back to the host, and nothing should have changed
18
+ * on screen.
19
+ */
20
+ load: (inputs: readonly LoadedInput[]) => void;
21
+ /**
22
+ * The build that is running — `BUILD_INFO` from `react-cheminfo/build-info`
23
+ * — so a host can record which version produced what it stores.
24
+ * @default null
25
+ */
26
+ build?: unknown;
27
+ }
28
+
29
+ /** Once per page: React may mount the caller twice, the host is greeted once. */
30
+ let started = false;
31
+
32
+ /**
33
+ * Introduce the tool to its parent frame, or to the window that opened it, and
34
+ * serve that host for the life of the page.
35
+ *
36
+ * Call it after the hook that applies the address: effects run in order, so
37
+ * the address has been applied by the time the host is greeted, and a host's
38
+ * structure replaces the one the frame's link named rather than the reverse.
39
+ * The latest `load` is always the one called, so it may close over state.
40
+ * @param tool - What the tool takes and gives.
41
+ * @param handlers - See {@link HostHandlers}.
42
+ */
43
+ export function useHostConnection(
44
+ tool: ToolInterface,
45
+ handlers: HostHandlers,
46
+ ): void {
47
+ const latest = useRef(handlers);
48
+ useEffect(() => {
49
+ latest.current = handlers;
50
+ });
51
+
52
+ useEffect(() => {
53
+ if (started) return;
54
+ const host = hostWindow();
55
+ if (host === null) return;
56
+ started = true;
57
+ import('../core/connectHost.ts')
58
+ .then(({ connectHost }) => {
59
+ connectHost({
60
+ host,
61
+ listen: (listener) => {
62
+ globalThis.addEventListener('message', listener);
63
+ return () => {
64
+ globalThis.removeEventListener('message', listener);
65
+ };
66
+ },
67
+ tool,
68
+ origin: globalThis.location.origin,
69
+ build: latest.current.build ?? null,
70
+ load: (inputs) => {
71
+ latest.current.load(inputs);
72
+ },
73
+ });
74
+ })
75
+ .catch(() => {
76
+ started = false;
77
+ });
78
+ }, [tool]);
79
+ }
80
+
81
+ function hostWindow(): Window | null {
82
+ const { parent, opener } = globalThis.window;
83
+ if (parent !== globalThis.window) return parent;
84
+ return (opener as Window | null) ?? null;
85
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A stand-in host for a tool's end-to-end tests, and the messages SciPeaks
3
+ * sends. Node only: it serves a page.
4
+ */
5
+
6
+ export type {
7
+ StandInHost,
8
+ StandInHostLog,
9
+ } from './host/testing/standInHost.ts';
10
+ export {
11
+ readStandInHostLog,
12
+ startStandInHost,
13
+ } from './host/testing/standInHost.ts';
14
+ export {
15
+ SCIPEAKS_ETHYLBENZENE_LOAD,
16
+ SCIPEAKS_ETHYLBENZENE_MOLFILE,
17
+ SCIPEAKS_HOST_NAME,
18
+ } from './host/testing/sciPeaks.ts';
package/src/host.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The host protocol: a page that frames a tool — an electronic lab notebook, a
3
+ * course — hands it typed data, checked against pipeline.cheminfo.org's kinds.
4
+ *
5
+ * Its own door because the schemas are checked with ajv. `useHostConnection`
6
+ * fetches ajv and the connection only when the page is framed, so a site that
7
+ * calls it ships nothing extra to a visitor who opened it directly.
8
+ */
9
+
10
+ export * from './host/core/index.ts';
11
+ export * from './host/ui/index.ts';