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:
@@ -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
32
ui/elm.json
Normal 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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = [] }
|
||||||
|
|||||||
@@ -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
|
||||||
++ ")"
|
++ ")"
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user