upend/ui/src/components/widgets/Compass.vue

202 lines
4.1 KiB
Vue

<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>
<div class="compass">
<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>
<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";
import { IEntry } from "@/types/base";
import { defineComponent, PropType, ref } from "vue";
export default defineComponent({
name: "Compass",
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 {
console.log(this.xVal);
return `${((this.xVal + this.xRange) / (this.xRange * 2)) * 100}%`;
},
},
setup(props) {
const attrs = asDict(props.attributes);
const xVal = ref(parseInt(attrs[props.xAttrName]) || -1);
const yVal = ref(parseInt(attrs[props.yAttrName]) || -1);
return {
xVal,
yVal,
};
},
});
</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>