feat: added scale buttons
This commit is contained in:
parent
a96734c394
commit
53c25c9da3
4 changed files with 391 additions and 22 deletions
|
|
@ -1,18 +1,20 @@
|
|||
/**
|
||||
* Client-side measurement toggle.
|
||||
* Default: metric units, collapsed times (rendered at build time).
|
||||
* Each measurement type gets its own toggle button.
|
||||
* Client-side measurement toggle + recipe scaling.
|
||||
* Default: metric units, collapsed times, 1x scale.
|
||||
*
|
||||
* Each measurement span has data-default and data-alt attributes
|
||||
* pre-computed at build time. This script just swaps between them
|
||||
* per measurement type.
|
||||
* Each measurement span has:
|
||||
* data-default / data-alt — pre-computed metric/imperial text (1x)
|
||||
* data-scalable — JSON with base values in g/ml for scaling
|
||||
* data-measurement-type — weight|volume|temperature|dimension|time
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Track which types are showing alt text
|
||||
// ─── State ──────────────────────────────────────────────
|
||||
|
||||
var showingAlt = {};
|
||||
var scale = 1;
|
||||
|
||||
var TYPE_LABELS = {
|
||||
temperature: { toAlt: '°F', toDefault: '°C' },
|
||||
|
|
@ -22,33 +24,201 @@
|
|||
time: { toAlt: 'hr+min', toDefault: 'min' },
|
||||
};
|
||||
|
||||
function toggleType(type) {
|
||||
showingAlt[type] = !showingAlt[type];
|
||||
var isAlt = showingAlt[type];
|
||||
// ─── Formatting (mirrors plugin logic) ──────────────────
|
||||
|
||||
// Update all spans of this type
|
||||
var spans = document.querySelectorAll('span.measurement[data-measurement-type="' + type + '"]');
|
||||
var NO_SPACE = { 'g':1, 'kg':1, 'ml':1, '°C':1, '°F':1, 'cm':1, 'mm':1 };
|
||||
|
||||
function formatNumber(n) {
|
||||
if (Number.isInteger(n) || Math.abs(n - Math.round(n)) < 0.001) {
|
||||
return Math.round(n).toString();
|
||||
}
|
||||
var decimals = Math.abs(n) >= 10 ? 1 : 2;
|
||||
return n.toFixed(decimals).replace(/\.?0+$/, '');
|
||||
}
|
||||
|
||||
function unitLabel(unit, plural) {
|
||||
var labels = {
|
||||
'g':'g', 'kg':'kg', 'oz':'oz', 'lb':'lb',
|
||||
'cup': plural ? 'cups' : 'cup',
|
||||
'tablespoon':'tbsp', 'teaspoon':'tsp',
|
||||
'ml':'ml', 'L':'L',
|
||||
'quart': plural ? 'quarts' : 'quart',
|
||||
'gallon': plural ? 'gallons' : 'gallon',
|
||||
'pint': plural ? 'pints' : 'pint',
|
||||
'fl oz':'fl oz',
|
||||
};
|
||||
return labels[unit] || unit;
|
||||
}
|
||||
|
||||
function formatVU(value, unit) {
|
||||
var label = unitLabel(unit, value !== 1);
|
||||
var space = NO_SPACE[unit] ? '' : ' ';
|
||||
return formatNumber(value) + space + label;
|
||||
}
|
||||
|
||||
// ─── Smart Unit Selection ───────────────────────────────
|
||||
|
||||
function smartMetricWeight(g) {
|
||||
return g >= 1000 ? { v: g / 1000, u: 'kg' } : { v: g, u: 'g' };
|
||||
}
|
||||
|
||||
function smartImperialWeight(oz) {
|
||||
return oz >= 16 ? { v: oz / 16, u: 'lb' } : { v: oz, u: 'oz' };
|
||||
}
|
||||
|
||||
function smartMetricVolume(ml) {
|
||||
return ml >= 1000 ? { v: ml / 1000, u: 'L' } : { v: ml, u: 'ml' };
|
||||
}
|
||||
|
||||
function smartImperialVolume(ml) {
|
||||
if (ml >= 236.588) return { v: ml / 236.588, u: 'cup' };
|
||||
if (ml >= 14.787) return { v: ml / 14.787, u: 'tablespoon' };
|
||||
return { v: ml / 4.929, u: 'teaspoon' };
|
||||
}
|
||||
|
||||
// ─── Scale Computation ──────────────────────────────────
|
||||
|
||||
function computeScaledText(data, scaleFactor, imperial) {
|
||||
var baseVal = data.base;
|
||||
var isRange = Array.isArray(baseVal);
|
||||
var prefix = data.approx ? '~' : '';
|
||||
|
||||
if (data.type === 'weight') {
|
||||
if (isRange) {
|
||||
var lo = baseVal[0] * scaleFactor;
|
||||
var hi = baseVal[1] * scaleFactor;
|
||||
var sLo, sHi;
|
||||
if (imperial) {
|
||||
sLo = smartImperialWeight(lo / 28.3495);
|
||||
sHi = smartImperialWeight(hi / 28.3495);
|
||||
} else {
|
||||
sLo = smartMetricWeight(lo);
|
||||
sHi = smartMetricWeight(hi);
|
||||
}
|
||||
if (sLo.u === sHi.u) {
|
||||
var space = NO_SPACE[sLo.u] ? '' : ' ';
|
||||
return prefix + formatNumber(sLo.v) + '-' + formatNumber(sHi.v) + space + unitLabel(sLo.u, sHi.v !== 1);
|
||||
}
|
||||
return prefix + formatVU(sLo.v, sLo.u) + '-' + formatVU(sHi.v, sHi.u);
|
||||
}
|
||||
var g = baseVal * scaleFactor;
|
||||
var s;
|
||||
if (imperial) {
|
||||
s = smartImperialWeight(g / 28.3495);
|
||||
} else {
|
||||
s = smartMetricWeight(g);
|
||||
}
|
||||
return prefix + formatVU(s.v, s.u);
|
||||
}
|
||||
|
||||
if (data.type === 'volume') {
|
||||
if (isRange) {
|
||||
var lo = baseVal[0] * scaleFactor;
|
||||
var hi = baseVal[1] * scaleFactor;
|
||||
var sLo, sHi;
|
||||
if (imperial) {
|
||||
sLo = smartImperialVolume(lo);
|
||||
sHi = smartImperialVolume(hi);
|
||||
} else {
|
||||
sLo = smartMetricVolume(lo);
|
||||
sHi = smartMetricVolume(hi);
|
||||
}
|
||||
if (sLo.u === sHi.u) {
|
||||
var space = NO_SPACE[sLo.u] ? '' : ' ';
|
||||
return prefix + formatNumber(sLo.v) + '-' + formatNumber(sHi.v) + space + unitLabel(sLo.u, sHi.v !== 1);
|
||||
}
|
||||
return prefix + formatVU(sLo.v, sLo.u) + '-' + formatVU(sHi.v, sHi.u);
|
||||
}
|
||||
var ml = baseVal * scaleFactor;
|
||||
var s;
|
||||
if (imperial) {
|
||||
s = smartImperialVolume(ml);
|
||||
} else {
|
||||
s = smartMetricVolume(ml);
|
||||
}
|
||||
return prefix + formatVU(s.v, s.u);
|
||||
}
|
||||
|
||||
if (data.type === 'count') {
|
||||
if (isRange) {
|
||||
var lo = baseVal[0] * scaleFactor;
|
||||
var hi = baseVal[1] * scaleFactor;
|
||||
return prefix + formatNumber(lo) + '-' + formatNumber(hi);
|
||||
}
|
||||
return prefix + formatNumber(baseVal * scaleFactor);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Update All Measurements ────────────────────────────
|
||||
|
||||
function updateAll() {
|
||||
var spans = document.querySelectorAll('span.measurement');
|
||||
spans.forEach(function (span) {
|
||||
var text = isAlt
|
||||
var type = span.getAttribute('data-measurement-type');
|
||||
var imperial = !!showingAlt[type];
|
||||
var scalableRaw = span.getAttribute('data-scalable');
|
||||
|
||||
if (scalableRaw && scale !== 1) {
|
||||
// Use scaling computation
|
||||
try {
|
||||
var data = JSON.parse(scalableRaw);
|
||||
var text = computeScaledText(data, scale, imperial);
|
||||
if (text) { span.textContent = text; return; }
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// No scaling or not scalable — use pre-computed text
|
||||
var text = imperial
|
||||
? span.getAttribute('data-alt')
|
||||
: span.getAttribute('data-default');
|
||||
if (text) span.textContent = text;
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Unit Toggle ────────────────────────────────────────
|
||||
|
||||
function toggleType(type) {
|
||||
showingAlt[type] = !showingAlt[type];
|
||||
updateAll();
|
||||
|
||||
// Update button text
|
||||
var btn = document.querySelector('.measurement-toggle-btn[data-toggle-type="' + type + '"]');
|
||||
if (btn) {
|
||||
var labels = TYPE_LABELS[type];
|
||||
btn.textContent = isAlt ? labels.toAlt : labels.toDefault;
|
||||
btn.textContent = showingAlt[type] ? labels.toAlt : labels.toDefault;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Scale Toggle ───────────────────────────────────────
|
||||
|
||||
function setScale(newScale) {
|
||||
scale = newScale;
|
||||
updateAll();
|
||||
updateScaleButtons();
|
||||
}
|
||||
|
||||
function updateScaleButtons() {
|
||||
var btns = document.querySelectorAll('.scale-btn');
|
||||
btns.forEach(function (btn) {
|
||||
var val = parseFloat(btn.getAttribute('data-scale'));
|
||||
if (val === scale) {
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Init ───────────────────────────────────────────────
|
||||
|
||||
function init() {
|
||||
var container = document.querySelector('.measurement-toggles');
|
||||
if (!container) return;
|
||||
|
||||
// Detect which types exist and have toggleable content
|
||||
// Detect which types have toggleable content
|
||||
var typeHasToggle = {};
|
||||
var hasScalable = false;
|
||||
var spans = document.querySelectorAll('span.measurement');
|
||||
spans.forEach(function (span) {
|
||||
var type = span.getAttribute('data-measurement-type');
|
||||
|
|
@ -56,11 +226,16 @@
|
|||
var def = span.getAttribute('data-default');
|
||||
var alt = span.getAttribute('data-alt');
|
||||
if (def !== alt) typeHasToggle[type] = true;
|
||||
if (span.getAttribute('data-scalable')) hasScalable = true;
|
||||
});
|
||||
|
||||
var typeOrder = ['temperature', 'weight', 'volume', 'dimension', 'time'];
|
||||
var hasAny = false;
|
||||
|
||||
// Unit toggle buttons
|
||||
var unitRow = document.createElement('div');
|
||||
unitRow.className = 'toggle-row';
|
||||
|
||||
var typeOrder = ['temperature', 'weight', 'volume', 'dimension', 'time'];
|
||||
typeOrder.forEach(function (type) {
|
||||
if (!typeHasToggle[type]) return;
|
||||
hasAny = true;
|
||||
|
|
@ -71,9 +246,55 @@
|
|||
btn.setAttribute('data-toggle-type', type);
|
||||
btn.textContent = TYPE_LABELS[type].toDefault;
|
||||
btn.addEventListener('click', function () { toggleType(type); });
|
||||
container.appendChild(btn);
|
||||
unitRow.appendChild(btn);
|
||||
});
|
||||
|
||||
if (hasAny) {
|
||||
container.appendChild(unitRow);
|
||||
}
|
||||
|
||||
// Scale buttons
|
||||
if (hasScalable) {
|
||||
hasAny = true;
|
||||
var scaleRow = document.createElement('div');
|
||||
scaleRow.className = 'toggle-row';
|
||||
|
||||
var scaleLabel = document.createElement('span');
|
||||
scaleLabel.className = 'scale-label';
|
||||
scaleLabel.textContent = 'Scale:';
|
||||
scaleRow.appendChild(scaleLabel);
|
||||
|
||||
var scales = [0.5, 1, 2, 3];
|
||||
scales.forEach(function (s) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'scale-btn' + (s === 1 ? ' active' : '');
|
||||
btn.setAttribute('data-scale', s);
|
||||
btn.textContent = s + 'x';
|
||||
btn.addEventListener('click', function () {
|
||||
setScale(s);
|
||||
var input = document.querySelector('.scale-input');
|
||||
if (input) input.value = '';
|
||||
});
|
||||
scaleRow.appendChild(btn);
|
||||
});
|
||||
|
||||
var input = document.createElement('input');
|
||||
input.type = 'number';
|
||||
input.className = 'scale-input';
|
||||
input.placeholder = 'Custom';
|
||||
input.min = '0.1';
|
||||
input.step = '0.1';
|
||||
input.addEventListener('input', function () {
|
||||
var val = parseFloat(input.value);
|
||||
if (val > 0 && !isNaN(val)) {
|
||||
setScale(val);
|
||||
}
|
||||
});
|
||||
scaleRow.appendChild(input);
|
||||
|
||||
container.appendChild(scaleRow);
|
||||
}
|
||||
|
||||
if (hasAny) {
|
||||
container.style.display = 'flex';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue