Start the UI transition from ScanList to Gallery

This commit is contained in:
Julian Stirling 2026-05-08 17:20:24 +01:00
parent d358d89334
commit 5f6b54247d
5 changed files with 71 additions and 76 deletions

View file

@ -85,7 +85,7 @@ import aboutContent from "./tabContentComponents/aboutContent.vue";
import controlContent from "./tabContentComponents/controlContent.vue"; import controlContent from "./tabContentComponents/controlContent.vue";
import loggingContent from "./tabContentComponents/loggingContent.vue"; import loggingContent from "./tabContentComponents/loggingContent.vue";
import powerContent from "./tabContentComponents/powerContent.vue"; import powerContent from "./tabContentComponents/powerContent.vue";
import scanListContent from "./tabContentComponents/scanListContent.vue"; import galleryContent from "./tabContentComponents/galleryContent.vue";
import settingsContent from "./tabContentComponents/settingsContent.vue"; import settingsContent from "./tabContentComponents/settingsContent.vue";
import slideScanContent from "./tabContentComponents/slideScanContent.vue"; import slideScanContent from "./tabContentComponents/slideScanContent.vue";
import viewContent from "./tabContentComponents/viewContent.vue"; import viewContent from "./tabContentComponents/viewContent.vue";
@ -158,10 +158,10 @@ export default {
requiredThings: ["smart_scan"], requiredThings: ["smart_scan"],
}, },
{ {
id: "scan-list", id: "gallery",
title: "Scan List", title: "Gallery",
icon: "photo_library", icon: "photo_library",
component: markRaw(scanListContent), component: markRaw(galleryContent),
requiredThings: ["smart_scan"], requiredThings: ["smart_scan"],
}, },
], ],

View file

