diff --git a/website/src/components/Tap.jsx b/website/src/components/Tap.jsx new file mode 100644 index 000000000..c84ce9feb --- /dev/null +++ b/website/src/components/Tap.jsx @@ -0,0 +1,71 @@ +import React, { useState, useRef, useEffect } from "react"; + +export default function Tap({ initialCps = 0.4, maxSamples = 3}) { + const [timestamps, setTimestamps] = useState([]); + const [cps, setCps] = useState(initialCps); + + function addTap(ts = Date.now()) { + setTimestamps(prev => { + const next = [...prev, ts].slice(-(maxSamples + 1)); + calcCps(next); + return next; + }); + } + + function calcCps(times) { + if (!times || times.length < 2) return; + const intervals = []; + for (let i = 1; i < times.length; i++) { + intervals.push(times[i] - times[i - 1]); + } + + const avgMs = intervals.reduce((a, b) => a + b, 0) / intervals.length; + const newCps = 1000 / avgMs; + if (Number.isFinite(newCps) && newCps > 0 && newCps < 1000) { + setCps(newCps); + } + } + + function handleTap(e) { + e && e.preventDefault(); + addTap(); + } + + function handleReset(e) { + e && e.preventDefault(); + reset(); + } + + useEffect(() => { + function onKey(e) { + if (e.code === "Space") { + e.preventDefault(); + addTap(); + } else if (e.code === "Backspace") { + e.preventDefault(); + reset(); + } + } + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, []); + + function reset() { + setTimestamps([]); + // setCps(initialCps); + } + + + return ( +
+
+
{cps.toFixed(2)} cps
+ +
+
+ ); +} diff --git a/website/src/pages/cycler.astro b/website/src/pages/cycler.astro index 06bfb15f1..eb3d5ed2a 100644 --- a/website/src/pages/cycler.astro +++ b/website/src/pages/cycler.astro @@ -1,6 +1,7 @@ --- import HeadCommon from '../components/HeadCommon.astro'; import { Vinyl } from '../components/Vinyl.jsx'; +import Tap from '../components/Tap.jsx'; --- @@ -11,5 +12,6 @@ import { Vinyl } from '../components/Vinyl.jsx';
Hello there.
+