mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
can now load claviature as a codemirror widget
This commit is contained in:
parent
1e75f045b7
commit
29dab578e7
@ -20,7 +20,8 @@ import { flash, isFlashEnabled } from './flash.mjs';
|
||||
import { highlightMiniLocations, isPatternHighlightingEnabled, updateMiniLocations } from './highlight.mjs';
|
||||
import { keybindings } from './keybindings.mjs';
|
||||
import { initTheme, activateTheme, theme } from './themes.mjs';
|
||||
import { updateWidgets, sliderPlugin } from './slider.mjs';
|
||||
import { sliderPlugin, updateSliderWidgets } from './slider.mjs';
|
||||
import { widgetPlugin, updateWidgets } from './widget.mjs';
|
||||
import { persistentAtom } from '@nanostores/persistent';
|
||||
|
||||
const extensions = {
|
||||
@ -72,6 +73,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo
|
||||
...initialSettings,
|
||||
javascript(),
|
||||
sliderPlugin,
|
||||
widgetPlugin,
|
||||
// indentOnInput(), // works without. already brought with javascript extension?
|
||||
// bracketMatching(), // does not do anything
|
||||
closeBrackets(),
|
||||
@ -187,7 +189,10 @@ export class StrudelMirror {
|
||||
// remember for when highlighting is toggled on
|
||||
this.miniLocations = options.meta?.miniLocations;
|
||||
this.widgets = options.meta?.widgets;
|
||||
updateWidgets(this.editor, this.widgets);
|
||||
const sliders = this.widgets.filter((w) => w.type === 'slider');
|
||||
updateSliderWidgets(this.editor, sliders);
|
||||
const widgets = this.widgets.filter((w) => w.type !== 'slider');
|
||||
updateWidgets(this.editor, widgets);
|
||||
updateMiniLocations(this.editor, this.miniLocations);
|
||||
replOptions?.afterEval?.(options);
|
||||
this.adjustDrawTime();
|
||||
|
||||
@ -3,3 +3,4 @@ export * from './highlight.mjs';
|
||||
export * from './flash.mjs';
|
||||
export * from './slider.mjs';
|
||||
export * from './themes.mjs';
|
||||
export * from './widget.mjs';
|
||||
|
||||
@ -1,10 +1,27 @@
|
||||
import { ref, pure } from '@strudel/core';
|
||||
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
import { StateEffect } from '@codemirror/state';
|
||||
|
||||
export const setSliderWidgets = StateEffect.define();
|
||||
|
||||
export const updateSliderWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: setSliderWidgets.of(widgets) });
|
||||
};
|
||||
|
||||
export let sliderValues = {};
|
||||
const getSliderID = (from) => `slider_${from}`;
|
||||
|
||||
function getSliders(widgetConfigs, view) {
|
||||
return widgetConfigs
|
||||
.filter((w) => w.type === 'slider')
|
||||
.map(({ from, to, value, min, max, step }) => {
|
||||
return Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, from, to, step, view),
|
||||
side: 0,
|
||||
}).range(from /* , to */);
|
||||
});
|
||||
}
|
||||
|
||||
export class SliderWidget extends WidgetType {
|
||||
constructor(value, min, max, from, to, step, view) {
|
||||
super();
|
||||
@ -60,21 +77,6 @@ export class SliderWidget extends WidgetType {
|
||||
}
|
||||
}
|
||||
|
||||
export const setWidgets = StateEffect.define();
|
||||
|
||||
export const updateWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: setWidgets.of(widgets) });
|
||||
};
|
||||
|
||||
function getWidgets(widgetConfigs, view) {
|
||||
return widgetConfigs.map(({ from, to, value, min, max, step }) => {
|
||||
return Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, from, to, step, view),
|
||||
side: 0,
|
||||
}).range(from /* , to */);
|
||||
});
|
||||
}
|
||||
|
||||
export const sliderPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
@ -99,8 +101,8 @@ export const sliderPlugin = ViewPlugin.fromClass(
|
||||
}
|
||||
}
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(setWidgets)) {
|
||||
this.decorations = Decoration.set(getWidgets(e.value, update.view));
|
||||
if (e.is(setSliderWidgets)) {
|
||||
this.decorations = Decoration.set(getSliders(e.value, update.view));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
95
packages/codemirror/widget.mjs
Normal file
95
packages/codemirror/widget.mjs
Normal file
@ -0,0 +1,95 @@
|
||||
import { StateEffect, StateField } from '@codemirror/state';
|
||||
import { Decoration, EditorView, WidgetType } from '@codemirror/view';
|
||||
import { Pattern } from '@strudel/core';
|
||||
|
||||
const getWidgetID = (from) => `ui_${from}`;
|
||||
|
||||
Pattern.prototype.ui = function (id, value) {
|
||||
// TODO: make this work with any web component
|
||||
return this.onFrame((haps) => {
|
||||
let el = document.getElementById(id);
|
||||
if (el) {
|
||||
let options = {};
|
||||
const keys = haps.map((h) => h.value.note);
|
||||
el.setAttribute(
|
||||
'options',
|
||||
JSON.stringify({
|
||||
...options,
|
||||
range: options.range || ['A2', 'C6'],
|
||||
colorize: [{ keys: keys, color: options.color || 'steelblue' }],
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const addWidget = StateEffect.define({
|
||||
map: ({ from, to }, change) => {
|
||||
return { from: change.mapPos(from), to: change.mapPos(to) };
|
||||
},
|
||||
});
|
||||
|
||||
export const updateWidgets = (view, widgets) => {
|
||||
view.dispatch({ effects: addWidget.of(widgets) });
|
||||
};
|
||||
|
||||
function getWidgets(widgetConfigs, view) {
|
||||
return widgetConfigs.map(({ from, to }) => {
|
||||
return Decoration.widget({
|
||||
widget: new BlockWidget(view, from),
|
||||
side: 0,
|
||||
block: true,
|
||||
}).range(to);
|
||||
});
|
||||
}
|
||||
|
||||
const widgetField = StateField.define(
|
||||
/* <DecorationSet> */ {
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(widgets, tr) {
|
||||
widgets = widgets.map(tr.changes);
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(addWidget)) {
|
||||
try {
|
||||
widgets = widgets.update({
|
||||
filter: () => false,
|
||||
add: getWidgets(e.value),
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('err', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
return widgets;
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
},
|
||||
);
|
||||
|
||||
export class BlockWidget extends WidgetType {
|
||||
constructor(view, from) {
|
||||
super();
|
||||
this.view = view;
|
||||
this.from = from;
|
||||
}
|
||||
eq() {
|
||||
return true;
|
||||
}
|
||||
toDOM() {
|
||||
const id = getWidgetID(this.from); // matches id generated in transpiler
|
||||
let el = document.getElementById(id);
|
||||
if (!el) {
|
||||
// TODO: make this work with any web component
|
||||
el = document.createElement('strudel-claviature');
|
||||
el.id = id;
|
||||
}
|
||||
return el;
|
||||
}
|
||||
ignoreEvent(e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const widgetPlugin = [widgetField];
|
||||
@ -34,7 +34,7 @@ export function transpiler(input, options = {}) {
|
||||
emitMiniLocations && collectMiniLocations(value, node);
|
||||
return this.replace(miniWithLocation(value, node));
|
||||
}
|
||||
if (isWidgetFunction(node)) {
|
||||
if (isSliderFunction(node)) {
|
||||
emitWidgets &&
|
||||
widgets.push({
|
||||
from: node.arguments[0].start,
|
||||
@ -43,6 +43,16 @@ export function transpiler(input, options = {}) {
|
||||
min: node.arguments[1]?.value ?? 0,
|
||||
max: node.arguments[2]?.value ?? 1,
|
||||
step: node.arguments[3]?.value,
|
||||
type: 'slider',
|
||||
});
|
||||
return this.replace(sliderWithLocation(node));
|
||||
}
|
||||
if (isUIFunction(node)) {
|
||||
emitWidgets &&
|
||||
widgets.push({
|
||||
from: node.arguments[0].start,
|
||||
to: node.end,
|
||||
type: node.arguments[0].value,
|
||||
});
|
||||
return this.replace(widgetWithLocation(node));
|
||||
}
|
||||
@ -105,11 +115,15 @@ function miniWithLocation(value, node) {
|
||||
|
||||
// these functions are connected to @strudel/codemirror -> slider.mjs
|
||||
// maybe someday there will be pluggable transpiler functions, then move this there
|
||||
function isWidgetFunction(node) {
|
||||
function isSliderFunction(node) {
|
||||
return node.type === 'CallExpression' && node.callee.name === 'slider';
|
||||
}
|
||||
|
||||
function widgetWithLocation(node) {
|
||||
function isUIFunction(node) {
|
||||
return node.type === 'CallExpression' && node.callee.property?.name === 'ui';
|
||||
}
|
||||
|
||||
function sliderWithLocation(node) {
|
||||
const id = 'slider_' + node.arguments[0].start; // use loc of first arg for id
|
||||
// add loc as identifier to first argument
|
||||
// the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
|
||||
@ -122,6 +136,18 @@ function widgetWithLocation(node) {
|
||||
return node;
|
||||
}
|
||||
|
||||
function widgetWithLocation(node) {
|
||||
const id = 'ui_' + node.arguments[0].start; // use loc of first arg for id
|
||||
// add loc as identifier to first argument
|
||||
// the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
|
||||
node.arguments.unshift({
|
||||
type: 'Literal',
|
||||
value: id,
|
||||
raw: id,
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
function isBareSamplesCall(node, parent) {
|
||||
return node.type === 'CallExpression' && node.callee.name === 'samples' && parent.type !== 'AwaitExpression';
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user