From d74d50c40a7d31cfaa5dd254c17f893eca1aed94 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 19:37:03 +0100 Subject: [PATCH 01/16] first steps of using StrudelMirror in main repl --- website/src/pages/vanilla/2.astro | 16 +++ website/src/repl/Repl2.jsx | 224 ++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 website/src/pages/vanilla/2.astro create mode 100644 website/src/repl/Repl2.jsx diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/vanilla/2.astro new file mode 100644 index 00000000..543d5fed --- /dev/null +++ b/website/src/pages/vanilla/2.astro @@ -0,0 +1,16 @@ +--- +import HeadCommon from '../../components/HeadCommon.astro'; +// import { Repl } from '../repl/Repl.jsx'; + +import { Repl2 } from '../../repl/Repl2'; +--- + + + + + Strudel REPL + + + + + diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx new file mode 100644 index 00000000..c4241a49 --- /dev/null +++ b/website/src/repl/Repl2.jsx @@ -0,0 +1,224 @@ +/* +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 { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; +import { cx } from '@strudel.cycles/react'; +import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { transpiler } from '@strudel.cycles/transpiler'; +import { StrudelMirror } from '@strudel/codemirror'; +/* import { writeText } from '@tauri-apps/api/clipboard'; +import { nanoid } from 'nanoid'; */ +import { createContext, useState } from 'react'; +import { useSettings } from '../settings.mjs'; +import { isTauri } from '../tauri.mjs'; +import { Panel } from './panel/Panel'; +import { Header } from './Header'; +import Loader from './Loader'; +import './Repl.css'; +import { useCallback, useRef, useEffect } from 'react'; +// import { prebake } from '@strudel/repl'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; + +export const ReplContext = createContext(null); + +export function Repl2({ embedded = false }) { + //const isEmbedded = embedded || window.location !== window.parent.location; + const isEmbedded = false; + const [lastShared, setLastShared] = useState(); + const { panelPosition, isZen } = useSettings(); + /* const replState = useStore($replstate); + const isDirty = useStore($repldirty); */ + const shouldDraw = true; + + const init = useCallback(({ code, shouldDraw }) => { + const drawTime = [0, 4]; + 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 () => { + let modules = [ + import('@strudel.cycles/core'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/xen'), + import('@strudel.cycles/webaudio'), + import('@strudel/codemirror'), + import('@strudel/hydra'), + import('@strudel.cycles/serial'), + import('@strudel.cycles/soundfonts'), + import('@strudel.cycles/csound'), + ]; + if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); + } else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + } + + const modulesLoading = evalScope( + controls, // sadly, this cannot be exported from core direclty + // settingPatterns, + ...modules, + ); + await Promise.all([modulesLoading, prebake()]); + }, + onUpdateState: (state) => { + setReplState({ ...state }); + }, + }); + // init settings + editor.setCode(code); + 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({ code: 's("bd")', shouldDraw }); + }); + } + return () => { + editor.clear(); + }; + }, []); + + // + // UI Actions + // + + const handleTogglePlay = async () => { + editorRef.current?.toggle(); + /* 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 handleUpdate = () => { + isDirty && activateCode(); + logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight'); + }; + + const handleShuffle = async () => { + // window.postMessage('strudel-shuffle'); + }; + + /* const handleShare = async () => { + const codeToShare = activeCode || code; + if (lastShared === codeToShare) { + logger(`Link already generated!`, 'error'); + return; + } + // generate uuid in the browser + const hash = nanoid(12); + const shareUrl = window.location.origin + window.location.pathname + '?' + hash; + const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); + if (!error) { + setLastShared(activeCode || code); + // copy shareUrl to clipboard + if (isTauri()) { + await writeText(shareUrl); + } else { + await navigator.clipboard.writeText(shareUrl); + } + const message = `Link copied to clipboard: ${shareUrl}`; + alert(message); + // alert(message); + logger(message, 'highlight'); + } else { + console.log('error', error); + const message = `Error: ${error.message}`; + // alert(message); + logger(message); + } + }; */ + const pending = false; + //const error = undefined; + // const { started, activeCode } = replState; + + const context = { + // scheduler, + embedded, + started, + pending, + isDirty, + lastShared, + activeCode, + // handleChangeCode: codemirror.handleChangeCode, + handleTogglePlay, + handleUpdate, + handleShuffle, + /* handleShare, */ + 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 && } +
+
+ ); +} From 7dac1e275d5389cda9bdb958774b10827c9f94d2 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 20:08:57 +0100 Subject: [PATCH 02/16] repl2: working update + shuffle buttons + faster loading --- packages/core/repl.mjs | 4 ++ website/src/repl/Repl2.jsx | 129 +++++++++++++++++++++++-------------- 2 files changed, 85 insertions(+), 48 deletions(-) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index b3c7a543..fd0d8a3b 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -62,6 +62,10 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true, shouldHush = true) => { + if (code === state.activeCode && state.started) { + logger('[eval] skip: not dirty') + return; + } if (!code) { throw new Error('no code to evaluate'); } diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index c4241a49..d153b80e 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -6,13 +6,24 @@ This program is free software: you can redistribute it and/or modify it under th import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; -import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio'; +import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; import { StrudelMirror } from '@strudel/codemirror'; +import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ import { createContext, useState } from 'react'; -import { useSettings } from '../settings.mjs'; +import { + useSettings, + settingsMap, + setLatestCode, + updateUserCode, + setActivePattern, + getActivePattern, + getUserPattern, + initUserCode, + settingPatterns, +} from '../settings.mjs'; import { isTauri } from '../tauri.mjs'; import { Panel } from './panel/Panel'; import { Header } from './Header'; @@ -21,9 +32,58 @@ import './Repl.css'; import { useCallback, useRef, useEffect } from 'react'; // import { prebake } from '@strudel/repl'; import { prebake /* , resetSounds */ } from './prebake.mjs'; +import * as tunes from './tunes.mjs'; export const ReplContext = createContext(null); +const { latestCode } = settingsMap.get(); + +initAudioOnFirstClick(); + +// Create a single supabase client for interacting with your database +const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); + +let modules = [ + import('@strudel.cycles/core'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/xen'), + import('@strudel.cycles/webaudio'), + import('@strudel/codemirror'), + import('@strudel/hydra'), + import('@strudel.cycles/serial'), + import('@strudel.cycles/soundfonts'), + import('@strudel.cycles/csound'), +]; +if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); +} else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); +} + +const modulesLoading = evalScope( + controls, // sadly, this cannot be exported from core direclty + settingPatterns, + ...modules, +); + +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; @@ -56,36 +116,7 @@ export function Repl2({ embedded = false }) { pattern: silence, drawTime, onDraw, - prebake: async () => { - let modules = [ - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel/codemirror'), - import('@strudel/hydra'), - import('@strudel.cycles/serial'), - import('@strudel.cycles/soundfonts'), - import('@strudel.cycles/csound'), - ]; - if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); - } else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); - } - - const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - // settingPatterns, - ...modules, - ); - await Promise.all([modulesLoading, prebake()]); - }, + prebake: async () => Promise.all([modulesLoading, presets]), onUpdateState: (state) => { setReplState({ ...state }); }, @@ -116,24 +147,19 @@ export function Repl2({ embedded = false }) { // UI Actions // - const handleTogglePlay = async () => { - editorRef.current?.toggle(); - /* 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 handleUpdate = () => { - isDirty && activateCode(); - logger('[repl] code updated! tip: you can also update the code by pressing ctrl+enter', 'highlight'); - }; - + const handleTogglePlay = async () => editorRef.current?.toggle(); + const handleUpdate = () => editorRef.current?.evaluate(); 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 () => { @@ -222,3 +248,10 @@ export function Repl2({ embedded = false }) { ); } + +function getRandomTune() { + const allTunes = Object.entries(tunes); + const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const [name, code] = randomItem(allTunes); + return { name, code }; +} From e19f799046de27730e498b79eff669e1399f46fa Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 20:52:00 +0100 Subject: [PATCH 03/16] wire up settings and theming --- packages/codemirror/codemirror.mjs | 4 ++ packages/core/repl.mjs | 4 -- website/src/components/HeadCommonNext.astro | 58 +++++++++++++++++++++ website/src/pages/vanilla/2.astro | 6 +-- website/src/repl/Repl2.jsx | 18 ++++++- 5 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 website/src/components/HeadCommonNext.astro diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6ad94209..a2c70599 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -186,6 +186,10 @@ export class StrudelMirror { this.root.style.backgroundColor = 'var(--background)'; cmEditor.style.backgroundColor = 'transparent'; } + const settings = codemirrorSettings.get(); + this.setFontSize(settings.fontSize); + this.setFontFamily(settings.fontFamily); + // stop this repl when another repl is started this.onStartRepl = (e) => { if (e.detail !== this.id) { diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index fd0d8a3b..b3c7a543 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -62,10 +62,6 @@ export function repl({ }; setTime(() => scheduler.now()); // TODO: refactor? const evaluate = async (code, autostart = true, shouldHush = true) => { - if (code === state.activeCode && state.started) { - logger('[eval] skip: not dirty') - return; - } if (!code) { throw new Error('no code to evaluate'); } diff --git a/website/src/components/HeadCommonNext.astro b/website/src/components/HeadCommonNext.astro new file mode 100644 index 00000000..9f323a7a --- /dev/null +++ b/website/src/components/HeadCommonNext.astro @@ -0,0 +1,58 @@ +--- +import { pwaInfo } from 'virtual:pwa-info'; +import '../styles/index.css'; + +const { BASE_URL } = import.meta.env; +const baseNoTrailing = BASE_URL.endsWith('/') ? BASE_URL.slice(0, -1) : BASE_URL; +--- + + + + + + + + + + + + + + + + + + + + + + + +{pwaInfo && } + + diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/vanilla/2.astro index 543d5fed..0db75a1d 100644 --- a/website/src/pages/vanilla/2.astro +++ b/website/src/pages/vanilla/2.astro @@ -1,13 +1,11 @@ --- -import HeadCommon from '../../components/HeadCommon.astro'; -// import { Repl } from '../repl/Repl.jsx'; - +import HeadCommonNext from '../../components/HeadCommonNext.astro'; import { Repl2 } from '../../repl/Repl2'; --- - + Strudel REPL diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index d153b80e..afc81198 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -8,7 +8,7 @@ import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.c import { cx } from '@strudel.cycles/react'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; -import { StrudelMirror } from '@strudel/codemirror'; +import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ @@ -33,6 +33,7 @@ import { useCallback, useRef, useEffect } from 'react'; // import { prebake } from '@strudel/repl'; import { prebake /* , resetSounds */ } from './prebake.mjs'; import * as tunes from './tunes.mjs'; +import { useStore } from '@nanostores/react'; export const ReplContext = createContext(null); @@ -139,10 +140,23 @@ export function Repl2({ embedded = false }) { }); } return () => { - editor.clear(); + editorRef.current?.clear(); }; }, []); + // 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 // From 5c41d6789cc40335c7dd0982ad327b5b10b2918f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 21:11:28 +0100 Subject: [PATCH 04/16] checkbox for isPatternHighlightingEnabled --- website/src/repl/panel/SettingsTab.jsx | 6 ++++++ website/src/settings.mjs | 2 ++ 2 files changed, 8 insertions(+) diff --git a/website/src/repl/panel/SettingsTab.jsx b/website/src/repl/panel/SettingsTab.jsx index cee5b286..3efab8f2 100644 --- a/website/src/repl/panel/SettingsTab.jsx +++ b/website/src/repl/panel/SettingsTab.jsx @@ -78,6 +78,7 @@ export function SettingsTab() { theme, keybindings, isLineNumbersDisplayed, + isPatternHighlightingEnabled, isActiveLineHighlighted, isAutoCompletionEnabled, isTooltipEnabled, @@ -153,6 +154,11 @@ export function SettingsTab() { onChange={(cbEvent) => settingsMap.setKey('isActiveLineHighlighted', cbEvent.target.checked)} value={isActiveLineHighlighted} /> + settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)} + value={isPatternHighlightingEnabled} + /> settingsMap.setKey('isAutoCompletionEnabled', cbEvent.target.checked)} diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 570b6446..489bd71c 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -12,6 +12,7 @@ export const defaultSettings = { isAutoCompletionEnabled: false, isTooltipEnabled: false, isLineWrappingEnabled: false, + isPatternHighlightingEnabled: true, theme: 'strudelTheme', fontFamily: 'monospace', fontSize: 18, @@ -50,6 +51,7 @@ export function useSettings() { isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false, isActiveLineHighlighted: [true, 'true'].includes(state.isActiveLineHighlighted) ? true : false, isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false, + isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false, isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false, isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false, fontSize: Number(state.fontSize), From 8c0065f563f8798ca20c12f0157f1fb0572e9199 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 23:53:54 +0100 Subject: [PATCH 05/16] pull out repl utility functions + repl2 initCode / switching patterns works now --- website/src/repl/Repl.jsx | 103 +---------------------------------- website/src/repl/Repl2.jsx | 91 +++++++++++++++---------------- website/src/repl/util.mjs | 109 +++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 146 deletions(-) create mode 100644 website/src/repl/util.mjs diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index d4f85434..d50995f2 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -42,45 +42,13 @@ import { isTauri } from '../tauri.mjs'; import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs'; import { writeText } from '@tauri-apps/api/clipboard'; import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; +import { getRandomTune, initCode, loadModules } from './util.mjs'; const { latestCode } = settingsMap.get(); initAudioOnFirstClick(); -// Create a single supabase client for interacting with your database -const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', -); - -let modules = [ - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel/codemirror'), - import('@strudel/hydra'), - import('@strudel.cycles/serial'), - import('@strudel.cycles/soundfonts'), - import('@strudel.cycles/csound'), -]; -if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); -} else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); -} - -const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - settingPatterns, - ...modules, -); - +const modulesLoading = loadModules(); const presets = prebake(); let drawContext, clearCanvas; @@ -91,43 +59,6 @@ if (typeof window !== 'undefined') { const getTime = () => getAudioContext().currentTime; -async function initCode() { - // load code from url hash (either short hash from database or decode long hash) - try { - const initialUrl = window.location.href; - const hash = initialUrl.split('?')[1]?.split('#')?.[0]; - const codeParam = window.location.href.split('#')[1] || ''; - // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) - if (codeParam) { - // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) - return hash2code(codeParam); - } else if (hash) { - return supabase - .from('code') - .select('code') - .eq('hash', hash) - .then(({ data, error }) => { - if (error) { - console.warn('failed to load hash', err); - } - if (data.length) { - //console.log('load hash from database', hash); - return data[0].code; - } - }); - } - } catch (err) { - console.warn('failed to decode', err); - } -} - -function getRandomTune() { - const allTunes = Object.entries(tunes); - const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; -} - const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); @@ -289,35 +220,7 @@ export function Repl({ embedded = false }) { await evaluate(code, false); }; - const handleShare = async () => { - const codeToShare = activeCode || code; - if (lastShared === codeToShare) { - logger(`Link already generated!`, 'error'); - return; - } - // generate uuid in the browser - const hash = nanoid(12); - const shareUrl = window.location.origin + window.location.pathname + '?' + hash; - const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); - if (!error) { - setLastShared(activeCode || code); - // copy shareUrl to clipboard - if (isTauri()) { - await writeText(shareUrl); - } else { - await navigator.clipboard.writeText(shareUrl); - } - const message = `Link copied to clipboard: ${shareUrl}`; - alert(message); - // alert(message); - logger(message, 'highlight'); - } else { - console.log('error', error); - const message = `Error: ${error.message}`; - // alert(message); - logger(message); - } - }; + const handleShare = async () => shareCode(activeCode || code); const context = { scheduler, embedded, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index afc81198..070e54e1 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger, getDrawContext, silence, evalScope, controls } from '@strudel.cycles/core'; +import { logger, getDrawContext, silence, code2hash } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; @@ -34,47 +34,16 @@ import { useCallback, useRef, useEffect } from 'react'; import { prebake /* , resetSounds */ } from './prebake.mjs'; import * as tunes from './tunes.mjs'; import { useStore } from '@nanostores/react'; +import { getRandomTune, loadModules, initCode } from './util.mjs'; +const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); const { latestCode } = settingsMap.get(); initAudioOnFirstClick(); -// Create a single supabase client for interacting with your database -const supabase = createClient( - 'https://pidxdsxphlhzjnzmifth.supabase.co', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', -); - -let modules = [ - import('@strudel.cycles/core'), - import('@strudel.cycles/tonal'), - import('@strudel.cycles/mini'), - import('@strudel.cycles/xen'), - import('@strudel.cycles/webaudio'), - import('@strudel/codemirror'), - import('@strudel/hydra'), - import('@strudel.cycles/serial'), - import('@strudel.cycles/soundfonts'), - import('@strudel.cycles/csound'), -]; -if (isTauri()) { - modules = modules.concat([ - import('@strudel/desktopbridge/loggerbridge.mjs'), - import('@strudel/desktopbridge/midibridge.mjs'), - import('@strudel/desktopbridge/oscbridge.mjs'), - ]); -} else { - modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); -} - -const modulesLoading = evalScope( - controls, // sadly, this cannot be exported from core direclty - settingPatterns, - ...modules, -); - +const modulesLoading = loadModules(); const presets = prebake(); let drawContext, clearCanvas; @@ -94,7 +63,7 @@ export function Repl2({ embedded = false }) { const isDirty = useStore($repldirty); */ const shouldDraw = true; - const init = useCallback(({ code, shouldDraw }) => { + const init = useCallback(({ shouldDraw }) => { const drawTime = [0, 4]; const drawContext = shouldDraw ? getDrawContext() : null; let onDraw; @@ -121,9 +90,34 @@ export function Repl2({ embedded = false }) { onUpdateState: (state) => { setReplState({ ...state }); }, + afterEval: ({ code }) => { + updateUserCode(code); + // setPending(false); + setLatestCode(code); + window.location.hash = '#' + code2hash(code); + }, }); // init settings - editor.setCode(code); + initCode().then((decoded) => { + console.log('init code'); + 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!`; + } + //registers samples that have been saved to the index DB + // registerSamplesFromDB(userSamplesDBConfig); + logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); + // setPending(false); + }); + editorRef.current = editor; }, []); @@ -136,7 +130,7 @@ export function Repl2({ embedded = false }) { setClient(true); if (!editorRef.current) { setTimeout(() => { - init({ code: 's("bd")', shouldDraw }); + init({ shouldDraw }); }); } return () => { @@ -162,7 +156,19 @@ export function Repl2({ embedded = false }) { // const handleTogglePlay = async () => editorRef.current?.toggle(); - const handleUpdate = () => editorRef.current?.evaluate(); + 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(); @@ -262,10 +268,3 @@ export function Repl2({ embedded = false }) { ); } - -function getRandomTune() { - const allTunes = Object.entries(tunes); - const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; - const [name, code] = randomItem(allTunes); - return { name, code }; -} diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs new file mode 100644 index 00000000..0da28c25 --- /dev/null +++ b/website/src/repl/util.mjs @@ -0,0 +1,109 @@ +import { controls, evalScope, hash2code } from '@strudel.cycles/core'; +import { settingPatterns } from '../settings.mjs'; +import { isTauri } from '../tauri.mjs'; +import './Repl.css'; +import * as tunes from './tunes.mjs'; +import { createClient } from '@supabase/supabase-js'; + +// Create a single supabase client for interacting with your database +const supabase = createClient( + 'https://pidxdsxphlhzjnzmifth.supabase.co', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM', +); + +export async function initCode() { + // load code from url hash (either short hash from database or decode long hash) + try { + const initialUrl = window.location.href; + const hash = initialUrl.split('?')[1]?.split('#')?.[0]; + const codeParam = window.location.href.split('#')[1] || ''; + // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length) + if (codeParam) { + // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) + return hash2code(codeParam); + } else if (hash) { + return supabase + .from('code') + .select('code') + .eq('hash', hash) + .then(({ data, error }) => { + if (error) { + console.warn('failed to load hash', err); + } + if (data.length) { + //console.log('load hash from database', hash); + return data[0].code; + } + }); + } + } catch (err) { + console.warn('failed to decode', err); + } +} + +export function getRandomTune() { + const allTunes = Object.entries(tunes); + const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)]; + const [name, code] = randomItem(allTunes); + return { name, code }; +} + +export function loadModules() { + let modules = [ + import('@strudel.cycles/core'), + import('@strudel.cycles/tonal'), + import('@strudel.cycles/mini'), + import('@strudel.cycles/xen'), + import('@strudel.cycles/webaudio'), + import('@strudel/codemirror'), + import('@strudel/hydra'), + import('@strudel.cycles/serial'), + import('@strudel.cycles/soundfonts'), + import('@strudel.cycles/csound'), + ]; + if (isTauri()) { + modules = modules.concat([ + import('@strudel/desktopbridge/loggerbridge.mjs'), + import('@strudel/desktopbridge/midibridge.mjs'), + import('@strudel/desktopbridge/oscbridge.mjs'), + ]); + } else { + modules = modules.concat([import('@strudel.cycles/midi'), import('@strudel.cycles/osc')]); + } + + return evalScope( + controls, // sadly, this cannot be exported from core direclty + settingPatterns, + ...modules, + ); +} + +export async function shareCode(codeToShare) { + // const codeToShare = activeCode || code; + if (lastShared === codeToShare) { + logger(`Link already generated!`, 'error'); + return; + } + // generate uuid in the browser + const hash = nanoid(12); + const shareUrl = window.location.origin + window.location.pathname + '?' + hash; + const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); + if (!error) { + setLastShared(activeCode || code); + // copy shareUrl to clipboard + if (isTauri()) { + await writeText(shareUrl); + } else { + await navigator.clipboard.writeText(shareUrl); + } + const message = `Link copied to clipboard: ${shareUrl}`; + alert(message); + // alert(message); + logger(message, 'highlight'); + } else { + console.log('error', error); + const message = `Error: ${error.message}`; + // alert(message); + logger(message); + } +} From 63ae95186bcf3bf49a43cf800734d10a271bf1a5 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:06:15 +0100 Subject: [PATCH 06/16] repl2: share + clean up imports --- website/src/repl/Repl.jsx | 56 +++++++++++---------------------- website/src/repl/Repl2.jsx | 64 ++++++++------------------------------ website/src/repl/util.mjs | 5 ++- 3 files changed, 36 insertions(+), 89 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index d50995f2..4cd1696a 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -4,45 +4,28 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { - cleanupDraw, - cleanupUi, - controls, - evalScope, - getDrawContext, - logger, - code2hash, - hash2code, -} from '@strudel.cycles/core'; -import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react'; -import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; -import { createClient } from '@supabase/supabase-js'; -import { nanoid } from 'nanoid'; -import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react'; -import './Repl.css'; -import { Panel } from './panel/Panel'; -import { Header } from './Header'; -import { prebake } from './prebake.mjs'; -import * as tunes from './tunes.mjs'; import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon'; -import { themes } from './themes.mjs'; -import { - settingsMap, - useSettings, - setLatestCode, - updateUserCode, - setActivePattern, - getActivePattern, - getUserPattern, - initUserCode, -} from '../settings.mjs'; -import Loader from './Loader'; -import { settingPatterns } from '../settings.mjs'; -import { isTauri } from '../tauri.mjs'; +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 { writeText } from '@tauri-apps/api/clipboard'; +import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio'; +import { createContext, useCallback, useEffect, useMemo, 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 { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; -import { getRandomTune, initCode, loadModules } from './util.mjs'; +import { Panel } from './panel/Panel'; +import { prebake } from './prebake.mjs'; +import { themes } from './themes.mjs'; +import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; const { latestCode } = settingsMap.get(); @@ -66,7 +49,6 @@ export const ReplContext = createContext(null); export function Repl({ embedded = false }) { const isEmbedded = embedded || window.location !== window.parent.location; const [view, setView] = useState(); // codemirror view - const [lastShared, setLastShared] = useState(); const [pending, setPending] = useState(true); const { theme, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 070e54e1..2ea527a2 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -4,37 +4,30 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { logger, getDrawContext, silence, code2hash } from '@strudel.cycles/core'; +import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core'; import { cx } from '@strudel.cycles/react'; -import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel.cycles/webaudio'; import { transpiler } from '@strudel.cycles/transpiler'; +import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; -import { createClient } from '@supabase/supabase-js'; /* import { writeText } from '@tauri-apps/api/clipboard'; import { nanoid } from 'nanoid'; */ -import { createContext, useState } from 'react'; +import { createContext, useCallback, useEffect, useRef, useState } from 'react'; import { - useSettings, - settingsMap, - setLatestCode, - updateUserCode, - setActivePattern, - getActivePattern, - getUserPattern, initUserCode, - settingPatterns, + setActivePattern, + setLatestCode, + settingsMap, + updateUserCode, + useSettings, } from '../settings.mjs'; -import { isTauri } from '../tauri.mjs'; -import { Panel } from './panel/Panel'; import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; -import { useCallback, useRef, useEffect } from 'react'; +import { Panel } from './panel/Panel'; // import { prebake } from '@strudel/repl'; -import { prebake /* , resetSounds */ } from './prebake.mjs'; -import * as tunes from './tunes.mjs'; import { useStore } from '@nanostores/react'; -import { getRandomTune, loadModules, initCode } from './util.mjs'; +import { prebake /* , resetSounds */ } from './prebake.mjs'; +import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; const { code: randomTune, name } = getRandomTune(); export const ReplContext = createContext(null); @@ -99,7 +92,6 @@ export function Repl2({ embedded = false }) { }); // init settings initCode().then((decoded) => { - console.log('init code'); let msg; if (decoded) { editor.setCode(decoded); @@ -182,35 +174,7 @@ export function Repl2({ embedded = false }) { editorRef.current.repl.evaluate(code); }; - /* const handleShare = async () => { - const codeToShare = activeCode || code; - if (lastShared === codeToShare) { - logger(`Link already generated!`, 'error'); - return; - } - // generate uuid in the browser - const hash = nanoid(12); - const shareUrl = window.location.origin + window.location.pathname + '?' + hash; - const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); - if (!error) { - setLastShared(activeCode || code); - // copy shareUrl to clipboard - if (isTauri()) { - await writeText(shareUrl); - } else { - await navigator.clipboard.writeText(shareUrl); - } - const message = `Link copied to clipboard: ${shareUrl}`; - alert(message); - // alert(message); - logger(message, 'highlight'); - } else { - console.log('error', error); - const message = `Error: ${error.message}`; - // alert(message); - logger(message); - } - }; */ + const handleShare = async () => shareCode(activeCode); const pending = false; //const error = undefined; // const { started, activeCode } = replState; @@ -223,12 +187,10 @@ export function Repl2({ embedded = false }) { isDirty, lastShared, activeCode, - // handleChangeCode: codemirror.handleChangeCode, handleTogglePlay, handleUpdate, handleShuffle, - /* handleShare, */ - handleShare: () => {}, + handleShare, }; return ( diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 0da28c25..ba459a1f 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -4,6 +4,8 @@ import { isTauri } from '../tauri.mjs'; import './Repl.css'; import * as tunes from './tunes.mjs'; import { createClient } from '@supabase/supabase-js'; +import { nanoid } from 'nanoid'; +import { writeText } from '@tauri-apps/api/clipboard'; // Create a single supabase client for interacting with your database const supabase = createClient( @@ -78,6 +80,7 @@ export function loadModules() { ); } +let lastShared; export async function shareCode(codeToShare) { // const codeToShare = activeCode || code; if (lastShared === codeToShare) { @@ -89,7 +92,7 @@ export async function shareCode(codeToShare) { const shareUrl = window.location.origin + window.location.pathname + '?' + hash; const { data, error } = await supabase.from('code').insert([{ code: codeToShare, hash }]); if (!error) { - setLastShared(activeCode || code); + lastShared = codeToShare; // copy shareUrl to clipboard if (isTauri()) { await writeText(shareUrl); From 4c337a5114dc4a108650c665c2a2d82291b836fb Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:06:59 +0100 Subject: [PATCH 07/16] fix: lint errors --- website/src/repl/util.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index ba459a1f..2b93b619 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -1,4 +1,4 @@ -import { controls, evalScope, hash2code } from '@strudel.cycles/core'; +import { controls, evalScope, hash2code, logger } from '@strudel.cycles/core'; import { settingPatterns } from '../settings.mjs'; import { isTauri } from '../tauri.mjs'; import './Repl.css'; @@ -30,7 +30,7 @@ export async function initCode() { .eq('hash', hash) .then(({ data, error }) => { if (error) { - console.warn('failed to load hash', err); + console.warn('failed to load hash', error); } if (data.length) { //console.log('load hash from database', hash); From 53484db76899e4cf291fbd038303488025a50c53 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 00:29:43 +0100 Subject: [PATCH 08/16] style fixes + remove lastShared state --- packages/codemirror/codemirror.mjs | 16 ++++++++++++++-- website/src/repl/Header.jsx | 3 +-- website/src/repl/Repl.jsx | 1 - website/src/repl/Repl2.jsx | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index a2c70599..e5692a55 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -108,7 +108,17 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo export class StrudelMirror { constructor(options) { - const { root, id, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options; + const { + root, + id, + initialCode = '', + onDraw, + drawTime = [-2, 2], + autodraw, + prebake, + bgFill = true, + ...replOptions + } = options; this.code = initialCode; this.root = root; this.miniLocations = []; @@ -183,7 +193,9 @@ export class StrudelMirror { const cmEditor = this.root.querySelector('.cm-editor'); if (cmEditor) { this.root.style.display = 'block'; - this.root.style.backgroundColor = 'var(--background)'; + if (bgFill) { + this.root.style.backgroundColor = 'var(--background)'; + } cmEditor.style.backgroundColor = 'transparent'; } const settings = codemirrorSettings.get(); diff --git a/website/src/repl/Header.jsx b/website/src/repl/Header.jsx index b6f13060..2592b917 100644 --- a/website/src/repl/Header.jsx +++ b/website/src/repl/Header.jsx @@ -18,7 +18,6 @@ export function Header({ context }) { started, pending, isDirty, - lastShared, activeCode, handleTogglePlay, handleUpdate, @@ -119,7 +118,7 @@ export function Header({ context }) { onClick={handleShare} > - share{lastShared && lastShared === (activeCode || code) ? 'd!' : ''} + share )} {!isEmbedded && ( diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 4cd1696a..eb2b495b 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -209,7 +209,6 @@ export function Repl({ embedded = false }) { started, pending, isDirty, - lastShared, activeCode, handleChangeCode, handleTogglePlay, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 2ea527a2..ccd46c94 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -28,6 +28,7 @@ import { Panel } from './panel/Panel'; 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); @@ -50,7 +51,6 @@ if (typeof window !== 'undefined') { export function Repl2({ embedded = false }) { //const isEmbedded = embedded || window.location !== window.parent.location; const isEmbedded = false; - const [lastShared, setLastShared] = useState(); const { panelPosition, isZen } = useSettings(); /* const replState = useStore($replstate); const isDirty = useStore($repldirty); */ @@ -89,6 +89,7 @@ export function Repl2({ embedded = false }) { setLatestCode(code); window.location.hash = '#' + code2hash(code); }, + bgFill: false, }); // init settings initCode().then((decoded) => { @@ -185,7 +186,6 @@ export function Repl2({ embedded = false }) { started, pending, isDirty, - lastShared, activeCode, handleTogglePlay, handleUpdate, From 1b55a5e3e00674c52f61a88cd9ff51e0e7b739ae Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 17:55:08 +0100 Subject: [PATCH 09/16] add trailing slash --- website/src/pages/technical-manual/docs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/src/pages/technical-manual/docs.mdx b/website/src/pages/technical-manual/docs.mdx index aa18eab4..386de6ad 100644 --- a/website/src/pages/technical-manual/docs.mdx +++ b/website/src/pages/technical-manual/docs.mdx @@ -9,7 +9,7 @@ The docs page is built ontop of astro's [docs site](https://github.com/withastro ## Adding a new Docs Page -1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.cc/learn/code (or locally under `http://localhost:4321/learn/code`) +1. add a `.mdx` file in a path under `website/src/pages/`, e.g. [website/src/pages/learn/code.mdx](https://raw.githubusercontent.com/tidalcycles/strudel/main/website/src/pages/learn/code.mdx) will be available under https://strudel.cc/learn/code/ (or locally under `http://localhost:4321/learn/code/`) 2. make sure to copy the top part of another existing docs page. Adjust the title accordingly 3. To add a link to the sidebar, add a new entry to `SIDEBAR` to [`config.ts`](https://github.com/tidalcycles/strudel/blob/main/website/src/config.ts) From 47aa4cf1982596ce407688fdc83349a091307b7e Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 17:56:07 +0100 Subject: [PATCH 10/16] autocomplete without react --- .../{Autocomplete.jsx => autocomplete.mjs} | 63 +++++++++---------- packages/codemirror/codemirror.mjs | 4 +- packages/codemirror/html.mjs | 17 +++++ 3 files changed, 48 insertions(+), 36 deletions(-) rename packages/codemirror/{Autocomplete.jsx => autocomplete.mjs} (61%) create mode 100644 packages/codemirror/html.mjs diff --git a/packages/codemirror/Autocomplete.jsx b/packages/codemirror/autocomplete.mjs similarity index 61% rename from packages/codemirror/Autocomplete.jsx rename to packages/codemirror/autocomplete.mjs index 18f172ee..05473b5e 100644 --- a/packages/codemirror/Autocomplete.jsx +++ b/packages/codemirror/autocomplete.mjs @@ -1,7 +1,7 @@ -import { createRoot } from 'react-dom/client'; import jsdoc from '../../doc.json'; // import { javascriptLanguage } from '@codemirror/lang-javascript'; import { autocompletion } from '@codemirror/autocomplete'; +import { h } from './html'; const getDocLabel = (doc) => doc.name || doc.longname; const getInnerText = (html) => { @@ -10,36 +10,32 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -export function Autocomplete({ doc }) { - return ( -
-

{getDocLabel(doc)}

-
-
    - {doc.params?.map(({ name, type, description }, i) => ( -
  • - {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)} : ''} -
  • - ))} -
