diff options
Diffstat (limited to 'src')
| -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 | ||||
| -rw-r--r-- | src/helpers/api.ts | 103 | ||||
| -rw-r--r-- | src/index.css | 6 | ||||
| -rw-r--r-- | src/index.tsx | 17 | ||||
| -rw-r--r-- | src/pages/*.tsx | 8 | ||||
| -rw-r--r-- | src/pages/index.module.css | 14 | ||||
| -rw-r--r-- | src/pages/index.tsx | 33 | ||||
| -rw-r--r-- | src/vite.env.d.ts | 2 |
11 files changed, 510 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> + ); +} diff --git a/src/helpers/api.ts b/src/helpers/api.ts new file mode 100644 index 0000000..112ba2f --- /dev/null +++ b/src/helpers/api.ts @@ -0,0 +1,103 @@ +import { type Button } from "@/components/Button"; +import { Timer } from "@/components/Timer"; + +export async function getButtons(): Promise<Button[]> { + return [ + { + type: 0, + name: "Brama Wjazdowa", + }, + { + type: 0, + name: "Brama Toyota", + }, + { + type: 0, + name: "Brama Passat", + }, + { + type: 1, + name: "Furtka", + }, + { + type: 0, + name: "Plafon Maks", + }, + { + type: 1, + name: "Ciepła woda 52,8°C", + }, + { + type: 1, + name: "Wentylator łazienka", + }, + { + type: 1, + name: "Wentylator Kuchnia Dolna", + }, + { + type: 1, + name: "Pompa źródło", + }, + { + type: 1, + name: "Nawodnienie", + }, + { + type: 1, + name: "Choinka+Astro", + }, + { + type: 1, + name: "Komputer", + }, + { + type: 1, + name: "Oświetlenie Zewnętrzne", + }, + { + type: 1, + name: "Blok. czujki ośw. zewn.", + }, + { + type: 1, + name: "Ogrzewanie 27,5°C", + }, + { + type: 1, + name: "Termometr - restart", + }, + ]; +} + +export async function getTimers(): Promise<Timer[]> { + return [ + { + title: "Otwórz bramę wjazową na:", + options: [ + { time: 15, unit: "m" }, + { time: 45, unit: "m" }, + { time: 1, unit: "h" }, + { time: 3, unit: "h" }, + { time: 24, unit: "h" }, + { time: 48, unit: "h" }, + ], + backgroundColor: "#ab03ab", + onTill: Math.floor(Date.now() / 1000) + 3 * 287721, + secondsLeftText: "Otwarte jeszcze przez:", + extraStopActions: [{ title: "Zamknij i wyłącz" }], + }, + { + title: "Włącz ogrzewanie na:", + secondsLeftText: "Włączone jeszcze na:", + options: [ + { time: 1, unit: "h" }, + { time: 1.5, unit: "h" }, + { time: 2, unit: "h" }, + { time: 3, unit: "h" }, + ], + backgroundColor: "#ab03ab", + onTill: null, + }, + ]; +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..f064787 --- /dev/null +++ b/src/index.css @@ -0,0 +1,6 @@ +body { + margin: 0; + background-color: black; + color: white; + padding: 0.3rem; +} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..de08c81 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,17 @@ +/* @refresh reload */ +import "./index.css"; + +import { Suspense } from "solid-js"; +import { render } from "solid-js/web"; + +import { Router } from "@solidjs/router"; +import routes from "~solid-pages"; + +render( + () => ( + <Router root={(props) => <Suspense>{props.children}</Suspense>}> + {routes} + </Router> + ), + document.getElementById("root"), +); diff --git a/src/pages/*.tsx b/src/pages/*.tsx new file mode 100644 index 0000000..a7021c6 --- /dev/null +++ b/src/pages/*.tsx @@ -0,0 +1,8 @@ +export default function NotFound() { + return ( + <section> + <h1>404: Not Found</h1> + <p>It's gone 😞</p> + </section> + ); +} diff --git a/src/pages/index.module.css b/src/pages/index.module.css new file mode 100644 index 0000000..9d6167d --- /dev/null +++ b/src/pages/index.module.css @@ -0,0 +1,14 @@ +.btns-cont { + --btn-gap: 0.3rem; + /* padding: var(--btn-gap); */ + display: grid; + justify-items: center; + align-items: center; + gap: var(--btn-gap); + grid-template-columns: repeat(2, 1fr); +} +.timers-cont { + hr { + margin-top: 2em; + } +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx new file mode 100644 index 0000000..d64f9e1 --- /dev/null +++ b/src/pages/index.tsx @@ -0,0 +1,33 @@ +import { For } from "solid-js"; +import { createAsync } from "@solidjs/router"; + +import Button from "@/components/Button"; +import Timer from "@/components/Timer"; +import { getButtons, getTimers } from "@/helpers/api"; +import styles from "./index.module.css"; + +export default function Home() { + const btns = createAsync(getButtons); + const timers = createAsync(getTimers); + + return ( + <> + <div + class={styles.btnsCont} + style={{ "--rows-count": Math.floor(btns()?.length / 2) }} + > + <For each={btns()}>{(btn) => <Button {...btn} />}</For> + </div> + <div class={styles.timersCont}> + <For each={timers()}> + {(timer) => ( + <> + <hr /> + <Timer {...timer} /> + </> + )} + </For> + </div> + </> + ); +} diff --git a/src/vite.env.d.ts b/src/vite.env.d.ts new file mode 100644 index 0000000..50d6f23 --- /dev/null +++ b/src/vite.env.d.ts @@ -0,0 +1,2 @@ +/// <reference types="vite/client" /> +/// <reference types="vite-plugin-pages/client-solid" /> |
