react-cheminfo 0.8.0 → 0.9.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.
- package/README.md +60 -2
- package/bin/check-tokens.mjs +105 -0
- package/lib/about/core/about.d.ts +95 -0
- package/lib/about/core/about.d.ts.map +1 -0
- package/lib/about/core/about.js +78 -0
- package/lib/about/core/about.js.map +1 -0
- package/lib/about/core/index.d.ts +3 -0
- package/lib/about/core/index.d.ts.map +1 -0
- package/lib/about/core/index.js +2 -0
- package/lib/about/core/index.js.map +1 -0
- package/lib/about/ui/AboutCitations.d.ts +14 -0
- package/lib/about/ui/AboutCitations.d.ts.map +1 -0
- package/lib/about/ui/AboutCitations.js +40 -0
- package/lib/about/ui/AboutCitations.js.map +1 -0
- package/lib/about/ui/AboutPage.d.ts +32 -0
- package/lib/about/ui/AboutPage.d.ts.map +1 -0
- package/lib/about/ui/AboutPage.js +70 -0
- package/lib/about/ui/AboutPage.js.map +1 -0
- package/lib/about/ui/AboutSection.d.ts +23 -0
- package/lib/about/ui/AboutSection.d.ts.map +1 -0
- package/lib/about/ui/AboutSection.js +28 -0
- package/lib/about/ui/AboutSection.js.map +1 -0
- package/lib/about/ui/index.d.ts +7 -0
- package/lib/about/ui/index.d.ts.map +1 -0
- package/lib/about/ui/index.js +4 -0
- package/lib/about/ui/index.js.map +1 -0
- package/lib/chrome/ui/index.d.ts +1 -0
- package/lib/chrome/ui/index.d.ts.map +1 -1
- package/lib/chrome/ui/index.js +3 -0
- package/lib/chrome/ui/index.js.map +1 -1
- package/lib/clipboard/ui/CodeBlock.d.ts.map +1 -1
- package/lib/clipboard/ui/CodeBlock.js +4 -1
- package/lib/clipboard/ui/CodeBlock.js.map +1 -1
- package/lib/color/ui/ColorScaleLegend.js +1 -1
- package/lib/color/ui/ColorScaleLegend.js.map +1 -1
- package/lib/core.d.ts +3 -0
- package/lib/core.d.ts.map +1 -1
- package/lib/core.js +5 -0
- package/lib/core.js.map +1 -1
- package/lib/credits/core/credits.d.ts +36 -0
- package/lib/credits/core/credits.d.ts.map +1 -1
- package/lib/credits/core/credits.js +42 -0
- package/lib/credits/core/credits.js.map +1 -1
- package/lib/credits/ui/CreditsList.js +1 -1
- package/lib/credits/ui/CreditsList.js.map +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js +1 -1
- package/lib/delimited/ui/DelimitedTextPanel.js.map +1 -1
- package/lib/ecosystem/core/sites.d.ts +1 -1
- package/lib/ecosystem/core/sites.d.ts.map +1 -1
- package/lib/ecosystem/core/sites.js +10 -0
- package/lib/ecosystem/core/sites.js.map +1 -1
- package/lib/ecosystem/core/tokens.d.ts.map +1 -1
- package/lib/ecosystem/core/tokens.js +1 -0
- package/lib/ecosystem/core/tokens.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemLinks.js +2 -2
- package/lib/ecosystem/ui/EcosystemLinks.js.map +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js +1 -1
- package/lib/ecosystem/ui/EcosystemMenu.js.map +1 -1
- package/lib/ecosystem/ui/SiteTile.d.ts.map +1 -1
- package/lib/ecosystem/ui/SiteTile.js +5 -5
- package/lib/ecosystem/ui/SiteTile.js.map +1 -1
- package/lib/ecosystem/ui/glyphs.d.ts.map +1 -1
- package/lib/ecosystem/ui/glyphs.js +3 -0
- package/lib/ecosystem/ui/glyphs.js.map +1 -1
- package/lib/orbital/core/index.d.ts +1 -1
- package/lib/orbital/core/index.d.ts.map +1 -1
- package/lib/orbital/core/index.js +1 -1
- package/lib/orbital/core/index.js.map +1 -1
- package/lib/orbital/core/realHarmonics.d.ts +5 -2
- package/lib/orbital/core/realHarmonics.d.ts.map +1 -1
- package/lib/orbital/core/realHarmonics.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts +18 -0
- package/lib/orbital/ui/AtomicOrbitalCanvas.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalCanvas.js +84 -7
- package/lib/orbital/ui/AtomicOrbitalCanvas.js.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts +13 -0
- package/lib/orbital/ui/AtomicOrbitalViewer.d.ts.map +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js +1 -1
- package/lib/orbital/ui/AtomicOrbitalViewer.js.map +1 -1
- package/lib/orbital/ui/camera.d.ts +15 -0
- package/lib/orbital/ui/camera.d.ts.map +1 -1
- package/lib/orbital/ui/camera.js +68 -0
- package/lib/orbital/ui/camera.js.map +1 -1
- package/lib/orbital/ui/renderVolume.d.ts.map +1 -1
- package/lib/orbital/ui/renderVolume.js +8 -1
- package/lib/orbital/ui/renderVolume.js.map +1 -1
- package/lib/orbital/ui/serial.d.ts +25 -0
- package/lib/orbital/ui/serial.d.ts.map +1 -0
- package/lib/orbital/ui/serial.js +25 -0
- package/lib/orbital/ui/serial.js.map +1 -0
- package/lib/orbital/ui/viewer.d.ts +23 -0
- package/lib/orbital/ui/viewer.d.ts.map +1 -1
- package/lib/orbital/ui/viewer.js +70 -17
- package/lib/orbital/ui/viewer.js.map +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js +1 -1
- package/lib/pedagogy/ui/ExerciseProgressHeader.js.map +1 -1
- package/lib/pedagogy/ui/GlossaryText.js +2 -2
- package/lib/pedagogy/ui/GlossaryText.js.map +1 -1
- package/lib/pedagogy/ui/ReferenceSectionBlock.js +5 -2
- package/lib/pedagogy/ui/ReferenceSectionBlock.js.map +1 -1
- package/lib/pedagogy/ui/SyntaxTooltip.js +2 -2
- package/lib/pedagogy/ui/SyntaxTooltip.js.map +1 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js +4 -1
- package/lib/pedagogy/ui/TutorialStepStrip.js.map +1 -1
- package/lib/periodic/ui/CategoryLegend.js +1 -1
- package/lib/periodic/ui/CategoryLegend.js.map +1 -1
- package/lib/periodic/ui/PeriodicTableChrome.js +2 -2
- package/lib/periodic/ui/PeriodicTableChrome.js.map +1 -1
- package/lib/seo/vite/ogCard.d.ts.map +1 -1
- package/lib/seo/vite/ogCard.js +1 -0
- package/lib/seo/vite/ogCard.js.map +1 -1
- package/lib/share/ui/ShareDialogContent.d.ts.map +1 -1
- package/lib/share/ui/ShareDialogContent.js +5 -2
- package/lib/share/ui/ShareDialogContent.js.map +1 -1
- package/lib/share/ui/SharePartOptions.js +1 -1
- package/lib/share/ui/SharePartOptions.js.map +1 -1
- package/lib/slides/core/demoLinks.d.ts +43 -0
- package/lib/slides/core/demoLinks.d.ts.map +1 -0
- package/lib/slides/core/demoLinks.js +104 -0
- package/lib/slides/core/demoLinks.js.map +1 -0
- package/lib/slides/core/index.d.ts +13 -0
- package/lib/slides/core/index.d.ts.map +1 -0
- package/lib/slides/core/index.js +7 -0
- package/lib/slides/core/index.js.map +1 -0
- package/lib/slides/core/sectionHeading.d.ts +19 -0
- package/lib/slides/core/sectionHeading.d.ts.map +1 -0
- package/lib/slides/core/sectionHeading.js +19 -0
- package/lib/slides/core/sectionHeading.js.map +1 -0
- package/lib/slides/core/slideNavigation.d.ts +38 -0
- package/lib/slides/core/slideNavigation.d.ts.map +1 -0
- package/lib/slides/core/slideNavigation.js +65 -0
- package/lib/slides/core/slideNavigation.js.map +1 -0
- package/lib/slides/core/talk.d.ts +82 -0
- package/lib/slides/core/talk.d.ts.map +1 -0
- package/lib/slides/core/talk.js +134 -0
- package/lib/slides/core/talk.js.map +1 -0
- package/lib/slides/core/talkManifest.d.ts +90 -0
- package/lib/slides/core/talkManifest.d.ts.map +1 -0
- package/lib/slides/core/talkManifest.js +109 -0
- package/lib/slides/core/talkManifest.js.map +1 -0
- package/lib/slides/core/talkOrigin.d.ts +50 -0
- package/lib/slides/core/talkOrigin.d.ts.map +1 -0
- package/lib/slides/core/talkOrigin.js +62 -0
- package/lib/slides/core/talkOrigin.js.map +1 -0
- package/lib/slides/ui/BackToSlides.d.ts +26 -0
- package/lib/slides/ui/BackToSlides.d.ts.map +1 -0
- package/lib/slides/ui/BackToSlides.js +24 -0
- package/lib/slides/ui/BackToSlides.js.map +1 -0
- package/lib/slides/ui/DemoLink.d.ts +42 -0
- package/lib/slides/ui/DemoLink.d.ts.map +1 -0
- package/lib/slides/ui/DemoLink.js +17 -0
- package/lib/slides/ui/DemoLink.js.map +1 -0
- package/lib/slides/ui/EmbedSlide.d.ts +19 -0
- package/lib/slides/ui/EmbedSlide.d.ts.map +1 -0
- package/lib/slides/ui/EmbedSlide.js +56 -0
- package/lib/slides/ui/EmbedSlide.js.map +1 -0
- package/lib/slides/ui/SectionSlide.d.ts +18 -0
- package/lib/slides/ui/SectionSlide.d.ts.map +1 -0
- package/lib/slides/ui/SectionSlide.js +18 -0
- package/lib/slides/ui/SectionSlide.js.map +1 -0
- package/lib/slides/ui/Slide.d.ts +52 -0
- package/lib/slides/ui/Slide.d.ts.map +1 -0
- package/lib/slides/ui/Slide.js +58 -0
- package/lib/slides/ui/Slide.js.map +1 -0
- package/lib/slides/ui/SlideBody.d.ts +32 -0
- package/lib/slides/ui/SlideBody.d.ts.map +1 -0
- package/lib/slides/ui/SlideBody.js +27 -0
- package/lib/slides/ui/SlideBody.js.map +1 -0
- package/lib/slides/ui/Slideshow.d.ts +93 -0
- package/lib/slides/ui/Slideshow.d.ts.map +1 -0
- package/lib/slides/ui/Slideshow.js +122 -0
- package/lib/slides/ui/Slideshow.js.map +1 -0
- package/lib/slides/ui/SlideshowBar.d.ts +33 -0
- package/lib/slides/ui/SlideshowBar.d.ts.map +1 -0
- package/lib/slides/ui/SlideshowBar.js +20 -0
- package/lib/slides/ui/SlideshowBar.js.map +1 -0
- package/lib/slides/ui/TalkList.d.ts +37 -0
- package/lib/slides/ui/TalkList.d.ts.map +1 -0
- package/lib/slides/ui/TalkList.js +68 -0
- package/lib/slides/ui/TalkList.js.map +1 -0
- package/lib/slides/ui/index.d.ts +19 -0
- package/lib/slides/ui/index.d.ts.map +1 -0
- package/lib/slides/ui/index.js +10 -0
- package/lib/slides/ui/index.js.map +1 -0
- package/lib/slides/vite/index.d.ts +3 -0
- package/lib/slides/vite/index.d.ts.map +1 -0
- package/lib/slides/vite/index.js +2 -0
- package/lib/slides/vite/index.js.map +1 -0
- package/lib/slides/vite/talks.d.ts +50 -0
- package/lib/slides/vite/talks.d.ts.map +1 -0
- package/lib/slides/vite/talks.js +87 -0
- package/lib/slides/vite/talks.js.map +1 -0
- package/lib/slides.d.ts +3 -0
- package/lib/slides.d.ts.map +1 -0
- package/lib/slides.js +3 -0
- package/lib/slides.js.map +1 -0
- package/lib/structure/ui/StructureEditor.js +2 -2
- package/lib/structure/ui/StructureEditor.js.map +1 -1
- package/lib/structure/ui/StructurePlaceholder.js +1 -1
- package/lib/structure/ui/StructurePlaceholder.js.map +1 -1
- package/lib/tokens/core/checkTokens.d.ts +51 -0
- package/lib/tokens/core/checkTokens.d.ts.map +1 -0
- package/lib/tokens/core/checkTokens.js +188 -0
- package/lib/tokens/core/checkTokens.js.map +1 -0
- package/lib/tokens/core/colors.d.ts +42 -0
- package/lib/tokens/core/colors.d.ts.map +1 -0
- package/lib/tokens/core/colors.js +119 -0
- package/lib/tokens/core/colors.js.map +1 -0
- package/lib/tokens/core/index.d.ts +3 -0
- package/lib/tokens/core/index.d.ts.map +1 -0
- package/lib/tokens/core/index.js +2 -0
- package/lib/tokens/core/index.js.map +1 -0
- package/lib/tokens/core/rules.d.ts +23 -0
- package/lib/tokens/core/rules.d.ts.map +1 -0
- package/lib/tokens/core/rules.js +90 -0
- package/lib/tokens/core/rules.js.map +1 -0
- package/lib/ui.d.ts +1 -0
- package/lib/ui.d.ts.map +1 -1
- package/lib/ui.js +1 -0
- package/lib/ui.js.map +1 -1
- package/lib/vite.d.ts +1 -0
- package/lib/vite.d.ts.map +1 -1
- package/lib/vite.js +1 -0
- package/lib/vite.js.map +1 -1
- package/package.json +11 -3
- package/src/about/core/about.ts +176 -0
- package/src/about/core/index.ts +2 -0
- package/src/about/ui/AboutCitations.tsx +79 -0
- package/src/about/ui/AboutPage.tsx +177 -0
- package/src/about/ui/AboutSection.tsx +56 -0
- package/src/about/ui/index.ts +6 -0
- package/src/chrome/ui/index.ts +3 -0
- package/src/clipboard/ui/CodeBlock.tsx +4 -1
- package/src/color/ui/ColorScaleLegend.tsx +1 -1
- package/src/core.ts +5 -0
- package/src/credits/core/credits.ts +42 -0
- package/src/credits/ui/CreditsList.tsx +1 -1
- package/src/delimited/ui/DelimitedTextPanel.tsx +1 -1
- package/src/ecosystem/core/sites.ts +12 -0
- package/src/ecosystem/core/tokens.ts +1 -0
- package/src/ecosystem/ui/EcosystemLinks.tsx +2 -2
- package/src/ecosystem/ui/EcosystemMenu.tsx +1 -1
- package/src/ecosystem/ui/SiteTile.tsx +7 -5
- package/src/ecosystem/ui/glyphs.tsx +40 -0
- package/src/orbital/core/index.ts +4 -1
- package/src/orbital/core/realHarmonics.ts +5 -2
- package/src/orbital/ui/AtomicOrbitalCanvas.tsx +126 -4
- package/src/orbital/ui/AtomicOrbitalViewer.tsx +14 -1
- package/src/orbital/ui/camera.ts +81 -0
- package/src/orbital/ui/renderVolume.ts +8 -1
- package/src/orbital/ui/serial.ts +35 -0
- package/src/orbital/ui/viewer.ts +72 -13
- package/src/pedagogy/ui/ExerciseProgressHeader.tsx +1 -1
- package/src/pedagogy/ui/GlossaryText.tsx +2 -2
- package/src/pedagogy/ui/ReferenceSectionBlock.tsx +5 -2
- package/src/pedagogy/ui/SyntaxTooltip.tsx +2 -2
- package/src/pedagogy/ui/TutorialStepStrip.tsx +4 -1
- package/src/periodic/ui/CategoryLegend.tsx +1 -1
- package/src/periodic/ui/PeriodicTableChrome.tsx +2 -2
- package/src/seo/vite/ogCard.ts +1 -0
- package/src/share/ui/ShareDialogContent.tsx +5 -2
- package/src/share/ui/SharePartOptions.tsx +1 -1
- package/src/slides/core/demoLinks.ts +113 -0
- package/src/slides/core/index.ts +22 -0
- package/src/slides/core/sectionHeading.ts +29 -0
- package/src/slides/core/slideNavigation.ts +77 -0
- package/src/slides/core/talk.ts +203 -0
- package/src/slides/core/talkManifest.ts +158 -0
- package/src/slides/core/talkOrigin.ts +83 -0
- package/src/slides/ui/BackToSlides.tsx +49 -0
- package/src/slides/ui/DemoLink.tsx +67 -0
- package/src/slides/ui/EmbedSlide.tsx +85 -0
- package/src/slides/ui/SectionSlide.tsx +44 -0
- package/src/slides/ui/Slide.tsx +143 -0
- package/src/slides/ui/SlideBody.tsx +64 -0
- package/src/slides/ui/Slideshow.tsx +282 -0
- package/src/slides/ui/SlideshowBar.tsx +83 -0
- package/src/slides/ui/TalkList.tsx +146 -0
- package/src/slides/ui/index.ts +18 -0
- package/src/slides/vite/index.ts +2 -0
- package/src/slides/vite/talks.ts +126 -0
- package/src/slides.ts +2 -0
- package/src/structure/ui/StructureEditor.tsx +2 -2
- package/src/structure/ui/StructurePlaceholder.tsx +1 -1
- package/src/tokens/core/checkTokens.ts +242 -0
- package/src/tokens/core/colors.ts +122 -0
- package/src/tokens/core/index.ts +6 -0
- package/src/tokens/core/rules.ts +103 -0
- package/src/ui.ts +1 -0
- package/src/vite.ts +1 -0
- package/styles/chrome.css +1 -0
- package/styles/slides.css +667 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// tokens-ok: file — every site's two colours are declared here.
|
|
1
2
|
/** The sites the menu links to, in the order they are listed. */
|
|
2
3
|
export type SiteId =
|
|
4
|
+
| 'learn'
|
|
3
5
|
| 'inchi'
|
|
4
6
|
| 'vcl'
|
|
5
7
|
| 'smiles'
|
|
@@ -70,6 +72,16 @@ export interface SiteMarkColors {
|
|
|
70
72
|
* go with them live in `marks.tsx`.
|
|
71
73
|
*/
|
|
72
74
|
export const ECOSYSTEM_SITES: readonly EcosystemSite[] = [
|
|
75
|
+
{
|
|
76
|
+
id: 'learn',
|
|
77
|
+
name: { lead: 'learn', alt: 'cheminfo', dot: true },
|
|
78
|
+
host: 'learn.cheminfo.org',
|
|
79
|
+
repository: 'https://github.com/cheminfo/learn.cheminfo.org',
|
|
80
|
+
tagline: 'Arrange the tools into a course and hand out its link.',
|
|
81
|
+
brand: '#0d9488',
|
|
82
|
+
brandAlt: '#b45309',
|
|
83
|
+
mark: { plate: '#0d9488', accent: '#f59e0b' },
|
|
84
|
+
},
|
|
73
85
|
{
|
|
74
86
|
id: 'inchi',
|
|
75
87
|
name: { lead: 'inchi', alt: 'cheminfo', dot: true },
|
|
@@ -17,7 +17,7 @@ const SECTION_STYLE: CSSProperties = {
|
|
|
17
17
|
};
|
|
18
18
|
const HEADING_STYLE: CSSProperties = {
|
|
19
19
|
margin: 0,
|
|
20
|
-
color: '#5b6875',
|
|
20
|
+
color: 'var(--text-muted, #5b6875)',
|
|
21
21
|
fontSize: '0.75rem',
|
|
22
22
|
fontWeight: 600,
|
|
23
23
|
};
|
|
@@ -40,7 +40,7 @@ const LINK_STYLE: CSSProperties = {
|
|
|
40
40
|
};
|
|
41
41
|
const CURRENT_STYLE: CSSProperties = {
|
|
42
42
|
...LINK_STYLE,
|
|
43
|
-
color: '#8a96a3',
|
|
43
|
+
color: 'var(--text-faint, #8a96a3)',
|
|
44
44
|
cursor: 'default',
|
|
45
45
|
};
|
|
46
46
|
|
|
@@ -11,7 +11,7 @@ const TILE_STYLE: CSSProperties = {
|
|
|
11
11
|
padding: '7px 8px',
|
|
12
12
|
border: '1px solid transparent',
|
|
13
13
|
borderRadius: 10,
|
|
14
|
-
color: '#16202c',
|
|
14
|
+
color: 'var(--text, #16202c)',
|
|
15
15
|
gap: 10,
|
|
16
16
|
textDecoration: 'none',
|
|
17
17
|
transition: 'background 120ms, border-color 120ms, transform 120ms',
|
|
@@ -37,7 +37,7 @@ const HERE_STYLE: CSSProperties = {
|
|
|
37
37
|
textTransform: 'uppercase',
|
|
38
38
|
};
|
|
39
39
|
const TAGLINE_STYLE: CSSProperties = {
|
|
40
|
-
color: '#5b6875',
|
|
40
|
+
color: 'var(--text-muted, #5b6875)',
|
|
41
41
|
fontSize: '0.75rem',
|
|
42
42
|
lineHeight: 1.35,
|
|
43
43
|
};
|
|
@@ -91,7 +91,9 @@ export function SiteTile(props: SiteTileProps): ReactElement {
|
|
|
91
91
|
<div style={NAME_STYLE}>
|
|
92
92
|
<span>
|
|
93
93
|
<span style={{ color: site.brand }}>{site.name.lead}</span>
|
|
94
|
-
{site.name.dot ?
|
|
94
|
+
{site.name.dot ? (
|
|
95
|
+
<span style={{ color: 'var(--text-faint, #8a96a3)' }}>.</span>
|
|
96
|
+
) : null}
|
|
95
97
|
<span style={{ color: site.brandAlt }}>{site.name.alt}</span>
|
|
96
98
|
</span>
|
|
97
99
|
{isCurrent ? (
|
|
@@ -134,7 +136,7 @@ export function SiteTile(props: SiteTileProps): ReactElement {
|
|
|
134
136
|
// A tile lights up in the colour of the site it opens, never in ours: the
|
|
135
137
|
// pointer moving down the grid is what makes the pairs of colours read.
|
|
136
138
|
function tint(site: EcosystemSite, isCurrent: boolean, lit: boolean): string {
|
|
137
|
-
if (isCurrent) return '#f5f7fa';
|
|
139
|
+
if (isCurrent) return 'var(--surface-sunken, #f5f7fa)';
|
|
138
140
|
return lit ? `color-mix(in oklab, ${site.brand} 9%, white)` : 'transparent';
|
|
139
141
|
}
|
|
140
142
|
|
|
@@ -143,6 +145,6 @@ function borderOf(
|
|
|
143
145
|
isCurrent: boolean,
|
|
144
146
|
lit: boolean,
|
|
145
147
|
): string {
|
|
146
|
-
if (isCurrent) return '#dfe3e8';
|
|
148
|
+
if (isCurrent) return 'var(--border, #dfe3e8)';
|
|
147
149
|
return lit ? `color-mix(in oklab, ${site.brand} 32%, white)` : 'transparent';
|
|
148
150
|
}
|
|
@@ -16,6 +16,46 @@ import type { SiteId } from '../core/sites.ts';
|
|
|
16
16
|
|
|
17
17
|
/** Every mark, keyed by site, drawn on top of its plate. */
|
|
18
18
|
export const GLYPHS: Record<SiteId, (alt: string) => ReactNode> = {
|
|
19
|
+
// Six tiles standing where the six atoms of a benzene ring stand: the
|
|
20
|
+
// arrangement of tiles is the product and the ring is the subject.
|
|
21
|
+
learn: (alt) => (
|
|
22
|
+
<g>
|
|
23
|
+
<rect x="12.85" y="4.15" width="6.3" height="6.3" rx="1.9" fill={alt} />
|
|
24
|
+
<rect
|
|
25
|
+
x="20.39"
|
|
26
|
+
y="8.5"
|
|
27
|
+
width="6.3"
|
|
28
|
+
height="6.3"
|
|
29
|
+
rx="1.9"
|
|
30
|
+
fill="#ffffff"
|
|
31
|
+
/>
|
|
32
|
+
<rect
|
|
33
|
+
x="20.39"
|
|
34
|
+
y="17.2"
|
|
35
|
+
width="6.3"
|
|
36
|
+
height="6.3"
|
|
37
|
+
rx="1.9"
|
|
38
|
+
fill="#ffffff"
|
|
39
|
+
/>
|
|
40
|
+
<rect
|
|
41
|
+
x="12.85"
|
|
42
|
+
y="21.55"
|
|
43
|
+
width="6.3"
|
|
44
|
+
height="6.3"
|
|
45
|
+
rx="1.9"
|
|
46
|
+
fill="#ffffff"
|
|
47
|
+
/>
|
|
48
|
+
<rect
|
|
49
|
+
x="5.31"
|
|
50
|
+
y="17.2"
|
|
51
|
+
width="6.3"
|
|
52
|
+
height="6.3"
|
|
53
|
+
rx="1.9"
|
|
54
|
+
fill="#ffffff"
|
|
55
|
+
/>
|
|
56
|
+
<rect x="5.31" y="8.5" width="6.3" height="6.3" rx="1.9" fill="#ffffff" />
|
|
57
|
+
</g>
|
|
58
|
+
),
|
|
19
59
|
// A hexagon with a tail: the structure, and the string it hashes into.
|
|
20
60
|
inchi: (alt) => (
|
|
21
61
|
<g
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type { ResolutionLimits } from './atomicGrid.ts';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
atomicGridResolution,
|
|
4
|
+
createAtomicOrbitalEvaluator,
|
|
5
|
+
} from './atomicGrid.ts';
|
|
3
6
|
export type { AtomicOrbital, AtomicOrbitalOptions } from './atomicOrbitals.ts';
|
|
4
7
|
export {
|
|
5
8
|
atomicOrbitalsOf,
|
|
@@ -32,8 +32,11 @@ export interface RealHarmonic {
|
|
|
32
32
|
* @param y - Offset from the nucleus along y, ångström.
|
|
33
33
|
* @param z - Offset from the nucleus along z, ångström.
|
|
34
34
|
* @param radius - Length of `(x, y, z)`, passed in because the caller has
|
|
35
|
-
* already computed it for the radial part.
|
|
36
|
-
*
|
|
35
|
+
* already computed it for the radial part. Never 0: a harmonic names a
|
|
36
|
+
* direction and the nucleus has none, so every ℓ > 0 divides by it. Build
|
|
37
|
+
* `ψ` with `createAtomicOrbitalEvaluator`, which answers that one point
|
|
38
|
+
* itself rather than paying for a branch on every sample.
|
|
39
|
+
* @returns The dimensionless amplitude.
|
|
37
40
|
*/
|
|
38
41
|
evaluate: (x: number, y: number, z: number, radius: number) => number;
|
|
39
42
|
}
|
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* reason the two components are separate files.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { Button } from '@blueprintjs/core';
|
|
9
10
|
import type { CSSProperties, ReactElement } from 'react';
|
|
10
11
|
import { useEffect, useRef, useState } from 'react';
|
|
11
12
|
|
|
13
|
+
import type { HelpContent } from '../../help/ui/HelpBody.tsx';
|
|
14
|
+
import { HelpTooltip } from '../../help/ui/HelpTooltip.tsx';
|
|
12
15
|
import type { ResolutionLimits } from '../core/atomicGrid.ts';
|
|
13
16
|
import type { PhasePalette } from '../core/palette.ts';
|
|
14
17
|
import { PHASE_PALETTES } from '../core/palette.ts';
|
|
@@ -39,6 +42,24 @@ export interface AtomicOrbitalCanvasProps {
|
|
|
39
42
|
* @default 56
|
|
40
43
|
*/
|
|
41
44
|
resolution?: number | ResolutionLimits;
|
|
45
|
+
/**
|
|
46
|
+
* Whether the labelled x, y, z frame is drawn through the nucleus when the
|
|
47
|
+
* canvas opens. A `3d_xz` is only `3d_xz` because of where its lobes sit
|
|
48
|
+
* against those axes, and a lone isosurface says nothing about that, so the
|
|
49
|
+
* frame is on unless a site says otherwise.
|
|
50
|
+
*
|
|
51
|
+
* The canvas owns the state from then on: the button in its corner flips it,
|
|
52
|
+
* and {@link AtomicOrbitalCanvasProps.onAxesChange} reports the new value to
|
|
53
|
+
* a site that wants to remember it for the next mount.
|
|
54
|
+
* @default true
|
|
55
|
+
*/
|
|
56
|
+
axes?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Called when the student flips the frame, with its new state — so a site can
|
|
59
|
+
* persist the choice and pass it back as `axes`.
|
|
60
|
+
* @default undefined
|
|
61
|
+
*/
|
|
62
|
+
onAxesChange?: (axes: boolean) => void;
|
|
42
63
|
/**
|
|
43
64
|
* Whether the scene turns on its own, which is what makes a still screenshot
|
|
44
65
|
* of a 3D shape readable.
|
|
@@ -82,9 +103,11 @@ export function AtomicOrbitalCanvas(
|
|
|
82
103
|
orbitalId,
|
|
83
104
|
palette = PHASE_PALETTES.textbook,
|
|
84
105
|
resolution = DEFAULT_RESOLUTION,
|
|
106
|
+
axes: initialAxes = true,
|
|
85
107
|
spinning = false,
|
|
86
108
|
spinSpeed = DEFAULT_SPIN_SPEED,
|
|
87
109
|
sample = sampleInProcess,
|
|
110
|
+
onAxesChange,
|
|
88
111
|
onNodeRadii,
|
|
89
112
|
onError,
|
|
90
113
|
} = props;
|
|
@@ -93,6 +116,15 @@ export function AtomicOrbitalCanvas(
|
|
|
93
116
|
const viewerRef = useRef<OrbitalViewer | null>(null);
|
|
94
117
|
const [drawn, setDrawn] = useState<string | null>(null);
|
|
95
118
|
|
|
119
|
+
// How far the drawn surface reaches, and whether the frame is wanted around
|
|
120
|
+
// it. Both are read by the sampling effect, which finishes long after the
|
|
121
|
+
// render that started it; keeping them in refs is what lets the frame be
|
|
122
|
+
// switched on without re-sampling the orbital.
|
|
123
|
+
const reachRef = useRef<number | null>(null);
|
|
124
|
+
const axesRef = useRef(initialAxes);
|
|
125
|
+
|
|
126
|
+
const [axes, setAxes] = useState(initialAxes);
|
|
127
|
+
|
|
96
128
|
// What the canvas is being asked to show. Comparing it with what it *is*
|
|
97
129
|
// showing gives the progress note without a state write on every prop change.
|
|
98
130
|
const wanted = `${atomicNumber}|${orbitalId}|${resolutionKey(resolution)}|${palette.id}`;
|
|
@@ -100,9 +132,9 @@ export function AtomicOrbitalCanvas(
|
|
|
100
132
|
|
|
101
133
|
// Callbacks are read through refs so a caller passing an inline arrow does
|
|
102
134
|
// not re-sample the orbital on every render of its parent.
|
|
103
|
-
const callbacks = useRef({ onNodeRadii, onError });
|
|
135
|
+
const callbacks = useRef({ onAxesChange, onNodeRadii, onError });
|
|
104
136
|
useEffect(() => {
|
|
105
|
-
callbacks.current = { onNodeRadii, onError };
|
|
137
|
+
callbacks.current = { onAxesChange, onNodeRadii, onError };
|
|
106
138
|
});
|
|
107
139
|
|
|
108
140
|
// Created and disposed once per mount. React 19 runs this twice in
|
|
@@ -136,7 +168,8 @@ export function AtomicOrbitalCanvas(
|
|
|
136
168
|
positiveColour: palette.positive,
|
|
137
169
|
negativeColour: palette.negative,
|
|
138
170
|
});
|
|
139
|
-
|
|
171
|
+
reachRef.current = reach ?? null;
|
|
172
|
+
await viewer.refit(await fitAxes(viewer, reach, axesRef.current));
|
|
140
173
|
if (!cancelled) setDrawn(wanted);
|
|
141
174
|
})
|
|
142
175
|
.catch((error: unknown) => {
|
|
@@ -150,6 +183,16 @@ export function AtomicOrbitalCanvas(
|
|
|
150
183
|
};
|
|
151
184
|
}, [atomicNumber, orbitalId, resolution, palette, sample, wanted]);
|
|
152
185
|
|
|
186
|
+
// Toggling the frame on an orbital already on screen: the field it was drawn
|
|
187
|
+
// from is unchanged, so nothing is re-sampled.
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
axesRef.current = axes;
|
|
190
|
+
const viewer = viewerRef.current;
|
|
191
|
+
const reach = reachRef.current;
|
|
192
|
+
if (viewer === null || reach === null) return;
|
|
193
|
+
void fitAxes(viewer, reach, axes).then((framed) => viewer.refit(framed));
|
|
194
|
+
}, [axes]);
|
|
195
|
+
|
|
153
196
|
useEffect(() => {
|
|
154
197
|
void viewerRef.current?.setSpin(spinning, spinSpeed);
|
|
155
198
|
}, [spinning, spinSpeed]);
|
|
@@ -157,10 +200,75 @@ export function AtomicOrbitalCanvas(
|
|
|
157
200
|
return (
|
|
158
201
|
<div ref={containerRef} style={CANVAS_STYLE}>
|
|
159
202
|
{busy && <div style={BUSY_STYLE}>Sampling…</div>}
|
|
203
|
+
<div style={CONTROLS_STYLE}>
|
|
204
|
+
<HelpTooltip content={AXES_HELP} placement="bottom">
|
|
205
|
+
<Button
|
|
206
|
+
variant="minimal"
|
|
207
|
+
size="small"
|
|
208
|
+
icon="grid"
|
|
209
|
+
active={axes}
|
|
210
|
+
aria-label="Show the x, y, z axes"
|
|
211
|
+
aria-pressed={axes}
|
|
212
|
+
onClick={() => {
|
|
213
|
+
setAxes(!axes);
|
|
214
|
+
callbacks.current.onAxesChange?.(!axes);
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
</HelpTooltip>
|
|
218
|
+
<HelpTooltip content={RESET_HELP} placement="bottom">
|
|
219
|
+
<Button
|
|
220
|
+
variant="minimal"
|
|
221
|
+
size="small"
|
|
222
|
+
icon="zoom-to-fit"
|
|
223
|
+
aria-label="Reset the view"
|
|
224
|
+
onClick={() => {
|
|
225
|
+
void viewerRef.current?.resetView();
|
|
226
|
+
}}
|
|
227
|
+
/>
|
|
228
|
+
</HelpTooltip>
|
|
229
|
+
</div>
|
|
160
230
|
</div>
|
|
161
231
|
);
|
|
162
232
|
}
|
|
163
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Draw the cartesian frame around the orbital, or remove it.
|
|
236
|
+
* @param viewer - The viewer holding the canvas.
|
|
237
|
+
* @param reach - How far the drawn surface reaches, as `showOrbital` returned
|
|
238
|
+
* it.
|
|
239
|
+
* @param axes - Whether the frame is wanted.
|
|
240
|
+
* @returns What the camera has to fit: the frame reaches past the surface, so
|
|
241
|
+
* turning it on has to zoom out or the labels sit off screen.
|
|
242
|
+
*/
|
|
243
|
+
async function fitAxes(
|
|
244
|
+
viewer: OrbitalViewer,
|
|
245
|
+
reach: number | undefined,
|
|
246
|
+
axes: boolean,
|
|
247
|
+
): Promise<number | undefined> {
|
|
248
|
+
if (!axes) {
|
|
249
|
+
await viewer.hideAxes();
|
|
250
|
+
return reach;
|
|
251
|
+
}
|
|
252
|
+
if (reach === undefined) return undefined;
|
|
253
|
+
return (await viewer.showAxes(reach)) ?? reach;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** What the frame button says it is for. */
|
|
257
|
+
const AXES_HELP: HelpContent = {
|
|
258
|
+
title: 'Cartesian axes',
|
|
259
|
+
body: 'Draw x, y and z through the nucleus. The label names an orbital by where its lobes sit against them.',
|
|
260
|
+
example: {
|
|
261
|
+
code: '3d_yz',
|
|
262
|
+
note: 'four lobes between the y and z axes, none on either.',
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** What the reset button says it is for. */
|
|
267
|
+
const RESET_HELP: HelpContent = {
|
|
268
|
+
title: 'Reset the view',
|
|
269
|
+
body: 'Back to the angle and zoom the orbital opened on. A change of orbital keeps whatever view you have turned it to.',
|
|
270
|
+
};
|
|
271
|
+
|
|
164
272
|
/** Samples per edge; 56 resolves the radial node of a 3s in about 25 ms. */
|
|
165
273
|
const DEFAULT_RESOLUTION = 56;
|
|
166
274
|
|
|
@@ -191,6 +299,20 @@ const CANVAS_STYLE: CSSProperties = {
|
|
|
191
299
|
overflow: 'hidden',
|
|
192
300
|
};
|
|
193
301
|
|
|
302
|
+
/**
|
|
303
|
+
* The two controls the canvas owns: the frame, and the way back to the framing
|
|
304
|
+
* the orbital opened on — a change of orbital now keeps whatever angle and zoom
|
|
305
|
+
* the student is on.
|
|
306
|
+
*/
|
|
307
|
+
const CONTROLS_STYLE: CSSProperties = {
|
|
308
|
+
position: 'absolute',
|
|
309
|
+
top: 4,
|
|
310
|
+
right: 4,
|
|
311
|
+
zIndex: 1,
|
|
312
|
+
display: 'flex',
|
|
313
|
+
gap: 2,
|
|
314
|
+
};
|
|
315
|
+
|
|
194
316
|
const BUSY_STYLE: CSSProperties = {
|
|
195
317
|
position: 'absolute',
|
|
196
318
|
top: 8,
|
|
@@ -199,6 +321,6 @@ const BUSY_STYLE: CSSProperties = {
|
|
|
199
321
|
padding: '3px 8px',
|
|
200
322
|
borderRadius: 3,
|
|
201
323
|
background: 'rgba(255, 255, 255, 0.85)',
|
|
202
|
-
color: '#5f6b7c',
|
|
324
|
+
color: 'var(--text-muted, #5f6b7c)',
|
|
203
325
|
fontSize: 11,
|
|
204
326
|
};
|
|
@@ -51,6 +51,19 @@ export interface AtomicOrbitalViewerProps {
|
|
|
51
51
|
* @default 56
|
|
52
52
|
*/
|
|
53
53
|
resolution?: number | ResolutionLimits;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the labelled x, y, z frame is drawn through the nucleus when the
|
|
56
|
+
* canvas opens — it is what tells a `3d_xz` from a `3d_yz`. The button in the
|
|
57
|
+
* canvas's corner flips it from there.
|
|
58
|
+
* @default true
|
|
59
|
+
*/
|
|
60
|
+
axes?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Called when the student flips the frame, with its new state — so a site can
|
|
63
|
+
* persist the choice and pass it back as `axes`.
|
|
64
|
+
* @default undefined
|
|
65
|
+
*/
|
|
66
|
+
onAxesChange?: (axes: boolean) => void;
|
|
54
67
|
/**
|
|
55
68
|
* Whether the scene turns on its own.
|
|
56
69
|
* @default false
|
|
@@ -145,7 +158,7 @@ const NOTE_STYLE: CSSProperties = {
|
|
|
145
158
|
padding: 12,
|
|
146
159
|
borderRadius: 3,
|
|
147
160
|
background: 'rgb(241 245 249)',
|
|
148
|
-
color: '#5f6b7c',
|
|
161
|
+
color: 'var(--text-muted, #5f6b7c)',
|
|
149
162
|
fontSize: 13,
|
|
150
163
|
textAlign: 'center',
|
|
151
164
|
};
|
package/src/orbital/ui/camera.ts
CHANGED
|
@@ -107,11 +107,92 @@ export function frameOrbital(
|
|
|
107
107
|
position: Vec3.add(Vec3.zero(), centre, offset),
|
|
108
108
|
up: Vec3.clone(ORBITAL_VIEW_UP),
|
|
109
109
|
radius,
|
|
110
|
+
...radiusMaxOf(canvas3d),
|
|
110
111
|
},
|
|
111
112
|
durationMs,
|
|
112
113
|
);
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Keep the view the student has set, and rescale it for an orbital of a
|
|
118
|
+
* different extent.
|
|
119
|
+
*
|
|
120
|
+
* Every atomic orbital is drawn at one canonical size, so the two extents are
|
|
121
|
+
* normally equal and the camera does not move at all: clicking through a shell
|
|
122
|
+
* keeps the rotation and the zoom instead of throwing them away. When they do
|
|
123
|
+
* differ, the offset from the target and the radius are multiplied by the same
|
|
124
|
+
* factor, which keeps the zoom *relative* to the orbital.
|
|
125
|
+
* @param plugin - The molstar context.
|
|
126
|
+
* @param previousRadius - Extent the camera is currently fitted to.
|
|
127
|
+
* @param orbitalRadius - Extent to fit now.
|
|
128
|
+
* @param durationMs - Transition length. Pass 0 for an instant jump.
|
|
129
|
+
*/
|
|
130
|
+
export function refitOrbital(
|
|
131
|
+
plugin: PluginContext,
|
|
132
|
+
previousRadius: number,
|
|
133
|
+
orbitalRadius: number,
|
|
134
|
+
durationMs = DEFAULT_CAMERA_DURATION,
|
|
135
|
+
): void {
|
|
136
|
+
const canvas3d = plugin.canvas3d;
|
|
137
|
+
if (canvas3d === undefined) return;
|
|
138
|
+
const scale = orbitalRadius / previousRadius;
|
|
139
|
+
if (!Number.isFinite(scale) || scale <= 0) {
|
|
140
|
+
frameOrbital(plugin, orbitalRadius, durationMs);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// The box a sampled orbital lives in changes with the orbital, and molstar
|
|
144
|
+
// only tracks that for a camera it also reframes, which this one is not.
|
|
145
|
+
const radiusMax = radiusMaxOf(canvas3d);
|
|
146
|
+
if (scale === 1) {
|
|
147
|
+
canvas3d.camera.setState(radiusMax, 0);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const { target, position, radius } = canvas3d.camera.state;
|
|
151
|
+
const offset = Vec3.sub(Vec3.zero(), position, target);
|
|
152
|
+
Vec3.scale(offset, offset, scale);
|
|
153
|
+
canvas3d.camera.setState(
|
|
154
|
+
{
|
|
155
|
+
position: Vec3.add(Vec3.zero(), target, offset),
|
|
156
|
+
radius: radius * scale,
|
|
157
|
+
...radiusMax,
|
|
158
|
+
},
|
|
159
|
+
durationMs,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The far plane's share of a camera state, left out when the scene cannot be
|
|
165
|
+
* measured yet.
|
|
166
|
+
* @param canvas3d - The canvas whose scene to measure.
|
|
167
|
+
* @returns `{ radiusMax }`, or an empty object to keep the camera's own value.
|
|
168
|
+
*/
|
|
169
|
+
function radiusMaxOf(canvas3d: PluginContext['canvas3d'] & object): {
|
|
170
|
+
radiusMax?: number;
|
|
171
|
+
} {
|
|
172
|
+
const radiusMax = sceneRadius(canvas3d);
|
|
173
|
+
return radiusMax > 0 ? { radiusMax } : {};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* How far the whole scene reaches, as molstar measures it for its near and far
|
|
178
|
+
* planes.
|
|
179
|
+
*
|
|
180
|
+
* `boundingSphere` covers the whole scene but is only recomputed on a draw, so
|
|
181
|
+
* in the moment after a representation was added it still reads 0 — and a zero
|
|
182
|
+
* `radiusMax` collapses the near and far planes onto the target, leaving the
|
|
183
|
+
* orbital sliced into a few thin arcs. The visible sphere is current there, so
|
|
184
|
+
* it stands in.
|
|
185
|
+
* @param canvas3d - The canvas whose scene to measure.
|
|
186
|
+
* @returns The radius, in scene units; 0 when neither sphere is ready.
|
|
187
|
+
*/
|
|
188
|
+
function sceneRadius(canvas3d: PluginContext['canvas3d'] & object): number {
|
|
189
|
+
const radius =
|
|
190
|
+
canvas3d.boundingSphere.radius > 0
|
|
191
|
+
? canvas3d.boundingSphere.radius
|
|
192
|
+
: canvas3d.boundingSphereVisible.radius;
|
|
193
|
+
return radius * canvas3d.props.sceneRadiusFactor;
|
|
194
|
+
}
|
|
195
|
+
|
|
115
196
|
/**
|
|
116
197
|
* Turn the automatic spin on or off.
|
|
117
198
|
* @param plugin - The molstar context.
|
|
@@ -108,7 +108,14 @@ export async function renderSampledVolume(
|
|
|
108
108
|
const drawn = surfaces.filter(
|
|
109
109
|
(surface): surface is Representation.Any => surface !== null,
|
|
110
110
|
);
|
|
111
|
-
|
|
111
|
+
// Added to whatever is already recorded rather than replacing it, so the
|
|
112
|
+
// record is every surface on the canvas and the next clear can take them all
|
|
113
|
+
// away. Overwriting it would strand any pair that reached the canvas without
|
|
114
|
+
// being the last one recorded, and a stranded pair is on screen for good.
|
|
115
|
+
representations.set(plugin, [
|
|
116
|
+
...(representations.get(plugin) ?? []),
|
|
117
|
+
...drawn,
|
|
118
|
+
]);
|
|
112
119
|
for (const representation of drawn) canvas3d.add(representation);
|
|
113
120
|
// add() only queues; nothing appears until the queue is committed.
|
|
114
121
|
canvas3d.commit();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running asynchronous work one piece at a time.
|
|
3
|
+
*
|
|
4
|
+
* Everything the viewer does mutates one canvas, and it does it across an
|
|
5
|
+
* `await`: replacing an orbital removes the surfaces on screen before it has
|
|
6
|
+
* finished building the new ones. Two of those interleaved both add their own
|
|
7
|
+
* pair and only one of them is remembered, so the atom ends up wearing several
|
|
8
|
+
* orbitals at once and the ones nobody recorded can never be removed again.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Runs what it is given after everything handed to it before. */
|
|
12
|
+
export interface SerialRunner {
|
|
13
|
+
/**
|
|
14
|
+
* Queue `task`, and resolve to what it returns.
|
|
15
|
+
* @param task - The work to run once the queue reaches it.
|
|
16
|
+
* @returns What `task` resolved to; its rejection reaches this caller alone.
|
|
17
|
+
*/
|
|
18
|
+
run: <Result>(task: () => Promise<Result>) => Promise<Result>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A queue of one.
|
|
23
|
+
* @returns The runner. A task that throws is reported to whoever queued it and
|
|
24
|
+
* never stalls the tasks behind it.
|
|
25
|
+
*/
|
|
26
|
+
export function createSerialRunner(): SerialRunner {
|
|
27
|
+
let tail: Promise<unknown> = Promise.resolve();
|
|
28
|
+
return {
|
|
29
|
+
run: <Result>(task: () => Promise<Result>): Promise<Result> => {
|
|
30
|
+
const result = tail.then(task);
|
|
31
|
+
tail = result.catch(() => undefined);
|
|
32
|
+
return result;
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|