Last active
May 15, 2021 18:05
-
-
Save lukegb/1f65dbf4c98c6891a7a1d89545299a5d to your computer and use it in GitHub Desktop.
shiptoasting
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
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
.total-money { | |
font-weight: bold; | |
} | |
.container td { | |
padding: 0.2rem 0.5rem; | |
} | |
.slider { | |
width: 10rem; | |
} | |
.slider-text { | |
width: 3rem; | |
} | |
.diff-expl { | |
text-align: right; | |
} | |
</style> | |
<title>How much do I need to Contribute To The Economy</title> | |
<h1>How much do I need to Contribute To The Economy</h1> | |
<table class="container"></table> | |
<p>Based on some recent <a href="https://www.standard.co.uk/news/uk/brits-drink-124-pints-each-struggling-pubs-covid-lockdown-b935186.html">news</a> and this <a href="https://www.companydebt.com/features/124-pints-to-save-the-pub/">source</a>.</p> | |
<template id="slider-template"> | |
<tr class="slider-holder"> | |
<td><input type="range" class="slider"></td> | |
<td><input type="number" class="slider-text"></td> | |
<td><span class="slider-value"></span></td> | |
<td>@ <span class="slider-each"></span>/ea</td> | |
<td><span class="slider-money"></span></td> | |
</tr> | |
</template> | |
<template id="total-template"> | |
<tr style="display:none"><!-- this is really just for debugging --> | |
<td colspan="3"></td> | |
<td class="diff-expl">Difference:</td> | |
<td><span class="diff-money"></span></td> | |
</tr> | |
<tr> | |
<td colspan="4"></td> | |
<td><span class="total-money"></span></td> | |
</tr> | |
</template> | |
<script type="module"> | |
const sum = (it, f) => it.reduce((acc, v) => acc + f(v), 0); | |
const i = (v) => parseInt(v, 10); | |
const MONEY_FORMAT = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }); | |
const container = document.querySelector('.container'); | |
const tmpl = document.querySelector('#slider-template'); | |
const PUB_THINGS = [ | |
{ | |
name: "Pints of Beer", | |
singularName: "Pint of Beer", | |
defaultValue: 124, | |
each: 394, | |
}, | |
{ | |
name: "Glasses of Wine", | |
singularName: "Glass of Wine", | |
defaultValue: 0, | |
each: 400, | |
}, | |
{ | |
name: "Roast Dinners", | |
singularName: "Roast Dinner", | |
defaultValue: 0, | |
each: 1220, | |
}, | |
{ | |
name: "Packs of Crisps", | |
singularName: "Pack of Crisps", | |
defaultValue: 0, | |
each: 50, | |
} | |
]; | |
const TOTAL = sum(PUB_THINGS, (v) => v.defaultValue*v.each); | |
const ELEMENTS = PUB_THINGS.map((v) => { | |
const content = tmpl.content.cloneNode(true); | |
const slider = content.querySelector('.slider'); | |
const text = content.querySelector('.slider-text'); | |
text.min = slider.min = 0; | |
text.max = slider.max = Math.ceil(TOTAL / v.each); | |
text.value = slider.value = v.defaultValue; | |
const textValue = content.querySelector('.slider-value'); | |
const money = content.querySelector('.slider-money'); | |
content.querySelector('.slider-each').textContent = MONEY_FORMAT.format(v.each / 100); | |
container.appendChild(content); | |
slider.addEventListener('change', () => { | |
text.value = slider.value; | |
updateElements(v.name); | |
}); | |
text.addEventListener('change', () => { | |
slider.value = text.value; | |
updateElements(v.name); | |
}); | |
return { | |
slider, | |
text, | |
textValue, | |
money, | |
...v, | |
} | |
}); | |
const {totalEl, diffEl} = (() => { | |
const content = document.querySelector('#total-template').content.cloneNode(true); | |
const totalEl = content.querySelector('.total-money'); | |
const diffEl = content.querySelector('.diff-money'); | |
container.appendChild(content); | |
return {totalEl, diffEl}; | |
})(); | |
const updateElements = (changedName) => { | |
let total = 0; | |
ELEMENTS.forEach((v) => { | |
v.textValue.textContent = v.slider.value == 1 ? v.singularName : v.name; | |
v.money.textContent = MONEY_FORMAT.format(v.each * i(v.slider.value) / 100); | |
total += v.each * i(v.slider.value); | |
}); | |
totalEl.textContent = MONEY_FORMAT.format(total / 100); | |
diffEl.textContent = MONEY_FORMAT.format((TOTAL - total) / 100); | |
if (changedName === null) return; | |
// If we know which slider was changed, try to bring all other non-zero sliders up to sync. | |
// If all other sliders are zero, then only increase the last one as a fallback. | |
// This is to ensure that we don't increase things that people have set to zero on purpose (e.g. because they don't like roast dinners or wine). | |
let epsilon = null; | |
let canBeAdjusted = []; | |
for (const e of ELEMENTS) { | |
if (e.name === changedName) continue; | |
if (e.slider.value == 0) continue; | |
canBeAdjusted.push(e); | |
if (epsilon === null || epsilon.each > e.each) { | |
epsilon = e; | |
} | |
} | |
if (epsilon === null) { | |
if (changedName === 'Packs of Crisps') { | |
// If everything is 0 other than packs of crisps, use beer. | |
epsilon = ELEMENTS[0]; | |
canBeAdjusted.push(ELEMENTS[0]); | |
} else { | |
// Always just adjust packs of crisps. | |
epsilon = ELEMENTS[ELEMENTS.length-1]; | |
canBeAdjusted.push(ELEMENTS[ELEMENTS.length-1]); | |
} | |
} | |
let totalOfAll = sum(canBeAdjusted, (v) => v.each); | |
let maxCanBeRemoved = canBeAdjusted.reduce((t, v) => Math.min(t, v.slider.value), TOTAL); | |
let difference = TOTAL - sum(ELEMENTS, (v) => v.slider.value*v.each); | |
console.log(`we're ${difference/100} short!`, epsilon.each); | |
// If we're *less* than TOTAL, we always adjust. | |
if (Math.abs(difference) < (epsilon.each/2) && difference < 0) return; // Nothing to do. | |
if (Math.abs(difference) >= (totalOfAll/2)) { | |
const amountToAddToAll = Math.max(-maxCanBeRemoved, Math.floor(difference / totalOfAll)); | |
canBeAdjusted.forEach((v) => { | |
v.slider.value = v.text.value = i(v.slider.value) + amountToAddToAll; | |
}); | |
difference -= Math.floor(difference / totalOfAll); | |
} | |
difference = TOTAL - sum(ELEMENTS, (v) => v.slider.value*v.each) | |
console.log(`Remaining difference: ${difference}; epsilon is ${epsilon.each}`); | |
// Mess with epsilon to make up the difference. | |
if (Math.abs(difference) >= (epsilon.each/2) || difference > 0) { | |
difference += epsilon.each * i(epsilon.slider.value); | |
epsilon.slider.value = epsilon.text.value = Math.max(0, Math.ceil(difference / epsilon.each)); | |
} | |
updateElements(null); | |
}; | |
updateElements(null); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment