Splitting logic for property control from logic to create UI from schema
This commit is contained in:
parent
e7e3a08210
commit
7f4b4ed1f3
3 changed files with 236 additions and 175 deletions
216
webapp/src/components/labThingsComponents/inputFromSchema.vue
Normal file
216
webapp/src/components/labThingsComponents/inputFromSchema.vue
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label v-if="dataType == 'number'" class="uk-form-label"
|
||||||
|
>{{ label }}
|
||||||
|
<div class="input-and-buttons-container">
|
||||||
|
<input
|
||||||
|
v-model="internalValue"
|
||||||
|
class="uk-form-small numeric-setting-line-input"
|
||||||
|
type="number"
|
||||||
|
@focusin="focusIn"
|
||||||
|
@focusout="focusOut"
|
||||||
|
@keydown="keyDown"
|
||||||
|
/>
|
||||||
|
<a class="button-next-to-input" @click="requestUpdate">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div v-if="dataType == 'boolean'" class="input-and-buttons-container">
|
||||||
|
<label class="uk-form-label numeric-setting-line-input">
|
||||||
|
<input
|
||||||
|
ref="checkbox"
|
||||||
|
v-model="internalValue"
|
||||||
|
class="uk-checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
@change="sendValue"
|
||||||
|
/>
|
||||||
|
{{ label }}
|
||||||
|
</label>
|
||||||
|
<a class="button-next-to-input" @click="requestUpdate">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<label v-if="dataType == 'number_array'" class="uk-form-label"
|
||||||
|
>{{ label }}
|
||||||
|
<div class="input-and-buttons-container">
|
||||||
|
<input
|
||||||
|
v-for="i in valueLength"
|
||||||
|
:key="i"
|
||||||
|
v-model="internalValue[i - 1]"
|
||||||
|
class="uk-form-small numeric-setting-line-input"
|
||||||
|
type="number"
|
||||||
|
@focusin="focusIn"
|
||||||
|
@focusout="focusOut"
|
||||||
|
@keydown="keyDown"
|
||||||
|
/>
|
||||||
|
<a class="button-next-to-input" @click="requestUpdate">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label v-if="dataType == 'number_object'" class="uk-form-label"
|
||||||
|
>{{ label }}
|
||||||
|
<div class="input-and-buttons-container">
|
||||||
|
<input
|
||||||
|
v-for="(_v, key) in value"
|
||||||
|
:key="key"
|
||||||
|
v-model="internalValue[key]"
|
||||||
|
class="uk-form-small numeric-setting-line-input"
|
||||||
|
type="number"
|
||||||
|
@focusin="focusIn"
|
||||||
|
@focusout="focusOut"
|
||||||
|
@keydown="keyDown"
|
||||||
|
/>
|
||||||
|
<a class="button-next-to-input" @click="requestUpdate">
|
||||||
|
<span class="material-symbols-outlined">refresh</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label v-if="dataType == 'other'" class="uk-form-label"
|
||||||
|
>{{ label }}
|
||||||
|
<div class="input-and-buttons-container">
|
||||||
|
<input
|
||||||
|
v-model="internalValue"
|
||||||
|
class="uk-form-small numeric-setting-line-input"
|
||||||
|
type="text"
|
||||||
|
disabled="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "InputFromSchema",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
dataSchema: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: null
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
internalValue: this.value,
|
||||||
|
valueOnEnter: undefined,
|
||||||
|
focused: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value(newValue) {
|
||||||
|
this.internalValue = newValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
valueLength: function() {
|
||||||
|
if (this.dataType == "number_array") {
|
||||||
|
if (this.internalValue == undefined) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this.internalValue.length;
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dataType: function() {
|
||||||
|
let prop = this.dataSchema;
|
||||||
|
if (prop == undefined) {
|
||||||
|
return "undefined";
|
||||||
|
}
|
||||||
|
const num_types = ["integer", "float", "number"];
|
||||||
|
if (num_types.includes(prop.type)) {
|
||||||
|
return "number";
|
||||||
|
}
|
||||||
|
if (prop.type == "array") {
|
||||||
|
if (num_types.includes(prop.items.type)) {
|
||||||
|
return "number_array";
|
||||||
|
}
|
||||||
|
if (Array.isArray(prop.items)) {
|
||||||
|
if (prop.items.every(t => num_types.includes(t.type))) {
|
||||||
|
return "number_array";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prop.type == "boolean") {
|
||||||
|
return "boolean";
|
||||||
|
}
|
||||||
|
if (prop.type == "object") {
|
||||||
|
let numeric = true;
|
||||||
|
for (let key in prop.properties) {
|
||||||
|
if (!num_types.includes(prop.properties[key].type)) {
|
||||||
|
numeric = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numeric) {
|
||||||
|
return "number_object";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "other";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
requestUpdate: async function() {
|
||||||
|
this.$emit("requestUpdate")
|
||||||
|
},
|
||||||
|
sendValue: async function() {
|
||||||
|
this.$emit("sendValue", this.internalValue)
|
||||||
|
},
|
||||||
|
checkboxUpdated: function() {
|
||||||
|
if (this.internalValue != this.$refs.checkbox.checked) {
|
||||||
|
this.internalValue = this.$refs.checkbox.checked;
|
||||||
|
this.sendValue();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusIn: function(event) {
|
||||||
|
this.valueOnEnter = event.target.value;
|
||||||
|
},
|
||||||
|
focusOut: function(event) {
|
||||||
|
if (this.valueOnEnter != event.target.value) {
|
||||||
|
this.sendValue(event.target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
keyDown: function(event) {
|
||||||
|
// Pressing enter should set the property, whether or not we think it's changed.
|
||||||
|
if (event.keyCode == 13) {
|
||||||
|
this.sendValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input-and-buttons-container {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-content: stretch;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.numeric-setting-line-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
width: 6em;
|
||||||
|
}
|
||||||
|
.button-next-to-input {
|
||||||
|
flex-grow: 0;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,90 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<input-from-schema
|
||||||
<label v-if="dataType == 'number'" class="uk-form-label"
|
v-model="value"
|
||||||
>{{ label }}
|
:data-schema="propertyDescription"
|
||||||
<div class="input-and-buttons-container">
|
:label="label"
|
||||||
<input
|
@requestUpdate="readProperty"
|
||||||
v-model="value"
|
@sendValue="writeProperty"
|
||||||
class="uk-form-small numeric-setting-line-input"
|
/>
|
||||||
type="number"
|
|
||||||
@focusin="focusIn"
|
|
||||||
@focusout="focusOut"
|
|
||||||
@keydown="keyDown"
|
|
||||||
/>
|
|
||||||
<a class="button-next-to-input" @click="readProperty">
|
|
||||||
<span class="material-symbols-outlined">refresh</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<div v-if="dataType == 'boolean'" class="input-and-buttons-container">
|
|
||||||
<label class="uk-form-label numeric-setting-line-input">
|
|
||||||
<input
|
|
||||||
ref="checkbox"
|
|
||||||
v-model="value"
|
|
||||||
class="uk-checkbox"
|
|
||||||
type="checkbox"
|
|
||||||
@change="writeProperty"
|
|
||||||
/>
|
|
||||||
{{ label }}
|
|
||||||
</label>
|
|
||||||
<a class="button-next-to-input" @click="readProperty">
|
|
||||||
<span class="material-symbols-outlined">refresh</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<label v-if="dataType == 'number_array'" class="uk-form-label"
|
|
||||||
>{{ label }}
|
|
||||||
<div class="input-and-buttons-container">
|
|
||||||
<input
|
|
||||||
v-for="i in valueLength"
|
|
||||||
:key="i"
|
|
||||||
v-model="value[i - 1]"
|
|
||||||
class="uk-form-small numeric-setting-line-input"
|
|
||||||
type="number"
|
|
||||||
@focusin="focusIn"
|
|
||||||
@focusout="focusOut"
|
|
||||||
@keydown="keyDown"
|
|
||||||
/>
|
|
||||||
<a class="button-next-to-input" @click="readProperty">
|
|
||||||
<span class="material-symbols-outlined">refresh</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label v-if="dataType == 'number_object'" class="uk-form-label"
|
|
||||||
>{{ label }}
|
|
||||||
<div class="input-and-buttons-container">
|
|
||||||
<input
|
|
||||||
v-for="(_v, key) in value"
|
|
||||||
:key="key"
|
|
||||||
v-model="value[key]"
|
|
||||||
class="uk-form-small numeric-setting-line-input"
|
|
||||||
type="number"
|
|
||||||
@focusin="focusIn"
|
|
||||||
@focusout="focusOut"
|
|
||||||
@keydown="keyDown"
|
|
||||||
/>
|
|
||||||
<a class="button-next-to-input" @click="readProperty">
|
|
||||||
<span class="material-symbols-outlined">refresh</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
<label v-if="dataType == 'other'" class="uk-form-label"
|
|
||||||
>{{ label }}
|
|
||||||
<div class="input-and-buttons-container">
|
|
||||||
<input
|
|
||||||
:value="value"
|
|
||||||
class="uk-form-small numeric-setting-line-input"
|
|
||||||
type="text"
|
|
||||||
disabled="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import InputFromSchema from "./inputFromSchema.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PropertyControl",
|
name: "PropertyControl",
|
||||||
|
|
||||||
|
components: {
|
||||||
|
InputFromSchema
|
||||||
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -110,25 +43,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: undefined,
|
value: undefined
|
||||||
valueOnEnter: undefined,
|
|
||||||
focused: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
valueLength: function() {
|
|
||||||
if (this.dataType == "number_array") {
|
|
||||||
if (this.value == undefined) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return this.value.length;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propertyDescription: function() {
|
propertyDescription: function() {
|
||||||
try {
|
try {
|
||||||
return this.thingDescription(this.thingName).properties[
|
return this.thingDescription(this.thingName).properties[
|
||||||
|
|
@ -137,42 +58,6 @@ export default {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
dataType: function() {
|
|
||||||
let prop = this.propertyDescription;
|
|
||||||
if (prop == undefined) {
|
|
||||||
return "undefined";
|
|
||||||
}
|
|
||||||
const num_types = ["integer", "float", "number"];
|
|
||||||
if (num_types.includes(prop.type)) {
|
|
||||||
return "number";
|
|
||||||
}
|
|
||||||
if (prop.type == "array") {
|
|
||||||
if (num_types.includes(prop.items.type)) {
|
|
||||||
return "number_array";
|
|
||||||
}
|
|
||||||
if (Array.isArray(prop.items)) {
|
|
||||||
if (prop.items.every(t => num_types.includes(t.type))) {
|
|
||||||
return "number_array";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (prop.type == "boolean") {
|
|
||||||
return "boolean";
|
|
||||||
}
|
|
||||||
if (prop.type == "object") {
|
|
||||||
let numeric = true;
|
|
||||||
for (let key in prop.properties) {
|
|
||||||
if (!num_types.includes(prop.properties[key].type)) {
|
|
||||||
numeric = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numeric) {
|
|
||||||
return "number_object";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "other";
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -201,9 +86,10 @@ export default {
|
||||||
this.value = data;
|
this.value = data;
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
writeProperty: async function() {
|
writeProperty: async function(requestedValue) {
|
||||||
try {
|
try {
|
||||||
let requestedValue = this.value;
|
console.log(requestedValue);
|
||||||
|
this.value=requestedValue;
|
||||||
await this.writeThingProperty(
|
await this.writeThingProperty(
|
||||||
this.thingName,
|
this.thingName,
|
||||||
this.propertyName,
|
this.propertyName,
|
||||||
|
|
@ -225,51 +111,9 @@ export default {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.modalError(error); // Let mixin handle error
|
this.modalError(error); // Let mixin handle error
|
||||||
}
|
}
|
||||||
},
|
|
||||||
checkboxUpdated: function() {
|
|
||||||
if (this.value != this.$refs.checkbox.checked) {
|
|
||||||
this.value = this.$refs.checkbox.checked;
|
|
||||||
this.writeProperty();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
focusIn: function(event) {
|
|
||||||
this.valueOnEnter = event.target.value;
|
|
||||||
},
|
|
||||||
focusOut: function(event) {
|
|
||||||
if (this.valueOnEnter != event.target.value) {
|
|
||||||
this.writeProperty(event.target.value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyDown: function(event) {
|
|
||||||
// Pressing enter should set the property, whether or not we think it's changed.
|
|
||||||
if (event.keyCode == 13) {
|
|
||||||
this.writeProperty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.input-and-buttons-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-content: stretch;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.numeric-setting-line-input {
|
|
||||||
flex-grow: 1;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
width: 6em;
|
|
||||||
}
|
|
||||||
.button-next-to-input {
|
|
||||||
flex-grow: 0;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
:thing="propertyData.thing"
|
:thing="propertyData.thing"
|
||||||
:property-name="propertyData.property_name"
|
:property-name="propertyData.property_name"
|
||||||
:thing-name="propertyData.thing"
|
:thing-name="propertyData.thing"
|
||||||
|
:label="propertyData.label"
|
||||||
:read-back="propertyData.read_back"
|
:read-back="propertyData.read_back"
|
||||||
:read-back-delay="propertyData.read_back_delay"
|
:read-back-delay="propertyData.read_back_delay"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue