🔧 ui: overall typescript compliance
- reduce use of `as` in favor of Vue's `UnwrapRef`
- add `wait_for` helper function
⚠️ known bug: PreviewDoor (DoorChooser) onClick not working
This commit is contained in:
parent
6ff5af45d5
commit
fd1a66ba25
12 changed files with 70 additions and 55 deletions
|
|
@ -49,9 +49,9 @@
|
||||||
<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 { ensure_loaded, name_door } from "@/lib/helpers";
|
import { ensure_loaded, Like, name_door } from "@/lib/helpers";
|
||||||
import { ImageData } from "@/lib/model";
|
import { ImageData } from "@/lib/model";
|
||||||
import { VueDoor } from "@/lib/rects/door";
|
import { Door } 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: VueDoor[];
|
doors: Like<Door>[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const store = advent22Store();
|
const store = advent22Store();
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { wait_for } from "@/lib/helpers";
|
||||||
import { Credentials } from "@/lib/model";
|
import { Credentials } from "@/lib/model";
|
||||||
import { nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
|
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
|
||||||
import BulmaButton from "./bulma/Button.vue";
|
import BulmaButton from "./bulma/Button.vue";
|
||||||
|
|
||||||
const username_input = useTemplateRef("username_input");
|
const username_input = useTemplateRef("username_input");
|
||||||
|
|
@ -78,9 +79,10 @@ onMounted(() => {
|
||||||
|
|
||||||
window.addEventListener("keydown", on_keydown);
|
window.addEventListener("keydown", on_keydown);
|
||||||
|
|
||||||
nextTick(() => {
|
wait_for(
|
||||||
username_input.value?.focus();
|
() => username_input.value !== null,
|
||||||
});
|
() => username_input.value!.focus(),
|
||||||
|
);
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener("keydown", on_keydown);
|
window.removeEventListener("keydown", on_keydown);
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DoorPlacer v-if="current_step === 0" v-model="(doors as Door[])" />
|
<DoorPlacer v-if="current_step === 0" v-model="doors" />
|
||||||
<DoorChooser v-if="current_step === 1" v-model="(doors as Door[])" />
|
<DoorChooser v-if="current_step === 1" v-model="doors" />
|
||||||
<div v-if="current_step === 2" class="card-content">
|
<div v-if="current_step === 2" class="card-content">
|
||||||
<Calendar :doors="(doors as Door[])" />
|
<Calendar :doors="doors" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="card-footer is-flex is-justify-content-space-around">
|
<footer class="card-footer is-flex is-justify-content-space-around">
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VueDoor } from "@/lib/rects/door";
|
import { Door } from "@/lib/rects/door";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
|
|
||||||
|
import { Like } from "@/lib/helpers";
|
||||||
import SVGRect from "./SVGRect.vue";
|
import SVGRect from "./SVGRect.vue";
|
||||||
|
|
||||||
const store = advent22Store();
|
const store = advent22Store();
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
door: VueDoor;
|
door: Like<Door>;
|
||||||
force_visible?: boolean;
|
force_visible?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { loading_success } from "@/lib/helpers";
|
import { Like, loading_success } from "@/lib/helpers";
|
||||||
import { VueRectangle } from "@/lib/rects/rectangle";
|
import { Rectangle } from "@/lib/rects/rectangle";
|
||||||
import { advent22Store } from "@/lib/store";
|
import { advent22Store } from "@/lib/store";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
variant: BulmaVariant;
|
variant: BulmaVariant;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
rectangle: VueRectangle;
|
rectangle: Like<Rectangle>;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
visible: true,
|
visible: true,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import { Rectangle } from "@/lib/rects/rectangle";
|
||||||
import { Vector2D } from "@/lib/rects/vector2d";
|
import { Vector2D } from "@/lib/rects/vector2d";
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
|
|
||||||
|
import { Like } from "@/lib/helpers";
|
||||||
import CalendarDoor from "../calendar/CalendarDoor.vue";
|
import CalendarDoor from "../calendar/CalendarDoor.vue";
|
||||||
import SVGRect from "../calendar/SVGRect.vue";
|
import SVGRect from "../calendar/SVGRect.vue";
|
||||||
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||||
|
|
@ -36,9 +37,9 @@ import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||||
type CanvasState =
|
type CanvasState =
|
||||||
| { kind: "idle" }
|
| { kind: "idle" }
|
||||||
| { kind: "drawing" }
|
| { kind: "drawing" }
|
||||||
| { kind: "dragging"; door: Door; origin: Vector2D };
|
| { kind: "dragging"; door: Like<Door>; origin: Vector2D };
|
||||||
|
|
||||||
const model = defineModel<Door[]>({ required: true });
|
const model = defineModel<Like<Door>[]>({ required: true });
|
||||||
|
|
||||||
const MIN_RECT_AREA = 300;
|
const MIN_RECT_AREA = 300;
|
||||||
const state = ref<CanvasState>({ kind: "idle" });
|
const state = ref<CanvasState>({ kind: "idle" });
|
||||||
|
|
@ -46,7 +47,7 @@ const preview = ref(new Rectangle());
|
||||||
|
|
||||||
const preview_visible = computed(() => state.value.kind !== "idle");
|
const preview_visible = computed(() => state.value.kind !== "idle");
|
||||||
|
|
||||||
function pop_door(point: Vector2D): Door | undefined {
|
function pop_door(point: Vector2D): Like<Door> | undefined {
|
||||||
const idx = model.value.findIndex((rect) => rect.position.contains(point));
|
const idx = model.value.findIndex((rect) => rect.position.contains(point));
|
||||||
|
|
||||||
if (idx === -1) {
|
if (idx === -1) {
|
||||||
|
|
@ -71,7 +72,7 @@ function draw_finish() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preview.value.area >= MIN_RECT_AREA) {
|
if (preview.value.area >= MIN_RECT_AREA) {
|
||||||
model.value.push(new Door(preview.value as Rectangle));
|
model.value.push(new Door(preview.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.value = { kind: "idle" };
|
state.value = { kind: "idle" };
|
||||||
|
|
@ -98,7 +99,7 @@ function drag_finish() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.value.push(new Door(preview.value as Rectangle, state.value.door.day));
|
model.value.push(new Door(preview.value, state.value.door.day));
|
||||||
|
|
||||||
state.value = { kind: "idle" };
|
state.value = { kind: "idle" };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ensure_loaded } from "@/lib/helpers";
|
import { ensure_loaded, Like } 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 ThouCanvas from "../calendar/ThouCanvas.vue";
|
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||||
import PreviewDoor from "./PreviewDoor.vue";
|
import PreviewDoor from "./PreviewDoor.vue";
|
||||||
|
|
||||||
const model = defineModel<Door[]>({ required: true });
|
const model = defineModel<Like<Door>[]>({ required: true });
|
||||||
const store = advent22Store();
|
const store = advent22Store();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ensure_loaded } from "@/lib/helpers";
|
import { ensure_loaded, Like } 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 DoorCanvas from "./DoorCanvas.vue";
|
import DoorCanvas from "./DoorCanvas.vue";
|
||||||
|
|
||||||
const model = defineModel<Door[]>({ required: true });
|
const model = defineModel<Like<Door>[]>({ required: true });
|
||||||
const store = advent22Store();
|
const store = advent22Store();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,12 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Door } from "@/lib/rects/door";
|
import { Door } from "@/lib/rects/door";
|
||||||
import { nextTick, ref, useTemplateRef } from "vue";
|
import { ref, useTemplateRef } from "vue";
|
||||||
|
|
||||||
|
import { Like, unwrap_like, wait_for } from "@/lib/helpers";
|
||||||
import SVGRect from "../calendar/SVGRect.vue";
|
import SVGRect from "../calendar/SVGRect.vue";
|
||||||
|
|
||||||
const model = defineModel<Door>({ required: true });
|
const model = defineModel<Like<Door>>({ required: true });
|
||||||
const day_input = useTemplateRef("day_input");
|
const day_input = useTemplateRef("day_input");
|
||||||
|
|
||||||
const day_str = ref("");
|
const day_str = ref("");
|
||||||
|
|
@ -44,18 +45,13 @@ function on_click(event: MouseEvent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!editing.value) {
|
if (editing.value) {
|
||||||
const day_input_focus = () => {
|
unwrap_like(model.value).day = day_str.value;
|
||||||
if (day_input.value === null) {
|
|
||||||
nextTick(day_input_focus);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
day_input.value.select();
|
|
||||||
};
|
|
||||||
day_input_focus();
|
|
||||||
} else {
|
} else {
|
||||||
model.value.day = day_str.value;
|
wait_for(
|
||||||
|
() => day_input.value !== null,
|
||||||
|
() => day_input.value!.select(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle_editing();
|
toggle_editing();
|
||||||
|
|
@ -67,7 +63,7 @@ function on_keydown(event: KeyboardEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
model.value.day = day_str.value;
|
unwrap_like(model.value).day = day_str.value;
|
||||||
toggle_editing();
|
toggle_editing();
|
||||||
} else if (event.key === "Delete") {
|
} else if (event.key === "Delete") {
|
||||||
model.value.day = -1;
|
model.value.day = -1;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { nextTick, UnwrapRef } from "vue";
|
||||||
import { APIError } from "./api_error";
|
import { APIError } from "./api_error";
|
||||||
|
|
||||||
export function objForEach<T>(
|
export function objForEach<T>(
|
||||||
|
|
@ -11,6 +12,12 @@ export function objForEach<T>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Like<T> = T | UnwrapRef<T>;
|
||||||
|
|
||||||
|
export function unwrap_like<T>(value: Like<T>): T {
|
||||||
|
return value as T;
|
||||||
|
}
|
||||||
|
|
||||||
export type Loading<T> = T | "loading" | "error";
|
export type Loading<T> = T | "loading" | "error";
|
||||||
|
|
||||||
export function loading_success<T>(o: Loading<T>): o is T {
|
export function loading_success<T>(o: Loading<T>): o is T {
|
||||||
|
|
@ -26,6 +33,18 @@ export function ensure_loaded<T>(o: Loading<T>): T {
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function wait_for(condition: () => boolean, action: () => void) {
|
||||||
|
const do_action = () => {
|
||||||
|
if (!condition()) {
|
||||||
|
nextTick(do_action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
action();
|
||||||
|
};
|
||||||
|
do_action();
|
||||||
|
}
|
||||||
|
|
||||||
export function handle_error(error: unknown) {
|
export function handle_error(error: unknown) {
|
||||||
if (error instanceof APIError) {
|
if (error instanceof APIError) {
|
||||||
error.alert();
|
error.alert();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { UnwrapRef } from "vue";
|
import { Like, unwrap_like } from "../helpers";
|
||||||
import { DoorSaved } from "../model";
|
import { DoorSaved } from "../model";
|
||||||
import { Rectangle } from "./rectangle";
|
import { Rectangle } from "./rectangle";
|
||||||
import { Vector2D } from "./vector2d";
|
import { Vector2D } from "./vector2d";
|
||||||
|
|
@ -9,26 +9,27 @@ export class Door {
|
||||||
private _day = Door.MIN_DAY;
|
private _day = Door.MIN_DAY;
|
||||||
public position: Rectangle;
|
public position: Rectangle;
|
||||||
|
|
||||||
constructor(position: Rectangle);
|
constructor(position: Like<Rectangle>);
|
||||||
constructor(position: Rectangle, day: number);
|
constructor(position: Like<Rectangle>, day: number);
|
||||||
constructor(position: Rectangle, day = Door.MIN_DAY) {
|
constructor(position: Like<Rectangle>, day = Door.MIN_DAY) {
|
||||||
this.day = day;
|
this.day = day;
|
||||||
this.position = position;
|
this.position = unwrap_like(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get day(): number {
|
public get day(): number {
|
||||||
return this._day;
|
return this._day;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set day(day: unknown) {
|
public set day(value: number | string) {
|
||||||
// integer coercion
|
// integer coercion
|
||||||
const result = Number(day);
|
let day = Number(value);
|
||||||
|
|
||||||
if (isNaN(result)) {
|
day =
|
||||||
this._day = Door.MIN_DAY;
|
!Number.isNaN(day) && Number.isFinite(day)
|
||||||
} else {
|
? Math.trunc(day)
|
||||||
this._day = Math.max(Math.floor(result), Door.MIN_DAY);
|
: Door.MIN_DAY;
|
||||||
}
|
|
||||||
|
this._day = Math.max(day, Door.MIN_DAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static load(serialized: DoorSaved): Door {
|
public static load(serialized: DoorSaved): Door {
|
||||||
|
|
@ -51,5 +52,3 @@ export class Door {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VueDoor = UnwrapRef<Door>;
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { UnwrapRef } from "vue";
|
|
||||||
import { Vector2D } from "./vector2d";
|
import { Vector2D } from "./vector2d";
|
||||||
|
|
||||||
export class Rectangle {
|
export class Rectangle {
|
||||||
|
|
@ -78,5 +77,3 @@ export class Rectangle {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VueRectangle = UnwrapRef<Rectangle>;
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue