| <script lang="ts"> | |
| /** Playback control over generated decoding steps. */ | |
| let { | |
| total, | |
| index = $bindable(), | |
| intervalMs = 420, | |
| }: { total: number; index: number; intervalMs?: number } = $props(); | |
| let playing = $state(false); | |
| let timer: ReturnType<typeof setInterval> | null = null; | |
| function stop() { | |
| if (timer) clearInterval(timer); | |
| timer = null; | |
| playing = false; | |
| } | |
| function toggle() { | |
| if (playing) { | |
| stop(); | |
| return; | |
| } | |
| if (index >= total - 1) index = 0; | |
| playing = true; | |
| timer = setInterval(() => { | |
| if (index >= total - 1) { | |
| stop(); | |
| return; | |
| } | |
| index = index + 1; | |
| }, intervalMs); | |
| } | |
| $effect(() => () => stop()); | |
| </script> | |
| <div class="player"> | |
| <button onclick={toggle} disabled={total === 0}>{playing ? 'pause' : 'play'}</button> | |
| <button onclick={() => { stop(); index = Math.max(0, index - 1); }} disabled={index <= 0}> | |
| prev | |
| </button> | |
| <button | |
| onclick={() => { stop(); index = Math.min(total - 1, index + 1); }} | |
| disabled={index >= total - 1}>next</button | |
| > | |
| <input | |
| type="range" | |
| min="0" | |
| max={Math.max(0, total - 1)} | |
| bind:value={index} | |
| oninput={stop} | |
| disabled={total === 0} | |
| /> | |
| <span class="mono small dim">step {total === 0 ? 0 : index + 1} / {total}</span> | |
| </div> | |
| <style> | |
| .player { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .player button { | |
| padding: 3px 9px; | |
| font-size: 12.5px; | |
| } | |
| input[type='range'] { | |
| flex: 1; | |
| min-width: 100px; | |
| } | |
| </style> | |