OpenSeadragon viewer implemented

This commit is contained in:
jaknapper 2025-05-15 18:04:23 +01:00 committed by Julian Stirling
parent cc3620d2ed
commit a9b94bc0d8
3 changed files with 166 additions and 8 deletions

View file

@ -17,6 +17,7 @@
"material-design-icons": "^3.0", "material-design-icons": "^3.0",
"material-symbols": "^0.14.3", "material-symbols": "^0.14.3",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"openseadragon": "^5.0.0",
"vue-observe-visibility": "^0.4" "vue-observe-visibility": "^0.4"
}, },
"devDependencies": { "devDependencies": {

View file

@ -0,0 +1,96 @@
<template>
<div v-observe-visibility="visibilityChanged">
<div id="openseadragon"></div>
</div>
</template>
<script>
import OpenSeaDragon from "openseadragon";
// Export main app
export default {
name: "OpenSeadragonViewer",
components: { },
props: {
src: {
type: String,
required: true
}
},
data: function() {
return {
osdViewer: null
};
},
watch: {
src: {
immediate: true,
handler(newVal) {
this.loadOpenSeaDragon(newVal);
}
}
},
async mounted() {
if (this.src) {
this.loadOpenSeaDragon(this.src);
}
},
beforeDestroy() {
// Remove global signal listener to perform a gallery refresh
this.osdViewer.destroy();
},
methods: {
visibilityChanged(isVisible) {
if (isVisible) {
this.loadOpenSeaDragon();
}else{
this.osdViewer.destroy();
}
},
async loadOpenSeaDragon() {
if (this.osdViewer) {
this.osdViewer.destroy();
}
this.osdViewer = OpenSeaDragon({
id: "openseadragon",
crossOriginPolicy: 'Anonymous',
tileSources: this.src,
showNavigationControl: false,
maxZoomPixelRatio: 2,
gestureSettingsMouse: {
clickToZoom: false
}
});
},
openFullscreen() {
if (this.osdViewer) {
this.osdViewer.setFullScreen(true);
}
},
}
};
</script>
<style lang="less" scoped>
#openseadragon {
width: 100%;
height: 100%;
background-color: black;
z-index:1;
}
#info-panel {
position: relative;
top: 10px;
left: 10px;
background-color: rgba(255, 255, 255, 0.7);
padding: 5px;
border-radius: 1px;
z-index: 1000;
}
</style>

View file

@ -1,8 +1,7 @@
<template> <template>
<div <div
v-observe-visibility="visibilityChanged" v-observe-visibility="visibilityChanged"
class="galleryDisplay uk-padding uk-padding-remove-top" class="galleryDisplay uk-padding uk-padding-remove-top">
>
<!-- Gallery nav bar --> <!-- Gallery nav bar -->
<nav <nav
class="gallery-navbar uk-navbar-container uk-navbar-transparent" class="gallery-navbar uk-navbar-container uk-navbar-transparent"
@ -35,6 +34,26 @@
</div> </div>
</nav> </nav>
<!-- Modal for scan display -->
<div id="scan-modal" ref="scanModal" uk-modal>
<div class="uk-modal-dialog uk-modal-body" v-if="selectedScan" style="width: 95%; height: 95%;">
<h2 class="uk-modal-title">
{{ selectedScan.name }}
<button class="uk-modal-close uk-float-right" type="button"><span class="material-symbols-outlined">close</span></button>
<button class="uk-float-right" type="button" @click="goFullscreen">
<span class="material-symbols-outlined">fullscreen</span>
</button>
</h2>
<div v-if="selectedScanDZIAvailable" id="viewer_container" style="height: 80%;">
<OpenSeadragonViewer
id="openseadragon"
ref="openseadragon"
:src="selectedScanDZI"
/>
</div>
</div>
</div>
<!-- Gallery --> <!-- Gallery -->
<div <div
v-if="$store.getters.ready" v-if="$store.getters.ready"
@ -50,7 +69,7 @@
</p> </p>
</div> </div>
<div v-for="item in scans" :key="item.id"> <div v-for="item in scans" :key="item.id">
<div class="uk-card"> <div class="uk-card" @click="showScan(item)">
<div class="uk-card-body"> <div class="uk-card-body">
<div class="uk-card-media-top"> <div class="uk-card-media-top">
<div <div
@ -65,7 +84,8 @@
</div> </div>
</div> </div>
<h3 class="uk-card-title" style="text-align: center;">{{ item.name }}</h3> <h3 class="uk-card-title" style="text-align: center;">{{ item.name }}</h3>
<action-button <div class="button-container">
<action-button
thing="smart_scan" thing="smart_scan"
action="download_zip" action="download_zip"
submit-label="Download ZIP" submit-label="Download ZIP"
@ -105,20 +125,25 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import axios from "axios"; import axios from "axios";
import UIkit from "uikit";
import actionButton from "../labThingsComponents/actionButton.vue"; import actionButton from "../labThingsComponents/actionButton.vue";
import OpenSeadragonViewer from "./scanListComponents/openSeadragonViewer.vue";
// Export main app // Export main app
export default { export default {
name: "ScanListContent", name: "ScanListContent",
components: { actionButton }, components: { actionButton, OpenSeadragonViewer },
data: function() { data: function() {
return { return {
scans: [] scans: [],
selectedScan: null,
osdViewer: null
}; };
}, },
@ -133,6 +158,16 @@ export default {
}, },
scansEmpty() { scansEmpty() {
return this.scans.length == 0; return this.scans.length == 0;
},
selectedScanDZI() {
if (this.selectedScan && this.dzi!="") {
return `${this.$store.getters.baseUri}/scans/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
} else {
return null;
}
},
selectedScanDZIAvailable() {
return this.selectedScan && this.selectedScan.dzi;
} }
}, },
@ -185,6 +220,9 @@ export default {
this.updateScans(); this.updateScans();
} }
}, },
goFullscreen() {
this.$refs.openseadragon.openFullscreen();
},
async updateScans() { async updateScans() {
try { try {
let scans = await this.readThingProperty("smart_scan", "scans"); let scans = await this.readThingProperty("smart_scan", "scans");
@ -194,7 +232,7 @@ export default {
scans.forEach(scan => { scans.forEach(scan => {
scan.modified = Date.parse(scan.modified); scan.modified = Date.parse(scan.modified);
scan.created = Date.parse(scan.created); scan.created = Date.parse(scan.created);
scan.can_stitch = !scan.stitch_available && scan.number_of_images > 3; scan.can_stitch = !scan.stitch_available && scan.number_of_images > 3 || !scan.dzi && scan.number_of_images > 3;
}); });
scans.sort((a, b) => { scans.sort((a, b) => {
return b.modified - a.modified; return b.modified - a.modified;
@ -256,7 +294,16 @@ export default {
console.log(link); console.log(link);
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
} },
showScan(scan) {
if (scan.dzi){
this.selectedScan = scan;
UIkit.modal(this.$refs.scanModal).show();
}
else {
this.modalError(`Scan not stitched for viewing in webapp, please download or stitch`)
}
},
} }
}; };
</script> </script>
@ -285,6 +332,20 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
} }
#openseadragon {
width: 100%;
height: 100%;
}
#info-panel {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(255, 255, 255, 0.7);
padding: 5px;
border-radius: 1px;
z-index: 1000;
}
/deep/ .capture-card { /deep/ .capture-card {
width: 300px; width: 300px;
height: 100%; // Used to have all cards in a row match their heights height: 100%; // Used to have all cards in a row match their heights