diff --git a/webapp/src/components/labThingsComponents/inputFromSchema.vue b/webapp/src/components/labThingsComponents/inputFromSchema.vue index 33c674c6..c9f95188 100644 --- a/webapp/src/components/labThingsComponents/inputFromSchema.vue +++ b/webapp/src/components/labThingsComponents/inputFromSchema.vue @@ -6,6 +6,7 @@ { + this.animateUpdate = false; + this.$emit('animationShown'); + }, 700); + } } }, computed: { @@ -170,6 +206,10 @@ export default { }, methods: { + resetInternalValue: function() { + // stringify and parse to ensure no internal mutation for objects + this.internalValue = JSON.parse(JSON.stringify(this.value)); + }, requestUpdate: async function() { this.$emit("requestUpdate") }, @@ -195,7 +235,23 @@ export default { if (event.keyCode == 13) { this.sendValue(); } + }, + updateIsEdited: function() { + this.isEdited = this.deepStringify(this.internalValue) !== this.deepStringify(this.value); + }, + deepStringify: function(val) { + if (Array.isArray(val)) { + return JSON.stringify(val.map(String)); + } + if (val && typeof val === 'object') { + const normalized = Object.fromEntries( + Object.entries(val).map(([k, v]) => [k, String(v)]) + ); + return JSON.stringify(normalized); + } + return JSON.stringify(String(val)); } + } }; @@ -215,4 +271,14 @@ export default { margin-right: 5px; width: 6em; } +.edited { + background-color: #fff3cd; +} +@keyframes green-flash { + 0% { background-color: #3fda63; } + 100% { background-color: white; } +} +.flash { + animation: green-flash 0.7s ease; +} diff --git a/webapp/src/components/labThingsComponents/propertyControl.vue b/webapp/src/components/labThingsComponents/propertyControl.vue index 055bbc9d..e9bc99c2 100644 --- a/webapp/src/components/labThingsComponents/propertyControl.vue +++ b/webapp/src/components/labThingsComponents/propertyControl.vue @@ -3,8 +3,10 @@ v-model="value" :data-schema="propertyDescription" :label="label" + :animate="animate" @requestUpdate="readProperty" @sendValue="writeProperty" + @animationShown="resetAnimate" /> @@ -45,7 +47,8 @@ export default { data() { return { - value: undefined + value: undefined, + animate: false }; }, @@ -98,14 +101,15 @@ export default { await new Promise(r => setTimeout(r, this.readBackDelay)); let newVal = await this.readProperty(); if (newVal == requestedValue) { - await this.modalNotify(`Set ${this.label} to ${newVal}.`); + this.animate = true; } else { + this.animate = true; await this.modalNotify( - `Set ${this.label} to ${newVal} (requested ${requestedValue}).` + `Set ${this.label} to ${newVal} (closest valid value to requested ${requestedValue}).` ); } } else { - await this.modalNotify(`Set ${this.label} to ${this.value}.`); + this.animate = true; } } catch (error) { // Use mixin to display error @@ -113,6 +117,9 @@ export default { // Re-read property to try to update to server value this.readProperty(); } + }, + resetAnimate: function() { + this.animate = false; } } }; diff --git a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue index f7e4abdd..2e7f1eba 100644 --- a/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue +++ b/webapp/src/components/tabContentComponents/backgroundDetectComponents/paneBackgroundDetect.vue @@ -10,8 +10,10 @@ v-model="backgroundDetectorStatus.settings" :data-schema="backgroundDetectorStatus.settings_schema" label="" + :animate="animate" @requestUpdate="readSettings" @sendValue="writeSettings" + @animationShown="resetAnimate" /> @@ -57,6 +59,7 @@ export default { data() { return { backgroundDetectorStatus: undefined, + animate: false }; }, @@ -87,6 +90,11 @@ export default { "update_detector_settings", {"data": requestedValue} ); + this.animate = true; + this.readSettings(); + }, + resetAnimate: function() { + this.animate = false; } }, async created() {