mirror of
https://code.lenaisten.de/Lenaisten/advent22.git
synced 2024-11-23 08:13:01 +00:00
CalendarImage -> DoorMapEditor
This commit is contained in:
parent
eae3eed29b
commit
10751cb798
6 changed files with 195 additions and 150 deletions
|
@ -9,7 +9,7 @@
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="subtitle has-text-centered">Der Gelöt</h2>
|
<h2 class="subtitle has-text-centered">Der Gelöt</h2>
|
||||||
|
|
||||||
<CalendarImage />
|
<DoorMapEditor />
|
||||||
|
|
||||||
<CalendarDoor
|
<CalendarDoor
|
||||||
v-for="(_, index) in 24"
|
v-for="(_, index) in 24"
|
||||||
|
@ -31,14 +31,14 @@
|
||||||
import { Options, Vue } from "vue-class-component";
|
import { Options, Vue } from "vue-class-component";
|
||||||
|
|
||||||
import CalendarDoor from "./components/CalendarDoor.vue";
|
import CalendarDoor from "./components/CalendarDoor.vue";
|
||||||
import CalendarImage from "./components/CalendarImage.vue";
|
import DoorMapEditor from "./components/DoorMapEditor.vue";
|
||||||
import ImageModal from "./components/ImageModal.vue";
|
import ImageModal from "./components/ImageModal.vue";
|
||||||
import LoginModal from "./components/LoginModal.vue";
|
import LoginModal from "./components/LoginModal.vue";
|
||||||
|
|
||||||
@Options({
|
@Options({
|
||||||
components: {
|
components: {
|
||||||
CalendarDoor,
|
CalendarDoor,
|
||||||
CalendarImage,
|
DoorMapEditor,
|
||||||
ImageModal,
|
ImageModal,
|
||||||
LoginModal,
|
LoginModal,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="container" ref="container">
|
|
||||||
<img id="background" ref="background" src="@/assets/adventskalender.png" />
|
|
||||||
<svg
|
|
||||||
id="drawpad"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 1000 1000"
|
|
||||||
preserveAspectRatio="none"
|
|
||||||
@pointerdown="on_pointerdown"
|
|
||||||
@pointermove="on_pointermove"
|
|
||||||
@pointerup="on_pointerup"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
v-if="preview_state.visible"
|
|
||||||
class="focus"
|
|
||||||
:x="preview_rectangle.left"
|
|
||||||
:y="preview_rectangle.top"
|
|
||||||
:width="preview_rectangle.width"
|
|
||||||
:height="preview_rectangle.height"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
v-for="(rect, index) in rectangles"
|
|
||||||
:key="'rect' + index"
|
|
||||||
:x="rect.left"
|
|
||||||
:y="rect.top"
|
|
||||||
:width="rect.width"
|
|
||||||
:height="rect.height"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Vue } from "vue-class-component";
|
|
||||||
import { Vector2D, Rectangle } from "./rectangles";
|
|
||||||
|
|
||||||
function get_event_thous(event: MouseEvent): Vector2D {
|
|
||||||
if (event.currentTarget === null) {
|
|
||||||
return new Vector2D();
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = event.currentTarget as Element;
|
|
||||||
|
|
||||||
return new Vector2D(
|
|
||||||
Math.round((event.offsetX / target.clientWidth) * 1000),
|
|
||||||
Math.round((event.offsetY / target.clientHeight) * 1000)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CalendarImage extends Vue {
|
|
||||||
// "preview" rectangle on click-drag
|
|
||||||
|
|
||||||
private preview_state = {
|
|
||||||
visible: false,
|
|
||||||
corner1: new Vector2D(),
|
|
||||||
corner2: new Vector2D(),
|
|
||||||
};
|
|
||||||
private readonly min_rect_area = 4;
|
|
||||||
private rectangles: Rectangle[] = [];
|
|
||||||
|
|
||||||
private on_pointerdown(event: MouseEvent) {
|
|
||||||
this.preview_state.visible = true;
|
|
||||||
this.preview_state.corner1 = get_event_thous(event);
|
|
||||||
this.preview_state.corner2 = get_event_thous(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private on_pointermove(event: MouseEvent) {
|
|
||||||
this.preview_state.corner2 = get_event_thous(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
private on_pointerup() {
|
|
||||||
this.preview_state.visible = false;
|
|
||||||
if (this.preview_rectangle.area >= this.min_rect_area) {
|
|
||||||
this.rectangles.push(this.preview_rectangle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private get preview_rectangle(): Rectangle {
|
|
||||||
return new Rectangle(
|
|
||||||
this.preview_state.corner1,
|
|
||||||
this.preview_state.corner2
|
|
||||||
).normalize();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hook "resize" events
|
|
||||||
|
|
||||||
private resize_observer?: ResizeObserver;
|
|
||||||
|
|
||||||
declare $refs: {
|
|
||||||
container: HTMLDivElement;
|
|
||||||
background: HTMLImageElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
private on_resize() {
|
|
||||||
this.$refs.container.style.setProperty(
|
|
||||||
"height",
|
|
||||||
this.$refs.background.offsetHeight + "px"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public mounted() {
|
|
||||||
this.resize_observer = new ResizeObserver(this.on_resize);
|
|
||||||
this.resize_observer.observe(this.$refs.background);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unmounted() {
|
|
||||||
if (this.resize_observer instanceof ResizeObserver) {
|
|
||||||
this.resize_observer.disconnect();
|
|
||||||
delete this.resize_observer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
div#container {
|
|
||||||
position: relative;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
img#background {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg#drawpad {
|
|
||||||
cursor: crosshair;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
rect {
|
|
||||||
fill: lightgreen;
|
|
||||||
stroke: green;
|
|
||||||
fill-opacity: 0.2;
|
|
||||||
stroke-opacity: 0.9;
|
|
||||||
stroke-width: 1;
|
|
||||||
|
|
||||||
&.focus {
|
|
||||||
fill: gold;
|
|
||||||
stroke: yellow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
64
ui/src/components/DoorMapEditor.vue
Normal file
64
ui/src/components/DoorMapEditor.vue
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<template>
|
||||||
|
<div ref="container">
|
||||||
|
<img ref="background" src="@/assets/adventskalender.png" />
|
||||||
|
<RectPad id="rectpad" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Options } from "vue-class-component";
|
||||||
|
import RectPad from "./rects/RectPad.vue";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {
|
||||||
|
RectPad,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class CalendarImage extends Vue {
|
||||||
|
private resize_observer?: ResizeObserver;
|
||||||
|
|
||||||
|
declare $refs: {
|
||||||
|
container: HTMLDivElement;
|
||||||
|
background: HTMLImageElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
private on_resize() {
|
||||||
|
this.$refs.container.style.setProperty(
|
||||||
|
"height",
|
||||||
|
this.$refs.background.offsetHeight + "px"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public mounted() {
|
||||||
|
this.resize_observer = new ResizeObserver(this.on_resize);
|
||||||
|
this.resize_observer.observe(this.$refs.background);
|
||||||
|
}
|
||||||
|
|
||||||
|
public unmounted() {
|
||||||
|
if (this.resize_observer instanceof ResizeObserver) {
|
||||||
|
this.resize_observer.disconnect();
|
||||||
|
delete this.resize_observer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
div {
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#rectpad {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
43
ui/src/components/rects/Rect.vue
Normal file
43
ui/src/components/rects/Rect.vue
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<template>
|
||||||
|
<rect
|
||||||
|
:class="focused ? 'focused' : ''"
|
||||||
|
:x="rectangle.left"
|
||||||
|
:y="rectangle.top"
|
||||||
|
:width="rectangle.width"
|
||||||
|
:height="rectangle.height"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Options } from "vue-class-component";
|
||||||
|
import { Rectangle } from "./rectangles";
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
props: {
|
||||||
|
focused: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
rectangle: Rectangle,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class Rect extends Vue {
|
||||||
|
private focused!: boolean;
|
||||||
|
private rectangle!: Rectangle;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
rect {
|
||||||
|
fill: lightgreen;
|
||||||
|
stroke: green;
|
||||||
|
fill-opacity: 0.2;
|
||||||
|
stroke-opacity: 0.9;
|
||||||
|
stroke-width: 1;
|
||||||
|
|
||||||
|
&.focused {
|
||||||
|
fill: gold;
|
||||||
|
stroke: yellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
85
ui/src/components/rects/RectPad.vue
Normal file
85
ui/src/components/rects/RectPad.vue
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1000 1000"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
@pointerdown="on_pointerdown"
|
||||||
|
@pointermove="on_pointermove"
|
||||||
|
@pointerup="on_pointerup"
|
||||||
|
>
|
||||||
|
<Rect
|
||||||
|
v-if="preview_state.visible"
|
||||||
|
:focused="true"
|
||||||
|
:rectangle="preview_rectangle"
|
||||||
|
/>
|
||||||
|
<Rect
|
||||||
|
v-for="(rect, index) in rectangles"
|
||||||
|
:key="'rect' + index"
|
||||||
|
:rectangle="rect"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Vue, Options } from "vue-class-component";
|
||||||
|
import { Vector2D, Rectangle } from "./rectangles";
|
||||||
|
import Rect from "./Rect.vue";
|
||||||
|
|
||||||
|
function get_event_thous(event: MouseEvent): Vector2D {
|
||||||
|
if (event.currentTarget === null) {
|
||||||
|
return new Vector2D();
|
||||||
|
}
|
||||||
|
|
||||||
|
let target = event.currentTarget as Element;
|
||||||
|
|
||||||
|
return new Vector2D(
|
||||||
|
Math.round((event.offsetX / target.clientWidth) * 1000),
|
||||||
|
Math.round((event.offsetY / target.clientHeight) * 1000)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Options({
|
||||||
|
components: {
|
||||||
|
Rect,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class RectPad extends Vue {
|
||||||
|
private preview_state = {
|
||||||
|
visible: false,
|
||||||
|
corner1: new Vector2D(),
|
||||||
|
corner2: new Vector2D(),
|
||||||
|
};
|
||||||
|
private readonly min_rect_area = 4;
|
||||||
|
private rectangles: Rectangle[] = [];
|
||||||
|
|
||||||
|
private on_pointerdown(event: MouseEvent) {
|
||||||
|
this.preview_state.visible = true;
|
||||||
|
this.preview_state.corner1 = get_event_thous(event);
|
||||||
|
this.preview_state.corner2 = get_event_thous(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_pointermove(event: MouseEvent) {
|
||||||
|
this.preview_state.corner2 = get_event_thous(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private on_pointerup() {
|
||||||
|
this.preview_state.visible = false;
|
||||||
|
if (this.preview_rectangle.area >= this.min_rect_area) {
|
||||||
|
this.rectangles.push(this.preview_rectangle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get preview_rectangle(): Rectangle {
|
||||||
|
return new Rectangle(
|
||||||
|
this.preview_state.corner1,
|
||||||
|
this.preview_state.corner2
|
||||||
|
).normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
svg {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue