implement web-utils in web-extension

This commit is contained in:
Sebastian 2022-12-06 11:28:56 -03:00
parent de9f10cbf0
commit 51bbf08d24
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
59 changed files with 243 additions and 753 deletions

View File

@ -40,6 +40,7 @@ function getFilesInDirectory(startPath, regex) {
return result
}
// eslint-disable-next-line no-undef
const BASE = process.cwd()
const allTestFiles = getFilesInDirectory(path.join(BASE, 'src'), /.test.ts$/)
@ -67,7 +68,9 @@ 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()
@ -118,10 +121,12 @@ export const buildConfig = {
],
}
esbuild
await esbuild
.build(buildConfig)
.catch((e) => {
// eslint-disable-next-line no-undef
console.log(e)
// eslint-disable-next-line no-undef
process.exit(1)
});

View File

@ -1,67 +1,38 @@
#!/usr/bin/env node
/* eslint-disable no-undef */
/*
This file is part of GNU Taler
(C) 2022 Taler Systems S.A.
import linaria from '@linaria/esbuild'
import esbuild from 'esbuild'
import { buildConfig } from "./build-fast-with-linaria.mjs"
import fs from 'fs';
import WebSocket from "ws";
import chokidar from "chokidar";
import path from "path"
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.
const devServerBroadcastDelay = 500
const devServerPort = 8002
const wss = new WebSocket.Server({ port: devServerPort });
const toWatch = ["./src"]
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.
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);
}
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/>
*/
const watcher = chokidar
.watch(toWatch, {
persistent: true,
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 100,
},
import { serve } from "@gnu-taler/web-util/lib/index.node";
import esbuild from 'esbuild';
import { buildConfig } from "./build-fast-with-linaria.mjs";
buildConfig.inject = ['./node_modules/@gnu-taler/web-util/lib/live-reload.mjs']
serve({
folder: './dist',
port: 8080,
source: './src',
development: true,
onUpdate: async () => esbuild.build(buildConfig)
})
.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" });
});
// FIXME: create a mocha test in the browser as it was before
fs.writeFileSync("dev-html/manifest.json", fs.readFileSync("manifest-v2.json"))
fs.writeFileSync("dev-html/mocha.css", fs.readFileSync("node_modules/mocha/mocha.css"))
fs.writeFileSync("dev-html/mocha.js", fs.readFileSync("node_modules/mocha/mocha.js"))
fs.writeFileSync("dev-html/mocha.js.map", fs.readFileSync("node_modules/mocha/mocha.js.map"))
const server = await esbuild
.serve({ servedir: 'dev-html' }, {
...buildConfig, outdir: 'dev-html/dist'
})
.catch((e) => {
console.log(e)
process.exit(1)
});
console.log(`Dev server is ready at http://localhost:${server.port}/.
http://localhost:${server.port}/stories.html for the components stories.
The server is running a using websocket at ${devServerPort} to notify code change and live reload.
`);
// fs.writeFileSync("dev-html/manifest.json", fs.readFileSync("manifest-v2.json"))
// fs.writeFileSync("dev-html/mocha.css", fs.readFileSync("node_modules/mocha/mocha.css"))
// fs.writeFileSync("dev-html/mocha.js", fs.readFileSync("node_modules/mocha/mocha.js"))
// fs.writeFileSync("dev-html/mocha.js.map", fs.readFileSync("node_modules/mocha/mocha.js.map"))

View File

