From a50a0a9a1ac7bb416f66d8577d8e901a5a9e7527 Mon Sep 17 00:00:00 2001 From: eikek Date: Sat, 8 Jan 2022 19:31:26 +0100 Subject: [PATCH] Bookmark queries scoped to user or collective --- .scalafmt.conf | 2 +- .../backend/ops/OClientSettings.scala | 5 +- .../src/main/resources/docspell-openapi.yml | 6 + .../routes/ClientSettingsRoutes.scala | 4 +- modules/webapp/src/main/elm/Api.elm | 89 ++++++++- .../src/main/elm/Comp/BookmarkQueryForm.elm | 179 ++++++++++++++++++ .../src/main/elm/Comp/BookmarkQueryManage.elm | 173 +++++++++++++++++ .../src/main/elm/Data/BookmarkedQuery.elm | 114 +++++++++++ .../elm/Messages/Comp/BookmarkQueryForm.elm | 46 +++++ .../elm/Messages/Comp/BookmarkQueryManage.elm | 46 +++++ .../src/main/elm/Messages/Page/Home.elm | 13 ++ .../webapp/src/main/elm/Page/Home/Data.elm | 11 ++ .../webapp/src/main/elm/Page/Home/Update.elm | 49 +++++ .../webapp/src/main/elm/Page/Home/View2.elm | 37 +++- 14 files changed, 767 insertions(+), 7 deletions(-) create mode 100644 modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm create mode 100644 modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm create mode 100644 modules/webapp/src/main/elm/Data/BookmarkedQuery.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryForm.elm create mode 100644 modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryManage.elm diff --git a/.scalafmt.conf b/.scalafmt.conf index c76c4213..bffbf1d3 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -2,7 +2,7 @@ version = "3.3.1" preset = default align.preset = some -runner.dialect = scala213 +runner.dialect = scala213source3 maxColumn = 90 diff --git a/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala b/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala index 3e6ff0c9..16b653a1 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala @@ -27,7 +27,10 @@ trait OClientSettings[F[_]] { def deleteCollective(clientId: Ident, account: AccountId): F[Boolean] def saveCollective(clientId: Ident, account: AccountId, data: Json): F[Unit] - def loadCollective(clientId: Ident, account: AccountId): F[Option[RClientSettingsCollective]] + def loadCollective( + clientId: Ident, + account: AccountId + ): F[Option[RClientSettingsCollective]] } diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index f7f691f0..bfa802a7 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -1923,6 +1923,9 @@ paths: an identifier to a client application. It returns a JSON structure. The server doesn't care about the actual data, since it is meant to be interpreted by clients. + + If there is nothing stored for the given `clientId` an empty + JSON object (`{}`) is returned! security: - authTokenHeader: [] responses: @@ -1994,6 +1997,9 @@ paths: `clientId` is an identifier to a client application. It returns a JSON structure. The server doesn't care about the actual data, since it is meant to be interpreted by clients. + + If there is nothing stored for the given `clientId` an empty + JSON object (`{}`) is returned! security: - authTokenHeader: [] responses: diff --git a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala index 79dcbd19..278b8154 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala @@ -53,7 +53,7 @@ object ClientSettingsRoutes { data <- backend.clientSettings.loadUser(clientId, user.account) res <- data match { case Some(d) => Ok(d.settingsData) - case None => NotFound() + case None => Ok(Map.empty[String, String]) } } yield res @@ -80,7 +80,7 @@ object ClientSettingsRoutes { data <- backend.clientSettings.loadCollective(clientId, user.account) res <- data match { case Some(d) => Ok(d.settingsData) - case None => NotFound() + case None => Ok(Map.empty[String, String]) } } yield res diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 4c66482d..9d5c9e84 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -6,7 +6,8 @@ module Api exposing - ( addConcEquip + ( addBookmark + , addConcEquip , addConcPerson , addCorrOrg , addCorrPerson @@ -52,6 +53,7 @@ module Api exposing , disableOtp , fileURL , getAttachmentMeta + , getBookmarks , getClientSettings , getCollective , getCollectiveSettings @@ -120,6 +122,7 @@ module Api exposing , restoreAllItems , restoreItem , sampleEvent + , saveBookmarks , saveClientSettings , searchShare , searchShareStats @@ -260,6 +263,7 @@ import Api.Model.User exposing (User) import Api.Model.UserList exposing (UserList) import Api.Model.UserPass exposing (UserPass) import Api.Model.VersionInfo exposing (VersionInfo) +import Data.BookmarkedQuery exposing (AllBookmarks, BookmarkedQuery, BookmarkedQueryDef, Bookmarks) import Data.ContactType exposing (ContactType) import Data.CustomFieldOrder exposing (CustomFieldOrder) import Data.EquipmentOrder exposing (EquipmentOrder) @@ -2285,6 +2289,89 @@ saveClientSettings flags settings receive = +--- Query Bookmarks + + +type alias BookmarkLocation = + Data.BookmarkedQuery.Location + + +bookmarkLocationUri : Flags -> BookmarkLocation -> String +bookmarkLocationUri flags loc = + case loc of + Data.BookmarkedQuery.User -> + flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClientBookmarks" + + Data.BookmarkedQuery.Collective -> + flags.config.baseUrl ++ "/api/v1/sec/clientSettings/collective/webClientBookmarks" + + +getBookmarksTask : Flags -> BookmarkLocation -> Task.Task Http.Error Bookmarks +getBookmarksTask flags loc = + Http2.authTask + { method = "GET" + , url = bookmarkLocationUri flags loc + , account = getAccount flags + , body = Http.emptyBody + , resolver = Http2.jsonResolver Data.BookmarkedQuery.bookmarksDecoder + , headers = [] + , timeout = Nothing + } + + +getBookmarksFor : Flags -> BookmarkLocation -> (Result Http.Error Bookmarks -> msg) -> Cmd msg +getBookmarksFor flags loc receive = + Task.attempt receive (getBookmarksTask flags loc) + + +getBookmarks : Flags -> (Result Http.Error AllBookmarks -> msg) -> Cmd msg +getBookmarks flags receive = + let + coll = + getBookmarksTask flags Data.BookmarkedQuery.Collective + + user = + getBookmarksTask flags Data.BookmarkedQuery.User + + combine bc bu = + AllBookmarks bc bu + in + Task.map2 combine coll user + |> Task.attempt receive + + +saveBookmarksTask : Flags -> BookmarkLocation -> Bookmarks -> Task.Task Http.Error BasicResult +saveBookmarksTask flags loc bookmarks = + Http2.authTask + { method = "PUT" + , url = bookmarkLocationUri flags loc + , account = getAccount flags + , body = Http.jsonBody (Data.BookmarkedQuery.bookmarksEncode bookmarks) + , resolver = Http2.jsonResolver Api.Model.BasicResult.decoder + , headers = [] + , timeout = Nothing + } + + +saveBookmarks : Flags -> Bookmarks -> BookmarkLocation -> (Result Http.Error BasicResult -> msg) -> Cmd msg +saveBookmarks flags bookmarks loc receive = + Task.attempt receive (saveBookmarksTask flags loc bookmarks) + + +addBookmark : Flags -> BookmarkedQueryDef -> (Result Http.Error BasicResult -> msg) -> Cmd msg +addBookmark flags model receive = + let + load = + getBookmarksTask flags model.location + + add current = + Data.BookmarkedQuery.add model.query current + |> saveBookmarksTask flags model.location + in + Task.andThen add load |> Task.attempt receive + + + --- OTP diff --git a/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm b/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm new file mode 100644 index 00000000..95456356 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/BookmarkQueryForm.elm @@ -0,0 +1,179 @@ +{- + Copyright 2020 Eike K. & Contributors + + SPDX-License-Identifier: AGPL-3.0-or-later +-} + + +module Comp.BookmarkQueryForm exposing (Model, Msg, get, init, initQuery, update, view) + +import Comp.Basic as B +import Comp.PowerSearchInput +import Data.BookmarkedQuery exposing (BookmarkedQueryDef, Location(..)) +import Data.Flags exposing (Flags) +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (onCheck, onInput) +import Messages.Comp.BookmarkQueryForm exposing (Texts) +import Styles as S +import Util.Maybe + + +type alias Model = + { name : Maybe String + , queryModel : Comp.PowerSearchInput.Model + , location : Location + } + + +initQuery : String -> ( Model, Cmd Msg ) +initQuery q = + let + res = + Comp.PowerSearchInput.update + (Comp.PowerSearchInput.setSearchString q) + Comp.PowerSearchInput.init + in + ( { name = Nothing + , queryModel = res.model + , location = User + } + , Cmd.batch + [ Cmd.map QueryMsg res.cmd + ] + ) + + +init : ( Model, Cmd Msg ) +init = + initQuery "" + + +isValid : Model -> Bool +isValid model = + Comp.PowerSearchInput.isValid model.queryModel + && model.name + /= Nothing + + +get : Model -> Maybe BookmarkedQueryDef +get model = + let + qStr = + Maybe.withDefault "" model.queryModel.input + in + if isValid model then + Just + { query = + { query = qStr + , name = Maybe.withDefault "" model.name + } + , location = model.location + } + + else + Nothing + + +type Msg + = SetName String + | QueryMsg Comp.PowerSearchInput.Msg + | SetLocation Location + + +update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) +update _ msg model = + case msg of + SetName n -> + ( { model | name = Util.Maybe.fromString n }, Cmd.none, Sub.none ) + + SetLocation loc -> + ( { model | location = loc }, Cmd.none, Sub.none ) + + QueryMsg lm -> + let + res = + Comp.PowerSearchInput.update lm model.queryModel + in + ( { model | queryModel = res.model } + , Cmd.map QueryMsg res.cmd + , Sub.map QueryMsg res.subs + ) + + + +--- View + + +view : Texts -> Model -> Html Msg +view texts model = + let + queryInput = + div + [ class "relative flex flex-grow flex-row" ] + [ Html.map QueryMsg + (Comp.PowerSearchInput.viewInput + { placeholder = texts.queryLabel + , extraAttrs = [] + } + model.queryModel + ) + , Html.map QueryMsg + (Comp.PowerSearchInput.viewResult [] model.queryModel) + ] + in + div + [ class "flex flex-col" ] + [ div [ class "mb-4" ] + [ label + [ for "sharename" + , class S.inputLabel + ] + [ text texts.basics.name + , B.inputRequired + ] + , input + [ type_ "text" + , onInput SetName + , placeholder texts.basics.name + , value <| Maybe.withDefault "" model.name + , id "sharename" + , class S.textInput + ] + [] + ] + , div [ class "flex flex-col mb-4 " ] + [ label [ class "inline-flex items-center" ] + [ input + [ type_ "radio" + , checked (model.location == User) + , onCheck (\_ -> SetLocation User) + , class S.radioInput + ] + [] + , span [ class "ml-2" ] [ text texts.userLocation ] + , span [ class "ml-3 opacity-75 text-sm" ] [ text texts.userLocationText ] + ] + , label [ class "inline-flex items-center" ] + [ input + [ type_ "radio" + , checked (model.location == Collective) + , class S.radioInput + , onCheck (\_ -> SetLocation Collective) + ] + [] + , span [ class "ml-2" ] [ text texts.collectiveLocation ] + , span [ class "ml-3 opacity-75 text-sm" ] [ text texts.collectiveLocationText ] + ] + ] + , div [ class "mb-4" ] + [ label + [ for "sharequery" + , class S.inputLabel + ] + [ text texts.queryLabel + , B.inputRequired + ] + , queryInput + ] + ] diff --git a/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm b/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm new file mode 100644 index 00000000..0b641d18 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/BookmarkQueryManage.elm @@ -0,0 +1,173 @@ +module Comp.BookmarkQueryManage exposing (..) + +import Api +import Api.Model.BasicResult exposing (BasicResult) +import Comp.Basic as B +import Comp.BookmarkQueryForm +import Data.BookmarkedQuery exposing (BookmarkedQueryDef) +import Data.Flags exposing (Flags) +import Html exposing (Html, div, text) +import Html.Attributes exposing (class, classList, href) +import Html.Events exposing (onClick) +import Http +import Messages.Comp.BookmarkQueryManage exposing (Texts) +import Styles as S + + +type alias Model = + { formModel : Comp.BookmarkQueryForm.Model + , loading : Bool + , formState : FormState + } + + +type FormState + = FormStateNone + | FormStateError Http.Error + | FormStateSaveError String + | FormStateInvalid + | FormStateSaved + + +init : String -> ( Model, Cmd Msg ) +init query = + let + ( fm, fc ) = + Comp.BookmarkQueryForm.initQuery query + in + ( { formModel = fm + , loading = False + , formState = FormStateNone + } + , Cmd.map FormMsg fc + ) + + +type Msg + = Submit + | Cancel + | FormMsg Comp.BookmarkQueryForm.Msg + | SaveResp (Result Http.Error BasicResult) + + + +--- Update + + +type FormResult + = Submitted BookmarkedQueryDef + | Cancelled + | Done + | None + + +type alias UpdateResult = + { model : Model + , cmd : Cmd Msg + , sub : Sub Msg + , outcome : FormResult + } + + +update : Flags -> Msg -> Model -> UpdateResult +update flags msg model = + let + empty = + { model = model + , cmd = Cmd.none + , sub = Sub.none + , outcome = None + } + in + case msg of + FormMsg lm -> + let + ( fm, fc, fs ) = + Comp.BookmarkQueryForm.update flags lm model.formModel + in + { model = { model | formModel = fm } + , cmd = Cmd.map FormMsg fc + , sub = Sub.map FormMsg fs + , outcome = None + } + + Submit -> + case Comp.BookmarkQueryForm.get model.formModel of + Just data -> + { empty | cmd = save flags data, outcome = Submitted data, model = { model | loading = True } } + + Nothing -> + { empty | model = { model | formState = FormStateInvalid } } + + Cancel -> + { model = model + , cmd = Cmd.none + , sub = Sub.none + , outcome = Cancelled + } + + SaveResp (Ok res) -> + if res.success then + { empty | model = { model | loading = False, formState = FormStateSaved }, outcome = Done } + + else + { empty | model = { model | loading = False, formState = FormStateSaveError res.message } } + + SaveResp (Err err) -> + { empty | model = { model | loading = False, formState = FormStateError err } } + + +save : Flags -> BookmarkedQueryDef -> Cmd Msg +save flags model = + Api.addBookmark flags model SaveResp + + + +--- View + + +view : Texts -> Model -> Html Msg +view texts model = + div [ class "relative" ] + [ B.loadingDimmer { label = "", active = model.loading } + , Html.map FormMsg (Comp.BookmarkQueryForm.view texts.form model.formModel) + , case model.formState of + FormStateNone -> + div [ class "hidden" ] [] + + FormStateError err -> + div [ class S.errorMessage ] + [ text <| texts.httpError err + ] + + FormStateInvalid -> + div [ class S.errorMessage ] + [ text texts.formInvalid + ] + + FormStateSaveError m -> + div [ class S.errorMessage ] + [ text m + ] + + FormStateSaved -> + div [ class S.successMessage ] + [ text texts.saved + ] + , div [ class "flex flex-row space-x-2 py-2" ] + [ B.primaryButton + { label = texts.basics.submit + , icon = "fa fa-save" + , disabled = False + , handler = onClick Submit + , attrs = [ href "#" ] + } + , B.secondaryButton + { label = texts.basics.cancel + , icon = "fa fa-times" + , disabled = False + , handler = onClick Cancel + , attrs = [ href "#" ] + } + ] + ] diff --git a/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm b/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm new file mode 100644 index 00000000..4c2c21cc --- /dev/null +++ b/modules/webapp/src/main/elm/Data/BookmarkedQuery.elm @@ -0,0 +1,114 @@ +module Data.BookmarkedQuery exposing + ( AllBookmarks + , BookmarkedQuery + , BookmarkedQueryDef + , Bookmarks + , Location(..) + , add + , bookmarksDecoder + , bookmarksEncode + , emptyBookmarks + , exists + , remove + ) + +import Json.Decode as D +import Json.Encode as E + + +type Location + = User + | Collective + + +type alias BookmarkedQuery = + { name : String + , query : String + } + + +bookmarkedQueryDecoder : D.Decoder BookmarkedQuery +bookmarkedQueryDecoder = + D.map2 BookmarkedQuery + (D.field "name" D.string) + (D.field "query" D.string) + + +bookmarkedQueryEncode : BookmarkedQuery -> E.Value +bookmarkedQueryEncode bq = + E.object + [ ( "name", E.string bq.name ) + , ( "query", E.string bq.query ) + ] + + +type alias BookmarkedQueryDef = + { query : BookmarkedQuery + , location : Location + } + + +type Bookmarks + = Bookmarks (List BookmarkedQuery) + + +emptyBookmarks : Bookmarks +emptyBookmarks = + Bookmarks [] + + +type alias AllBookmarks = + { collective : Bookmarks + , user : Bookmarks + } + + +{-| Checks wether a bookmark of this name already exists. +-} +exists : String -> Bookmarks -> Bool +exists name bookmarks = + case bookmarks of + Bookmarks list -> + List.any (\b -> b.name == name) list + + +remove : String -> Bookmarks -> Bookmarks +remove name bookmarks = + case bookmarks of + Bookmarks list -> + Bookmarks <| List.filter (\b -> b.name /= name) list + + +sortByName : Bookmarks -> Bookmarks +sortByName bm = + case bm of + Bookmarks all -> + Bookmarks <| List.sortBy .name all + + +add : BookmarkedQuery -> Bookmarks -> Bookmarks +add query bookmarks = + case remove query.name bookmarks of + Bookmarks all -> + sortByName (Bookmarks (query :: all)) + + +bookmarksDecoder : D.Decoder Bookmarks +bookmarksDecoder = + D.maybe + (D.field "bookmarks" + (D.list bookmarkedQueryDecoder + |> D.map Bookmarks + |> D.map sortByName + ) + ) + |> D.map (Maybe.withDefault emptyBookmarks) + + +bookmarksEncode : Bookmarks -> E.Value +bookmarksEncode bookmarks = + case bookmarks of + Bookmarks all -> + E.object + [ ( "bookmarks", E.list bookmarkedQueryEncode all ) + ] diff --git a/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryForm.elm b/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryForm.elm new file mode 100644 index 00000000..1fcf3fa4 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryForm.elm @@ -0,0 +1,46 @@ +{- + Copyright 2020 Eike K. & Contributors + + SPDX-License-Identifier: AGPL-3.0-or-later +-} + + +module Messages.Comp.BookmarkQueryForm exposing + ( Texts + , de + , gb + ) + +import Messages.Basics + + +type alias Texts = + { basics : Messages.Basics.Texts + , queryLabel : String + , userLocation : String + , userLocationText : String + , collectiveLocation : String + , collectiveLocationText : String + } + + +gb : Texts +gb = + { basics = Messages.Basics.gb + , queryLabel = "Query" + , userLocation = "User scope" + , userLocationText = "The bookmarked query is just for you" + , collectiveLocation = "Collective scope" + , collectiveLocationText = "The bookmarked query can be used and edited by all users" + } + + +de : Texts +de = + { basics = Messages.Basics.de + , queryLabel = "Abfrage" + , userLocation = "Persönliches Bookmark" + , userLocationText = "Der Bookmark ist nur für dich" + , collectiveLocation = "Kollektiv-Bookmark" + , collectiveLocationText = "Der Bookmark kann von allen Benutzer verwendet werden" + } diff --git a/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryManage.elm b/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryManage.elm new file mode 100644 index 00000000..db568072 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/BookmarkQueryManage.elm @@ -0,0 +1,46 @@ +{- + Copyright 2020 Eike K. & Contributors + + SPDX-License-Identifier: AGPL-3.0-or-later +-} + + +module Messages.Comp.BookmarkQueryManage exposing + ( Texts + , de + , gb + ) + +import Http +import Messages.Basics +import Messages.Comp.BookmarkQueryForm +import Messages.Comp.HttpError + + +type alias Texts = + { basics : Messages.Basics.Texts + , form : Messages.Comp.BookmarkQueryForm.Texts + , httpError : Http.Error -> String + , formInvalid : String + , saved : String + } + + +gb : Texts +gb = + { basics = Messages.Basics.gb + , form = Messages.Comp.BookmarkQueryForm.gb + , httpError = Messages.Comp.HttpError.gb + , formInvalid = "Please correct errors in the form" + , saved = "Bookmark saved" + } + + +de : Texts +de = + { basics = Messages.Basics.de + , form = Messages.Comp.BookmarkQueryForm.de + , httpError = Messages.Comp.HttpError.de + , formInvalid = "Bitte korrigiere das Formular" + , saved = "Bookmark gespeichert" + } diff --git a/modules/webapp/src/main/elm/Messages/Page/Home.elm b/modules/webapp/src/main/elm/Messages/Page/Home.elm index 3269cad8..38b5386f 100644 --- a/modules/webapp/src/main/elm/Messages/Page/Home.elm +++ b/modules/webapp/src/main/elm/Messages/Page/Home.elm @@ -12,6 +12,7 @@ module Messages.Page.Home exposing ) import Messages.Basics +import Messages.Comp.BookmarkQueryManage import Messages.Comp.ItemCardList import Messages.Comp.ItemMerge import Messages.Comp.PublishItems @@ -26,6 +27,7 @@ type alias Texts = , sideMenu : Messages.Page.HomeSideMenu.Texts , itemMerge : Messages.Comp.ItemMerge.Texts , publishItems : Messages.Comp.PublishItems.Texts + , bookmarkManage : Messages.Comp.BookmarkQueryManage.Texts , contentSearch : String , searchInNames : String , selectModeTitle : String @@ -46,6 +48,7 @@ type alias Texts = , mergeItemsTitle : Int -> String , publishItemsTitle : Int -> String , publishCurrentQueryTitle : String + , shareResults : String , nothingSelectedToShare : String , loadMore : String , thatsAll : String @@ -53,6 +56,8 @@ type alias Texts = , listView : String , tileView : String , expandCollapseRows : String + , bookmarkQuery : String + , nothingToBookmark : String } @@ -64,6 +69,7 @@ gb = , sideMenu = Messages.Page.HomeSideMenu.gb , itemMerge = Messages.Comp.ItemMerge.gb , publishItems = Messages.Comp.PublishItems.gb + , bookmarkManage = Messages.Comp.BookmarkQueryManage.gb , contentSearch = "Content search…" , searchInNames = "Search in names…" , selectModeTitle = "Select Mode" @@ -84,6 +90,7 @@ gb = , mergeItemsTitle = \n -> "Merge " ++ String.fromInt n ++ " selected items" , publishItemsTitle = \n -> "Publish " ++ String.fromInt n ++ " selected items" , publishCurrentQueryTitle = "Publish current results" + , shareResults = "Share Results" , nothingSelectedToShare = "Sharing everything doesn't work. You need to apply some criteria." , loadMore = "Load more…" , thatsAll = "That's all" @@ -91,6 +98,8 @@ gb = , listView = "List view" , tileView = "Tile view" , expandCollapseRows = "Expand/Collapse all" + , bookmarkQuery = "Bookmark query" + , nothingToBookmark = "Nothing selected to bookmark" } @@ -102,6 +111,7 @@ de = , sideMenu = Messages.Page.HomeSideMenu.de , itemMerge = Messages.Comp.ItemMerge.de , publishItems = Messages.Comp.PublishItems.de + , bookmarkManage = Messages.Comp.BookmarkQueryManage.de , contentSearch = "Volltextsuche…" , searchInNames = "Suche in Namen…" , selectModeTitle = "Auswahlmodus" @@ -122,6 +132,7 @@ de = , mergeItemsTitle = \n -> String.fromInt n ++ " gewählte Dokumente zusammenführen" , publishItemsTitle = \n -> String.fromInt n ++ " gewählte Dokumente publizieren" , publishCurrentQueryTitle = "Aktuelle Ansicht publizieren" + , shareResults = "Ergebnisse teilen" , nothingSelectedToShare = "Alles kann nicht geteilt werden; es muss etwas gesucht werden." , loadMore = "Mehr laden…" , thatsAll = "Mehr gibt es nicht" @@ -129,4 +140,6 @@ de = , listView = "Listenansicht" , tileView = "Kachelansicht" , expandCollapseRows = "Alle ein-/ausklappen" + , bookmarkQuery = "Abfrage merken" + , nothingToBookmark = "Keine Abfrage vorhanden" } diff --git a/modules/webapp/src/main/elm/Page/Home/Data.elm b/modules/webapp/src/main/elm/Page/Home/Data.elm index 17fddc42..b55d31aa 100644 --- a/modules/webapp/src/main/elm/Page/Home/Data.elm +++ b/modules/webapp/src/main/elm/Page/Home/Data.elm @@ -13,6 +13,7 @@ module Page.Home.Data exposing , SearchType(..) , SelectActionMode(..) , SelectViewModel + , TopWidgetModel(..) , ViewMode(..) , createQuery , doSearchCmd @@ -30,6 +31,7 @@ import Api.Model.BasicResult exposing (BasicResult) import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.SearchStats exposing (SearchStats) import Browser.Dom as Dom +import Comp.BookmarkQueryManage import Comp.ItemCardList import Comp.ItemDetail.FormChange exposing (FormChange) import Comp.ItemDetail.MultiEditMenu exposing (SaveNameState(..)) @@ -68,9 +70,15 @@ type alias Model = , powerSearchInput : Comp.PowerSearchInput.Model , viewMenuOpen : Bool , itemRowsOpen : Set String + , topWidgetModel : TopWidgetModel } +type TopWidgetModel + = TopWidgetHidden + | BookmarkQuery Comp.BookmarkQueryManage.Model + + type ConfirmModalValue = ConfirmReprocessItems | ConfirmDelete @@ -137,6 +145,7 @@ init flags viewMode = , powerSearchInput = Comp.PowerSearchInput.init , viewMenuOpen = False , itemRowsOpen = Set.empty + , topWidgetModel = TopWidgetHidden } @@ -238,6 +247,8 @@ type Msg | ToggleShowGroups | ToggleArrange ItemArrange | ToggleExpandCollapseRows + | ToggleBookmarkCurrentQueryView + | BookmarkQueryMsg Comp.BookmarkQueryManage.Msg type SearchType diff --git a/modules/webapp/src/main/elm/Page/Home/Update.elm b/modules/webapp/src/main/elm/Page/Home/Update.elm index 960ad4d9..e06ec59d 100644 --- a/modules/webapp/src/main/elm/Page/Home/Update.elm +++ b/modules/webapp/src/main/elm/Page/Home/Update.elm @@ -13,6 +13,7 @@ module Page.Home.Update exposing import Api import Api.Model.ItemLightList exposing (ItemLightList) import Browser.Navigation as Nav +import Comp.BookmarkQueryManage import Comp.ItemCardList import Comp.ItemDetail.FormChange exposing (FormChange(..)) import Comp.ItemDetail.MultiEditMenu exposing (SaveNameState(..)) @@ -927,6 +928,54 @@ update mId key flags texts settings msg model = Nothing -> noSub ( model, Cmd.none ) + ToggleBookmarkCurrentQueryView -> + case createQuery model of + Just q -> + case model.topWidgetModel of + BookmarkQuery _ -> + noSub ( { model | topWidgetModel = TopWidgetHidden, viewMenuOpen = False }, Cmd.none ) + + TopWidgetHidden -> + let + ( qm, qc ) = + Comp.BookmarkQueryManage.init (Q.render q) + in + noSub + ( { model | topWidgetModel = BookmarkQuery qm, viewMenuOpen = False } + , Cmd.map BookmarkQueryMsg qc + ) + + Nothing -> + noSub ( model, Cmd.none ) + + BookmarkQueryMsg lm -> + case model.topWidgetModel of + BookmarkQuery bm -> + let + res = + Comp.BookmarkQueryManage.update flags lm bm + + nextModel = + if + res.outcome + == Comp.BookmarkQueryManage.Cancelled + || res.outcome + == Comp.BookmarkQueryManage.Done + then + TopWidgetHidden + + else + BookmarkQuery res.model + in + makeResult + ( { model | topWidgetModel = nextModel } + , Cmd.map BookmarkQueryMsg res.cmd + , Sub.map BookmarkQueryMsg res.sub + ) + + TopWidgetHidden -> + noSub ( model, Cmd.none ) + PublishViewMsg lmsg -> case model.viewMode of PublishView inPM -> diff --git a/modules/webapp/src/main/elm/Page/Home/View2.elm b/modules/webapp/src/main/elm/Page/Home/View2.elm index 550bc112..e75788b5 100644 --- a/modules/webapp/src/main/elm/Page/Home/View2.elm +++ b/modules/webapp/src/main/elm/Page/Home/View2.elm @@ -9,6 +9,7 @@ module Page.Home.View2 exposing (viewContent, viewSidebar) import Api import Comp.Basic as B +import Comp.BookmarkQueryManage import Comp.ConfirmModal import Comp.ItemCardList import Comp.ItemMerge @@ -103,7 +104,21 @@ mainView texts flags settings model = body Nothing -> - itemCardList texts flags settings model + bookmarkQueryWidget texts settings flags model + ++ itemCardList texts flags settings model + + +bookmarkQueryWidget : Texts -> UiSettings -> Flags -> Model -> List (Html Msg) +bookmarkQueryWidget texts settings flags model = + case model.topWidgetModel of + BookmarkQuery m -> + [ div [ class "px-2 mb-4 border-l border-r border-b dark:border-slate-600" ] + [ Html.map BookmarkQueryMsg (Comp.BookmarkQueryManage.view texts.bookmarkManage m) + ] + ] + + TopWidgetHidden -> + [] itemPublishView : Texts -> UiSettings -> Flags -> SelectViewModel -> List (Html Msg) @@ -354,7 +369,7 @@ defaultMenuBar texts flags settings model = ] } , menuSep - , { label = "Share Results" + , { label = texts.shareResults , icon = Icons.shareIcon "" , disabled = createQuery model == Nothing , attrs = @@ -372,6 +387,24 @@ defaultMenuBar texts flags settings model = onClick TogglePublishCurrentQueryView ] } + , { label = texts.bookmarkQuery + , icon = i [ class "fa fa-bookmark" ] [] + , disabled = createQuery model == Nothing + , attrs = + [ title <| + if createQuery model == Nothing then + texts.nothingToBookmark + + else + texts.bookmarkQuery + , href "#" + , if createQuery model == Nothing then + class "" + + else + onClick ToggleBookmarkCurrentQueryView + ] + } , { label = if settings.cardPreviewFullWidth then texts.fullHeightPreviewTitle