1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-17 17:47:03 +00:00

ui: Get UI to compile with Elm 0.19

This commit is contained in:
James Hillyerd
2018-11-13 22:03:36 -08:00
parent 5ccdece541
commit fe20854173
15 changed files with 210 additions and 180 deletions

View File

@@ -1,35 +0,0 @@
{
"version": "1.0.0",
"summary": "Elm powered UI for Inbucket",
"repository": "https://github.com/jhillyerd/inbucket.git",
"license": "MIT",
"source-directories": [
"src"
],
"exposed-modules": [],
"proxy": {
"/api": {
"target": "http://localhost:9000",
"ws": true
},
"/debug": {
"target": "http://localhost:9000"
},
"/serve": {
"target": "http://localhost:9000"
}
},
"dependencies": {
"NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0",
"basti1302/elm-human-readable-filesize": "1.1.0 <= v < 2.0.0",
"elm-lang/core": "5.1.1 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0",
"elm-lang/http": "1.0.0 <= v < 2.0.0",
"elm-lang/navigation": "2.1.0 <= v < 3.0.0",
"elm-lang/svg": "2.0.0 <= v < 3.0.0",
"evancz/url-parser": "2.0.1 <= v < 3.0.0",
"jweir/sparkline": "3.0.0 <= v < 4.0.0",
"ryannhg/elm-date-format": "2.1.2 <= v < 3.0.0"
},
"elm-version": "0.18.0 <= v < 0.19.0"
}

32
ui/elm.json Normal file
View File

@@ -0,0 +1,32 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.0",
"dependencies": {
"direct": {
"NoRedInk/elm-json-decode-pipeline": "1.0.0",
"basti1302/elm-human-readable-filesize": "1.1.1",
"elm/browser": "1.0.1",
"elm/core": "1.0.0",
"elm/html": "1.0.0",
"elm/http": "1.0.0",
"elm/json": "1.0.0",
"elm/svg": "1.0.1",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"jweir/sparkline": "4.0.0",
"ryannhg/date-format": "2.1.0"
},
"indirect": {
"elm/regex": "1.0.0",
"elm/virtual-dom": "1.0.2",
"myrho/elm-round": "1.0.4"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View File

@@ -1,21 +1,11 @@
module Data.Date exposing (date) module Data.Date exposing (date)
import Date exposing (Date)
import Json.Decode as Decode exposing (..) import Json.Decode as Decode exposing (..)
import Time exposing (Posix)
{-| Decode an ISO 8601 date {-| Decode a POSIX milliseconds timestamp. Currently faked until backend API is updated.
-} -}
date : Decoder Date date : Decoder Posix
date = date =
let succeed (Time.millisToPosix 0)
convert : String -> Decoder Date
convert raw =
case Date.fromString raw of
Ok date ->
succeed date
Err error ->
fail error
in
string |> andThen convert

View File

@@ -1,9 +1,9 @@
module Data.Message exposing (Attachment, Message, attachmentDecoder, decoder) module Data.Message exposing (Attachment, Message, attachmentDecoder, decoder)
import Data.Date exposing (date) import Data.Date exposing (date)
import Date exposing (Date)
import Json.Decode as Decode exposing (..) import Json.Decode as Decode exposing (..)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Time exposing (Posix)
type alias Message = type alias Message =
@@ -12,7 +12,7 @@ type alias Message =
, from : String , from : String
, to : List String , to : List String
, subject : String , subject : String
, date : Date , date : Posix
, size : Int , size : Int
, seen : Bool , seen : Bool
, text : String , text : String
@@ -30,7 +30,7 @@ type alias Attachment =
decoder : Decoder Message decoder : Decoder Message
decoder = decoder =
decode Message succeed Message
|> required "mailbox" string |> required "mailbox" string
|> required "id" string |> required "id" string
|> optional "from" string "" |> optional "from" string ""
@@ -46,7 +46,7 @@ decoder =
attachmentDecoder : Decoder Attachment attachmentDecoder : Decoder Attachment
attachmentDecoder = attachmentDecoder =
decode Attachment succeed Attachment
|> required "id" string |> required "id" string
|> required "filename" string |> required "filename" string
|> required "content-type" string |> required "content-type" string

View File

@@ -1,9 +1,9 @@
module Data.MessageHeader exposing (MessageHeader, decoder) module Data.MessageHeader exposing (MessageHeader, decoder)
import Data.Date exposing (date) import Data.Date exposing (date)
import Date exposing (Date)
import Json.Decode as Decode exposing (..) import Json.Decode as Decode exposing (..)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Time exposing (Posix)
type alias MessageHeader = type alias MessageHeader =
@@ -12,7 +12,7 @@ type alias MessageHeader =
, from : String , from : String
, to : List String , to : List String
, subject : String , subject : String
, date : Date , date : Posix
, size : Int , size : Int
, seen : Bool , seen : Bool
} }
@@ -20,7 +20,7 @@ type alias MessageHeader =
decoder : Decoder MessageHeader decoder : Decoder MessageHeader
decoder = decoder =
decode MessageHeader succeed MessageHeader
|> required "mailbox" string |> required "mailbox" string
|> required "id" string |> required "id" string
|> optional "from" string "" |> optional "from" string ""

View File

@@ -31,7 +31,7 @@ type alias Metrics =
decoder : Decoder Metrics decoder : Decoder Metrics
decoder = decoder =
decode Metrics succeed Metrics
|> requiredAt [ "memstats", "Sys" ] int |> requiredAt [ "memstats", "Sys" ] int
|> requiredAt [ "memstats", "HeapSys" ] int |> requiredAt [ "memstats", "HeapSys" ] int
|> requiredAt [ "memstats", "HeapAlloc" ] int |> requiredAt [ "memstats", "HeapAlloc" ] int
@@ -59,4 +59,4 @@ decoder =
-} -}
decodeIntList : Decoder (List Int) decodeIntList : Decoder (List Int)
decodeIntList = decodeIntList =
map (String.split "," >> List.map (String.toInt >> Result.withDefault 0)) string map (String.split "," >> List.map (String.toInt >> Maybe.withDefault 0)) string

View File

@@ -9,13 +9,15 @@ module Data.Session exposing
, update , update
) )
import Json.Decode as Decode exposing (..) import Browser.Navigation as Nav
import Json.Decode exposing (..)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Navigation exposing (Location) import Url exposing (Url)
type alias Session = type alias Session =
{ host : String { key : Nav.Key
, host : String
, flash : String , flash : String
, routing : Bool , routing : Bool
, persistent : Persistent , persistent : Persistent
@@ -36,9 +38,9 @@ type Msg
| AddRecent String | AddRecent String
init : Location -> Persistent -> Session init : Nav.Key -> Url -> Persistent -> Session
init location persistent = init key location persistent =
Session location.host "" True persistent Session key location.host "" True persistent
update : Msg -> Session -> Session update : Msg -> Session -> Session
@@ -84,10 +86,10 @@ none =
decoder : Decoder Persistent decoder : Decoder Persistent
decoder = decoder =
decode Persistent succeed Persistent
|> optional "recentMailboxes" (list string) [] |> optional "recentMailboxes" (list string) []
decodeValueWithDefault : Value -> Persistent decodeValueWithDefault : Value -> Persistent
decodeValueWithDefault = decodeValueWithDefault =
Decode.decodeValue decoder >> Result.withDefault { recentMailboxes = [] } decodeValue decoder >> Result.withDefault { recentMailboxes = [] }

View File

@@ -42,11 +42,11 @@ errorString error =
"HTTP Network error" "HTTP Network error"
Http.BadStatus res -> Http.BadStatus res ->
"Bad HTTP status: " ++ toString res.status.code "Bad HTTP status: " ++ String.fromInt res.status.code
Http.BadPayload msg res -> Http.BadPayload msg res ->
"Bad HTTP payload: " "Bad HTTP payload: "
++ msg ++ msg
++ " (" ++ " ("
++ toString res.status.code ++ String.fromInt res.status.code
++ ")" ++ ")"

View File

@@ -1,15 +1,31 @@
module Main exposing (Model, Msg(..), Page(..), applySession, init, main, pageSubscriptions, sessionChange, setRoute, subscriptions, update, updatePage, view) module Main exposing
( Model
, Msg(..)
, Page(..)
, applySession
, init
, main
, pageSubscriptions
, sessionChange
, setRoute
, subscriptions
, update
, updatePage
, view
)
import Browser exposing (Document, UrlRequest)
import Browser.Navigation as Nav
import Data.Session as Session exposing (Session, decoder) import Data.Session as Session exposing (Session, decoder)
import Html exposing (..) import Html exposing (..)
import Json.Decode as Decode exposing (Value) import Json.Decode as D exposing (Value)
import Navigation exposing (Location)
import Page.Home as Home import Page.Home as Home
import Page.Mailbox as Mailbox import Page.Mailbox as Mailbox
import Page.Monitor as Monitor import Page.Monitor as Monitor
import Page.Status as Status import Page.Status as Status
import Ports import Ports
import Route exposing (Route) import Route exposing (Route)
import Url exposing (Url)
import Views.Page as Page exposing (ActivePage(..), frame) import Views.Page as Page exposing (ActivePage(..), frame)
@@ -31,11 +47,11 @@ type alias Model =
} }
init : Value -> Location -> ( Model, Cmd Msg ) init : Value -> Url -> Nav.Key -> ( Model, Cmd Msg )
init sessionValue location = init sessionValue location key =
let let
session = session =
Session.init location (Session.decodeValueWithDefault sessionValue) Session.init key location (Session.decodeValueWithDefault sessionValue)
( subModel, _ ) = ( subModel, _ ) =
Home.init Home.init
@@ -47,15 +63,16 @@ init sessionValue location =
} }
route = route =
Route.fromLocation location Route.fromUrl location
in in
applySession (setRoute route model) applySession (setRoute route model)
type Msg type Msg
= SetRoute Route = SetRoute Route
| NewRoute Route | UrlChanged Url
| UpdateSession (Result String Session.Persistent) | LinkClicked UrlRequest
| UpdateSession (Result D.Error Session.Persistent)
| MailboxNameInput String | MailboxNameInput String
| ViewMailbox String | ViewMailbox String
| HomeMsg Home.Msg | HomeMsg Home.Msg
@@ -76,9 +93,9 @@ subscriptions model =
] ]
sessionChange : Sub (Result String Session.Persistent) sessionChange : Sub (Result D.Error Session.Persistent)
sessionChange = sessionChange =
Ports.onSessionChange (Decode.decodeValue Session.decoder) Ports.onSessionChange (D.decodeValue Session.decoder)
pageSubscriptions : Page -> Sub Msg pageSubscriptions : Page -> Sub Msg
@@ -105,19 +122,27 @@ update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
applySession <| applySession <|
case msg of case msg of
SetRoute route -> LinkClicked req ->
-- Updates broser URL to requested route. case req of
( model, Route.newUrl route, Session.none ) Browser.Internal url ->
( model, Nav.pushUrl model.session.key (Url.toString url), Session.none )
NewRoute route -> _ ->
Debug.todo "implement external links"
UrlChanged url ->
-- Responds to new browser URL. -- Responds to new browser URL.
if model.session.routing then if model.session.routing then
setRoute route model setRoute (Route.fromUrl url) model
else else
-- Skip once, but re-enable routing. -- Skip once, but re-enable routing.
( model, Cmd.none, Session.EnableRouting ) ( model, Cmd.none, Session.EnableRouting )
SetRoute route ->
-- Updates broser URL to requested route.
( model, Route.newUrl model.session.key route, Session.none )
UpdateSession (Ok persistent) -> UpdateSession (Ok persistent) ->
let let
session = session =
@@ -140,7 +165,7 @@ update msg model =
ViewMailbox name -> ViewMailbox name ->
( { model | mailboxName = "" } ( { model | mailboxName = "" }
, Route.newUrl (Route.Mailbox name) , Route.newUrl model.session.key (Route.Mailbox name)
, Session.none , Session.none
) )
@@ -269,7 +294,7 @@ applySession ( model, cmd, sessionMsg ) =
-- VIEW -- VIEW
view : Model -> Html Msg view : Model -> Document Msg
view model = view model =
let let
mailbox = mailbox =
@@ -291,22 +316,24 @@ view model =
frame = frame =
Page.frame controls model.session Page.frame controls model.session
in in
case model.page of Document "Inbucket Document"
Home subModel -> [ case model.page of
Html.map HomeMsg (Home.view model.session subModel) Home subModel ->
|> frame Page.Other Html.map HomeMsg (Home.view model.session subModel)
|> frame Page.Other
Mailbox subModel -> Mailbox subModel ->
Html.map MailboxMsg (Mailbox.view model.session subModel) Html.map MailboxMsg (Mailbox.view model.session subModel)
|> frame Page.Mailbox |> frame Page.Mailbox
Monitor subModel -> Monitor subModel ->
Html.map MonitorMsg (Monitor.view model.session subModel) Html.map MonitorMsg (Monitor.view model.session subModel)
|> frame Page.Monitor |> frame Page.Monitor
Status subModel -> Status subModel ->
Html.map StatusMsg (Status.view model.session subModel) Html.map StatusMsg (Status.view model.session subModel)
|> frame Page.Status |> frame Page.Status
]
@@ -315,9 +342,11 @@ view model =
main : Program Value Model Msg main : Program Value Model Msg
main = main =
Navigation.programWithFlags (Route.fromLocation >> NewRoute) Browser.application
{ init = init { init = init
, view = view , view = view
, update = update , update = update
, subscriptions = subscriptions , subscriptions = subscriptions
, onUrlChange = UrlChanged
, onUrlRequest = LinkClicked
} }

