From 6f975533f075cc761184b5a7392522264eca6fc1 Mon Sep 17 00:00:00 2001 From: Ken D'Ambrosio Date: Thu, 18 Jun 2026 23:00:17 +0000 Subject: Add per-photo rotate-left/right widget to admin edit page Many older photos (especially pre-EXIF-orientation cameras) are off by a multiple of 90 degrees with no metadata to auto-correct. Each image row in the admin file table now has rotate buttons that physically rotate the file (auto-orienting first to normalize any existing EXIF tag), swap the stored width/height, and clear the cached thumbnail so it regenerates from the corrected image. Co-Authored-By: Claude Sonnet 4.6 --- app.rb | 37 +++++++++++++++++++++++++++++++++++++ public/css/style.css | 3 +++ views/admin/album.erb | 11 +++++++++++ 3 files changed, 51 insertions(+) diff --git a/app.rb b/app.rb index c5f533b..a977455 100644 --- a/app.rb +++ b/app.rb @@ -702,6 +702,43 @@ def save_edits(rel, dir) atomic_write(File.join(dir, 'album.json'), JSON.pretty_generate(data)) end +post '/admin/rotate/' do + require_admin! + rotate_file('', MEDIA_ROOT) +end + +post '/admin/rotate/*' do + require_admin! + rel = params[:splat].first.chomp('/') + rotate_file(rel, resolve_dir(rel)) +end + +def rotate_file(rel, dir) + name = File.basename(params[:file].to_s) + full = File.join(dir, name) + halt 404 unless File.file?(full) + halt 400 unless IMAGE_EXTS.include?(File.extname(name).downcase.delete_prefix('.')) + + degrees = params[:dir] == 'left' ? -90 : 90 + img = MiniMagick::Image.open(full) + img.combine_options do |c| + c.auto_orient + c.rotate degrees + end + img.write(full) + + data = load_album(dir) + data['files'] ||= {} + meta = (data['files'][name] ||= {}) + meta['width'], meta['height'] = meta['height'], meta['width'] if meta['width'] && meta['height'] + atomic_write(File.join(dir, 'album.json'), JSON.pretty_generate(data)) + + thumb = thumb_cache_path(rel.empty? ? name : "#{rel}/#{name}") + File.unlink(thumb) if File.exist?(thumb) + + redirect "/admin/edit/#{rel}" +end + # ── Background update ───────────────────────────────────────────────────────── post '/admin/update' do diff --git a/public/css/style.css b/public/css/style.css index 03d869f..edc9d90 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -355,6 +355,9 @@ legend { padding: 0 8px; color: var(--text-dim); font-size: .85rem; } .files-table tr:hover td { background: var(--bg2); } .filename { color: var(--text-dim); font-size: .8rem; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .visible-cell { text-align: center; } +.rotate-cell { text-align: center; white-space: nowrap; } +.rotate-form { display: inline-flex; gap: 4px; margin: 0; } +.rotate-form .btn-sm { padding: 2px 8px; font-size: 1rem; line-height: 1; } .files-table input[type=text] { width: 100%; background: var(--bg3); border: 1px solid transparent; diff --git a/views/admin/album.erb b/views/admin/album.erb index 8c9ebe1..d1b8cb8 100644 --- a/views/admin/album.erb +++ b/views/admin/album.erb @@ -63,6 +63,7 @@ Caption Visible Cover + Rotate Delete @@ -70,6 +71,7 @@ <% @files.each do |name| %> <% meta = (@data['files'] || {})[name] || {} %> <% file_rel = @rel.empty? ? name : "#{@rel}/#{name}" %> + <% is_image = IMAGE_EXTS.include?(File.extname(name).downcase.delete_prefix('.')) %> > @@ -84,6 +86,15 @@ > + + <% if is_image %> +
+ + + +
+ <% end %> + -- cgit v1.2.3