/ tutorials / wave-acoustics
Sign in
tutorials / wave-acoustics
wave-acoustics / src / ui / ChapterView.tsx
54 lines · 2.0 KBBlameHistoryRaw
1// A chapter: prose with its labs and figures dropped in where the text calls
2// for them.
3//
4// The prose is one markdown file per chapter, with a line
5//
6// <!--lab:convergence-->
7// <!--fig:five-point-->
8//
9// wherever a lab or a static figure belongs. Splitting on those markers keeps
10// the writing in one readable file instead of scattered through JSX.
11import { useMemo } from 'react'
12import Markdown from './Markdown.tsx'
13import Lab from './Lab.tsx'
14import type { Chapter } from './types.ts'
15import { FIGURES } from '../figures/index.ts'
17const MARKER = /<!--\s*(lab|fig):([a-z0-9_-]+)\s*-->/gi
19export default function ChapterView({ chapter }: { chapter: Chapter }) {
20 const pieces = useMemo(() => {
21 const out: ({ md: string } | { labId: string } | { figId: string })[] = []
22 let last = 0
23 MARKER.lastIndex = 0
24 let m: RegExpExecArray | null
25 while ((m = MARKER.exec(chapter.text)) !== null) {
26 out.push({ md: chapter.text.slice(last, m.index) })
27 out.push(m[1] === 'fig' ? { figId: m[2] } : { labId: m[2] })
28 last = m.index + m[0].length
29 }
30 out.push({ md: chapter.text.slice(last) })
31 return out
32 }, [chapter.text])
34 return (
35 <article className="chapter">
36 <header className="chapter-head">
37 <div className="chapter-part">{chapter.part}</div>
38 <h2>{chapter.title}</h2>
39 <p className="chapter-blurb">{chapter.blurb}</p>
40 </header>
41 {pieces.map((p, i) => {
42 if ('md' in p) return p.md.trim() === '' ? null : <Markdown key={i} src={p.md} />
43 if ('figId' in p) {
44 const Fig = FIGURES[p.figId]
45 if (!Fig) return <p key={i} className="lab-error">missing figure: {p.figId}</p>
46 return <Fig key={`${chapter.id}-fig-${p.figId}`} />
47 }
48 const lab = chapter.labs.find((l) => l.id === p.labId)
49 if (!lab) return <p key={i} className="lab-error">missing lab: {p.labId}</p>
50 return <Lab key={`${chapter.id}-${lab.id}`} spec={lab} />
51 })}
52 </article>
53 )
↑↓ move↵openescclose