diff --git a/webapp/src/main.js b/webapp/src/main.js index cad61001..2eef77b7 100644 --- a/webapp/src/main.js +++ b/webapp/src/main.js @@ -1,7 +1,6 @@ import Vue from "vue"; import App from "./App.vue"; import store from "./store"; -import axios from "axios"; import UIkit from "uikit"; import VueObserveVisibility from "vue-observe-visibility"; @@ -9,6 +8,9 @@ import VueObserveVisibility from "vue-observe-visibility"; // Import MD icons import "material-symbols/outlined.css"; +import queryMixin from "@/mixins/labThingsMixins.js"; +import modalMixin from "@/mixins/modalMixins.js"; + // UIKit overrides UIkit.mixin( { @@ -24,187 +26,8 @@ Vue.use(VueObserveVisibility); Vue.config.productionTip = false; -Vue.mixin({ - methods: { - thingDescription(thing) { - return this.$store.getters["wot/thingDescription"](thing); - }, - thingAvailable(thing) { - return this.$store.getters["wot/thingAvailable"](thing); - }, - thingPropertyUrl(thing, property, allowUndefined = false) { - return this.$store.getters["wot/thingPropertyUrl"]( - thing, - property, - "readproperty", - allowUndefined, - ); - }, - thingActionAvailable(thing, action) { - return this.$store.getters["wot/thingAffordanceAvailable"](thing, "actions", action); - }, - thingPropertyAvailable(thing, property) { - return this.$store.getters["wot/thingAffordanceAvailable"](thing, "properties", property); - }, - async readThingProperty(thing, property, silenceErrors = false) { - let url = this.$store.getters["wot/thingPropertyUrl"](thing, property, "readproperty", false); - try { - let response = await axios.get(url); - return response.data; - } catch (error) { - if (!silenceErrors) this.modalError(error); - return undefined; - } - }, - async writeThingProperty(thing, property, value) { - let url = this.$store.getters["wot/thingPropertyUrl"]( - thing, - property, - "writeproperty", - false, - ); - // `false` fails because axios somehow eats it! - // Other values should not be stringified or pydantic - // can't parse them. - if ((value === false) | (value === true)) { - value = JSON.stringify(value); - } - await axios.put(url, value); - }, - async invokeAction(thing, action, data) { - let url = this.$store.getters["wot/thingActionUrl"](thing, action, "invokeaction", false); - try { - let response = await axios.post(url, data); - return response; - } catch (error) { - this.modalError(error); - return undefined; - } - }, - thingActionUrl(thing, action, allowUndefined = false) { - let url = this.$store.getters["wot/thingActionUrl"]( - thing, - action, - "invokeaction", - allowUndefined, - ); - return url; - }, - modalConfirm: function (modalText) { - var context = this; - - // Stop GPU preview to show modal - context.$root.$emit("globalTogglePreview", false); - - // force OK to be capitalised - UIkit.modal.i18n = { ok: "OK", cancel: "Cancel" }; - - var showModal = function (resolve, reject) { - UIkit.modal - .confirm(modalText, { stack: true }) - .then( - function () { - resolve(); - }, - function () { - reject(); - }, - ) - .finally(function () { - // Re-enable the GPU preview, if it was active before the modal - if (context.$store.state.autoGpuPreview) { - context.$root.$emit("globalTogglePreview", true); - } - context.$root.$emit("modalClosed"); - }); - }; - return new Promise(showModal); - }, - - modalNotify: function (message, status = "success") { - UIkit.notification({ - message: message, - status: status, - }); - }, - - modalDialog: function (title, message) { - UIkit.modal.dialog( - ` - -
-

${title}

-
-
-

${message}

