diff options
| author | Jan Tuomi <jan.tuomi@valuemotive.com> | 2021-10-29 16:52:15 +0300 |
|---|---|---|
| committer | Jan Tuomi <jan.tuomi@valuemotive.com> | 2021-10-29 16:52:15 +0300 |
| commit | e500394ed975055657fde19d3cd1c106888734b5 (patch) | |
| tree | 56990da5fd026bc6321fef3bba113b0e486cc216 | |
| parent | a972058d20b773f4488df51babb40156c5a335e1 (diff) | |
Implement stuff
| -rw-r--r-- | db.json | 62 | ||||
| -rwxr-xr-x | scripts/_build-and-serve.sh | 4 | ||||
| -rwxr-xr-x | scripts/build-prod.sh | 6 | ||||
| -rwxr-xr-x | scripts/debug.sh | 5 | ||||
| -rw-r--r-- | src/Config.elm | 6 | ||||
| -rw-r--r-- | src/Main.elm | 72 | ||||
| -rw-r--r-- | src/Pages/Feed.elm | 57 | ||||
| -rw-r--r-- | src/Types.elm | 13 | ||||
| -rw-r--r-- | src/index.html | 22 | ||||
| -rw-r--r-- | vendor/main.css | 1 |
10 files changed, 202 insertions, 46 deletions
@@ -8,7 +8,67 @@ "id": "EA8E23FC-8F12-404A-9A68-576FBDF72934", "name": "George Technoman" }, - "likes": 28 + "likes": 38 + }, + { + "content": "asd", + "createdAt": 1635513526, + "likes": 1, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "-_5iRYd" + }, + { + "content": "new post :)))", + "createdAt": 1635513654, + "likes": 1, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "sgiJnlL" + }, + { + "content": "I'll have two number nines", + "createdAt": 1635514095, + "likes": 1, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "g5Vggns" + }, + { + "content": "asdasdasd", + "createdAt": 1635514147, + "likes": 0, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "bIZavcS" + }, + { + "content": "asdasdas", + "createdAt": 1635514155, + "likes": 0, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "bsb19RF" + }, + { + "content": "asdasd", + "createdAt": 1635514207, + "likes": 0, + "author": { + "id": "logged-in-user-id", + "name": "Logged in user" + }, + "id": "miEWafM" } ] }
\ No newline at end of file diff --git a/scripts/_build-and-serve.sh b/scripts/_build-and-serve.sh index 7e98061..3d5dac6 100755 --- a/scripts/_build-and-serve.sh +++ b/scripts/_build-and-serve.sh @@ -3,4 +3,6 @@ PORT=${1} mkdir -p build && rm -r build/* -elm make src/Main.elm --debug --output build/index.html && python3 -m http.server ${PORT} -d build +cp -r vendor/* build/ +cp src/index.html build/index.html +elm make src/Main.elm --debug --output build/main.js && python3 -m http.server ${PORT} -d build diff --git a/scripts/build-prod.sh b/scripts/build-prod.sh index 7f73a6f..0a5d8a3 100755 --- a/scripts/build-prod.sh +++ b/scripts/build-prod.sh @@ -1,8 +1,8 @@ #!/bin/bash TOP_LEVEL="$(git rev-parse --show-toplevel)" -OUT_FILE="build/index.js" -OUT_MIN_FILE="build/index.min.js" +OUT_FILE="build/main.js" +OUT_MIN_FILE="build/main.min.js" mkdir -p build && rm -r build/* @@ -12,7 +12,7 @@ echo ":: Outputting index.html to build/index.html" elm make src/Main.elm --optimize --output="${OUT_FILE}" uglifyjs "${OUT_FILE}" --compress 'pure_funcs=[F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9],pure_getters,keep_fargs=false,unsafe_comps,unsafe' | uglifyjs --mangle --output "${OUT_MIN_FILE}" -cp index.prod.html build/index.html +cp src/index.html build/index.html echo ":: Compiled size: $(wc -c $OUT_FILE) bytes ($OUT_FILE)" echo ":: Minified size: $(wc -c $OUT_MIN_FILE) bytes ($OUT_MIN_FILE)" diff --git a/scripts/debug.sh b/scripts/debug.sh index 0ae14a3..711df8d 100755 --- a/scripts/debug.sh +++ b/scripts/debug.sh @@ -3,7 +3,8 @@ TOP_LEVEL="$(git rev-parse --show-toplevel)" PORT="${1:-8001}" -echo ":: Watching directory src/ and serving continuous builds on http://localhost:${PORT}" +echo ":: Watching directory src/ and serving continuous builds on http://localhost:${PORT}." +echo ":: Touch a file in src/ to compile & serve!" -bash scripts/_build-and-serve.sh ${PORT} +# bash scripts/_build-and-serve.sh ${PORT} fswatch -or src | xargs -n1 -I{} bash scripts/_build-and-serve.sh ${PORT} diff --git a/src/Config.elm b/src/Config.elm index c3df2b0..ecd263b 100644 --- a/src/Config.elm +++ b/src/Config.elm @@ -9,6 +9,7 @@ baseUrl = type ApiResource = ApiPosts | ApiLikePost String + | ApiCompose makeApiUrl : ApiResource -> String @@ -16,8 +17,11 @@ makeApiUrl res = String.replace "{baseUrl}" baseUrl <| case res of ApiPosts -> - "{baseUrl}/posts" + "{baseUrl}/posts?_sort=createdAt&_order=desc" ApiLikePost postId -> "{baseUrl}/posts/{postId}" |> String.replace "{postId}" postId + + ApiCompose -> + "{baseUrl}/posts" diff --git a/src/Main.elm b/src/Main.elm index d359eaa..bd145e0 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,4 +1,4 @@ -module Main exposing (..) +port module Main exposing (..) import Browser exposing (Document) import Browser.Navigation exposing (Key) @@ -10,7 +10,7 @@ import Json.Encode import Pages.Feed exposing (feedView) import Task import Time -import Types exposing (Author, DisplayableError(..), Model, Msg(..), Post) +import Types exposing (Author, Model, Msg(..), Post) import Url exposing (Url) @@ -27,14 +27,21 @@ main = +-- PORTS + + +port showAlert : String -> Cmd msg + + + -- INIT init : () -> Url -> Key -> ( Model, Cmd Msg ) init _ _ _ = - ( { posts = [] - , displayError = DNoError + ( { posts = Nothing , now = Time.millisToPosix 0 + , composeInputValue = "" } , Cmd.batch [ getPosts, Task.perform SetNowPosix Time.now ] ) @@ -57,26 +64,51 @@ update msg model = ( model, getPosts ) GotPosts (Result.Ok posts) -> - ( { model | posts = posts }, Cmd.none ) + ( { model | posts = Just posts }, Cmd.none ) GotPosts (Result.Err httpErr) -> - ( { model | displayError = DHttpError httpErr } - , Cmd.none + ( model + , showAlert (Debug.toString httpErr) ) LikePost post -> ( model, likePost post ) LikedPost (Result.Ok post) -> - ( { model | posts = replaceMatchingPost post model.posts } + ( { model | posts = Just <| replaceMatchingPost post (Maybe.withDefault [] model.posts) } , Cmd.none ) LikedPost (Result.Err httpErr) -> - ( { model | displayError = DHttpError httpErr } + ( model + , showAlert (Debug.toString httpErr) + ) + + ComposeInputChanged value -> + ( { model | composeInputValue = value }, Cmd.none ) + + ComposePost -> + ( model + , if String.length model.composeInputValue > 0 then + composePost model + + else + Cmd.none + ) + + ComposedPost (Result.Ok post) -> + ( { model + | posts = Maybe.map (\posts -> post :: posts) model.posts + , composeInputValue = "" + } , Cmd.none ) + ComposedPost (Result.Err httpErr) -> + ( model + , showAlert (Debug.toString httpErr) + ) + replaceMatchingPost : Post -> List Post -> List Post replaceMatchingPost post posts = @@ -116,6 +148,28 @@ likePost post = } +composePost : Model -> Cmd Msg +composePost model = + Http.post + { url = makeApiUrl Config.ApiCompose + , expect = Http.expectJson ComposedPost postDecoder + , body = + Http.jsonBody + (Json.Encode.object + [ ( "content", Json.Encode.string model.composeInputValue ) + , ( "createdAt", Json.Encode.int <| Time.posixToMillis model.now // 1000 ) + , ( "likes", Json.Encode.int 0 ) + , ( "author" + , Json.Encode.object + [ ( "id", Json.Encode.string "logged-in-user-id" ) + , ( "name", Json.Encode.string "Logged in user" ) + ] + ) + ] + ) + } + + subscriptions : Model -> Sub Msg subscriptions _ = Time.every 5000 SetNowPosix diff --git a/src/Pages/Feed.elm b/src/Pages/Feed.elm index a848200..583c097 100644 --- a/src/Pages/Feed.elm +++ b/src/Pages/Feed.elm @@ -3,10 +3,10 @@ module Pages.Feed exposing (..) import Css exposing (..) import DateFormat.Relative exposing (relativeTime) import Html.Styled exposing (..) -import Html.Styled.Attributes exposing (css) -import Html.Styled.Events exposing (onClick) +import Html.Styled.Attributes exposing (css, placeholder, type_, value) +import Html.Styled.Events exposing (onClick, onInput, onSubmit) import Time exposing (now) -import Types exposing (DisplayableError(..), Model, Msg(..), Post) +import Types exposing (Model, Msg(..), Post) styles = @@ -19,6 +19,7 @@ styles = css [ padding2 (px 15) (px 20) , backgroundColor (hex "#eee") + , marginBottom (px 10) ] , postTitle = css @@ -28,25 +29,18 @@ styles = [ marginRight (px 5) , cursor pointer ] + , composeForm = + css + [ displayFlex + , flexFlow2 row wrap + ] + , composeInput = + css + [ flex (int 1) + ] } -derrorToHtml : DisplayableError -> Html Msg -derrorToHtml derror = - case derror of - DNoError -> - text "" - - DHttpError httpErr -> - text <| Debug.toString httpErr - - -derrorDiv : Model -> Html Msg -derrorDiv model = - div [] - [ derrorToHtml model.displayError ] - - postDiv : Time.Posix -> Post -> Html Msg postDiv now post = div [ styles.post ] @@ -64,7 +58,26 @@ postDiv now post = postsDiv : Model -> Html Msg postsDiv model = - div [] <| List.map (postDiv model.now) model.posts + case model.posts of + Just posts -> + div [] <| List.map (postDiv model.now) posts + + Nothing -> + div [] [ text "Loading posts..." ] + + +composeDiv : Model -> Html Msg +composeDiv model = + form [ styles.composeForm, onSubmit ComposePost ] + [ input + [ styles.composeInput + , placeholder "Type a new post..." + , onInput ComposeInputChanged + , value model.composeInputValue + ] + [] + , button [ type_ "submit" ] [ text "➡️" ] + ] headerSection : Model -> Html msg @@ -77,8 +90,8 @@ headerSection _ = mainSection : Model -> Html Msg mainSection model = main_ [] <| - [ derrorDiv model - , postsDiv model + [ postsDiv model + , composeDiv model ] diff --git a/src/Types.elm b/src/Types.elm index 02e1257..3736bbf 100644 --- a/src/Types.elm +++ b/src/Types.elm @@ -5,8 +5,8 @@ import Time type alias Model = - { posts : List Post - , displayError : DisplayableError + { posts : Maybe (List Post) + , composeInputValue : String , now : Time.Posix } @@ -20,11 +20,10 @@ type Msg | GotPosts (Result Http.Error (List Post)) | LikePost Post | LikedPost (Result Http.Error Post) - - -type DisplayableError - = DHttpError Http.Error - | DNoError + -- COMPOSE POST + | ComposeInputChanged String + | ComposePost + | ComposedPost (Result Http.Error Post) type alias Author = diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..ccf508a --- /dev/null +++ b/src/index.html @@ -0,0 +1,22 @@ +<html> + +<head> + <meta charset="UTF-8"> + <title>Main</title> + <link rel="stylesheet" href="main.css"> + </link> + <script src="main.js"></script> +</head> + +<body> + <div id="app"></div> + <script> + var app = Elm.Main.init({ + node: document.getElementById("app") + }); + + app.ports.showAlert.subscribe((message) => window.alert(message)); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/vendor/main.css b/vendor/main.css new file mode 100644 index 0000000..9a39f0a --- /dev/null +++ b/vendor/main.css @@ -0,0 +1 @@ +body{font-family:Open Sans,Arial;color:#454545;font-size:16px;margin:2em auto;max-width:800px;padding:1em;line-height:1.4;text-align:justify}html.contrast body{color:#050505}html.contrast blockquote{color:#11151a}html.contrast blockquote:before{color:#262626}html.contrast a{color:#03f}html.contrast a:visited{color:#7d013e}html.contrast span.wr{color:#800}html.contrast span.mfw{color:#4d0000}@media screen and (prefers-color-scheme:light){html.inverted{background-color:#000}html.inverted body{color:#d9d9d9}html.inverted #contrast,html.inverted #invmode{color:#fff;background-color:#000}html.inverted blockquote{color:#d3c9be}html.inverted blockquote:before{color:#b8b8b8}html.inverted a{color:#00a2e7}html.inverted a:visited{color:#ca1a70}html.inverted span.wr{color:#d24637}html.inverted span.mfw{color:#b00000}html.inverted.contrast{background-color:#000}html.inverted.contrast body{color:#fff}html.inverted.contrast #contrast,html.inverted.contrast #invmode{color:#fff;background-color:#000}html.inverted.contrast blockquote{color:#f8f6f5}html.inverted.contrast blockquote:before{color:#e5e5e5}html.inverted.contrast a{color:#44c7ff}html.inverted.contrast a:visited{color:#e9579e}html.inverted.contrast span.wr{color:#db695d}html.inverted.contrast span.mfw{color:#ff0d0d}}@media (prefers-color-scheme:dark){html:not(.inverted){background-color:#000}html:not(.inverted) body{color:#d9d9d9}html:not(.inverted) #contrast,html:not(.inverted) #invmode{color:#fff;background-color:#000}html:not(.inverted) blockquote{color:#d3c9be}html:not(.inverted) blockquote:before{color:#b8b8b8}html:not(.inverted) a{color:#00a2e7}html:not(.inverted) a:visited{color:#ca1a70}html:not(.inverted) span.wr{color:#d24637}html:not(.inverted) span.mfw{color:#b00000}html:not(.inverted).contrast{background-color:#000}html:not(.inverted).contrast body{color:#fff}html:not(.inverted).contrast #contrast,html:not(.inverted).contrast #invmode{color:#fff;background-color:#000}html:not(.inverted).contrast blockquote{color:#f8f6f5}html:not(.inverted).contrast blockquote:before{color:#e5e5e5}html:not(.inverted).contrast a{color:#44c7ff}html:not(.inverted).contrast a:visited{color:#e9579e}html:not(.inverted).contrast span.wr{color:#db695d}html:not(.inverted).contrast span.mfw{color:#ff0d0d}html.inverted html{background-color:#fefefe}}a{color:#07a}a:visited{color:#941352}.noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}span.citneed{vertical-align:top;font-size:.7em;padding-left:.3em}small{font-size:.4em}p.st{margin-top:-1em}div.fancyPositioning div.picture-left{float:left;width:40%;overflow:hidden;margin-right:1em}div.fancyPositioning div.picture-left img{width:100%}div.fancyPositioning div.picture-left figure{margin:10px}div.fancyPositioning div.picture-left figure figcaption{font-size:.7em}div.fancyPositioning div.tleft{float:left;width:55%}div.fancyPositioning div.tleft p:first-child{margin-top:0}div.fancyPositioning:after{display:block;content:"";clear:both}ul li img{height:1em}blockquote{color:#456;margin-left:0;margin-top:2em;margin-bottom:2em}blockquote span{float:left;margin-left:1rem;padding-top:1rem}blockquote author{display:block;clear:both;font-size:.6em;margin-left:2.4rem;font-style:oblique}blockquote author:before{content:"- ";margin-right:1em}blockquote:before{font-family:Times New Roman,Times,Arial;color:#666;content:open-quote;font-size:2.2em;font-weight:600;float:left;margin-top:0;margin-right:.2rem;width:1.2rem}blockquote:after{content:"";display:block;clear:both}@media screen and (max-width:500px){body{text-align:left}div.fancyPositioning div.picture-left,div.fancyPositioning div.tleft{float:none;width:inherit}blockquote span{width:80%}blockquote author{padding-top:1em;width:80%;margin-left:15%}blockquote author:before{content:"";margin-right:inherit}}span.visited{color:#941352}span.visited-maroon{color:#85144b}span.wr{color:#c0392b;font-weight:600}button.cont-inv,span.wr{text-decoration:underline}button.cont-inv{cursor:pointer;border-radius:2px;position:fixed;right:10px;font-size:.8em;border:0;padding:2px 5px}#contrast{color:#000;top:10px}#contrast,#invmode{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#invmode{color:#fff;background-color:#000;position:fixed;top:34px;text-decoration:underline}@media screen and (max-width:1080px){#contrast,#invmode{position:absolute}}span.sb{color:#00e}span.sb,span.sv{cursor:not-allowed}span.sv{color:#551a8b}span.foufoufou{color:#444;font-weight:700}span.foufoufou:before{content:"";display:inline-block;width:1em;height:1em;margin-left:.2em;margin-right:.2em;background-color:#444}span.foufivfoufivfoufiv{color:#454545;font-weight:700}span.foufivfoufivfoufiv:before{content:"";display:inline-block;width:1em;height:1em;margin-left:.2em;margin-right:.2em;background-color:#454545}span.mfw{color:#730000}a.kopimi,a.kopimi img.kopimi{display:block;margin-left:auto;margin-right:auto}a.kopimi img.kopimi{height:2em}p.fakepre{font-family:monospace;font-size:.9em}
\ No newline at end of file |
