eval only on ctrl+enter, not on every keystroke

This commit is contained in:
Felix Roos 2022-02-16 20:12:22 +01:00
parent 70f858361f
commit 47d29bc654

View File

@ -12,7 +12,7 @@ import { isNote } from 'tone';
import { useWebMidi } from './midi'; import { useWebMidi } from './midi';
const [_, codeParam] = window.location.href.split('#'); const [_, codeParam] = window.location.href.split('#');
const decoded = atob(codeParam || ''); const decoded = atob(decodeURIComponent(codeParam || ''));
const getHotCode = async () => { const getHotCode = async () => {
return fetch('/hot.js') return fetch('/hot.js')
@ -39,17 +39,37 @@ function getRandomTune() {
const randomTune = getRandomTune(); const randomTune = getRandomTune();
function App() { function App() {
const [mode, setMode] = useState<string>('javascript');
const [code, setCode] = useState<string>(decoded || randomTune); const [code, setCode] = useState<string>(decoded || randomTune);
const [activeCode, setActiveCode] = useState<string>();
const [log, setLog] = useState(''); const [log, setLog] = useState('');
const logBox = useRef<any>(); const logBox = useRef<any>();
const [error, setError] = useState<Error>(); const [error, setError] = useState<Error>();
const [pattern, setPattern] = useState<Pattern>(); const [pattern, setPattern] = useState<Pattern>();
const [activePattern, setActivePattern] = useState<Pattern>(); const [activePattern, setActivePattern] = useState<Pattern>();
const activatePattern = (_pattern = pattern) => { const dirty = code !== activeCode;
setActivePattern(() => _pattern); const activateCode = (_code = code) => {
window.location.hash = '#' + btoa(code); if (activeCode && !dirty) {
!cycle.started && cycle.start(); setError(undefined);
return;
}
try {
const parsed = evaluate(_code);
setPattern(() => parsed.pattern);
activatePattern(parsed.pattern);
setError(undefined);
setActiveCode(_code);
} catch (err: any) {
setError(err);
}
};
const activatePattern = (_pattern) => {
try {
setActivePattern(() => _pattern);
window.location.hash = '#' + encodeURIComponent(btoa(code));
!cycle.started && cycle.start();
} catch (err: any) {
setError(err);
}
}; };
const [isHot, setIsHot] = useState(false); // set to true to enable live coding in hot.js, using dev server const [isHot, setIsHot] = useState(false); // set to true to enable live coding in hot.js, using dev server
const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`); const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`);
@ -102,7 +122,7 @@ function App() {
if (e.ctrlKey || e.altKey) { if (e.ctrlKey || e.altKey) {
switch (e.code) { switch (e.code) {
case 'Enter': case 'Enter':
activatePattern(); activateCode();
break; break;
case 'Period': case 'Period':
cycle.stop(); cycle.stop();
@ -111,40 +131,22 @@ function App() {
}; };
document.addEventListener('keypress', handleKeyPress); document.addEventListener('keypress', handleKeyPress);
return () => document.removeEventListener('keypress', handleKeyPress); return () => document.removeEventListener('keypress', handleKeyPress);
}, [pattern]); }, [pattern, code]);
// parse pattern when code changes // parse pattern when code changes
useEffect(() => { useEffect(() => {
let _code = code;
// handle hot mode
if (isHot) { if (isHot) {
if (typeof hot !== 'string') { if (typeof hot !== 'string') {
getHotCode().then((_code) => { getHotCode().then((_code) => {
setCode(_code); // setCode(_code);
setMode('javascript');
}); // if using HMR, just use changed file }); // if using HMR, just use changed file
activatePattern(hot); activatePattern(hot);
return; return;
} else { } else {
_code = hot; setCode(hot);
setCode(_code); activateCode(hot);
} }
} }
// normal mode
try {
const parsed = evaluate(_code);
// need arrow function here! otherwise if user returns a function, react will think it's a state reducer
// only first time, then need ctrl+enter
setPattern(() => parsed.pattern);
if (isHot) {
activatePattern(parsed.pattern);
}
setMode(parsed.mode);
setError(undefined);
} catch (err: any) {
console.warn(err);
setError(err);
}
}, [code, isHot]); }, [code, isHot]);
// scroll log box to bottom when log changes // scroll log box to bottom when log changes
@ -175,7 +177,7 @@ function App() {
<button <button
onClick={() => { onClick={() => {
const _code = getRandomTune(); const _code = getRandomTune();
console.log('tune',_code); // uncomment this to debug when random code fails console.log('tune', _code); // uncomment this to debug when random code fails
setCode(_code); setCode(_code);
const parsed = evaluate(_code); const parsed = evaluate(_code);
// Tone.Transport.cancel(Tone.Transport.seconds); // Tone.Transport.cancel(Tone.Transport.seconds);
@ -204,7 +206,7 @@ function App() {
value={code} value={code}
readOnly={isHot} readOnly={isHot}
options={{ options={{
mode, mode: 'javascript',
theme: 'material', theme: 'material',
lineNumbers: true, lineNumbers: true,
}} }}
@ -218,20 +220,22 @@ function App() {
<span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right"> <span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right">
{!cycle.started {!cycle.started
? `press ctrl+enter to play\n` ? `press ctrl+enter to play\n`
: !isHot && activePattern !== pattern : !isHot && code !== activeCode
? `ctrl+enter to update\n` ? `ctrl+enter to update\n`
: 'no changes\n'} : 'no changes\n'}
{!isHot && <>{{ pegjs: 'mini' }[mode] || mode} mode</>}
{isHot && '🔥 hot mode: go to hot.js to edit pattern, then save'} {isHot && '🔥 hot mode: go to hot.js to edit pattern, then save'}
</span> </span>
</div> </div>
{error && <div className="absolute right-2 bottom-2 text-red-500">{error?.message || 'unknown error'}</div>} {error && (
<div className={cx('absolute right-2 bottom-2', 'text-red-500')}>{error?.message || 'unknown error'}</div>
)}
</div> </div>
<button <button
className="flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500" className="flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500"
onClick={() => { onClick={() => {
if (!cycle.started) { if (!cycle.started) {
activatePattern(); // activatePattern();
activateCode();
} else { } else {
cycle.stop(); cycle.stop();
} }