1import { 2 initEditor, 3 codemirrorSettings, 4 flash, 5 compartments, 6 extensions, 7 parseBooleans, 8 activateTheme, 9 updateMiniLocations, 10 highlightMiniLocations, 11} from '@strudel/codemirror'; 12import { evalScope, hash2code, code2hash } from '@strudel/core'; 13import { Framer } from '@strudel/draw'; 14import { persistentAtom } from '@nanostores/persistent'; 15import { DoughRepl } from './dough-repl.mjs'; 16 17let initialCode = '$: note("c a f e")'; 18 19export const code = persistentAtom('vanilla-repl-code', initialCode, { 20 encode: JSON.stringify, 21 decode: JSON.parse, 22}); 23 24if (typeof window !== 'undefined') { 25 try { 26 const codeParam = window.location.href.split('#')[1] || ''; 27 if (codeParam) { 28 const codeFromHash = hash2code(codeParam); 29 code.set(codeFromHash); 30 } 31 } catch (err) { 32 console.error('could not init code from url'); 33 } 34} 35 36export class DoughMirror { 37 constructor(options) { 38 const { root, initialCode = code.get(), bgFill = true } = options; 39 this.root = root; 40 this.code = initialCode; 41 this.repl = new DoughRepl(); 42 this.prebaked = this.prebake(); 43 44 // init codemirror 45 this.editor = initEditor({ 46 root, 47 initialCode: this.code, 48 onChange: (v) => { 49 if (v.docChanged) { 50 this.code = v.state.doc.toString(); 51 code.set(this.code); 52 } 53 }, 54 onEvaluate: this.evaluate.bind(this), 55 onStop: () => this.stop(), 56 mondo: false, 57 }); 58 const settings = codemirrorSettings.get(); 59 this.setFontSize(settings.fontSize); 60 this.setFontFamily(settings.fontFamily); 61 62 // init event highlighting 63 this.framer = new Framer( 64 (time) => { 65 const frameHaps = this.repl.processHaps(); 66 highlightMiniLocations(this.editor, time, frameHaps); 67 }, 68 (err) => console.log('Framer error', err), 69 ); 70 } 71 prebake() { 72 const modulesLoading = evalScope(import('@strudel/core'), import('@strudel/tonal'), import('@strudel/mini')); 73 return Promise.all([modulesLoading, this.repl.prebake()]); 74 } 75 async evaluate() { 76 this.framer.start(); 77 this.flash(); 78 await this.prebaked; 79 const { miniLocations } = await this.repl.evaluate(this.code); 80 window.location.hash = '#' + code2hash(this.code); 81 updateMiniLocations(this.editor, miniLocations); 82 } 83 stop() { 84 this.repl.stop(); 85 this.framer.stop(); 86 highlightMiniLocations(this.editor, 0, []); 87 } 88 // added synonym (compared to StrudelMirror) 89 settings(settings = {}) { 90 this.updateSettings(settings); 91 } 92 93 // the rest is copy pasted from StrudelMirror: 94 95 updateSettings(settings = {}) { 96 settings.fontSize && this.setFontSize(settings.fontSize); 97 settings.fontFamily && this.setFontFamily(settings.fontFamily); 98 for (let key in extensions) { 99 if (key in settings) { 100 this.reconfigureExtension(key, settings[key]); 101 } 102 } 103 const updated = { ...codemirrorSettings.get(), ...settings }; 104 // console.log(updated); 105 codemirrorSettings.set(updated); 106 } 107 reconfigureExtension(key, value) { 108 if (!extensions[key]) { 109 console.warn(`extension ${key} is not known`); 110 return; 111 } 112 value = parseBooleans(value); 113 const newValue = extensions[key](value, this); 114 this.editor.dispatch({ 115 effects: compartments[key].reconfigure(newValue), 116 }); 117 if (key === 'theme') { 118 activateTheme(value); 119 } 120 } 121 flash(ms) { 122 flash(this.editor, ms); 123 } 124 setFontSize(size) { 125 this.root.style.fontSize = size + 'px'; 126 } 127 setFontFamily(family) { 128 this.root.style.fontFamily = family; 129 const scroller = this.root.querySelector('.cm-scroller'); 130 if (scroller) { 131 scroller.style.fontFamily = family; 132 } 133 } 134 setCode(code, offset = 0) { 135 const changes = { 136 from: 0, 137 to: this.editor.state.doc.length + offset, 138 insert: code, 139 }; 140 this.editor.dispatch({ changes }); 141 } 142 getCursorLocation() { 143 return this.editor.state.selection.main.head; 144 } 145 setCursorLocation(col) { 146 return this.editor.dispatch({ selection: { anchor: col } }); 147 } 148 appendCode(code) { 149 const cursor = this.getCursorLocation(); 150 this.setCode(this.code + code); 151 this.setCursorLocation(cursor); 152 } 153}