summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app.rb37
-rw-r--r--public/css/style.css3
-rw-r--r--views/admin/album.erb11
3 files changed, 51 insertions, 0 deletions
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 @@
<th>Caption</th>
<th>Visible</th>
<th>Cover</th>
+ <th>Rotate</th>
<th>Delete</th>
</tr>
</thead>
@@ -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('.')) %>
<tr<%= ' class="original-file"' if meta['transcoded_to'] %>>
<td><img src="/thumb/<%= file_rel %>" width="60" height="60" loading="lazy" style="object-fit:cover"></td>
<td class="filename">
@@ -84,6 +86,15 @@
<td class="cover-cell">
<input type="radio" name="album_cover_file" value="<%= name %>" class="cover-radio"<%= ' checked' if @data['cover'] == name %>>
</td>
+ <td class="rotate-cell">
+ <% if is_image %>
+ <form method="post" action="/admin/rotate/<%= @rel %>" class="rotate-form">
+ <input type="hidden" name="file" value="<%= ERB::Util.html_escape(name) %>">
+ <button type="submit" name="dir" value="left" class="btn btn-sm" title="Rotate left">⟲</button>
+ <button type="submit" name="dir" value="right" class="btn btn-sm" title="Rotate right">⟳</button>
+ </form>
+ <% end %>
+ </td>
<td class="delete-cell">
<input type="checkbox" name="file_delete[<%= name %>]" value="1" class="delete-check">
</td>