Add a mini menu, make sync button a generic button, and allow reseting properties

This commit is contained in:
Julian Stirling 2026-07-01 14:14:27 +01:00
parent ace42bcf2e
commit a2ebed1006
6 changed files with 343 additions and 93 deletions

View file

@ -0,0 +1,64 @@
<template>
<a class="sync-button" @click.prevent="$emit('click')">
<span class="material-symbols-outlined sync-icon">
{{ icon }}
</span>
<span v-if="showLabel" class="icon-label ofm-hidden-link">
{{ label }}
</span>
</a>
</template>
<script>
export default {
name: "IconButton",
props: {
icon: {
type: String,
default: "sync",
},
showLabel: {
type: Boolean,
default: false,
},
label: {
type: String,
default: "",
},
},
emits: ["click"],
};
</script>
<style scoped>
.sync-button {
display: inline-flex;
flex-grow: 0;
padding-left: 5px;
padding-right: 5px;
vertical-align: middle;
cursor: pointer;
user-select: none;
text-decoration: none !important;
}
.material-symbols-outlined.sync-icon {
color: #888;
transition:
transform 0.3s ease,
color 0.3s ease;
}
.material-symbols-outlined.sync-icon:hover {
transform: rotate(-90deg);
color: #c5247f;
text-decoration: none !important;
}
.icon-label {
padding-left: 10px;
padding-top: 3px;
}
</style>

View file

@ -0,0 +1,73 @@
<template>
<div ref="mini-menu" class="mini-menu">
<a class="icon" @click.prevent="isOpen = !isOpen">
<span class="material-symbols-outlined"> more_vert </span>
</a>
<div v-if="isOpen" class="dropdown-menu ofm-close-pars ofm-opaque-element">
<slot :close="close"></slot>
</div>
</div>
</template>
<script>
export default {
name: "MiniMenu",
props: {},
data() {
return {
isOpen: false,
};
},
mounted() {
window.addEventListener("click", this.handleClickOutside);
},
beforeUnmount() {
window.removeEventListener("click", this.handleClickOutside);
},
methods: {
handleClickOutside(event) {
if (!this.$refs["mini-menu"].contains(event.target)) {
this.isOpen = false;
}
},
close() {
this.isOpen = false;
},
},
};
</script>
<style lang="less" scoped>
@import url("../../assets/less/variables.less");
.mini-menu {
position: relative;
}
.dropdown-menu {
position: absolute;
top: 25px;
right: 0;
border: 1px solid #ccc;
border-radius: 4px;
max-height: 250px;
width: 200px;
overflow-y: auto;
z-index: 1000;
padding-top: 5px;
}
.icon {
user-select: none;
color: #888;
}
.icon:hover {
color: #c5247f;
}
</style>

View file

