diff options
author | Sebastian <sebasjm@gmail.com> | 2021-11-10 10:20:52 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-11-10 11:57:11 -0300 |
commit | a62deeef5d0cbe5fa98be390eac0e03bcae0f0b5 (patch) | |
tree | b7e5f4944b3c19bcdb267a95701f1b9ad6fdac16 /packages/anastasis-webui/src/components | |
parent | e03b0d1b9b60dbafe6b70db3bd07158cd65773e5 (diff) | |
download | wallet-core-a62deeef5d0cbe5fa98be390eac0e03bcae0f0b5.tar.xz |
prettier
Diffstat (limited to 'packages/anastasis-webui/src/components')
15 files changed, 895 insertions, 588 deletions
diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx index 92bef2219..33f3a7258 100644 --- a/packages/anastasis-webui/src/components/AsyncButton.tsx +++ b/packages/anastasis-webui/src/components/AsyncButton.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { ComponentChildren, h, VNode } from "preact"; // import { LoadingModal } from "../modal"; @@ -31,19 +31,26 @@ type Props = { [rest: string]: any; }; -export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VNode { +export function AsyncButton({ + onClick, + disabled, + children, + ...rest +}: Props): VNode { const { isLoading, request } = useAsync(onClick); // if (isSlow) { // return <LoadingModal onCancel={cancel} />; // } - if (isLoading) { + if (isLoading) { return <button class="button">Loading...</button>; } - return <span data-tooltip={rest['data-tooltip']} style={{marginLeft: 5}}> - <button {...rest} onClick={request} disabled={disabled}> - {children} - </button> - </span>; + return ( + <span data-tooltip={rest["data-tooltip"]} style={{ marginLeft: 5 }}> + <button {...rest} onClick={request} disabled={disabled}> + {children} + </button> + </span> + ); } diff --git a/packages/anastasis-webui/src/components/Notifications.tsx b/packages/anastasis-webui/src/components/Notifications.tsx index 097ebb4de..e34550386 100644 --- a/packages/anastasis-webui/src/components/Notifications.tsx +++ b/packages/anastasis-webui/src/components/Notifications.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; @@ -27,7 +27,7 @@ export interface Notification { type: MessageType; } -export type MessageType = 'INFO' | 'WARN' | 'ERROR' | 'SUCCESS' +export type MessageType = "INFO" | "WARN" | "ERROR" | "SUCCESS"; interface Props { notifications: Notification[]; @@ -36,24 +36,39 @@ interface Props { function messageStyle(type: MessageType): string { switch (type) { - case "INFO": return "message is-info"; - case "WARN": return "message is-warning"; - case "ERROR": return "message is-danger"; - case "SUCCESS": return "message is-success"; - default: return "message" + case "INFO": + return "message is-info"; + case "WARN": + return "message is-warning"; + case "ERROR": + return "message is-danger"; + case "SUCCESS": + return "message is-success"; + default: + return "message"; } } -export function Notifications({ notifications, removeNotification }: Props): VNode { - return <div class="block"> - {notifications.map((n, i) => <article key={i} class={messageStyle(n.type)}> - <div class="message-header"> - <p>{n.message}</p> - {removeNotification && <button class="delete" onClick={() => removeNotification && removeNotification(n)} />} - </div> - {n.description && <div class="message-body"> - {n.description} - </div>} - </article>)} - </div> -}
\ No newline at end of file +export function Notifications({ + notifications, + removeNotification, +}: Props): VNode { + return ( + <div class="block"> + {notifications.map((n, i) => ( + <article key={i} class={messageStyle(n.type)}> + <div class="message-header"> + <p>{n.message}</p> + {removeNotification && ( + <button + class="delete" + onClick={() => removeNotification && removeNotification(n)} + /> + )} + </div> + {n.description && <div class="message-body">{n.description}</div>} + </article> + ))} + </div> + ); +} diff --git a/packages/anastasis-webui/src/components/QR.tsx b/packages/anastasis-webui/src/components/QR.tsx index 48f1a7c12..9a05f6097 100644 --- a/packages/anastasis-webui/src/components/QR.tsx +++ b/packages/anastasis-webui/src/components/QR.tsx @@ -21,15 +21,28 @@ import qrcode from "qrcode-generator"; export function QR({ text }: { text: string }): VNode { const divRef = useRef<HTMLDivElement>(null); useEffect(() => { - const qr = qrcode(0, 'L'); + const qr = qrcode(0, "L"); qr.addData(text); qr.make(); - if (divRef.current) divRef.current.innerHTML = qr.createSvgTag({ - scalable: true, - }); + if (divRef.current) + divRef.current.innerHTML = qr.createSvgTag({ + scalable: true, + }); }); - return <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> - <div style={{ width: '50%', minWidth: 200, maxWidth: 300 }} ref={divRef} /> - </div>; + return ( + <div + style={{ + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > + <div + style={{ width: "50%", minWidth: 200, maxWidth: 300 }} + ref={divRef} + /> + </div> + ); } diff --git a/packages/anastasis-webui/src/components/app.tsx b/packages/anastasis-webui/src/components/app.tsx index c6b4cfc14..4c6683c0c 100644 --- a/packages/anastasis-webui/src/components/app.tsx +++ b/packages/anastasis-webui/src/components/app.tsx @@ -1,6 +1,5 @@ import { FunctionalComponent, h } from "preact"; import { TranslationProvider } from "../context/translation"; - import AnastasisClient from "../pages/home"; const App: FunctionalComponent = () => { diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index 3148c953f..0b6a7e316 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -19,56 +19,66 @@ export function DateInput(props: DateInputProps): VNode { inputRef.current?.focus(); } }, [props.grabFocus]); - const [opened, setOpened] = useState(false) + const [opened, setOpened] = useState(false); const value = props.bind[0] || ""; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; - const calendar = subYears(new Date(), 30) - - return <div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <div class="field has-addons"> - <p class="control"> - <input - type="text" - class={showError ? 'input is-danger' : 'input'} - value={value} - onInput={(e) => { - const text = e.currentTarget.value - setDirty(true) - props.bind[1](text); - }} - ref={inputRef} /> - </p> - <p class="control"> - <a class="button" onClick={() => { setOpened(true) }}> - <span class="icon"><i class="mdi mdi-calendar" /></span> - </a> - </p> + const calendar = subYears(new Date(), 30); + + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <div class="field has-addons"> + <p class="control"> + <input + type="text" + class={showError ? "input is-danger" : "input"} + value={value} + onInput={(e) => { + const text = e.currentTarget.value; + setDirty(true); + props.bind[1](text); + }} + ref={inputRef} + /> + </p> + <p class="control"> + <a + class="button" + onClick={() => { + setOpened(true); + }} + > + <span class="icon"> + <i class="mdi mdi-calendar" /> + </span> + </a> + </p> + </div> </div> + <p class="help">Using the format yyyy-mm-dd</p> + {showError && <p class="help is-danger">{props.error}</p>} + <DatePicker + opened={opened} + initialDate={calendar} + years={props.years} + closeFunction={() => setOpened(false)} + dateReceiver={(d) => { + setDirty(true); + const v = format(d, "yyyy-MM-dd"); + props.bind[1](v); + }} + /> </div> - <p class="help">Using the format yyyy-mm-dd</p> - {showError && <p class="help is-danger">{props.error}</p>} - <DatePicker - opened={opened} - initialDate={calendar} - years={props.years} - closeFunction={() => setOpened(false)} - dateReceiver={(d) => { - setDirty(true) - const v = format(d, 'yyyy-MM-dd') - props.bind[1](v); - }} - /> - </div> - ; - + ); } diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx index e21418fea..fe676f284 100644 --- a/packages/anastasis-webui/src/components/fields/EmailInput.tsx +++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx @@ -18,27 +18,34 @@ export function EmailInput(props: TextInputProps): VNode { } }, [props.grabFocus]); const value = props.bind[0]; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error - return (<div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control has-icons-right"> - <input - value={value} - required - placeholder={props.placeholder} - type="email" - class={showError ? 'input is-danger' : 'input'} - onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} - ref={inputRef} - style={{ display: "block" }} /> + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control has-icons-right"> + <input + value={value} + required + placeholder={props.placeholder} + type="email" + class={showError ? "input is-danger" : "input"} + onInput={(e) => { + setDirty(true); + props.bind[1]((e.target as HTMLInputElement).value); + }} + ref={inputRef} + style={{ display: "block" }} + /> + </div> + {showError && <p class="help is-danger">{props.error}</p>} </div> - {showError && <p class="help is-danger">{props.error}</p>} - </div> ); } diff --git a/packages/anastasis-webui/src/components/fields/FileInput.tsx b/packages/anastasis-webui/src/components/fields/FileInput.tsx index 8b144ea43..52d6eab4a 100644 --- a/packages/anastasis-webui/src/components/fields/FileInput.tsx +++ b/packages/anastasis-webui/src/components/fields/FileInput.tsx @@ -15,14 +15,14 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { TextInputProps } from "./TextInput"; -const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; export function FileInput(props: TextInputProps): VNode { const inputRef = useRef<HTMLInputElement>(null); @@ -34,48 +34,54 @@ export function FileInput(props: TextInputProps): VNode { const value = props.bind[0]; // const [dirty, setDirty] = useState(false) - const image = useRef<HTMLInputElement>(null) - const [sizeError, setSizeError] = useState(false) + const image = useRef<HTMLInputElement>(null); + const [sizeError, setSizeError] = useState(false); function onChange(v: string): void { // setDirty(true); props.bind[1](v); } - return <div class="field"> - <label class="label"> - <a onClick={() => image.current?.click()}> - {props.label} - </a> - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <input - ref={image} style={{ display: 'none' }} - type="file" name={String(name)} - onChange={e => { - const f: FileList | null = e.currentTarget.files - if (!f || f.length != 1) { - return onChange("") - } - if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { - setSizeError(true) - return onChange("") - } - setSizeError(false) - return f[0].arrayBuffer().then(b => { - const b64 = btoa( - new Uint8Array(b) - .reduce((data, byte) => data + String.fromCharCode(byte), '') - ) - return onChange(`data:${f[0].type};base64,${b64}` as any) - }) - }} /> - {props.error && <p class="help is-danger">{props.error}</p>} - {sizeError && <p class="help is-danger"> - File should be smaller than 1 MB - </p>} + return ( + <div class="field"> + <label class="label"> + <a onClick={() => image.current?.click()}>{props.label}</a> + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <input + ref={image} + style={{ display: "none" }} + type="file" + name={String(name)} + onChange={(e) => { + const f: FileList | null = e.currentTarget.files; + if (!f || f.length != 1) { + return onChange(""); + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true); + return onChange(""); + } + setSizeError(false); + return f[0].arrayBuffer().then((b) => { + const b64 = btoa( + new Uint8Array(b).reduce( + (data, byte) => data + String.fromCharCode(byte), + "", + ), + ); + return onChange(`data:${f[0].type};base64,${b64}` as any); + }); + }} + /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && ( + <p class="help is-danger">File should be smaller than 1 MB</p> + )} + </div> </div> - </div> + ); } - diff --git a/packages/anastasis-webui/src/components/fields/ImageInput.tsx b/packages/anastasis-webui/src/components/fields/ImageInput.tsx index d5bf643d4..3f8cc58dd 100644 --- a/packages/anastasis-webui/src/components/fields/ImageInput.tsx +++ b/packages/anastasis-webui/src/components/fields/ImageInput.tsx @@ -15,15 +15,15 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import emptyImage from "../../assets/empty.png"; import { TextInputProps } from "./TextInput"; -const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024; export function ImageInput(props: TextInputProps): VNode { const inputRef = useRef<HTMLInputElement>(null); @@ -35,47 +35,59 @@ export function ImageInput(props: TextInputProps): VNode { const value = props.bind[0]; // const [dirty, setDirty] = useState(false) - const image = useRef<HTMLInputElement>(null) - const [sizeError, setSizeError] = useState(false) + const image = useRef<HTMLInputElement>(null); + const [sizeError, setSizeError] = useState(false); function onChange(v: string): void { // setDirty(true); props.bind[1](v); } - return <div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control"> - <img src={!value ? emptyImage : value} style={{ width: 200, height: 200 }} onClick={() => image.current?.click()} /> - <input - ref={image} style={{ display: 'none' }} - type="file" name={String(name)} - onChange={e => { - const f: FileList | null = e.currentTarget.files - if (!f || f.length != 1) { - return onChange(emptyImage) - } - if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { - setSizeError(true) - return onChange(emptyImage) - } - setSizeError(false) - return f[0].arrayBuffer().then(b => { - const b64 = btoa( - new Uint8Array(b) - .reduce((data, byte) => data + String.fromCharCode(byte), '') - ) - return onChange(`data:${f[0].type};base64,${b64}` as any) - }) - }} /> - {props.error && <p class="help is-danger">{props.error}</p>} - {sizeError && <p class="help is-danger"> - Image should be smaller than 1 MB - </p>} + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control"> + <img + src={!value ? emptyImage : value} + style={{ width: 200, height: 200 }} + onClick={() => image.current?.click()} + /> + <input + ref={image} + style={{ display: "none" }} + type="file" + name={String(name)} + onChange={(e) => { + const f: FileList | null = e.currentTarget.files; + if (!f || f.length != 1) { + return onChange(emptyImage); + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true); + return onChange(emptyImage); + } + setSizeError(false); + return f[0].arrayBuffer().then((b) => { + const b64 = btoa( + new Uint8Array(b).reduce( + (data, byte) => data + String.fromCharCode(byte), + "", + ), + ); + return onChange(`data:${f[0].type};base64,${b64}` as any); + }); + }} + /> + {props.error && <p class="help is-danger">{props.error}</p>} + {sizeError && ( + <p class="help is-danger">Image should be smaller than 1 MB</p> + )} + </div> </div> - </div> + ); } - diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx index c093689c5..fd0c658ed 100644 --- a/packages/anastasis-webui/src/components/fields/TextInput.tsx +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -18,25 +18,32 @@ export function TextInput(props: TextInputProps): VNode { } }, [props.grabFocus]); const value = props.bind[0]; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error - return (<div class="field"> - <label class="label"> - {props.label} - {props.tooltip && <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> - <i class="mdi mdi-information" /> - </span>} - </label> - <div class="control has-icons-right"> - <input - value={value} - placeholder={props.placeholder} - class={showError ? 'input is-danger' : 'input'} - onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} - ref={inputRef} - style={{ display: "block" }} /> + const [dirty, setDirty] = useState(false); + const showError = dirty && props.error; + return ( + <div class="field"> + <label class="label"> + {props.label} + {props.tooltip && ( + <span class="icon has-tooltip-right" data-tooltip={props.tooltip}> + <i class="mdi mdi-information" /> + </span> + )} + </label> + <div class="control has-icons-right"> + <input + value={value} + placeholder={props.placeholder} + class={showError ? "input is-danger" : "input"} + onInput={(e) => { + setDirty(true); + props.bind[1]((e.target as HTMLInputElement).value); + }} + ref={inputRef} + style={{ display: "block" }} + /> + </div> + {showError && <p class="help is-danger">{props.error}</p>} </div> - {showError && <p class="help is-danger">{props.error}</p>} - </div> ); } diff --git a/packages/anastasis-webui/src/components/menu/LangSelector.tsx b/packages/anastasis-webui/src/components/menu/LangSelector.tsx index 0f91abd7e..fa22a29c0 100644 --- a/packages/anastasis-webui/src/components/menu/LangSelector.tsx +++ b/packages/anastasis-webui/src/components/menu/LangSelector.tsx @@ -15,59 +15,78 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import langIcon from '../../assets/icons/languageicon.svg'; +import langIcon from "../../assets/icons/languageicon.svg"; import { useTranslationContext } from "../../context/translation"; -import { strings as messages } from '../../i18n/strings' +import { strings as messages } from "../../i18n/strings"; type LangsNames = { - [P in keyof typeof messages]: string -} + [P in keyof typeof messages]: string; +}; const names: LangsNames = { - es: 'Español [es]', - en: 'English [en]', - fr: 'Français [fr]', - de: 'Deutsch [de]', - sv: 'Svenska [sv]', - it: 'Italiano [it]', -} + es: "Español [es]", + en: "English [en]", + fr: "Français [fr]", + de: "Deutsch [de]", + sv: "Svenska [sv]", + it: "Italiano [it]", +}; function getLangName(s: keyof LangsNames | string): string { - if (names[s]) return names[s] - return String(s) + if (names[s]) return names[s]; + return String(s); } export function LangSelector(): VNode { - const [updatingLang, setUpdatingLang] = useState(false) - const { lang, changeLanguage } = useTranslationContext() + const [updatingLang, setUpdatingLang] = useState(false); + const { lang, changeLanguage } = useTranslationContext(); - return <div class="dropdown is-active "> - <div class="dropdown-trigger"> - <button class="button has-tooltip-left" - data-tooltip="change language selection" - aria-haspopup="true" - aria-controls="dropdown-menu" onClick={() => setUpdatingLang(!updatingLang)}> - <div class="icon is-small is-left"> - <img src={langIcon} /> - </div> - <span>{getLangName(lang)}</span> - <div class="icon is-right"> - <i class="mdi mdi-chevron-down" /> + return ( + <div class="dropdown is-active "> + <div class="dropdown-trigger"> + <button + class="button has-tooltip-left" + data-tooltip="change language selection" + aria-haspopup="true" + aria-controls="dropdown-menu" + onClick={() => setUpdatingLang(!updatingLang)} + > + <div class="icon is-small is-left"> + <img src={langIcon} /> + </div> + <span>{getLangName(lang)}</span> + <div class="icon is-right"> + <i class="mdi mdi-chevron-down" /> + </div> + </button> + </div> + {updatingLang && ( + <div class="dropdown-menu" id="dropdown-menu" role="menu"> + <div class="dropdown-content"> + {Object.keys(messages) + .filter((l) => l !== lang) + .map((l) => ( + <a + key={l} + class="dropdown-item" + value={l} + onClick={() => { + changeLanguage(l); + setUpdatingLang(false); + }} + > + {getLangName(l)} + </a> + ))} + </div> </div> - </button> + )} </div> - {updatingLang && <div class="dropdown-menu" id="dropdown-menu" role="menu"> - <div class="dropdown-content"> - {Object.keys(messages) - .filter((l) => l !== lang) - .map(l => <a key={l} class="dropdown-item" value={l} onClick={() => { changeLanguage(l); setUpdatingLang(false) }}>{getLangName(l)}</a>)} - </div> - </div>} - </div> -}
\ No newline at end of file + ); +} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index a40f4be09..c73369dd6 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -15,16 +15,15 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { Fragment, h, VNode } from 'preact'; -import { BackupStates, RecoveryStates } from '../../../../anastasis-core/lib'; -import { useAnastasisContext } from '../../context/anastasis'; -import { Translate } from '../../i18n'; -import { LangSelector } from './LangSelector'; +import { Fragment, h, VNode } from "preact"; +import { BackupStates, RecoveryStates } from "../../../../anastasis-core/lib"; +import { useAnastasisContext } from "../../context/anastasis"; +import { Translate } from "../../i18n"; +import { LangSelector } from "./LangSelector"; interface Props { mobile?: boolean; @@ -32,10 +31,10 @@ interface Props { export function Sidebar({ mobile }: Props): VNode { // const config = useConfigContext(); - const config = { version: 'none' } + const config = { version: "none" }; // FIXME: add replacement for __VERSION__ with the current version - const process = { env: { __VERSION__: '0.0.0' } } - const reducer = useAnastasisContext()! + const process = { env: { __VERSION__: "0.0.0" } }; + const reducer = useAnastasisContext()!; return ( <aside class="aside is-placed-left is-expanded"> @@ -44,114 +43,235 @@ export function Sidebar({ mobile }: Props): VNode { </div>} */} <div class="aside-tools"> <div class="aside-tools-label"> - <div><b>Anastasis</b></div> - <div class="is-size-7 has-text-right" style={{ lineHeight: 0, marginTop: -10 }}> + <div> + <b>Anastasis</b> + </div> + <div + class="is-size-7 has-text-right" + style={{ lineHeight: 0, marginTop: -10 }} + > Version {process.env.__VERSION__} ({config.version}) </div> </div> </div> <div class="menu is-menu-main"> - {!reducer.currentReducerState && + {!reducer.currentReducerState && ( <p class="menu-label"> <Translate>Backup or Recorver</Translate> </p> - } + )} <ul class="menu-list"> - {!reducer.currentReducerState && + {!reducer.currentReducerState && ( <li> <div class="ml-4"> - <span class="menu-item-label"><Translate>Select one option</Translate></span> - </div> - </li> - } - {reducer.currentReducerState && reducer.currentReducerState.backup_state ? <Fragment> - <li class={reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting || - reducer.currentReducerState.backup_state === BackupStates.CountrySelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Location</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.UserAttributesCollecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Personal information</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.AuthenticationsEditing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Authorization methods</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesReviewing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Policies</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.backup_state === BackupStates.SecretEditing ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Secret input</Translate></span> + <span class="menu-item-label"> + <Translate>Select one option</Translate> + </span> </div> </li> - {/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}> + )} + {reducer.currentReducerState && + reducer.currentReducerState.backup_state ? ( + <Fragment> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.ContinentSelecting || + reducer.currentReducerState.backup_state === + BackupStates.CountrySelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Location</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.UserAttributesCollecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Personal information</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.AuthenticationsEditing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Authorization methods</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.PoliciesReviewing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Policies</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.SecretEditing + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret input</Translate> + </span> + </div> + </li> + {/* <li class={reducer.currentReducerState.backup_state === BackupStates.PoliciesPaying ? 'is-active' : ''}> <div class="ml-4"> <span class="menu-item-label"><Translate>Payment (optional)</Translate></span> </div> </li> */} - <li class={reducer.currentReducerState.backup_state === BackupStates.BackupFinished ? 'is-active' : ''}> - <div class="ml-4"> - - <span class="menu-item-label"><Translate>Backup completed</Translate></span> - </div> - </li> - {/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> + <li + class={ + reducer.currentReducerState.backup_state === + BackupStates.BackupFinished + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Backup completed</Translate> + </span> + </div> + </li> + {/* <li class={reducer.currentReducerState.backup_state === BackupStates.TruthsPaying ? 'is-active' : ''}> <div class="ml-4"> <span class="menu-item-label"><Translate>Truth Paying</Translate></span> </div> </li> */} - </Fragment> : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && <Fragment> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting || - reducer.currentReducerState.recovery_state === RecoveryStates.CountrySelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Location</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.UserAttributesCollecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Personal information</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.SecretSelecting ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Secret selection</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSelecting || - reducer.currentReducerState.recovery_state === RecoveryStates.ChallengeSolving ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Solve Challenges</Translate></span> - </div> - </li> - <li class={reducer.currentReducerState.recovery_state === RecoveryStates.RecoveryFinished ? 'is-active' : ''}> - <div class="ml-4"> - <span class="menu-item-label"><Translate>Secret recovered</Translate></span> - </div> - </li> - </Fragment>)} - {reducer.currentReducerState && + </Fragment> + ) : ( + reducer.currentReducerState && + reducer.currentReducerState?.recovery_state && ( + <Fragment> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.ContinentSelecting || + reducer.currentReducerState.recovery_state === + RecoveryStates.CountrySelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Location</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.UserAttributesCollecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Personal information</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.SecretSelecting + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret selection</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.ChallengeSelecting || + reducer.currentReducerState.recovery_state === + RecoveryStates.ChallengeSolving + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Solve Challenges</Translate> + </span> + </div> + </li> + <li + class={ + reducer.currentReducerState.recovery_state === + RecoveryStates.RecoveryFinished + ? "is-active" + : "" + } + > + <div class="ml-4"> + <span class="menu-item-label"> + <Translate>Secret recovered</Translate> + </span> + </div> + </li> + </Fragment> + ) + )} + {reducer.currentReducerState && ( <li> <div class="buttons ml-4"> - <button class="button is-danger is-right" onClick={() => reducer.reset()}>Reset session</button> + <button + class="button is-danger is-right" + onClick={() => reducer.reset()} + > + Reset session + </button> </div> </li> - } - + )} + {/* <li> + <div class="buttons ml-4"> + <button class="button is-info is-right" >Manage providers</button> + </div> + </li> */} </ul> </div> </aside> ); } - diff --git a/packages/anastasis-webui/src/components/menu/index.tsx b/packages/anastasis-webui/src/components/menu/index.tsx index fd4aab149..99d0f7646 100644 --- a/packages/anastasis-webui/src/components/menu/index.tsx +++ b/packages/anastasis-webui/src/components/menu/index.tsx @@ -85,8 +85,8 @@ export function NotificationCard({ n.type === "ERROR" ? "message is-danger" : n.type === "WARN" - ? "message is-warning" - : "message is-info" + ? "message is-warning" + : "message is-info" } > <div class="message-header"> @@ -113,7 +113,7 @@ export function NotYetReadyAppMenu({ return ( <div class="has-aside-mobile-expanded" - // class={mobileOpen ? "has-aside-mobile-expanded" : ""} + // class={mobileOpen ? "has-aside-mobile-expanded" : ""} onClick={() => setMobileOpen(false)} > <NavigationBar diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx index eb5d8145d..d689db386 100644 --- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, Component } from "preact"; @@ -34,83 +34,71 @@ interface State { selectYearMode: boolean; currentDate: Date; } -const now = new Date() +const now = new Date(); const monthArrShortFull = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December' -] + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; const monthArrShort = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec' -] - -const dayArr = [ - 'Sun', - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat' -] - -const yearArr: number[] = [] - + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +const dayArr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const yearArr: number[] = []; // inspired by https://codepen.io/m4r1vs/pen/MOOxyE export class DatePicker extends Component<Props, State> { - closeDatePicker() { this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent } /** - * Gets fired when a day gets clicked. - * @param {object} e The event thrown by the <span /> element clicked - */ + * Gets fired when a day gets clicked. + * @param {object} e The event thrown by the <span /> element clicked + */ dayClicked(e: any) { - const element = e.target; // the actual element clicked - if (element.innerHTML === '') return false; // don't continue if <span /> empty + if (element.innerHTML === "") return false; // don't continue if <span /> empty // get date from clicked element (gets attached when rendered) - const date = new Date(element.getAttribute('data-value')); + const date = new Date(element.getAttribute("data-value")); // update the state this.setState({ currentDate: date }); - this.passDateToParent(date) + this.passDateToParent(date); } /** - * returns days in month as array - * @param {number} month the month to display - * @param {number} year the year to display - */ + * returns days in month as array + * @param {number} month the month to display + * @param {number} year the year to display + */ getDaysByMonth(month: number, year: number) { - const calendar = []; const date = new Date(year, month, 1); // month to display @@ -122,15 +110,17 @@ export class DatePicker extends Component<Props, State> { // the calendar is 7*6 fields big, so 42 loops for (let i = 0; i < 42; i++) { - if (i >= firstDay && day !== null) day = day + 1; if (day !== null && day > lastDate) day = null; // append the calendar Array calendar.push({ - day: (day === 0 || day === null) ? null : day, // null or number - date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date() - today: (day === now.getDate() && month === now.getMonth() && year === now.getFullYear()) // boolean + day: day === 0 || day === null ? null : day, // null or number + date: day === 0 || day === null ? null : new Date(year, month, day), // null or Date() + today: + day === now.getDate() && + month === now.getMonth() && + year === now.getFullYear(), // boolean }); } @@ -138,51 +128,48 @@ export class DatePicker extends Component<Props, State> { } /** - * Display previous month by updating state - */ + * Display previous month by updating state + */ displayPrevMonth() { if (this.state.displayedMonth <= 0) { this.setState({ displayedMonth: 11, - displayedYear: this.state.displayedYear - 1 + displayedYear: this.state.displayedYear - 1, }); - } - else { + } else { this.setState({ - displayedMonth: this.state.displayedMonth - 1 + displayedMonth: this.state.displayedMonth - 1, }); } } /** - * Display next month by updating state - */ + * Display next month by updating state + */ displayNextMonth() { if (this.state.displayedMonth >= 11) { this.setState({ displayedMonth: 0, - displayedYear: this.state.displayedYear + 1 + displayedYear: this.state.displayedYear + 1, }); - } - else { + } else { this.setState({ - displayedMonth: this.state.displayedMonth + 1 + displayedMonth: this.state.displayedMonth + 1, }); } } /** - * Display the selected month (gets fired when clicking on the date string) - */ + * Display the selected month (gets fired when clicking on the date string) + */ displaySelectedMonth() { if (this.state.selectYearMode) { this.toggleYearSelector(); - } - else { + } else { if (!this.state.currentDate) return false; this.setState({ displayedMonth: this.state.currentDate.getMonth(), - displayedYear: this.state.currentDate.getFullYear() + displayedYear: this.state.currentDate.getFullYear(), }); } } @@ -194,17 +181,21 @@ export class DatePicker extends Component<Props, State> { changeDisplayedYear(e: any) { const element = e.target; this.toggleYearSelector(); - this.setState({ displayedYear: parseInt(element.innerHTML, 10), displayedMonth: 0 }); + this.setState({ + displayedYear: parseInt(element.innerHTML, 10), + displayedMonth: 0, + }); } /** - * Pass the selected date to parent when 'OK' is clicked - */ + * Pass the selected date to parent when 'OK' is clicked + */ passSavedDateDateToParent() { - this.passDateToParent(this.state.currentDate) + this.passDateToParent(this.state.currentDate); } passDateToParent(date: Date) { - if (typeof this.props.dateReceiver === 'function') this.props.dateReceiver(date); + if (typeof this.props.dateReceiver === "function") + this.props.dateReceiver(date); this.closeDatePicker(); } @@ -233,94 +224,133 @@ export class DatePicker extends Component<Props, State> { currentDate: initial, displayedMonth: initial.getMonth(), displayedYear: initial.getFullYear(), - selectYearMode: false - } + selectYearMode: false, + }; } render() { - - const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state; + const { + currentDate, + displayedMonth, + displayedYear, + selectYearMode, + } = this.state; return ( <div> - <div class={`datePicker ${ this.props.opened && "datePicker--opened"}`}> - + <div class={`datePicker ${this.props.opened && "datePicker--opened"}`}> <div class="datePicker--titles"> - <h3 style={{ - color: selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.toggleYearSelector}>{currentDate.getFullYear()}</h3> - <h2 style={{ - color: !selectYearMode ? 'rgba(255,255,255,.87)' : 'rgba(255,255,255,.57)' - }} onClick={this.displaySelectedMonth}> - {dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} + <h3 + style={{ + color: selectYearMode + ? "rgba(255,255,255,.87)" + : "rgba(255,255,255,.57)", + }} + onClick={this.toggleYearSelector} + > + {currentDate.getFullYear()} + </h3> + <h2 + style={{ + color: !selectYearMode + ? "rgba(255,255,255,.87)" + : "rgba(255,255,255,.57)", + }} + onClick={this.displaySelectedMonth} + > + {dayArr[currentDate.getDay()]},{" "} + {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} </h2> </div> - {!selectYearMode && <nav> - <span onClick={this.displayPrevMonth} class="icon"><i style={{ transform: 'rotate(180deg)' }} class="mdi mdi-forward" /></span> - <h4>{monthArrShortFull[displayedMonth]} {displayedYear}</h4> - <span onClick={this.displayNextMonth} class="icon"><i class="mdi mdi-forward" /></span> - </nav>} + {!selectYearMode && ( + <nav> + <span onClick={this.displayPrevMonth} class="icon"> + <i + style={{ transform: "rotate(180deg)" }} + class="mdi mdi-forward" + /> + </span> + <h4> + {monthArrShortFull[displayedMonth]} {displayedYear} + </h4> + <span onClick={this.displayNextMonth} class="icon"> + <i class="mdi mdi-forward" /> + </span> + </nav> + )} <div class="datePicker--scroll"> - - {!selectYearMode && <div class="datePicker--calendar" > - - <div class="datePicker--dayNames"> - {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => <span key={i}>{day}</span>)} - </div> - - <div onClick={this.dayClicked} class="datePicker--days"> - - {/* + {!selectYearMode && ( + <div class="datePicker--calendar"> + <div class="datePicker--dayNames"> + {["S", "M", "T", "W", "T", "F", "S"].map((day, i) => ( + <span key={i}>{day}</span> + ))} + </div> + + <div onClick={this.dayClicked} class="datePicker--days"> + {/* Loop through the calendar object returned by getDaysByMonth(). */} - {this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear) - .map( - day => { - let selected = false; - - if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString()); - - return (<span key={day.day} - class={(day.today ? 'datePicker--today ' : '') + (selected ? 'datePicker--selected' : '')} + {this.getDaysByMonth( + this.state.displayedMonth, + this.state.displayedYear, + ).map((day) => { + let selected = false; + + if (currentDate && day.date) + selected = + currentDate.toLocaleDateString() === + day.date.toLocaleDateString(); + + return ( + <span + key={day.day} + class={ + (day.today ? "datePicker--today " : "") + + (selected ? "datePicker--selected" : "") + } disabled={!day.date} data-value={day.date} > {day.day} - </span>) - } - ) - } - + </span> + ); + })} + </div> </div> - - </div>} - - {selectYearMode && <div class="datePicker--selectYear"> - {(this.props.years || yearArr).map(year => ( - <span key={year} class={(year === displayedYear) ? 'selected' : ''} onClick={this.changeDisplayedYear}> - {year} - </span> - ))} - - </div>} - + )} + + {selectYearMode && ( + <div class="datePicker--selectYear"> + {(this.props.years || yearArr).map((year) => ( + <span + key={year} + class={year === displayedYear ? "selected" : ""} + onClick={this.changeDisplayedYear} + > + {year} + </span> + ))} + </div> + )} </div> </div> - <div class="datePicker--background" onClick={this.closeDatePicker} style={{ - display: this.props.opened ? 'block' : 'none', - }} + <div + class="datePicker--background" + onClick={this.closeDatePicker} + style={{ + display: this.props.opened ? "block" : "none", + }} /> - </div> - ) + ); } } - for (let i = 2010; i <= now.getFullYear() + 10; i++) { yearArr.push(i); } diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx index 275c80fa6..7f96cc15b 100644 --- a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx @@ -15,36 +15,41 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { h, FunctionalComponent } from 'preact'; -import { useState } from 'preact/hooks'; -import { DurationPicker as TestedComponent } from './DurationPicker'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { h, FunctionalComponent } from "preact"; +import { useState } from "preact/hooks"; +import { DurationPicker as TestedComponent } from "./DurationPicker"; export default { - title: 'Components/Picker/Duration', + title: "Components/Picker/Duration", component: TestedComponent, argTypes: { - onCreate: { action: 'onCreate' }, - goBack: { action: 'goBack' }, - } + onCreate: { action: "onCreate" }, + goBack: { action: "goBack" }, + }, }; -function createExample<Props>(Component: FunctionalComponent<Props>, props: Partial<Props>) { - const r = (args: any) => <Component {...args} /> - r.args = props - return r +function createExample<Props>( + Component: FunctionalComponent<Props>, + props: Partial<Props>, +) { + const r = (args: any) => <Component {...args} />; + r.args = props; + return r; } export const Example = createExample(TestedComponent, { - days: true, minutes: true, hours: true, seconds: true, - value: 10000000 + days: true, + minutes: true, + hours: true, + seconds: true, + value: 10000000, }); export const WithState = () => { - const [v,s] = useState<number>(1000000) - return <TestedComponent value={v} onChange={s} days minutes hours seconds /> -} + const [v, s] = useState<number>(1000000); + return <TestedComponent value={v} onChange={s} days minutes hours seconds />; +}; diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx index 235a63e2d..8a1faf4d0 100644 --- a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx @@ -15,9 +15,9 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -30,75 +30,123 @@ export interface Props { seconds?: boolean; days?: boolean; onChange: (value: number) => void; - value: number + value: number; } // inspiration taken from https://github.com/flurmbo/react-duration-picker -export function DurationPicker({ days, hours, minutes, seconds, onChange, value }: Props): VNode { - const ss = 1000 - const ms = ss * 60 - const hs = ms * 60 - const ds = hs * 24 - const i18n = useTranslator() - - return <div class="rdp-picker"> - {days && <DurationColumn unit={i18n`days`} max={99} - value={Math.floor(value / ds)} - onDecrease={value >= ds ? () => onChange(value - ds) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} - onChange={diff => onChange(value + diff * ds)} - />} - {hours && <DurationColumn unit={i18n`hours`} max={23} min={1} - value={Math.floor(value / hs) % 24} - onDecrease={value >= hs ? () => onChange(value - hs) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} - onChange={diff => onChange(value + diff * hs)} - />} - {minutes && <DurationColumn unit={i18n`minutes`} max={59} min={1} - value={Math.floor(value / ms) % 60} - onDecrease={value >= ms ? () => onChange(value - ms) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} - onChange={diff => onChange(value + diff * ms)} - />} - {seconds && <DurationColumn unit={i18n`seconds`} max={59} - value={Math.floor(value / ss) % 60} - onDecrease={value >= ss ? () => onChange(value - ss) : undefined} - onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} - onChange={diff => onChange(value + diff * ss)} - />} - </div> +export function DurationPicker({ + days, + hours, + minutes, + seconds, + onChange, + value, +}: Props): VNode { + const ss = 1000; + const ms = ss * 60; + const hs = ms * 60; + const ds = hs * 24; + const i18n = useTranslator(); + + return ( + <div class="rdp-picker"> + {days && ( + <DurationColumn + unit={i18n`days`} + max={99} + value={Math.floor(value / ds)} + onDecrease={value >= ds ? () => onChange(value - ds) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} + onChange={(diff) => onChange(value + diff * ds)} + /> + )} + {hours && ( + <DurationColumn + unit={i18n`hours`} + max={23} + min={1} + value={Math.floor(value / hs) % 24} + onDecrease={value >= hs ? () => onChange(value - hs) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} + onChange={(diff) => onChange(value + diff * hs)} + /> + )} + {minutes && ( + <DurationColumn + unit={i18n`minutes`} + max={59} + min={1} + value={Math.floor(value / ms) % 60} + onDecrease={value >= ms ? () => onChange(value - ms) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} + onChange={(diff) => onChange(value + diff * ms)} + /> + )} + {seconds && ( + <DurationColumn + unit={i18n`seconds`} + max={59} + value={Math.floor(value / ss) % 60} + onDecrease={value >= ss ? () => onChange(value - ss) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} + onChange={(diff) => onChange(value + diff * ss)} + /> + )} + </div> + ); } interface ColProps { - unit: string, - min?: number, - max: number, - value: number, + unit: string; + min?: number; + max: number; + value: number; onIncrease?: () => void; onDecrease?: () => void; onChange?: (diff: number) => void; } -function InputNumber({ initial, onChange }: { initial: number, onChange: (n: number) => void }) { - const [value, handler] = useState<{v:string}>({ - v: toTwoDigitString(initial) - }) - - return <input - value={value.v} - onBlur={(e) => onChange(parseInt(value.v, 10))} - onInput={(e) => { - e.preventDefault() - const n = Number.parseInt(e.currentTarget.value, 10); - if (isNaN(n)) return handler({v:toTwoDigitString(initial)}) - return handler({v:toTwoDigitString(n)}) - }} - style={{ width: 50, border: 'none', fontSize: 'inherit', background: 'inherit' }} /> -} +function InputNumber({ + initial, + onChange, +}: { + initial: number; + onChange: (n: number) => void; +}) { + const [value, handler] = useState<{ v: string }>({ + v: toTwoDigitString(initial), + }); -function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onChange }: ColProps): VNode { + return ( + <input + value={value.v} + onBlur={(e) => onChange(parseInt(value.v, 10))} + onInput={(e) => { + e.preventDefault(); + const n = Number.parseInt(e.currentTarget.value, 10); + if (isNaN(n)) return handler({ v: toTwoDigitString(initial) }); + return handler({ v: toTwoDigitString(n) }); + }} + style={{ + width: 50, + border: "none", + fontSize: "inherit", + background: "inherit", + }} + /> + ); +} - const cellHeight = 35 +function DurationColumn({ + unit, + min = 0, + max, + value, + onIncrease, + onDecrease, + onChange, +}: ColProps): VNode { + const cellHeight = 35; return ( <div class="rdp-column-container"> <div class="rdp-masked-div"> @@ -106,49 +154,58 @@ function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onC <hr class="rdp-reticule" style={{ top: cellHeight * 3 - 1 }} /> <div class="rdp-column" style={{ top: 0 }}> - <div class="rdp-cell" key={value - 2}> - {onDecrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} - onClick={onDecrease}> - <span class="icon"> - <i class="mdi mdi-chevron-up" /> - </span> - </button>} + {onDecrease && ( + <button + style={{ width: "100%", textAlign: "center", margin: 5 }} + onClick={onDecrease} + > + <span class="icon"> + <i class="mdi mdi-chevron-up" /> + </span> + </button> + )} </div> <div class="rdp-cell" key={value - 1}> - {value > min ? toTwoDigitString(value - 1) : ''} + {value > min ? toTwoDigitString(value - 1) : ""} </div> <div class="rdp-cell rdp-center" key={value}> - {onChange ? - <InputNumber initial={value} onChange={(n) => onChange(n - value)} /> : + {onChange ? ( + <InputNumber + initial={value} + onChange={(n) => onChange(n - value)} + /> + ) : ( toTwoDigitString(value) - } + )} <div>{unit}</div> </div> <div class="rdp-cell" key={value + 1}> - {value < max ? toTwoDigitString(value + 1) : ''} + {value < max ? toTwoDigitString(value + 1) : ""} </div> <div class="rdp-cell" key={value + 2}> - {onIncrease && <button style={{ width: '100%', textAlign: 'center', margin: 5 }} - onClick={onIncrease}> - <span class="icon"> - <i class="mdi mdi-chevron-down" /> - </span> - </button>} + {onIncrease && ( + <button + style={{ width: "100%", textAlign: "center", margin: 5 }} + onClick={onIncrease} + > + <span class="icon"> + <i class="mdi mdi-chevron-down" /> + </span> + </button> + )} </div> - </div> </div> </div> ); } - function toTwoDigitString(n: number) { if (n < 10) { return `0${n}`; } return `${n}`; -}
\ No newline at end of file +} |