diff options
author | Yorhel <git@yorhel.nl> | 2020-07-05 10:55:18 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2020-07-05 10:55:18 +0200 |
commit | a7909fb9e21bf1e31e85d8795a72c74707543c68 (patch) | |
tree | 6e1f2fb3f57e6a84a8cbb3ef1de0f48bc4dd4b91 | |
parent | 0a6254431d97a2f4de9429e738e58c4dca3bfaa5 (diff) |
VN::Edit: Add screenshot manager
-rw-r--r-- | data/style.css | 11 | ||||
-rw-r--r-- | elm/Lib/Image.elm | 23 | ||||
-rw-r--r-- | elm/VNEdit.elm | 126 | ||||
-rw-r--r-- | elm/VNEdit.js | 6 | ||||
-rw-r--r-- | lib/VNWeb/Chars/Edit.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/Chars/Page.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/Elm.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/Releases/Elm.pm | 2 | ||||
-rw-r--r-- | lib/VNWeb/User/Lists.pm | 12 | ||||
-rw-r--r-- | lib/VNWeb/VN/Edit.pm | 32 |
10 files changed, 196 insertions, 22 deletions
diff --git a/data/style.css b/data/style.css index d7de2cea..6b369278 100644 --- a/data/style.css +++ b/data/style.css @@ -563,6 +563,12 @@ div#vntags { margin: 0 30px 0 30px; border-top: 1px solid $bo #scr_table select { width: 400px; } div.scr_uploader { visibility: hidden; overflow: hidden; width: 1px; height: 1px; position: absolute; display: none; left: 0; top: 0; } +.vnedit_scr { width: 95%; margin: auto } +.vnedit_scr > tr:nth-child(odd) > td { background: $boxbg$ } +.vnedit_scr > tr > td { border-bottom: 1px solid $border$ } +.vnedit_scr > tr > td:nth-child(1) { padding: 10px; width: 136px } +.vnedit_scr > tr > td:nth-child(2) { width: 10px; padding-top: 20px } + /***** VN Release tab *****/ @@ -641,9 +647,10 @@ div.chardetails.charsep { margin-top: 30px } .imghover { margin: 0 auto; position: relative; display: block; text-align: center } .imghover input:checked ~ div.imghover--warning { display: none } .imghover input:not(:checked) ~ div.imghover--visible { display: none } -.imghover div.imghover--visible a { display: none; white-space: nowrap; font-size: 11px } +.imghover div.imghover--visible a { border-bottom: 0 } +.imghover div.imghover--visible .imghover--overlay { display: none; white-space: nowrap; font-size: 11px } .imghover:hover div.imghover--visible { position: absolute } -.imghover:hover div.imghover--visible a { display: block; position: absolute; right: 0; bottom: 0; padding: 5px 10px; background: $secbg$; border: 0 } +.imghover:hover div.imghover--visible .imghover--overlay { display: block; position: absolute; right: 0; bottom: 0; padding: 5px 10px; background: $secbg$; border: 0 } .imghover div.imghover--warning { border: 1px solid $border$; background: $secbg$; box-sizing: border-box; padding: 10px 5px } diff --git a/elm/Lib/Image.elm b/elm/Lib/Image.elm index 44ce8240..d5e6f229 100644 --- a/elm/Lib/Image.elm +++ b/elm/Lib/Image.elm @@ -113,10 +113,23 @@ viewImg image = (Error e, _) -> b [ class "standout" ] [ text <| Api.showResponse e ] (_, Nothing) -> text "No image." (_, Just i) -> - label [ class "imghover", style "width" (String.fromInt i.width++"px"), style "height" (String.fromInt i.height++"px") ] + let + maxWidth = toFloat <| if String.startsWith "sf" i.id then 136 else 10000 + maxHeight = toFloat <| if String.startsWith "sf" i.id then 102 else 10000 + sWidth = maxWidth / toFloat i.width + sHeight = maxHeight / toFloat i.height + scale = Basics.min 1 <| if sWidth < sHeight then sWidth else sHeight + imgWidth = round <| scale * toFloat i.width + imgHeight = round <| scale * toFloat i.height + in + -- TODO: Onclick iv.js support for screenshot thumbnails + label [ class "imghover", style "width" (String.fromInt imgWidth++"px"), style "height" (String.fromInt imgHeight++"px") ] [ div [ class "imghover--visible" ] - [ img [ src (imageUrl i.id) ] [] - , a [ href <| "/img/"++i.id ] <| + [ if String.startsWith "sf" i.id + then a [ href (imageUrl i.id), attribute "data-iv" <| String.fromInt i.width ++ "x" ++ String.fromInt i.height ++ ":scr" ] + [ img [ src <| imageUrl <| String.replace "sf" "st" i.id ] [] ] + else img [ src <| imageUrl i.id ] [] + , a [ class "imghover--overlay", href <| "/img/"++i.id ] <| case (i.sexual_avg, i.violence_avg) of (Just sex, Just vio) -> -- XXX: These thresholds are subject to change, maybe just show the numbers here? @@ -143,12 +156,12 @@ viewVote model = Api.Error e -> [ tr [] [ td [ colspan 2 ] [ b [ class "standout" ] [ text (Api.showResponse e) ] ] ] ] _ -> [] , tr [] - [ td [] + [ td [ style "white-space" "nowrap" ] [ label [] [ inputRadio "" (i.my_sexual == Just 0) (MySex 0), text " Safe" ], br [] [] , label [] [ inputRadio "" (i.my_sexual == Just 1) (MySex 1), text " Suggestive" ], br [] [] , label [] [ inputRadio "" (i.my_sexual == Just 2) (MySex 2), text " Explicit" ] ] - , td [] + , td [ style "white-space" "nowrap" ] [ label [] [ inputRadio "" (i.my_violence == Just 0) (MyVio 0), text " Tame" ], br [] [] , label [] [ inputRadio "" (i.my_violence == Just 1) (MyVio 1), text " Violent" ], br [] [] , label [] [ inputRadio "" (i.my_violence == Just 2) (MyVio 2), text " Brutal" ] diff --git a/elm/VNEdit.elm b/elm/VNEdit.elm index 6fa3aaaa..8266fc5b 100644 --- a/elm/VNEdit.elm +++ b/elm/VNEdit.elm @@ -1,4 +1,4 @@ -module VNEdit exposing (main) +port module VNEdit exposing (main) import Html exposing (..) import Html.Events exposing (..) @@ -14,6 +14,7 @@ import Lib.Util exposing (..) import Lib.Html exposing (..) import Lib.TextPreview as TP import Lib.Autocomplete as A +import Lib.RDate as RDate import Lib.Api as Api import Lib.Editsum as Editsum import Lib.Image as Img @@ -31,6 +32,8 @@ main = Browser.element } +port ivRefresh : Bool -> Cmd msg + type Tab = General | Image @@ -54,6 +57,11 @@ type alias Model = , anime : List GVE.RecvAnime , animeSearch : A.Model GApi.ApiAnimeResult , image : Img.Image + , screenshots : List (Int,Img.Image,Maybe Int) -- internal id, img, rel + , scrUplRel : Maybe Int + , scrUplNum : Maybe Int + , scrId : Int -- latest used internal id + , releases : List GVE.RecvReleases , id : Maybe Int } @@ -73,6 +81,11 @@ init d = , anime = d.anime , animeSearch = A.init "" , image = Img.info d.image_info + , screenshots = List.indexedMap (\n i -> (n, Img.info (Just i.info), i.rid)) d.screenshots + , scrUplRel = Nothing + , scrUplNum = Nothing + , scrId = 100 + , releases = d.releases , id = d.id } @@ -92,6 +105,7 @@ encode model = , l_renai = model.lRenai , anime = List.map (\a -> { aid = a.aid }) model.anime , image = model.image.id + , screenshots = List.map (\(_,i,r) -> { scr = Maybe.withDefault "" i.id, rid = r }) model.screenshots } animeConfig : A.Config Msg GApi.ApiAnimeResult @@ -115,6 +129,12 @@ type Msg | ImageSelect | ImageSelected File | ImageMsg Img.Msg + | ScrUplRel (Maybe Int) + | ScrUplSel + | ScrUpl File (List File) + | ScrMsg Int Img.Msg + | ScrRel Int (Maybe Int) + | ScrDel Int update : Msg -> Model -> (Model, Cmd Msg) @@ -145,6 +165,28 @@ update msg model = ImageSelected f -> let (nm, nc) = Img.upload Api.Cv f in ({ model | image = nm }, Cmd.map ImageMsg nc) ImageMsg m -> let (nm, nc) = Img.update m model.image in ({ model | image = nm }, Cmd.map ImageMsg nc) + ScrUplRel s -> ({ model | scrUplRel = s }, Cmd.none) + ScrUplSel -> (model, FSel.files ["image/png", "image/jpg"] ScrUpl) + ScrUpl f1 fl -> + if 1 + List.length fl > 10 - List.length model.screenshots + then ({ model | scrUplNum = Just (1 + List.length fl) }, Cmd.none) + else + let imgs = List.map (Img.upload Api.Sf) (f1::fl) + in ( { model + | scrId = model.scrId + 100 + , scrUplNum = Nothing + , screenshots = model.screenshots ++ List.indexedMap (\n (i,_) -> (model.scrId+n,i,model.scrUplRel)) imgs + } + , List.indexedMap (\n (_,c) -> Cmd.map (ScrMsg (model.scrId+n)) c) imgs |> Cmd.batch) + ScrMsg id m -> + let f (i,s,r) = + if i /= id then ((i,s,r), Cmd.none) + else let (nm,nc) = Img.update m s in ((i,nm,r), Cmd.map (ScrMsg id) nc) + lst = List.map f model.screenshots + in ({ model | screenshots = List.map Tuple.first lst }, Cmd.batch (ivRefresh True :: List.map Tuple.second lst)) + ScrRel n s -> ({ model | screenshots = List.map (\(i,img,r) -> if i == n then (i,img,s) else (i,img,r)) model.screenshots }, Cmd.none) + ScrDel n -> ({ model | screenshots = List.filter (\(i,_,_) -> i /= n) model.screenshots }, ivRefresh True) + Submit -> ({ model | state = Api.Loading }, GVE.send (encode model) Submitted) Submitted (GApi.Redirect s) -> (model, load s) Submitted r -> ({ model | state = Api.Error r }, Cmd.none) @@ -154,6 +196,7 @@ isValid : Model -> Bool isValid model = not ( (model.title /= "" && model.title == model.original) || not (Img.isValid model.image) + || List.any (\(_,i,r) -> r == Nothing || not (Img.isValid i)) model.screenshots ) @@ -219,6 +262,85 @@ view model = ] ] ] + screenshots = + let + showrel r = "[" ++ (RDate.format (RDate.expand r.released)) ++ " " ++ (String.join "," r.lang) ++ "] " ++ r.title ++ " (r" ++ String.fromInt r.id ++ ")" + rellist = List.map (\r -> (Just r.id, showrel r)) model.releases + scr n (id, i, rel) = tr [] <| + let imgdim = Maybe.map (\nfo -> (nfo.width, nfo.height)) i.img |> Maybe.withDefault (0,0) + relnfo = List.filter (\r -> Just r.id == rel) model.releases |> List.head + reldim = relnfo |> Maybe.andThen (\r -> if r.reso_x == 0 then Nothing else Just (r.reso_x, r.reso_y)) + dimstr (x,y) = String.fromInt x ++ "x" ++ String.fromInt y + in + [ td [] [ Img.viewImg i ] + , td [] [ Img.viewVote i |> Maybe.map (Html.map (ScrMsg id)) |> Maybe.withDefault (text "") ] + , td [] + [ b [] [ text <| "Screenshot #" ++ String.fromInt (n+1) ] + , text " (", a [ href "#", onClickD (ScrDel id) ] [ text "remove" ], text ")" + , br [] [] + , text <| "Image resolution: " ++ dimstr imgdim + , br [] [] + , text <| Maybe.withDefault "" <| Maybe.map (\dim -> "Release resolution: " ++ dimstr dim) reldim + , span [] <| + if reldim == Nothing then [ br [] [] ] + else if reldim == Just imgdim then [ text " ✔", br [] [] ] + else [ text " ❌" + , br [] [] + , b [ class "standout" ] [ text "WARNING: Resolutions do not match, please take screenshots with the correct resolution and make sure to crop them correctly!" ] + ] + , br [] [] + , inputSelect "" rel (ScrRel id) [style "width" "500px"] <| rellist ++ + case (relnfo, rel) of + (_, Nothing) -> [(Nothing, "[No release selected]")] + (Nothing, Just r) -> [(Just r, "[Deleted or unlinked release: r" ++ String.fromInt r ++ "]")] + _ -> [] + ] + ] + + add = + let free = 10 - List.length model.screenshots + in + if free <= 0 + then [ b [] [ text "Enough screenshots" ] + , br [] [] + , text "The limit of 10 screenshots per visual novel has been reached. If you want to add a new screenshot, please remove an existing one first." + ] + else + [ b [] [ text "Add screenshots" ] + , br [] [] + , text <| String.fromInt free ++ " more screenshot" ++ (if free == 1 then "" else "s") ++ " can be added." + , br [] [] + , inputSelect "" model.scrUplRel ScrUplRel [style "width" "500px"] ((Nothing, "-- select release --") :: rellist) + , br [] [] + , if model.scrUplRel == Nothing then text "" else span [] + [ inputButton "Select images" ScrUplSel [] + , case model.scrUplNum of + Just num -> text " Too many images selected." + Nothing -> text "" + , br [] [] + ] + , br [] [] + , b [] [ text "Important reminder" ] + , ul [] + [ li [] [ text "Screenshots must be in the native resolution of the game" ] + , li [] [ text "Screenshots must not include window borders and should not have copyright markings" ] + , li [] [ text "Don't only upload event CGs" ] + ] + , text "Read the ", a [ href "/d2#6" ] [ text "full guidelines" ], text " for more information." + ] + in + if model.id == Nothing + then text <| "Screenshots can be uploaded when this visual novel once it has a release entry associated with it. " + ++ "To do so, first create this entry without screenshots, then create the appropriate release entries, and finally come back to this form by editing the visual novel." + else if List.isEmpty model.releases + then p [] + [ text "This visual novel does not have any releases associated with it (yet). Please " + , a [ href <| "/v" ++ Maybe.withDefault "" (Maybe.map String.fromInt model.id) ++ "/add" ] [ text "add the appropriate release entries" ] + , text " first and then come back to this form to upload screenshots." + ] + else table [ class "vnedit_scr" ] + <| tfoot [] [ tr [] [ td [] [], td [ colspan 2 ] add ] ] :: List.indexedMap scr model.screenshots + in form_ Submit (model.state == Api.Loading) [ div [ class "maintabs left" ] @@ -237,7 +359,7 @@ view model = , div [ class "mainbox", classList [("hidden", model.tab /= Staff && model.tab /= All)] ] [ h1 [] [ text "Staff" ] ] , div [ class "mainbox", classList [("hidden", model.tab /= Cast && model.tab /= All)] ] [ h1 [] [ text "Cast" ] ] , div [ class "mainbox", classList [("hidden", model.tab /= Relations && model.tab /= All)] ] [ h1 [] [ text "Relations" ] ] - , div [ class "mainbox", classList [("hidden", model.tab /= Screenshots && model.tab /= All)] ] [ h1 [] [ text "Screenshots" ] ] + , div [ class "mainbox", classList [("hidden", model.tab /= Screenshots && model.tab /= All)] ] [ h1 [] [ text "Screenshots" ], screenshots ] , div [ class "mainbox" ] [ fieldset [ class "submit" ] [ Html.map Editsum (Editsum.view model.editsum) , submitButton "Submit" model.state (isValid model) diff --git a/elm/VNEdit.js b/elm/VNEdit.js new file mode 100644 index 00000000..9d07036a --- /dev/null +++ b/elm/VNEdit.js @@ -0,0 +1,6 @@ +wrap_elm_init('VNEdit', function(init, opt) { + var app = init(opt); + app.ports.ivRefresh.subscribe(function() { + setTimeout(ivInit, 10); + }); +}); diff --git a/lib/VNWeb/Chars/Edit.pm b/lib/VNWeb/Chars/Edit.pm index 6fb6a494..185466e3 100644 --- a/lib/VNWeb/Chars/Edit.pm +++ b/lib/VNWeb/Chars/Edit.pm @@ -65,7 +65,7 @@ sub enrich_releases { $e->{releases} = [ map !$vns{$_->{vid}}++ ? { id => $_->{vid} } : (), $e->{vns}->@* ]; enrich rels => id => vid => sub { sql ' - SELECT rv.vid, r.id, r.title, r.original, r.released, r.type as rtype + SELECT rv.vid, r.id, r.title, r.original, r.released, r.type as rtype, r.reso_x, r.reso_y FROM releases r JOIN releases_vn rv ON rv.id = r.id WHERE NOT r.hidden AND rv.vid IN', $_, ' diff --git a/lib/VNWeb/Chars/Page.pm b/lib/VNWeb/Chars/Page.pm index ebb6224b..77aa3c97 100644 --- a/lib/VNWeb/Chars/Page.pm +++ b/lib/VNWeb/Chars/Page.pm @@ -91,7 +91,7 @@ sub image_ { input_ type => 'checkbox', class => 'visuallyhidden', $hidden ? () : (checked => 'checked') if $hide_on_click; div_ class => 'imghover--visible', sub { img_ src => tuwf->imgurl($img->{id}), alt => $c->{name}; - a_ href => "/img/$img->{id}?view=".viewset(show_nsfw=>1), + a_ class => 'imghover--overlay', href => "/img/$img->{id}?view=".viewset(show_nsfw=>1), $img->{votecount} ? sprintf '%s / %s (%d)', $sexd, $viod, $img->{votecount} : 'Not flagged'; }; div_ class => 'imghover--warning', sub { diff --git a/lib/VNWeb/Elm.pm b/lib/VNWeb/Elm.pm index b0f4b0f0..d07a19d6 100644 --- a/lib/VNWeb/Elm.pm +++ b/lib/VNWeb/Elm.pm @@ -57,6 +57,8 @@ our %apis = ( original => { required => 0, default => '' }, released => { uint => 1 }, rtype => {}, + reso_x => { uint => 1 }, + reso_y => { uint => 1 }, lang => { type => 'array', values => {} }, platforms=> { type => 'array', values => {} }, } } ], diff --git a/lib/VNWeb/Releases/Elm.pm b/lib/VNWeb/Releases/Elm.pm index b151de41..f942488f 100644 --- a/lib/VNWeb/Releases/Elm.pm +++ b/lib/VNWeb/Releases/Elm.pm @@ -7,7 +7,7 @@ use VNWeb::Prelude; elm_api Release => undef, { vid => { id => 1 } }, sub { my($data) = @_; my $l = tuwf->dbAlli( - 'SELECT r.id, r.title, r.original, r.type AS rtype, r.released + 'SELECT r.id, r.title, r.original, r.type AS rtype, r.released, r.reso_x, r.reso_y FROM releases r JOIN releases_vn rv ON rv.id = r.id WHERE NOT r.hidden diff --git a/lib/VNWeb/User/Lists.pm b/lib/VNWeb/User/Lists.pm index bbc986d2..ff287c5e 100644 --- a/lib/VNWeb/User/Lists.pm +++ b/lib/VNWeb/User/Lists.pm @@ -166,15 +166,7 @@ my $VNOPT = form_compile any => { uid => { id => 1 }, vid => { id => 1 }, notes => {}, - rels => { aoh => { # Same structure as 'elm_Releases' response - id => { id => 1 }, - title => {}, - original => {}, - released => { uint => 1 }, - rtype => {}, - lang => { type => 'array', values => {} }, - platforms=> { type => 'array', values => {} }, - } }, + rels => $VNWeb::Elm::apis{Releases}[0], relstatus => { type => 'array', values => { uint => 1 } }, # List of release statuses, same order as rels }; @@ -469,7 +461,7 @@ sub listing_ { enrich_flatten labels => id => vid => sql('SELECT vid, lbl FROM ulist_vns_labels WHERE uid =', \$uid, 'AND vid IN'), $lst; enrich rels => id => vid => sub { sql ' - SELECT rv.vid, r.id, r.title, r.original, r.released, r.type as rtype, rl.status + SELECT rv.vid, r.id, r.title, r.original, r.released, r.type as rtype, rl.status, r.reso_x, r.reso_y FROM rlists rl JOIN releases r ON rl.rid = r.id JOIN releases_vn rv ON rv.id = r.id diff --git a/lib/VNWeb/VN/Edit.pm b/lib/VNWeb/VN/Edit.pm index 322a7ba2..625d477d 100644 --- a/lib/VNWeb/VN/Edit.pm +++ b/lib/VNWeb/VN/Edit.pm @@ -20,11 +20,17 @@ my $FORM = { } }, image => { required => 0, vndbid => 'cv' }, image_info => { _when => 'out', required => 0, type => 'hash', keys => $VNWeb::Elm::apis{ImageResult}[0]{aoh} }, + screenshots=> { sort_keys => 'scr', aoh => { + scr => { vndbid => 'sf' }, + rid => { required => 0, id => 1 }, + info => { _when => 'out', type => 'hash', keys => $VNWeb::Elm::apis{ImageResult}[0]{aoh} }, + } }, hidden => { anybool => 1 }, locked => { anybool => 1 }, authmod => { _when => 'out', anybool => 1 }, editsum => { _when => 'in out', editsum => 1 }, + releases => { _when => 'out', $VNWeb::Elm::apis{Releases}[0]->%* }, }; my $FORM_OUT = form_compile out => $FORM; @@ -46,9 +52,21 @@ TUWF::get qr{/$RE{vrev}/edit} => sub { } else { $e->{image_info} = undef; } + $_->{info} = {id=>$_->{scr}} for $e->{screenshots}->@*; + enrich_image 0, [map $_->{info}, $e->{screenshots}->@*]; enrich_merge aid => 'SELECT id AS aid, title_romaji AS title, title_kanji AS original FROM anime WHERE id IN', $e->{anime}; + $e->{releases} = tuwf->dbAlli(' + SELECT rv.vid, r.id, r.title, r.original, r.released, r.type as rtype, r.reso_x, r.reso_y + FROM releases r + JOIN releases_vn rv ON rv.id = r.id + WHERE NOT r.hidden AND rv.vid =', \$e->{id}, ' + ORDER BY r.released, r.title, r.id' + ); + enrich_flatten lang => id => id => sub { sql('SELECT id, lang FROM releases_lang WHERE id IN', $_, 'ORDER BY lang') }, $e->{releases}; + enrich_flatten platforms => id => id => sub { sql('SELECT id, platform FROM releases_platforms WHERE id IN', $_, 'ORDER BY platform') }, $e->{releases}; + framework_ title => "Edit $e->{title}", type => 'v', dbobj => $e, tab => 'edit', sub { editmsg_ v => $e, "Edit $e->{title}"; @@ -85,6 +103,20 @@ elm_api VNEdit => $FORM_OUT, $FORM_IN, sub { validate_dbid 'SELECT id FROM anime WHERE id IN', map $_->{aid}, $data->{anime}->@*; validate_dbid 'SELECT id FROM images WHERE id IN', $data->{image} if $data->{image}; + validate_dbid 'SELECT id FROM images WHERE id IN', map $_->{scr}, $data->{screenshots}->@*; + + die "Screenshot without releases assigned" if grep !$_->{rid}, $data->{screenshots}->@*; # This is only the case for *very* old revisions, form disallows this now. + # Allow linking to deleted or moved releases only if the previous revision also had that. + # (The form really should encourage the user to fix that, but disallowing the edit seems a bit overkill) + validate_dbid sub { ' + SELECT r.id FROM releases r JOIN releases_vn rv ON r.id = rv.id WHERE NOT r.hidden AND rv.vid =', \$e->{id}, ' AND r.id IN', $_, ' + UNION + SELECT rid FROM vn_screenshots WHERE id =', \$e->{id}, 'AND rid IN', $_ + }, map $_->{rid}, $data->{screenshots}->@*; + + $data->{image_nsfw} = $e->{image_nsfw}||0; + my %oldscr = map +($_->{scr}, $_->{nsfw}), @{ $e->{screenshots}||[] }; + $_->{nsfw} = $oldscr{$_->{scr}}||0 for $data->{screenshots}->@*; return elm_Unchanged if !$new && !form_changed $FORM_CMP, $data, $e; my($id,undef,$rev) = db_edit v => $e->{id}, $data; |