Add initial spike

main
Bram van den Heuvel 2024-11-03 13:10:57 +01:00
parent 4e76fe6e09
commit 3b370138aa
9 changed files with 1833 additions and 0 deletions

View File

@ -0,0 +1,43 @@
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 Html.Attributes
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
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
}
)

View File

@ -0,0 +1,45 @@
module Items.Introduction exposing (..)
import Color exposing (Color)
import Element exposing (Element)
import Element.Background
import Widget.Material.Typography
import Theme
-- MODEL
type alias Model = ()
type alias Msg = ()
-- UPDATE
-- SUBSCRIPTIONS
-- VIEW
view :
{ colorBackground : Color
, width : Int
} -> 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."
, text "Instead, it offers you a more debug-like display of a user account. This helps when trying to do bulk operations, or to discover information about a client."
]
|> Element.column
[ Element.Background.color (Theme.toElmUiColor data.colorBackground)
, Element.padding 30
, Element.spacing 20
, Element.width (Element.px data.width)
]
header : String -> Element msg
header =
Element.text >> Element.el Widget.Material.Typography.h1
>> List.singleton >> Element.paragraph []
text : String -> Element msg
text =
Element.text >> List.singleton >> Element.paragraph []

198
src/Items/LoginScreen.elm Normal file
View File

