fix #7731
This commit is contained in:
parent
ed7aa0806d
commit
04eec324bf
@ -31,7 +31,11 @@ export default {
|
||||
};
|
||||
|
||||
function RenderAmount(): VNode {
|
||||
const [value, setValue] = useState<AmountJson | undefined>(undefined);
|
||||
const [value, setValue] = useState<AmountJson | undefined>({
|
||||
currency: "USD",
|
||||
value: 1,
|
||||
fraction: 0,
|
||||
});
|
||||
|
||||
const error = value === undefined ? undefined : undefined;
|
||||
|
||||
@ -53,7 +57,10 @@ function RenderAmount(): VNode {
|
||||
handler={handler}
|
||||
/>
|
||||
<p>
|
||||
<pre>{JSON.stringify(value, undefined, 2)}</pre>
|
||||
<pre>
|
||||
value : {value?.value} <br />
|
||||
fraction : {value?.fraction}
|
||||
</pre>
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useState } from "preact/hooks";
|
||||
import { useTranslationContext } from "../context/translation.js";
|
||||
import { AmountFieldHandler } from "../mui/handlers.js";
|
||||
import { TextField } from "../mui/TextField.js";
|
||||
|
||||
@ -47,80 +48,49 @@ export function AmountField({
|
||||
required?: boolean;
|
||||
handler: AmountFieldHandler;
|
||||
}): VNode {
|
||||
const { i18n } = useTranslationContext();
|
||||
const [unit, setUnit] = useState(1);
|
||||
const [decimalPlaces, setDecimalPlaces] = useState<number | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [error, setError] = useState<string>("");
|
||||
|
||||
const normal = normalize(handler.value, unit);
|
||||
const previousValue = Amounts.stringifyValue(normal);
|
||||
|
||||
const [textValue, setTextValue] = useState<string>(previousValue);
|
||||
|
||||
function updateUnit(newUnit: number) {
|
||||
setUnit(newUnit);
|
||||
const newNorm = normalize(handler.value, newUnit);
|
||||
setTextValue(Amounts.stringifyValue(newNorm));
|
||||
}
|
||||
|
||||
const currency = handler.value.currency;
|
||||
|
||||
let hd = Math.floor(Math.log10(highestDenom || 1) / 3);
|
||||
let ld = Math.ceil((-1 * Math.log10(lowestDenom || 1)) / 3);
|
||||
|
||||
const currencyLabels: Array<{ name: string; unit: number }> = [
|
||||
{
|
||||
name: currency,
|
||||
unit: 1,
|
||||
},
|
||||
];
|
||||
|
||||
while (hd > 0) {
|
||||
currencyLabels.push({
|
||||
name: `${HIGH_DENOM_SYMBOL[hd]}${currency}`,
|
||||
unit: Math.pow(10, hd * 3),
|
||||
});
|
||||
hd--;
|
||||
}
|
||||
while (ld > 0) {
|
||||
currencyLabels.push({
|
||||
name: `${LOW_DENOM_SYMBOL[ld]}${currency}`,
|
||||
unit: Math.pow(10, -1 * ld * 3),
|
||||
});
|
||||
ld--;
|
||||
}
|
||||
|
||||
const previousValue = Amounts.stringifyValue(handler.value, decimalPlaces);
|
||||
|
||||
const normal = normalize(handler.value, unit) ?? handler.value;
|
||||
|
||||
let textValue = Amounts.stringifyValue(normal, decimalPlaces);
|
||||
if (decimalPlaces === 0) {
|
||||
textValue += ".";
|
||||
}
|
||||
const currencyLabels = buildLabelsForCurrency(
|
||||
currency,
|
||||
lowestDenom,
|
||||
highestDenom,
|
||||
);
|
||||
|
||||
function positiveAmount(value: string): string {
|
||||
// setDotAtTheEnd(value.endsWith("."));
|
||||
// const dotAtTheEnd = value.endsWith(".");
|
||||
if (!value) {
|
||||
if (handler.onInput) {
|
||||
handler.onInput(Amounts.zeroOfCurrency(currency));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
//remove all but last dot
|
||||
const withoutDots = value.replace(/(\.)(?=.*\1)/g, "");
|
||||
const parts = withoutDots.split(".");
|
||||
setDecimalPlaces(parts.length === 1 ? undefined : parts[1].length);
|
||||
} else
|
||||
try {
|
||||
const parsed = Amounts.parseOrThrow(`${currency}:${value.trim()}`);
|
||||
|
||||
//FIXME: should normalize before parsing
|
||||
//parsing first add some restriction on the rage of the values
|
||||
const parsed = parseValue(currency, withoutDots);
|
||||
const realValue = denormalize(parsed, unit);
|
||||
|
||||
if (!parsed || parsed.value < 0) {
|
||||
return previousValue;
|
||||
if (handler.onInput) {
|
||||
handler.onInput(realValue);
|
||||
}
|
||||
setError("");
|
||||
} catch (e) {
|
||||
setError(i18n.str`Amount is not valid`);
|
||||
}
|
||||
|
||||
const realValue = denormalize(parsed, unit);
|
||||
|
||||
// console.log(real, unit, normal);
|
||||
if (realValue && handler.onInput) {
|
||||
handler.onInput(realValue);
|
||||
}
|
||||
return withoutDots;
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
return previousValue;
|
||||
setTextValue(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -149,7 +119,7 @@ export function AmountField({
|
||||
disabled={!handler.onInput}
|
||||
onChange={(e) => {
|
||||
const unit = Number.parseFloat(e.currentTarget.value);
|
||||
setUnit(unit);
|
||||
updateUnit(unit);
|
||||
}}
|
||||
value={String(unit)}
|
||||
style={{
|
||||
@ -171,29 +141,11 @@ export function AmountField({
|
||||
disabled={!handler.onInput}
|
||||
onInput={positiveAmount}
|
||||
/>
|
||||
{error && <div style={{ color: "red" }}>{error}</div>}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
function parseValue(currency: string, s: string): AmountJson | undefined {
|
||||
const [intPart, fractPart] = s.split(".");
|
||||
const tailPart = !fractPart
|
||||
? "0"
|
||||
: fractPart.substring(0, amountFractionalLength);
|
||||
|
||||
const value = Number.parseInt(intPart, 10);
|
||||
const parsedTail = Number.parseFloat(`.${tailPart}`);
|
||||
if (Number.isNaN(value) || Number.isNaN(parsedTail)) {
|
||||
return undefined;
|
||||
}
|
||||
if (value > amountMaxValue) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fraction = Math.round(amountFractionalBase * parsedTail);
|
||||
return { currency, fraction, value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the real value of a normalized unit
|
||||
* If the value is 20 and the unit is kilo == 1000 the returned value will be amount * 1000
|
||||
@ -201,7 +153,7 @@ function parseValue(currency: string, s: string): AmountJson | undefined {
|
||||
* @param unit
|
||||
* @returns
|
||||
*/
|
||||
function denormalize(amount: AmountJson, unit: number): AmountJson | undefined {
|
||||
function denormalize(amount: AmountJson, unit: number): AmountJson {
|
||||
if (unit === 1 || Amounts.isZero(amount)) return amount;
|
||||
const result =
|
||||
unit < 1
|
||||
@ -218,7 +170,7 @@ function denormalize(amount: AmountJson, unit: number): AmountJson | undefined {
|
||||
* @param unit
|
||||
* @returns
|
||||
*/
|
||||
function normalize(amount: AmountJson, unit: number): AmountJson | undefined {
|
||||
function normalize(amount: AmountJson, unit: number): AmountJson {
|
||||
if (unit === 1 || Amounts.isZero(amount)) return amount;
|
||||
const result =
|
||||
unit < 1
|
||||
@ -226,3 +178,43 @@ function normalize(amount: AmountJson, unit: number): AmountJson | undefined {
|
||||
: Amounts.divide(amount, unit);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take every label in HIGH_DENOM_SYMBOL and LOW_DENOM_SYMBOL and create
|
||||
* which create the corresponding unit multiplier
|
||||
* @param currency
|
||||
* @param lowestDenom
|
||||
* @param highestDenom
|
||||
* @returns
|
||||
*/
|
||||
function buildLabelsForCurrency(
|
||||
currency: string,
|
||||
lowestDenom: number,
|
||||
highestDenom: number,
|
||||
): Array<{ name: string; unit: number }> {
|
||||
let hd = Math.floor(Math.log10(highestDenom || 1) / 3);
|
||||
let ld = Math.ceil((-1 * Math.log10(lowestDenom || 1)) / 3);
|
||||
|
||||
const result: Array<{ name: string; unit: number }> = [
|
||||
{
|
||||
name: currency,
|
||||
unit: 1,
|
||||
},
|
||||
];
|
||||
|
||||
while (hd > 0) {
|
||||
result.push({
|
||||
name: `${HIGH_DENOM_SYMBOL[hd]}${currency}`,
|
||||
unit: Math.pow(10, hd * 3),
|
||||
});
|
||||
hd--;
|
||||
}
|
||||
while (ld > 0) {
|
||||
result.push({
|
||||
name: `${LOW_DENOM_SYMBOL[ld]}${currency}`,
|
||||
unit: Math.pow(10, -1 * ld * 3),
|
||||
});
|
||||
ld--;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user