From ad9bd2db95f4cf95ed23d123976b2a72c38f7ba0 Mon Sep 17 00:00:00 2001 From: Correl Date: Sat, 23 Jan 2021 00:03:27 -0500 Subject: [PATCH] Add dark mode --- assets/js/app.js | 23 +++- assets/src/Main.elm | 26 ++++- assets/src/PlanningPokerAPI.elm | 15 +++ assets/src/PlanningPokerEntry.elm | 39 ++++--- assets/src/PlanningPokerRoom.elm | 61 +++++----- assets/src/PlanningPokerUI.elm | 185 ++++++++++++++++++++++-------- 6 files changed, 252 insertions(+), 97 deletions(-) diff --git a/assets/js/app.js b/assets/js/app.js index 9eefc6f..417e474 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -24,13 +24,30 @@ var room_id = uuid4() var socket = new Socket("/socket", {params: {player_id: player_id}}) socket.connect() +function getCookie(cname) { + var name = cname + "="; + var decodedCookie = decodeURIComponent(document.cookie); + var ca = decodedCookie.split(';'); + for(var i = 0; i { channel.push(action.type, action.data) }) + // Theme changes + app.ports.saveTheme.subscribe(theme => { + document.cookie = "theme=" + theme + }) channel.join() .receive("ok", resp => { console.log("Joined successfully", resp); diff --git a/assets/src/Main.elm b/assets/src/Main.elm index 7593fd9..6b66934 100644 --- a/assets/src/Main.elm +++ b/assets/src/Main.elm @@ -7,6 +7,7 @@ import Html import PlanningPokerEntry as Entry import PlanningPokerNotFound as NotFound import PlanningPokerRoom as Room +import PlanningPokerUI as UI import Url exposing (Url) import Url.Parser as Parser exposing ((), Parser, s, string) @@ -16,6 +17,7 @@ type alias Flags = , room : String , height : Int , width : Int + , theme : String } @@ -24,6 +26,7 @@ type alias Model = , key : Nav.Key , player : String , room : String + , theme : UI.Theme , dimensions : { width : Int, height : Int } } @@ -48,12 +51,25 @@ type Msg init : Flags -> Url -> Nav.Key -> ( Model, Cmd Msg ) -init { player, room, width, height } url key = +init { player, room, width, height, theme } url key = + let + parseTheme themeString = + case String.toLower themeString of + "light" -> + UI.Light + + "dark" -> + UI.Dark + + _ -> + UI.Light + in updateUrl url { page = NotFound , key = key , player = player , room = room + , theme = parseTheme theme , dimensions = { width = width, height = height } } @@ -104,14 +120,15 @@ updateUrl : Url -> Model -> ( Model, Cmd Msg ) updateUrl url model = case Parser.parse parser url of Just Entry -> - toEntry model (Entry.init model.room) + toEntry model (Entry.init model.theme model.room) Just (Room id) -> case model.page of EntryPage entryModel -> toRoom model (Room.init - { id = id + { theme = entryModel.theme + , id = id , player = model.player , roomName = "Planning Poker" , playerName = entryModel.playerName @@ -121,7 +138,8 @@ updateUrl url model = _ -> toRoom model (Room.init - { id = id + { theme = model.theme + , id = id , player = model.player , roomName = "Planning Poker" , playerName = "" diff --git a/assets/src/PlanningPokerAPI.elm b/assets/src/PlanningPokerAPI.elm index eae7f3a..c8aca69 100644 --- a/assets/src/PlanningPokerAPI.elm +++ b/assets/src/PlanningPokerAPI.elm @@ -8,11 +8,13 @@ port module PlanningPokerAPI exposing , newProfile , reset , reveal + , updateTheme , vote ) import Json.Decode as Decode import Json.Encode as Encode +import PlanningPokerUI as UI type RoomAction @@ -28,6 +30,9 @@ port joinRoom : { room : String } -> Cmd msg port roomActions : Encode.Value -> Cmd msg +port saveTheme : String -> Cmd msg + + newProfile : { playerName : String } -> Cmd msg newProfile = NewProfile >> encodeAction >> roomActions @@ -74,6 +79,16 @@ encodeAction action = wrap "reveal" (Encode.object []) +updateTheme : UI.Theme -> Cmd msg +updateTheme theme = + case theme of + UI.Light -> + saveTheme "light" + + UI.Dark -> + saveTheme "dark" + + port gotPresenceState : (Decode.Value -> msg) -> Sub msg diff --git a/assets/src/PlanningPokerEntry.elm b/assets/src/PlanningPokerEntry.elm index 105cf88..22aafd5 100644 --- a/assets/src/PlanningPokerEntry.elm +++ b/assets/src/PlanningPokerEntry.elm @@ -13,7 +13,8 @@ import PlanningPokerUI as UI type alias Model = - { room : String + { theme : UI.Theme + , room : String , playerName : String , player : Maybe String , error : Maybe String @@ -22,12 +23,14 @@ type alias Model = type Msg = PlayerNameChanged String + | UpdateTheme UI.Theme | CreateRoom -init : String -> ( Model, Cmd Msg ) -init room = - ( { room = room +init : UI.Theme -> String -> ( Model, Cmd Msg ) +init theme room = + ( { theme = theme + , room = room , playerName = "" , player = Nothing , error = Nothing @@ -49,40 +52,48 @@ update key msg model = else ( model, Cmd.none ) + UpdateTheme theme -> + ( { model | theme = theme }, API.updateTheme theme ) + view : Model -> Document Msg view model = - { title = "Planning Poker" - , body = [ layout model ] - } + UI.toDocument model.theme + { title = "Planning Poker" + , body = [ layout model ] + } -layout : Model -> Html Msg +layout : Model -> Element Msg layout model = - Element.layout [] <| - column + column [ width fill, height fill, centerY ] + [ column [ width fill, centerY, spacing 30 ] [ el [ centerX ] (text "Oh, hey!") , el [ centerX ] (text "Tell us who you are") - , Input.text [ centerX, width (px 300), UI.onEnter CreateRoom ] + , UI.textInput model.theme + [ centerX, width (px 300), UI.onEnter CreateRoom ] { onChange = PlayerNameChanged , text = model.playerName , label = Input.labelHidden "Your name" , placeholder = Just (Input.placeholder [] (text "Your name")) } , el [ centerX ] (text "then") - , UI.actionButton [ centerX ] + , UI.actionButton model.theme + [ centerX ] { isActive = not (String.isEmpty model.playerName) , onPress = CreateRoom , label = text "Make a room!" } , el [ centerX - , Background.color UI.colors.errorBackground + , Background.color (UI.colors model.theme).errorBackground , padding 20 - , Font.color UI.colors.errorForeground + , Font.color (UI.colors model.theme).errorForeground , transparent (model.error == Nothing) ] <| text (Maybe.withDefault " " model.error) ] + , column [] [ UI.themePicker model.theme UpdateTheme ] + ] diff --git a/assets/src/PlanningPokerRoom.elm b/assets/src/PlanningPokerRoom.elm index 7479a82..2b0af23 100644 --- a/assets/src/PlanningPokerRoom.elm +++ b/assets/src/PlanningPokerRoom.elm @@ -22,7 +22,8 @@ import PlanningPokerUI as UI type alias Model = - { state : State + { theme : UI.Theme + , state : State , room : Room , player : String , playerName : String @@ -45,6 +46,7 @@ type Msg | GotVote (Result Decode.Error ReceivedVote) | GotReveal | GotReset + | UpdateTheme UI.Theme type Presence @@ -89,13 +91,14 @@ type Vote init : - { id : String + { theme : UI.Theme + , id : String , player : String , roomName : String , playerName : String } -> ( Model, Cmd Msg ) -init { id, player, roomName, playerName } = +init { theme, id, player, roomName, playerName } = let room = { id = id @@ -115,7 +118,8 @@ init { id, player, roomName, playerName } = ] ) in - ( { room = room + ( { theme = theme + , room = room , state = state , player = player , playerName = playerName @@ -217,6 +221,9 @@ update key msg model = , Cmd.none ) + UpdateTheme theme -> + ( { model | theme = theme }, API.updateTheme theme ) + view : { height : Int, width : Int } -> Model -> Document Msg view dimensions model = @@ -231,20 +238,22 @@ view dimensions model = in case model.state of Playing -> - UI.toDocument + UI.toDocument model.theme { title = model.room.name , body = - [ navBar { title = model.room.name, playerName = playerName } + [ navBar model.theme { title = model.room.name, playerName = playerName } , viewRoom device model + , UI.themePicker model.theme UpdateTheme ] } Joining -> - UI.toDocument + UI.toDocument model.theme { title = model.room.name , body = - [ navBar { title = model.room.name, playerName = playerName } - , joinForm model.room model.playerName + [ navBar model.theme { title = model.room.name, playerName = playerName } + , joinForm model.theme model.room model.playerName + , UI.themePicker model.theme UpdateTheme ] } @@ -258,7 +267,7 @@ viewRoom device model = in case device.class of Phone -> - column [ width fill, spacing 20 ] + column [ width fill, spacing 20, centerY ] [ viewPlayers (Dict.values model.room.players) model.showVotes , el [ width (fillPortion 3), alignTop ] <| viewCards model myVote @@ -266,7 +275,7 @@ viewRoom device model = ] _ -> - column [ width fill, spacing 20 ] + column [ width fill, spacing 20, centerY, alignTop ] [ row [ width fill ] [ el [ width (fillPortion 3), alignTop ] <| @@ -279,23 +288,16 @@ viewRoom device model = ] -navBar : { title : String, playerName : String } -> Element Msg -navBar { title, playerName } = - row - [ Background.color UI.colors.primary - , height (px 50) - , width fill - , padding 10 - ] +navBar : UI.Theme -> { title : String, playerName : String } -> Element Msg +navBar theme { title, playerName } = + UI.navBar theme [ el [ Font.alignLeft - , Font.color UI.colors.background , width fill ] (text title) , el [ Font.alignRight - , Font.color UI.colors.background ] (text playerName) ] @@ -309,10 +311,10 @@ viewCards model selected = selectedColor = if enabled then - UI.colors.selected + (UI.colors model.theme).selected else - UI.colors.disabled + (UI.colors model.theme).disabled card value = Input.button @@ -328,7 +330,7 @@ viewCards model selected = selectedColor else - UI.colors.background + (UI.colors model.theme).background , Font.size 50 ] { onPress = @@ -405,13 +407,13 @@ viewPlayers playerList showVotes = moderatorTools : Model -> Element Msg moderatorTools model = row [ centerX, spacing 20 ] - [ UI.actionButton + [ UI.actionButton model.theme [ centerX ] { isActive = not model.showVotes , onPress = Reveal , label = text "Reveal" } - , UI.actionButton + , UI.actionButton model.theme [ centerX ] { isActive = True , onPress = Reset @@ -420,8 +422,8 @@ moderatorTools model = ] -joinForm : Room -> String -> Element Msg -joinForm room playerName = +joinForm : UI.Theme -> Room -> String -> Element Msg +joinForm theme room playerName = let players = Dict.values room.players @@ -441,7 +443,8 @@ joinForm room playerName = , label = Input.labelHidden "Your name" , placeholder = Just (Input.placeholder [] (text "Your name")) } - , UI.actionButton [ centerX ] + , UI.actionButton theme + [ centerX ] { isActive = not (String.isEmpty playerName) , onPress = JoinRoom , label = text "Join!" diff --git a/assets/src/PlanningPokerUI.elm b/assets/src/PlanningPokerUI.elm index b40f643..7f9d50f 100644 --- a/assets/src/PlanningPokerUI.elm +++ b/assets/src/PlanningPokerUI.elm @@ -1,9 +1,13 @@ module PlanningPokerUI exposing - ( actionButton + ( Theme(..) + , actionButton , colors , fontSizes , heroText + , navBar , onEnter + , textInput + , themePicker , toDocument ) @@ -16,20 +20,63 @@ import Html.Events import Json.Decode as Decode -colors = +type Theme + = Light + | Dark + + +colors theme = let - primary = - blue + blue : Color + blue = + rgb255 100 100 255 + + lightGrey : Color + lightGrey = + rgb255 200 200 200 + + grey : Color + grey = + rgb255 50 50 50 + + darkGrey : Color + darkGrey = + rgb255 20 20 20 + + red : Color + red = + rgb255 255 100 100 + + white : Color + white = + rgb255 255 255 255 + + black : Color + black = + rgb255 0 0 0 in - { primary = primary - , background = white - , selected = primary - , disabled = lightGrey - , errorBackground = red - , errorForeground = white - , text = black - , buttonText = white - } + case theme of + Light -> + { primary = blue + , background = white + , selected = blue + , disabled = lightGrey + , errorBackground = red + , errorForeground = white + , text = black + , buttonText = white + } + + Dark -> + { primary = blue + , background = darkGrey + , selected = blue + , disabled = grey + , errorBackground = red + , errorForeground = white + , text = white + , buttonText = lightGrey + } fontSizes = @@ -39,56 +86,58 @@ fontSizes = } -blue : Color -blue = - rgb255 100 100 255 +textInput : + Theme + -> List (Attribute msg) + -> + { onChange : String -> msg + , text : String + , label : Input.Label msg + , placeholder : Maybe (Input.Placeholder msg) + } + -> Element msg +textInput theme attrs { onChange, text, label, placeholder } = + Input.text + (Font.color (colors theme).text + :: Background.color (colors theme).background + :: attrs + ) + { onChange = onChange, text = text, label = label, placeholder = placeholder } -lightGrey : Color -lightGrey = - rgb255 200 200 200 - - -red : Color -red = - rgb255 255 100 100 - - -white : Color -white = - rgb255 255 255 255 - - -black : Color -black = - rgb255 0 0 0 - - -actionButton : - List (Attribute msg) +button : + Theme + -> List (Attribute msg) -> { isActive : Bool, onPress : msg, label : Element msg } -> Element msg -actionButton attrs { isActive, onPress, label } = +button theme attrs { isActive, onPress, label } = let ( color, maybeEvent ) = if isActive then - ( blue, Just onPress ) + ( (colors theme).primary, Just onPress ) else - ( lightGrey, Nothing ) + ( (colors theme).disabled, Nothing ) in Input.button - ([ padding 20 - , Background.color color - , Font.color white - ] - ++ attrs + (Background.color color + :: Font.color (colors theme).buttonText + :: attrs ) { onPress = maybeEvent , label = label } +actionButton : + Theme + -> List (Attribute msg) + -> { isActive : Bool, onPress : msg, label : Element msg } + -> Element msg +actionButton theme attrs opts = + button theme (padding 20 :: attrs) opts + + heroText : List (Attribute msg) -> String @@ -97,11 +146,49 @@ heroText attrs s = el ([ Font.size fontSizes.huge ] ++ attrs) (text s) -toDocument : { title : String, body : List (Element msg) } -> Document msg -toDocument { title, body } = +navBar : Theme -> List (Element msg) -> Element msg +navBar theme elements = + row + [ Background.color (colors theme).primary + , Font.color (colors theme).buttonText + , height (px 50) + , width fill + , padding 10 + ] + elements + + +themePicker : Theme -> (Theme -> msg) -> Element msg +themePicker theme onChange = + row [] + [ case theme of + Light -> + button theme + [ padding 5 ] + { isActive = True + , onPress = onChange Dark + , label = text "Dark mode 🌙" + } + + Dark -> + button theme + [ padding 5 ] + { isActive = True + , onPress = onChange Light + , label = text "Light mode 🌞" + } + ] + + +toDocument : Theme -> { title : String, body : List (Element msg) } -> Document msg +toDocument theme { title, body } = { title = title , body = - [ layout [] <| + [ layout + [ Font.color (colors theme).text + , Background.color (colors theme).background + ] + <| column [ width fill, height fill, spacing 20 ] body ] }