Files
gods-eye-view/src/ui/styleParameters.js
T

115 lines
3.6 KiB
JavaScript

import { h, render, withDirectives } from 'vue';
import { formatNumber, t } from '../i18n/index.js';
import { vRangeValue } from './rail/rangeValue.js';
/** A slider value in the UI language: 0.85 in English, 0,85 in Spanish. */
const formatParam = (value, digits) =>
formatNumber(value, {
minimumFractionDigits: digits,
maximumFractionDigits: digits,
useGrouping: false,
});
/**
* The generated rows: a label, the shared native slider and its value in the
* UI language. A render function, so the portable input package needs no
* single-file-component compiler.
*/
const StyleParameterRows = {
props: {
rows: { type: Array, required: true },
onInput: { type: Function, required: true },
},
render() {
return this.rows.map((row) =>
h('div', { key: row.name, class: 'param-slider-row' }, [
h('span', { class: 'param-label' }, row.label),
withDirectives(
h('input', {
type: 'range',
class: 'gev-quantitative-slider param-slider',
min: row.min,
max: row.max,
step: row.step,
'aria-label': row.label,
onInput: (event) => this.onInput(row.name, event.target.value),
}),
[[vRangeValue, row.value]],
),
h('span', { class: 'param-value' }, row.text),
]),
);
},
};
/**
* Own generated parameter rows and their input handling. Renders
* StyleParameterRows into `container` and patches it synchronously.
* The caller supplies uniform metadata/value access and rendering/persistence
* callbacks. Replacing or destroying rows revokes all old input handlers.
* @param {object} options
* @param {HTMLElement} options.container Container owned by this component.
* @returns {{render: Function, clear: Function, destroy: Function}}
*/
export function createStyleParameters({ container }) {
let rows = [];
let onInput = () => {};
// Each render's handlers act only while that render is current.
let generation = 0;
let destroyed = false;
const paint = () =>
render(h(StyleParameterRows, { rows, onInput }), container);
const clear = () => {
if (destroyed) return;
generation++;
rows = [];
onInput = () => {};
render(null, container);
};
return {
/** Render uniform metadata with readValue, writeValue and onChange callbacks. */
render({ uniforms, readValue, writeValue, onChange }) {
if (destroyed) return;
clear();
const current = generation;
rows = Object.entries(uniforms).map(([name, metadata]) => ({
name,
label: t(metadata.label),
min: metadata.min,
max: metadata.max,
step: metadata.max <= 1 ? '0.01' : '0.1',
digits: metadata.max <= 1 ? 2 : 1,
value: readValue(name),
text: '',
}));
onInput = (name, raw) => {
if (destroyed || current !== generation) return;
const value = parseFloat(raw);
writeValue(name, value);
rows = rows.map((row) =>
row.name === name
? { ...row, value, text: formatParam(value, row.digits) }
: row,
);
paint();
onChange();
};
paint();
// The readout shows the value the slider holds once the browser has
// clamped and snapped it to its step.
const sliders = container.querySelectorAll('input.param-slider');
rows = rows.map((row, index) => ({
...row,
text: formatParam(parseFloat(sliders[index].value), row.digits),
}));
paint();
},
clear,
destroy() {
if (destroyed) return;
clear();
destroyed = true;
},
};
}