aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/WalletActivity.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/components/WalletActivity.tsx505
1 files changed, 228 insertions, 277 deletions
diff --git a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
index 69a2c0675..41b0c5c76 100644
--- a/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
+++ b/packages/taler-wallet-webextension/src/components/WalletActivity.tsx
@@ -15,6 +15,7 @@
*/
import {
AbsoluteTime,
+ ExchangeStateTransitionNotification,
NotificationType,
ObservabilityEventType,
RequestProgressNotification,
@@ -26,38 +27,76 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
-import { Fragment, JSX, VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { Pages } from "../NavigationBar.js";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { useSettings } from "../hooks/useSettings.js";
import { Button } from "../mui/Button.js";
+import { SafeHandler } from "../mui/handlers.js";
import { WxApiType } from "../wxApi.js";
import { Modal } from "./Modal.js";
import { Time } from "./Time.js";
+import { TextField } from "../mui/TextField.js";
+import { WalletActivityTrack } from "../wxBackend.js";
-interface Props extends JSX.HTMLAttributes {}
+const OPEN_ACTIVITY_HEIGHT_PX = 250;
+const CLOSE_ACTIVITY_HEIGHT_PX = 40;
-export function WalletActivity({}: Props): VNode {
+export function WalletActivity(): VNode {
const { i18n } = useTranslationContext();
- const [settings, updateSettings] = useSettings();
- const api = useBackendContext();
+ const [, updateSettings] = useSettings();
+
+ const [collapsed, setCollcapsed] = useState(true);
+
useEffect(() => {
- document.body.style.marginBottom = "250px";
+ document.body.style.marginBottom = `${
+ collapsed ? CLOSE_ACTIVITY_HEIGHT_PX : OPEN_ACTIVITY_HEIGHT_PX
+ }px`;
return () => {
document.body.style.marginBottom = "0px";
};
- });
- const [table, setTable] = useState<"tasks" | "events">("tasks");
+ }, [collapsed]);
+
+ const [table, setTable] = useState<"tasks" | "events">("events");
+ if (collapsed) {
+ return (
+ <div
+ style={{
+ position: "fixed",
+ bottom: 0,
+ background: "lightgrey",
+ zIndex: 1,
+ height: CLOSE_ACTIVITY_HEIGHT_PX,
+ overflowY: "scroll",
+ width: "100%",
+ }}
+ onClick={() => {
+ setCollcapsed(!collapsed);
+ }}
+ >
+ <div
+ style={{
+ display: "flex",
+ justifyContent: "space-around",
+ marginTop: 10,
+ cursor: "pointer",
+ }}
+ >
+ click here to open
+ </div>
+ </div>
+ );
+ }
return (
<div
style={{
position: "fixed",
bottom: 0,
- background: "white",
+ background: "lightgrey",
zIndex: 1,
- height: 250,
+ height: OPEN_ACTIVITY_HEIGHT_PX,
overflowY: "scroll",
width: "100%",
}}
@@ -65,23 +104,22 @@ export function WalletActivity({}: Props): VNode {
<div
style={{
display: "flex",
- justifyContent: "space-between",
- float: "right",
+ justifyContent: "space-around",
+ cursor: "pointer",
+ }}
+ onClick={() => {
+ setCollcapsed(!collapsed);
}}
>
- <div />
- <div>
- <div
- style={{ padding: 4, margin: 2, border: "solid 1px black" }}
- onClick={() => {
- updateSettings("showWalletActivity", false);
- }}
- >
- close
- </div>
- </div>
- </div>
- <div style={{ display: "flex", justifyContent: "space-around" }}>
+ <Button
+ variant={table === "events" ? "contained" : "outlined"}
+ style={{ margin: 4 }}
+ onClick={async () => {
+ setTable("events");
+ }}
+ >
+ <i18n.Translate>Events</i18n.Translate>
+ </Button>
<Button
variant={table === "tasks" ? "contained" : "outlined"}
style={{ margin: 4 }}
@@ -89,31 +127,38 @@ export function WalletActivity({}: Props): VNode {
setTable("tasks");
}}
>
- <i18n.Translate>Tasks</i18n.Translate>
+ <i18n.Translate>Active tasks</i18n.Translate>
</Button>
+
<Button
- variant={table === "events" ? "contained" : "outlined"}
+ variant="outlined"
style={{ margin: 4 }}
onClick={async () => {
- setTable("events");
+ updateSettings("showWalletActivity", false);
}}
>
- <i18n.Translate>Events</i18n.Translate>
+ <i18n.Translate>Close</i18n.Translate>
</Button>
</div>
- {(function (): VNode {
- switch (table) {
- case "events": {
- return <ObservabilityEventsTable />;
- }
- case "tasks": {
- return <ActiveTasksTable />;
- }
- default: {
- assertUnreachable(table);
+ <div
+ style={{
+ backgroundColor: "white",
+ }}
+ >
+ {(function (): VNode {
+ switch (table) {
+ case "events": {
+ return <ObservabilityEventsTable />;
+ }
+ case "tasks": {
+ return <ActiveTasksTable />;
+ }
+ default: {
+ assertUnreachable(table);
+ }
}
- }
- })()}
+ })()}
+ </div>
</div>
);
}
@@ -122,21 +167,6 @@ interface MoreInfoPRops {
events: (WalletNotification & { when: AbsoluteTime })[];
onClick: (content: VNode) => void;
}
-type Notif = {
- id: string;
- events: (WalletNotification & { when: AbsoluteTime })[];
- description: string;
- start: AbsoluteTime;
- end: AbsoluteTime;
- reference:
- | {
- eventType: NotificationType;
- referenceType: "task" | "transaction" | "operation" | "exchange";
- id: string;
- }
- | undefined;
- MoreInfo: (p: MoreInfoPRops) => VNode;
-};
function ShowBalanceChange({ events }: MoreInfoPRops): VNode {
if (!events.length) return <Fragment />;
@@ -267,10 +297,7 @@ function ShowTransactionStateTransition({
</Fragment>
);
}
-function ShowExchangeStateTransition({
- events,
- onClick,
-}: MoreInfoPRops): VNode {
+function ShowExchangeStateTransition({ events }: MoreInfoPRops): VNode {
if (!events.length) return <Fragment />;
const not = events[0];
if (not.type !== NotificationType.ExchangeStateTransition)
@@ -323,7 +350,7 @@ type ObservaNotifWithTime = (
};
function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode {
// let prev: ObservaNotifWithTime;
- const asd = events.map((not) => {
+ const asd = events.map((not, idx) => {
if (
not.type !== NotificationType.RequestObservabilityEvent &&
not.type !== NotificationType.TaskObservabilityEvent
@@ -364,7 +391,12 @@ function ShowObservabilityEvent({ events, onClick }: MoreInfoPRops): VNode {
})();
return (
- <ShowObervavilityDetails title={title} notif={not} onClick={onClick} />
+ <ShowObervavilityDetails
+ key={idx}
+ title={title}
+ notif={not}
+ onClick={onClick}
+ />
);
});
return (
@@ -673,235 +705,64 @@ function ShowObervavilityDetails({
}
}
-function getNotificationFor(
- id: string,
- event: WalletNotification,
- start: AbsoluteTime,
- list: Notif[],
-): Notif | undefined {
- const eventWithTime = { ...event, when: start };
- switch (event.type) {
- case NotificationType.BalanceChange: {
- return {
- id,
- events: [eventWithTime],
- reference: {
- eventType: event.type,
- referenceType: "transaction",
- id: event.hintTransactionId,
- },
- description: "Balance change",
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowBalanceChange,
- };
- }
- case NotificationType.BackupOperationError: {
- return {
- id,
- events: [eventWithTime],
- reference: undefined,
- description: "Backup error",
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowBackupOperationError,
- };
- }
- case NotificationType.TransactionStateTransition: {
- const found = list.find(
- (a) =>
- a.reference?.eventType === event.type &&
- a.reference.id === event.transactionId,
- );
- if (found) {
- found.end = start;
- found.events.unshift(eventWithTime);
- return undefined;
- }
- return {
- id,
- events: [eventWithTime],
- reference: {
- eventType: event.type,
- referenceType: "transaction",
- id: event.transactionId,
- },
- description: event.type,
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowTransactionStateTransition,
- };
- }
- case NotificationType.ExchangeStateTransition: {
- const found = list.find(
- (a) =>
- a.reference?.eventType === event.type &&
- a.reference.id === event.exchangeBaseUrl,
- );
- if (found) {
- found.end = start;
- found.events.unshift(eventWithTime);
- return undefined;
- }
- return {
- id,
- events: [eventWithTime],
- description: "Exchange update",
- reference: {
- eventType: event.type,
- referenceType: "exchange",
- id: event.exchangeBaseUrl,
- },
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowExchangeStateTransition,
- };
- }
- case NotificationType.TaskObservabilityEvent: {
- const found = list.find(
- (a) =>
- a.reference?.eventType === event.type &&
- a.reference.id === event.taskId,
- );
- if (found) {
- found.end = start;
- found.events.unshift(eventWithTime);
- return undefined;
- }
- return {
- id,
- events: [eventWithTime],
- reference: {
- eventType: event.type,
- referenceType: "task",
- id: event.taskId,
- },
- description: `Task update ${event.taskId}`,
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent,
- };
- }
- case NotificationType.WithdrawalOperationTransition: {
- const found = list.find(
- (a) =>
- a.reference?.eventType === event.type && a.reference.id === event.uri,
- );
- if (found) {
- found.end = start;
- found.events.unshift(eventWithTime);
- return undefined;
- }
- return {
- id,
- events: [eventWithTime],
- reference: {
- eventType: event.type,
- referenceType: "task",
- id: event.uri,
- },
- description: `Withdrawal operation updated`,
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent,
- };
- }
- case NotificationType.RequestObservabilityEvent: {
- const found = list.find(
- (a) =>
- a.reference?.eventType === event.type &&
- a.reference.id === event.requestId,
- );
- if (found) {
- found.end = start;
- found.events.unshift(eventWithTime);
- return undefined;
- }
- return {
- id,
- events: [eventWithTime],
- reference: {
- eventType: event.type,
- referenceType: "operation",
- id: event.requestId,
- },
- description: `wallet.${event.operation}(${event.requestId})`,
- start,
- end: AbsoluteTime.never(),
- MoreInfo: ShowObservabilityEvent,
- };
- }
- case NotificationType.Idle:
- return undefined;
- default: {
- assertUnreachable(event);
- }
- }
-}
-
-function refresh(api: WxApiType, onUpdate: (list: Notif[]) => void) {
+function refresh(
+ api: WxApiType,
+ onUpdate: (list: WalletActivityTrack[]) => void,
+ filter: string,
+) {
api.background
- .call("getNotifications", undefined)
+ .call("getNotifications", { filter })
.then((notif) => {
- const list: Notif[] = [];
- for (const n of notif) {
- if (
- n.notification.type === NotificationType.RequestObservabilityEvent &&
- n.notification.operation === "getActiveTasks"
- ) {
- //ignore monitor request
- continue;
- }
- const event = getNotificationFor(
- String(list.length),
- n.notification,
- n.when,
- list,
- );
- // pepe.
- if (event) {
- list.unshift(event);
- }
- }
- onUpdate(list);
+ onUpdate(notif);
})
.catch((error) => {
console.log(error);
});
}
-export function ObservabilityEventsTable({}: {}): VNode {
+export function ObservabilityEventsTable(): VNode {
const { i18n } = useTranslationContext();
const api = useBackendContext();
- const [notifications, setNotifications] = useState<Notif[]>([]);
+ const [notifications, setNotifications] = useState<WalletActivityTrack[]>([]);
const [showDetails, setShowDetails] = useState<VNode>();
+ const [filter, onChangeFilter] = useState("");
useEffect(() => {
let lastTimeout: ReturnType<typeof setTimeout>;
function periodicRefresh() {
- refresh(api, setNotifications);
+ refresh(api, setNotifications, filter);
lastTimeout = setTimeout(() => {
periodicRefresh();
}, 1000);
- //clear on unload
return () => {
clearTimeout(lastTimeout);
};
}
return periodicRefresh();
- }, [1]);
+ }, [filter]);
return (
<div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
+ <TextField
+ label="Filter"
+ variant="outlined"
+ value={filter}
+ onChange={onChangeFilter}
+ />
<div
- style={{ padding: 4, margin: 2, border: "solid 1px black" }}
+ style={{
+ padding: 4,
+ margin: 2,
+ border: "solid 1px black",
+ alignSelf: "center",
+ }}
onClick={() => {
- api.background.call("clearNotifications", undefined).then((d) => {
- refresh(api, setNotifications);
+ api.background.call("clearNotifications", undefined).then(() => {
+ refresh(api, setNotifications, filter);
});
}}
>
@@ -914,7 +775,7 @@ export function ObservabilityEventsTable({}: {}): VNode {
onClose={{
onClick: (async () => {
setShowDetails(undefined);
- }) as any,
+ }) as SafeHandler<void>,
}}
>
{showDetails}
@@ -932,7 +793,40 @@ export function ObservabilityEventsTable({}: {}): VNode {
padding: 4,
}}
>
- <div style={{ padding: 4 }}>{not.description}</div>
+ <div style={{ padding: 4 }}>
+ {(() => {
+ switch (not.type) {
+ case NotificationType.BalanceChange:
+ return i18n.str`Balance change`;
+ case NotificationType.BackupOperationError:
+ return i18n.str`Backup failed`;
+ case NotificationType.TransactionStateTransition:
+ return i18n.str`Transaction updated`;
+ case NotificationType.ExchangeStateTransition:
+ return i18n.str`Exchange updated`;
+ case NotificationType.Idle:
+ return i18n.str`Idle`;
+ case NotificationType.TaskObservabilityEvent:
+ return i18n.str`task.${
+ (not.events[0] as TaskProgressNotification).taskId
+ }`;
+ case NotificationType.RequestObservabilityEvent:
+ return i18n.str`wallet.${
+ (not.events[0] as RequestProgressNotification)
+ .operation
+ }(${
+ (not.events[0] as RequestProgressNotification)
+ .requestId
+ })`;
+ case NotificationType.WithdrawalOperationTransition: {
+ return `---`;
+ }
+ default: {
+ assertUnreachable(not.type);
+ }
+ }
+ })()}
+ </div>
<div style={{ padding: 4 }}>
<Time timestamp={not.start} format="yyyy/MM/dd HH:mm:ss" />
</div>
@@ -941,12 +835,76 @@ export function ObservabilityEventsTable({}: {}): VNode {
</div>
</div>
</summary>
- <not.MoreInfo
- events={not.events}
- onClick={(details) => {
- setShowDetails(details);
- }}
- />
+ {(() => {
+ switch (not.type) {
+ case NotificationType.BalanceChange: {
+ return (
+ <ShowBalanceChange
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.BackupOperationError: {
+ return (
+ <ShowBackupOperationError
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.TransactionStateTransition: {
+ return (
+ <ShowTransactionStateTransition
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.ExchangeStateTransition: {
+ return (
+ <ShowExchangeStateTransition
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.Idle: {
+ return <div>not implemented</div>;
+ }
+ case NotificationType.TaskObservabilityEvent: {
+ return (
+ <ShowObservabilityEvent
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.RequestObservabilityEvent: {
+ return (
+ <ShowObservabilityEvent
+ events={not.events}
+ onClick={(details) => {
+ setShowDetails(details);
+ }}
+ />
+ );
+ }
+ case NotificationType.WithdrawalOperationTransition: {
+ return <div>not implemented</div>;
+ }
+ }
+ })()}
</details>
);
})}
@@ -965,7 +923,7 @@ function ErroDetailModal({
<Modal
title="Full detail"
onClose={{
- onClick: onClose as any,
+ onClick: onClose as SafeHandler<void>,
}}
>
<dl>
@@ -987,7 +945,7 @@ function ErroDetailModal({
);
}
-export function ActiveTasksTable({}: {}): VNode {
+export function ActiveTasksTable(): VNode {
const { i18n } = useTranslationContext();
const api = useBackendContext();
const state = useAsyncAsHook(() => {
@@ -1006,13 +964,6 @@ export function ActiveTasksTable({}: {}): VNode {
};
}, [tasks]);
- // const listenAllEvents = Array.from<NotificationType>({ length: 1 });
- // listenAllEvents.includes = () => true
- // useEffect(() => {
- // return api.listener.onUpdateNotification(listenAllEvents, (notif) => {
- // state?.retry()
- // });
- // });
return (
<Fragment>
{showError && (
@@ -1051,7 +1002,7 @@ export function ActiveTasksTable({}: {}): VNode {
{tasks.map((task) => {
const [type, id] = task.taskId.split(":");
return (
- <tr>
+ <tr key={id}>
<td>{type}</td>
<td title={id}>{id.substring(0, 10)}</td>
<td>