Combine the various property controls into one component

propertyControl now handles several different property types,
based on the thing description. This reduces duplication and
should keep the code cleaner.

In the future this will become a part of a labthings-vue package,
I hope!
This commit is contained in:
Richard Bowman 2023-11-29 23:54:00 +00:00
parent 595101248e
commit 488493385f
3 changed files with 100 additions and 32 deletions

View file

@ -44,7 +44,7 @@
<i class="material-icons">refresh</i> <i class="material-icons">refresh</i>
</a> </a>
</div> </div>
<div v-else class="input-and-buttons-container"> <div v-if="dataType == 'other'" class="input-and-buttons-container">
<input <input
:value="value" :value="value"
class="uk-form-small numeric-setting-line-input" class="uk-form-small numeric-setting-line-input"
@ -62,6 +62,10 @@ export default {
name: "PropertyControl", name: "PropertyControl",
props: { props: {
label: {
type: String,
default: ""
},
propertyName: { propertyName: {
type: String, type: String,
required: true required: true
@ -79,7 +83,7 @@ export default {
data: () => { data: () => {
return { return {
value: {}, value: undefined,
valueOnEnter: undefined, valueOnEnter: undefined,
focused: false focused: false
}; };
@ -90,21 +94,35 @@ export default {
return this.readBackDelay !== undefined; return this.readBackDelay !== undefined;
}, },
propertyDescription: function() { propertyDescription: function() {
return this.thingDescription.properties[this.propertyName]; try {
return this.thingDescription.properties[this.propertyName];
} catch (error) {
return undefined;
}
},
readPropertyUrl: function() {
let href = this.formHref("readproperty");
return this.prependTdBaseUri(href);
},
writePropertyUrl: function() {
let href = this.formHref("writeproperty");
return this.prependTdBaseUri(href);
}, },
dataType: function() { dataType: function() {
let prop = this.propertyDescription; let prop = this.propertyDescription;
if (prop == undefined) {
return "undefined";
}
const num_types = ["integer", "float", "number"]; const num_types = ["integer", "float", "number"];
if (num_types.includes(prop.type)) { if (num_types.includes(prop.type)) {
return "number"; return "number";
} }
console.log("Prop type", prop.type, "is not in", num_types);
if (prop.type == "array") { if (prop.type == "array") {
if (num_types.includes(prop.items)) { if (num_types.includes(prop.items.type)) {
return "number_array"; return "number_array";
} }
if (Array.isArray(prop.items)) { if (Array.isArray(prop.items)) {
if (prop.items.every(t => num_types.includes(t))) { if (prop.items.every(t => num_types.includes(t.type))) {
return "number_array"; return "number_array";
} }
} }
@ -125,21 +143,60 @@ export default {
} }
}, },
watch: {
readPropertyUrl: function() {
// Ensure we read the property once the URL is known
this.readProperty();
}
},
mounted: function() { mounted: function() {
this.readProperty(); // Read the property when we're mounted - usually this won't
// work because the URL isn't set yet. However, it's helpful if
// the app is reloaded (e.g. from a dev server).
if (this.value == undefined) {
this.readProperty();
}
}, },
methods: { methods: {
formHref: function(op = "readproperty") {
if (this.thingDescription == undefined) return undefined;
try {
let forms = this.propertyDescription.forms;
let readForm = forms.find(f => f.op == op || f.op.includes(op));
return readForm.href;
} catch (error) {
console.log(
`Failed to find form for ${op} on ${this.propertyName}`,
error
);
return undefined;
}
},
prependTdBaseUri: function(href) {
if (href == undefined) return undefined;
if (href.startsWith("http")) return href;
if ("base" in this.thingDescription) {
if (href.startsWith("/")) href = href.slice(1);
if (!this.thingDescription.base.endsWith("/"))
this.thingDescription.base += "/";
return this.thingDescription.base + href;
}
return href;
},
readProperty: async function() { readProperty: async function() {
let response = await axios.get(this.propertyUrl); console.log(`Reading property ${this.propertyName}`);
let response = await axios.get(this.readPropertyUrl);
console.log(`Read property ${this.propertyName}`);
this.value = response.data; this.value = response.data;
console.log("Read property", this.propertyUrl, response.data); console.log("Read property", this.readPropertyUrl, response.data);
return response.data; return response.data;
}, },
writeProperty: async function() { writeProperty: async function() {
try { try {
let requestedValue = this.value; let requestedValue = this.value;
await axios.post(this.propertyUrl, requestedValue); await axios.post(this.writePropertyUrl, requestedValue);
if (this.readBack) { if (this.readBack) {
await new Promise(r => setTimeout(r, this.readBackDelay)); await new Promise(r => setTimeout(r, this.readBackDelay));
let newVal = await this.readProperty(); let newVal = await this.readProperty();

View file

@ -87,7 +87,7 @@ export default {
computed: { computed: {
baseUri: function() { baseUri: function() {
return `${this.$store.getters.baseUri}/`; return `${this.$store.getters.baseUri}/`;
}, }
}, },
mounted: function() { mounted: function() {
@ -98,7 +98,7 @@ export default {
methods: { methods: {
updateConfiguration: async function() { updateConfiguration: async function() {
console.log("Showing the configuration is not yet fully implemented") console.log("Showing the configuration is not yet fully implemented");
// Retrieve TDs for camera and stage // Retrieve TDs for camera and stage
let things = {}; let things = {};
for (let thing of ["camera", "stage"]) { for (let thing of ["camera", "stage"]) {
@ -106,17 +106,20 @@ export default {
let response = await axios.get(this.baseUri + thing); // Get Thing Description let response = await axios.get(this.baseUri + thing); // Get Thing Description
things[thing] = response.data; things[thing] = response.data;
} catch (error) { } catch (error) {
console.log(thing + " was missing", error) console.log(thing + " was missing", error);
} }
} }
this.things = things; // We must set this.things in order to trigger the UI update this.things = things; // We must set this.things in order to trigger the UI update
}, },
updateActions: async function() { updateActions: async function() {
try { try {
let response = await axios.get(this.baseUri + "system_control"); // Get Thing Description let response = await axios.get(this.baseUri + "system_control"); // Get Thing Description
this.systemControlActions = response.data.actions; this.systemControlActions = response.data.actions;
} catch (error) { } catch (error) {
console.log("system control was missing - shutdown/restart buttons will not appear", error) console.log(
"system control was missing - shutdown/restart buttons will not appear",
error
);
} }
}, },
shutdownRequest: function() { shutdownRequest: function() {
@ -124,7 +127,7 @@ export default {
() => { () => {
this.$store.commit("resetState"); this.$store.commit("resetState");
// Post and silence errors // Post and silence errors
axios.post(this.baseUri + 'system_control/shutdown').catch(() => {}); axios.post(this.baseUri + "system_control/shutdown").catch(() => {});
}, },
() => {} () => {}
); );
@ -134,7 +137,7 @@ export default {
() => { () => {
this.$store.commit("resetState"); this.$store.commit("resetState");
// Post and silence errors // Post and silence errors
axios.post(this.baseUri + 'system_control/restart').catch(() => {}); axios.post(this.baseUri + "system_control/restart").catch(() => {});
}, },
() => {} () => {}
); );

View file

@ -12,19 +12,22 @@
<li class="uk-open"> <li class="uk-open">
<a class="uk-accordion-title" href="#">Pi Camera Settings</a> <a class="uk-accordion-title" href="#">Pi Camera Settings</a>
<div class="uk-accordion-content"> <div class="uk-accordion-content">
<NumericSettingLine <PropertyControl
label="Exposure time" label="Exposure time"
:property-url="cameraUri + 'exposure_time'" property-name="exposure_time"
:thing-description="thingDescription"
:read-back-delay="1000" :read-back-delay="1000"
/> />
<NumericSettingLine <PropertyControl
label="Analogue gain" label="Analogue gain"
:property-url="cameraUri + 'analogue_gain'" property-name="analogue_gain"
:thing-description="thingDescription"
:read-back-delay="1000" :read-back-delay="1000"
/> />
<NumericArraySettingLine <PropertyControl
label="Colour gains" label="Colour gains"
:property-url="cameraUri + 'colour_gains'" property-name="colour_gains"
:thing-description="thingDescription"
:read-back-delay="1000" :read-back-delay="1000"
/> />
</div> </div>
@ -32,14 +35,16 @@
<li class="uk-open"> <li class="uk-open">
<a class="uk-accordion-title" href="#">Image Quality</a> <a class="uk-accordion-title" href="#">Image Quality</a>
<div class="uk-accordion-content"> <div class="uk-accordion-content">
<NumericSettingLine <PropertyControl
label="MJPEG stream bit rate" label="MJPEG stream bit rate"
:property-url="cameraUri + 'mjpeg_bitrate'" property-name="mjpeg_bitrate"
:thing-description="thingDescription"
:read-back-delay="100" :read-back-delay="100"
/> />
<NumericArraySettingLine <PropertyControl
label="MJPEG stream resolution" label="MJPEG stream resolution"
:property-url="cameraUri + 'stream_resolution'" property-name="stream_resolution"
:thing-description="thingDescription"
:read-back-delay="100" :read-back-delay="100"
/> />
</div> </div>
@ -92,8 +97,7 @@
<script> <script>
import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue"; import cameraCalibrationSettings from "./cameraSettingsComponents/cameraCalibrationSettings.vue";
import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue"; import miniStreamDisplay from "../../genericComponents/miniStreamDisplay.vue";
import NumericSettingLine from "../../genericComponents/numericSettingLine.vue"; import PropertyControl from "../../labThingsComponents/propertyControl.vue";
import NumericArraySettingLine from "../../genericComponents/numericArraySettingLine.vue";
// Export main app // Export main app
export default { export default {
@ -102,8 +106,7 @@ export default {
components: { components: {
cameraCalibrationSettings, cameraCalibrationSettings,
miniStreamDisplay, miniStreamDisplay,
NumericSettingLine, PropertyControl
NumericArraySettingLine
}, },
data: function() { data: function() {
@ -133,7 +136,8 @@ export default {
{ text: "Normal (30fps)", value: 30 }, { text: "Normal (30fps)", value: 30 },
{ text: "Low (15fps)", value: 15 }, { text: "Low (15fps)", value: 15 },
{ text: "Very low (10fps)", value: 10 } { text: "Very low (10fps)", value: 10 }
] ],
thingDescription: undefined
}; };
}, },
@ -141,6 +145,10 @@ export default {
cameraUri: function() { cameraUri: function() {
return `${this.$store.getters.baseUri}/camera/`; return `${this.$store.getters.baseUri}/camera/`;
} }
},
mounted: async function() {
this.thingDescription = await this.getThingDescription("camera");
} }
}; };
</script> </script>