Create separate Program module
parent
bf8e33ee1c
commit
ecc69034f3
107
elm/Main.elm
107
elm/Main.elm
|
|
@ -1,22 +1,23 @@
|
||||||
module Main exposing (main)
|
module Main exposing (main)
|
||||||
|
|
||||||
import Api
|
|
||||||
import Browser
|
|
||||||
import Element exposing (Element)
|
import Element exposing (Element)
|
||||||
import Element.Background
|
|
||||||
import GameList exposing (Game, GameList)
|
import GameList exposing (Game, GameList)
|
||||||
import Http
|
import Json.Decode as D
|
||||||
import ScreenSize exposing (ScreenSize)
|
import Layout
|
||||||
import Theme
|
import Material.Icons as Icons
|
||||||
|
import Program
|
||||||
|
import Widget.Icon
|
||||||
|
|
||||||
|
|
||||||
main : Program () Model Msg
|
|
||||||
main =
|
main =
|
||||||
Browser.document
|
Program.document
|
||||||
{ init = init
|
{ flagsDecoder = D.string
|
||||||
, view = view
|
, headers = headers
|
||||||
, update = update
|
, init = init
|
||||||
, subscriptions = subscriptions
|
, subscriptions = subscriptions
|
||||||
|
, title = always "Coolio!" -- TODO
|
||||||
|
, update = update
|
||||||
|
, view = view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,10 +27,8 @@ main =
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ baseUrl : String
|
{ baseUrl : String
|
||||||
, flavor : Theme.Flavor
|
|
||||||
, games : GameList
|
, games : GameList
|
||||||
, screen : Screen
|
, screen : Screen
|
||||||
, size : ScreenSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -42,25 +41,25 @@ type Screen
|
||||||
type Msg
|
type Msg
|
||||||
= OnGameList GameList.Msg
|
= OnGameList GameList.Msg
|
||||||
| OnScreen Screen
|
| OnScreen Screen
|
||||||
| OnScreenSize ScreenSize
|
|
||||||
|
|
||||||
|
|
||||||
init : () -> ( Model, Cmd Msg )
|
init : Result D.Error String -> ( Model, Cmd Msg )
|
||||||
init () =
|
init baseUrl =
|
||||||
let
|
let
|
||||||
( gmdl, gmsg ) =
|
( gmdl, gmsg ) =
|
||||||
GameList.init {}
|
GameList.init {}
|
||||||
in
|
in
|
||||||
( { baseUrl = "http://localhost:5000"
|
( { baseUrl =
|
||||||
, flavor = Theme.Latte
|
case baseUrl of
|
||||||
|
Ok s ->
|
||||||
|
s
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
"http://localhost:5000"
|
||||||
, games = gmdl
|
, games = gmdl
|
||||||
, screen = ViewGameSelectionMenu
|
, screen = ViewGameSelectionMenu
|
||||||
, size = ScreenSize.init
|
|
||||||
}
|
}
|
||||||
, Cmd.batch
|
, Cmd.map OnGameList gmsg
|
||||||
[ ScreenSize.updateScreenSize OnScreenSize
|
|
||||||
, Cmd.map OnGameList gmsg
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -79,9 +78,6 @@ update msg model =
|
||||||
OnScreen screen ->
|
OnScreen screen ->
|
||||||
( { model | screen = screen }, Cmd.none )
|
( { model | screen = screen }, Cmd.none )
|
||||||
|
|
||||||
OnScreenSize size ->
|
|
||||||
( { model | size = size }, Cmd.none )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- SUBSCRIPTIONS
|
-- SUBSCRIPTIONS
|
||||||
|
|
@ -89,59 +85,50 @@ update msg model =
|
||||||
|
|
||||||
subscriptions : Model -> Sub Msg
|
subscriptions : Model -> Sub Msg
|
||||||
subscriptions model =
|
subscriptions model =
|
||||||
Sub.batch
|
Sub.map OnGameList (GameList.subscriptions model.games)
|
||||||
[ ScreenSize.onResize OnScreenSize
|
|
||||||
, Sub.map OnGameList (GameList.subscriptions model.games)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- VIEW
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
view : Model -> Browser.Document Msg
|
headers : Model -> List { icon : Widget.Icon.Icon Msg, onPress : Msg }
|
||||||
view model =
|
headers model =
|
||||||
{ title =
|
|
||||||
case model.screen of
|
|
||||||
ViewCreateGame ->
|
|
||||||
"Create Game | Bot-Man-Toe"
|
|
||||||
|
|
||||||
ViewGameSelectionMenu ->
|
|
||||||
"Menu | Bot-Man-Toe"
|
|
||||||
|
|
||||||
ViewGame _ ->
|
|
||||||
"Replay | Bot-Man-Toe"
|
|
||||||
, body =
|
|
||||||
viewScreen model
|
|
||||||
|> Element.layout
|
|
||||||
[ Element.Background.color (Theme.baseUI model.flavor)
|
|
||||||
]
|
|
||||||
|> List.singleton
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
viewScreen : Model -> Element Msg
|
|
||||||
viewScreen model =
|
|
||||||
case model.screen of
|
case model.screen of
|
||||||
|
ViewCreateGame ->
|
||||||
|
[ { icon = Layout.iconAsIcon Icons.arrow_back, onPress = OnScreen ViewGameSelectionMenu }
|
||||||
|
]
|
||||||
|
|
||||||
|
ViewGameSelectionMenu ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
ViewGame _ ->
|
||||||
|
[ { icon = Layout.iconAsIcon Icons.arrow_back, onPress = OnScreen ViewGameSelectionMenu }
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
view : Program.ViewBox Model -> Element Msg
|
||||||
|
view data =
|
||||||
|
case data.model.screen of
|
||||||
ViewCreateGame ->
|
ViewCreateGame ->
|
||||||
Element.text "Create game menu!"
|
Element.text "Create game menu!"
|
||||||
|
|
||||||
ViewGameSelectionMenu ->
|
ViewGameSelectionMenu ->
|
||||||
GameList.viewSelection
|
GameList.viewSelection
|
||||||
{ flavor = model.flavor
|
{ flavor = data.flavor
|
||||||
, height = model.size.height
|
, height = data.size.height
|
||||||
, model = model.games
|
, model = data.model.games
|
||||||
, onCreateGame = OnScreen ViewCreateGame
|
, onCreateGame = OnScreen ViewCreateGame
|
||||||
, onNavigateToGame = OnScreen << ViewGame
|
, onNavigateToGame = OnScreen << ViewGame
|
||||||
, width = model.size.width
|
, width = data.size.width
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewGame game ->
|
ViewGame game ->
|
||||||
GameList.viewGame
|
GameList.viewGame
|
||||||
{ flavor = model.flavor
|
{ flavor = data.flavor
|
||||||
, game = game
|
, game = game
|
||||||
, height = model.size.height
|
, height = data.size.height
|
||||||
, onNavigateBack = OnScreen ViewGameSelectionMenu
|
, onNavigateBack = OnScreen ViewGameSelectionMenu
|
||||||
, toMsg = OnGameList
|
, toMsg = OnGameList
|
||||||
, width = model.size.width
|
, width = data.size.width
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
module Program exposing (Px, ViewBox, document, element)
|
||||||
|
|
||||||
|
import Browser
|
||||||
|
import Color
|
||||||
|
import Element exposing (Element)
|
||||||
|
import Element.Background
|
||||||
|
import Element.Events
|
||||||
|
import Element.Font
|
||||||
|
import Html
|
||||||
|
import Json.Decode as D
|
||||||
|
import Layout
|
||||||
|
import Pixels exposing (Pixels)
|
||||||
|
import Quantity exposing (Quantity)
|
||||||
|
import ScreenSize exposing (ScreenSize)
|
||||||
|
import Svg
|
||||||
|
import Svg.Attributes
|
||||||
|
import Theme
|
||||||
|
import Widget.Icon
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model model =
|
||||||
|
{ content : model
|
||||||
|
, flavor : Theme.Flavor
|
||||||
|
, size : ScreenSize
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg msg
|
||||||
|
= OnContent msg
|
||||||
|
| OnFlavor Theme.Flavor
|
||||||
|
| OnScreenSize ScreenSize
|
||||||
|
|
||||||
|
|
||||||
|
type alias Px =
|
||||||
|
Quantity Int Pixels
|
||||||
|
|
||||||
|
|
||||||
|
type alias ViewBox model =
|
||||||
|
{ flavor : Theme.Flavor, model : model, size : ScreenSize }
|
||||||
|
|
||||||
|
|
||||||
|
element :
|
||||||
|
{ flagsDecoder : D.Decoder flags
|
||||||
|
, init : Result D.Error flags -> ( model, Cmd msg )
|
||||||
|
, subscriptions : model -> Sub msg
|
||||||
|
, update : msg -> model -> ( model, Cmd msg )
|
||||||
|
, view : ViewBox model -> Element msg
|
||||||
|
}
|
||||||
|
-> Program D.Value (Model model) (Msg msg)
|
||||||
|
element data =
|
||||||
|
Browser.element
|
||||||
|
{ init = init { f = data.init, d = data.flagsDecoder }
|
||||||
|
, subscriptions = subscriptions data.subscriptions
|
||||||
|
, update = update data.update
|
||||||
|
, view =
|
||||||
|
view
|
||||||
|
{ body = data.view
|
||||||
|
, headers = always []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document :
|
||||||
|
{ flagsDecoder : D.Decoder flags
|
||||||
|
, headers : model -> List { icon : Widget.Icon.Icon msg, onPress : msg }
|
||||||
|
, init : Result D.Error flags -> ( model, Cmd msg )
|
||||||
|
, subscriptions : model -> Sub msg
|
||||||
|
, title : model -> String
|
||||||
|
, update : msg -> model -> ( model, Cmd msg )
|
||||||
|
, view : ViewBox model -> Element msg
|
||||||
|
}
|
||||||
|
-> Program D.Value (Model model) (Msg msg)
|
||||||
|
document data =
|
||||||
|
Browser.document
|
||||||
|
{ init = init { f = data.init, d = data.flagsDecoder }
|
||||||
|
, subscriptions = subscriptions data.subscriptions
|
||||||
|
, update = update data.update
|
||||||
|
, view =
|
||||||
|
\model ->
|
||||||
|
{ title = data.title model.content
|
||||||
|
, body = [ view { body = data.view, headers = data.headers } model ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- INIT
|
||||||
|
|
||||||
|
|
||||||
|
init :
|
||||||
|
{ f : Result D.Error flags -> ( model, Cmd msg )
|
||||||
|
, d : D.Decoder flags
|
||||||
|
}
|
||||||
|
-> D.Value
|
||||||
|
-> ( Model model, Cmd (Msg msg) )
|
||||||
|
init data blob =
|
||||||
|
case data.f (D.decodeValue data.d blob) of
|
||||||
|
( mdl, msg ) ->
|
||||||
|
( { content = mdl
|
||||||
|
, flavor = Theme.Frappe
|
||||||
|
, size = ScreenSize.init
|
||||||
|
}
|
||||||
|
, Cmd.batch
|
||||||
|
[ Cmd.map OnContent msg
|
||||||
|
, ScreenSize.updateScreenSize OnScreenSize
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- UPDATE
|
||||||
|
|
||||||
|
|
||||||
|
update :
|
||||||
|
(msg -> model -> ( model, Cmd msg ))
|
||||||
|
-> Msg msg
|
||||||
|
-> Model model
|
||||||
|
-> ( Model model, Cmd (Msg msg) )
|
||||||
|
update f msg model =
|
||||||
|
case msg of
|
||||||
|
OnContent m ->
|
||||||
|
case f m model.content of
|
||||||
|
( newMdl, newMsg ) ->
|
||||||
|
( { model | content = newMdl }
|
||||||
|
, Cmd.map OnContent newMsg
|
||||||
|
)
|
||||||
|
|
||||||
|
OnFlavor flavor ->
|
||||||
|
( { model | flavor = flavor }, Cmd.none )
|
||||||
|
|
||||||
|
OnScreenSize size ->
|
||||||
|
( { model | size = size }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- SUBSCRIPTIONS
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : (model -> Sub msg) -> Model model -> Sub (Msg msg)
|
||||||
|
subscriptions f model =
|
||||||
|
Sub.batch
|
||||||
|
[ Sub.map OnContent <| f model.content
|
||||||
|
, ScreenSize.onResize OnScreenSize
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- VIEW
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
{ body : ViewBox model -> Element msg
|
||||||
|
, headers : model -> List { icon : Widget.Icon.Icon msg, onPress : msg }
|
||||||
|
}
|
||||||
|
-> Model model
|
||||||
|
-> Html.Html (Msg msg)
|
||||||
|
view data model =
|
||||||
|
let
|
||||||
|
preferredNavBarHeight =
|
||||||
|
Pixels.pixels 40
|
||||||
|
|
||||||
|
showNavBar =
|
||||||
|
preferredNavBarHeight
|
||||||
|
|> Quantity.multiplyBy 6
|
||||||
|
|> Quantity.lessThanOrEqualTo model.size.height
|
||||||
|
|
||||||
|
contentHeight =
|
||||||
|
if showNavBar then
|
||||||
|
model.size.height |> Quantity.minus preferredNavBarHeight
|
||||||
|
|
||||||
|
else
|
||||||
|
model.size.height
|
||||||
|
in
|
||||||
|
[ viewNavBar
|
||||||
|
{ headers = data.headers model.content
|
||||||
|
, iconHeight = preferredNavBarHeight
|
||||||
|
, model = model
|
||||||
|
}
|
||||||
|
, data.body
|
||||||
|
{ flavor = model.flavor
|
||||||
|
, model = model.content
|
||||||
|
, size = { height = contentHeight, width = model.size.width }
|
||||||
|
}
|
||||||
|
|> Element.map OnContent
|
||||||
|
]
|
||||||
|
|> Element.column [ Element.width Element.fill ]
|
||||||
|
|> Element.layout
|
||||||
|
[ Element.Background.color (Theme.baseUI model.flavor)
|
||||||
|
, Element.Font.color (Theme.textUI model.flavor)
|
||||||
|
, Element.width <| Element.px <| Pixels.inPixels model.size.width
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewFlavorPicker :
|
||||||
|
{ currentFlavor : Theme.Flavor
|
||||||
|
, flavorToPick : Theme.Flavor
|
||||||
|
, onClick : Theme.Flavor -> msg
|
||||||
|
, size : Px
|
||||||
|
}
|
||||||
|
-> Element msg
|
||||||
|
viewFlavorPicker data =
|
||||||
|
Layout.svg
|
||||||
|
{ aspectRatio = 1 / 1
|
||||||
|
, height = Pixels.inPixels data.size
|
||||||
|
, svg =
|
||||||
|
Svg.circle
|
||||||
|
[ Svg.Attributes.cx "5"
|
||||||
|
, Svg.Attributes.cy "5"
|
||||||
|
, Svg.Attributes.r "4"
|
||||||
|
, Svg.Attributes.strokeWidth "1"
|
||||||
|
, Svg.Attributes.fill (Color.toCssString <| Theme.base data.flavorToPick)
|
||||||
|
, Svg.Attributes.stroke (Color.toCssString <| Theme.crust data.currentFlavor)
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, viewMinY = 0
|
||||||
|
, viewMaxY = 10
|
||||||
|
, viewMinX = 0
|
||||||
|
, viewMaxX = 10
|
||||||
|
, width = Pixels.inPixels data.size
|
||||||
|
}
|
||||||
|
|> (if data.currentFlavor /= data.flavorToPick then
|
||||||
|
Element.el [ Element.Events.onClick (data.onClick data.flavorToPick) ]
|
||||||
|
|
||||||
|
else
|
||||||
|
identity
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
viewNavBar :
|
||||||
|
{ headers : List { icon : Widget.Icon.Icon msg, onPress : msg }
|
||||||
|
, iconHeight : Px
|
||||||
|
, model : Model model
|
||||||
|
}
|
||||||
|
-> Element (Msg msg)
|
||||||
|
viewNavBar data =
|
||||||
|
let
|
||||||
|
heightAttr =
|
||||||
|
Quantity.twice data.iconHeight
|
||||||
|
|> Pixels.inPixels
|
||||||
|
|> Element.px
|
||||||
|
|> Element.height
|
||||||
|
|
||||||
|
widthAttr =
|
||||||
|
Quantity.twice data.iconHeight
|
||||||
|
|> Pixels.inPixels
|
||||||
|
|> Element.px
|
||||||
|
|> Element.width
|
||||||
|
in
|
||||||
|
Element.row
|
||||||
|
[ Element.Background.color <| Theme.mantleUI data.model.flavor
|
||||||
|
, Element.width Element.fill
|
||||||
|
]
|
||||||
|
[ data.headers
|
||||||
|
|> List.map
|
||||||
|
(viewNavBarIcon
|
||||||
|
{ flavor = data.model.flavor
|
||||||
|
, height = Quantity.twice data.iconHeight
|
||||||
|
, heightIcon = data.iconHeight
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> Element.row []
|
||||||
|
, Element.el [ heightAttr, Element.width Element.fill ] Element.none
|
||||||
|
, [ Theme.Latte, Theme.Frappe, Theme.Macchiato, Theme.Mocha ]
|
||||||
|
|> List.map
|
||||||
|
(\flavor ->
|
||||||
|
viewFlavorPicker
|
||||||
|
{ currentFlavor = data.model.flavor
|
||||||
|
, flavorToPick = flavor
|
||||||
|
, onClick = OnFlavor
|
||||||
|
, size = data.iconHeight
|
||||||
|
}
|
||||||
|
|> Element.el [ Element.centerX, Element.centerY ]
|
||||||
|
|> Element.el [ heightAttr, widthAttr ]
|
||||||
|
)
|
||||||
|
|> Element.row []
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
viewNavBarIcon :
|
||||||
|
{ flavor : Theme.Flavor
|
||||||
|
, height : Px
|
||||||
|
, heightIcon : Px
|
||||||
|
}
|
||||||
|
-> { icon : Widget.Icon.Icon msg, onPress : msg }
|
||||||
|
-> Element (Msg msg)
|
||||||
|
viewNavBarIcon { flavor, height, heightIcon } { icon, onPress } =
|
||||||
|
-- TODO: Implement coloring for hover + onclick
|
||||||
|
icon { color = Theme.text flavor, size = Pixels.inPixels heightIcon }
|
||||||
|
|> Element.el
|
||||||
|
[ Element.centerX
|
||||||
|
, Element.centerY
|
||||||
|
, Element.height <| Element.px <| Pixels.inPixels heightIcon
|
||||||
|
, Element.width <| Element.px <| Pixels.inPixels heightIcon
|
||||||
|
]
|
||||||
|
|> Element.el
|
||||||
|
[ Element.Events.onClick onPress
|
||||||
|
, Element.height <| Element.px <| Pixels.inPixels height
|
||||||
|
, Element.width <| Element.px <| Pixels.inPixels height
|
||||||
|
]
|
||||||
|
|> Element.map OnContent
|
||||||
Loading…
Reference in New Issue