diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 40a1a03f..7db3da94 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -1,11 +1,11 @@ --- -import HeadCommon from '../components/HeadCommon.astro'; -import { Repl } from '../repl/Repl.jsx'; +import HeadCommonNext from '../components/HeadCommonNext.astro'; +import { Repl } from '../repl/Repl'; --- - + Strudel REPL diff --git a/website/src/pages/next/index.astro b/website/src/pages/next/index.astro deleted file mode 100644 index 0db75a1d..00000000 --- a/website/src/pages/next/index.astro +++ /dev/null @@ -1,14 +0,0 @@ ---- -import HeadCommonNext from '../../components/HeadCommonNext.astro'; -import { Repl2 } from '../../repl/Repl2'; ---- - - - - - Strudel REPL - - - - - diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 65cc3473..4aa677ad 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -1,15 +1,17 @@ /* -App.js - +Repl.jsx - Copyright (C) 2022 Strudel contributors - see This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; -import { cleanupDraw, cleanupUi, code2hash, getDrawContext, logger } from '@strudel.cycles/core'; -import { CodeMirror, cx, flash, useHighlighting, useKeydown, useStrudel } from '@strudel.cycles/react'; -import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; -import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; -import { createContext, useCallback, useEffect, useMemo, useState } from 'react'; +import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; +import { cx } from '@strudel.cycles/react'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; +import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; +/* import { writeText } from '@tauri-apps/api/clipboard'; +import { nanoid } from 'nanoid'; */ +import { createContext, useCallback, useEffect, useRef, useState } from 'react'; import { initUserCode, setActivePattern, @@ -22,9 +24,14 @@ import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; import { Panel } from './panel/Panel'; -import { prebake } from './prebake.mjs'; -import { themes } from './themes.mjs'; +// import { prebake } from '@strudel/repl'; +import { useStore } from '@nanostores/react'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; +import './Repl.css'; + +const { code: randomTune, name } = getRandomTune(); +export const ReplContext = createContext(null); const { latestCode } = settingsMap.get(); @@ -39,185 +46,151 @@ if (typeof window !== 'undefined') { clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); } -const getTime = () => getAudioContext().currentTime; - -const { code: randomTune, name } = getRandomTune(); - -export const ReplContext = createContext(null); +// const getTime = () => getAudioContext().currentTime; export function Repl({ embedded = false }) { - const isEmbedded = embedded || window.location !== window.parent.location; - const [view, setView] = useState(); // codemirror view - const [pending, setPending] = useState(true); - const { - theme, - keybindings, - fontSize, - fontFamily, - isLineNumbersDisplayed, - isActiveLineHighlighted, - isAutoCompletionEnabled, - isTooltipEnabled, - isLineWrappingEnabled, - panelPosition, - isZen, - } = useSettings(); + //const isEmbedded = embedded || window.location !== window.parent.location; + const isEmbedded = false; + const { panelPosition, isZen } = useSettings(); + /* const replState = useStore($replstate); + const isDirty = useStore($repldirty); */ + const shouldDraw = true; - const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]); - const { setWidgets } = useWidgets(view); - const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = - useStrudel({ - initialCode: '// LOADING...', + const init = useCallback(({ shouldDraw }) => { + const drawTime = [-2, 2]; + const drawContext = shouldDraw ? getDrawContext() : null; + let onDraw; + if (shouldDraw) { + onDraw = (haps, time, frame, painters) => { + painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); + painters?.forEach((painter) => { + // ctx time haps drawTime paintOptions + painter(drawContext, time, haps, drawTime, { clear: false }); + }); + }; + } + const editor = new StrudelMirror({ defaultOutput: webaudioOutput, - getTime, - beforeEval: async () => { - setPending(true); - await modulesLoading; - cleanupUi(); - cleanupDraw(); + getTime: () => getAudioContext().currentTime, + transpiler, + autodraw: false, + root: containerRef.current, + initialCode: '// LOADING', + pattern: silence, + drawTime, + onDraw, + prebake: async () => Promise.all([modulesLoading, presets]), + onUpdateState: (state) => { + setReplState({ ...state }); }, - afterEval: ({ code, meta }) => { + afterEval: ({ code }) => { updateUserCode(code); - setMiniLocations(meta.miniLocations); - setWidgets(meta.widgets); - setPending(false); + // setPending(false); setLatestCode(code); window.location.hash = '#' + code2hash(code); }, - onEvalError: (err) => { - setPending(false); - }, - onToggle: (play) => { - if (!play) { - cleanupDraw(false); - window.postMessage('strudel-stop'); - } else { - window.postMessage('strudel-start'); - } - }, - drawContext, - // drawTime: [0, 6], - paintOptions, + bgFill: false, }); - - // init code - useEffect(() => { + // init settings initCode().then((decoded) => { let msg; if (decoded) { - setCode(decoded); + editor.setCode(decoded); initUserCode(decoded); msg = `I have loaded the code from the URL.`; } else if (latestCode) { - setCode(latestCode); + editor.setCode(latestCode); msg = `Your last session has been loaded!`; } /* if(randomTune) */ else { - setCode(randomTune); + editor.setCode(randomTune); msg = `A random code snippet named "${name}" has been loaded!`; } - //registers samples that have been saved to the index DB logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - setPending(false); + // setPending(false); }); + + editorRef.current = editor; }, []); - // 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], - ), - ); + const [replState, setReplState] = useState({}); + const { started, isDirty, error, activeCode } = replState; + const editorRef = useRef(); + const containerRef = useRef(); + const [client, setClient] = useState(false); + useEffect(() => { + setClient(true); + if (!editorRef.current) { + setTimeout(() => { + init({ shouldDraw }); + }); + } + return () => { + editorRef.current?.clear(); + delete editorRef.current; + }; + }, []); - // highlighting - const { setMiniLocations } = useHighlighting({ - view, - pattern, - active: started && !activeCode?.includes('strudel disable-highlighting'), - getTime: () => scheduler.now(), - }); + // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! + // this will be the case when the main repl is being replaced + const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) }); + useEffect(() => { + let editorSettings = {}; + Object.keys(defaultSettings).forEach((key) => { + if (_settings.hasOwnProperty(key)) { + editorSettings[key] = _settings[key]; + } + }); + editorRef.current?.updateSettings(editorSettings); + }, [_settings]); // // UI Actions // - const handleChangeCode = useCallback( - (c) => { - setCode(c); - // started && logger('[edit] code changed. hit ctrl+enter to update'); - }, - [started], - ); - const handleSelectionChange = useCallback((selection) => { - // TODO: scroll to selected function in reference - // console.log('selectino change', selection.ranges[0].from); - }, []); - - const handleTogglePlay = async () => { - await getAudioContext().resume(); // fixes no sound in ios webkit - if (!started) { - logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight'); - activateCode(); - } else { - logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight'); - stop(); - } - }; + const handleTogglePlay = async () => editorRef.current?.toggle(); const handleUpdate = async (newCode, reset = false) => { if (reset) { clearCanvas(); resetLoadedSounds(); - scheduler.setCps(1); + editorRef.current.repl.setCps(1); await prebake(); // declare default samples } - (newCode || isDirty) && activateCode(newCode); + if (newCode || isDirty) { + editorRef.current.setCode(newCode); + editorRef.current.repl.evaluate(newCode); + } logger('[repl] code updated!'); }; - const handleShuffle = async () => { + // window.postMessage('strudel-shuffle'); const { code, name } = getRandomTune(); logger(`[repl] ✨ loading random tune "${name}"`); setActivePattern(name); clearCanvas(); resetLoadedSounds(); - scheduler.setCps(1); + editorRef.current.repl.setCps(1); await prebake(); // declare default samples - await evaluate(code, false); + editorRef.current.setCode(code); + editorRef.current.repl.evaluate(code); }; - const handleShare = async () => shareCode(activeCode || code); + const handleShare = async () => shareCode(activeCode); + const pending = false; + //const error = undefined; + // const { started, activeCode } = replState; + const context = { - scheduler, + // scheduler, embedded, started, pending, isDirty, activeCode, - handleChangeCode, handleTogglePlay, handleUpdate, handleShuffle, handleShare, }; - const currentTheme = useMemo(() => themes[theme] || themes.strudelTheme, [theme]); - const handleViewChanged = useCallback((v) => { - setView(v); - }, []); return ( // bg-gradient-to-t from-blue-900 to-slate-900 @@ -231,7 +204,7 @@ export function Repl({ embedded = false }) { >
- {isEmbedded && !started && ( + {/* isEmbedded && !started && ( - )} + ) */}
-
- -
+
{panelPosition === 'right' && !isEmbedded && }
{error && ( diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx deleted file mode 100644 index fff89e52..00000000 --- a/website/src/repl/Repl2.jsx +++ /dev/null @@ -1,231 +0,0 @@ -/* -App.js - -Copyright (C) 2022 Strudel contributors - see -This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . -*/ - -import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; -import { cx } from '@strudel.cycles/react'; -import { transpiler } from '@strudel.cycles/transpiler'; -import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; -import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; -/* import { writeText } from '@tauri-apps/api/clipboard'; -import { nanoid } from 'nanoid'; */ -import { createContext, useCallback, useEffect, useRef, useState } from 'react'; -import { - initUserCode, - setActivePattern, - setLatestCode, - settingsMap, - updateUserCode, - useSettings, -} from '../settings.mjs'; -import { Header } from './Header'; -import Loader from './Loader'; -import './Repl.css'; -import { Panel } from './panel/Panel'; -// import { prebake } from '@strudel/repl'; -import { useStore } from '@nanostores/react'; -import { prebake /* , resetSounds */ } from './prebake.mjs'; -import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; -import './Repl.css'; - -const { code: randomTune, name } = getRandomTune(); -export const ReplContext = createContext(null); - -const { latestCode } = settingsMap.get(); - -initAudioOnFirstClick(); - -const modulesLoading = loadModules(); -const presets = prebake(); - -let drawContext, clearCanvas; -if (typeof window !== 'undefined') { - drawContext = getDrawContext(); - clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width); -} - -// const getTime = () => getAudioContext().currentTime; - -export function Repl2({ embedded = false }) { - //const isEmbedded = embedded || window.location !== window.parent.location; - const isEmbedded = false; - const { panelPosition, isZen } = useSettings(); - /* const replState = useStore($replstate); - const isDirty = useStore($repldirty); */ - const shouldDraw = true; - - const init = useCallback(({ shouldDraw }) => { - const drawTime = [-2, 2]; - const drawContext = shouldDraw ? getDrawContext() : null; - let onDraw; - if (shouldDraw) { - onDraw = (haps, time, frame, painters) => { - painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2); - painters?.forEach((painter) => { - // ctx time haps drawTime paintOptions - painter(drawContext, time, haps, drawTime, { clear: false }); - }); - }; - } - const editor = new StrudelMirror({ - defaultOutput: webaudioOutput, - getTime: () => getAudioContext().currentTime, - transpiler, - autodraw: false, - root: containerRef.current, - initialCode: '// LOADING', - pattern: silence, - drawTime, - onDraw, - prebake: async () => Promise.all([modulesLoading, presets]), - onUpdateState: (state) => { - setReplState({ ...state }); - }, - afterEval: ({ code }) => { - updateUserCode(code); - // setPending(false); - setLatestCode(code); - window.location.hash = '#' + code2hash(code); - }, - bgFill: false, - }); - // init settings - initCode().then((decoded) => { - let msg; - if (decoded) { - editor.setCode(decoded); - initUserCode(decoded); - msg = `I have loaded the code from the URL.`; - } else if (latestCode) { - editor.setCode(latestCode); - msg = `Your last session has been loaded!`; - } /* if(randomTune) */ else { - editor.setCode(randomTune); - msg = `A random code snippet named "${name}" has been loaded!`; - } - logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); - // setPending(false); - }); - - editorRef.current = editor; - }, []); - - const [replState, setReplState] = useState({}); - const { started, isDirty, error, activeCode } = replState; - const editorRef = useRef(); - const containerRef = useRef(); - const [client, setClient] = useState(false); - useEffect(() => { - setClient(true); - if (!editorRef.current) { - setTimeout(() => { - init({ shouldDraw }); - }); - } - return () => { - editorRef.current?.clear(); - delete editorRef.current; - }; - }, []); - - // this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly! - // this will be the case when the main repl is being replaced - const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) }); - useEffect(() => { - let editorSettings = {}; - Object.keys(defaultSettings).forEach((key) => { - if (_settings.hasOwnProperty(key)) { - editorSettings[key] = _settings[key]; - } - }); - editorRef.current?.updateSettings(editorSettings); - }, [_settings]); - - // - // UI Actions - // - - const handleTogglePlay = async () => editorRef.current?.toggle(); - const handleUpdate = async (newCode, reset = false) => { - if (reset) { - clearCanvas(); - resetLoadedSounds(); - editorRef.current.repl.setCps(1); - await prebake(); // declare default samples - } - if (newCode || isDirty) { - editorRef.current.setCode(newCode); - editorRef.current.repl.evaluate(newCode); - } - logger('[repl] code updated!'); - }; - const handleShuffle = async () => { - // window.postMessage('strudel-shuffle'); - const { code, name } = getRandomTune(); - logger(`[repl] ✨ loading random tune "${name}"`); - setActivePattern(name); - clearCanvas(); - resetLoadedSounds(); - editorRef.current.repl.setCps(1); - await prebake(); // declare default samples - editorRef.current.setCode(code); - editorRef.current.repl.evaluate(code); - }; - - const handleShare = async () => shareCode(activeCode); - const pending = false; - //const error = undefined; - // const { started, activeCode } = replState; - - const context = { - // scheduler, - embedded, - started, - pending, - isDirty, - activeCode, - handleTogglePlay, - handleUpdate, - handleShuffle, - handleShare, - }; - - return ( - // bg-gradient-to-t from-blue-900 to-slate-900 - // bg-gradient-to-t from-green-900 to-slate-900 - -
- -
- {/* isEmbedded && !started && ( - - ) */} -
-
- {panelPosition === 'right' && !isEmbedded && } -
- {error && ( -
{error.message || 'Unknown Error :-/'}
- )} - {panelPosition === 'bottom' && !isEmbedded && } -
-
- ); -}