@ -0,0 +1,198 @@
module Items.LoginScreen 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.
-}
import Color exposing (Color)
import Element exposing (Element)
import Element.Background
import Element.Border
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
, password : String
, username : String
}
type Msg
= SetAccessToken String
| SetPassword String
| SetUsername String
| SwitchMethod LoginMethod
type LoginMethod
= AccessToken
| Password
init : Model
init =
{ accessToken = ""
, loginMethod = AccessToken
, password = ""
, username = ""
}
-- UPDATE
update : Msg -> Model -> Model
update msg model =
case msg of
SetAccessToken name ->
{ model | accessToken = name }
SetPassword name ->
{ model | password = name }
SetUsername name ->
{ model | username = name }
SwitchMethod method ->
{ model | loginMethod = method }
-- VIEW
view :
{ colorBackground : Color
, colorMain : Color
, colorMenu : Color
, colorText : Color
, colorTextField : Color
, height : Int
, flavor : Theme.Flavor
, model : Model
, onFlavorPick : Theme.Flavor -> msg
, onSubmit : Matrix.Vault -> msg
, toMsg : Msg -> msg
, width : Int
} -> Element msg
view data =
[ viewLoginMethodPicker
{ color = data.colorMain
, loginMethod = data.model.loginMethod
, toMsg = SwitchMethod >> data.toMsg
}
, Layout.textInput
{ color = data.colorTextField
, label = "username"
, onChange = SetUsername >> data.toMsg
, placeholder = Just "@alice:example.org"
, text = data.model.username
}
, case data.model.loginMethod of
AccessToken ->
Layout.passwordInput
{ color = data.colorTextField
, label = "access-token"
, onChange = SetAccessToken >> data.toMsg
, placeholder = Just "syt_p12smN_aR0KbfXxDr3RVESXNr3I_k5Ay88"
, show = False
, text = data.model.accessToken
}
Password ->
Layout.passwordInput
{ color = data.colorTextField
, label = "password"
, onChange = SetPassword >> data.toMsg
, placeholder = Just "Password"
, show = False
, 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 ]
]
|> Element.column
[ Element.Background.color (Theme.toElmUiColor data.colorMenu)
, Element.Border.rounded 25
, Element.centerX
, Element.centerY
, Element.height (Element.px 300)
, Element.padding 30
, Element.spaceEvenly
, Element.width (Element.px 400)
]
|> Element.el
[ Element.Background.color (Theme.toElmUiColor data.colorBackground)
, Element.inFront
( FlavorPicker.view
{ height = 30
, flavor = data.flavor
, onClick = data.onFlavorPick
, themeIcon = always data.colorText
, width = 30
}
|> Element.el [ Element.alignRight ]
|> Element.el [ Element.padding 10, Element.width Element.fill ]
)
, Element.height (Element.px data.height)
, Element.width (Element.px data.width)
]
toVault : Model -> Maybe Matrix.Vault
toVault model =
case model.loginMethod of
AccessToken ->
if model.accessToken == "" then
Nothing
else
model.username
|> Matrix.fromUserId
|> Maybe.map (Matrix.Settings.setAccessToken model.accessToken)
Password ->
if model.password == "" then
Nothing
else
model.username
|> Matrix.fromUserId
|> Maybe.map (Matrix.Settings.setPassword model.password)
viewLoginMethodPicker : { color : Color, loginMethod : LoginMethod, toMsg : LoginMethod -> msg } -> Element msg
viewLoginMethodPicker data =
Layout.tab
{ color = data.color
, content = always Element.none
, items =
[ { icon = Layout.iconAsIcon Material.Icons.key
, 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
}

112
src/Items/VaultPicker.elm Normal file
View File

@ -0,0 +1,112 @@
module Items.VaultPicker exposing (..)
import Color exposing (Color)
import Element exposing (Element)
import Iddict exposing (Iddict)
import Material.Icons
import Matrix
import Layout
-- MODEL
type alias Model =
{ hover : Maybe Int, vaults : Iddict NamedVault }
type alias NamedVault = { name : String, vault : Matrix.Vault }
type Msg
= AddVault String Matrix.Vault
| OnHover Int
| OnHoverOut Int
| OnRemoveVault Int
init : List NamedVault -> Model
init items =
{ hover = Nothing
, vaults =
items
|> List.indexedMap Tuple.pair
|> Iddict.fromList
}
-- UPDATE
addVault : NamedVault -> Model -> Model
addVault data =
AddVault data.name data.vault |> update
update : Msg -> Model -> Model
update msg model =
case msg of
AddVault name vault ->
{ model
| vaults =
model.vaults
|> Iddict.insert { name = name, vault = vault }
|> Tuple.second
}
OnHover i ->
{ model | hover = Just i }
OnHoverOut i ->
case model.hover of
Just h ->
if i == h then
{ model | hover = Nothing }
else
model
Nothing ->
model
OnRemoveVault i ->
{ model | vaults = Iddict.remove i model.vaults }
-- VIEW
getVault : Int -> Model -> Maybe Matrix.Vault
getVault i model =
Iddict.get i model.vaults |> Maybe.map .vault
view :
{ colorItem : Color
, colorList : Color
, model : Model
, onAddVault : msg
, onSelectVault : Int -> msg
, width : Int
} -> Element msg
view data =
Layout.sideList
{ color = data.colorList
, items =
data.model.vaults
|> Iddict.toList
|> List.map
(\(vid, { name, vault }) ->
Layout.itemWithSubtext
{ color = data.colorItem
, leftIcon = always Element.none
, onPress = Just (data.onSelectVault vid)
, rightIcon = Layout.iconAsIcon Material.Icons.launch
, text = name
, title = "Vault #" ++ String.fromInt vid
}
)
|> (\items ->
[ Layout.itemWithSubtext
{ color = data.colorItem
, leftIcon = Layout.iconAsIcon Material.Icons.add_circle
, onPress = Just data.onAddVault
, rightIcon = always Element.none
, text = "Click here"
, title = "Add new"
}
]
|> List.append items
)
}
|> Element.el
[ Element.width (Element.px data.width) ]

170
src/Items/VaultScreen.elm Normal file
View File

@ -0,0 +1,170 @@
module Items.VaultScreen exposing (..)
import Color exposing (Color)
import Element exposing (Element)
import Layout
import Matrix
import Matrix.Room
import Queue exposing (Queue)
-- MODEL
type alias Model =
{ recentLogs : Queue { channel : String, content : String }
, screen : Screen
}
type Msg
= OnSwitchScreen Screen
| OnVaultUpdate Matrix.VaultUpdate
type Screen
= LandingScreen
| Room String
init : Model
init =
{ recentLogs = Queue.empty
, screen = LandingScreen
}
maxItemsInRecentLogsQueue : Int
maxItemsInRecentLogsQueue = 100
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnSwitchScreen screen ->
( { model | screen = screen }, Cmd.none )
OnVaultUpdate vu ->
( { model
| recentLogs =
model.recentLogs
|> addItemsToQueue (Matrix.logs vu)
|> stripQueueToSize maxItemsInRecentLogsQueue
}
, Cmd.none
)
addItemsToQueue : List a -> Queue a -> Queue a
addItemsToQueue items queue =
List.foldl Queue.enqueue queue items
stripQueueToSize : Int -> Queue a -> Queue a
stripQueueToSize i queue =
if Queue.length queue > i then
stripQueueToSize i (Queue.dequeue queue)
else
queue
-- VIEW
view :
{ colorSelectedRoom : Color
, colorText : Color
, height : Int
, model : Model
, onVaultUpdate : Matrix.VaultUpdate -> msg
, vault : Matrix.Vault
, toMsg : Msg -> msg
, width : Int
} -> Element msg
view data =
viewScreen
{ colorSelectedRoom = data.colorSelectedRoom
, colorText = data.colorText
, height = data.height
, model = data.model
, onVaultUpdate = data.onVaultUpdate
, vault = data.vault
, screen = data.model.screen
, toMsg = data.toMsg
, width = data.width
}
viewScreen :
{ colorSelectedRoom : Color
, colorText : Color
, height : Int
, model : Model
, onVaultUpdate : Matrix.VaultUpdate -> msg
, vault : Matrix.Vault
, screen : Screen
, toMsg : Msg -> msg
, width : Int
} -> Element msg
viewScreen data =
case data.screen of
LandingScreen ->
Element.wrappedRow
[ Element.height (Element.px data.height)
, Element.width (Element.px data.width)
]
[ viewRoomList
{ colorSelected = data.colorSelectedRoom
, colorText = data.colorText
, rooms = Matrix.rooms data.vault
, toMsg = data.toMsg
}
]
Room roomId ->
case Matrix.fromRoomId roomId data.vault of
Just room ->
viewRoom
{ height = data.height
, model = room
, toMsg = data.toMsg
, width = data.width
}
Nothing ->
viewScreen { data | screen = LandingScreen }
viewRoom :
{ height : Int
, model : Matrix.Room.Room
, toMsg : Msg -> msg
, width : Int
} -> Element msg
viewRoom data =
[ Matrix.Room.name data.model
|> Maybe.withDefault "Nameless room"
|> Element.text
]
|> Element.column []
viewRoomList :
{ colorSelected : Color
, colorText : Color
, rooms : List Matrix.Room.Room
, toMsg : Msg -> msg
} -> Element msg
viewRoomList data =
Layout.sideList
{ color = data.colorSelected
, items =
data.rooms
|> List.map
(\room ->
Layout.itemWithSubtext
{ color = data.colorText
, leftIcon = always Element.none -- TODO: Add room image
, onPress =
Matrix.Room.roomId room
|> Room
|> OnSwitchScreen
|> data.toMsg
|> Just
, rightIcon = always Element.none -- TODO: Choose icon?
, text = Matrix.Room.roomId room
, title =
Matrix.Room.name room
|> Maybe.withDefault "Nameless room"
}
)
}

178
src/Items/WelcomeScreen.elm Normal file
View File

@ -0,0 +1,178 @@
module Items.WelcomeScreen exposing (..)
import Color exposing (Color)
import Element exposing (Element)
import Element.Background
import Items.FlavorPicker as FlavorPicker
import Items.Introduction as Intro
import Items.LoginScreen as Login
import Items.VaultPicker as VaultPicker
import Matrix
import Theme
-- MODEL
type alias Model =
{ loginView : Maybe Login.Model
, vaultView : VaultPicker.Model
}
type Msg
= OnAddVault
| OnLoginView Login.Msg
| OnSubmitVault { name : String, vault : Matrix.Vault }
| OnVaultView VaultPicker.Msg
init : List { name : String, vault : Matrix.Vault } -> Model
init vaults =
{ loginView = Nothing
, vaultView = VaultPicker.init vaults
}
-- UPDATE
update : Msg -> Model -> Model
update msg model =
case msg of
OnAddVault ->
case model.loginView of
Just _ ->
model
Nothing ->
{ model | loginView = Just Login.init }
OnLoginView m ->
{ model
| loginView = Maybe.map (Login.update m) model.loginView
}
OnSubmitVault vault ->
{ model
| loginView = Nothing
, vaultView = VaultPicker.addVault vault model.vaultView
}
OnVaultView m ->
{ model
| vaultView = VaultPicker.update m model.vaultView
}
-- VIEW
getVault : Int -> Model -> Maybe Matrix.Vault
getVault i model =
VaultPicker.getVault i model.vaultView
view :
{ colorBackground : Color
, colorBackground2 : Color
, colorMain : Color
, colorMenu : Color
, colorText : Color
, colorTextField : Color
, height : Int
, flavor : Theme.Flavor
, model : Model
, onFlavorPick : Theme.Flavor -> msg
, onSelectVault : Int -> msg
, toMsg : Msg -> msg
, width : Int
} -> Element msg
view 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
in
case data.model.loginView of
Just m ->
direction
[ Element.height (Element.px data.height)
, Element.width (Element.px data.width)
]
[ viewIntroduction
{ colorBackground = data.colorBackground
, colorItem = data.colorText
, colorList = data.colorMenu
, height = height
, model = data.model.vaultView
, onSelectVault = data.onSelectVault
, toMsg = data.toMsg
, width = width
}
, Login.view
{ colorBackground = data.colorBackground2
, colorMain = data.colorMain
, colorMenu = data.colorMenu
, colorText = data.colorText
, colorTextField = data.colorTextField
, height = height
, flavor = data.flavor
, model = m
, onFlavorPick = data.onFlavorPick
, onSubmit = \vault -> OnSubmitVault { name = "New Vault", vault = vault } |> data.toMsg
, toMsg = OnLoginView >> data.toMsg
, width = width
}
]
Nothing ->
viewIntroduction
{ colorBackground = data.colorBackground
, colorItem = data.colorText
, colorList = data.colorMenu
, height = data.height
, model = data.model.vaultView
, onSelectVault = data.onSelectVault
, toMsg = data.toMsg
, width = data.width
}
|> Element.el
[ Element.Background.color (Theme.toElmUiColor data.colorBackground)
, Element.inFront
( FlavorPicker.view
{ height = 30
, flavor = data.flavor
, onClick = data.onFlavorPick
, themeIcon = always data.colorText
, width = 30
}
|> Element.el [ Element.alignRight ]
|> Element.el [ Element.padding 10, Element.width Element.fill ]
)
]
viewIntroduction :
{ colorBackground : Color
, colorItem : Color
, colorList : Color
, height : Int
, model : VaultPicker.Model
, onSelectVault : Int -> msg
, toMsg : Msg -> msg
, width : Int
} -> Element msg
viewIntroduction data =
[ Intro.view
{ colorBackground = data.colorBackground
, width = data.width
}
, VaultPicker.view
{ colorItem = data.colorItem
, colorList = data.colorList
, onAddVault = data.toMsg OnAddVault
, onSelectVault = data.onSelectVault
, model = data.model
, width = Basics.min 360 (4 * data.width // 5)
}
|> Element.el
[ Element.centerX
]
]
|> Element.column
[ Element.height (Element.px data.height)
, Element.scrollbarY
, Element.width (Element.px data.width)
]

277
src/Layout.elm Normal file
View File

@ -0,0 +1,277 @@
module Layout exposing
( tab
, iconAsElement, iconAsIcon
, containedButton, outlinedButton, textButton, sideList
, textInput, passwordInput
, loadingIndicator, itemWithSubtext
)
{-|
# Layout
The layout module exposes some boilerplate functions that have produce a
beautiful Material design Elm webpage.
## Elements
@docs tab
## Icons
@docs iconAsElement, iconAsIcon
## Buttons
@docs containedButton, outlinedButton, textButton
## Text fields
@docs textInput, passwordInput
## Items in a list
@docs itemWithSubtext
## Lists
@docs sideList
## Other elements
@docs loadingIndicator
-}
import Color exposing (Color)
import Element exposing (Element)
import Element.Input
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.
-}
containedButton :
{ buttonColor : Color
, clickColor : Color
, icon : Icon msg
, onPress : Maybe msg
, text : String
}
-> Element msg
containedButton data =
Widget.button
({ primary = data.buttonColor, onPrimary = data.clickColor }
|> singlePalette
|> Material.containedButton
|> Customize.elementButton [ Element.width Element.fill ]
)
{ text = data.text, icon = data.icon, onPress = data.onPress }
iconAsElement :
{ color : Color
, height : Int
, icon : Material.Icons.Types.Icon msg
, width : Int
} -> Element msg
iconAsElement data =
data.icon
|> iconAsIcon
|> (|>) { size = Basics.min data.height data.width, color = data.color }
|> Element.el [ Element.centerX, Element.centerY ]
|> Element.el
[ Element.height (Element.px data.height)
, 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 :
{ color : Color
, leftIcon : Widget.Icon.Icon msg
, onPress : Maybe msg
, rightIcon : Widget.Icon.Icon msg
, text : String
, title : String
}
-> Widget.Item msg
itemWithSubtext data =
Widget.multiLineItem
( { primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.multiLineItem
)
{ content = data.rightIcon
, icon = data.leftIcon
, onPress = data.onPress
, title = data.title
, text = data.text
}
{-| Circular loading bar indicator
-}
loadingIndicator :
{ color : Color
}
-> Element msg
loadingIndicator data =
Widget.circularProgressIndicator
( { primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.progressIndicator
)
Nothing
{-| An outlined button representing an important action within a group.
-}
outlinedButton :
{ color : Color
, icon : Icon msg
, onPress : Maybe msg
, text : String
}
-> Element msg
outlinedButton data =
Widget.button
({ primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.outlinedButton
)
{ text = data.text, icon = data.icon, onPress = data.onPress }
{-| Show a password field
-}
passwordInput :
{ color : Color
, label : String
, onChange : String -> msg
, placeholder : Maybe String
, show : Bool
, text : String
}
-> Element msg
passwordInput data =
Widget.currentPasswordInputV2
({ primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.passwordInput
|> Customize.elementRow [ Element.width Element.fill ]
)
{ label = data.label
, onChange = data.onChange
, placeholder =
data.placeholder
|> Maybe.map Element.text
|> Maybe.map (Element.Input.placeholder [])
, show = data.show
, text = data.text
}
{-| Create a simple palette.
-}
singlePalette : { primary : Color, onPrimary : Color } -> Material.Palette
singlePalette { primary, onPrimary } =
{ primary = primary
, secondary = primary
, background = primary
, surface = primary
, error = primary
, on =
{ primary = onPrimary
, secondary = onPrimary
, background = onPrimary
, surface = onPrimary
, error = onPrimary
}
}
sideList : { color : Color, items : List (Widget.Item msg) }-> Element msg
sideList data =
Widget.itemList
( { primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.sideSheet
)
data.items
{-| A tab selector that always has an item selected.
-}
tab :
{ color : Color
, content : Int -> Element msg
, items : List { text : String, icon : Icon msg }
, onSelect : Int -> msg
, selected : Int
}
-> Element msg
tab data =
Widget.tab
({ primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.tab
)
{ tabs =
{ onSelect = data.onSelect >> Just
, options = data.items
, selected = Just data.selected
}
, content = \_ -> data.content data.selected
}
{-| A text button representing an important action within a group.
-}
textButton :
{ icon : Icon msg
, onPress : Maybe msg
, text : String
, color : Color
}
-> Element msg
textButton data =
Widget.button
({ primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.textButton
)
{ text = data.text, icon = data.icon, onPress = data.onPress }
{-| Text input element.
-}
textInput :
{ color : Color
, label : String
, onChange : String -> msg
, placeholder : Maybe String
, text : String
}
-> Element msg
textInput data =
Widget.textInput
({ primary = data.color, onPrimary = data.color }
|> singlePalette
|> Material.textInput
|> Customize.elementRow [ Element.width Element.fill ]
)
{ chips = []
, text = data.text
, placeholder =
data.placeholder
|> Maybe.map Element.text
|> Maybe.map (Element.Input.placeholder [])
, label = data.label
, onChange = data.onChange
}

227
src/Main.elm Normal file
View File

@ -0,0 +1,227 @@
module Main exposing (..)
import Browser
import Browser.Dom
import Browser.Events
import Element exposing (Element)
import Element.Background
import Task
import Theme
import Items.LoginScreen as LoginScreen
import Items.VaultScreen as VaultScreen
import Items.WelcomeScreen as WelcomeScreen
import Element.Font
import Items.VaultScreen as VaultScreen
main : Program () Model Msg
main =
Browser.document
{ init = init
, subscriptions = subscriptions
, update = update
, view = view
}
-- MODEL
type alias Model =
{ flavor : Theme.Flavor
, height : Int
, screen : Screen
, width : Int
}
type Screen
= ScreenLogin LoginScreen.Model
| ScreenWelcome WelcomeScreen.Model
| ScreenVault WelcomeScreen.Model Int VaultScreen.Model
type Msg
= OnScreenLogin LoginScreen.Msg
| OnScreenVault Int VaultScreen.Msg
| OnScreenWelcome WelcomeScreen.Msg
| OnSelectVault Int
| Pass
| ScreenSize { height : Int, width : Int }
| SetFlavor Theme.Flavor
init : () -> ( Model, Cmd Msg )
init () =
( { flavor = Theme.Latte
, height = 480
, screen = ScreenWelcome (WelcomeScreen.init [])
, width = 720
}
, Browser.Dom.getViewport
|> Task.perform
(\viewport ->
ScreenSize
{ height = floor viewport.viewport.height
, width = floor viewport.viewport.width
}
)
)
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnScreenLogin m ->
case model.screen of
ScreenLogin mdl ->
case LoginScreen.update m mdl of
newMdl ->
( { model | screen = ScreenLogin newMdl }
, Cmd.none
)
_ ->
( model, Cmd.none )
OnScreenVault i m ->
case model.screen of
ScreenVault welcomeMdl j mdl ->
if i == j then
case VaultScreen.update m mdl of
( newMdl, cmd ) ->
( { model | screen = ScreenVault welcomeMdl i newMdl }
, Cmd.map (OnScreenVault i) cmd
)
else
( model, Cmd.none )
_ ->
( model, Cmd.none )
OnScreenWelcome m ->
case model.screen of
ScreenWelcome mdl ->
case WelcomeScreen.update m mdl of
newMdl ->
( { model | screen = ScreenWelcome newMdl }
, Cmd.none
)
_ ->
( model, Cmd.none )
OnSelectVault i ->
case model.screen of
ScreenVault welcomeMdl j _ ->
if i == j then
( model, Cmd.none )
else
( { model | screen = ScreenVault welcomeMdl i VaultScreen.init }
, Cmd.none
)
ScreenWelcome mdl ->
( { model | screen = ScreenVault mdl i VaultScreen.init }
, Cmd.none
)
_ ->
( model, Cmd.none )
Pass ->
( model, Cmd.none )
ScreenSize { height, width } ->
( { model | height = height, width = width }
, Cmd.none
)
SetFlavor flavor ->
( { model | flavor = flavor }, Cmd.none )
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.batch
[ Browser.Events.onResize (\w h -> ScreenSize { width = w, height = h })
]
-- VIEW
view : Model -> Browser.Document Msg
view model =
{ title = "Matrix Plier"
, body =
model
|> viewScreen
|> Element.layout
[ Element.Background.color (Theme.baseUI model.flavor)
, Element.Font.color (Theme.textUI model.flavor)
]
|> List.singleton
}
viewScreen : Model -> Element Msg
viewScreen model =
case model.screen of
ScreenLogin mdl ->
LoginScreen.view
{ colorBackground = Theme.base model.flavor
, colorMain = Theme.mauve model.flavor
, colorMenu = Theme.mantle model.flavor
, colorText = Theme.text model.flavor
, colorTextField = Theme.surface0 model.flavor
, flavor = model.flavor
, height = model.height
, model = mdl
, onFlavorPick = SetFlavor
, onSubmit = always Pass
, toMsg = OnScreenLogin
, width = model.width
}
ScreenVault welcomeMdl i mdl ->
case WelcomeScreen.getVault i welcomeMdl of
Just vault ->
VaultScreen.view
{ colorSelectedRoom = Theme.mantle model.flavor
, colorText = Theme.text model.flavor
, height = model.height
, model = mdl
, onVaultUpdate = always Pass
, toMsg = OnScreenVault i
, vault = vault
, width = model.width
}
Nothing ->
viewScreen { model | screen = ScreenWelcome welcomeMdl }
ScreenWelcome mdl ->
WelcomeScreen.view
{ colorBackground = Theme.base model.flavor
, colorBackground2 = Theme.mantle model.flavor
, colorMain = Theme.mauve model.flavor
, colorMenu = Theme.surface0 model.flavor
, colorText = Theme.text model.flavor
, colorTextField = Theme.surface1 model.flavor
, flavor = model.flavor
, height = model.height
, model = mdl
, onFlavorPick = SetFlavor
, onSelectVault = OnSelectVault
, toMsg = OnScreenWelcome
, width = model.width
}

583
src/Theme.elm Normal file
View File

@ -0,0 +1,583 @@
module Theme exposing (..)
{-|
# Theme
The Theme helps pick colors from different color schemes.
-}
import Catppuccin.Frappe as CF
import Catppuccin.Latte as CL
import Catppuccin.Macchiato as CA
import Catppuccin.Mocha as CO
import Color
import Element
import Element.Background
{-| Catppuccin flavor used for display.
-}
type Flavor
= Frappe
| Latte
| Macchiato
| Mocha
background : (Flavor -> Color.Color) -> Flavor -> Element.Attribute msg
background toColor =
toColor >> toElmUiColor >> Element.Background.color
toElmUiColor : Color.Color -> Element.Color
toElmUiColor =
Color.toRgba >> Element.fromRgb
rosewater : Flavor -> Color.Color
rosewater flavor =
case flavor of
Frappe ->
CF.rosewater
Latte ->
CL.rosewater
Macchiato ->
CA.rosewater
Mocha ->
CO.rosewater
rosewaterUI : Flavor -> Element.Color
rosewaterUI =
rosewater >> toElmUiColor
flamingo : Flavor -> Color.Color
flamingo flavor =
case flavor of
Frappe ->
CF.flamingo
Latte ->
CL.flamingo
Macchiato ->
CA.flamingo
Mocha ->
CO.flamingo
flamingoUI : Flavor -> Element.Color
flamingoUI =
flamingo >> toElmUiColor
pink : Flavor -> Color.Color
pink flavor =
case flavor of
Frappe ->
CF.pink
Latte ->
CL.pink
Macchiato ->
CA.pink
Mocha ->
CO.pink
pinkUI : Flavor -> Element.Color
pinkUI =
pink >> toElmUiColor
mauve : Flavor -> Color.Color
mauve flavor =
case flavor of
Frappe ->
CF.mauve
Latte ->
CL.mauve
Macchiato ->
CA.mauve
Mocha ->
CO.mauve
mauveUI : Flavor -> Element.Color
mauveUI =
mauve >> toElmUiColor
red : Flavor -> Color.Color
red flavor =
case flavor of
Frappe ->
CF.red
Latte ->
CL.red
Macchiato ->
CA.red
Mocha ->
CO.red
redUI : Flavor -> Element.Color
redUI =
red >> toElmUiColor
maroon : Flavor -> Color.Color
maroon flavor =
case flavor of
Frappe ->
CF.maroon
Latte ->
CL.maroon
Macchiato ->
CA.maroon
Mocha ->
CO.maroon
maroonUI : Flavor -> Element.Color
maroonUI =
maroon >> toElmUiColor
peach : Flavor -> Color.Color
peach flavor =
case flavor of
Frappe ->
CF.peach
Latte ->
CL.peach
Macchiato ->
CA.peach
Mocha ->
CO.peach
peachUI : Flavor -> Element.Color
peachUI =
peach >> toElmUiColor
yellow : Flavor -> Color.Color
yellow flavor =
case flavor of
Frappe ->
CF.yellow
Latte ->
CL.yellow
Macchiato ->
CA.yellow
Mocha ->
CO.yellow
yellowUI : Flavor -> Element.Color
yellowUI =
yellow >> toElmUiColor
green : Flavor -> Color.Color
green flavor =
case flavor of
Frappe ->
CF.green
Latte ->
CL.green
Macchiato ->
CA.green
Mocha ->
CO.green
greenUI : Flavor -> Element.Color
greenUI =
green >> toElmUiColor
teal : Flavor -> Color.Color
teal flavor =
case flavor of
Frappe ->
CF.teal
Latte ->
CL.teal
Macchiato ->
CA.teal
Mocha ->
CO.teal
tealUI : Flavor -> Element.Color
tealUI =
teal >> toElmUiColor
sky : Flavor -> Color.Color
sky flavor =
case flavor of
Frappe ->
CF.sky
Latte ->
CL.sky
Macchiato ->
CA.sky
Mocha ->
CO.sky
skyUI : Flavor -> Element.Color
skyUI =
sky >> toElmUiColor
sapphire : Flavor -> Color.Color
sapphire flavor =
case flavor of
Frappe ->
CF.sapphire
Latte ->
CL.sapphire
Macchiato ->
CA.sapphire
Mocha ->
CO.sapphire
sapphireUI : Flavor -> Element.Color
sapphireUI =
sapphire >> toElmUiColor
blue : Flavor -> Color.Color
blue flavor =
case flavor of
Frappe ->
CF.blue
Latte ->
CL.blue
Macchiato ->
CA.blue
Mocha ->
CO.blue
blueUI : Flavor -> Element.Color
blueUI =
blue >> toElmUiColor
lavender : Flavor -> Color.Color
lavender flavor =
case flavor of
Frappe ->
CF.lavender
Latte ->
CL.lavender
Macchiato ->
CA.lavender
Mocha ->
CO.lavender
lavenderUI : Flavor -> Element.Color
lavenderUI =
lavender >> toElmUiColor
text : Flavor -> Color.Color
text flavor =
case flavor of
Frappe ->
CF.text
Latte ->
CL.text
Macchiato ->
CA.text
Mocha ->
CO.text
textUI : Flavor -> Element.Color
textUI =
text >> toElmUiColor
subtext1 : Flavor -> Color.Color
subtext1 flavor =
case flavor of
Frappe ->
CF.subtext1
Latte ->
CL.subtext1
Macchiato ->
CA.subtext1
Mocha ->
CO.subtext1
subtext1UI : Flavor -> Element.Color
subtext1UI =
subtext1 >> toElmUiColor
subtext0 : Flavor -> Color.Color
subtext0 flavor =
case flavor of
Frappe ->
CF.subtext0
Latte ->
CL.subtext0
Macchiato ->
CA.subtext0
Mocha ->
CO.subtext0
subtext0UI : Flavor -> Element.Color
subtext0UI =
subtext0 >> toElmUiColor
overlay2 : Flavor -> Color.Color
overlay2 flavor =
case flavor of
Frappe ->
CF.overlay2
Latte ->
CL.overlay2
Macchiato ->
CA.overlay2
Mocha ->
CO.overlay2
overlay2UI : Flavor -> Element.Color
overlay2UI =
overlay2 >> toElmUiColor
overlay1 : Flavor -> Color.Color
overlay1 flavor =
case flavor of
Frappe ->
CF.overlay1
Latte ->
CL.overlay1
Macchiato ->
CA.overlay1
Mocha ->
CO.overlay1
overlay1UI : Flavor -> Element.Color
overlay1UI =
overlay1 >> toElmUiColor
overlay0 : Flavor -> Color.Color
overlay0 flavor =
case flavor of
Frappe ->
CF.overlay0
Latte ->
CL.overlay0
Macchiato ->
CA.overlay0
Mocha ->
CO.overlay0
overlay0UI : Flavor -> Element.Color
overlay0UI =
overlay0 >> toElmUiColor
surface2 : Flavor -> Color.Color
surface2 flavor =
case flavor of
Frappe ->
CF.surface2
Latte ->
CL.surface2
Macchiato ->
CA.surface2
Mocha ->
CO.surface2
surface2UI : Flavor -> Element.Color
surface2UI =
surface2 >> toElmUiColor
surface1 : Flavor -> Color.Color
surface1 flavor =
case flavor of
Frappe ->
CF.surface1
Latte ->
CL.surface1
Macchiato ->
CA.surface1
Mocha ->
CO.surface1
surface1UI : Flavor -> Element.Color
surface1UI =
surface1 >> toElmUiColor
surface0 : Flavor -> Color.Color
surface0 flavor =
case flavor of
Frappe ->
CF.surface0
Latte ->
CL.surface0
Macchiato ->
CA.surface0
Mocha ->
CO.surface0
surface0UI : Flavor -> Element.Color
surface0UI =
surface0 >> toElmUiColor
base : Flavor -> Color.Color
base flavor =
case flavor of
Frappe ->
CF.base
Latte ->
CL.base
Macchiato ->
CA.base
Mocha ->
CO.base
baseUI : Flavor -> Element.Color
baseUI =
base >> toElmUiColor
mantle : Flavor -> Color.Color
mantle flavor =
case flavor of
Frappe ->
CF.mantle
Latte ->
CL.mantle
Macchiato ->
CA.mantle
Mocha ->
CO.mantle
mantleUI : Flavor -> Element.Color
mantleUI =
mantle >> toElmUiColor
crust : Flavor -> Color.Color
crust flavor =
case flavor of
Frappe ->
CF.crust
Latte ->
CL.crust
Macchiato ->
CA.crust
Mocha ->
CO.crust
crustUI : Flavor -> Element.Color
crustUI =
crust >> toElmUiColor