Erstes Commit
This commit is contained in:
5
hugo/static/js/alpine-3.14.9.min.js
vendored
Normal file
5
hugo/static/js/alpine-3.14.9.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
hugo/static/js/alpine-ajax-0.12.2.min.js
vendored
Normal file
1
hugo/static/js/alpine-ajax-0.12.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
280
hugo/static/js/converter.js
Normal file
280
hugo/static/js/converter.js
Normal file
@@ -0,0 +1,280 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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 || '')
|
||||
.toUpperCase();
|
||||
const toCode = (config.toCurrency || '')
|
||||
.toUpperCase();
|
||||
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 = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Alpine.js component for the conversion form.
|
||||
* Handles category/from/to dropdowns and swap button.
|
||||
* @param {object} defaults - Initial values
|
||||
* @returns {object} Alpine component data
|
||||
*/
|
||||
function conversionForm(defaults) {
|
||||
return {
|
||||
category: defaults.category,
|
||||
from: defaults.from,
|
||||
to: defaults.to,
|
||||
|
||||
get actionUrl() {
|
||||
const search = window.location.search;
|
||||
return '/' + this.from + '-in-' + this.to + '/'
|
||||
+ (search || '');
|
||||
},
|
||||
|
||||
swapWithResult() {
|
||||
const resultEl = document.getElementById('result');
|
||||
const resultVal = resultEl?.value || '';
|
||||
if (resultVal) {
|
||||
history.replaceState(
|
||||
null, '', '?v=' + resultVal.replace(/,/g, '.'));
|
||||
}
|
||||
const temp = this.from;
|
||||
this.from = this.to;
|
||||
this.to = temp;
|
||||
this.$nextTick(() => {
|
||||
this.$refs.navLink.click();
|
||||
});
|
||||
},
|
||||
|
||||
onCategoryChange() {
|
||||
const cat = UNITS_DATA[this.category];
|
||||
if (!cat || !cat.units) return;
|
||||
const units = Object.keys(cat.units);
|
||||
this.from = units[0];
|
||||
this.to = units[1] || units[0];
|
||||
this.$nextTick(() => {
|
||||
this.$refs.navLink.click();
|
||||
});
|
||||
},
|
||||
|
||||
init() {
|
||||
this.$watch('category', (val, old) => {
|
||||
if (val && old && val !== old) {
|
||||
this.onCategoryChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
3
hugo/static/js/decimal.js-light.min.js
vendored
Normal file
3
hugo/static/js/decimal.js-light.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user