react-cheminfo 0.34.0 → 0.36.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 (225) hide show
  1. package/README.md +28 -28
  2. package/bin/check-seo.mjs +333 -0
  3. package/lib/about/core/about.d.ts +1 -30
  4. package/lib/about/core/about.d.ts.map +1 -1
  5. package/lib/about/core/about.js +0 -16
  6. package/lib/about/core/about.js.map +1 -1
  7. package/lib/about/core/index.d.ts +0 -1
  8. package/lib/about/core/index.d.ts.map +1 -1
  9. package/lib/about/core/index.js +0 -1
  10. package/lib/about/core/index.js.map +1 -1
  11. package/lib/about/ui/AboutBuild.d.ts +2 -5
  12. package/lib/about/ui/AboutBuild.d.ts.map +1 -1
  13. package/lib/about/ui/AboutBuild.js +7 -19
  14. package/lib/about/ui/AboutBuild.js.map +1 -1
  15. package/lib/about/ui/AboutPage.d.ts +2 -2
  16. package/lib/about/ui/AboutPage.d.ts.map +1 -1
  17. package/lib/about/ui/AboutPage.js +3 -14
  18. package/lib/about/ui/AboutPage.js.map +1 -1
  19. package/lib/about/ui/AboutVersion.d.ts +0 -4
  20. package/lib/about/ui/AboutVersion.d.ts.map +1 -1
  21. package/lib/about/ui/AboutVersion.js +3 -31
  22. package/lib/about/ui/AboutVersion.js.map +1 -1
  23. package/lib/download/core/index.d.ts +0 -5
  24. package/lib/download/core/index.d.ts.map +1 -1
  25. package/lib/download/core/index.js +0 -3
  26. package/lib/download/core/index.js.map +1 -1
  27. package/lib/download/ui/FigureDownload.d.ts +4 -55
  28. package/lib/download/ui/FigureDownload.d.ts.map +1 -1
  29. package/lib/download/ui/FigureDownload.js +6 -66
  30. package/lib/download/ui/FigureDownload.js.map +1 -1
  31. package/lib/download/ui/FigureDownloadPanel.d.ts +16 -2
  32. package/lib/download/ui/FigureDownloadPanel.d.ts.map +1 -1
  33. package/lib/download/ui/FigureDownloadPanel.js +11 -30
  34. package/lib/download/ui/FigureDownloadPanel.js.map +1 -1
  35. package/lib/download/ui/figureDownloadHint.d.ts +30 -0
  36. package/lib/download/ui/figureDownloadHint.d.ts.map +1 -0
  37. package/lib/download/ui/figureDownloadHint.js +33 -0
  38. package/lib/download/ui/figureDownloadHint.js.map +1 -0
  39. package/lib/download/ui/index.d.ts +3 -10
  40. package/lib/download/ui/index.d.ts.map +1 -1
  41. package/lib/download/ui/index.js +1 -5
  42. package/lib/download/ui/index.js.map +1 -1
  43. package/lib/download/ui/useFigureActions.d.ts +50 -0
  44. package/lib/download/ui/useFigureActions.d.ts.map +1 -0
  45. package/lib/download/ui/useFigureActions.js +58 -0
  46. package/lib/download/ui/useFigureActions.js.map +1 -0
  47. package/lib/download/ui/useFigureDownload.d.ts +85 -0
  48. package/lib/download/ui/useFigureDownload.d.ts.map +1 -0
  49. package/lib/download/ui/useFigureDownload.js +75 -0
  50. package/lib/download/ui/useFigureDownload.js.map +1 -0
  51. package/lib/ecosystem/core/sites.d.ts.map +1 -1
  52. package/lib/ecosystem/core/sites.js +0 -40
  53. package/lib/ecosystem/core/sites.js.map +1 -1
  54. package/lib/ecosystem/core/types.d.ts +0 -10
  55. package/lib/ecosystem/core/types.d.ts.map +1 -1
  56. package/lib/i18n/core/chromeCatalog.d.ts +1 -1
  57. package/lib/i18n/core/chromeCatalog.d.ts.map +1 -1
  58. package/lib/ir/core/irCommands.d.ts +2 -2
  59. package/lib/ir/core/irCommands.js +2 -2
  60. package/lib/ir/core/irCommands.js.map +1 -1
  61. package/lib/ir/ui/IrAboutDialog.d.ts.map +1 -1
  62. package/lib/ir/ui/IrAboutDialog.js +1 -1
  63. package/lib/ir/ui/IrAboutDialog.js.map +1 -1
  64. package/lib/ir/ui/IrCanvas.d.ts +6 -0
  65. package/lib/ir/ui/IrCanvas.d.ts.map +1 -1
  66. package/lib/ir/ui/IrCanvas.js +2 -2
  67. package/lib/ir/ui/IrCanvas.js.map +1 -1
  68. package/lib/ir/ui/IrShell.d.ts.map +1 -1
  69. package/lib/ir/ui/IrShell.js +16 -11
  70. package/lib/ir/ui/IrShell.js.map +1 -1
  71. package/lib/ir/ui/IrToolbar.d.ts +11 -1
  72. package/lib/ir/ui/IrToolbar.d.ts.map +1 -1
  73. package/lib/ir/ui/IrToolbar.js +12 -2
  74. package/lib/ir/ui/IrToolbar.js.map +1 -1
  75. package/lib/ir/ui/index.d.ts +1 -0
  76. package/lib/ir/ui/index.d.ts.map +1 -1
  77. package/lib/ir/ui/index.js.map +1 -1
  78. package/lib/language/core/index.d.ts +2 -0
  79. package/lib/language/core/index.d.ts.map +1 -1
  80. package/lib/language/core/index.js +1 -0
  81. package/lib/language/core/index.js.map +1 -1
  82. package/lib/language/core/languagePath.d.ts +52 -0
  83. package/lib/language/core/languagePath.d.ts.map +1 -0
  84. package/lib/language/core/languagePath.js +65 -0
  85. package/lib/language/core/languagePath.js.map +1 -0
  86. package/lib/locales/de.json +2 -4
  87. package/lib/locales/en.json +2 -4
  88. package/lib/locales/es.json +2 -4
  89. package/lib/locales/fr.json +2 -4
  90. package/lib/locales/it.json +2 -4
  91. package/lib/overlay/ui/OverlayPanel.d.ts +6 -0
  92. package/lib/overlay/ui/OverlayPanel.d.ts.map +1 -1
  93. package/lib/overlay/ui/OverlayPanel.js +2 -2
  94. package/lib/overlay/ui/OverlayPanel.js.map +1 -1
  95. package/lib/overlay/ui/overlayPanelStyles.d.ts +2 -1
  96. package/lib/overlay/ui/overlayPanelStyles.d.ts.map +1 -1
  97. package/lib/overlay/ui/overlayPanelStyles.js +3 -1
  98. package/lib/overlay/ui/overlayPanelStyles.js.map +1 -1
  99. package/lib/periodic/ui/ElementCell.d.ts +0 -6
  100. package/lib/periodic/ui/ElementCell.d.ts.map +1 -1
  101. package/lib/periodic/ui/ElementCell.js +34 -30
  102. package/lib/periodic/ui/ElementCell.js.map +1 -1
  103. package/lib/periodic/ui/PeriodicTable.d.ts.map +1 -1
  104. package/lib/periodic/ui/PeriodicTable.js +12 -15
  105. package/lib/periodic/ui/PeriodicTable.js.map +1 -1
  106. package/lib/seo/core/alternates.d.ts +40 -0
  107. package/lib/seo/core/alternates.d.ts.map +1 -0
  108. package/lib/seo/core/alternates.js +42 -0
  109. package/lib/seo/core/alternates.js.map +1 -0
  110. package/lib/seo/core/index.d.ts +3 -0
  111. package/lib/seo/core/index.d.ts.map +1 -1
  112. package/lib/seo/core/index.js +2 -0
  113. package/lib/seo/core/index.js.map +1 -1
  114. package/lib/seo/core/noscript.d.ts +9 -0
  115. package/lib/seo/core/noscript.d.ts.map +1 -1
  116. package/lib/seo/core/noscript.js +7 -2
  117. package/lib/seo/core/noscript.js.map +1 -1
  118. package/lib/seo/core/pageMeta.d.ts +23 -0
  119. package/lib/seo/core/pageMeta.d.ts.map +1 -1
  120. package/lib/seo/core/pageMeta.js +52 -3
  121. package/lib/seo/core/pageMeta.js.map +1 -1
  122. package/lib/seo/core/pageProse.d.ts +76 -0
  123. package/lib/seo/core/pageProse.d.ts.map +1 -0
  124. package/lib/seo/core/pageProse.js +92 -0
  125. package/lib/seo/core/pageProse.js.map +1 -0
  126. package/lib/seo/core/routeProblems.d.ts +33 -0
  127. package/lib/seo/core/routeProblems.d.ts.map +1 -0
  128. package/lib/seo/core/routeProblems.js +89 -0
  129. package/lib/seo/core/routeProblems.js.map +1 -0
  130. package/lib/seo/core/routes.d.ts +14 -0
  131. package/lib/seo/core/routes.d.ts.map +1 -1
  132. package/lib/seo/core/routes.js.map +1 -1
  133. package/lib/seo/core/siteFiles.d.ts.map +1 -1
  134. package/lib/seo/core/siteFiles.js +2 -1
  135. package/lib/seo/core/siteFiles.js.map +1 -1
  136. package/lib/seo/vite/prerender.d.ts +13 -0
  137. package/lib/seo/vite/prerender.d.ts.map +1 -1
  138. package/lib/seo/vite/prerender.js +17 -10
  139. package/lib/seo/vite/prerender.js.map +1 -1
  140. package/package.json +2 -1
  141. package/src/about/core/about.ts +1 -51
  142. package/src/about/core/index.ts +0 -1
  143. package/src/about/ui/AboutBuild.tsx +8 -37
  144. package/src/about/ui/AboutPage.tsx +8 -64
  145. package/src/about/ui/AboutVersion.tsx +5 -45
  146. package/src/download/core/index.ts +0 -25
  147. package/src/download/ui/FigureDownload.tsx +11 -148
  148. package/src/download/ui/FigureDownloadPanel.tsx +46 -45
  149. package/src/download/ui/figureDownloadHint.ts +52 -0
  150. package/src/download/ui/index.ts +6 -10
  151. package/src/download/ui/useFigureActions.ts +113 -0
  152. package/src/download/ui/useFigureDownload.tsx +191 -0
  153. package/src/ecosystem/core/sites.ts +0 -40
  154. package/src/ecosystem/core/types.ts +0 -10
  155. package/src/ir/core/irCommands.ts +2 -2
  156. package/src/ir/ui/IrAboutDialog.tsx +0 -13
  157. package/src/ir/ui/IrCanvas.tsx +8 -2
  158. package/src/ir/ui/IrShell.tsx +23 -25
  159. package/src/ir/ui/IrToolbar.tsx +36 -6
  160. package/src/ir/ui/index.ts +1 -0
  161. package/src/language/core/index.ts +2 -0
  162. package/src/language/core/languagePath.ts +78 -0
  163. package/src/locales/de.json +2 -4
  164. package/src/locales/en.json +2 -4
  165. package/src/locales/es.json +2 -4
  166. package/src/locales/fr.json +2 -4
  167. package/src/locales/it.json +2 -4
  168. package/src/overlay/ui/OverlayPanel.tsx +8 -2
  169. package/src/overlay/ui/overlayPanelStyles.ts +3 -0
  170. package/src/periodic/ui/ElementCell.tsx +34 -37
  171. package/src/periodic/ui/PeriodicTable.tsx +12 -17
  172. package/src/seo/core/alternates.ts +65 -0
  173. package/src/seo/core/index.ts +3 -0
  174. package/src/seo/core/noscript.ts +18 -2
  175. package/src/seo/core/pageMeta.ts +74 -3
  176. package/src/seo/core/pageProse.ts +146 -0
  177. package/src/seo/core/routeProblems.ts +111 -0
  178. package/src/seo/core/routes.ts +14 -0
  179. package/src/seo/core/siteFiles.ts +2 -1
  180. package/src/seo/vite/prerender.ts +30 -11
  181. package/lib/about/core/repository.d.ts +0 -14
  182. package/lib/about/core/repository.d.ts.map +0 -1
  183. package/lib/about/core/repository.js +0 -21
  184. package/lib/about/core/repository.js.map +0 -1
  185. package/lib/download/core/exportStyles.d.ts +0 -22
  186. package/lib/download/core/exportStyles.d.ts.map +0 -1
  187. package/lib/download/core/exportStyles.js +0 -19
  188. package/lib/download/core/exportStyles.js.map +0 -1
  189. package/lib/download/core/imageResolution.d.ts +0 -88
  190. package/lib/download/core/imageResolution.d.ts.map +0 -1
  191. package/lib/download/core/imageResolution.js +0 -77
  192. package/lib/download/core/imageResolution.js.map +0 -1
  193. package/lib/download/core/pictureExport.d.ts +0 -93
  194. package/lib/download/core/pictureExport.d.ts.map +0 -1
  195. package/lib/download/core/pictureExport.js +0 -116
  196. package/lib/download/core/pictureExport.js.map +0 -1
  197. package/lib/download/ui/ExportDialogFrame.d.ts +0 -46
  198. package/lib/download/ui/ExportDialogFrame.d.ts.map +0 -1
  199. package/lib/download/ui/ExportDialogFrame.js +0 -24
  200. package/lib/download/ui/ExportDialogFrame.js.map +0 -1
  201. package/lib/download/ui/ExportImageDialog.d.ts +0 -47
  202. package/lib/download/ui/ExportImageDialog.d.ts.map +0 -1
  203. package/lib/download/ui/ExportImageDialog.js +0 -25
  204. package/lib/download/ui/ExportImageDialog.js.map +0 -1
  205. package/lib/download/ui/ExportNameField.d.ts +0 -29
  206. package/lib/download/ui/ExportNameField.d.ts.map +0 -1
  207. package/lib/download/ui/ExportNameField.js +0 -99
  208. package/lib/download/ui/ExportNameField.js.map +0 -1
  209. package/lib/download/ui/ExportPictureSection.d.ts +0 -38
  210. package/lib/download/ui/ExportPictureSection.d.ts.map +0 -1
  211. package/lib/download/ui/ExportPictureSection.js +0 -48
  212. package/lib/download/ui/ExportPictureSection.js.map +0 -1
  213. package/lib/download/ui/useDrawingBox.d.ts +0 -23
  214. package/lib/download/ui/useDrawingBox.d.ts.map +0 -1
  215. package/lib/download/ui/useDrawingBox.js +0 -21
  216. package/lib/download/ui/useDrawingBox.js.map +0 -1
  217. package/src/about/core/repository.ts +0 -22
  218. package/src/download/core/exportStyles.ts +0 -20
  219. package/src/download/core/imageResolution.ts +0 -118
  220. package/src/download/core/pictureExport.ts +0 -207
  221. package/src/download/ui/ExportDialogFrame.tsx +0 -86
  222. package/src/download/ui/ExportImageDialog.tsx +0 -91
  223. package/src/download/ui/ExportNameField.tsx +0 -148
  224. package/src/download/ui/ExportPictureSection.tsx +0 -142
  225. package/src/download/ui/useDrawingBox.ts +0 -37
