From 2934d799efd87de22dadd7117a1f49f76a6d8542 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Fri, 4 Sep 2020 14:47:36 -0700 Subject: [PATCH 01/17] Add a GitHub workflow for building a snapshot release --- .github/workflows/release-snapshot.yml | 32 ++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/release-snapshot.yml diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release-snapshot.yml new file mode 100644 index 0000000..9f9ad14 --- /dev/null +++ b/.github/workflows/release-snapshot.yml @@ -0,0 +1,32 @@ +name: Release (Snapshot) +on: + push: + branches: [ "master", "develop" ] + pull_request: +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + - name: Install Node.js + uses: actions/setup-node@v1 + with: + node-version: '10.x' + - name: Install Elm + uses: jorelali/setup-elm@v2 + with: + elm-version: 0.19.1 + - name: Build frontend + run: | + npm ci + npm run build + working-directory: ./ui + - name: Build release artifacts + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --snapshot From ce5bfddaa5b5df3828cb05d14e152e7bee36cf37 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 5 Sep 2020 14:21:42 -0700 Subject: [PATCH 02/17] Migrate release process from travis to github (#175) * set fetch depth to 0 * Only snapshot when not tagged * Run deploy for v* tags * travis: remove deploy stage --- .../{release-snapshot.yml => release.yml} | 22 ++++++++++++++----- .travis.yml | 16 -------------- 2 files changed, 17 insertions(+), 21 deletions(-) rename .github/workflows/{release-snapshot.yml => release.yml} (55%) diff --git a/.github/workflows/release-snapshot.yml b/.github/workflows/release.yml similarity index 55% rename from .github/workflows/release-snapshot.yml rename to .github/workflows/release.yml index 9f9ad14..85032c8 100644 --- a/.github/workflows/release-snapshot.yml +++ b/.github/workflows/release.yml @@ -1,22 +1,25 @@ -name: Release (Snapshot) +name: Build and Release on: push: branches: [ "master", "develop" ] + tags: [ "v*" ] pull_request: jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Install Go + with: + fetch-depth: 0 + - name: Setup Go uses: actions/setup-go@v2 with: go-version: 1.15 - - name: Install Node.js + - name: Setup Node.js uses: actions/setup-node@v1 with: node-version: '10.x' - - name: Install Elm + - name: Setup Elm uses: jorelali/setup-elm@v2 with: elm-version: 0.19.1 @@ -25,8 +28,17 @@ jobs: npm ci npm run build working-directory: ./ui - - name: Build release artifacts + - name: Test build release uses: goreleaser/goreleaser-action@v2 + if: "!startsWith(github.ref, 'refs/tags/v')" with: version: latest args: release --snapshot + - name: Build and publish release + uses: goreleaser/goreleaser-action@v2 + if: "startsWith(github.ref, 'refs/tags/v')" + with: + version: latest + args: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.travis.yml b/.travis.yml index 883be88..3c286cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,22 +25,6 @@ jobs: script: - "elm-format --validate ." - "npm run build" - - stage: deploy - go: "1.15.x" - before_install: - - "nvm install 10.19.0" - install: - - "cd ui" - - "npm ci" - - "npm run build" - - "cd .." - script: "curl -sL https://git.io/goreleaser | bash" - addons: - apt: - packages: - - rpm stages: - test - - name: deploy - if: tag IS present From 6598b091140a66e5a194cdbdb20012727e92bb0c Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sun, 6 Sep 2020 16:38:33 -0700 Subject: [PATCH 03/17] ui: Start dev server with default host, not 0.0.0.0 0.0.0.0 does not work well with WSL2 on Windows 10 --- ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index 0fb49d6..7f13330 100644 --- a/ui/package.json +++ b/ui/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "webpack --mode production", "watch": "webpack --mode development --watch", - "dev": "webpack-dev-server --mode development --host 0.0.0.0 --port 3000 --hot", + "dev": "webpack-dev-server --mode development --port 3000 --hot", "errors": "webpack --mode development --display-error-details" }, "dependencies": {}, From cf4c5a29bb05bd029221c890ea5ff9842ba295cb Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 12 Sep 2020 16:32:40 -0700 Subject: [PATCH 04/17] ui: Force file watch on dev server file watch stopped working --- ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index 7f13330..441a60a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "webpack --mode production", "watch": "webpack --mode development --watch", - "dev": "webpack-dev-server --mode development --port 3000 --hot", + "dev": "webpack-dev-server --mode development --port 3000 --hot --watch", "errors": "webpack --mode development --display-error-details" }, "dependencies": {}, From 2162a4caaa703a05f764b2156b50a33e14742c35 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 12 Sep 2020 19:45:14 -0700 Subject: [PATCH 05/17] ui: Add an Effect system to handle global state and Elm Cmds (#176) All pages now leverage Effects for most of their Session and Cmd requests. More work required for routing and other lingering Cmd use. --- ui/src/Api.elm | 4 +- ui/src/Effect.elm | 304 ++++++++++++++++++++++++++++++++++++++++ ui/src/Main.elm | 100 +++++++------ ui/src/Page/Home.elm | 16 +-- ui/src/Page/Mailbox.elm | 146 +++++++++---------- ui/src/Page/Monitor.elm | 24 ++-- ui/src/Page/Status.elm | 31 ++-- ui/src/Timer.elm | 2 + 8 files changed, 460 insertions(+), 167 deletions(-) create mode 100644 ui/src/Effect.elm diff --git a/ui/src/Api.elm b/ui/src/Api.elm index 2edd3cb..669f49a 100644 --- a/ui/src/Api.elm +++ b/ui/src/Api.elm @@ -1,5 +1,7 @@ module Api exposing - ( deleteMessage + ( DataResult + , HttpResult + , deleteMessage , getGreeting , getHeaderList , getMessage diff --git a/ui/src/Effect.elm b/ui/src/Effect.elm new file mode 100644 index 0000000..818e960 --- /dev/null +++ b/ui/src/Effect.elm @@ -0,0 +1,304 @@ +module Effect exposing + ( Effect + , addRecent + , batch + , deleteMessage + , disableRouting + , enableRouting + , getGreeting + , getHeaderList + , getMessage + , getServerConfig + , getServerMetrics + , map + , markMessageSeen + , none + , perform + , posixTime + , purgeMailbox + , schedule + , showFlash + , wrap + ) + +import Api exposing (DataResult, HttpResult) +import Data.Message exposing (Message) +import Data.MessageHeader exposing (MessageHeader) +import Data.Metrics exposing (Metrics) +import Data.ServerConfig exposing (ServerConfig) +import Data.Session as Session exposing (Session) +import Task +import Time +import Timer exposing (Timer) + + +type Effect msg + = None + | Batch (List (Effect msg)) + | Command (Cmd msg) + | PosixTime (Time.Posix -> msg) + | ScheduleTimer (Timer -> msg) Timer Float + | ApiEffect (ApiEffect msg) + | SessionEffect SessionEffect + + +type ApiEffect msg + = DeleteMessage (HttpResult msg) String String + | GetGreeting (DataResult msg String) + | GetServerConfig (DataResult msg ServerConfig) + | GetServerMetrics (DataResult msg Metrics) + | GetHeaderList (DataResult msg (List MessageHeader)) String + | GetMessage (DataResult msg Message) String String + | MarkMessageSeen (HttpResult msg) String String + | PurgeMailbox (HttpResult msg) String + + +type SessionEffect + = FlashClear + | FlashShow Session.Flash + | RecentAdd String + | RoutingDisable + | RoutingEnable + + +{-| Packs a List of Effects into a single Effect +-} +batch : List (Effect msg) -> Effect msg +batch effects = + Batch effects + + +{-| Transform message types produced by an effect. +-} +map : (a -> b) -> Effect a -> Effect b +map f effect = + case effect of + None -> + None + + Batch effects -> + Batch <| List.map (map f) effects + + Command cmd -> + Command <| Cmd.map f cmd + + PosixTime toMsg -> + PosixTime <| toMsg >> f + + ScheduleTimer toMsg timer millis -> + ScheduleTimer (toMsg >> f) timer millis + + ApiEffect apiEffect -> + ApiEffect <| mapApi f apiEffect + + SessionEffect sessionEffect -> + SessionEffect sessionEffect + + +mapApi : (a -> b) -> ApiEffect a -> ApiEffect b +mapApi f effect = + case effect of + DeleteMessage result mailbox id -> + DeleteMessage (result >> f) mailbox id + + GetGreeting result -> + GetGreeting (result >> f) + + GetServerConfig result -> + GetServerConfig (result >> f) + + GetServerMetrics result -> + GetServerMetrics (result >> f) + + GetHeaderList result mailbox -> + GetHeaderList (result >> f) mailbox + + GetMessage result mailbox id -> + GetMessage (result >> f) mailbox id + + MarkMessageSeen result mailbox id -> + MarkMessageSeen (result >> f) mailbox id + + PurgeMailbox result mailbox -> + PurgeMailbox (result >> f) mailbox + + +{-| Applies an effect by updating the session and/or producing a Cmd. +-} +perform : ( Session, Effect msg ) -> ( Session, Cmd msg ) +perform ( session, effect ) = + case effect of + None -> + ( session, Cmd.none ) + + Batch effects -> + List.foldl batchPerform ( session, [] ) effects + |> Tuple.mapSecond Cmd.batch + + Command cmd -> + ( session, cmd ) + + PosixTime toMsg -> + ( session, Task.perform toMsg Time.now ) + + ScheduleTimer toMsg timer millis -> + ( session, Timer.schedule toMsg timer millis ) + + ApiEffect apiEffect -> + performApi ( session, apiEffect ) + + SessionEffect sessionEffect -> + performSession ( session, sessionEffect ) + + +performApi : ( Session, ApiEffect msg ) -> ( Session, Cmd msg ) +performApi ( session, effect ) = + case effect of + DeleteMessage toMsg mailbox id -> + ( session, Api.deleteMessage session toMsg mailbox id ) + + GetGreeting toMsg -> + ( session, Api.getGreeting session toMsg ) + + GetServerConfig toMsg -> + ( session, Api.getServerConfig session toMsg ) + + GetServerMetrics toMsg -> + ( session, Api.getServerMetrics session toMsg ) + + GetHeaderList toMsg mailbox -> + ( session, Api.getHeaderList session toMsg mailbox ) + + GetMessage toMsg mailbox id -> + ( session, Api.getMessage session toMsg mailbox id ) + + MarkMessageSeen toMsg mailbox id -> + ( session, Api.markMessageSeen session toMsg mailbox id ) + + PurgeMailbox toMsg mailbox -> + ( session, Api.purgeMailbox session toMsg mailbox ) + + +performSession : ( Session, SessionEffect ) -> ( Session, Cmd msg ) +performSession ( session, effect ) = + case effect of + RecentAdd mailbox -> + ( Session.addRecent mailbox session, Cmd.none ) + + FlashClear -> + ( Session.clearFlash session, Cmd.none ) + + FlashShow flash -> + ( Session.showFlash flash session, Cmd.none ) + + RoutingDisable -> + ( Session.disableRouting session, Cmd.none ) + + RoutingEnable -> + ( Session.enableRouting session, Cmd.none ) + + + +-- EFFECT CONSTRUCTORS + + +none : Effect msg +none = + None + + +{-| Adds specified mailbox to the recently viewed list +-} +addRecent : String -> Effect msg +addRecent mailbox = + SessionEffect (RecentAdd mailbox) + + +disableRouting : Effect msg +disableRouting = + SessionEffect RoutingDisable + + +enableRouting : Effect msg +enableRouting = + SessionEffect RoutingEnable + + +clearFlash : Effect msg +clearFlash = + SessionEffect FlashClear + + +showFlash : Session.Flash -> Effect msg +showFlash flash = + SessionEffect (FlashShow flash) + + +deleteMessage : HttpResult msg -> String -> String -> Effect msg +deleteMessage toMsg mailboxName id = + ApiEffect (DeleteMessage toMsg mailboxName id) + + +getGreeting : DataResult msg String -> Effect msg +getGreeting toMsg = + ApiEffect (GetGreeting toMsg) + + +getHeaderList : DataResult msg (List MessageHeader) -> String -> Effect msg +getHeaderList toMsg mailboxName = + ApiEffect (GetHeaderList toMsg mailboxName) + + +getServerConfig : DataResult msg ServerConfig -> Effect msg +getServerConfig toMsg = + ApiEffect (GetServerConfig toMsg) + + +getServerMetrics : DataResult msg Metrics -> Effect msg +getServerMetrics toMsg = + ApiEffect (GetServerMetrics toMsg) + + +getMessage : DataResult msg Message -> String -> String -> Effect msg +getMessage toMsg mailboxName id = + ApiEffect (GetMessage toMsg mailboxName id) + + +markMessageSeen : HttpResult msg -> String -> String -> Effect msg +markMessageSeen toMsg mailboxName id = + ApiEffect (MarkMessageSeen toMsg mailboxName id) + + +posixTime : (Time.Posix -> msg) -> Effect msg +posixTime toMsg = + PosixTime toMsg + + +purgeMailbox : HttpResult msg -> String -> Effect msg +purgeMailbox toMsg mailboxName = + ApiEffect (PurgeMailbox toMsg mailboxName) + + +{-| Schedules a Timer to fire after the specified delay. +-} +schedule : (Timer -> msg) -> Timer -> Float -> Effect msg +schedule toMsg timer millis = + ScheduleTimer toMsg timer millis + + +{-| Wrap a Cmd into an Effect. This is a temporary function to aid in the transition to the effect +pattern. +-} +wrap : Cmd msg -> Effect msg +wrap cmd = + Command cmd + + + +-- UTILITY + + +batchPerform : Effect msg -> ( Session, List (Cmd msg) ) -> ( Session, List (Cmd msg) ) +batchPerform effect ( session, cmds ) = + perform ( session, effect ) + |> Tuple.mapSecond (\cmd -> cmd :: cmds) diff --git a/ui/src/Main.elm b/ui/src/Main.elm index 30a9698..e7c1e2c 100644 --- a/ui/src/Main.elm +++ b/ui/src/Main.elm @@ -4,6 +4,7 @@ import Browser exposing (Document, UrlRequest) import Browser.Navigation as Nav import Data.AppConfig as AppConfig exposing (AppConfig) import Data.Session as Session exposing (Session) +import Effect exposing (Effect) import Html exposing (Html) import Json.Decode as D exposing (Value) import Layout @@ -58,6 +59,8 @@ init configValue location key = Session.initError key location (D.errorToString error) ( subModel, _ ) = + -- Home.init effect is discarded because this subModel will be immediately replaced + -- when we change routes to the specified location. Home.init session initModel = @@ -67,11 +70,9 @@ init configValue location key = route = session.router.fromUrl location - - ( model, cmd ) = - changeRouteTo route initModel in - ( model, Cmd.batch [ cmd, Task.perform TimeZoneLoaded Time.here ] ) + changeRouteTo route initModel + |> Tuple.mapSecond (\cmd -> Cmd.batch [ cmd, Task.perform TimeZoneLoaded Time.here ]) type Msg @@ -206,12 +207,12 @@ updateMain msg model session = ) _ -> - updatePage msg model + updatePage msg model |> performEffects {-| Delegate incoming messages to their respective sub-pages. -} -updatePage : Msg -> Model -> ( Model, Cmd Msg ) +updatePage : Msg -> Model -> ( Model, Effect Msg ) updatePage msg model = case ( msg, model.page ) of ( HomeMsg subMsg, Home subModel ) -> @@ -232,61 +233,70 @@ updatePage msg model = ( _, _ ) -> -- Disregard messages destined for the wrong page. - ( model, Cmd.none ) + ( model, Effect.none ) changeRouteTo : Route -> Model -> ( Model, Cmd Msg ) changeRouteTo route model = let session = - getSession model |> Session.clearFlash + Session.clearFlash (getSession model) newModel = { model | layout = Layout.reset model.layout } in - case route of - Route.Home -> - Home.init session - |> updateWith Home HomeMsg newModel + performEffects <| + case route of + Route.Home -> + Home.init session + |> updateWith Home HomeMsg newModel - Route.Mailbox name -> - Mailbox.init session name Nothing - |> updateWith Mailbox MailboxMsg newModel + Route.Mailbox name -> + Mailbox.init session name Nothing + |> updateWith Mailbox MailboxMsg newModel - Route.Message mailbox id -> - Mailbox.init session mailbox (Just id) - |> updateWith Mailbox MailboxMsg newModel + Route.Message mailbox id -> + Mailbox.init session mailbox (Just id) + |> updateWith Mailbox MailboxMsg newModel - Route.Monitor -> - if session.config.monitorVisible then - Monitor.init session - |> updateWith Monitor MonitorMsg newModel + Route.Monitor -> + if session.config.monitorVisible then + Monitor.init session + |> updateWith Monitor MonitorMsg newModel - else + else + let + flash = + { title = "Disabled route requested" + , table = [ ( "Error", "Monitor disabled by configuration." ) ] + } + in + ( applyToModelSession (Session.showFlash flash) newModel + , Effect.none + ) + + Route.Status -> + Status.init session + |> updateWith Status StatusMsg newModel + + Route.Unknown path -> + -- Unknown routes display Home with an error flash. let flash = - { title = "Disabled route requested" - , table = [ ( "Error", "Monitor disabled by configuration." ) ] + { title = "Unknown route requested" + , table = [ ( "Path", path ) ] } in - ( applyToModelSession (Session.showFlash flash) newModel - , Cmd.none - ) + Home.init (Session.showFlash flash session) + |> updateWith Home HomeMsg newModel - Route.Status -> - Status.init session - |> updateWith Status StatusMsg newModel - Route.Unknown path -> - -- Unknown routes display Home with an error flash. - let - flash = - { title = "Unknown route requested" - , table = [ ( "Path", path ) ] - } - in - Home.init (Session.showFlash flash session) - |> updateWith Home HomeMsg newModel +{-| Perform effects by updating model and/or producing Cmds to be executed. +-} +performEffects : ( Model, Effect Msg ) -> ( Model, Cmd Msg ) +performEffects ( model, effect ) = + Effect.perform ( getSession model, effect ) + |> Tuple.mapFirst (\newSession -> updateSession model newSession) getSession : Model -> Session @@ -332,11 +342,11 @@ updateWith : (subModel -> PageModel) -> (subMsg -> Msg) -> Model - -> ( subModel, Cmd subMsg ) - -> ( Model, Cmd Msg ) -updateWith toPage toMsg model ( subModel, subCmd ) = + -> ( subModel, Effect subMsg ) + -> ( Model, Effect Msg ) +updateWith toPage toMsg model ( subModel, subEffect ) = ( { model | page = toPage subModel } - , Cmd.map toMsg subCmd + , Effect.map toMsg subEffect ) diff --git a/ui/src/Page/Home.elm b/ui/src/Page/Home.elm index b85d291..e3572dd 100644 --- a/ui/src/Page/Home.elm +++ b/ui/src/Page/Home.elm @@ -1,7 +1,7 @@ module Page.Home exposing (Model, Msg, init, update, view) -import Api -import Data.Session as Session exposing (Session) +import Data.Session exposing (Session) +import Effect exposing (Effect) import Html exposing (Html) import Html.Attributes exposing (class, property) import HttpUtil @@ -18,9 +18,9 @@ type alias Model = } -init : Session -> ( Model, Cmd Msg ) +init : Session -> ( Model, Effect Msg ) init session = - ( Model session "", Api.getGreeting session GreetingLoaded ) + ( Model session "", Effect.getGreeting GreetingLoaded ) @@ -31,16 +31,14 @@ type Msg = GreetingLoaded (Result HttpUtil.Error String) -update : Msg -> Model -> ( Model, Cmd Msg ) +update : Msg -> Model -> ( Model, Effect Msg ) update msg model = case msg of GreetingLoaded (Ok greeting) -> - ( { model | greeting = greeting }, Cmd.none ) + ( { model | greeting = greeting }, Effect.none ) GreetingLoaded (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index 7ef4ac4..3153b05 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -1,12 +1,13 @@ -module Page.Mailbox exposing (Model, Msg, init, load, subscriptions, update, view) +module Page.Mailbox exposing (Model, Msg, init, subscriptions, update, view) import Api import Browser.Navigation as Nav import Data.Message as Message exposing (Message) import Data.MessageHeader exposing (MessageHeader) -import Data.Session as Session exposing (Session) +import Data.Session exposing (Session) import DateFormat as DF import DateFormat.Relative as Relative +import Effect exposing (Effect) import Html exposing ( Attribute @@ -54,7 +55,6 @@ import Json.Decode as D import Json.Encode as E import Modal import Route -import Task import Time exposing (Posix) import Timer exposing (Timer) @@ -103,7 +103,7 @@ type alias Model = } -init : Session -> String -> Maybe MessageID -> ( Model, Cmd Msg ) +init : Session -> String -> Maybe MessageID -> ( Model, Effect Msg ) init session mailboxName selection = ( { session = session , mailboxName = mailboxName @@ -114,16 +114,11 @@ init session mailboxName selection = , markSeenTimer = Timer.empty , now = Time.millisToPosix 0 } - , load session mailboxName - ) - - -load : Session -> String -> Cmd Msg -load session mailboxName = - Cmd.batch - [ Task.perform Tick Time.now - , Api.getHeaderList session ListLoaded mailboxName + , Effect.batch + [ Effect.posixTime Tick + , Effect.getHeaderList ListLoaded mailboxName ] + ) @@ -159,38 +154,38 @@ type Msg | ModalFocused Modal.Msg -update : Msg -> Model -> ( Model, Cmd Msg ) +update : Msg -> Model -> ( Model, Effect Msg ) update msg model = case msg of ClickMessage id -> - ( updateSelected { model | session = Session.disableRouting model.session } id - , Cmd.batch + ( updateSelected model id + , Effect.batch [ -- Update browser location. - Route.Message model.mailboxName id + Effect.disableRouting + , Route.Message model.mailboxName id |> model.session.router.toPath |> Nav.replaceUrl model.session.key - , Api.getMessage model.session MessageLoaded model.mailboxName id + |> Effect.wrap + , Effect.getMessage MessageLoaded model.mailboxName id ] ) CloseMessage -> case model.state of ShowingList list _ -> - ( { model | state = ShowingList list NoMessage }, Cmd.none ) + ( { model | state = ShowingList list NoMessage }, Effect.none ) _ -> - ( model, Cmd.none ) + ( model, Effect.none ) DeleteMessage message -> updateDeleteMessage model message DeletedMessage (Ok _) -> - ( model, Cmd.none ) + ( model, Effect.none ) DeletedMessage (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) ListKeyPress id keyCode -> case keyCode of @@ -198,7 +193,7 @@ update msg model = updateOpenMessage model id _ -> - ( model, Cmd.none ) + ( model, Effect.none ) ListLoaded (Ok headers) -> case model.state of @@ -214,63 +209,51 @@ update msg model = updateOpenMessage newModel id Nothing -> - ( { newModel - | session = Session.addRecent model.mailboxName model.session - } - , Cmd.none - ) + ( newModel, Effect.addRecent newModel.mailboxName ) _ -> - ( model, Cmd.none ) + ( model, Effect.none ) ListLoaded (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) MarkSeenLoaded (Ok _) -> - ( model, Cmd.none ) + ( model, Effect.none ) MarkSeenLoaded (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) MessageLoaded (Ok message) -> updateMessageResult model message MessageLoaded (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) MessageBody bodyMode -> - ( { model | bodyMode = bodyMode }, Cmd.none ) + ( { model | bodyMode = bodyMode }, Effect.none ) ModalFocused message -> ( { model | session = Modal.updateSession message model.session } - , Cmd.none + , Effect.none ) OnSearchInput searchInput -> updateSearchInput model searchInput PurgeMailboxPrompt -> - ( { model | promptPurge = True }, Modal.resetFocusCmd ModalFocused ) + ( { model | promptPurge = True }, Modal.resetFocusCmd ModalFocused |> Effect.wrap ) PurgeMailboxCanceled -> - ( { model | promptPurge = False }, Cmd.none ) + ( { model | promptPurge = False }, Effect.none ) PurgeMailboxConfirmed -> updateTriggerPurge model PurgedMailbox (Ok _) -> - ( model, Cmd.none ) + ( model, Effect.none ) PurgedMailbox (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) MarkSeenTriggered timer -> if timer == model.markSeenTimer then @@ -278,15 +261,15 @@ update msg model = updateMarkMessageSeen model else - ( model, Cmd.none ) + ( model, Effect.none ) Tick now -> - ( { model | now = now }, Cmd.none ) + ( { model | now = now }, Effect.none ) {-| Replace the currently displayed message. -} -updateMessageResult : Model -> Message -> ( Model, Cmd Msg ) +updateMessageResult : Model -> Message -> ( Model, Effect Msg ) updateMessageResult model message = let bodyMode = @@ -298,7 +281,7 @@ updateMessageResult model message = in case model.state of LoadingList _ -> - ( model, Cmd.none ) + ( model, Effect.none ) ShowingList list _ -> let @@ -314,38 +297,38 @@ updateMessageResult model message = , markSeenTimer = newTimer } -- Set 1500ms delay before reporting message as seen to backend. - , Timer.schedule MarkSeenTriggered newTimer 1500 + , Effect.schedule MarkSeenTriggered newTimer 1500 ) {-| Updates model and triggers commands to purge this mailbox. -} -updateTriggerPurge : Model -> ( Model, Cmd Msg ) +updateTriggerPurge : Model -> ( Model, Effect Msg ) updateTriggerPurge model = let - cmd = - Cmd.batch + effects = + Effect.batch [ Route.Mailbox model.mailboxName |> model.session.router.toPath |> Nav.replaceUrl model.session.key - , Api.purgeMailbox model.session PurgedMailbox model.mailboxName + |> Effect.wrap + , Effect.purgeMailbox PurgedMailbox model.mailboxName ] in case model.state of ShowingList _ _ -> ( { model | promptPurge = False - , session = Session.disableRouting model.session , state = ShowingList (MessageList [] Nothing "") NoMessage } - , cmd + , Effect.batch [ Effect.disableRouting, effects ] ) _ -> - ( model, cmd ) + ( model, effects ) -updateSearchInput : Model -> String -> ( Model, Cmd Msg ) +updateSearchInput : Model -> String -> ( Model, Effect Msg ) updateSearchInput model searchInput = let searchFilter = @@ -357,14 +340,14 @@ updateSearchInput model searchInput = in case model.state of LoadingList _ -> - ( model, Cmd.none ) + ( model, Effect.none ) ShowingList list messageState -> ( { model | searchInput = searchInput , state = ShowingList { list | searchFilter = searchFilter } messageState } - , Cmd.none + , Effect.none ) @@ -396,7 +379,7 @@ updateSelected model id = { model | state = ShowingList newList (Transitioning visible) } -updateDeleteMessage : Model -> Message -> ( Model, Cmd Msg ) +updateDeleteMessage : Model -> Message -> ( Model, Effect Msg ) updateDeleteMessage model message = let filter f messageList = @@ -404,26 +387,24 @@ updateDeleteMessage model message = in case model.state of ShowingList list _ -> - ( { model - | session = Session.disableRouting model.session - , state = - ShowingList (filter (\x -> x.id /= message.id) list) NoMessage - } - , Cmd.batch - [ Api.deleteMessage model.session DeletedMessage message.mailbox message.id + ( { model | state = ShowingList (filter (\x -> x.id /= message.id) list) NoMessage } + , Effect.batch + [ Effect.deleteMessage DeletedMessage message.mailbox message.id + , Effect.disableRouting , Route.Mailbox model.mailboxName |> model.session.router.toPath |> Nav.replaceUrl model.session.key + |> Effect.wrap ] ) _ -> - ( model, Cmd.none ) + ( model, Effect.none ) {-| Updates both the active message, and the message list to mark the currently viewed message as seen. -} -updateMarkMessageSeen : Model -> ( Model, Cmd Msg ) +updateMarkMessageSeen : Model -> ( Model, Effect Msg ) updateMarkMessageSeen model = case model.state of ShowingList messages (ShowingMessage visibleMessage) -> @@ -442,21 +423,20 @@ updateMarkMessageSeen model = | state = ShowingList newMessages (ShowingMessage { visibleMessage | seen = True }) } - , Api.markMessageSeen model.session MarkSeenLoaded visibleMessage.mailbox visibleMessage.id + , Effect.markMessageSeen MarkSeenLoaded visibleMessage.mailbox visibleMessage.id ) _ -> - ( model, Cmd.none ) + ( model, Effect.none ) -updateOpenMessage : Model -> String -> ( Model, Cmd Msg ) +updateOpenMessage : Model -> String -> ( Model, Effect Msg ) updateOpenMessage model id = - let - newModel = - { model | session = Session.addRecent model.mailboxName model.session } - in - ( updateSelected newModel id - , Api.getMessage model.session MessageLoaded model.mailboxName id + ( updateSelected model id + , Effect.batch + [ Effect.addRecent model.mailboxName + , Effect.getMessage MessageLoaded model.mailboxName id + ] ) diff --git a/ui/src/Page/Monitor.elm b/ui/src/Page/Monitor.elm index 86cd699..07fff3c 100644 --- a/ui/src/Page/Monitor.elm +++ b/ui/src/Page/Monitor.elm @@ -5,6 +5,7 @@ import Browser.Navigation as Nav import Data.MessageHeader as MessageHeader exposing (MessageHeader) import Data.Session as Session exposing (Session) import DateFormat as DF +import Effect exposing (Effect) import Html exposing ( Attribute @@ -41,9 +42,9 @@ type alias Model = } -init : Session -> ( Model, Cmd Msg ) +init : Session -> ( Model, Effect Msg ) init session = - ( Model session False [], Cmd.none ) + ( Model session False [], Effect.none ) @@ -58,20 +59,20 @@ type Msg | MessageKeyPress MessageHeader Int -update : Msg -> Model -> ( Model, Cmd Msg ) +update : Msg -> Model -> ( Model, Effect Msg ) update msg model = case msg of Connected True -> - ( { model | connected = True, messages = [] }, Cmd.none ) + ( { model | connected = True, messages = [] }, Effect.none ) Connected False -> - ( { model | connected = False }, Cmd.none ) + ( { model | connected = False }, Effect.none ) MessageReceived value -> case D.decodeValue (MessageHeader.decoder |> D.at [ "detail" ]) value of Ok header -> ( { model | messages = header :: List.take 500 model.messages } - , Cmd.none + , Effect.none ) Err err -> @@ -81,12 +82,10 @@ update msg model = , table = [ ( "Error", D.errorToString err ) ] } in - ( { model | session = Session.showFlash flash model.session } - , Cmd.none - ) + ( model, Effect.showFlash flash ) Clear -> - ( { model | messages = [] }, Cmd.none ) + ( { model | messages = [] }, Effect.none ) OpenMessage header -> openMessage header model @@ -97,15 +96,16 @@ update msg model = openMessage header model _ -> - ( model, Cmd.none ) + ( model, Effect.none ) -openMessage : MessageHeader -> Model -> ( Model, Cmd Msg ) +openMessage : MessageHeader -> Model -> ( Model, Effect Msg ) openMessage header model = ( model , Route.Message header.mailbox header.id |> model.session.router.toPath |> Nav.replaceUrl model.session.key + |> Effect.wrap ) diff --git a/ui/src/Page/Status.elm b/ui/src/Page/Status.elm index e1b6781..3e81723 100644 --- a/ui/src/Page/Status.elm +++ b/ui/src/Page/Status.elm @@ -1,10 +1,10 @@ module Page.Status exposing (Model, Msg, init, subscriptions, update, view) -import Api import Data.Metrics exposing (Metrics) import Data.ServerConfig exposing (ServerConfig) -import Data.Session as Session exposing (Session) +import Data.Session exposing (Session) import DateFormat.Relative as Relative +import Effect exposing (Effect) import Filesize import Html exposing @@ -19,7 +19,6 @@ import Html.Attributes exposing (class) import HttpUtil import Sparkline as Spark import Svg.Attributes as SvgAttrib -import Task import Time exposing (Posix) @@ -60,7 +59,7 @@ type alias Metric = } -init : Session -> ( Model, Cmd Msg ) +init : Session -> ( Model, Effect Msg ) init session = ( { session = session , now = Time.millisToPosix 0 @@ -82,9 +81,9 @@ init session = , retainedCount = Metric "Stored Messages" 0 fmtInt graphZero initDataSet 60 , retainedSize = Metric "Store Size" 0 Filesize.format graphZero initDataSet 60 } - , Cmd.batch - [ Task.perform Tick Time.now - , Api.getServerConfig session ServerConfigLoaded + , Effect.batch + [ Effect.posixTime Tick + , Effect.getServerConfig ServerConfigLoaded ] ) @@ -114,27 +113,25 @@ type Msg | Tick Posix -update : Msg -> Model -> ( Model, Cmd Msg ) +update : Msg -> Model -> ( Model, Effect Msg ) update msg model = case msg of MetricsReceived (Ok metrics) -> - ( updateMetrics metrics model, Cmd.none ) + ( updateMetrics metrics model, Effect.none ) MetricsReceived (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) ServerConfigLoaded (Ok config) -> - ( { model | config = Just config }, Cmd.none ) + ( { model | config = Just config }, Effect.none ) ServerConfigLoaded (Err err) -> - ( { model | session = Session.showFlash (HttpUtil.errorFlash err) model.session } - , Cmd.none - ) + ( model, Effect.showFlash (HttpUtil.errorFlash err) ) Tick time -> - ( { model | now = time }, Api.getServerMetrics model.session MetricsReceived ) + ( { model | now = time } + , Effect.getServerMetrics MetricsReceived + ) {-| Update all metrics in Model; increment xCounter. diff --git a/ui/src/Timer.elm b/ui/src/Timer.elm index fc82d44..1787950 100644 --- a/ui/src/Timer.elm +++ b/ui/src/Timer.elm @@ -49,6 +49,8 @@ cancel previous = previous +{-| Increments the timer identity, preventing integer overflow. +-} next : Int -> Int next index = if index > 2 ^ 30 then From 8adfd8223268e3ee8fb5493aac4b86ead7aa7550 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sun, 13 Sep 2020 10:39:16 -0700 Subject: [PATCH 06/17] ui: add `npm run clean` script --- ui/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/package.json b/ui/package.json index 441a60a..9063068 100644 --- a/ui/package.json +++ b/ui/package.json @@ -4,6 +4,7 @@ "license": "MIT", "private": true, "scripts": { + "clean": "rm -rf dist elm-stuff", "build": "webpack --mode production", "watch": "webpack --mode development --watch", "dev": "webpack-dev-server --mode development --port 3000 --hot --watch", From 5c5b0f819b62057a6bc82612cf88902cecd6bd07 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sun, 13 Sep 2020 17:08:11 -0700 Subject: [PATCH 07/17] Effects refactor continued (#177) * Use Effects instead of replaceUrl in Mailbox * Add Effect.navigateRoute to handle monitor message clicks * Add a focusModal effect for mailbox purge * Remove temporary Cmd wrapper Effect --- ui/src/Effect.elm | 77 +++++++++++++++++++++++++++++++++-------- ui/src/Page/Mailbox.elm | 45 +++++++----------------- ui/src/Page/Monitor.elm | 8 ++--- 3 files changed, 77 insertions(+), 53 deletions(-) diff --git a/ui/src/Effect.elm b/ui/src/Effect.elm index 818e960..2907417 100644 --- a/ui/src/Effect.elm +++ b/ui/src/Effect.elm @@ -5,6 +5,7 @@ module Effect exposing , deleteMessage , disableRouting , enableRouting + , focusModal , getGreeting , getHeaderList , getMessage @@ -12,21 +13,25 @@ module Effect exposing , getServerMetrics , map , markMessageSeen + , navigateRoute , none , perform , posixTime , purgeMailbox , schedule , showFlash - , wrap + , updateRoute ) import Api exposing (DataResult, HttpResult) +import Browser.Navigation as Nav import Data.Message exposing (Message) import Data.MessageHeader exposing (MessageHeader) import Data.Metrics exposing (Metrics) import Data.ServerConfig exposing (ServerConfig) import Data.Session as Session exposing (Session) +import Modal +import Route exposing (Route) import Task import Time import Timer exposing (Timer) @@ -34,11 +39,13 @@ import Timer exposing (Timer) type Effect msg = None - | Batch (List (Effect msg)) - | Command (Cmd msg) - | PosixTime (Time.Posix -> msg) - | ScheduleTimer (Timer -> msg) Timer Float | ApiEffect (ApiEffect msg) + | Batch (List (Effect msg)) + | ModalFocus (Modal.Msg -> msg) + | PosixTime (Time.Posix -> msg) + | RouteNavigate Bool Route + | RouteUpdate Route + | ScheduleTimer (Timer -> msg) Timer Float | SessionEffect SessionEffect @@ -79,8 +86,8 @@ map f effect = Batch effects -> Batch <| List.map (map f) effects - Command cmd -> - Command <| Cmd.map f cmd + ModalFocus toMsg -> + ModalFocus <| toMsg >> f PosixTime toMsg -> PosixTime <| toMsg >> f @@ -88,6 +95,12 @@ map f effect = ScheduleTimer toMsg timer millis -> ScheduleTimer (toMsg >> f) timer millis + RouteNavigate pushHistory route -> + RouteNavigate pushHistory route + + RouteUpdate route -> + RouteUpdate route + ApiEffect apiEffect -> ApiEffect <| mapApi f apiEffect @@ -135,8 +148,8 @@ perform ( session, effect ) = List.foldl batchPerform ( session, [] ) effects |> Tuple.mapSecond Cmd.batch - Command cmd -> - ( session, cmd ) + ModalFocus toMsg -> + ( session, Modal.resetFocusCmd toMsg ) PosixTime toMsg -> ( session, Task.perform toMsg Time.now ) @@ -144,6 +157,27 @@ perform ( session, effect ) = ScheduleTimer toMsg timer millis -> ( session, Timer.schedule toMsg timer millis ) + RouteNavigate pushHistory route -> + let + url = + -- TODO replace Session.router + session.router.toPath route + in + ( Session.enableRouting session + , if pushHistory then + Nav.pushUrl session.key url + + else + Nav.replaceUrl session.key url + ) + + RouteUpdate route -> + ( Session.disableRouting session + , -- TODO replace Session.router + session.router.toPath route + |> Nav.replaceUrl session.key + ) + ApiEffect apiEffect -> performApi ( session, apiEffect ) @@ -234,6 +268,13 @@ showFlash flash = SessionEffect (FlashShow flash) +{-| Locks focus to the `modal-dialog` dom ID. +-} +focusModal : (Modal.Msg -> msg) -> Effect msg +focusModal toMsg = + ModalFocus toMsg + + deleteMessage : HttpResult msg -> String -> String -> Effect msg deleteMessage toMsg mailboxName id = ApiEffect (DeleteMessage toMsg mailboxName id) @@ -286,12 +327,20 @@ schedule toMsg timer millis = ScheduleTimer toMsg timer millis -{-| Wrap a Cmd into an Effect. This is a temporary function to aid in the transition to the effect -pattern. +{-| Updates the browsers displayed URL to the specified route, and triggers the route to be +handled by the frontend. -} -wrap : Cmd msg -> Effect msg -wrap cmd = - Command cmd +navigateRoute : Bool -> Route -> Effect msg +navigateRoute pushHistory route = + RouteNavigate pushHistory route + + +{-| Updates the browsers displayed URL to the specified route. Does not trigger our own route +handling. +-} +updateRoute : Route -> Effect msg +updateRoute route = + RouteUpdate route diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index 3153b05..a3a8ce9 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -1,7 +1,6 @@ module Page.Mailbox exposing (Model, Msg, init, subscriptions, update, view) import Api -import Browser.Navigation as Nav import Data.Message as Message exposing (Message) import Data.MessageHeader exposing (MessageHeader) import Data.Session exposing (Session) @@ -161,11 +160,7 @@ update msg model = ( updateSelected model id , Effect.batch [ -- Update browser location. - Effect.disableRouting - , Route.Message model.mailboxName id - |> model.session.router.toPath - |> Nav.replaceUrl model.session.key - |> Effect.wrap + Effect.updateRoute (Route.Message model.mailboxName id) , Effect.getMessage MessageLoaded model.mailboxName id ] ) @@ -241,7 +236,7 @@ update msg model = updateSearchInput model searchInput PurgeMailboxPrompt -> - ( { model | promptPurge = True }, Modal.resetFocusCmd ModalFocused |> Effect.wrap ) + ( { model | promptPurge = True }, Effect.focusModal ModalFocused ) PurgeMailboxCanceled -> ( { model | promptPurge = False }, Effect.none ) @@ -305,27 +300,15 @@ updateMessageResult model message = -} updateTriggerPurge : Model -> ( Model, Effect Msg ) updateTriggerPurge model = - let - effects = - Effect.batch - [ Route.Mailbox model.mailboxName - |> model.session.router.toPath - |> Nav.replaceUrl model.session.key - |> Effect.wrap - , Effect.purgeMailbox PurgedMailbox model.mailboxName - ] - in - case model.state of - ShowingList _ _ -> - ( { model - | promptPurge = False - , state = ShowingList (MessageList [] Nothing "") NoMessage - } - , Effect.batch [ Effect.disableRouting, effects ] - ) - - _ -> - ( model, effects ) + ( { model + | promptPurge = False + , state = ShowingList (MessageList [] Nothing "") NoMessage + } + , Effect.batch + [ Effect.updateRoute (Route.Mailbox model.mailboxName) + , Effect.purgeMailbox PurgedMailbox model.mailboxName + ] + ) updateSearchInput : Model -> String -> ( Model, Effect Msg ) @@ -390,11 +373,7 @@ updateDeleteMessage model message = ( { model | state = ShowingList (filter (\x -> x.id /= message.id) list) NoMessage } , Effect.batch [ Effect.deleteMessage DeletedMessage message.mailbox message.id - , Effect.disableRouting - , Route.Mailbox model.mailboxName - |> model.session.router.toPath - |> Nav.replaceUrl model.session.key - |> Effect.wrap + , Effect.updateRoute (Route.Mailbox model.mailboxName) ] ) diff --git a/ui/src/Page/Monitor.elm b/ui/src/Page/Monitor.elm index 07fff3c..016c27c 100644 --- a/ui/src/Page/Monitor.elm +++ b/ui/src/Page/Monitor.elm @@ -1,9 +1,8 @@ module Page.Monitor exposing (Model, Msg, init, update, view) import Api -import Browser.Navigation as Nav import Data.MessageHeader as MessageHeader exposing (MessageHeader) -import Data.Session as Session exposing (Session) +import Data.Session exposing (Session) import DateFormat as DF import Effect exposing (Effect) import Html @@ -102,10 +101,7 @@ update msg model = openMessage : MessageHeader -> Model -> ( Model, Effect Msg ) openMessage header model = ( model - , Route.Message header.mailbox header.id - |> model.session.router.toPath - |> Nav.replaceUrl model.session.key - |> Effect.wrap + , Effect.navigateRoute True (Route.Message header.mailbox header.id) ) From 4648d8e5931c42a77f7e0c65abd939e2afd5db3d Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Mon, 21 Sep 2020 16:16:00 -0700 Subject: [PATCH 08/17] ui: Use OpenSans font (#178) --- CHANGELOG.md | 7 +++++++ ui/package-lock.json | 5 +++++ ui/package.json | 4 +++- ui/src/index.js | 1 + ui/src/main.css | 5 +++-- ui/src/navbar.css | 1 + ui/webpack.config.js | 2 -- 7 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acbc472..dc8859b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Changed +- The UI now includes the Open Sans webfont instead of relying on browser/OS + fonts + + ## [v3.0.0-beta3] ### Added diff --git a/ui/package-lock.json b/ui/package-lock.json index a2884da..d8bf76d 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -5192,6 +5192,11 @@ "wrappy": "1" } }, + "opensans-npm-webfont": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/opensans-npm-webfont/-/opensans-npm-webfont-1.0.0.tgz", + "integrity": "sha512-2ehgrX+NpoxLOil30tYGr0cDsDXSJn9gon6PfM1Ki0CxZF6ui9Mi6Dm5DGglKyK2QiX0gMb6Ch7VmRHwfc4M6Q==" + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", diff --git a/ui/package.json b/ui/package.json index 9063068..b23127a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,7 +10,9 @@ "dev": "webpack-dev-server --mode development --port 3000 --hot --watch", "errors": "webpack --mode development --display-error-details" }, - "dependencies": {}, + "dependencies": { + "opensans-npm-webfont": "^1.0.0" + }, "devDependencies": { "@babel/core": "^7.11.4", "@babel/preset-env": "^7.11.0", diff --git a/ui/src/index.js b/ui/src/index.js index 9d2adf5..be5eb29 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -3,6 +3,7 @@ import './mailbox.css' import './navbar.css' import '@fortawesome/fontawesome-free/css/all.css' import '@webcomponents/webcomponentsjs/webcomponents-bundle' +import 'opensans-npm-webfont' import { Elm } from './Main.elm' import './monitorMessages' import './renderedHtml' diff --git a/ui/src/main.css b/ui/src/main.css index 3e2d826..57e8bd3 100644 --- a/ui/src/main.css +++ b/ui/src/main.css @@ -46,8 +46,9 @@ body { } body, button, input, table { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: "Open Sans", Helvetica, Arial, sans-serif; font-size: 14px; + font-weight: 400; line-height: 1.43; color: var(--primary-color); } @@ -245,7 +246,7 @@ h3 { font-weight: 400; height: 30px; margin: 0; - padding: 5px 10px; + padding: 5px 10px 6px; text-align: center; text-decoration: none; text-shadow: 0 -1px 0 rgba(0,0,0,0.2); diff --git a/ui/src/navbar.css b/ui/src/navbar.css index 6b0e118..00454d5 100644 --- a/ui/src/navbar.css +++ b/ui/src/navbar.css @@ -25,6 +25,7 @@ .navbar-brand { font-size: 18px; + font-weight: 600; } .navbar-toggle { diff --git a/ui/webpack.config.js b/ui/webpack.config.js index 4e577e7..206778e 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -35,12 +35,10 @@ module.exports = (env, argv) => { ], }, { - include: [/\/src/, /\/node_modules\/@fortawesome\/fontawesome-free\/css/], test: /\.css$/, loader: ['style-loader', 'css-loader'], }, { - include: [/\/node_modules\/@fortawesome\/fontawesome-free\/webfonts/], test: /\.(eot|svg|ttf|woff|woff2)$/, loader: 'file-loader', options: { From 407ae87a3bb1cea52320812a6abb76f8e1ac096f Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Mon, 21 Sep 2020 20:11:32 -0700 Subject: [PATCH 09/17] ui: Add refresh button to mailbox page (#179) `socketConnected` is not implemented, but will be used when we implement #92 --- ui/src/Effect.elm | 8 +++ ui/src/Page/Mailbox.elm | 136 ++++++++++++++++++++++++++++------------ 2 files changed, 103 insertions(+), 41 deletions(-) diff --git a/ui/src/Effect.elm b/ui/src/Effect.elm index 2907417..60bdbe9 100644 --- a/ui/src/Effect.elm +++ b/ui/src/Effect.elm @@ -1,6 +1,7 @@ module Effect exposing ( Effect , addRecent + , append , batch , deleteMessage , disableRouting @@ -68,6 +69,13 @@ type SessionEffect | RoutingEnable +{-| Appends a new effect to a model/effect tuple. +-} +append : Effect msg -> ( a, Effect msg ) -> ( a, Effect msg ) +append e ( model, effect ) = + ( model, batch [ effect, e ] ) + + {-| Packs a List of Effects into a single Effect -} batch : List (Effect msg) -> Effect msg diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index a3a8ce9..5ebe183 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -94,6 +94,7 @@ type alias Model = { session : Session , mailboxName : String , state : State + , socketConnected : Bool , bodyMode : Body , searchInput : String , promptPurge : Bool @@ -107,6 +108,7 @@ init session mailboxName selection = ( { session = session , mailboxName = mailboxName , state = LoadingList selection + , socketConnected = False , bodyMode = SafeHtmlBody , searchInput = "" , promptPurge = False @@ -136,6 +138,7 @@ subscriptions _ = type Msg = ListLoaded (Result HttpUtil.Error (List MessageHeader)) | ClickMessage MessageID + | ClickRefresh | ListKeyPress String Int | CloseMessage | MessageLoaded (Result HttpUtil.Error Message) @@ -165,6 +168,21 @@ update msg model = ] ) + ClickRefresh -> + let + selection = + case model.state of + ShowingList _ (ShowingMessage message) -> + Just message.id + + _ -> + Nothing + in + -- Reset to loading state, preserving the current message selection. + ( { model | state = LoadingList selection } + , Effect.getHeaderList ListLoaded model.mailboxName + ) + CloseMessage -> case model.state of ShowingList list _ -> @@ -191,23 +209,7 @@ update msg model = ( model, Effect.none ) ListLoaded (Ok headers) -> - case model.state of - LoadingList selection -> - let - newModel = - { model - | state = ShowingList (MessageList headers Nothing "") NoMessage - } - in - case selection of - Just id -> - updateOpenMessage newModel id - - Nothing -> - ( newModel, Effect.addRecent newModel.mailboxName ) - - _ -> - ( model, Effect.none ) + updateListLoaded model headers ListLoaded (Err err) -> ( model, Effect.showFlash (HttpUtil.errorFlash err) ) @@ -262,6 +264,33 @@ update msg model = ( { model | now = now }, Effect.none ) +updateListLoaded : Model -> List MessageHeader -> ( Model, Effect Msg ) +updateListLoaded model headers = + case model.state of + LoadingList selection -> + let + newModel = + { model + | state = ShowingList (MessageList headers Nothing "") NoMessage + } + in + Effect.append (Effect.addRecent newModel.mailboxName) <| + case selection of + Just id -> + -- Don't try to load selected message if not present in headers. + if List.any (\header -> Just header.id == selection) headers then + updateOpenMessage newModel id + + else + ( newModel, Effect.updateRoute (Route.Mailbox model.mailboxName) ) + + Nothing -> + ( newModel, Effect.none ) + + _ -> + ( model, Effect.none ) + + {-| Replace the currently displayed message. -} updateMessageResult : Model -> Message -> ( Model, Effect Msg ) @@ -412,10 +441,7 @@ updateMarkMessageSeen model = updateOpenMessage : Model -> String -> ( Model, Effect Msg ) updateOpenMessage model id = ( updateSelected model id - , Effect.batch - [ Effect.addRecent model.mailboxName - , Effect.getMessage MessageLoaded model.mailboxName id - ] + , Effect.getMessage MessageLoaded model.mailboxName id ) @@ -438,26 +464,7 @@ view model = , modal = viewModal model.promptPurge , content = [ div [ class ("mailbox " ++ mode) ] - [ aside [ class "message-list-controls" ] - [ input - [ type_ "text" - , placeholder "search" - , Events.onInput OnSearchInput - , value model.searchInput - ] - [] - , button - [ Events.onClick (OnSearchInput "") - , disabled (model.searchInput == "") - , alt "Clear Search" - ] - [ i [ class "fas fa-times" ] [] ] - , button - [ Events.onClick PurgeMailboxPrompt - , alt "Purge Mailbox" - ] - [ i [ class "fas fa-trash" ] [] ] - ] + [ viewMessageListControls model , viewMessageList model , main_ [ class "message" ] @@ -498,6 +505,53 @@ viewModal promptPurge = Nothing +viewMessageListControls : Model -> Html Msg +viewMessageListControls model = + let + clearButton = + Just <| + button + [ Events.onClick (OnSearchInput "") + , disabled (model.searchInput == "") + , alt "Clear Search" + ] + [ i [ class "fas fa-times" ] [] ] + + purgeButton = + Just <| + button + [ Events.onClick PurgeMailboxPrompt + , alt "Purge Mailbox" + ] + [ i [ class "fas fa-trash" ] [] ] + + refreshButton = + if model.socketConnected then + Nothing + + else + Just <| + button + [ Events.onClick ClickRefresh + , alt "Refresh Mailbox" + ] + [ i [ class "fas fa-sync" ] [] ] + + searchInput = + Just <| + input + [ type_ "text" + , placeholder "search" + , Events.onInput OnSearchInput + , value model.searchInput + ] + [] + in + [ searchInput, clearButton, refreshButton, purgeButton ] + |> List.filterMap identity + |> aside [ class "message-list-controls" ] + + viewMessageList : Model -> Html Msg viewMessageList model = aside [ class "message-list" ] <| From 361bbec2937a81ab4b46a37ffd35dff2fa500b46 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Tue, 22 Sep 2020 14:11:06 -0700 Subject: [PATCH 10/17] ui: Keyboard accessibility focus highlights (#180) * Focus indication for mailbox message list * Add focus for monitor message list --- ui/src/mailbox.css | 15 +++++++++++++++ ui/src/main.css | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/ui/src/mailbox.css b/ui/src/mailbox.css index c0207b4..af26a8f 100644 --- a/ui/src/mailbox.css +++ b/ui/src/mailbox.css @@ -98,9 +98,14 @@ border-width: 1px; border-style: none solid solid solid; cursor: pointer; + outline: none; padding: 5px 8px; } +.message-list-entry:focus { + background-color: var(--focused-bg-color) !important; +} + .message-list-entry.selected { background-color: var(--selected-color); } @@ -109,6 +114,10 @@ border-style: solid; } +.message-list-entry:focus .subject { + color: var(--focused-color); +} + .message-list-entry .subject { color: var(--high-color); } @@ -117,6 +126,12 @@ font-weight: bold; } +.message-list-entry:focus .from, +.message-list-entry:focus .date { + color: var(--focused-color); + opacity: 0.8; +} + .message-list-entry .from, .message-list-entry .date { color: var(--low-color); diff --git a/ui/src/main.css b/ui/src/main.css index 57e8bd3..46cd7f0 100644 --- a/ui/src/main.css +++ b/ui/src/main.css @@ -9,6 +9,8 @@ --border-color: #ddd; --placeholder-color: #9f9f9f; --selected-color: #eee; + --focused-color: #fff; + --focused-bg-color: #337ab7; } html, body, div, span, applet, object, iframe, @@ -352,3 +354,9 @@ h3 { background-color: var(--selected-color); cursor: pointer; } + +.monitor tr:focus { + color: var(--focused-color); + background-color: var(--focused-bg-color); + outline: none; +} From 698b0406c8eee911132601aa5992ee651152e43f Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Wed, 23 Sep 2020 20:43:51 -0700 Subject: [PATCH 11/17] Readme updates (#183) * Add docker build badge * Rephrase things * add dev guide link * Remove brew tap section until #68 is fixed --- README.md | 62 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index b7673d3..013cf08 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ Inbucket ============================================================================= [![Build Status](https://travis-ci.org/inbucket/inbucket.png?branch=master)][Build Status] +[![Docker Image](https://github.com/inbucket/inbucket/workflows/Docker%20Image/badge.svg)][Docker Image] Inbucket is an email testing service; it will accept messages for any email -address and make them available via web, REST and POP3. Once compiled, -Inbucket does not have any external dependencies (HTTP, SMTP, POP3 and storage -are all built in). +address and make them available via web, REST and POP3 interfaces. Once +compiled, Inbucket does not have any external dependencies - HTTP, SMTP, POP3 +and storage are all built in. A Go client for the REST API is available in `github.com/inbucket/inbucket/pkg/rest/client` - [Go API docs] @@ -14,6 +15,7 @@ Read more at the [Inbucket Website] ![Screenshot](http://www.inbucket.org/images/inbucket-ss1.png "Viewing a message") + ## Development Status Inbucket is currently production quality: it is being used for real work. @@ -29,15 +31,6 @@ tracks our `master` branch (releases), `latest` tracks our unstable `development` branch. -## Homebrew Tap - -(currently broken, being tracked in [issue -#68](https://github.com/inbucket/inbucket/issues/68)) - -Inbucket has an OS X [Homebrew] tap available as [jhillyerd/inbucket][Homebrew Tap], -see the `README.md` there for installation instructions. - - ## Building from Source You will need functioning [Go] and [Node.js] installations for this to work. @@ -45,17 +38,20 @@ You will need functioning [Go] and [Node.js] installations for this to work. ```sh git clone https://github.com/inbucket/inbucket.git cd inbucket/ui -npm i +npm ci npm run build cd .. go build ./cmd/inbucket ``` -_Note:_ You may also use the included Makefile to build and test the Go binaries. +For more information on building and development flows, check out the +[Development Quickstart] page of our wiki. + +### Configure and Launch Inbucket reads its configuration from environment variables, but comes with -built in sane defaults. It should work on most Unix and OS X machines as is. -Launch the daemon: +reasonable defaults built-in. It should work on most Unix and OS X machines as +is. Launch the daemon: ```sh ./inbucket @@ -65,27 +61,29 @@ By default the SMTP server will be listening on localhost port 2500 and the web interface will be available at [localhost:9000](http://localhost:9000/). See doc/[config.md] for more information on configuring Inbucket, but you will -likely find the [Configurator] tool easier to use. +likely find the [Configurator] tool the easiest way to generate a configuration. ## About -Inbucket is written in [Go] +Inbucket is written in [Go] and [Elm]. Inbucket is open source software released under the MIT License. The latest version can be found at https://github.com/inbucket/inbucket -[Build Status]: https://travis-ci.org/inbucket/inbucket -[Change Log]: https://github.com/inbucket/inbucket/blob/master/CHANGELOG.md -[config.md]: https://github.com/inbucket/inbucket/blob/master/doc/config.md -[Configurator]: https://www.inbucket.org/configurator/ -[CONTRIBUTING.md]: https://github.com/inbucket/inbucket/blob/develop/CONTRIBUTING.md -[Docker Image]: https://www.inbucket.org/binaries/docker.html -[From Source]: https://www.inbucket.org/installation/from-source.html -[Go]: https://golang.org/ -[Go API docs]: https://godoc.org/github.com/inbucket/inbucket/pkg/rest/client -[Homebrew]: http://brew.sh/ -[Homebrew Tap]: https://github.com/inbucket/homebrew-inbucket -[Inbucket Website]: https://www.inbucket.org/ -[Issues List]: https://github.com/inbucket/inbucket/issues?state=open -[Node.js]: https://nodejs.org/en/ +[Build Status]: https://travis-ci.org/inbucket/inbucket +[Change Log]: https://github.com/inbucket/inbucket/blob/master/CHANGELOG.md +[config.md]: https://github.com/inbucket/inbucket/blob/master/doc/config.md +[Configurator]: https://www.inbucket.org/configurator/ +[CONTRIBUTING.md]: https://github.com/inbucket/inbucket/blob/develop/CONTRIBUTING.md +[Development Quickstart]: https://github.com/inbucket/inbucket/wiki/Development-Quickstart +[Docker Image]: https://www.inbucket.org/binaries/docker.html +[Elm]: https://elm-lang.org/ +[From Source]: https://www.inbucket.org/installation/from-source.html +[Go]: https://golang.org/ +[Go API docs]: https://pkg.go.dev/github.com/inbucket/inbucket/pkg/rest/client +[Homebrew]: http://brew.sh/ +[Homebrew Tap]: https://github.com/inbucket/homebrew-inbucket +[Inbucket Website]: https://www.inbucket.org/ +[Issues List]: https://github.com/inbucket/inbucket/issues?state=open +[Node.js]: https://nodejs.org/en/ From e56365b9a0cadcfef7e9d433ce6c999c74aa6070 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Wed, 23 Sep 2020 21:39:27 -0700 Subject: [PATCH 12/17] Update deps (#184) * backend: update Go dependencies * frontend: update npm dependencies --- go.mod | 4 +- go.sum | 8 +- ui/package-lock.json | 476 ++++++++++++++++++++----------------------- ui/package.json | 12 +- 4 files changed, 231 insertions(+), 269 deletions(-) diff --git a/go.mod b/go.mod index 03ce509..bc67de4 100644 --- a/go.mod +++ b/go.mod @@ -15,9 +15,9 @@ require ( github.com/microcosm-cc/bluemonday v1.0.4 github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/rs/zerolog v1.19.0 + github.com/rs/zerolog v1.20.0 github.com/stretchr/testify v1.6.1 - golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/net v0.0.0-20200923182212-328152dc79b1 golang.org/x/text v0.3.3 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect ) diff --git a/go.sum b/go.sum index ab30628..16fd38d 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.19.0 h1:hYz4ZVdUgjXTBUmrkrw55j1nHx68LfOKIQk5IYtyScg= -github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= +github.com/rs/zerolog v1.20.0 h1:38k9hgtUBdxFwE34yS8rTHmHBa4eN16E4DJlv177LNs= +github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= @@ -66,8 +66,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1 h1:Iu68XRPd67wN4aRGGWwwq6bZo/25jR6uu52l/j2KkUE= +golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/ui/package-lock.json b/ui/package-lock.json index d8bf76d..fcb5b70 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -25,19 +25,19 @@ } }, "@babel/core": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.4.tgz", - "integrity": "sha512-5deljj5HlqRXN+5oJTY7Zs37iH3z3b++KjiKtIsJy1NrjOOVSEaJHEetLBhyu0aQOSNNZ/0IuEAan9GzRuDXHg==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.4", + "@babel/generator": "^7.11.6", "@babel/helper-module-transforms": "^7.11.0", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.4", + "@babel/parser": "^7.11.5", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -46,23 +46,15 @@ "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@babel/generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.4.tgz", - "integrity": "sha512-Rn26vueFx0eOoz7iifCN2UHT6rGtnkSGWSoDRIy8jZN3B91PzeSULbswfLoOWuTuAcNwpG/mxy+uCTDnZ9Mp1g==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { - "@babel/types": "^7.11.0", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -133,14 +125,6 @@ "@babel/helper-function-name": "^7.10.4", "@babel/types": "^7.10.5", "lodash": "^4.17.19" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@babel/helper-explode-assignable-expression": { @@ -212,14 +196,6 @@ "@babel/template": "^7.10.4", "@babel/types": "^7.11.0", "lodash": "^4.17.19" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@babel/helper-optimise-call-expression": { @@ -244,14 +220,6 @@ "dev": true, "requires": { "lodash": "^4.17.19" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@babel/helper-remap-async-to-generator": { @@ -347,9 +315,9 @@ } }, "@babel/parser": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.4.tgz", - "integrity": "sha512-MggwidiH+E9j5Sh8pbrX5sJvMcsqS5o+7iB42M9/k0CD63MjYbdP4nhSh7uB5wnv2/RVzTZFTxzF/kIa5mrCqA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -909,9 +877,9 @@ } }, "@babel/preset-env": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz", - "integrity": "sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", "dev": true, "requires": { "@babel/compat-data": "^7.11.0", @@ -976,7 +944,7 @@ "@babel/plugin-transform-unicode-escapes": "^7.10.4", "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.0", + "@babel/types": "^7.11.5", "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -1018,47 +986,31 @@ } }, "@babel/traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", - "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", + "@babel/generator": "^7.11.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@babel/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", - "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - } } }, "@fortawesome/fontawesome-free": { @@ -1084,9 +1036,9 @@ } }, "@types/html-minifier-terser": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", - "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==", "dev": true }, "@types/json-schema": { @@ -1137,9 +1089,9 @@ } }, "@types/webpack": { - "version": "4.41.21", - "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", - "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", + "version": "4.41.22", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.22.tgz", + "integrity": "sha512-JQDJK6pj8OMV9gWOnN1dcLCyU9Hzs6lux0wBO4lr1+gyEhIBR9U3FMrz12t2GPkg110XAxEAw2WHF6g7nZIbRQ==", "dev": true, "requires": { "@types/anymatch": "*", @@ -1762,6 +1714,16 @@ "dev": true, "optional": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -1987,15 +1949,15 @@ } }, "browserslist": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", - "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.4.tgz", + "integrity": "sha512-7FOuawafVdEwa5Jv4nzeik/PepAjVte6HmVGHsjt2bC237jeL9QlcTBDF3PnHEvcC6uHwLGYPwZHNZMB7wWAnw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001111", - "electron-to-chromium": "^1.3.523", - "escalade": "^3.0.2", - "node-releases": "^1.1.60" + "caniuse-lite": "^1.0.30001135", + "electron-to-chromium": "^1.3.570", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" } }, "buffer": { @@ -2075,12 +2037,6 @@ "once": "^1.3.0", "path-is-absolute": "^1.0.0" } - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true } } }, @@ -2111,10 +2067,16 @@ "tslib": "^1.10.0" } }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, "caniuse-lite": { - "version": "1.0.30001120", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001120.tgz", - "integrity": "sha512-JBP68okZs1X8D7MQTY602jxMYBmXEKOFkzTBaNSkubooMPFOAv2TXWaKle7qgHpjLDhUzA/TMT0qsNleVyXGUQ==", + "version": "1.0.30001135", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001135.tgz", + "integrity": "sha512-ziNcheTGTHlu9g34EVoHQdIu5g4foc8EsxMGC7Xkokmvw0dqNtX8BS8RgCgFBaAiSp2IdjvBxNdh0ssib28eVQ==", "dev": true }, "chalk": { @@ -2527,9 +2489,9 @@ } }, "css-loader": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.2.2.tgz", - "integrity": "sha512-omVGsTkZPVwVRpckeUnLshPp12KsmMSLqYxs12+RzM9jRR5Y+Idn/tBffjXRvOE+qW7if24cuceFJqYR5FmGBg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-4.3.0.tgz", + "integrity": "sha512-rdezjCjScIrsL8BSYszgT4s476IcNKt6yX69t0pHjJVnPUTDpn4WfIpDQTN3wCJvUvfsz/mFjuGOekf3PY3NUg==", "dev": true, "requires": { "camelcase": "^6.0.0", @@ -2542,14 +2504,14 @@ "postcss-modules-scope": "^2.2.0", "postcss-modules-values": "^3.0.0", "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", + "schema-utils": "^2.7.1", "semver": "^7.3.2" }, "dependencies": { "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -2564,50 +2526,21 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "semver": { @@ -2864,9 +2797,9 @@ }, "dependencies": { "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", "dev": true } } @@ -2931,9 +2864,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.555", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.555.tgz", - "integrity": "sha512-/55x3nF2feXFZ5tdGUOr00TxnUjUgdxhrn+eCJ1FAcoAt+cKQTjQkUC5XF4frMWE1R5sjHk+JueuBalimfe5Pg==", + "version": "1.3.571", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.571.tgz", + "integrity": "sha512-UYEQ2Gtc50kqmyOmOVtj6Oqi38lm5yRJY3pLuWt6UIot0No1L09uu6Ja6/1XKwmz/p0eJFZTUZi+khd1PV1hHA==", "dev": true }, "elliptic": { @@ -3019,9 +2952,9 @@ "dev": true }, "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, "encodeurl": { @@ -3131,9 +3064,9 @@ } }, "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", "dev": true }, "escape-html": { @@ -3159,12 +3092,20 @@ } }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -3174,9 +3115,9 @@ "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "etag": { @@ -3465,19 +3406,19 @@ "dev": true }, "file-loader": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.0.0.tgz", - "integrity": "sha512-/aMOAYEFXDdjG0wytpTL5YQLfZnnTmLNjn+AIrJ/6HVnTfDqLsVKUUwkDf4I4kgex36BvjuXEn/TX9B/1ESyqQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.0.tgz", + "integrity": "sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==", "dev": true, "requires": { "loader-utils": "^2.0.0", - "schema-utils": "^2.6.5" + "schema-utils": "^2.7.1" }, "dependencies": { "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -3492,48 +3433,32 @@ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } } } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -4007,9 +3932,9 @@ } }, "html-webpack-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.4.1.tgz", - "integrity": "sha512-nEtdEIsIGXdXGG7MjTTZlmhqhpHU9pJFc1OYxcP36c5/ZKP6b0BJMww2QTvJGQYA9aMxUnjDujpZdYcVOXiBCQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.0.tgz", + "integrity": "sha512-MouoXEYSjTzCrjIxWwg8gxL5fE2X2WZJLmBYXlaJhQUH5K/b5OrqmV7T4dB7iu0xkmJ6JlUuV6fFVtnqbPopZw==", "dev": true, "requires": { "@types/html-minifier-terser": "^5.0.0", @@ -4023,6 +3948,26 @@ "util.promisify": "1.0.0" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -4284,9 +4229,9 @@ "dev": true }, "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true }, "is-data-descriptor": { @@ -4535,39 +4480,14 @@ "dev": true }, "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", "dev": true, "requires": { "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } - } - } + "emojis-list": "^3.0.0", + "json5": "^2.1.2" } }, "locate-path": { @@ -4862,6 +4782,13 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -4975,9 +4902,9 @@ } }, "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", "dev": true }, "normalize-path": { @@ -5470,9 +5397,9 @@ "dev": true }, "postcss": { - "version": "7.0.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz", - "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==", + "version": "7.0.34", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.34.tgz", + "integrity": "sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -5539,14 +5466,15 @@ } }, "postcss-selector-parser": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz", - "integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.3.tgz", + "integrity": "sha512-0ClFaY4X1ra21LRqbW6y3rUbWcxnSVkDFG57R7Nxus9J9myPFlv+jYDMohzpkBx0RrjjiqjtycpchQ+PLGmZ9w==", "dev": true, "requires": { "cssesc": "^3.0.0", "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" } }, "postcss-value-parser": { @@ -5755,9 +5683,9 @@ } }, "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==", "dev": true }, "regenerate-unicode-properties": { @@ -5864,9 +5792,9 @@ } }, "regexpu-core": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.0.tgz", - "integrity": "sha512-TQ4KXRnIn6tz6tjnrXEkD/sshygKH/j5KzK86X8MkeHyZ8qst/LZ89j3X4/8HEIfHANTFIP/AbXakeRhWIl5YQ==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", "dev": true, "requires": { "regenerate": "^1.4.0", @@ -5950,9 +5878,9 @@ "dev": true }, "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -7347,7 +7275,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "glob-parent": { "version": "3.1.0", @@ -7406,9 +7338,9 @@ } }, "webpack": { - "version": "4.44.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", - "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -7437,9 +7369,9 @@ }, "dependencies": { "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -7460,6 +7392,26 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -7790,7 +7742,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "get-caller-file": { "version": "2.0.5", @@ -8034,6 +7990,12 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/ui/package.json b/ui/package.json index b23127a..7f2514a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,19 +14,19 @@ "opensans-npm-webfont": "^1.0.0" }, "devDependencies": { - "@babel/core": "^7.11.4", - "@babel/preset-env": "^7.11.0", + "@babel/core": "^7.11.6", + "@babel/preset-env": "^7.11.5", "@fortawesome/fontawesome-free": "^5.14.0", "@webcomponents/webcomponentsjs": "^2.4.4", "babel-loader": "^8.1.0", - "css-loader": "^4.2.2", + "css-loader": "^4.3.0", "elm-hot-webpack-loader": "^1.1.7", "elm-webpack-loader": "^7.0.1", - "file-loader": "^6.0.0", - "html-webpack-plugin": "^4.4.1", + "file-loader": "^6.1.0", + "html-webpack-plugin": "^4.5.0", "node-elm-compiler": "^5.0.5", "style-loader": "^1.2.1", - "webpack": "^4.44.1", + "webpack": "^4.44.2", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" } From 7c8764957974bec879cda9c02caa7b9213b1f8f1 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Wed, 23 Sep 2020 23:00:29 -0700 Subject: [PATCH 13/17] ui: Convert Layout to use Effects --- ui/src/Effect.elm | 11 ++++++++++ ui/src/Layout.elm | 52 ++++++++++++++++------------------------------- ui/src/Main.elm | 8 +++----- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/ui/src/Effect.elm b/ui/src/Effect.elm index 60bdbe9..c1165b9 100644 --- a/ui/src/Effect.elm +++ b/ui/src/Effect.elm @@ -3,10 +3,12 @@ module Effect exposing , addRecent , append , batch + , clearFlash , deleteMessage , disableRouting , enableRouting , focusModal + , focusModalResult , getGreeting , getHeaderList , getMessage @@ -64,6 +66,7 @@ type ApiEffect msg type SessionEffect = FlashClear | FlashShow Session.Flash + | ModalFocusResult Modal.Msg | RecentAdd String | RoutingDisable | RoutingEnable @@ -233,6 +236,9 @@ performSession ( session, effect ) = FlashShow flash -> ( Session.showFlash flash session, Cmd.none ) + ModalFocusResult result -> + ( Modal.updateSession result session, Cmd.none ) + RoutingDisable -> ( Session.disableRouting session, Cmd.none ) @@ -283,6 +289,11 @@ focusModal toMsg = ModalFocus toMsg +focusModalResult : Modal.Msg -> Effect msg +focusModalResult msg = + SessionEffect (ModalFocusResult msg) + + deleteMessage : HttpResult msg -> String -> String -> Effect msg deleteMessage toMsg mailboxName id = ApiEffect (DeleteMessage toMsg mailboxName id) diff --git a/ui/src/Layout.elm b/ui/src/Layout.elm index 16d4c21..b141808 100644 --- a/ui/src/Layout.elm +++ b/ui/src/Layout.elm @@ -1,7 +1,7 @@ module Layout exposing (Model, Msg, Page(..), frame, init, reset, update) -import Browser.Navigation as Nav import Data.Session as Session exposing (Session) +import Effect exposing (Effect) import Html exposing ( Attribute @@ -39,7 +39,7 @@ import Html.Attributes ) import Html.Events as Events import Modal -import Route exposing (Route) +import Route import Timer exposing (Timer) @@ -96,46 +96,31 @@ type Msg | RecentMenuToggled -update : Msg -> Model msg -> Session -> ( Model msg, Session, Cmd msg ) -update msg model session = +update : Msg -> Model msg -> ( Model msg, Effect msg ) +update msg model = case msg of ClearFlash -> - ( model - , Session.clearFlash session - , Cmd.none - ) + ( model, Effect.clearFlash ) MainMenuToggled -> - ( { model | mainMenuVisible = not model.mainMenuVisible } - , session - , Cmd.none - ) + ( { model | mainMenuVisible = not model.mainMenuVisible }, Effect.none ) ModalFocused message -> - ( model - , Modal.updateSession message session - , Cmd.none - ) + ( model, Effect.focusModalResult message ) ModalUnfocused -> - ( model, session, Modal.resetFocusCmd (ModalFocused >> model.mapMsg) ) + ( model, Effect.focusModal (ModalFocused >> model.mapMsg) ) OnMailboxNameInput name -> - ( { model | mailboxName = name } - , session - , Cmd.none - ) + ( { model | mailboxName = name }, Effect.none ) OpenMailbox -> if model.mailboxName == "" then - ( model, session, Cmd.none ) + ( model, Effect.none ) else ( model - , session - , Route.Mailbox model.mailboxName - |> session.router.toPath - |> Nav.pushUrl session.key + , Effect.navigateRoute True (Route.Mailbox model.mailboxName) ) RecentMenuMouseOver -> @@ -143,20 +128,19 @@ update msg model session = | recentMenuVisible = True , recentMenuTimer = Timer.cancel model.recentMenuTimer } - , session - , Cmd.none + , Effect.none ) RecentMenuMouseOut -> let + -- Keep the recent menu open for a moment even if the mouse leaves it. newTimer = Timer.replace model.recentMenuTimer in ( { model | recentMenuTimer = newTimer } - , session - , Timer.schedule (RecentMenuTimeout >> model.mapMsg) newTimer 400 + , Effect.schedule (RecentMenuTimeout >> model.mapMsg) newTimer 400 ) RecentMenuTimeout timer -> @@ -165,18 +149,16 @@ update msg model session = | recentMenuVisible = False , recentMenuTimer = Timer.cancel timer } - , session - , Cmd.none + , Effect.none ) else -- Timer was no longer valid. - ( model, session, Cmd.none ) + ( model, Effect.none ) RecentMenuToggled -> ( { model | recentMenuVisible = not model.recentMenuVisible } - , session - , Cmd.none + , Effect.none ) diff --git a/ui/src/Main.elm b/ui/src/Main.elm index e7c1e2c..1b38eba 100644 --- a/ui/src/Main.elm +++ b/ui/src/Main.elm @@ -199,12 +199,10 @@ updateMain msg model session = LayoutMsg subMsg -> let - ( layout, newSession, cmd ) = - Layout.update subMsg model.layout session + ( layout, effect ) = + Layout.update subMsg model.layout in - ( updateSession { model | layout = layout } newSession - , cmd - ) + ( { model | layout = layout }, effect ) |> performEffects _ -> updatePage msg model |> performEffects From ebd25a60e122ac7b30816603bbaa2caef46dcb18 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Thu, 24 Sep 2020 11:19:04 -0700 Subject: [PATCH 14/17] ui: Remove to do comments, must keep session.router --- ui/src/Effect.elm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/src/Effect.elm b/ui/src/Effect.elm index c1165b9..05989c6 100644 --- a/ui/src/Effect.elm +++ b/ui/src/Effect.elm @@ -171,7 +171,6 @@ perform ( session, effect ) = RouteNavigate pushHistory route -> let url = - -- TODO replace Session.router session.router.toPath route in ( Session.enableRouting session @@ -184,8 +183,7 @@ perform ( session, effect ) = RouteUpdate route -> ( Session.disableRouting session - , -- TODO replace Session.router - session.router.toPath route + , session.router.toPath route |> Nav.replaceUrl session.key ) From 9b03c311db5d55c1facf9be7a3147415bdd5e31b Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Thu, 24 Sep 2020 15:59:12 -0700 Subject: [PATCH 15/17] ui: Replace Mailbox Session use with ServeUrl (#185) Plus a couple UI padding tweaks --- ui/src/Page/Mailbox.elm | 42 ++++++++++++++++++++++++----------------- ui/src/Page/Status.elm | 2 +- ui/src/main.css | 2 +- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index 5ebe183..53adaf6 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -103,6 +103,10 @@ type alias Model = } +type alias ServeUrl = + List String -> String + + init : Session -> String -> Maybe MessageID -> ( Model, Effect Msg ) init session mailboxName selection = ( { session = session @@ -230,9 +234,7 @@ update msg model = ( { model | bodyMode = bodyMode }, Effect.none ) ModalFocused message -> - ( { model | session = Modal.updateSession message model.session } - , Effect.none - ) + ( model, Effect.focusModalResult message ) OnSearchInput searchInput -> updateSearchInput model searchInput @@ -452,6 +454,10 @@ updateOpenMessage model id = view : Model -> { title : String, modal : Maybe (Html Msg), content : List (Html Msg) } view model = let + serveUrl : ServeUrl + serveUrl = + Api.serveUrl model.session + mode = case model.state of ShowingList _ (ShowingMessage _) -> @@ -476,10 +482,10 @@ view model = ) ShowingList _ (ShowingMessage message) -> - viewMessage model.session model.session.zone message model.bodyMode + viewMessage serveUrl model.session.zone message model.bodyMode ShowingList _ (Transitioning message) -> - viewMessage model.session model.session.zone message model.bodyMode + viewMessage serveUrl model.session.zone message model.bodyMode _ -> text "" @@ -584,14 +590,14 @@ messageChip model selected message = ] -viewMessage : Session -> Time.Zone -> Message -> Body -> Html Msg -viewMessage session zone message bodyMode = +viewMessage : ServeUrl -> Time.Zone -> Message -> Body -> Html Msg +viewMessage serveUrl zone message bodyMode = let htmlUrl = - Api.serveUrl session [ "mailbox", message.mailbox, message.id, "html" ] + serveUrl [ "mailbox", message.mailbox, message.id, "html" ] sourceUrl = - Api.serveUrl session [ "mailbox", message.mailbox, message.id, "source" ] + serveUrl [ "mailbox", message.mailbox, message.id, "source" ] htmlButton = if message.html == "" then @@ -622,7 +628,7 @@ viewMessage session zone message bodyMode = ] , messageErrors message , messageBody message bodyMode - , attachments session message + , attachments serveUrl message ] @@ -685,20 +691,22 @@ messageBody message bodyMode = ] -attachments : Session -> Message -> Html Msg -attachments session message = +attachments : ServeUrl -> Message -> Html Msg +attachments serveUrl message = if List.isEmpty message.attachments then - div [] [] + text "" else - table [ class "attachments well" ] (List.map (attachmentRow session message) message.attachments) + message.attachments + |> List.map (attachmentRow serveUrl message) + |> table [ class "attachments well" ] -attachmentRow : Session -> Message -> Message.Attachment -> Html Msg -attachmentRow session message attach = +attachmentRow : ServeUrl -> Message -> Message.Attachment -> Html Msg +attachmentRow serveUrl message attach = let url = - Api.serveUrl session + serveUrl [ "mailbox" , message.mailbox , message.id diff --git a/ui/src/Page/Status.elm b/ui/src/Page/Status.elm index 3e81723..0f28929 100644 --- a/ui/src/Page/Status.elm +++ b/ui/src/Page/Status.elm @@ -413,7 +413,7 @@ viewMetric metric = , div [ class "value" ] [ text (metric.formatter metric.value) ] , div [ class "graph" ] [ metric.graph metric.history - , text ("(" ++ String.fromInt metric.minutes ++ "min)") + , text (" (" ++ String.fromInt metric.minutes ++ "min)") ] ] diff --git a/ui/src/main.css b/ui/src/main.css index 46cd7f0..5edc7b4 100644 --- a/ui/src/main.css +++ b/ui/src/main.css @@ -76,7 +76,7 @@ a.button { font-size: 11px; font-style: normal; margin: 4px; - padding: 3px 8px; + padding: 3px 8px 4px; text-decoration: none; text-shadow: 0 -1px 0 rgba(0,0,0,0.2); } From 6ce045ddb7ce1c5c2598f7073833600ea1083012 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Thu, 24 Sep 2020 20:43:56 -0700 Subject: [PATCH 16/17] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc8859b..6200dc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Refresh button to reload mailbox contents +- Improved keyboard (tab) focus highlights + ### Changed - The UI now includes the Open Sans webfont instead of relying on browser/OS fonts From e74f5e51167d2ce99af0655d94ffde6e92e53cb6 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Thu, 24 Sep 2020 20:54:07 -0700 Subject: [PATCH 17/17] Update changelog for 3.0.0-rc1 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6200dc9..635157f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). -## [Unreleased] +## [v3.0.0-rc1] ### Added - Refresh button to reload mailbox contents @@ -239,6 +239,7 @@ No change from beta1. specific message. [Unreleased]: https://github.com/inbucket/inbucket/compare/master...develop +[v3.0.0-rc1]: https://github.com/inbucket/inbucket/compare/v3.0.0-beta3...v3.0.0-rc1 [v3.0.0-beta3]: https://github.com/inbucket/inbucket/compare/v3.0.0-beta2...v3.0.0-beta3 [v3.0.0-beta2]: https://github.com/inbucket/inbucket/compare/v3.0.0-beta1...v3.0.0-beta2 [v3.0.0-beta1]: https://github.com/inbucket/inbucket/compare/v2.1.0...v3.0.0-beta1