If you start to get a very complicated file with lots of functions (or have to communicate variables back and forth a lot between components), it can be useful to put these variables/functions in a global svelte.js store. (A bit similar to a Vue Pinia store or React Redux store,although not exactly the same).
E.g., i separated the logic for doubling/halving mix quantities with maths.svelte.js;
(basically it converts everything to ml, then back to a logical measure like teaspoons or cups, and displays things in fractions rather than decimals,and handles actions like doubling/halving etc., no need to understand everything in detail;)
maths.svelte.js
// Refer to docs; <https://svelte.dev/docs/svelte/stores>
import { writable, get } from 'svelte/store';
export function approximateFraction(decimal, maxDenominator) {
let numerator = 1;
let denominator = 1;
let minError = Math.abs(decimal - numerator / denominator);
for (let d = 2; d <= maxDenominator; d++) {
const n = Math.round(decimal * d);
const error = Math.abs(decimal - n / d);
if (error < minError) {
minError = error;
numerator = n;
denominator = d;
}
}
return [numerator, denominator];
}
export function wholeAndFraction(float,maxDenominator=4) {
let integerPart = Math.floor(float);
const decimalPart = float - integerPart;
const [numerator, denominator] = approximateFraction(decimalPart, maxDenominator);
const floatFraction = (numerator !== 0 && numerator!=denominator) ? `${numerator}/${denominator}` : '';
if (numerator==denominator) {
integerPart+=1
}
return `${integerPart >= 1 ? integerPart : ''}${floatFraction ? ' ' : ''}${floatFraction}`;
}
export const useOriginals = writable(false);
export function switchOriginals(){
useOriginals.update((value) =>! value);
// To trigger the quantities to update.. workaround
multiplier.update((value) => value+1);
multiplier.update((value) => value-1);
}
export const multiplier = writable(1);
export let totalStr = writable('');;
export function double(mix, measures) {
multiplier.update((value) => value * 2);
console.log(get(multiplier))
calculateTotals(mix, measures, get(multiplier));
}
export function half(mix, measures) {
multiplier.update((value) => value / 2);
console.log(get(multiplier))
calculateTotals(mix, measures, get(multiplier));
}
export function original(mix, measures) {
multiplier.set(1);
console.log(get(multiplier))
calculateTotals(mix, measures, get(multiplier));
}
export function toMl(quantity, unit){
if (unit === 'Ts') {
return quantity * 5;
} else if (unit === 'Tbsp') {
return quantity * 15;
} else if (unit === 'Cups') {
return quantity * 240;
}
else if (unit==='pinches'){
return quantity *0.315
}
else if (unit === 'dl') {
return quantity * 100;
} else if (unit === 'l') {
return quantity * 1000;
}
else if (unit === 'ml') {
return quantity * 1;
}
}
export function mlStandardized(ml, of=true){
if (ml >
10000){
return(`a truckload ${of?'of':''}`);
}
if (ml > 150) {
return(`${wholeAndFraction(ml / 240,4)} Cups`);
} else if (ml > 10) {
return(`${wholeAndFraction(ml / 15,4)} Tbsp`);
} else if (ml > 1.2) {
return(`${wholeAndFraction(ml / 5,4)} Ts`);
} else if (ml>=0.05){
return(`${wholeAndFraction(ml / 0.315,4)} Pinches`)
}else{
return(`no${of?'':'t'} detectable`);
}
}
//To ensure this is updated when multiplier changes, the multiplier is in the ingredient view
export function transformIngredient(quantity,unit){
//if user wants to use original units or unit is not a volume measure;
if (get(useOriginals) || !(['Tbsp','Ts','Cups','dl','l','ml']).includes(unit)){
return(`${wholeAndFraction(quantity)} ${unit}`);
}
else {
let ml = toMl(quantity, unit)
let totalMl = ml;
return(mlStandardized(totalMl));
}
}
export function calculateTotals(mix, measures) {
let newTotal = 0;
mix.data.ingredients.forEach((ingredient) => {
const measure = measures.data.find((m) => m.id == ingredient.measure_id);
if (!measure) return;
newTotal += toMl(ingredient.quantity, measure.name)??0;
});
newTotal*=get(multiplier)
console.log(newTotal)
totalStr.set(mlStandardized(newTotal,false))
}
You can then import these functions and variables in various components, and by using ‘writable’, you can update these variables from several different components and they will all know what the current state of the variable is.
E.g. in Show.Svelte, I’m importing these functions and variables to handle clicks on buttons like ‘double’, ‘half’, ‘original’ and ‘show originals’;
import {
calculateTotals,
totalStr,
multiplier,
double,
half,
original,
useOriginals,
switchOriginals
} from './MixesLogic/maths.svelte.js';
All this logic then doesn’t have to clutter the component itself.
As said, we can use the same functions and variables in multiple components. I separated the ingredients from the main Show.Svelte. In the ingredient.Svelte (for every ingredient in the list), I then import multiplier variable and the transformingredient function to use them there;
import { multiplier, transformIngredient } from '../MixesLogic/maths.svelte';
{transformIngredient(ingredient?.quantity * multiplierValue, measureName)}
<span> {ingredient?.name}</span>
so that 0.5 teaspoons becomes 1/2 teaspoons, and if a user has pressed double on the Show.Svelte component (changing the multiplierValue), it will know to update the quantity to 1 teaspoons;