diff --git a/packages/react/examples/nano-repl/src/App.jsx b/packages/react/examples/nano-repl/src/App.jsx index 086923ac6..796f672bd 100644 --- a/packages/react/examples/nano-repl/src/App.jsx +++ b/packages/react/examples/nano-repl/src/App.jsx @@ -1,5 +1,5 @@ import { controls, evalScope } from '@strudel.cycles/core'; -import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react'; +import { CodeMirror, useHighlighting, useStrudel, flash } from '@strudel.cycles/react'; import { getAudioContext, initAudioOnFirstClick, @@ -93,32 +93,6 @@ function App() { }); const error = evalError || schedulerError; - useKeydown( - useCallback( - async (e) => { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - e.preventDefault(); - flash(view); - await evaluate(code); - if (e.shiftKey) { - panic(); - scheduler.stop(); - scheduler.start(); - } - if (!scheduler.started) { - scheduler.start(); - } - } else if (e.code === 'Period') { - scheduler.stop(); - panic(); - e.preventDefault(); - } - } - }, - [scheduler, evaluate, view, code], - ), - ); return (
{error &&

error {error.message}

} - + { + flash(view); + await evaluate(code); + if (!scheduler.started) { + scheduler.start(); + } + }} + onReEvaluate={async () => { + await evaluate(code); + panic(); + scheduler.stop(); + scheduler.start(); + }} + onStop={() => { + scheduler.stop(); + panic(); + }} + /> ); } diff --git a/packages/react/src/components/CodeMirror6.jsx b/packages/react/src/components/CodeMirror6.jsx index 63d4d5a13..e863b1ba3 100644 --- a/packages/react/src/components/CodeMirror6.jsx +++ b/packages/react/src/components/CodeMirror6.jsx @@ -29,6 +29,10 @@ export default function CodeMirror({ onViewChanged, onSelectionChange, onDocChange, + onEvaluate, + onReEvaluate, + onPanic, + onStop, theme, keybindings, isLineNumbersDisplayed, @@ -94,14 +98,33 @@ export default function CodeMirror({ _extensions.push(autocompletion({ override: [] })); } - _extensions.push([keymap.of({})]); + _extensions.push( + keymap.of([ + { + key: 'Ctrl-Enter', + run: () => onEvaluate?.(), + }, + { + key: 'Ctrl-.', + run: () => onStop?.(), + }, + { + key: 'Ctrl-Shift-.', + run: () => (onPanic ? onPanic() : onStop?.()), + }, + { + key: 'Ctrl-Shift-Enter', + run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()), + }, + ]), + ); if (isLineWrappingEnabled) { _extensions.push(EditorView.lineWrapping); } return _extensions; - }, [keybindings, isAutoCompletionEnabled, isLineWrappingEnabled]); + }, [keybindings, isAutoCompletionEnabled, isLineWrappingEnabled, onEvaluate, onStop]); const basicSetup = useMemo(() => ({ lineNumbers: isLineNumbersDisplayed }), [isLineNumbersDisplayed]); diff --git a/packages/react/src/components/MiniRepl.jsx b/packages/react/src/components/MiniRepl.jsx index 125008747..fb14f954e 100644 --- a/packages/react/src/components/MiniRepl.jsx +++ b/packages/react/src/components/MiniRepl.jsx @@ -10,7 +10,6 @@ import { Icon } from './Icon'; import './style.css'; import { logger } from '@strudel.cycles/core'; import useEvent from '../hooks/useEvent.mjs'; -import useKeydown from '../hooks/useKeydown.mjs'; const getTime = () => getAudioContext().currentTime; @@ -92,27 +91,6 @@ export function MiniRepl({ getTime: () => scheduler.now(), }); - // keyboard shortcuts - useKeydown( - useCallback( - async (e) => { - if (view?.hasFocus) { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - e.preventDefault(); - flash(view); - await activateCode(); - } else if (e.key === '.' || e.code === 'Period') { - stop(); - e.preventDefault(); - } - } - } - }, - [activateCode, stop, view], - ), - ); - const [log, setLog] = useState([]); useLogger( useCallback((e) => { @@ -164,6 +142,11 @@ export function MiniRepl({ fontSize={fontSize} keybindings={keybindings} isLineNumbersDisplayed={isLineNumbersDisplayed} + onEvaluate={() => { + flash(view); + activateCode(); + }} + onStop={() => stop()} /> )} {error &&
{error.message}
} diff --git a/packages/react/src/hooks/useKeydown.mjs b/packages/react/src/hooks/useKeydown.mjs deleted file mode 100644 index 88d1cb940..000000000 --- a/packages/react/src/hooks/useKeydown.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { useLayoutEffect } from 'react'; - -// set active pattern on ctrl+enter -const useKeydown = (callback) => - useLayoutEffect(() => { - window.addEventListener('keydown', callback, true); - return () => window.removeEventListener('keydown', callback, true); - }, [callback]); - -export default useKeydown; diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 5bde3c111..377f91a5e 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -5,8 +5,8 @@ This program is free software: you can redistribute it and/or modify it under th */ import { cleanupDraw, cleanupUi, getDrawContext, logger } from '@strudel.cycles/core'; -import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react'; -import { getAudioContext, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; +import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react'; +import { getAudioContext, resetLoadedSounds, webaudioOutput, panic } from '@strudel.cycles/webaudio'; import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react'; @@ -155,29 +155,6 @@ export function Repl({ embedded = false }) { }); }, []); - // keyboard shortcuts - useKeydown( - useCallback( - async (e) => { - if (e.ctrlKey || e.altKey) { - if (e.code === 'Enter') { - if (getAudioContext().state !== 'running') { - alert('please click play to initialize the audio. you can use shortcuts after that!'); - return; - } - e.preventDefault(); - flash(view); - await activateCode(); - } else if (e.key === '.' || e.code === 'Period') { - stop(); - e.preventDefault(); - } - } - }, - [activateCode, stop, view], - ), - ); - // highlighting const { setMiniLocations } = useHighlighting({ view, @@ -310,6 +287,24 @@ export function Repl({ embedded = false }) { onChange={handleChangeCode} onViewChanged={handleViewChanged} onSelectionChange={handleSelectionChange} + onEvaluate={() => { + if (getAudioContext().state !== 'running') { + alert('please click play to initialize the audio. you can use shortcuts after that!'); + return; + } + flash(view); + activateCode(); + }} + onReEvaluate={() => { + stop(); + panic(); + activateCode(); + }} + onPanic={() => { + stop(); + panic(); + }} + onStop={() => stop()} /> {panelPosition === 'right' && !isEmbedded &&