-
-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
431 additions
and
155 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './codemirror.mjs'; | ||
export * from './highlight.mjs'; | ||
export * from './flash.mjs'; | ||
export * from './slider.mjs'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { ref, pure } from '@strudel.cycles/core'; | ||
import { WidgetType, ViewPlugin, Decoration } from '@codemirror/view'; | ||
import { StateEffect, StateField } from '@codemirror/state'; | ||
|
||
export let sliderValues = {}; | ||
const getSliderID = (from) => `slider_${from}`; | ||
|
||
export class SliderWidget extends WidgetType { | ||
constructor(value, min, max, from, to, view) { | ||
super(); | ||
this.value = value; | ||
this.min = min; | ||
this.max = max; | ||
this.from = from; | ||
this.originalFrom = from; | ||
this.to = to; | ||
this.view = view; | ||
} | ||
|
||
eq() { | ||
return false; | ||
} | ||
|
||
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.originalValue = this.value; | ||
// to make sure the code stays in sync, let's save the original value | ||
// becuase .value automatically clamps values so it'll desync with the code | ||
slider.value = slider.originalValue; | ||
slider.from = this.from; | ||
slider.originalFrom = this.originalFrom; | ||
slider.to = this.to; | ||
slider.style = 'width:64px;margin-right:4px;transform:translateY(4px)'; | ||
this.slider = slider; | ||
slider.addEventListener('input', (e) => { | ||
const next = e.target.value; | ||
let insert = next; | ||
//let insert = next.toFixed(2); | ||
const to = slider.from + slider.originalValue.length; | ||
let change = { from: slider.from, to, insert }; | ||
slider.originalValue = insert; | ||
slider.value = insert; | ||
this.view.dispatch({ changes: change }); | ||
const id = getSliderID(slider.originalFrom); // matches id generated in transpiler | ||
window.postMessage({ type: 'cm-slider', value: Number(next), id }); | ||
}); | ||
return wrap; | ||
} | ||
|
||
ignoreEvent(e) { | ||
return true; | ||
} | ||
} | ||
|
||
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 }) => { | ||
return Decoration.widget({ | ||
widget: new SliderWidget(value, min, max, from, to, view), | ||
side: 0, | ||
}).range(from /* , to */); | ||
}); | ||
} | ||
|
||
export const sliderPlugin = ViewPlugin.fromClass( | ||
class { | ||
decorations; //: DecorationSet | ||
|
||
constructor(view /* : EditorView */) { | ||
this.decorations = Decoration.set([]); | ||
} | ||
|
||
update(update /* : ViewUpdate */) { | ||
update.transactions.forEach((tr) => { | ||
if (tr.docChanged) { | ||
this.decorations = this.decorations.map(tr.changes); | ||
const iterator = this.decorations.iter(); | ||
while (iterator.value) { | ||
// when the widgets are moved, we need to tell the dom node the current position | ||
// this is important because the updateSliderValue function has to work with the dom node | ||
iterator.value.widget.slider.from = iterator.from; | ||
iterator.value.widget.slider.to = iterator.to; | ||
iterator.next(); | ||
} | ||
} | ||
for (let e of tr.effects) { | ||
if (e.is(setWidgets)) { | ||
this.decorations = Decoration.set(getWidgets(e.value, update.view)); | ||
} | ||
} | ||
}); | ||
} | ||
}, | ||
{ | ||
decorations: (v) => v.decorations, | ||
}, | ||
); | ||
|
||
export let slider = (value) => { | ||
console.warn('slider will only work when the transpiler is used... passing value as is'); | ||
return pure(value); | ||
}; | ||
// function transpiled from slider = (value, min, max) | ||
export let sliderWithID = (id, value, min, max) => { | ||
sliderValues[id] = value; // sync state at eval time (code -> state) | ||
return ref(() => sliderValues[id]); // use state at query time | ||
}; | ||
// update state when sliders are moved | ||
if (typeof window !== 'undefined') { | ||
window.addEventListener('message', (e) => { | ||
if (e.data.type === 'cm-slider') { | ||
if (sliderValues[e.data.id] !== undefined) { | ||
// update state when slider is moved | ||
sliderValues[e.data.id] = e.data.value; | ||
} else { | ||
console.warn(`slider with id "${e.data.id}" is not registered. Only ${Object.keys(sliderValues)}`); | ||
} | ||
} | ||
}); | ||
} |
Oops, something went wrong.