advent22/ui/src/plugins/store.ts

234 lines
6.3 KiB
TypeScript
Raw Normal View History

2023-11-09 23:08:35 +00:00
import { Credentials, DoorSaved, SiteConfigModel } from "@/lib/api";
2023-11-09 23:14:19 +00:00
import { Door } from "@/lib/door";
2023-11-04 15:58:31 +00:00
import { Advent22 } from "@/plugins/advent22";
2023-11-09 23:08:35 +00:00
import { RemovableRef, useLocalStorage } from "@vueuse/core";
import { AxiosBasicCredentials, AxiosError } from "axios";
import { toast } from "bulma-toast";
import { acceptHMRUpdate, defineStore } from "pinia";
2023-11-05 21:57:50 +00:00
declare global {
interface Navigator {
readonly msMaxTouchPoints: number;
}
}
2023-11-09 23:08:35 +00:00
type State = {
advent22: Advent22;
api_creds: RemovableRef<Credentials>;
is_initialized: boolean;
on_initialized: (() => void)[];
2023-11-09 23:08:35 +00:00
is_touch_device: boolean;
is_admin: boolean;
site_config: SiteConfigModel;
calendar_background_image: string | undefined;
2023-11-09 23:08:35 +00:00
calendar_aspect_ratio: number;
2023-11-09 23:14:19 +00:00
user_doors: Door[];
2023-11-09 23:08:35 +00:00
next_door_target: number | null;
};
export const advent22Store = defineStore({
id: "advent22",
2023-11-09 23:08:35 +00:00
state: (): State => ({
advent22: new Advent22(),
api_creds: useLocalStorage("advent22/auth", ["", ""]),
is_initialized: false,
on_initialized: [],
2023-11-05 21:57:50 +00:00
is_touch_device:
window.matchMedia("(any-hover: none)").matches ||
"ontouchstart" in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0,
2023-11-03 14:40:44 +00:00
is_admin: false,
site_config: {
title: document.title,
subtitle: "",
2023-11-03 14:40:44 +00:00
content: "",
footer: "",
2023-11-09 23:08:35 +00:00
},
calendar_background_image: undefined,
2023-11-05 22:36:58 +00:00
calendar_aspect_ratio: 1,
2023-11-09 23:08:35 +00:00
user_doors: [],
next_door_target: null,
2023-11-03 14:40:44 +00:00
}),
2023-11-02 00:37:00 +00:00
getters: {
axios_creds: (state): AxiosBasicCredentials => {
const [username, password] = state.api_creds;
return { username: username, password: password };
},
},
actions: {
2023-11-09 23:08:35 +00:00
init(): void {
2023-11-24 11:57:33 +00:00
this.update()
.then(() => {
this.is_initialized = true;
for (const callback of this.on_initialized) callback();
2023-11-03 14:40:44 +00:00
})
.catch(this.alert_user_error);
},
format_user_error([reason, endpoint]: [unknown, string]): string {
let msg =
"Unbekannter Fehler, bitte wiederholen! Besteht das Problem länger, bitte Admin benachrichtigen!";
let code = "U";
const result = () => `${msg} (Fehlercode: ${code}/${endpoint})`;
if (!(reason instanceof AxiosError)) return result();
switch (reason.code) {
case "ECONNABORTED":
// API unerreichbar
msg =
"API antwortet nicht, bitte später wiederholen! Besteht das Problem länger, bitte Admin benachrichtigen!";
code = "D";
break;
case "ERR_NETWORK":
// Netzwerk nicht verbunden
msg = "Sieht aus, als sei deine Netzwerkverbindung gestört.";
code = "N";
break;
default:
if (reason.response === undefined) return result();
switch (reason.response.status) {
case 401:
// UNAUTHORIZED
msg = "Netter Versuch :)";
code = "A";
break;
case 422:
// UNPROCESSABLE ENTITY
msg = "Funktion ist kaputt, bitte Admin benachrichtigen!";
code = "I";
break;
default:
// HTTP
code = `H${reason.response.status}`;
break;
}
break;
}
return result();
},
alert_user_error(param: [unknown, string]): void {
toast({
message: this.format_user_error(param),
type: "is-danger",
});
2023-11-03 14:40:44 +00:00
},
2023-11-24 11:57:33 +00:00
update(): Promise<void> {
return new Promise((resolve, reject) => {
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;
if (link.parentElement === null)
document.getElementsByTagName("head")[0].appendChild(link);
})
.catch(() => {});
Promise.all([
this.update_is_admin(),
this.advent22.api_get<SiteConfigModel>("user/site_config"),
this.advent22.api_get_blob("user/background_image"),
this.advent22.api_get<DoorSaved[]>("user/doors"),
this.advent22.api_get<number | null>("user/next_door"),
])
.then(
([
is_admin,
site_config,
background_image,
user_doors,
next_door,
]) => {
is_admin; // discard value
document.title = site_config.title;
if (site_config.subtitle !== "")
document.title += " " + site_config.subtitle;
this.site_config = site_config;
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));
}
if (next_door !== null)
this.next_door_target = Date.now() + next_door;
resolve();
},
)
.catch(reject);
});
},
when_initialized(callback: () => void): void {
if (this.is_initialized) {
callback();
} else {
this.on_initialized.push(callback);
}
},
update_is_admin(): Promise<boolean> {
2023-11-09 23:08:35 +00:00
return new Promise((resolve, reject) => {
2023-11-04 15:58:31 +00:00
this.advent22
.api_get<boolean>("admin/is_admin")
2023-11-02 00:37:00 +00:00
.then((is_admin) => {
this.is_admin = is_admin;
resolve(is_admin);
})
.catch(reject);
});
},
login(creds: Credentials): Promise<boolean> {
this.api_creds = creds;
return this.update_is_admin();
},
2023-11-02 00:37:00 +00:00
logout(): Promise<boolean> {
2023-11-05 21:57:50 +00:00
return this.login(["", ""]);
},
toggle_touch_device(): void {
this.is_touch_device = !this.is_touch_device;
},
2023-11-05 22:36:58 +00:00
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;
},
},
});
if (import.meta.webpackHot) {
import.meta.webpackHot.accept(
acceptHMRUpdate(advent22Store, import.meta.webpackHot),
);
}