🚧 wip on ui: rework for vue 3 composition API

This commit is contained in:
Jörn-Michael Miehe 2025-12-07 03:28:00 +00:00
parent 9079fca30f
commit 39b56d8bd0
18 changed files with 118 additions and 115 deletions

View file

@ -33,10 +33,10 @@
</div> </div>
<div class="level-right"> <div class="level-right">
<div class="level-item"> <div class="level-item">
<TouchButton class="tag is-warning" /> <TouchButton class="is-small is-warning" />
</div> </div>
<div class="level-item"> <div class="level-item">
<AdminButton class="tag is-link is-outlined" /> <AdminButton class="is-small is-link is-outlined" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -14,12 +14,13 @@
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { Credentials } from "@/lib/model"; import { Credentials } from "@/lib/model";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { ref } from "vue";
import BulmaButton from "./bulma/Button.vue"; import BulmaButton from "./bulma/Button.vue";
import LoginModal from "./LoginModal.vue"; import LoginModal from "./LoginModal.vue";
let modal_visible = false; const modal_visible = ref(false);
let is_busy = false; const is_busy = ref(false);
const store = advent22Store(); const store = advent22Store();
function on_click() { function on_click() {
@ -27,22 +28,22 @@ function on_click() {
store.logout(); store.logout();
} else { } else {
// show login modal // show login modal
is_busy = true; is_busy.value = true;
modal_visible = true; modal_visible.value = true;
} }
} }
function on_submit(creds: Credentials) { function on_submit(creds: Credentials) {
modal_visible = false; modal_visible.value = false;
store store
.login(creds) .login(creds)
.catch((error) => APIError.alert(error)) .catch((error) => APIError.alert(error))
.finally(() => (is_busy = false)); .finally(() => (is_busy.value = false));
} }
function on_cancel() { function on_cancel() {
modal_visible = false; modal_visible.value = false;
is_busy = false; is_busy.value = false;
} }
</script> </script>

View file

