Removed node-wot but kept centralised TD store

I had intended to store a ConsumedThing for each Thing, in
a module in the store. Until that is possible (will require some
more attention to dependency management) I will just store
the TDs instead.

This commit includes a lot of the code I wrote for node-wot use,
but without any dependence on node-wot.
This commit is contained in:
Richard Bowman 2023-11-29 22:43:55 +00:00
parent 45d2884159
commit 595101248e
6 changed files with 265 additions and 16 deletions

View file

@ -305,22 +305,22 @@ export default {
},
methods: {
checkConnection: function() {
async checkConnection() {
var baseUri = this.$store.getters.baseUri;
this.$store.commit("changeWaiting", true);
axios
// TODO: more robust check - e.g. use a microscope Thing
.get(`${baseUri}/stage/`)
.then(() => {
this.$store.commit("setConnected");
this.$store.commit("setErrorMessage", null);
})
.catch(error => {
this.$store.commit("setErrorMessage", error);
})
.finally(() => {
this.$store.commit("changeWaiting", false);
});
// TODO: more robust check - e.g. use a microscope Thing
// TODO: should we purge existing consumedThings?
try {
await axios.get(`${baseUri}/things/`);
await this.getThingDescription("stage");
await this.getThingDescription("camera");
this.$store.commit("setConnected");
this.$store.commit("setErrorMessage", null);
} catch (error) {
this.$store.commit("setErrorMessage", error);
} finally {
this.$store.commit("changeWaiting", false);
}
},
handleExit: function() {

View file

@ -0,0 +1,200 @@
<template>
<div>
<label class="uk-form-label">{{ label }}</label>
<div v-if="dataType == 'number'" class="input-and-buttons-container">
<input
v-model="value"
class="uk-form-small numeric-setting-line-input"
type="number"
@focusin="focusIn"
@focusout="focusOut"
@keydown="keyDown"
/>
<a class="button-next-to-input" @click="readProperty">
<i class="material-icons">refresh</i>
</a>
</div>
<div v-if="dataType == 'number_array'" class="input-and-buttons-container">
<input
v-for="i in value.length"
: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">
<i class="material-icons">refresh</i>
</a>
</div>
<div v-if="dataType == 'number_object'" 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">
<i class="material-icons">refresh</i>
</a>
</div>
<div v-else class="input-and-buttons-container">
<input
:value="value"
class="uk-form-small numeric-setting-line-input"
type="text"
disabled="true"
/>
</div>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "PropertyControl",
props: {
propertyName: {
type: String,
required: true
},
thingDescription: {
type: Object,
required: true
},
readBackDelay: {
type: Number,
default: undefined,
required: false
}
},
data: () => {
return {
value: {},
valueOnEnter: undefined,
focused: false
};
},
computed: {
readBack: function() {
return this.readBackDelay !== undefined;
},
propertyDescription: function() {
return this.thingDescription.properties[this.propertyName];
},
dataType: function() {
let prop = this.propertyDescription;
const num_types = ["integer", "float", "number"];
if (num_types.includes(prop.type)) {
return "number";
}
console.log("Prop type", prop.type, "is not in", num_types);
if (prop.type == "array") {
if (num_types.includes(prop.items)) {
return "number_array";
}
if (Array.isArray(prop.items)) {
if (prop.items.every(t => num_types.includes(t))) {
return "number_array";
}
}
}
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";
}
},
mounted: function() {
this.readProperty();
},
methods: {
readProperty: async function() {
let response = await axios.get(this.propertyUrl);
this.value = response.data;
console.log("Read property", this.propertyUrl, response.data);
return response.data;
},
writeProperty: async function() {
try {
let requestedValue = this.value;
await axios.post(this.propertyUrl, requestedValue);
if (this.readBack) {
await new Promise(r => setTimeout(r, this.readBackDelay));
let newVal = await this.readProperty();
if (newVal == requestedValue) {
await this.modalNotify(`Set ${this.label} to ${newVal}.`);
} else {
await this.modalNotify(
`Set ${this.label} to ${newVal} (requested ${requestedValue}).`
);
}
} else {
await this.modalNotify(`Set ${this.label} to ${this.value}.`);
}
} catch (error) {
this.modalError(error); // Let mixin handle error
}
},
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>
<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>

View file

@ -51,7 +51,7 @@ export default {
return {
settings: null,
actions: {},
properties: {},
properties: {}
};
},

View file

@ -34,6 +34,16 @@ Vue.config.productionTip = false;
Vue.mixin({
methods: {
async getThingDescription(thing) {
// Get the thing description for the given thing
let origin = this.$store.state.origin;
let uri = `${origin}/${thing}/`;
// We cache TDs in the store, and fetch only if needed.
if (!(uri in store.state.wot.thingDescriptions)) {
await store.dispatch("wot/fetchThingDescription", uri);
}
return store.state.wot.thingDescriptions[uri];
},
modalConfirm: function(modalText) {
var context = this;

View file

@ -1,5 +1,6 @@
import Vue from "vue";
import Vuex from "vuex";
import wotStoreModule from "./wot-client";
Vue.use(Vuex);
@ -76,7 +77,8 @@ function getOriginFromLocation() {
export default new Vuex.Store({
modules: {
imjoy: moduleImjoy
imjoy: moduleImjoy,
wot: wotStoreModule
},
state: {
origin: getOriginFromLocation(),

37
webapp/src/wot-client.js Normal file
View file

@ -0,0 +1,37 @@
import axios from "axios";
export const wotStoreModule = {
namespaced: true,
state: () => ({
thingDescriptions: {},
servient: null,
helpers: null
}),
mutations: {
addThingDescription(state, { thingUri, thingDescription }) {
state.thingDescriptions[thingUri] = thingDescription;
},
removeThingDescription(state, thingUri) {
delete state.thingDescriptions[thingUri];
},
removeAllThingDescriptions(state) {
state.thingDescriptions = {};
}
},
actions: {
async start() {
// Set up thing client - not currently used.
},
async fetchThingDescription({ commit }, uri) {
// Fetch the thing description from the given URI and consume it
// NB this should only be called once, or we'll duplicate effort.
// Deduplication should be done elsewhere.
let response = await axios.get(uri);
let td = response.data;
commit("addThingDescription", { thingUri: uri, thingDescription: td });
}
},
getters: {}
};
export default wotStoreModule;