react-cheminfo 0.33.0 → 0.35.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 (248) hide show
  1. package/README.md +28 -28
  2. package/lib/about/core/about.d.ts +1 -30
  3. package/lib/about/core/about.d.ts.map +1 -1
  4. package/lib/about/core/about.js +0 -16
  5. package/lib/about/core/about.js.map +1 -1
  6. package/lib/about/core/index.d.ts +0 -1
  7. package/lib/about/core/index.d.ts.map +1 -1
  8. package/lib/about/core/index.js +0 -1
  9. package/lib/about/core/index.js.map +1 -1
  10. package/lib/about/ui/AboutBuild.d.ts +2 -5
  11. package/lib/about/ui/AboutBuild.d.ts.map +1 -1
  12. package/lib/about/ui/AboutBuild.js +7 -19
  13. package/lib/about/ui/AboutBuild.js.map +1 -1
  14. package/lib/about/ui/AboutPage.d.ts +2 -2
  15. package/lib/about/ui/AboutPage.d.ts.map +1 -1
  16. package/lib/about/ui/AboutPage.js +3 -14
  17. package/lib/about/ui/AboutPage.js.map +1 -1
  18. package/lib/about/ui/AboutVersion.d.ts +0 -4
  19. package/lib/about/ui/AboutVersion.d.ts.map +1 -1
  20. package/lib/about/ui/AboutVersion.js +3 -31
  21. package/lib/about/ui/AboutVersion.js.map +1 -1
  22. package/lib/axis/core/axisMarks.d.ts +54 -0
  23. package/lib/axis/core/axisMarks.d.ts.map +1 -0
  24. package/lib/axis/core/axisMarks.js +69 -0
  25. package/lib/axis/core/axisMarks.js.map +1 -0
  26. package/lib/axis/core/axisScale.d.ts +31 -0
  27. package/lib/axis/core/axisScale.d.ts.map +1 -0
  28. package/lib/axis/core/axisScale.js +31 -0
  29. package/lib/axis/core/axisScale.js.map +1 -0
  30. package/lib/axis/core/index.d.ts +16 -0
  31. package/lib/axis/core/index.d.ts.map +1 -0
  32. package/lib/axis/core/index.js +13 -0
  33. package/lib/axis/core/index.js.map +1 -0
  34. package/lib/axis/core/logAxisBounds.d.ts +78 -0
  35. package/lib/axis/core/logAxisBounds.d.ts.map +1 -0
  36. package/lib/axis/core/logAxisBounds.js +129 -0
  37. package/lib/axis/core/logAxisBounds.js.map +1 -0
  38. package/lib/axis/ui/AxisScaleControl.d.ts +66 -0
  39. package/lib/axis/ui/AxisScaleControl.d.ts.map +1 -0
  40. package/lib/axis/ui/AxisScaleControl.js +26 -0
  41. package/lib/axis/ui/AxisScaleControl.js.map +1 -0
  42. package/lib/axis/ui/index.d.ts +6 -0
  43. package/lib/axis/ui/index.d.ts.map +1 -0
  44. package/lib/axis/ui/index.js +5 -0
  45. package/lib/axis/ui/index.js.map +1 -0
  46. package/lib/core.d.ts +1 -0
  47. package/lib/core.d.ts.map +1 -1
  48. package/lib/core.js +1 -0
  49. package/lib/core.js.map +1 -1
  50. package/lib/delimited/core/index.d.ts +2 -0
  51. package/lib/delimited/core/index.d.ts.map +1 -1
  52. package/lib/delimited/core/index.js +1 -0
  53. package/lib/delimited/core/index.js.map +1 -1
  54. package/lib/delimited/core/tableRows.d.ts +49 -0
  55. package/lib/delimited/core/tableRows.d.ts.map +1 -0
  56. package/lib/delimited/core/tableRows.js +63 -0
  57. package/lib/delimited/core/tableRows.js.map +1 -0
  58. package/lib/delimited/ui/DelimitedTextPanel.d.ts +9 -0
  59. package/lib/delimited/ui/DelimitedTextPanel.d.ts.map +1 -1
  60. package/lib/delimited/ui/DelimitedTextPanel.js +3 -2
  61. package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
  62. package/lib/delimited/ui/TableDataButton.d.ts +96 -0
  63. package/lib/delimited/ui/TableDataButton.d.ts.map +1 -0
  64. package/lib/delimited/ui/TableDataButton.js +39 -0
  65. package/lib/delimited/ui/TableDataButton.js.map +1 -0
  66. package/lib/delimited/ui/index.d.ts +2 -0
  67. package/lib/delimited/ui/index.d.ts.map +1 -1
  68. package/lib/delimited/ui/index.js +1 -0
  69. package/lib/delimited/ui/index.js.map +1 -1
  70. package/lib/download/core/index.d.ts +0 -5
  71. package/lib/download/core/index.d.ts.map +1 -1
  72. package/lib/download/core/index.js +0 -3
  73. package/lib/download/core/index.js.map +1 -1
  74. package/lib/download/ui/FigureDownload.d.ts +4 -55
  75. package/lib/download/ui/FigureDownload.d.ts.map +1 -1
  76. package/lib/download/ui/FigureDownload.js +6 -66
  77. package/lib/download/ui/FigureDownload.js.map +1 -1
  78. package/lib/download/ui/FigureDownloadPanel.d.ts +16 -2
  79. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  80. package/lib/download/ui/FigureDownloadPanel.js +11 -30
  81. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  82. package/lib/download/ui/figureDownloadHint.d.ts +30 -0
  83. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -0
  84. package/lib/download/ui/figureDownloadHint.js +33 -0
  85. package/lib/download/ui/figureDownloadHint.js.map +1 -0
  86. package/lib/download/ui/index.d.ts +3 -10
  87. package/lib/download/ui/index.d.ts.map +1 -1
  88. package/lib/download/ui/index.js +1 -5
  89. package/lib/download/ui/index.js.map +1 -1
  90. package/lib/download/ui/useFigureActions.d.ts +50 -0
  91. package/lib/download/ui/useFigureActions.d.ts.map +1 -0
  92. package/lib/download/ui/useFigureActions.js +58 -0
  93. package/lib/download/ui/useFigureActions.js.map +1 -0
  94. package/lib/download/ui/useFigureDownload.d.ts +85 -0
  95. package/lib/download/ui/useFigureDownload.d.ts.map +1 -0
  96. package/lib/download/ui/useFigureDownload.js +75 -0
  97. package/lib/download/ui/useFigureDownload.js.map +1 -0
  98. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  99. package/lib/ecosystem/core/sites.js +0 -40
  100. package/lib/ecosystem/core/sites.js.map +1 -1
  101. package/lib/ecosystem/core/types.d.ts +0 -10
  102. package/lib/ecosystem/core/types.d.ts.map +1 -1
  103. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  104. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  105. package/lib/ir/core/irCommands.d.ts +2 -2
  106. package/lib/ir/core/irCommands.js +2 -2
  107. package/lib/ir/core/irCommands.js.map +1 -1
  108. package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
  109. package/lib/ir/ui/IrAboutDialog.js +1 -1
  110. package/lib/ir/ui/IrAboutDialog.js.map +1 -1
  111. package/lib/ir/ui/IrCanvas.d.ts +6 -0
  112. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  113. package/lib/ir/ui/IrCanvas.js +2 -2
  114. package/lib/ir/ui/IrCanvas.js.map +1 -1
  115. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  116. package/lib/ir/ui/IrShell.js +16 -11
  117. package/lib/ir/ui/IrShell.js.map +1 -1
  118. package/lib/ir/ui/IrToolbar.d.ts +11 -1
  119. package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
  120. package/lib/ir/ui/IrToolbar.js +12 -2
  121. package/lib/ir/ui/IrToolbar.js.map +1 -1
  122. package/lib/ir/ui/index.d.ts +1 -0
  123. package/lib/ir/ui/index.d.ts.map +1 -1
  124. package/lib/ir/ui/index.js.map +1 -1
  125. package/lib/locales/de.json +8 -4
  126. package/lib/locales/en.json +8 -4
  127. package/lib/locales/es.json +8 -4
  128. package/lib/locales/fr.json +8 -4
  129. package/lib/locales/it.json +8 -4
  130. package/lib/overlay/ui/OverlayPanel.d.ts +6 -0
  131. package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
  132. package/lib/overlay/ui/OverlayPanel.js +2 -2
  133. package/lib/overlay/ui/OverlayPanel.js.map +1 -1
  134. package/lib/overlay/ui/overlayPanelStyles.d.ts +2 -1
  135. package/lib/overlay/ui/overlayPanelStyles.d.ts.map +1 -1
  136. package/lib/overlay/ui/overlayPanelStyles.js +3 -1
  137. package/lib/overlay/ui/overlayPanelStyles.js.map +1 -1
  138. package/lib/periodic/core/index.d.ts +1 -1
  139. package/lib/periodic/core/index.d.ts.map +1 -1
  140. package/lib/periodic/core/index.js +1 -1
  141. package/lib/periodic/core/index.js.map +1 -1
  142. package/lib/periodic/core/layout.d.ts +18 -0
  143. package/lib/periodic/core/layout.d.ts.map +1 -1
  144. package/lib/periodic/core/layout.js +9 -0
  145. package/lib/periodic/core/layout.js.map +1 -1
  146. package/lib/periodic/ui/ElementCell.d.ts +0 -6
  147. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  148. package/lib/periodic/ui/ElementCell.js +35 -19
  149. package/lib/periodic/ui/ElementCell.js.map +1 -1
  150. package/lib/periodic/ui/PeriodicTable.d.ts +12 -1
  151. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  152. package/lib/periodic/ui/PeriodicTable.js +34 -15
  153. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  154. package/lib/ui.d.ts +1 -0
  155. package/lib/ui.d.ts.map +1 -1
  156. package/lib/ui.js +1 -0
  157. package/lib/ui.js.map +1 -1
  158. package/package.json +1 -1
  159. package/src/about/core/about.ts +1 -51
  160. package/src/about/core/index.ts +0 -1
  161. package/src/about/ui/AboutBuild.tsx +8 -37
  162. package/src/about/ui/AboutPage.tsx +8 -64
  163. package/src/about/ui/AboutVersion.tsx +5 -45
  164. package/src/axis/core/axisMarks.ts +114 -0
  165. package/src/axis/core/axisScale.ts +41 -0
  166. package/src/axis/core/index.ts +20 -0
  167. package/src/axis/core/logAxisBounds.ts +198 -0
  168. package/src/axis/ui/AxisScaleControl.tsx +103 -0
  169. package/src/axis/ui/index.ts +9 -0
  170. package/src/core.ts +1 -0
  171. package/src/delimited/core/index.ts +2 -0
  172. package/src/delimited/core/tableRows.ts +93 -0
  173. package/src/delimited/ui/DelimitedTextPanel.tsx +25 -9
  174. package/src/delimited/ui/TableDataButton.tsx +162 -0
  175. package/src/delimited/ui/index.ts +2 -0
  176. package/src/download/core/index.ts +0 -25
  177. package/src/download/ui/FigureDownload.tsx +11 -148
  178. package/src/download/ui/FigureDownloadPanel.tsx +46 -45
  179. package/src/download/ui/figureDownloadHint.ts +52 -0
  180. package/src/download/ui/index.ts +6 -10
  181. package/src/download/ui/useFigureActions.ts +113 -0
  182. package/src/download/ui/useFigureDownload.tsx +191 -0
  183. package/src/ecosystem/core/sites.ts +0 -40
  184. package/src/ecosystem/core/types.ts +0 -10
  185. package/src/ir/core/irCommands.ts +2 -2
  186. package/src/ir/ui/IrAboutDialog.tsx +0 -13
  187. package/src/ir/ui/IrCanvas.tsx +8 -2
  188. package/src/ir/ui/IrShell.tsx +23 -25
  189. package/src/ir/ui/IrToolbar.tsx +36 -6
  190. package/src/ir/ui/index.ts +1 -0
  191. package/src/locales/de.json +8 -4
  192. package/src/locales/en.json +8 -4
  193. package/src/locales/es.json +8 -4
  194. package/src/locales/fr.json +8 -4
  195. package/src/locales/it.json +8 -4
  196. package/src/overlay/ui/OverlayPanel.tsx +8 -2
  197. package/src/overlay/ui/overlayPanelStyles.ts +3 -0
  198. package/src/periodic/core/index.ts +1 -0
  199. package/src/periodic/core/layout.ts +19 -0
  200. package/src/periodic/ui/ElementCell.tsx +35 -26
  201. package/src/periodic/ui/PeriodicTable.tsx +55 -16
  202. package/src/ui.ts +1 -0
  203. package/styles/chrome.css +71 -0
  204. package/lib/about/core/repository.d.ts +0 -14
  205. package/lib/about/core/repository.d.ts.map +0 -1
  206. package/lib/about/core/repository.js +0 -21
  207. package/lib/about/core/repository.js.map +0 -1
  208. package/lib/download/core/exportStyles.d.ts +0 -22
  209. package/lib/download/core/exportStyles.d.ts.map +0 -1
  210. package/lib/download/core/exportStyles.js +0 -19
  211. package/lib/download/core/exportStyles.js.map +0 -1
  212. package/lib/download/core/imageResolution.d.ts +0 -88
  213. package/lib/download/core/imageResolution.d.ts.map +0 -1
  214. package/lib/download/core/imageResolution.js +0 -77
  215. package/lib/download/core/imageResolution.js.map +0 -1
  216. package/lib/download/core/pictureExport.d.ts +0 -93
  217. package/lib/download/core/pictureExport.d.ts.map +0 -1
  218. package/lib/download/core/pictureExport.js +0 -116
  219. package/lib/download/core/pictureExport.js.map +0 -1
  220. package/lib/download/ui/ExportDialogFrame.d.ts +0 -46
  221. package/lib/download/ui/ExportDialogFrame.d.ts.map +0 -1
  222. package/lib/download/ui/ExportDialogFrame.js +0 -24
  223. package/lib/download/ui/ExportDialogFrame.js.map +0 -1
  224. package/lib/download/ui/ExportImageDialog.d.ts +0 -47
  225. package/lib/download/ui/ExportImageDialog.d.ts.map +0 -1
  226. package/lib/download/ui/ExportImageDialog.js +0 -25
  227. package/lib/download/ui/ExportImageDialog.js.map +0 -1
  228. package/lib/download/ui/ExportNameField.d.ts +0 -29
  229. package/lib/download/ui/ExportNameField.d.ts.map +0 -1
  230. package/lib/download/ui/ExportNameField.js +0 -99
  231. package/lib/download/ui/ExportNameField.js.map +0 -1
  232. package/lib/download/ui/ExportPictureSection.d.ts +0 -38
  233. package/lib/download/ui/ExportPictureSection.d.ts.map +0 -1
  234. package/lib/download/ui/ExportPictureSection.js +0 -48
  235. package/lib/download/ui/ExportPictureSection.js.map +0 -1
  236. package/lib/download/ui/useDrawingBox.d.ts +0 -23
  237. package/lib/download/ui/useDrawingBox.d.ts.map +0 -1
  238. package/lib/download/ui/useDrawingBox.js +0 -21
  239. package/lib/download/ui/useDrawingBox.js.map +0 -1
  240. package/src/about/core/repository.ts +0 -22
  241. package/src/download/core/exportStyles.ts +0 -20
  242. package/src/download/core/imageResolution.ts +0 -118
  243. package/src/download/core/pictureExport.ts +0 -207
  244. package/src/download/ui/ExportDialogFrame.tsx +0 -86
  245. package/src/download/ui/ExportImageDialog.tsx +0 -91
  246. package/src/download/ui/ExportNameField.tsx +0 -148
  247. package/src/download/ui/ExportPictureSection.tsx +0 -142
  248. package/src/download/ui/useDrawingBox.ts +0 -37
@@ -0,0 +1,113 @@
1
+ import { useCallback, useState } from 'react';
2
+
3
+ import { writeImageToClipboard } from '../../clipboard/core/copyPng.ts';
4
+ import type { FigureFormat } from '../core/downloadFigure.ts';
5
+ import { downloadFigure } from '../core/downloadFigure.ts';
6
+ import { figurePng } from '../core/figurePng.ts';
7
+ import type { FigurePixels } from '../core/figureScale.ts';
8
+ import { figureSvg } from '../core/figureSvg.ts';
9
+
10
+ import type { FigureRedraw } from './useFigureRedraw.tsx';
11
+
12
+ /** What the last copy came to, when it is worth a line of its own. */
13
+ export type FigureNotice = 'copied' | 'copyUnsupported';
14
+
15
+ /** What {@link useFigureActions} works from. */
16
+ export interface FigureActionsOptions {
17
+ /** The `id` of the box the figure is mounted in. */
18
+ targetId: string;
19
+ /** Which file save writes. */
20
+ format: FigureFormat;
21
+ /** The multiple of the figure on screen it is painted at. */
22
+ scale: number;
23
+ /** What the saved file is called, without its extension. */
24
+ fileName?: string;
25
+ /** What is painted under the figure. */
26
+ background?: string;
27
+ /**
28
+ * The size the figure is drawn again at, off the page, or `null` to take
29
+ * the figure as it is on screen.
30
+ */
31
+ redrawAt: FigurePixels | null;
32
+ /** Draws the figure again, from {@link FigureRedraw}. */
33
+ redraw: FigureRedraw['redraw'];
34
+ }
35
+
36
+ /** What {@link useFigureActions} hands back. */
37
+ export interface FigureActions {
38
+ /** Write the figure to a file, in the format picked. */
39
+ save: () => void;
40
+ /** Put the figure on the clipboard, as a PNG at the resolution picked. */
41
+ copy: () => void;
42
+ /** Whether a file or a copy is being written right now. */
43
+ busy: boolean;
44
+ /** What went wrong the last time, if anything. */
45
+ failure: string | null;
46
+ /** What the last copy came to, if it is still what the panel describes. */
47
+ notice: FigureNotice | null;
48
+ /** Forget the last outcome, once the choices it answered have changed. */
49
+ clear: () => void;
50
+ }
51
+
52
+ /**
53
+ * The two things the figure panel does: save the figure, and copy it.
54
+ *
55
+ * A copy is always a PNG, whatever format save is set to: a clipboard carries
56
+ * a picture, and the one every slide, chat window and issue accepts is a PNG.
57
+ * @param options - See {@link FigureActionsOptions}.
58
+ * @returns See {@link FigureActions}.
59
+ */
60
+ export function useFigureActions(options: FigureActionsOptions): FigureActions {
61
+ const { targetId, format, scale, fileName, background } = options;
62
+ const { redrawAt, redraw } = options;
63
+ const [busy, setBusy] = useState(false);
64
+ const [failure, setFailure] = useState<string | null>(null);
65
+ const [notice, setNotice] = useState<FigureNotice | null>(null);
66
+
67
+ function withFigure(write: (box: string | Element) => Promise<void>) {
68
+ return redrawAt === null
69
+ ? write(targetId)
70
+ : redraw(targetId, redrawAt, write);
71
+ }
72
+
73
+ function run(task: Promise<FigureNotice | null>): void {
74
+ setBusy(true);
75
+ setFailure(null);
76
+ setNotice(null);
77
+ void task
78
+ .then(setNotice, (error: unknown) => {
79
+ setFailure(error instanceof Error ? error.message : String(error));
80
+ })
81
+ .finally(() => setBusy(false));
82
+ }
83
+
84
+ function save(): void {
85
+ const options = { format, scale, fileName, background };
86
+ run(withFigure((box) => downloadFigure(box, options)).then(() => null));
87
+ }
88
+
89
+ function copy(): void {
90
+ // The clipboard is written before anything is awaited, with the picture
91
+ // still being painted: Safari only allows a write the click itself started.
92
+ const png = new Promise<Blob>((resolve, reject) => {
93
+ withFigure(async (box) => {
94
+ resolve(await figurePng(figureSvg(box, { background }), scale));
95
+ }).catch(reject);
96
+ });
97
+ // A browser whose clipboard takes text only never reads the picture, so
98
+ // its failure would otherwise go unhandled.
99
+ png.catch(() => undefined);
100
+ run(
101
+ writeImageToClipboard(png).then((copied) =>
102
+ copied ? 'copied' : 'copyUnsupported',
103
+ ),
104
+ );
105
+ }
106
+
107
+ const clear = useCallback(() => {
108
+ setFailure(null);
109
+ setNotice(null);
110
+ }, []);
111
+
112
+ return { save, copy, busy, failure, notice, clear };
113
+ }
@@ -0,0 +1,191 @@
1
+ import type { ReactElement, ReactNode } from 'react';
2
+ import { useCallback, useState } from 'react';
3
+
4
+ import { useChromeT } from '../../i18n/ui/useT.ts';
5
+ import type { FigureFormat } from '../core/downloadFigure.ts';
6
+ import type { FigureLayout } from '../core/figureLayout.ts';
7
+ import { figureLayoutRedraws, figureLayoutSize } from '../core/figureLayout.ts';
8
+ import type { FigurePixels } from '../core/figureScale.ts';
9
+ import {
10
+ DEFAULT_FIGURE_SCALE,
11
+ FIGURE_SCALES,
12
+ defaultFigureScale,
13
+ } from '../core/figureScale.ts';
14
+ import { figureSize } from '../core/figureTarget.ts';
15
+
16
+ import { FigureDownloadPanel } from './FigureDownloadPanel.tsx';
17
+ import { useFigureActions } from './useFigureActions.ts';
18
+ import type { FigureRenderer } from './useFigureRedraw.tsx';
19
+ import { useFigureRedraw } from './useFigureRedraw.tsx';
20
+
21
+ /** What the "Save figure" panel is set up with. */
22
+ export interface FigureDownloadOptions {
23
+ /**
24
+ * The `id` of the box the figure is mounted in. Everything drawn inside it
25
+ * is saved — sixteen charts of a pair grid as readily as one scatter plot —
26
+ * and the controls floating over it are left behind.
27
+ */
28
+ targetId: string;
29
+ /**
30
+ * What the saved file is called, without its extension. Name it after the
31
+ * data rather than after the tool: a reader with four of these in a
32
+ * downloads folder cannot tell four `figure.png` apart.
33
+ * @default 'figure'
34
+ */
35
+ fileName?: string;
36
+ /**
37
+ * The format it opens on.
38
+ * @default 'png'
39
+ */
40
+ defaultFormat?: FigureFormat;
41
+ /**
42
+ * The resolution a PNG opens on, as a multiple of the figure on screen. An
43
+ * SVG opens at 1×, the size it has on screen, and each format keeps the
44
+ * multiple the reader last picked for it.
45
+ * @default 2
46
+ */
47
+ defaultScale?: number;
48
+ /**
49
+ * The resolutions offered.
50
+ * @default [1, 2, 3, 4]
51
+ */
52
+ scales?: readonly number[];
53
+ /**
54
+ * What is painted under the figure. `transparent` leaves it unpainted, for a
55
+ * figure going onto a coloured slide.
56
+ * @default the surface the figure is drawn on
57
+ */
58
+ background?: string;
59
+ /**
60
+ * What the panel is called.
61
+ * @default the chrome's own line, in the language of the page
62
+ */
63
+ title?: string;
64
+ /**
65
+ * Draws the figure at a size, for a file. When it is given the panel offers
66
+ * a size — as shown, 4:3, 16:9, a journal column, or typed — and a figure
67
+ * saved at a size other than its own is drawn again at that size, off the
68
+ * page, so its axes and labels are laid out for the new shape rather than
69
+ * stretched into it. Draw exactly what is on screen, with the same data,
70
+ * zoom and colours; only the box changes.
71
+ * @default undefined — the figure is saved at the size it has on screen
72
+ */
73
+ renderFigure?: FigureRenderer;
74
+ }
75
+
76
+ /** What {@link useFigureDownload} hands back. */
77
+ export interface FigureDownloadState {
78
+ /** Whether the panel is showing. */
79
+ isOpen: boolean;
80
+ /**
81
+ * Show or hide the panel. Showing it measures the figure, so hand this to
82
+ * the popover's `onInteraction` rather than a setter of your own.
83
+ */
84
+ setOpen: (open: boolean) => void;
85
+ /** The panel, for the popover's `content`. */
86
+ panel: ReactElement;
87
+ /** The format picked, for a trigger that shows it. */
88
+ format: FigureFormat;
89
+ /**
90
+ * The copy drawn off the page while a figure is saved at another size, to
91
+ * be rendered anywhere in the caller's tree.
92
+ */
93
+ portal: ReactNode;
94
+ }
95
+
96
+ /**
97
+ * The "Save figure" panel — save as PNG or SVG, or copy as PNG — for a caller
98
+ * that brings its own trigger, such as a button in an editor's toolbar.
99
+ * `FigureDownload` is the same panel behind the family's own glyph.
100
+ * @param options - See {@link FigureDownloadOptions}.
101
+ * @returns See {@link FigureDownloadState}.
102
+ */
103
+ export function useFigureDownload(
104
+ options: FigureDownloadOptions,
105
+ ): FigureDownloadState {
106
+ const { targetId, fileName, background, scales = FIGURE_SCALES } = options;
107
+ const { defaultFormat = 'png', defaultScale = DEFAULT_FIGURE_SCALE } =
108
+ options;
109
+ const { title, renderFigure } = options;
110
+
111
+ const t = useChromeT();
112
+ const [isOpen, setIsOpen] = useState(false);
113
+ const [format, setFormat] = useState<FigureFormat>(defaultFormat);
114
+ const [picked, setPicked] = useState<Record<FigureFormat, number>>(() => ({
115
+ png: defaultScale,
116
+ svg: defaultFigureScale('svg'),
117
+ }));
118
+ const scale = picked[format];
119
+ const [size, setSize] = useState<FigurePixels | null>(null);
120
+ const [layout, setLayout] = useState<FigureLayout>('screen');
121
+ const [custom, setCustom] = useState<FigurePixels | null>(null);
122
+ const { redraw, portal } = useFigureRedraw(renderFigure);
123
+ const redrawAt =
124
+ renderFigure !== undefined &&
125
+ size !== null &&
126
+ figureLayoutRedraws(layout, size, custom ?? size)
127
+ ? figureLayoutSize(layout, size, custom ?? size)
128
+ : null;
129
+ const { save, copy, busy, failure, notice, clear } = useFigureActions({
130
+ targetId,
131
+ format,
132
+ scale,
133
+ fileName,
134
+ background,
135
+ redrawAt,
136
+ redraw,
137
+ });
138
+
139
+ // The panel is held open from here rather than left to itself, because the
140
+ // figure has to be measured at the moment it opens: its size is what the
141
+ // panel writes out, and reading a bounding box on every render would cost a
142
+ // layout pass on a bar that is re-rendered on every pointer move.
143
+ const setOpen = useCallback(
144
+ (next: boolean) => {
145
+ setIsOpen(next);
146
+ if (!next) return;
147
+ setSize(figureSize(targetId));
148
+ clear();
149
+ },
150
+ [targetId, clear],
151
+ );
152
+
153
+ // What the last copy came to only describes the choices it was made with.
154
+ function changed<T>(apply: (value: T) => void): (value: T) => void {
155
+ return (value) => {
156
+ clear();
157
+ apply(value);
158
+ };
159
+ }
160
+
161
+ const panel = (
162
+ <FigureDownloadPanel
163
+ title={title ?? t('download.saveFigure')}
164
+ format={format}
165
+ scale={scale}
166
+ scales={scales}
167
+ size={size}
168
+ failure={failure}
169
+ notice={notice}
170
+ saving={busy}
171
+ onFormatChange={changed(setFormat)}
172
+ onScaleChange={changed((next: number) =>
173
+ setPicked((current) => ({ ...current, [format]: next })),
174
+ )}
175
+ onSave={save}
176
+ onCopy={copy}
177
+ sizing={
178
+ renderFigure === undefined
179
+ ? undefined
180
+ : {
181
+ layout,
182
+ custom,
183
+ onLayoutChange: changed(setLayout),
184
+ onCustomChange: changed(setCustom),
185
+ }
186
+ }
187
+ />
188
+ );
189
+
190
+ return { isOpen, setOpen, panel, format, portal };
191
+ }
@@ -20,7 +20,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
20
20
  id: 'learn',