View File

@@ -3,7 +3,6 @@ module Page.Mailbox exposing (Model, Msg, init, load, subscriptions, update, vie
import Data.Message as Message exposing (Message) import Data.Message as Message exposing (Message)
import Data.MessageHeader as MessageHeader exposing (MessageHeader) import Data.MessageHeader as MessageHeader exposing (MessageHeader)
import Data.Session as Session exposing (Session) import Data.Session as Session exposing (Session)
import Date exposing (Date)
import DateFormat import DateFormat
import DateFormat.Relative as Relative import DateFormat.Relative as Relative
import Html exposing (..) import Html exposing (..)
@@ -11,7 +10,7 @@ import Html.Attributes
exposing exposing
( class ( class
, classList , classList
, downloadAs , download
, href , href
, id , id
, placeholder , placeholder
@@ -28,7 +27,7 @@ import Json.Encode as Encode
import Ports import Ports
import Route import Route
import Task import Task
import Time exposing (Time) import Time exposing (Posix)
@@ -65,7 +64,7 @@ type alias MessageList =
type alias VisibleMessage = type alias VisibleMessage =
{ message : Message { message : Message
, markSeenAt : Maybe Time , markSeenAt : Maybe Int
} }
@@ -74,13 +73,13 @@ type alias Model =
, state : State , state : State
, bodyMode : Body , bodyMode : Body
, searchInput : String , searchInput : String
, now : Date , now : Posix
} }
init : String -> Maybe MessageID -> ( Model, Cmd Msg ) init : String -> Maybe MessageID -> ( Model, Cmd Msg )
init mailboxName selection = init mailboxName selection =
( Model mailboxName (LoadingList selection) SafeHtmlBody "" (Date.fromTime 0) ( Model mailboxName (LoadingList selection) SafeHtmlBody "" (Time.millisToPosix 0)
, load mailboxName , load mailboxName
) )
@@ -108,13 +107,13 @@ subscriptions model =
Sub.none Sub.none
else else
Time.every (250 * Time.millisecond) SeenTick Time.every 250 SeenTick
_ -> _ ->
Sub.none Sub.none
in in
Sub.batch Sub.batch
[ Time.every (30 * Time.second) Tick [ Time.every (30 * 1000) Tick
, subSeen , subSeen
] ]
@@ -131,12 +130,12 @@ type Msg
| MarkSeenResult (Result Http.Error ()) | MarkSeenResult (Result Http.Error ())
| MessageResult (Result Http.Error Message) | MessageResult (Result Http.Error Message)
| MessageBody Body | MessageBody Body
| OpenedTime Time | OpenedTime Posix
| Purge | Purge
| PurgeResult (Result Http.Error ()) | PurgeResult (Result Http.Error ())
| SearchInput String | SearchInput String
| SeenTick Time | SeenTick Posix
| Tick Time | Tick Posix
| ViewMessage MessageID | ViewMessage MessageID
@@ -147,7 +146,7 @@ update session msg model =
( updateSelected model id ( updateSelected model id
, Cmd.batch , Cmd.batch
[ -- Update browser location. [ -- Update browser location.
Route.newUrl (Route.Message model.mailboxName id) Route.newUrl session.key (Route.Message model.mailboxName id)
, getMessage model.mailboxName id , getMessage model.mailboxName id
] ]
, Session.DisableRouting , Session.DisableRouting
@@ -216,13 +215,17 @@ update session msg model =
( model, Cmd.none, Session.none ) ( model, Cmd.none, Session.none )
else else
-- Set delay before reporting message as seen to backend. -- Set 1500ms delay before reporting message as seen to backend.
let
markSeenAt =
Time.posixToMillis time + 1500
in
( { model ( { model
| state = | state =
ShowingList list ShowingList list
(ShowingMessage (ShowingMessage
{ visible { visible
| markSeenAt = Just (time + (1.5 * Time.second)) | markSeenAt = Just markSeenAt
} }
) )
} }
@@ -247,7 +250,7 @@ update session msg model =
ShowingList _ (ShowingMessage { message, markSeenAt }) -> ShowingList _ (ShowingMessage { message, markSeenAt }) ->
case markSeenAt of case markSeenAt of
Just deadline -> Just deadline ->
if now >= deadline then if Time.posixToMillis now >= deadline then
updateMarkMessageSeen model message updateMarkMessageSeen model message
else else
@@ -260,7 +263,7 @@ update session msg model =
( model, Cmd.none, Session.none ) ( model, Cmd.none, Session.none )
Tick now -> Tick now ->
( { model | now = Date.fromTime now }, Cmd.none, Session.none ) ( { model | now = now }, Cmd.none, Session.none )
{-| Replace the currently displayed message. {-| Replace the currently displayed message.
@@ -530,14 +533,14 @@ messageChip model selected message =
viewMessage : Message -> Body -> Html Msg viewMessage : Message -> Body -> Html Msg
viewMessage message bodyMode = viewMessage message bodyMode =
let let
sourceUrl message = sourceUrl =
"/serve/m/" ++ message.mailbox ++ "/" ++ message.id ++ "/source" "/serve/m/" ++ message.mailbox ++ "/" ++ message.id ++ "/source"
in in
div [] div []
[ div [ class "button-bar" ] [ div [ class "button-bar" ]
[ button [ class "danger", onClick (DeleteMessage message) ] [ text "Delete" ] [ button [ class "danger", onClick (DeleteMessage message) ] [ text "Delete" ]
, a , a
[ href (sourceUrl message), target "_blank" ] [ href sourceUrl, target "_blank" ]
[ button [] [ text "Source" ] ] [ button [] [ text "Source" ] ]
] ]
, dl [ id "message-header" ] , dl [ id "message-header" ]
@@ -616,16 +619,16 @@ attachmentRow baseUrl attach =
[ a [ href url, target "_blank" ] [ text attach.fileName ] [ a [ href url, target "_blank" ] [ text attach.fileName ]
, text (" (" ++ attach.contentType ++ ") ") , text (" (" ++ attach.contentType ++ ") ")
] ]
, td [] [ a [ href url, downloadAs attach.fileName, class "button" ] [ text "Download" ] ] , td [] [ a [ href url, download attach.fileName, class "button" ] [ text "Download" ] ]
] ]
relativeDate : Model -> Date -> Html Msg relativeDate : Model -> Posix -> Html Msg
relativeDate model date = relativeDate model date =
Relative.relativeTime model.now date |> text Relative.relativeTime model.now date |> text
verboseDate : Date -> Html Msg verboseDate : Posix -> Html Msg
verboseDate date = verboseDate date =
DateFormat.format DateFormat.format
[ DateFormat.monthNameFull [ DateFormat.monthNameFull
@@ -642,6 +645,7 @@ verboseDate date =
, DateFormat.text " " , DateFormat.text " "
, DateFormat.amPmUppercase , DateFormat.amPmUppercase
] ]
Time.utc
date date
|> text |> text

View File

@@ -2,7 +2,6 @@ module Page.Monitor exposing (Model, Msg, init, subscriptions, update, view)
import Data.MessageHeader as MessageHeader exposing (MessageHeader) import Data.MessageHeader as MessageHeader exposing (MessageHeader)
import Data.Session as Session exposing (Session) import Data.Session as Session exposing (Session)
import Date exposing (Date)
import DateFormat import DateFormat
exposing exposing
( amPmUppercase ( amPmUppercase
@@ -10,7 +9,7 @@ import DateFormat
, format , format
, hourNumber , hourNumber
, minuteFixed , minuteFixed
, monthNameFirstThree , monthNameAbbreviated
) )
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@@ -18,6 +17,7 @@ import Html.Events as Events
import Json.Decode as D import Json.Decode as D
import Ports import Ports
import Route import Route
import Time exposing (Posix)
@@ -63,7 +63,7 @@ subscriptions model =
type Msg type Msg
= MonitorResult (Result String MonitorMessage) = MonitorResult (Result D.Error MonitorMessage)
| OpenMessage MessageHeader | OpenMessage MessageHeader
@@ -78,15 +78,15 @@ update session msg model =
MonitorResult (Ok (Connected status)) -> MonitorResult (Ok (Connected status)) ->
( { model | connected = status }, Cmd.none, Session.none ) ( { model | connected = status }, Cmd.none, Session.none )
MonitorResult (Ok (Message msg)) -> MonitorResult (Ok (Message header)) ->
( { model | messages = msg :: model.messages }, Cmd.none, Session.none ) ( { model | messages = header :: model.messages }, Cmd.none, Session.none )
MonitorResult (Err err) -> MonitorResult (Err err) ->
( model, Cmd.none, Session.SetFlash err ) ( model, Cmd.none, Session.SetFlash (D.errorToString err) )
OpenMessage msg -> OpenMessage header ->
( model ( model
, Route.newUrl (Route.Message msg.mailbox msg.id) , Route.newUrl session.key (Route.Message header.mailbox header.id)
, Session.none , Session.none
) )
@@ -133,12 +133,12 @@ viewMessage message =
] ]
shortDate : Date -> Html Msg shortDate : Posix -> Html Msg
shortDate date = shortDate date =
format format
[ dayOfMonthFixed [ dayOfMonthFixed
, DateFormat.text "-" , DateFormat.text "-"
, monthNameFirstThree , monthNameAbbreviated
, DateFormat.text " " , DateFormat.text " "
, hourNumber , hourNumber
, DateFormat.text ":" , DateFormat.text ":"
@@ -146,5 +146,6 @@ shortDate date =
, DateFormat.text " " , DateFormat.text " "
, amPmUppercase , amPmUppercase
] ]
Time.utc
date date
|> text |> text

View File

@@ -9,7 +9,7 @@ import Http exposing (Error)
import HttpUtil import HttpUtil
import Sparkline exposing (DataSet, Point, Size, sparkline) import Sparkline exposing (DataSet, Point, Size, sparkline)
import Svg.Attributes as SvgAttrib import Svg.Attributes as SvgAttrib
import Time exposing (Time) import Time exposing (Posix)
@@ -84,7 +84,7 @@ load =
subscriptions : Model -> Sub Msg subscriptions : Model -> Sub Msg
subscriptions model = subscriptions model =
Time.every (10 * Time.second) Tick Time.every (10 * 1000) Tick
@@ -93,7 +93,7 @@ subscriptions model =
type Msg type Msg
= NewMetrics (Result Http.Error Metrics) = NewMetrics (Result Http.Error Metrics)
| Tick Time | Tick Posix
update : Session -> Msg -> Model -> ( Model, Cmd Msg, Session.Msg ) update : Session -> Msg -> Model -> ( Model, Cmd Msg, Session.Msg )
@@ -256,7 +256,7 @@ viewMetric metric =
, div [ class "value" ] [ text (metric.formatter metric.value) ] , div [ class "value" ] [ text (metric.formatter metric.value) ]
, div [ class "graph" ] , div [ class "graph" ]
[ metric.graph metric.history [ metric.graph metric.history
, text ("(" ++ toString metric.minutes ++ "min)") , text ("(" ++ String.fromInt metric.minutes ++ "min)")
] ]
] ]
@@ -280,7 +280,11 @@ graphNull =
graphSize : Size graphSize : Size
graphSize = graphSize =
( 180, 16, 0, 0 ) { width = 180
, height = 16
, marginLR = 0
, marginTB = 0
}
areaStyle : Sparkline.Param a -> Sparkline.Param a areaStyle : Sparkline.Param a -> Sparkline.Param a
@@ -400,4 +404,4 @@ fmtInt n =
else else
thousands (String.slice 0 -3 str) ++ "," ++ String.right 3 str thousands (String.slice 0 -3 str) ++ "," ++ String.right 3 str
in in
thousands (toString n) thousands (String.fromInt n)

View File

@@ -1,9 +1,10 @@
module Route exposing (Route(..), fromLocation, href, modifyUrl, newUrl) module Route exposing (Route(..), fromUrl, href, modifyUrl, newUrl)
import Browser.Navigation as Navigation exposing (Key)
import Html exposing (Attribute) import Html exposing (Attribute)
import Html.Attributes as Attr import Html.Attributes as Attr
import Navigation exposing (Location) import Url exposing (Url)
import UrlParser as Url exposing ((</>), Parser, parseHash, s, string) import Url.Parser as Parser exposing ((</>), Parser, map, oneOf, s, string, top)
type Route type Route
@@ -15,14 +16,14 @@ type Route
| Status | Status
matcher : Parser (Route -> a) a routeParser : Parser (Route -> a) a
matcher = routeParser =
Url.oneOf oneOf
[ Url.map Home (s "") [ map Home top
, Url.map Message (s "m" </> string </> string) , map Message (s "m" </> string </> string)
, Url.map Mailbox (s "m" </> string) , map Mailbox (s "m" </> string)
, Url.map Monitor (s "monitor") , map Monitor (s "monitor")
, Url.map Status (s "status") , map Status (s "status")
] ]
@@ -49,37 +50,35 @@ routeToString page =
Status -> Status ->
[ "status" ] [ "status" ]
in in
"/#/" ++ String.join "/" pieces "/" ++ String.join "/" pieces
-- PUBLIC HELPERS -- PUBLIC HELPERS
href : Route -> Attribute msg href : Key -> Route -> Attribute msg
href route = href key route =
Attr.href (routeToString route) Attr.href (routeToString route)
modifyUrl : Route -> Cmd msg modifyUrl : Key -> Route -> Cmd msg
modifyUrl = modifyUrl key =
routeToString >> Navigation.modifyUrl routeToString >> Navigation.replaceUrl key
newUrl : Route -> Cmd msg newUrl : Key -> Route -> Cmd msg
newUrl = newUrl key =
routeToString >> Navigation.newUrl routeToString >> Navigation.pushUrl key
fromLocation : Location -> Route {-| Returns the Route for a given URL; by matching the path after # (fragment.)
fromLocation location = -}
if String.isEmpty location.hash then fromUrl : Url -> Route
Home fromUrl location =
case Parser.parse routeParser location of
Nothing ->
Unknown location.path
else Just route ->
case parseHash matcher location of route
Nothing ->
Unknown location.hash
Just route ->
route

View File

@@ -39,10 +39,11 @@ frame controls session page content =
div [ id "app" ] div [ id "app" ]
[ header [] [ header []
[ ul [ class "navbar", attribute "role" "navigation" ] [ ul [ class "navbar", attribute "role" "navigation" ]
[ li [ id "navbar-brand" ] [ a [ Route.href Route.Home ] [ text "@ inbucket" ] ] [ li [ id "navbar-brand" ]
, navbarLink page Route.Monitor [ text "Monitor" ] [ a [ Route.href session.key Route.Home ] [ text "@ inbucket" ] ]
, navbarLink page Route.Status [ text "Status" ] , navbarLink session page Route.Monitor [ text "Monitor" ]
, navbarRecent page controls , navbarLink session page Route.Status [ text "Status" ]
, navbarRecent session page controls
, li [ id "navbar-mailbox" ] , li [ id "navbar-mailbox" ]
[ form [ Events.onSubmit (controls.viewMailbox controls.mailboxValue) ] [ form [ Events.onSubmit (controls.viewMailbox controls.mailboxValue) ]
[ input [ input
@@ -70,19 +71,19 @@ frame controls session page content =
] ]
navbarLink : ActivePage -> Route -> List (Html a) -> Html a navbarLink : Session -> ActivePage -> Route -> List (Html a) -> Html a
navbarLink page route linkContent = navbarLink session page route linkContent =
li [ classList [ ( "navbar-active", isActive page route ) ] ] li [ classList [ ( "navbar-active", isActive page route ) ] ]
[ a [ Route.href route ] linkContent ] [ a [ Route.href session.key route ] linkContent ]
{-| Renders list of recent mailboxes, selecting the currently active mailbox. {-| Renders list of recent mailboxes, selecting the currently active mailbox.
-} -}
navbarRecent : ActivePage -> FrameControls msg -> Html msg navbarRecent : Session -> ActivePage -> FrameControls msg -> Html msg
navbarRecent page controls = navbarRecent session page controls =
let let
recentItemLink mailbox = recentItemLink mailbox =
a [ Route.href (Route.Mailbox mailbox) ] [ text mailbox ] a [ Route.href session.key (Route.Mailbox mailbox) ] [ text mailbox ]
active = active =
page == Mailbox page == Mailbox

View File

@@ -1,10 +1,13 @@
import './main.css' import './main.css'
import { Main } from './Main.elm' import { Elm } from './Main.elm'
import registerServiceWorker from './registerServiceWorker' import registerServiceWorker from './registerServiceWorker'
import registerMonitorPorts from './registerMonitor' import registerMonitorPorts from './registerMonitor'
// App startup. // App startup.
var app = Main.embed(document.getElementById('root'), sessionObject()) var app = Elm.Main.init({
node: document.getElementById('root'),
flags: sessionObject()
})
// Message monitor. // Message monitor.
registerMonitorPorts(app) registerMonitorPorts(app)