summaryrefslogtreecommitdiff
path: root/elm
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2020-05-12 19:04:46 +0200
committerYorhel <git@yorhel.nl>2020-05-13 15:33:23 +0200
commita6814eea0cfe2a0bf9db9779e94c3dd398361522 (patch)
treea398dcd66c2306d7d535b7746b2788a9fd961f31 /elm
parente169129934ac56d0f0758c981da009523bf4619f (diff)
Chars::Edit: Add image editing
Diffstat (limited to 'elm')
-rw-r--r--elm/CharEdit.elm71
-rw-r--r--elm/ImageFlagging.elm8
-rw-r--r--elm/Lib/Api.elm25
-rw-r--r--elm/Lib/Util.elm8
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"