21
21
  name: { lead: 'learn', alt: 'cheminfo', dot: true },
22
22
  host: 'learn.cheminfo.org',
23
- repository: 'https://github.com/cheminfo/learn.cheminfo.org',
24
23
  tagline: 'Arrange the tools into a course and hand out its link.',
25
24
  group: 'computing',
26
25
  brand: '#0d9488',
@@ -31,7 +30,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
31
30
  id: 'inchi',
32
31
  name: { lead: 'inchi', alt: 'cheminfo', dot: true },
33
32
  host: 'inchi.cheminfo.org',
34
- repository: 'https://github.com/cheminfo/inchi.cheminfo.org',
35
33
  tagline: 'InChI and InChIKey from a structure, in the browser.',
36
34
  group: 'structures',
37
35
  brand: '#5b21b6',
@@ -42,7 +40,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
42
40
  id: 'vcl',
43
41
  name: { lead: 'vcl', alt: 'cheminfo', dot: true },
44
42
  host: 'vcl.cheminfo.org',
45
- repository: 'https://github.com/cheminfo/vcl.cheminfo.org',
46
43
  tagline: 'Combine a core and fragments into a screened library.',
47
44
  group: 'research',
48
45
  brand: '#2d72d2',
@@ -53,7 +50,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
53
50
  id: 'smiles',
54
51
  name: { lead: 'smiles', alt: 'cheminfo', dot: true },
55
52
  host: 'smiles.cheminfo.org',
56
- repository: 'https://github.com/cheminfo/smiles.cheminfo.org',
57
53
  tagline: 'Draw a structure, read its SMILES, learn the notation.',
58
54
  group: 'structures',
59
55
  brand: '#1c6e42',
@@ -64,8 +60,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
64
60
  id: 'openbabel',
65
61
  name: { lead: 'openbabel', alt: 'cheminfo', dot: true },
66
62
  host: 'openbabel.cheminfo.org',
67
- repository: 'https://github.com/cheminfo/openbabel.cheminfo.org',
68
- publicRepository: true,
69
63
  tagline: 'Convert a structure between any pair of chemical file formats.',
70
64
  group: 'structures',
71
65
  brand: '#0f766e',
@@ -76,7 +70,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
76
70
  id: 'chemcalc',
77
71
  name: { lead: 'Chem', alt: 'Calc' },
78
72
  host: 'www.chemcalc.org',
79
- repository: 'https://github.com/cheminfo/chemcalc.org',
80
73
  tagline: 'Molecular formula, exact mass and isotopic distribution.',
81
74
  group: 'basics',
82
75
  brand: '#5b52e0',
@@ -87,8 +80,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
87
80
  id: 'dbe',
88
81
  name: { lead: 'dbe', alt: 'cheminfo', dot: true },
89
82
  host: 'dbe.cheminfo.org',
90
- repository: 'https://github.com/cheminfo/dbe.cheminfo.org',
91
- publicRepository: true,
92
83
  tagline: 'Count rings and double bonds from a formula or a structure.',
93
84
  group: 'basics',
94
85
  brand: '#63123f',
@@ -101,8 +92,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
101
92
  id: 'nmrium',
102
93
  name: { lead: 'NMR', alt: 'ium' },
103
94
  host: 'www.nmrium.org',
104
- repository: 'https://github.com/cheminfo/nmrium',
105
- publicRepository: true,
106
95
  tagline: 'Process and assign NMR spectra in the browser.',
107
96
  group: 'spectra',
108
97
  brand: '#ea580c',
@@ -116,7 +105,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
116
105
  // of the cheminfo tools.
117
106
  name: { lead: 'metabo', alt: 'nmrium', dot: true },
118
107
  host: 'metabo.nmrium.com',
119
- repository: 'https://github.com/cheminfo/metabo',
120
108
  tagline: 'Turn a batch of NMR spectra into a map of your samples.',
121
109
  group: 'spectra',
