diff options
| author | Ken D'Ambrosio <ken@jots.org> | 2026-06-08 21:09:47 +0000 |
|---|---|---|
| committer | Ken D'Ambrosio <ken@jots.org> | 2026-06-08 21:09:47 +0000 |
| commit | 7f6325fe213ed46ff5479ffd34b0e212426d48f2 (patch) | |
| tree | 46430a22dc791ce5c8018eeb7bce2c857fd17cd6 /app.rb | |
| parent | 00f63c03b7c5de68aea6a2305886bc1953a722b6 (diff) | |
Add people cluster detail page with face move/merge and hover preview
Each cluster in /admin/people now links to a detail page showing all
faces in a grid. From there you can rename the cluster, move individual
faces to another named person (or spin off a new cluster), or merge the
entire cluster into another. Hovering any face crop shows the original
full photo for context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'app.rb')
| -rw-r--r-- | app.rb | 77 |
1 files changed, 77 insertions, 0 deletions
@@ -895,6 +895,83 @@ post '/admin/people/:uuid' do redirect '/admin/people' end +get '/admin/people/:uuid' do + require_admin! + data = load_people_data + people = data['people'] || {} + halt 404 unless people.key?(params[:uuid]) + + pd = people[params[:uuid]] + @title = pd['name'] || 'Unnamed cluster' + @uuid = params[:uuid] + @name = pd['name'] + @members = pd['members'] || [] + @count = @members.length + + @named_others = people + .reject { |k, _| k == params[:uuid] } + .select { |_, v| v['name'] } + .map { |k, v| { uuid: k, name: v['name'] } } + .sort_by { |x| x[:name].downcase } + + @all_others = people + .reject { |k, _| k == params[:uuid] } + .map { |k, v| { uuid: k, name: v['name'] || "(unnamed ยท #{(v['members'] || []).length})" } } + .sort_by { |x| x[:name].downcase } + + erb :'admin/person_detail' +end + +post '/admin/people/:uuid/move' do + require_admin! + src = params[:uuid] + rel = params['rel'] + box = JSON.parse(params['box']).map(&:to_i) + to = params['to'].to_s.strip + + return redirect "/admin/people/#{src}" if to.empty? + + data = load_people_data + people = data['people'] || {} + halt 404 unless people.key?(src) + + member = people[src]['members'].find { |m| m['rel'] == rel && m['box'].map(&:to_i) == box } + halt 404 unless member + people[src]['members'].delete(member) + + if to == 'new' + new_uid = SecureRandom.uuid + people[new_uid] = { 'name' => nil, 'slug' => nil, 'members' => [member] } + else + halt 404 unless people.key?(to) + people[to]['members'] << member + end + + data['people'] = people + atomic_write(PEOPLE_PATH, JSON.pretty_generate(data)) + + people[src] ? redirect("/admin/people/#{src}") : redirect('/admin/people') +end + +post '/admin/people/:uuid/merge' do + require_admin! + src = params[:uuid] + into = params['into'].to_s.strip + + return redirect '/admin/people' if into.empty? + + data = load_people_data + people = data['people'] || {} + halt 404 unless people.key?(src) && people.key?(into) + + people[into]['members'].concat(people[src]['members']) + people.delete(src) + data['people'] = people + + atomic_write(PEOPLE_PATH, JSON.pretty_generate(data)) + redirect "/admin/people/#{into}" +end + get '/people' do data = load_people_data people = (data['people'] || {}).select { |_, p| p['name'] && p['slug'] } |