@@ -10,7 +10,7 @@ import { providers } from './providers.ts';
10
10
 
11
11
  /**
12
12
  * What a site says about itself: the one page a visitor opens to find out what
13
- * the tool is, what it stands on, and who to tell when it breaks.
13
+ * the tool is, who provides it, and what it stands on.
14
14
  *
15
15
  * Every field is content, never markup. A site writes this record and the page
16
16
  * is drawn from it, which is what keeps fourteen About pages in one order and
@@ -53,35 +53,12 @@ export interface AboutContent {
53
53
  * @default undefined
54
54
  */
55
55
  providedBy?: readonly ProviderId[];
56
- /**
57
- * Licence the site itself is published under.
58
- * @default 'MIT'
59
- */
60
- license?: string;
61
- /**
62
- * Where the sources live.
63
- * @default the repository of the ECOSYSTEM_SITES record
64
- */
65
- repository?: string;
66
- /**
67
- * Whether a visitor can open that repository. A site naming a repository of
68
- * its own says so here too, since the family's record then describes another
69
- * one.
70
- * @default the `publicRepository` of the ECOSYSTEM_SITES record
71
- */
72
- publicRepository?: boolean;
73
56
  /**
74
57
  * Which build is running, from `react-cheminfo/build-info` — never written
75
58
  * by hand, which is what keeps it true after the next release.
76
59
  * @default undefined — the site's build does not publish one
77
60
  */
