replace main repl
This commit is contained in:
parent
2ed3a5c582
commit
201fe726b6
4 changed files with 113 additions and 397 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
import HeadCommon from '../components/HeadCommon.astro';
|
import HeadCommonNext from '../components/HeadCommonNext.astro';
|
||||||
import { Repl } from '../repl/Repl.jsx';
|
import { Repl } from '../repl/Repl';
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en" class="dark">
|
<html lang="en" class="dark">
|
||||||
<head>
|
<head>
|
||||||
<HeadCommon />
|
<HeadCommonNext />
|
||||||
<title>Strudel REPL</title>
|
<title>Strudel REPL</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-app-height bg-background">
|
<body class="h-app-height bg-background">
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
---
|
|
||||||
import HeadCommonNext from '../../components/HeadCommonNext.astro';
|
|
||||||
import { Repl2 } from '../../repl/Repl2';
|
|
||||||
---
|
|
||||||
|
|
||||||
<html lang="en" class="dark">
|
|
||||||
<head>
|
|
||||||
<HeadCommonNext />
|
|
||||||
<title>Strudel REPL</title>
|
|
||||||
</head>
|
|
||||||
<body class="h-app-height bg-background">
|
|
||||||
<Repl2 client:only="react" />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
/*
|
/*
|
||||||
App.js - <short description TODO>
|
Repl.jsx - <short description TODO>
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
|
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
|
||||||
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/>.
|
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 PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
import { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core';
|
||||||
import { cleanupDraw, cleanupUi, code2hash, getDrawContext, logger } from '@strudel.cycles/core';
|
import { cx } from '@strudel.cycles/react';
|
||||||
import { CodeMirror, cx, flash, useHighlighting, useKeydown, useStrudel } from '@strudel.cycles/react';
|
import { transpiler } from '@strudel.cycles/transpiler';
|
||||||
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
|
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||||
import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
|
/* import { writeText } from '@tauri-apps/api/clipboard';
|
||||||
|
import { nanoid } from 'nanoid'; */
|
||||||
|
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
initUserCode,
|
initUserCode,
|
||||||
setActivePattern,
|
setActivePattern,
|
||||||
|
|
@ -22,9 +24,14 @@ import { Header } from './Header';
|
||||||
import Loader from './Loader';
|
import Loader from './Loader';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
import { Panel } from './panel/Panel';
|
import { Panel } from './panel/Panel';
|
||||||
import { prebake } from './prebake.mjs';
|
// import { prebake } from '@strudel/repl';
|
||||||
import { themes } from './themes.mjs';
|
import { useStore } from '@nanostores/react';
|
||||||
|
import { prebake /* , resetSounds */ } from './prebake.mjs';
|
||||||
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
||||||
|
import './Repl.css';
|
||||||
|
|
||||||
|
const { code: randomTune, name } = getRandomTune();
|
||||||
|
export const ReplContext = createContext(null);
|
||||||
|
|
||||||
const { latestCode } = settingsMap.get();
|
const { latestCode } = settingsMap.get();
|
||||||
|
|
||||||
|
|
@ -39,185 +46,151 @@ if (typeof window !== 'undefined') {
|
||||||
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
|
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTime = () => getAudioContext().currentTime;
|
// const getTime = () => getAudioContext().currentTime;
|
||||||
|
|
||||||
const { code: randomTune, name } = getRandomTune();
|
|
||||||
|
|
||||||
export const ReplContext = createContext(null);
|
|
||||||
|
|
||||||
export function Repl({ embedded = false }) {
|
export function Repl({ embedded = false }) {
|
||||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
//const isEmbedded = embedded || window.location !== window.parent.location;
|
||||||
const [view, setView] = useState(); // codemirror view
|
const isEmbedded = false;
|
||||||
const [pending, setPending] = useState(true);
|
const { panelPosition, isZen } = useSettings();
|
||||||
const {
|
/* const replState = useStore($replstate);
|
||||||
theme,
|
const isDirty = useStore($repldirty); */
|
||||||
keybindings,
|
const shouldDraw = true;
|
||||||
fontSize,
|
|
||||||
fontFamily,
|
|
||||||
isLineNumbersDisplayed,
|
|
||||||
isActiveLineHighlighted,
|
|
||||||
isAutoCompletionEnabled,
|
|
||||||
isTooltipEnabled,
|
|
||||||
isLineWrappingEnabled,
|
|
||||||
panelPosition,
|
|
||||||
isZen,
|
|
||||||
} = useSettings();
|
|
||||||
|
|
||||||
const paintOptions = useMemo(() => ({ fontFamily }), [fontFamily]);
|
const init = useCallback(({ shouldDraw }) => {
|
||||||
const { setWidgets } = useWidgets(view);
|
const drawTime = [-2, 2];
|
||||||
const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } =
|
const drawContext = shouldDraw ? getDrawContext() : null;
|
||||||
useStrudel({
|
let onDraw;
|
||||||
initialCode: '// LOADING...',
|
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,
|
defaultOutput: webaudioOutput,
|
||||||
getTime,
|
getTime: () => getAudioContext().currentTime,
|
||||||
beforeEval: async () => {
|
transpiler,
|
||||||
setPending(true);
|
autodraw: false,
|
||||||
await modulesLoading;
|
root: containerRef.current,
|
||||||
cleanupUi();
|
initialCode: '// LOADING',
|
||||||
cleanupDraw();
|
pattern: silence,
|
||||||
|
drawTime,
|
||||||
|
onDraw,
|
||||||
|
prebake: async () => Promise.all([modulesLoading, presets]),
|
||||||
|
onUpdateState: (state) => {
|
||||||
|
setReplState({ ...state });
|
||||||
},
|
},
|
||||||
afterEval: ({ code, meta }) => {
|
afterEval: ({ code }) => {
|
||||||
updateUserCode(code);
|
updateUserCode(code);
|
||||||
setMiniLocations(meta.miniLocations);
|
// setPending(false);
|
||||||
setWidgets(meta.widgets);
|
|
||||||
setPending(false);
|
|
||||||
setLatestCode(code);
|
setLatestCode(code);
|
||||||
window.location.hash = '#' + code2hash(code);
|
window.location.hash = '#' + code2hash(code);
|
||||||
},
|
},
|
||||||
onEvalError: (err) => {
|
bgFill: false,
|
||||||
setPending(false);
|
|
||||||
},
|
|
||||||
onToggle: (play) => {
|
|
||||||
if (!play) {
|
|
||||||
cleanupDraw(false);
|
|
||||||
window.postMessage('strudel-stop');
|
|
||||||
} else {
|
|
||||||
window.postMessage('strudel-start');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawContext,
|
|
||||||
// drawTime: [0, 6],
|
|
||||||
paintOptions,
|
|
||||||
});
|
});
|
||||||
|
// init settings
|
||||||
// init code
|
|
||||||
useEffect(() => {
|
|
||||||
initCode().then((decoded) => {
|
initCode().then((decoded) => {
|
||||||
let msg;
|
let msg;
|
||||||
if (decoded) {
|
if (decoded) {
|
||||||
setCode(decoded);
|
editor.setCode(decoded);
|
||||||
initUserCode(decoded);
|
initUserCode(decoded);
|
||||||
msg = `I have loaded the code from the URL.`;
|
msg = `I have loaded the code from the URL.`;
|
||||||
} else if (latestCode) {
|
} else if (latestCode) {
|
||||||
setCode(latestCode);
|
editor.setCode(latestCode);
|
||||||
msg = `Your last session has been loaded!`;
|
msg = `Your last session has been loaded!`;
|
||||||
} /* if(randomTune) */ else {
|
} /* if(randomTune) */ else {
|
||||||
setCode(randomTune);
|
editor.setCode(randomTune);
|
||||||
msg = `A random code snippet named "${name}" has been loaded!`;
|
msg = `A random code snippet named "${name}" has been loaded!`;
|
||||||
}
|
}
|
||||||
//registers samples that have been saved to the index DB
|
|
||||||
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
||||||
setPending(false);
|
// setPending(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
editorRef.current = editor;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// keyboard shortcuts
|
const [replState, setReplState] = useState({});
|
||||||
useKeydown(
|
const { started, isDirty, error, activeCode } = replState;
|
||||||
useCallback(
|
const editorRef = useRef();
|
||||||
async (e) => {
|
const containerRef = useRef();
|
||||||
if (e.ctrlKey || e.altKey) {
|
const [client, setClient] = useState(false);
|
||||||
if (e.code === 'Enter') {
|
useEffect(() => {
|
||||||
if (getAudioContext().state !== 'running') {
|
setClient(true);
|
||||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
if (!editorRef.current) {
|
||||||
return;
|
setTimeout(() => {
|
||||||
}
|
init({ shouldDraw });
|
||||||
e.preventDefault();
|
});
|
||||||
flash(view);
|
}
|
||||||
await activateCode();
|
return () => {
|
||||||
} else if (e.key === '.' || e.code === 'Period') {
|
editorRef.current?.clear();
|
||||||
stop();
|
delete editorRef.current;
|
||||||
e.preventDefault();
|
};
|
||||||
}
|
}, []);
|
||||||
}
|
|
||||||
},
|
|
||||||
[activateCode, stop, view],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// highlighting
|
// this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly!
|
||||||
const { setMiniLocations } = useHighlighting({
|
// this will be the case when the main repl is being replaced
|
||||||
view,
|
const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) });
|
||||||
pattern,
|
useEffect(() => {
|
||||||
active: started && !activeCode?.includes('strudel disable-highlighting'),
|
let editorSettings = {};
|
||||||
getTime: () => scheduler.now(),
|
Object.keys(defaultSettings).forEach((key) => {
|
||||||
});
|
if (_settings.hasOwnProperty(key)) {
|
||||||
|
editorSettings[key] = _settings[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
editorRef.current?.updateSettings(editorSettings);
|
||||||
|
}, [_settings]);
|
||||||
|
|
||||||
//
|
//
|
||||||
// UI Actions
|
// UI Actions
|
||||||
//
|
//
|
||||||
|
|
||||||
const handleChangeCode = useCallback(
|
const handleTogglePlay = async () => editorRef.current?.toggle();
|
||||||
(c) => {
|
|
||||||
setCode(c);
|
|
||||||
// started && logger('[edit] code changed. hit ctrl+enter to update');
|
|
||||||
},
|
|
||||||
[started],
|
|
||||||
);
|
|
||||||
const handleSelectionChange = useCallback((selection) => {
|
|
||||||
// TODO: scroll to selected function in reference
|
|
||||||
// console.log('selectino change', selection.ranges[0].from);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleTogglePlay = async () => {
|
|
||||||
await getAudioContext().resume(); // fixes no sound in ios webkit
|
|
||||||
if (!started) {
|
|
||||||
logger('[repl] started. tip: you can also start by pressing ctrl+enter', 'highlight');
|
|
||||||
activateCode();
|
|
||||||
} else {
|
|
||||||
logger('[repl] stopped. tip: you can also stop by pressing ctrl+dot', 'highlight');
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const handleUpdate = async (newCode, reset = false) => {
|
const handleUpdate = async (newCode, reset = false) => {
|
||||||
if (reset) {
|
if (reset) {
|
||||||
clearCanvas();
|
clearCanvas();
|
||||||
resetLoadedSounds();
|
resetLoadedSounds();
|
||||||
scheduler.setCps(1);
|
editorRef.current.repl.setCps(1);
|
||||||
await prebake(); // declare default samples
|
await prebake(); // declare default samples
|
||||||
}
|
}
|
||||||
(newCode || isDirty) && activateCode(newCode);
|
if (newCode || isDirty) {
|
||||||
|
editorRef.current.setCode(newCode);
|
||||||
|
editorRef.current.repl.evaluate(newCode);
|
||||||
|
}
|
||||||
logger('[repl] code updated!');
|
logger('[repl] code updated!');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShuffle = async () => {
|
const handleShuffle = async () => {
|
||||||
|
// window.postMessage('strudel-shuffle');
|
||||||
const { code, name } = getRandomTune();
|
const { code, name } = getRandomTune();
|
||||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||||
setActivePattern(name);
|
setActivePattern(name);
|
||||||
clearCanvas();
|
clearCanvas();
|
||||||
resetLoadedSounds();
|
resetLoadedSounds();
|
||||||
scheduler.setCps(1);
|
editorRef.current.repl.setCps(1);
|
||||||
await prebake(); // declare default samples
|
await prebake(); // declare default samples
|
||||||
await evaluate(code, false);
|
editorRef.current.setCode(code);
|
||||||
|
editorRef.current.repl.evaluate(code);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShare = async () => shareCode(activeCode || code);
|
const handleShare = async () => shareCode(activeCode);
|
||||||
|
const pending = false;
|
||||||
|
//const error = undefined;
|
||||||
|
// const { started, activeCode } = replState;
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
scheduler,
|
// scheduler,
|
||||||
embedded,
|
embedded,
|
||||||
started,
|
started,
|
||||||
pending,
|
pending,
|
||||||
isDirty,
|
isDirty,
|
||||||
activeCode,
|
activeCode,
|
||||||
handleChangeCode,
|
|
||||||
handleTogglePlay,
|
handleTogglePlay,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleShuffle,
|
handleShuffle,
|
||||||
handleShare,
|
handleShare,
|
||||||
};
|
};
|
||||||
const currentTheme = useMemo(() => themes[theme] || themes.strudelTheme, [theme]);
|
|
||||||
const handleViewChanged = useCallback((v) => {
|
|
||||||
setView(v);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
// bg-gradient-to-t from-blue-900 to-slate-900
|
||||||
|
|
@ -231,7 +204,7 @@ export function Repl({ embedded = false }) {
|
||||||
>
|
>
|
||||||
<Loader active={pending} />
|
<Loader active={pending} />
|
||||||
<Header context={context} />
|
<Header context={context} />
|
||||||
{isEmbedded && !started && (
|
{/* isEmbedded && !started && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleTogglePlay()}
|
onClick={() => handleTogglePlay()}
|
||||||
className="text-white text-2xl fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-[1000] m-auto p-4 bg-black rounded-md flex items-center space-x-2"
|
className="text-white text-2xl fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-[1000] m-auto p-4 bg-black rounded-md flex items-center space-x-2"
|
||||||
|
|
@ -239,25 +212,13 @@ export function Repl({ embedded = false }) {
|
||||||
<PlayCircleIcon className="w-6 h-6" />
|
<PlayCircleIcon className="w-6 h-6" />
|
||||||
<span>play</span>
|
<span>play</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
) */}
|
||||||
<div className="grow flex relative overflow-hidden">
|
<div className="grow flex relative overflow-hidden">
|
||||||
<section className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')} id="code">
|
<section
|
||||||
<CodeMirror
|
className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')}
|
||||||
theme={currentTheme}
|
id="code"
|
||||||
value={code}
|
ref={containerRef}
|
||||||
keybindings={keybindings}
|
></section>
|
||||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
|
||||||
isActiveLineHighlighted={isActiveLineHighlighted}
|
|
||||||
isAutoCompletionEnabled={isAutoCompletionEnabled}
|
|
||||||
isTooltipEnabled={isTooltipEnabled}
|
|
||||||
isLineWrappingEnabled={isLineWrappingEnabled}
|
|
||||||
fontSize={fontSize}
|
|
||||||
fontFamily={fontFamily}
|
|
||||||
onChange={handleChangeCode}
|
|
||||||
onViewChanged={handleViewChanged}
|
|
||||||
onSelectionChange={handleSelectionChange}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
{panelPosition === 'right' && !isEmbedded && <Panel context={context} />}
|
{panelPosition === 'right' && !isEmbedded && <Panel context={context} />}
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
|
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
/*
|
|
||||||
App.js - <short description TODO>
|
|
||||||
Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/strudel/blob/main/repl/src/App.js>
|
|
||||||
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 { code2hash, getDrawContext, logger, silence } from '@strudel.cycles/core';
|
|
||||||
import { cx } from '@strudel.cycles/react';
|
|
||||||
import { transpiler } from '@strudel.cycles/transpiler';
|
|
||||||
import { getAudioContext, initAudioOnFirstClick, webaudioOutput } from '@strudel.cycles/webaudio';
|
|
||||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
|
||||||
/* import { writeText } from '@tauri-apps/api/clipboard';
|
|
||||||
import { nanoid } from 'nanoid'; */
|
|
||||||
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
|
|
||||||
import {
|
|
||||||
initUserCode,
|
|
||||||
setActivePattern,
|
|
||||||
setLatestCode,
|
|
||||||
settingsMap,
|
|
||||||
updateUserCode,
|
|
||||||
useSettings,
|
|
||||||
} from '../settings.mjs';
|
|
||||||
import { Header } from './Header';
|
|
||||||
import Loader from './Loader';
|
|
||||||
import './Repl.css';
|
|
||||||
import { Panel } from './panel/Panel';
|
|
||||||
// import { prebake } from '@strudel/repl';
|
|
||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { prebake /* , resetSounds */ } from './prebake.mjs';
|
|
||||||
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
|
||||||
import './Repl.css';
|
|
||||||
|
|
||||||
const { code: randomTune, name } = getRandomTune();
|
|
||||||
export const ReplContext = createContext(null);
|
|
||||||
|
|
||||||
const { latestCode } = settingsMap.get();
|
|
||||||
|
|
||||||
initAudioOnFirstClick();
|
|
||||||
|
|
||||||
const modulesLoading = loadModules();
|
|
||||||
const presets = prebake();
|
|
||||||
|
|
||||||
let drawContext, clearCanvas;
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
drawContext = getDrawContext();
|
|
||||||
clearCanvas = () => drawContext.clearRect(0, 0, drawContext.canvas.height, drawContext.canvas.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
// const getTime = () => getAudioContext().currentTime;
|
|
||||||
|
|
||||||
export function Repl2({ embedded = false }) {
|
|
||||||
//const isEmbedded = embedded || window.location !== window.parent.location;
|
|
||||||
const isEmbedded = false;
|
|
||||||
const { panelPosition, isZen } = useSettings();
|
|
||||||
/* const replState = useStore($replstate);
|
|
||||||
const isDirty = useStore($repldirty); */
|
|
||||||
const shouldDraw = true;
|
|
||||||
|
|
||||||
const init = useCallback(({ shouldDraw }) => {
|
|
||||||
const drawTime = [-2, 2];
|
|
||||||
const drawContext = shouldDraw ? getDrawContext() : null;
|
|
||||||
let onDraw;
|
|
||||||
if (shouldDraw) {
|
|
||||||
onDraw = (haps, time, frame, painters) => {
|
|
||||||
painters.length && drawContext.clearRect(0, 0, drawContext.canvas.width * 2, drawContext.canvas.height * 2);
|
|
||||||
painters?.forEach((painter) => {
|
|
||||||
// ctx time haps drawTime paintOptions
|
|
||||||
painter(drawContext, time, haps, drawTime, { clear: false });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const editor = new StrudelMirror({
|
|
||||||
defaultOutput: webaudioOutput,
|
|
||||||
getTime: () => getAudioContext().currentTime,
|
|
||||||
transpiler,
|
|
||||||
autodraw: false,
|
|
||||||
root: containerRef.current,
|
|
||||||
initialCode: '// LOADING',
|
|
||||||
pattern: silence,
|
|
||||||
drawTime,
|
|
||||||
onDraw,
|
|
||||||
prebake: async () => Promise.all([modulesLoading, presets]),
|
|
||||||
onUpdateState: (state) => {
|
|
||||||
setReplState({ ...state });
|
|
||||||
},
|
|
||||||
afterEval: ({ code }) => {
|
|
||||||
updateUserCode(code);
|
|
||||||
// setPending(false);
|
|
||||||
setLatestCode(code);
|
|
||||||
window.location.hash = '#' + code2hash(code);
|
|
||||||
},
|
|
||||||
bgFill: false,
|
|
||||||
});
|
|
||||||
// init settings
|
|
||||||
initCode().then((decoded) => {
|
|
||||||
let msg;
|
|
||||||
if (decoded) {
|
|
||||||
editor.setCode(decoded);
|
|
||||||
initUserCode(decoded);
|
|
||||||
msg = `I have loaded the code from the URL.`;
|
|
||||||
} else if (latestCode) {
|
|
||||||
editor.setCode(latestCode);
|
|
||||||
msg = `Your last session has been loaded!`;
|
|
||||||
} /* if(randomTune) */ else {
|
|
||||||
editor.setCode(randomTune);
|
|
||||||
msg = `A random code snippet named "${name}" has been loaded!`;
|
|
||||||
}
|
|
||||||
logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
|
|
||||||
// setPending(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
editorRef.current = editor;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [replState, setReplState] = useState({});
|
|
||||||
const { started, isDirty, error, activeCode } = replState;
|
|
||||||
const editorRef = useRef();
|
|
||||||
const containerRef = useRef();
|
|
||||||
const [client, setClient] = useState(false);
|
|
||||||
useEffect(() => {
|
|
||||||
setClient(true);
|
|
||||||
if (!editorRef.current) {
|
|
||||||
setTimeout(() => {
|
|
||||||
init({ shouldDraw });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
editorRef.current?.clear();
|
|
||||||
delete editorRef.current;
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// this can be simplified once SettingsTab has been refactored to change codemirrorSettings directly!
|
|
||||||
// this will be the case when the main repl is being replaced
|
|
||||||
const _settings = useStore(settingsMap, { keys: Object.keys(defaultSettings) });
|
|
||||||
useEffect(() => {
|
|
||||||
let editorSettings = {};
|
|
||||||
Object.keys(defaultSettings).forEach((key) => {
|
|
||||||
if (_settings.hasOwnProperty(key)) {
|
|
||||||
editorSettings[key] = _settings[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
editorRef.current?.updateSettings(editorSettings);
|
|
||||||
}, [_settings]);
|
|
||||||
|
|
||||||
//
|
|
||||||
// UI Actions
|
|
||||||
//
|
|
||||||
|
|
||||||
const handleTogglePlay = async () => editorRef.current?.toggle();
|
|
||||||
const handleUpdate = async (newCode, reset = false) => {
|
|
||||||
if (reset) {
|
|
||||||
clearCanvas();
|
|
||||||
resetLoadedSounds();
|
|
||||||
editorRef.current.repl.setCps(1);
|
|
||||||
await prebake(); // declare default samples
|
|
||||||
}
|
|
||||||
if (newCode || isDirty) {
|
|
||||||
editorRef.current.setCode(newCode);
|
|
||||||
editorRef.current.repl.evaluate(newCode);
|
|
||||||
}
|
|
||||||
logger('[repl] code updated!');
|
|
||||||
};
|
|
||||||
const handleShuffle = async () => {
|
|
||||||
// window.postMessage('strudel-shuffle');
|
|
||||||
const { code, name } = getRandomTune();
|
|
||||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
|
||||||
setActivePattern(name);
|
|
||||||
clearCanvas();
|
|
||||||
resetLoadedSounds();
|
|
||||||
editorRef.current.repl.setCps(1);
|
|
||||||
await prebake(); // declare default samples
|
|
||||||
editorRef.current.setCode(code);
|
|
||||||
editorRef.current.repl.evaluate(code);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleShare = async () => shareCode(activeCode);
|
|
||||||
const pending = false;
|
|
||||||
//const error = undefined;
|
|
||||||
// const { started, activeCode } = replState;
|
|
||||||
|
|
||||||
const context = {
|
|
||||||
// scheduler,
|
|
||||||
embedded,
|
|
||||||
started,
|
|
||||||
pending,
|
|
||||||
isDirty,
|
|
||||||
activeCode,
|
|
||||||
handleTogglePlay,
|
|
||||||
handleUpdate,
|
|
||||||
handleShuffle,
|
|
||||||
handleShare,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
|
||||||
// bg-gradient-to-t from-green-900 to-slate-900
|
|
||||||
<ReplContext.Provider value={context}>
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'h-full flex flex-col relative',
|
|
||||||
// overflow-hidden
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Loader active={pending} />
|
|
||||||
<Header context={context} />
|
|
||||||
{/* isEmbedded && !started && (
|
|
||||||
<button
|
|
||||||
onClick={() => handleTogglePlay()}
|
|
||||||
className="text-white text-2xl fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-[1000] m-auto p-4 bg-black rounded-md flex items-center space-x-2"
|
|
||||||
>
|
|
||||||
<PlayCircleIcon className="w-6 h-6" />
|
|
||||||
<span>play</span>
|
|
||||||
</button>
|
|
||||||
) */}
|
|
||||||
<div className="grow flex relative overflow-hidden">
|
|
||||||
<section
|
|
||||||
className={'text-gray-100 cursor-text pb-0 overflow-auto grow' + (isZen ? ' px-10' : '')}
|
|
||||||
id="code"
|
|
||||||
ref={containerRef}
|
|
||||||
></section>
|
|
||||||
{panelPosition === 'right' && !isEmbedded && <Panel context={context} />}
|
|
||||||
</div>
|
|
||||||
{error && (
|
|
||||||
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
|
|
||||||
)}
|
|
||||||
{panelPosition === 'bottom' && !isEmbedded && <Panel context={context} />}
|
|
||||||
</div>
|
|
||||||
</ReplContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue