diff --git a/package.json b/package.json index 5113888..f58e626 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,6 @@ "webpack": "^5.37.1", "webpack-cli": "^4.7.0", "webpack-dev-server": "^3.11.2", - "z-makeelement": "^1.0.3", "z-pagerouter": "^1.0.2" } } diff --git a/src/elements/CopyableInputBox.tsx b/src/elements/CopyableInputBox.tsx index 346f115..a71547b 100644 --- a/src/elements/CopyableInputBox.tsx +++ b/src/elements/CopyableInputBox.tsx @@ -1,5 +1,5 @@ import { Component, JSX, createRef } from "preact"; -import { MarginInline } from "./ReactMarginInline"; +import { MarginInline } from "./MarginInline"; import { addClipboardNotifications } from "../pageUtils"; import ClipboardJS from "clipboard"; import i18next from "i18next"; diff --git a/src/elements/Form.ts b/src/elements/Form.ts deleted file mode 100644 index 8f0c32c..0000000 --- a/src/elements/Form.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable */ -import { ElementInfo, makeElement } from "z-makeelement"; -/* eslint-enable */ - -export function Form( - children: Element[], - submit: (form: HTMLFormElement) => unknown, - options: Partial = {}, -): HTMLFormElement { - const form = makeElement({ - tag: "form", - children: children, - ...options, - }) as HTMLFormElement; - - form.addEventListener("submit", (e: Event) => { - e.preventDefault(); - submit(form); - }); - - return form; -} diff --git a/src/elements/ReactForm.tsx b/src/elements/Form.tsx similarity index 100% rename from src/elements/ReactForm.tsx rename to src/elements/Form.tsx diff --git a/src/elements/InputWithTitle.tsx b/src/elements/InputWithTitle.tsx index 5bcda4a..87ca382 100644 --- a/src/elements/InputWithTitle.tsx +++ b/src/elements/InputWithTitle.tsx @@ -1,5 +1,5 @@ import { JSX } from "preact"; -import { Margin } from "./ReactMargin"; +import { Margin } from "./Margin"; export type InputWithTitleProps = { title: string; diff --git a/src/elements/Margin.ts b/src/elements/Margin.ts deleted file mode 100644 index 5f8629d..0000000 --- a/src/elements/Margin.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { makeElement } from "z-makeelement"; - -export function Margin(children: Element | Element[]): Element { - return makeElement({ - tag: "div", - class: "uk-margin", - children: children, - }); -} diff --git a/src/elements/ReactMargin.tsx b/src/elements/Margin.tsx similarity index 100% rename from src/elements/ReactMargin.tsx rename to src/elements/Margin.tsx diff --git a/src/elements/MarginInline.ts b/src/elements/MarginInline.ts deleted file mode 100644 index 644d55f..0000000 --- a/src/elements/MarginInline.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { makeElement } from "z-makeelement"; - -export function MarginInline(children: Element | Element[]): Element { - return makeElement({ - tag: "div", - class: "uk-margin", - children: makeElement({ - tag: "div", - class: "uk-inline", - children: children, - }), - }); -} diff --git a/src/elements/ReactMarginInline.tsx b/src/elements/MarginInline.tsx similarity index 100% rename from src/elements/ReactMarginInline.tsx rename to src/elements/MarginInline.tsx diff --git a/src/elements/QRScanner.ts b/src/elements/QRScanner.ts deleted file mode 100644 index 0426de8..0000000 --- a/src/elements/QRScanner.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Margin } from "./Margin"; -import { makeElement } from "z-makeelement"; -import QrScanner from "qr-scanner"; - -/* eslint-disable import/no-unresolved */ -// @ts-ignore -import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js"; -QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource])); - -export interface QRScannerType extends HTMLElement { - deinit(): void; -} - -export async function QRScanner(onScan: (code: string) => void): Promise { - const webcamVideo = makeElement({ - tag: "video", - }) as HTMLVideoElement; - - const QRInput = makeElement({ - tag: "div", - children: [Margin(webcamVideo)], - }) as QRScannerType; - - const stream = await navigator.mediaDevices.getUserMedia({ - video: { - facingMode: "environment", - }, - audio: false, - }); - webcamVideo.srcObject = stream; - const lastSeenValue = ""; - const qrScanner = new QrScanner(webcamVideo, function (value) { - if (lastSeenValue == value) return; - onScan(value); - }); - await qrScanner.start(); - - QRInput.deinit = () => { - try { - stream.getTracks().forEach(function (track) { - track.stop(); - }); - } catch (_) { - () => {}; - } - }; - - return QRInput; -} diff --git a/src/elements/QRScanner.tsx b/src/elements/QRScanner.tsx new file mode 100644 index 0000000..9611794 --- /dev/null +++ b/src/elements/QRScanner.tsx @@ -0,0 +1,58 @@ +/* eslint-disable import/no-unresolved */ +// @ts-ignore +import qrScannerWorkerSource from "!!raw-loader!qr-scanner/qr-scanner-worker.min.js"; +QrScanner.WORKER_PATH = URL.createObjectURL(new Blob([qrScannerWorkerSource])); +// end ignore + +import { Component, JSX, createRef } from "preact"; +import { Margin } from "./Margin"; +import QrScanner from "qr-scanner"; + +export type QRScannerProps = { + onScan: (code: string) => void; +}; + +export class QRScanner extends Component { + videoElement = createRef(); + + stream: MediaStream; + + componentDidMount(): void { + void navigator.mediaDevices + .getUserMedia({ + video: { + facingMode: "environment", + }, + audio: false, + }) + .then((stream) => { + this.stream = stream; + this.videoElement.current.srcObject = stream; + const lastSeenValue = ""; + const qrScanner = new QrScanner(this.videoElement.current, (value) => { + if (lastSeenValue == value) return; + this.props.onScan(value); + }); + void qrScanner.start(); + }); + } + componentWillUnmount(): void { + try { + this.stream.getTracks().forEach(function (track) { + track.stop(); + }); + } catch (_) { + () => {}; + } + } + + render(): JSX.Element { + return ( +
+ + + +
+ ); + } +} diff --git a/src/elements/ReactTile.tsx b/src/elements/Tile.tsx similarity index 100% rename from src/elements/ReactTile.tsx rename to src/elements/Tile.tsx diff --git a/src/pages/Access/AccessHome.tsx b/src/pages/Access/AccessHome.tsx index 46fe9e6..44920de 100644 --- a/src/pages/Access/AccessHome.tsx +++ b/src/pages/Access/AccessHome.tsx @@ -1,5 +1,5 @@ import { Page } from "../../types/Page"; -import { Tile } from "../../elements/ReactTile"; +import { Tile } from "../../elements/Tile"; import { notImplemented, prePageChecks } from "../../pageUtils"; import { render } from "preact"; import i18next from "i18next"; diff --git a/src/pages/Access/Auth/userpass/UserPassUserEdit.tsx b/src/pages/Access/Auth/userpass/UserPassUserEdit.tsx index 64028d0..3fbb9f5 100644 --- a/src/pages/Access/Auth/userpass/UserPassUserEdit.tsx +++ b/src/pages/Access/Auth/userpass/UserPassUserEdit.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../../elements/ReactForm"; +import { Form } from "../../../../elements/Form"; import { InputWithTitle } from "../../../../elements/InputWithTitle"; -import { MarginInline } from "../../../../elements/ReactMarginInline"; +import { MarginInline } from "../../../../elements/MarginInline"; import { Page } from "../../../../types/Page"; import { UserType } from "../../../../api/types/userpass/user"; import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser"; diff --git a/src/pages/Access/Auth/userpass/UserPassUserNew.tsx b/src/pages/Access/Auth/userpass/UserPassUserNew.tsx index e2b6003..9b1f8bf 100644 --- a/src/pages/Access/Auth/userpass/UserPassUserNew.tsx +++ b/src/pages/Access/Auth/userpass/UserPassUserNew.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../../elements/ReactForm"; -import { Margin } from "../../../../elements/ReactMargin"; -import { MarginInline } from "../../../../elements/ReactMarginInline"; +import { Form } from "../../../../elements/Form"; +import { Margin } from "../../../../elements/Margin"; +import { MarginInline } from "../../../../elements/MarginInline"; import { Page } from "../../../../types/Page"; import { UserType } from "../../../../api/types/userpass/user"; import { createOrUpdateUserPassUser } from "../../../../api/auth/userpass/createOrUpdateUserPassUser"; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 95b14cc..74ecff8 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,5 +1,5 @@ import { Page } from "../types/Page"; -import { Tile } from "../elements/ReactTile"; +import { Tile } from "../elements/Tile"; import { TokenInfo } from "../api/types/token"; import { lookupSelf } from "../api/sys/lookupSelf"; import { prePageChecks, setErrorText } from "../pageUtils"; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 627a00f..4bf5309 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,7 +1,7 @@ import { Component, JSX, render } from "preact"; -import { Form } from "../elements/ReactForm"; -import { Margin } from "../elements/ReactMargin"; -import { MarginInline } from "../elements/ReactMarginInline"; +import { Form } from "../elements/Form"; +import { Margin } from "../elements/Margin"; +import { MarginInline } from "../elements/MarginInline"; import { Page } from "../types/Page"; import { lookupSelf } from "../api/sys/lookupSelf"; import { setErrorText } from "../pageUtils"; diff --git a/src/pages/PwGen.tsx b/src/pages/PwGen.tsx index 97ff994..f0fa980 100644 --- a/src/pages/PwGen.tsx +++ b/src/pages/PwGen.tsx @@ -1,7 +1,7 @@ import { Component, JSX, createRef, render } from "preact"; import { CopyableInputBox } from "../elements/CopyableInputBox"; -import { Form } from "../elements/ReactForm"; -import { Margin } from "../elements/ReactMargin"; +import { Form } from "../elements/Form"; +import { Margin } from "../elements/Margin"; import { Page } from "../types/Page"; import i18next from "i18next"; diff --git a/src/pages/Secrets/KeyValue/KeyValueNew.tsx b/src/pages/Secrets/KeyValue/KeyValueNew.tsx index 42d0a31..9eb6e49 100644 --- a/src/pages/Secrets/KeyValue/KeyValueNew.tsx +++ b/src/pages/Secrets/KeyValue/KeyValueNew.tsx @@ -1,5 +1,5 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { createOrUpdateSecret } from "../../../api/kv/createOrUpdateSecret"; diff --git a/src/pages/Secrets/NewEngines/NewKVEngine.tsx b/src/pages/Secrets/NewEngines/NewKVEngine.tsx index e75ffc8..ccbcb0a 100644 --- a/src/pages/Secrets/NewEngines/NewKVEngine.tsx +++ b/src/pages/Secrets/NewEngines/NewKVEngine.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { newMount } from "../../../api/sys/newMount"; import { render } from "preact"; diff --git a/src/pages/Secrets/NewEngines/NewTOTPEngine.tsx b/src/pages/Secrets/NewEngines/NewTOTPEngine.tsx index 70c25dd..bc2e553 100644 --- a/src/pages/Secrets/NewEngines/NewTOTPEngine.tsx +++ b/src/pages/Secrets/NewEngines/NewTOTPEngine.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { newMount } from "../../../api/sys/newMount"; import { render } from "preact"; diff --git a/src/pages/Secrets/NewEngines/NewTransitEngine.tsx b/src/pages/Secrets/NewEngines/NewTransitEngine.tsx index 464e898..cd24159 100644 --- a/src/pages/Secrets/NewEngines/NewTransitEngine.tsx +++ b/src/pages/Secrets/NewEngines/NewTransitEngine.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { newMount } from "../../../api/sys/newMount"; import { render } from "preact"; diff --git a/src/pages/Secrets/NewSecretsEngine.tsx b/src/pages/Secrets/NewSecretsEngine.tsx index 79d87b1..a898aec 100644 --- a/src/pages/Secrets/NewSecretsEngine.tsx +++ b/src/pages/Secrets/NewSecretsEngine.tsx @@ -1,5 +1,5 @@ import { Page } from "../../types/Page"; -import { Tile } from "../../elements/ReactTile"; +import { Tile } from "../../elements/Tile"; import { render } from "preact"; import i18next from "i18next"; diff --git a/src/pages/Secrets/TOTP/TOTPNew.tsx b/src/pages/Secrets/TOTP/TOTPNew.tsx index c71cad2..4c567d6 100644 --- a/src/pages/Secrets/TOTP/TOTPNew.tsx +++ b/src/pages/Secrets/TOTP/TOTPNew.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { addNewTOTP } from "../../../api/totp/addNewTOTP"; diff --git a/src/pages/Secrets/TOTP/TOTPView.tsx b/src/pages/Secrets/TOTP/TOTPView.tsx index 7cf3e6f..3f43a02 100644 --- a/src/pages/Secrets/TOTP/TOTPView.tsx +++ b/src/pages/Secrets/TOTP/TOTPView.tsx @@ -1,7 +1,7 @@ import { Component, JSX, render } from "preact"; import { CopyableInputBox } from "../../../elements/CopyableInputBox"; import { DoesNotExistError } from "../../../types/internalErrors"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { PageRouter } from "z-pagerouter"; import { PageState } from "../../../PageState"; diff --git a/src/pages/Secrets/Transit/NewTransitKey.tsx b/src/pages/Secrets/Transit/NewTransitKey.tsx index b4a7690..ac243c0 100644 --- a/src/pages/Secrets/Transit/NewTransitKey.tsx +++ b/src/pages/Secrets/Transit/NewTransitKey.tsx @@ -1,6 +1,6 @@ -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; -import { MarginInline } from "../../../elements/ReactMarginInline"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; +import { MarginInline } from "../../../elements/MarginInline"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { newTransitKey } from "../../../api/transit/newTransitKey"; diff --git a/src/pages/Secrets/Transit/TransitDecrypt.tsx b/src/pages/Secrets/Transit/TransitDecrypt.tsx index ce64305..f137b83 100644 --- a/src/pages/Secrets/Transit/TransitDecrypt.tsx +++ b/src/pages/Secrets/Transit/TransitDecrypt.tsx @@ -1,8 +1,8 @@ import { CopyableModal } from "../../../elements/CopyableModal"; import { FileUploadInput } from "../../../elements/FileUploadInput"; -import { Form } from "../../../elements/ReactForm"; +import { Form } from "../../../elements/Form"; import { InputWithTitle } from "../../../elements/InputWithTitle"; -import { Margin } from "../../../elements/ReactMargin"; +import { Margin } from "../../../elements/Margin"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { fileToBase64 } from "../../../htmlUtils"; diff --git a/src/pages/Secrets/Transit/TransitEncrypt.tsx b/src/pages/Secrets/Transit/TransitEncrypt.tsx index 3527313..ed095f4 100644 --- a/src/pages/Secrets/Transit/TransitEncrypt.tsx +++ b/src/pages/Secrets/Transit/TransitEncrypt.tsx @@ -1,8 +1,8 @@ import { CopyableModal } from "../../../elements/CopyableModal"; import { FileUploadInput } from "../../../elements/FileUploadInput"; -import { Form } from "../../../elements/ReactForm"; +import { Form } from "../../../elements/Form"; import { InputWithTitle } from "../../../elements/InputWithTitle"; -import { Margin } from "../../../elements/ReactMargin"; +import { Margin } from "../../../elements/Margin"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { fileToBase64 } from "../../../htmlUtils"; diff --git a/src/pages/Secrets/Transit/TransitRewrap.tsx b/src/pages/Secrets/Transit/TransitRewrap.tsx index a585e3e..e498eec 100644 --- a/src/pages/Secrets/Transit/TransitRewrap.tsx +++ b/src/pages/Secrets/Transit/TransitRewrap.tsx @@ -1,6 +1,6 @@ import { CopyableModal } from "../../../elements/CopyableModal"; -import { Form } from "../../../elements/ReactForm"; -import { Margin } from "../../../elements/ReactMargin"; +import { Form } from "../../../elements/Form"; +import { Margin } from "../../../elements/Margin"; import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { getTransitKey } from "../../../api/transit/getTransitKey"; diff --git a/src/pages/Secrets/Transit/TransitViewSecret.tsx b/src/pages/Secrets/Transit/TransitViewSecret.tsx index debaa9c..d5ff550 100644 --- a/src/pages/Secrets/Transit/TransitViewSecret.tsx +++ b/src/pages/Secrets/Transit/TransitViewSecret.tsx @@ -1,6 +1,6 @@ import { Page } from "../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; -import { Tile } from "../../../elements/ReactTile"; +import { Tile } from "../../../elements/Tile"; import { getTransitKey } from "../../../api/transit/getTransitKey"; import { render } from "preact"; import i18next from "i18next"; diff --git a/src/pages/SetLanguage.tsx b/src/pages/SetLanguage.tsx index 468f57f..0746294 100644 --- a/src/pages/SetLanguage.tsx +++ b/src/pages/SetLanguage.tsx @@ -2,9 +2,9 @@ import translations from "../translations/index.mjs"; // ts-unignore -import { Form } from "../elements/ReactForm"; -import { Margin } from "../elements/ReactMargin"; -import { MarginInline } from "../elements/ReactMarginInline"; +import { Form } from "../elements/Form"; +import { Margin } from "../elements/Margin"; +import { MarginInline } from "../elements/MarginInline"; import { Page } from "../types/Page"; import { reloadNavBar } from "../elements/NavBar"; import { render } from "preact"; diff --git a/src/pages/SetVaultURL.tsx b/src/pages/SetVaultURL.tsx index 6384910..822408a 100644 --- a/src/pages/SetVaultURL.tsx +++ b/src/pages/SetVaultURL.tsx @@ -1,5 +1,5 @@ -import { Form } from "../elements/ReactForm"; -import { Margin } from "../elements/ReactMargin"; +import { Form } from "../elements/Form"; +import { Margin } from "../elements/Margin"; import { Page } from "../types/Page"; import { render } from "preact"; diff --git a/src/pages/Unseal.ts b/src/pages/Unseal.ts deleted file mode 100644 index a94a842..0000000 --- a/src/pages/Unseal.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { Form } from "../elements/Form"; -import { MarginInline } from "../elements/MarginInline"; -import { Page } from "../types/Page"; -import { QRScanner, QRScannerType } from "../elements/QRScanner"; -import { SealStatusType, getSealStatus } from "../api/sys/getSealStatus"; -import { makeElement } from "z-makeelement"; -import { setErrorText } from "../pageUtils"; -import { submitUnsealKey } from "../api/sys/submitUnsealKey"; -import i18next from "i18next"; - -const UnsealInputModes = { - FORM_INPUT: "FORM_INPUT", - QR_INPUT: "QR_INPUT", -}; - -export class UnsealPage extends Page { - constructor() { - super(); - this.mode = UnsealInputModes.FORM_INPUT; - } - - mode: string; - refresher: number; - qrScanner: QRScannerType; - unsealProgress: HTMLProgressElement; - unsealProgressText: HTMLParagraphElement; - unsealInputContent: HTMLElement; - unsealKeyForm: HTMLFormElement; - - async cleanup(): Promise { - this.deinitWebcam(); - clearInterval(this.refresher); - } - - deinitWebcam(): void { - try { - this.qrScanner.deinit(); - } catch (_) { - // Do Nothing - } - } - - makeRefresher(): void { - const id = setInterval(async () => { - await this.doRefresh(); - return; - }, 1000); - this.refresher = id as unknown as number; - } - - async doRefresh(): Promise { - const status = await getSealStatus(); - await this.updateSealProgress(status); - } - - async render(): Promise { - this.unsealProgress = makeElement({ - tag: "progress", - class: "uk-progress", - attributes: { value: "0", max: "0" }, - }) as HTMLProgressElement; - this.unsealProgressText = makeElement({ - tag: "p", - text: i18next.t("unseal_keys_progress", { - progress: "0", - keys_needed: "0", - }), - }) as HTMLParagraphElement; - this.unsealInputContent = makeElement({ - tag: "div", - }); - await this.router.setPageContent( - makeElement({ - tag: "div", - children: [ - this.unsealProgress, - makeElement({ - tag: "p", - id: "errorText", - class: ["uk-text-danger", "uk-margin-top"], - }), - this.unsealProgressText, - this.unsealInputContent, - ], - }), - ); - await this.switchInputMode(this.mode); - await this.updateSealProgress(await getSealStatus()); - this.makeRefresher(); - } - - setButtons(method: string): void { - const newMethod: string = - method == UnsealInputModes.FORM_INPUT - ? UnsealInputModes.QR_INPUT - : UnsealInputModes.FORM_INPUT; - const buttonText: string = - newMethod == UnsealInputModes.FORM_INPUT - ? i18next.t("unseal_input_btn") - : i18next.t("unseal_qr_btn"); - this.unsealInputContent.appendChild( - makeElement({ - tag: "button", - class: ["uk-button", "uk-button-primary"], - text: buttonText, - onclick: async () => { - await this.switchInputMode(newMethod); - }, - }), - ); - } - - async switchInputMode(method: string): Promise { - this.deinitWebcam(); - this.unsealInputContent.querySelectorAll("*").forEach((n) => n.remove()); - if (method == UnsealInputModes.FORM_INPUT) this.makeUnsealForm(); - if (method == UnsealInputModes.QR_INPUT) await this.makeQRInput(); - this.setButtons(method); - } - - makeUnsealForm(): void { - this.unsealKeyForm = Form( - [ - MarginInline( - makeElement({ - tag: "input", - class: ["uk-input", "uk-form-width-medium"], - attributes: { - required: "true", - type: "password", - placeholder: i18next.t("key_input_placeholder"), - name: "key", - }, - }), - ), - MarginInline( - makeElement({ - tag: "button", - class: ["uk-button", "uk-button-primary"], - text: i18next.t("submit_key_btn"), - }), - ), - ], - async (_) => { - await this.handleKeySubmit(); - }, - ); - this.unsealInputContent.appendChild(this.unsealKeyForm); - } - - async makeQRInput(): Promise { - this.qrScanner = await QRScanner(async (code: string) => { - await this.submitKey(code); - }); - this.unsealInputContent.appendChild(this.qrScanner); - } - - async updateSealProgress(data: SealStatusType): Promise { - const progress = data.progress; - const keysNeeded = data.t; - const text = this.unsealProgressText; - text.innerText = i18next.t("unseal_keys_progress", { - progress: String(progress), - keys_needed: String(keysNeeded), - }); - const progressBar = this.unsealProgress; - progressBar.value = progress; - progressBar.max = keysNeeded; - if (!data.sealed) { - progressBar.value = keysNeeded; - await this.router.changePage("HOME"); - } - } - - async submitKey(key: string): Promise { - try { - await submitUnsealKey(key); - await this.updateSealProgress(await getSealStatus()); - } catch (e: unknown) { - const error = e as Error; - setErrorText(error.message); - } - } - - async handleKeySubmit(): Promise { - const formData = new FormData(this.unsealKeyForm); - - await this.submitKey(formData.get("key") as string); - } - get name(): string { - return i18next.t("unseal_vault_text"); - } -} diff --git a/src/pages/Unseal.tsx b/src/pages/Unseal.tsx new file mode 100644 index 0000000..31dbc0b --- /dev/null +++ b/src/pages/Unseal.tsx @@ -0,0 +1,157 @@ +import { Component, JSX } from "preact"; +import { Form } from "../elements/Form"; +import { MarginInline } from "../elements/MarginInline"; +import { Page } from "../types/Page"; +import { QRScanner } from "../elements/QRScanner"; +import { getSealStatus } from "../api/sys/getSealStatus"; +import { render } from "preact/compat"; +import { setErrorText } from "../pageUtils"; +import { submitUnsealKey } from "../api/sys/submitUnsealKey"; +import { toStr } from "../utils"; +import i18next from "i18next"; + +const UnsealInputModes = { + FORM_INPUT: "FORM_INPUT", + QR_INPUT: "QR_INPUT", +}; + +export type UnsealFormInputProps = { + onSubmit: (code: string) => void; +}; + +export function UnsealFormInput(props: UnsealFormInputProps): JSX.Element { + return ( +
{ + props.onSubmit(data.get("unsealKey") as string); + }} + > + + + + + + +
+ ); +} + +type UnsealPageState = { + mode: string; + keys_submitted: number; + keys_needed: number; +}; + +export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState> { + constructor() { + super(); + this.state = { + mode: UnsealInputModes.FORM_INPUT, + keys_submitted: 0, + keys_needed: 0, + }; + } + + timer: number; + + async submitKey(key: string): Promise { + try { + await submitUnsealKey(key); + this.updateStateWithSealStatus(); + } catch (e: unknown) { + const error = e as Error; + setErrorText(error.message); + } + } + + updateStateWithSealStatus(): void { + void getSealStatus().then((data) => { + this.setState({ + keys_submitted: data.progress, + keys_needed: data.t, + }); + if (!data.sealed) { + void this.props.page.router.changePage("HOME"); + } + }); + } + + componentWillUnmount(): void { + clearInterval(this.timer); + } + + componentDidMount(): void { + this.updateStateWithSealStatus(); + this.timer = setInterval(() => { + this.updateStateWithSealStatus(); + }, 500) as unknown as number; + } + + render(): JSX.Element { + return ( +
+ + +

+ +

+ {i18next.t("unseal_keys_progress", { + progress: toStr(this.state.keys_submitted), + keys_needed: toStr(this.state.keys_needed), + })} +

+ + {this.state.mode == UnsealInputModes.FORM_INPUT && ( + this.submitKey(code)} /> + )} + + {this.state.mode == UnsealInputModes.QR_INPUT && ( + this.submitKey(code)} /> + )} + + +
+ ); + } +} + +export class UnsealPage extends Page { + constructor() { + super(); + } + + async render(): Promise { + render(, this.router.pageContentElement); + } + + get name(): string { + return i18next.t("unseal_vault_text"); + } +}