mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-14 07:08:34 +00:00
checkbox + number slider
This commit is contained in:
parent
8a741952df
commit
aa094bf930
87
packages/codemirror/checkbox.mjs
Normal file
87
packages/codemirror/checkbox.mjs
Normal file
@ -0,0 +1,87 @@
|
||||
import { WidgetType } from '@codemirror/view';
|
||||
import { ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
|
||||
export class CheckboxWidget extends WidgetType {
|
||||
constructor(checked) {
|
||||
super();
|
||||
this.checked = checked;
|
||||
}
|
||||
|
||||
eq(other) {
|
||||
return other.checked == this.checked;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let wrap = document.createElement('span');
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.className = 'cm-boolean-toggle';
|
||||
let box = wrap.appendChild(document.createElement('input'));
|
||||
box.type = 'checkbox';
|
||||
box.checked = this.checked;
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ignoreEvent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// EditorView
|
||||
export function checkboxes(view) {
|
||||
let widgets = [];
|
||||
for (let { from, to } of view.visibleRanges) {
|
||||
syntaxTree(view.state).iterate({
|
||||
from,
|
||||
to,
|
||||
enter: (node) => {
|
||||
if (node.name == 'BooleanLiteral') {
|
||||
let isTrue = view.state.doc.sliceString(node.from, node.to) == 'true';
|
||||
let deco = Decoration.widget({
|
||||
widget: new CheckboxWidget(isTrue),
|
||||
side: 1,
|
||||
});
|
||||
widgets.push(deco.range(node.from));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
return Decoration.set(widgets);
|
||||
}
|
||||
|
||||
export const checkboxPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
|
||||
constructor(view /* : EditorView */) {
|
||||
this.decorations = checkboxes(view);
|
||||
}
|
||||
|
||||
update(update /* : ViewUpdate */) {
|
||||
if (update.docChanged || update.viewportChanged) this.decorations = checkboxes(update.view);
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
|
||||
eventHandlers: {
|
||||
mousedown: (e, view) => {
|
||||
let target = e.target; /* as HTMLElement */
|
||||
if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-boolean-toggle'))
|
||||
return toggleBoolean(view, view.posAtDOM(target));
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function toggleBoolean(view /* : EditorView */, pos /* : number */) {
|
||||
let before = view.state.doc.sliceString(Math.max(0, pos), pos + 5).trim();
|
||||
let change;
|
||||
if (!['true', 'false'].includes(before)) {
|
||||
return false;
|
||||
}
|
||||
let insert = before === 'true' ? 'false' : 'true';
|
||||
change = { from: pos, to: pos + before.length, insert };
|
||||
view.dispatch({ changes: change });
|
||||
return true;
|
||||
}
|
||||
129
packages/codemirror/slider.mjs
Normal file
129
packages/codemirror/slider.mjs
Normal file
@ -0,0 +1,129 @@
|
||||
import { WidgetType } from '@codemirror/view';
|
||||
import { ViewPlugin, Decoration } from '@codemirror/view';
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
|
||||
export class SliderWidget extends WidgetType {
|
||||
constructor(value, min, max, from, to) {
|
||||
super();
|
||||
this.value = value;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
eq(other) {
|
||||
const isSame = other.value.toFixed(4) == this.value.toFixed(4);
|
||||
return isSame;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
let wrap = document.createElement('span');
|
||||
wrap.setAttribute('aria-hidden', 'true');
|
||||
wrap.className = 'cm-slider'; // inline-flex items-center
|
||||
let slider = wrap.appendChild(document.createElement('input'));
|
||||
slider.type = 'range';
|
||||
slider.min = this.min;
|
||||
slider.max = this.max;
|
||||
slider.step = (this.max - this.min) / 1000;
|
||||
slider.value = this.value;
|
||||
slider.from = this.from;
|
||||
slider.to = this.to;
|
||||
slider.className = 'w-16';
|
||||
return wrap;
|
||||
}
|
||||
|
||||
ignoreEvent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// EditorView
|
||||
export function sliders(view) {
|
||||
let widgets = [];
|
||||
for (let { from, to } of view.visibleRanges) {
|
||||
syntaxTree(view.state).iterate({
|
||||
from,
|
||||
to,
|
||||
enter: (node) => {
|
||||
if (node.name == 'Number') {
|
||||
let value = view.state.doc.sliceString(node.from, node.to);
|
||||
value = Number(value);
|
||||
/* let min = Math.min(0, value);
|
||||
let max = Math.max(value, 1); */
|
||||
let min = 0;
|
||||
let max = 10;
|
||||
//console.log('from', node.from, 'to', node.to);
|
||||
let deco = Decoration.widget({
|
||||
widget: new SliderWidget(value, min, max, node.from, node.to),
|
||||
side: 1,
|
||||
});
|
||||
widgets.push(deco.range(node.from));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
return Decoration.set(widgets);
|
||||
}
|
||||
|
||||
let draggedSlider, init;
|
||||
export const sliderPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations; //: DecorationSet
|
||||
|
||||
constructor(view /* : EditorView */) {
|
||||
this.decorations = sliders(view);
|
||||
}
|
||||
|
||||
update(update /* : ViewUpdate */) {
|
||||
if (update.docChanged || update.viewportChanged) {
|
||||
!init && (this.decorations = sliders(update.view));
|
||||
//init = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
|
||||
eventHandlers: {
|
||||
mousedown: (e, view) => {
|
||||
let target = e.target; /* as HTMLElement */
|
||||
if (target.nodeName == 'INPUT' && target.parentElement.classList.contains('cm-slider')) {
|
||||
draggedSlider = target;
|
||||
// remember offsetLeft / clientWidth, as they will vanish inside mousemove events for some reason
|
||||
draggedSlider._offsetLeft = draggedSlider.offsetLeft;
|
||||
draggedSlider._clientWidth = draggedSlider.clientWidth;
|
||||
return updateSliderValue(view, e);
|
||||
}
|
||||
},
|
||||
mouseup: () => {
|
||||
draggedSlider = undefined;
|
||||
},
|
||||
mousemove: (e, view) => {
|
||||
draggedSlider && updateSliderValue(view, e);
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function updateSliderValue(view, e) {
|
||||
const mouseX = e.clientX;
|
||||
let progress = (mouseX - draggedSlider._offsetLeft) / draggedSlider._clientWidth;
|
||||
progress = Math.max(Math.min(1, progress), 0);
|
||||
let min = Number(draggedSlider.min);
|
||||
let max = Number(draggedSlider.max);
|
||||
const next = Number(progress * (max - min) + min);
|
||||
let insert = next.toFixed(2);
|
||||
let before = view.state.doc.sliceString(draggedSlider.from, draggedSlider.to).trim();
|
||||
before = Number(before).toFixed(4);
|
||||
if (before === next) {
|
||||
return false;
|
||||
}
|
||||
//console.log('before', before, '->', insert);
|
||||
let change = { from: draggedSlider.from, to: draggedSlider.to, insert };
|
||||
draggedSlider.to = draggedSlider.from + insert.length;
|
||||
//console.log('change', change);
|
||||
view.dispatch({ changes: change });
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -15,10 +15,12 @@ import {
|
||||
updateMiniLocations,
|
||||
} from '@strudel/codemirror';
|
||||
import './style.css';
|
||||
import { checkboxPlugin } from '@strudel/codemirror/checkbox.mjs';
|
||||
import { sliderPlugin } from '@strudel/codemirror/slider.mjs';
|
||||
|
||||
export { flash, highlightMiniLocations, updateMiniLocations };
|
||||
|
||||
const staticExtensions = [javascript(), flashField, highlightExtension];
|
||||
const staticExtensions = [javascript(), flashField, highlightExtension, checkboxPlugin, sliderPlugin];
|
||||
|
||||
export default function CodeMirror({
|
||||
value,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user