diff options
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 +}  | 
