Merge branch 'vue-matrix-display' into 'v3'
A matrix display component with copy button that copys as code. See merge request openflexure/openflexure-microscope-server!436
This commit is contained in:
commit
c89a2d24c2
3 changed files with 192 additions and 9 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="CSMSettings" class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
|
<div id="CSMSettings" class="uk-grid uk-grid-divider uk-child-width-expand" uk-grid>
|
||||||
<div class="uk-width-xlarge">
|
<div class="uk-width-large">
|
||||||
<h3>Camera to Stage Mapping</h3>
|
<h3>Camera to Stage Mapping</h3>
|
||||||
<p>
|
<p>
|
||||||
Camera-stage mapping allows the stage to move relative to the camera view. This enables
|
Camera-stage mapping allows the stage to move relative to the camera view. This enables
|
||||||
|
|
|
||||||
|
|
@ -20,28 +20,43 @@
|
||||||
v-show="showExtraSettings"
|
v-show="showExtraSettings"
|
||||||
type="button"
|
type="button"
|
||||||
class="uk-button uk-button-default uk-width-1-1"
|
class="uk-button uk-button-default uk-width-1-1"
|
||||||
|
:disabled="!csmMatrix"
|
||||||
|
:class="{ 'uk-disabled': !csmMatrix }"
|
||||||
@click="getCalibrationData()"
|
@click="getCalibrationData()"
|
||||||
>
|
>
|
||||||
Download Calibration Data
|
Download Calibration Data
|
||||||
</button>
|
</button>
|
||||||
<div v-if="csmMatrix != 'undefined'" style="margin: 10px">
|
<div v-if="csmMatrix" style="margin: 10px">
|
||||||
<details>
|
<details>
|
||||||
<summary>Calibration Details</summary>
|
<summary>Calibration Details</summary>
|
||||||
<strong>CSM calculated for images with a resolution of {{ csmResolution }}</strong>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
Current CSM Matrix: <tt>{{ csmMatrix }}</tt>
|
<strong>Current CSM Matrix:</strong>
|
||||||
|
<br />
|
||||||
|
<matrixDisplay :matrix="csmMatrix" />
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
CSM calculated for images with a resolution of:
|
||||||
|
<br />
|
||||||
|
<matrixDisplay :matrix="csmResolution" :bracket-height="1.5" />
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>Pixels per motor step: {{ csmRatio }}</li>
|
<li>Pixels per motor step: {{ csmRatio }}</li>
|
||||||
<li>Full field of view: {{ csmFOV }} motor steps</li>
|
<li>
|
||||||
|
Full field of view in motor steps:
|
||||||
|
<br />
|
||||||
|
<matrixDisplay :matrix="csmFOV" :bracket-height="1.5" />
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-else><strong>No Calibration Available</strong></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
import ActionButton from "@/components/labThingsComponents/actionButton.vue";
|
||||||
|
import matrixDisplay from "@/components/ui/matrixDisplay.vue";
|
||||||
|
|
||||||
// Export main app
|
// Export main app
|
||||||
export default {
|
export default {
|
||||||
|
|
@ -49,6 +64,7 @@ export default {
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
ActionButton,
|
ActionButton,
|
||||||
|
matrixDisplay,
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -61,9 +77,10 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
csmMatrix: "undefined",
|
csmMatrix: undefined,
|
||||||
csmResolution: "undefined",
|
csmResolution: undefined,
|
||||||
csmRatio: "undefined",
|
csmRatio: undefined,
|
||||||
|
csmFOV: undefined,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
166
webapp/src/components/ui/matrixDisplay.vue
Normal file
166
webapp/src/components/ui/matrixDisplay.vue
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
<template>
|
||||||
|
<div class="matrix-container uk-inline">
|
||||||
|
<div class="matrix-wrapper">
|
||||||
|
<div class="matrix-bracket" :style="bracketSize">(</div>
|
||||||
|
|
||||||
|
<div class="matrix-grid">
|
||||||
|
<div v-for="(row, i) in always2DMatrix" :key="i" class="matrix-row">
|
||||||
|
<span v-for="(cell, j) in row" :key="j" class="matrix-cell">
|
||||||
|
{{ cell }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="matrix-bracket" :style="bracketSize">)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="copy-button"
|
||||||
|
:class="{ success: copied }"
|
||||||
|
:title="'Copy as Python list'"
|
||||||
|
@click="copyMatrix"
|
||||||
|
>
|
||||||
|
<span class="material-symbols-outlined">{{ copySymbol }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "MatrixDisplay",
|
||||||
|
props: {
|
||||||
|
matrix: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
bracketHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
copied: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
always2DMatrix() {
|
||||||
|
if (!this.matrix || this.matrix.length === 0) return [];
|
||||||
|
if (!Array.isArray(this.matrix[0])) {
|
||||||
|
return [this.matrix];
|
||||||
|
}
|
||||||
|
return this.matrix;
|
||||||
|
},
|
||||||
|
copySymbol() {
|
||||||
|
return this.copied ? "check" : "content_copy";
|
||||||
|
},
|
||||||
|
bracketSize() {
|
||||||
|
return { fontSize: `${this.bracketHeight}rem`, lineHeight: "1" };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
copyMatrix() {
|
||||||
|
const pythonFormat = JSON.stringify(this.matrix)
|
||||||
|
.replaceAll("],", "], ")
|
||||||
|
.replaceAll(",", ", ");
|
||||||
|
this.copyText(pythonFormat);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Cannot use modern text copy as we don't have https.
|
||||||
|
*
|
||||||
|
* This uses the deprecated execCommand as navigator.clipboard.writeText() is not
|
||||||
|
* available.
|
||||||
|
*/
|
||||||
|
copyText(text) {
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = text;
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
textarea.style.opacity = "0";
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.focus();
|
||||||
|
textarea.select();
|
||||||
|
try {
|
||||||
|
document.execCommand("copy");
|
||||||
|
this.copied = true;
|
||||||
|
setTimeout(() => (this.copied = false), 1000);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Copy failed:", err);
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
@import "../../assets/less/theme.less";
|
||||||
|
|
||||||
|
.matrix-container {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-bracket {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-grid {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-cell {
|
||||||
|
padding: 2px 6px;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
position: absolute;
|
||||||
|
top: -5px;
|
||||||
|
right: -20px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
margin-left: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: @global-muted-color;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button:hover {
|
||||||
|
color: @global-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hook-inverse() {
|
||||||
|
.copy-button:hover {
|
||||||
|
color: @inverse-global-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button.success {
|
||||||
|
color: #4caf50;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #4caf50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue