Skip to content

Instantly share code, notes, and snippets.

@rtfeldman
Created June 22, 2017 20:49
Show Gist options
  • Save rtfeldman/5b680c64be829c6dbd78c073c269d313 to your computer and use it in GitHub Desktop.
Save rtfeldman/5b680c64be829c6dbd78c073c269d313 to your computer and use it in GitHub Desktop.
module Views.Page exposing (ActivePage(..), bodyId, frame)
{-| The frame around a typical page - that is, the header and footer.
-}
import Color
import Data.User as User exposing (User, Username)
import Data.UserPhoto as UserPhoto exposing (UserPhoto)
import Element exposing (Element, el)
import Element.Attributes exposing (alignBottom, alignLeft, justify, paddingXY, spacing, spacingXY, verticalCenter)
import Html
import Html.Attributes exposing (..)
import Html.Lazy exposing (lazy2)
import Route exposing (Route)
import Style exposing (..)
import Style.Border as Border
import Style.Color as Color
import Style.Font as Font
import Style.Transition as Transition
import Util exposing ((=>))
import Views.Spinner exposing (spinner)
{-| Determines which navbar link (if any) will be rendered as active.
Note that we don't enumerate every page here, because the navbar doesn't
have links for every page. Anything that's not part of the navbar falls
under Other.
-}
type ActivePage
= Other
| Home
| Login
| Register
| Settings
| Profile Username
| NewArticle
{-| Take a page's Html and frame it with a header and footer.
The caller provides the current user, so we can display in either
"signed in" (rendering username) or "signed out" mode.
isLoading is for determining whether we should show a loading spinner
in the header. (This comes up during slow page transitions.)
-}
frame : Bool -> Maybe User -> ActivePage -> Html msg -> Html msg
frame isLoading user page content =
div [ class "page-frame" ]
[ viewHeader page user isLoading
, content
, viewFooter
]
viewHeader : ActivePage -> Maybe User -> Bool -> Html msg
viewHeader page user isLoading =
nav [ class "navbar navbar-light" ]
[ div [ class "container" ]
[ a [ class "navbar-brand", Route.href Route.Home ]
[ text "conduit" ]
, ul [ class "nav navbar-nav pull-xs-right" ] <|
lazy2 Util.viewIf isLoading spinner
:: navbarLink (page == Home) Route.Home [ text "Home" ]
:: viewSignIn page user
]
]
viewSignIn : ActivePage -> Maybe User -> List (Html msg)
viewSignIn page user =
case user of
Nothing ->
[ navbarLink (page == Login) Route.Login [ text "Sign in" ]
, navbarLink (page == Register) Route.Register [ text "Sign up" ]
]
Just user ->
[ navbarLink (page == NewArticle) Route.NewArticle [ i [ class "ion-compose" ] [], text " New Post" ]
, navbarLink (page == Settings) Route.Settings [ i [ class "ion-gear-a" ] [], text " Settings" ]
, navbarLink
(page == Profile user.username)
(Route.Profile user.username)
[ img [ class "user-pic", UserPhoto.src user.image ] []
, User.usernameToHtml user.username
]
, navbarLink False Route.Logout [ text "Sign out" ]
]
type alias Elem msg =
Html msg
row =
Debug.crash "TODO"
link =
Debug.crash "TODO"
paragraph =
Debug.crash "TODO"
unstyled =
Debug.crash "TODO"
style =
Debug.crash "TODO"
text =
Debug.crash "TODO"
-- By default, name the styled *element* rather than the style itself.
-- If a style will be reused across different elements, name the reusable style.
logoLink : String -> Elem msg -> Elem msg
logoLink =
link <|
style
[ Color.text (Color.rgb 92 184 92)
, Font.typeface [ "Titillium Web", "sans-serif" ]
, Font.size 16
]
attributionParagraph : List (Elem msg) -> Elem msg
attributionParagraph =
paragraph [] <|
style [ Color.text (Color.rgb 187 187 187) ]
footerRow : List (Elem msg) -> Elem msg
footerRow =
let
footerStyle =
Style.style
[ Color.background (Color.rgb 243 243 243)
, Font.typeface [ "Source Sans Pro", "sans-serif" ]
, Font.size 12
]
in
Element.footer footerStyle [ verticalCenter, spacing 10, paddingXY 77.5 21 ]
<style>.acb234 { } </style>
button [ onClick Foo ] [ text "blah" ]
button Foo [] [ text "blah" ]
footerLink : String -> Elem msg -> Elem msg
footerLink =
link <|
style
[ Color.text (Color.rgb 92 184 92)
, Font.typeface [ "Titillium Web", "sans-serif" ]
]
plainLink : String -> Elem msg -> Elem msg
plainLink =
link unstyled
viewFooter : Elem msg
viewFooter =
footerRow <|
[ text "conduit" |> logoLink "/"
, attributionParagraph
[ text "An interactive learning project from "
, text "Thinkster" |> footerLink "https://thinkster.io"
, text ". Code & design licensed under MIT."
]
]
viewFooter2 =
row Footer
[ verticalCenter, spacing 10, paddingXY 77.5 21 ]
[ link "/" (el Logo [] (text "conduit"))
, paragraph Attribution
[]
[ text "An interactive learning project from "
, link "https://thinkster.io" <|
el Link [] (text "Thinkster")
, text ". Code & design licensed under MIT."
]
]
-- `variation` can work like `classList` - give it a list of ( Style, Bool )
-- pairs. It will always generate the class for the Style, but it will either
-- include it (or not) in the element's `class` atrtibute based on the `Bool`.
--
--
--
-- logoStyle : Element msg -> Element msg
-- logoStyle =
-- style
-- [ Color.text (Color.rgb 92 184 92)
-- , Font.typeface [ "Titillium Web", "sans-serif" ]
-- , Font.size 16
-- ]
--
--
-- footerStyle : Element msg -> Element msg
-- footerStyle =
-- style
-- [ Color.background (Color.rgb 243 243 243)
-- , Font.typeface [ "Source Sans Pro", "sans-serif" ]
-- , Font.size 12
-- ]
--
-- Design note: each Element contains a Dict of styles, keyed on hash of css.
-- When using an element combinator, or applying children to a given element,
-- those dicts get unioned together (ignoring duplicates). In this way,
-- once we get to the root, we just have all the styles already and can go for it.
-- Can use a checksum to avoid regenerating <style>.
--
-- NOTE: for semantic purposes, there'd be no reason ever to use <button> -
-- except that we know things about buttons! They always have onClick. Same way
-- <input> always has onInput and defaultValue [hope hope hope it's value later]
-- So why not have the button API be: button : msg -> Element msg -> Element msg
-- and do the onClick automatically? EXACTLY! Do that! Now it's more convenient
-- than using <el> or whatever. And it's more semantic! Wins all around. Also,
-- if we detect that a <button> is inside a <form>, we should not submit
-- that <form> automatically.
--
-- OTHER questions: what about tabIndex? Is there some cool way we can do that
-- automatically? Seems unlikely, unfortunately.
-- TODO make it so on /settings the footer is glued to the bottom of the page, even if there's no content in between
type Styles
= None
| Footer
| Logo
| Attribution
| Link
| ActiveNavItem
| NavItem
stylesheet : StyleSheet Styles variation
stylesheet =
Style.stylesheet
[ Style.style None []
, Style.style Logo
[ Color.text (Color.rgb 92 184 92)
, Font.typeface [ "Titillium Web", "sans-serif" ]
, Font.size 16
]
, Style.style Footer
[ Color.background (Color.rgb 243 243 243)
, Font.typeface [ "Source Sans Pro", "sans-serif" ]
, Font.size 12
]
, Style.style Attribution
[ Color.text (Color.rgb 187 187 187) ]
, Style.style Link
[ Color.text (Color.rgb 92 184 92)
, cursor "pointer"
, hover
[ Color.text (Color.rgb 61 139 61)
]
]
]
navbarLink : Bool -> Route -> Element Styles variation msg -> Element Styles variation msg
navbarLink isActive route linkContent =
let
activeStyle =
if isActive then
ActiveNavItem
else
NavItem
in
Element.link (Route.toUrl route) <|
el activeStyle [] linkContent
navbarLink2 : Bool -> Route -> List (Html msg) -> Html msg
navbarLink2 isActive route linkContent =
li [ classList [ ( "nav-item", True ), ( "active", isActive ) ] ]
[ a [ class "nav-link", Route.href route ] linkContent ]
{-| This id comes from index.html.
The Feed uses it to scroll to the top of the page (by ID) when switching pages
in the pagination sense.
-}
bodyId : String
bodyId =
"page-body"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment