diff --git a/src/Items/FlavorPicker.elm b/src/Items/FlavorPicker.elm index 8096260..b17b59e 100644 --- a/src/Items/FlavorPicker.elm +++ b/src/Items/FlavorPicker.elm @@ -1,43 +1,58 @@ module Items.FlavorPicker exposing (..) + {-| This module allows the user to pick whatever flavor they want to use. -} import Color exposing (Color) import Element exposing (Element) +import Element.Events import Html.Attributes +import Layout import Material.Icons import Theme exposing (Flavor(..)) -import Layout -import Element.Events + + -- MODEL - -- UPDATE - -- VIEW + view : { height : Int , flavor : Flavor , onClick : Flavor -> msg , themeIcon : Flavor -> Color , width : Int - } -> Element msg + } + -> Element msg view data = let - lightMode = data.flavor == Latte - icon = if lightMode then Material.Icons.dark_mode else Material.Icons.light_mode - newFlavor = if lightMode then Frappe else Latte - in - Element.el - [ Element.Events.onClick (data.onClick newFlavor) - , Element.htmlAttribute (Html.Attributes.style "cursor" "pointer") - ] - ( Layout.iconAsElement - { color = data.themeIcon data.flavor - , height = data.height - , icon = icon - , width = data.width - } - ) + lightMode = + data.flavor == Latte + icon = + if lightMode then + Material.Icons.dark_mode + + else + Material.Icons.light_mode + + newFlavor = + if lightMode then + Frappe + + else + Latte + in + Element.el + [ Element.Events.onClick (data.onClick newFlavor) + , Element.htmlAttribute (Html.Attributes.style "cursor" "pointer") + ] + (Layout.iconAsElement + { color = data.themeIcon data.flavor + , height = data.height + , icon = icon + , width = data.width + } + ) diff --git a/src/Items/Introduction.elm b/src/Items/Introduction.elm index fdc487e..c0dd2a3 100644 --- a/src/Items/Introduction.elm +++ b/src/Items/Introduction.elm @@ -1,28 +1,35 @@ module Items.Introduction exposing (..) - import Color exposing (Color) import Element exposing (Element) import Element.Background -import Widget.Material.Typography import Theme +import Widget.Material.Typography + + -- MODEL -type alias Model = () -type alias Msg = () +type alias Model = + () + + +type alias Msg = + () + + -- UPDATE - -- SUBSCRIPTIONS - -- VIEW + view : { colorBackground : Color , width : Int - } -> Element msg + } + -> Element msg view data = [ header "Martiplier" , text "Martiplier (short for Matrix Plier) is a unique client. It doesn't let you browse rooms and have chat conversations." @@ -35,10 +42,14 @@ view data = , Element.width (Element.px data.width) ] + header : String -> Element msg header = - Element.text >> Element.el Widget.Material.Typography.h1 - >> List.singleton >> Element.paragraph [] + Element.text + >> Element.el Widget.Material.Typography.h1 + >> List.singleton + >> Element.paragraph [] + text : String -> Element msg text = diff --git a/src/Items/ItemPicker.elm b/src/Items/ItemPicker.elm index 9c5128f..b7ba26c 100644 --- a/src/Items/ItemPicker.elm +++ b/src/Items/ItemPicker.elm @@ -1,48 +1,65 @@ module Items.ItemPicker exposing (..) -import Iddict exposing (Iddict) +import Color exposing (Color) import Element exposing (Element) +import Iddict exposing (Iddict) import Layout import Material.Icons -import Color exposing (Color) + + -- MODEL + type alias Model a = { hover : Maybe Int , items : Iddict a } -type Msg = OnHover Int | OnHoverOut Int + +type Msg + = OnHover Int + | OnHoverOut Int + init : Iddict a -> Model a init iddict = { hover = Nothing, items = iddict } + + -- UPDATE + update : Msg -> Model a -> Model a update msg model = case msg of OnHover i -> { model | hover = Just i } - + OnHoverOut i -> if model.hover == Just i then { model | hover = Nothing } + else model + updateContent : (Iddict a -> Iddict a) -> Model a -> Model a updateContent f model = { model | items = f model.items } + + -- VIEW + {-| Extract the original data set out of the item picker. -} extract : Model a -> Iddict a -extract = .items +extract = + .items + {-| Display the item picker. Note that the item should not be wider than 360px. -} @@ -56,7 +73,8 @@ view : , onAddNew : Maybe msg , onClick : Int -> msg , width : Int - } -> Element msg + } + -> Element msg view data = Layout.sideList { color = data.colorMenu @@ -75,21 +93,21 @@ view data = } ) |> (\items -> - case data.onAddNew of - Nothing -> - items - - Just onAddNew -> - List.append items - [ Layout.itemWithSubtext - { color = data.colorText - , leftIcon = Layout.iconAsIcon Material.Icons.add_circle - , onPress = Just onAddNew - , rightIcon = always Element.none - , text = "Click here" - , title = "Add new" - } - ] - ) + case data.onAddNew of + Nothing -> + items + + Just onAddNew -> + List.append items + [ Layout.itemWithSubtext + { color = data.colorText + , leftIcon = Layout.iconAsIcon Material.Icons.add_circle + , onPress = Just onAddNew + , rightIcon = always Element.none + , text = "Click here" + , title = "Add new" + } + ] + ) , width = data.width } diff --git a/src/Items/LoginView.elm b/src/Items/LoginView.elm index 69a72df..49d3fc2 100644 --- a/src/Items/LoginView.elm +++ b/src/Items/LoginView.elm @@ -1,4 +1,5 @@ module Items.LoginView exposing (..) + {-| The Login screen allows the user to log in, as well as view a short display of what to expect from the Matrix client. -} @@ -7,15 +8,18 @@ import Color exposing (Color) import Element exposing (Element) import Element.Background import Element.Border +import Items.FlavorPicker as FlavorPicker +import Layout +import Material.Icons import Matrix import Matrix.Settings import Theme -import Layout -import Items.FlavorPicker as FlavorPicker -import Material.Icons + + -- MODEL + type alias Model = { accessToken : String , loginMethod : LoginMethod @@ -23,16 +27,19 @@ type alias Model = , username : String } + type Msg = SetAccessToken String | SetPassword String | SetUsername String | SwitchMethod LoginMethod + type LoginMethod = AccessToken | Password + init : Model init = { accessToken = "" @@ -41,8 +48,11 @@ init = , username = "" } + + -- UPDATE + update : Msg -> Model -> Model update msg model = case msg of @@ -58,8 +68,11 @@ update msg model = SwitchMethod method -> { model | loginMethod = method } + + -- VIEW + view : { colorBackground : Color , colorMain : Color @@ -73,7 +86,8 @@ view : , onSubmit : Matrix.Vault -> msg , toMsg : Msg -> msg , width : Int - } -> Element msg + } + -> Element msg view data = [ viewLoginMethodPicker { color = data.colorMain @@ -97,7 +111,7 @@ view data = , show = False , text = data.model.accessToken } - + Password -> Layout.passwordInput { color = data.colorTextField @@ -108,17 +122,17 @@ view data = , text = data.model.password } , Layout.containedButton - { buttonColor = data.colorMain - , clickColor = data.colorText - , icon = always Element.none - , onPress = - data.model - |> toVault - |> Maybe.map data.onSubmit - , text = "LOG IN" - } - |> Element.el - [ Element.centerX ] + { buttonColor = data.colorMain + , clickColor = data.colorText + , icon = always Element.none + , onPress = + data.model + |> toVault + |> Maybe.map data.onSubmit + , text = "LOG IN" + } + |> Element.el + [ Element.centerX ] ] |> Element.column [ Element.Background.color (Theme.toElmUiColor data.colorMenu) @@ -133,7 +147,7 @@ view data = |> Element.el [ Element.Background.color (Theme.toElmUiColor data.colorBackground) , Element.inFront - ( FlavorPicker.view + (FlavorPicker.view { height = 30 , flavor = data.flavor , onClick = data.onFlavorPick @@ -147,6 +161,7 @@ view data = , Element.width (Element.px data.width) ] + toVault : Model -> Maybe Matrix.Vault toVault model = case model.loginMethod of @@ -168,6 +183,7 @@ toVault model = |> Matrix.fromUserId |> Maybe.map (Matrix.Settings.setPassword model.password) + viewLoginMethodPicker : { color : Color, loginMethod : LoginMethod, toMsg : LoginMethod -> msg } -> Element msg viewLoginMethodPicker data = Layout.tab @@ -175,24 +191,24 @@ viewLoginMethodPicker data = , content = always Element.none , items = [ { icon = Layout.iconAsIcon Material.Icons.key - , text = "Access token" + , text = "Access token" } , { icon = Layout.iconAsIcon Material.Icons.password , text = "Password" } ] , onSelect = - (\i -> - if i == 0 then - data.toMsg AccessToken - else - data.toMsg Password - ) - , selected = - case data.loginMethod of - AccessToken -> - 0 - - Password -> - 1 + \i -> + if i == 0 then + data.toMsg AccessToken + + else + data.toMsg Password + , selected = + case data.loginMethod of + AccessToken -> + 0 + + Password -> + 1 } diff --git a/src/Items/VaultList.elm b/src/Items/VaultList.elm index af549a9..09b713b 100644 --- a/src/Items/VaultList.elm +++ b/src/Items/VaultList.elm @@ -1,63 +1,83 @@ module Items.VaultList exposing (..) -{-| # Vault list + +{-| + + +# Vault list The vault list contains a list of stored vaults that can be picked from. + -} import Iddict exposing (Iddict) import Matrix + + -- MODEL -type alias Model = Iddict VaultBlock + +type alias Model = + Iddict VaultBlock + type Msg = AddVault { name : String, vault : Matrix.Vault } | OnVaultUpdate Int Matrix.VaultUpdate + type alias VaultBlock = { logs : List { channel : String, content : String } , name : String , vault : Matrix.Vault } + init : Model -init = Iddict.empty +init = + Iddict.empty + + -- UPDATE + addVault : { name : String, vault : Matrix.Vault } -> Model -> Model addVault = AddVault >> update + insertVaultUpdate : Int -> Matrix.VaultUpdate -> Model -> Model insertVaultUpdate i vu = update (OnVaultUpdate i vu) + update : Msg -> Model -> Model update msg model = case msg of AddVault { name, vault } -> Iddict.insert { name = name, logs = [], vault = vault } model |> Tuple.second - + OnVaultUpdate i vu -> Iddict.update i (Maybe.map (\block -> { block - | logs = - List.append (Matrix.logs vu) block.logs - |> List.take 500 - , vault = Matrix.update vu block.vault + | logs = + List.append (Matrix.logs vu) block.logs + |> List.take 500 + , vault = Matrix.update vu block.vault } ) ) model + + -- VIEW + getVault : Int -> Model -> Maybe Matrix.Vault getVault i model = Iddict.get i model |> Maybe.map .vault - diff --git a/src/Layout.elm b/src/Layout.elm index 1af9132..8bf6121 100644 --- a/src/Layout.elm +++ b/src/Layout.elm @@ -1,9 +1,12 @@ module Layout exposing - ( tab, twoBlocks + ( twoBlocks + , tab, sideIconBar , iconAsElement, iconAsIcon - , containedButton, outlinedButton, textButton, sideList + , containedButton, outlinedButton, textButton , textInput, passwordInput - , loadingIndicator, itemWithSubtext + , itemWithSubtext + , sideList + , loadingIndicator ) {-| @@ -19,14 +22,17 @@ beautiful Material design Elm webpage. @docs twoBlocks + ## Elements -@docs tab +@docs tab, sideIconBar + ## Icons @docs iconAsElement, iconAsIcon + ## Buttons @docs containedButton, outlinedButton, textButton @@ -36,14 +42,17 @@ beautiful Material design Elm webpage. @docs textInput, passwordInput + ## Items in a list @docs itemWithSubtext + ## Lists @docs sideList + ## Other elements @docs loadingIndicator @@ -52,12 +61,13 @@ beautiful Material design Elm webpage. import Color exposing (Color) import Element exposing (Element) +import Element.Events import Element.Input +import Material.Icons.Types import Widget import Widget.Customize as Customize import Widget.Icon exposing (Icon) import Widget.Material as Material -import Material.Icons.Types {-| A contained button representing the most important action of a group. @@ -79,12 +89,14 @@ containedButton data = ) { text = data.text, icon = data.icon, onPress = data.onPress } + iconAsElement : { color : Color , height : Int , icon : Material.Icons.Types.Icon msg , width : Int - } -> Element msg + } + -> Element msg iconAsElement data = data.icon |> iconAsIcon @@ -95,10 +107,12 @@ iconAsElement data = , Element.width (Element.px data.width) ] + iconAsIcon : Material.Icons.Types.Icon msg -> Widget.Icon.Icon msg iconAsIcon = Widget.Icon.elmMaterialIcons Material.Icons.Types.Color + {-| Multiline item -} itemWithSubtext : @@ -109,10 +123,10 @@ itemWithSubtext : , text : String , title : String } - -> Widget.Item msg + -> Widget.Item msg itemWithSubtext data = Widget.multiLineItem - ( { primary = data.color, onPrimary = data.color } + ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.multiLineItem ) @@ -123,20 +137,22 @@ itemWithSubtext data = , text = data.text } + {-| Circular loading bar indicator -} loadingIndicator : { color : Color } - -> Element msg + -> Element msg loadingIndicator data = Widget.circularProgressIndicator - ( { primary = data.color, onPrimary = data.color } + ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.progressIndicator ) Nothing + {-| An outlined button representing an important action within a group. -} outlinedButton : @@ -154,6 +170,7 @@ outlinedButton data = ) { text = data.text, icon = data.icon, onPress = data.onPress } + {-| Show a password field -} passwordInput : @@ -201,13 +218,51 @@ singlePalette { primary, onPrimary } = } } -sideList : { color : Color, items : List (Widget.Item msg), width : Int }-> Element msg + +sideIconBar : + { colorBackground : Color + , colorText : Color + , height : Int + , items : List { icon : Widget.Icon.Icon msg, onPress : msg, text : String } + , width : Int + } + -> Element msg +sideIconBar data = + let + buttonHeight = + round (toFloat data.width * 1.618) + + iconSize = + data.width // 2 + in + data.items + |> List.map + (\item -> + [ item.icon { size = iconSize, color = data.colorText } + , Element.paragraph [] [ Element.text item.text ] + ] + |> Element.column [ Element.centerX, Element.centerY ] + |> Element.el + [ Element.Events.onClick item.onPress + , Element.height (Element.px buttonHeight) + , Element.width (Element.px data.width) + ] + ) + |> Element.column + [ Element.height (Element.px data.height) + , Element.scrollbarY + , Element.width (Element.px data.width) + ] + + +sideList : { color : Color, items : List (Widget.Item msg), width : Int } -> Element msg sideList data = let - width px = Element.width (Element.px px) + width px = + Element.width (Element.px px) in Widget.itemList - ( { primary = data.color, onPrimary = data.color } + ({ primary = data.color, onPrimary = data.color } |> singlePalette |> Material.sideSheet ) @@ -215,6 +270,7 @@ sideList data = |> Element.el [ Element.centerX, width (Basics.min 360 data.width) ] |> Element.el [ width data.width ] + {-| A tab selector that always has an item selected. -} tab : @@ -285,6 +341,7 @@ textInput data = , onChange = data.onChange } + {-| Two blocks either next to each other or below each other, depending on the screen shape. -} @@ -293,18 +350,38 @@ twoBlocks : , el1 : { height : Int, width : Int } -> Element msg , el2 : { height : Int, width : Int } -> Element msg , width : Int - } -> Element msg + } + -> Element msg twoBlocks data = let - goesVertical = 2 * data.width <= 3 * data.height - direction = if goesVertical then Element.column else Element.row - width = if goesVertical then data.width else data.width // 2 - height = if goesVertical then data.height // 2 else data.height + goesVertical = + 2 * data.width <= 3 * data.height + + direction = + if goesVertical then + Element.column + + else + Element.row + + width = + if goesVertical then + data.width + + else + data.width // 2 + + height = + if goesVertical then + data.height // 2 + + else + data.height in - direction - [ Element.height (Element.px data.height) - , Element.width (Element.px data.width) - ] - [ data.el1 { height = height, width = width } - , data.el2 { height = height, width = width } - ] + direction + [ Element.height (Element.px data.height) + , Element.width (Element.px data.width) + ] + [ data.el1 { height = height, width = width } + , data.el2 { height = height, width = width } + ] diff --git a/src/Main.elm b/src/Main.elm index b6f380d..e80ed81 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -5,14 +5,14 @@ import Browser.Dom import Browser.Events import Element exposing (Element) import Element.Background +import Element.Font +import Items.VaultList as VaultList +import Matrix +import Recursion +import Screen.Vault as VaultScreen +import Screen.Welcome as WelcomeScreen import Task import Theme -import Items.VaultList as VaultList -import Screen.Welcome as WelcomeScreen -import Screen.Vault as VaultScreen -import Element.Font -import Recursion -import Matrix main : Program () Model Msg @@ -36,10 +36,12 @@ type alias Model = , width : Int } + type Screen = ScreenWelcome WelcomeScreen.Model | ScreenVault Vaults Int VaultScreen.Model + type Msg = OnScreenVault Int VaultScreen.Msg | OnScreenWelcome WelcomeScreen.Msg @@ -49,7 +51,9 @@ type Msg | ScreenSize { height : Int, width : Int } | SetFlavor Theme.Flavor -type alias Vaults = VaultList.Model + +type alias Vaults = + VaultList.Model init : () -> ( Model, Cmd Msg ) @@ -86,12 +90,13 @@ update msg model = ( { model | screen = ScreenVault welcomeMdl i newMdl } , Cmd.none ) + else ( model, Cmd.none ) _ -> ( model, Cmd.none ) - + OnScreenWelcome m -> case model.screen of ScreenWelcome mdl -> @@ -100,34 +105,35 @@ update msg model = ( { model | screen = ScreenWelcome newMdl } , Cmd.none ) - + _ -> ( model, Cmd.none ) - + OnSelectVault vaults i -> ( { model - | screen = + | screen = case model.screen of ScreenWelcome _ -> ScreenVault vaults i VaultScreen.init - + ScreenVault _ j old -> if i == j then ScreenVault vaults j old + else ScreenVault vaults i VaultScreen.init } , Cmd.none ) - + OnVaultUpdate i vu -> ( { model - | screen = + | screen = case model.screen of ScreenWelcome mdl -> WelcomeScreen.updateVault i vu mdl |> ScreenWelcome - + ScreenVault vaults j mdl -> ScreenVault (VaultList.insertVaultUpdate i vu vaults) j mdl } @@ -141,7 +147,7 @@ update msg model = ( { model | height = height, width = width } , Cmd.none ) - + SetFlavor flavor -> ( { model | flavor = flavor }, Cmd.none ) @@ -174,15 +180,27 @@ view model = |> List.singleton } + viewScreen : Model -> Element Msg viewScreen model = let - colorBackground = Theme.base model.flavor - colorBackground2 = Theme.mantle model.flavor - colorMain = Theme.mauve model.flavor - colorSurface0 = Theme.surface0 model.flavor - colorSurface1 = Theme.surface1 model.flavor - colorText = Theme.text model.flavor + colorBackground = + Theme.base model.flavor + + colorBackground2 = + Theme.mantle model.flavor + + colorMain = + Theme.mauve model.flavor + + colorSurface0 = + Theme.surface0 model.flavor + + colorSurface1 = + Theme.surface1 model.flavor + + colorText = + Theme.text model.flavor in Recursion.runRecursion (\screen -> @@ -201,7 +219,7 @@ viewScreen model = , width = model.width } |> Recursion.base - + Nothing -> WelcomeScreen.fromVaults vaults |> ScreenWelcome @@ -226,4 +244,3 @@ viewScreen model = |> Recursion.base ) model.screen - diff --git a/src/Screen/Vault.elm b/src/Screen/Vault.elm index e6cb49e..a4d39ba 100644 --- a/src/Screen/Vault.elm +++ b/src/Screen/Vault.elm @@ -4,25 +4,39 @@ import Color exposing (Color) import Element exposing (Element) import Matrix + + -- MODEL -type alias Model = () -type alias Msg = () +type alias Model = + () + + +type alias Msg = + () + init : Model -init = () +init = + () + + -- UPDATE + update : Msg -> Model -> Model update msg model = case msg of () -> model + + -- VIEW + view : { colorBackground : Color , colorText : Color @@ -32,7 +46,7 @@ view : , toMsg : Msg -> msg , vault : Matrix.Vault , width : Int - } -> Element msg + } + -> Element msg view data = Element.none - diff --git a/src/Screen/Welcome.elm b/src/Screen/Welcome.elm index 6b9d5f5..ed3fcb6 100644 --- a/src/Screen/Welcome.elm +++ b/src/Screen/Welcome.elm @@ -1,23 +1,27 @@ module Screen.Welcome exposing (..) import Color exposing (Color) +import Element exposing (Element) import Items.Introduction as Introduction import Items.ItemPicker as ItemPicker -import Items.VaultList as VaultList -import Matrix import Items.LoginView as LoginView -import Element exposing (Element) +import Items.VaultList as VaultList import Layout +import Matrix import Matrix.Settings import Theme + + -- MODEL + type alias Model = { login : Maybe LoginView.Model , vaults : ItemPicker.Model VaultList.VaultBlock } + type Msg = OnAddNew | OnLogin LoginView.Msg @@ -25,20 +29,25 @@ type Msg | OnVaultUpdate Int Matrix.VaultUpdate | OnVaults ItemPicker.Msg + init : Model init = { login = Nothing -- Just LoginView.init , vaults = ItemPicker.init VaultList.init } + fromVaults : VaultList.Model -> Model fromVaults items = { login = Nothing , vaults = ItemPicker.init items } + + -- UPDATE + update : Msg -> Model -> Model update msg model = case msg of @@ -47,24 +56,28 @@ update msg model = OnLogin m -> { model | login = Maybe.map (LoginView.update m) model.login } - + OnSubmitVault block -> { login = Nothing , vaults = ItemPicker.updateContent (VaultList.addVault block) model.vaults } - + OnVaultUpdate i vu -> { model | vaults = ItemPicker.updateContent (VaultList.insertVaultUpdate i vu) model.vaults } - + OnVaults m -> { model | vaults = ItemPicker.update m model.vaults } + updateVault : Int -> Matrix.VaultUpdate -> Model -> Model updateVault i vu = update (OnVaultUpdate i vu) + + -- VIEW + view : { colorBackground : Color , colorBackground2 : Color @@ -79,10 +92,12 @@ view : , onSelectVault : VaultList.Model -> Int -> msg , toMsg : Msg -> msg , width : Int - } -> Element msg + } + -> Element msg view data = let - onSelectVault = data.onSelectVault (ItemPicker.extract data.model.vaults) + onSelectVault = + data.onSelectVault (ItemPicker.extract data.model.vaults) in case data.model.login of Just login -> @@ -141,9 +156,10 @@ viewIntroduction : , onAddNew : Maybe msg , onSelectVault : Int -> msg , width : Int - } -> Element msg + } + -> Element msg viewIntroduction data = - [ Introduction.view { colorBackground = data.colorBackground, width = data.width } + [ Introduction.view { colorBackground = data.colorBackground, width = data.width } , ItemPicker.view { colorMenu = data.colorMenu , colorText = data.colorText