mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-01 13:29:25 -04:00
- encapsulate repl state from react
- might still be buggy! - smarter way to flush sounds
This commit is contained in:
@@ -28,7 +28,7 @@ export function MiniRepl({
|
||||
useEffect(() => {
|
||||
// we have to load this package on the client
|
||||
// because codemirror throws an error on the server
|
||||
Promise.all([import('@strudel.cycles/react'), init])
|
||||
Promise.all([import('@strudel.cycles/react')])
|
||||
.then(([res]) => setRepl(() => res.MiniRepl))
|
||||
.catch((err) => console.error(err));
|
||||
}, []);
|
||||
|
||||
+41
-126
@@ -4,25 +4,25 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cleanupDraw, cleanupUi, getDrawContext, logger } from '@strudel.cycles/core';
|
||||
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
|
||||
import { getAudioContext, resetLoadedSounds, webaudioOutput, panic } from '@strudel.cycles/webaudio';
|
||||
import PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||
import { getDrawContext, logger } from '@strudel.cycles/core';
|
||||
import { CodeMirror, cx } from '@strudel.cycles/react';
|
||||
import { getAudioContext, resetLoadedSounds } from '@strudel.cycles/webaudio';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { writeText } from '@tauri-apps/api/clipboard';
|
||||
import { nanoid } from 'nanoid';
|
||||
import React, { createContext, useCallback, useEffect, useState, useMemo } from 'react';
|
||||
import './Repl.css';
|
||||
import { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { settingsMap, useSettings } from '../settings.mjs';
|
||||
import { isTauri } from '../tauri.mjs';
|
||||
import { Footer } from './Footer';
|
||||
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 } from '../settings.mjs';
|
||||
import Loader from './Loader';
|
||||
import { code2hash, hash2code } from './helpers.mjs';
|
||||
import { isTauri } from '../tauri.mjs';
|
||||
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
|
||||
import { writeText } from '@tauri-apps/api/clipboard';
|
||||
import './Repl.css';
|
||||
import { hash2code, code2hash } from './helpers.mjs';
|
||||
import * as tunes from './tunes.mjs';
|
||||
import { useRepl } from './useRepl';
|
||||
import { setLatestCode } from '../settings.mjs';
|
||||
import { resetSounds } from './prebake.mjs';
|
||||
|
||||
const { latestCode } = settingsMap.get();
|
||||
|
||||
@@ -32,16 +32,12 @@ const supabase = createClient(
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
|
||||
);
|
||||
|
||||
const init = prebake();
|
||||
|
||||
let drawContext, clearCanvas;
|
||||
let clearCanvas;
|
||||
if (typeof window !== 'undefined') {
|
||||
drawContext = getDrawContext();
|
||||
const drawContext = getDrawContext();
|
||||
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
|
||||
}
|
||||
|
||||
const getTime = () => getAudioContext().currentTime;
|
||||
|
||||
async function initCode() {
|
||||
// load code from url hash (either short hash from database or decode long hash)
|
||||
try {
|
||||
@@ -85,56 +81,30 @@ 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,
|
||||
keybindings,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
isLineNumbersDisplayed,
|
||||
isAutoCompletionEnabled,
|
||||
isLineWrappingEnabled,
|
||||
panelPosition,
|
||||
isZen,
|
||||
} = useSettings();
|
||||
const { panelPosition, isZen } = useSettings();
|
||||
|
||||
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
|
||||
const { setWidgets } = useWidgets(view);
|
||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
||||
useStrudel({
|
||||
initialCode: '// LOADING...',
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime,
|
||||
beforeEval: async () => {
|
||||
setPending(true);
|
||||
await init;
|
||||
cleanupUi();
|
||||
cleanupDraw();
|
||||
},
|
||||
afterEval: ({ code, meta }) => {
|
||||
setMiniLocations(meta.miniLocations);
|
||||
setWidgets(meta.widgets);
|
||||
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,
|
||||
});
|
||||
const {
|
||||
codemirror,
|
||||
code,
|
||||
setCode,
|
||||
scheduler,
|
||||
evaluate,
|
||||
activateCode,
|
||||
isDirty,
|
||||
activeCode,
|
||||
pattern,
|
||||
started,
|
||||
stop,
|
||||
error,
|
||||
pending,
|
||||
setPending,
|
||||
} = useRepl({
|
||||
afterEval: ({ code }) => {
|
||||
setLatestCode(code);
|
||||
window.location.hash = '#' + code2hash(code);
|
||||
},
|
||||
});
|
||||
|
||||
// init code
|
||||
useEffect(() => {
|
||||
@@ -155,30 +125,10 @@ export function Repl({ embedded = false }) {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// highlighting
|
||||
const { setMiniLocations } = useHighlighting({
|
||||
view,
|
||||
pattern,
|
||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.now(),
|
||||
});
|
||||
|
||||
//
|
||||
// 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) {
|
||||
@@ -198,9 +148,8 @@ export function Repl({ embedded = false }) {
|
||||
const { code, name } = getRandomTune();
|
||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
await resetSounds();
|
||||
scheduler.setCps(1);
|
||||
await prebake(); // declare default samples
|
||||
await evaluate(code, false);
|
||||
};
|
||||
|
||||
@@ -241,16 +190,12 @@ export function Repl({ embedded = false }) {
|
||||
isDirty,
|
||||
lastShared,
|
||||
activeCode,
|
||||
handleChangeCode,
|
||||
handleChangeCode: codemirror.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
|
||||
@@ -275,37 +220,7 @@ export function Repl({ embedded = false }) {
|
||||
)}
|
||||
<div className="grow flex relative overflow-hidden">
|
||||
<section className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')} id="code">
|
||||
<CodeMirror
|
||||
theme={currentTheme}
|
||||
value={code}
|
||||
keybindings={keybindings}
|
||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||
isAutoCompletionEnabled={isAutoCompletionEnabled}
|
||||
isLineWrappingEnabled={isLineWrappingEnabled}
|
||||
fontSize={fontSize}
|
||||
fontFamily={fontFamily}
|
||||
onChange={handleChangeCode}
|
||||
onViewChanged={handleViewChanged}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onEvaluate={() => {
|
||||
if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
}
|
||||
flash(view);
|
||||
activateCode();
|
||||
}}
|
||||
onReEvaluate={() => {
|
||||
stop();
|
||||
panic();
|
||||
activateCode();
|
||||
}}
|
||||
onPanic={() => {
|
||||
stop();
|
||||
panic();
|
||||
}}
|
||||
onStop={() => stop()}
|
||||
/>
|
||||
<CodeMirror {...codemirror} />
|
||||
</section>
|
||||
{panelPosition === 'right' && !isEmbedded && <Footer context={context} />}
|
||||
</div>
|
||||
|
||||
@@ -4,41 +4,10 @@ import './piano.mjs';
|
||||
import './files.mjs';
|
||||
import { isTauri } from '../tauri.mjs';
|
||||
import { settingPatterns } from '../settings.mjs';
|
||||
import { soundMap } from '@strudel.cycles/webaudio';
|
||||
|
||||
export async function prebake() {
|
||||
const initAudio = initAudioOnFirstClick();
|
||||
// lazy load modules
|
||||
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')]);
|
||||
}
|
||||
// register modules in global scope
|
||||
const modulesLoading = evalScope(
|
||||
controls, // sadly, this cannot be exported from core direclty
|
||||
settingPatterns,
|
||||
...modules,
|
||||
);
|
||||
// register sounds and samples
|
||||
export function registerStockSounds() {
|
||||
return Promise.all([
|
||||
initAudio,
|
||||
modulesLoading,
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
//registerSoundfonts(),
|
||||
@@ -145,11 +114,51 @@ export async function prebake() {
|
||||
],
|
||||
},
|
||||
'github:tidalcycles/Dirt-Samples/master/',
|
||||
{ prebake: true },
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function prebake() {
|
||||
const initAudio = initAudioOnFirstClick();
|
||||
// lazy load modules
|
||||
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')]);
|
||||
}
|
||||
// register modules in global scope
|
||||
const modulesLoading = evalScope(
|
||||
controls, // sadly, this cannot be exported from core direclty
|
||||
settingPatterns,
|
||||
...modules,
|
||||
);
|
||||
// register sounds and samples
|
||||
return Promise.all([initAudio, modulesLoading, registerStockSounds()]);
|
||||
// await samples('github:tidalcycles/Dirt-Samples/master');
|
||||
}
|
||||
|
||||
export const resetSounds = () => {
|
||||
soundMap.set({});
|
||||
registerStockSounds();
|
||||
};
|
||||
|
||||
const maxPan = noteToMidi('C8');
|
||||
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { webrepl, $replstate, setReplState } from './webrepl.mjs';
|
||||
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useHighlighting, flash, usePatternFrame } from '@strudel.cycles/react';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
import { themes } from './themes.mjs';
|
||||
import { updateWidgets } from '@strudel/codemirror';
|
||||
import { getDrawContext } from '@strudel.cycles/core';
|
||||
|
||||
export function useRepl({
|
||||
drawContext = getDrawContext(),
|
||||
editPattern,
|
||||
evalOnMount = false,
|
||||
canvasId,
|
||||
drawTime = [-2, 2],
|
||||
afterEval,
|
||||
} = {}) {
|
||||
const [view, setView] = useState(); // codemirror view
|
||||
const handleViewChanged = useCallback((v) => setView(v), []);
|
||||
const setCode = (code) => setReplState('code', code);
|
||||
const setPending = (pending) => setReplState('pending', pending);
|
||||
const handleChangeCode = useCallback((c) => setReplState('code', c), []); // [started] ?
|
||||
// const handleSelectionChange = useCallback((selection) => {}, []);
|
||||
// onSelectionChange={handleSelectionChange}
|
||||
|
||||
const repl = webrepl({ editPattern, afterEval });
|
||||
const { evaluate, stop, scheduler } = repl;
|
||||
const { schedulerError, evalError, code, started, pattern, miniLocations, activeCode, widgets, pending } =
|
||||
useStore($replstate);
|
||||
const error = schedulerError || evalError;
|
||||
const settings = useSettings();
|
||||
const {
|
||||
theme,
|
||||
keybindings,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
isLineNumbersDisplayed,
|
||||
isAutoCompletionEnabled,
|
||||
isLineWrappingEnabled,
|
||||
panelPosition,
|
||||
isZen,
|
||||
} = settings;
|
||||
|
||||
// very ugly draw logic
|
||||
const shouldPaint = useCallback((pat) => !!pat?.context?.onPaint, []);
|
||||
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
|
||||
const id = useMemo(() => s4(), []);
|
||||
canvasId = canvasId || `canvas-${id}`; // draw logic
|
||||
const onDraw = useCallback(
|
||||
(pattern, time, haps, drawTime) => {
|
||||
const { onPaint } = pattern.context || {};
|
||||
const ctx = typeof drawContext === 'function' ? drawContext(canvasId) : drawContext;
|
||||
onPaint?.(ctx, time, haps, drawTime, paintOptions);
|
||||
},
|
||||
[drawContext, canvasId, paintOptions],
|
||||
);
|
||||
const drawFirstFrame = useCallback(
|
||||
(pat) => {
|
||||
if (shouldPaint(pat)) {
|
||||
const [_, lookahead] = drawTime;
|
||||
const haps = pat.queryArc(0, lookahead);
|
||||
// draw at -0.001 to avoid activating haps at 0
|
||||
onDraw(pat, -0.001, haps, drawTime);
|
||||
}
|
||||
},
|
||||
[drawTime, onDraw, shouldPaint],
|
||||
);
|
||||
const inited = useRef();
|
||||
useEffect(() => {
|
||||
if (!inited.current && evalOnMount && code) {
|
||||
inited.current = true;
|
||||
evaluate(code, false).then((pat) => drawFirstFrame(pat));
|
||||
}
|
||||
}, [evalOnMount, code, evaluate, drawFirstFrame]);
|
||||
usePatternFrame({
|
||||
pattern,
|
||||
started: shouldPaint(pattern) && started,
|
||||
getTime: () => scheduler.now(),
|
||||
drawTime,
|
||||
onDraw,
|
||||
});
|
||||
//
|
||||
|
||||
const currentTheme = useMemo(() => themes[theme] || themes.strudelTheme, [theme]);
|
||||
const isDirty = code !== activeCode;
|
||||
// highlighting => should probably also just live outside of react...
|
||||
const { setMiniLocations } = useHighlighting({
|
||||
view,
|
||||
pattern,
|
||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
||||
getTime: () => scheduler.now(),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (view) {
|
||||
updateWidgets(view, widgets);
|
||||
}
|
||||
}, [view, widgets]);
|
||||
|
||||
useEffect(() => {
|
||||
setMiniLocations(miniLocations);
|
||||
}, [miniLocations]);
|
||||
|
||||
const activateCode = useCallback(
|
||||
async (autostart = true) => {
|
||||
const res = await evaluate(code, autostart);
|
||||
// broadcast({ type: 'start', from: id });
|
||||
return res;
|
||||
},
|
||||
[evaluate, code],
|
||||
);
|
||||
|
||||
// Codemirror hooks
|
||||
const onEvaluate = () => {
|
||||
if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
}
|
||||
flash(view);
|
||||
activateCode();
|
||||
};
|
||||
const onReEvaluate = () => {
|
||||
stop();
|
||||
panic();
|
||||
activateCode();
|
||||
};
|
||||
const onPanic = () => {
|
||||
stop();
|
||||
panic();
|
||||
};
|
||||
const onStop = () => stop();
|
||||
|
||||
const codemirror = {
|
||||
theme: currentTheme,
|
||||
value: code,
|
||||
keybindings: keybindings,
|
||||
isLineNumbersDisplayed: isLineNumbersDisplayed,
|
||||
isAutoCompletionEnabled: isAutoCompletionEnabled,
|
||||
isLineWrappingEnabled: isLineWrappingEnabled,
|
||||
fontSize: fontSize,
|
||||
fontFamily: fontFamily,
|
||||
onChange: handleChangeCode,
|
||||
onViewChanged: handleViewChanged,
|
||||
onEvaluate: onEvaluate,
|
||||
onReEvaluate: onReEvaluate,
|
||||
onPanic: onPanic,
|
||||
onStop: onStop,
|
||||
};
|
||||
|
||||
return {
|
||||
code,
|
||||
setCode,
|
||||
scheduler,
|
||||
evaluate,
|
||||
activateCode,
|
||||
isDirty,
|
||||
activeCode,
|
||||
pattern,
|
||||
started,
|
||||
stop,
|
||||
error,
|
||||
widgets,
|
||||
pending,
|
||||
setPending,
|
||||
codemirror,
|
||||
};
|
||||
}
|
||||
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { cleanupDraw, cleanupUi, repl, getDrawContext, silence } from '@strudel.cycles/core';
|
||||
import { webaudioOutput, getAudioContext } from '@strudel.cycles/webaudio';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { prebake } from './prebake.mjs';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
export const $replstate = atom({
|
||||
schedulerError: undefined,
|
||||
evalError: undefined,
|
||||
code: '// LOADING',
|
||||
activeCode: '// LOADING',
|
||||
pattern: silence,
|
||||
miniLocations: [],
|
||||
widgets: [],
|
||||
pending: true,
|
||||
});
|
||||
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
|
||||
|
||||
let instance;
|
||||
export function webrepl({ beforeEval, onEvalError, afterEval, editPattern } = {}) {
|
||||
if (instance) {
|
||||
// TODO: find way to attack hooks (beforeEval, ...) to existing instance
|
||||
// maybe use subscriber pattern
|
||||
return instance;
|
||||
}
|
||||
if (typeof window === 'undefined') {
|
||||
throw new Error('webrepl can only be created in a browser!');
|
||||
}
|
||||
const init = prebake(); // load modules + samples
|
||||
|
||||
instance = repl({
|
||||
// interval,
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
onSchedulerError: (err) => setReplState('schedulerError', err),
|
||||
onEvalError: (err) => {
|
||||
setReplState('evalError', err);
|
||||
setReplState('pending', false);
|
||||
onEvalError?.(err);
|
||||
},
|
||||
drawContext: getDrawContext(),
|
||||
transpiler,
|
||||
editPattern,
|
||||
beforeEval: async ({ code }) => {
|
||||
setReplState('code', code);
|
||||
setReplState('pending', true);
|
||||
await init;
|
||||
await beforeEval?.();
|
||||
cleanupUi();
|
||||
cleanupDraw();
|
||||
},
|
||||
afterEval: async (res) => {
|
||||
const { pattern: _pattern, code, meta } = res;
|
||||
setReplState('miniLocations', meta?.miniLocations || []);
|
||||
setReplState('widgets', meta?.widgets || []);
|
||||
|
||||
setReplState('activeCode', code);
|
||||
setReplState('pattern', _pattern);
|
||||
setReplState('evalError', undefined);
|
||||
setReplState('schedulerError', undefined);
|
||||
setReplState('pending', false);
|
||||
await afterEval?.(res);
|
||||
},
|
||||
onToggle: (started) => {
|
||||
setReplState('started', started);
|
||||
// this was so far only used in main repl...
|
||||
// is this also relevant for mini repl?
|
||||
if (!started) {
|
||||
cleanupDraw(false);
|
||||
window.postMessage('strudel-stop');
|
||||
} else {
|
||||
window.postMessage('strudel-start');
|
||||
}
|
||||
},
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
Reference in New Issue
Block a user