diff options
Diffstat (limited to 'packages')
4 files changed, 114 insertions, 4 deletions
| diff --git a/packages/anastasis-webui/src/components/FlieButton.tsx b/packages/anastasis-webui/src/components/FlieButton.tsx new file mode 100644 index 000000000..aab0b6170 --- /dev/null +++ b/packages/anastasis-webui/src/components/FlieButton.tsx @@ -0,0 +1,57 @@ +import { h, VNode } from "preact"; +import { useRef, useState } from "preact/hooks"; + +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; + +export interface FileTypeContent { +  content: string; +  type: string; +  name: string; +} + +interface Props { +  label: string; +  onChange: (v: FileTypeContent | undefined) => void; +} +export function FileButton(props: Props): VNode { +  const fileInputRef = useRef<HTMLInputElement>(null); +  const [sizeError, setSizeError] = useState(false); +  return ( +    <div> +      <button class="button" onClick={(e) => fileInputRef.current?.click()}> +        <span>{props.label}</span> +      </button> +      <input +        ref={fileInputRef} +        style={{ display: "none" }} +        type="file" +        onChange={(e) => { +          const f: FileList | null = e.currentTarget.files; +          if (!f || f.length != 1) { +            return props.onChange(undefined); +          } +          console.log(f); +          if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { +            setSizeError(true); +            return props.onChange(undefined); +          } +          setSizeError(false); +          return f[0].arrayBuffer().then((b) => { +            const content = new Uint8Array(b).reduce( +              (data, byte) => data + String.fromCharCode(byte), +              "", +            ); +            return props.onChange({ +              content, +              name: f[0].name, +              type: f[0].type, +            }); +          }); +        }} +      /> +      {sizeError && ( +        <p class="help is-danger">File should be smaller than 1 MB</p> +      )} +    </div> +  ); +} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index c73369dd6..6c8189fb9 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -36,6 +36,14 @@ export function Sidebar({ mobile }: Props): VNode {    const process = { env: { __VERSION__: "0.0.0" } };    const reducer = useAnastasisContext()!; +  function saveSession(): void { +    const state = reducer.exportState(); +    const link = document.createElement("a"); +    link.download = "anastasis.json"; +    link.href = `data:text/plain,${state}`; +    link.click(); +  } +    return (      <aside class="aside is-placed-left is-expanded">        {/* {mobile && <div class="footer" onClick={(e) => { return e.stopImmediatePropagation() }}> @@ -171,6 +179,16 @@ export function Sidebar({ mobile }: Props): VNode {                  <span class="menu-item-label"><Translate>Truth Paying</Translate></span>                </div>              </li> */} +              <li> +                <div class="buttons ml-4"> +                  <button +                    class="button is-primary is-right" +                    onClick={saveSession} +                  > +                    Save backup session +                  </button> +                </div> +              </li>              </Fragment>            ) : (              reducer.currentReducerState && @@ -250,6 +268,16 @@ export function Sidebar({ mobile }: Props): VNode {                      </span>                    </div>                  </li> +                <li> +                  <div class="buttons ml-4"> +                    <button +                      class="button is-primary is-right" +                      onClick={saveSession} +                    > +                      Save recovery session +                    </button> +                  </div> +                </li>                </Fragment>              )            )} diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index 1ef28a168..7b101baa0 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -107,6 +107,8 @@ export interface AnastasisReducerApi {    reset: () => void;    back: () => Promise<void>;    transition(action: string, args: any): Promise<void>; +  exportState: () => string; +  importState: (s: string) => void;    /**     * Run multiple reducer steps in a transaction without     * affecting the UI-visible transition state in-between. @@ -129,7 +131,7 @@ function storageSet(key: string, value: any): void {    }  } -function restoreState(): any { +function getStateFromStorage(): any {    let state: any;    try {      const s = storageGet("anastasisReducerState"); @@ -148,7 +150,7 @@ function restoreState(): any {  export function useAnastasisReducer(): AnastasisReducerApi {    const [anastasisState, setAnastasisStateInternal] = useState<AnastasisState>(      () => ({ -      reducerState: restoreState(), +      reducerState: getStateFromStorage(),        currentError: undefined,      }),    ); @@ -165,7 +167,7 @@ export function useAnastasisReducer(): AnastasisReducerApi {      setAnastasisStateInternal(newState);    }; -  async function doTransition(action: string, args: any) { +  async function doTransition(action: string, args: any): Promise<void> {      console.log("reducing with", action, args);      let s: ReducerState;      if (remoteReducer) { @@ -210,6 +212,18 @@ export function useAnastasisReducer(): AnastasisReducerApi {          });        }      }, +    exportState() { +      const state = getStateFromStorage() +      return JSON.stringify(state) +    }, +    importState(s: string) { +      try { +        const state = JSON.parse(s) +        setAnastasisState({ reducerState: state, currentError: undefined }) +      } catch (e) { +        throw Error('could not restore the state') +      } +    },      async startRecover() {        let s: ReducerState;        if (remoteReducer) { @@ -287,7 +301,7 @@ export function useAnastasisReducer(): AnastasisReducerApi {  }  class ReducerTxImpl implements ReducerTransactionHandle { -  constructor(public transactionState: ReducerState) {} +  constructor(public transactionState: ReducerState) { }    async transition(action: string, args: any): Promise<ReducerState> {      let s: ReducerState;      if (remoteReducer) { diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx index 8b24ef684..628ae4a34 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx @@ -1,4 +1,6 @@  import { h, VNode } from "preact"; +import { FileInput } from "../../components/fields/FileInput"; +import { FileButton } from "../../components/FlieButton";  import { useAnastasisContext } from "../../context/anastasis";  import { AnastasisClientFrame } from "./index"; @@ -34,6 +36,15 @@ export function StartScreen(): VNode {                <span>Recover a secret</span>              </button> +            <FileButton +              label="Restore a session" +              onChange={(content) => { +                if (content?.type === "application/json") { +                  reducer.importState(content.content); +                } +              }} +            /> +              {/* <button class="button">                <div class="icon"><i class="mdi mdi-file" /></div>                <span>Restore a session</span> | 