@ -1,11 +1,9 @@
<template>
<div>
<label
v-if="useDropdown"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="useDropdown">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div class="input-and-buttons-container">
<select
v-model="internalValue"
@ -16,15 +14,36 @@
{{ display }}
</option>
</select>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
</label>
<label
v-if="!useDropdown && dataType == 'number'"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="!useDropdown && dataType == 'number'">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div class="input-and-buttons-container">
<div class="uk-inline number-wrapper">
<input
@ -50,14 +69,34 @@
</button>
</div>
</div>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
</label>
<div v-if="!useDropdown && dataType == 'boolean'" class="input-and-buttons-container">
<label
class="uk-form-label property-input"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
<label class="property-input">
<input
ref="checkbox"
v-model="internalValue"
@ -66,16 +105,39 @@
type="checkbox"
@change="sendValue"
/>
{{ label }}
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
</label>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
<label
v-if="!useDropdown && dataType == 'number_array'"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="!useDropdown && dataType == 'number_array'">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div class="input-and-buttons-container">
<input
v-for="i in valueLength"
@ -90,15 +152,36 @@
@keydown="keyDown"
@animationend="animationEnd"
/>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
</label>
<label
v-if="!useDropdown && dataType == 'number_object'"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="!useDropdown && dataType == 'number_object'">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div v-for="(val, key) in modelValue" :key="key">
<label>{{ internalLabels[key] }}</label>
<div class="input-and-buttons-container">
@ -113,16 +196,37 @@
@keydown="keyDown"
@animationend="animationEnd"
/>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
</div>
</label>
<label
v-if="!useDropdown && dataType == 'string'"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="!useDropdown && dataType == 'string'">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div class="input-and-buttons-container">
<input
v-model="internalValue"
@ -134,15 +238,36 @@
@keydown="keyDown"
@animationend="animationEnd"
/>
<sync-property-button @click="requestUpdate" />
<mini-menu v-slot="{ close }">
<p>
<icon-button
icon="refresh"
:show-label="true"
label="Reset to default"
@click="
resetDefault();
close();
"
/>
</p>
<p>
<icon-button
icon="sync"
:show-label="true"
label="Re-sync value"
@click="
requestUpdate;
close();
"
/>
</p>
</mini-menu>
</div>
</label>
<label
v-if="!useDropdown && dataType == 'other'"
class="uk-form-label"
:class="{ 'ofm-highlighted-label': isChangedFomDefault }"
>
{{ label }}
<label v-if="!useDropdown && dataType == 'other'">
<span class="uk-form-label" :class="{ 'ofm-highlighted-label': isChangedFomDefault }">
{{ label }}
</span>
<div class="input-and-buttons-container">
<input
v-model="internalValue"
@ -156,13 +281,15 @@
</template>
<script>
import syncPropertyButton from "./syncPropertyButton.vue";
import iconButton from "@/components/genericComponents/iconButton.vue";
import MiniMenu from "@/components/genericComponents/miniMenu.vue";
export default {
name: "InputFromSchema",
components: {
syncPropertyButton,
iconButton,
MiniMenu,
},
props: {
dataSchema: {
@ -195,7 +322,7 @@ export default {
},
},
emits: ["requestUpdate", "sendValue", "animationShown"],
emits: ["requestUpdate", "resetDefault", "sendValue", "animationShown"],
compatConfig: { COMPONENT_V_MODEL: false },
@ -355,6 +482,11 @@ export default {
this.internalValue = this.modelValue;
this.$emit("requestUpdate");
},
resetDefault: async function () {
if (this.isChangedFomDefault) {
this.$emit("sendValue", this.dataSchema.default);
}
},
sendValue: async function () {
this.$emit("sendValue", this.internalValue);
},

View file

@ -1,40 +0,0 @@
<template>
<a class="sync-button" @click.prevent="$emit('click')">
<span
class="material-symbols-outlined sync-icon"
title="Reread value from microscope. Required if microscope is updated externally"
>
sync
</span>
</a>
</template>
<script>
export default {
name: "SyncPropertyButton",
emits: ["click"],
};
</script>
<style scoped>
.sync-button {
flex-grow: 0;
padding-left: 5px;
padding-right: 5px;
vertical-align: middle;
cursor: pointer;
}
.material-symbols-outlined.sync-icon {
color: #888;
transition:
transform 0.3s ease,
color 0.3s ease;
}
.material-symbols-outlined.sync-icon:hover {
transform: rotate(-90deg);
color: #c5247f;
}
</style>

View file

@ -22,7 +22,7 @@ and zero position buttons. It also includes the d-pad.
type="number"
@keyup.enter="startMoveTask"
/>
<sync-property-button @click="updatePosition" />
<icon-button icon="sync" @click="updatePosition" />
</div>
<p>
<action-button
@ -59,7 +59,7 @@ and zero position buttons. It also includes the d-pad.
<script>
import ActionButton from "../../labThingsComponents/actionButton.vue";
import syncPropertyButton from "../../labThingsComponents/syncPropertyButton.vue";
import iconButton from "@/components/genericComponents/iconButton.vue";
import stageControlButtons from "./stageControlButtons.vue";
import { eventBus } from "../../../eventBus.js";
@ -68,7 +68,7 @@ export default {
components: {
ActionButton,
syncPropertyButton,
iconButton,
stageControlButtons,
},