@ -12,16 +12,16 @@
/> />
</div> </div>
</div> </div>
<h3 class="uk-card-title scan-card-title">{{ scanData.name }}</h3> <h3 class="uk-card-title gallery-card-title">{{ itemData.name }}</h3>
<div class="button-container"> <div class="button-container">
<div class="uk-button-group scan-card-buttons"> <div class="uk-button-group gallery-card-buttons">
<action-button <action-button
class="uk-width-1-2" class="uk-width-1-2"
thing="smart_scan" thing="smart_scan"
action="download_zip" action="download_zip"
submit-label="Download All" submit-label="Download All"
:can-terminate="false" :can-terminate="false"
:submit-data="{ scan_name: scanData.name }" :submit-data="{ scan_name: itemData.name }"
:button-primary="true" :button-primary="true"
@response="downloadZipFile" @response="downloadZipFile"
@error="modalError" @error="modalError"
@ -29,45 +29,45 @@
<EndpointButton <EndpointButton
class="uk-width-1-2" class="uk-width-1-2"
:button-primary="true" :button-primary="true"
:is-disabled="!scanData.stitch_available" :is-disabled="!itemData.stitch_available"
:url="downloadStitchFile" :url="downloadStitchFile"
button-label="Download JPEG" button-label="Download JPEG"
/> />
</div> </div>
<button class="uk-button uk-button-default uk-width-1-1" @click="deleteScan">Delete</button> <button class="uk-button uk-button-default uk-width-1-1" @click="deleteScan">Delete</button>
<action-button <action-button
v-if="scanData.can_stitch | (scanData.stitch_available & !scanData.dzi)" v-if="itemData.can_stitch | (itemData.stitch_available & !itemData.dzi)"
submit-label="Stitch Images" submit-label="Stitch Images"
thing="smart_scan" thing="smart_scan"
action="stitch_scan" action="stitch_scan"
:can-terminate="true" :can-terminate="true"
:submit-data="{ scan_name: scanData.name }" :submit-data="{ scan_name: itemData.name }"
:button-primary="false" :button-primary="false"
:modal-progress="true" :modal-progress="true"
@error="modalError" @error="modalError"
/> />
<button <button
v-if="scanData.dzi" v-if="itemData.dzi"
class="uk-button uk-button-default uk-width-1-1" class="uk-button uk-button-default uk-width-1-1"
@click="requestViewer" @click="requestViewer"
> >
Show Stitched Scan Show Stitched Scan
</button> </button>
</div> </div>
<div class="scan-info"> <div class="item-info">
<ul> <ul>
<li>{{ scanData.number_of_images }} images</li> <li>{{ itemData.number_of_images }} images</li>
<li>Created: {{ formatDate(scanData.created) }}</li> <li>Created: {{ formatDate(itemData.created) }}</li>
<li>Duration: {{ formatDuration(scanData.duration) }}</li> <li>Duration: {{ formatDuration(itemData.duration) }}</li>
</ul> </ul>
<ul> <ul>
<li v-if="scanData.number_of_images < 3" class="warning-msg"> <li v-if="itemData.number_of_images < 3" class="warning-msg">
Not enough images to stitch Not enough images to stitch
</li> </li>
<li v-else-if="!scanData.dzi && scanData.stitch_available" class="alert-msg"> <li v-else-if="!itemData.dzi && itemData.stitch_available" class="alert-msg">
Interactive preview not available Interactive preview not available
</li> </li>
<li v-else-if="!scanData.stitch_available" class="alert-msg"> <li v-else-if="!itemData.stitch_available" class="alert-msg">
High quality stitch not available High quality stitch not available
</li> </li>
</ul> </ul>
@ -85,21 +85,17 @@ import { useSettingsStore } from "@/stores/settings.js";
// Export main app // Export main app
export default { export default {
name: "ScanCard", name: "GalleryCard",
components: { components: {
actionButton, actionButton,
EndpointButton, EndpointButton,
}, },
props: { props: {
scanData: { itemData: {
type: Object, type: Object,
required: true, required: true,
}, },
scansUri: {
type: String,
required: true,
},
}, },
emits: ["viewer-requested", "update-requested"], emits: ["viewer-requested", "update-requested"],
@ -107,10 +103,10 @@ export default {
computed: { computed: {
...mapState(useSettingsStore, ["baseUri"]), ...mapState(useSettingsStore, ["baseUri"]),
downloadStitchFile() { downloadStitchFile() {
return `${this.baseUri}/smart_scan/get_stitch/${this.scanData.name}`; return `${this.baseUri}/smart_scan/get_stitch/${this.itemData.name}`;
}, },
thumbnailPath() { thumbnailPath() {
return `${this.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.scanData.name}&modified=${this.scanData.modified}`; return `${this.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.itemData.name}&modified=${this.itemData.modified}`;
}, },
}, },
@ -142,14 +138,14 @@ export default {
}, },
requestViewer() { requestViewer() {
// Notify parent that thumbnail was clicked // Notify parent that thumbnail was clicked
this.$emit("viewer-requested", this.scanData); this.$emit("viewer-requested", this.itemData);
}, },
async deleteScan() { async deleteScan() {
try { try {
await this.modalConfirm(`Are you sure you want to delete ${this.scanData.name}?`); await this.modalConfirm(`Are you sure you want to delete ${this.itemData.name}?`);
await axios.delete(`${this.scansUri}/${this.scanData.name}`); await axios.delete(`${this.baseUri}/smart_scan/scans/${this.itemData.name}`);
this.$emit("update-requested"); this.$emit("update-requested");
this.modalNotify(`Deleted ${this.scanData.name}`); this.modalNotify(`Deleted ${this.itemData.name}`);
} catch (e) { } catch (e) {
// if the confirmation was cancelled, it's rejected with null error // if the confirmation was cancelled, it's rejected with null error
if (e) this.modalError(e); if (e) this.modalError(e);
@ -189,11 +185,11 @@ ul {
font-weight: bold; font-weight: bold;
} }
.scan-card-buttons { .gallery-card-buttons {
width: 100%; width: 100%;
} }
.scan-card-title { .gallery-card-title {
text-align: center; text-align: center;
} }
</style> </style>

View file

@ -5,7 +5,7 @@
<!-- Right side buttons --> <!-- Right side buttons -->
<div class="uk-navbar-right"> <div class="uk-navbar-right">
<div class="uk-grid"> <div class="uk-grid">
<div class="scan-list-button"> <div class="gallery-button">
<action-button <action-button
class="uk-width-1-1" class="uk-width-1-1"
thing="smart_scan" thing="smart_scan"
@ -21,7 +21,7 @@
</div> </div>
<div> <div>
<button <button
class="uk-button uk-button-default uk-width-1-1 scan-list-button" class="uk-button uk-button-default uk-width-1-1 gallery-button"
type="button" type="button"
@click="deleteAllScans()" @click="deleteAllScans()"
> >
@ -30,11 +30,11 @@
</div> </div>
<div> <div>
<button <button
class="uk-button uk-button-default uk-width-1-1 scan-list-button" class="uk-button uk-button-default uk-width-1-1 gallery-button"
type="button" type="button"
@click="updateScans()" @click="refreshGallery()"
> >
Refresh Scans Refresh
</button> </button>
</div> </div>
</div> </div>
@ -47,16 +47,15 @@
<div v-if="ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link"> <div v-if="ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
<!-- Gallery capture cards --> <!-- Gallery capture cards -->
<div class="gallery-grid uk-grid-match" uk-grid> <div class="gallery-grid uk-grid-match" uk-grid>
<div v-if="scansEmpty"> <div v-if="noItems">
<h2>No scans available</h2> <h2>Nothing to show</h2>
<p>There are no scans available to show.</p> <p>There is no captured data to show..</p>
</div> </div>
<div v-for="scanData in paginatedScans" :key="scanData.id"> <div v-for="itemData in paginatedItems" :key="itemData.id">
<scan-card <gallery-card
:scan-data="scanData" :item-data="itemData"
:scans-uri="scansUri"
@viewer-requested="showScan" @viewer-requested="showScan"
@update-requested="updateScans" @update-requested="refreshGallery"
/> />
</div> </div>
</div> </div>
@ -73,8 +72,8 @@
import axios from "axios"; import axios from "axios";
import PaginateLinks from "@/components/genericComponents/paginateLinks.vue"; import PaginateLinks from "@/components/genericComponents/paginateLinks.vue";
import actionButton from "../labThingsComponents/actionButton.vue"; import actionButton from "../labThingsComponents/actionButton.vue";
import scanCard from "./scanListComponents/scanCard.vue"; import galleryCard from "./galleryComponents/galleryCard.vue";
import ScanViewerModal from "./scanListComponents/scanViewer.vue"; import ScanViewerModal from "./galleryComponents/scanViewer.vue";
import { eventBus } from "../../eventBus.js"; import { eventBus } from "../../eventBus.js";
import { useIntersectionObserver } from "@vueuse/core"; import { useIntersectionObserver } from "@vueuse/core";
import { useSettingsStore } from "@/stores/settings.js"; import { useSettingsStore } from "@/stores/settings.js";
@ -83,10 +82,10 @@ import { watch } from "vue";
// Export main app // Export main app
export default { export default {
name: "ScanListContent", name: "GalleryContent",
components: { components: {
actionButton, actionButton,
scanCard, galleryCard,
ScanViewerModal, ScanViewerModal,
PaginateLinks, PaginateLinks,
}, },
@ -95,7 +94,7 @@ export default {
data: function () { data: function () {
return { return {
scans: [], all_items: [],
selectedScan: null, selectedScan: null,
osdViewer: null, osdViewer: null,
currentPage: 1, currentPage: 1,
@ -110,8 +109,8 @@ export default {
// The actual property does not exist. So allowUndefined=true // The actual property does not exist. So allowUndefined=true
return this.thingPropertyUrl("smart_scan", "scans", true); return this.thingPropertyUrl("smart_scan", "scans", true);
}, },
scansEmpty() { noItmesy() {
return this.scans.length == 0; return this.all_items.length == 0;
}, },
selectedScanDZI() { selectedScanDZI() {
if (this.selectedScan && this.selectedScan.dzi != "") { if (this.selectedScan && this.selectedScan.dzi != "") {
@ -121,11 +120,11 @@ export default {
} }
}, },
totalPages() { totalPages() {
return Math.ceil(this.scans.length / this.itemsPerPage); return Math.ceil(this.all_items.length / this.itemsPerPage);
}, },
paginatedScans() { paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage; const start = (this.currentPage - 1) * this.itemsPerPage;
return this.scans.slice(start, start + this.itemsPerPage); return this.all_items.slice(start, start + this.itemsPerPage);
}, },
}, },
@ -134,9 +133,9 @@ export default {
const { ready } = storeToRefs(store); const { ready } = storeToRefs(store);
watch(ready, (isReady) => { watch(ready, (isReady) => {
if (isReady) { if (isReady) {
this.updateScans(); this.refreshGallery();
} else { } else {
this.scans = []; this.all_items = [];
} }
}); });
useIntersectionObserver( useIntersectionObserver(
@ -149,51 +148,51 @@ export default {
}, },
); );
// Update on mount (does nothing if not connected) // Update on mount (does nothing if not connected)
await this.updateScans(); await this.refreshGallery();
// A global signal listener to perform a gallery refresh // A global signal listener to perform a gallery refresh
eventBus.on("globalUpdateScans", () => { eventBus.on("globalRefreshGallery", () => {
this.updateScans(); this.refreshGallery();
}); });
eventBus.on("modalClosed", () => { eventBus.on("modalClosed", () => {
// Handle the modal closed event here // Handle the modal closed event here
this.updateScans(); this.refreshGallery();
}); });
}, },
beforeUnmount() { beforeUnmount() {
// Remove global signal listener to perform a gallery refresh // Remove global signal listener to perform a gallery refresh
eventBus.off("globalUpdateScans", this.updateScans); eventBus.off("globalRefreshGallery", this.refreshGallery);
// Then we call that function here to unwatch // Then we call that function here to unwatch
if (this.unwatchStoreFunction) { if (this.unwatchStoreFunction) {
this.unwatchStoreFunction(); this.unwatchStoreFunction();
this.unwatchStoreFunction = null; this.unwatchStoreFunction = null;
} }
eventBus.off("modalClosed", this.updateScans); // Clean up event listener eventBus.off("modalClosed", this.refreshGallery); // Clean up event listener
}, },
methods: { methods: {
visibilityChanged(isVisible) { visibilityChanged(isVisible) {
if (isVisible) { if (isVisible) {
this.updateScans(); this.refreshGallery();
} }
}, },
async updateScans() { async refreshGallery() {
try { try {
let scans = await this.readThingProperty("gallery", "list_data"); let all_items = await this.readThingProperty("gallery", "list_data");
if (!scans | (scans.length == 0)) { if (!all_items | (all_items.length == 0)) {
this.scans = scans; this.all_items = all_items;
} }
scans.forEach((scan) => { all_items.forEach((item) => {
scan.can_stitch = !scan.stitch_available && scan.number_of_images > 3; item.can_stitch = !item.stitch_available && item.number_of_images > 3;
}); });
scans.sort((a, b) => { all_items.sort((a, b) => {
return b.created - a.created; return b.created - a.created;
}); });
this.scans = scans; this.all_items = all_items;
} catch (err) { } catch (err) {
console.error("Failed to refresh scans"); console.error("Failed to refresh gallery items.");
console.error(err); console.error(err);
this.scans = []; this.all_items = [];
} }
}, },
async deleteAllScans() { async deleteAllScans() {
@ -203,7 +202,7 @@ export default {
"This is <b>irreversible</b>!", "This is <b>irreversible</b>!",
); );
await axios.delete(`${this.scansUri}`); await axios.delete(`${this.scansUri}`);
await this.updateScans(); await this.refreshGallery();
this.modalNotify("Deleted all scans."); this.modalNotify("Deleted all scans.");
} catch (e) { } catch (e) {
// if the confirmation was cancelled, it's rejected with null error // if the confirmation was cancelled, it's rejected with null error
@ -240,7 +239,7 @@ export default {
margin-bottom: 30px; margin-bottom: 30px;
} }
.scan-list-button { .gallery-button {
margin-top: 5px; margin-top: 5px;
margin-bottom: 2px; margin-bottom: 2px;
} }