diff options
Diffstat (limited to 'packages/anastasis-webui/src')
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 = { |