diff options
author | Yorhel <git@yorhel.nl> | 2020-05-12 19:04:46 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2020-05-13 15:33:23 +0200 |
commit | a6814eea0cfe2a0bf9db9779e94c3dd398361522 (patch) | |
tree | a398dcd66c2306d7d535b7746b2788a9fd961f31 /elm | |
parent | e169129934ac56d0f0758c981da009523bf4619f (diff) |
Chars::Edit: Add image editing
Diffstat (limited to 'elm')
-rw-r--r-- | elm/CharEdit.elm | 71 | ||||
-rw-r--r-- | elm/ImageFlagging.elm | 8 | ||||
-rw-r--r-- | elm/Lib/Api.elm | 25 | ||||
-rw-r--r-- | elm/Lib/Util.elm | 8 |
4 files changed, 103 insertions, 9 deletions
diff --git a/elm/CharEdit.elm b/elm/CharEdit.elm index 39ec8d4e..87927fc8 100644 --- a/elm/CharEdit.elm +++ b/elm/CharEdit.elm @@ -5,6 +5,8 @@ import Html.Events exposing (..) import Html.Attributes exposing (..) import Browser import Browser.Navigation exposing (load) +import File exposing (File) +import File.Select as FSel import Lib.Util exposing (..) import Lib.Html exposing (..) import Lib.TextPreview as TP @@ -25,8 +27,14 @@ main = Browser.element } +type Tab + = General + | Image + | All + type alias Model = { state : Api.State + , tab : Tab , editsum : Editsum.Model , name : String , original : String @@ -48,6 +56,8 @@ type alias Model = , mainHas : Bool , mainName : String , mainSearch : A.Model GApi.ApiCharResult + , image : Maybe String + , imageState : Api.State , id : Maybe Int } @@ -55,6 +65,7 @@ type alias Model = init : GCE.Recv -> Model init d = { state = Api.Normal + , tab = General , editsum = { authmod = d.authmod, editsum = TP.bbcode d.editsum, locked = d.locked, hidden = d.hidden } , name = d.name , original = d.original @@ -76,6 +87,8 @@ init d = , mainHas = d.main /= Nothing , mainName = d.main_name , mainSearch = A.init "" + , image = d.image + , imageState = Api.Normal , id = d.id } @@ -102,6 +115,7 @@ encode model = , bloodt = model.bloodt , cup_size = model.cupSize , main = if model.mainHas then model.main else Nothing + , image = model.image } mainConfig : A.Config Msg GApi.ApiCharResult @@ -109,6 +123,7 @@ mainConfig = { wrap = MainSearch, id = "mainadd", source = A.charSource } type Msg = Editsum Editsum.Msg + | Tab Tab | Submit | Submitted GApi.Response | Name String @@ -128,12 +143,17 @@ type Msg | CupSize String | MainHas Bool | MainSearch (A.Msg GApi.ApiCharResult) + | ImageSet String + | ImageSelect + | ImageSelected File + | ImageLoaded 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) Name s -> ({ model | name = s }, Cmd.none) Original s -> ({ model | original = s }, Cmd.none) Alias s -> ({ model | alias = s }, Cmd.none) @@ -160,6 +180,12 @@ update msg model = Just m2 -> ({ model | mainSearch = A.clear nm "", main = Just m2.id, mainName = m2.name }, c) Nothing -> ({ model | mainSearch = A.clear nm "", main = Just m1.id, mainName = m1.name }, c) + ImageSet s -> ({ model | image = if s == "" then Nothing else Just s}, Cmd.none) + ImageSelect -> (model, FSel.file ["image/png", "image/jpg"] ImageSelected) + ImageSelected f -> ({ model | imageState = Api.Loading }, Api.postImage Api.Ch f ImageLoaded) + ImageLoaded (GApi.Image i _ _) -> ({ model | image = Just i, imageState = Api.Normal }, Cmd.none) + ImageLoaded e -> ({ model | imageState = Api.Error e }, Cmd.none) + Submit -> ({ model | state = Api.Loading }, GCE.send (encode model) Submitted) Submitted (GApi.Redirect s) -> (model, load s) Submitted r -> ({ model | state = Api.Error r }, Cmd.none) @@ -173,10 +199,8 @@ isValid model = not view : Model -> Html Msg view model = - form_ Submit (model.state == Api.Loading) - [ div [ class "mainbox" ] - [ h1 [] [ text "General info" ] - , table [ class "formtable" ] <| + let + geninfo = [ formField "name::Name (romaji)" [ inputText "name" model.name Name GCE.valName ] , formField "original::Original name" [ inputText "original" model.original Original GCE.valOriginal @@ -237,7 +261,46 @@ view model = , A.view mainConfig model.mainSearch [placeholder "Set character..."] ] ] + + image = + div [ class "formimage" ] + [ div [] [ + case model.image of + Nothing -> text "No image." + Just id -> img [ src (imageUrl id) ] [] + ] + , div [] + [ h2 [] [ text "Image ID" ] + , inputText "" (Maybe.withDefault "" model.image) ImageSet GCE.valImage + , Maybe.withDefault (text "") <| Maybe.map (\i -> a [ href <| "/img/"++i ] [ text " (flagging)" ]) model.image + , 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 [] + , case model.imageState of + Api.Normal -> text "" + Api.Loading -> span [ class "spinner" ] [] + Api.Error e -> b [ class "standout" ] [ text <| Api.showResponse e ] + , br [] [] + , text "Image must be in JPEG or PNG format and at most 10 MiB. Images larger than 256x300 will automatically be resized." + -- TODO: Add image flagging vote thingy here after uploading a new image. + ] + ] + + in + 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 == 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" ] [ fieldset [ class "submit" ] [ Html.map Editsum (Editsum.view model.editsum) , submitButton "Submit" model.state (isValid model) diff --git a/elm/ImageFlagging.elm b/elm/ImageFlagging.elm index 66e8cbe0..0e99f1b5 100644 --- a/elm/ImageFlagging.elm +++ b/elm/ImageFlagging.elm @@ -147,7 +147,7 @@ update msg model = -- Preload next image pre (m, c) = case Array.get (m.index+1) m.images of - Just i -> (m, Cmd.batch [ c, preload i.url ]) + Just i -> (m, Cmd.batch [ c, preload (imageUrl i.id) ]) Nothing -> (m, c) in case msg of @@ -249,7 +249,7 @@ view model = ] , div [ style "width" (px boxwidth), style "height" (px boxheight) ] <| -- Don't use an <img> here, changing the src= causes the old image to be displayed with the wrong dimensions while the new image is being loaded. - [ a [ href i.url, style "background-image" ("url("++i.url++")") + [ a [ href (imageUrl i.id), style "background-image" ("url("++imageUrl i.id++")") , style "background-size" (if i.width > boxwidth || i.height > boxheight then "contain" else "auto") ] [ text "" ] ] , div [] @@ -266,7 +266,7 @@ view model = , span [] [ a [ href <| "/img/" ++ i.id ] [ text i.id ] , b [ class "grayedout" ] [ text " / " ] - , a [ href i.url ] [ text <| String.fromInt i.width ++ "x" ++ String.fromInt i.height ] + , a [ href (imageUrl i.id) ] [ text <| String.fromInt i.width ++ "x" ++ String.fromInt i.height ] ] ] , div [] <| if i.token == Nothing then [] else @@ -325,7 +325,7 @@ view model = ] , votestats i , if model.fullscreen -- really lazy fullscreen mode - then div [ class "fullscreen", style "background-image" ("url("++i.url++")"), onClick (Fullscreen False) ] [ text "" ] + then div [ class "fullscreen", style "background-image" ("url("++imageUrl i.id++")"), onClick (Fullscreen False) ] [ text "" ] else text "" ] diff --git a/elm/Lib/Api.elm b/elm/Lib/Api.elm index 63f519cc..5dfda6d8 100644 --- a/elm/Lib/Api.elm +++ b/elm/Lib/Api.elm @@ -1,6 +1,7 @@ module Lib.Api exposing (..) import Json.Encode as JE +import File exposing (File) import Http import Gen.Api exposing (..) @@ -22,7 +23,7 @@ showResponse res = in case res of HTTPError (Http.Timeout) -> "Network timeout, please try again later." HTTPError (Http.NetworkError) -> "Network error, please try again later." - HTTPError (Http.BadStatus r) -> "Server error " ++ String.fromInt r ++ ", please try again later, or report an issue if this persists." + HTTPError (Http.BadStatus r) -> "Server error " ++ String.fromInt r ++ ", please try again later or report an issue if this persists." HTTPError (Http.BadBody r) -> "Invalid response from the server, please report a bug (debug info: " ++ r ++")." HTTPError (Http.BadUrl _) -> unexp Success -> unexp @@ -42,6 +43,8 @@ showResponse res = DoubleIP -> "You can only register one account from the same IP within 24 hours." BadCurPass -> "Current password is invalid." MailChange -> unexp + ImgFormat -> "Unrecognized image format, only JPEG and PNG are accepted." + Image _ _ _ -> unexp Releases _ -> unexp BoardResult _ -> unexp TagResult _ -> unexp @@ -69,3 +72,23 @@ post name body msg = , body = Http.jsonBody body , expect = expectResponse msg } + + +type ImageType + = Ch + | Cv + | Sf + +postImage : ImageType -> File -> (Response -> msg) -> Cmd msg +postImage ty file msg = + Http.post + { url = "/elm/ImageUpload.json" + , body = Http.multipartBody + [ Http.stringPart "type" <| case ty of + Cv -> "cv" + Sf -> "sf" + Ch -> "ch" + , Http.filePart "img" file + ] + , expect = expectResponse msg + } diff --git a/elm/Lib/Util.elm b/elm/Lib/Util.elm index f840d003..34189565 100644 --- a/elm/Lib/Util.elm +++ b/elm/Lib/Util.elm @@ -2,6 +2,7 @@ module Lib.Util exposing (..) import Dict import Task +import Lib.Ffi as Ffi -- Delete an element from a List delidx : Int -> List a -> List a @@ -57,3 +58,10 @@ validateGtin = || n >= 9770000000000 || modBy 10 (check n) /= 0 in String.filter Char.isDigit >> String.toInt >> Maybe.map (not << inval) >> Maybe.withDefault False + + +-- Convert an image ID (e.g. "sf500") into a URL. +imageUrl : String -> String +imageUrl id = + let num = String.dropLeft 2 id |> String.toInt |> Maybe.withDefault 0 + in Ffi.urlStatic ++ "/" ++ String.left 2 id ++ "/" ++ String.fromInt (modBy 10 (num // 10)) ++ String.fromInt (modBy 10 num) ++ "/" ++ String.fromInt num ++ ".jpg" |