port module VNEdit exposing (main)
import Html exposing (..)
import Html.Events exposing (..)
import Html.Keyed as K
import Html.Attributes exposing (..)
import Browser
import Browser.Navigation exposing (load)
import Dict
import Set
import File exposing (File)
import File.Select as FSel
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
import Gen.VN as GV
import Gen.VNEdit as GVE
import Gen.Types as GT
import Gen.Api as GApi
main : Program GVE.Recv Model Msg
main = Browser.element
{ init = \e -> (init e, Cmd.none)
, view = view
, update = update
, subscriptions = always Sub.none
}
port ivRefresh : Bool -> Cmd msg
type Tab
= General
| Image
| Staff
| Cast
| Screenshots
| All
type alias Model =
{ state : Api.State
, tab : Tab
, editsum : Editsum.Model
, title : String
, original : String
, alias : String
, desc : TP.Model
, length : Int
, lWikidata : Maybe Int
, lRenai : String
, vns : List GVE.RecvRelations
, vnSearch : A.Model GApi.ApiVNResult
, anime : List GVE.RecvAnime
, animeSearch : A.Model GApi.ApiAnimeResult
, image : Img.Image
, staff : List GVE.RecvStaff
, staffSearch : A.Model GApi.ApiStaffResult
, seiyuu : List GVE.RecvSeiyuu
, seiyuuSearch: A.Model GApi.ApiStaffResult
, seiyuuDef : Int -- character id for newly added seiyuu
, 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
, chars : List GVE.RecvChars
, id : Maybe Int
, dupCheck : Bool
, dupVNs : List GApi.ApiVNResult
}
init : GVE.Recv -> Model
init d =
{ state = Api.Normal
, tab = General
, editsum = { authmod = d.authmod, editsum = TP.bbcode d.editsum, locked = d.locked, hidden = d.hidden }
, title = d.title
, original = d.original
, alias = d.alias
, desc = TP.bbcode d.desc
, length = d.length
, lWikidata = d.l_wikidata
, lRenai = d.l_renai
, vns = d.relations
, vnSearch = A.init ""
, anime = d.anime
, animeSearch = A.init ""
, image = Img.info d.image_info
, staff = d.staff
, staffSearch = A.init ""
, seiyuu = d.seiyuu
, seiyuuSearch= A.init ""
, seiyuuDef = Maybe.withDefault 0 <| List.head <| List.map (\c -> c.id) d.chars
, screenshots = List.indexedMap (\n i -> (n, Img.info (Just i.info), i.rid)) d.screenshots
, scrUplRel = Nothing
, scrUplNum = Nothing
, scrId = 100
, releases = d.releases
, chars = d.chars
, id = d.id
, dupCheck = False
, dupVNs = []
}
encode : Model -> GVE.Send
encode model =
{ id = model.id
, editsum = model.editsum.editsum.data
, hidden = model.editsum.hidden
, locked = model.editsum.locked
, title = model.title
, original = model.original
, alias = model.alias
, desc = model.desc.data
, length = model.length
, l_wikidata = model.lWikidata
, l_renai = model.lRenai
, relations = List.map (\v -> { vid = v.vid, relation = v.relation, official = v.official }) model.vns
, anime = List.map (\a -> { aid = a.aid }) model.anime
, image = model.image.id
, staff = List.map (\s -> { aid = s.aid, role = s.role, note = s.note }) model.staff
, seiyuu = List.map (\s -> { aid = s.aid, cid = s.cid, note = s.note }) model.seiyuu
, screenshots = List.map (\(_,i,r) -> { scr = Maybe.withDefault "" i.id, rid = r }) model.screenshots
}
vnConfig : A.Config Msg GApi.ApiVNResult
vnConfig = { wrap = VNSearch, id = "relationadd", source = A.vnSource }
animeConfig : A.Config Msg GApi.ApiAnimeResult
animeConfig = { wrap = AnimeSearch, id = "animeadd", source = A.animeSource }
staffConfig : A.Config Msg GApi.ApiStaffResult
staffConfig = { wrap = StaffSearch, id = "staffadd", source = A.staffSource }
seiyuuConfig : A.Config Msg GApi.ApiStaffResult
seiyuuConfig = { wrap = SeiyuuSearch, id = "seiyuuadd", source = A.staffSource }
type Msg
= Editsum Editsum.Msg
| Tab Tab
| Submit
| Submitted GApi.Response
| Title String
| Original String
| Alias String
| Desc TP.Msg
| Length Int
| LWikidata (Maybe Int)
| LRenai String
| VNDel Int
| VNRel Int String
| VNOfficial Int Bool
| VNSearch (A.Msg GApi.ApiVNResult)
| AnimeDel Int
| AnimeSearch (A.Msg GApi.ApiAnimeResult)
| ImageSet String Bool
| ImageSelect
| ImageSelected File
| ImageMsg Img.Msg
| StaffDel Int
| StaffRole Int String
| StaffNote Int String
| StaffSearch (A.Msg GApi.ApiStaffResult)
| SeiyuuDef Int
| SeiyuuDel Int
| SeiyuuChar Int Int
| SeiyuuNote Int String
| SeiyuuSearch (A.Msg GApi.ApiStaffResult)
| ScrUplRel (Maybe Int)
| ScrUplSel
| ScrUpl File (List File)
| ScrMsg Int Img.Msg
| ScrRel Int (Maybe Int)
| ScrDel Int
| DupSubmit
| DupResults GApi.Response
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Editsum m -> let (nm,nc) = Editsum.update m model.editsum in ({ model | editsum = nm }, Cmd.map Editsum nc)
Tab t -> ({ model | tab = t }, Cmd.none)
Title s -> ({ model | title = s, dupVNs = [] }, Cmd.none)
Original s -> ({ model | original = s, dupVNs = [] }, Cmd.none)
Alias s -> ({ model | alias = s, dupVNs = [] }, Cmd.none)
Desc m -> let (nm,nc) = TP.update m model.desc in ({ model | desc = nm }, Cmd.map Desc nc)
Length n -> ({ model | length = n }, Cmd.none)
LWikidata n-> ({ model | lWikidata = n }, Cmd.none)
LRenai s -> ({ model | lRenai = s }, Cmd.none)
VNDel idx -> ({ model | vns = delidx idx model.vns }, Cmd.none)
VNRel idx rel -> ({ model | vns = modidx idx (\v -> { v | relation = rel }) model.vns }, Cmd.none)
VNOfficial idx o -> ({ model | vns = modidx idx (\v -> { v | official = o }) model.vns }, Cmd.none)
VNSearch m ->
let (nm, c, res) = A.update vnConfig m model.vnSearch
in case res of
Nothing -> ({ model | vnSearch = nm }, c)
Just v ->
if List.any (\l -> l.vid == v.id) model.vns
then ({ model | vnSearch = A.clear nm "" }, c)
else ({ model | vnSearch = A.clear nm "", vns = model.vns ++ [{ vid = v.id, title = v.title, original = v.original, relation = "seq", official = True }] }, c)
AnimeDel i -> ({ model | anime = delidx i model.anime }, Cmd.none)
AnimeSearch m ->
let (nm, c, res) = A.update animeConfig m model.animeSearch
in case res of
Nothing -> ({ model | animeSearch = nm }, c)
Just a ->
if List.any (\l -> l.aid == a.id) model.anime
then ({ model | animeSearch = A.clear nm "" }, c)
else ({ model | animeSearch = A.clear nm "", anime = model.anime ++ [{ aid = a.id, title = a.title, original = a.original }] }, c)
ImageSet s b -> let (nm, nc) = Img.new b s in ({ model | image = nm }, Cmd.map ImageMsg nc)
ImageSelect -> (model, FSel.file ["image/png", "image/jpg"] ImageSelected)
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)
StaffDel idx -> ({ model | staff = delidx idx model.staff }, Cmd.none)
StaffRole idx v -> ({ model | staff = modidx idx (\s -> { s | role = v }) model.staff }, Cmd.none)
StaffNote idx v -> ({ model | staff = modidx idx (\s -> { s | note = v }) model.staff }, Cmd.none)
StaffSearch m ->
let (nm, c, res) = A.update staffConfig m model.staffSearch
in case res of
Nothing -> ({ model | staffSearch = nm }, c)
Just s -> ({ model | staffSearch = A.clear nm "", staff = model.staff ++ [{ id = s.id, aid = s.aid, name = s.name, original = s.original, role = "staff", note = "" }] }, c)
SeiyuuDef c -> ({ model | seiyuuDef = c }, Cmd.none)
SeiyuuDel idx -> ({ model | seiyuu = delidx idx model.seiyuu }, Cmd.none)
SeiyuuChar idx v -> ({ model | seiyuu = modidx idx (\s -> { s | cid = v }) model.seiyuu }, Cmd.none)
SeiyuuNote idx v -> ({ model | seiyuu = modidx idx (\s -> { s | note = v }) model.seiyuu }, Cmd.none)
SeiyuuSearch m ->
let (nm, c, res) = A.update seiyuuConfig m model.seiyuuSearch
in case res of
Nothing -> ({ model | seiyuuSearch = nm }, c)
Just s -> ({ model | seiyuuSearch = A.clear nm "", seiyuu = model.seiyuu ++ [{ id = s.id, aid = s.aid, name = s.name, original = s.original, cid = model.seiyuuDef, note = "" }] }, c)
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)
DupSubmit ->
if List.isEmpty model.dupVNs
then ({ model | state = Api.Loading }, GV.send { hidden = True, search = model.title :: model.original :: String.lines model.alias } DupResults)
else ({ model | dupCheck = True, dupVNs = [] }, Cmd.none)
DupResults (GApi.VNResult vns) ->
if List.isEmpty vns
then ({ model | state = Api.Normal, dupCheck = True, dupVNs = [] }, Cmd.none)
else ({ model | state = Api.Normal, dupVNs = vns }, Cmd.none)
DupResults r -> ({ model | state = Api.Error r }, Cmd.none)
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)
-- TODO: Fuzzier matching? Exclude stuff like 'x Edition', etc.
relAlias : Model -> Maybe GVE.RecvReleases
relAlias model =
let a = String.toLower model.alias |> String.lines |> List.filter (\l -> l /= "") |> Set.fromList
in List.filter (\r -> Set.member (String.toLower r.title) a || Set.member (String.toLower r.original) a) model.releases |> List.head
isValid : Model -> Bool
isValid model = not
( (model.title /= "" && model.title == model.original)
|| relAlias model /= Nothing
|| not (Img.isValid model.image)
|| List.any (\(_,i,r) -> r == Nothing || not (Img.isValid i)) model.screenshots
|| hasDuplicates (List.map (\s -> (s.aid, s.role)) model.staff)
|| hasDuplicates (List.map (\s -> (s.aid, s.cid)) model.seiyuu)
)
view : Model -> Html Msg
view model =
let
titles =
[ formField "title::Title (romaji)"
[ inputText "title" model.title Title (style "width" "500px" :: GVE.valTitle)
, if containsNonLatin model.title
then b [ class "standout" ] [ br [] [], text "This title field should only contain latin-alphabet characters, please put the \"actual\" title in the field below and the romanization above." ]
else text ""
]
, formField "original::Original title"
[ inputText "original" model.original Original (style "width" "500px" :: GVE.valOriginal)
, if model.title /= "" && model.title == model.original
then b [ class "standout" ] [ br [] [], text "Should not be the same as the Title (romaji). Leave blank is the original title is already in the latin alphabet" ]
else if model.original /= "" && String.toLower model.title /= String.toLower model.original && not (containsNonLatin model.original)
then b [ class "standout" ] [ br [] [], text "Original title does not seem to contain any non-latin characters. Leave this field empty if the title is already in the latin alphabet" ]
else text ""
]
, formField "alias::Aliases"
[ inputTextArea "alias" model.alias Alias (rows 3 :: GVE.valAlias)
, br [] []
, if hasDuplicates <| String.lines <| String.toLower model.alias
then b [ class "standout" ] [ text "List contains duplicate aliases.", br [] [] ]
else
case relAlias model of
Nothing -> text ""
Just r -> span []
[ b [ class "standout" ] [ text "Release titles should not be added as alias." ]
, br [] []
, text "Release: "
, a [ href <| "/r"++String.fromInt r.id ] [ text r.title ]
, br [] [], br [] []
]
, text "List of alternative titles or abbreviations. One line for each alias. Can include both official (japanese/english) titles and unofficial titles used around net."
, br [] []
, text "Titles that are listed in the releases should not be added here!"
]
]
geninfo = titles ++
[ formField "desc::Description"
[ TP.view "desc" model.desc Desc 600 (style "height" "180px" :: GVE.valDesc) [ b [ class "standout" ] [ text "English please!" ] ]
, text "Short description of the main story. Please do not include spoilers, and don't forget to list the source in case you didn't write the description yourself."
]
, formField "length::Length" [ inputSelect "length" model.length Length [] GT.vnLengths ]
, formField "l_wikidata::Wikidata ID" [ inputWikidata "l_wikidata" model.lWikidata LWikidata ]
, formField "l_renai::Renai.us link" [ text "http://renai.us/game/", inputText "l_renai" model.lRenai LRenai [], text ".shtml" ]
, tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Database relations" ] ]
, formField "Related VNs"
[ if List.isEmpty model.vns then text ""
else table [] <| List.indexedMap (\i v -> tr []
[ td [ style "text-align" "right" ] [ b [ class "grayedout" ] [ text <| "v" ++ String.fromInt v.vid ++ ":" ] ]
, td [ style "text-align" "right"] [ a [ href <| "/v" ++ String.fromInt v.vid ] [ text v.title ] ]
, td []
[ text "is an "
, label [] [ inputCheck "" v.official (VNOfficial i), text " official" ]
, inputSelect "" v.relation (VNRel i) [] GT.vnRelations
, text " of this VN"
]
, td [] [ inputButton "remove" (VNDel i) [] ]
]
) model.vns
, A.view vnConfig model.vnSearch [placeholder "Add visual novel..."]
]
, tr [ class "newpart" ] [ td [ colspan 2 ] [] ]
, formField "Related anime"
[ if List.isEmpty model.anime then text ""
else table [] <| List.indexedMap (\i e -> tr []
[ td [ style "text-align" "right" ] [ b [ class "grayedout" ] [ text <| "a" ++ String.fromInt e.aid ++ ":" ] ]
, td [] [ a [ href <| "https://anidb.net/anime/" ++ String.fromInt e.aid ] [ text e.title ] ]
, td [] [ inputButton "remove" (AnimeDel i) [] ]
]
) model.anime
, A.view animeConfig model.animeSearch [placeholder "Add anime..."]
]
]
image =
table [ class "formimage" ] [ tr []
[ td [] [ Img.viewImg model.image ]
, td []
[ h2 [] [ text "Image ID" ]
, input ([ type_ "text", class "text", tabindex 10, value (Maybe.withDefault "" model.image.id), onInputValidation ImageSet ] ++ GVE.valImage) []
, br [] []
, text "Use an image that already exists on the server or empty to remove the current image."
, br_ 2
, h2 [] [ text "Upload new image" ]
, inputButton "Browse image" ImageSelect []
, br [] []
, text "Preferably the cover of the CD/DVD/package. Image must be in JPEG or PNG format and at most 10 MiB. Images larger than 256x400 will automatically be resized."
, case Img.viewVote model.image of
Nothing -> text ""
Just v ->
div []
[ br [] []
, text "Please flag this image: (see the ", a [ href "/d19" ] [ text "image flagging guidelines" ], text " for guidance)"
, Html.map ImageMsg v
]
]
] ]
staff =
let
head =
if List.isEmpty model.staff then [] else [
thead [] [ tr []
[ td [] []
, td [] [ text "Staff" ]
, td [] [ text "Role" ]
, td [] [ text "Note" ]
, td [] []
] ] ]
foot =
tfoot [] [ tr [] [ td [] [], td [ colspan 4 ]
[ br [] []
, if hasDuplicates (List.map (\s -> (s.aid, s.role)) model.staff)
then b [ class "standout" ] [ text "List contains duplicate staff roles.", br [] [] ]
else text ""
, A.view staffConfig model.staffSearch [placeholder "Add staff..."]
, text "Can't find the person you're looking for? You can "
, a [ href "/s/new" ] [ text "create a new entry" ]
, text ", but "
, a [ href "/s/all" ] [ text "please check for aliasses first." ]
, br_ 2
, text "Some guidelines:"
, ul []
[ li [] [ text "Please add major staff only, i.e. people who had a significant and noticable impact on the work." ]
, li [] [ text "If one person performed several roles, you can add multiple entries with different major roles." ]
]
] ] ]
item n s = tr []
[ td [ style "text-align" "right" ] [ b [ class "grayedout" ] [ text <| "s" ++ String.fromInt s.id ++ ":" ] ]
, td [] [ a [ href <| "/s" ++ String.fromInt s.id ] [ text s.name ] ]
, td [] [ inputSelect "" s.role (StaffRole n) [style "width" "150px" ] GT.creditTypes ]
, td [] [ inputText "" s.note (StaffNote n) (style "width" "300px" :: GVE.valStaffNote) ]
, td [] [ inputButton "remove" (StaffDel n) [] ]
]
in table [] <| head ++ [ foot ] ++ List.indexedMap item model.staff
cast =
let
chars = List.map (\c -> (c.id, c.name ++ " (c" ++ String.fromInt c.id ++ ")")) model.chars
head =
if List.isEmpty model.seiyuu then [] else [
thead [] [ tr []
[ td [] [ text "Character" ]
, td [] [ text "Cast" ]
, td [] [ text "Note" ]
, td [] []
] ] ]
foot =
tfoot [] [ tr [] [ td [ colspan 4 ]
[ br [] []
, b [] [ text "Add cast" ]
, br [] []
, if hasDuplicates (List.map (\s -> (s.aid, s.cid)) model.seiyuu)
then b [ class "standout" ] [ text "List contains duplicate cast roles.", br [] [] ]
else text ""
, inputSelect "" model.seiyuuDef SeiyuuDef [] chars
, text " voiced by "
, div [ style "display" "inline-block" ] [ A.view seiyuuConfig model.seiyuuSearch [] ]
, br [] []
, text "Can't find the person you're looking for? You can "
, a [ href "/s/new" ] [ text "create a new entry" ]
, text ", but "
, a [ href "/s/all" ] [ text "please check for aliasses first." ]
] ] ]
item n s = tr []
[ td [] [ inputSelect "" s.cid (SeiyuuChar n) []
<| chars ++ if List.any (\c -> c.id == s.cid) model.chars then [] else [(s.cid, "[deleted/moved character: c" ++ String.fromInt s.cid ++ "]")] ]
, td []
[ b [ class "grayedout" ] [ text <| "s" ++ String.fromInt s.id ++ ":" ]
, a [ href <| "/s" ++ String.fromInt s.id ] [ text s.name ] ]
, td [] [ inputText "" s.note (SeiyuuNote n) (style "width" "300px" :: GVE.valSeiyuuNote) ]
, td [] [ inputButton "remove" (SeiyuuDel n) [] ]
]
in
if model.id == Nothing
then text <| "Voice actors can be added to this visual novel once it has character entries associated with it. "
++ "To do so, first create this entry without cast, then create the appropriate character entries, and finally come back to this form by editing the visual novel."
else if List.isEmpty model.chars && List.isEmpty model.seiyuu
then p []
[ text "This visual novel does not have any characters associated with it (yet). Please "
, a [ href <| "/v" ++ Maybe.withDefault "" (Maybe.map String.fromInt model.id) ++ "/addchar" ] [ text "add the appropriate character entries" ]
, text " first and then come back to this form to assign voice actors."
]
else table [] <| head ++ [ foot ] ++ List.indexedMap item model.seiyuu
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) = (String.fromInt id, tr [] <|
let getdim img = Maybe.map (\nfo -> (nfo.width, nfo.height)) img |> Maybe.withDefault (0,0)
imgdim = getdim i.img
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 == Just imgdim then [ text " ✔", br [] [] ]
else if reldim /= Nothing
then [ 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!" ]
]
else if i.img /= Nothing && rel /= Nothing && List.any (\(_,si,sr) -> sr == rel && si.img /= Nothing && imgdim /= getdim si.img) model.screenshots
then [ b [ class "standout" ] [ text "WARNING: Inconsistent image resolutions for the same release, please take screenshots with the correct resolution and make sure to crop them correctly!" ]
, br [] []
]
else [ br [] [] ]
, 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 to 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.screenshots && 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 ] ]
, K.node "tbody" [] <| List.indexedMap scr model.screenshots
]
newform () =
form_ DupSubmit (model.state == Api.Loading)
[ div [ class "mainbox" ] [ h1 [] [ text "Add a new visual novel" ], table [ class "formtable" ] titles ]
, div [ class "mainbox" ]
[ if List.isEmpty model.dupVNs then text "" else
div []
[ h1 [] [ text "Possible duplicates" ]
, text "The following is a list of visual novels that match the title(s) you gave. "
, text "Please check this list to avoid creating a duplicate visual novel entry. "
, text "Be especially wary of items that have been deleted! To see why an entry has been deleted, click on its title."
, ul [] <| List.map (\v -> li []
[ a [ href <| "/v" ++ String.fromInt v.id ] [ text v.title ]
, if v.hidden then b [ class "standout" ] [ text " (deleted)" ] else text ""
]
) model.dupVNs
]
, fieldset [ class "submit" ] [ submitButton (if List.isEmpty model.dupVNs then "Continue" else "Continue anyway") model.state (isValid model) ]
]
]
fullform () =
form_ Submit (model.state == Api.Loading)
[ div [ class "maintabs left" ]
[ ul []
[ li [ classList [("tabselected", model.tab == General )] ] [ a [ href "#", onClickD (Tab General ) ] [ text "General info" ] ]
, li [ classList [("tabselected", model.tab == Image )] ] [ a [ href "#", onClickD (Tab Image ) ] [ text "Image" ] ]
, li [ classList [("tabselected", model.tab == Staff )] ] [ a [ href "#", onClickD (Tab Staff ) ] [ text "Staff" ] ]
, li [ classList [("tabselected", model.tab == Cast )] ] [ a [ href "#", onClickD (Tab Cast ) ] [ text "Cast" ] ]
, li [ classList [("tabselected", model.tab == Screenshots)] ] [ a [ href "#", onClickD (Tab Screenshots) ] [ text "Screenshots" ] ]
, li [ classList [("tabselected", model.tab == All )] ] [ a [ href "#", onClickD (Tab All ) ] [ text "All items" ] ]
]
]
, div [ class "mainbox", classList [("hidden", model.tab /= General && model.tab /= All)] ] [ h1 [] [ text "General info" ], table [ class "formtable" ] geninfo ]
, div [ class "mainbox", classList [("hidden", model.tab /= Image && model.tab /= All)] ] [ h1 [] [ text "Image" ], image ]
, div [ class "mainbox", classList [("hidden", model.tab /= Staff && model.tab /= All)] ] [ h1 [] [ text "Staff" ], staff ]
, div [ class "mainbox", classList [("hidden", model.tab /= Cast && model.tab /= All)] ] [ h1 [] [ text "Cast" ], cast ]
, 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)
]
]
]
in if model.id == Nothing && not model.dupCheck then newform () else fullform ()