-
- {doc.examples?.map((example, i) => ( -
-
 {
-                console.log('ola!');
-                navigator.clipboard.writeText(example);
-                e.stopPropagation();
-              }}
-            >
-              {example}
-            
-
- ))} -
-
- ); +function Autocomplete({ doc }) { + return h`
+

${getDocLabel(doc)}

+${doc.description} +
    + ${doc.params?.map( + ({ name, type, description }) => + `
  • ${name} : ${type.names?.join(' | ')} ${description ? ` - ${getInnerText(description)}` : ''}
  • `, + )} +
+
+ ${doc.examples?.map((example) => `
${example}
`)} +
+
`[0]; + /* +
 {
+  console.log('ola!');
+  navigator.clipboard.writeText(example);
+  e.stopPropagation();
+}}
+>
+{example}
+
+*/ } const jsdocCompletions = jsdoc.docs @@ -56,9 +52,8 @@ const jsdocCompletions = jsdoc.docs // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. info: () => { const node = document.createElement('div'); - // if Autocomplete is non-interactive, it could also be rendered at build time.. - // .. using renderToStaticMarkup - createRoot(node).render(); + const ac = Autocomplete({ doc }); + node.appendChild(ac); return node; }, type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index e5692a55..234e8e1a 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -6,7 +6,7 @@ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language' import { Compartment, EditorState, Prec } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; -// import { isAutoCompletionEnabled } from './Autocomplete'; +import { isAutoCompletionEnabled } from './autocomplete.mjs'; import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; @@ -18,7 +18,7 @@ const extensions = { isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []), isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), theme, - // isAutoCompletionEnabled, + isAutoCompletionEnabled, isPatternHighlightingEnabled, isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, diff --git a/packages/codemirror/html.mjs b/packages/codemirror/html.mjs new file mode 100644 index 00000000..4b4d82e4 --- /dev/null +++ b/packages/codemirror/html.mjs @@ -0,0 +1,17 @@ +const parser = new DOMParser(); +export let html = (string) => { + return parser.parseFromString(string, 'text/html').querySelectorAll('*'); +}; +let parseChunk = (chunk) => { + if (Array.isArray(chunk)) return chunk.flat().join(''); + if (chunk === undefined) return ''; + return chunk; +}; +export let h = (strings, ...vars) => { + let string = ''; + for (let i in strings) { + string += parseChunk(strings[i]); + string += parseChunk(vars[i]); + } + return html(string); +}; From f524dde5aec807f6d8e475182ac6d1e6b5758ee1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 18:03:58 +0100 Subject: [PATCH 11/16] tooltip without react + simplify autocomplete --- packages/codemirror/autocomplete.mjs | 11 ++-- packages/codemirror/codemirror.mjs | 2 + packages/codemirror/tooltip.mjs | 76 ++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 packages/codemirror/tooltip.mjs diff --git a/packages/codemirror/autocomplete.mjs b/packages/codemirror/autocomplete.mjs index 05473b5e..c065c365 100644 --- a/packages/codemirror/autocomplete.mjs +++ b/packages/codemirror/autocomplete.mjs @@ -10,9 +10,9 @@ const getInnerText = (html) => { return div.textContent || div.innerText || ''; }; -function Autocomplete({ doc }) { +export function Autocomplete({ doc, label }) { return h`
-

