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();
+ },
+ },
+};