@@ -3,13 +3,14 @@ import { ref, watch } from 'vue'
33import { useThumbnailsStore } from ' @/stores/thumbnails'
44import { useConfigurationStore } from ' @/stores/configuration'
55import { useAlertsStore } from ' @/stores/alerts'
6+ import ImageDownloadMenu from ' @/components/Global/ImageDownloadMenu.vue'
67import FilterBadge from ' ./FilterBadge.vue'
78import AnalysisView from ' ../../views/AnalysisView.vue'
89
910const props = defineProps ({
1011 images: {
11- type: [ Array , Boolean ] ,
12- default: false
12+ type: Array ,
13+ default : () => []
1314 },
1415 selectedImages: {
1516 type: Array ,
@@ -22,6 +23,10 @@ const props = defineProps({
2223 allowSelection: {
2324 type: Boolean ,
2425 default: false
26+ },
27+ enableImageCards: {
28+ type: Boolean ,
29+ default: true
2530 }
2631})
2732
@@ -32,38 +37,21 @@ const emit = defineEmits(['selectImage'])
3237const showAnalysisDialog = ref (false )
3338const imageDetails = ref ({})
3439const analysisImage = ref ({})
35- var doubleClickTimer = 0
3640
37- const handleClick = (basename ) => {
38- clearTimeout (doubleClickTimer)
39- // timeout length indicates how long to wait for a second click before treating as a single click
40- doubleClickTimer = setTimeout (() => {
41- emit (' selectImage' , basename)
42- doubleClickTimer = 0
43- }, 250 )
41+ async function ensureLargeCachedUrl (image ) {
42+ if (! image .largeCachedUrl ) {
43+ const url = image .large_url || image .largeThumbUrl || ' '
44+ image .largeCachedUrl = await thumbnailsStore .cacheImage (' large' , configurationStore .archiveType , url, image .basename )
45+ }
46+ return image .largeCachedUrl
4447}
4548
46- const handleDoubleClick = (image ) => {
47- clearTimeout (doubleClickTimer)
49+ const launchAnalysis = async (image ) => {
4850 alertsStore .setAlert (' info' , ` Opening ${ image? .basename } for analysis` )
49- launchAnalysis(image)
50- }
51-
52- const launchAnalysis = (image) => {
5351 try {
54- if (!image.largeCachedUrl) {
55- image.largeCachedUrl = ref('')
56- const url = image.large_url || image.largeThumbUrl || ''
57- thumbnailsStore.cacheImage('large', configurationStore.archiveType, url, image.basename).then((cachedUrl) => {
58- image.largeCachedUrl = cachedUrl
59- analysisImage.value = image
60- showAnalysisDialog.value = true
61- })
62- }
63- else {
64- analysisImage.value = image
65- showAnalysisDialog.value = true
66- }
52+ await ensureLargeCachedUrl(image)
53+ analysisImage.value = image
54+ showAnalysisDialog.value = true
6755 } catch {
6856 alertsStore.setAlert('error', ` Failed to open ${image? .basename }` )
6957 }
@@ -74,7 +62,6 @@ const isSelected = (basename) => {
7462}
7563
7664watch(() => props.images, () => {
77- if (!props.images) return
7865 props.images.forEach(image => {
7966 if (image.basename && !(image.basename in imageDetails.value)) {
8067 imageDetails.value[image.basename] = ref('')
@@ -90,22 +77,27 @@ watch(() => props.images, () => {
9077
9178<template>
9279 <v-row>
93- <template v-if="props.images">
94- <v-col
95- v-for="(image, index) in props.images"
96- :key="index"
97- :cols="columnSpan"
98- class="image-grid-col"
80+ <v-col
81+ v-for="(image, index) in props.images"
82+ :key="index"
83+ :cols="columnSpan"
84+ class="image-grid-col"
85+ >
86+ <v-sheet
87+ v-if="image.basename in imageDetails && imageDetails[image.basename]"
88+ class="pa-2"
89+ color="var(--secondary-background)"
90+ :elevation="2"
91+ rounded
92+ :class="{ 'selected-image': isSelected(image.basename) }"
93+ @click="emit('selectImage', image.basename)"
9994 >
10095 <v-img
101- v-if="image.basename in imageDetails && imageDetails[image.basename]"
10296 :src="imageDetails[image.basename]"
10397 :alt="image.basename"
98+ rounded
10499 cover
105- :class="{ 'selected-image': isSelected(image.basename) }"
106100 aspect-ratio="1"
107- @click="handleClick(image.basename)"
108- @dblclick="handleDoubleClick(image)"
109101 >
110102 <filter-badge
111103 v-if="image.filter || image.FILTER"
@@ -116,33 +108,37 @@ watch(() => props.images, () => {
116108 class="image-text-overlay"
117109 >{{ image.operationIndex }}</span>
118110 </v-img>
119- <v-skeleton-loader
120- v-else
121- type="card"
122- color="var(--secondary-background)"
123- bg-color="var(--primary-background)"
124- />
125- </v-col>
126- </template>
127- <template v-else>
128- <v-col
129- v-for="n in 10"
130- :key="n"
131- :cols="columnSpan"
132- class="image-grid-col"
133- >
134- <v-skeleton-loader
135- type="card"
136- class="ma-1"
137- color="var(--secondary-background)"
138- bg-color="var(--primary-background)"
139- />
140- </v-col>
141- </template>
111+ <div
112+ v-if="props.enableImageCards"
113+ class="d-flex flex-row ga-2 align-center mt-2"
114+ >
115+ <p class="text-subtitle-2 mr-auto prevent-select single-line-text">
116+ {{ image.target_name || image.operationName }}
117+ </p>
118+ <v-icon
119+ icon="mdi-eye"
120+ color="var(--primary-interactive)"
121+ @click.stop="launchAnalysis(image)"
122+ />
123+ <image-download-menu
124+ :fits-url="image.url || image.fits_url || ''"
125+ :jpg-url="image.largeCachedUrl || image.large_url || ''"
126+ :image-name="image.basename"
127+ speed-dial-location="top right"
128+ :enable-scaled-download="false"
129+ />
130+ </div>
131+ </v-sheet>
132+ <v-skeleton-loader
133+ v-else
134+ type="card"
135+ color="var(--secondary-background)"
136+ bg-color="var(--primary-background)"
137+ />
138+ </v-col>
142139 </v-row>
143140 <v-dialog
144141 v-model="showAnalysisDialog"
145- persistent
146142 fullscreen
147143 >
148144 <analysis-view
@@ -154,9 +150,8 @@ watch(() => props.images, () => {
154150
155151<style scoped>
156152.selected-image {
157- border : 0.3rem solid var(--primary-interactive);
153+ outline : 0.3rem solid var(--primary-interactive);
158154}
159-
160155.image-text-overlay {
161156 color: var(--text);
162157 font-weight: bold;
0 commit comments