summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/anastasis-webui/src')
-rw-r--r--packages/anastasis-webui/src/components/menu/SideBar.tsx33
-rw-r--r--packages/anastasis-webui/src/components/picker/DurationPicker.tsx12
-rw-r--r--packages/anastasis-webui/src/context/translation.ts41
-rw-r--r--packages/anastasis-webui/src/hooks/index.ts71
-rw-r--r--packages/anastasis-webui/src/hooks/useLang.ts (renamed from packages/anastasis-webui/src/main.ts)33
-rw-r--r--packages/anastasis-webui/src/hooks/useLocalStorage.ts80
-rw-r--r--packages/anastasis-webui/src/i18n/index.tsx211
-rw-r--r--packages/anastasis-webui/src/index.html42
-rw-r--r--packages/anastasis-webui/src/index.test.ts (renamed from packages/anastasis-webui/src/main.test.ts)0
-rw-r--r--packages/anastasis-webui/src/index.ts25
-rw-r--r--packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx4
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx6
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx6
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx6
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx1
-rw-r--r--packages/anastasis-webui/src/scss/_mixins.scss2
-rw-r--r--packages/anastasis-webui/src/stories.tsx365
-rw-r--r--packages/anastasis-webui/src/test-utils.ts12
-rw-r--r--packages/anastasis-webui/src/utils/index.tsx16
46 files changed, 286 insertions, 707 deletions
diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx
index 51e854944..3dac73e04 100644
--- a/packages/anastasis-webui/src/components/menu/SideBar.tsx
+++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx
@@ -22,7 +22,7 @@
import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core";
import { Fragment, h, VNode } from "preact";
import { useAnastasisContext } from "../../context/anastasis.js";
-import { Translate } from "../../i18n/index.js";
+import { useTranslationContext } from "../../context/translation.js";
interface Props {
mobile?: boolean;
@@ -34,6 +34,7 @@ const VERSION_WITH_HASH = GIT_HASH ? `${VERSION}-${GIT_HASH}` : VERSION;
export function Sidebar({ mobile }: Props): VNode {
const reducer = useAnastasisContext()!;
+ const { i18n } = useTranslationContext();
function saveSession(): void {
const state = reducer.exportState();
@@ -64,7 +65,7 @@ export function Sidebar({ mobile }: Props): VNode {
<div class="menu is-menu-main">
{!reducer.currentReducerState && (
<p class="menu-label">
- <Translate>Backup or Recorver</Translate>
+ <i18n.Translate>Backup or Recorver</i18n.Translate>
</p>
)}
<ul class="menu-list">
@@ -72,7 +73,7 @@ export function Sidebar({ mobile }: Props): VNode {
<li>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Select one option</Translate>
+ <i18n.Translate>Select one option</i18n.Translate>
</span>
</div>
</li>
@@ -91,7 +92,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Location</Translate>
+ <i18n.Translate>Location</i18n.Translate>
</span>
</div>
</li>
@@ -105,7 +106,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Personal information</Translate>
+ <i18n.Translate>Personal information</i18n.Translate>
</span>
</div>
</li>
@@ -119,7 +120,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Authorization methods</Translate>
+ <i18n.Translate>Authorization methods</i18n.Translate>
</span>
</div>
</li>
@@ -133,7 +134,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Policies</Translate>
+ <i18n.Translate>Policies</i18n.Translate>
</span>
</div>
</li>
@@ -147,14 +148,14 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Secret input</Translate>
+ <i18n.Translate>Secret input</i18n.Translate>
</span>
</div>
</li>
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
<div class="ml-4">
- <span class="menu-item-label"><Translate>Payment (optional)</Translate></span>
+ <span class="menu-item-label"><i18n.Translate>Payment (optional)</i18n.Translate></span>
</div>
</li> */}
<li
@@ -167,14 +168,14 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Backup completed</Translate>
+ <i18n.Translate>Backup completed</i18n.Translate>
</span>
</div>
</li>
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
<div class="ml-4">
- <span class="menu-item-label"><Translate>Truth Paying</Translate></span>
+ <span class="menu-item-label"><i18n.Translate>Truth Paying</i18n.Translate></span>
</div>
</li> */}
{reducer.currentReducerState.backup_state !==
@@ -219,7 +220,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Location</Translate>
+ <i18n.Translate>Location</i18n.Translate>
</span>
</div>
</li>
@@ -233,7 +234,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Personal information</Translate>
+ <i18n.Translate>Personal information</i18n.Translate>
</span>
</div>
</li>
@@ -247,7 +248,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Secret selection</Translate>
+ <i18n.Translate>Secret selection</i18n.Translate>
</span>
</div>
</li>
@@ -263,7 +264,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Solve Challenges</Translate>
+ <i18n.Translate>Solve Challenges</i18n.Translate>
</span>
</div>
</li>
@@ -277,7 +278,7 @@ export function Sidebar({ mobile }: Props): VNode {
>
<div class="ml-4">
<span class="menu-item-label">
- <Translate>Secret recovered</Translate>
+ <i18n.Translate>Secret recovered</i18n.Translate>
</span>
</div>
</li>
diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
index 12ed158dd..c4caaec9f 100644
--- a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
@@ -21,7 +21,7 @@
import { h, VNode } from "preact";
import { useState } from "preact/hooks";
-import { useTranslator } from "../../i18n/index.js";
+import { useTranslationContext } from "../../context/translation.js";
import "../../scss/DurationPicker.scss";
export interface Props {
@@ -46,13 +46,13 @@ export function DurationPicker({
const ms = ss * 60;
const hs = ms * 60;
const ds = hs * 24;
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
return (
<div class="rdp-picker">
{days && (
<DurationColumn
- unit={i18n`days`}
+ unit={i18n.str`days`}
max={99}
value={Math.floor(value / ds)}
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
@@ -62,7 +62,7 @@ export function DurationPicker({
)}
{hours && (
<DurationColumn
- unit={i18n`hours`}
+ unit={i18n.str`hours`}
max={23}
min={1}
value={Math.floor(value / hs) % 24}
@@ -73,7 +73,7 @@ export function DurationPicker({
)}
{minutes && (
<DurationColumn
- unit={i18n`minutes`}
+ unit={i18n.str`minutes`}
max={59}
min={1}
value={Math.floor(value / ms) % 60}
@@ -84,7 +84,7 @@ export function DurationPicker({
)}
{seconds && (
<DurationColumn
- unit={i18n`seconds`}
+ unit={i18n.str`seconds`}
max={59}
value={Math.floor(value / ss) % 60}
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
diff --git a/packages/anastasis-webui/src/context/translation.ts b/packages/anastasis-webui/src/context/translation.ts
index 87704a13f..44faaa456 100644
--- a/packages/anastasis-webui/src/context/translation.ts
+++ b/packages/anastasis-webui/src/context/translation.ts
@@ -19,23 +19,42 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { i18n, setupI18n } from "@gnu-taler/taler-util";
import { createContext, h, VNode } from "preact";
import { useContext, useEffect } from "preact/hooks";
-import { useLang } from "../hooks/index.js";
-import * as jedLib from "jed";
+import { useLang } from "../hooks/useLang.js";
import { strings } from "../i18n/strings.js";
interface Type {
lang: string;
- handler: any;
+ supportedLang: { [id in keyof typeof supportedLang]: string };
changeLanguage: (l: string) => void;
+ i18n: typeof i18n;
+ isSaved: boolean;
}
+
+const supportedLang = {
+ es: "Español [es]",
+ ja: "日本語 [ja]",
+ en: "English [en]",
+ fr: "Français [fr]",
+ de: "Deutsch [de]",
+ sv: "Svenska [sv]",
+ it: "Italiano [it]",
+ // ko: "한국어 [ko]",
+ // ru: "Ру́сский язы́к [ru]",
+ tr: "Türk [tr]",
+ navigator: "Defined by navigator",
+};
+
const initial = {
lang: "en",
- handler: null,
+ supportedLang,
changeLanguage: () => {
// do not change anything
},
+ i18n,
+ isSaved: false,
};
const Context = createContext<Type>(initial);
@@ -50,15 +69,23 @@ export const TranslationProvider = ({
children,
forceLang,
}: Props): VNode => {
- const [lang, changeLanguage] = useLang(initial);
+ const [lang, changeLanguage, isSaved] = useLang(initial);
useEffect(() => {
if (forceLang) {
changeLanguage(forceLang);
}
});
- const handler = new jedLib.Jed(strings[lang] || strings["en"]);
+ useEffect(() => {
+ setupI18n(lang, strings);
+ }, [lang]);
+ if (forceLang) {
+ setupI18n(forceLang, strings);
+ } else {
+ setupI18n(lang, strings);
+ }
+
return h(Context.Provider, {
- value: { lang, handler, changeLanguage },
+ value: { lang, changeLanguage, supportedLang, i18n, isSaved },
children,
});
};
diff --git a/packages/anastasis-webui/src/hooks/index.ts b/packages/anastasis-webui/src/hooks/index.ts
index c03e834d7..2dbf4fa5c 100644
--- a/packages/anastasis-webui/src/hooks/index.ts
+++ b/packages/anastasis-webui/src/hooks/index.ts
@@ -19,7 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { StateUpdater, useState } from "preact/hooks";
+import { StateUpdater } from "preact/hooks";
+import { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js";
+
export type ValueOrFunction<T> = T | ((p: T) => T);
const calculateRootPath = () => {
@@ -69,69 +71,4 @@ export function useBackendInstanceToken(
}
return [token, setToken];
-}
-
-export function useLang(initial?: string): [string, StateUpdater<string>] {
- const browserLang =
- typeof window !== "undefined"
- ? navigator.language || (navigator as any).userLanguage
- : undefined;
- const defaultLang = (browserLang || initial || "en").substring(0, 2);
- return useNotNullLocalStorage("lang-preference", defaultLang);
-}
-
-export function useLocalStorage(
- key: string,
- initialValue?: string,
-): [string | undefined, StateUpdater<string | undefined>] {
- const [storedValue, setStoredValue] = useState<string | undefined>(
- (): string | undefined => {
- return typeof window !== "undefined"
- ? window.localStorage.getItem(key) || initialValue
- : initialValue;
- },
- );
-
- const setValue = (
- value?: string | ((val?: string) => string | undefined),
- ) => {
- setStoredValue((p) => {
- const toStore = value instanceof Function ? value(p) : value;
- if (typeof window !== "undefined") {
- if (!toStore) {
- window.localStorage.removeItem(key);
- } else {
- window.localStorage.setItem(key, toStore);
- }
- }
- return toStore;
- });
- };
-
- return [storedValue, setValue];
-}
-
-export function useNotNullLocalStorage(
- key: string,
- initialValue: string,
-): [string, StateUpdater<string>] {
- const [storedValue, setStoredValue] = useState<string>((): string => {
- return typeof window !== "undefined"
- ? window.localStorage.getItem(key) || initialValue
- : initialValue;
- });
-
- const setValue = (value: string | ((val: string) => string)) => {
- const valueToStore = value instanceof Function ? value(storedValue) : value;
- setStoredValue(valueToStore);
- if (typeof window !== "undefined") {
- if (!valueToStore) {
- window.localStorage.removeItem(key);
- } else {
- window.localStorage.setItem(key, valueToStore);
- }
- }
- };
-
- return [storedValue, setValue];
-}
+} \ No newline at end of file
diff --git a/packages/anastasis-webui/src/main.ts b/packages/anastasis-webui/src/hooks/useLang.ts
index 72ab257eb..5b02c5255 100644
--- a/packages/anastasis-webui/src/main.ts
+++ b/packages/anastasis-webui/src/hooks/useLang.ts
@@ -13,29 +13,18 @@
You should have received a copy of the GNU Affero General Public License along with
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { setupI18n } from "@gnu-taler/taler-util";
-import { h, render } from "preact";
-import App from "./components/app.js";
-function main(): void {
- try {
- const container = document.getElementById("container");
- if (!container) {
- throw Error("container not found, can't mount page contents");
- }
- render(h(App, {}), container);
- } catch (e) {
- console.error("got error", e);
- if (e instanceof Error) {
- document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
- }
- }
-}
+import { useNotNullLocalStorage } from "./useLocalStorage.js";
-// setupI18n("en", strings);
+function getBrowserLang(): string | undefined {
+ if (window.navigator.languages) return window.navigator.languages[0];
+ if (window.navigator.language) return window.navigator.language;
+ return undefined;
+}
-if (document.readyState === "loading") {
- document.addEventListener("DOMContentLoaded", main);
-} else {
- main();
+export function useLang(
+ initial?: string,
+): [string, (s: string) => void, boolean] {
+ const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
+ return useNotNullLocalStorage("lang-preference", defaultLang);
}
diff --git a/packages/anastasis-webui/src/hooks/useLocalStorage.ts b/packages/anastasis-webui/src/hooks/useLocalStorage.ts
new file mode 100644
index 000000000..ed5b491f2
--- /dev/null
+++ b/packages/anastasis-webui/src/hooks/useLocalStorage.ts
@@ -0,0 +1,80 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { StateUpdater, useState } from "preact/hooks";
+
+export function useLocalStorage(
+ key: string,
+ initialValue?: string,
+): [string | undefined, StateUpdater<string | undefined>] {
+ const [storedValue, setStoredValue] = useState<string | undefined>(
+ (): string | undefined => {
+ return typeof window !== "undefined"
+ ? window.localStorage.getItem(key) || initialValue
+ : initialValue;
+ },
+ );
+
+ const setValue = (
+ value?: string | ((val?: string) => string | undefined),
+ ): void => {
+ setStoredValue((p) => {
+ const toStore = value instanceof Function ? value(p) : value;
+ if (typeof window !== "undefined") {
+ if (!toStore) {
+ window.localStorage.removeItem(key);
+ } else {
+ window.localStorage.setItem(key, toStore);
+ }
+ }
+ return toStore;
+ });
+ };
+
+ return [storedValue, setValue];
+}
+
+//TODO: merge with the above function
+export function useNotNullLocalStorage(
+ key: string,
+ initialValue: string,
+): [string, StateUpdater<string>, boolean] {
+ const [storedValue, setStoredValue] = useState<string>((): string => {
+ return typeof window !== "undefined"
+ ? window.localStorage.getItem(key) || initialValue
+ : initialValue;
+ });
+
+ const setValue = (value: string | ((val: string) => string)): void => {
+ const valueToStore = value instanceof Function ? value(storedValue) : value;
+ setStoredValue(valueToStore);
+ if (typeof window !== "undefined") {
+ if (!valueToStore) {
+ window.localStorage.removeItem(key);
+ } else {
+ window.localStorage.setItem(key, valueToStore);
+ }
+ }
+ };
+
+ const isSaved = window.localStorage.getItem(key) !== null;
+ return [storedValue, setValue, isSaved];
+}
diff --git a/packages/anastasis-webui/src/i18n/index.tsx b/packages/anastasis-webui/src/i18n/index.tsx
deleted file mode 100644
index 01e3cdd3a..000000000
--- a/packages/anastasis-webui/src/i18n/index.tsx
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- 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 <http://www.gnu.org/licenses/>
- */
-
-/**
- * Translation helpers for React components and template literals.
- */
-
-/**
- * Imports
- */
-import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
-
-import { useTranslationContext } from "../context/translation.js";
-
-export function useTranslator() {
- const ctx = useTranslationContext();
- const jed = ctx.handler;
- return function str(
- stringSeq: TemplateStringsArray,
- ...values: any[]
- ): string {
- const s = toI18nString(stringSeq);
- if (!s) return s;
- const tr = jed
- .translate(s)
- .ifPlural(1, s)
- .fetch(...values);
- return tr;
- };
-}
-
-/**
- * Convert template strings to a msgid
- */
-function toI18nString(stringSeq: ReadonlyArray<string>): string {
- let s = "";
- for (let i = 0; i < stringSeq.length; i++) {
- s += stringSeq[i];
- if (i < stringSeq.length - 1) {
- s += `%${i + 1}$s`;
- }
- }
- return s;
-}
-
-interface TranslateSwitchProps {
- target: number;
- children: ComponentChildren;
-}
-
-function stringifyChildren(children: ComponentChildren): string {
- let n = 1;
- const ss = (children instanceof Array ? children : [children]).map((c) => {
- if (typeof c === "string") {
- return c;
- }
- return `%${n++}$s`;
- });
- const s = ss.join("").replace(/ +/g, " ").trim();
- return s;
-}
-
-interface TranslateProps {
- children: ComponentChildren;
- /**
- * Component that the translated element should be wrapped in.
- * Defaults to "div".
- */
- wrap?: any;
-
- /**
- * Props to give to the wrapped component.
- */
- wrapProps?: any;
-}
-
-function getTranslatedChildren(
- translation: string,
- children: ComponentChildren,
-): ComponentChild[] {
- const tr = translation.split(/%(\d+)\$s/);
- const childArray = children instanceof Array ? children : [children];
- // Merge consecutive string children.
- const placeholderChildren = Array<ComponentChild>();
- for (let i = 0; i < childArray.length; i++) {
- const x = childArray[i];
- if (x === undefined) {
- continue;
- } else if (typeof x === "string") {
- continue;
- } else {
- placeholderChildren.push(x);
- }
- }
- const result = Array<ComponentChild>();
- for (let i = 0; i < tr.length; i++) {
- if (i % 2 == 0) {
- // Text
- result.push(tr[i]);
- } else {
- const childIdx = Number.parseInt(tr[i], 10) - 1;
- result.push(placeholderChildren[childIdx]);
- }
- }
- return result;
-}
-
-/**
- * Translate text node children of this component.
- * If a child component might produce a text node, it must be wrapped
- * in a another non-text element.
- *
- * Example:
- * ```
- * <Translate>
- * Hello. Your score is <span><PlayerScore player={player} /></span>
- * </Translate>
- * ```
- */
-export function Translate({ children }: TranslateProps): VNode {
- const s = stringifyChildren(children);
- const ctx = useTranslationContext();
- const translation: string = ctx.handler.ngettext(s, s, 1);
- const result = getTranslatedChildren(translation, children);
- return <Fragment>{result}</Fragment>;
-}
-
-/**
- * Switch translation based on singular or plural based on the target prop.
- * Should only contain TranslateSingular and TransplatePlural as children.
- *
- * Example:
- * ```
- * <TranslateSwitch target={n}>
- * <TranslateSingular>I have {n} apple.</TranslateSingular>
- * <TranslatePlural>I have {n} apples.</TranslatePlural>
- * </TranslateSwitch>
- * ```
- */
-export function TranslateSwitch({ children, target }: TranslateSwitchProps) {
- let singular: VNode<TranslationPluralProps> | undefined;
- let plural: VNode<TranslationPluralProps> | undefined;
- // const children = this.props.children;
- if (children) {
- (children instanceof Array ? children : [children]).forEach(
- (child: any) => {
- if (child.type === TranslatePlural) {
- plural = child;
- }
- if (child.type === TranslateSingular) {
- singular = child;
- }
- },
- );
- }
- if (!singular || !plural) {
- console.error("translation not found");
- return h("span", {}, ["translation not found"]);
- }
- singular.props.target = target;
- plural.props.target = target;
- // We're looking up the translation based on the
- // singular, even if we must use the plural form.
- return singular;
-}
-
-interface TranslationPluralProps {
- children: ComponentChildren;
- target: number;
-}
-
-/**
- * See [[TranslateSwitch]].
- */
-export function TranslatePlural({
- children,
- target,
-}: TranslationPluralProps): VNode {
- const s = stringifyChildren(children);
- const ctx = useTranslationContext();
- const translation = ctx.handler.ngettext(s, s, 1);
- const result = getTranslatedChildren(translation, children);
- return <Fragment>{result}</Fragment>;
-}
-
-/**
- * See [[TranslateSwitch]].
- */
-export function TranslateSingular({
- children,
- target,
-}: TranslationPluralProps): VNode {
- const s = stringifyChildren(children);
- const ctx = useTranslationContext();
- const translation = ctx.handler.ngettext(s, s, target);
- const result = getTranslatedChildren(translation, children);
- return <Fragment>{result}</Fragment>;
-}
diff --git a/packages/anastasis-webui/src/index.html b/packages/anastasis-webui/src/index.html
new file mode 100644
index 000000000..90a795ae3
--- /dev/null
+++ b/packages/anastasis-webui/src/index.html
@@ -0,0 +1,42 @@
+<!--
+ This file is part of GNU Taler
+ (C) 2021--2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Sebastian Javier Marchano
+-->
+<!DOCTYPE html>
+<html
+ lang="en"
+ class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
+>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <meta charset="utf-8" />
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <meta name="mobile-web-app-capable" content="yes" />
+ <meta name="apple-mobile-web-app-capable" content="yes" />
+ <link
+ rel="icon"
+ href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/Qn3//3ryn/795Tf/WrpP/2LCW/8B6T//w4Nb///////Pn4P+/d0v/9u3n/+7d0v/EhV7//v///+HDr//fxLD/zph2/+TJt//8/Pv/woBX//Lm3f/y5dz/v3hN//bu6f/JjGn/4sW0///////Df1j/8OLZ//v6+P+/elH/+vj1//jy7f+/elL//////+zYzP/Eg13//////967p//MlHT/wn5X///////v4Nb/yY1s///////jw7H/06KG////////////z5t9/+fNvf//////x4pn//Pp4v/8+vn/w39X/8WEX///////5s/A/9CbfP//////27Oc/9y2n////////////9itlf/gu6f//////86Vdf/r2Mz//////8SCXP/Df1j//////+7d0v/KkG7//////+HBrf/VpYr////////////RnoH/5sq6///////Ii2n/8ubf//39/P/Cf1j/xohk/+bNvv//////wn5W//Tq4//58/D/wHxV//7+/f/59fH/v3xU//39/P/w4Nf/xIFb///////hw7H/yo9t/+/f1f/AeU3/+/n2/+nSxP/FhmD//////9qzm//Upon/4MSx/96+qf//////xINc/+3bz//48e3/v3hN//Pn3///////6M+//752S//gw6//06aK/8J+VP/kzLr/zZd1/8OCWv/q18r/17KZ/9Ooi//fv6r/v3dK/+vWyP///////v39///////27un/1aeK/9Opjv/m1cf/1KCC/9a0nP/n08T/0Jx8/82YdP/QnHz/16yR//jx7P///////v39///////+/f3///7+///////+//7//v7+///////+/v7//v/+/////////////////////////v7//v79///////////////////+/v/+/Pv//v39///+/v/+/Pv///7+//7+/f/+/Pv//v39//79/P/+/Pv///7+////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="
+ />
+ <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
+ <title>Anastasis</title>
+ <!-- Entry point for the demobank SPA. -->
+ <script type="module" src="index.js"></script>
+ <link rel="stylesheet" href="index.css" />
+ </head>
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/packages/anastasis-webui/src/main.test.ts b/packages/anastasis-webui/src/index.test.ts
index 1a87e3857..1a87e3857 100644
--- a/packages/anastasis-webui/src/main.test.ts
+++ b/packages/anastasis-webui/src/index.test.ts
diff --git a/packages/anastasis-webui/src/index.ts b/packages/anastasis-webui/src/index.ts
index e04c44a31..d7b2164ab 100644
--- a/packages/anastasis-webui/src/index.ts
+++ b/packages/anastasis-webui/src/index.ts
@@ -13,7 +13,30 @@
You should have received a copy of the GNU Affero General Public License along with
GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { setupI18n } from "@gnu-taler/taler-util";
+import { h, render } from "preact";
import App from "./components/app.js";
import "./scss/main.scss";
-export default App;
+function main(): void {
+ try {
+ const container = document.getElementById("container");
+ if (!container) {
+ throw Error("container not found, can't mount page contents");
+ }
+ render(h(App, {}), container);
+ } catch (e) {
+ console.error("got error", e);
+ if (e instanceof Error) {
+ document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
+ }
+ }
+}
+
+// setupI18n("en", strings);
+
+if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", main);
+} else {
+ main();
+}
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
index dc41d9c1a..268189ed8 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/stories.tsx
@@ -24,6 +24,7 @@ import { createExampleWithoutAnastasis } from "../../../utils/index.jsx";
import { WithoutProviderType, WithProviderType } from "./views.jsx";
export default {
+ title: "Adding Provider Screen",
args: {
order: 1,
},
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx
index e397e0b65..19557a12f 100644
--- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx
+++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen/views.tsx
@@ -23,8 +23,10 @@ import { TextInput } from "../../../components/fields/TextInput.js";
import { Notifications } from "../../../components/Notifications.js";
import { AnastasisClientFrame } from "../index.js";
import { testProvider, WithoutType, WithType } from "./index.js";
+import { useTranslationContext } from "../../../context/translation.js";
export function WithProviderType(props: WithType): VNode {
+ const { i18n } = useTranslationContext();
return (
<AnastasisClientFrame
hideNav
@@ -33,7 +35,7 @@ export function WithProviderType(props: WithType): VNode {
>
<div>
<Notifications notifications={props.notifications} />
- <p>Add a provider url for a {props.providerLabel} service</p>
+ <p>{i18n.str`Add a provider url for a ${props.providerLabel} service`}</p>
<div class="container">
<TextInput
label="Provider URL"
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
index b1569f184..38fc1b56b 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen.js";
export default {
+ title: "Attribute Entry Screen",
component: TestedComponent,
args: {
order: 3,
diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
index c4901085d..ba48e2d5c 100644
--- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen.js";
export default {
+ title: "Authentication Editor Screen",
component: TestedComponent,
args: {
order: 4,
diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
index f50a72f8a..8aeaec25c 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen.js";
export default {
+ title: "Backup finish",
component: TestedComponent,
args: {
order: 8,
diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
index 552cb069f..d2471755a 100644
--- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx
@@ -28,6 +28,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen.js";
export default {
+ title: "Challenge overview",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
index 0d4895a0b..cd41fe03a 100644
--- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen.js";
export default {
+ title: "Challenge paying",
component: TestedComponent,
args: {
order: 10,
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
index 3994b7377..12a79c56c 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen.js";
export default {
+ title: "Continent selection",
component: TestedComponent,
args: {
order: 2,
diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
index 75619ba05..1e3650300 100644
--- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen.js";
export default {
+ title: "Edit policies",
args: {
order: 6,
},
diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
index 54833234d..56c224d34 100644
--- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen.js";
export default {
+ title: "Policies paying",
component: TestedComponent,
args: {
order: 9,
diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
index eda8968b2..1eb2ae50c 100644
--- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx
@@ -25,6 +25,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen.js";
export default {
+ title: "Recovery Finished",
args: {
order: 7,
},
diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
index 036455bce..c5003d6a0 100644
--- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen.js";
export default {
+ title: "Reviewing Policies",
args: {
order: 6,
},
diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
index 7a03116e7..dbf8bf128 100644
--- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen.js";
export default {
+ title: "Secret editor",
component: TestedComponent,
args: {
order: 7,
diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
index b457937f8..7669668ee 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx
@@ -27,6 +27,7 @@ import {
} from "./SecretSelectionScreen.js";
export default {
+ title: "Secret selection",
component: SecretSelectionScreen,
args: {
order: 4,
diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
index 5b3a70dd0..1058ae126 100644
--- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { SolveScreen as TestedComponent } from "./SolveScreen.js";
export default {
+ title: "Solve Screen",
component: TestedComponent,
args: {
order: 6,
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
index 3d54a9fd6..960426098 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { StartScreen as TestedComponent } from "./StartScreen.js";
export default {
+ title: "Start screen",
component: TestedComponent,
args: {
order: 1,
diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
index 81bab4868..40ed5117c 100644
--- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen.js";
export default {
+ title: "Truths Paying",
component: TestedComponent,
args: {
order: 10,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
index 38391d10d..4a2d76ca3 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Email setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
index db9abc86c..cc378d8f6 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx
@@ -27,6 +27,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Email solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx
index d4e034a37..6a9595a83 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx
@@ -23,7 +23,7 @@ import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/AsyncButton.js";
import { TextInput } from "../../../components/fields/TextInput.js";
import { useAnastasisContext } from "../../../context/anastasis.js";
-import { useTranslator } from "../../../i18n/index.js";
+import { useTranslationContext } from "../../../context/translation.js";
import { AnastasisClientFrame } from "../index.js";
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
import { shouldHideConfirm } from "./helpers.js";
@@ -53,7 +53,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
_setAnswer(result);
}
const [expanded, setExpanded] = useState(false);
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const reducer = useAnastasisContext();
if (!reducer) {
@@ -124,7 +124,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
const error =
answer.length > 21
- ? i18n`The answer should not be greater than 21 characters.`
+ ? i18n.str`The answer should not be greater than 21 characters.`
: undefined;
return (
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
index 5f3de47ff..dfe3850f1 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: IBAN setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
index c06611127..8a9a3f7a0 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: IBAN Solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
index 892de6023..8a32c45c1 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Post setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
index 8f7dc5ff9..702ba2810 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Post solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx
index 725382c58..8204ab1cf 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx
@@ -19,7 +19,7 @@ import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/AsyncButton.js";
import { TextInput } from "../../../components/fields/TextInput.js";
import { useAnastasisContext } from "../../../context/anastasis.js";
-import { useTranslator } from "../../../i18n/index.js";
+import { useTranslationContext } from "../../../context/translation.js";
import { AnastasisClientFrame } from "../index.js";
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
import { shouldHideConfirm } from "./helpers.js";
@@ -48,7 +48,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
_setAnswer(result);
}
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const reducer = useAnastasisContext();
if (!reducer) {
@@ -119,7 +119,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
const error =
answer.length > 21
- ? i18n`The answer should not be greater than 21 characters.`
+ ? i18n.str`The answer should not be greater than 21 characters.`
: undefined;
return (
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
index 736e7bfa8..2e108b4e6 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Question setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
index 182538775..f7116bf6f 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx
@@ -27,6 +27,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Question solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
index 0d58dbdcf..b2c6cb61d 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: SMS setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
index f1717eff0..2064f12ff 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: SMS solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx
index 965efbe60..58bb53c4f 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx
@@ -19,7 +19,7 @@ import { useState } from "preact/hooks";
import { AsyncButton } from "../../../components/AsyncButton.js";
import { TextInput } from "../../../components/fields/TextInput.js";
import { useAnastasisContext } from "../../../context/anastasis.js";
-import { useTranslator } from "../../../i18n/index.js";
+import { useTranslationContext } from "../../../context/translation.js";
import { AnastasisClientFrame } from "../index.js";
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
import { shouldHideConfirm } from "./helpers.js";
@@ -48,7 +48,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
_setAnswer(result);
}
- const i18n = useTranslator();
+ const { i18n } = useTranslationContext();
const [expanded, setExpanded] = useState(false);
const reducer = useAnastasisContext();
@@ -120,7 +120,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
const error =
answer.length > 21
- ? i18n`The answer should not be greater than 21 characters.`
+ ? i18n.str`The answer should not be greater than 21 characters.`
: undefined;
return (
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
index e22053b96..5582590f7 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx
@@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Totp setup",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
index 354516d80..20cd7e3c9 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx
@@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
export default {
+ title: "Auth method: Totp solve",
component: TestedComponent,
args: {
order: 5,
diff --git a/packages/anastasis-webui/src/scss/_mixins.scss b/packages/anastasis-webui/src/scss/_mixins.scss
index 64315785b..a0fe6e93e 100644
--- a/packages/anastasis-webui/src/scss/_mixins.scss
+++ b/packages/anastasis-webui/src/scss/_mixins.scss
@@ -28,7 +28,7 @@
width: $icon-base-width;
&.has-update-mark:after {
- right: ($icon-base-width / 2) - 0.85;
+ right: calc($icon-base-width / 2) - 0.85;
}
}
}
diff --git a/packages/anastasis-webui/src/stories.tsx b/packages/anastasis-webui/src/stories.tsx
index 7d22deece..f345f082d 100644
--- a/packages/anastasis-webui/src/stories.tsx
+++ b/packages/anastasis-webui/src/stories.tsx
@@ -18,302 +18,24 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
-import { setupI18n } from "@gnu-taler/taler-util";
-import { ComponentChild, Fragment, h, render, VNode } from "preact";
-import { useEffect, useErrorBoundary, useState } from "preact/hooks";
import { strings } from "./i18n/strings.js";
-import * as pages from "./pages/home/index.storiesNo.js";
-const url = new URL(window.location.href);
-const lang = url.searchParams.get("lang") || "en";
+import * as pages from "./pages/home/index.storiesNo.js";
-setupI18n(lang, strings);
+import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
-const Page = ({ children }: any) => <div class="page">{children}</div>;
-const SideBar = ({ children }: any) => <div class="sidebar">{children}</div>;
-const Content = ({ children }: any) => <div class="content">{children}</div>;
-
-function parseExampleImport(
- group: string,
- im: any,
- name?: string,
-): ComponentItem {
- const component = name || im.default.title;
- const order: number = im.default.args?.order || 0;
- return {
- name: component,
- order,
- examples: Object.entries(im)
- .filter(([k]) => k !== "default")
- .map(
- ([name, render]) =>
- ({
- group,
- component,
- name,
- render,
- } as ExampleItem),
- ),
- };
-}
+import "./scss/main.scss";
function SortStories(a: any, b: any): number {
return (a?.order ?? 0) - (b?.order ?? 0);
}
-const allExamples = Object.entries({ pages }).map(([title, value]) => {
- return {
- title,
- list: Object.entries(value)
- .filter(([name]) => name != "default")
- .map(([name, value]) => parseExampleImport(title, value, name))
- .sort(SortStories),
- };
-});
-
-interface ComponentItem {
- name: string;
- order: number;
- examples: ExampleItem[];
-}
-
-interface ExampleItem {
- group: string;
- component: string;
- name: string;
- render: {
- (args: any): VNode;
- args: any;
- };
-}
-
-function findByGroupComponentName(
- group: string,
- component: string,
- name: string,
-): ExampleItem | undefined {
- const gl = allExamples.filter((e) => e.title === group);
- if (gl.length === 0) {
- return undefined;
- }
- const cl = gl[0].list.filter((l) => l.name === component);
- if (cl.length === 0) {
- return undefined;
- }
- const el = cl[0].examples.filter((c) => c.name === name);
- if (el.length === 0) {
- return undefined;
- }
- return el[0];
-}
-
-function getContentForExample(item: ExampleItem | undefined): () => VNode {
- if (!item)
- return function SelectExampleMessage() {
- return <div>select example from the list on the left</div>;
- };
- const example = findByGroupComponentName(
- item.group,
- item.component,
- item.name,
- );
- if (!example)
- return function ExampleNotFoundMessage() {
- return <div>example not found</div>;
- };
- return () => example.render(example.render.args);
-}
-
-function ExampleList({
- name,
- list,
- selected,
- onSelectStory,
-}: {
- name: string;
- list: {
- name: string;
- examples: ExampleItem[];
- }[];
- selected: ExampleItem | undefined;
- onSelectStory: (i: ExampleItem, id: string) => void;
-}): VNode {
- const [isOpen, setOpen] = useState(selected && selected.group === name);
- return (
- <ol>
- <div onClick={() => setOpen(!isOpen)}>{name}</div>
- <div data-hide={!isOpen}>
- {list.map((k) => (
- <li key={k.name}>
- <dl>
- <dt>{k.name}</dt>
- {k.examples.map((r) => {
- const e = encodeURIComponent;
- const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`;
- function doSelection(e: any): void {
- e.preventDefault();
- location.hash = `#${eId}`;
- onSelectStory(r, eId);
- }
- const isSelected =
- selected &&
- selected.component === r.component &&
- selected.group === r.group &&
- selected.name === r.name;
- return (
- <dd
- id={eId}
- key={r.name}
- data-selected={isSelected}
- onClick={doSelection}
- >
- <a href={`#${eId}`} onClick={doSelection}>
- {r.name}
- </a>
- </dd>
- );
- })}
- </dl>
- </li>
- ))}
- </div>
- </ol>
- );
-}
-
-// function getWrapperForGroup(group: string): FunctionComponent {
-// switch (group) {
-// case "popup":
-// return function PopupWrapper({ children }: any) {
-// return (
-// <Fragment>
-// <PopupNavBar />
-// <PopupBox>{children}</PopupBox>
-// </Fragment>
-// );
-// };
-// case "wallet":
-// return function WalletWrapper({ children }: any) {
-// return (
-// <Fragment>
-// <LogoHeader />
-// <WalletNavBar />
-// <WalletBox>{children}</WalletBox>
-// </Fragment>
-// );
-// };
-// case "cta":
-// return function WalletWrapper({ children }: any) {
-// return (
-// <Fragment>
-// <WalletBox>{children}</WalletBox>
-// </Fragment>
-// );
-// };
-// default:
-// return Fragment;
-// }
-// }
-
-function ErrorReport({
- children,
- selected,
-}: {
- children: ComponentChild;
- selected: ExampleItem | undefined;
-}): VNode {
- const [error] = useErrorBoundary();
- if (error) {
- return (
- <div class="error_report">
- <p>Error was thrown trying to render</p>
- {selected && (
- <ul>
- <li>
- <b>group</b>: {selected.group}
- </li>
- <li>
- <b>component</b>: {selected.component}
- </li>
- <li>
- <b>example</b>: {selected.name}
- </li>
- <li>
- <b>args</b>:{" "}
- <pre>{JSON.stringify(selected.render.args, undefined, 2)}</pre>
- </li>
- </ul>
- )}
- <p>{error.message}</p>
- <pre>{error.stack}</pre>
- </div>
- );
- }
- return <Fragment>{children}</Fragment>;
-}
-
-function getSelectionFromLocationHash(hash: string): ExampleItem | undefined {
- if (!hash) return undefined;
- const parts = hash.substring(1).split("-");
- if (parts.length < 3) return undefined;
- return findByGroupComponentName(
- decodeURIComponent(parts[0]),
- decodeURIComponent(parts[1]),
- decodeURIComponent(parts[2]),
- );
-}
-
-function Application(): VNode {
- const initialSelection = getSelectionFromLocationHash(location.hash);
- const [selected, updateSelected] = useState<ExampleItem | undefined>(
- initialSelection,
- );
- useEffect(() => {
- if (location.hash) {
- const hash = location.hash.substring(1);
- const found = document.getElementById(hash);
- if (found) {
- setTimeout(() => {
- found.scrollIntoView({
- block: "center",
- });
- }, 10);
- }
- }
- }, []);
-
- const ExampleContent = getContentForExample(selected);
-
- // const GroupWrapper = getWrapperForGroup(selected?.group || "default");
-
- return (
- <Page>
- <LiveReload />
- <SideBar>
- {allExamples.map((e) => (
- <ExampleList
- key={e.title}
- name={e.title}
- list={e.list}
- selected={selected}
- onSelectStory={(item, htmlId) => {
- document.getElementById(htmlId)?.scrollIntoView({
- block: "center",
- });
- updateSelected(item);
- }}
- />
- ))}
- <hr />
- </SideBar>
- <Content>
- <ErrorReport selected={selected}>
- {/* <GroupWrapper> */}
- <ExampleContent />
- {/* </GroupWrapper> */}
- </ErrorReport>
- </Content>
- </Page>
+function main(): void {
+ renderStories(
+ { pages },
+ {
+ strings,
+ },
);
}
@@ -322,72 +44,3 @@ if (document.readyState === "loading") {
} else {
main();
}
-function main(): void {
- try {
- const container = document.getElementById("container");
- if (!container) {
- throw Error("container not found, can't mount page contents");
- }
- render(<Application />, container);
- } catch (e) {
- console.error("got error", e);
- if (e instanceof Error) {
- document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
- }
- }
-}
-
-let liveReloadMounted = false;
-function LiveReload({ port = 8002 }: { port?: number }): VNode {
- const [isReloading, setIsReloading] = useState(false);
- useEffect(() => {
- if (!liveReloadMounted) {
- setupLiveReload(port, () => {
- setIsReloading(true);
- window.location.reload();
- });
- liveReloadMounted = true;
- }
- });
-
- if (isReloading) {
- return (
- <div
- style={{
- position: "absolute",
- width: "100%",
- height: "100%",
- backgroundColor: "rgba(0,0,0,0.5)",
- color: "white",
- display: "flex",
- justifyContent: "center",
- }}
- >
- <h1 style={{ margin: "auto" }}>reloading...</h1>
- </div>
- );
- }
- return <Fragment />;
-}
-
-function setupLiveReload(port: number, onReload: () => void): void {
- const socketPath = `ws://localhost:8003/socket`;
- // const socketPath = `${protocol}//${host}:${port}/socket`;
-
- const ws = new WebSocket(socketPath);
- ws.onmessage = (message) => {
- const event = JSON.parse(message.data);
- if (event.type === "LOG") {
- console.log(event.message);
- }
- if (event.type === "RELOAD") {
- onReload();
- }
- };
- ws.onerror = (error) => {
- console.error(error);
- };
- ws.onclose = (e) => {
- console.log("disconnected", e);
- };
-}
diff --git a/packages/anastasis-webui/src/test-utils.ts b/packages/anastasis-webui/src/test-utils.ts
index 1fcc753ee..f220540f1 100644
--- a/packages/anastasis-webui/src/test-utils.ts
+++ b/packages/anastasis-webui/src/test-utils.ts
@@ -41,8 +41,10 @@ export function createExample<Props>(
// check how we can build evaluatedProps in render time
const evaluatedProps = typeof props === "function" ? props() : props;
const Render = (args: any): VNode => create(Component, args);
- Render.args = evaluatedProps;
- return Render;
+ return {
+ component: Render,
+ props: evaluatedProps
+ };
}
export function createExampleWithCustomContext<Props, ContextProps>(
@@ -58,8 +60,10 @@ export function createExampleWithCustomContext<Props, ContextProps>(
...contextProps,
children: [Render(args)],
} as any);
- WithContext.args = evaluatedProps;
- return WithContext;
+ return {
+ component: WithContext,
+ props: evaluatedProps
+ };
}
export function NullLink({
diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx
index 78973e38f..4cf839473 100644
--- a/packages/anastasis-webui/src/utils/index.tsx
+++ b/packages/anastasis-webui/src/utils/index.tsx
@@ -37,16 +37,18 @@ export function createExampleWithoutAnastasis<Props>(
// check how we can build evaluatedProps in render time
const evaluatedProps = typeof props === "function" ? props() : props;
const Render = (args: any): VNode => h(Component, args);
- Render.args = evaluatedProps;
- return Render;
+ return {
+ component: Render,
+ props: evaluatedProps,
+ };
}
export function createExample<Props>(
Component: FunctionalComponent<Props>,
currentReducerState?: ReducerState,
props?: Partial<Props>,
-): { (args: Props): VNode } {
- const r = (args: Props): VNode => {
+): ComponentChildren {
+ const Render = (args: Props): VNode => {
return (
<AnastasisProvider
value={{
@@ -74,8 +76,10 @@ export function createExample<Props>(
</AnastasisProvider>
);
};
- r.args = props;
- return r;
+ return {
+ component: Render,
+ props: props,
+ };
}
const base = {