From ede2d9b758f9946249e4aa44cafb8d0d18f3002e Mon Sep 17 00:00:00 2001 From: Maciek Korsan Date: Thu, 21 Mar 2024 20:57:25 +0100 Subject: [PATCH] Bookmarks links --- ruby_event_store-browser/elm/src/Layout.elm | 81 ++++++++++++++++--- .../elm/src/Page/ShowEvent.elm | 29 +++++-- ruby_event_store-browser/public/bootstrap.js | 25 ++++-- 3 files changed, 112 insertions(+), 23 deletions(-) diff --git a/ruby_event_store-browser/elm/src/Layout.elm b/ruby_event_store-browser/elm/src/Layout.elm index e9c35d93f9..5fcdc5edcf 100644 --- a/ruby_event_store-browser/elm/src/Layout.elm +++ b/ruby_event_store-browser/elm/src/Layout.elm @@ -23,6 +23,8 @@ import WrappedModel exposing (..) type Msg = TimeZoneSelected String | SearchMsg Search.Msg + | KeyPress String Bool Bool + | ToggleBookmarksMenu | ToggleDialog | SearchedStreamsFetched (Result Http.Error (List SearchStream)) | OnSelect Search.Stream @@ -32,10 +34,21 @@ type Msg type alias Model = { search : Search.Model Msg + , displayBookmarksMenu : Bool + , bookmarks : List Bookmark + } + + +type alias Bookmark = + { label : String + , link : String + , itemType : String } port toggleDialog : String -> Cmd msg + + port requestSearch : (String -> msg) -> Sub msg @@ -48,6 +61,8 @@ buildModel : Model buildModel = { search = Search.init OnSelect OnQueryChanged + , displayBookmarksMenu = False + , bookmarks = [ { itemType = "Stream", label = "Bookmark 1", link = "/" }, { itemType = "Stream", label = "Bookmark 2", link = "/" } ] } @@ -69,7 +84,7 @@ update msg model = ( newSearch, cmd ) = Search.update searchMsg model.internal.search in - ( { model | internal = Model newSearch }, cmd ) + ( { model | internal = Model newSearch model.internal.displayBookmarksMenu model.internal.bookmarks }, cmd ) OnSelect streamName -> ( model @@ -122,6 +137,20 @@ update msg model = RequestSearch _ -> ( model, toggleDialog searchModalId ) + ToggleBookmarksMenu -> + ( { model | internal = Model model.internal.search (not model.internal.displayBookmarksMenu) model.internal.bookmarks }, Cmd.none ) + + KeyPress key isMetaDown isCtrlDown -> + case ( key, isMetaDown, isCtrlDown ) of + ( "k", True, False ) -> + ( model, toggleDialog searchModalId ) + + ( "k", False, True ) -> + ( model, toggleDialog searchModalId ) + + _ -> + ( model, Cmd.none ) + ToggleDialog -> ( model, toggleDialog searchModalId ) @@ -136,7 +165,7 @@ update msg model = newModel = { searchModel | streams = streams_ } in - ( { model | internal = Model newModel }, Cmd.none ) + ( { model | internal = Model newModel model.internal.displayBookmarksMenu model.internal.bookmarks }, Cmd.none ) SearchedStreamsFetched (Err _) -> let @@ -146,7 +175,7 @@ update msg model = newModel = { searchModel | streams = [] } in - ( { model | internal = Model newModel }, Cmd.none ) + ( { model | internal = Model newModel model.internal.displayBookmarksMenu model.internal.bookmarks }, Cmd.none ) view : (Msg -> a) -> WrappedModel Model -> Html a -> Html a @@ -180,7 +209,7 @@ viewIncorrectConfig = browserNavigation : WrappedModel Model -> Html Msg browserNavigation model = nav - [ class "flex bg-red-700 px-8 h-16" ] + [ class "flex bg-red-700 px-8 h-16 justify-between sticky" ] [ div [ class "flex items-center" ] [ a @@ -190,11 +219,8 @@ browserNavigation model = [ text "Ruby Event Store" ] ] , div - [ class "flex-1" ] - [] - , div - [ class "flex items-center" ] - [ fakeSearchInput ] + [ class "flex items-center gap-2" ] + [ fakeSearchInput, bookmarksMenu model ] ] @@ -259,11 +285,46 @@ timeZoneSelect time = ) +visibleBookmarksMenu : Bool -> String +visibleBookmarksMenu displayBookmarksMenu = + if displayBookmarksMenu then + "block" + + else + "hidden" + + +bookmarkToHtml : Bookmark -> Html Msg +bookmarkToHtml bookmark = + li [] + [ a [ href bookmark.link, class "whitespace-nowrap p-4 hover:bg-gray-100" ] [ text bookmark.label ] + ] + + +bookmarksMenu : WrappedModel Model -> Html Msg +bookmarksMenu model = + div [ class "relative" ] + [ button + [ onClick ToggleBookmarksMenu + , class "text-red-100 outline-none text-sm flex gap-2 items-center bg-red-800 hover:bg-red-900 h-9 px-3 rounded" + ] + [ FeatherIcons.bookmark + |> FeatherIcons.withClass "size-4" + |> FeatherIcons.toHtml [] + ] + , div [ class ("absolute translate-y-4 right-0 top-full bg-white shadow rounded-lg " ++ visibleBookmarksMenu model.internal.displayBookmarksMenu) ] + [ model.internal.bookmarks + |> List.map bookmarkToHtml + |> ul [ class "text-gray-800 text-sm space-y-4" ] + ] + ] + + fakeSearchInput : Html Msg fakeSearchInput = button [ onClick ToggleDialog - , class "text-red-100 outline-none text-sm flex gap-2 items-center bg-red-800 hover:bg-red-900 py-2 px-3 rounded" + , class "text-red-100 outline-none text-sm flex gap-2 items-center bg-red-800 hover:bg-red-900 h-9 px-3 rounded" ] [ FeatherIcons.search |> FeatherIcons.withClass "size-4" diff --git a/ruby_event_store-browser/elm/src/Page/ShowEvent.elm b/ruby_event_store-browser/elm/src/Page/ShowEvent.elm index 8fd0af33ec..3a08c2dcc3 100644 --- a/ruby_event_store-browser/elm/src/Page/ShowEvent.elm +++ b/ruby_event_store-browser/elm/src/Page/ShowEvent.elm @@ -64,6 +64,9 @@ initModel flags eventId = port copyToClipboard : String -> Cmd msg +port toggleBookmark : String -> Cmd msg + + -- UPDATE @@ -75,6 +78,7 @@ type Msg | CausedEventsFetched (Result Http.Error (Api.PaginatedList Api.Event)) | CausedStreamFetched (Result Http.Error Api.Stream) | Copy String + | ToggleBookmark String initCmd : Flags -> String -> Cmd Msg @@ -136,6 +140,9 @@ update msg model = CausedEventsFetched (Err _) -> ( { model | causedEvents = Api.Failure }, Cmd.none ) + ToggleBookmark id -> + ( model, toggleBookmark id ) + apiEventToEvent : Api.Event -> Event apiEventToEvent e = @@ -242,24 +249,31 @@ showEvent baseUrl event maybeCausedEvents selectedTime = [ header [ class "flex items-start justify-between gap-4 flex-wrap md:flex-nowrap" ] - [ div [ class "flex flex-col"] + [ div [ class "flex flex-col items-start" ] [ h1 [ class "font-bold text-2xl break-words min-w-0 mb-2" ] [ text event.eventType ] - , p [ class "flex gap-2 md:items-center min-w-0 text-sm flex-wrap"] - [ - span [ class "whitespace-nowrap text-xs text-gray-500 uppercase font-bold uppercase"] + , p [ class "flex gap-2 md:items-center min-w-0 text-sm flex-wrap mb-4" ] + [ span [ class "whitespace-nowrap text-xs text-gray-500 uppercase font-bold uppercase" ] [ text "Event ID:" - ], - - button [ class "flex items-center text-left gap-2 group font-mono text-gray-800 font-bold text-sm", onClick (Copy event.eventId) ] + ] + , button [ class "flex items-center text-left gap-2 group font-mono text-gray-800 font-bold text-sm", onClick (Copy event.eventId) ] [ text event.eventId , FeatherIcons.clipboard |> FeatherIcons.withClass "size-4 -translate-y-0.5 opacity-0 group-hover:opacity-100 " |> FeatherIcons.toHtml [] ] ] + , button [ onClick (ToggleBookmark event.eventId) ] + [ span [ class "flex items-center gap-1 p-1.5 px-2 rounded text-[.65rem] uppercase tracking-wide font-medium bg-red-100 hover:bg-red-200 text-red-700 hover:text-red-900 " ] + [ FeatherIcons.bookmark + |> FeatherIcons.withClass "size-3" + |> FeatherIcons.toHtml [] + , text + "bookmark" + ] + ] ] , div [ class "space-y-4" @@ -284,7 +298,6 @@ showEvent baseUrl event maybeCausedEvents selectedTime = ] ] ] - , div [ class "w-full text-left grid md:grid-cols-2 gap-8 overflow-hidden" ] diff --git a/ruby_event_store-browser/public/bootstrap.js b/ruby_event_store-browser/public/bootstrap.js index 2e344b9d8b..97d127c998 100644 --- a/ruby_event_store-browser/public/bootstrap.js +++ b/ruby_event_store-browser/public/bootstrap.js @@ -1,19 +1,34 @@ const app = Elm.Main.init({ - flags: JSON.parse(document.querySelector("meta[name='ruby-event-store-browser-settings']").getAttribute("content")), + flags: JSON.parse( + document + .querySelector("meta[name='ruby-event-store-browser-settings']") + .getAttribute("content") + ), }); -app.ports.copyToClipboard.subscribe(function(message) { +app.ports.copyToClipboard.subscribe(function (message) { navigator.clipboard.writeText(message); }); -app.ports.toggleDialog.subscribe(function(id) { - const dialog = document.querySelector(`#${id}`) +app.ports.toggleDialog.subscribe(function (id) { + const dialog = document.querySelector(`#${id}`); dialog.open ? dialog.close() : dialog.showModal(); }); window.addEventListener("keydown", (event) => { if (event.key === "k" && (event.ctrlKey || event.metaKey)) { - app.ports.requestSearch.send("") + app.ports.requestSearch.send(""); event.preventDefault(); } }); + +app.ports.toggleBookmark.subscribe(function (id) { + const bookmarks = JSON.parse(localStorage.getItem("bookmarks")) || []; + if (bookmarks.indexOf(id) >= 0) { + bookmarks.splice(bookmarks.indexOf(id), 1); + } else { + bookmarks.push(id); + } + console.log(bookmarks); + localStorage.setItem("bookmarks", JSON.stringify(bookmarks)); +});