diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index cf326ae..855fb8c 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -2,33 +2,34 @@ module.exports = { root: true, env: { - node: true + node: true, }, - 'extends': [ - 'plugin:vue/vue3-essential', - 'eslint:recommended', - '@vue/typescript/recommended' + extends: [ + "plugin:vue/vue3-essential", + "eslint:recommended", + "@vue/typescript/recommended", ], parserOptions: { - ecmaVersion: 2020 + ecmaVersion: 2020, }, rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' + "no-empty": "off", + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", }, overrides: [ { files: [ - '**/__tests__/*.{j,t}s?(x)', - '**/tests/unit/**/*.spec.{j,t}s?(x)' + "**/__tests__/*.{j,t}s?(x)", + "**/tests/unit/**/*.spec.{j,t}s?(x)", ], env: { - mocha: true - } - } - ] -} + mocha: true, + }, + }, + ], +}; diff --git a/ui/src/components/Calendar.vue b/ui/src/components/Calendar.vue index 9082ee3..dcb5860 100644 --- a/ui/src/components/Calendar.vue +++ b/ui/src/components/Calendar.vue @@ -29,7 +29,7 @@
- +
- +
- +
diff --git a/ui/src/lib/helpers.ts b/ui/src/lib/helpers.ts index 9d9850b..d9987a0 100644 --- a/ui/src/lib/helpers.ts +++ b/ui/src/lib/helpers.ts @@ -1,3 +1,5 @@ +import { APIError } from "./api_error"; + export function objForEach( obj: T, f: (k: keyof T, v: T[keyof T]) => void, @@ -8,3 +10,20 @@ export function objForEach( } } } + +export type Loading = T | "loading" | "error"; + +export function loading_success(o: Loading): o is T { + if (o === "loading") return false; + if (o === "error") return false; + + return true; +} + +export function handle_error(error: unknown) { + if (error instanceof APIError) { + error.alert(); + } else { + console.error(error); + } +} diff --git a/ui/src/lib/model.ts b/ui/src/lib/model.ts index 4e06814..2a0b0e9 100644 --- a/ui/src/lib/model.ts +++ b/ui/src/lib/model.ts @@ -60,7 +60,6 @@ export interface DoorSaved { export interface ImageData { height: number; width: number; - aspect_ratio: number; data_url: string; } diff --git a/ui/src/lib/store.ts b/ui/src/lib/store.ts index b56a8f1..e12ef2a 100644 --- a/ui/src/lib/store.ts +++ b/ui/src/lib/store.ts @@ -1,6 +1,7 @@ import { acceptHMRUpdate, defineStore } from "pinia"; import { API } from "./api"; -import { Credentials, DoorSaved, SiteConfigModel } from "./model"; +import { Loading, loading_success } from "./helpers"; +import { Credentials, DoorSaved, ImageData, SiteConfigModel } from "./model"; import { Door } from "./rects/door"; declare global { @@ -13,9 +14,8 @@ type State = { on_initialized: (() => void)[]; is_touch_device: boolean; is_admin: boolean; - site_config: SiteConfigModel; - calendar_background_image: string | undefined; - calendar_aspect_ratio: number; + site_config: Loading; + background_image: Loading; user_doors: Door[]; next_door_target: number | null; }; @@ -31,14 +31,8 @@ export const advent22Store = defineStore({ navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0, is_admin: false, - site_config: { - title: document.title, - subtitle: "", - content: "", - footer: "", - }, - calendar_background_image: undefined, - calendar_aspect_ratio: 1, + site_config: "loading", + background_image: "loading", user_doors: [], next_door_target: null, }), @@ -48,83 +42,58 @@ export const advent22Store = defineStore({ this.update().then(() => this.on_initialized.forEach((fn) => fn())); }, - update(): Promise { - return new Promise((resolve, reject) => { - API.request("user/favicon"); - this.advent22 - .api_get_blob("user/favicon") - .then((favicon_src) => { - const link: HTMLLinkElement = - document.querySelector("link[rel*='icon']") || - document.createElement("link"); - link.rel = "shortcut icon"; - link.type = "image/x-icon"; - link.href = favicon_src; + async update(): Promise { + try { + const favicon = await API.request("user/favicon"); - if (link.parentElement === null) - document.getElementsByTagName("head")[0].appendChild(link); - }) - .catch(() => {}); + const link: HTMLLinkElement = + document.querySelector("link[rel*='icon']") || + document.createElement("link"); + link.rel = "shortcut icon"; + link.type = "image/x-icon"; + link.href = favicon.data_url; - Promise.all([ + if (link.parentElement === null) + document.getElementsByTagName("head")[0].appendChild(link); + } catch {} + + const [is_admin, site_config, background_image, user_doors, next_door] = + await Promise.all([ this.update_is_admin(), - this.advent22.api_get("user/site_config"), - this.advent22.api_get_blob("user/background_image"), - this.advent22.api_get("user/doors"), - this.advent22.api_get("user/next_door"), - ]) - .then( - ([ - is_admin, - site_config, - background_image, - user_doors, - next_door, - ]) => { - is_admin; // discard value + API.request("user/site_config"), + API.request("user/background_image"), + API.request("user/doors"), + API.request("user/next_door"), + ]); + is_admin; // discard value - document.title = site_config.title; + document.title = site_config.title; - if (site_config.subtitle !== "") - document.title += " – " + site_config.subtitle; + if (site_config.subtitle !== "") + document.title += " – " + site_config.subtitle; - this.site_config = site_config; + this.site_config = site_config; + this.background_image = background_image; - this.calendar_background_image = background_image; + this.user_doors.length = 0; + for (const door_saved of user_doors) { + this.user_doors.push(Door.load(door_saved)); + } - this.user_doors.length = 0; - for (const door_saved of user_doors) { - this.user_doors.push(Door.load(door_saved)); - } - - if (next_door !== null) - this.next_door_target = Date.now() + next_door; - - resolve(); - }, - ) - .catch(reject); - }); + if (next_door !== null) this.next_door_target = Date.now() + next_door; }, when_initialized(callback: () => void): void { - if (this.is_initialized) { + if (loading_success(this.site_config)) { callback(); } else { this.on_initialized.push(callback); } }, - update_is_admin(): Promise { - return new Promise((resolve, reject) => { - this.advent22 - .api_get("admin/is_admin") - .then((is_admin) => { - this.is_admin = is_admin; - resolve(is_admin); - }) - .catch(reject); - }); + async update_is_admin(): Promise { + this.is_admin = await API.request("admin/is_admin"); + return this.is_admin; }, login(creds: Credentials): Promise { @@ -139,14 +108,6 @@ export const advent22Store = defineStore({ toggle_touch_device(): void { this.is_touch_device = !this.is_touch_device; }, - - set_calendar_aspect_ratio(rect: DOMRectReadOnly): void { - const result = rect.width / rect.height; - - // filter suspicious results - if (result !== 0 && isFinite(result) && !isNaN(result)) - this.calendar_aspect_ratio = result; - }, }, });