120 lines
4.1 KiB
TypeScript
120 lines
4.1 KiB
TypeScript
/* The free-variable sliders both calculators wear: one value per variable
|
|
in a scope the compile pass reads, one slider per variable anchored under
|
|
the row that claims it — the row declaring it ("k", "k = 2") when one
|
|
asks, else the first row speaking it — and the values kept between visits
|
|
per calculator. A name the scope does not carry yet reads as 1, where
|
|
every slider starts. */
|
|
|
|
export interface VarSlider {
|
|
name: string
|
|
value: number
|
|
min: number
|
|
max: number
|
|
}
|
|
|
|
/* one row's reading, as far as sliders care: the variables it speaks and
|
|
the declaration its text claims (a null start says "k", not "k = 2") */
|
|
export interface RowVarDecls {
|
|
rowId: number
|
|
vars: string[]
|
|
declared: { name: string; value: number | null } | null
|
|
}
|
|
|
|
/* how far a slider reaches either side of zero; a value set past an end —
|
|
by "k = 40" or by typing — pulls that end out so the thumb can reach it */
|
|
const SLIDER_MIN = -10
|
|
const SLIDER_MAX = 10
|
|
|
|
export function useVarSliders(
|
|
varValues: Record<string, number>,
|
|
decls: () => RowVarDecls[],
|
|
storageKey: string,
|
|
) {
|
|
/* every variable spoken, with the value its "k = 2" row asks to start at
|
|
— or 1, where sliders start. One variable wears one slider however many
|
|
rows speak it. */
|
|
const varStarts = computed(() => {
|
|
const starts = new Map<string, number>()
|
|
for (const d of decls()) {
|
|
if (d.declared && !starts.has(d.declared.name)) starts.set(d.declared.name, d.declared.value ?? 1)
|
|
for (const name of d.vars) if (!starts.has(name)) starts.set(name, 1)
|
|
}
|
|
return starts
|
|
})
|
|
|
|
// a variable the rows only now started speaking takes its start value;
|
|
// one the reader already moved keeps the value they left it at. Filling
|
|
// from the first pass keeps what the server draws and what the reader
|
|
// finds after mounting the same
|
|
function fillDefaults() {
|
|
for (const [name, value] of varStarts.value) {
|
|
if (!(name in varValues)) varValues[name] = value
|
|
}
|
|
}
|
|
watch(varStarts, fillDefaults, { immediate: true })
|
|
|
|
/* one slider per variable, filed under the row it sits beneath: the row
|
|
claiming it ("k", "k = 2") wins, else the first row that speaks it */
|
|
const slidersByRow = computed(() => {
|
|
const anchor = new Map<string, number>()
|
|
for (const d of decls()) {
|
|
if (d.declared && !anchor.has(d.declared.name)) anchor.set(d.declared.name, d.rowId)
|
|
}
|
|
for (const d of decls()) {
|
|
for (const name of d.vars) {
|
|
if (!anchor.has(name)) anchor.set(name, d.rowId)
|
|
}
|
|
}
|
|
const byRow = new Map<number, VarSlider[]>()
|
|
for (const d of decls()) {
|
|
for (const name of d.vars) {
|
|
if (anchor.get(name) !== d.rowId) continue
|
|
const value = varValues[name] ?? 1
|
|
const list = byRow.get(d.rowId) ?? []
|
|
list.push({ name, value, min: Math.min(SLIDER_MIN, value), max: Math.max(SLIDER_MAX, value) })
|
|
byRow.set(d.rowId, list)
|
|
}
|
|
}
|
|
return byRow
|
|
})
|
|
|
|
function setVar(name: string, raw: number) {
|
|
if (Number.isFinite(raw)) varValues[name] = raw
|
|
}
|
|
|
|
/* the values keep themselves — only the ones a row still speaks, so a
|
|
variable nobody asks for anymore is forgotten on the next save rather
|
|
than haunting the store */
|
|
function save() {
|
|
try {
|
|
const kept: Record<string, number> = {}
|
|
for (const [name, value] of Object.entries(varValues)) {
|
|
if (varStarts.value.has(name) && Number.isFinite(value)) kept[name] = value
|
|
}
|
|
localStorage.setItem(storageKey, JSON.stringify(kept))
|
|
} catch {
|
|
/* private mode etc. — the slider values just won't persist */
|
|
}
|
|
}
|
|
watch([varValues, varStarts], save)
|
|
|
|
onMounted(() => {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey)
|
|
if (raw) {
|
|
const stored: unknown = JSON.parse(raw)
|
|
if (typeof stored === 'object' && stored !== null) {
|
|
for (const [name, value] of Object.entries(stored)) {
|
|
if (typeof value === 'number' && Number.isFinite(value)) varValues[name] = value
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
/* storage unreadable — the sliders start where their rows ask */
|
|
}
|
|
fillDefaults()
|
|
})
|
|
|
|
return { slidersByRow, setVar }
|
|
}
|