diff options
58 files changed, 430 insertions, 1074 deletions
diff --git a/packages/anastasis-webui/build.mjs b/packages/anastasis-webui/build.mjs new file mode 100755 index 000000000..ebe914541 --- /dev/null +++ b/packages/anastasis-webui/build.mjs @@ -0,0 +1,147 @@ +#!/usr/bin/env node +/* + 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/> + */ +/* eslint-disable no-undef */ +import esbuild from 'esbuild' +import fs from 'fs'; +import path from "path" +import sass from "sass"; + +// eslint-disable-next-line no-undef +const BASE = process.cwd(); + +const preact = path.join( + BASE, + "node_modules", + "preact", + "compat", + "dist", + "compat.module.js", +); + +const preactCompatPlugin = { + name: "preact-compat", + setup(build) { + build.onResolve({ filter: /^(react-dom|react)$/ }, (args) => { + return { + path: preact, + }; + }); + }, +}; + + +let GIT_ROOT = BASE +while (!fs.existsSync(path.join(GIT_ROOT, '.git')) && GIT_ROOT !== '/') { + GIT_ROOT = path.join(GIT_ROOT, '../') +} +if (GIT_ROOT === '/') { + // eslint-disable-next-line no-undef + console.log("not found") + // eslint-disable-next-line no-undef + process.exit(1); +} +const GIT_HASH = GIT_ROOT === '/' ? undefined : git_hash() + + +let _package = JSON.parse(fs.readFileSync(path.join(BASE, 'package.json'))); + +function git_hash() { + const rev = fs.readFileSync(path.join(GIT_ROOT, '.git', 'HEAD')).toString().trim().split(/.*[: ]/).slice(-1)[0]; + if (rev.indexOf('/') === -1) { + return rev; + } else { + return fs.readFileSync(path.join(GIT_ROOT, '.git', rev)).toString().trim(); + } +} + +const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/ + +const buildSassPlugin = { + name: "custom-build-sass", + setup(build) { + + build.onLoad({ filter: DEFAULT_SASS_FILTER }, ({ path: file }) => { + const resolveDir = path.dirname(file) + const { css: contents } = sass.compile(file, { loadPaths: ["./"] }) + + return { + resolveDir, + loader: 'css', + contents + } + }); + + }, +}; + +function copyFilesPlugin(options) { + if (!options.basedir) { + options.basedir = process.cwd() + } + return { + name: "copy-files", + setup(build) { + build.onEnd(() => { + for (const fop of options) { + fs.copyFileSync(path.join(options.basedir, fop.src), path.join(options.basedir, fop.dest)); + } + }); + }, + }; +} + +export const buildConfig = { + entryPoints: ['src/index.ts', 'src/stories.tsx'], + bundle: true, + outdir: 'dist', + minify: false, + loader: { + '.svg': 'dataurl', + '.ttf': 'file', + '.woff': 'file', + '.woff2': 'file', + '.eot': 'file', + }, + target: [ + 'es6' + ], + format: 'esm', + platform: 'browser', + sourcemap: true, + jsxFactory: 'h', + jsxFragment: 'Fragment', + define: { + '__VERSION__': `"${_package.version}"`, + '__GIT_HASH__': `"${GIT_HASH}"`, + }, + plugins: [ + preactCompatPlugin, + copyFilesPlugin([ + { + src: "./src/index.html", + dest: "./dist/index.html", + }, + ]), + buildSassPlugin + ], +} + +await esbuild.build(buildConfig) + + + + diff --git a/packages/anastasis-webui/clean_and_build.sh b/packages/anastasis-webui/clean_and_build.sh deleted file mode 100755 index 9486848fe..000000000 --- a/packages/anastasis-webui/clean_and_build.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -echo clean -rm -rf dist -mkdir -p dist/fonts -cp \ - src/scss/fonts/XRXV3I6Li01BKofINeaE.ttf \ - src/scss/fonts/materialdesignicons-webfont-4.9.95.ttf \ - src/scss/fonts/materialdesignicons-webfont-4.9.95.woff \ - src/scss/fonts/materialdesignicons-webfont-4.9.95.woff2 \ - dist/fonts - -VERSION=$(jq -r .version package.json) -GIT_HASH=$(git rev-parse --short HEAD) - -function build_css() { - pnpm exec sass -I . ./src/scss/main.scss dist/main.css -} -function build_js() { - pnpm exec esbuild --log-level=error --define:process.env.__VERSION__=\"${VERSION}\" --define:process.env.__GIT_HASH__=\"${GIT_HASH}\" --bundle $1 --outdir=dist --target=es6 --loader:.svg=dataurl --format=iife --sourcemap --jsx-factory=h --jsx-fragment=Fragment --platform=browser --minify -} - -function build_html() { - cat html/$1.html \ - | sed -e '/ANASTASIS_SCRIPT_CONTENT/ {' -e 'r dist/main.js' -e 'd' -e '}' \ - | sed -e '/ANASTASIS_STYLE_CONTENT/ {' -e 'r dist/main.css' -e 'd' -e '}' \ - >dist/$1.html -} - -function cleanup { - trap - SIGHUP SIGINT SIGTERM SIGQUIT - echo -n "Cleaning up... " - wait - kill -- -$$ - exit 1 -} -trap cleanup SIGHUP SIGINT SIGTERM SIGQUIT - -set -e -echo compile -build_css & -build_js src/main.ts & -build_js src/stories.tsx & -build_js src/main.test.ts & -for file in $(find src/ -name test.ts); do build_js $file; done & -wait -n -wait -n -wait -n -wait -n -wait -n -pnpm run --silent test -- -R dot - -echo html -build_html ui -build_html ui-dev -build_html stories - -if [ "WATCH" == "$1" ]; then - - echo watch mode - echo Writing any file in the src directory will trigger a browser reload. - echo Be sure that the watcher server is running. - echo ./watch/serve.sh - inotifywait -e close_write -r src -q -m | while read line; do - echo $(date) $line - build_js src/main.ts - build_html ui-dev - build_js src/stories.tsx - build_html stories - ./watch/send.sh '{"type":"RELOAD"}' - done; -fi diff --git a/packages/anastasis-webui/dev.mjs b/packages/anastasis-webui/dev.mjs index 3f4915ffc..0446603dc 100755 --- a/packages/anastasis-webui/dev.mjs +++ b/packages/anastasis-webui/dev.mjs @@ -14,87 +14,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/> */ -/* eslint-disable no-undef */ -import esbuild from 'esbuild' -import fs from 'fs'; -import WebSocket from "ws"; -import chokidar from "chokidar"; -const devServerBroadcastDelay = 500 -const devServerPort = 8002 -const wss = new WebSocket.Server({ port: devServerPort }); -const toWatch = ["./src"] - -function broadcast(file, event) { - setTimeout(() => { - wss.clients.forEach((client) => { - if (client.readyState === WebSocket.OPEN) { - console.log(new Date(), file) - client.send(JSON.stringify(event)); - } - }); - }, devServerBroadcastDelay); -} - -const watcher = chokidar - .watch(toWatch, { - persistent: true, - ignoreInitial: true, - awaitWriteFinish: { - stabilityThreshold: 100, - pollInterval: 100, - }, - }) - .on("error", (error) => console.error(error)) - .on("change", async (file) => { - broadcast(file, { type: "RELOAD" }); - }) - .on("add", async (file) => { - broadcast(file, { type: "RELOAD" }); - }) - .on("unlink", async (file) => { - broadcast(file, { type: "RELOAD" }); - }); - -/** - * Just bundling UI Stories. - * FIXME: add linaria CSS after implementing Material so CSS will be bundled - */ -fs.writeFileSync("dist/index.html", fs.readFileSync("html/stories.html")) -fs.writeFileSync("dist/mocha.css", fs.readFileSync("node_modules/mocha/mocha.css")) -fs.writeFileSync("dist/mocha.js", fs.readFileSync("node_modules/mocha/mocha.js")) -fs.writeFileSync("dist/mocha.js.map", fs.readFileSync("node_modules/mocha/mocha.js.map")) - -export const buildConfig = { - entryPoints: ['src/main.ts', 'src/stories.tsx'], - bundle: true, - outdir: 'dist', - minify: false, - loader: { - '.svg': 'dataurl', - }, - target: [ - 'es6' - ], - format: 'iife', - platform: 'browser', - sourcemap: true, - jsxFactory: 'h', - jsxFragment: 'Fragment', -} - -const server = await esbuild - .serve({ servedir: 'dist' }, { - ...buildConfig, outdir: 'dist' - }) - .catch((e) => { - console.log(e) - process.exit(1) - }); - -console.log(`Dev server is ready at http://localhost:${server.port}/. -The server is running a using websocket at ${devServerPort} to notify code change and live reload. -`); +import { serve } from "@gnu-taler/web-util/lib/index.node"; +import esbuild from 'esbuild'; +import { buildConfig } from "./build.mjs"; +buildConfig.inject = ['./node_modules/@gnu-taler/web-util/lib/live-reload.mjs'] +serve({ + folder: './dist', + port: 8080, + source: './src', + development: true, + onUpdate: async () => esbuild.build(buildConfig) +}) diff --git a/packages/anastasis-webui/html/stories.html b/packages/anastasis-webui/html/stories.html deleted file mode 100644 index 9f41fdeaf..000000000 --- a/packages/anastasis-webui/html/stories.html +++ /dev/null @@ -1,72 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Stories</title> - <style> - /* page css */ - div.page { - margin: 0px; - padding: 0px; - font-size: 100%; - font-family: Arial, Helvetica, sans-serif; - } - div.page p:not([class]) { - margin-bottom: 1em; - margin-top: 1em; - } - div.page { - width: 100%; - display: flex; - flex-direction: row; - } - /* sidebar css */ - div.sidebar { - min-width: 200px; - height: calc(100vh - 20px); - overflow-y: visible; - overflow-x: hidden; - scroll-behavior: smooth; - } - div.sidebar > ol { - padding: 4px; - } - div.sidebar div:first-child { - background-color: lightcoral; - cursor: pointer; - } - div.sidebar div[data-hide="true"] { - display: none; - } - div.sidebar dd { - margin-left: 1em; - padding: 4px; - cursor: pointer; - border-radius: 4px; - margin-bottom: 4px; - } - div.sidebar dd:nth-child(even) { - background-color: lightgray; - } - div.sidebar dd:nth-child(odd) { - background-color: lightblue; - } - div.sidebar a { - color: black; - } - div.sidebar dd[data-selected] { - background-color: green; - } - - /* content css */ - div.content { - width: 100%; - padding: 20px; - } - </style> - <script src="./stories.js"></script> - <link rel="stylesheet" href="./main.css" /> - </head> - <body> - <taler-stories id="container"></taler-stories> - </body> -</html> diff --git a/packages/anastasis-webui/html/ui-dev.html b/packages/anastasis-webui/html/ui-dev.html deleted file mode 100644 index 2790d5678..000000000 --- a/packages/anastasis-webui/html/ui-dev.html +++ /dev/null @@ -1,65 +0,0 @@ -<!DOCTYPE html> -<html - lang="en" - class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded" -> - <head> - <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" /> - <style id="style-id" type="text/css"> - /* <![CDATA[ */ - ANASTASIS_STYLE_CONTENT - /* <![CDATA[ */ - </style> - </head> - - <body> - <div id="container" class="anastasis-container"></div> - <script id="code" type="application/javascript"> - ANASTASIS_SCRIPT_CONTENT; - </script> - <script type="application/javascript"> - function setupLiveReload() { - const socketPath = `ws://localhost:8003/socket`; - console.log("connecting to ", socketPath); - 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") { - window.location.reload(); - } - if (event.type === "UPDATE") { - document.body.removeChild(document.getElementById("container")); - const d = document.createElement("div"); - d.setAttribute("id", "container"); - d.setAttribute("class", "anastasis-container"); - document.body.appendChild(d); - const s = document.createElement("script"); - s.setAttribute("id", "code"); - s.setAttribute("type", "application/javascript"); - s.textContent = atob(event.content); - document.body.appendChild(s); - } - }; - ws.onerror = (error) => { - console.error(error); - }; - ws.onclose = (e) => { - setTimeout(setupLiveReload, 500); - }; - } - setupLiveReload(); - </script> - </body> -</html> diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json index de70d05fc..c01856243 100644 --- a/packages/anastasis-webui/package.json +++ b/packages/anastasis-webui/package.json @@ -15,11 +15,12 @@ "dependencies": { "@gnu-taler/anastasis-core": "workspace:*", "@gnu-taler/taler-util": "workspace:*", + "@gnu-taler/web-util": "workspace:*", "@types/chai": "^4.3.0", "chai": "^4.3.6", "date-fns": "2.29.2", "jed": "1.1.1", - "preact": "^10.5.15", + "preact": "10.11.3", "preact-render-to-string": "^5.1.19", "preact-router": "^3.2.1", "qrcode-generator": "^1.4.4" @@ -41,12 +42,9 @@ "bulma": "^0.9.3", "bulma-checkbox": "^1.1.1", "bulma-radio": "^1.1.1", - "chokidar": "^3.5.3", - "eslint-plugin-header": "^3.1.1", "jssha": "^3.2.0", "mocha": "^9.2.0", - "sass": "1.32.13", - "typescript": "^4.8.4", - "ws": "7.4.5" + "sass": "1.56.1", + "typescript": "^4.8.4" } -} +}
\ No newline at end of file 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/html/ui.html b/packages/anastasis-webui/src/index.html index 6672eba6a..90a795ae3 100644 --- a/packages/anastasis-webui/html/ui.html +++ b/packages/anastasis-webui/src/index.html @@ -1,28 +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" /> - <style id="style-id"> - ANASTASIS_STYLE_CONTENT - </style> + <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" class="anastasis-container"></div> - <script> - ANASTASIS_SCRIPT_CONTENT; - </script> + <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 = { diff --git a/packages/anastasis-webui/watch/reply.sh b/packages/anastasis-webui/watch/reply.sh deleted file mode 100755 index 20cbff37e..000000000 --- a/packages/anastasis-webui/watch/reply.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -SERVER_KEY=258EAFA5-E914-47DA-95CA-C5AB0DC85B11 - -while read line; do - LINE=$(echo $line | tr -d '\r') - case $LINE in - Sec-WebSocket-Key:*) - CLIENT_KEY="${LINE:19}" - export WS_ACCEPT=$( echo -n $CLIENT_KEY$SERVER_KEY | sha1sum | xxd -r -p | base64 ) - ;; - "") break ;; - esac -done - -cat watch/web_socket_server.reply | sed 's/$'"/`echo \\\r`/" | envsubst '$WS_ACCEPT' - -tail -n 0 -F /tmp/send_signal 2> /dev/null - diff --git a/packages/anastasis-webui/watch/send.sh b/packages/anastasis-webui/watch/send.sh deleted file mode 100755 index 184cd2491..000000000 --- a/packages/anastasis-webui/watch/send.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -#https://datatracker.ietf.org/doc/html/rfc6455#page-65 - -COMMAND=$1 -LEN=$(printf '%x\n' ${#COMMAND}) - -#text command -OPCODE=81 - -cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) >> /tmp/send_signal - diff --git a/packages/anastasis-webui/watch/send2.sh b/packages/anastasis-webui/watch/send2.sh deleted file mode 100755 index 6a2881c19..000000000 --- a/packages/anastasis-webui/watch/send2.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -#https://datatracker.ietf.org/doc/html/rfc6455#page-65 - -CONTENT=$( cat $1 | base64 -w 0 ) -COMMAND='{"type":"UPDATE","'$CONTENT'"}' -LEN=$(printf '%0*x\n' 4 ${#COMMAND}) -echo $LEN -LEN=00000138 -#text command -OPCODE=81 - -cat <(echo -n $OPCODE$LEN | xxd -r -p) <(echo -n $COMMAND) >> /tmp/send_signal - diff --git a/packages/anastasis-webui/watch/serve.sh b/packages/anastasis-webui/watch/serve.sh deleted file mode 100755 index f4e9595d5..000000000 --- a/packages/anastasis-webui/watch/serve.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -#clean up -rm /tmp/send_signal - -socat TCP-LISTEN:8003,fork,reuseaddr,keepalive EXEC:"./watch/reply.sh" - diff --git a/packages/anastasis-webui/watch/web_socket_client.request b/packages/anastasis-webui/watch/web_socket_client.request deleted file mode 100644 index e7077b0cb..000000000 --- a/packages/anastasis-webui/watch/web_socket_client.request +++ /dev/null @@ -1,6 +0,0 @@ -GET /socket HTTP/1.1 -Connection: Upgrade -Upgrade: websocket -Sec-WebSocket-Version: 13 -Sec-WebSocket-Key: aaaaaaaaaaaaaaaaaaaaaa== - diff --git a/packages/anastasis-webui/watch/web_socket_server.reply b/packages/anastasis-webui/watch/web_socket_server.reply deleted file mode 100644 index b4e0db001..000000000 --- a/packages/anastasis-webui/watch/web_socket_server.reply +++ /dev/null @@ -1,5 +0,0 @@ -HTTP/1.1 101 Switching Protocols -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Accept: $WS_ACCEPT - |