From 6ff45b81bf7779d3a0ae4ffb7db13660a669ba50 Mon Sep 17 00:00:00 2001 From: kritzl Date: Thu, 14 May 2026 15:56:35 +0200 Subject: [PATCH] move script to own file --- app/src/assets/main.ts | 214 ++++++++++++++++++++++++++++ app/src/pages/[lang]/index.astro | 231 ++----------------------------- 2 files changed, 228 insertions(+), 217 deletions(-) create mode 100644 app/src/assets/main.ts diff --git a/app/src/assets/main.ts b/app/src/assets/main.ts new file mode 100644 index 0000000..d107178 --- /dev/null +++ b/app/src/assets/main.ts @@ -0,0 +1,214 @@ +import {Fetcher} from "openapi-typescript-fetch" +import {type paths} from "../api/schema" + +const fetcher = Fetcher.for() + +const list: HTMLDivElement = document.querySelector("#list")! +const template: HTMLTemplateElement = document.querySelector("#template-door")! + +type DoorType = { + id: string; + label: string; + state: "unlocked" | "locked" | "unknown" | "unlocking" | "locking"; + batteryLow: boolean; + unreachable: boolean; + jammed: boolean; +} + +type AuthType = { + username: string; + authorized: boolean; + authenticated: boolean; + until: Date | null; + recentLogout: boolean; +} + +const auth: AuthType = { + username: "user", + authorized: true, + authenticated: false, + until: null, + recentLogout: false, +} + +const apiError: { + current: "serverError" | null; +} = { + current: null, +} + +const doors: Array = [] + +function loadAuthFromLocalStorage() { + const localAuth = JSON.parse(localStorage.getItem("auth") ?? "") + auth.recentLogout = localAuth.recentLogout // set recentLogout true, if user was logged in before + auth.authenticated = localAuth.authenticated + auth.authorized = localAuth.authorized + auth.until = localAuth.until ? new Date(localAuth.until) : null + auth.username = localAuth.username +} + +async function checkUser() { + const getUserInfo = fetcher.path("/api/user-info/").method("get").create() + + try { + const {status, data: userInfo} = await getUserInfo({}) + + apiError.current = null + auth.authenticated = userInfo.is_logged_in + auth.authorized = true + auth.until = userInfo.guaranteed_session_until ? new Date(userInfo.guaranteed_session_until) : null + auth.username = userInfo.user_info?.username ?? "" + auth.recentLogout = false + } catch (e) { + // check which operation threw the exception + if (e instanceof getUserInfo.Error) { + const error = e.getActualType() + + if (error.status === 401) { + if (!auth.recentLogout) + auth.recentLogout = auth.authenticated // set recentLogout true, if user was logged in before + auth.authenticated = false + auth.authorized = false + auth.until = null + auth.username = "" + } else if (error.status >= 500 && error.status < 600) { + apiError.current = "serverError" + } else { + console.error("unknown error:", error) + } + } + } finally { + localStorage.setItem("auth", JSON.stringify(auth)) + refresh() + } +} + +async function fetchDoors() { + const getDoors = fetcher.path("/api/locks/").method("get").create() + + try { + const {status, data: doorInfo} = await getDoors({}) + + apiError.current = null + while (doors.length) { + doors.pop() + } + doorInfo.forEach(door => { + let state: DoorType["state"] = "unknown" + + switch (door.status.activity_state) { + case "locking": + case "unlocking": + case "unknown": + state = door.status.activity_state + break + case "stable": + state = door.status.lock_state + } + + doors.push({ + id: door.name, // TODO: replace by actual ID + label: door.name, + state: state, + batteryLow: door.status.is_low_battery, + unreachable: door.status.is_unreachable, + jammed: door.status.is_error_jammed, + }) + }) + } catch (e) { + // check which operation threw the exception + if (e instanceof getDoors.Error) { + const error = e.getActualType() + + if (error.status === 401) { + console.log("unauthorized") + } else if (error.status >= 500 && error.status < 600) { + apiError.current = "serverError" + } else { + console.error("unknown error:", error) + } + } + } finally { + refresh() + } +} + +function setDoorInfo(doorElement: HTMLDivElement, door: DoorType) { + const labelElement: HTMLDivElement = doorElement.querySelector("[data-label]")! + const buttonElements: Array = Array.from(doorElement.querySelectorAll("button")) + + doorElement.dataset.id = door.id + doorElement.dataset.state = door.state + + if (auth.authenticated && auth.authorized) { + doorElement.dataset.active = "" + } else { + delete doorElement.dataset.active + } + + if (door.batteryLow) { + doorElement.dataset.battery = "" + } else { + delete doorElement.dataset.battery + } + + if (door.unreachable) { + doorElement.dataset.unreachable = "" + } else { + delete doorElement.dataset.unreachable + } + + if (door.jammed) { + doorElement.dataset.jammed = "" + } else { + delete doorElement.dataset.jammed + } + + labelElement.innerHTML = door.label + + buttonElements.forEach(button => { + button.disabled = ["unlocking", "locking"].includes(door.state) || apiError.current !== null + }) +} + +function refresh() { + for (const door of doors) { + let doorElement: HTMLDivElement | null = list.querySelector(`[data-id='${door.id}']`) + if (doorElement) { + setDoorInfo(doorElement, door) + continue + } + + const targetElement = document.importNode(template.content, true) + doorElement = targetElement.querySelector("[data-state]")! + setDoorInfo(doorElement, door) + list.appendChild(targetElement) + } + + const alertUnauthenticated: HTMLDivElement = document.querySelector("#alert-unauthenticated")! + const alertUnauthorized: HTMLDivElement = document.querySelector("#alert-unauthorized")! + const alertLoggedout: HTMLDivElement = document.querySelector("#alert-loggedout")! + const alertServerError: HTMLDivElement = document.querySelector("#alert-serverError")! + const alertDivider: HTMLDivElement = document.querySelector("#alert-divider")! + const badgeUsername: HTMLDivElement = document.querySelector("#badge-username")! + const buttonLogin: HTMLDivElement = document.querySelector("#button-login")! + const usernameElement: HTMLSpanElement = badgeUsername.querySelector("#username")! + + alertUnauthenticated.classList.toggle("hidden", auth.authenticated) + alertUnauthorized.classList.toggle("hidden", !auth.authenticated || auth.authorized) + alertLoggedout.classList.toggle("hidden", !auth.recentLogout) + alertServerError.classList.toggle("hidden", apiError.current !== "serverError") + alertDivider.classList.toggle("hidden", auth.authenticated && auth.authorized && !auth.recentLogout && apiError.current === null) + badgeUsername.classList.toggle("hidden", !auth.authenticated) + usernameElement.innerHTML = auth.username + buttonLogin.classList.toggle("hidden", auth.authenticated) +} + +loadAuthFromLocalStorage() +fetchDoors() +checkUser() + +document.addEventListener("loadeddata", () => { + refresh() +}) \ No newline at end of file diff --git a/app/src/pages/[lang]/index.astro b/app/src/pages/[lang]/index.astro index f717817..d4af8b5 100644 --- a/app/src/pages/[lang]/index.astro +++ b/app/src/pages/[lang]/index.astro @@ -20,222 +20,7 @@ const t = useTranslations(lang) - +