Skip to content

Commit

Permalink
Merge pull request #866 from tidalcycles/vanilla-repl-2
Browse files Browse the repository at this point in the history
more work on vanilla repl: repl web component + package
  • Loading branch information
felixroos committed Dec 25, 2023
2 parents 466574a + de4460a commit 8b4be4d
Show file tree
Hide file tree
Showing 29 changed files with 971 additions and 747 deletions.
74 changes: 63 additions & 11 deletions packages/codemirror/codemirror.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import { Pattern, Drawer, repl, cleanupDraw } from '@strudel.cycles/core';
import { flash, isFlashEnabled } from './flash.mjs';
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
import { keybindings } from './keybindings.mjs';
import { theme } from './themes.mjs';
import { initTheme, activateTheme, theme } from './themes.mjs';
import { updateWidgets, sliderPlugin } from './slider.mjs';
import { persistentAtom } from '@nanostores/persistent';

const extensions = {
isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
Expand All @@ -25,11 +26,32 @@ const extensions = {
};
const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));

export const defaultSettings = {
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isActiveLineHighlighted: false,
isAutoCompletionEnabled: false,
isPatternHighlightingEnabled: true,
isFlashEnabled: true,
isTooltipEnabled: false,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
fontSize: 18,
};

export const codemirrorSettings = persistentAtom('codemirror-settings', defaultSettings, {
encode: JSON.stringify,
decode: JSON.parse,
});

// https://codemirror.net/docs/guide/
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root }) {
const settings = codemirrorSettings.get();
const initialSettings = Object.keys(compartments).map((key) =>
compartments[key].of(extensions[key](parseBooleans(settings[key]))),
);
initTheme(settings.theme);
let state = EditorState.create({
doc: initialCode,
extensions: [
Expand Down Expand Up @@ -86,36 +108,44 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, set

export class StrudelMirror {
constructor(options) {
const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
const { root, id, initialCode = '', onDraw, drawTime = [-2, 2], autodraw, prebake, ...replOptions } = options;
this.code = initialCode;
this.root = root;
this.miniLocations = [];
this.widgets = [];
this.painters = [];
this.onDraw = onDraw;
const self = this;
this.id = id || s4();

this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
this.highlight(currentFrame, time);
this.onDraw?.(haps, time, currentFrame, this.painters);
}, drawTime);

// this approach might not work with multiple repls on screen..
// this approach does not work with multiple repls on screen
// TODO: refactor onPaint usages + find fix, maybe remove painters here?
Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint);
return this;
};

this.prebaked = prebake();
// this.drawFirstFrame();
autodraw && this.drawFirstFrame();

this.repl = repl({
...replOptions,
onToggle: (started) => {
replOptions?.onToggle?.(started);
if (started) {
this.drawer.start(this.repl.scheduler);
// stop other repls when this one is started
document.dispatchEvent(
new CustomEvent('start-repl', {
detail: this.id,
}),
);
} else {
this.drawer.stop();
updateMiniLocations(this.editor, []);
Expand All @@ -140,23 +170,29 @@ export class StrudelMirror {
});
this.editor = initEditor({
root,
settings,
initialCode,
onChange: (v) => {
if (v.docChanged) {
this.code = v.state.doc.toString();
// TODO: repl is still untouched to make sure the old Repl.jsx stays untouched..
// this.repl.setCode(this.code);
this.repl.setCode?.(this.code);
}
},
onEvaluate: () => this.evaluate(),
onStop: () => this.stop(),
});
const cmEditor = this.root.querySelector('.cm-editor');
if (cmEditor) {
this.root.style.display = 'block';
this.root.style.backgroundColor = 'var(--background)';
cmEditor.style.backgroundColor = 'transparent';
}
// stop this repl when another repl is started
this.onStartRepl = (e) => {
if (e.detail !== this.id) {
this.stop();
}
};
document.addEventListener('start-repl', this.onStartRepl);
}
async drawFirstFrame() {
if (!this.onDraw) {
Expand All @@ -166,8 +202,9 @@ export class StrudelMirror {
await this.prebaked;
try {
await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler);
this.onDraw?.(this.drawer.visibleHaps, 0, []);
this.drawer.invalidate(this.repl.scheduler, -0.001);
// draw at -0.001 to avoid haps at 0 to be visualized as active
this.onDraw?.(this.drawer.visibleHaps, -0.001, [], this.painters);
} catch (err) {
console.warn('first frame could not be painted');
}
Expand All @@ -181,7 +218,7 @@ export class StrudelMirror {
}
async toggle() {
if (this.repl.scheduler.started) {
this.repl.scheduler.stop();
this.repl.stop();
} else {
this.evaluate();
}
Expand Down Expand Up @@ -212,6 +249,9 @@ export class StrudelMirror {
this.editor.dispatch({
effects: compartments[key].reconfigure(newValue),
});
if (key === 'theme') {
activateTheme(value);
}
}
setLineWrappingEnabled(enabled) {
this.reconfigureExtension('isLineWrappingEnabled', enabled);
Expand All @@ -231,6 +271,8 @@ export class StrudelMirror {
for (let key in extensions) {
this.reconfigureExtension(key, settings[key]);
}
const updated = { ...codemirrorSettings.get(), ...settings };
codemirrorSettings.set(updated);
}
changeSetting(key, value) {
if (extensions[key]) {
Expand All @@ -246,8 +288,18 @@ export class StrudelMirror {
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
this.editor.dispatch({ changes });
}
clear() {
this.onStartRepl && document.removeEventListener('start-repl', this.onStartRepl);
}
}

function parseBooleans(value) {
return { true: true, false: false }[value] ?? value;
}

// helper function to generate repl ids
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
24 changes: 0 additions & 24 deletions packages/codemirror/examples/strudelmirror/.gitignore

This file was deleted.

87 changes: 0 additions & 87 deletions packages/codemirror/examples/strudelmirror/index.html

This file was deleted.

0 comments on commit 8b4be4d

Please sign in to comment.