/ proof-of-concept / toy-instruments
Sign in
proof-of-concept / toy-instruments
Widget: exaggeration dropdown in the motion bar
Jeremy Magland <jmagland@flatironinstitute.org> committed commit d3b29d49fff1 parent 40acdb6 Browse files
3 changed files+28−7
README.mdmodified+2−2View file
@@ -77,8 +77,8 @@ Runs are cached on [artifact-cache](https://artifact-cache.tempory.net) (app `to
7777 <toy-instrument-sim name="crash" exaggeration="5" settings="https://magland.github.io/toy-instruments/#/bead-cymbal?…&preset=0&gpu=1"></toy-instrument-sim>
7878 ```
7979
80-- `settings` is a link from "Copy link to these settings" (or its `#/<instrument>?…` part). `exaggeration` magnifies the drawn motion (default 5); it is not part of the cache key. `name` names the downloaded `.wav`.
81-- The reader sees the model at rest (the 3D view without its tabs, view buttons or exaggeration input; drag to rotate) and two buttons. "Play sound" downloads the light entry and plays it in an audio player; "Play motion (N MB)" downloads the frames and plays the replay in slow motion, with pause, a time slider and a speed choice. Each row expands in place, so the other button does not move. On load the widget only sends HEAD requests (for the link's backend first, then the other).
80+- `settings` is a link from "Copy link to these settings" (or its `#/<instrument>?…` part). `exaggeration` magnifies the drawn motion (default 5; the reader can change it in the motion bar, from ×1 to ×100); it is not part of the cache key. `name` names the downloaded `.wav`.
81+- The reader sees the model at rest (the 3D view without its tabs, view buttons or exaggeration input; drag to rotate) and two buttons. "Play sound" downloads the light entry and plays it in an audio player; "Play motion (N MB)" downloads the frames and plays the replay in slow motion, with pause, a time slider, an exaggeration choice and a speed choice. Each row expands in place, so the other button does not move. On load the widget only sends HEAD requests (for the link's backend first, then the other).
8282 - If the cache has no result, or cannot be reached, the widget offers to run the simulation in the browser, with a rough time estimate, and then shows both players. With the cache passcode saved in that browser (under "Shared cache" in the widget), the run is stored under the same key.
8383 - "Open the full app" shows the whole instrument page (every parameter, the plots, Run and Load cached) over the host page, starting from the widget's settings; Close or Escape returns to the post, and reopening keeps its state. It is the bundle's own copy of the page (not the live app in a frame), so it computes the same cache keys as the widget. It has no back link or "Copy link", and leaves the address alone.
8484 - The key hashes what the bundle's own model code builds, so a page that keeps one build of the bundle asks for, and can always refill, the same entries, whatever later versions of the app do. jeremy.magland.org uploads each build under a content hash and never replaces it.
src/embed.tsmodified+19−5View file
@@ -17,8 +17,9 @@ import type { SimResult } from './sim/types';
1717 // instrument, for embedding in other pages (blog posts).
1818 //
1919 // - settings: a link from "Copy link to these settings" (or just its #/<instrument>?… part).
20-// - exaggeration: how much the motion is magnified in the replay (default 5). It only
21-// affects the drawing, so it is not part of the cache key.
20+// - exaggeration: how much the motion is magnified in the replay (default 5); the reader
21+// can change it in the motion bar. It only affects the drawing, so it is not part of the
22+// cache key.
2223 // - name: file name of the downloaded .wav (default toy-instrument).
2324 //
2425 // The reader sees the 3D model with "Play sound" and "Play motion" buttons; each downloads
@@ -75,6 +76,7 @@ class ToyInstrumentSim extends HTMLElement {
7576 <button class="btn-small" data-r="m-play">Play</button>
7677 <input type="range" min="0" max="0" value="0" data-r="m-slider" aria-label="time">
7778 <span class="mono" data-r="m-label"></span>
79+ <label class="inline">exaggeration <select data-r="m-exag"></select></label>
7880 <label class="inline">speed <select data-r="m-rate">${RATES.map(([v, l]) => `<option value="${v}">${l}</option>`).join('')}</select></label>
7981 </div>
8082 </div>
@@ -150,8 +152,6 @@ class ToyInstrumentSim extends HTMLElement {
150152 }
151153 this.built = built;
152154
153- const ex = Number(this.getAttribute('exaggeration') ?? 5);
154- if (ex > 0) this.exaggeration = ex;
155155 this.scene = new BeadScene(this.q('scene'), this.timeline, { simple: true, exaggeration: this.exaggeration });
156156 this.scene.showSetup(built.scene);
157157 this.followTheme();
@@ -310,11 +310,22 @@ class ToyInstrumentSim extends HTMLElement {
310310 play.addEventListener('click', () => tl.toggle());
311311 slider.addEventListener('input', () => tl.seek(Number(slider.value)));
312312 rate.addEventListener('change', () => tl.setRate(Number(rate.value)));
313+ // exaggeration choices, including the attribute's value
314+ const exag = this.q<HTMLSelectElement>('m-exag');
315+ const ex = Number(this.getAttribute('exaggeration') ?? 5);
316+ if (ex > 0) this.exaggeration = ex;
317+ const choices = [...new Set([...EXAGGERATIONS, this.exaggeration])].sort((a, b) => a - b);
318+ exag.innerHTML = choices.map((v) => `<option value="${v}">×${fmtNum(v)}</option>`).join('');
319+ exag.value = String(this.exaggeration);
320+ exag.addEventListener('change', () => {
321+ this.exaggeration = Number(exag.value);
322+ this.scene?.setExaggeration(this.exaggeration);
323+ });
313324 const sync = () => {
314325 play.textContent = tl.playing ? 'Pause' : 'Play';
315326 slider.max = String(tl.times.length - 1);
316327 slider.value = String(tl.frame);
317- label.textContent = `t = ${(tl.times[tl.frame] * 1000).toFixed(1)} ms, motion drawn ×${fmtNum(this.exaggeration)}`;
328+ label.textContent = `t = ${(tl.times[tl.frame] * 1000).toFixed(1)} ms`;
318329 rate.value = String(tl.rate);
319330 };
320331 tl.onChange(sync);
@@ -421,6 +432,9 @@ class ToyInstrumentSim extends HTMLElement {
421432 }
422433 }
423434
435+// choices in the motion bar's exaggeration dropdown
436+const EXAGGERATIONS = [1, 2, 5, 10, 20, 50, 100];
437+
424438 function duration(s: number): string {
425439 if (s < 90) return `${fmtNum(Math.max(1, Math.round(s)))} s`;
426440 if (s < 5400) return `${fmtNum(Math.round(s / 60))} min`;
src/ui/scene3d.tsmodified+7−0View file
@@ -152,6 +152,13 @@ export class BeadScene {
152152 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => this.rebuild());
153153 }
154154
155+ // set the replay's displacement exaggeration (kept for later replays)
156+ setExaggeration(x: number) {
157+ this.opts.exaggeration = this.exaggeration = x;
158+ this.exagInput.value = fmtNum(x);
159+ this.update();
160+ }
161+
155162 // redraw with the current colors (after a theme change the view cannot see by itself)
156163 restyle() {
157164 this.rebuild();
↑↓ move↵openescclose