2021-06-26 17:55:26 +02:00
|
|
|
<template>
|
|
|
|
<div class="compass-widget">
|
|
|
|
<div class="compass-row-helper">
|
|
|
|
<div class="compass-label compass-label-left">{{ leftLabel }}</div>
|
|
|
|
<div class="compass-container">
|
|
|
|
<div class="compass-label compass-label-top">{{ topLabel }}</div>
|
2021-06-27 00:46:06 +02:00
|
|
|
<div
|
|
|
|
class="compass"
|
|
|
|
ref="compass"
|
|
|
|
@mousedown="onMouse"
|
|
|
|
@mousemove="onMouse"
|
|
|
|
>
|
2021-06-26 17:55:26 +02:00
|
|
|
<div class="compass-x-axis"></div>
|
|
|
|
<div class="compass-y-axis"></div>
|
|
|
|
<div
|
|
|
|
class="compass-marker compass-select-marker"
|
|
|
|
:style="{
|
|
|
|
bottom: markerBottom,
|
|
|
|
left: markerLeft,
|
|
|
|
}"
|
|
|
|
></div>
|
|
|
|
<div class="context-markers"></div>
|
|
|
|
</div>
|
|
|
|
<div class="compass-label compass-label-bottom">{{ bottomLabel }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="compass-label compass-label-right">{{ rightLabel }}</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="compass-fields">
|
|
|
|
<div class="compass-field">
|
|
|
|
<label for="compass_field_x_{{ widget.name }}">
|
|
|
|
{{ xLabel }}
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
v-model.number="xVal"
|
|
|
|
:min="-xRange"
|
|
|
|
:max="xRange"
|
|
|
|
/>
|
|
|
|
</div>
|
2021-06-27 00:46:06 +02:00
|
|
|
<sl-icon-button name="save" :disabled="!dirty" @click="submit" />
|
2021-06-26 17:55:26 +02:00
|
|
|
<div class="compass-field">
|
|
|
|
<label for="compass_field_y_{{ widget.name }}">
|
|
|
|
{{ yLabel }}
|
|
|
|
</label>
|
|
|
|
<input
|
|
|
|
type="number"
|
|
|
|
v-model.number="yVal"
|
|
|
|
:min="-yRange"
|
|
|
|
:max="yRange"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts">
|
|
|
|
import { asDict } from "@/lib/entity";
|
2021-06-27 00:46:06 +02:00
|
|
|
import { AttributeChange, IEntry } from "@/types/base";
|
|
|
|
import { defineComponent, PropType, ref, watch } from "vue";
|
2021-06-26 17:55:26 +02:00
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
name: "Compass",
|
2021-06-27 00:46:06 +02:00
|
|
|
emits: ["edit"],
|
2021-06-26 17:55:26 +02:00
|
|
|
props: {
|
|
|
|
attributes: {
|
|
|
|
type: Array as PropType<[string, IEntry][]>,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
xAttrName: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
yAttrName: {
|
|
|
|
type: String,
|
|
|
|
required: true,
|
|
|
|
},
|
|
|
|
xLabel: {
|
|
|
|
type: String,
|
|
|
|
default: "X",
|
|
|
|
},
|
|
|
|
yLabel: {
|
|
|
|
type: String,
|
|
|
|
default: "Y",
|
|
|
|
},
|
|
|
|
xRange: {
|
|
|
|
type: Number,
|
|
|
|
default: 100,
|
|
|
|
},
|
|
|
|
yRange: {
|
|
|
|
type: Number,
|
|
|
|
default: 100,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
topLabel(): string | undefined {
|
|
|
|
return this.yLabel.includes("//")
|
|
|
|
? this.yLabel.split("//")[1]
|
|
|
|
: undefined;
|
|
|
|
},
|
|
|
|
bottomLabel(): string | undefined {
|
|
|
|
return this.yLabel.includes("//")
|
|
|
|
? this.yLabel.split("//")[0]
|
|
|
|
: undefined;
|
|
|
|
},
|
|
|
|
leftLabel(): string | undefined {
|
|
|
|
return this.xLabel.includes("//")
|
|
|
|
? this.xLabel.split("//")[0]
|
|
|
|
: undefined;
|
|
|
|
},
|
|
|
|
rightLabel(): string | undefined {
|
|
|
|
return this.xLabel.includes("//")
|
|
|
|
? this.xLabel.split("//")[1]
|
|
|
|
: undefined;
|
|
|
|
},
|
|
|
|
markerBottom(): string {
|
|
|
|
return `${((this.yVal + this.yRange) / (this.yRange * 2)) * 100}%`;
|
|
|
|
},
|
|
|
|
markerLeft(): string {
|
|
|
|
return `${((this.xVal + this.xRange) / (this.xRange * 2)) * 100}%`;
|
|
|
|
},
|
2021-06-27 00:46:06 +02:00
|
|
|
dirty(): boolean {
|
|
|
|
return this.origXVal !== this.xVal || this.origYVal !== this.yVal;
|
|
|
|
},
|
2021-06-26 17:55:26 +02:00
|
|
|
},
|
|
|
|
setup(props) {
|
|
|
|
const attrs = asDict(props.attributes);
|
2021-06-27 00:46:06 +02:00
|
|
|
const origXVal = ref(parseInt(attrs[props.xAttrName]) || -1);
|
|
|
|
const origYVal = ref(parseInt(attrs[props.yAttrName]) || -1);
|
|
|
|
const xVal = ref(origXVal.value);
|
|
|
|
const yVal = ref(origYVal.value);
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => props.attributes,
|
|
|
|
() => {
|
|
|
|
const attrs = asDict(props.attributes);
|
|
|
|
origXVal.value = parseInt(attrs[props.xAttrName]) || -1;
|
|
|
|
origYVal.value = parseInt(attrs[props.yAttrName]) || -1;
|
|
|
|
}
|
|
|
|
);
|
2021-06-26 17:55:26 +02:00
|
|
|
|
|
|
|
return {
|
|
|
|
xVal,
|
|
|
|
yVal,
|
2021-06-27 00:46:06 +02:00
|
|
|
origXVal,
|
|
|
|
origYVal,
|
2021-06-26 17:55:26 +02:00
|
|
|
};
|
|
|
|
},
|
2021-06-27 00:46:06 +02:00
|
|
|
methods: {
|
|
|
|
submit() {
|
|
|
|
const xValAddr = this.attributes.find(
|
|
|
|
([_, attr]) => attr.attribute === this.xAttrName
|
|
|
|
)?.[0];
|
|
|
|
|
|
|
|
if (xValAddr) {
|
|
|
|
this.$emit("edit", {
|
|
|
|
type: "delete",
|
|
|
|
addr: xValAddr,
|
|
|
|
} as AttributeChange);
|
|
|
|
|
|
|
|
// this.$emit("edit", {
|
|
|
|
// type: "update",
|
|
|
|
// addr: xValAddr,
|
|
|
|
// value: this.xVal,
|
|
|
|
// } as AttributeChange);
|
|
|
|
}
|
|
|
|
this.$emit("edit", {
|
|
|
|
type: "create",
|
|
|
|
attribute: this.xAttrName,
|
|
|
|
value: this.xVal,
|
|
|
|
} as AttributeChange);
|
|
|
|
|
|
|
|
const yValAddr = this.attributes.find(
|
|
|
|
([_, attr]) => attr.attribute === this.yAttrName
|
|
|
|
)?.[0];
|
|
|
|
|
|
|
|
if (yValAddr) {
|
|
|
|
this.$emit("edit", {
|
|
|
|
type: "delete",
|
|
|
|
addr: yValAddr,
|
|
|
|
} as AttributeChange);
|
|
|
|
|
|
|
|
// this.$emit("edit", {
|
|
|
|
// type: "update",
|
|
|
|
// addr: yValAddr,
|
|
|
|
// value: this.yVal,
|
|
|
|
// } as AttributeChange);
|
|
|
|
}
|
|
|
|
this.$emit("edit", {
|
|
|
|
type: "create",
|
|
|
|
attribute: this.yAttrName,
|
|
|
|
value: this.yVal,
|
|
|
|
} as AttributeChange);
|
|
|
|
},
|
|
|
|
onMouse(ev: MouseEvent) {
|
|
|
|
if (ev.buttons > 0) {
|
|
|
|
const bbox = (
|
|
|
|
this.$refs.compass as HTMLDivElement
|
|
|
|
).getBoundingClientRect();
|
|
|
|
this.xVal = Math.round(
|
|
|
|
((ev.clientX - bbox.x - bbox.width / 2) / bbox.width) *
|
|
|
|
this.xRange *
|
|
|
|
2
|
|
|
|
);
|
|
|
|
this.yVal = Math.round(
|
|
|
|
((ev.clientY - bbox.y - bbox.height / 2) / bbox.height) *
|
|
|
|
this.yRange *
|
|
|
|
-2
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
},
|
2021-06-26 17:55:26 +02:00
|
|
|
});
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.compass-container {
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass {
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
width: 100%;
|
|
|
|
padding-top: 100%; /* 1:1 Aspect Ratio */
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-row-helper {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-label {
|
|
|
|
margin: 0.5em;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-marker {
|
|
|
|
position: absolute;
|
|
|
|
width: 1.5%;
|
|
|
|
height: 1.5%;
|
|
|
|
background: red;
|
|
|
|
}
|
|
|
|
|
|
|
|
.context-markers div {
|
|
|
|
opacity: 25%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-x-axis {
|
|
|
|
position: absolute;
|
|
|
|
top: 50%;
|
|
|
|
left: 0;
|
|
|
|
height: 0;
|
|
|
|
width: 100%;
|
|
|
|
border: 1px dashed gray;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-y-axis {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 50%;
|
|
|
|
width: 0;
|
|
|
|
height: 100%;
|
|
|
|
border: 1px dashed gray;
|
|
|
|
}
|
|
|
|
|
|
|
|
.compass-fields {
|
|
|
|
display: flex;
|
|
|
|
justify-content: center;
|
|
|
|
margin: 1rem 0;
|
|
|
|
|
|
|
|
.compass-field {
|
|
|
|
margin: 0 1em;
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
label {
|
|
|
|
display: inline-block;
|
|
|
|
margin: 0 0.5em;
|
|
|
|
}
|
|
|
|
|
|
|
|
input {
|
|
|
|
width: 4em;
|
|
|
|
font-family: monospace;
|
|
|
|
text-align: left;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|