122
110
  brand: '#4d7c0f',
@@ -129,7 +117,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
129
117
  id: 'derepflow',
130
118
  name: { lead: 'Derep', alt: 'Flow', ink: 'lead' },
131
119
  host: 'derepflow.cheminfo.org',
132
- repository: 'https://github.com/cheminfo/derepflow',
133
120
  tagline: 'Identify the constituents of a mixture from its ¹³C spectrum.',
134
121
  group: 'spectra',
135
122
  brand: '#127ba3',
@@ -140,8 +127,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
140
127
  id: 'surge',
141
128
  name: { lead: 'surge', alt: 'cheminfo', dot: true },
142
129
  host: 'surge.cheminfo.org',
143
- repository: 'https://github.com/cheminfo/surge.cheminfo.org',
144
- publicRepository: true,
145
130
  tagline: 'Every constitutional isomer of a molecular formula.',
146
131
  group: 'structures',
147
132
  brand: '#4338ca',
@@ -152,8 +137,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
152
137
  id: 'tex',
153
138
  name: { lead: 'tex', alt: 'cheminfo', dot: true },
154
139
  host: 'tex.cheminfo.org',
155
- repository: 'https://github.com/cheminfo/tex.cheminfo.org',
156
- publicRepository: true,
157
140
  tagline: 'LaTeX formulas rendered to SVG or PNG from a URL.',
