diff options
author | Sebastian <sebasjm@gmail.com> | 2022-06-05 23:10:51 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-06-05 23:11:38 -0300 |
commit | abb47b60ad6aa82f68c88c10b0fa614785cd123c (patch) | |
tree | 79fa39379ece376dd106c3865ca4b2f8531dc30d /packages/anastasis-webui/src/stories.tsx | |
parent | fee5de75624a1d8e42ac695876ab188ab2d92921 (diff) | |
download | wallet-core-abb47b60ad6aa82f68c88c10b0fa614785cd123c.tar.xz |
dev script without storybook
Diffstat (limited to 'packages/anastasis-webui/src/stories.tsx')
-rw-r--r-- | packages/anastasis-webui/src/stories.tsx | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/packages/anastasis-webui/src/stories.tsx b/packages/anastasis-webui/src/stories.tsx new file mode 100644 index 000000000..2b830766f --- /dev/null +++ b/packages/anastasis-webui/src/stories.tsx @@ -0,0 +1,381 @@ +/* + This file is part of GNU Taler + (C) 2021 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 (sebasjm) + */ +import { setupI18n } from "@gnu-taler/taler-util"; +import { ComponentChild, Fragment, h, render, VNode } from "preact"; +import { useEffect, useErrorBoundary, useState } from "preact/hooks"; +import { strings } from "./i18n/strings.js"; +import * as pages from "./pages/home/index.storiesNo.js"; + +const url = new URL(window.location.href); +const lang = url.searchParams.get("lang") || "en"; + +setupI18n(lang, strings); + +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): ComponentItem { + const component = 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 { + return (a?.order ?? 0) - (b?.order ?? 0); +} + +const allExamples = Object.entries({ pages }).map(([title, value]) => ({ + title, + list: value.default + .map((s) => parseExampleImport(title, s)) + .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)}`; + 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); + }} + > + {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> + ); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", main); +} else { + main(); +} +function main(): void { + try { + const container = document.getElementById("container"); + if (!container) { + throw Error("container not found, can't mount page contents"); + } + render(<Application />, container); + } catch (e) { + console.error("got error", e); + if (e instanceof Error) { + document.body.innerText = `Fatal error: "${e.message}". Please report this bug at https://bugs.gnunet.org/.`; + } + } +} + +let liveReloadMounted = false; +function LiveReload({ port = 8002 }: { port?: number }): VNode { + const [isReloading, setIsReloading] = useState(false); + useEffect(() => { + if (!liveReloadMounted) { + setupLiveReload(port, () => { + setIsReloading(true); + window.location.reload(); + }); + liveReloadMounted = true; + } + }); + + if (isReloading) { + return ( + <div + style={{ + position: "absolute", + width: "100%", + height: "100%", + backgroundColor: "rgba(0,0,0,0.5)", + color: "white", + display: "flex", + justifyContent: "center", + }} + > + <h1 style={{ margin: "auto" }}>reloading...</h1> + </div> + ); + } + return <Fragment />; +} + +function setupLiveReload(port: number, onReload: () => void): void { + const 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); + }; +} |