advent22/ui/src/components/editor/DoorCanvas.vue
Jörn-Michael Miehe f72d8e65a5 🧰 ui: tooling update
- new devcontainer spec "18-bookworm" -> "20-trixie"
- fixed "vue serve"
- improved vscode settings.json
- improved typescript config
- minor formatting
2026-02-12 21:59:41 +00:00

119 lines
2.9 KiB
Vue

<template>
<ThouCanvas
@mousedown.left="draw_start"
@mouseup.left="draw_finish"
@mousedown.right="drag_start"
@mouseup.right="drag_finish"
@mousemove="on_mousemove"
@click.middle="remove_rect"
@dblclick.left="remove_rect"
>
<CalendarDoor
v-for="(door, index) in model"
:key="`door-${index}`"
:door="door"
force_visible
/>
<SVGRect
v-if="preview_visible"
variant="success"
:rectangle="preview"
visible
/>
</ThouCanvas>
</template>
<script setup lang="ts">
import { Door } from "@/lib/rects/door";
import { Rectangle } from "@/lib/rects/rectangle";
import { Vector2D } from "@/lib/rects/vector2d";
import { computed, ref } from "vue";
import type { VueLike } from "@/lib/helpers";
import CalendarDoor from "../calendar/CalendarDoor.vue";
import SVGRect from "../calendar/SVGRect.vue";
import ThouCanvas from "../calendar/ThouCanvas.vue";
type CanvasState =
| { kind: "idle" }
| { kind: "drawing" }
| { kind: "dragging"; door: VueLike<Door>; origin: Vector2D };
const model = defineModel<VueLike<Door>[]>({ required: true });
const MIN_RECT_AREA = 300;
const state = ref<CanvasState>({ kind: "idle" });
const preview = ref(new Rectangle());
const preview_visible = computed(() => state.value.kind !== "idle");
function pop_door(point: Vector2D): VueLike<Door> | undefined {
const idx = model.value.findIndex((rect) => rect.position.contains(point));
if (idx === -1) return;
return model.value.splice(idx, 1)[0];
}
function draw_start(event: MouseEvent, point: Vector2D): void {
if (preview_visible.value) return;
preview.value = new Rectangle(point, point);
state.value = { kind: "drawing" };
}
function draw_finish(): void {
if (state.value.kind !== "drawing") return;
if (preview.value.area >= MIN_RECT_AREA) {
model.value.push(new Door(preview.value));
}
state.value = { kind: "idle" };
}
function drag_start(event: MouseEvent, point: Vector2D): void {
if (preview_visible.value) return;
const drag_door = pop_door(point);
if (drag_door === undefined) return;
preview.value = drag_door.position;
state.value = { kind: "dragging", door: drag_door, origin: point };
}
function drag_finish(): void {
if (state.value.kind !== "dragging") return;
model.value.push(new Door(preview.value, state.value.door.day));
state.value = { kind: "idle" };
}
function on_mousemove(event: MouseEvent, point: Vector2D): void {
if (state.value.kind === "drawing") {
preview.value = preview.value.update(undefined, point);
} else if (state.value.kind === "dragging") {
const movement = point.minus(state.value.origin);
preview.value = state.value.door.position.move(movement);
}
}
function remove_rect(event: MouseEvent, point: Vector2D): void {
if (preview_visible.value) return;
pop_door(point);
}
</script>
<style lang="scss" scoped>
svg {
cursor: crosshair;
* {
pointer-events: none;
}
}
</style>