first part of refactoring

- use `ImageData` and `Loading` types for image transfer
-  consolidate library directory
- remove `advent22` plugin in favor of static `API` class
This commit is contained in:
Jörn-Michael Miehe 2024-08-26 14:14:07 +00:00
parent e3e19a0572
commit 109dec73d2
14 changed files with 113 additions and 72 deletions

View file

@ -7,7 +7,18 @@
</section>
<section class="section px-3">
<div class="container">
<progress
v-if="store.background_image === 'loading'"
class="progress is-primary"
max="100"
/>
<div
v-else-if="store.background_image === 'error'"
class="notification is-danger"
>
Hintergrundbild konnte nicht geladen werden
</div>
<div v-else class="container">
<AdminView v-if="store.is_admin" />
<UserView v-else />
</div>

View file

@ -15,6 +15,7 @@ import { Credentials } from "@/lib/model";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
import { APIError } from "@/lib/api_error";
import BulmaButton from "./bulma/Button.vue";
import LoginModal from "./LoginModal.vue";
@ -44,7 +45,7 @@ export default class extends Vue {
this.store
.login(creds)
.catch(this.store.alert_user_error)
.catch((error) => APIError.alert(error))
.finally(() => (this.is_busy = false));
}

View file

@ -29,14 +29,14 @@
<figure>
<div class="image is-unselectable">
<img :src="store.background_image" />
<img :src="_ensure_loaded(store.background_image).data_url" />
<ThouCanvas>
<CalendarDoor
v-for="(door, index) in doors"
:key="`door-${index}`"
:door="door"
:visible="store.is_touch_device"
:title="$advent22.name_door(door.day)"
:title="_name_door(door.day)"
@click="door_click(door.day)"
style="cursor: pointer"
/>
@ -46,6 +46,10 @@
</template>
<script lang="ts">
import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error";
import { ensure_loaded, Loading, name_door } from "@/lib/helpers";
import { ImageData } from "@/lib/model";
import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
@ -96,26 +100,32 @@ export default class extends Vue {
});
}
public door_click(day: number) {
public async door_click(day: number) {
if (this.toast_timeout !== undefined) clearTimeout(this.toast_timeout);
this.toast?.hide();
if (this.multi_modal === undefined) return;
this.multi_modal.show_progress();
this.$advent22
.api_get_blob(`user/image_${day}`)
.then((image_src) => {
this.multi_modal!.show_image(image_src, this.$advent22.name_door(day));
})
.catch((error) => {
this.store.alert_user_error(error);
try {
const day_image = await API.request<ImageData>(`user/image_${day}`);
this.multi_modal!.show_image(day_image.data_url, name_door(day));
} catch (error) {
APIError.alert(error);
this.multi_modal!.hide();
});
}
}
public beforeUnmount(): void {
this.toast?.hide();
}
public _ensure_loaded<T>(o: Loading<T>): T {
return ensure_loaded(o);
}
public _name_door(day: number): string {
return name_door(day);
}
}
</script>

View file

@ -1,5 +1,4 @@
<template>
<template v-if="store.is_initialized === true">
<Calendar :doors="store.user_doors" />
<hr />
<div class="content" v-html="store.site_config.content" />
@ -18,8 +17,6 @@
<CountDown :until="store.next_door_target" />
</template>
</div>
</template>
<progress v-else class="progress is-primary" max="100" />
</template>
<script lang="ts">

View file

