From 884ffe7e02b8546245a5222af5067b36d2d55db5 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 10 Nov 2017 19:26:39 +0100 Subject: [PATCH] Add UX to note people in pictures. --- admin.js | 91 ++++++++++++++++++++++++--------------------- src/server/admin.rs | 55 ++++++++++++++++++++++++++- src/server/mod.rs | 30 ++++++++++++--- 3 files changed, 128 insertions(+), 48 deletions(-) diff --git a/admin.js b/admin.js index 8451d19..9730b8f 100644 --- a/admin.js +++ b/admin.js @@ -31,11 +31,12 @@ function rpadmin() { var list; - function tag_form(event) { + function tag_form(event, category) { event.target.disabled = true; var imgid = details.dataset.imgid; var f = document.createElement("form"); - f.action = "/adm/tag"; + f.className = category; + f.action = "/adm/" + category; f.method = "post"; var i = document.createElement("input"); i.type="hidden"; @@ -46,8 +47,43 @@ function rpadmin() { i.type = "text"; i.autocomplete="off"; i.tabindex="1"; - i.name = "tag"; - i.addEventListener('keyup', do_complete); + i.name = category; + i.addEventListener('keyup', e => { + let c = e.code; + if (c == 'ArrowUp' || c == 'ArrowDown' || c == 'Escape' || c == 'Enter') { + return true; + } + let i = e.target, v = i.value; + if (v.length > 0) { + let r = new XMLHttpRequest(); + r.onload = function() { + let t = JSON.parse(this.responseText); + list.innerHTML = ''; + t.map(x => { + let a = document.createElement('a'); + a.innerHTML = x; + a.tabIndex = 2; + a.href = "#"; + a.onclick = function(e) { + i.value = x; + list.innerHTML = ''; + i.focus(); + e.preventDefault(); + e.stopPropagation(); + return true; + } + list.appendChild(a) + }) + }; + r.open('GET', document.location.origin + '/ac/' + category + '?q=' + encodeURIComponent(v)); + r.send(null); + } else { + list.innerHTML = ''; + } + e.preventDefault(); + e.stopPropagation(); + return false; + }); f.appendChild(i); list = document.createElement("div"); list.className = "completions"; @@ -76,43 +112,6 @@ function rpadmin() { i.focus(); } - function do_complete(e) { - let c = e.code; - if (c == 'ArrowUp' || c == 'ArrowDown' || c == 'Escape' || c == 'Enter') { - return true; - } - let i = e.target, v = i.value; - if (v.length > 0) { - let r = new XMLHttpRequest(); - r.onload = function() { - let t = JSON.parse(this.responseText); - list.innerHTML = ''; - t.map(x => { - let a = document.createElement('a'); - a.innerHTML = x; - a.tabIndex = 2; - a.href = "#"; - a.onclick = function(e) { - i.value = x; - list.innerHTML = ''; - i.focus(); - e.preventDefault(); - e.stopPropagation(); - return true; - } - list.appendChild(a) - }) - }; - r.open('GET', document.location.origin + '/ac?q=' + encodeURIComponent(v)); - r.send(null); - } else { - list.innerHTML = ''; - } - e.preventDefault(); - e.stopPropagation(); - return false; - } - var meta = details.querySelector('.meta'); if (meta) { p = document.createElement("p"); @@ -132,11 +131,19 @@ function rpadmin() { p.appendChild(document.createTextNode(" ")); r = document.createElement("button"); - r.onclick = tag_form; + r.onclick = e => tag_form(e, 'tag'); r.innerHTML = "🏷"; r.title = "Tag photo"; r.accessKey = "T"; p.appendChild(r); + + p.appendChild(document.createTextNode(" ")); + r = document.createElement("button"); + r.onclick = e => tag_form(e, 'person'); + r.innerHTML = "\u263a"; + r.title = "Person in picture"; + r.accessKey = "P"; + p.appendChild(r); meta.appendChild(p); } } diff --git a/src/server/admin.rs b/src/server/admin.rs index a632036..0a8591a 100644 --- a/src/server/admin.rs +++ b/src/server/admin.rs @@ -51,7 +51,7 @@ fn rotate_params(req: &mut Request) -> QResult<(Option, Option)> { )) } -pub fn tag<'mw>( +pub fn set_tag<'mw>( req: &mut Request, res: Response<'mw>, ) -> MiddlewareResult<'mw> { @@ -101,6 +101,59 @@ fn tag_params(req: &mut Request) -> QResult<(Option, Option)> { )) } +pub fn set_person<'mw>( + req: &mut Request, + res: Response<'mw>, +) -> MiddlewareResult<'mw> { + if !req.authorized_user().is_some() { + return res.error(StatusCode::Unauthorized, "permission denied"); + } + if let (Some(image), Some(name)) = try_with!(res, person_params(req)) { + let c: &PgConnection = &req.db_conn(); + use diesel; + use models::{NewPerson, NewPhotoPerson, Person, PhotoPerson}; + let person = { + use schema::people::dsl::*; + people + .filter(person_name.ilike(&name)) + .first::(c) + .or_else(|_| { + diesel::insert(&NewPerson { + person_name: &name, + slug: &slugify(&name), + }).into(people) + .get_result::(c) + }) + .expect("Find or create tag") + }; + use schema::photo_people::dsl::*; + let q = photo_people + .filter(photo_id.eq(image)) + .filter(person_id.eq(person.id)); + if q.first::(c).is_ok() { + info!("Photo #{} already has {:?}", image, person); + } else { + info!("Add {:?} on photo #{}!", person, image); + diesel::insert( + &NewPhotoPerson { photo_id: image, person_id: person.id }, + ).into(photo_people) + .execute(c) + .expect("Name person in photo"); + } + return res.redirect(format!("/img/{}", image)); + } + info!("Missing image and/or angle to rotate or image not found"); + res.not_found("") +} + +fn person_params(req: &mut Request) -> QResult<(Option, Option)> { + let data = req.form_body()?; + Ok(( + data.get("image").and_then(|s| s.parse().ok()), + data.get("person").map(String::from), + )) +} + pub fn slugify(val: &str) -> String { val.chars() .map(|c| match c { diff --git a/src/server/mod.rs b/src/server/mod.rs index 3c8ac0b..4a47908 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -33,6 +33,7 @@ use photosdirmiddleware::{PhotosDirMiddleware, PhotosDirRequestExtensions}; use memcachemiddleware::*; use pidfiles::handle_pid_file; +use rustc_serialize::json::ToJson; use templates; use self::nickelext::{FromSlug, MyResponse, far_expires}; @@ -74,10 +75,12 @@ pub fn run(args: &ArgMatches) -> Result<(), Error> { wrap3!(server.post "/login", do_login); wrap3!(server.get "/logout", logout); wrap3!(server.get "/", all_years); - use self::admin::{rotate, tag}; - wrap3!(server.get "/ac", auto_complete); + use self::admin::{rotate, set_person, set_tag}; + wrap3!(server.get "/ac/tag", auto_complete_tag); + wrap3!(server.get "/ac/person", auto_complete_person); wrap3!(server.post "/adm/rotate", rotate); - wrap3!(server.post "/adm/tag", tag); + wrap3!(server.post "/adm/tag", set_tag); + wrap3!(server.post "/adm/person", set_person); wrap3!(server.get "/img/{}[-]{}\\.jpg", show_image: id, size); wrap3!(server.get "/img/{}", photo_details: id); wrap3!(server.get "/tag/", tag_all); @@ -609,12 +612,11 @@ impl Link { } } -fn auto_complete<'mw>( +fn auto_complete_tag<'mw>( req: &mut Request, res: Response<'mw>, ) -> MiddlewareResult<'mw> { if let Some(q) = req.query().get("q").map(String::from) { - use rustc_serialize::json::ToJson; use schema::tags::dsl::{tag_name, tags}; let c: &PgConnection = &req.db_conn(); let q = tags.select(tag_name) @@ -626,3 +628,21 @@ fn auto_complete<'mw>( res.not_found("No such tag") } } + +fn auto_complete_person<'mw>( + req: &mut Request, + res: Response<'mw>, +) -> MiddlewareResult<'mw> { + if let Some(q) = req.query().get("q").map(String::from) { + use schema::people::dsl::{people, person_name}; + let c: &PgConnection = &req.db_conn(); + let q = people + .select(person_name) + .filter(person_name.ilike(q + "%")) + .order(person_name) + .limit(15); + res.send(q.load::(c).unwrap().to_json()) + } else { + res.not_found("No such tag") + } +}