using web-utils in anastasis-webui
This commit is contained in:
parent
e382b02203
commit
d3a6544bc5
147
packages/anastasis-webui/build.mjs
Executable file
147
packages/anastasis-webui/build.mjs
Executable file
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
@ -14,87 +14,18 @@
|
|||||||
You should have received a copy of the GNU Affero General Public License along with
|
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/>
|
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
|
import { serve } from "@gnu-taler/web-util/lib/index.node";
|
||||||
const devServerPort = 8002
|
import esbuild from 'esbuild';
|
||||||
const wss = new WebSocket.Server({ port: devServerPort });
|
import { buildConfig } from "./build.mjs";
|
||||||
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.
|
|
||||||
`);
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
@ -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>
|
|
@ -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>
|
|
@ -15,11 +15,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/anastasis-core": "workspace:*",
|
"@gnu-taler/anastasis-core": "workspace:*",
|
||||||
"@gnu-taler/taler-util": "workspace:*",
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
|
"@gnu-taler/web-util": "workspace:*",
|
||||||
"@types/chai": "^4.3.0",
|
"@types/chai": "^4.3.0",
|
||||||
"chai": "^4.3.6",
|
"chai": "^4.3.6",
|
||||||
"date-fns": "2.29.2",
|
"date-fns": "2.29.2",
|
||||||
"jed": "1.1.1",
|
"jed": "1.1.1",
|
||||||
"preact": "^10.5.15",
|
"preact": "10.11.3",
|
||||||
"preact-render-to-string": "^5.1.19",
|
"preact-render-to-string": "^5.1.19",
|
||||||
"preact-router": "^3.2.1",
|
"preact-router": "^3.2.1",
|
||||||
"qrcode-generator": "^1.4.4"
|
"qrcode-generator": "^1.4.4"
|
||||||
@ -41,12 +42,9 @@
|
|||||||
"bulma": "^0.9.3",
|
"bulma": "^0.9.3",
|
||||||
"bulma-checkbox": "^1.1.1",
|
"bulma-checkbox": "^1.1.1",
|
||||||
"bulma-radio": "^1.1.1",
|
"bulma-radio": "^1.1.1",
|
||||||
"chokidar": "^3.5.3",
|
|
||||||
"eslint-plugin-header": "^3.1.1",
|
|
||||||
"jssha": "^3.2.0",
|
"jssha": "^3.2.0",
|
||||||
"mocha": "^9.2.0",
|
"mocha": "^9.2.0",
|
||||||
"sass": "1.32.13",
|
"sass": "1.56.1",
|
||||||
"typescript": "^4.8.4",
|
"typescript": "^4.8.4"
|
||||||
"ws": "7.4.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@
|
|||||||
import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core";
|
import { BackupStates, RecoveryStates } from "@gnu-taler/anastasis-core";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useAnastasisContext } from "../../context/anastasis.js";
|
import { useAnastasisContext } from "../../context/anastasis.js";
|
||||||
import { Translate } from "../../i18n/index.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
@ -34,6 +34,7 @@ const VERSION_WITH_HASH = GIT_HASH ? `${VERSION}-${GIT_HASH}` : VERSION;
|
|||||||
|
|
||||||
export function Sidebar({ mobile }: Props): VNode {
|
export function Sidebar({ mobile }: Props): VNode {
|
||||||
const reducer = useAnastasisContext()!;
|
const reducer = useAnastasisContext()!;
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
function saveSession(): void {
|
function saveSession(): void {
|
||||||
const state = reducer.exportState();
|
const state = reducer.exportState();
|
||||||
@ -64,7 +65,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
<div class="menu is-menu-main">
|
<div class="menu is-menu-main">
|
||||||
{!reducer.currentReducerState && (
|
{!reducer.currentReducerState && (
|
||||||
<p class="menu-label">
|
<p class="menu-label">
|
||||||
<Translate>Backup or Recorver</Translate>
|
<i18n.Translate>Backup or Recorver</i18n.Translate>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<ul class="menu-list">
|
<ul class="menu-list">
|
||||||
@ -72,7 +73,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
<li>
|
<li>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Select one option</Translate>
|
<i18n.Translate>Select one option</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -91,7 +92,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Location</Translate>
|
<i18n.Translate>Location</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -105,7 +106,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Personal information</Translate>
|
<i18n.Translate>Personal information</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -119,7 +120,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Authorization methods</Translate>
|
<i18n.Translate>Authorization methods</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -133,7 +134,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Policies</Translate>
|
<i18n.Translate>Policies</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -147,14 +148,14 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Secret input</Translate>
|
<i18n.Translate>Secret input</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}>
|
||||||
<div class="ml-4">
|
<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>
|
</div>
|
||||||
</li> */}
|
</li> */}
|
||||||
<li
|
<li
|
||||||
@ -167,14 +168,14 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Backup completed</Translate>
|
<i18n.Translate>Backup completed</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
|
{/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}>
|
||||||
<div class="ml-4">
|
<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>
|
</div>
|
||||||
</li> */}
|
</li> */}
|
||||||
{reducer.currentReducerState.backup_state !==
|
{reducer.currentReducerState.backup_state !==
|
||||||
@ -219,7 +220,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Location</Translate>
|
<i18n.Translate>Location</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -233,7 +234,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Personal information</Translate>
|
<i18n.Translate>Personal information</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -247,7 +248,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Secret selection</Translate>
|
<i18n.Translate>Secret selection</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -263,7 +264,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Solve Challenges</Translate>
|
<i18n.Translate>Solve Challenges</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@ -277,7 +278,7 @@ export function Sidebar({ mobile }: Props): VNode {
|
|||||||
>
|
>
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<span class="menu-item-label">
|
<span class="menu-item-label">
|
||||||
<Translate>Secret recovered</Translate>
|
<i18n.Translate>Secret recovered</i18n.Translate>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { useTranslator } from "../../i18n/index.js";
|
import { useTranslationContext } from "../../context/translation.js";
|
||||||
import "../../scss/DurationPicker.scss";
|
import "../../scss/DurationPicker.scss";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -46,13 +46,13 @@ export function DurationPicker({
|
|||||||
const ms = ss * 60;
|
const ms = ss * 60;
|
||||||
const hs = ms * 60;
|
const hs = ms * 60;
|
||||||
const ds = hs * 24;
|
const ds = hs * 24;
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="rdp-picker">
|
<div class="rdp-picker">
|
||||||
{days && (
|
{days && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`days`}
|
unit={i18n.str`days`}
|
||||||
max={99}
|
max={99}
|
||||||
value={Math.floor(value / ds)}
|
value={Math.floor(value / ds)}
|
||||||
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
onDecrease={value >= ds ? () => onChange(value - ds) : undefined}
|
||||||
@ -62,7 +62,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{hours && (
|
{hours && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`hours`}
|
unit={i18n.str`hours`}
|
||||||
max={23}
|
max={23}
|
||||||
min={1}
|
min={1}
|
||||||
value={Math.floor(value / hs) % 24}
|
value={Math.floor(value / hs) % 24}
|
||||||
@ -73,7 +73,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{minutes && (
|
{minutes && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`minutes`}
|
unit={i18n.str`minutes`}
|
||||||
max={59}
|
max={59}
|
||||||
min={1}
|
min={1}
|
||||||
value={Math.floor(value / ms) % 60}
|
value={Math.floor(value / ms) % 60}
|
||||||
@ -84,7 +84,7 @@ export function DurationPicker({
|
|||||||
)}
|
)}
|
||||||
{seconds && (
|
{seconds && (
|
||||||
<DurationColumn
|
<DurationColumn
|
||||||
unit={i18n`seconds`}
|
unit={i18n.str`seconds`}
|
||||||
max={59}
|
max={59}
|
||||||
value={Math.floor(value / ss) % 60}
|
value={Math.floor(value / ss) % 60}
|
||||||
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
onDecrease={value >= ss ? () => onChange(value - ss) : undefined}
|
||||||
|
@ -19,23 +19,42 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @author Sebastian Javier Marchano (sebasjm)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { i18n, setupI18n } from "@gnu-taler/taler-util";
|
||||||
import { createContext, h, VNode } from "preact";
|
import { createContext, h, VNode } from "preact";
|
||||||
import { useContext, useEffect } from "preact/hooks";
|
import { useContext, useEffect } from "preact/hooks";
|
||||||
import { useLang } from "../hooks/index.js";
|
import { useLang } from "../hooks/useLang.js";
|
||||||
import * as jedLib from "jed";
|
|
||||||
import { strings } from "../i18n/strings.js";
|
import { strings } from "../i18n/strings.js";
|
||||||
|
|
||||||
interface Type {
|
interface Type {
|
||||||
lang: string;
|
lang: string;
|
||||||
handler: any;
|
supportedLang: { [id in keyof typeof supportedLang]: string };
|
||||||
changeLanguage: (l: string) => void;
|
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 = {
|
const initial = {
|
||||||
lang: "en",
|
lang: "en",
|
||||||
handler: null,
|
supportedLang,
|
||||||
changeLanguage: () => {
|
changeLanguage: () => {
|
||||||
// do not change anything
|
// do not change anything
|
||||||
},
|
},
|
||||||
|
i18n,
|
||||||
|
isSaved: false,
|
||||||
};
|
};
|
||||||
const Context = createContext<Type>(initial);
|
const Context = createContext<Type>(initial);
|
||||||
|
|
||||||
@ -50,15 +69,23 @@ export const TranslationProvider = ({
|
|||||||
children,
|
children,
|
||||||
forceLang,
|
forceLang,
|
||||||
}: Props): VNode => {
|
}: Props): VNode => {
|
||||||
const [lang, changeLanguage] = useLang(initial);
|
const [lang, changeLanguage, isSaved] = useLang(initial);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (forceLang) {
|
if (forceLang) {
|
||||||
changeLanguage(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, {
|
return h(Context.Provider, {
|
||||||
value: { lang, handler, changeLanguage },
|
value: { lang, changeLanguage, supportedLang, i18n, isSaved },
|
||||||
children,
|
children,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -19,7 +19,9 @@
|
|||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @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);
|
export type ValueOrFunction<T> = T | ((p: T) => T);
|
||||||
|
|
||||||
const calculateRootPath = () => {
|
const calculateRootPath = () => {
|
||||||
@ -69,69 +71,4 @@ export function useBackendInstanceToken(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [token, setToken];
|
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];
|
|
||||||
}
|
|
@ -13,29 +13,18 @@
|
|||||||
You should have received a copy of the GNU Affero General Public License along with
|
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/>
|
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 {
|
import { useNotNullLocalStorage } from "./useLocalStorage.js";
|
||||||
try {
|
|
||||||
const container = document.getElementById("container");
|
function getBrowserLang(): string | undefined {
|
||||||
if (!container) {
|
if (window.navigator.languages) return window.navigator.languages[0];
|
||||||
throw Error("container not found, can't mount page contents");
|
if (window.navigator.language) return window.navigator.language;
|
||||||
}
|
return undefined;
|
||||||
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);
|
export function useLang(
|
||||||
|
initial?: string,
|
||||||
if (document.readyState === "loading") {
|
): [string, (s: string) => void, boolean] {
|
||||||
document.addEventListener("DOMContentLoaded", main);
|
const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
|
||||||
} else {
|
return useNotNullLocalStorage("lang-preference", defaultLang);
|
||||||
main();
|
|
||||||
}
|
}
|
80
packages/anastasis-webui/src/hooks/useLocalStorage.ts
Normal file
80
packages/anastasis-webui/src/hooks/useLocalStorage.ts
Normal file
@ -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];
|
||||||
|
}
|
@ -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>;
|
|
||||||
}
|
|
@ -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>
|
<!DOCTYPE html>
|
||||||
<html
|
<html
|
||||||
lang="en"
|
lang="en"
|
||||||
class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
|
class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top has-aside-expanded"
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="icon"
|
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=="
|
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" />
|
<link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
|
||||||
<style id="style-id">
|
<title>Anastasis</title>
|
||||||
ANASTASIS_STYLE_CONTENT
|
<!-- Entry point for the demobank SPA. -->
|
||||||
</style>
|
<script type="module" src="index.js"></script>
|
||||||
|
<link rel="stylesheet" href="index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="container" class="anastasis-container"></div>
|
<div id="container"></div>
|
||||||
<script>
|
|
||||||
ANASTASIS_SCRIPT_CONTENT;
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -13,7 +13,30 @@
|
|||||||
You should have received a copy of the GNU Affero General Public License along with
|
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/>
|
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 App from "./components/app.js";
|
||||||
import "./scss/main.scss";
|
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();
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import { createExampleWithoutAnastasis } from "../../../utils/index.jsx";
|
|||||||
import { WithoutProviderType, WithProviderType } from "./views.jsx";
|
import { WithoutProviderType, WithProviderType } from "./views.jsx";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Adding Provider Screen",
|
||||||
args: {
|
args: {
|
||||||
order: 1,
|
order: 1,
|
||||||
},
|
},
|
||||||
|
@ -23,8 +23,10 @@ import { TextInput } from "../../../components/fields/TextInput.js";
|
|||||||
import { Notifications } from "../../../components/Notifications.js";
|
import { Notifications } from "../../../components/Notifications.js";
|
||||||
import { AnastasisClientFrame } from "../index.js";
|
import { AnastasisClientFrame } from "../index.js";
|
||||||
import { testProvider, WithoutType, WithType } from "./index.js";
|
import { testProvider, WithoutType, WithType } from "./index.js";
|
||||||
|
import { useTranslationContext } from "../../../context/translation.js";
|
||||||
|
|
||||||
export function WithProviderType(props: WithType): VNode {
|
export function WithProviderType(props: WithType): VNode {
|
||||||
|
const { i18n } = useTranslationContext();
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame
|
<AnastasisClientFrame
|
||||||
hideNav
|
hideNav
|
||||||
@ -33,7 +35,7 @@ export function WithProviderType(props: WithType): VNode {
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<Notifications notifications={props.notifications} />
|
<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">
|
<div class="container">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Provider URL"
|
label="Provider URL"
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen.js";
|
import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Attribute Entry Screen",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 3,
|
order: 3,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen.js";
|
import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Authentication Editor Screen",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen.js";
|
import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Backup finish",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 8,
|
order: 8,
|
||||||
|
@ -28,6 +28,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen.js";
|
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Challenge overview",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen.js";
|
import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Challenge paying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen.js";
|
import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Continent selection",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 2,
|
order: 2,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen.js";
|
import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Edit policies",
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen.js";
|
import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Policies paying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 9,
|
order: 9,
|
||||||
|
@ -25,6 +25,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen.js";
|
import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Recovery Finished",
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen.js";
|
import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Reviewing Policies",
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen.js";
|
import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Secret editor",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
} from "./SecretSelectionScreen.js";
|
} from "./SecretSelectionScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Secret selection",
|
||||||
component: SecretSelectionScreen,
|
component: SecretSelectionScreen,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { SolveScreen as TestedComponent } from "./SolveScreen.js";
|
import { SolveScreen as TestedComponent } from "./SolveScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Solve Screen",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { StartScreen as TestedComponent } from "./StartScreen.js";
|
import { StartScreen as TestedComponent } from "./StartScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Start screen",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 1,
|
order: 1,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils/index.js";
|
|||||||
import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen.js";
|
import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Truths Paying",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Email setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -27,6 +27,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Email solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -23,7 +23,7 @@ import { useState } from "preact/hooks";
|
|||||||
import { AsyncButton } from "../../../components/AsyncButton.js";
|
import { AsyncButton } from "../../../components/AsyncButton.js";
|
||||||
import { TextInput } from "../../../components/fields/TextInput.js";
|
import { TextInput } from "../../../components/fields/TextInput.js";
|
||||||
import { useAnastasisContext } from "../../../context/anastasis.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 { AnastasisClientFrame } from "../index.js";
|
||||||
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
||||||
import { shouldHideConfirm } from "./helpers.js";
|
import { shouldHideConfirm } from "./helpers.js";
|
||||||
@ -53,7 +53,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
_setAnswer(result);
|
_setAnswer(result);
|
||||||
}
|
}
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
@ -124,7 +124,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
|
|
||||||
const error =
|
const error =
|
||||||
answer.length > 21
|
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;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: IBAN setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: IBAN Solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Post setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Post solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -19,7 +19,7 @@ import { useState } from "preact/hooks";
|
|||||||
import { AsyncButton } from "../../../components/AsyncButton.js";
|
import { AsyncButton } from "../../../components/AsyncButton.js";
|
||||||
import { TextInput } from "../../../components/fields/TextInput.js";
|
import { TextInput } from "../../../components/fields/TextInput.js";
|
||||||
import { useAnastasisContext } from "../../../context/anastasis.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 { AnastasisClientFrame } from "../index.js";
|
||||||
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
||||||
import { shouldHideConfirm } from "./helpers.js";
|
import { shouldHideConfirm } from "./helpers.js";
|
||||||
@ -48,7 +48,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
|
|
||||||
_setAnswer(result);
|
_setAnswer(result);
|
||||||
}
|
}
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
@ -119,7 +119,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
|
|
||||||
const error =
|
const error =
|
||||||
answer.length > 21
|
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;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Question setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -27,6 +27,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Question solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: SMS setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: SMS solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -19,7 +19,7 @@ import { useState } from "preact/hooks";
|
|||||||
import { AsyncButton } from "../../../components/AsyncButton.js";
|
import { AsyncButton } from "../../../components/AsyncButton.js";
|
||||||
import { TextInput } from "../../../components/fields/TextInput.js";
|
import { TextInput } from "../../../components/fields/TextInput.js";
|
||||||
import { useAnastasisContext } from "../../../context/anastasis.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 { AnastasisClientFrame } from "../index.js";
|
||||||
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
import { SolveOverviewFeedbackDisplay } from "../SolveScreen.js";
|
||||||
import { shouldHideConfirm } from "./helpers.js";
|
import { shouldHideConfirm } from "./helpers.js";
|
||||||
@ -48,7 +48,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
|
|
||||||
_setAnswer(result);
|
_setAnswer(result);
|
||||||
}
|
}
|
||||||
const i18n = useTranslator();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
@ -120,7 +120,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode {
|
|||||||
|
|
||||||
const error =
|
const error =
|
||||||
answer.length > 21
|
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;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -23,6 +23,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Totp setup",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -24,6 +24,7 @@ import { createExample, reducerStatesExample } from "../../../utils/index.js";
|
|||||||
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
import { authMethods as TestedComponent, KnownAuthMethods } from "./index.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
title: "Auth method: Totp solve",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
width: $icon-base-width;
|
width: $icon-base-width;
|
||||||
|
|
||||||
&.has-update-mark:after {
|
&.has-update-mark:after {
|
||||||
right: ($icon-base-width / 2) - 0.85;
|
right: calc($icon-base-width / 2) - 0.85;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,302 +18,24 @@
|
|||||||
*
|
*
|
||||||
* @author Sebastian Javier Marchano (sebasjm)
|
* @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 { strings } from "./i18n/strings.js";
|
||||||
|
|
||||||
import * as pages from "./pages/home/index.storiesNo.js";
|
import * as pages from "./pages/home/index.storiesNo.js";
|
||||||
|
|
||||||
const url = new URL(window.location.href);
|
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
|
||||||
const lang = url.searchParams.get("lang") || "en";
|
|
||||||
|
|
||||||
setupI18n(lang, strings);
|
import "./scss/main.scss";
|
||||||
|
|
||||||
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),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function SortStories(a: any, b: any): number {
|
function SortStories(a: any, b: any): number {
|
||||||
return (a?.order ?? 0) - (b?.order ?? 0);
|
return (a?.order ?? 0) - (b?.order ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allExamples = Object.entries({ pages }).map(([title, value]) => {
|
function main(): void {
|
||||||
return {
|
renderStories(
|
||||||
title,
|
{ pages },
|
||||||
list: Object.entries(value)
|
{
|
||||||
.filter(([name]) => name != "default")
|
strings,
|
||||||
.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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,72 +44,3 @@ if (document.readyState === "loading") {
|
|||||||
} else {
|
} else {
|
||||||
main();
|
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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -41,8 +41,10 @@ export function createExample<Props>(
|
|||||||
// check how we can build evaluatedProps in render time
|
// check how we can build evaluatedProps in render time
|
||||||
const evaluatedProps = typeof props === "function" ? props() : props;
|
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||||
const Render = (args: any): VNode => create(Component, args);
|
const Render = (args: any): VNode => create(Component, args);
|
||||||
Render.args = evaluatedProps;
|
return {
|
||||||
return Render;
|
component: Render,
|
||||||
|
props: evaluatedProps
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createExampleWithCustomContext<Props, ContextProps>(
|
export function createExampleWithCustomContext<Props, ContextProps>(
|
||||||
@ -58,8 +60,10 @@ export function createExampleWithCustomContext<Props, ContextProps>(
|
|||||||
...contextProps,
|
...contextProps,
|
||||||
children: [Render(args)],
|
children: [Render(args)],
|
||||||
} as any);
|
} as any);
|
||||||
WithContext.args = evaluatedProps;
|
return {
|
||||||
return WithContext;
|
component: WithContext,
|
||||||
|
props: evaluatedProps
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NullLink({
|
export function NullLink({
|
||||||
|
@ -37,16 +37,18 @@ export function createExampleWithoutAnastasis<Props>(
|
|||||||
// check how we can build evaluatedProps in render time
|
// check how we can build evaluatedProps in render time
|
||||||
const evaluatedProps = typeof props === "function" ? props() : props;
|
const evaluatedProps = typeof props === "function" ? props() : props;
|
||||||
const Render = (args: any): VNode => h(Component, args);
|
const Render = (args: any): VNode => h(Component, args);
|
||||||
Render.args = evaluatedProps;
|
return {
|
||||||
return Render;
|
component: Render,
|
||||||
|
props: evaluatedProps,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createExample<Props>(
|
export function createExample<Props>(
|
||||||
Component: FunctionalComponent<Props>,
|
Component: FunctionalComponent<Props>,
|
||||||
currentReducerState?: ReducerState,
|
currentReducerState?: ReducerState,
|
||||||
props?: Partial<Props>,
|
props?: Partial<Props>,
|
||||||
): { (args: Props): VNode } {
|
): ComponentChildren {
|
||||||
const r = (args: Props): VNode => {
|
const Render = (args: Props): VNode => {
|
||||||
return (
|
return (
|
||||||
<AnastasisProvider
|
<AnastasisProvider
|
||||||
value={{
|
value={{
|
||||||
@ -74,8 +76,10 @@ export function createExample<Props>(
|
|||||||
</AnastasisProvider>
|
</AnastasisProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
r.args = props;
|
return {
|
||||||
return r;
|
component: Render,
|
||||||
|
props: props,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
#clean up
|
|
||||||
rm /tmp/send_signal
|
|
||||||
|
|
||||||
socat TCP-LISTEN:8003,fork,reuseaddr,keepalive EXEC:"./watch/reply.sh"
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
GET /socket HTTP/1.1
|
|
||||||
Connection: Upgrade
|
|
||||||
Upgrade: websocket
|
|
||||||
Sec-WebSocket-Version: 13
|
|
||||||
Sec-WebSocket-Key: aaaaaaaaaaaaaaaaaaaaaa==
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
|||||||
HTTP/1.1 101 Switching Protocols
|
|
||||||
Upgrade: websocket
|
|
||||||
Connection: Upgrade
|
|
||||||
Sec-WebSocket-Accept: $WS_ACCEPT
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user