summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJan Tuomi <jan.tuomi@valuemotive.com>2021-10-29 16:52:15 +0300
committerJan Tuomi <jan.tuomi@valuemotive.com>2021-10-29 16:52:15 +0300
commite500394ed975055657fde19d3cd1c106888734b5 (patch)
tree56990da5fd026bc6321fef3bba113b0e486cc216
parenta972058d20b773f4488df51babb40156c5a335e1 (diff)
Implement stuff
-rw-r--r--db.json62
-rwxr-xr-xscripts/_build-and-serve.sh4
-rwxr-xr-xscripts/build-prod.sh6
-rwxr-xr-xscripts/debug.sh5
-rw-r--r--src/Config.elm6
-rw-r--r--src/Main.elm72
-rw-r--r--src/Pages/Feed.elm57
-rw-r--r--src/Types.elm13
-rw-r--r--src/index.html22
-rw-r--r--vendor/main.css1
10 files changed, 202 insertions, 46 deletions
diff --git a/db.json b/db.json
index e91b02e..5e4635a 100644
--- a/db.json
+++ b/db.json
@@ -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