diff --git a/ruby_event_store-browser/elm/elm.json b/ruby_event_store-browser/elm/elm.json index cae5b5912d..ed9b880c1e 100644 --- a/ruby_event_store-browser/elm/elm.json +++ b/ruby_event_store-browser/elm/elm.json @@ -1,38 +1,42 @@ { - "type": "application", - "source-directories": ["./src"], - "elm-version": "0.19.1", - "dependencies": { - "direct": { - "NoRedInk/elm-json-decode-pipeline": "1.0.1", - "damienklinnert/elm-spinner": "3.0.2", - "elm/browser": "1.0.2", - "elm/core": "1.0.5", - "elm/html": "1.0.0", - "elm/http": "2.0.0", - "elm/json": "1.1.3", - "elm/time": "1.0.0", - "elm/url": "1.0.0", - "elm-community/maybe-extra": "5.3.0", - "klazuka/elm-json-tree-view": "2.1.0", - "rtfeldman/elm-iso8601-date-strings": "1.1.4", - "ryannhg/date-format": "2.3.0" + "type": "application", + "source-directories": [ + "./src" + ], + "elm-version": "0.19.1", + "dependencies": { + "direct": { + "NoRedInk/elm-json-decode-pipeline": "1.0.1", + "damienklinnert/elm-spinner": "3.0.2", + "elm/browser": "1.0.2", + "elm/core": "1.0.5", + "elm/html": "1.0.0", + "elm/http": "2.0.0", + "elm/json": "1.1.3", + "elm/time": "1.0.0", + "elm/url": "1.0.0", + "elm-community/list-extra": "8.7.0", + "elm-community/maybe-extra": "5.3.0", + "justinmimbs/timezone-data": "10.1.0", + "klazuka/elm-json-tree-view": "2.1.0", + "rtfeldman/elm-iso8601-date-strings": "1.1.4", + "ryannhg/date-format": "2.3.0" + }, + "indirect": { + "avh4/elm-color": "1.0.0", + "elm/bytes": "1.0.8", + "elm/file": "1.0.5", + "elm/parser": "1.1.0", + "elm/virtual-dom": "1.0.2" + } }, - "indirect": { - "avh4/elm-color": "1.0.0", - "elm/bytes": "1.0.8", - "elm/file": "1.0.5", - "elm/parser": "1.1.0", - "elm/virtual-dom": "1.0.2", - "robinheghan/murmur3": "1.0.0" + "test-dependencies": { + "direct": { + "elm-explorations/test": "2.2.0" + }, + "indirect": { + "elm/random": "1.0.0", + "robinheghan/murmur3": "1.0.0" + } } - }, - "test-dependencies": { - "direct": { - "elm-explorations/test": "2.2.0" - }, - "indirect": { - "elm/random": "1.0.0" - } - } } diff --git a/ruby_event_store-browser/elm/src/BrowserTime.elm b/ruby_event_store-browser/elm/src/BrowserTime.elm new file mode 100644 index 0000000000..a89d55b1ab --- /dev/null +++ b/ruby_event_store-browser/elm/src/BrowserTime.elm @@ -0,0 +1,34 @@ +module BrowserTime exposing (TimeZone, defaultTimeZone, format) + +import DateFormat exposing (dayOfMonthFixed, hourMilitaryFixed, millisecondFixed, minuteFixed, monthFixed, secondFixed, text, yearNumber) +import Time + + +type alias TimeZone = + { zone : Time.Zone, zoneName : String } + + +defaultTimeZone : TimeZone +defaultTimeZone = + { zone = Time.utc, zoneName = "UTC" } + + +format : TimeZone -> Time.Posix -> String +format { zone, zoneName } time = + DateFormat.format + [ dayOfMonthFixed + , text "." + , monthFixed + , text "." + , yearNumber + , text " " + , hourMilitaryFixed + , text ":" + , minuteFixed + , text ":" + , secondFixed + , text "." + , millisecondFixed + ] + zone + time diff --git a/ruby_event_store-browser/elm/src/Layout.elm b/ruby_event_store-browser/elm/src/Layout.elm index f32d13f949..0a86ddc966 100644 --- a/ruby_event_store-browser/elm/src/Layout.elm +++ b/ruby_event_store-browser/elm/src/Layout.elm @@ -1,11 +1,14 @@ module Layout exposing (Model, Msg, buildModel, update, view, viewIncorrectConfig, viewNotFound) import Browser.Navigation -import Flags exposing (Flags) +import BrowserTime +import Dict import Html exposing (..) -import Html.Attributes exposing (class, href, placeholder, value) +import Html.Attributes exposing (class, href, placeholder, selected, value) import Html.Events exposing (onInput, onSubmit) +import List.Extra import Route +import TimeZone exposing (zones) import Url import WrappedModel exposing (..) @@ -13,6 +16,7 @@ import WrappedModel exposing (..) type Msg = GoToStream | GoToStreamChanged String + | TimeZoneSelected String type alias Model = @@ -26,14 +30,48 @@ buildModel = } -update : Msg -> WrappedModel Model -> ( Model, Cmd Msg ) +update : Msg -> WrappedModel Model -> ( WrappedModel Model, Cmd Msg ) update msg model = case msg of GoToStream -> - ( { goToStream = "" }, Browser.Navigation.pushUrl model.key (Route.streamUrl model.flags.rootUrl model.internal.goToStream) ) + ( { model | internal = Model "" }, Browser.Navigation.pushUrl model.key (Route.streamUrl model.flags.rootUrl model.internal.goToStream) ) GoToStreamChanged newValue -> - ( { goToStream = newValue }, Cmd.none ) + ( { model | internal = Model newValue }, Cmd.none ) + + TimeZoneSelected zoneName -> + let + defaultTimeZone = + BrowserTime.defaultTimeZone + in + if zoneName == defaultTimeZone.zoneName then + let + time = + model.time + + newTime = + { time | selected = defaultTimeZone } + in + ( { model | time = newTime }, Cmd.none ) + + else + let + maybeZone = + Dict.get zoneName zones + in + case maybeZone of + Just zone -> + let + time = + model.time + + newTime = + { time | selected = BrowserTime.TimeZone (zone ()) zoneName } + in + ( { model | time = newTime }, Cmd.none ) + + Nothing -> + ( model, Cmd.none ) view : (Msg -> a) -> WrappedModel Model -> Html a -> Html a @@ -47,7 +85,7 @@ view layoutMsgBuilder model pageView = [ class "bg-white" ] [ pageView ] , footer [] - [ Html.map layoutMsgBuilder (browserFooter model.flags) + [ Html.map layoutMsgBuilder (browserFooter model) ] ] @@ -92,8 +130,13 @@ browserNavigation model = ] -browserFooter : Flags -> Html Msg -browserFooter flags = +availableTimeZones : BrowserTime.TimeZone -> List BrowserTime.TimeZone +availableTimeZones detectedTime = + List.Extra.unique [ BrowserTime.defaultTimeZone, detectedTime ] + + +browserFooter : WrappedModel Model -> Html Msg +browserFooter { flags, time } = let spacer = span @@ -101,9 +144,9 @@ browserFooter flags = [ text "•" ] in footer - [ class "border-gray-400 border-t py-4" ] + [ class "border-gray-400 border-t py-4 px-8 flex justify-between" ] [ div - [ class "flex justify-center text-gray-500 text-sm" ] + [ class "text-gray-500 text-sm" ] [ text ("RubyEventStore v" ++ flags.resVersion) , spacer , a @@ -118,4 +161,20 @@ browserFooter flags = ] [ text "Support" ] ] + , div + [ class "text-gray-500 text-sm" ] + [ Html.select + [ onInput TimeZoneSelected ] + (List.map + (\timeZone -> + option + [ value timeZone.zoneName + , selected (timeZone == time.selected) + ] + [ text timeZone.zoneName + ] + ) + (availableTimeZones time.detected) + ) + ] ] diff --git a/ruby_event_store-browser/elm/src/Main.elm b/ruby_event_store-browser/elm/src/Main.elm index 7c526c69ce..9225edd766 100644 --- a/ruby_event_store-browser/elm/src/Main.elm +++ b/ruby_event_store-browser/elm/src/Main.elm @@ -2,12 +2,16 @@ module Main exposing (main) import Browser import Browser.Navigation +import BrowserTime import Flags exposing (Flags, RawFlags, buildFlags) import Html exposing (..) import Layout import Page.ShowEvent import Page.ShowStream import Route +import Task +import Time +import TimeZone import Url import Url.Parser exposing (()) import WrappedModel exposing (..) @@ -30,6 +34,10 @@ type alias Model = , flags : Maybe Flags , key : Browser.Navigation.Key , layout : Layout.Model + , time : + { detected : BrowserTime.TimeZone + , selected : BrowserTime.TimeZone + } } @@ -39,6 +47,7 @@ type Msg | GotLayoutMsg Layout.Msg | GotShowEventMsg Page.ShowEvent.Msg | GotShowStreamMsg Page.ShowStream.Msg + | ReceiveTimeZone (Result TimeZone.Error ( String, Time.Zone )) type Page @@ -65,9 +74,21 @@ buildModel rawFlags location key = , flags = buildFlags rawFlags , key = key , layout = Layout.buildModel + , time = + { detected = BrowserTime.defaultTimeZone + , selected = BrowserTime.defaultTimeZone + } } + + ( model, cmd ) = + navigate initModel location in - navigate initModel location + ( model + , Cmd.batch + [ cmd + , TimeZone.getZone |> Task.attempt ReceiveTimeZone + ] + ) update : Msg -> Model -> ( Model, Cmd Msg ) @@ -113,10 +134,31 @@ update msg model = Just flags -> let - ( layoutModel, layoutCmd ) = - Layout.update layoutMsg (WrappedModel model.layout model.key flags) + ( wrappedModel, layoutCmd ) = + Layout.update layoutMsg (WrappedModel model.layout model.key model.time flags) + + time = + model.time + + newTime = + { time | selected = wrappedModel.time.selected } in - ( { model | layout = layoutModel }, Cmd.map GotLayoutMsg layoutCmd ) + ( { model | layout = wrappedModel.internal, time = newTime } + , Cmd.map GotLayoutMsg layoutCmd + ) + + ( ReceiveTimeZone (Ok ( zoneName, zone )), _ ) -> + let + detectedTime = + { zone = zone, zoneName = zoneName } + + time = + model.time + + newTime = + { time | detected = detectedTime, selected = detectedTime } + in + ( { model | time = newTime }, Cmd.none ) ( _, _ ) -> ( model, Cmd.none ) @@ -159,20 +201,16 @@ view model = case model.flags of Nothing -> { title = fullTitle Nothing - , body = - [ div [] - [ Layout.viewIncorrectConfig - ] - ] + , body = [ div [] [ Layout.viewIncorrectConfig ] ] } Just flags -> let ( title, content ) = - viewPage model.page + viewPage model.page model.time.selected wrappedModel = - WrappedModel model.layout model.key flags + WrappedModel model.layout model.key model.time flags in { title = fullTitle title , body = [ div [] [ Layout.view GotLayoutMsg wrappedModel content ] ] @@ -189,20 +227,20 @@ fullTitle maybePageTitle = "RubyEventStore::Browser" -viewPage : Page -> ( Maybe String, Html Msg ) -viewPage page = +viewPage : Page -> BrowserTime.TimeZone -> ( Maybe String, Html Msg ) +viewPage page selectedTime = case page of ShowStream pageModel -> let ( title, content ) = - Page.ShowStream.view pageModel + Page.ShowStream.view pageModel selectedTime in ( Just title, Html.map GotShowStreamMsg content ) ShowEvent pageModel -> let ( title, content ) = - Page.ShowEvent.view pageModel + Page.ShowEvent.view pageModel in ( Just title, Html.map GotShowEventMsg content ) diff --git a/ruby_event_store-browser/elm/src/Page/ShowEvent.elm b/ruby_event_store-browser/elm/src/Page/ShowEvent.elm index 40003ac55b..a1ce1cf6bc 100644 --- a/ruby_event_store-browser/elm/src/Page/ShowEvent.elm +++ b/ruby_event_store-browser/elm/src/Page/ShowEvent.elm @@ -1,6 +1,7 @@ module Page.ShowEvent exposing (Model, Msg(..), initCmd, initModel, showJsonTree, subscriptions, update, view) import Api +import BrowserTime import Flags exposing (Flags) import Html exposing (..) import Html.Attributes exposing (class, colspan, href) @@ -213,8 +214,8 @@ showEvent baseUrl event maybeCausedEvents = [ class "w-full text-left grid md:grid-cols-3 gap-8 overflow-hidden" ] [ section [ class "space-y-4" ] - [ h2 [ class "border-gray-400 border-b text-gray-500 uppercase font-bold text-xs pb-2" ] [ text "Event ID"] - , div [ class "text-sm font-medium font-mono"] [ text event.eventId ] + [ h2 [ class "border-gray-400 border-b text-gray-500 uppercase font-bold text-xs pb-2" ] [ text "Event ID" ] + , div [ class "text-sm font-medium font-mono" ] [ text event.eventId ] ] , section [ class "space-y-4" ] [ h2 [ class "border-gray-400 border-b text-gray-500 uppercase font-bold text-xs pb-2" ] [ text "Raw Data" ] diff --git a/ruby_event_store-browser/elm/src/Page/ShowStream.elm b/ruby_event_store-browser/elm/src/Page/ShowStream.elm index 87583d25ab..63f5e7b489 100644 --- a/ruby_event_store-browser/elm/src/Page/ShowStream.elm +++ b/ruby_event_store-browser/elm/src/Page/ShowStream.elm @@ -1,13 +1,13 @@ module Page.ShowStream exposing (Model, Msg(..), initCmd, initModel, update, view) import Api +import BrowserTime import Flags exposing (Flags) import Html exposing (..) import Html.Attributes exposing (class, disabled, href) import Html.Events exposing (onClick) import Http import Route -import TimeHelpers exposing (formatTimestamp) import Url @@ -80,8 +80,8 @@ update msg model = ( { model | problems = serverErrors }, Cmd.none ) -view : Model -> ( String, Html Msg ) -view { streamName, events, relatedStreams, problems, flags } = +view : Model -> BrowserTime.TimeZone -> ( String, Html Msg ) +view { streamName, events, relatedStreams, problems, flags } selectedTime = let title = "Stream " ++ streamName @@ -92,7 +92,7 @@ view { streamName, events, relatedStreams, problems, flags } = case problems of [] -> ( title - , browseEvents flags.rootUrl header events relatedStreams + , browseEvents flags.rootUrl header events relatedStreams selectedTime ) _ -> @@ -108,8 +108,8 @@ view { streamName, events, relatedStreams, problems, flags } = ) -browseEvents : Url.Url -> String -> Api.PaginatedList Api.Event -> Maybe (List String) -> Html Msg -browseEvents baseUrl title { links, events } relatedStreams = +browseEvents : Url.Url -> String -> Api.PaginatedList Api.Event -> Maybe (List String) -> BrowserTime.TimeZone -> Html Msg +browseEvents baseUrl title { links, events } relatedStreams timeZone = div [ class "py-8" ] [ div [ class "flex px-8 justify-between" ] @@ -118,7 +118,7 @@ browseEvents baseUrl title { links, events } relatedStreams = [ text title ] , div [] [ displayPagination links ] ] - , div [ class "px-8" ] [ renderResults baseUrl events ] + , div [ class "px-8" ] [ renderResults baseUrl events timeZone ] , div [] [ renderRelatedStreams baseUrl relatedStreams ] ] @@ -223,8 +223,8 @@ firstPageButton link = [ text "first" ] -renderResults : Url.Url -> List Api.Event -> Html Msg -renderResults baseUrl events = +renderResults : Url.Url -> List Api.Event -> BrowserTime.TimeZone -> Html Msg +renderResults baseUrl events timeZone = case events of [] -> p @@ -252,13 +252,13 @@ renderResults baseUrl events = ] , tbody [ class "align-top" ] - (List.map (itemRow baseUrl) events) + (List.map (itemRow baseUrl timeZone) events) ] -itemRow : Url.Url -> Api.Event -> Html Msg -itemRow baseUrl { eventType, createdAt, eventId } = - tr [ class "border-gray-50 border-b hover:bg-gray-100"] +itemRow : Url.Url -> BrowserTime.TimeZone -> Api.Event -> Html Msg +itemRow baseUrl timeZone { eventType, createdAt, eventId } = + tr [ class "border-gray-50 border-b hover:bg-gray-100" ] [ td [ class "py-2 px-4 align-middle" ] [ a @@ -272,5 +272,5 @@ itemRow baseUrl { eventType, createdAt, eventId } = [ text eventId ] , td [ class "py-2 pr-4 font-mono text-sm leading-none font-medium text-right align-middle" ] - [ text (formatTimestamp createdAt) ] + [ text (BrowserTime.format timeZone createdAt) ] ] diff --git a/ruby_event_store-browser/elm/src/TimeHelpers.elm b/ruby_event_store-browser/elm/src/TimeHelpers.elm deleted file mode 100644 index 5a340cc7c4..0000000000 --- a/ruby_event_store-browser/elm/src/TimeHelpers.elm +++ /dev/null @@ -1,25 +0,0 @@ -module TimeHelpers exposing (formatTimestamp) - -import DateFormat exposing (..) -import Time - - -formatTimestamp : Time.Posix -> String -formatTimestamp time = - format - [ dayOfMonthFixed - , text "." - , monthFixed - , text "." - , yearNumber - , text ", " - , hourMilitaryFixed - , text ":" - , minuteFixed - , text ":" - , secondFixed - , text "." - , millisecondFixed - ] - Time.utc - time diff --git a/ruby_event_store-browser/elm/src/WrappedModel.elm b/ruby_event_store-browser/elm/src/WrappedModel.elm index 055ba2aba5..1bbfdf338e 100644 --- a/ruby_event_store-browser/elm/src/WrappedModel.elm +++ b/ruby_event_store-browser/elm/src/WrappedModel.elm @@ -1,11 +1,16 @@ module WrappedModel exposing (WrappedModel) import Browser.Navigation +import BrowserTime import Flags exposing (Flags) type alias WrappedModel a = { internal : a , key : Browser.Navigation.Key + , time : + { selected : BrowserTime.TimeZone + , detected : BrowserTime.TimeZone + } , flags : Flags }