${getDocLabel(doc)}

+

${label || getDocLabel(doc)}

${doc.description}
    ${doc.params?.map( @@ -50,12 +50,7 @@ const jsdocCompletions = jsdoc.docs .map((doc) /*: Completion */ => ({ label: getDocLabel(doc), // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label. - info: () => { - const node = document.createElement('div'); - const ac = Autocomplete({ doc }); - node.appendChild(ac); - return node; - }, + info: () => Autocomplete({ doc }), type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type })); diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 234e8e1a..6713ac6f 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -7,6 +7,7 @@ import { Compartment, EditorState, Prec } from '@codemirror/state'; import { EditorView, highlightActiveLineGutter, highlightActiveLine, keymap, lineNumbers } from '@codemirror/view'; import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core'; import { isAutoCompletionEnabled } from './autocomplete.mjs'; +import { isTooltipEnabled } from './tooltip.mjs'; import { flash, isFlashEnabled } from './flash.mjs'; import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs'; import { keybindings } from './keybindings.mjs'; @@ -19,6 +20,7 @@ const extensions = { isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []), theme, isAutoCompletionEnabled, + isTooltipEnabled, isPatternHighlightingEnabled, isActiveLineHighlighted: (on) => (on ? [highlightActiveLine(), highlightActiveLineGutter()] : []), isFlashEnabled, diff --git a/packages/codemirror/tooltip.mjs b/packages/codemirror/tooltip.mjs new file mode 100644 index 00000000..7b7a36db --- /dev/null +++ b/packages/codemirror/tooltip.mjs @@ -0,0 +1,76 @@ +import { hoverTooltip } from '@codemirror/view'; +import jsdoc from '../../doc.json'; +import { Autocomplete } from './autocomplete.mjs'; + +const getDocLabel = (doc) => doc.name || doc.longname; + +let ctrlDown = false; + +// Record Control key event to trigger or block the tooltip depending on the state +window.addEventListener( + 'keyup', + function (e) { + if (e.key == 'Control') { + ctrlDown = false; + } + }, + true, +); + +window.addEventListener( + 'keydown', + function (e) { + if (e.key == 'Control') { + ctrlDown = true; + } + }, + true, +); + +export const strudelTooltip = hoverTooltip( + (view, pos, side) => { + // Word selection from CodeMirror Hover Tooltip example https://codemirror.net/examples/tooltip/#hover-tooltips + if (!ctrlDown) { + return null; + } + let { from, to, text } = view.state.doc.lineAt(pos); + let start = pos, + end = pos; + while (start > from && /\w/.test(text[start - from - 1])) { + start--; + } + while (end < to && /\w/.test(text[end - from])) { + end++; + } + if ((start == pos && side < 0) || (end == pos && side > 0)) { + return null; + } + let word = text.slice(start - from, end - from); + // Get entry from Strudel documentation + let entry = jsdoc.docs.filter((doc) => getDocLabel(doc) === word)[0]; + if (!entry) { + // Try for synonyms + entry = jsdoc.docs.filter((doc) => doc.synonyms && doc.synonyms.includes(word))[0]; + if (!entry) { + return null; + } + } + + return { + pos: start, + end, + above: false, + arrow: true, + create(view) { + let dom = document.createElement('div'); + dom.className = 'strudel-tooltip'; + const ac = Autocomplete({ doc: entry, label: word }); + dom.appendChild(ac); + return { dom }; + }, + }; + }, + { hoverTime: 10 }, +); + +export const isTooltipEnabled = (on) => (on ? strudelTooltip : []); From f44ceb46cd726ed93bd444434d1449bd702f4e40 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 26 Dec 2023 18:26:51 +0100 Subject: [PATCH 12/16] remove react-dom dependency --- packages/codemirror/package.json | 1 - pnpm-lock.yaml | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json index 0c57db8b..8810a607 100644 --- a/packages/codemirror/package.json +++ b/packages/codemirror/package.json @@ -47,7 +47,6 @@ "@strudel.cycles/core": "workspace:*", "@uiw/codemirror-themes": "^4.19.16", "@uiw/codemirror-themes-all": "^4.19.16", - "react-dom": "^18.2.0", "nanostores": "^0.8.1", "@nanostores/persistent": "^0.8.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fecabff..5892af73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,9 +123,6 @@ importers: nanostores: specifier: ^0.8.1 version: 0.8.1 - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) devDependencies: vite: specifier: ^4.3.3 From d7c2cf562ffdc9eecde8ead2111aa3c143b0fc65 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 11:40:07 +0100 Subject: [PATCH 13/16] fix: hot reloading --- website/src/repl/Repl2.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index ccd46c94..4a899726 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -128,6 +128,7 @@ export function Repl2({ embedded = false }) { } return () => { editorRef.current?.clear(); + delete editorRef.current; }; }, []); From e2a082f748de41cc493de401432ccd0ecf236467 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 11:42:18 +0100 Subject: [PATCH 14/16] move repl2 to /next --- .../{vanilla/2.astro => next/index.astro} | 0 website/src/pages/vanilla/index.astro | 92 -------------- website/src/repl/vanilla/vanilla.css | 34 ----- website/src/repl/vanilla/vanilla.mjs | 117 ------------------ 4 files changed, 243 deletions(-) rename website/src/pages/{vanilla/2.astro => next/index.astro} (100%) delete mode 100644 website/src/pages/vanilla/index.astro delete mode 100644 website/src/repl/vanilla/vanilla.css delete mode 100644 website/src/repl/vanilla/vanilla.mjs diff --git a/website/src/pages/vanilla/2.astro b/website/src/pages/next/index.astro similarity index 100% rename from website/src/pages/vanilla/2.astro rename to website/src/pages/next/index.astro diff --git a/website/src/pages/vanilla/index.astro b/website/src/pages/vanilla/index.astro deleted file mode 100644 index d4ea40b8..00000000 --- a/website/src/pages/vanilla/index.astro +++ /dev/null @@ -1,92 +0,0 @@ - - - Strudel Vanilla REPL - - -
    -
    -
    -
    - -
    - -
    - -
    - -
    - -
    - -
    - - - -
    -
    - - - - - diff --git a/website/src/repl/vanilla/vanilla.css b/website/src/repl/vanilla/vanilla.css deleted file mode 100644 index 5387fb04..00000000 --- a/website/src/repl/vanilla/vanilla.css +++ /dev/null @@ -1,34 +0,0 @@ -body, -input { - font-family: monospace; - background-color: black; - color: white; -} - -input, -select { - background-color: black !important; -} - -html, -body, -#editor, -.cm-editor, -.cm-scroller { - padding: 0; - margin: 0; - height: 100%; -} - -.settings { - position: fixed; - right: 0; - top: 0; - z-index: 1000; - display: flex-col; - padding: 10px; -} - -.settings > form > * + * { - margin-top: 10px; -} diff --git a/website/src/repl/vanilla/vanilla.mjs b/website/src/repl/vanilla/vanilla.mjs deleted file mode 100644 index 6f0cc058..00000000 --- a/website/src/repl/vanilla/vanilla.mjs +++ /dev/null @@ -1,117 +0,0 @@ -import { hash2code, logger } from '@strudel.cycles/core'; -import { codemirrorSettings, defaultSettings } from '@strudel/codemirror'; -import './vanilla.css'; - -let editor; - -async function run() { - const repl = document.getElementById('editor'); - editor = repl.editor; - logger(`Welcome to Strudel! Click into the editor and then hit ctrl+enter to run the code!`, 'highlight'); - const codeParam = window.location.href.split('#')[1] || ''; - - const initialCode = codeParam - ? hash2code(codeParam) - : `// @date 23-11-30 -// "teigrührgerät" @by froos - -stack( - stack( - s("bd(<3!3 5>,6)/2").bank('RolandTR707') - , - s("~ sd:<0 1>").bank('RolandTR707').room("<0 .5>") - .lastOf(8, x=>x.segment("12").end(.2).gain(isaw)) - , - s("[tb ~ tb]").bank('RolandTR707') - .clip(0).release(.08).room(.2) - ).off(-1/6, x=>x.speed(.7).gain(.2).degrade()) - , - stack( - note(",6) ~!2 [f1?]*2>") - .s("sawtooth").lpf(perlin.range(400,1000)) - .lpa(.1).lpenv(-3).room(.2) - .lpq(8).noise(.2) - .add(note("0,.1")) - , - chord("<~ Gm9 ~!2>") - .dict('ireal').voicing() - .s("sawtooth").vib("2:.1") - .lpf(1000).lpa(.1).lpenv(-4) - .room(.5) - , - n(run(3)).chord("/8") - .dict('ireal-ext') - .off(1/2, add(n(4))) - .voicing() - .clip(.1).release(.05) - .s("sine").jux(rev) - .sometimesBy(sine.slow(16), add(note(12))) - .room(.75) - .lpf(sine.range(200,2000).slow(16)) - .gain(saw.slow(4).div(2)) - ).add(note(perlin.range(0,.5))) -)`; - - editor.setCode(initialCode); // simpler alternative to above init - - // settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key])); - onEvent('strudel-toggle-play', () => editor.toggle()); -} - -run(); - -function onEvent(key, callback) { - const listener = (e) => { - if (e.data === key) { - callback(); - } - }; - window.addEventListener('message', listener); - return () => window.removeEventListener('message', listener); -} - -// settings form -function getInput(form, name) { - return form.querySelector(`input[name=${name}]`) || form.querySelector(`select[name=${name}]`); -} -function getFormValues(form, initial) { - const entries = Object.entries(initial).map(([key, initialValue]) => { - const input = getInput(form, key); - if (!input) { - return [key, initialValue]; // fallback - } - if (input.type === 'checkbox') { - return [key, input.checked]; - } - if (input.type === 'number') { - return [key, Number(input.value)]; - } - if (input.tagName === 'SELECT') { - return [key, input.value]; - } - return [key, input.value]; - }); - return Object.fromEntries(entries); -} -function setFormValues(form, values) { - Object.entries(values).forEach(([key, value]) => { - const input = getInput(form, key); - if (!input) { - return; - } - if (input.type === 'checkbox') { - input.checked = !!value; - } else if (input.type === 'number') { - input.value = value; - } else if (input.tagName) { - input.value = value; - } - }); -} - -const form = document.querySelector('form[name=settings]'); -setFormValues(form, codemirrorSettings.get()); -form.addEventListener('change', () => { - const values = getFormValues(form, defaultSettings); - editor?.updateSettings(values); -}); From 8bde87246bc8bdb8e8b865b88f3bda3b4f4a13e1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 12:45:27 +0100 Subject: [PATCH 15/16] load imported samples directly in prebake --- website/src/repl/Repl.jsx | 2 -- website/src/repl/idbutils.mjs | 2 +- website/src/repl/prebake.mjs | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index eb2b495b..65cc3473 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -21,7 +21,6 @@ import { import { Header } from './Header'; import Loader from './Loader'; import './Repl.css'; -import { registerSamplesFromDB, userSamplesDBConfig } from './idbutils.mjs'; import { Panel } from './panel/Panel'; import { prebake } from './prebake.mjs'; import { themes } from './themes.mjs'; @@ -117,7 +116,6 @@ export function Repl({ embedded = false }) { msg = `A random code snippet named "${name}" has been loaded!`; } //registers samples that have been saved to the index DB - registerSamplesFromDB(userSamplesDBConfig); logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); setPending(false); }); diff --git a/website/src/repl/idbutils.mjs b/website/src/repl/idbutils.mjs index 414006b9..18e15d20 100644 --- a/website/src/repl/idbutils.mjs +++ b/website/src/repl/idbutils.mjs @@ -24,7 +24,7 @@ const clearIDB = () => { }; // queries the DB, and registers the sounds so they can be played -export const registerSamplesFromDB = (config, onComplete = () => {}) => { +export const registerSamplesFromDB = (config = userSamplesDBConfig, onComplete = () => {}) => { openDB(config, (objectStore) => { let query = objectStore.getAll(); query.onsuccess = (event) => { diff --git a/website/src/repl/prebake.mjs b/website/src/repl/prebake.mjs index 68f6f8a1..96a484e7 100644 --- a/website/src/repl/prebake.mjs +++ b/website/src/repl/prebake.mjs @@ -1,5 +1,6 @@ import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core'; import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio'; +import { registerSamplesFromDB } from './idbutils.mjs'; import './piano.mjs'; import './files.mjs'; @@ -12,6 +13,7 @@ export async function prebake() { await Promise.all([ registerSynthSounds(), registerZZFXSounds(), + registerSamplesFromDB(), //registerSoundfonts(), // need dynamic import here, because importing @strudel.cycles/soundfonts fails on server: // => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically From 39e81aa77cd5858a927c7a773c92434b7777acbc Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Wed, 27 Dec 2023 12:46:10 +0100 Subject: [PATCH 16/16] reduce drawTime to fix perf (for now) --- packages/codemirror/codemirror.mjs | 2 +- website/src/repl/Repl2.jsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6713ac6f..29dca867 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -115,7 +115,7 @@ export class StrudelMirror { id, initialCode = '', onDraw, - drawTime = [-2, 2], + drawTime = [0, 0], autodraw, prebake, bgFill = true, diff --git a/website/src/repl/Repl2.jsx b/website/src/repl/Repl2.jsx index 4a899726..641d2a91 100644 --- a/website/src/repl/Repl2.jsx +++ b/website/src/repl/Repl2.jsx @@ -57,7 +57,10 @@ export function Repl2({ embedded = false }) { const shouldDraw = true; const init = useCallback(({ shouldDraw }) => { - const drawTime = [0, 4]; + // TODO: find way to make spiral & punchcard work (if there's any) + // upping the 2nd value leads to slow eval times + // because Drawer.invalidate might query alot at one time + const drawTime = [0, 0]; const drawContext = shouldDraw ? getDrawContext() : null; let onDraw; if (shouldDraw) { @@ -105,8 +108,6 @@ export function Repl2({ embedded = false }) { editor.setCode(randomTune); msg = `A random code snippet named "${name}" has been loaded!`; } - //registers samples that have been saved to the index DB - // registerSamplesFromDB(userSamplesDBConfig); logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight'); // setPending(false); });