diff --git a/TODO-4.2.1.md b/TODO-4.2.1.md index d2fb4b5..36b0dbd 100644 --- a/TODO-4.2.1.md +++ b/TODO-4.2.1.md @@ -1,7 +1,9 @@ # Stegasoo 4.2.1 Plan ## Bugs -- [ ] Fix EXIF viewer panel not loading metadata in Web UI +- [x] Fix EXIF viewer panel not loading metadata in Web UI + - Redesigned with card-based grid layout and categories + - Compact styling for better space usage - [x] DCT mode: portrait photos export rotated 90° (EXIF orientation not handled) - Added `_apply_exif_orientation()` to apply EXIF rotation before embedding - [x] DCT mode: add rotation fallback (try as-is, rotate 90°, retry on failure) @@ -12,7 +14,10 @@ - Dynamic UI shows "DCT Safe" for JPEGs, warning for other formats ## Tools Audit -- [ ] Web UI tools - full shakedown and fixes +- [x] Web UI tools - full shakedown and fixes + - Compress, Rotate, Strip, EXIF viewer all working + - Rotate uses jpegtran for lossless JPEG rotation + - Compact UI styling - [ ] CLI tools - full shakedown and fixes ## AUR Packages diff --git a/aur/PKGBUILD b/aur/PKGBUILD index f301a41..9a4bc29 100644 --- a/aur/PKGBUILD +++ b/aur/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Aaron D. Lee pkgname=stegasoo-git -pkgver=4.2.0.r0.g530e5de +pkgver=4.2.0.r0.g2ebc42f pkgrel=1 pkgdesc="Secure steganography with hybrid photo + passphrase + PIN authentication" arch=('x86_64') diff --git a/frontends/web/static/style.css b/frontends/web/static/style.css index b81c2e4..ffca7ae 100644 --- a/frontends/web/static/style.css +++ b/frontends/web/static/style.css @@ -2247,7 +2247,7 @@ footer { display: none; width: 100%; flex: 1; - padding: 1.25rem; + padding: 0.5rem; } .tool-section.active { @@ -2255,33 +2255,92 @@ footer { flex-direction: column; } -/* EXIF Table in Results */ -.tool-exif-table { - font-size: 0.8rem; - max-height: 250px; +/* EXIF Grid Layout */ +.exif-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 0.3rem; + max-height: 280px; overflow-y: auto; + padding: 0.15rem; } -.tool-exif-table table { - width: 100%; +.exif-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 4px; + padding: 0.25rem 0.4rem; } -.tool-exif-table th, -.tool-exif-table td { - padding: 0.35rem 0.5rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.05); +.exif-card:hover { + background: rgba(255, 255, 255, 0.06); } -.tool-exif-table th { +.exif-card-label { + font-size: 0.55rem; font-weight: 500; - color: rgba(255, 255, 255, 0.5); - text-align: left; - width: 40%; + color: rgba(255, 255, 255, 0.4); + text-transform: uppercase; + letter-spacing: 0.02em; + margin-bottom: 0.1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.tool-exif-table td { +.exif-card-value { + font-size: 0.7rem; font-family: 'SF Mono', 'Consolas', monospace; - word-break: break-all; + color: rgba(255, 255, 255, 0.85); + word-break: break-word; + line-height: 1.2; +} + +.exif-card-value.truncated { + max-height: 2.4em; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +/* Category headers */ +.exif-category { + grid-column: 1 / -1; + font-size: 0.6rem; + font-weight: 600; + color: var(--bs-primary); + text-transform: uppercase; + letter-spacing: 0.04em; + padding: 0.35rem 0 0.15rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + margin-top: 0.15rem; +} + +.exif-category:first-child { + margin-top: 0; + padding-top: 0; +} + +/* Compact tool headers and actions */ +.tool-results-header { + padding-bottom: 0.35rem; + margin-bottom: 0.35rem; +} + +.tool-results-header h6 { + font-size: 0.8rem; + margin-bottom: 0; +} + +.tool-results-header small { + font-size: 0.65rem; +} + +.tool-results-actions { + padding-top: 0.35rem; + margin-top: 0.35rem; } /* Loading State */ diff --git a/frontends/web/templates/tools.html b/frontends/web/templates/tools.html index f78f7c2..02781b2 100644 --- a/frontends/web/templates/tools.html +++ b/frontends/web/templates/tools.html @@ -283,10 +283,8 @@ Drop an image to view metadata
-
- - -
+
+
@@ -657,20 +655,70 @@ setupDropZone('exifZone', 'exifFile', async (file) => { const data = await res.json(); if (data.success) { - const tbody = document.getElementById('exifTable'); - const entries = Object.entries(data.exif).sort((a, b) => a[0].localeCompare(b[0])); + const grid = document.getElementById('exifGrid'); + const entries = Object.entries(data.exif); if (entries.length === 0) { - tbody.innerHTML = ''; + grid.innerHTML = ''; document.getElementById('exifNoData').classList.remove('d-none'); document.getElementById('exifNoData').innerHTML = 'No metadata found'; } else { document.getElementById('exifNoData').classList.add('d-none'); - tbody.innerHTML = entries.map(([key, value]) => { + + // Categorize EXIF fields + const categories = { + 'Camera': ['Make', 'Model', 'Software', 'LensMake', 'LensModel', 'BodySerialNumber'], + 'Image': ['ImageWidth', 'ImageLength', 'Orientation', 'ResolutionUnit', 'XResolution', 'YResolution', 'ColorSpace', 'ExifImageWidth', 'ExifImageHeight'], + 'Date/Time': ['DateTime', 'DateTimeOriginal', 'DateTimeDigitized', 'SubsecTime', 'SubsecTimeOriginal', 'SubsecTimeDigitized', 'OffsetTime', 'OffsetTimeOriginal'], + 'Exposure': ['ExposureTime', 'FNumber', 'ExposureProgram', 'ISOSpeedRatings', 'ExposureBiasValue', 'MaxApertureValue', 'MeteringMode', 'Flash', 'FocalLength', 'FocalLengthIn35mmFilm', 'WhiteBalance', 'ExposureMode', 'DigitalZoomRatio', 'SceneCaptureType', 'Contrast', 'Saturation', 'Sharpness'], + 'GPS': ['GPSInfo', 'GPSLatitude', 'GPSLatitudeRef', 'GPSLongitude', 'GPSLongitudeRef', 'GPSAltitude', 'GPSAltitudeRef', 'GPSTimeStamp', 'GPSDateStamp'], + }; + + const categorized = {}; + const other = []; + const allCategoryFields = new Set(Object.values(categories).flat()); + + entries.forEach(([key, value]) => { + let found = false; + for (const [cat, fields] of Object.entries(categories)) { + if (fields.includes(key)) { + if (!categorized[cat]) categorized[cat] = []; + categorized[cat].push([key, value]); + found = true; + break; + } + } + if (!found) other.push([key, value]); + }); + + // Render cards + let html = ''; + const renderCard = ([key, value]) => { let displayVal = typeof value === 'object' ? JSON.stringify(value) : String(value); - if (displayVal.length > 40) displayVal = displayVal.substring(0, 37) + '...'; - return `${key}${displayVal}`; - }).join(''); + const needsTruncate = displayVal.length > 60; + if (needsTruncate) displayVal = displayVal.substring(0, 57) + '...'; + const fullVal = typeof value === 'object' ? JSON.stringify(value) : String(value); + return `
+
${key}
+
${displayVal}
+
`; + }; + + // Render each category + for (const [cat, fields] of Object.entries(categories)) { + if (categorized[cat] && categorized[cat].length > 0) { + html += `
${cat}
`; + html += categorized[cat].map(renderCard).join(''); + } + } + + // Render other fields + if (other.length > 0) { + html += `
Other
`; + html += other.map(renderCard).join(''); + } + + grid.innerHTML = html; } document.getElementById('exifEmpty').classList.add('d-none');