aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/components/AmountField.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/AmountField.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/components/AmountField.tsx185
1 files changed, 158 insertions, 27 deletions
diff --git a/packages/taler-wallet-webextension/src/components/AmountField.tsx b/packages/taler-wallet-webextension/src/components/AmountField.tsx
index 1c57be0df..6081e70ff 100644
--- a/packages/taler-wallet-webextension/src/components/AmountField.tsx
+++ b/packages/taler-wallet-webextension/src/components/AmountField.tsx
@@ -14,51 +14,182 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import {
+ amountFractionalBase,
+ amountFractionalLength,
+ AmountJson,
+ amountMaxValue,
+ Amounts,
+ Result,
+} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
-import { TextFieldHandler } from "../mui/handlers.js";
+import { useState } from "preact/hooks";
+import { AmountFieldHandler } from "../mui/handlers.js";
import { TextField } from "../mui/TextField.js";
-import { ErrorText } from "./styled/index.js";
+
+const HIGH_DENOM_SYMBOL = ["", "K", "M", "G", "T", "P"];
+const LOW_DENOM_SYMBOL = ["", "m", "mm", "n", "p", "f"];
export function AmountField({
label,
handler,
- currency,
+ lowestDenom = 1,
+ highestDenom = 1,
required,
}: {
label: VNode;
+ lowestDenom?: number;
+ highestDenom?: number;
required?: boolean;
- currency: string;
- handler: TextFieldHandler;
+ handler: AmountFieldHandler;
}): VNode {
+ const [unit, setUnit] = useState(1);
+ const [dotAtTheEnd, setDotAtTheEnd] = useState(false);
+ 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 prev = Amounts.stringifyValue(handler.value);
+
function positiveAmount(value: string): string {
- if (!value) return "";
- try {
- const num = Number.parseFloat(value);
- if (Number.isNaN(num) || num < 0) return handler.value;
+ setDotAtTheEnd(value.endsWith("."));
+ if (!value) {
if (handler.onInput) {
- handler.onInput(value);
+ handler.onInput(Amounts.zeroOfCurrency(currency));
}
- return value;
+ return "";
+ }
+ try {
+ //remove all but last dot
+ const parsed = value.replace(/(\.)(?=.*\1)/g, "");
+ const real = parseValue(currency, parsed);
+
+ if (!real || real.value < 0) {
+ return prev;
+ }
+
+ const normal = normalize(real, unit);
+
+ console.log(real, unit, normal);
+ if (normal && handler.onInput) {
+ handler.onInput(normal);
+ }
+ return parsed;
} catch (e) {
// do nothing
}
- return handler.value;
+ return prev;
}
+
+ const normal = denormalize(handler.value, unit) ?? handler.value;
+
+ const textValue = Amounts.stringifyValue(normal) + (dotAtTheEnd ? "." : "");
return (
- <TextField
- label={label}
- type="number"
- min="0"
- step="0.1"
- variant="filled"
- error={handler.error}
- required={required}
- startAdornment={
- <div style={{ padding: "25px 12px 8px 12px" }}>{currency}</div>
- }
- value={handler.value}
- disabled={!handler.onInput}
- onInput={positiveAmount}
- />
+ <Fragment>
+ <TextField
+ label={label}
+ type="text"
+ min="0"
+ inputmode="decimal"
+ step="0.1"
+ variant="filled"
+ error={handler.error}
+ required={required}
+ startAdornment={
+ currencyLabels.length === 1 ? (
+ <div
+ style={{
+ marginTop: 20,
+ padding: "5px 12px 8px 12px",
+ }}
+ >
+ {currency}
+ </div>
+ ) : (
+ <select
+ disabled={!handler.onInput}
+ onChange={(e) => {
+ const unit = Number.parseFloat(e.currentTarget.value);
+ setUnit(unit);
+ }}
+ value={String(unit)}
+ style={{
+ marginTop: 20,
+ padding: "5px 12px 8px 12px",
+ background: "transparent",
+ border: 0,
+ }}
+ >
+ {currencyLabels.map((c) => (
+ <option key={c} value={c.unit}>
+ <div>{c.name}</div>
+ </option>
+ ))}
+ </select>
+ )
+ }
+ value={textValue}
+ disabled={!handler.onInput}
+ onInput={positiveAmount}
+ />
+ </Fragment>
);
}
+
+function parseValue(currency: string, s: string): AmountJson | undefined {
+ const [intPart, fractPart] = s.split(".");
+ const tail = "." + (fractPart || "0");
+ if (tail.length > amountFractionalLength + 1) {
+ return undefined;
+ }
+ const value = Number.parseInt(intPart, 10);
+ if (Number.isNaN(value) || value > amountMaxValue) {
+ return undefined;
+ }
+ return {
+ currency,
+ fraction: Math.round(amountFractionalBase * Number.parseFloat(tail)),
+ value,
+ };
+}
+
+function normalize(amount: AmountJson, unit: number): AmountJson | undefined {
+ if (unit === 1 || Amounts.isZero(amount)) return amount;
+ const result =
+ unit < 1
+ ? Amounts.divide(amount, 1 / unit)
+ : Amounts.mult(amount, unit).amount;
+ return result;
+}
+
+function denormalize(amount: AmountJson, unit: number): AmountJson | undefined {
+ if (unit === 1 || Amounts.isZero(amount)) return amount;
+ const result =
+ unit < 1
+ ? Amounts.mult(amount, 1 / unit).amount
+ : Amounts.divide(amount, unit);
+ return result;
+}