-
- `, - { stack: true }, - ); - }, - - modalError: function (error) { - var errormsg = this.getErrorMessage(error); - this.$store.commit("setErrorMessage", errormsg); - UIkit.notification({ - message: `${errormsg}`, - status: "danger", - }); - }, - - getErrorMessage: function (error) { - // Format the error. - let data = this.getErrorData(error); - // Get error data may get an object. Handle edge cases and if not try to use - // JSON to get the best string. This stops "objectObject" showing as an error. - if (data === null) return "null"; - if (data === undefined) return "undefined"; - if (typeof data === "string") return data; - try { - return JSON.stringify(data, null, 2); - } catch (err) { - return String(data); - } - }, - getErrorData: function (error) { - // If a response was obtained, extract the most specific message - if (error.response) { - // If the response is a nicely formatted JSON response from the server - if (error.response.data.message) { - return error.response.data.message; - } - if (error.response.data.detail) { - try { - return error.response.data.detail[0].msg; - } catch (err) { - return error.response.data.detail; - } - } - // If the response is just some generic error response - if (error.response.data) { - return error.response.data; - } - return error.response; - } - // If we have an error object with a message, use that - if (error.message) return error.message; - // At this point just formatting the whole error object is the best we can do. - return error; - }, - - showModalElement: function (element) { - UIkit.modal(element).show(); - }, - - hideModalElement: function (element) { - UIkit.modal(element).hide(); - }, - - toggleModalElement: function (element) { - UIkit.modal(element).toggle(); - }, - }, -}); +Vue.mixin(queryMixin); +Vue.mixin(modalMixin); new Vue({ store, diff --git a/webapp/src/mixins/labThingsMixins.js b/webapp/src/mixins/labThingsMixins.js new file mode 100644 index 00000000..84938d70 --- /dev/null +++ b/webapp/src/mixins/labThingsMixins.js @@ -0,0 +1,78 @@ +/** + * @fileoverview + * Global mixin providing access to the LabThings (WoT) API. + * + * This mixin is registered globally in `main.js` using. Do not + * manually import it in components. + */ + +import axios from "axios"; + +export default { + methods: { + thingDescription(thing) { + return this.$store.getters["wot/thingDescription"](thing); + }, + thingAvailable(thing) { + return this.$store.getters["wot/thingAvailable"](thing); + }, + thingPropertyUrl(thing, property, allowUndefined = false) { + return this.$store.getters["wot/thingPropertyUrl"]( + thing, + property, + "readproperty", + allowUndefined, + ); + }, + thingActionAvailable(thing, action) { + return this.$store.getters["wot/thingAffordanceAvailable"](thing, "actions", action); + }, + thingPropertyAvailable(thing, property) { + return this.$store.getters["wot/thingAffordanceAvailable"](thing, "properties", property); + }, + async readThingProperty(thing, property, silenceErrors = false) { + let url = this.$store.getters["wot/thingPropertyUrl"](thing, property, "readproperty", false); + try { + let response = await axios.get(url); + return response.data; + } catch (error) { + if (!silenceErrors) this.modalError(error); + return undefined; + } + }, + async writeThingProperty(thing, property, value) { + let url = this.$store.getters["wot/thingPropertyUrl"]( + thing, + property, + "writeproperty", + false, + ); + // `false` fails because axios somehow eats it! + // Other values should not be stringified or pydantic + // can't parse them. + if ((value === false) | (value === true)) { + value = JSON.stringify(value); + } + await axios.put(url, value); + }, + async invokeAction(thing, action, data) { + let url = this.$store.getters["wot/thingActionUrl"](thing, action, "invokeaction", false); + try { + let response = await axios.post(url, data); + return response; + } catch (error) { + this.modalError(error); + return undefined; + } + }, + thingActionUrl(thing, action, allowUndefined = false) { + let url = this.$store.getters["wot/thingActionUrl"]( + thing, + action, + "invokeaction", + allowUndefined, + ); + return url; + }, + }, +}; diff --git a/webapp/src/mixins/modalMixins.js b/webapp/src/mixins/modalMixins.js new file mode 100644 index 00000000..f13dc21f --- /dev/null +++ b/webapp/src/mixins/modalMixins.js @@ -0,0 +1,127 @@ +/** + * @fileoverview + * Global mixin providing modal functionality. + * + * This mixin is registered globally in `main.js` using. Do not + * manually import it in components. + */ + +import UIkit from "uikit"; + +export default { + methods: { + modalConfirm: function (modalText) { + var context = this; + + // Stop GPU preview to show modal + context.$root.$emit("globalTogglePreview", false); + + // force OK to be capitalised + UIkit.modal.i18n = { ok: "OK", cancel: "Cancel" }; + + var showModal = function (resolve, reject) { + UIkit.modal + .confirm(modalText, { stack: true }) + .then( + function () { + resolve(); + }, + function () { + reject(); + }, + ) + .finally(function () { + // Re-enable the GPU preview, if it was active before the modal + if (context.$store.state.autoGpuPreview) { + context.$root.$emit("globalTogglePreview", true); + } + context.$root.$emit("modalClosed"); + }); + }; + return new Promise(showModal); + }, + + modalNotify: function (message, status = "success") { + UIkit.notification({ + message: message, + status: status, + }); + }, + + modalDialog: function (title, message) { + UIkit.modal.dialog( + ` + +
+

${title}

+
+
+

${message}

+
+ `, + { stack: true }, + ); + }, + + modalError: function (error) { + var errormsg = this.getErrorMessage(error); + this.$store.commit("setErrorMessage", errormsg); + UIkit.notification({ + message: `${errormsg}`, + status: "danger", + }); + }, + + getErrorMessage: function (error) { + // Format the error. + let data = this.getErrorData(error); + // Get error data may get an object. Handle edge cases and if not try to use + // JSON to get the best string. This stops "objectObject" showing as an error. + if (data === null) return "null"; + if (data === undefined) return "undefined"; + if (typeof data === "string") return data; + try { + return JSON.stringify(data, null, 2); + } catch (err) { + return String(data); + } + }, + getErrorData: function (error) { + // If a response was obtained, extract the most specific message + if (error.response) { + // If the response is a nicely formatted JSON response from the server + if (error.response.data.message) { + return error.response.data.message; + } + if (error.response.data.detail) { + try { + return error.response.data.detail[0].msg; + } catch (err) { + return error.response.data.detail; + } + } + // If the response is just some generic error response + if (error.response.data) { + return error.response.data; + } + return error.response; + } + // If we have an error object with a message, use that + if (error.message) return error.message; + // At this point just formatting the whole error object is the best we can do. + return error; + }, + + showModalElement: function (element) { + UIkit.modal(element).show(); + }, + + hideModalElement: function (element) { + UIkit.modal(element).hide(); + }, + + toggleModalElement: function (element) { + UIkit.modal(element).toggle(); + }, + }, +};