From 8c0065f563f8798ca20c12f0157f1fb0572e9199 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Mon, 25 Dec 2023 23:53:54 +0100 Subject: [PATCH] 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); + } +}