@ -51,7 +51,7 @@ import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { ensure_loaded, name_door } from "@/lib/helpers"; import { ensure_loaded, name_door } from "@/lib/helpers";
import { ImageData } from "@/lib/model"; import { ImageData } from "@/lib/model";
import { Door } from "@/lib/rects/door"; import { VueDoor } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { onBeforeUnmount } from "vue"; import { onBeforeUnmount } from "vue";
@ -62,7 +62,7 @@ import CalendarDoor from "./calendar/CalendarDoor.vue";
import ThouCanvas from "./calendar/ThouCanvas.vue"; import ThouCanvas from "./calendar/ThouCanvas.vue";
defineProps<{ defineProps<{
doors: Door[]; doors: VueDoor[];
}>(); }>();
const store = advent22Store(); const store = advent22Store();
@ -81,7 +81,7 @@ function on_toast_handle(handle: HBulmaToast) {
if (store.is_touch_device) return; if (store.is_touch_device) return;
store.when_initialized(() => { store.when_initialized(() => {
toast_timeout = setTimeout(() => { toast_timeout = window.setTimeout(() => {
if (store.user_doors.length === 0) return; if (store.user_doors.length === 0) return;
if (store.is_touch_device) return; if (store.is_touch_device) return;
@ -91,7 +91,7 @@ function on_toast_handle(handle: HBulmaToast) {
} }
async function door_click(day: number) { async function door_click(day: number) {
if (toast_timeout !== undefined) clearTimeout(toast_timeout); window.clearTimeout(toast_timeout);
toast?.hide(); toast?.hide();
if (modal === undefined) return; if (modal === undefined) return;

View file

@ -4,46 +4,45 @@
<script setup lang="ts"> <script setup lang="ts">
import { Duration } from "luxon"; import { Duration } from "luxon";
import { onBeforeUnmount, onMounted } from "vue"; import { onBeforeUnmount, onMounted, ref } from "vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
until: number; until: number;
tick_time: number; tick_time?: number;
}>(), }>(),
{ tick_time: 200 }, { tick_time: 200 },
); );
let interval_id: number | null = null; let interval_id: number | undefined;
let string_repr = ""; const string_repr = ref("");
function tick(): void {
const distance_ms = props.until - Date.now();
if (distance_ms <= 0) {
string_repr = "Jetzt!";
return;
}
const distance = Duration.fromMillis(distance_ms);
const d_days = distance.shiftTo("day").mapUnits(Math.floor);
const d_hms = distance.minus(d_days).shiftTo("hour", "minute", "second");
if (d_days.days > 0) {
string_repr = d_days.toHuman() + " ";
} else {
string_repr = "";
}
string_repr += d_hms.toFormat("hh:mm:ss");
}
onMounted(() => { onMounted(() => {
function tick(): void {
const distance_ms = props.until - Date.now();
if (distance_ms <= 0) {
string_repr.value = "Jetzt!";
return;
}
const distance = Duration.fromMillis(distance_ms);
const d_days = distance.shiftTo("day").mapUnits(Math.floor);
const d_hms = distance.minus(d_days).shiftTo("hour", "minute", "second");
if (d_days.days > 0) {
string_repr.value = d_days.toHuman() + " ";
} else {
string_repr.value = "";
}
string_repr.value += d_hms.toFormat("hh:mm:ss");
}
tick(); tick();
interval_id = window.setInterval(tick, props.tick_time); interval_id = window.setInterval(tick, props.tick_time);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (interval_id === null) return;
window.clearInterval(interval_id); window.clearInterval(interval_id);
}); });
</script> </script>

View file

@ -52,7 +52,6 @@ import { Credentials } from "@/lib/model";
import { nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue"; import { nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
import BulmaButton from "./bulma/Button.vue"; import BulmaButton from "./bulma/Button.vue";
defineProps<{ visible: boolean }>();
const username_input = useTemplateRef("username_input"); const username_input = useTemplateRef("username_input");
const emit = defineEmits<{ const emit = defineEmits<{

View file

@ -1,5 +1,5 @@
<template> <template>
<Calendar :doors="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" />
<div class="content has-text-primary"> <div class="content has-text-primary">
@ -20,12 +20,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import Calendar from "./Calendar.vue"; import Calendar from "./Calendar.vue";
import CountDown from "./CountDown.vue"; import CountDown from "./CountDown.vue";
const store = advent22Store(); const store = advent22Store();
const doors = store.user_doors as Door[];
</script> </script>

View file

@ -49,17 +49,18 @@
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { name_door, objForEach } from "@/lib/helpers"; import { name_door, objForEach } from "@/lib/helpers";
import { ImageData, NumStrDict } from "@/lib/model"; import { ImageData, NumStrDict } from "@/lib/model";
import { ref } from "vue";
import MultiModal, { HMultiModal } from "../MultiModal.vue"; import MultiModal, { HMultiModal } from "../MultiModal.vue";
import BulmaButton from "../bulma/Button.vue"; import BulmaButton from "../bulma/Button.vue";
import BulmaDrawer from "../bulma/Drawer.vue"; import BulmaDrawer from "../bulma/Drawer.vue";
const day_data: { const day_data = ref<{
[day: number]: { [day: number]: {
part: string; part: string;
image_name: string; image_name: string;
}; };
} = {}; }>({});
let modal: HMultiModal | undefined; let modal: HMultiModal | undefined;
@ -74,19 +75,19 @@ function on_open(ready: () => void, fail: () => void) {
]) ])
.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) => {
if (!(day in day_data)) { if (!(day in day_data.value)) {
day_data[day] = { part: "", image_name: "" }; day_data.value[day] = { part: "", image_name: "" };
} }
}; };
objForEach(day_parts, (day, part) => { objForEach(day_parts, (day, part) => {
_ensure_day_in_data(day); _ensure_day_in_data(day);
day_data[day].part = part; day_data.value[day].part = part;
}); });
objForEach(day_image_names, (day, image_name) => { objForEach(day_image_names, (day, image_name) => {
_ensure_day_in_data(day); _ensure_day_in_data(day);
day_data[day].image_name = image_name; day_data.value[day].image_name = image_name;
}); });
ready(); ready();

View file

@ -188,6 +188,7 @@ import { API } from "@/lib/api";
import { AdminConfigModel, Credentials, DoorSaved } from "@/lib/model"; import { AdminConfigModel, Credentials, DoorSaved } from "@/lib/model";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { ref } from "vue";
import BulmaDrawer from "../bulma/Drawer.vue"; import BulmaDrawer from "../bulma/Drawer.vue";
import BulmaSecret from "../bulma/Secret.vue"; import BulmaSecret from "../bulma/Secret.vue";
@ -195,7 +196,7 @@ import CountDown from "../CountDown.vue";
const store = advent22Store(); const store = advent22Store();
let admin_config_model: AdminConfigModel = { const admin_config_model = ref<AdminConfigModel>({
solution: { solution: {
value: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", value: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
whitespace: "KEEP", whitespace: "KEEP",
@ -233,14 +234,14 @@ let admin_config_model: AdminConfigModel = {
cache_ttl: 0, cache_ttl: 0,
config_file: "sed diam nonumy", config_file: "sed diam nonumy",
}, },
}; });
let doors: DoorSaved[] = []; const doors = ref<DoorSaved[]>([]);
let dav_credentials: Credentials = ["", ""]; const dav_credentials = ref<Credentials>(["", ""]);
let ui_credentials: Credentials = ["", ""]; const ui_credentials = ref<Credentials>(["", ""]);
function fmt_puzzle_date(name: keyof AdminConfigModel["puzzle"]): string { function fmt_puzzle_date(name: keyof AdminConfigModel["puzzle"]): string {
const iso_date = admin_config_model.puzzle[name]; const iso_date = admin_config_model.value.puzzle[name];
if (!(typeof iso_date === "string")) return "-"; if (!(typeof iso_date === "string")) return "-";
return DateTime.fromISO(iso_date).toLocaleString(DateTime.DATE_SHORT); return DateTime.fromISO(iso_date).toLocaleString(DateTime.DATE_SHORT);
@ -255,8 +256,8 @@ function on_open(ready: () => void, fail: () => void): void {
.then(([store_update, new_admin_config_model, new_doors]) => { .then(([store_update, new_admin_config_model, new_doors]) => {
store_update; // discard value store_update; // discard value
admin_config_model = new_admin_config_model; admin_config_model.value = new_admin_config_model;
doors = new_doors; doors.value = new_doors;
ready(); ready();
}) })
@ -265,13 +266,13 @@ function on_open(ready: () => void, fail: () => void): void {
function load_dav_credentials(): void { function load_dav_credentials(): void {
API.request<Credentials>("admin/dav_credentials") API.request<Credentials>("admin/dav_credentials")
.then((creds) => (dav_credentials = creds)) .then((creds) => (dav_credentials.value = creds))
.catch(() => {}); .catch(() => {});
} }
function load_ui_credentials(): void { function load_ui_credentials(): void {
API.request<Credentials>("admin/ui_credentials") API.request<Credentials>("admin/ui_credentials")
.then((creds) => (ui_credentials = creds)) .then((creds) => (ui_credentials.value = creds))
.catch(() => {}); .catch(() => {});
} }
</script> </script>

View file

@ -37,10 +37,10 @@
</div> </div>
</div> </div>
<DoorPlacer v-if="current_step === 0" v-model="doors" /> <DoorPlacer v-if="current_step === 0" v-model="(doors as Door[])" />
<DoorChooser v-if="current_step === 1" v-model="doors" /> <DoorChooser v-if="current_step === 1" v-model="(doors as Door[])" />
<div v-if="current_step === 2" class="card-content"> <div v-if="current_step === 2" class="card-content">
<Calendar :doors="doors" /> <Calendar :doors="(doors as Door[])" />
</div> </div>
<footer class="card-footer is-flex is-justify-content-space-around"> <footer class="card-footer is-flex is-justify-content-space-around">
@ -71,11 +71,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { Step } from "@/lib/helpers";
import { DoorSaved } from "@/lib/model"; import { DoorSaved } from "@/lib/model";
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { toast } from "bulma-toast"; import { toast } from "bulma-toast";
import { ref } from "vue"; import { ref } from "vue";
import { BCStep } from "../bulma/Breadcrumbs.vue";
import Calendar from "../Calendar.vue"; import Calendar from "../Calendar.vue";
import BulmaBreadcrumbs from "../bulma/Breadcrumbs.vue"; import BulmaBreadcrumbs from "../bulma/Breadcrumbs.vue";
@ -84,26 +84,25 @@ import BulmaDrawer from "../bulma/Drawer.vue";
import DoorChooser from "../editor/DoorChooser.vue"; import DoorChooser from "../editor/DoorChooser.vue";
import DoorPlacer from "../editor/DoorPlacer.vue"; import DoorPlacer from "../editor/DoorPlacer.vue";
const steps: Step[] = [ const steps: BCStep[] = [
{ label: "Platzieren", icon: "fa-solid fa-crosshairs" }, { label: "Platzieren", icon: "fa-solid fa-crosshairs" },
{ label: "Ordnen", icon: "fa-solid fa-list-ol" }, { label: "Ordnen", icon: "fa-solid fa-list-ol" },
{ label: "Vorschau", icon: "fa-solid fa-magnifying-glass" }, { label: "Vorschau", icon: "fa-solid fa-magnifying-glass" },
]; ];
const doors: Door[] = []; const doors = ref<Door[]>([]);
const current_step = ref(0); const current_step = ref(0);
let loading_doors = false; const loading_doors = ref(false);
let saving_doors = false; const saving_doors = ref(false);
function load_doors(): Promise<void> { function load_doors(): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
API.request<DoorSaved[]>("admin/doors") API.request<DoorSaved[]>("admin/doors")
.then((data) => { .then((data) => {
doors.length = 0; doors.value.length = 0;
for (const value of data) { for (const value of data) {
doors.push(Door.load(value)); doors.value.push(Door.load(value));
} }
resolve(); resolve();
@ -119,7 +118,7 @@ function save_doors(): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const data: DoorSaved[] = []; const data: DoorSaved[] = [];
for (const door of doors) { for (const door of doors.value) {
data.push(door.save()); data.push(door.save());
} }
@ -138,7 +137,7 @@ function on_open(ready: () => void, fail: () => void): void {
function on_download() { function on_download() {
if (confirm("Aktuelle Änderungen verwerfen und Status vom Server laden?")) { if (confirm("Aktuelle Änderungen verwerfen und Status vom Server laden?")) {
loading_doors = true; loading_doors.value = true;
load_doors() load_doors()
.then(() => .then(() =>
@ -149,20 +148,20 @@ function on_download() {
}), }),
) )
.catch(() => {}) .catch(() => {})
.finally(() => (loading_doors = false)); .finally(() => (loading_doors.value = false));
} }
} }
function on_discard() { function on_discard() {
if (confirm("Alle Türchen löschen? (nur lokal)")) { if (confirm("Alle Türchen löschen? (nur lokal)")) {
// empty `doors` array // empty `doors` array
doors.length = 0; doors.value.length = 0;
} }
} }
function on_upload() { function on_upload() {
if (confirm("Aktuelle Änderungen an den Server schicken?")) { if (confirm("Aktuelle Änderungen an den Server schicken?")) {
saving_doors = true; saving_doors.value = true;
save_doors() save_doors()
.then(() => { .then(() => {
@ -175,9 +174,9 @@ function on_upload() {
}), }),
) )
.catch(() => {}) .catch(() => {})
.finally(() => (saving_doors = false)); .finally(() => (saving_doors.value = false));
}) })
.catch(() => (saving_doors = false)); .catch(() => (saving_doors.value = false));
} }
} }
</script> </script>

View file

@ -20,11 +20,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Step } from "@/lib/helpers"; export interface BCStep {
label: string;
icon: string | string[];
}
const model = defineModel<number>({ required: true }); const model = defineModel<number>({ required: true });
defineProps<{ defineProps<{
steps: Step[]; steps: BCStep[];
}>(); }>();
</script> </script>

View file

@ -9,7 +9,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Options as ToastOptions, toast } from "bulma-toast"; import { Options as ToastOptions, toast } from "bulma-toast";
import { onMounted, ref } from "vue"; import { onMounted, useTemplateRef } from "vue";
export type HBulmaToast = { export type HBulmaToast = {
show(options: ToastOptions): void; show(options: ToastOptions): void;
@ -20,7 +20,7 @@ const emit = defineEmits<{
(event: "handle", handle: HBulmaToast): void; (event: "handle", handle: HBulmaToast): void;
}>(); }>();
const message = ref<HTMLDivElement | null>(null); const message = useTemplateRef("message");
onMounted(() => onMounted(() =>
emit("handle", { emit("handle", {
@ -34,10 +34,10 @@ onMounted(() =>
}); });
}, },
hide() { hide() {
if (!(message.value instanceof HTMLElement)) return; if (message.value === null) return;
const toast_div = message.value.parentElement; const toast_div = message.value.parentElement;
if (!(toast_div instanceof HTMLDivElement)) return; if (toast_div === null) return;
const dbutton = toast_div.querySelector("button.delete"); const dbutton = toast_div.querySelector("button.delete");
if (!(dbutton instanceof HTMLButtonElement)) return; if (!(dbutton instanceof HTMLButtonElement)) return;

View file

@ -14,7 +14,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Door } from "@/lib/rects/door"; import { VueDoor } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import SVGRect from "./SVGRect.vue"; import SVGRect from "./SVGRect.vue";
@ -23,8 +23,8 @@ const store = advent22Store();
withDefaults( withDefaults(
defineProps<{ defineProps<{
door: Door; door: VueDoor;
force_visible: boolean; force_visible?: boolean;
}>(), }>(),
{ {
force_visible: false, force_visible: false,

View file

@ -21,7 +21,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { loading_success } from "@/lib/helpers"; import { loading_success } from "@/lib/helpers";
import { Rectangle } from "@/lib/rects/rectangle"; import { VueRectangle } from "@/lib/rects/rectangle";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
const store = advent22Store(); const store = advent22Store();
@ -38,7 +38,7 @@ withDefaults(
defineProps<{ defineProps<{
variant: BulmaVariant; variant: BulmaVariant;
visible?: boolean; visible?: boolean;
rectangle: Rectangle; rectangle: VueRectangle;
}>(), }>(),
{ {
visible: true, visible: true,

View file

@ -27,6 +27,7 @@
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { Rectangle } from "@/lib/rects/rectangle"; import { Rectangle } from "@/lib/rects/rectangle";
import { Vector2D } from "@/lib/rects/vector2d"; import { Vector2D } from "@/lib/rects/vector2d";
import { ref } from "vue";
import CalendarDoor from "../calendar/CalendarDoor.vue"; import CalendarDoor from "../calendar/CalendarDoor.vue";
import SVGRect from "../calendar/SVGRect.vue"; import SVGRect from "../calendar/SVGRect.vue";
@ -40,11 +41,11 @@ type CanvasState =
const model = defineModel<Door[]>({ required: true }); const model = defineModel<Door[]>({ required: true });
const MIN_RECT_AREA = 300; const MIN_RECT_AREA = 300;
let state: CanvasState = { kind: "idle" }; const state = ref<CanvasState>({ kind: "idle" });
let preview = new Rectangle(); const preview = ref(new Rectangle());
function preview_visible(): boolean { function preview_visible(): boolean {
return state.kind !== "idle"; return state.value.kind !== "idle";
} }
function pop_door(point: Vector2D): Door | undefined { function pop_door(point: Vector2D): Door | undefined {
@ -62,18 +63,18 @@ function draw_start(event: MouseEvent, point: Vector2D) {
return; return;
} }
preview = new Rectangle(point, point); preview.value = new Rectangle(point, point);
state = { kind: "drawing" }; state.value = { kind: "drawing" };
} }
function draw_finish() { function draw_finish() {
if (state.kind !== "drawing" || preview.area < MIN_RECT_AREA) { if (state.value.kind !== "drawing" || preview.value.area < MIN_RECT_AREA) {
return; return;
} }
model.value.push(new Door(preview)); model.value.push(new Door(preview.value as Rectangle));
state = { kind: "idle" }; state.value = { kind: "idle" };
} }
function drag_start(event: MouseEvent, point: Vector2D) { function drag_start(event: MouseEvent, point: Vector2D) {
@ -87,27 +88,27 @@ function drag_start(event: MouseEvent, point: Vector2D) {
return; return;
} }
preview = drag_door.position; preview.value = drag_door.position;
state = { kind: "dragging", door: drag_door, origin: point }; state.value = { kind: "dragging", door: drag_door, origin: point };
} }
function drag_finish() { function drag_finish() {
if (state.kind !== "dragging") { if (state.value.kind !== "dragging") {
return; return;
} }
model.value.push(new Door(preview, state.door.day)); model.value.push(new Door(preview.value as Rectangle, state.value.door.day));
state = { kind: "idle" }; state.value = { kind: "idle" };
} }
function on_mousemove(event: MouseEvent, point: Vector2D) { function on_mousemove(event: MouseEvent, point: Vector2D) {
if (state.kind === "drawing") { if (state.value.kind === "drawing") {
preview = preview.update(undefined, point); preview.value = preview.value.update(undefined, point);
} else if (state.kind === "dragging") { } else if (state.value.kind === "dragging") {
const movement = point.minus(state.origin); const movement = point.minus(state.value.origin);
preview = state.door.position.move(movement); preview.value = state.value.door.position.move(movement);
} }
} }

View file

@ -32,11 +32,11 @@ const model = defineModel<Door>({ required: true });
const day_input = useTemplateRef("day_input"); const day_input = useTemplateRef("day_input");
const day_str = ref(""); const day_str = ref("");
let editing = false; const editing = ref(false);
function toggle_editing() { function toggle_editing() {
day_str.value = String(model.value.day); day_str.value = String(model.value.day);
editing = !editing; editing.value = !editing.value;
} }
function on_click(event: MouseEvent) { function on_click(event: MouseEvent) {
@ -44,7 +44,7 @@ function on_click(event: MouseEvent) {
return; return;
} }
if (!editing) { if (!editing.value) {
const day_input_focus = () => { const day_input_focus = () => {
if (day_input.value === null) { if (day_input.value === null) {
nextTick(day_input_focus); nextTick(day_input_focus);
@ -62,7 +62,7 @@ function on_click(event: MouseEvent) {
} }
function on_keydown(event: KeyboardEvent) { function on_keydown(event: KeyboardEvent) {
if (!editing) { if (!editing.value) {
return; return;
} }

View file

@ -37,8 +37,3 @@ export function handle_error(error: unknown) {
export function name_door(day: number): string { export function name_door(day: number): string {
return `Türchen ${day}`; return `Türchen ${day}`;
} }
export interface Step {
label: string;
icon: string | string[];
}

View file

@ -1,3 +1,4 @@
import { UnwrapRef } from "vue";
import { DoorSaved } from "../model"; import { DoorSaved } from "../model";
import { Rectangle } from "./rectangle"; import { Rectangle } from "./rectangle";
import { Vector2D } from "./vector2d"; import { Vector2D } from "./vector2d";
@ -50,3 +51,5 @@ export class Door {
}; };
} }
} }
export type VueDoor = UnwrapRef<Door>;

View file

@ -1,3 +1,4 @@
import { UnwrapRef } from "vue";
import { Vector2D } from "./vector2d"; import { Vector2D } from "./vector2d";
export class Rectangle { export class Rectangle {
@ -77,3 +78,5 @@ export class Rectangle {
); );
} }
} }
export type VueRectangle = UnwrapRef<Rectangle>;