From 556a4ffcc27ada21b9e00e1e4ef91ef94367ea57 Mon Sep 17 00:00:00 2001 From: Mitch Ross Date: Sat, 7 Feb 2026 15:52:52 -0500 Subject: [PATCH] tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. utils/weather_sat.py — Added delete_all_images() method that globs for *.png, *.jpg, *.jpeg in the output dir, unlinks each, clears _images list, and returns the count. 2. routes/weather_sat.py — Added DELETE /weather-sat/images route that calls decoder.delete_all_images() and returns {'status': 'ok', 'deleted': count}. 3. static/js/modes/weather-satellite.js: - Added currentModalFilename state variable - renderGallery() now sorts images by timestamp descending, groups by date using toLocaleDateString(), renders date headers spanning the grid, and adds a delete overlay button on each card - showImage() accepts a filename param, stores it in currentModalFilename, and creates a modal toolbar with a delete button - Added deleteImage(filename) — confirm dialog → DELETE /weather-sat/images/{filename} → filter from array → re-render + close modal - Added deleteAllImages() — confirm dialog → DELETE /weather-sat/images → clear array → re-render - Exposed deleteImage, deleteAllImages, and _getModalFilename in public API 4. static/css/modes/weather-satellite.css: - Added position: relative to .wxsat-image-card - .wxsat-image-actions — absolute top-right overlay, hidden by default, appears on card hover - .wxsat-image-actions button — dark background, turns red on hover - .wxsat-date-header — full-grid-width date separator with dimmed uppercase text - .wxsat-modal-toolbar — absolute top-left in modal for the delete button - .wxsat-modal-btn.delete — turns red on hover - .wxsat-gallery-clear-btn — subtle icon button, pushed right via margin-left: auto, turns red on hover - Updated .wxsat-gallery-header from justify-content: space-between to gap: 8px for proper 3-child layout 5. templates/index.html — Added clear-all trash button with SVG icon in the gallery header, wired to WeatherSat.deleteAllImages(). --- routes/weather_sat.py | 12 +++ static/css/modes/weather-satellite.css | 107 +++++++++++++++++++++- static/js/modes/weather-satellite.js | 122 ++++++++++++++++++++++--- templates/index.html | 5 + utils/weather_sat.py | 13 +++ 5 files changed, 246 insertions(+), 13 deletions(-) diff --git a/routes/weather_sat.py b/routes/weather_sat.py index 7170155..649b7ac 100644 --- a/routes/weather_sat.py +++ b/routes/weather_sat.py @@ -301,6 +301,18 @@ def delete_image(filename: str): return jsonify({'status': 'error', 'message': 'Image not found'}), 404 +@weather_sat_bp.route('/images', methods=['DELETE']) +def delete_all_images(): + """Delete all decoded weather satellite images. + + Returns: + JSON with count of deleted images. + """ + decoder = get_weather_sat_decoder() + count = decoder.delete_all_images() + return jsonify({'status': 'ok', 'deleted': count}) + + @weather_sat_bp.route('/stream') def stream_progress(): """SSE stream of capture/decode progress. diff --git a/static/css/modes/weather-satellite.css b/static/css/modes/weather-satellite.css index d2589b5..ea7e961 100644 --- a/static/css/modes/weather-satellite.css +++ b/static/css/modes/weather-satellite.css @@ -530,7 +530,7 @@ .wxsat-gallery-header { display: flex; align-items: center; - justify-content: space-between; + gap: 8px; padding: 10px 14px; background: var(--bg-tertiary, #1a1f2e); border-bottom: 1px solid var(--border-color, #2a3040); @@ -561,6 +561,7 @@ } .wxsat-image-card { + position: relative; background: var(--bg-primary, #0d1117); border: 1px solid var(--border-color, #2a3040); border-radius: 6px; @@ -575,6 +576,43 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } +.wxsat-image-clickable { + display: block; +} + +.wxsat-image-actions { + position: absolute; + top: 6px; + right: 6px; + opacity: 0; + transition: opacity 0.2s; + z-index: 2; +} + +.wxsat-image-card:hover .wxsat-image-actions { + opacity: 1; +} + +.wxsat-image-actions button { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 4px; + background: rgba(0, 0, 0, 0.7); + color: var(--text-secondary, #999); + cursor: pointer; + transition: all 0.2s; +} + +.wxsat-image-actions button:hover { + background: rgba(255, 68, 68, 0.9); + color: #fff; +} + .wxsat-image-preview { width: 100%; aspect-ratio: 4/3; @@ -607,6 +645,23 @@ margin-top: 2px; } +/* Date group headers */ +.wxsat-date-header { + grid-column: 1 / -1; + font-size: 11px; + font-family: 'JetBrains Mono', monospace; + color: var(--text-dim, #666); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 8px 0 4px; + border-bottom: 1px solid var(--border-color, #2a3040); + margin-bottom: 4px; +} + +.wxsat-date-header:first-child { + padding-top: 0; +} + /* Empty state */ .wxsat-gallery-empty { display: flex; @@ -734,6 +789,56 @@ text-align: center; } +.wxsat-modal-toolbar { + position: absolute; + top: 16px; + left: 24px; + z-index: 10001; + display: flex; + gap: 8px; +} + +.wxsat-modal-btn { + display: flex; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + background: rgba(0, 0, 0, 0.6); + color: var(--text-secondary, #999); + cursor: pointer; + transition: all 0.2s; +} + +.wxsat-modal-btn.delete:hover { + background: rgba(255, 68, 68, 0.9); + border-color: #ff4444; + color: #fff; +} + +/* Gallery clear-all button */ +.wxsat-gallery-clear-btn { + display: flex; + align-items: center; + justify-content: center; + margin-left: auto; + padding: 4px; + border: none; + border-radius: 4px; + background: transparent; + color: var(--text-dim, #666); + cursor: pointer; + transition: all 0.2s; +} + +.wxsat-gallery-clear-btn:hover { + color: #ff4444; + background: rgba(255, 68, 68, 0.1); +} + /* ===== Responsive ===== */ @media (max-width: 1100px) { .wxsat-content { diff --git a/static/js/modes/weather-satellite.js b/static/js/modes/weather-satellite.js index a4a6327..50e79ef 100644 --- a/static/js/modes/weather-satellite.js +++ b/static/js/modes/weather-satellite.js @@ -21,6 +21,7 @@ const WeatherSat = (function() { let consoleCollapsed = false; let currentPhase = 'idle'; let consoleAutoHideTimer = null; + let currentModalFilename = null; /** * Initialize the Weather Satellite mode @@ -1005,7 +1006,7 @@ const WeatherSat = (function() { } /** - * Render image gallery + * Render image gallery grouped by date */ function renderGallery() { const gallery = document.getElementById('wxsatGallery'); @@ -1026,28 +1027,69 @@ const WeatherSat = (function() { return; } - gallery.innerHTML = images.map(img => ` -
- ${escapeHtml(img.satellite)} ${escapeHtml(img.product)} -
-
${escapeHtml(img.satellite)}
-
${escapeHtml(img.product || img.mode)}
-
${formatTimestamp(img.timestamp)}
-
-
- `).join(''); + // Sort by timestamp descending + const sorted = [...images].sort((a, b) => { + return new Date(b.timestamp || 0) - new Date(a.timestamp || 0); + }); + + // Group by date + const groups = {}; + sorted.forEach(img => { + const dateKey = img.timestamp + ? new Date(img.timestamp).toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }) + : 'Unknown Date'; + if (!groups[dateKey]) groups[dateKey] = []; + groups[dateKey].push(img); + }); + + let html = ''; + for (const [date, imgs] of Object.entries(groups)) { + html += `
${escapeHtml(date)}
`; + html += imgs.map(img => { + const fn = escapeHtml(img.filename || img.url.split('/').pop()); + return ` +
+
+ ${escapeHtml(img.satellite)} ${escapeHtml(img.product)} +
+
${escapeHtml(img.satellite)}
+
${escapeHtml(img.product || img.mode)}
+
${formatTimestamp(img.timestamp)}
+
+
+
+ +
+
`; + }).join(''); + } + + gallery.innerHTML = html; } /** * Show full-size image */ - function showImage(url, satellite, product) { + function showImage(url, satellite, product, filename) { + currentModalFilename = filename || null; + let modal = document.getElementById('wxsatImageModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'wxsatImageModal'; modal.className = 'wxsat-image-modal'; modal.innerHTML = ` +
+ +
Weather Satellite Image
@@ -1074,6 +1116,59 @@ const WeatherSat = (function() { if (modal) modal.classList.remove('show'); } + /** + * Delete a single image + */ + async function deleteImage(filename) { + if (!filename) return; + if (!confirm(`Delete this image?`)) return; + + try { + const response = await fetch(`/weather-sat/images/${encodeURIComponent(filename)}`, { method: 'DELETE' }); + const data = await response.json(); + + if (data.status === 'deleted') { + images = images.filter(img => { + const imgFn = img.filename || img.url.split('/').pop(); + return imgFn !== filename; + }); + updateImageCount(images.length); + renderGallery(); + closeImage(); + } else { + showNotification('Weather Sat', data.message || 'Failed to delete image'); + } + } catch (err) { + console.error('Failed to delete image:', err); + showNotification('Weather Sat', 'Failed to delete image'); + } + } + + /** + * Delete all images + */ + async function deleteAllImages() { + if (images.length === 0) return; + if (!confirm(`Delete all ${images.length} decoded images?`)) return; + + try { + const response = await fetch('/weather-sat/images', { method: 'DELETE' }); + const data = await response.json(); + + if (data.status === 'ok') { + images = []; + updateImageCount(0); + renderGallery(); + showNotification('Weather Sat', `Deleted ${data.deleted} images`); + } else { + showNotification('Weather Sat', 'Failed to delete images'); + } + } catch (err) { + console.error('Failed to delete all images:', err); + showNotification('Weather Sat', 'Failed to delete images'); + } + } + /** * Format timestamp */ @@ -1218,10 +1313,13 @@ const WeatherSat = (function() { loadPasses, showImage, closeImage, + deleteImage, + deleteAllImages, useGPS, toggleScheduler, invalidateMap, toggleConsole, + _getModalFilename: () => currentModalFilename, }; })(); diff --git a/templates/index.html b/templates/index.html index 49f306e..b5773a9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2212,6 +2212,11 @@