summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/anastasis-webui/src/components/picker/DurationPicker.tsx')
-rw-r--r--packages/anastasis-webui/src/components/picker/DurationPicker.tsx211
1 files changed, 211 insertions, 0 deletions
diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
new file mode 100644
index 000000000..c4caaec9f
--- /dev/null
+++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx
@@ -0,0 +1,211 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useTranslationContext } from "../../context/translation.js";
+import "../../scss/DurationPicker.scss";
+
+export interface Props {
+ hours?: boolean;
+ minutes?: boolean;
+ seconds?: boolean;
+ days?: boolean;
+ onChange: (value: number) => void;
+ 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 } = useTranslationContext();
+
+ return (
+ <div class="rdp-picker">
+ {days && (
+ <DurationColumn
+ unit={i18n.str`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.str`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.str`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.str`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;
+ 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 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">
+ <hr class="rdp-reticule" style={{ top: cellHeight * 2 - 1 }} />
+ <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>
+ )}
+ </div>
+ <div class="rdp-cell" key={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)}
+ />
+ ) : (
+ toTwoDigitString(value)
+ )}
+ <div>{unit}</div>
+ </div>
+
+ <div class="rdp-cell" key={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>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+function toTwoDigitString(n: number) {
+ if (n < 10) {
+ return `0${n}`;
+ }
+ return `${n}`;
+}