Add dark mode

This commit is contained in:
Correl Roush 2021-01-23 00:03:27 -05:00
parent 129f249ca2
commit ad9bd2db95
6 changed files with 252 additions and 97 deletions

View file

@ -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 <ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
var app = Elm.Main.init({
node: document.getElementById("elm-main"),
flags: {
player: player_id,
room: room_id,
width: window.innerWidth,
height: window.innerHeight
height: window.innerHeight,
theme: getCookie("theme")
}
})
@ -51,6 +68,10 @@ app.ports.joinRoom.subscribe(options => {
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);

View file

@ -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 = ""

View file

@ -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

View file

@ -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 ]
]

View file

@ -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!"

View file

@ -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
]
}