/*
This file is part of GNU Anastasis
(C) 2021-2022 Anastasis SARL
GNU Anastasis is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with
GNU Anastasis; see the file COPYING. If not, see
*/
import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core";
import {
ComponentChildren,
Fragment,
FunctionalComponent,
h,
VNode,
} from "preact";
import { useCallback, useEffect, useErrorBoundary } from "preact/hooks";
import { AsyncButton } from "../../components/AsyncButton.js";
import { Menu } from "../../components/menu/index.js";
import { Notifications } from "../../components/Notifications.js";
import {
AnastasisProvider,
useAnastasisContext,
} from "../../context/anastasis.js";
import {
AnastasisReducerApi,
useAnastasisReducer,
} from "../../hooks/use-anastasis-reducer.js";
import { AttributeEntryScreen } from "./AttributeEntryScreen.js";
import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen.js";
import { BackupFinishedScreen } from "./BackupFinishedScreen.js";
import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen.js";
import { ChallengePayingScreen } from "./ChallengePayingScreen.js";
import { ContinentSelectionScreen } from "./ContinentSelectionScreen.js";
import { PoliciesPayingScreen } from "./PoliciesPayingScreen.js";
import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen.js";
import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen.js";
import { SecretEditorScreen } from "./SecretEditorScreen.js";
import { SecretSelectionScreen } from "./SecretSelectionScreen.js";
import { SolveScreen } from "./SolveScreen.js";
import { StartScreen } from "./StartScreen.js";
import { TruthsPayingScreen } from "./TruthsPayingScreen.js";
function isBackup(reducer: AnastasisReducerApi): boolean {
return reducer.currentReducerState?.reducer_type === "backup";
}
export function withProcessLabel(
reducer: AnastasisReducerApi,
text: string,
): string {
if (isBackup(reducer)) {
return `Backup: ${text}`;
}
return `Recovery: ${text}`;
}
interface AnastasisClientFrameProps {
onNext?(): Promise;
/**
* Override for the "back" functionality.
*/
onBack?(): Promise;
title: string;
children: ComponentChildren;
/**
* Should back/next buttons be provided?
*/
hideNav?: boolean;
/**
* Hide only the "next" button.
*/
hideNext?: string;
}
function ErrorBoundary(props: {
reducer: AnastasisReducerApi;
children: ComponentChildren;
}): VNode {
const [error, resetError] = useErrorBoundary((error) =>
console.log("got error", error),
);
if (error) {
return (
Error:
{error.stack}
);
}
return
{props.children}
;
}
let currentHistoryId = 0;
export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
const reducer = useAnastasisContext();
const doBack = async (): Promise => {
if (props.onBack) {
await props.onBack();
} else {
if (!reducer) return;
await reducer.back();
}
};
const doNext = async (fromPopstate?: boolean): Promise => {
if (!fromPopstate) {
try {
const nextId: number =
(history.state && typeof history.state.id === "number"
? history.state.id
: 0) + 1;
currentHistoryId = nextId;
history.pushState({ id: nextId }, "unused", `#${nextId}`);
} catch (e) {
console.log(e);
}
}
if (props.onNext) {
await props.onNext();
} else {
if (!reducer) return;
await reducer.transition("next", {});
}
};
const handleKeyPress = (
e: h.JSX.TargetedKeyboardEvent,
): void => {
console.log("Got key press", e.key);
// FIXME: By default, "next" action should be executed here
};
const browserOnBackButton = useCallback(async (ev: PopStateEvent) => {
//check if we are going back or forward
if (!ev.state || ev.state.id === 0 || ev.state.id < currentHistoryId) {
await doBack();
} else {
await doNext(true);
}
// reducer
return false;
}, []);
useEffect(() => {
window.addEventListener("popstate", browserOnBackButton);
return () => {
window.removeEventListener("popstate", browserOnBackButton);
};
}, []);
if (!reducer) {
return
Fatal: Reducer must be in context.
;
}
return (
handleKeyPress(e)}>
{props.title}
{props.children}
{!props.hideNav ? (
doNext()}
disabled={props.hideNext !== undefined}
>
Next
);
}
/**
* Show a dismissible error banner if there is a current error.
*/
function ErrorBanner(): VNode | null {
const reducer = useAnastasisContext();
if (!reducer || !reducer.currentError) return null;
return (
);
}
export default AnastasisClient;