module ReleaseEdit exposing (main)
import Html exposing (..)
import Html.Events exposing (..)
import Html.Attributes exposing (..)
import Browser
import Browser.Navigation exposing (load)
import Browser.Dom as Dom
import Bitwise as B
import Set
import Task
import Process
import Lib.Util exposing (..)
import Lib.Html exposing (..)
import Lib.TextPreview as TP
import Lib.Api as Api
import Lib.DropDown as DD
import Lib.Editsum as Editsum
import Lib.RDate as D
import Lib.Autocomplete as A
import Lib.ExtLinks as EL
import Gen.ReleaseEdit as GRE
import Gen.Types as GT
import Gen.Api as GApi
import Gen.ExtLinks as GEL
main : Program GRE.Recv Model Msg
main = Browser.element
{ init = \e -> (init e, Cmd.none)
, view = view
, update = update
, subscriptions = \m -> DD.sub m.platDd
}
type alias Model =
{ state : Api.State
, titles : List GRE.RecvTitles
, vntitles : List GRE.RecvVntitles
, olang : String
, official : Bool
, patch : Bool
, freeware : Bool
, hasEro : Bool
, doujin : Bool
, plat : Set.Set String
, platDd : DD.Config Msg
, media : List GRE.RecvMedia
, gtin : String
, gtinValid : Bool
, catalog : String
, released : D.RDate
, minage : Maybe Int
, uncensored : Maybe Bool
, resoX : Int
, resoY : Int
, reso : A.Model GApi.ApiResolutions
, voiced : Int
, ani_some : Bool
, ani_story : Int
, ani_ero : Int
, ani_story_sp : Maybe Int
, ani_story_cg : Maybe Int
, ani_cutscene : Maybe Int
, ani_ero_sp : Maybe Int
, ani_ero_cg : Maybe Int
, ani_face : Maybe Bool
, ani_bg : Maybe Bool
, website : String
, engine : A.Model GApi.ApiEngines
, extlinks : EL.Model GRE.RecvExtlinks
, vn : List GRE.RecvVn
, vnAdd : A.Model GApi.ApiVNResult
, prod : List GRE.RecvProducers
, prodAdd : A.Model GApi.ApiProducerResult
, notes : TP.Model
, editsum : Editsum.Model
, id : Maybe String
}
hasAni x = x /= Nothing && x /= Just 0 && x /= Just 1
init : GRE.Recv -> Model
init d =
{ state = Api.Normal
, titles = d.titles
, vntitles = d.vntitles
, olang = d.olang
, official = d.official
, patch = d.patch
, freeware = d.freeware
, hasEro = d.has_ero
, doujin = d.doujin
, plat = Set.fromList <| List.map (\e -> e.platform) d.platforms
, platDd = DD.init "platforms" PlatOpen
, media = List.map (\m -> { m | qty = if m.qty == 0 then 1 else m.qty }) d.media
, gtin = if d.gtin == "0" then "" else String.padLeft 12 '0' d.gtin
, gtinValid = True
, catalog = d.catalog
, released = d.released
, minage = d.minage
, uncensored = d.uncensored
, resoX = d.reso_x
, resoY = d.reso_y
, reso = A.init (resoFmt True d.reso_x d.reso_y)
, voiced = d.voiced
, ani_some = hasAni d.ani_story_sp || hasAni d.ani_story_cg || hasAni d.ani_cutscene
|| hasAni d.ani_ero_sp || hasAni d.ani_ero_cg
|| (d.ani_face /= Nothing && d.ani_face /= Just False)
|| (d.ani_bg /= Nothing && d.ani_bg /= Just False)
, ani_story = d.ani_story
, ani_ero = d.ani_ero
, ani_story_sp = d.ani_story_sp
, ani_story_cg = d.ani_story_cg
, ani_cutscene = d.ani_cutscene
, ani_ero_sp = d.ani_ero_sp
, ani_ero_cg = d.ani_ero_cg
, ani_face = d.ani_face
, ani_bg = d.ani_bg
, website = d.website
, engine = A.init d.engine
, extlinks = EL.new d.extlinks GEL.releaseSites
, vn = d.vn
, vnAdd = A.init ""
, prod = d.producers
, prodAdd = A.init ""
, notes = TP.bbcode d.notes
, editsum = { authmod = d.authmod, editsum = TP.bbcode d.editsum, locked = d.locked, hidden = d.hidden, hasawait = False }
, id = d.id
}
encode : Model -> GRE.Send
encode model =
{ id = model.id
, editsum = model.editsum.editsum.data
, hidden = model.editsum.hidden
, locked = model.editsum.locked
, titles = model.titles
, olang = model.olang
, official = model.official
, patch = model.patch
, freeware = model.freeware
, has_ero = model.hasEro
, doujin = model.doujin
, platforms = List.map (\l -> {platform=l}) <| Set.toList model.plat
, media = model.media
, gtin = model.gtin
, catalog = model.catalog
, released = model.released
, minage = model.minage
, uncensored = model.uncensored
, reso_x = model.resoX
, reso_y = model.resoY
, voiced = model.voiced
, ani_story = model.ani_story
, ani_ero = model.ani_ero
, ani_story_sp = model.ani_story_sp
, ani_story_cg = model.ani_story_cg
, ani_cutscene = model.ani_cutscene
, ani_ero_sp = model.ani_ero_sp
, ani_ero_cg = model.ani_ero_cg
, ani_face = model.ani_face
, ani_bg = model.ani_bg
, website = model.website
, engine = model.engine.value
, extlinks = model.extlinks.links
, vn = List.map (\l -> {vid=l.vid, rtype=l.rtype}) model.vn
, producers = List.map (\l -> {pid=l.pid, developer=l.developer, publisher=l.publisher}) model.prod
, notes = model.notes.data
}
vnConfig : A.Config Msg GApi.ApiVNResult
vnConfig = { wrap = VNSearch, id = "vnadd", source = A.vnSource }
producerConfig : A.Config Msg GApi.ApiProducerResult
producerConfig = { wrap = ProdSearch, id = "prodadd", source = A.producerSource }
resoConfig : A.Config Msg GApi.ApiResolutions
resoConfig = { wrap = Resolution, id = "resolution", source = A.resolutionSource }
engineConfig : A.Config Msg GApi.ApiEngines
engineConfig = { wrap = Engine, id = "engine", source = A.engineSource }
type Msg
= Noop
| TitleAdd String
| TitleDel Int
| TitleLang Int String
| TitleTitle Int String
| TitleLatin Int String
| TitleMtl Int Bool
| TitleMain String
| Official Bool
| Patch Bool
| Freeware Bool
| HasEro Bool
| Plat String Bool
| PlatOpen Bool
| MediaType Int String
| MediaQty Int Int
| MediaDel Int
| Gtin String
| Catalog String
| Released D.RDate
| Minage (Maybe Int)
| Uncensored (Maybe Bool)
| Resolution (A.Msg GApi.ApiResolutions)
| Voiced Int
| AniStory Int
| AniEro Int
| AniUnknown Bool
| AniNoAni Bool
| AniSome Bool
| AniStorySp (Maybe Int)
| AniStoryCg (Maybe Int)
| AniCutscene (Maybe Int)
| AniEroSp (Maybe Int)
| AniEroCg (Maybe Int)
| AniFace (Maybe Bool)
| AniBg (Maybe Bool)
| Website String
| Engine (A.Msg GApi.ApiEngines)
| ExtLinks (EL.Msg GRE.RecvExtlinks)
| VNRType Int String
| VNDel Int
| VNSearch (A.Msg GApi.ApiVNResult)
| ProdDel Int
| ProdRole Int (Bool, Bool)
| ProdSearch (A.Msg GApi.ApiProducerResult)
| Notes (TP.Msg)
| Editsum Editsum.Msg
| Submit
| Submitted GApi.Response
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
Noop -> (model, Cmd.none)
TitleAdd s ->
let def = List.filter (\e -> e.lang == s) model.vntitles |> List.head
title = Maybe.map (\e -> e.title) def
latin = Maybe.andThen (\e -> e.latin) def
in ({ model | titles = model.titles ++ [{ lang = s, title = title, latin = latin, mtl = False }], olang = if List.isEmpty model.titles then s else model.olang }
, Task.attempt (always Noop) (Dom.focus ("title_" ++ s)))
TitleDel i -> ({ model | titles = delidx i model.titles }, Cmd.none)
TitleLang i s -> ({ model | titles = modidx i (\e -> { e | lang = s }) model.titles }, Cmd.none)
TitleTitle i s -> ({ model | titles = modidx i (\e -> { e | title = if s == "" then Nothing else Just s }) model.titles }, Cmd.none)
TitleLatin i s -> ({ model | titles = modidx i (\e -> { e | latin = if s == "" then Nothing else Just s }) model.titles }, Cmd.none)
TitleMtl i s -> ({ model | titles = modidx i (\e -> { e | mtl = s }) model.titles }, Cmd.none)
TitleMain s -> ({ model | olang = s }, Cmd.none)
Official b -> ({ model | official = b }, Cmd.none)
Patch b -> ({ model | patch = b }, Cmd.none)
Freeware b -> ({ model | freeware = b }, Cmd.none)
HasEro b -> ({ model | hasEro = b }, Cmd.none)
Plat s b -> ({ model | plat = if b then Set.insert s model.plat else Set.remove s model.plat }, Cmd.none)
PlatOpen b -> ({ model | platDd = DD.toggle model.platDd b }, Cmd.none)
MediaType n s -> ({ model | media = if s /= "unk" && n == List.length model.media then model.media ++ [{medium = s, qty = 1}] else modidx n (\m -> { m | medium = s }) model.media }, Cmd.none)
MediaQty n i -> ({ model | media = modidx n (\m -> { m | qty = i }) model.media }, Cmd.none)
MediaDel i -> ({ model | media = delidx i model.media }, Cmd.none)
Gtin s -> ({ model | gtin = String.replace "-" "" s, gtinValid = s == "" || validateGtin s }, Cmd.none)
Catalog s -> ({ model | catalog = s }, Cmd.none)
Released d -> ({ model | released = d }, Cmd.none)
Minage i -> ({ model | minage = i }, Cmd.none)
Uncensored b->({ model | uncensored = b }, Cmd.none)
Resolution m->
let (nm, c, en) = A.update resoConfig m model.reso
nmod = { model | reso = Maybe.withDefault nm <| Maybe.map (\e -> A.clear nm e.resolution) en }
n2mod = case resoParse True nmod.reso.value of
Just (x,y) -> { nmod | resoX = x, resoY = y }
Nothing -> nmod
in (n2mod, c)
Voiced i -> ({ model | voiced = i }, Cmd.none)
AniStory i -> ({ model | ani_story = i }, Cmd.none)
AniEro i -> ({ model | ani_ero = i }, Cmd.none)
AniUnknown b -> ({ model | ani_some = False, ani_story_sp = Nothing, ani_story_cg = Nothing, ani_cutscene = Nothing
, ani_ero_sp = Nothing, ani_ero_cg = Nothing
, ani_face = Nothing, ani_bg = Nothing }, Cmd.none)
AniNoAni b -> ({ model | ani_some = False, ani_story_sp = Just 0, ani_story_cg = Just 0, ani_cutscene = Just 1
, ani_ero_sp = if model.minage == Just 18 then Just 1 else Nothing
, ani_ero_cg = if model.minage == Just 18 then Just 0 else Nothing
, ani_face = Just False, ani_bg = Just False }, Cmd.none)
AniSome b -> ({ model | ani_some = True }, Cmd.none)
AniStorySp i -> ({ model | ani_story_sp = i }, Cmd.none)
AniStoryCg i -> ({ model | ani_story_cg = i }, Cmd.none)
AniEroSp i -> ({ model | ani_ero_sp = i }, Cmd.none)
AniEroCg i -> ({ model | ani_ero_cg = i }, Cmd.none)
AniCutscene i-> ({ model | ani_cutscene = i }, Cmd.none)
AniFace b -> ({ model | ani_face = b }, Cmd.none)
AniBg b -> ({ model | ani_bg = b }, Cmd.none)
Website s -> ({ model | website = s }, Cmd.none)
Engine m ->
let (nm, c, en) = A.update engineConfig m model.engine
nmod = case en of
Just e -> A.clear nm e.engine
Nothing -> nm
in ({ model | engine = nmod }, c)
ExtLinks m -> ({ model | extlinks = EL.update m model.extlinks }, Cmd.none)
VNRType i s-> ({ model | vn = modidx i (\v -> { v | rtype = s }) model.vn }, Cmd.none)
VNDel i -> ({ model | vn = delidx i model.vn }, Cmd.none)
VNSearch m ->
let (nm, c, res) = A.update vnConfig m model.vnAdd
in case res of
Nothing -> ({ model | vnAdd = nm }, c)
Just v ->
if List.any (\vn -> vn.vid == v.id) model.vn
then ({ model | vnAdd = nm }, c)
else ({ model | vnAdd = A.clear nm "", vn = model.vn ++ [{ vid = v.id, title = v.title, rtype = "complete" }] }, c)
ProdDel i -> ({ model | prod = delidx i model.prod }, Cmd.none)
ProdRole i (d,p) -> ({ model | prod = modidx i (\e -> { e | developer = d, publisher = p }) model.prod }, Cmd.none)
ProdSearch m ->
let (nm, c, res) = A.update producerConfig m model.prodAdd
in case res of
Nothing -> ({ model | prodAdd = nm }, c)
Just p ->
if List.any (\e -> e.pid == p.id) model.prod
then ({ model | prodAdd = nm }, c)
else ({ model | prodAdd = A.clear nm "", prod = model.prod ++ [{ pid = p.id, name = p.name, developer = True, publisher = True}] }, c)
Notes m -> let (nm, nc) = TP.update m model.notes in ({ model | notes = nm }, Cmd.map Notes nc)
Editsum m -> let (nm,nc) = Editsum.update m model.editsum in ({ model | editsum = nm }, Cmd.map Editsum nc)
Submit -> ({ model | state = Api.Loading }, GRE.send (encode model) Submitted)
Submitted (GApi.Redirect s) -> (model, load s)
Submitted r -> ({ model | state = Api.Error r }, Cmd.none)
isValid : Model -> Bool
isValid model = not
( List.any (\e -> e.title /= Nothing && e.title == e.latin) model.titles
|| List.isEmpty model.titles
|| hasDuplicates (List.map (\m -> (m.medium, m.qty)) model.media)
|| not model.gtinValid
|| List.isEmpty model.vn
|| resoParse True model.reso.value == Nothing
)
viewAnimation : Bool -> String -> (Maybe Int -> Msg) -> Maybe Int -> List (Html Msg)
viewAnimation cut na m v =
let isset mask = mask == B.and mask (Maybe.withDefault 0 v)
set mask b = m <| if b then Just (B.or mask (Maybe.withDefault 0 v))
else if Maybe.map (\x -> B.and x (4+8+16+32)) v == Just mask then Nothing
else Just (B.and (B.xor (B.complement 0) mask) (Maybe.withDefault 0 v))
lbl typ txt =
if v == Nothing || (typ == 0 && v == Just 0) || (typ == 1 && v == Just 1) || (typ == 2 && v /= Just 0 && v /= Just 1)
then text txt
else small [] [ text txt ]
in
[ if cut then text "" else
label [] [ inputCheck "" (v == Just 0) (\b -> m <| if b then Just 0 else Nothing), lbl 0 " Not animated", br [] [] ]
, label [] [ inputCheck "" (v == Just 1) (\b -> m <| if b then Just 1 else Nothing), lbl 1 na ], br [] []
, label [] [ inputCheck "" (isset 4) (set 4), lbl 2 " Hand Drawn" ], br [] []
, label [] [ inputCheck "" (isset 8) (set 8), lbl 2 " Vectorial" ], br [] []
, label [] [ inputCheck "" (isset 16) (set 16), lbl 2 " 3D" ], br [] []
, label [] [ inputCheck "" (isset 32) (set 32), lbl 2 " Live action" ]
, if cut || v == Nothing || v == Just 0 || v == Just 1 then text "" else span []
[ br [] []
, inputSelect ""
(B.and (256+512) (Maybe.withDefault 0 v))
(\i -> m (Just (B.or i (B.and (Maybe.withDefault 0 v) (B.xor (B.complement 0) (256+512))))))
[style "width" "150px"]
[ (0, "- frequency -"), (256, "Some scenes"), (512, "All scenes") ]
]
]
viewTitle : Model -> Int -> GRE.RecvTitles -> Html Msg
viewTitle model i e = tr []
[ td [] [ langIcon e.lang ]
, td []
[ inputText ("title_"++e.lang) (Maybe.withDefault "" e.title) (TitleTitle i)
( style "width" "500px"
:: placeholder (if e.lang == model.olang then "Title (in the original script)" else "Title (leave empty to use the main title)")
:: required (e.lang == model.olang)
:: GRE.valTitlesTitle)
, if not (e.latin /= Nothing || containsNonLatin (Maybe.withDefault "" e.title)) then text "" else span []
[ br [] []
, inputText "" (Maybe.withDefault "" e.latin) (TitleLatin i) (style "width" "500px" :: placeholder "Romanization" :: GRE.valTitlesLatin)
, case e.latin of
Just s -> if containsNonLatin s then b [] [ br [] [], text "Romanization should only consist of characters in the latin alphabet." ] else text ""
Nothing -> text ""
]
, if List.length model.titles == 1 then text "" else span []
[ br [] []
, label [] [ inputRadio "olang" (e.lang == model.olang) (\_ -> TitleMain e.lang), text " main title" ]
]
, br [] []
, label [] [ inputCheck "" e.mtl (TitleMtl i), text " Machine translation" ]
, if e.lang == model.olang then text "" else span []
[ br [] [], inputButton "remove" (TitleDel i) [] ]
, br_ 2
]
]
viewGen : Model -> Html Msg
viewGen model =
table [ class "formtable" ] <|
[ formField "Languages & titles"
[ table [] <| List.indexedMap (viewTitle model) model.titles
, inputSelect "" "" TitleAdd [] <| ("", "- Add language -") :: List.filter (\(l,_) -> not (List.any (\e -> e.lang == l) model.titles)) scriptLangs
]
, tr [ class "newpart" ] [ td [] [] ]
, formField "" [ label [] [ inputCheck "" model.official Official, text " Official (i.e. sanctioned by the original developer of the visual novel)" ] ]
, formField "" [ label [] [ inputCheck "" model.patch Patch , text " This release is a patch to another release.", text " (*)" ] ]
, formField "" [ label [] [ inputCheck "" model.freeware Freeware, text " Freeware (i.e. available at no cost)" ] ]
, formField "" [ label [] [ inputCheck "" model.hasEro HasEro , text " Contains erotic scenes", text " (*)" ] ]
, formField "minage::Age rating" [ inputSelect "minage" model.minage Minage [] ((Nothing, "Unknown") :: List.map (Tuple.mapFirst Just) GT.ageRatings) ]
, formField "Release date" [ D.view model.released False False Released, text " Leave month or day blank if they are unknown." ]
, tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Format" ] ]
, formField "Platform(s)"
[ div [ class "elm_dd_input", style "width" "500px" ] [ DD.view model.platDd Api.Normal
(if Set.isEmpty model.plat
then text "No platform selected"
else span [] <| List.intersperse (text ", ") <| List.map (\(p,t) -> span [ style "white-space" "nowrap" ] [ platformIcon p, text t ]) <| List.filter (\(p,_) -> Set.member p model.plat) GT.platforms)
<| \() -> [ ul [ style "columns" "2"] <| List.map (\(p,t) -> li [ classList [("separator", p == "web")] ] [ linkRadio (Set.member p model.plat) (Plat p) [ platformIcon p, text t ] ]) GT.platforms ]
] ]
, formField "Media"
[ table [] <| List.indexedMap (\i m ->
let q = List.filter (\(s,_,_) -> m.medium == s) GT.media |> List.head |> Maybe.map (\(_,_,x) -> x) |> Maybe.withDefault False
in tr []
[ td [] [ inputSelect "" m.medium (MediaType i) [] <| (if m.medium == "unk" then [("unk", "- Add medium -")] else []) ++ List.map (\(a,b,_) -> (a,b)) GT.media ]
, td [] [ if q then inputSelect "" m.qty (MediaQty i) [ style "width" "100px" ] <| List.map (\a -> (a,String.fromInt a)) <| List.range 1 40 else text "" ]
, td [] [ if m.medium == "unk" then text "" else inputButton "remove" (MediaDel i) [] ]
]
) <| model.media ++ [{medium = "unk", qty = 0}]
, if hasDuplicates (List.map (\m -> (m.medium, m.qty)) model.media)
then b [] [ text "List contains duplicates", br [] [] ]
else text ""
]
, if model.patch then text "" else
formField "engine::Engine" [ A.view engineConfig model.engine [] ]
, if model.patch then text "" else
formField "resolution::Resolution"
[ A.view resoConfig model.reso []
, if resoParse True model.reso.value == Nothing then b [] [ text " Invalid resolution" ] else text ""
]
, if model.patch then text "" else
formField "voiced::Voiced" [ inputSelect "voiced" model.voiced Voiced [] GT.voiced ]
, if not model.hasEro then text "" else
formField "uncensored::Censoring"
[ inputSelect "uncensored" model.uncensored Uncensored []
[ (Nothing, "Unknown")
, (Just False, "Censored graphics")
, (Just True, "Uncensored graphics") ]
, text " Whether erotic graphics are censored with mosaic or other optical censoring." ]
] ++ (if model.patch then [] else
[ tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Animation" ] ]
, formField "Presets"
[ linkRadio (not model.ani_some && model.ani_face == Nothing) AniUnknown [ text "Unknown" ], text " | "
, linkRadio (not model.ani_some && model.ani_face == Just False) AniNoAni [ text "No animation" ], text " | "
, linkRadio model.ani_some AniSome [ text "Some animation" ]
]
, if not model.ani_some then text "" else
formField "Story scenes" [ table [] [ tr []
[ td [ style "width" "180px" ] <| [ strong [] [ text "Character sprites:" ], br [] [] ] ++ viewAnimation False " No sprites" AniStorySp model.ani_story_sp
, td [ style "width" "180px" ] <| [ strong [] [ text "CGs:" ], br [] [] ] ++ viewAnimation False " No CGs" AniStoryCg model.ani_story_cg
, td [] <| [ strong [] [ text "Cutscenes:" ], br [] [] ] ++ viewAnimation True " No cutscenes" AniCutscene model.ani_cutscene
]
] ]
, if not model.ani_some || not model.hasEro then text "" else
formField "Erotic scenes" [ table [] [ tr []
[ td [ style "width" "180px" ] <| [ strong [] [ text "Character sprites:" ], br [] [] ] ++ viewAnimation False " No sprites" AniEroSp model.ani_ero_sp
, td [] <| [ strong [] [ text "CGs:" ], br [] [] ] ++ viewAnimation False " No CGs" AniEroCg model.ani_ero_cg
]
] ]
, if not model.ani_some then text "" else
formField "Effects" [ table []
[ tr []
[ td [] [ text "Character lip movement and/or eye blink: " ]
, td []
[ label [] [ inputRadio "ani_face" (model.ani_face == Nothing) (always (AniFace Nothing)), text " Unknown or N/A" ], text " / "
, label [] [ inputRadio "ani_face" (model.ani_face == Just False) (always (AniFace (Just False))), text " No" ], text " / "
, label [] [ inputRadio "ani_face" (model.ani_face == Just True) (always (AniFace (Just True))), text " Yes" ]
]
]
, tr []
[ td [] [ text "Background effects: " ]
, td []
[ label [] [ inputRadio "ani_bg" (model.ani_bg == Nothing) (always (AniBg Nothing)), text " Unknown or N/A" ], text " / "
, label [] [ inputRadio "ani_bg" (model.ani_bg == Just False) (always (AniBg (Just False))), text " No" ], text " / "
, label [] [ inputRadio "ani_bg" (model.ani_bg == Just True) (always (AniBg (Just True))), text " Yes" ]
]
]
] ]
]) ++
[ tr [ class "newpart" ] [ td [ colspan 2 ] [ text "External identifiers & links" ] ]
, formField "gtin::JAN/UPC/EAN"
[ inputText "gtin" model.gtin Gtin [pattern "[0-9]+"]
, if not model.gtinValid then b [] [ text "Invalid GTIN code" ] else text ""
]
, formField "catalog::Catalog number" [ inputText "catalog" model.catalog Catalog GRE.valCatalog ]
, formField "website::Website" [ inputText "website" model.website Website (style "width" "500px" :: GRE.valWebsite) ]
, tr [ class "newpart" ] [ td [ colspan 2 ] [] ]
, formField "External Links" [ Html.map ExtLinks (EL.view model.extlinks) ]
, tr [ class "newpart" ] [ td [ colspan 2 ] [ text "Database relations" ] ]
, formField "Visual novels"
[ if List.isEmpty model.vn then b [] [ text "No visual novels selected.", br [] [] ]
else table [] <| List.indexedMap (\i v -> tr []
[ td [ style "text-align" "right" ] [ small [] [ text <| v.vid ++ ":" ] ]
, td [] [ a [ href <| "/" ++ v.vid ] [ text v.title ] ]
, td [] [ inputSelect "" v.rtype (VNRType i) [style "width" "100px"] GT.releaseTypes ]
, td [] [ inputButton "remove" (VNDel i) [] ]
]
) model.vn
, A.view vnConfig model.vnAdd [placeholder "Add visual novel..."]
]
, tr [ class "newpart" ] [ td [ colspan 2 ] [] ]
, formField "Producers"
[ table [ class "compact" ] <| List.indexedMap (\i p -> tr []
[ td [ style "text-align" "right" ] [ small [] [ text <| p.pid ++ ":" ] ]
, td [] [ a [ href <| "/" ++ p.pid ] [ text p.name ] ]
, td [] [ inputSelect "" (p.developer, p.publisher) (ProdRole i) [style "width" "100px"] [((True,False), "Developer"), ((False,True), "Publisher"), ((True,True), "Both")] ]
, td [] [ inputButton "remove" (ProdDel i) [] ]
]
) model.prod
, A.view producerConfig model.prodAdd [placeholder "Add producer..."]
]
, tr [ class "newpart" ] [ td [ colspan 2 ] [] ]
, formField "notes::Notes"
[ TP.view "notes" model.notes Notes 700 [] [ b [] [ text " (English please!) " ] ]
, text "Miscellaneous notes/comments, information that does not fit in the above fields. E.g.: Types of censoring or for which releases this patch applies."
]
]
view : Model -> Html Msg
view model =
form_ "" Submit (model.state == Api.Loading)
[ article []
[ h1 [] [ text "General info" ]
, viewGen model
]
, article [ class "submit" ]
[ Html.map Editsum (Editsum.view model.editsum)
, submitButton "Submit" model.state (isValid model)
]
]