@ -46,8 +46,9 @@
</template>
<script lang="ts">
import { objForEach } from "@/lib/helpers";
import { NumStrDict } from "@/lib/model";
import { API } from "@/lib/api";
import { name_door, objForEach } from "@/lib/helpers";
import { ImageData, NumStrDict } from "@/lib/model";
import { Options, Vue } from "vue-class-component";
import MultiModal from "../MultiModal.vue";
@ -77,8 +78,8 @@ export default class extends Vue {
public on_open(ready: () => void, fail: () => void): void {
Promise.all([
this.$advent22.api_get<NumStrDict>("admin/day_parts"),
this.$advent22.api_get<NumStrDict>("admin/day_image_names"),
API.request<NumStrDict>("admin/day_parts"),
API.request<NumStrDict>("admin/day_image_names"),
])
.then(([day_parts, day_image_names]) => {
const _ensure_day_in_data = (day: number) => {
@ -102,16 +103,16 @@ export default class extends Vue {
.catch(fail);
}
public door_click(day: number) {
public async door_click(day: number) {
if (this.multi_modal === undefined) return;
this.multi_modal.show_progress();
this.$advent22
.api_get_blob(`user/image_${day}`)
.then((image_src) =>
this.multi_modal!.show_image(image_src, this.$advent22.name_door(day)),
)
.catch(() => this.multi_modal!.hide());
try {
const day_image = await API.request<ImageData>(`user/image_${day}`);
this.multi_modal!.show_image(day_image.data_url, name_door(day));
} catch (error) {
this.multi_modal!.hide();
}
}
}
</script>

View file

@ -189,6 +189,7 @@ import { advent22Store } from "@/lib/store";
import { DateTime } from "luxon";
import { Options, Vue } from "vue-class-component";
import { API } from "@/lib/api";
import BulmaDrawer from "../bulma/Drawer.vue";
import BulmaSecret from "../bulma/Secret.vue";
import CountDown from "../CountDown.vue";
@ -256,8 +257,8 @@ export default class extends Vue {
public on_open(ready: () => void, fail: () => void): void {
Promise.all([
this.store.update(),
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
this.$advent22.api_get<DoorSaved[]>("admin/doors"),
API.request<AdminConfigModel>("admin/config_model"),
API.request<DoorSaved[]>("admin/doors"),
])
.then(([store_update, admin_config_model, doors]) => {
store_update; // discard value
@ -271,15 +272,13 @@ export default class extends Vue {
}
public load_dav_credentials(): void {
this.$advent22
.api_get<Credentials>("admin/dav_credentials")
API.request<Credentials>("admin/dav_credentials")
.then((creds) => (this.dav_credentials = creds))
.catch(() => {});
}
public load_ui_credentials(): void {
this.$advent22
.api_get<Credentials>("admin/ui_credentials")
API.request<Credentials>("admin/ui_credentials")
.then((creds) => (this.ui_credentials = creds))
.catch(() => {});
}

View file

@ -74,6 +74,8 @@ import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error";
import { toast } from "bulma-toast";
import Calendar from "../Calendar.vue";
import BulmaBreadcrumbs, { Step } from "../bulma/Breadcrumbs.vue";
@ -107,8 +109,7 @@ export default class extends Vue {
private load_doors(): Promise<void> {
return new Promise<void>((resolve, reject) => {
this.$advent22
.api_get<DoorSaved[]>("admin/doors")
API.request<DoorSaved[]>("admin/doors")
.then((data) => {
this.doors.length = 0;
@ -119,7 +120,7 @@ export default class extends Vue {
resolve();
})
.catch((error) => {
this.store.alert_user_error(error);
APIError.alert(error);
reject();
});
});
@ -133,11 +134,10 @@ export default class extends Vue {
data.push(door.save());
}
this.$advent22
.api_put("admin/doors", data)
API.request<void>({ endpoint: "admin/doors", method: "PUT", data: data })
.then(resolve)
.catch((error) => {
this.store.alert_user_error(error);
APIError.alert(error);
reject();
});
});

View file

@ -1,10 +1,10 @@
<template>
<foreignObject
:x="Math.round(store.calendar_aspect_ratio * rectangle.left)"
:x="Math.round(aspect_ratio * rectangle.left)"
:y="rectangle.top"
:width="Math.round(store.calendar_aspect_ratio * rectangle.width)"
:width="Math.round(aspect_ratio * rectangle.width)"
:height="rectangle.height"
:style="`transform: scaleX(${1 / store.calendar_aspect_ratio})`"
:style="`transform: scaleX(${1 / aspect_ratio})`"
>
<div
xmlns="http://www.w3.org/1999/xhtml"
@ -18,6 +18,7 @@
</template>
<script lang="ts">
import { loading_success } from "@/lib/helpers";
import { Rectangle } from "@/lib/rects/rectangle";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
@ -59,6 +60,14 @@ export default class extends Vue {
return result;
}
public get aspect_ratio(): number {
if (!loading_success(this.store.background_image)) return 1;
return (
this.store.background_image.height / this.store.background_image.width
);
}
}
</script>

View file

@ -57,15 +57,6 @@ function mouse_event_validator(event: object, point: object): boolean {
export default class extends Vue {
public readonly store = advent22Store();
public mounted(): void {
new ResizeObserver(([first, ...rest]) => {
if (rest.length > 0)
console.warn(`Unexpected ${rest.length} extra entries!`);
this.store.set_calendar_aspect_ratio(first.contentRect);
}).observe(this.$el);
}
public transform_mouse_event(event: MouseEvent) {
const point = get_event_thous(event);
this.$emit(event.type, event, point);

View file

@ -11,7 +11,7 @@
</ul>
</div>
<figure class="image is-unselectable">
<img :src="store.background_image" />
<img :src="_ensure_loaded(store.background_image).data_url" />
<ThouCanvas>
<PreviewDoor
v-for="(door, index) in doors"
@ -24,6 +24,7 @@
</template>
<script lang="ts">
import { ensure_loaded, Loading } from "@/lib/helpers";
import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
@ -43,5 +44,9 @@ import PreviewDoor from "./PreviewDoor.vue";
export default class extends Vue {
public doors!: Door[];
public readonly store = advent22Store();
public _ensure_loaded<T>(o: Loading<T>): T {
return ensure_loaded(o);
}
}
</script>

View file

@ -9,13 +9,14 @@
</ul>
</div>
<figure class="image is-unselectable">
<img :src="store.background_image" />
<img :src="_ensure_loaded(store.background_image).data_url" />
<DoorCanvas :doors="doors" />
</figure>
</div>
</template>
<script lang="ts">
import { ensure_loaded, Loading } from "@/lib/helpers";
import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store";
import { Options, Vue } from "vue-class-component";
@ -33,5 +34,9 @@ import DoorCanvas from "./DoorCanvas.vue";
export default class extends Vue {
public doors!: Door[];
public readonly store = advent22Store();
public _ensure_loaded<T>(o: Loading<T>): T {
return ensure_loaded(o);
}
}
</script>

View file

@ -70,4 +70,8 @@ export class APIError extends Error {
type: "is-danger",
});
}
public static alert(error: unknown) {
new APIError(error, "").alert();
}
}

View file

@ -20,6 +20,12 @@ export function loading_success<T>(o: Loading<T>): o is T {
return true;
}
export function ensure_loaded<T>(o: Loading<T>): T {
if (!loading_success(o)) throw "";
return o;
}
export function handle_error(error: unknown) {
if (error instanceof APIError) {
error.alert();
@ -27,3 +33,7 @@ export function handle_error(error: unknown) {
console.error(error);
}
}
export function name_door(day: number): string {
return `Türchen ${day}`;
}

View file

@ -1,5 +1,4 @@
import { advent22Store } from "@/lib/store";
import { Advent22Plugin } from "@/plugins/advent22";
import { FontAwesomePlugin } from "@/plugins/fontawesome";
import * as bulmaToast from "bulma-toast";
import { createPinia } from "pinia";
@ -10,10 +9,9 @@ import "@/main.scss";
const app = createApp(App);
app.use(Advent22Plugin);
app.use(FontAwesomePlugin);
app.use(createPinia());
advent22Store().init();
app.mount("#app");