diff options
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Button.module.css | 29 | ||||
| -rw-r--r-- | src/components/Button.tsx | 57 | ||||
| -rw-r--r-- | src/components/Timer.module.css | 63 | ||||
| -rw-r--r-- | src/components/Timer.tsx | 178 |
4 files changed, 327 insertions, 0 deletions
diff --git a/src/components/Button.module.css b/src/components/Button.module.css new file mode 100644 index 0000000..7041eaf --- /dev/null +++ b/src/components/Button.module.css @@ -0,0 +1,29 @@ +.btn:last-child { + margin-bottom: 0; +} +.generic-button { + display: grid; + place-items: center; + border: 3px solid #222; + color: white; + border-radius: 15px; + font-size: 1.6em; + width: 100%; + transition: transform 0.05s; + user-select: none; +} +.generic-button:enabled:active { + transform: scale(0.8); + box-shadow: inset 0px 0px 5px 0px black; +} +.generic-button:disabled { + filter: grayscale(0.8); +} +.btn { + composes: generic-button; + --page-size-without-margins: ( + 100svh - (var(--rows-count) + 1) * var(--btn-gap) + ); + --btn-height: calc(var(--page-size-without-margins) / var(--rows-count)); + height: var(--btn-height); +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..031fc73 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,57 @@ +import styles from "./Button.module.css"; +import { createEffect, createSignal } from "solid-js"; + +enum BtnType { + click = 0, + switch = 1, +} +export type Button = + | { + type: BtnType.click; + name: string; + color?: string; + } + | { + type: BtnType.switch; + name: string; + }; + +export default function Button(props: Button) { + const [value, setValue] = createSignal(false); + const [bgColor, setBgColor] = createSignal<string>(); + + createEffect(() => { + if (props.type === BtnType.click) { + setBgColor(value() ? "pink" : "blue"); + } else { + setBgColor(value() ? "green" : "red"); + } + }); + + function onClick() { + if (props.type === BtnType.click) { + if (value() === true) { + return; + } + setValue(true); + setTimeout(() => { + setValue(false); + }, 3000); + } else { + setValue((old) => !old); + } + } + + return ( + <button + on:click={onClick} + class={styles.btn} + style={{ "background-color": bgColor() }} + > + { + //btnText() + } + {props.name} + </button> + ); +} diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css new file mode 100644 index 0000000..b748c9e --- /dev/null +++ b/src/components/Timer.module.css @@ -0,0 +1,63 @@ +.cont { + h2 { + margin-top: 0.4em; + margin-bottom: 1rem; + } + input { + width: 2em; + font-size: 1.5em; + padding: 0.3em; + } +} +.custom-option { + display: grid; + align-items: center; + grid-template-columns: 1fr 2fr 1fr; + margin-bottom: 1em; +} +.units { + display: inline-flex; + gap: 1.5em; + span { + font-size: 2em; + } +} +.not-selected-unit { + font-weight: lighter; +} +.selected-unit { + font-weight: bold; + text-decoration: underline; +} +.darkgray { + color: darkgray; +} +.custom-option-btn { + composes: generic-button from "./Button.module.css"; + background-color: green; + height: 2em; +} +.extra-stop-btn { + composes: generic-button from "./Button.module.css"; + background-color: red; + padding: 0.5em; + margin-top: 0.5em; +} + +.time-left { + margin-bottom: 1em; + h2 { + display: inline; + color: lightblue; + } +} + +.options { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} +.option { + composes: generic-button from "./Button.module.css"; + height: calc((100vw - 2rem) / 3 / 1.5); +} diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx new file mode 100644 index 0000000..61a98f5 --- /dev/null +++ b/src/components/Timer.tsx @@ -0,0 +1,178 @@ +import { createSignal, For, Match, Show, Switch } from "solid-js"; +import styles from "./Timer.module.css"; + +export const TIME_UNITS = ["s", "m", "h", "d"] as const; +export type Timer = { + title: string; + options: { + time: number; + unit: (typeof TIME_UNITS)[number]; + }[]; + backgroundColor: string; + onTill: number | null; + secondsLeftText: string; + extraStopActions: { title: string }[]; +}; + +export default function Timer(props: Timer) { + const [onTill, setOnTill] = createSignal(props.onTill); + const [unit, setUnit] = createSignal<(typeof TIME_UNITS)[number]>( + TIME_UNITS[1], + ); + const [formattedTime, setFormattedTime] = createSignal(""); + const [disabled, setDisabled] = createSignal(onTill() !== null); + const [customValue, setCustomValue] = createSignal<number | "">(""); + + let _intervalId = null; + function toggleAutoUpdate() { + if (_intervalId === null) { + updateTime(); + _intervalId = setInterval(updateTime, 1000); + } else { + clearInterval(_intervalId); + _intervalId = null; + } + } + if (props.onTill !== null) { + toggleAutoUpdate(); + } + function updateTime() { + const s = Math.floor(onTill() - Date.now() / 1000); + const days = Math.floor(s / (24 * 3600)); + const hours = Math.floor((s % (24 * 3600)) / 3600); + const minutes = Math.floor((s % 3600) / 60); + const seconds = s % 60; + + let text = ""; + if (days > 0) { + text += days + "d : "; + } + if (hours > 0 || text.length > 0) { + text += hours + "h : "; + } + if (minutes > 0 || text.length > 0) { + text += minutes + "m : "; + } + if (seconds > 0 || text.length > 0) { + text += seconds + "s"; + } + setFormattedTime(text); + } + function startStopClick() { + const val = customValue(); + if (disabled() === false && val !== "") { + optionClick({ time: val, unit: unit() }); + } else if (disabled() === true) { + setDisabled(false); + setOnTill(null); + toggleAutoUpdate(); + } + } + + function optionClick(option: Timer["options"][number]) { + setDisabled(true); + let newOnTill = Math.floor(Date.now() / 1000); + if (option.unit === "s") { + newOnTill += option.time; + } else if (option.unit === "m") { + newOnTill += option.time * 60; + } else if (option.unit === "h") { + newOnTill += option.time * 3600; + } else { + newOnTill += option.time * 3600 * 24; + } + setOnTill(newOnTill); + toggleAutoUpdate(); + } + + function extraStopClick(act: Timer["extraStopActions"][number]) { + // Some api call for the extra + startStopClick(); + } + return ( + <div class={styles.cont}> + <h2>{props.title}</h2> + + <div class={styles.customOption}> + <input + type="number" + value={customValue()} + on:input={(e) => + setCustomValue(e.target.value ? parseFloat(e.target.value) : "") + } + disabled={disabled()} + min="1" + /> + <div class={styles.units}> + <For each={TIME_UNITS}> + {(u) => ( + <span + on:click={() => (disabled() ? null : setUnit(u))} + classList={{ + [styles.selectedUnit]: unit() === u, + [styles.notSelectedUnit]: unit() !== u, + [styles.darkgray]: disabled() === true, + }} + > + {u} + </span> + )} + </For> + </div> + <button + class={styles.customOptionBtn} + on:click={startStopClick} + disabled={ + disabled() + ? false + : customValue() === "" || (customValue() as number) <= 0 + } + style={{ "background-color": disabled() ? "red" : "green" }} + > + {disabled() ? "Wyłącz" : "Włącz"} + </button> + {/*<Show when={disabled() === true}>*/} + <For each={props.extraStopActions}> + {(act) => ( + <button + disabled={disabled() === false} + on:click={[extraStopClick, act]} + class={styles.extraStopBtn} + > + {act.title} + </button> + )} + </For> + {/*</Show>*/} + </div> + + <div class={styles.timeLeft}> + <Switch> + <Match when={onTill() === null}> + <span>Wyłączone</span> + <h2> </h2> + </Match> + <Match when={onTill() > 0}> + <span>{props.secondsLeftText} </span> + <h2>{formattedTime()}</h2> + </Match> + </Switch> + </div> + + <div class={styles.options}> + <For each={props.options}> + {(option) => ( + <button + class={styles.option} + on:click={[optionClick, option]} + style={{ "background-color": props.backgroundColor }} + disabled={disabled()} + > + {option.time} {option.unit} + </button> + )} + </For> + </div> + </div> + ); +} |
