1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
import { OperationFail, OperationOk, OperationResult, TalerError, TranslatedString } from "@gnu-taler/taler-util";
// import { NotificationMessage, notifyInfo } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { HTMLAttributes, useEffect, useState, useTransition } from "preact/compat";
import { NotificationMessage, buildRequestErrorMessage, notifyInfo, useTranslationContext } from "../index.browser.js";
// import { useBankCoreApiContext } from "../context/config.js";
// function errorMap<T extends OperationFail<unknown>>(resp: T, map: (d: T["case"]) => TranslatedString): void {
export interface ButtonHandler<T extends OperationResult<A, B>, A, B> {
onClick: () => Promise<T | undefined>,
onNotification: (n: NotificationMessage) => void;
onOperationSuccess: ((result:T extends OperationOk<any> ? T :never) => void) | ((result:T extends OperationOk<any> ? T :never) => TranslatedString | undefined),
onOperationFail: (d: T extends OperationFail<any> ? T : never) => TranslatedString;
onOperationComplete?: () => void;
}
interface Props<T extends OperationResult<A, B>, A, B> extends HTMLAttributes<HTMLButtonElement> {
handler: ButtonHandler<T, A, B> | undefined,
}
/**
* This button accept an async function and report a notification
* on error or success.
*
* When the async function is running the inner text will change into
* a "loading" animation.
*
* @param param0
* @returns
*/
export function Button<T extends OperationResult<A, B>, A, B>({
handler,
children,
disabled,
onClick:clickEvent,
...rest
}: Props<T, A, B>): VNode {
const {i18n} = useTranslationContext();
const [running, setRunning] = useState(false)
return <button {...rest} disabled={disabled || running} onClick={(e) => {
e.preventDefault();
if (!handler) { return; }
setRunning(true)
handler.onClick().then((resp) => {
if (resp) {
if (resp.type === "ok") {
const result: OperationOk<any> = resp
// @ts-expect-error this is an operationOk
const msg = handler.onOperationSuccess(result)
if (msg) {
notifyInfo(msg)
}
}
if (resp.type === "fail") {
// @ts-expect-error this is an operationFail
const error: OperationFail<any> = resp;
// @ts-expect-error this is an operationFail
const title = handler.onOperationFail(error)
handler.onNotification({
title,
type: "error",
description: error.detail.hint as TranslatedString,
debug: error.detail,
})
}
}
if (handler.onOperationComplete) {
handler.onOperationComplete()
}
setRunning(false)
}).catch(error => {
console.error(error)
if (error instanceof TalerError) {
handler.onNotification(buildRequestErrorMessage(i18n, error))
} else {
const description = (error instanceof Error ?
error.message : String(error)) as TranslatedString
handler.onNotification({
title: i18n.str`Operation failed`,
type: "error",
description,
})
}
if (handler.onOperationComplete) {
handler.onOperationComplete()
}
setRunning(false)
})
}} >
{running ? <Wait /> : children}
</button>
}
function Wait():VNode {
return <Fragment>
<style>{`
#l1 { width: 120px;
height: 20px;
-webkit-mask: radial-gradient(circle closest-side, currentColor 90%, #0000) left/20% 100%;
background: linear-gradient(currentColor 0 0) left/0% 100% no-repeat #ddd;
animation: l17 2s infinite steps(6);
}
@keyframes l17 {
100% {background-size:120% 100%}
`}
</style>
<div id="l1" />
</Fragment>
}
|