@ -25,12 +25,11 @@
"@gnu-taler/taler-wallet-core": "workspace:*",
"date-fns": "^2.29.2",
"history": "4.10.1",
"preact": "^10.6.5",
"preact": "10.11.3",
"preact-router": "3.2.1",
"qr-scanner": "^1.4.1",
"qrcode-generator": "^1.4.4",
"tslib": "^2.4.0",
"ws": "7.4.5"
"tslib": "^2.4.0"
},
"eslintConfig": {
"plugins": [
@ -44,6 +43,7 @@
}
},
"devDependencies": {
"@gnu-taler/web-util": "workspace:*",
"@babel/core": "7.18.9",
"@babel/plugin-transform-modules-commonjs": "7.18.6",
"@babel/plugin-transform-react-jsx-source": "7.18.6",
@ -54,8 +54,6 @@
"@linaria/core": "3.0.0-beta.22",
"@linaria/react": "3.0.0-beta.22",
"@linaria/webpack-loader": "3.0.0-beta.22",
"@testing-library/preact": "^2.0.1",
"@testing-library/preact-hooks": "^1.1.0",
"@types/chai": "^4.3.0",
"@types/chrome": "0.0.197",
"@types/history": "^4.7.8",
@ -64,7 +62,6 @@
"babel-loader": "^8.2.3",
"babel-plugin-transform-react-jsx": "^6.24.1",
"chai": "^4.3.6",
"chokidar": "^3.5.3",
"esbuild": "^0.15.13",
"mocha": "^9.2.0",
"nyc": "^15.1.0",

View File

@ -24,7 +24,7 @@ import { Fragment, h, VNode } from "preact";
import { Amount } from "./Amount.js";
export default {
title: "components/amount",
title: "amount",
component: Amount,
};

View File

@ -29,7 +29,7 @@ import { AmountFieldHandler, TextFieldHandler } from "../mui/handlers.js";
import { AmountField } from "./AmountField.js";
export default {
title: "components/amountField",
title: "amountField",
};
function RenderAmount(): VNode {

View File

@ -26,7 +26,7 @@ import { Banner } from "./Banner.js";
import { SvgIcon } from "./styled/index.js";
import wifiIcon from "../svg/wifi.svg";
export default {
title: "mui/banner",
title: "banner",
component: Banner,
};

View File

@ -28,7 +28,7 @@ import { createExample } from "../test-utils.js";
import { PendingTransactionsView as TestedComponent } from "./PendingTransactions.js";
export default {
title: "component/PendingTransactions",
title: "PendingTransactions",
component: TestedComponent,
};

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { QR } from "./QR.js";
export default {
title: "wallet/qr",
title: "qr",
};
export const Restore = createExample(QR, {

View File

@ -29,7 +29,7 @@ import {
} from "./ShowFullContractTermPopup.js";
export default {
title: "component/ShowFullContractTermPopup",
title: "ShowFullContractTermPopup",
};
const cd: WalletContractData = {

View File

@ -19,12 +19,10 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import * as a1 from "./Banner.stories.js";
import * as a2 from "./PendingTransactions.stories.js";
import * as a3 from "./Amount.stories.js";
import * as a4 from "./ShowFullContractTermPopup.stories.js";
import * as a5 from "./TermsOfService/stories.js";
import * as a6 from "./QR.stories";
import * as a7 from "./AmountField.stories.js";
export default [a1, a2, a3, a4, a5, a6, a7];
export * as a1 from "./Banner.stories.js";
export * as a2 from "./PendingTransactions.stories.js";
export * as a3 from "./Amount.stories.js";
export * as a4 from "./ShowFullContractTermPopup.stories.js";
export * as a5 from "./TermsOfService/stories.js";
export * as a6 from "./QR.stories";
export * as a7 from "./AmountField.stories.js";

View File

@ -24,7 +24,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "cta/deposit",
title: "deposit",
};
export const Ready = createExample(ReadyView, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/invoice create",
title: "invoice create",
};
export const Ready = createExample(ReadyView, {

View File

@ -24,7 +24,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/invoice payment",
title: "invoice payment",
};
export const Ready = createExample(ReadyView, {

View File

@ -30,7 +30,7 @@ import { BaseView } from "./views.js";
import beer from "../../../static-dev/beer.png";
export default {
title: "cta/payment",
title: "payment",
component: BaseView,
argTypes: {},
};

View File

@ -24,5 +24,5 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "cta/recovery",
title: "recovery",
};

View File

@ -24,7 +24,7 @@ import beer from "../../../static-dev/beer.png";
import { createExample } from "../../test-utils.js";
import { IgnoredView, InProgressView, ReadyView } from "./views.js";
export default {
title: "cta/refund",
title: "refund",
};
export const InProgress = createExample(InProgressView, {

View File

@ -24,7 +24,7 @@ import { createExample } from "../../test-utils.js";
import { AcceptedView, ReadyView } from "./views.js";
export default {
title: "cta/tip",
title: "tip",
};
export const Accepted = createExample(AcceptedView, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/transfer create",
title: "transfer create",
};
export const Ready = createExample(ReadyView, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/transfer pickup",
title: "transfer pickup",
};
export const Ready = createExample(ReadyView, {

View File

@ -25,7 +25,7 @@ import { createExample } from "../../test-utils.js";
import { SuccessView } from "./views.js";
export default {
title: "cta/withdraw",
title: "withdraw",
};
const exchangeList = {

View File

@ -19,14 +19,12 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import * as a1 from "./Deposit/stories.jsx";
import * as a3 from "./Payment/stories.jsx";
import * as a4 from "./Refund/stories.jsx";
import * as a5 from "./Tip/stories.jsx";
import * as a6 from "./Withdraw/stories.jsx";
import * as a8 from "./InvoiceCreate/stories.js";
import * as a9 from "./InvoicePay/stories.js";
import * as a10 from "./TransferCreate/stories.js";
import * as a11 from "./TransferPickup/stories.js";
export default [a1, a3, a4, a5, a6, a8, a9, a10, a11];
export * as a1 from "./Deposit/stories.jsx";
export * as a3 from "./Payment/stories.jsx";
export * as a4 from "./Refund/stories.jsx";
export * as a5 from "./Tip/stories.jsx";
export * as a6 from "./Withdraw/stories.jsx";
export * as a8 from "./InvoiceCreate/stories.js";
export * as a9 from "./InvoicePay/stories.js";
export * as a10 from "./TransferCreate/stories.js";
export * as a11 from "./TransferPickup/stories.js";

View File

@ -24,7 +24,7 @@ import { ComponentChildren, Fragment, h, VNode } from "preact";
import { Alert } from "./Alert.jsx";
export default {
title: "mui/alert",
title: "alert",
component: Alert,
};

View File

@ -26,8 +26,7 @@ import SendIcon from "../svg/send_24px.svg";
import { styled } from "@linaria/react";
export default {
title: "mui/button",
component: Button,
title: "Button",
};
const Stack = styled.div`

View File

@ -23,7 +23,7 @@ import { Grid } from "./Grid.js";
import { Fragment, h, VNode } from "preact";
export default {
title: "mui/grid",
title: "grid",
component: Grid,
};

View File

@ -25,7 +25,7 @@ import { Menu, MenuItem } from "./Menu.jsx";
import { Paper } from "./Paper.js";
export default {
title: "mui/menu",
title: "menu",
component: Menu,
};

View File

@ -23,7 +23,7 @@ import { h, VNode } from "preact";
import { Paper } from "./Paper.js";
export default {
title: "mui/paper",
title: "paper",
component: Paper,
};

View File

@ -25,7 +25,7 @@ import { useState } from "preact/hooks";
import { TextField, Props } from "./TextField.js";
export default {
title: "mui/TextField",
title: "TextField",
component: TextField,
};

View File

@ -19,11 +19,9 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import * as a1 from "./Button.stories.js";
import * as a3 from "./Grid.stories.js";
import * as a4 from "./Paper.stories.js";
import * as a5 from "./TextField.stories.js";
import * as a6 from "./Alert.stories.js";
import * as a7 from "./Menu.stories.js";
export default [a1, a3, a4, a5, a6, a7];
export * as a1 from "./Button.stories.js";
export * as a3 from "./Grid.stories.js";
export * as a4 from "./Paper.stories.js";
export * as a5 from "./TextField.stories.js";
export * as a6 from "./Alert.stories.js";
export * as a7 from "./Menu.stories.js";

View File

@ -23,9 +23,7 @@ import { createExample } from "../test-utils.js";
import { BalanceView as TestedComponent } from "./BalancePage.js";
export default {
title: "popup/balance",
component: TestedComponent,
argTypes: {},
title: "balance",
};
export const EmptyBalance = createExample(TestedComponent, {

View File

@ -23,8 +23,7 @@ import { createExample } from "../test-utils.js";
import { TalerActionFound as TestedComponent } from "./TalerActionFound.js";
export default {
title: "popup/TalerActionFound",
component: TestedComponent,
title: "TalerActionFound",
};
export const PayAction = createExample(TestedComponent, {

View File

@ -19,7 +19,5 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import * as a2 from "./Balance.stories.js";
import * as a6 from "./TalerActionFound.stories.js";
export default [a2, a6];
export * as a1 from "./Balance.stories.js";
export * as a2 from "./TalerActionFound.stories.js";

View File

@ -19,37 +19,33 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { setupI18n } from "@gnu-taler/taler-util";
import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser";
import { setupPlatform } from "./platform/api.js";
import chromeAPI from "./platform/chrome.js";
import { renderNodeOrBrowser } from "./test-utils.js";
import * as components from "./components/index.stories.js";
import * as cta from "./cta/index.stories.js";
import * as mui from "./mui/index.stories.js";
import { setupPlatform } from "./platform/api.js";
import chromeAPI from "./platform/chrome.js";
import * as popup from "./popup/index.stories.js";
import { renderNodeOrBrowser } from "./test-utils.js";
import * as wallet from "./wallet/index.stories.js";
setupI18n("en", { en: {} });
setupPlatform(chromeAPI);
function testThisStory(st: any): any {
describe(`example "${(st as any).default.title}"`, () => {
Object.keys(st).forEach((k) => {
const Component = (st as any)[k];
if (k === "default" || !Component) return;
it(`example: ${k}`, () => {
renderNodeOrBrowser(Component, Component.args);
});
});
});
}
describe("render every storybook example", () => {
[popup, wallet, cta, mui, components].forEach(function testAll(st: any) {
if (Array.isArray(st.default)) {
st.default.forEach(testAll);
} else {
testThisStory(st);
}
});
describe("All the examples:", () => {
const cms = parseGroupImport({ popup, wallet, cta, mui, components })
cms.forEach(group => {
describe(`Example for group "${group.title}:"`, () => {
group.list.forEach(component => {
describe(`Component ${component.name}:`, () => {
component.examples.forEach(example => {
it(`should render example: ${example.name}`, () => {
renderNodeOrBrowser(example.render.component, example.render.props)
})
})
})
})
})
})
});

View File

@ -18,432 +18,27 @@
*
* @author Sebastian Javier Marchano (sebasjm)
*/
import { setupI18n } from "@gnu-taler/taler-util";
import { styled } from "@linaria/react";
import {
ComponentChild,
ComponentChildren,
Fragment,
FunctionComponent,
h,
render,
VNode,
} from "preact";
import { useEffect, useErrorBoundary, useState } from "preact/hooks";
import { Fragment, FunctionComponent, h } from "preact";
import { LogoHeader } from "./components/LogoHeader.js";
import { PopupBox, WalletBox } from "./components/styled/index.js";
import * as mui from "./mui/index.stories.js";
import { strings } from "./i18n/strings.js";
import { PopupNavBar, WalletNavBar } from "./NavigationBar.js";
import * as components from "./components/index.stories.js";
import * as cta from "./cta/index.stories.js";
import * as mui from "./mui/index.stories.js";
import * as popup from "./popup/index.stories.js";
import * as wallet from "./wallet/index.stories.js";
import * as cta from "./cta/index.stories.js";
import * as components from "./components/index.stories.js";
import { strings } from "./i18n/strings.js";
import { setupPlatform } from "./platform/api.js";
import chromeAPI from "./platform/chrome.js";
import firefoxAPI from "./platform/firefox.js";
const url = new URL(window.location.href);
const lang = url.searchParams.get("lang") || "en";
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
setupI18n(lang, strings);
const Page = styled.div`
* {
font-family: Arial, Helvetica, sans-serif;
}
p:not([class]) {
margin-bottom: 1em;
margin-top: 1em;
}
width: 100%;
display: flex;
flex-direction: row;
`;
const SideBar = styled.div`
min-width: var(--with-size);
height: calc(100vh - 20px);
overflow-y: visible;
overflow-x: hidden;
scroll-behavior: smooth;
* {
margin: 0px;
padding: 0px;
}
& > {
ol {
padding: 4px;
div:first-child {
background-color: lightcoral;
cursor: pointer;
}
div[data-hide="true"] {
display: none;
}
dd {
margin-left: 1em;
padding: 4px;
cursor: pointer;
border-radius: 4px;
margin-bottom: 4px;
}
dd:nth-child(even) {
background-color: lightgray;
}
dd:nth-child(odd) {
background-color: lightblue;
}
a {
color: black;
}
dd[data-selected] {
background-color: green;
}
}
}
`;
const ResizeHandleDiv = styled.div`
width: 10px;
background: #ddd;
cursor: ew-resize;
`;
function ResizeHandle({ onUpdate }: { onUpdate: (x: number) => void }): VNode {
const [start, setStart] = useState<number | undefined>(undefined);
return (
<ResizeHandleDiv
onMouseDown={(e: any) => {
setStart(e.pageX);
console.log("active", e.pageX);
return false;
}}
onMouseMove={(e: any) => {
if (start !== undefined) {
onUpdate(e.pageX - start);
}
return false;
}}
onMouseUp={() => {
setStart(undefined);
return false;
}}
/>
);
}
const Content = styled.div`
width: 100%;
padding: 20px;
`;
function parseExampleImport(group: string, im: any): ComponentItem {
const component = im.default.title;
return {
name: component,
examples: Object.entries(im)
.filter(([k]) => k !== "default")
.map(
([name, render]) =>
({
group,
component,
name,
render,
} as ExampleItem),
),
};
}
const allExamples = Object.entries({ popup, wallet, cta, mui, components }).map(
([title, value]) => ({
title,
list: value.default.map((s) => parseExampleImport(title, s)),
}),
);
interface ComponentItem {
name: string;
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.substring(k.name.indexOf("/") + 1)}</dt>
{k.examples.map((r) => {
const e = encodeURIComponent;
const eId = `${e(r.group)}-${e(r.component)}-${e(r.name)}`;
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}>
<a
href={`#${eId}`}
onClick={(e) => {
e.preventDefault();
location.hash = `#${eId}`;
onSelectStory(r, eId);
history.pushState({}, "", `#${eId}`);
}}
>
{r.name}
</a>
</dd>
);
})}
</dl>
</li>
))}
</div>
</ol>
);
}
/**
* Prevents the UI from redirecting and inform the dev
* where the <a /> should have redirected
* @returns
*/
function PreventLinkNavigation({
children,
}: {
children: ComponentChildren;
}): VNode {
return (
<div
onClick={(e) => {
let t: any = e.target;
do {
if (t.localName === "a" && t.getAttribute("href")) {
alert(`should navigate to: ${t.attributes.href.value}`);
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
return false;
}
} while ((t = t.parentNode));
}}
>
{children}
</div>
);
}
function getWrapperForGroup(group: string): FunctionComponent {
switch (group) {
case "popup":
return function PopupWrapper({ children }: any) {
return (
<PreventLinkNavigation>
<PopupNavBar />
<PopupBox>{children}</PopupBox>
</PreventLinkNavigation>
);
};
case "wallet":
return function WalletWrapper({ children }: any) {
return (
<PreventLinkNavigation>
<LogoHeader />
<WalletNavBar />
<WalletBox>{children}</WalletBox>
</PreventLinkNavigation>
);
};
case "cta":
return function WalletWrapper({ children }: any) {
return (
<PreventLinkNavigation>
<WalletBox>{children}</WalletBox>
</PreventLinkNavigation>
);
};
default:
return Fragment;
}
}
function ErrorReport({
children,
selected,
}: {
children: ComponentChild;
selected: ExampleItem | undefined;
}): VNode {
const [error, resetError] = useErrorBoundary();
//if there is an error, reset when unloading this component
useEffect(() => (error ? resetError : undefined));
if (error) {
return (
<div>
<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");
const [sidebarWidth, setSidebarWidth] = useState(200);
return (
<Page>
<LiveReload />
<SideBar style={{ "--with-size": `${sidebarWidth}px` }}>
{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>
<ResizeHandle
onUpdate={(x) => {
setSidebarWidth((s) => s + x);
}}
/>
<Content>
<ErrorReport selected={selected}>
<GroupWrapper>
<ExampleContent />
</GroupWrapper>
</ErrorReport>
</Content>
</Page>
function main(): void {
renderStories(
{ popup, wallet, cta, mui, components },
{
strings,
getWrapperForGroup,
},
);
}
@ -452,82 +47,36 @@ if (document.readyState === "loading") {
} else {
main();
}
function main(): void {
try {
const container = document.getElementById("container");
if (!container) {
throw Error("container not found, can't mount page contents");
}
render(<Application />, container);
} catch (e) {
console.error("got error", e);
if (e instanceof Error) {
document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`;
}
}
}
let liveReloadMounted = false;
function LiveReload({ port = 8002 }: { port?: number }): VNode {
const [isReloading, setIsReloading] = useState(false);
useEffect(() => {
if (!liveReloadMounted) {
setupLiveReload(port, () => {
setIsReloading(true);
window.location.reload();
});
liveReloadMounted = true;
}
});
if (isReloading) {
function getWrapperForGroup(group: string): FunctionComponent {
switch (group) {
case "popup":
return function PopupWrapper({ children }: any) {
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>
<Fragment>
<PopupNavBar />
<PopupBox>{children}</PopupBox>
</Fragment>
);
}
return <Fragment />;
}
function setupLiveReload(port: number, onReload: () => void): void {
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
const host = location.hostname;
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);
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;
}
const isFirefox = typeof (window as any)["InstallTrigger"] !== "undefined";
//FIXME: create different entry point for any platform instead of
//switching in runtime
if (isFirefox) {
console.log("Wallet setup for Firefox API");
setupPlatform(firefoxAPI);
} else {
console.log("Wallet setup for Chrome API");
setupPlatform(chromeAPI);
}

View File

@ -49,8 +49,12 @@ export function createExample<Props>(
// check how we can build evaluatedProps in render time
const evaluatedProps = typeof props === "function" ? props() : props;
const Render = (args: any): VNode => create(Component, args);
Render.args = evaluatedProps;
return Render;
// Render.args = evaluatedProps;
return {
component: Render,
props: evaluatedProps
};
}
export function createExampleWithCustomContext<Props, ContextProps>(
@ -66,8 +70,11 @@ export function createExampleWithCustomContext<Props, ContextProps>(
...contextProps,
children: [Render(args)],
} as any);
WithContext.args = evaluatedProps;
return WithContext;
return {
component: WithContext,
props: evaluatedProps
};
}
export function NullLink({

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ConfirmProviderView, SelectProviderView } from "./views.js";
export default {
title: "wallet/backup/confirm",
title: "add backup provider",
};
export const DemoService = createExample(ConfirmProviderView, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { AddNewActionView as TestedComponent } from "./AddNewActionView.js";
export default {
title: "wallet/add new action",
title: "add new action",
component: TestedComponent,
argTypes: {
setDeviceName: () => Promise.resolve(),

View File

@ -29,13 +29,7 @@ import { createExample } from "../test-utils.js";
import { TalerProtocolTimestamp } from "@gnu-taler/taler-util";
export default {
title: "wallet/backup/list",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },
onDelete: { action: "onDelete" },
onBack: { action: "onBack" },
},
title: "backup",
};
export const LotOfProviders = createExample(TestedComponent, {

View File

@ -25,7 +25,7 @@ import { labelForAccountType } from "./state.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/deposit",
title: "deposit",
};
// const ac = parsePaytoUri("payto://iban/ES8877998399652238")!;

View File

@ -74,7 +74,7 @@ export namespace State {
}
export type Contact = {
icon: string;
icon_type: string;
name: string;
description: string;
};

View File

@ -21,7 +21,6 @@ import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { assertUnreachable, RecursiveState } from "../../utils/index.js";
import { wxApi } from "../../wxApi.js";
import { Contact, Props, State } from "./index.js";
import bankIcon from "../../svg/ri-bank-line.svg";
export function useComponentState(
props: Props,
@ -44,17 +43,17 @@ export function useComponentState(
: [
{
name: "International Bank",
icon: bankIcon, //FIXME: should be decided in the view
icon_type: 'bank',
description: "account ending with 3454",
},
{
name: "Max",
icon: bankIcon,
icon_type: 'bank',
description: "account ending with 3454",
},
{
name: "Alex",
icon: bankIcon,
icon_type: 'bank',
description: "account ending with 3454",
},
];

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView, SelectCurrencyView } from "./views.js";
export default {
title: "wallet/destination",
title: "destination",
};
export const GetCash = createExample(ReadyView, {

View File

@ -377,6 +377,7 @@ export function ReadySendView({
</Container>
);
}
import bankIcon from "../../svg/ri-bank-line.svg";
function RowExample({
info,
@ -385,15 +386,22 @@ function RowExample({
info: Contact;
disabled?: boolean;
}): VNode {
const icon = info.icon_type === "bank" ? bankIcon : undefined;
return (
<MediaExample data-disabled={disabled}>
<MediaLeft>
<CircleDiv>
{icon !== undefined ? (
<SvgIcon
title={info.name}
dangerouslySetInnerHTML={{ __html: info.icon }}
dangerouslySetInnerHTML={{
__html: icon,
}}
color="currentColor"
/>
) : (
<span>A</span>
)}
</CircleDiv>
</MediaLeft>
<MediaBody>

View File

@ -24,7 +24,7 @@ import { createExample } from "../test-utils.js";
import { View as TestedComponent } from "./DeveloperPage.js";
export default {
title: "wallet/developer",
title: "developer",
component: TestedComponent,
argTypes: {
setDeviceName: () => Promise.resolve(),

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { ExchangeAddConfirmPage as TestedComponent } from "./ExchangeAddConfirm.js";
export default {
title: "wallet/exchange add/confirm",
title: "exchange add confirm",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },

View File

@ -24,13 +24,7 @@ import { queryToSlashKeys } from "../utils/index.js";
import { ExchangeSetUrlPage as TestedComponent } from "./ExchangeSetUrl.js";
export default {
title: "wallet/exchange add/set url",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },
onDelete: { action: "onDelete" },
onBack: { action: "onBack" },
},
title: "exchange add set url",
};
export const ExpectedUSD = createExample(TestedComponent, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ComparingView, ReadyView } from "./views.js";
export default {
title: "wallet/select exchange",
title: "select exchange",
};
export const Bitcoin1 = createExample(ReadyView, {

View File

@ -40,7 +40,7 @@ import { HistoryView as TestedComponent } from "./History.js";
import { createExample } from "../test-utils.js";
export default {
title: "wallet/balance",
title: "balance",
component: TestedComponent,
};

View File

@ -23,7 +23,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/manage account",
title: "manage account",
};
const nullFunction = async () => {

View File

@ -24,7 +24,7 @@ import { createExample } from "../../test-utils.js";
import { ReadyView } from "./views.js";
export default {
title: "wallet/notifications",
title: "notifications",
};
export const Ready = createExample(ReadyView, {

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { ConfirmProviderView as TestedComponent } from "./ProviderAddPage.js";
export default {
title: "wallet/backup/confirm",
title: "confirm",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { SetUrlView as TestedComponent } from "./ProviderAddPage.js";
export default {
title: "wallet/backup/add",
title: "add",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },

View File

@ -25,7 +25,7 @@ import { createExample } from "../test-utils.js";
import { ProviderView as TestedComponent } from "./ProviderDetailPage.js";
export default {
title: "wallet/backup/details",
title: "provider details",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { QrReaderPage } from "./QrReader.js";
export default {
title: "wallet/qr reader",
title: "qr reader",
};
export const Reading = createExample(QrReaderPage, {});

View File

@ -24,7 +24,7 @@ import { createExample } from "../test-utils.js";
import { ReserveCreated as TestedComponent } from "./ReserveCreated.js";
export default {
title: "wallet/manual withdraw/reserve created",
title: "reserve created",
component: TestedComponent,
argTypes: {},
};

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { SettingsView as TestedComponent } from "./Settings.js";
export default {
title: "wallet/settings",
title: "settings",
component: TestedComponent,
argTypes: {
setDeviceName: () => Promise.resolve(),

View File

@ -46,7 +46,7 @@ import {
import { TransactionView as TestedComponent } from "./Transaction.js";
export default {
title: "wallet/history/details",
title: "transaction details",
component: TestedComponent,
argTypes: {
onRetry: { action: "onRetry" },

View File

@ -23,7 +23,7 @@ import { createExample } from "../test-utils.js";
import { View as TestedComponent } from "./Welcome.js";
export default {
title: "wallet/welcome",
title: "welcome",
component: TestedComponent,
};

View File

@ -19,42 +19,21 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import * as a1 from "./Backup.stories.js";
import * as a4 from "./DepositPage/stories.js";
import * as a5 from "./ExchangeAddConfirm.stories.js";
import * as a6 from "./ExchangeAddSetUrl.stories.js";
import * as a7 from "./History.stories.js";
import * as a8 from "./AddBackupProvider/stories.js";
import * as a10 from "./ProviderDetail.stories.js";
import * as a11 from "./ReserveCreated.stories.js";
import * as a12 from "./Settings.stories.js";
import * as a13 from "./Transaction.stories.js";
import * as a14 from "./Welcome.stories.js";
import * as a15 from "./AddNewActionView.stories.js";
import * as a16 from "./DeveloperPage.stories.js";
import * as a17 from "./QrReader.stories.js";
import * as a18 from "./DestinationSelection/stories.js";
import * as a19 from "./ExchangeSelection/stories.js";
import * as a20 from "./ManageAccount/stories.js";
import * as a21 from "./Notifications/stories.js";
export default [
a1,
a4,
a5,
a6,
a7,
a8,
a10,
a11,
a12,
a13,
a14,
a15,
a16,
a17,
a18,
a19,
a20,
a21,
];
export * as a1 from "./Backup.stories.js";
export * as a4 from "./DepositPage/stories.js";
export * as a5 from "./ExchangeAddConfirm.stories.js";
export * as a6 from "./ExchangeAddSetUrl.stories.js";
export * as a7 from "./History.stories.js";
export * as a8 from "./AddBackupProvider/stories.js";
export * as a10 from "./ProviderDetail.stories.js";
export * as a11 from "./ReserveCreated.stories.js";
export * as a12 from "./Settings.stories.js";
export * as a13 from "./Transaction.stories.js";
export * as a14 from "./Welcome.stories.js";
export * as a15 from "./AddNewActionView.stories.js";
export * as a16 from "./DeveloperPage.stories.js";
export * as a17 from "./QrReader.stories.js";
export * as a18 from "./DestinationSelection/stories.js";
export * as a19 from "./ExchangeSelection/stories.js";
export * as a20 from "./ManageAccount/stories.js";
export * as a21 from "./Notifications/stories.js";

View File

@ -31,6 +31,9 @@
{
"path": "../taler-wallet-core/"
},
{
"path": "../web-util/"
},
{
"path": "../taler-util/"
}