78
61
  build?: BuildInfo;
79
- /**
80
- * Where a problem is reported: an address a visitor can actually open, so a
81
- * site whose sources are private names one of its own or says nothing.
82
- * @default the repository's /issues, and none at all when it is private
83
- */
84
- issues?: string;
85
62
  }
86
63
 
87
64
  /** One person who made a site. */
@@ -114,14 +91,8 @@ export interface ResolvedAbout {
114
91
  people: readonly AboutPerson[];
115
92
  /** The institutions providing the site, empty when the site names none. */
116
93
  providedBy: ProviderEntry[];
117
- license: string;
118
- repository: string;
119
- /** Whether a visitor can open the repository, and so whether it is named. */
120
- publicRepository: boolean;
121
94
  /** Which build is running, or `undefined` when the site does not say. */
122
95
  build: BuildInfo | undefined;
123
- /** Where a problem is reported, or `undefined` when there is nowhere open. */
124
- issues: string | undefined;
125
96
  }
126
97
 
127
98
  /**
@@ -137,10 +108,6 @@ export function resolveAbout(content: AboutContent): ResolvedAbout {
137
108
  typeof content.siteId === 'string'
138
109
  ? siteById(content.siteId)
139
110
  : content.siteId;
140
- const repository = content.repository ?? site.repository;
141
- const publicRepository =
142
- content.publicRepository ?? site.publicRepository ?? false;
143
-
144
111
  return {
145
112
  site,
146
113
  what: content.what,
@@ -150,17 +117,7 @@ export function resolveAbout(content: AboutContent): ResolvedAbout {
150
117
  cite: content.cite ?? [],
151
118
  people: content.people ?? [],
152
119
  providedBy: providers(content.providedBy ?? []),
153
- license: content.license ?? DEFAULT_LICENSE,
154
- repository,
155
- publicRepository,
156
120
  build: content.build,
157
- // A tracker behind a private repository answers 404 to every visitor, so
158
- // a site that names none of its own asks for a report nowhere.
159
- issues:
160
- content.issues ??
161
- (publicRepository
162
- ? `${withoutTrailingSlash(repository)}/issues`
163
- : undefined),
164
121
  };
165
122
  }
166
123
 
@@ -222,16 +179,9 @@ export function aboutProblems(content: AboutContent): string[] {
222
179
  return problems;
223
180
  }
224
181
 
225
- /** What a site is published under unless it says otherwise. */
226
- const DEFAULT_LICENSE = 'MIT';
227
-
228
182
  const WHAT_LIMIT = 160;
