summaryrefslogtreecommitdiff
path: root/elm/VNEdit.elm
diff options
context:
space:
mode:
Diffstat (limited to 'elm/VNEdit.elm')
-rw-r--r--elm/VNEdit.elm465
1 files changed, 316 insertions, 149 deletions
diff --git a/elm/VNEdit.elm b/elm/VNEdit.elm
index 4fadbf2d..751cab61 100644
--- a/elm/VNEdit.elm
+++ b/elm/VNEdit.elm
@@ -6,10 +6,15 @@ import Html.Keyed as K
import Html.Attributes exposing (..)
import Browser
import Browser.Navigation exposing (load)
+import Browser.Dom as Dom
import Dict
import Set
+import Task
+import Date
+import Process
import File exposing (File)
import File.Select as FSel
+import Lib.Ffi as Ffi
import Lib.Util exposing (..)
import Lib.Html exposing (..)
import Lib.TextPreview as TP
@@ -26,7 +31,7 @@ import Gen.Api as GApi
main : Program GVE.Recv Model Msg
main = Browser.element
- { init = \e -> (init e, Cmd.none)
+ { init = \e -> (init e, Date.today |> Task.perform Today)
, view = view
, update = update
, subscriptions = always Sub.none
@@ -46,11 +51,14 @@ type Tab
type alias Model =
{ state : Api.State
, tab : Tab
+ , today : Int
+ , invalidDis : Bool
, editsum : Editsum.Model
- , title : String
- , original : String
+ , titles : List GVE.RecvTitles
, alias : String
- , desc : TP.Model
+ , description : TP.Model
+ , devStatus : Int
+ , olang : String
, length : Int
, lWikidata : Maybe Int
, lRenai : String
@@ -59,18 +67,22 @@ type alias Model =
, anime : List GVE.RecvAnime
, animeSearch : A.Model GApi.ApiAnimeResult
, image : Img.Image
+ , editions : List GVE.RecvEditions
, staff : List GVE.RecvStaff
- , staffSearch : A.Model GApi.ApiStaffResult
+ -- Search boxes matching the list of editions (n+1), first entry is for the NULL edition.
+ , staffSearch : List (A.Config Msg GApi.ApiStaffResult, 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
+ , seiyuuDef : String -- character id for newly added seiyuu
+ , screenshots : List (Int,Img.Image,Maybe String) -- internal id, img, rel
+ , scrQueue : List File
+ , scrUplRel : Maybe String
, scrUplNum : Maybe Int
, scrId : Int -- latest used internal id
, releases : List GVE.RecvReleases
+ , reltitles : List { id: String, title: String }
, chars : List GVE.RecvChars
- , id : Maybe Int
+ , id : Maybe String
, dupCheck : Bool
, dupVNs : List GApi.ApiVNResult
}
@@ -80,11 +92,14 @@ 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
+ , today = 0
+ , invalidDis = False
+ , editsum = { authmod = d.authmod, editsum = TP.bbcode d.editsum, locked = d.locked, hidden = d.hidden, hasawait = False }
+ , titles = d.titles
, alias = d.alias
- , desc = TP.bbcode d.desc
+ , description = TP.bbcode d.description
+ , devStatus = d.devstatus
+ , olang = d.olang
, length = d.length
, lWikidata = d.l_wikidata
, lRenai = d.l_renai
@@ -93,16 +108,19 @@ init d =
, anime = d.anime
, animeSearch = A.init ""
, image = Img.info d.image_info
+ , editions = d.editions
, staff = d.staff
- , staffSearch = A.init ""
+ , staffSearch = (staffConfig Nothing, A.init "") :: List.map (\e -> (staffConfig (Just e.eid), A.init "")) d.editions
, seiyuu = d.seiyuu
, seiyuuSearch= A.init ""
- , seiyuuDef = Maybe.withDefault 0 <| List.head <| List.map (\c -> c.id) d.chars
+ , seiyuuDef = Maybe.withDefault "" <| List.head <| List.map (\c -> c.id) d.chars
, screenshots = List.indexedMap (\n i -> (n, Img.info (Just i.info), i.rid)) d.screenshots
+ , scrQueue = []
, scrUplRel = Nothing
, scrUplNum = Nothing
, scrId = 100
, releases = d.releases
+ , reltitles = d.reltitles
, chars = d.chars
, id = d.id
, dupCheck = False
@@ -116,18 +134,20 @@ encode model =
, editsum = model.editsum.editsum.data
, hidden = model.editsum.hidden
, locked = model.editsum.locked
- , title = model.title
- , original = model.original
+ , titles = model.titles
, alias = model.alias
- , desc = model.desc.data
+ , devstatus = model.devStatus
+ , description = model.description.data
+ , olang = model.olang
, 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
+ , editions = model.editions
+ , staff = List.map (\s -> { aid = s.aid, eid = s.eid, note = s.note, role = s.role }) 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
}
@@ -135,26 +155,40 @@ 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 }
+animeConfig = { wrap = AnimeSearch, id = "animeadd", source = A.animeSource False }
-staffConfig : A.Config Msg GApi.ApiStaffResult
-staffConfig = { wrap = StaffSearch, id = "staffadd", source = A.staffSource }
+staffConfig : Maybe Int -> A.Config Msg GApi.ApiStaffResult
+staffConfig eid =
+ { wrap = (StaffSearch eid)
+ , id = "staffadd-" ++ Maybe.withDefault "" (Maybe.map String.fromInt eid)
+ , source = A.staffSource
+ }
seiyuuConfig : A.Config Msg GApi.ApiStaffResult
seiyuuConfig = { wrap = SeiyuuSearch, id = "seiyuuadd", source = A.staffSource }
type Msg
- = Editsum Editsum.Msg
+ = Noop
+ | Today Date.Date
+ | Editsum Editsum.Msg
| Tab Tab
+ | Invalid Tab
+ | InvalidEnable
| Submit
| Submitted GApi.Response
- | Title String
- | Original String
| Alias String
| Desc TP.Msg
+ | DevStatus Int
| Length Int
| LWikidata (Maybe Int)
| LRenai String
+ | TitleAdd String
+ | TitleDel Int
+ | TitleLang Int String
+ | TitleTitle Int String
+ | TitleLatin Int String
+ | TitleOfficial Int Bool
+ | TitleMain Int String
| VNDel Int
| VNRel Int String
| VNOfficial Int Bool
@@ -165,38 +199,70 @@ type Msg
| ImageSelect
| ImageSelected File
| ImageMsg Img.Msg
+ | EditionAdd
+ | EditionLang Int (Maybe String)
+ | EditionName Int String
+ | EditionOfficial Int Bool
+ | EditionDel Int Int
| StaffDel Int
| StaffRole Int String
| StaffNote Int String
- | StaffSearch (A.Msg GApi.ApiStaffResult)
- | SeiyuuDef Int
+ | StaffSearch (Maybe Int) (A.Msg GApi.ApiStaffResult)
+ | SeiyuuDef String
| SeiyuuDel Int
- | SeiyuuChar Int Int
+ | SeiyuuChar Int String
| SeiyuuNote Int String
| SeiyuuSearch (A.Msg GApi.ApiStaffResult)
- | ScrUplRel (Maybe Int)
+ | ScrUplRel (Maybe String)
| ScrUplSel
| ScrUpl File (List File)
| ScrMsg Int Img.Msg
- | ScrRel Int (Maybe Int)
+ | ScrRel Int (Maybe String)
| ScrDel Int
| DupSubmit
| DupResults GApi.Response
+scrProcessQueue : (Model, Cmd Msg) -> (Model, Cmd Msg)
+scrProcessQueue (model, msg) =
+ case model.scrQueue of
+ (f::fl) ->
+ if List.any (\(_,i,_) -> i.imgState == Img.Loading) model.screenshots
+ then (model, msg)
+ else
+ let (im,ic) = Img.upload Api.Sf f
+ in ( { model | scrQueue = fl, scrId = model.scrId + 1, screenshots = model.screenshots ++ [(model.scrId, im, model.scrUplRel)] }
+ , Cmd.batch [ msg, Cmd.map (ScrMsg model.scrId) ic ] )
+ _ -> (model, msg)
+
+
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
+ Noop -> (model, Cmd.none)
+ Today d -> ({ model | today = RDate.fromDate d |> RDate.compact }, Cmd.none)
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)
+ Invalid t -> if model.invalidDis || model.tab == All || model.tab == t then (model, Cmd.none) else
+ ({ model | tab = t, invalidDis = True }, Task.attempt (always InvalidEnable) (Ffi.elemCall "reportValidity" "mainform" |> Task.andThen (\_ -> Process.sleep 100)))
+ InvalidEnable -> ({ model | invalidDis = False }, 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)
+ Desc m -> let (nm,nc) = TP.update m model.description in ({ model | description = nm }, Cmd.map Desc nc)
+ DevStatus b-> ({ model | devStatus = b }, Cmd.none)
Length n -> ({ model | length = n }, Cmd.none)
LWikidata n-> ({ model | lWikidata = n }, Cmd.none)
LRenai s -> ({ model | lRenai = s }, Cmd.none)
+ TitleAdd s ->
+ ({ model | titles = model.titles ++ [{ lang = s, title = "", latin = Nothing, official = True }], 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 = 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)
+ TitleOfficial i s -> ({ model | titles = modidx i (\e -> { e | official = s }) model.titles }, Cmd.none)
+ TitleMain i s -> ({ model | olang = s, titles = modidx i (\e -> { e | official = True }) model.titles }, 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)
@@ -207,7 +273,7 @@ update msg model =
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)
+ else ({ model | vnSearch = A.clear nm "", vns = model.vns ++ [{ vid = v.id, title = v.title, relation = "seq", official = True }] }, c)
AnimeDel i -> ({ model | anime = delidx i model.anime }, Cmd.none)
AnimeSearch m ->
@@ -220,18 +286,47 @@ update msg model =
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)
+ ImageSelect -> (model, FSel.file ["image/png", "image/jpeg", "image/webp", "image/avif", "image/jxl"] 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)
+ EditionAdd ->
+ let f n acc =
+ case acc of
+ Just x -> Just x
+ Nothing -> if not (List.isEmpty (List.filter (\i -> i.eid == n) model.editions)) then Nothing else Just n
+ newid = List.range 0 500 |> List.foldl f Nothing |> Maybe.withDefault 0
+ in ({ model
+ | editions = model.editions ++ [{ eid = newid, lang = Nothing, name = "", official = True }]
+ , staffSearch = model.staffSearch ++ [(staffConfig (Just newid), A.init "")]
+ }, Cmd.none)
+ EditionDel idx eid ->
+ ({ model
+ | editions = delidx idx model.editions
+ , staffSearch = delidx (idx + 1) model.staffSearch
+ , staff = List.filter (\s -> s.eid /= Just eid) model.staff
+ }, Cmd.none)
+ EditionLang idx v -> ({ model | editions = modidx idx (\s -> { s | lang = v }) model.editions }, Cmd.none)
+ EditionName idx v -> ({ model | editions = modidx idx (\s -> { s | name = v }) model.editions }, Cmd.none)
+ EditionOfficial idx v -> ({ model | editions = modidx idx (\s -> { s | official = v }) model.editions }, Cmd.none)
+
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)
+ StaffSearch eid m ->
+ let idx = List.indexedMap Tuple.pair model.editions
+ |> List.filterMap (\(n,e) -> if Just e.eid == eid then Just (n+1) else Nothing)
+ |> List.head |> Maybe.withDefault 0
+ in case List.drop idx model.staffSearch |> List.head of
+ Nothing -> (model, Cmd.none)
+ Just (sconfig, smodel) ->
+ let (nm, c, res) = A.update sconfig m smodel
+ nnm = if res == Nothing then nm else A.clear nm ""
+ nsearch = modidx idx (\(oc,om) -> (oc,nnm)) model.staffSearch
+ nstaff s = [{ id = s.id, aid = s.aid, eid = eid, title = s.title, alttitle = s.alttitle, role = "staff", note = "" }]
+ in case res of
+ Nothing -> ({ model | staffSearch = nsearch }, c)
+ Just s -> ({ model | staffSearch = nsearch, staff = model.staff ++ nstaff s }, c)
SeiyuuDef c -> ({ model | seiyuuDef = c }, Cmd.none)
SeiyuuDel idx -> ({ model | seiyuu = delidx idx model.seiyuu }, Cmd.none)
@@ -241,33 +336,26 @@ update msg model =
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)
+ Just s -> ({ model | seiyuuSearch = A.clear nm "", seiyuu = model.seiyuu ++ [{ id = s.id, aid = s.aid, title = s.title, alttitle = s.alttitle, cid = model.seiyuuDef, note = "" }] }, c)
ScrUplRel s -> ({ model | scrUplRel = s }, Cmd.none)
- ScrUplSel -> (model, FSel.files ["image/png", "image/jpg"] ScrUpl)
+ ScrUplSel -> (model, FSel.files ["image/png", "image/jpeg", "image/webp", "image/avif", "image/jxl"] 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)
+ else scrProcessQueue ({ model | scrQueue = (f1::fl), scrUplNum = Nothing }, Cmd.none)
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))
+ in scrProcessQueue ({ 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)
+ then ({ model | state = Api.Loading }, GV.send { hidden = True, search = (List.concatMap (\e -> [e.title, Maybe.withDefault "" e.latin]) model.titles) ++ String.lines model.alias } DupResults)
else ({ model | dupCheck = True, dupVNs = [] }, Cmd.none)
DupResults (GApi.VNResult vns) ->
if List.isEmpty vns
@@ -281,19 +369,22 @@ update msg model =
-- TODO: Fuzzier matching? Exclude stuff like 'x Edition', etc.
-relAlias : Model -> Maybe GVE.RecvReleases
+relAlias : Model -> Maybe { id: String, title: String }
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
+ in List.filter (\r -> Set.member (String.toLower r.title) a) model.reltitles |> List.head
isValid : Model -> Bool
isValid model = not
- ( (model.title /= "" && model.title == model.original)
+ ( List.any (\e -> e.title /= "" && Just e.title == e.latin) model.titles
+ || List.isEmpty model.titles
|| 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)
+ || not (List.isEmpty model.scrQueue)
+ || hasDuplicates (List.map (\e -> (Maybe.withDefault "" e.lang, e.name)) model.editions)
+ || hasDuplicates (List.map (\s -> (s.aid, Maybe.withDefault -1 s.eid, s.role)) model.staff)
|| hasDuplicates (List.map (\s -> (s.aid, s.cid)) model.seiyuu)
)
@@ -301,37 +392,57 @@ isValid model = not
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 ""
+ title i e = tr []
+ [ td [] [ langIcon e.lang ]
+ , td []
+ [ inputText ("title_"++e.lang) e.title (TitleTitle i) (style "width" "500px" :: onInvalid (Invalid General) :: placeholder "Title (in the original script)" :: GVE.valTitlesTitle)
+ , if not (e.latin /= Nothing || containsNonLatin e.title) then text "" else span []
+ [ br [] []
+ , inputText "" (Maybe.withDefault "" e.latin) (TitleLatin i) (style "width" "500px" :: required True :: onInvalid (Invalid General) :: placeholder "Romanization" :: GVE.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 i e.lang), text " main title (the language the VN was originally written in)" ]
+ ]
+ , if e.lang == model.olang then text "" else span []
+ [ br [] []
+ , label [] [ inputCheck "" e.official (TitleOfficial i), text " official title (from the developer or licensed localization; not from a fan translation)" ]
+ , br [] []
+ , inputButton "remove" (TitleDel i) []
+ ]
+ , br_ 2
]
- , 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 ""
+ ]
+
+ titles =
+ let lines = List.filter (\e -> e /= "") <| String.lines <| String.toLower model.alias
+ in
+ [ formField "Title(s)"
+ [ table [] <| List.indexedMap title model.titles
+ , inputSelect "" "" TitleAdd [] <| ("", "- Add title -") :: List.filter (\(l,_) -> not (List.any (\e -> e.lang == l) model.titles)) scriptLangs
+ , br_ 2
]
, formField "alias::Aliases"
- [ inputTextArea "alias" model.alias Alias (rows 3 :: GVE.valAlias)
+ [ inputTextArea "alias" model.alias Alias (rows 3 :: onInvalid (Invalid General) :: GVE.valAlias)
, br [] []
- , if hasDuplicates <| String.lines <| String.toLower model.alias
- then b [ class "standout" ] [ text "List contains duplicate aliases.", br [] [] ]
+ , if hasDuplicates lines
+ then b [] [ text "List contains duplicate aliases.", br [] [] ]
+ else if contains lines <| List.map String.toLower <| List.concatMap (\e -> [e.title, Maybe.withDefault "" e.latin]) model.titles
+ then b [] [ text "Titles listed above should not also be added as alias.", br [] [] ]
else
case relAlias model of
Nothing -> text ""
Just r -> span []
- [ b [ class "standout" ] [ text "Release titles should not be added as alias." ]
+ [ b [] [ text "Release titles should not be added as alias." ]
, br [] []
, text "Release: "
- , a [ href <| "/r"++String.fromInt r.id ] [ text r.title ]
+ , a [ href <| "/"++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."
+ , text "List of additional 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!"
]
@@ -339,19 +450,43 @@ view model =
geninfo = titles ++
[ formField "desc::Description"
- [ TP.view "desc" model.desc Desc 600 (style "height" "180px" :: GVE.valDesc) [ b [ class "standout" ] [ text "English please!" ] ]
+ [ TP.view "desc" model.description Desc 600 (style "height" "180px" :: onInvalid (Invalid General) :: GVE.valDescription) [ b [] [ 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" ]
+ , formField "devstatus::Development status"
+ [ inputSelect "devstatus" model.devStatus DevStatus [] GT.devStatus
+ , if model.devStatus == 0
+ && not (List.isEmpty model.releases)
+ && List.isEmpty (List.filter (\r -> r.rtype == "complete" && r.released <= model.today) model.releases)
+ then span []
+ [ br [] []
+ , b [] [ text "Development is marked as finished, but there is no complete release in the database." ]
+ , br [] []
+ , text "Please adjust the development status or ensure there is a completed release."
+ ]
+ else text ""
+ , if model.devStatus /= 0
+ && not (List.isEmpty (List.filter (\r -> r.rtype == "complete" && r.released <= model.today) model.releases))
+ then span []
+ [ br [] []
+ , b [] [ text "Development is not marked as finished, but there is a complete release in the database." ]
+ , br [] []
+ , text "Please adjust the development status or set the release to partial or TBA."
+ ]
+ else text ""
+ ]
+ , formField "length::Length"
+ [ inputSelect "length" model.length Length [] GT.vnLengths
+ , text " (only displayed if there are no length votes)" ]
+ , formField "l_wikidata::Wikidata ID" [ inputWikidata "l_wikidata" model.lWikidata LWikidata [onInvalid (Invalid General)] ]
+ , formField "l_renai::Renai.us link" [ text "http://renai.us/game/", inputText "l_renai" model.lRenai LRenai (onInvalid (Invalid General) :: GVE.valL_Renai), 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 [ style "text-align" "right" ] [ small [] [ text <| v.vid ++ ":" ] ]
+ , td [ style "text-align" "right"] [ a [ href <| "/" ++ v.vid ] [ text v.title ] ]
, td []
[ text "is an "
, label [] [ inputCheck "" v.official (VNOfficial i), text " official" ]
@@ -367,7 +502,7 @@ view model =
, 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 [ style "text-align" "right" ] [ small [] [ 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) [] ]
]
@@ -381,66 +516,94 @@ view model =
[ 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) []
+ , input ([ type_ "text", class "text", tabindex 10, value (Maybe.withDefault "" model.image.id), onInputValidation ImageSet, onInvalid (Invalid Image) ] ++ 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
+ , text "Preferably the cover of the CD/DVD/package."
+ , br [] []
+ , text "Supported file types: JPEG, PNG, WebP, AVIF or JXL, at most 10 MiB."
+ , br [] []
+ , text "Images larger than 256x400 are automatically resized."
+ , case Img.viewVote model.image ImageMsg (Invalid 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
+ , v
]
]
] ]
staff =
let
- head =
- if List.isEmpty model.staff then [] else [
+ head lst =
+ if List.isEmpty lst then text "" else
thead [] [ tr []
[ td [] []
, td [] [ text "Staff" ]
, td [] [ text "Role" ]
, td [] [ text "Note" ]
, td [] []
- ] ] ]
- foot =
+ ] ]
+ foot searchn lst (sconfig, smodel) =
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 [] [] ]
+ [ text ""
+ , if hasDuplicates (List.map (\(_,s) -> (s.aid, s.role)) lst)
+ then b [] [ 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." ]
+ , A.view sconfig smodel [placeholder "Add staff..."]
+ , if searchn > 0 then text "" else span []
+ [ 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 [] []
+ , 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 ] ]
+ item (n,s) = tr []
+ [ td [ style "text-align" "right" ] [ small [] [ text <| s.id ++ ":" ] ]
+ , td [] [ a [ href <| "/" ++ s.id ] [ text s.title ], text <| if s.alttitle == s.title then "" else " " ++ s.alttitle ]
, td [] [ inputSelect "" s.role (StaffRole n) [style "width" "150px" ] GT.creditTypes ]
- , td [] [ inputText "" s.note (StaffNote n) (style "width" "300px" :: GVE.valStaffNote) ]
+ , td [] [ inputText "" s.note (StaffNote n) (style "width" "300px" :: onInvalid (Invalid Staff) :: GVE.valStaffNote) ]
, td [] [ inputButton "remove" (StaffDel n) [] ]
]
- in table [] <| head ++ [ foot ] ++ List.indexedMap item model.staff
+ edition searchn edi =
+ let eid = Maybe.map (\e -> e.eid) edi
+ lst = List.indexedMap Tuple.pair model.staff |> List.filter (\(_,s) -> s.eid == eid)
+ sch = List.drop searchn model.staffSearch |> List.head
+ in div [style "margin" "0 0 30px 0"]
+ [ Maybe.withDefault (if List.isEmpty model.editions then text "" else h2 [] [ text "Original edition" ])
+ <| Maybe.map (\e -> h2 [] [ text (if e.name == "" then "New edition" else e.name) ]) edi
+ , case edi of
+ Nothing -> text ""
+ Just e ->
+ div [style "margin" "5px 0 0 15px"]
+ [ inputText "" e.name (EditionName (searchn-1)) (placeholder "Edition title" :: style "width" "300px" :: onInvalid (Invalid Staff) :: GVE.valEditionsName)
+ , inputSelect "" e.lang (EditionLang (searchn-1)) [style "width" "150px"]
+ ((Nothing, "Original language") :: List.map (\(i,l) -> (Just i, l)) scriptLangs)
+ , text " ", label [] [ inputCheck "" e.official (EditionOfficial (searchn-1)), text " official" ]
+ , inputButton "remove edition" (EditionDel (searchn-1) e.eid) [style "margin-left" "30px"]
+ ]
+ , table [style "margin" "5px 0 0 15px"]
+ <| head lst
+ :: Maybe.withDefault (text "") (Maybe.map (foot searchn lst) sch)
+ :: List.map item lst
+ ]
+ in edition 0 Nothing
+ :: List.indexedMap (\n e -> edition (n+1) (Just e)) model.editions
+ ++ [ br [] [], inputButton "Add edition" EditionAdd [] ]
+
+
cast =
let
- chars = List.map (\c -> (c.id, c.name ++ " (c" ++ String.fromInt c.id ++ ")")) model.chars
+ chars = List.map (\c -> (c.id, c.title ++ " (" ++ c.id ++ ")")) model.chars
head =
if List.isEmpty model.seiyuu then [] else [
thead [] [ tr []
@@ -452,10 +615,10 @@ view model =
foot =
tfoot [] [ tr [] [ td [ colspan 4 ]
[ br [] []
- , b [] [ text "Add cast" ]
+ , strong [] [ 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 [] [] ]
+ then b [] [ text "List contains duplicate cast roles.", br [] [] ]
else text ""
, inputSelect "" model.seiyuuDef SeiyuuDef [] chars
, text " voiced by "
@@ -468,11 +631,11 @@ view model =
] ] ]
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 ++ "]")] ]
+ <| chars ++ if List.any (\c -> c.id == s.cid) model.chars then [] else [(s.cid, "[deleted/moved character: " ++ 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) ]
+ [ small [] [ text <| s.id ++ ":" ]
+ , a [ href <| "/" ++ s.id ] [ text s.title ], text <| if s.title == s.alttitle then "" else " " ++ s.alttitle ]
+ , td [] [ inputText "" s.note (SeiyuuNote n) (style "width" "300px" :: onInvalid (Invalid Cast) :: GVE.valSeiyuuNote) ]
, td [] [ inputButton "remove" (SeiyuuDel n) [] ]
]
in
@@ -482,15 +645,14 @@ view model =
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" ]
+ , a [ href <| "/" ++ Maybe.withDefault "" 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
+ rellist = List.map (\r -> (Just r.id, RDate.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
@@ -499,9 +661,9 @@ view model =
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 [] [ Img.viewVote i (ScrMsg id) (Invalid Screenshots) |> Maybe.withDefault (text "") ]
, td []
- [ b [] [ text <| "Screenshot #" ++ String.fromInt (n+1) ]
+ [ strong [] [ text <| "Screenshot #" ++ String.fromInt (n+1) ]
, text " (", a [ href "#", onClickD (ScrDel id) ] [ text "remove" ], text ")"
, br [] []
, text <| "Image resolution: " ++ dimstr imgdim
@@ -512,10 +674,10 @@ view model =
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!" ]
+ , b [] [ 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!" ]
+ then [ b [] [ 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 [] [] ]
@@ -523,7 +685,7 @@ view model =
, 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 ++ "]")]
+ (Nothing, Just r) -> [(Just r, "[Deleted or unlinked release: " ++ r ++ "]")]
_ -> []
]
])
@@ -531,13 +693,19 @@ view model =
add =
let free = 10 - List.length model.screenshots
in
- if free <= 0
- then [ b [] [ text "Enough screenshots" ]
+ if not (List.isEmpty model.scrQueue)
+ then [ strong [] [ text "Uploading screenshots" ]
+ , br [] []
+ , text <| (String.fromInt (List.length model.scrQueue)) ++ " remaining... "
+ , span [ class "spinner" ] []
+ ]
+ else if free <= 0
+ then [ strong [] [ 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" ]
+ [ strong [] [ text "Add screenshots" ]
, br [] []
, text <| String.fromInt free ++ " more screenshot" ++ (if free == 1 then "" else "s") ++ " can be added."
, br [] []
@@ -551,7 +719,7 @@ view model =
, br [] []
]
, br [] []
- , b [] [ text "Important reminder" ]
+ , strong [] [ 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" ]
@@ -566,7 +734,7 @@ view model =
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" ]
+ , a [ href <| "/" ++ Maybe.withDefault "" model.id ++ "/add" ] [ text "add the appropriate release entries" ]
, text " first and then come back to this form to upload screenshots."
]
else
@@ -576,29 +744,29 @@ view model =
]
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 []
+ form_ "" DupSubmit (model.state == Api.Loading)
+ [ article [] [ h1 [] [ text "Add a new visual novel" ], table [ class "formtable" ] titles ]
+ , if List.isEmpty model.dupVNs then text "" else
+ article []
+ [ 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 ""
+ [ a [ href <| "/" ++ v.id ] [ text v.title ]
+ , if v.hidden then b [] [ text " (deleted)" ] else text ""
]
) model.dupVNs
]
- , fieldset [ class "submit" ] [ submitButton (if List.isEmpty model.dupVNs then "Continue" else "Continue anyway") model.state (isValid model) ]
]
+ , article [ 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 []
+ form_ "mainform" Submit (model.state == Api.Loading)
+ [ nav []
+ [ menu []
[ 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" ] ]
@@ -607,15 +775,14 @@ view model =
, 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)
- ]
+ , article [ classList [("hidden", model.tab /= General && model.tab /= All)] ] [ h1 [] [ text "General info" ], table [ class "formtable" ] geninfo ]
+ , article [ classList [("hidden", model.tab /= Image && model.tab /= All)] ] [ h1 [] [ text "Image" ], image ]
+ , article [ classList [("hidden", model.tab /= Staff && model.tab /= All)] ] ( h1 [] [ text "Staff" ] :: staff )
+ , article [ classList [("hidden", model.tab /= Cast && model.tab /= All)] ] [ h1 [] [ text "Cast" ], cast ]
+ , article [ classList [("hidden", model.tab /= Screenshots && model.tab /= All)] ] [ h1 [] [ text "Screenshots" ], screenshots ]
+ , article [ 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 ()