158
141
  group: 'computing',
159
142
  brand: '#a21caf',
@@ -164,7 +147,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
164
147
  id: 'lcao',
165
148
  name: { lead: 'lcao', alt: 'cheminfo', dot: true },
166
149
  host: 'lcao.cheminfo.org',
167
- repository: 'https://github.com/cheminfo/lcao.cheminfo.org',
168
150
  tagline: 'Combine atomic orbitals into molecular ones.',
169
151
  group: 'basics',
170
152
  brand: '#1565c0',
@@ -175,8 +157,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
175
157
  id: 'regexp',
176
158
  name: { lead: 'regexp', alt: 'cheminfo', dot: true },
177
159
  host: 'regexp.cheminfo.org',
178
- repository: 'https://github.com/cheminfo/regexp.cheminfo.org',
179
- publicRepository: true,
180
160
  tagline: 'Learn regular expressions in a live playground.',
181
161
  group: 'computing',
182
162
  brand: '#1e3a8a',
@@ -187,8 +167,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
187
167
  id: 'pdb',
188
168
  name: { lead: 'pdb', alt: 'cheminfo', dot: true },
189
169
  host: 'pdb.cheminfo.org',
190
- repository: 'https://github.com/cheminfo/pdb.cheminfo.org',
191
- publicRepository: true,
192
170
  tagline: 'A fast look at any Protein Data Bank entry.',
193
171
  group: 'research',
194
172
  brand: '#2563eb',
@@ -199,8 +177,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
199
177
  id: 'elucidation',
200
178
  name: { lead: 'elucidation', alt: 'cheminfo', dot: true },
201
179
  host: 'elucidation.cheminfo.org',
202
- repository: 'https://github.com/cheminfo/elucidation.cheminfo.org',
203
- publicRepository: true,
204
180
  tagline: 'A structure from a 1H NMR spectrum and a formula.',
205
181
  group: 'spectra',
206
182
  brand: '#7e22ce',
@@ -211,7 +187,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
211
187
  id: 'equilibrium',
212
188
  name: { lead: 'Equi', alt: 'Librium' },
213
189
  host: 'equilibrium.cheminfo.org',
214
- repository: 'https://github.com/cheminfo/equilibrium.cheminfo.org',
215
190
  tagline: 'Chemical equilibria: pH, speciation and titration curves.',
216
191
  group: 'basics',
217
192
  brand: '#0b5754',
@@ -222,8 +197,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
222
197
  id: 'polycarp',
223
198
  name: { lead: 'Poly', alt: 'Carp' },
224
199
  host: 'polycarp.cheminfo.org',
225
- repository: 'https://github.com/cheminfo/polycarp.cheminfo.org',
226
- publicRepository: true,
227
200
  tagline: 'Predict the microstructure of a radical copolymerisation.',
228
201
  group: 'research',
229
202
  brand: '#701a75',
@@ -234,7 +207,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
234
207
  id: '3d',
235
208
  name: { lead: '3d', alt: 'cheminfo', dot: true },
236
209
  host: '3d.cheminfo.org',
237
- repository: 'https://github.com/cheminfo/3d.cheminfo.org',
238
210
  tagline: 'Conformers in 3D from a structure you draw.',
239
211
  group: 'structures',
240
212
  brand: '#155e75',
@@ -245,7 +217,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
245
217
  id: 'periodic-table',
246
218
  name: { lead: 'Periodic', alt: 'Table' },
247
219
  host: 'periodic-table.cheminfo.org',
248
- repository: 'https://github.com/cheminfo/periodic-table.cheminfo.org',
249
220
  tagline: 'The periodic table, coloured, plotted and taken apart.',
250
221
  group: 'basics',
251
222
  brand: '#15803d',
@@ -256,8 +227,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
256
227
  id: 'database',
257
228
  name: { lead: 'database', alt: 'cheminfo', dot: true },
258
229
  host: 'database.cheminfo.org',
259
- repository: 'https://github.com/cheminfo/database.cheminfo.org',
260
- publicRepository: true,
261
230
  tagline: 'Query a real chemical dataset in SQL and in Mango.',
262
231
  group: 'research',
263
232
  brand: '#7c2d12',
@@ -268,8 +237,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
268
237
  id: 'symmetry',
269
238
  name: { lead: 'Symme', alt: 'Try' },
270
239
  host: 'symmetry.cheminfo.org',
271
- repository: 'https://github.com/cheminfo/symmetry.cheminfo.org',
272
- publicRepository: true,
273
240
  tagline: 'Find the symmetry of a molecule, a crystal or a pattern.',
274
241
  group: 'basics',
275
242
  brand: '#9f1239',
@@ -282,8 +249,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
282
249
  id: 'osiris',
283
250
  name: { lead: 'osiris', alt: 'cheminfo', dot: true },
284
251
  host: 'osiris.cheminfo.org',
285
- repository: 'https://github.com/cheminfo/osiris.cheminfo.org',
286
- publicRepository: true,
287
252
  tagline: 'Predicted toxicity risks and properties, one molecule or many.',
288
253
  group: 'research',
289
254
  brand: '#854d0e',
@@ -297,8 +262,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
297
262
  id: 'atoms',
298
263
  name: { lead: 'atoms', alt: 'cheminfo', dot: true },
299
264
  host: 'atoms.cheminfo.org',
300
- repository: 'https://github.com/cheminfo/atoms.cheminfo.org',
301
- publicRepository: true,
302
265
  tagline: 'Electrons, neutrons, Lewis structures and oxidation states.',
303
266
  group: 'practice',
304
267
  brand: '#b91c1c',
@@ -311,8 +274,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
311
274
  id: 'moles',
312
275
  name: { lead: 'moles', alt: 'cheminfo', dot: true },
313
276
  host: 'moles.cheminfo.org',
314
- repository: 'https://github.com/cheminfo/moles.cheminfo.org',
315
- publicRepository: true,
316
277
  tagline: 'Balance a reaction, and work out its mass composition.',
317
278
  group: 'practice',
318
279
  brand: '#334155',
@@ -325,7 +286,6 @@ export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
325
286
  id: 'inorganic',
326
287
  name: { lead: 'inorganic', alt: 'cheminfo', dot: true },
327
288
  host: 'inorganic.cheminfo.org',
328
- repository: 'https://github.com/cheminfo/inorganic.cheminfo.org',
329
289
  tagline: 'Name an inorganic compound, and write its formula.',
330
290
  group: 'practice',
331
291
  brand: '#a3620a',
@@ -65,16 +65,6 @@ export interface SiteRecord {
65
65
  name: SiteName;
66
66
  /** Where the site lives, written the way it is typed. */
67
67
  host: string;
68
- /** Where the sources live, and what a Source link opens. */
69
- repository: string;
70
- /**
71
- * Whether a visitor can open that repository. A private one is named
72
- * nowhere: no licence-and-source section, no link from the version, no issue
73
- * tracker a reader cannot reach. It is stated rather than assumed, so a site
74
- * whose record forgets it advertises nothing rather than a dead link.
75
- * @default false
76
- */
77
- publicRepository?: boolean;
78
68
  /** One line on what the site does. */
79
69
  tagline: string;
80
70
  /**
@@ -120,10 +120,10 @@ export const irCommands = {
120
120
  'Writes what each labelled band might be under its wavenumber. Worth turning off once the chart is crowded.',
121
121
  },
122
122
  exportImage: {
123
- title: 'Export as an image…',
123
+ title: 'Save figure…',
124
124
  shortcut: { keys: ['x'] },
125
125
  description:
126
- 'The chart as it stands, at a chosen resolution — PNG, or SVG, which is drawn again at whatever size it is printed rather than enlarged.',
126
+ 'The chart as it stands, saved as a PNG or an SVG at a chosen resolution, or copied as a PNG.',
127
127
  },
128
128
  clear: {
129
129
  title: 'Close every spectrum',
@@ -82,19 +82,6 @@ export function IrAboutDialog(props: IrAboutDialogProps) {
82
82
  reader with a colour vision deficiency.
83
83
  </li>
84
84
  </ul>
85
-
86
- <h4 style={headingStyle}>Licence</h4>
87
- <p style={paragraphStyle}>
88
- MIT, in the{' '}
89
- <a
90
- href="https://github.com/cheminfo/glycans"
91
- target="_blank"
92
- rel="noopener noreferrer"
93
- >
94
- cheminfo/glycans
95
- </a>{' '}
96
- monorepo.
97
- </p>
98
85
  </DialogBody>
99
86
  </Dialog>
100
87
  );
@@ -26,6 +26,12 @@ export interface IrCanvasProps {
26
26
  * @default undefined
27
27
  */
28
28
  attachChart?: (element: HTMLDivElement | null) => void;
29
+ /**
30
+ * The `id` put on the box the chart is drawn in, which the "Save figure"
31
+ * panel is pointed at.
32
+ * @default undefined
33
+ */
34
+ chartId?: string;
29
35
  }
30
36
 
31
37
  /**
@@ -44,7 +50,7 @@ export interface IrCanvasProps {
44
50
  * @returns The canvas.
45
51
  */
46
52
  export function IrCanvas(props: IrCanvasProps) {
47
- const { highlight = null, onHighlight, attachChart } = props;
53
+ const { highlight = null, onHighlight, attachChart, chartId } = props;
48
54
  const { state, visibleSpectra, assigned } = useIrEditorState();
49
55
  const actions = useIrActions();
50
56
  const { domain, tool, selectedId } = state.view;
@@ -59,7 +65,7 @@ export function IrCanvas(props: IrCanvasProps) {
59
65
 
60
66
  return (
61
67
  <div style={rootStyle}>
62
- <div ref={attachChart} style={chartStyle}>
68
+ <div ref={attachChart} id={chartId} style={chartStyle}>
63
69
  <IrChart
64
70
  spectra={visibleSpectra}
65
71
  mode={mode}