229
183
  const CAN_MIN = 3;
230
184
  const CAN_MAX = 6;
231
185
  const CAN_LINE_LIMIT = 90;
232
186
  const PARAGRAPH_MAX = 2;
233
187
  const PARAGRAPH_LIMIT = 400;
234
-
235
- function withoutTrailingSlash(url: string): string {
236
- return url.endsWith('/') ? url.slice(0, -1) : url;
237
- }
@@ -1,4 +1,3 @@
1
- export { githubSources } from './repository.ts';
2
1
  export type { AboutContent, AboutPerson, ResolvedAbout } from './about.ts';
3
2
  export { aboutProblems, resolveAbout } from './about.ts';
4
3
  export type { ProviderEntry, ProviderId } from './providers.ts';
@@ -1,15 +1,12 @@
1
- import type { CSSProperties, ReactElement, ReactNode } from 'react';
1
+ import type { CSSProperties, ReactElement } from 'react';
2
2
 
3
3
  import type { BuildInfo } from '../../build/core/buildInfo.ts';
4
4
  import { formatBuiltAt, shortCommit } from '../../build/core/buildInfo.ts';
5
5
  import { useChromeT } from '../../i18n/ui/useT.ts';
6
- import { githubSources } from '../core/repository.ts';
7
6
 
