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:
parent
e3e19a0572
commit
109dec73d2
14 changed files with 113 additions and 72 deletions
|
@ -7,7 +7,18 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="section px-3">
|
<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" />
|
<AdminView v-if="store.is_admin" />
|
||||||
<UserView v-else />
|
<UserView v-else />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { Credentials } from "@/lib/model";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
import { APIError } from "@/lib/api_error";
|
||||||
import BulmaButton from "./bulma/Button.vue";
|
import BulmaButton from "./bulma/Button.vue";
|
||||||
import LoginModal from "./LoginModal.vue";
|
import LoginModal from "./LoginModal.vue";
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ export default class extends Vue {
|
||||||
|
|
||||||
this.store
|
this.store
|
||||||
.login(creds)
|
.login(creds)
|
||||||
.catch(this.store.alert_user_error)
|
.catch((error) => APIError.alert(error))
|
||||||
.finally(() => (this.is_busy = false));
|
.finally(() => (this.is_busy = false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,14 @@
|
||||||
|
|
||||||
<figure>
|
<figure>
|
||||||
<div class="image is-unselectable">
|
<div class="image is-unselectable">
|
||||||
<img :src="store.background_image" />
|
<img :src="_ensure_loaded(store.background_image).data_url" />
|
||||||
<ThouCanvas>
|
<ThouCanvas>
|
||||||
<CalendarDoor
|
<CalendarDoor
|
||||||
v-for="(door, index) in doors"
|
v-for="(door, index) in doors"
|
||||||
:key="`door-${index}`"
|
:key="`door-${index}`"
|
||||||
:door="door"
|
:door="door"
|
||||||
:visible="store.is_touch_device"
|
:visible="store.is_touch_device"
|
||||||
:title="$advent22.name_door(door.day)"
|
:title="_name_door(door.day)"
|
||||||
@click="door_click(door.day)"
|
@click="door_click(door.day)"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -46,6 +46,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { Door } from "@/lib/rects/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
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);
|
if (this.toast_timeout !== undefined) clearTimeout(this.toast_timeout);
|
||||||
this.toast?.hide();
|
this.toast?.hide();
|
||||||
|
|
||||||
if (this.multi_modal === undefined) return;
|
if (this.multi_modal === undefined) return;
|
||||||
this.multi_modal.show_progress();
|
this.multi_modal.show_progress();
|
||||||
|
|
||||||
this.$advent22
|
try {
|
||||||
.api_get_blob(`user/image_${day}`)
|
const day_image = await API.request<ImageData>(`user/image_${day}`);
|
||||||
.then((image_src) => {
|
this.multi_modal!.show_image(day_image.data_url, name_door(day));
|
||||||
this.multi_modal!.show_image(image_src, this.$advent22.name_door(day));
|
} catch (error) {
|
||||||
})
|
APIError.alert(error);
|
||||||
.catch((error) => {
|
|
||||||
this.store.alert_user_error(error);
|
|
||||||
this.multi_modal!.hide();
|
this.multi_modal!.hide();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public beforeUnmount(): void {
|
public beforeUnmount(): void {
|
||||||
this.toast?.hide();
|
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>
|
</script>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<template>
|
<template>
|
||||||
<template v-if="store.is_initialized === true">
|
|
||||||
<Calendar :doors="store.user_doors" />
|
<Calendar :doors="store.user_doors" />
|
||||||
<hr />
|
<hr />
|
||||||
<div class="content" v-html="store.site_config.content" />
|
<div class="content" v-html="store.site_config.content" />
|
||||||
|
@ -18,8 +17,6 @@
|
||||||
<CountDown :until="store.next_door_target" />
|
<CountDown :until="store.next_door_target" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
<progress v-else class="progress is-primary" max="100" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
|
|
@ -46,8 +46,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { objForEach } from "@/lib/helpers";
|
import { API } from "@/lib/api";
|
||||||
import { NumStrDict } from "@/lib/model";
|
import { name_door, objForEach } from "@/lib/helpers";
|
||||||
|
import { ImageData, 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";
|
||||||
|
@ -77,8 +78,8 @@ export default class extends Vue {
|
||||||
|
|
||||||
public on_open(ready: () => void, fail: () => void): void {
|
public on_open(ready: () => void, fail: () => void): void {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.$advent22.api_get<NumStrDict>("admin/day_parts"),
|
API.request<NumStrDict>("admin/day_parts"),
|
||||||
this.$advent22.api_get<NumStrDict>("admin/day_image_names"),
|
API.request<NumStrDict>("admin/day_image_names"),
|
||||||
])
|
])
|
||||||
.then(([day_parts, day_image_names]) => {
|
.then(([day_parts, day_image_names]) => {
|
||||||
const _ensure_day_in_data = (day: number) => {
|
const _ensure_day_in_data = (day: number) => {
|
||||||
|
@ -102,16 +103,16 @@ export default class extends Vue {
|
||||||
.catch(fail);
|
.catch(fail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public door_click(day: number) {
|
public async door_click(day: number) {
|
||||||
if (this.multi_modal === undefined) return;
|
if (this.multi_modal === undefined) return;
|
||||||
this.multi_modal.show_progress();
|
this.multi_modal.show_progress();
|
||||||
|
|
||||||
this.$advent22
|
try {
|
||||||
.api_get_blob(`user/image_${day}`)
|
const day_image = await API.request<ImageData>(`user/image_${day}`);
|
||||||
.then((image_src) =>
|
this.multi_modal!.show_image(day_image.data_url, name_door(day));
|
||||||
this.multi_modal!.show_image(image_src, this.$advent22.name_door(day)),
|
} catch (error) {
|
||||||
)
|
this.multi_modal!.hide();
|
||||||
.catch(() => this.multi_modal!.hide());
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -189,6 +189,7 @@ import { advent22Store } from "@/lib/store";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
import { API } from "@/lib/api";
|
||||||
import BulmaDrawer from "../bulma/Drawer.vue";
|
import BulmaDrawer from "../bulma/Drawer.vue";
|
||||||
import BulmaSecret from "../bulma/Secret.vue";
|
import BulmaSecret from "../bulma/Secret.vue";
|
||||||
import CountDown from "../CountDown.vue";
|
import CountDown from "../CountDown.vue";
|
||||||
|
@ -256,8 +257,8 @@ export default class extends Vue {
|
||||||
public on_open(ready: () => void, fail: () => void): void {
|
public on_open(ready: () => void, fail: () => void): void {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
this.store.update(),
|
this.store.update(),
|
||||||
this.$advent22.api_get<AdminConfigModel>("admin/config_model"),
|
API.request<AdminConfigModel>("admin/config_model"),
|
||||||
this.$advent22.api_get<DoorSaved[]>("admin/doors"),
|
API.request<DoorSaved[]>("admin/doors"),
|
||||||
])
|
])
|
||||||
.then(([store_update, admin_config_model, doors]) => {
|
.then(([store_update, admin_config_model, doors]) => {
|
||||||
store_update; // discard value
|
store_update; // discard value
|
||||||
|
@ -271,15 +272,13 @@ export default class extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
public load_dav_credentials(): void {
|
public load_dav_credentials(): void {
|
||||||
this.$advent22
|
API.request<Credentials>("admin/dav_credentials")
|
||||||
.api_get<Credentials>("admin/dav_credentials")
|
|
||||||
.then((creds) => (this.dav_credentials = creds))
|
.then((creds) => (this.dav_credentials = creds))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public load_ui_credentials(): void {
|
public load_ui_credentials(): void {
|
||||||
this.$advent22
|
API.request<Credentials>("admin/ui_credentials")
|
||||||
.api_get<Credentials>("admin/ui_credentials")
|
|
||||||
.then((creds) => (this.ui_credentials = creds))
|
.then((creds) => (this.ui_credentials = creds))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,8 @@ import { Door } from "@/lib/rects/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
|
import { API } from "@/lib/api";
|
||||||
|
import { APIError } from "@/lib/api_error";
|
||||||
import { toast } from "bulma-toast";
|
import { toast } from "bulma-toast";
|
||||||
import Calendar from "../Calendar.vue";
|
import Calendar from "../Calendar.vue";
|
||||||
import BulmaBreadcrumbs, { Step } from "../bulma/Breadcrumbs.vue";
|
import BulmaBreadcrumbs, { Step } from "../bulma/Breadcrumbs.vue";
|
||||||
|
@ -107,8 +109,7 @@ export default class extends Vue {
|
||||||
|
|
||||||
private load_doors(): Promise<void> {
|
private load_doors(): Promise<void> {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this.$advent22
|
API.request<DoorSaved[]>("admin/doors")
|
||||||
.api_get<DoorSaved[]>("admin/doors")
|
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.doors.length = 0;
|
this.doors.length = 0;
|
||||||
|
|
||||||
|
@ -119,7 +120,7 @@ export default class extends Vue {
|
||||||
resolve();
|
resolve();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.store.alert_user_error(error);
|
APIError.alert(error);
|
||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -133,11 +134,10 @@ export default class extends Vue {
|
||||||
data.push(door.save());
|
data.push(door.save());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$advent22
|
API.request<void>({ endpoint: "admin/doors", method: "PUT", data: data })
|
||||||
.api_put("admin/doors", data)
|
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.store.alert_user_error(error);
|
APIError.alert(error);
|
||||||
reject();
|
reject();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<foreignObject
|
<foreignObject
|
||||||
:x="Math.round(store.calendar_aspect_ratio * rectangle.left)"
|
:x="Math.round(aspect_ratio * rectangle.left)"
|
||||||
:y="rectangle.top"
|
:y="rectangle.top"
|
||||||
:width="Math.round(store.calendar_aspect_ratio * rectangle.width)"
|
:width="Math.round(aspect_ratio * rectangle.width)"
|
||||||
:height="rectangle.height"
|
:height="rectangle.height"
|
||||||
:style="`transform: scaleX(${1 / store.calendar_aspect_ratio})`"
|
:style="`transform: scaleX(${1 / aspect_ratio})`"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
xmlns="http://www.w3.org/1999/xhtml"
|
xmlns="http://www.w3.org/1999/xhtml"
|
||||||
|
@ -18,6 +18,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { loading_success } from "@/lib/helpers";
|
||||||
import { Rectangle } from "@/lib/rects/rectangle";
|
import { Rectangle } from "@/lib/rects/rectangle";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
@ -59,6 +60,14 @@ export default class extends Vue {
|
||||||
|
|
||||||
return result;
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -57,15 +57,6 @@ function mouse_event_validator(event: object, point: object): boolean {
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public readonly store = advent22Store();
|
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) {
|
public transform_mouse_event(event: MouseEvent) {
|
||||||
const point = get_event_thous(event);
|
const point = get_event_thous(event);
|
||||||
this.$emit(event.type, event, point);
|
this.$emit(event.type, event, point);
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<figure class="image is-unselectable">
|
<figure class="image is-unselectable">
|
||||||
<img :src="store.background_image" />
|
<img :src="_ensure_loaded(store.background_image).data_url" />
|
||||||
<ThouCanvas>
|
<ThouCanvas>
|
||||||
<PreviewDoor
|
<PreviewDoor
|
||||||
v-for="(door, index) in doors"
|
v-for="(door, index) in doors"
|
||||||
|
@ -24,6 +24,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { ensure_loaded, Loading } from "@/lib/helpers";
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/rects/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
@ -43,5 +44,9 @@ import PreviewDoor from "./PreviewDoor.vue";
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public doors!: Door[];
|
public doors!: Door[];
|
||||||
public readonly store = advent22Store();
|
public readonly store = advent22Store();
|
||||||
|
|
||||||
|
public _ensure_loaded<T>(o: Loading<T>): T {
|
||||||
|
return ensure_loaded(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<figure class="image is-unselectable">
|
<figure class="image is-unselectable">
|
||||||
<img :src="store.background_image" />
|
<img :src="_ensure_loaded(store.background_image).data_url" />
|
||||||
<DoorCanvas :doors="doors" />
|
<DoorCanvas :doors="doors" />
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { ensure_loaded, Loading } from "@/lib/helpers";
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/rects/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
@ -33,5 +34,9 @@ import DoorCanvas from "./DoorCanvas.vue";
|
||||||
export default class extends Vue {
|
export default class extends Vue {
|
||||||
public doors!: Door[];
|
public doors!: Door[];
|
||||||
public readonly store = advent22Store();
|
public readonly store = advent22Store();
|
||||||
|
|
||||||
|
public _ensure_loaded<T>(o: Loading<T>): T {
|
||||||
|
return ensure_loaded(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -70,4 +70,8 @@ export class APIError extends Error {
|
||||||
type: "is-danger",
|
type: "is-danger",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static alert(error: unknown) {
|
||||||
|
new APIError(error, "").alert();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,12 @@ export function loading_success<T>(o: Loading<T>): o is T {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensure_loaded<T>(o: Loading<T>): T {
|
||||||
|
if (!loading_success(o)) throw "";
|
||||||
|
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
export function handle_error(error: unknown) {
|
export function handle_error(error: unknown) {
|
||||||
if (error instanceof APIError) {
|
if (error instanceof APIError) {
|
||||||
error.alert();
|
error.alert();
|
||||||
|
@ -27,3 +33,7 @@ export function handle_error(error: unknown) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function name_door(day: number): string {
|
||||||
|
return `Türchen ${day}`;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { Advent22Plugin } from "@/plugins/advent22";
|
|
||||||
import { FontAwesomePlugin } from "@/plugins/fontawesome";
|
import { FontAwesomePlugin } from "@/plugins/fontawesome";
|
||||||
import * as bulmaToast from "bulma-toast";
|
import * as bulmaToast from "bulma-toast";
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
|
@ -10,10 +9,9 @@ import "@/main.scss";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(Advent22Plugin);
|
|
||||||
app.use(FontAwesomePlugin);
|
app.use(FontAwesomePlugin);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
|
|
||||||
advent22Store().init();
|
advent22Store().init();
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
Loading…
Reference in a new issue