A matrix display component with copy button that copys as code.
This commit is contained in:
parent
5fd16bd113
commit
045410b2e6
3 changed files with 173 additions and 5 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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>
|
||||
<p>
|
||||
Camera-stage mapping allows the stage to move relative to the camera view. This enables
|
||||
|
|
|
|||
|
|
@ -27,13 +27,22 @@
|
|||
<div v-if="csmMatrix != 'undefined'" style="margin: 10px">
|
||||
<details>
|
||||
<summary>Calibration Details</summary>
|
||||
<strong>CSM calculated for images with a resolution of {{ csmResolution }}</strong>
|
||||
<strong>CSM calculated for images with a resolution of:</strong>
|
||||
<br />
|
||||
<matrixDisplay :matrix="csmResolution" :bracket-height="1.5" />
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Current CSM Matrix: <tt>{{ csmMatrix }}</tt>
|
||||
Current CSM Matrix:
|
||||
<br />
|
||||
<matrixDisplay :matrix="csmMatrix" />
|
||||
</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>
|
||||
</details>
|
||||
</div>
|
||||
|
|
@ -41,7 +50,8 @@
|
|||
</template>
|
||||
|
||||
<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 default {
|
||||
|
|
@ -49,6 +59,7 @@ export default {
|
|||
|
||||
components: {
|
||||
ActionButton,
|
||||
matrixDisplay,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
|
|
|||
157
webapp/src/components/ui/matrixDisplay.vue
Normal file
157
webapp/src/components/ui/matrixDisplay.vue
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
<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>
|
||||
.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: #666;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
color: #000;
|
||||
}
|
||||
.copy-button {
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.copy-button.success {
|
||||
color: #4caf50;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue