Skip to content

Commit 9445bd4

Browse files
committed
Add download button to viewer and mosaic interfaces for original media files
1 parent 6b53f3a commit 9445bd4

2 files changed

Lines changed: 91 additions & 3 deletions

File tree

frontend-user-interface/assets/css/styles.css

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ main { flex: 1; position: relative; }
8080
/* Reserved: placeholder class no longer used; keep for compatibility */
8181
.mosaic-placeholder { display: none; }
8282

83+
/* Mosaic download button (bottom-right) */
84+
.mosaic-download-btn { position: absolute; right: 4px; bottom: 4px; background: rgba(37,99,235,0.92); color: #fff; border: none; border-radius: 12px; padding: 4px 6px; font-size: 11px; line-height: 1; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 1px 4px rgba(0,0,0,0.2); z-index: 5; }
85+
.mosaic-tile:hover .mosaic-download-btn, .mosaic-tile:focus-within .mosaic-download-btn { opacity: 1; transform: translateY(0); }
86+
.mosaic-download-btn:hover { background: #1d4ed8; }
87+
8388
/* Tooltip for mosaic photo tiles */
8489
.mosaic-photo-tooltip {
8590
position: fixed;
@@ -180,13 +185,20 @@ section#tab-mosaic.tab.active { display: flex; }
180185
}
181186

182187
/* Hover edit button on viewer image */
183-
.img-edit-btn { position: absolute; right: 8px; bottom: 8px; background: rgba(37,99,235,0.92); color: #fff; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; }
188+
.img-edit-btn { position: absolute; right: 50px; bottom: 8px; background: rgba(37,99,235,0.92); color: #fff; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; }
184189
.image-container:hover .img-edit-btn { opacity: 1; transform: translateY(0); }
185190
.img-edit-btn:hover { background: #1d4ed8; }
186191
/* Hide image edit button in fullscreen mode */
187192
.image-container:fullscreen .img-edit-btn { display: none !important; }
188193
.image-container:-webkit-full-screen .img-edit-btn { display: none !important; }
189194

195+
/* Download button (Viewer) - Added to the right of edit button */
196+
.img-download-btn { position: absolute; right: 8px; bottom: 8px; background: rgba(37,99,235,0.92); color: #fff; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; }
197+
.image-container:hover .img-download-btn { opacity: 1; transform: translateY(0); }
198+
.img-download-btn:hover { background: #1d4ed8; }
199+
.image-container:fullscreen .img-download-btn { display: none !important; }
200+
.image-container:-webkit-full-screen .img-download-btn { display: none !important; }
201+
190202
/* Add face button (Viewer) */
191203
.img-add-btn { position: absolute; right: 92px; bottom: 8px; background: rgba(37,99,235,0.92); color: #fff; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; }
192204
.image-container:hover .img-add-btn { opacity: 1; transform: translateY(0); }
@@ -195,7 +207,7 @@ section#tab-mosaic.tab.active { display: flex; }
195207
.image-container:-webkit-full-screen .img-add-btn { display: none !important; }
196208

197209
/* Confirm-all inferred faces button (Viewer) */
198-
.img-confirm-btn { position: absolute; right: 176px; bottom: 8px; background: rgba(245,158,11,0.95); color: #111827; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; display: none; }
210+
.img-confirm-btn { position: absolute; right: 200px; bottom: 8px; background: rgba(245,158,11,0.95); color: #111827; border: none; border-radius: 16px; padding: 6px 10px; font-size: 12px; cursor: pointer; opacity: 0; transform: translateY(4px); transition: opacity 160ms ease, transform 160ms ease; box-shadow: 0 2px 6px rgba(0,0,0,0.2); z-index: 5; display: none; }
199211
.image-container:hover .img-confirm-btn { opacity: 1; transform: translateY(0); }
200212
.img-confirm-btn:hover { background: rgba(245,158,11,1); }
201213
/* Hide confirm-all button in fullscreen mode (same as edit) */

frontend-user-interface/assets/js/app.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2696,6 +2696,41 @@ function createMosaicTile(media) {
26962696
// Do not set src yet; we will assign it once preload completes
26972697
tile.appendChild(hiImg);
26982698

2699+
// Inject Download button
2700+
const dBtn = document.createElement('button');
2701+
dBtn.className = 'mosaic-download-btn';
2702+
dBtn.type = 'button';
2703+
dBtn.title = 'Download original image';
2704+
dBtn.textContent = '⬇';
2705+
dBtn.addEventListener('click', (e) => {
2706+
e.stopPropagation();
2707+
const url = api.mediaOriginalUrl(media.accessKey);
2708+
const dateVal = media.shootDateTime || (media.original ? media.original.cameraShootDateTime : null);
2709+
let ext = 'jpg';
2710+
if (media.original && media.original.kind === 'Video') { ext = 'mp4'; }
2711+
let filename = `selected_unknown.${ext}`;
2712+
if (dateVal) {
2713+
const d = new Date(dateVal);
2714+
if (!isNaN(d.getTime())) {
2715+
const pad = (n) => n.toString().padStart(2, '0');
2716+
const yyyy = d.getFullYear();
2717+
const MM = pad(d.getMonth() + 1);
2718+
const dd = pad(d.getDate());
2719+
const HH = pad(d.getHours());
2720+
const mm = pad(d.getMinutes());
2721+
const ss = pad(d.getSeconds());
2722+
filename = `selected_${yyyy}${MM}${dd}_${HH}${mm}${ss}.${ext}`;
2723+
}
2724+
}
2725+
const a = document.createElement('a');
2726+
a.href = url;
2727+
a.download = filename;
2728+
document.body.appendChild(a);
2729+
a.click();
2730+
document.body.removeChild(a);
2731+
});
2732+
tile.appendChild(dBtn);
2733+
26992734
async function showNormalized() {
27002735
try {
27012736
if (tile.__normalizedLoaded) {
@@ -5223,10 +5258,51 @@ function init() {
52235258
btn.className = 'img-edit-btn';
52245259
btn.type = 'button';
52255260
btn.title = 'Edit media';
5226-
btn.textContent = '✎ Edit';
5261+
btn.textContent = '✎';
52275262
btn.addEventListener('click', (e) => { e.stopPropagation(); if (currentMedia) openMediaEditModal(currentMedia); else alert('No media loaded'); });
52285263
cont.appendChild(btn);
52295264
}
5265+
// Inject Download button (bottom-right, right of edit button)
5266+
if (cont && !document.getElementById('img-download-btn')) {
5267+
const dBtn = document.createElement('button');
5268+
dBtn.id = 'img-download-btn';
5269+
dBtn.className = 'img-download-btn';
5270+
dBtn.type = 'button';
5271+
dBtn.title = 'Download original image';
5272+
dBtn.textContent = '⬇';
5273+
dBtn.addEventListener('click', (e) => {
5274+
e.stopPropagation();
5275+
if (currentMedia) {
5276+
const url = api.mediaOriginalUrl(currentMedia.accessKey);
5277+
const dateVal = currentMedia.shootDateTime || (currentMedia.original ? currentMedia.original.cameraShootDateTime : null);
5278+
let ext = 'jpg';
5279+
if (currentMedia.original && currentMedia.original.kind === 'Video') { ext = 'mp4'; }
5280+
let filename = `selected_unknown.${ext}`;
5281+
if (dateVal) {
5282+
const d = new Date(dateVal);
5283+
if (!isNaN(d.getTime())) {
5284+
const pad = (n) => n.toString().padStart(2, '0');
5285+
const yyyy = d.getFullYear();
5286+
const MM = pad(d.getMonth() + 1);
5287+
const dd = pad(d.getDate());
5288+
const HH = pad(d.getHours());
5289+
const mm = pad(d.getMinutes());
5290+
const ss = pad(d.getSeconds());
5291+
filename = `selected_${yyyy}${MM}${dd}_${HH}${mm}${ss}.${ext}`;
5292+
}
5293+
}
5294+
const a = document.createElement('a');
5295+
a.href = url;
5296+
a.download = filename;
5297+
document.body.appendChild(a);
5298+
a.click();
5299+
document.body.removeChild(a);
5300+
} else {
5301+
alert('No media loaded');
5302+
}
5303+
});
5304+
cont.appendChild(dBtn);
5305+
}
52305306
// Inject Add Face button near the edit button
52315307
if (cont && !document.getElementById('img-add-btn')) {
52325308
const addBtn = document.createElement('button');

0 commit comments

Comments
 (0)