allow any web component to become a widget

This commit is contained in:
Felix Roos 2024-03-15 00:27:16 +01:00
parent 29dab578e7
commit 82c4926f19
5 changed files with 36 additions and 46 deletions

View File

@ -1,18 +1,14 @@
export * from './Claviature.jsx'; export * from './Claviature.jsx';
import { Pattern } from '@strudel/core'; import { Pattern } from '@strudel/core';
import { registerWidget } from '@strudel/transpiler';
Pattern.prototype.claviature = function (options = {}) { registerWidget('claviature', 'strudel-claviature');
if (!window.claviature) {
window.claviature = document.createElement('strudel-claviature'); Pattern.prototype.claviature = function (id, options = {}) {
window.claviature.style.position = 'absolute';
window.claviature.style.bottom = 0;
window.claviature.style.left = 0;
document.body.append(window.claviature);
}
return this.onFrame((haps) => { return this.onFrame((haps) => {
const keys = haps.map((h) => h.value.note); const keys = haps.map((h) => h.value.note);
// console.log('keys',keys); let el = document.getElementById(id);
window.claviature.setAttribute( el?.setAttribute(
'options', 'options',
JSON.stringify({ JSON.stringify({
...options, ...options,

View File

@ -28,6 +28,7 @@
"homepage": "https://github.com/tidalcycles/strudel#readme", "homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": { "dependencies": {
"@strudel/core": "workspace:*", "@strudel/core": "workspace:*",
"@strudel/transpiler": "workspace:*",
"claviature": "^0.1.0", "claviature": "^0.1.0",
"solid-element": "^1.8.0", "solid-element": "^1.8.0",
"solid-js": "^1.8.15", "solid-js": "^1.8.15",

View File

@ -1,27 +1,7 @@
import { StateEffect, StateField } from '@codemirror/state'; import { StateEffect, StateField } from '@codemirror/state';
import { Decoration, EditorView, WidgetType } from '@codemirror/view'; import { Decoration, EditorView, WidgetType } from '@codemirror/view';
import { Pattern } from '@strudel/core';
const getWidgetID = (from) => `ui_${from}`; const getWidgetID = (from) => `widget_${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({ export const addWidget = StateEffect.define({
map: ({ from, to }, change) => { map: ({ from, to }, change) => {
@ -33,10 +13,10 @@ export const updateWidgets = (view, widgets) => {
view.dispatch({ effects: addWidget.of(widgets) }); view.dispatch({ effects: addWidget.of(widgets) });
}; };
function getWidgets(widgetConfigs, view) { function getWidgets(widgetConfigs) {
return widgetConfigs.map(({ from, to }) => { return widgetConfigs.map(({ to, type }) => {
return Decoration.widget({ return Decoration.widget({
widget: new BlockWidget(view, from), widget: new BlockWidget(to, type),
side: 0, side: 0,
block: true, block: true,
}).range(to); }).range(to);
@ -69,20 +49,19 @@ const widgetField = StateField.define(
); );
export class BlockWidget extends WidgetType { export class BlockWidget extends WidgetType {
constructor(view, from) { constructor(col, type) {
super(); super();
this.view = view; this.col = col;
this.from = from; this.type = type;
} }
eq() { eq() {
return true; return true;
} }
toDOM() { toDOM() {
const id = getWidgetID(this.from); // matches id generated in transpiler const id = getWidgetID(this.col); // matches id generated in transpiler
let el = document.getElementById(id); let el = document.getElementById(id);
if (!el) { if (!el) {
// TODO: make this work with any web component el = document.createElement(this.type);
el = document.createElement('strudel-claviature');
el.id = id; el.id = id;
} }
return el; return el;

View File

@ -3,6 +3,18 @@ import { parse } from 'acorn';
import escodegen from 'escodegen'; import escodegen from 'escodegen';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
let widgetComponents = {};
// this function allows registering a pattern method name and component name
// e.g. register('claviature', 'strudel-claviature')
// .. will map the Pattern method 'claviature' to the web component 'strudel-claviature'
// the transpiler will turn .claviature(...args) into .claviature(id, ...args)
// the widgetPlugin of @strudel/codemirror will automatically create an instance of 'strudel-claviature'
// .. so you only have to implement the actual .claviature method (or what you've called it..)
export function registerWidget(name, tagName) {
widgetComponents[name] = tagName;
}
export function transpiler(input, options = {}) { export function transpiler(input, options = {}) {
const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options; const { wrapAsync = false, addReturn = true, emitMiniLocations = true, emitWidgets = true } = options;
@ -47,12 +59,11 @@ export function transpiler(input, options = {}) {
}); });
return this.replace(sliderWithLocation(node)); return this.replace(sliderWithLocation(node));
} }
if (isUIFunction(node)) { if (isWidgetMethod(node)) {
emitWidgets && emitWidgets &&
widgets.push({ widgets.push({
from: node.arguments[0].start,
to: node.end, to: node.end,
type: node.arguments[0].value, type: widgetComponents[node.callee.property.name],
}); });
return this.replace(widgetWithLocation(node)); return this.replace(widgetWithLocation(node));
} }
@ -119,8 +130,8 @@ function isSliderFunction(node) {
return node.type === 'CallExpression' && node.callee.name === 'slider'; return node.type === 'CallExpression' && node.callee.name === 'slider';
} }
function isUIFunction(node) { function isWidgetMethod(node) {
return node.type === 'CallExpression' && node.callee.property?.name === 'ui'; return node.type === 'CallExpression' && Object.keys(widgetComponents).includes(node.callee.property?.name);
} }
function sliderWithLocation(node) { function sliderWithLocation(node) {
@ -137,7 +148,7 @@ function sliderWithLocation(node) {
} }
function widgetWithLocation(node) { function widgetWithLocation(node) {
const id = 'ui_' + node.arguments[0].start; // use loc of first arg for id const id = 'widget_' + node.end;
// add loc as identifier to first argument // add loc as identifier to first argument
// the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?) // the sliderWithID function is assumed to be sliderWithID(id, value, min?, max?)
node.arguments.unshift({ node.arguments.unshift({

3
pnpm-lock.yaml generated
View File

@ -145,6 +145,9 @@ importers:
'@strudel/core': '@strudel/core':
specifier: workspace:* specifier: workspace:*
version: link:../core version: link:../core
'@strudel/transpiler':
specifier: workspace:*
version: link:../transpiler
claviature: claviature:
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0