mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 22:35:15 -04:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d6114beb6 | |||
| 82380a0f4e | |||
| c030f456c3 | |||
| 694e44dc8a | |||
| 351da196b9 | |||
| e27ee03517 | |||
| 19db419c53 | |||
| ff60a89668 | |||
| 7cc17fefcb | |||
| 76ee9485f4 | |||
| 1d65f09f3c | |||
| cc97f6395d | |||
| 33eef619e4 | |||
| e9e3b97621 | |||
| 1f765e54ca | |||
| 2b81cc5d0c | |||
| ca054dbd74 | |||
| a8433f61a4 | |||
| 5850106eef | |||
| 95a9ab6182 | |||
| ba19c4ddce | |||
| b49b1c98ae | |||
| 7c00aa1a36 | |||
| 07d6bb3c44 | |||
| 3f2e2554be | |||
| 9ebb7edde9 | |||
| a89091cbc7 | |||
| 0d1a99deb2 | |||
| b71f8a33d7 |
@@ -0,0 +1,88 @@
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import jsdoc from '../../doc.json';
|
||||
// import { javascriptLanguage } from '@codemirror/lang-javascript';
|
||||
import { autocompletion } from '@codemirror/autocomplete';
|
||||
|
||||
const getDocLabel = (doc) => doc.name || doc.longname;
|
||||
const getInnerText = (html) => {
|
||||
var div = document.createElement('div');
|
||||
div.innerHTML = html;
|
||||
return div.textContent || div.innerText || '';
|
||||
};
|
||||
|
||||
export function Autocomplete({ doc }) {
|
||||
return (
|
||||
<div className="prose dark:prose-invert max-h-[400px] overflow-auto">
|
||||
<h3 className="pt-0 mt-0">{getDocLabel(doc)}</h3>
|
||||
<div dangerouslySetInnerHTML={{ __html: doc.description }} />
|
||||
<ul>
|
||||
{doc.params?.map(({ name, type, description }, i) => (
|
||||
<li key={i}>
|
||||
{name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}</> : ''}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div>
|
||||
{doc.examples?.map((example, i) => (
|
||||
<div key={i}>
|
||||
<pre
|
||||
className="cursor-pointer"
|
||||
onMouseDown={(e) => {
|
||||
console.log('ola!');
|
||||
navigator.clipboard.writeText(example);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{example}
|
||||
</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const jsdocCompletions = jsdoc.docs
|
||||
.filter(
|
||||
(doc) =>
|
||||
getDocLabel(doc) &&
|
||||
!getDocLabel(doc).startsWith('_') &&
|
||||
!['package'].includes(doc.kind) &&
|
||||
!['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
|
||||
)
|
||||
// https://codemirror.net/docs/ref/#autocomplete.Completion
|
||||
.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');
|
||||
// if Autocomplete is non-interactive, it could also be rendered at build time..
|
||||
// .. using renderToStaticMarkup
|
||||
createRoot(node).render(<Autocomplete doc={doc} />);
|
||||
return node;
|
||||
},
|
||||
type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
|
||||
}));
|
||||
|
||||
export const strudelAutocomplete = (context /* : CompletionContext */) => {
|
||||
let word = context.matchBefore(/\w*/);
|
||||
if (word.from == word.to && !context.explicit) return null;
|
||||
return {
|
||||
from: word.from,
|
||||
options: jsdocCompletions,
|
||||
/* options: [
|
||||
{ label: 'match', type: 'keyword' },
|
||||
{ label: 'hello', type: 'variable', info: '(World)' },
|
||||
{ label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
|
||||
], */
|
||||
};
|
||||
};
|
||||
|
||||
export function isAutoCompletionEnabled(on) {
|
||||
return on
|
||||
? [
|
||||
autocompletion({ override: [strudelAutocomplete] }),
|
||||
//javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }),
|
||||
]
|
||||
: []; // autocompletion({ override: [] })
|
||||
}
|
||||
@@ -1,36 +1,78 @@
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { closeBrackets } from '@codemirror/autocomplete';
|
||||
// import { search, highlightSelectionMatches } from '@codemirror/search';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
|
||||
import { Drawer, repl } from '@strudel.cycles/core';
|
||||
import { flashField, flash } from './flash.mjs';
|
||||
import { highlightExtension, highlightMiniLocations } from './highlight.mjs';
|
||||
import { oneDark } from './themes/one-dark';
|
||||
import { Compartment, EditorState } 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 { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { theme } from './themes.mjs';
|
||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||
|
||||
const extensions = {
|
||||
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
|
||||
isLineNumbersDisplayed: (on) => (on ? lineNumbers() : []),
|
||||
theme,
|
||||
isAutoCompletionEnabled,
|
||||
isPatternHighlightingEnabled,
|
||||
isFlashEnabled,
|
||||
keybindings,
|
||||
};
|
||||
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
|
||||
|
||||
// https://codemirror.net/docs/guide/
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
|
||||
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
|
||||
const initialSettings = Object.keys(compartments).map((key) =>
|
||||
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
|
||||
);
|
||||
let state = EditorState.create({
|
||||
doc: initialCode,
|
||||
extensions: [
|
||||
theme,
|
||||
/* search(),
|
||||
highlightSelectionMatches(), */
|
||||
...initialSettings,
|
||||
javascript(),
|
||||
lineNumbers(),
|
||||
highlightExtension,
|
||||
sliderPlugin,
|
||||
// indentOnInput(), // works without. already brought with javascript extension?
|
||||
// bracketMatching(), // does not do anything
|
||||
closeBrackets(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightActiveLine(),
|
||||
syntaxHighlighting(defaultHighlightStyle),
|
||||
keymap.of(defaultKeymap),
|
||||
flashField,
|
||||
history(),
|
||||
EditorView.updateListener.of((v) => onChange(v)),
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate(),
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop(),
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
run: (_, e) => {
|
||||
e.preventDefault();
|
||||
onStop?.();
|
||||
},
|
||||
},
|
||||
/* {
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-Enter',
|
||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||
}, */
|
||||
]),
|
||||
],
|
||||
});
|
||||
@@ -43,71 +85,158 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the
|
||||
|
||||
export class StrudelMirror {
|
||||
constructor(options) {
|
||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, ...replOptions } = options;
|
||||
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
|
||||
this.code = initialCode;
|
||||
this.root = root;
|
||||
this.miniLocations = [];
|
||||
this.widgets = [];
|
||||
this.painters = [];
|
||||
this.onDraw = onDraw;
|
||||
const self = this;
|
||||
|
||||
this.drawer = new Drawer((haps, time) => {
|
||||
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
|
||||
this.highlight(currentFrame, time);
|
||||
onDraw?.(haps, time, currentFrame);
|
||||
this.onDraw?.(haps, time, currentFrame, this.painters);
|
||||
}, drawTime);
|
||||
|
||||
const prebaked = prebake();
|
||||
prebaked.then(async () => {
|
||||
if (!onDraw) {
|
||||
return;
|
||||
}
|
||||
const { scheduler, evaluate } = await this.repl;
|
||||
// draw first frame instantly
|
||||
prebaked.then(async () => {
|
||||
await evaluate(this.code, false);
|
||||
this.drawer.invalidate(scheduler);
|
||||
onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||
});
|
||||
});
|
||||
// this approach might not work with multiple repls on screen..
|
||||
Pattern.prototype.onPaint = function (onPaint) {
|
||||
self.painters.push(onPaint);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.prebaked = prebake();
|
||||
// this.drawFirstFrame();
|
||||
|
||||
this.repl = repl({
|
||||
...replOptions,
|
||||
onToggle: async (started) => {
|
||||
onToggle: (started) => {
|
||||
replOptions?.onToggle?.(started);
|
||||
const { scheduler } = await this.repl;
|
||||
if (started) {
|
||||
this.drawer.start(scheduler);
|
||||
this.drawer.start(this.repl.scheduler);
|
||||
} else {
|
||||
this.drawer.stop();
|
||||
updateMiniLocations(this.editor, []);
|
||||
cleanupDraw(false);
|
||||
}
|
||||
},
|
||||
beforeEval: async () => {
|
||||
await prebaked;
|
||||
cleanupDraw();
|
||||
this.painters = [];
|
||||
await this.prebaked;
|
||||
await replOptions?.beforeEval?.();
|
||||
},
|
||||
afterEval: (options) => {
|
||||
// remember for when highlighting is toggled on
|
||||
this.miniLocations = options.meta?.miniLocations;
|
||||
this.widgets = options.meta?.widgets;
|
||||
updateWidgets(this.editor, this.widgets);
|
||||
updateMiniLocations(this.editor, this.miniLocations);
|
||||
replOptions?.afterEval?.(options);
|
||||
this.drawer.invalidate();
|
||||
},
|
||||
});
|
||||
this.editor = initEditor({
|
||||
root,
|
||||
settings,
|
||||
initialCode,
|
||||
onChange: (v) => {
|
||||
this.code = v.state.doc.toString();
|
||||
if (v.docChanged) {
|
||||
this.code = v.state.doc.toString();
|
||||
this.repl.setCode(this.code);
|
||||
}
|
||||
},
|
||||
onEvaluate: () => this.evaluate(),
|
||||
onStop: () => this.stop(),
|
||||
});
|
||||
}
|
||||
async drawFirstFrame() {
|
||||
if (!this.onDraw) {
|
||||
return;
|
||||
}
|
||||
// draw first frame instantly
|
||||
await this.prebaked;
|
||||
try {
|
||||
await this.repl.evaluate(this.code, false);
|
||||
this.drawer.invalidate(this.repl.scheduler);
|
||||
this.onDraw?.(this.drawer.visibleHaps, 0, []);
|
||||
} catch (err) {
|
||||
console.warn('first frame could not be painted');
|
||||
}
|
||||
}
|
||||
async evaluate() {
|
||||
const { evaluate } = await this.repl;
|
||||
this.flash();
|
||||
await evaluate(this.code);
|
||||
await this.repl.evaluate(this.code);
|
||||
}
|
||||
async stop() {
|
||||
const { scheduler } = await this.repl;
|
||||
scheduler.stop();
|
||||
this.repl.scheduler.stop();
|
||||
}
|
||||
async toggle() {
|
||||
if (this.repl.scheduler.started) {
|
||||
this.repl.scheduler.stop();
|
||||
} else {
|
||||
this.evaluate();
|
||||
}
|
||||
}
|
||||
flash(ms) {
|
||||
flash(this.editor, ms);
|
||||
}
|
||||
highlight(haps, time) {
|
||||
highlightMiniLocations(this.editor.view, time, haps);
|
||||
highlightMiniLocations(this.editor, time, haps);
|
||||
}
|
||||
setFontSize(size) {
|
||||
this.root.style.fontSize = size + 'px';
|
||||
}
|
||||
setFontFamily(family) {
|
||||
this.root.style.fontFamily = family;
|
||||
}
|
||||
reconfigureExtension(key, value) {
|
||||
if (!extensions[key]) {
|
||||
console.warn(`extension ${key} is not known`);
|
||||
return;
|
||||
}
|
||||
value = parseBooleans(value);
|
||||
const newValue = extensions[key](value, this);
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
});
|
||||
}
|
||||
setLineWrappingEnabled(enabled) {
|
||||
this.reconfigureExtension('isLineWrappingEnabled', enabled);
|
||||
}
|
||||
setLineNumbersDisplayed(enabled) {
|
||||
this.reconfigureExtension('isLineNumbersDisplayed', enabled);
|
||||
}
|
||||
setTheme(theme) {
|
||||
this.reconfigureExtension('theme', theme);
|
||||
}
|
||||
setAutocompletionEnabled(enabled) {
|
||||
this.reconfigureExtension('isAutoCompletionEnabled', enabled);
|
||||
}
|
||||
updateSettings(settings) {
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
for (let key in extensions) {
|
||||
this.reconfigureExtension(key, settings[key]);
|
||||
}
|
||||
}
|
||||
changeSetting(key, value) {
|
||||
if (extensions[key]) {
|
||||
this.reconfigureExtension(key, value);
|
||||
return;
|
||||
} else if (key === 'fontFamily') {
|
||||
this.setFontFamily(value);
|
||||
} else if (key === 'fontSize') {
|
||||
this.setFontSize(value);
|
||||
}
|
||||
}
|
||||
setCode(code) {
|
||||
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
|
||||
this.editor.dispatch({ changes });
|
||||
}
|
||||
}
|
||||
|
||||
function parseBooleans(value) {
|
||||
return { true: true, false: false }[value] ?? value;
|
||||
}
|
||||
|
||||
@@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => {
|
||||
view.dispatch({ effects: setFlash.of(false) });
|
||||
}, ms);
|
||||
};
|
||||
|
||||
export const isFlashEnabled = (on) => (on ? flashField : []);
|
||||
|
||||
@@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
|
||||
});
|
||||
|
||||
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
|
||||
|
||||
export const isPatternHighlightingEnabled = (on, config) => {
|
||||
on &&
|
||||
config &&
|
||||
setTimeout(() => {
|
||||
updateMiniLocations(config.editor, config.miniLocations);
|
||||
}, 100);
|
||||
return on ? highlightExtension : [];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Prec } from '@codemirror/state';
|
||||
import { keymap, ViewPlugin } from '@codemirror/view';
|
||||
// import { searchKeymap } from '@codemirror/search';
|
||||
import { emacs } from '@replit/codemirror-emacs';
|
||||
import { vim } from '@replit/codemirror-vim';
|
||||
import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
|
||||
import { defaultKeymap, historyKeymap } from '@codemirror/commands';
|
||||
|
||||
const vscodePlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor() {}
|
||||
},
|
||||
{
|
||||
provide: () => {
|
||||
return Prec.highest(keymap.of([...vscodeKeymap]));
|
||||
},
|
||||
},
|
||||
);
|
||||
const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
|
||||
|
||||
const keymaps = {
|
||||
vim,
|
||||
emacs,
|
||||
vscode: vscodeExtension,
|
||||
};
|
||||
|
||||
export function keybindings(name) {
|
||||
const active = keymaps[name];
|
||||
return [keymap.of(defaultKeymap), keymap.of(historyKeymap), active ? active() : []];
|
||||
// keymap.of(searchKeymap),
|
||||
}
|
||||
@@ -33,13 +33,21 @@
|
||||
},
|
||||
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.6.0",
|
||||
"@codemirror/commands": "^6.2.4",
|
||||
"@codemirror/lang-javascript": "^6.1.7",
|
||||
"@codemirror/language": "^6.6.0",
|
||||
"@codemirror/search": "^6.0.0",
|
||||
"@codemirror/state": "^6.2.0",
|
||||
"@codemirror/view": "^6.10.0",
|
||||
"@lezer/highlight": "^1.1.4",
|
||||
"@strudel.cycles/core": "workspace:*"
|
||||
"@replit/codemirror-emacs": "^6.0.1",
|
||||
"@replit/codemirror-vim": "^6.0.14",
|
||||
"@replit/codemirror-vscode-keymap": "^6.0.2",
|
||||
"@strudel.cycles/core": "workspace:*",
|
||||
"@uiw/codemirror-themes": "^4.19.16",
|
||||
"@uiw/codemirror-themes-all": "^4.19.16",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.3.3"
|
||||
|
||||
Vendored
+484
@@ -0,0 +1,484 @@
|
||||
import {
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
gruvboxDark,
|
||||
materialDark,
|
||||
nord,
|
||||
okaidia,
|
||||
solarizedDark,
|
||||
sublime,
|
||||
tokyoNight,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
xcodeLight,
|
||||
} from '@uiw/codemirror-themes-all';
|
||||
|
||||
import strudelTheme from './themes/strudel-theme';
|
||||
import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
|
||||
import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
|
||||
import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
|
||||
import teletext, { settings as teletextSettings } from './themes/teletext';
|
||||
import algoboy, { settings as algoboySettings } from './themes/algoboy';
|
||||
import terminal, { settings as terminalSettings } from './themes/terminal';
|
||||
|
||||
export const themes = {
|
||||
strudelTheme,
|
||||
bluescreen,
|
||||
blackscreen,
|
||||
whitescreen,
|
||||
teletext,
|
||||
algoboy,
|
||||
terminal,
|
||||
abcdef,
|
||||
androidstudio,
|
||||
atomone,
|
||||
aura,
|
||||
bespin,
|
||||
darcula,
|
||||
dracula,
|
||||
duotoneDark,
|
||||
eclipse,
|
||||
githubDark,
|
||||
gruvboxDark,
|
||||
materialDark,
|
||||
nord,
|
||||
okaidia,
|
||||
solarizedDark,
|
||||
sublime,
|
||||
tokyoNight,
|
||||
tokyoNightStorm,
|
||||
vscodeDark,
|
||||
xcodeDark,
|
||||
bbedit,
|
||||
duotoneLight,
|
||||
githubLight,
|
||||
gruvboxLight,
|
||||
materialLight,
|
||||
noctisLilac,
|
||||
solarizedLight,
|
||||
tokyoNightDay,
|
||||
xcodeLight,
|
||||
};
|
||||
|
||||
// lineBackground is background with 50% opacity, to make sure the selection below is visible
|
||||
|
||||
export const settings = {
|
||||
strudelTheme: {
|
||||
background: '#222',
|
||||
lineBackground: '#22222299',
|
||||
foreground: '#fff',
|
||||
// foreground: '#75baff',
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
bluescreen: bluescreenSettings,
|
||||
blackscreen: blackscreenSettings,
|
||||
whitescreen: whitescreenSettings,
|
||||
teletext: teletextSettings,
|
||||
algoboy: algoboySettings,
|
||||
terminal: terminalSettings,
|
||||
abcdef: {
|
||||
background: '#0f0f0f',
|
||||
lineBackground: '#0f0f0f99',
|
||||
foreground: '#defdef',
|
||||
caret: '#00FF00',
|
||||
selection: '#515151',
|
||||
selectionMatch: '#515151',
|
||||
gutterBackground: '#555',
|
||||
gutterForeground: '#FFFFFF',
|
||||
lineHighlight: '#314151',
|
||||
},
|
||||
androidstudio: {
|
||||
background: '#282b2e',
|
||||
lineBackground: '#282b2e99',
|
||||
foreground: '#a9b7c6',
|
||||
caret: '#00FF00',
|
||||
selection: '#343739',
|
||||
selectionMatch: '#343739',
|
||||
lineHighlight: '#343739',
|
||||
},
|
||||
atomone: {
|
||||
background: '#272C35',
|
||||
lineBackground: '#272C3599',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#ffffff30',
|
||||
selectionMatch: '#2B323D',
|
||||
gutterBackground: '#272C35',
|
||||
gutterForeground: '#465063',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#2B323D',
|
||||
},
|
||||
aura: {
|
||||
background: '#21202e',
|
||||
lineBackground: '#21202e99',
|
||||
foreground: '#edecee',
|
||||
caret: '#a277ff',
|
||||
selection: '#3d375e7f',
|
||||
selectionMatch: '#3d375e7f',
|
||||
gutterBackground: '#21202e',
|
||||
gutterForeground: '#edecee',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#a394f033',
|
||||
},
|
||||
bbedit: {
|
||||
light: true,
|
||||
background: '#FFFFFF',
|
||||
lineBackground: '#FFFFFF99',
|
||||
foreground: '#000000',
|
||||
caret: '#FBAC52',
|
||||
selection: '#FFD420',
|
||||
selectionMatch: '#FFD420',
|
||||
gutterBackground: '#f5f5f5',
|
||||
gutterForeground: '#4D4D4C',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#00000012',
|
||||
},
|
||||
bespin: {
|
||||
background: '#28211c',
|
||||
lineBackground: '#28211c99',
|
||||
foreground: '#9d9b97',
|
||||
caret: '#797977',
|
||||
selection: '#36312e',
|
||||
selectionMatch: '#4f382b',
|
||||
gutterBackground: '#28211c',
|
||||
gutterForeground: '#666666',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
darcula: {
|
||||
background: '#2B2B2B',
|
||||
lineBackground: '#2B2B2B99',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#FFFFFF',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterForeground: '#999',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
dracula: {
|
||||
background: '#282a36',
|
||||
lineBackground: '#282a3699',
|
||||
foreground: '#f8f8f2',
|
||||
caret: '#f8f8f0',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
selectionMatch: 'rgba(255, 255, 255, 0.2)',
|
||||
gutterBackground: '#282a36',
|
||||
gutterForeground: '#6D8A88',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: 'rgba(255, 255, 255, 0.1)',
|
||||
},
|
||||
duotoneLight: {
|
||||
light: true,
|
||||
background: '#faf8f5',
|
||||
lineBackground: '#faf8f599',
|
||||
foreground: '#b29762',
|
||||
caret: '#93abdc',
|
||||
selection: '#e3dcce',
|
||||
selectionMatch: '#e3dcce',
|
||||
gutterBackground: '#faf8f5',
|
||||
gutterForeground: '#cdc4b1',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#EFEFEF',
|
||||
},
|
||||
duotoneDark: {
|
||||
background: '#2a2734',
|
||||
lineBackground: '#2a273499',
|
||||
foreground: '#6c6783',
|
||||
caret: '#ffad5c',
|
||||
selection: 'rgba(255, 255, 255, 0.1)',
|
||||
gutterBackground: '#2a2734',
|
||||
gutterForeground: '#545167',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
eclipse: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#000',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#f7f7f7',
|
||||
gutterForeground: '#999',
|
||||
lineHighlight: '#e8f2ff',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
githubLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#24292e',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#6e7781',
|
||||
},
|
||||
githubDark: {
|
||||
background: '#0d1117',
|
||||
lineBackground: '#0d111799',
|
||||
foreground: '#c9d1d9',
|
||||
caret: '#c9d1d9',
|
||||
selection: '#003d73',
|
||||
selectionMatch: '#003d73',
|
||||
lineHighlight: '#36334280',
|
||||
},
|
||||
gruvboxDark: {
|
||||
background: '#282828',
|
||||
lineBackground: '#28282899',
|
||||
foreground: '#ebdbb2',
|
||||
caret: '#ebdbb2',
|
||||
selection: '#bdae93',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#3c3836',
|
||||
gutterBackground: '#282828',
|
||||
gutterForeground: '#7c6f64',
|
||||
},
|
||||
gruvboxLight: {
|
||||
light: true,
|
||||
background: '#fbf1c7',
|
||||
lineBackground: '#fbf1c799',
|
||||
foreground: '#3c3836',
|
||||
caret: '#af3a03',
|
||||
selection: '#ebdbb2',
|
||||
selectionMatch: '#bdae93',
|
||||
lineHighlight: '#ebdbb2',
|
||||
gutterBackground: '#ebdbb2',
|
||||
gutterForeground: '#665c54',
|
||||
gutterBorder: 'transparent',
|
||||
},
|
||||
materialDark: {
|
||||
background: '#2e3235',
|
||||
lineBackground: '#2e323599',
|
||||
foreground: '#bdbdbd',
|
||||
caret: '#a0a4ae',
|
||||
selection: '#d7d4f0',
|
||||
selectionMatch: '#d7d4f0',
|
||||
gutterBackground: '#2e3235',
|
||||
gutterForeground: '#999',
|
||||
gutterActiveForeground: '#4f5b66',
|
||||
lineHighlight: '#545b61',
|
||||
},
|
||||
materialLight: {
|
||||
light: true,
|
||||
background: '#FAFAFA',
|
||||
lineBackground: '#FAFAFA99',
|
||||
foreground: '#90A4AE',
|
||||
caret: '#272727',
|
||||
selection: '#80CBC440',
|
||||
selectionMatch: '#FAFAFA',
|
||||
gutterBackground: '#FAFAFA',
|
||||
gutterForeground: '#90A4AE',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#CCD7DA50',
|
||||
},
|
||||
noctisLilac: {
|
||||
light: true,
|
||||
background: '#f2f1f8',
|
||||
lineBackground: '#f2f1f899',
|
||||
foreground: '#0c006b',
|
||||
caret: '#5c49e9',
|
||||
selection: '#d5d1f2',
|
||||
selectionMatch: '#d5d1f2',
|
||||
gutterBackground: '#f2f1f8',
|
||||
gutterForeground: '#0c006b70',
|
||||
lineHighlight: '#e1def3',
|
||||
},
|
||||
nord: {
|
||||
background: '#2e3440',
|
||||
lineBackground: '#2e344099',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#3b4252',
|
||||
selectionMatch: '#e5e9f0',
|
||||
gutterBackground: '#2e3440',
|
||||
gutterForeground: '#4c566a',
|
||||
gutterActiveForeground: '#d8dee9',
|
||||
lineHighlight: '#4c566a',
|
||||
},
|
||||
okaidia: {
|
||||
background: '#272822',
|
||||
lineBackground: '#27282299',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FFFFFF',
|
||||
selection: '#49483E',
|
||||
selectionMatch: '#49483E',
|
||||
gutterBackground: '#272822',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
solarizedLight: {
|
||||
light: true,
|
||||
background: '#fdf6e3',
|
||||
lineBackground: '#fdf6e399',
|
||||
foreground: '#657b83',
|
||||
caret: '#586e75',
|
||||
selection: '#dfd9c8',
|
||||
selectionMatch: '#dfd9c8',
|
||||
gutterBackground: '#00000010',
|
||||
gutterForeground: '#657b83',
|
||||
lineHighlight: '#dfd9c8',
|
||||
},
|
||||
solarizedDark: {
|
||||
background: '#002b36',
|
||||
lineBackground: '#002b3699',
|
||||
foreground: '#93a1a1',
|
||||
caret: '#839496',
|
||||
selection: '#173541',
|
||||
selectionMatch: '#aafe661a',
|
||||
gutterBackground: '#00252f',
|
||||
gutterForeground: '#839496',
|
||||
lineHighlight: '#173541',
|
||||
},
|
||||
sublime: {
|
||||
background: '#303841',
|
||||
lineBackground: '#30384199',
|
||||
foreground: '#FFFFFF',
|
||||
caret: '#FBAC52',
|
||||
selection: '#4C5964',
|
||||
selectionMatch: '#3A546E',
|
||||
gutterBackground: '#303841',
|
||||
gutterForeground: '#FFFFFF70',
|
||||
lineHighlight: '#00000059',
|
||||
},
|
||||
tokyoNightDay: {
|
||||
light: true,
|
||||
background: '#e1e2e7',
|
||||
lineBackground: '#e1e2e799',
|
||||
foreground: '#3760bf',
|
||||
caret: '#3760bf',
|
||||
selection: '#99a7df',
|
||||
selectionMatch: '#99a7df',
|
||||
gutterBackground: '#e1e2e7',
|
||||
gutterForeground: '#3760bf',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#5f5faf11',
|
||||
},
|
||||
tokyoNightStorm: {
|
||||
background: '#24283b',
|
||||
lineBackground: '#24283b99',
|
||||
foreground: '#7982a9',
|
||||
caret: '#c0caf5',
|
||||
selection: '#6f7bb630',
|
||||
selectionMatch: '#1f2335',
|
||||
gutterBackground: '#24283b',
|
||||
gutterForeground: '#7982a9',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#292e42',
|
||||
},
|
||||
tokyoNight: {
|
||||
background: '#1a1b26',
|
||||
lineBackground: '#1a1b2699',
|
||||
foreground: '#787c99',
|
||||
caret: '#c0caf5',
|
||||
selection: '#515c7e40',
|
||||
selectionMatch: '#16161e',
|
||||
gutterBackground: '#1a1b26',
|
||||
gutterForeground: '#787c99',
|
||||
gutterBorder: 'transparent',
|
||||
lineHighlight: '#1e202e',
|
||||
},
|
||||
vscodeDark: {
|
||||
background: '#1e1e1e',
|
||||
lineBackground: '#1e1e1e99',
|
||||
foreground: '#9cdcfe',
|
||||
caret: '#c6c6c6',
|
||||
selection: '#6199ff2f',
|
||||
selectionMatch: '#72a1ff59',
|
||||
lineHighlight: '#ffffff0f',
|
||||
gutterBackground: '#1e1e1e',
|
||||
gutterForeground: '#838383',
|
||||
gutterActiveForeground: '#fff',
|
||||
},
|
||||
xcodeLight: {
|
||||
light: true,
|
||||
background: '#fff',
|
||||
lineBackground: '#ffffff99',
|
||||
foreground: '#3D3D3D',
|
||||
selection: '#BBDFFF',
|
||||
selectionMatch: '#BBDFFF',
|
||||
gutterBackground: '#fff',
|
||||
gutterForeground: '#AFAFAF',
|
||||
lineHighlight: '#EDF4FF',
|
||||
},
|
||||
xcodeDark: {
|
||||
background: '#292A30',
|
||||
lineBackground: '#292A3099',
|
||||
foreground: '#CECFD0',
|
||||
caret: '#fff',
|
||||
selection: '#727377',
|
||||
selectionMatch: '#727377',
|
||||
lineHighlight: '#2F3239',
|
||||
},
|
||||
};
|
||||
|
||||
function getColors(str) {
|
||||
const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
|
||||
const colors = [];
|
||||
|
||||
let match;
|
||||
while ((match = colorRegex.exec(str)) !== null) {
|
||||
const color = match[0];
|
||||
if (!colors.includes(color)) {
|
||||
colors.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
// TODO: remove
|
||||
export function themeColors(theme) {
|
||||
return getColors(stringifySafe(theme));
|
||||
}
|
||||
|
||||
function getCircularReplacer() {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (seen.has(value)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
}
|
||||
|
||||
function stringifySafe(json) {
|
||||
return JSON.stringify(json, getCircularReplacer());
|
||||
}
|
||||
|
||||
export function injectStyle(rule) {
|
||||
const newStyle = document.createElement('style');
|
||||
document.head.appendChild(newStyle);
|
||||
const styleSheet = newStyle.sheet;
|
||||
const ruleIndex = styleSheet.insertRule(rule, 0);
|
||||
return () => styleSheet.deleteRule(ruleIndex);
|
||||
}
|
||||
|
||||
export const theme = (theme) => themes[theme] || themes.strudelTheme;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#9bbc0f',
|
||||
foreground: '#0f380f', // whats that?
|
||||
caret: '#0f380f',
|
||||
selection: '#306230',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#8bac0f',
|
||||
lineBackground: '#9bbc0f50',
|
||||
//lineBackground: 'transparent',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#0f380f',
|
||||
light: true,
|
||||
customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#0f380f' },
|
||||
{ tag: t.operator, color: '#0f380f' },
|
||||
{ tag: t.special(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.typeName, color: '#0f380f' },
|
||||
{ tag: t.atom, color: '#0f380f' },
|
||||
{ tag: t.number, color: '#0f380f' },
|
||||
{ tag: t.definition(t.variableName), color: '#0f380f' },
|
||||
{ tag: t.string, color: '#0f380f' },
|
||||
{ tag: t.special(t.string), color: '#0f380f' },
|
||||
{ tag: t.comment, color: '#0f380f' },
|
||||
{ tag: t.variableName, color: '#0f380f' },
|
||||
{ tag: t.tagName, color: '#0f380f' },
|
||||
{ tag: t.bracket, color: '#0f380f' },
|
||||
{ tag: t.meta, color: '#0f380f' },
|
||||
{ tag: t.attributeName, color: '#0f380f' },
|
||||
{ tag: t.propertyName, color: '#0f380f' },
|
||||
{ tag: t.className, color: '#0f380f' },
|
||||
{ tag: t.invalid, color: '#0f380f' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#0f380f' },
|
||||
],
|
||||
});
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
lineBackground: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
],
|
||||
});
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: '#051DB5',
|
||||
lineBackground: '#051DB550',
|
||||
foreground: 'white', // whats that?
|
||||
caret: 'white',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'white' },
|
||||
{ tag: t.operator, color: 'white' },
|
||||
{ tag: t.special(t.variableName), color: 'white' },
|
||||
{ tag: t.typeName, color: 'white' },
|
||||
{ tag: t.atom, color: 'white' },
|
||||
{ tag: t.number, color: 'white' },
|
||||
{ tag: t.definition(t.variableName), color: 'white' },
|
||||
{ tag: t.string, color: 'white' },
|
||||
{ tag: t.special(t.string), color: 'white' },
|
||||
{ tag: t.comment, color: 'white' },
|
||||
{ tag: t.variableName, color: 'white' },
|
||||
{ tag: t.tagName, color: 'white' },
|
||||
{ tag: t.bracket, color: 'white' },
|
||||
{ tag: t.meta, color: 'white' },
|
||||
{ tag: t.attributeName, color: 'white' },
|
||||
{ tag: t.propertyName, color: 'white' },
|
||||
{ tag: t.className, color: 'white' },
|
||||
{ tag: t.invalid, color: 'white' },
|
||||
{ tag: [t.unit, t.punctuation], color: 'white' },
|
||||
],
|
||||
});
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
|
||||
|
||||
const chalky = '#e5c07b',
|
||||
coral = '#e06c75',
|
||||
cyan = '#56b6c2',
|
||||
invalid = '#ffffff',
|
||||
ivory = '#abb2bf',
|
||||
stone = '#7d8799', // Brightened compared to original to increase contrast
|
||||
malibu = '#61afef',
|
||||
sage = '#98c379',
|
||||
whiskey = '#d19a66',
|
||||
violet = '#c678dd',
|
||||
darkBackground = '#21252b',
|
||||
highlightBackground = '#2c313a',
|
||||
background = '#282c34',
|
||||
tooltipBackground = '#353a42',
|
||||
selection = '#3E4451',
|
||||
cursor = '#528bff';
|
||||
|
||||
/// The colors used in the theme, as CSS color strings.
|
||||
export const color = {
|
||||
chalky,
|
||||
coral,
|
||||
cyan,
|
||||
invalid,
|
||||
ivory,
|
||||
stone,
|
||||
malibu,
|
||||
sage,
|
||||
whiskey,
|
||||
violet,
|
||||
darkBackground,
|
||||
highlightBackground,
|
||||
background,
|
||||
tooltipBackground,
|
||||
selection,
|
||||
cursor,
|
||||
};
|
||||
|
||||
/// The editor theme styles for One Dark.
|
||||
export const oneDarkTheme = EditorView.theme(
|
||||
{
|
||||
'&': {
|
||||
color: ivory,
|
||||
backgroundColor: background,
|
||||
},
|
||||
|
||||
'.cm-content': {
|
||||
caretColor: cursor,
|
||||
},
|
||||
|
||||
'.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
|
||||
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
||||
{ backgroundColor: selection },
|
||||
|
||||
'.cm-panels': { backgroundColor: darkBackground, color: ivory },
|
||||
'.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
|
||||
'.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
|
||||
|
||||
'.cm-searchMatch': {
|
||||
backgroundColor: '#72a1ff59',
|
||||
outline: '1px solid #457dff',
|
||||
},
|
||||
'.cm-searchMatch.cm-searchMatch-selected': {
|
||||
backgroundColor: '#6199ff2f',
|
||||
},
|
||||
|
||||
'.cm-activeLine': { backgroundColor: '#6699ff0b' },
|
||||
'.cm-selectionMatch': { backgroundColor: '#aafe661a' },
|
||||
|
||||
'&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
|
||||
backgroundColor: '#bad0f847',
|
||||
},
|
||||
|
||||
'.cm-gutters': {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
border: 'none',
|
||||
},
|
||||
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: highlightBackground,
|
||||
},
|
||||
|
||||
'.cm-foldPlaceholder': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
color: '#ddd',
|
||||
},
|
||||
|
||||
'.cm-tooltip': {
|
||||
border: 'none',
|
||||
backgroundColor: tooltipBackground,
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:before': {
|
||||
borderTopColor: 'transparent',
|
||||
borderBottomColor: 'transparent',
|
||||
},
|
||||
'.cm-tooltip .cm-tooltip-arrow:after': {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground,
|
||||
},
|
||||
'.cm-tooltip-autocomplete': {
|
||||
'& > ul > li[aria-selected]': {
|
||||
backgroundColor: highlightBackground,
|
||||
color: ivory,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ dark: true },
|
||||
);
|
||||
|
||||
/// The highlighting style for code in the One Dark theme.
|
||||
export const oneDarkHighlightStyle = HighlightStyle.define([
|
||||
{ tag: t.keyword, color: violet },
|
||||
{ tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
|
||||
{ tag: [t.function(t.variableName), t.labelName], color: malibu },
|
||||
{ tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
|
||||
{ tag: [t.definition(t.name), t.separator], color: ivory },
|
||||
{ tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
|
||||
{ tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
|
||||
{ tag: [t.meta, t.comment], color: stone },
|
||||
{ tag: t.strong, fontWeight: 'bold' },
|
||||
{ tag: t.emphasis, fontStyle: 'italic' },
|
||||
{ tag: t.strikethrough, textDecoration: 'line-through' },
|
||||
{ tag: t.link, color: stone, textDecoration: 'underline' },
|
||||
{ tag: t.heading, fontWeight: 'bold', color: coral },
|
||||
{ tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
|
||||
{ tag: [t.processingInstruction, t.string, t.inserted], color: sage },
|
||||
{ tag: t.invalid, color: invalid },
|
||||
]);
|
||||
|
||||
/// Extension to enable the One Dark theme (both the editor theme and
|
||||
/// the highlight style).
|
||||
export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings: {
|
||||
background: '#222',
|
||||
foreground: '#75baff', // whats that?
|
||||
caret: '#ffcc00',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#036dd626',
|
||||
// lineHighlight: '#8a91991a', // original
|
||||
lineHighlight: '#00000050',
|
||||
gutterBackground: 'transparent',
|
||||
// gutterForeground: '#8a919966',
|
||||
gutterForeground: '#8a919966',
|
||||
},
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#c792ea' },
|
||||
{ tag: t.operator, color: '#89ddff' },
|
||||
{ tag: t.special(t.variableName), color: '#eeffff' },
|
||||
// { tag: t.typeName, color: '#f07178' }, // original
|
||||
{ tag: t.typeName, color: '#c3e88d' },
|
||||
{ tag: t.atom, color: '#f78c6c' },
|
||||
// { tag: t.number, color: '#ff5370' }, // original
|
||||
{ tag: t.number, color: '#c3e88d' },
|
||||
{ tag: t.definition(t.variableName), color: '#82aaff' },
|
||||
{ tag: t.string, color: '#c3e88d' },
|
||||
// { tag: t.special(t.string), color: '#f07178' }, // original
|
||||
{ tag: t.special(t.string), color: '#c3e88d' },
|
||||
{ tag: t.comment, color: '#7d8799' },
|
||||
// { tag: t.variableName, color: '#f07178' }, // original
|
||||
{ tag: t.variableName, color: '#c792ea' },
|
||||
// { tag: t.tagName, color: '#ff5370' }, // original
|
||||
{ tag: t.tagName, color: '#c3e88d' },
|
||||
{ tag: t.bracket, color: '#525154' },
|
||||
// { tag: t.bracket, color: '#a2a1a4' }, // original
|
||||
{ tag: t.meta, color: '#ffcb6b' },
|
||||
{ tag: t.attributeName, color: '#c792ea' },
|
||||
{ tag: t.propertyName, color: '#c792ea' },
|
||||
|
||||
{ tag: t.className, color: '#decb6b' },
|
||||
{ tag: t.invalid, color: '#ffffff' },
|
||||
{ tag: [t.unit, t.punctuation], color: '#82aaff' },
|
||||
],
|
||||
});
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
|
||||
let colorA = '#6edee4';
|
||||
//let colorB = 'magenta';
|
||||
let colorB = 'white';
|
||||
let colorC = 'red';
|
||||
let colorD = '#f8fc55';
|
||||
|
||||
export const settings = {
|
||||
background: '#000000',
|
||||
foreground: colorA, // whats that?
|
||||
caret: colorC,
|
||||
selection: colorD,
|
||||
selectionMatch: colorA,
|
||||
lineHighlight: '#6edee440', // panel bg
|
||||
lineBackground: '#00000040',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
customStyle: '.cm-line { line-height: 1 }',
|
||||
};
|
||||
|
||||
let punctuation = colorD;
|
||||
let mini = colorB;
|
||||
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: colorA },
|
||||
{ tag: t.operator, color: mini },
|
||||
{ tag: t.special(t.variableName), color: colorA },
|
||||
{ tag: t.typeName, color: colorA },
|
||||
{ tag: t.atom, color: colorA },
|
||||
{ tag: t.number, color: mini },
|
||||
{ tag: t.definition(t.variableName), color: colorA },
|
||||
{ tag: t.string, color: mini },
|
||||
{ tag: t.special(t.string), color: mini },
|
||||
{ tag: t.comment, color: punctuation },
|
||||
{ tag: t.variableName, color: colorA },
|
||||
{ tag: t.tagName, color: colorA },
|
||||
{ tag: t.bracket, color: punctuation },
|
||||
{ tag: t.meta, color: colorA },
|
||||
{ tag: t.attributeName, color: colorA },
|
||||
{ tag: t.propertyName, color: colorA }, // methods
|
||||
{ tag: t.className, color: colorA },
|
||||
{ tag: t.invalid, color: colorC },
|
||||
{ tag: [t.unit, t.punctuation], color: punctuation },
|
||||
],
|
||||
});
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'black',
|
||||
foreground: '#41FF00', // whats that?
|
||||
caret: '#41FF00',
|
||||
selection: '#ffffff20',
|
||||
selectionMatch: '#036dd626',
|
||||
lineHighlight: '#ffffff10',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: '#8a919966',
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'dark',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: '#41FF00' },
|
||||
{ tag: t.operator, color: '#41FF00' },
|
||||
{ tag: t.special(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.typeName, color: '#41FF00' },
|
||||
{ tag: t.atom, color: '#41FF00' },
|
||||
{ tag: t.number, color: '#41FF00' },
|
||||
{ tag: t.definition(t.variableName), color: '#41FF00' },
|
||||
{ tag: t.string, color: '#41FF00' },
|
||||
{ tag: t.special(t.string), color: '#41FF00' },
|
||||
{ tag: t.comment, color: '#41FF00' },
|
||||
{ tag: t.variableName, color: '#41FF00' },
|
||||
{ tag: t.tagName, color: '#41FF00' },
|
||||
{ tag: t.bracket, color: '#41FF00' },
|
||||
{ tag: t.meta, color: '#41FF00' },
|
||||
{ tag: t.attributeName, color: '#41FF00' },
|
||||
{ tag: t.propertyName, color: '#41FF00' },
|
||||
{ tag: t.className, color: '#41FF00' },
|
||||
{ tag: t.invalid, color: '#41FF00' },
|
||||
],
|
||||
});
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
import { createTheme } from '@uiw/codemirror-themes';
|
||||
export const settings = {
|
||||
background: 'white',
|
||||
foreground: 'black', // whats that?
|
||||
caret: 'black',
|
||||
selection: 'rgba(128, 203, 196, 0.5)',
|
||||
selectionMatch: '#ffffff26',
|
||||
lineHighlight: '#cccccc50',
|
||||
lineBackground: '#ffffff50',
|
||||
gutterBackground: 'transparent',
|
||||
gutterForeground: 'black',
|
||||
light: true,
|
||||
};
|
||||
export default createTheme({
|
||||
theme: 'light',
|
||||
settings,
|
||||
styles: [
|
||||
{ tag: t.keyword, color: 'black' },
|
||||
{ tag: t.operator, color: 'black' },
|
||||
{ tag: t.special(t.variableName), color: 'black' },
|
||||
{ tag: t.typeName, color: 'black' },
|
||||
{ tag: t.atom, color: 'black' },
|
||||
{ tag: t.number, color: 'black' },
|
||||
{ tag: t.definition(t.variableName), color: 'black' },
|
||||
{ tag: t.string, color: 'black' },
|
||||
{ tag: t.special(t.string), color: 'black' },
|
||||
{ tag: t.comment, color: 'black' },
|
||||
{ tag: t.variableName, color: 'black' },
|
||||
{ tag: t.tagName, color: 'black' },
|
||||
{ tag: t.bracket, color: 'black' },
|
||||
{ tag: t.meta, color: 'black' },
|
||||
{ tag: t.attributeName, color: 'black' },
|
||||
{ tag: t.propertyName, color: 'black' },
|
||||
{ tag: t.className, color: 'black' },
|
||||
{ tag: t.invalid, color: 'black' },
|
||||
],
|
||||
});
|
||||
@@ -146,7 +146,7 @@ export class Drawer {
|
||||
);
|
||||
}
|
||||
invalidate(scheduler = this.scheduler) {
|
||||
if (!scheduler) {
|
||||
if (!scheduler || !scheduler.pattern) {
|
||||
return;
|
||||
}
|
||||
this.scheduler = scheduler;
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
},
|
||||
"homepage": "https://strudel.cc",
|
||||
"dependencies": {
|
||||
"fraction.js": "^4.2.0"
|
||||
"fraction.js": "^4.2.0",
|
||||
"nanostores": "^0.8.1"
|
||||
},
|
||||
"gitHead": "0e26d4e741500f5bae35b023608f062a794905c2",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -120,6 +120,7 @@ export function pianoroll({
|
||||
hideInactive = 0,
|
||||
colorizeInactive = 1,
|
||||
fontFamily,
|
||||
clear = true,
|
||||
ctx,
|
||||
} = {}) {
|
||||
const w = ctx.canvas.width;
|
||||
@@ -165,7 +166,7 @@ export function pianoroll({
|
||||
barThickness = fold ? valueAxis / foldValues.length : valueAxis / valueExtent;
|
||||
ctx.fillStyle = background;
|
||||
ctx.globalAlpha = 1; // reset!
|
||||
if (!smear) {
|
||||
if (clear && !smear) {
|
||||
ctx.clearRect(0, 0, w, h);
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
}
|
||||
|
||||
+34
-2
@@ -4,6 +4,22 @@ import { logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
import { register } from './pattern.mjs';
|
||||
import { atom, computed } from 'nanostores';
|
||||
|
||||
export const $replstate = atom({
|
||||
schedulerError: undefined,
|
||||
evalError: undefined,
|
||||
code: '// LOADING',
|
||||
activeCode: '// LOADING',
|
||||
pattern: undefined,
|
||||
miniLocations: [],
|
||||
widgets: [],
|
||||
pending: true,
|
||||
started: false,
|
||||
});
|
||||
export const $repldirty = computed($replstate, (s) => s.code !== s.activeCode);
|
||||
|
||||
export const setReplState = (key, value) => $replstate.set({ ...$replstate.get(), [key]: value });
|
||||
|
||||
export function repl({
|
||||
interval,
|
||||
@@ -22,7 +38,10 @@ export function repl({
|
||||
onTrigger: getTrigger({ defaultOutput, getTime }),
|
||||
onError: onSchedulerError,
|
||||
getTime,
|
||||
onToggle,
|
||||
onToggle: (started) => {
|
||||
setReplState('started', started);
|
||||
onToggle?.(started);
|
||||
},
|
||||
});
|
||||
let playPatterns = [];
|
||||
const setPattern = (pattern, autostart = true) => {
|
||||
@@ -35,6 +54,8 @@ export function repl({
|
||||
throw new Error('no code to evaluate');
|
||||
}
|
||||
try {
|
||||
setReplState('code', code);
|
||||
setReplState('pending', true);
|
||||
await beforeEval?.({ code });
|
||||
playPatterns = [];
|
||||
let { pattern, meta } = await _evaluate(code, transpiler);
|
||||
@@ -43,17 +64,27 @@ export function repl({
|
||||
}
|
||||
logger(`[eval] code updated`);
|
||||
setPattern(pattern, autostart);
|
||||
setReplState('miniLocations', meta?.miniLocations || []);
|
||||
setReplState('widgets', meta?.widgets || []);
|
||||
setReplState('activeCode', code);
|
||||
setReplState('pattern', pattern);
|
||||
setReplState('evalError', undefined);
|
||||
setReplState('schedulerError', undefined);
|
||||
setReplState('pending', false);
|
||||
afterEval?.({ code, pattern, meta });
|
||||
return pattern;
|
||||
} catch (err) {
|
||||
// console.warn(`[repl] eval error: ${err.message}`);
|
||||
logger(`[eval] error: ${err.message}`, 'error');
|
||||
setReplState('evalError', err);
|
||||
setReplState('pending', false);
|
||||
onEvalError?.(err);
|
||||
}
|
||||
};
|
||||
const stop = () => scheduler.stop();
|
||||
const start = () => scheduler.start();
|
||||
const pause = () => scheduler.pause();
|
||||
const toggle = () => scheduler.toggle();
|
||||
const setCps = (cps) => scheduler.setCps(cps);
|
||||
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
||||
|
||||
@@ -87,7 +118,8 @@ export function repl({
|
||||
setcpm: setCpm,
|
||||
});
|
||||
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern };
|
||||
const setCode = (c) => setReplState('code', c);
|
||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle };
|
||||
}
|
||||
|
||||
export const getTrigger =
|
||||
|
||||
@@ -67,9 +67,9 @@ Pattern.prototype.spiral = function (options = {}) {
|
||||
// logSpiral = true,
|
||||
} = options;
|
||||
|
||||
function spiral({ ctx, time, haps, drawTime }) {
|
||||
function spiral({ ctx, time, haps, drawTime, clear }) {
|
||||
const [w, h] = [ctx.canvas.width, ctx.canvas.height];
|
||||
ctx.clearRect(0, 0, w * 2, h * 2);
|
||||
clear && ctx.clearRect(0, 0, w * 2, h * 2);
|
||||
const [cx, cy] = [w / 2, h / 2];
|
||||
const settings = {
|
||||
margin: size / stretch,
|
||||
@@ -114,5 +114,5 @@ Pattern.prototype.spiral = function (options = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
return this.onPaint((ctx, time, haps, drawTime) => spiral({ ctx, time, haps, drawTime }));
|
||||
return this.onPaint((ctx, time, haps, drawTime, options) => spiral({ ctx, time, haps, drawTime, ...options }));
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { controls, evalScope } from '@strudel.cycles/core';
|
||||
import { CodeMirror, useHighlighting, useKeydown, useStrudel, flash } from '@strudel.cycles/react';
|
||||
import { CodeMirror, useHighlighting, useStrudel, flash } from '@strudel.cycles/react';
|
||||
import {
|
||||
getAudioContext,
|
||||
initAudioOnFirstClick,
|
||||
@@ -93,32 +93,6 @@ function App() {
|
||||
});
|
||||
|
||||
const error = evalError || schedulerError;
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await evaluate(code);
|
||||
if (e.shiftKey) {
|
||||
panic();
|
||||
scheduler.stop();
|
||||
scheduler.start();
|
||||
}
|
||||
if (!scheduler.started) {
|
||||
scheduler.start();
|
||||
}
|
||||
} else if (e.code === 'Period') {
|
||||
scheduler.stop();
|
||||
panic();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
[scheduler, evaluate, view, code],
|
||||
),
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<nav className="z-[12] w-full flex justify-center fixed bottom-0">
|
||||
@@ -136,7 +110,28 @@ function App() {
|
||||
</div>
|
||||
{error && <p>error {error.message}</p>}
|
||||
</nav>
|
||||
<CodeMirror value={code} onChange={setCode} onViewChanged={setView} />
|
||||
<CodeMirror
|
||||
value={code}
|
||||
onChange={setCode}
|
||||
onViewChanged={setView}
|
||||
onEvaluate={async () => {
|
||||
flash(view);
|
||||
await evaluate(code);
|
||||
if (!scheduler.started) {
|
||||
scheduler.start();
|
||||
}
|
||||
}}
|
||||
onReEvaluate={async () => {
|
||||
await evaluate(code);
|
||||
panic();
|
||||
scheduler.stop();
|
||||
scheduler.start();
|
||||
}}
|
||||
onStop={() => {
|
||||
scheduler.stop();
|
||||
panic();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ export default function CodeMirror({
|
||||
onViewChanged,
|
||||
onSelectionChange,
|
||||
onDocChange,
|
||||
onEvaluate,
|
||||
onReEvaluate,
|
||||
onPanic,
|
||||
onStop,
|
||||
theme,
|
||||
keybindings,
|
||||
isLineNumbersDisplayed,
|
||||
@@ -95,19 +99,58 @@ export default function CodeMirror({
|
||||
} else {
|
||||
_extensions.push(autocompletion({ override: [] }));
|
||||
}
|
||||
|
||||
if (isTooltipEnabled) {
|
||||
_extensions.push(strudelTooltip);
|
||||
}
|
||||
|
||||
_extensions.push([keymap.of({})]);
|
||||
//_extensions.push([keymap.of({})]);
|
||||
|
||||
if (isLineWrappingEnabled) {
|
||||
_extensions.push(EditorView.lineWrapping);
|
||||
}
|
||||
|
||||
_extensions.push(
|
||||
keymap.of([
|
||||
{
|
||||
key: 'Ctrl-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-Enter',
|
||||
run: () => onEvaluate?.(),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-.',
|
||||
run: () => onStop?.(),
|
||||
},
|
||||
{
|
||||
key: 'Alt-.',
|
||||
run: (_, e) => {
|
||||
e.preventDefault();
|
||||
onStop?.();
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-.',
|
||||
run: () => (onPanic ? onPanic() : onStop?.()),
|
||||
},
|
||||
{
|
||||
key: 'Ctrl-Shift-Enter',
|
||||
run: () => (onReEvaluate ? onReEvaluate() : onEvaluate?.()),
|
||||
},
|
||||
]),
|
||||
);
|
||||
|
||||
return _extensions;
|
||||
}, [keybindings, isAutoCompletionEnabled, isTooltipEnabled, isLineWrappingEnabled]);
|
||||
}, [
|
||||
keybindings,
|
||||
isAutoCompletionEnabled,
|
||||
isTooltipEnabled,
|
||||
isLineWrappingEnabled,
|
||||
onEvaluate,
|
||||
onReEvaluate,
|
||||
onStop,
|
||||
onPanic,
|
||||
]);
|
||||
|
||||
const basicSetup = useMemo(() => ({ lineNumbers: isLineNumbersDisplayed }), [isLineNumbersDisplayed]);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Icon } from './Icon';
|
||||
import './style.css';
|
||||
import { logger } from '@strudel.cycles/core';
|
||||
import useEvent from '../hooks/useEvent.mjs';
|
||||
import useKeydown from '../hooks/useKeydown.mjs';
|
||||
|
||||
const getTime = () => getAudioContext().currentTime;
|
||||
|
||||
@@ -92,27 +91,6 @@ export function MiniRepl({
|
||||
getTime: () => scheduler.now(),
|
||||
});
|
||||
|
||||
// keyboard shortcuts
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (view?.hasFocus) {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await activateCode();
|
||||
} else if (e.key === '.' || e.code === 'Period') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[activateCode, stop, view],
|
||||
),
|
||||
);
|
||||
|
||||
const [log, setLog] = useState([]);
|
||||
useLogger(
|
||||
useCallback((e) => {
|
||||
@@ -164,6 +142,11 @@ export function MiniRepl({
|
||||
fontSize={fontSize}
|
||||
keybindings={keybindings}
|
||||
isLineNumbersDisplayed={isLineNumbersDisplayed}
|
||||
onEvaluate={() => {
|
||||
flash(view);
|
||||
activateCode();
|
||||
}}
|
||||
onStop={() => stop()}
|
||||
/>
|
||||
)}
|
||||
{error && <div className="text-right p-1 text-md text-red-200">{error.message}</div>}
|
||||
|
||||
Generated
+88
-1
@@ -71,6 +71,9 @@ importers:
|
||||
|
||||
packages/codemirror:
|
||||
dependencies:
|
||||
'@codemirror/autocomplete':
|
||||
specifier: ^6.6.0
|
||||
version: 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands':
|
||||
specifier: ^6.2.4
|
||||
version: 6.2.4
|
||||
@@ -80,6 +83,9 @@ importers:
|
||||
'@codemirror/language':
|
||||
specifier: ^6.6.0
|
||||
version: 6.6.0
|
||||
'@codemirror/search':
|
||||
specifier: ^6.0.0
|
||||
version: 6.5.4
|
||||
'@codemirror/state':
|
||||
specifier: ^6.2.0
|
||||
version: 6.2.0
|
||||
@@ -89,9 +95,27 @@ importers:
|
||||
'@lezer/highlight':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
'@replit/codemirror-emacs':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
'@replit/codemirror-vim':
|
||||
specifier: ^6.0.14
|
||||
version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
'@replit/codemirror-vscode-keymap':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
'@strudel.cycles/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@uiw/codemirror-themes':
|
||||
specifier: ^4.19.16
|
||||
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
'@uiw/codemirror-themes-all':
|
||||
specifier: ^4.19.16
|
||||
version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
@@ -102,6 +126,9 @@ importers:
|
||||
fraction.js:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
nanostores:
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1
|
||||
devDependencies:
|
||||
vite:
|
||||
specifier: ^4.3.3
|
||||
@@ -2475,6 +2502,14 @@ packages:
|
||||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
/@codemirror/search@6.5.4:
|
||||
resolution: {integrity: sha512-YoTrvjv9e8EbPs58opjZKyJ3ewFrVSUzQ/4WXlULQLSDDr1nGPJ67mMXFNNVYwdFhybzhrzrtqgHmtpJwIF+8g==}
|
||||
dependencies:
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.10.0
|
||||
crelt: 1.0.5
|
||||
dev: false
|
||||
|
||||
/@codemirror/state@6.2.0:
|
||||
resolution: {integrity: sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==}
|
||||
dev: false
|
||||
@@ -3963,6 +3998,22 @@ packages:
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-emacs@6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||
resolution: {integrity: sha512-2WYkODZGH1QVAXWuOxTMCwktkoZyv/BjYdJi2A5w4fRrmOQFuIACzb6pO9dgU3J+Pm2naeiX2C8veZr/3/r6AA==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.2
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/search': ^6.0.0
|
||||
'@codemirror/state': ^6.0.1
|
||||
'@codemirror/view': ^6.3.0
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands': 6.2.4
|
||||
'@codemirror/search': 6.5.4
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
|
||||
peerDependencies:
|
||||
@@ -3979,6 +4030,22 @@ packages:
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-vim@6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||
resolution: {integrity: sha512-wwhqhvL76FdRTdwfUWpKCbv0hkp2fvivfMosDVlL/popqOiNLtUhL02ThgHZH8mus/NkVr5Mj582lyFZqQrjOA==}
|
||||
peerDependencies:
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/language': ^6.1.0
|
||||
'@codemirror/search': ^6.2.0
|
||||
'@codemirror/state': ^6.0.1
|
||||
'@codemirror/view': ^6.0.3
|
||||
dependencies:
|
||||
'@codemirror/commands': 6.2.4
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/search': 6.5.4
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||
resolution: {integrity: sha512-j45qTwGxzpsv82lMD/NreGDORFKSctMDVkGRopaP+OrzSzv+pXDQuU3LnFvKpasyjVT0lf+PKG1v2DSCn/vxxg==}
|
||||
peerDependencies:
|
||||
@@ -3999,6 +4066,26 @@ packages:
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.5.4)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0):
|
||||
resolution: {integrity: sha512-j45qTwGxzpsv82lMD/NreGDORFKSctMDVkGRopaP+OrzSzv+pXDQuU3LnFvKpasyjVT0lf+PKG1v2DSCn/vxxg==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.0
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/lint': ^6.0.0
|
||||
'@codemirror/search': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
|
||||
'@codemirror/commands': 6.2.4
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/lint': 6.1.0
|
||||
'@codemirror/search': 6.5.4
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.10.0
|
||||
dev: false
|
||||
|
||||
/@rollup/plugin-babel@5.3.1(@babel/core@7.23.2)(rollup@2.79.1):
|
||||
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
@@ -6120,7 +6207,7 @@ packages:
|
||||
'@codemirror/commands': 6.2.4
|
||||
'@codemirror/language': 6.6.0
|
||||
'@codemirror/lint': 6.1.0
|
||||
'@codemirror/search': 6.2.3
|
||||
'@codemirror/search': 6.5.4
|
||||
'@codemirror/state': 6.2.0
|
||||
'@codemirror/view': 6.10.0
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -1,32 +1,14 @@
|
||||
import { evalScope, controls, noteToMidi } from '@strudel.cycles/core';
|
||||
import { initAudioOnFirstClick } from '@strudel.cycles/webaudio';
|
||||
import { noteToMidi } from '@strudel.cycles/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { prebake } from '../repl/prebake';
|
||||
import { themes, settings } from '../repl/themes.mjs';
|
||||
import { themes } from '../repl/themes.mjs';
|
||||
import './MiniRepl.css';
|
||||
import { useSettings } from '../settings.mjs';
|
||||
import Claviature from '@components/Claviature';
|
||||
|
||||
let modules;
|
||||
let init;
|
||||
if (typeof window !== 'undefined') {
|
||||
modules = evalScope(
|
||||
controls,
|
||||
import('@strudel.cycles/core'),
|
||||
import('@strudel.cycles/tonal'),
|
||||
import('@strudel.cycles/mini'),
|
||||
import('@strudel.cycles/midi'),
|
||||
import('@strudel.cycles/xen'),
|
||||
import('@strudel.cycles/webaudio'),
|
||||
import('@strudel.cycles/osc'),
|
||||
import('@strudel.cycles/csound'),
|
||||
import('@strudel.cycles/soundfonts'),
|
||||
import('@strudel/hydra'),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
initAudioOnFirstClick();
|
||||
prebake();
|
||||
init = prebake();
|
||||
}
|
||||
|
||||
export function MiniRepl({
|
||||
@@ -46,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'), modules])
|
||||
Promise.all([import('@strudel.cycles/react'), init])
|
||||
.then(([res]) => setRepl(() => res.MiniRepl))
|
||||
.catch((err) => console.error(err));
|
||||
}, []);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
import HeadCommon from '../components/HeadCommon.astro';
|
||||
import VanillaRepl from '../repl/VanillaRepl.astro';
|
||||
---
|
||||
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<HeadCommon />
|
||||
<title>Strudel Vanilla REPL</title>
|
||||
</head>
|
||||
<body class="h-app-height bg-background">
|
||||
<VanillaRepl />
|
||||
</body>
|
||||
</html>
|
||||
@@ -385,6 +385,8 @@ function SettingsTab({ scheduler }) {
|
||||
keybindings,
|
||||
isLineNumbersDisplayed,
|
||||
isAutoCompletionEnabled,
|
||||
isFlashEnabled,
|
||||
isPatternHighlightingEnabled,
|
||||
isTooltipEnabled,
|
||||
isLineWrappingEnabled,
|
||||
fontSize,
|
||||
@@ -468,6 +470,16 @@ function SettingsTab({ scheduler }) {
|
||||
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
|
||||
value={isLineWrappingEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable pattern highlighting"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)}
|
||||
value={isPatternHighlightingEnabled}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Enable flashing on update"
|
||||
onChange={(cbEvent) => settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
|
||||
value={isFlashEnabled}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label="Zen Mode">Try clicking the logo in the top left!</FormItem>
|
||||
<FormItem label="Reset Settings">
|
||||
|
||||
+22
-68
@@ -4,22 +4,21 @@ 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, controls, evalScope, getDrawContext, logger } 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 { cleanupDraw, cleanupUi, logger, getDrawContext } from '@strudel.cycles/core';
|
||||
import { CodeMirror, cx, flash, useHighlighting, useStrudel } from '@strudel.cycles/react';
|
||||
import { getAudioContext, initAudioOnFirstClick, 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 { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
import { prebake } from './prebake.mjs';
|
||||
import { prebake, resetSounds } 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 { settingPatterns } from '../settings.mjs';
|
||||
import { code2hash, hash2code } from './helpers.mjs';
|
||||
import { isTauri } from '../tauri.mjs';
|
||||
import { useWidgets } from '@strudel.cycles/react/src/hooks/useWidgets.mjs';
|
||||
@@ -35,41 +34,7 @@ const supabase = createClient(
|
||||
'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 init = prebake();
|
||||
|
||||
const getTime = () => getAudioContext().currentTime;
|
||||
|
||||
@@ -141,7 +106,7 @@ export function Repl({ embedded = false }) {
|
||||
getTime,
|
||||
beforeEval: async () => {
|
||||
setPending(true);
|
||||
await modulesLoading;
|
||||
await init;
|
||||
cleanupUi();
|
||||
cleanupDraw();
|
||||
},
|
||||
@@ -163,7 +128,7 @@ export function Repl({ embedded = false }) {
|
||||
window.postMessage('strudel-start');
|
||||
}
|
||||
},
|
||||
drawContext,
|
||||
drawContext: getDrawContext(),
|
||||
// drawTime: [0, 6],
|
||||
paintOptions,
|
||||
});
|
||||
@@ -187,29 +152,6 @@ export function Repl({ embedded = false }) {
|
||||
});
|
||||
}, []);
|
||||
|
||||
// keyboard shortcuts
|
||||
useKeydown(
|
||||
useCallback(
|
||||
async (e) => {
|
||||
if (e.ctrlKey || e.altKey) {
|
||||
if (e.code === 'Enter') {
|
||||
if (getAudioContext().state !== 'running') {
|
||||
alert('please click play to initialize the audio. you can use shortcuts after that!');
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
flash(view);
|
||||
await activateCode();
|
||||
} else if (e.key === '.' || e.code === 'Period') {
|
||||
stop();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
[activateCode, stop, view],
|
||||
),
|
||||
);
|
||||
|
||||
// highlighting
|
||||
const { setMiniLocations } = useHighlighting({
|
||||
view,
|
||||
@@ -252,10 +194,9 @@ export function Repl({ embedded = false }) {
|
||||
const handleShuffle = async () => {
|
||||
const { code, name } = getRandomTune();
|
||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||
clearCanvas();
|
||||
resetLoadedSounds();
|
||||
cleanupDraw();
|
||||
await resetSounds();
|
||||
scheduler.setCps(1);
|
||||
await prebake(); // declare default samples
|
||||
await evaluate(code, false);
|
||||
};
|
||||
|
||||
@@ -307,6 +248,17 @@ export function Repl({ embedded = false }) {
|
||||
setView(v);
|
||||
}, []);
|
||||
|
||||
// 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 onStop = () => stop();
|
||||
|
||||
return (
|
||||
// bg-gradient-to-t from-blue-900 to-slate-900
|
||||
// bg-gradient-to-t from-green-900 to-slate-900
|
||||
@@ -343,6 +295,8 @@ export function Repl({ embedded = false }) {
|
||||
onChange={handleChangeCode}
|
||||
onViewChanged={handleViewChanged}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onEvaluate={onEvaluate}
|
||||
onStop={onStop}
|
||||
/>
|
||||
</section>
|
||||
{panelPosition === 'right' && !isEmbedded && <Footer context={context} />}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
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 PlayCircleIcon from '@heroicons/react/20/solid/PlayCircleIcon';
|
||||
import { getDrawContext, logger, $replstate, $repldirty } from '@strudel.cycles/core';
|
||||
import { cx } from '@strudel.cycles/react';
|
||||
import { getAudioContext } from '@strudel.cycles/webaudio';
|
||||
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 { Footer } from './Footer';
|
||||
import { Header } from './Header';
|
||||
import Loader from './Loader';
|
||||
import './Repl.css';
|
||||
import { resetSounds } from './prebake.mjs';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { getRandomTune } from './helpers.mjs';
|
||||
|
||||
export const ReplContext = createContext(null);
|
||||
|
||||
export function Repl({ 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);
|
||||
|
||||
//
|
||||
// UI Actions
|
||||
//
|
||||
|
||||
const handleTogglePlay = async () => {
|
||||
window.postMessage('strudel-toggle-play');
|
||||
/* 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,
|
||||
};
|
||||
|
||||
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={() => window.postMessage('strudel-container')}
|
||||
>
|
||||
{/* <CodeMirror {...codemirror} /> */}
|
||||
</section>
|
||||
{panelPosition === 'right' && !isEmbedded && <Footer context={context} />}
|
||||
</div>
|
||||
{error && (
|
||||
<div className="text-red-500 p-4 bg-lineHighlight animate-pulse">{error.message || 'Unknown Error :-/'}</div>
|
||||
)}
|
||||
{panelPosition === 'bottom' && !isEmbedded && <Footer context={context} />}
|
||||
</div>
|
||||
</ReplContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
import HeadCommon from '../components/HeadCommon.astro';
|
||||
import { Repl } from '../repl/Repl2.jsx';
|
||||
---
|
||||
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<HeadCommon />
|
||||
<title>Strudel REPL</title>
|
||||
</head>
|
||||
<body class="h-app-height bg-background">
|
||||
<Repl client:only="react" />
|
||||
<script src="./vanillarepl.mjs"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as tunes from './tunes.mjs';
|
||||
|
||||
export function unicodeToBase64(text) {
|
||||
const utf8Bytes = new TextEncoder().encode(text);
|
||||
const base64String = btoa(String.fromCharCode(...utf8Bytes));
|
||||
@@ -23,3 +25,10 @@ export function hash2code(hash) {
|
||||
return base64ToUnicode(decodeURIComponent(hash));
|
||||
//return atob(decodeURIComponent(codeParam || ''));
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { Pattern, noteToMidi, valueToMidi } from '@strudel.cycles/core';
|
||||
import { registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
|
||||
import { Pattern, noteToMidi, valueToMidi, controls, evalScope } from '@strudel.cycles/core';
|
||||
import { initAudioOnFirstClick, registerSynthSounds, registerZZFXSounds, samples } from '@strudel.cycles/webaudio';
|
||||
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() {
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
await Promise.all([
|
||||
export function registerStockSounds() {
|
||||
return Promise.all([
|
||||
registerSynthSounds(),
|
||||
registerZZFXSounds(),
|
||||
//registerSoundfonts(),
|
||||
@@ -14,6 +15,8 @@ export async function prebake() {
|
||||
// => getting "window is not defined", as soon as "@strudel.cycles/soundfonts" is imported statically
|
||||
// seems to be a problem with soundfont2
|
||||
import('@strudel.cycles/soundfonts').then(({ registerSoundfonts }) => registerSoundfonts()),
|
||||
// https://archive.org/details/SalamanderGrandPianoV3
|
||||
// License: CC-by http://creativecommons.org/licenses/by/3.0/ Author: Alexander Holm
|
||||
samples(`./piano.json`, `./piano/`, { prebake: true }),
|
||||
// https://github.com/sgossner/VCSL/
|
||||
// https://api.github.com/repositories/126427031/contents/
|
||||
@@ -111,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({});
|
||||
return registerStockSounds();
|
||||
};
|
||||
|
||||
const maxPan = noteToMidi('C8');
|
||||
const panwidth = (pan, width) => pan * width + (1 - width) / 2;
|
||||
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/* 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,135 @@
|
||||
import { logger, getDrawContext, silence } from '@strudel.cycles/core';
|
||||
import { StrudelMirror } from '@strudel/codemirror';
|
||||
import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||
import { transpiler } from '@strudel.cycles/transpiler';
|
||||
import { prebake, resetSounds } from './prebake.mjs';
|
||||
import { settingsMap } from '@src/settings.mjs';
|
||||
import { setLatestCode } from '../settings.mjs';
|
||||
import { hash2code, code2hash } from './helpers.mjs';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { getRandomTune } from './helpers.mjs';
|
||||
|
||||
const supabase = createClient(
|
||||
'https://pidxdsxphlhzjnzmifth.supabase.co',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
|
||||
);
|
||||
|
||||
async function initCodeFromUrl() {
|
||||
// await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
// 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', error);
|
||||
}
|
||||
if (data.length) {
|
||||
//console.log('load hash from database', hash);
|
||||
return data[0].code;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('failed to decode', err);
|
||||
}
|
||||
}
|
||||
const initialCode = initCodeFromUrl();
|
||||
|
||||
async function run() {
|
||||
const container = document.getElementById('code');
|
||||
if (!container) {
|
||||
console.warn('could not init: no container found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
|
||||
const settings = settingsMap.get();
|
||||
|
||||
const drawContext = getDrawContext();
|
||||
const drawTime = [-2, 2];
|
||||
const editor = new StrudelMirror({
|
||||
defaultOutput: webaudioOutput,
|
||||
getTime: () => getAudioContext().currentTime,
|
||||
transpiler,
|
||||
root: container,
|
||||
initialCode: '// LOADING',
|
||||
pattern: silence,
|
||||
settings,
|
||||
drawTime,
|
||||
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 });
|
||||
});
|
||||
},
|
||||
prebake: () => prebake(),
|
||||
afterEval: ({ code }) => {
|
||||
setLatestCode(code);
|
||||
window.location.hash = '#' + code2hash(code);
|
||||
},
|
||||
});
|
||||
|
||||
// init settings
|
||||
editor.updateSettings(settings);
|
||||
const decoded = await initialCode;
|
||||
let msg;
|
||||
if (decoded) {
|
||||
editor.setCode(decoded);
|
||||
msg = `I have loaded the code from the URL.`;
|
||||
} else if (settings.latestCode) {
|
||||
editor.setCode(settings.latestCode);
|
||||
msg = `Your last session has been loaded!`;
|
||||
} else {
|
||||
const { code: randomTune, name } = getRandomTune();
|
||||
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);
|
||||
|
||||
settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
|
||||
|
||||
onEvent('strudel-toggle-play', () => editor.toggle());
|
||||
onEvent('strudel-shuffle', async () => {
|
||||
const { code, name } = getRandomTune();
|
||||
logger(`[repl] ✨ loading random tune "${name}"`);
|
||||
console.log(code);
|
||||
editor.setCode(code);
|
||||
await resetSounds();
|
||||
editor.repl.setCps(1);
|
||||
editor.repl.evaluate(code, false);
|
||||
});
|
||||
|
||||
// const isEmbedded = embedded || window.location !== window.parent.location;
|
||||
}
|
||||
|
||||
let inited = false;
|
||||
onEvent('strudel-container', () => {
|
||||
if (!inited) {
|
||||
inited = true;
|
||||
run();
|
||||
}
|
||||
});
|
||||
|
||||
function onEvent(key, callback) {
|
||||
const listener = (e) => {
|
||||
if (e.data === key) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
window.addEventListener('message', listener);
|
||||
return () => window.removeEventListener('message', listener);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/* 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;
|
||||
}
|
||||
*/
|
||||
@@ -7,6 +7,8 @@ export const defaultSettings = {
|
||||
keybindings: 'codemirror',
|
||||
isLineNumbersDisplayed: true,
|
||||
isAutoCompletionEnabled: false,
|
||||
isPatternHighlightingEnabled: true,
|
||||
isFlashEnabled: true,
|
||||
isTooltipEnabled: false,
|
||||
isLineWrappingEnabled: false,
|
||||
theme: 'strudelTheme',
|
||||
@@ -29,6 +31,8 @@ export function useSettings() {
|
||||
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
|
||||
isTooltipEnabled: [true, 'true'].includes(state.isTooltipEnabled) ? true : false,
|
||||
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
|
||||
isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false,
|
||||
isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,
|
||||
fontSize: Number(state.fontSize),
|
||||
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom',
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"paths": {
|
||||
"@components/*": ["src/components/*"],
|
||||
"@src/*": ["src/*"],
|
||||
"~/*": ["/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user