diff --git a/.gitignore b/.gitignore index cc239aa..d9466bf 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules dist .next out -.vercel \ No newline at end of file +.vercel +public/examples \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f6bf56..5ed9363 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,8 @@ echo("Hello World"); This is a [pull request](https://github.com/recho-dev/recho/pull/82) you can refer to. +If you prefer a `snapshot thumbnail`, create an image file named `[SKETCH-NAME].snap.[FORMAT]` in `app/examples`, such as [sin-wave-radios.snap.png](./app/examples/sin-wave-radios.snap.png) for [sin-wave-radios.recho.js](/app/examples/sin-wave-radios.recho.js). + ## Editor & Runtime Development If you want to contribute to the editor or runtime, make sure to add test files in `test/js/` and register them in `test/js/index.js`. diff --git a/app/Thumbnail.jsx b/app/Thumbnail.jsx index 2d02e65..e95b26a 100644 --- a/app/Thumbnail.jsx +++ b/app/Thumbnail.jsx @@ -6,7 +6,7 @@ function removeEmptyLines(string) { return first + rest.join("\n"); } -export function Thumbnail({html, outputStartLine = null}) { +export function Thumbnail({html, outputStartLine = null, snap = null}) { if (outputStartLine === null) return
; const $ = load(html); const lines = $("span.line"); @@ -15,5 +15,10 @@ export function Thumbnail({html, outputStartLine = null}) { $(lines[i]).remove(); } } + if (snap) { + return ( +
+ ); + } return
; } diff --git a/app/examples/page.jsx b/app/examples/page.jsx index b917694..d43ce14 100644 --- a/app/examples/page.jsx +++ b/app/examples/page.jsx @@ -19,7 +19,7 @@ export default function Page() {
- +
diff --git a/app/examples/sin-wave-radios.recho.js b/app/examples/sin-wave-radios.recho.js new file mode 100644 index 0000000..55d094d --- /dev/null +++ b/app/examples/sin-wave-radios.recho.js @@ -0,0 +1,51 @@ +/** + * @title Sin Wave Radios + * @author Bairui Su + * @created 2025-09-27 + * @pull_request 128 + * @github pearmini + */ + +/** + * ============================================================================ + * = Sin Wave Radios = + * ============================================================================ + * + * This example explores using inputs to create some visual patterns in Recho. + * For now, we can only manually set the values of the inputs to simulate the + * sine wave, based on the computed values. In the future, inputs values can be + * programmatically updated. + */ + +recho.radio(4, ['','','','','','','','','']); +recho.radio(5, ['','','','','','','','','']); +recho.radio(6, ['','','','','','','','','']); +recho.radio(7, ['','','','','','','','','']); +recho.radio(8, ['','','','','','','','','']); +recho.radio(8, ['','','','','','','','','']); +recho.radio(8, ['','','','','','','','','']); +recho.radio(7, ['','','','','','','','','']); +recho.radio(6, ['','','','','','','','','']); +recho.radio(5, ['','','','','','','','','']); +recho.radio(4, ['','','','','','','','','']); +recho.radio(3, ['','','','','','','','','']); +recho.radio(2, ['','','','','','','','','']); +recho.radio(1, ['','','','','','','','','']); +recho.radio(0, ['','','','','','','','','']); +recho.radio(0, ['','','','','','','','','']); +recho.radio(0, ['','','','','','','','','']); +recho.radio(1, ['','','','','','','','','']); +recho.radio(2, ['','','','','','','','','']); +recho.radio(3, ['','','','','','','','','']); +recho.radio(4, ['','','','','','','','','']); + +//➜ [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ] +//➜ [ 0, 0.3141592653589793, 0.6283185307179586, 0.9424777960769379, 1.2566370614359172, 1.5707963267948966, 1.8849555921538759, 2.199114857512855, 2.5132741228718345, 2.827433388230814, 3.141592653589793… +//➜ [ 4, 5, 6, 7, 8, 8, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 1, 2, 3, 4 ] +{ + const n = 20; + const m = 8; + const I = echo([...Array.from({length: n}, (_, i) => i), n]); + const X = echo(I.map(i => i / n * Math.PI * 2)); + echo(X.map(x => Math.round(Math.sin(x) * m / 2 + m / 2))); +} \ No newline at end of file diff --git a/app/examples/sin-wave-radios.snap.png b/app/examples/sin-wave-radios.snap.png new file mode 100644 index 0000000..ee821fb Binary files /dev/null and b/app/examples/sin-wave-radios.snap.png differ diff --git a/app/utils.js b/app/utils.js index 4a1887e..da376fa 100644 --- a/app/utils.js +++ b/app/utils.js @@ -18,6 +18,22 @@ export function removeJSMeta(content) { return content.replace(/^\/\*\*([\s\S]*?)\*\//, "").trimStart(); } +function ensureSnapImageInPublic(snapFile, slug) { + if (!snapFile) return null; + const publicExamplesDir = path.join(process.cwd(), "public", "examples"); + const publicSnapPath = path.join(publicExamplesDir, snapFile); + if (!fs.existsSync(publicExamplesDir)) { + fs.mkdirSync(publicExamplesDir, {recursive: true}); + } + if (!fs.existsSync(publicSnapPath)) { + const sourcePath = path.join(process.cwd(), "app", "examples", snapFile); + if (fs.existsSync(sourcePath)) { + fs.copyFileSync(sourcePath, publicSnapPath); + } + } + return snapFile; +} + export function getAllJSDocs() { const dir = path.join(process.cwd(), "app/docs/"); const files = fs.readdirSync(dir); @@ -39,15 +55,19 @@ export function getAllJSExamples() { const content = fs.readFileSync(path.join(dir, file), "utf8"); const meta = parseJSMeta(content); const {startLine, endLine} = findFirstOutputRange(content); + const slug = file.replace(".recho.js", ""); + const snap = files.find((f) => f.startsWith(slug + ".snap")); + const publicSnap = ensureSnapImageInPublic(snap, slug); if (!meta) { throw new Error(`No meta found in ${file}`); } return { ...meta, content, - slug: file.replace(".recho.js", ""), + slug, outputStartLine: meta.thumbnail_start ?? startLine, outputEndLine: endLine, + snap: publicSnap, }; }); }