Files
umrechner/hugo/static/js/converter.js

258 lines
6.7 KiB
JavaScript

'use strict';
/**
* Alpine.js navigation helpers for conversion page.
* Handles navigation via hidden AJAX link.
*/
function navActions() {
return {
navigate(url) {
this.$refs.ajaxLink.href = url;
this.$nextTick(() => this.$refs.ajaxLink.click());
},
navigateWithValue(url) {
const el = document.getElementById('value');
const val = el ? el.value.replace(/,/g, '.') : '1';
this.$refs.ajaxLink.href = url + '?v=' + val;
this.$nextTick(() => this.$refs.ajaxLink.click());
},
navigateWithResult(url) {
const el = document.getElementById('result');
const val = el ? el.value.replace(/,/g, '.') : '';
if (val) {
this.$refs.ajaxLink.href = url + '?v=' + val;
} else {
this.$refs.ajaxLink.href = url;
}
this.$nextTick(() => this.$refs.ajaxLink.click());
}
};
}
/**
* Factory function for Alpine.js converter components.
* @param {string} engine - 'linear', 'intermediate' or 'runtime'
* @param {object} config - Engine-specific configuration
* @returns {object} Alpine component data
*/
function createConverter(engine, config) {
if (engine === 'runtime') {
return {
inputValue: '1',
result: '',
rates: {},
ratesError: '',
async init() {
await this.loadRates();
const params = new URLSearchParams(
window.location.search);
const valueParam = params.get('v');
if (valueParam) {
this.inputValue = valueParam.replace(/,/g, '.');
}
this.$watch('inputValue', val => {
if (val && val.indexOf(',') !== -1) {
this.inputValue = val.replace(/,/g, '.');
}
});
this.calculate();
},
async loadRates() {
const pbUrl = document.querySelector(
'meta[name="pocketbase-url"]')?.content
|| 'https://www.alphabreed.com';
try {
const response = await fetch(
`${pbUrl}/api/collections/currencies/records`);
if (!response.ok) {
this.ratesError =
'Wechselkurse konnten nicht geladen werden.';
return;
}
const data = await response.json();
for (const item of data.items || []) {
this.rates[item.id] = item.rate;
}
} catch (e) {
this.ratesError =
'Wechselkurse konnten nicht geladen werden.';
}
},
calculate() {
try {
this.result = '';
if (Object.keys(this.rates).length === 0) {
return;
}
const normalized = parseNumber(
this.inputValue);
if (!normalized || isNaN(normalized)) {
return;
}
const fromCode = config.fromCurrency || '';
const toCode = config.toCurrency || '';
const fromRate = fromCode === 'eur'
? 1 : this.rates[fromCode];
const toRate = toCode === 'eur'
? 1 : this.rates[toCode];
if (!fromRate || !toRate) {
return;
}
const value = new Decimal(normalized);
const rate = new Decimal(toRate)
.dividedBy(new Decimal(fromRate));
const rawResult = value.times(rate);
this.result = prettyNumber(rawResult);
const normalizedInputValue = this.inputValue
.replace(/,/g, '.');
if (normalizedInputValue
&& normalizedInputValue !== '0') {
history.replaceState(
null, '', '?v=' + normalizedInputValue);
}
} catch (e) {
this.result = '';
}
}
};
}
return {
inputValue: '1',
result: '',
ratesError: '',
init() {
const params = new URLSearchParams(
window.location.search);
const valueParam = params.get('v');
if (valueParam) {
this.inputValue = valueParam.replace(/,/g, '.');
}
this.$watch('inputValue', val => {
if (val && val.indexOf(',') !== -1) {
this.inputValue = val.replace(/,/g, '.');
}
});
this.calculate();
},
calculate() {
try {
const normalized = parseNumber(this.inputValue);
if (!normalized || isNaN(normalized)) {
this.result = '';
return;
}
const value = new Decimal(normalized);
let rawResult;
if (engine === 'linear') {
const fromFactor = new Decimal(
config.fromFactor);
const toFactor = new Decimal(
config.toFactor);
rawResult = value.times(fromFactor)
.dividedBy(toFactor);
} else if (engine === 'intermediate') {
rawResult = convertTemperature(
value,
config.fromUnit,
config.toUnit
);
} else {
this.result = '';
return;
}
this.result = prettyNumber(rawResult);
const normalizedInputValue = this.inputValue
.replace(/,/g, '.');
if (normalizedInputValue
&& normalizedInputValue !== '0') {
history.replaceState(
null, '', '?v=' + normalizedInputValue);
}
} catch (e) {
this.result = '';
}
}
};
}
/**
* Temperature conversion via Celsius intermediate.
* @param {Decimal} value
* @param {string} fromUnit
* @param {string} toUnit
* @returns {number}
*/
function convertTemperature(value, fromUnit, toUnit) {
let v = value.toNumber();
if (fromUnit === 'fahrenheit') {
v = (v - 32) * 5 / 9;
} else if (fromUnit === 'kelvin') {
v = v - 273.15;
} else if (fromUnit === 'rankine') {
v = (v - 491.67) * 5 / 9;
} else if (fromUnit === 'reaumur') {
v = v * 5 / 4;
}
if (toUnit === 'fahrenheit') {
v = v * 9 / 5 + 32;
} else if (toUnit === 'kelvin') {
v = v + 273.15;
} else if (toUnit === 'rankine') {
v = (v + 273.15) * 9 / 5;
} else if (toUnit === 'reaumur') {
v = v * 4 / 5;
}
return v;
}
/**
* Formats a number as plain digits with dot as decimal separator.
* @param {Decimal|string|number} num
* @param {number} minPrecision - Minimum decimal places
* @param {number} maxPrecision - Maximum decimal places
* @returns {string} Formatted number string
*/
function prettyNumber(num, minPrecision, maxPrecision) {
minPrecision = minPrecision || 4;
maxPrecision = maxPrecision || 10;
const d = new Decimal(num);
const absVal = d.abs();
// Larger numbers need fewer decimal places for readable output;
// smaller numbers get more to stay meaningful.
let precision = maxPrecision;
if (absVal.gte(1000000)) {
precision = 2;
} else if (absVal.gte(1000)) {
precision = 4;
} else if (absVal.gte(1)) {
precision = 6;
} else if (absVal.gte(0.001)) {
precision = 8;
}
precision = Math.max(minPrecision,
Math.min(precision, maxPrecision));
let str = d.toFixed(precision);
if (str.indexOf('.') !== -1) {
str = str.replace(/0+$/, '');
str = str.replace(/\.$/, '');
}
return str;
}
/**
* Parses a German number string.
* Accepts both comma and dot as decimal separator.
* @param {string} input
* @returns {string} Normalized string with dot as decimal
*/
function parseNumber(input) {
if (!input) return '0';
const hasComma = input.indexOf(',') !== -1;
const hasDot = input.indexOf('.') !== -1;
if (hasComma && hasDot) {
return input.replace(/\./g, '').replace(/,/g, '.') || '0';
}
return input.replace(/,/g, '.') || '0';
}