8
7
  export interface AboutBuildProps {
9
8
  /** What the build published about itself. */
10
9
  build: BuildInfo;
11
- /** Where the sources live, which is what the version and commit link into. */
12
- repository: string;
13
10
  }
14
11
 
15
12
  /**
@@ -17,23 +14,21 @@ export interface AboutBuildProps {
17
14
  *
18
15
  * A report of something going wrong is worth answering only when we know what
19
16
  * was running. The version itself is in the hero, where a reader is asked to
20
- * quote it from; this line carries the rest, and the commit links into the
21
- * repository, so the change that caused a problem is one click from the page
22
- * that shows it.
17
+ * quote it from; this line carries the rest, so a report can name the exact
18
+ * build the problem was seen on.
23
19
  * @param props - See {@link AboutBuildProps}.
24
20
  * @returns The line.
25
21
  */
26
22
  export function AboutBuild(props: AboutBuildProps): ReactElement {
27
- const { build, repository } = props;
23
+ const { build } = props;
28
24
  const t = useChromeT();
29
- const sources = githubSources(repository);
30
25
  const builtAt = formatBuiltAt(build.builtAt);
31
26
  if (build.commit === undefined) {
32
27
  return <p style={PARAGRAPH_STYLE}>{t('about.built', { builtAt })}</p>;
33
28
  }
34
29
 
35
- // The commit is a link inside a sentence whose word order is the
36
- // translator's, so the message is split where it names it.
30
+ // The commit sits inside a sentence whose word order is the translator's,
31
+ // so the message names where it goes and is split there.
37
32
  const [before = '', after = ''] = t('about.builtFromCommit', {
38
33
  builtAt,
39
34
  }).split('{commit}');
@@ -41,36 +36,14 @@ export function AboutBuild(props: AboutBuildProps): ReactElement {
41
36
  return (
42
37
  <p style={PARAGRAPH_STYLE}>
43
38
  {before}
44
- <Reference
45
- href={
46
- sources === undefined
47
- ? undefined
48
- : `${sources}/commit/${build.commit}`
49
- }
50
- >
51
- {shortCommit(build.commit)}
52
- </Reference>
39
+ <code style={CODE_STYLE}>{shortCommit(build.commit)}</code>
53
40
  {after}
54
41
  </p>
55
42
  );
56
43
  }
57
44
 
58
- function Reference(props: {
59
- href: string | undefined;
60
- children: ReactNode;
61
- }): ReactElement {
62
- const { href, children } = props;
63
- if (href === undefined) return <code style={CODE_STYLE}>{children}</code>;
64
-
65
- return (
66
- <a href={href} target="_blank" rel="noopener noreferrer" style={LINK_STYLE}>
67
- <code style={CODE_STYLE}>{children}</code>
68
- </a>
69
- );
70
- }
71
-
72
45
  const PARAGRAPH_STYLE = {
73
- margin: '8px 0 0',
46
+ margin: 0,
74
47
  color: 'var(--text-muted)',
75
48
  fontSize: 13,
76
49
  } as const satisfies CSSProperties;
@@ -79,5 +52,3 @@ const CODE_STYLE = {
79
52
  fontFamily: 'var(--font-mono, ui-monospace, monospace)',
80
53
  fontSize: 13,
81
54
  } as const satisfies CSSProperties;
82
-
83
- const LINK_STYLE = { color: 'var(--accent)' } as const satisfies CSSProperties;
@@ -47,8 +47,8 @@ export interface AboutPageProps {
47
47
 
48
48
  /**
49
49
  * The About page of a site of the family: what the tool is, who provides it,
50
- * what a visitor can do with it, what it is built on, how to cite it, and where
51
- * to report a problem — always in that order.
50
+ * what a visitor can do with it, what it is built on, and how to cite it —
51
+ * always in that order.
52
52
  *
53
53
  * The order is the point. A reader who has read one of our About pages knows
54
54
  * where the credits are on the other thirteen, and a site that writes its own
@@ -83,11 +83,7 @@ export function AboutPage(props: AboutPageProps): ReactElement {
83
83
  </p>
84
84
  <p style={WHAT_STYLE}>{about.what}</p>
85
85
  </div>
86
- <AboutVersion
87
- build={about.build}
88
- repository={about.repository}
89
- publicRepository={about.publicRepository}
90
- />
86
+ <AboutVersion build={about.build} />
91
87
  </header>
92
88
 
93
89
  {about.people.length === 0 && about.providedBy.length === 0 ? null : (
@@ -133,68 +129,18 @@ export function AboutPage(props: AboutPageProps): ReactElement {
133
129
  )}
134
130
 
135
131
  {/*
136
- A repository nobody outside can open is named nowhere: a licence a
137
- reader cannot act on and a link that answers 404 say less than silence.
138
- The version stays in the hero, unlinked, so a report still names a build.
132
+ The build the reader has open, last and under no heading: it is what a
133
+ report has to name, not something anybody came to the page to read.
139
134
  */}
140
- {!about.publicRepository ? null : (
141
- <AboutSection
142
- title={t('about.licenceAndSource')}
143
- className="about-licence"
144
- >
145
- <p style={FIRST_PARAGRAPH_STYLE}>
146
- {around(
147
- t('about.licence', { license: about.license }),
148
- 'sources',
149
- <ExternalLink key="sources" href={about.repository} />,
150
- )}
151
- </p>
152
- {about.build === undefined ? null : (
153
- <AboutBuild build={about.build} repository={about.repository} />
154
- )}
155
- </AboutSection>
156
- )}
157
-
158
- {/*
159
- Asking for a report is worth nothing without somewhere to send it, and
160
- the tracker of a private repository answers 404 to every visitor.
161
- */}
162
- {about.issues === undefined ? null : (
163
- <AboutSection title={t('about.foundAProblem')} className="about-issues">
164
- <p style={FIRST_PARAGRAPH_STYLE}>
165
- {around(
166
- t('about.reportIt'),
167
- 'tracker',
168
- <ExternalLink key="tracker" href={about.issues} />,
169
- )}
170
- </p>
135
+ {about.build === undefined ? null : (
136
+ <AboutSection className="about-build">
137
+ <AboutBuild build={about.build} />
171
138
  </AboutSection>
172
139
  )}
173
140
  </div>
174
141
  );
175
142
  }
176
143
 
177
- // A link sits inside a sentence whose word order is the translator's, so the
178
- // message names where it goes with a placeholder and is split there.
179
- function around(
180
- message: string,
181
- placeholder: string,
182
- link: ReactNode,
183
- ): ReactNode[] {
184
- const [before = '', after = ''] = message.split(`{${placeholder}}`);
185
- return [before, link, after];
186
- }
187
-
188
- function ExternalLink(props: { href: string }): ReactElement {
189
- const { href } = props;
190
-
191
- return (
192
- <a href={href} target="_blank" rel="noopener noreferrer" style={LINK_STYLE}>
193
- {href.replace(/^https?:\/\//, '')}
194
- </a>
195
- );
196
- }
197
-
198
144
  const PAGE_STYLE = {
199
145
  display: 'flex',
200
146
  flexDirection: 'column',
@@ -250,5 +196,3 @@ const FIRST_PARAGRAPH_STYLE = {
250
196
  const PARAGRAPH_STYLE = {
251
197
  margin: '8px 0 0',
252
198
  } as const satisfies CSSProperties;
253
-
254
- const LINK_STYLE = { color: 'var(--accent)' } as const satisfies CSSProperties;
@@ -14,17 +14,11 @@ import {
14
14
  buildLabel,
15
15
  buildStamp,
16
16
  buildSummary,
17
- releasedVersion,
18
17
  } from '../../build/core/buildInfo.ts';
19
- import { githubSources } from '../core/repository.ts';
20
18
 
21
19
  export interface AboutVersionProps {
22
20
  /** What the build published about itself, or `undefined` when it published none. */
23
21
  build: BuildInfo | undefined;
24
- /** Where the sources live, which is what the badge links into. */
25
- repository: string;
26
- /** Whether a visitor can open that repository, and so whether it is linked. */
27
- publicRepository: boolean;
28
22
  }
29
23
 
30
24
  /**
@@ -34,53 +28,24 @@ export interface AboutVersionProps {
34
28
  * @returns The badge, or `null` when the build says nothing worth showing.
35
29
  */
36
30
  export function AboutVersion(props: AboutVersionProps): ReactElement | null {
37
- const { build, repository, publicRepository } = props;
31
+ const { build } = props;
38
32
  const label = buildLabel(build);
39
33
  if (build === undefined || label === undefined) return null;
40
- const href = publicRepository ? addressOf(build, repository) : undefined;
41
- const summary = buildSummary(build);
42
34
  // The instant belongs on the badge rather than in its hover: a version
43
35
  // nobody can date says nothing about how old the page they read is.
44
36
  const badge = `${label} · ${buildStamp(build.builtAt)}`;
45
- if (href === undefined) {
46
- return (
47
- <span style={BADGE_STYLE} className="about-version" title={summary}>
48
- {badge}
49
- </span>
50
- );
51
- }
52
37
 
53
38
  return (
54
- <a
39
+ <span
40
+ style={BADGE_STYLE}
55
41
  className="about-version"
56
- href={href}
57
- title={summary}
58
- target="_blank"
59
- rel="noopener noreferrer"
60
- style={LINK_BADGE_STYLE}
42
+ title={buildSummary(build)}
61
43
  >
62
44
  {badge}
63
- </a>
45
+ </span>
64
46
  );
65
47
  }
66
48
 
67
- /**
68
- * What the badge opens: the release it names, or the commit it was built from.
69
- * @param build - What the build published about itself.
70
- * @param repository - Where the sources live.
71
- * @returns The address, or `undefined` when nothing inside the repository can
72
- * be addressed.
73
- */
74
- function addressOf(build: BuildInfo, repository: string): string | undefined {
75
- const sources = githubSources(repository);
76
- if (sources === undefined) return undefined;
77
- const version = releasedVersion(build);
78
- if (version !== undefined) return `${sources}/releases/tag/v${version}`;
79
- return build.commit === undefined
80
- ? undefined
81
- : `${sources}/commit/${build.commit}`;
82
- }
83
-
84
49
  const BADGE_STYLE = {
85
50
  flex: '0 0 auto',
86
51
  fontFamily: 'var(--font-mono, ui-monospace, monospace)',
@@ -88,8 +53,3 @@ const BADGE_STYLE = {
88
53
  color: 'var(--text-muted)',
89
54
  whiteSpace: 'nowrap',
90
55
  } as const satisfies CSSProperties;
91
-
92
- const LINK_BADGE_STYLE = {
93
- ...BADGE_STYLE,
94
- color: 'var(--accent)',
95
- } as const satisfies CSSProperties;
@@ -51,31 +51,6 @@ export {
51
51
  } from './figureTarget.ts';
52
52
  export { sanitizeFileName } from './sanitizeFileName.ts';
53
53
 
54
- export {
55
- exportButtonsStyle,
56
- exportNoteStyle,
57
- exportSectionStyle,
58
- } from './exportStyles.ts';
59
- export type {
60
- ImageResolution,
61
- ImageResolutionId,
62
- ImageSize,
63
- } from './imageResolution.ts';
64
- export {
65
- DEFAULT_IMAGE_RESOLUTION,
66
- IMAGE_RESOLUTIONS,
67
- formatImageSize,
68
- imageResolution,
69
- imageSize,
70
- } from './imageResolution.ts';
71
- export type { PictureFrame, PictureOptions } from './pictureExport.ts';
72
- export {
73
- PICTURE_PADDING,
74
- copyPicture,
75
- pictureSize,
76
- savePngPicture,
77
- saveSvgPicture,
78
- } from './pictureExport.ts';
79
54
  export { drawingIn, stackDrawings } from './stackedDrawing.ts';
80
55
  export type { SvgBox } from './svgBounds.ts';
81
56
  export { CONTENT_ATTRIBUTE, contentBox, elementBox } from './svgBounds.ts';
@@ -1,74 +1,20 @@
1
1
  import type { IconName } from '@blueprintjs/core';
2
2
  import { PopoverNext } from '@blueprintjs/core';
3
3
  import type { ReactElement } from 'react';
4
- import { useState } from 'react';
5
4
 
6
5
  import { useChromeT } from '../../i18n/ui/useT.ts';
7
6
  import { OverlayIconButton } from '../../overlay/ui/OverlayIconButton.tsx';
8
- import type { FigureFormat } from '../core/downloadFigure.ts';
9
- import { downloadFigure } from '../core/downloadFigure.ts';
10
- import type { FigureLayout } from '../core/figureLayout.ts';
11
- import { figureLayoutRedraws, figureLayoutSize } from '../core/figureLayout.ts';
12
- import type { FigurePixels } from '../core/figureScale.ts';
13
- import {
14
- DEFAULT_FIGURE_SCALE,
15
- FIGURE_SCALES,
16
- defaultFigureScale,
17
- } from '../core/figureScale.ts';
18
- import { figureSize } from '../core/figureTarget.ts';
19
7
 
20
- import { FigureDownloadPanel } from './FigureDownloadPanel.tsx';
21
- import type { FigureRenderer } from './useFigureRedraw.tsx';
22
- import { useFigureRedraw } from './useFigureRedraw.tsx';
8
+ import type { FigureDownloadOptions } from './useFigureDownload.tsx';
9
+ import { useFigureDownload } from './useFigureDownload.tsx';
23
10
 
24
11
  /** What {@link FigureDownload} needs. */
25
- export interface FigureDownloadProps {
26
- /**
27
- * The `id` of the box the figure is mounted in. Everything drawn inside it
28
- * is saved — sixteen charts of a pair grid as readily as one scatter plot —
29
- * and the controls floating over it are left behind.
30
- */
31
- targetId: string;
32
- /**
33
- * What the saved file is called, without its extension. Name it after the
34
- * data rather than after the tool: a reader with four of these in a
35
- * downloads folder cannot tell four `figure.png` apart.
36
- * @default 'figure'
37
- */
38
- fileName?: string;
39
- /**
40
- * The format it opens on.
41
- * @default 'png'
42
- */
43
- defaultFormat?: FigureFormat;
44
- /**
45
- * The resolution a PNG opens on, as a multiple of the figure on screen. An
46
- * SVG opens at 1×, the size it has on screen, and each format keeps the
47
- * multiple the reader last picked for it.
48
- * @default 2
49
- */
50
- defaultScale?: number;
51
- /**
52
- * The resolutions offered.
53
- * @default [1, 2, 3, 4]
54
- */
55
- scales?: readonly number[];
56
- /**
57
- * What is painted under the figure. `transparent` leaves it unpainted, for a
58
- * figure going onto a coloured slide.
59
- * @default the surface the figure is drawn on
60
- */
61
- background?: string;
12
+ export interface FigureDownloadProps extends FigureDownloadOptions {
62
13
  /**
63
14
  * What the glyph is called, for the pointer and for a screen reader.
64
15
  * @default the chrome's own line, in the language of the page
65
16
  */
66
17
  label?: string;
67
- /**
68
- * What the panel behind it is called.
69
- * @default the chrome's own line, in the language of the page
70
- */
71
- title?: string;
72
18
  /**
73
19
  * Its glyph.
74
20
  * @default 'download'
@@ -79,20 +25,11 @@ export interface FigureDownloadProps {
79
25
  * @default undefined
80
26
  */
81
27
  testId?: string;
82
- /**
83
- * Draws the figure at a size, for a file. When it is given the panel offers
84
- * a size — as shown, 4:3, 16:9, a journal column, or typed — and a figure
85
- * saved at a size other than its own is drawn again at that size, off the
86
- * page, so its axes and labels are laid out for the new shape rather than
87
- * stretched into it. Draw exactly what is on screen, with the same data,
88
- * zoom and colours; only the box changes.
89
- * @default undefined — the figure is saved at the size it has on screen
90
- */
91
- renderFigure?: FigureRenderer;
92
28
  }
93
29
 
94
30
  /**
95
- * The glyph that takes the figure off the page as a file.
31
+ * The glyph that takes the figure off the page, as a file or onto the
32
+ * clipboard.
96
33
  *
97
34
  * It works from the `id` of the box the figure is mounted in rather than from
98
35
  * the figure itself, so the control is free to sit in the bar above the
@@ -112,97 +49,23 @@ export interface FigureDownloadProps {
112
49
  * @returns The glyph and its panel.
113
50
  */
114
51
  export function FigureDownload(props: FigureDownloadProps): ReactElement {
115
- const { targetId, fileName, background, scales = FIGURE_SCALES } = props;
116
- const { defaultFormat = 'png', defaultScale = DEFAULT_FIGURE_SCALE } = props;
117
- const { label, title } = props;
118
- const { icon = 'download', testId, renderFigure } = props;
119
-
52
+ const { label, icon = 'download', testId, ...options } = props;
120
53
  const t = useChromeT();
121
- const [open, setOpen] = useState(false);
122
- const [format, setFormat] = useState<FigureFormat>(defaultFormat);
123
- const [picked, setPicked] = useState<Record<FigureFormat, number>>(() => ({
124
- png: defaultScale,
125
- svg: defaultFigureScale('svg'),
126
- }));
127
- const scale = picked[format];
128
- const [size, setSize] = useState<FigurePixels | null>(null);
129
- const [failure, setFailure] = useState<string | null>(null);
130
- const [saving, setSaving] = useState(false);
131
- const [layout, setLayout] = useState<FigureLayout>('screen');
132
- const [custom, setCustom] = useState<FigurePixels | null>(null);
133
- const { redraw, portal } = useFigureRedraw(renderFigure);
134
-
135
- // The panel is held open from here rather than left to itself, because the
136
- // figure has to be measured at the moment it opens: its size is what the
137
- // panel writes out, and reading a bounding box on every render would cost a
138
- // layout pass on a bar that is re-rendered on every pointer move.
139
- function interact(next: boolean): void {
140
- setOpen(next);
141
- if (!next) return;
142
- setSize(figureSize(targetId));
143
- setFailure(null);
144
- }
145
-
146
- async function save(): Promise<void> {
147
- setSaving(true);
148
- try {
149
- const options = { format, scale, fileName, background };
150
- if (
151
- renderFigure !== undefined &&
152
- size !== null &&
153
- figureLayoutRedraws(layout, size, custom ?? size)
154
- ) {
155
- const drawn = figureLayoutSize(layout, size, custom ?? size);
156
- await redraw(targetId, drawn, (box) => downloadFigure(box, options));
157
- } else {
158
- await downloadFigure(targetId, options);
159
- }
160
- setFailure(null);
161
- } catch (error) {
162
- setFailure(error instanceof Error ? error.message : String(error));
163
- } finally {
164
- setSaving(false);
165
- }
166
- }
54
+ const { isOpen, setOpen, panel, format, portal } = useFigureDownload(options);
167
55
 
168
56
  return (
169
57
  <>
170
58
  <PopoverNext
171
- isOpen={open}
59
+ isOpen={isOpen}
172
60
  placement="bottom-end"
173
- onInteraction={interact}
174
- content={
175
- <FigureDownloadPanel
176
- title={title ?? t('download.saveFigure')}
177
- format={format}
178
- scale={scale}
179
- scales={scales}
180
- size={size}
181
- failure={failure}
182
- saving={saving}
183
- onFormatChange={setFormat}
184
- onScaleChange={(next) =>
185
- setPicked((current) => ({ ...current, [format]: next }))
186
- }
187
- onSave={() => void save()}
188
- sizing={
189
- renderFigure === undefined
190
- ? undefined
191
- : {
192
- layout,
193
- custom,
194
- onLayoutChange: setLayout,
195
- onCustomChange: setCustom,
196
- }
197
- }
198
- />
199
- }
61
+ onInteraction={setOpen}
62
+ content={panel}
200
63
  >
201
64
  <OverlayIconButton
202
65
  icon={icon}
203
66
  label={label ?? t('download.saveThisFigure')}
204
67
  value={format.toUpperCase()}
205
- active={open}
68
+ active={isOpen}
206
69
  testId={testId}
207
70
  opensMenu
208
71
  />