From 51bbf08d241f0eff8847f6656accee03c0626d88 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Tue, 6 Dec 2022 11:28:56 -0300
Subject: implement web-utils in web-extension

---
 packages/taler-wallet-webextension/src/stories.tsx | 499 +--------------------
 1 file changed, 24 insertions(+), 475 deletions(-)

(limited to 'packages/taler-wallet-webextension/src/stories.tsx')

diff --git a/packages/taler-wallet-webextension/src/stories.tsx b/packages/taler-wallet-webextension/src/stories.tsx
index 02cc15393..8834b8084 100644
--- a/packages/taler-wallet-webextension/src/stories.tsx
+++ b/packages/taler-wallet-webextension/src/stories.tsx
@@ -18,516 +18,65 @@
  *
  * @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>
+function main(): void {
+  renderStories(
+    { popup, wallet, cta, mui, components },
+    {
+      strings,
+      getWrapperForGroup,
+    },
   );
 }
 
-/**
- * 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>
-  );
+if (document.readyState === "loading") {
+  document.addEventListener("DOMContentLoaded", main);
+} else {
+  main();
 }
-
 function getWrapperForGroup(group: string): FunctionComponent {
   switch (group) {
     case "popup":
       return function PopupWrapper({ children }: any) {
         return (
-          <PreventLinkNavigation>
+          <Fragment>
             <PopupNavBar />
             <PopupBox>{children}</PopupBox>
-          </PreventLinkNavigation>
+          </Fragment>
         );
       };
     case "wallet":
       return function WalletWrapper({ children }: any) {
         return (
-          <PreventLinkNavigation>
+          <Fragment>
             <LogoHeader />
             <WalletNavBar />
             <WalletBox>{children}</WalletBox>
-          </PreventLinkNavigation>
+          </Fragment>
         );
       };
     case "cta":
       return function WalletWrapper({ children }: any) {
         return (
-          <PreventLinkNavigation>
+          <Fragment>
             <WalletBox>{children}</WalletBox>
-          </PreventLinkNavigation>
+          </Fragment>
         );
       };
     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>
-  );
-}
-
-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);
-  };
-}
-
-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);
-}
-- 
cgit v1.2.3

