From 270123cc18ee8acff4b2819f4a609045bd5194f5 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Thu, 4 Nov 2021 18:06:11 +0200 Subject: Add navigation and profile --- src/Header.elm | 12 ++++++++---- src/Main.elm | 47 ++++++++++++++++++++++++++++++++++++++++------- src/Pages/Feed.elm | 28 +++++++++++----------------- src/Pages/Profile.elm | 25 +++++++++++++++++++++++++ src/Routes.elm | 12 ++++++++++++ src/Types.elm | 15 +++++++++++++++ 6 files changed, 111 insertions(+), 28 deletions(-) create mode 100644 src/Pages/Profile.elm create mode 100644 src/Routes.elm (limited to 'src') diff --git a/src/Header.elm b/src/Header.elm index 257b308..36469aa 100644 --- a/src/Header.elm +++ b/src/Header.elm @@ -1,8 +1,8 @@ module Header exposing (..) import Css exposing (..) -import Html.Styled exposing (Html, button, div, text) -import Html.Styled.Attributes exposing (css) +import Html.Styled exposing (..) +import Html.Styled.Attributes exposing (css, href) import Html.Styled.Events exposing (onClick) import Types exposing (Model, Msg(..)) import Utils exposing (templ) @@ -14,6 +14,7 @@ styles = [ displayFlex , flexFlow2 row wrap , justifyContent flexEnd + , alignItems center , width (pct 100) , marginBottom (px 10) ] @@ -22,13 +23,16 @@ styles = [ marginLeft (px 10) , cursor pointer ] + , flexPad = css [ flex (int 1) ] } headerView : Model -> List (Html Msg) headerView model = - [ div [ styles.container ] - [ div [] [ "Logged in as {0}" |> templ [ model.userData.name ] |> text ] + [ header [ styles.container ] + [ a [ href "/" ] [ h1 [] [ text "😎 SOMEBOOK" ] ] + , div [ styles.flexPad ] [] + , div [] [ "Logged in as {0}" |> templ [ model.userData.name ] |> text ] , button [ styles.logoutButton, onClick RequestLogout ] [ text "Logout" ] ] ] diff --git a/src/Main.elm b/src/Main.elm index 32cd992..35cf413 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -1,7 +1,7 @@ port module Main exposing (..) import Browser exposing (Document) -import Browser.Navigation exposing (Key) +import Browser.Navigation as Nav import Config exposing (makeApiUrl) import Header exposing (headerView) import Html.Styled exposing (..) @@ -10,11 +10,14 @@ import Json.Decode exposing (Decoder, field, int, list, map2, map7, maybe, strin import Json.Encode import Json.Encode.Extra import Pages.Feed exposing (feedView) +import Pages.Profile exposing (profileView) import RemoteData +import Routes exposing (routeParser) import Task import Time -import Types exposing (Author, Flags, Model, Msg(..), Post) +import Types exposing (Author, Flags, Model, Msg(..), Post, Route(..)) import Url exposing (Url) +import Url.Parser exposing ((), Parser, oneOf, s) import Utils exposing (httpErrorToString, listFlat) @@ -25,8 +28,8 @@ main = , view = view , update = update , subscriptions = subscriptions - , onUrlRequest = \_ -> NoOp - , onUrlChange = \_ -> NoOp + , onUrlRequest = LinkClicked + , onUrlChange = UrlChanged } @@ -44,13 +47,14 @@ port requestLogout : () -> Cmd msg -- INIT -init : Flags -> Url -> Key -> ( Model, Cmd Msg ) -init flags _ key = +init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg ) +init flags url key = let initModel : Model initModel = { now = Time.millisToPosix 0 , key = key + , url = url , userData = flags.userData , apiURL = flags.apiURL , posts = RemoteData.Loading @@ -82,6 +86,18 @@ update msg model = RequestLogout -> ( model, requestLogout () ) + LinkClicked urlRequest -> + case urlRequest of + Browser.Internal url -> + ( model, Nav.pushUrl model.key (Url.toString url) ) + + Browser.External href -> + ( model, Nav.load href ) + + UrlChanged url -> + ( { model | url = url }, Cmd.none ) + + -- BUSINESS LOGIC GetPosts -> ( model, getPosts model ) @@ -251,11 +267,28 @@ postDecoder = -- VIEW +urlToView : Url -> (Model -> List (Html Msg)) +urlToView url = + let + route = + Url.Parser.parse routeParser url + in + case route of + Just Index -> + feedView + + Just (Profile id) -> + profileView id + + Nothing -> + feedView + + view : Model -> Document Msg view model = { title = "SOMEBOOK" , body = - [ headerView model, feedView model ] + [ headerView model, urlToView model.url model ] |> listFlat |> List.map toUnstyled } diff --git a/src/Pages/Feed.elm b/src/Pages/Feed.elm index 53f60a2..a7c2767 100644 --- a/src/Pages/Feed.elm +++ b/src/Pages/Feed.elm @@ -3,13 +3,14 @@ module Pages.Feed exposing (..) import Css exposing (..) import DateFormat.Relative exposing (relativeTime) import Html.Styled exposing (..) -import Html.Styled.Attributes exposing (css, placeholder, src, type_, value) +import Html.Styled.Attributes exposing (css, href, placeholder, src, type_, value) import Html.Styled.Events exposing (onClick, onInput, onSubmit) import Html.Styled.Keyed as Keyed import Html.Styled.Lazy exposing (lazy) import RemoteData exposing (RemoteData(..)) import Time exposing (now) import Types exposing (Model, Msg(..), Post) +import Utils exposing (templ) styles = @@ -72,12 +73,14 @@ postDiv now post = in div [ styles.post ] - [ span [] - [ img [ styles.postProfilePicture, src post.userPictureUrl ] [] ] - , span [ styles.postHeader ] - [ h3 [ styles.postTitle ] [ text post.author.name ] - , i [] - [ text <| relativeTime now post.createdAt + [ a [ href <| templ [ post.author.id ] "/profile/{0}" ] + [ span [] + [ img [ styles.postProfilePicture, src post.userPictureUrl ] [] ] + , span [ styles.postHeader ] + [ h3 [ styles.postTitle ] [ text post.author.name ] + , i [] + [ text <| relativeTime now post.createdAt + ] ] ] , p [] [ text post.content ] @@ -131,13 +134,6 @@ composeDiv model = ] -headerSection : Model -> Html msg -headerSection _ = - header [] - [ h1 [] [ text "😎 SOMEBOOK" ] - ] - - mainSection : Model -> Html Msg mainSection model = main_ [] <| @@ -149,9 +145,7 @@ mainSection model = body : Model -> Html Msg body model = div [ styles.page ] - [ headerSection model - , mainSection model - ] + [ mainSection model ] feedView : Model -> List (Html Msg) diff --git a/src/Pages/Profile.elm b/src/Pages/Profile.elm new file mode 100644 index 0000000..a564b3e --- /dev/null +++ b/src/Pages/Profile.elm @@ -0,0 +1,25 @@ +module Pages.Profile exposing (..) + +import Css exposing (marginBottom, px) +import Html.Styled exposing (..) +import Html.Styled.Attributes exposing (css) +import Types exposing (Model, Msg, UrlSegmentUserId) + + +styles = + { container = + css + [ marginBottom (px 10) + ] + } + + +body : Model -> UrlSegmentUserId -> Html Msg +body _ id = + div [ styles.container ] + [ text id ] + + +profileView : UrlSegmentUserId -> Model -> List (Html Msg) +profileView userId model = + [ body model userId ] diff --git a/src/Routes.elm b/src/Routes.elm new file mode 100644 index 0000000..04c7eb1 --- /dev/null +++ b/src/Routes.elm @@ -0,0 +1,12 @@ +module Routes exposing (..) + +import Types exposing (Route(..)) +import Url.Parser exposing ((), Parser, oneOf, s, string, top) + + +routeParser : Parser (Route -> a) a +routeParser = + oneOf + [ Url.Parser.map Index top + , Url.Parser.map Profile (s "profile" string) + ] diff --git a/src/Types.elm b/src/Types.elm index f10223e..aeb7d67 100644 --- a/src/Types.elm +++ b/src/Types.elm @@ -1,14 +1,17 @@ module Types exposing (..) +import Browser exposing (UrlRequest) import Browser.Navigation exposing (Key) import Http import RemoteData exposing (RemoteData) import Time +import Url exposing (Url) type alias Model = { now : Time.Posix , key : Key + , url : Url , userData : UserData , apiURL : String , posts : RemoteData (List Post) @@ -35,6 +38,9 @@ type Msg | RequestLogout -- TIME | SetNowPosix Time.Posix + -- NAVIGATION + | UrlChanged Url + | LinkClicked UrlRequest -- POSTS | GetPosts | GotPosts (Result Http.Error (List Post)) @@ -47,12 +53,21 @@ type Msg | ComposedPost (Result Http.Error Post) +type Route + = Index + | Profile UrlSegmentUserId + + type alias Author = { id : String , name : String } +type alias UrlSegmentUserId = + String + + type alias Post = { id : String , content : String -- cgit v1.3