Compare commits
No commits in common. "55081b24d81669aea40d4c4d7f0129080a10bc0f" and "0c01237456aaaccc2f55315cc489760cbab98fcd" have entirely different histories.
55081b24d8
...
0c01237456
29 changed files with 298 additions and 264 deletions
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
import { advent22Store } from "./lib/store";
|
import { advent22Store } from "./plugins/store";
|
||||||
|
|
||||||
import AdminView from "./components/admin/AdminView.vue";
|
import AdminView from "./components/admin/AdminView.vue";
|
||||||
import AdminButton from "./components/AdminButton.vue";
|
import AdminButton from "./components/AdminButton.vue";
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Credentials } from "@/lib/model";
|
import { Credentials } from "@/lib/api";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import BulmaButton from "./bulma/Button.vue";
|
import BulmaButton from "./bulma/Button.vue";
|
||||||
|
|
|
@ -46,8 +46,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import MultiModal from "./MultiModal.vue";
|
import MultiModal from "./MultiModal.vue";
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import BulmaButton from "./bulma/Button.vue";
|
import BulmaButton from "./bulma/Button.vue";
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import Calendar from "./Calendar.vue";
|
import Calendar from "./Calendar.vue";
|
||||||
|
|
|
@ -46,8 +46,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { objForEach } from "@/lib/helpers";
|
import { NumStrDict, objForEach } from "@/lib/api";
|
||||||
import { NumStrDict } from "@/lib/model";
|
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import MultiModal from "../MultiModal.vue";
|
import MultiModal from "../MultiModal.vue";
|
||||||
|
|
|
@ -184,8 +184,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AdminConfigModel, Credentials, DoorSaved } from "@/lib/model";
|
import { AdminConfigModel, Credentials, DoorSaved } from "@/lib/api";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { DoorSaved } from "@/lib/model";
|
import { DoorSaved } from "@/lib/api";
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import { toast } from "bulma-toast";
|
import { toast } from "bulma-toast";
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import SVGRect from "./SVGRect.vue";
|
import SVGRect from "./SVGRect.vue";
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Rectangle } from "@/lib/rects/rectangle";
|
import { Rectangle } from "@/lib/rectangle";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
type BulmaVariant =
|
type BulmaVariant =
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Vector2D } from "@/lib/rects/vector2d";
|
import { Vector2D } from "@/lib/vector2d";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
function get_event_thous(event: MouseEvent): Vector2D {
|
function get_event_thous(event: MouseEvent): Vector2D {
|
||||||
|
|
|
@ -24,9 +24,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { Rectangle } from "@/lib/rects/rectangle";
|
import { Rectangle } from "@/lib/rectangle";
|
||||||
import { Vector2D } from "@/lib/rects/vector2d";
|
import { Vector2D } from "@/lib/vector2d";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import CalendarDoor from "../calendar/CalendarDoor.vue";
|
import CalendarDoor from "../calendar/CalendarDoor.vue";
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/plugins/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import DoorCanvas from "./DoorCanvas.vue";
|
import DoorCanvas from "./DoorCanvas.vue";
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/door";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import SVGRect from "../calendar/SVGRect.vue";
|
import SVGRect from "../calendar/SVGRect.vue";
|
||||||
|
|
10
ui/src/d.ts/shims-advent22.d.ts
vendored
Normal file
10
ui/src/d.ts/shims-advent22.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Advent22 } from "@/plugins/advent22";
|
||||||
|
|
||||||
|
declare module "@vue/runtime-core" {
|
||||||
|
// bind to `this` keyword
|
||||||
|
interface ComponentCustomProperties {
|
||||||
|
$advent22: Advent22;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
|
@ -1,82 +1,71 @@
|
||||||
import axios, {
|
export interface AdminConfigModel {
|
||||||
AxiosBasicCredentials,
|
solution: {
|
||||||
type AxiosRequestConfig,
|
value: string;
|
||||||
type Method,
|
whitespace: string;
|
||||||
type RawAxiosRequestHeaders,
|
special_chars: string;
|
||||||
} from "axios";
|
case: string;
|
||||||
import { APIError } from "./api_error";
|
clean: string;
|
||||||
|
|
||||||
interface Params {
|
|
||||||
endpoint: string;
|
|
||||||
method?: Method;
|
|
||||||
data?: unknown;
|
|
||||||
headers?: RawAxiosRequestHeaders;
|
|
||||||
config?: AxiosRequestConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class API {
|
|
||||||
private static get api_baseurl(): string {
|
|
||||||
// in production mode, return "proto://hostname/api"
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
|
||||||
return `${window.location.protocol}//${window.location.host}/api`;
|
|
||||||
} else if (process.env.NODE_ENV !== "development") {
|
|
||||||
// not in prouction or development mode
|
|
||||||
console.warn("Unexpected NODE_ENV value");
|
|
||||||
}
|
|
||||||
|
|
||||||
// in development mode, return "proto://hostname:8000/api"
|
|
||||||
return `${window.location.protocol}//${window.location.hostname}:8000/api`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly axios = axios.create({
|
|
||||||
timeout: 10e3,
|
|
||||||
baseURL: this.api_baseurl,
|
|
||||||
});
|
|
||||||
|
|
||||||
private static readonly storage_key = "advent22/credentials";
|
|
||||||
|
|
||||||
public static set creds(value: AxiosBasicCredentials | null) {
|
|
||||||
if (value === null) {
|
|
||||||
localStorage.removeItem(this.storage_key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
localStorage.setItem(this.storage_key, JSON.stringify(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static get creds(): AxiosBasicCredentials | undefined {
|
|
||||||
const auth_json = localStorage.getItem(this.storage_key);
|
|
||||||
if (auth_json !== null) return JSON.parse(auth_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static get_axios_config({
|
|
||||||
endpoint,
|
|
||||||
method = "GET",
|
|
||||||
data,
|
|
||||||
headers = {},
|
|
||||||
config = {},
|
|
||||||
}: Params): AxiosRequestConfig {
|
|
||||||
return {
|
|
||||||
url: endpoint,
|
|
||||||
method: method,
|
|
||||||
data: data,
|
|
||||||
auth: this.creds,
|
|
||||||
headers: headers,
|
|
||||||
...config,
|
|
||||||
};
|
};
|
||||||
}
|
puzzle: {
|
||||||
|
first: string;
|
||||||
|
next: string | null;
|
||||||
|
last: string;
|
||||||
|
end: string;
|
||||||
|
seed: string;
|
||||||
|
extra_days: number[];
|
||||||
|
skip_empty: boolean;
|
||||||
|
};
|
||||||
|
calendar: {
|
||||||
|
config_file: string;
|
||||||
|
background: string;
|
||||||
|
favicon: string;
|
||||||
|
};
|
||||||
|
image: {
|
||||||
|
size: number;
|
||||||
|
border: number;
|
||||||
|
};
|
||||||
|
fonts: { file: string; size: number }[];
|
||||||
|
redis: {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
db: number;
|
||||||
|
protocol: number;
|
||||||
|
};
|
||||||
|
webdav: {
|
||||||
|
url: string;
|
||||||
|
cache_ttl: number;
|
||||||
|
config_file: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static async request<T = string>(p: Params): Promise<T>;
|
export interface SiteConfigModel {
|
||||||
public static async request<T = string>(p: string): Promise<T>;
|
title: string;
|
||||||
public static async request<T = string>(p: Params | string): Promise<T> {
|
subtitle: string;
|
||||||
if (typeof p === "string") p = { endpoint: p };
|
content: string;
|
||||||
|
footer: string;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
export interface NumStrDict {
|
||||||
const response = await this.axios.request<T>(this.get_axios_config(p));
|
[key: number]: string;
|
||||||
return response.data;
|
}
|
||||||
} catch (reason) {
|
|
||||||
console.error(`Failed to query ${p.endpoint}: ${reason}`);
|
export interface DoorSaved {
|
||||||
throw new APIError(reason, p.endpoint);
|
day: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Credentials = [username: string, password: string];
|
||||||
|
|
||||||
|
export function objForEach<T>(
|
||||||
|
obj: T,
|
||||||
|
f: (k: keyof T, v: T[keyof T]) => void,
|
||||||
|
): void {
|
||||||
|
for (const k in obj) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
||||||
|
f(k, obj[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
import { AxiosError } from "axios";
|
|
||||||
import { toast } from "bulma-toast";
|
|
||||||
|
|
||||||
export class APIError extends Error {
|
|
||||||
reason: unknown;
|
|
||||||
axios_error: AxiosError | null = null;
|
|
||||||
|
|
||||||
constructor(reason: unknown, endpoint: string) {
|
|
||||||
super(endpoint); // sets this.message to the endpoint
|
|
||||||
this.reason = reason;
|
|
||||||
Object.setPrototypeOf(this, APIError.prototype);
|
|
||||||
|
|
||||||
if (reason instanceof AxiosError) {
|
|
||||||
this.axios_error = reason;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public format(): string {
|
|
||||||
let msg =
|
|
||||||
"Unbekannter Fehler, bitte wiederholen! Besteht das Problem länger, bitte Admin benachrichtigen!";
|
|
||||||
let code = "U";
|
|
||||||
const result = () => `${msg} (Fehlercode: ${code}/${this.message})`;
|
|
||||||
|
|
||||||
if (this.axios_error === null) return result();
|
|
||||||
|
|
||||||
switch (this.axios_error.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 (this.axios_error.response === undefined) return result();
|
|
||||||
|
|
||||||
switch (this.axios_error.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${this.axios_error.response.status}`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result();
|
|
||||||
}
|
|
||||||
|
|
||||||
public alert() {
|
|
||||||
toast({
|
|
||||||
message: this.format(),
|
|
||||||
type: "is-danger",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DoorSaved } from "../model";
|
import { DoorSaved } from "./api";
|
||||||
import { Rectangle } from "./rectangle";
|
import { Rectangle } from "./rectangle";
|
||||||
import { Vector2D } from "./vector2d";
|
import { Vector2D } from "./vector2d";
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
export function objForEach<T>(
|
|
||||||
obj: T,
|
|
||||||
f: (k: keyof T, v: T[keyof T]) => void,
|
|
||||||
): void {
|
|
||||||
for (const k in obj) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
||||||
f(k, obj[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
export interface AdminConfigModel {
|
|
||||||
solution: {
|
|
||||||
value: string;
|
|
||||||
whitespace: string;
|
|
||||||
special_chars: string;
|
|
||||||
case: string;
|
|
||||||
clean: string;
|
|
||||||
};
|
|
||||||
puzzle: {
|
|
||||||
first: string;
|
|
||||||
next: string | null;
|
|
||||||
last: string;
|
|
||||||
end: string;
|
|
||||||
seed: string;
|
|
||||||
extra_days: number[];
|
|
||||||
skip_empty: boolean;
|
|
||||||
};
|
|
||||||
calendar: {
|
|
||||||
config_file: string;
|
|
||||||
background: string;
|
|
||||||
favicon: string;
|
|
||||||
};
|
|
||||||
image: {
|
|
||||||
size: number;
|
|
||||||
border: number;
|
|
||||||
};
|
|
||||||
fonts: { file: string; size: number }[];
|
|
||||||
redis: {
|
|
||||||
host: string;
|
|
||||||
port: number;
|
|
||||||
db: number;
|
|
||||||
protocol: number;
|
|
||||||
};
|
|
||||||
webdav: {
|
|
||||||
url: string;
|
|
||||||
cache_ttl: number;
|
|
||||||
config_file: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SiteConfigModel {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
content: string;
|
|
||||||
footer: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NumStrDict {
|
|
||||||
[key: number]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DoorSaved {
|
|
||||||
day: number;
|
|
||||||
x1: number;
|
|
||||||
y1: number;
|
|
||||||
x2: number;
|
|
||||||
y2: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageData {
|
|
||||||
height: number;
|
|
||||||
width: number;
|
|
||||||
aspect_ratio: number;
|
|
||||||
data_url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Credentials = [username: string, password: string];
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { advent22Store } from "@/lib/store";
|
|
||||||
import { Advent22Plugin } from "@/plugins/advent22";
|
import { Advent22Plugin } from "@/plugins/advent22";
|
||||||
import { FontAwesomePlugin } from "@/plugins/fontawesome";
|
import { FontAwesomePlugin } from "@/plugins/fontawesome";
|
||||||
|
import { advent22Store } from "@/plugins/store";
|
||||||
import * as bulmaToast from "bulma-toast";
|
import * as bulmaToast from "bulma-toast";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
|
110
ui/src/plugins/advent22.ts
Normal file
110
ui/src/plugins/advent22.ts
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import axios, { AxiosInstance, ResponseType } from "axios";
|
||||||
|
import { App, Plugin } from "vue";
|
||||||
|
import { advent22Store } from "./store";
|
||||||
|
|
||||||
|
export class Advent22 {
|
||||||
|
private axios: AxiosInstance;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.axios = axios.create({
|
||||||
|
timeout: 10e3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get api_baseurl(): string {
|
||||||
|
// in production mode, return "//host/api"
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
return `//${window.location.host}/api`;
|
||||||
|
} else if (process.env.NODE_ENV !== "development") {
|
||||||
|
// not in prouction or development mode
|
||||||
|
console.warn("Unexpected NODE_ENV value");
|
||||||
|
}
|
||||||
|
|
||||||
|
// in development mode, return "//hostname:8000/api"
|
||||||
|
return `//${window.location.hostname}:8000/api`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public name_door(day: number): string {
|
||||||
|
return `Türchen ${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public api_url(): string;
|
||||||
|
public api_url(endpoint: string): string;
|
||||||
|
public api_url(endpoint?: string): string {
|
||||||
|
if (endpoint === undefined) {
|
||||||
|
return this.api_baseurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (endpoint.startsWith("/")) {
|
||||||
|
endpoint = endpoint.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${this.api_baseurl}/${endpoint}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _api_get<T>(endpoint: string): Promise<T>;
|
||||||
|
private _api_get<T>(endpoint: string, responseType: ResponseType): Promise<T>;
|
||||||
|
private _api_get<T>(
|
||||||
|
endpoint: string,
|
||||||
|
responseType: ResponseType = "json",
|
||||||
|
): Promise<T> {
|
||||||
|
const req_config = {
|
||||||
|
auth: advent22Store().axios_creds,
|
||||||
|
responseType: responseType,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
this.axios
|
||||||
|
.get<T>(this.api_url(endpoint), req_config)
|
||||||
|
.then((response) => resolve(response.data))
|
||||||
|
.catch((reason) => {
|
||||||
|
console.error(`Failed to query ${endpoint}: ${reason}`);
|
||||||
|
reject([reason, endpoint]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public api_get<T>(endpoint: string): Promise<T> {
|
||||||
|
return this._api_get<T>(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public api_get_blob(endpoint: string): Promise<string> {
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
this._api_get<Blob>(endpoint, "blob")
|
||||||
|
.then((data: Blob) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(data);
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (typeof reader.result === "string") {
|
||||||
|
resolve(reader.result);
|
||||||
|
} else {
|
||||||
|
reject(["failed data url", endpoint]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public api_put(endpoint: string, data: unknown): Promise<void> {
|
||||||
|
const req_config = {
|
||||||
|
auth: advent22Store().axios_creds,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this.axios
|
||||||
|
.put(this.api_url(endpoint), data, req_config)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch((reason) => {
|
||||||
|
console.error(`Failed to query ${endpoint}: ${reason}`);
|
||||||
|
reject([reason, endpoint]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Advent22Plugin: Plugin = {
|
||||||
|
install(app: App) {
|
||||||
|
app.config.globalProperties.$advent22 = new Advent22();
|
||||||
|
},
|
||||||
|
};
|
|
@ -4,11 +4,11 @@ import { App, Plugin } from "vue";
|
||||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
|
|
||||||
/* import specific icons */
|
/* import specific icons */
|
||||||
// import { fab } from "@fortawesome/free-brands-svg-icons";
|
import { fab } from "@fortawesome/free-brands-svg-icons";
|
||||||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
/* add icons to the library */
|
/* add icons to the library */
|
||||||
library.add(fas);
|
library.add(fas, fab);
|
||||||
|
|
||||||
/* import font awesome icon component */
|
/* import font awesome icon component */
|
||||||
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { Credentials, DoorSaved, SiteConfigModel } from "@/lib/api";
|
||||||
|
import { Door } from "@/lib/door";
|
||||||
|
import { Advent22 } from "@/plugins/advent22";
|
||||||
|
import { RemovableRef, useLocalStorage } from "@vueuse/core";
|
||||||
|
import { AxiosBasicCredentials, AxiosError } from "axios";
|
||||||
|
import { toast } from "bulma-toast";
|
||||||
import { acceptHMRUpdate, defineStore } from "pinia";
|
import { acceptHMRUpdate, defineStore } from "pinia";
|
||||||
import { API } from "./api";
|
|
||||||
import { Credentials, DoorSaved, SiteConfigModel } from "./model";
|
|
||||||
import { Door } from "./rects/door";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Navigator {
|
interface Navigator {
|
||||||
|
@ -10,6 +13,9 @@ declare global {
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
|
advent22: Advent22;
|
||||||
|
api_creds: RemovableRef<Credentials>;
|
||||||
|
is_initialized: boolean;
|
||||||
on_initialized: (() => void)[];
|
on_initialized: (() => void)[];
|
||||||
is_touch_device: boolean;
|
is_touch_device: boolean;
|
||||||
is_admin: boolean;
|
is_admin: boolean;
|
||||||
|
@ -24,6 +30,9 @@ export const advent22Store = defineStore({
|
||||||
id: "advent22",
|
id: "advent22",
|
||||||
|
|
||||||
state: (): State => ({
|
state: (): State => ({
|
||||||
|
advent22: new Advent22(),
|
||||||
|
api_creds: useLocalStorage("advent22/auth", ["", ""]),
|
||||||
|
is_initialized: false,
|
||||||
on_initialized: [],
|
on_initialized: [],
|
||||||
is_touch_device:
|
is_touch_device:
|
||||||
window.matchMedia("(any-hover: none)").matches ||
|
window.matchMedia("(any-hover: none)").matches ||
|
||||||
|
@ -43,14 +52,81 @@ export const advent22Store = defineStore({
|
||||||
next_door_target: null,
|
next_door_target: null,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
axios_creds: (state): AxiosBasicCredentials => {
|
||||||
|
const [username, password] = state.api_creds;
|
||||||
|
return { username: username, password: password };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
init(): void {
|
init(): void {
|
||||||
this.update().then(() => this.on_initialized.forEach((fn) => fn()));
|
this.update()
|
||||||
|
.then(() => {
|
||||||
|
this.is_initialized = true;
|
||||||
|
for (const callback of this.on_initialized) callback();
|
||||||
|
})
|
||||||
|
.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",
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
update(): Promise<void> {
|
update(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
API.request("user/favicon");
|
|
||||||
this.advent22
|
this.advent22
|
||||||
.api_get_blob("user/favicon")
|
.api_get_blob("user/favicon")
|
||||||
.then((favicon_src) => {
|
.then((favicon_src) => {
|
||||||
|
@ -128,7 +204,7 @@ export const advent22Store = defineStore({
|
||||||
},
|
},
|
||||||
|
|
||||||
login(creds: Credentials): Promise<boolean> {
|
login(creds: Credentials): Promise<boolean> {
|
||||||
API.creds = { username: creds[0], password: creds[1] };
|
this.api_creds = creds;
|
||||||
return this.update_is_admin();
|
return this.update_is_admin();
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import { Rectangle } from "@/lib/rects/rectangle";
|
import { Rectangle } from "@/lib/rectangle";
|
||||||
import { Vector2D } from "@/lib/rects/vector2d";
|
import { Vector2D } from "@/lib/vector2d";
|
||||||
|
|
||||||
describe("Rectangle Tests", () => {
|
describe("Rectangle Tests", () => {
|
||||||
const v1 = new Vector2D(1, 2);
|
const v1 = new Vector2D(1, 2);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
|
||||||
import { Vector2D } from "@/lib/rects/vector2d";
|
import { Vector2D } from "@/lib/vector2d";
|
||||||
|
|
||||||
describe("Vector2D Tests", () => {
|
describe("Vector2D Tests", () => {
|
||||||
const v = new Vector2D(1, 2);
|
const v = new Vector2D(1, 2);
|
||||||
|
|
Loading…
Reference in a new issue