Start showing images in gallery (requires tidy)
This commit is contained in:
parent
1e12900506
commit
5a07721fc2
6 changed files with 123 additions and 29 deletions
|
|
@ -8,7 +8,7 @@ import shutil
|
|||
import threading
|
||||
import zipfile
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Mapping, Optional, Self
|
||||
from typing import Any, Literal, Mapping, Optional, Self
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
|
|
@ -55,6 +55,8 @@ class ScanInfo(BaseModel):
|
|||
number_of_images: int
|
||||
stitch_available: bool
|
||||
dzi: Optional[str]
|
||||
thing: str = "smart_scan"
|
||||
card_type: Literal["Scan"] = "Scan"
|
||||
|
||||
|
||||
class BaseScanData(BaseModel):
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from typing import Any, Literal, Mapping, Optional, Self
|
|||
|
||||
import numpy as np
|
||||
import piexif
|
||||
from fastapi import Response
|
||||
from fastapi import HTTPException, Response
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -199,6 +199,16 @@ class CaptureMode(BaseModel):
|
|||
"""The resolution to save the image. Use None to save as captured."""
|
||||
|
||||
|
||||
class CaptureInfo(BaseModel):
|
||||
"""Summary information for the UI about an image."""
|
||||
|
||||
name: str
|
||||
created: float
|
||||
modified: float
|
||||
thing: str
|
||||
card_type: Literal["Capture"] = "Capture"
|
||||
|
||||
|
||||
class BaseCamera(OFMThing, ABC):
|
||||
"""The base class for all cameras. All cameras must directly inherit from this class.
|
||||
|
||||
|
|
@ -275,6 +285,65 @@ class BaseCamera(OFMThing, ABC):
|
|||
jpeg_data = await self.lores_mjpeg_stream.grab_frame()
|
||||
return Response(content=jpeg_data, media_type="image/jpeg")
|
||||
|
||||
# Register with gallery.
|
||||
_show_data_in_gallery = True
|
||||
|
||||
@property
|
||||
def gallery_data_name(self) -> str:
|
||||
"""Name under which data shows up in gallery."""
|
||||
return "Captures"
|
||||
|
||||
@property
|
||||
def gallery_data_schema(self) -> type[CaptureInfo]:
|
||||
"""The schema (BaseModel) for passing data to the gallery."""
|
||||
return CaptureInfo
|
||||
|
||||
def get_data_for_gallery(self) -> list[CaptureInfo]:
|
||||
"""Return all the information about the saved captures."""
|
||||
files = os.listdir(self._data_dir)
|
||||
captures = []
|
||||
for filename in files:
|
||||
full_path = os.path.join(self.data_dir, filename)
|
||||
if os.path.isfile(full_path) and (
|
||||
BASE_IMAGE_FORMATS["jpeg"].path_matches(filename)
|
||||
or BASE_IMAGE_FORMATS["png"].path_matches(filename)
|
||||
):
|
||||
captures.append(
|
||||
CaptureInfo(
|
||||
name=filename,
|
||||
created=os.path.getctime(full_path),
|
||||
modified=os.path.getmtime(full_path),
|
||||
thing=self.name,
|
||||
)
|
||||
)
|
||||
return captures
|
||||
|
||||
@lt.endpoint(
|
||||
"delete",
|
||||
"capture/{name}",
|
||||
responses={
|
||||
200: {"description": "Successfully deleted capture"},
|
||||
400: {"description": "An error occurred while trying to delete capture"},
|
||||
},
|
||||
)
|
||||
def delete_capture(self, name: str) -> None:
|
||||
"""Delete the specified capture.
|
||||
|
||||
This endpoint allows captures to be deleted from disk.
|
||||
|
||||
:param name: The name of the capture to delete
|
||||
"""
|
||||
full_path = os.path.join(self.data_dir, name)
|
||||
if not os.path.exists(full_path):
|
||||
self.logger.warning(f"Cannot find a capture of name {name}")
|
||||
raise HTTPException(400, "Capture not found")
|
||||
try:
|
||||
os.remove(full_path)
|
||||
except IOError as e:
|
||||
raise HTTPException(
|
||||
400, "Couldn't delete capture, check log for details"
|
||||
) from e
|
||||
|
||||
@property
|
||||
def focus_fom(self) -> int:
|
||||
"""Return the focus figure of merit.
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
<div class="view-image uk-padding-remove uk-height-1-1">
|
||||
<img
|
||||
id="thumbnail-stitched-image"
|
||||
:class="[itemData?.dzi ? 'thumbnail-fit clickable' : 'thumbnail-fit disabled']"
|
||||
:class="[viewerAvailable ? 'thumbnail-fit clickable' : 'thumbnail-fit disabled']"
|
||||
class="thumbnail-fit"
|
||||
:src="thumbnailPath"
|
||||
onerror="this.src = '/titleiconpink.svg'"
|
||||
@click="itemData?.dzi && requestViewer()"
|
||||
@click="viewerAvailable && requestViewer()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="uk-card-title gallery-card-title">{{ itemData.name }}</h3>
|
||||
<div class="button-container">
|
||||
<div class="uk-button-group gallery-card-buttons">
|
||||
<div v-if="itemData.card_type === 'Scan'" class="uk-button-group gallery-card-buttons">
|
||||
<action-button
|
||||
class="uk-width-1-2"
|
||||
thing="smart_scan"
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
Show Stitched Scan
|
||||
</button>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<div v-if="itemData.card_type === 'Scan'" class="item-info">
|
||||
<ul>
|
||||
<li>{{ itemData.number_of_images }} images</li>
|
||||
<li>Created: {{ formatDate(itemData.created) }}</li>
|
||||
|
|
@ -107,7 +107,14 @@ export default {
|
|||
return `${this.baseUri}/smart_scan/get_stitch/${this.itemData.name}`;
|
||||
},
|
||||
thumbnailPath() {
|
||||
return `${this.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.itemData.name}&modified=${this.itemData.modified}`;
|
||||
if (this.itemData.card_type === "Scan") {
|
||||
return `${this.baseUri}/smart_scan/scans/stitched_thumbnail.jpg?scan_name=${this.itemData.name}&modified=${this.itemData.modified}`;
|
||||
}
|
||||
return `${this.baseUri}/data/${this.itemData.thing}/${this.itemData.name}`;
|
||||
},
|
||||
viewerAvailable() {
|
||||
if (this.itemData.card_type === "Scan") return Boolean(this.itemData?.dzi);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -144,7 +151,13 @@ export default {
|
|||
async deleteScan() {
|
||||
try {
|
||||
await this.modalConfirm(`Are you sure you want to delete ${this.itemData.name}?`);
|
||||
await axios.delete(`${this.baseUri}/smart_scan/scans/${this.itemData.name}`);
|
||||
if (this.itemData.card_type === "Scan") {
|
||||
await axios.delete(`${this.baseUri}/smart_scan/scans/${this.itemData.name}`);
|
||||
} else {
|
||||
await axios.delete(
|
||||
`${this.baseUri}/${this.itemData.thing}/capture/${this.itemData.name}`,
|
||||
);
|
||||
}
|
||||
this.$emit("update-requested");
|
||||
this.modalNotify(`Deleted ${this.itemData.name}`);
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default {
|
|||
|
||||
props: {
|
||||
src: {
|
||||
type: String,
|
||||
type: [String, Object],
|
||||
required: true,
|
||||
},
|
||||
brightness: {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div id="scan-modal" ref="scanModal" uk-modal>
|
||||
<div v-if="selectedScan" id="scan-modal-body" class="uk-modal-dialog uk-modal-body">
|
||||
<div v-if="selectedItem" id="scan-modal-body" class="uk-modal-dialog uk-modal-body">
|
||||
<h2 id="scan-modal-title" class="uk-modal-title">
|
||||
{{ selectedScan.name }}
|
||||
{{ selectedItem.name }}
|
||||
<button class="uk-modal-close uk-float-right" type="button">
|
||||
<span class="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
|
|
@ -12,11 +12,11 @@
|
|||
</h2>
|
||||
|
||||
<!-- Viewer -->
|
||||
<div v-if="selectedScanDZI" id="viewer_container" class="viewer_container">
|
||||
<div v-if="imageSource" id="viewer_container" class="viewer_container">
|
||||
<OpenSeadragonViewer
|
||||
id="openseadragon"
|
||||
ref="openseadragon"
|
||||
:src="selectedScanDZI"
|
||||
:src="imageSource"
|
||||
:brightness="brightness"
|
||||
:contrast="contrast"
|
||||
:saturation="saturation"
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div v-if="selectedScanDZI" class="viewer-controls">
|
||||
<div v-if="imageSource" class="viewer-controls">
|
||||
<div class="controlsContainer">
|
||||
<label>
|
||||
Brightness
|
||||
|
|
@ -63,7 +63,7 @@ export default {
|
|||
OpenSeadragonViewer,
|
||||
},
|
||||
props: {
|
||||
selectedScan: {
|
||||
selectedItem: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
|
|
@ -79,9 +79,14 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
selectedScanDZI() {
|
||||
if (this.selectedScan && this.selectedScan.dzi) {
|
||||
return `${this.baseUri}/data/smart_scan/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
|
||||
imageSource() {
|
||||
if (this.selectedItem?.card_type === "Scan" && this.selectedItem?.dzi) {
|
||||
return `${this.baseUri}/data/smart_scan/${this.selectedItem.name}/images/${this.selectedItem.dzi}`;
|
||||
} else if (this.selectedItem?.card_type === "Capture") {
|
||||
return {
|
||||
type: "image",
|
||||
url: `${this.baseUri}/data/${this.selectedItem.thing}/${this.selectedItem.name}`,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
<ScanViewerModal ref="scanViewer" :selected-scan="selectedScan" :base-uri="baseUri" />
|
||||
<ScanViewerModal ref="scanViewer" :selected-item="selectedItem" :base-uri="baseUri" />
|
||||
|
||||
<!-- Gallery -->
|
||||
<div v-if="ready" class="uk-padding-remove-top" uk-lightbox="toggle: .lightbox-link">
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
<div v-for="itemData in paginatedItems" :key="itemData.id">
|
||||
<gallery-card
|
||||
:item-data="itemData"
|
||||
@viewer-requested="showScan"
|
||||
@viewer-requested="showItem"
|
||||
@update-requested="refreshGallery"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -95,7 +95,7 @@ export default {
|
|||
data: function () {
|
||||
return {
|
||||
all_items: [],
|
||||
selectedScan: null,
|
||||
selectedItem: null,
|
||||
osdViewer: null,
|
||||
currentPage: 1,
|
||||
itemsPerPage: 18,
|
||||
|
|
@ -112,9 +112,9 @@ export default {
|
|||
noItems() {
|
||||
return !this.all_items || this.all_items?.length === 0;
|
||||
},
|
||||
selectedScanDZI() {
|
||||
if (this.selectedScan && this.selectedScan.dzi != "") {
|
||||
return `${this.baseUri}/data/smart_scan/${this.selectedScan.name}/images/${this.selectedScan.dzi}`;
|
||||
selectedItemDZI() {
|
||||
if (this.selectedItem && this.selectedItem.dzi != "") {
|
||||
return `${this.baseUri}/data/smart_scan/${this.selectedItem.name}/images/${this.selectedItem.dzi}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -209,12 +209,17 @@ export default {
|
|||
if (e) this.modalError(e);
|
||||
}
|
||||
},
|
||||
showScan(scan) {
|
||||
if (scan.dzi) {
|
||||
this.selectedScan = scan;
|
||||
this.$refs.scanViewer.show();
|
||||
showItem(itemData) {
|
||||
if (itemData.card_type === "Scan") {
|
||||
if (itemData.dzi) {
|
||||
this.selectedItem = itemData;
|
||||
this.$refs.scanViewer.show();
|
||||
} else {
|
||||
this.modalError("Scan not stitched for viewing in webapp, please download or stitch");
|
||||
}
|
||||
} else {
|
||||
this.modalError("Scan not stitched for viewing in webapp, please download or stitch");
|
||||
this.selectedItem = itemData;
|
||||
this.$refs.scanViewer.show();
|
||||
}
|
||||
},
|
||||
changePage(page) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue