🔧 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">
|
||||
import { API } from "@/lib/api";
|
||||
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 { VueDoor } from "@/lib/rects/door";
|
||||
import { Door } from "@/lib/rects/door";
|
||||
import { advent22Store } from "@/lib/store";
|
||||
|
||||
import { onBeforeUnmount } from "vue";
|
||||
|
|
@ -62,7 +62,7 @@ import CalendarDoor from "./calendar/CalendarDoor.vue";
|
|||
import ThouCanvas from "./calendar/ThouCanvas.vue";
|
||||
|
||||
defineProps<{
|
||||
doors: VueDoor[];
|
||||
doors: Like<Door>[];
|
||||
}>();
|
||||
|
||||
const store = advent22Store();
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { wait_for } from "@/lib/helpers";
|
||||
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";
|
||||
|
||||
const username_input = useTemplateRef("username_input");
|
||||
|
|
@ -78,9 +79,10 @@ onMounted(() => {
|
|||
|
||||
window.addEventListener("keydown", on_keydown);
|
||||
|
||||
nextTick(() => {
|
||||
username_input.value?.focus();
|
||||
});
|
||||
wait_for(
|
||||
() => username_input.value !== null,
|
||||
() => username_input.value!.focus(),
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("keydown", on_keydown);
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<DoorPlacer v-if="current_step === 0" v-model="(doors as Door[])" />
|
||||
<DoorChooser v-if="current_step === 1" v-model="(doors as Door[])" />
|
||||
<DoorPlacer v-if="current_step === 0" v-model="doors" />
|
||||
<DoorChooser v-if="current_step === 1" v-model="doors" />
|
||||
<div v-if="current_step === 2" class="card-content">
|
||||
<Calendar :doors="(doors as Door[])" />
|
||||
<Calendar :doors="doors" />
|
||||
</div>
|
||||
|
||||
<footer class="card-footer is-flex is-justify-content-space-around">
|
||||
|
|
|
|||
|
|
@ -14,16 +14,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueDoor } from "@/lib/rects/door";
|
||||
import { Door } from "@/lib/rects/door";
|
||||
import { advent22Store } from "@/lib/store";
|
||||
|
||||
import { Like } from "@/lib/helpers";
|
||||
import SVGRect from "./SVGRect.vue";
|
||||
|
||||
const store = advent22Store();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
door: VueDoor;
|
||||
door: Like<Door>;
|
||||
force_visible?: boolean;
|
||||
}>(),
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { loading_success } from "@/lib/helpers";
|
||||
import { VueRectangle } from "@/lib/rects/rectangle";
|
||||
import { Like, loading_success } from "@/lib/helpers";
|
||||
import { Rectangle } from "@/lib/rects/rectangle";
|
||||
import { advent22Store } from "@/lib/store";
|
||||
import { computed } from "vue";
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ withDefaults(
|
|||
defineProps<{
|
||||
variant: BulmaVariant;
|
||||
visible?: boolean;
|
||||
rectangle: VueRectangle;
|
||||
rectangle: Like<Rectangle>;
|
||||
}>(),
|
||||
{
|
||||
visible: true,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { Rectangle } from "@/lib/rects/rectangle";
|
|||
import { Vector2D } from "@/lib/rects/vector2d";
|
||||
import { computed, ref } from "vue";
|
||||
|
||||
import { Like } from "@/lib/helpers";
|
||||
import CalendarDoor from "../calendar/CalendarDoor.vue";
|
||||
import SVGRect from "../calendar/SVGRect.vue";
|
||||
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||
|
|
@ -36,9 +37,9 @@ import ThouCanvas from "../calendar/ThouCanvas.vue";
|
|||
type CanvasState =
|
||||
| { kind: "idle" }
|
||||
| { 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 state = ref<CanvasState>({ kind: "idle" });
|
||||
|
|
@ -46,7 +47,7 @@ const preview = ref(new Rectangle());
|
|||
|
||||
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));
|
||||
|
||||
if (idx === -1) {
|
||||
|
|
@ -71,7 +72,7 @@ function draw_finish() {
|
|||
}
|
||||
|
||||
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" };
|
||||
|
|
@ -98,7 +99,7 @@ function drag_finish() {
|
|||
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" };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ensure_loaded } from "@/lib/helpers";
|
||||
import { ensure_loaded, Like } from "@/lib/helpers";
|
||||
import { Door } from "@/lib/rects/door";
|
||||
import { advent22Store } from "@/lib/store";
|
||||
|
||||
import ThouCanvas from "../calendar/ThouCanvas.vue";
|
||||
import PreviewDoor from "./PreviewDoor.vue";
|
||||
|
||||
const model = defineModel<Door[]>({ required: true });
|
||||
const model = defineModel<Like<Door>[]>({ required: true });
|
||||
const store = advent22Store();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ensure_loaded } from "@/lib/helpers";
|
||||
import { ensure_loaded, Like } from "@/lib/helpers";
|
||||
import { Door } from "@/lib/rects/door";
|
||||
import { advent22Store } from "@/lib/store";
|
||||
|
||||
import DoorCanvas from "./DoorCanvas.vue";
|
||||
|
||||
const model = defineModel<Door[]>({ required: true });
|
||||
const model = defineModel<Like<Door>[]>({ required: true });
|
||||
const store = advent22Store();
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
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";
|
||||
|
||||
const model = defineModel<Door>({ required: true });
|
||||
const model = defineModel<Like<Door>>({ required: true });
|
||||
const day_input = useTemplateRef("day_input");
|
||||
|
||||
const day_str = ref("");
|
||||
|
|
@ -44,18 +45,13 @@ function on_click(event: MouseEvent) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!editing.value) {
|
||||
const day_input_focus = () => {
|
||||
if (day_input.value === null) {
|
||||
nextTick(day_input_focus);
|
||||
return;
|
||||
}
|
||||
|
||||
day_input.value.select();
|
||||
};
|
||||
day_input_focus();
|
||||
if (editing.value) {
|
||||
unwrap_like(model.value).day = day_str.value;
|
||||
} else {
|
||||
model.value.day = day_str.value;
|
||||
wait_for(
|
||||
() => day_input.value !== null,
|
||||
() => day_input.value!.select(),
|
||||
);
|
||||
}
|
||||
|
||||
toggle_editing();
|
||||
|
|
@ -67,7 +63,7 @@ function on_keydown(event: KeyboardEvent) {
|
|||
}
|
||||
|
||||
if (event.key === "Enter") {
|
||||
model.value.day = day_str.value;
|
||||
unwrap_like(model.value).day = day_str.value;
|
||||
toggle_editing();
|
||||
} else if (event.key === "Delete") {
|
||||
model.value.day = -1;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { nextTick, UnwrapRef } from "vue";
|
||||
import { APIError } from "./api_error";
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (error instanceof APIError) {
|
||||
error.alert();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { UnwrapRef } from "vue";
|
||||
import { Like, unwrap_like } from "../helpers";
|
||||
import { DoorSaved } from "../model";
|
||||
import { Rectangle } from "./rectangle";
|
||||
import { Vector2D } from "./vector2d";
|
||||
|
|
@ -9,26 +9,27 @@ export class Door {
|
|||
private _day = Door.MIN_DAY;
|
||||
public position: Rectangle;
|
||||
|
||||
constructor(position: Rectangle);
|
||||
constructor(position: Rectangle, day: number);
|
||||
constructor(position: Rectangle, day = Door.MIN_DAY) {
|
||||
constructor(position: Like<Rectangle>);
|
||||
constructor(position: Like<Rectangle>, day: number);
|
||||
constructor(position: Like<Rectangle>, day = Door.MIN_DAY) {
|
||||
this.day = day;
|
||||
this.position = position;
|
||||
this.position = unwrap_like(position);
|
||||
}
|
||||
|
||||
public get day(): number {
|
||||
return this._day;
|
||||
}
|
||||
|
||||
public set day(day: unknown) {
|
||||
public set day(value: number | string) {
|
||||
// integer coercion
|
||||
const result = Number(day);
|
||||
let day = Number(value);
|
||||
|
||||
if (isNaN(result)) {
|
||||
this._day = Door.MIN_DAY;
|
||||
} else {
|
||||
this._day = Math.max(Math.floor(result), Door.MIN_DAY);
|
||||
}
|
||||
day =
|
||||
!Number.isNaN(day) && Number.isFinite(day)
|
||||
? Math.trunc(day)
|
||||
: Door.MIN_DAY;
|
||||
|
||||
this._day = Math.max(day, Door.MIN_DAY);
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
export class Rectangle {
|
||||
|
|
@ -78,5 +77,3 @@ export class Rectangle {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
export type VueRectangle = UnwrapRef<Rectangle>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue