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 4d2d71a2..560e4db4 100644 --- a/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala +++ b/modules/backend/src/main/scala/docspell/backend/ops/OClientSettings.scala @@ -6,6 +6,7 @@ package docspell.backend.ops +import cats.Semigroup import cats.data.OptionT import cats.effect.{Async, Resource} import cats.implicits._ @@ -32,6 +33,7 @@ trait OClientSettings[F[_]] { account: AccountId ): F[Option[RClientSettingsCollective]] + def loadMerged(clientId: Ident, account: AccountId): F[Option[Json]] } object OClientSettings { @@ -108,5 +110,14 @@ object OClientSettings { data <- OptionT(store.transact(RClientSettingsUser.find(clientId, userId))) } yield data).value + def loadMerged(clientId: Ident, account: AccountId) = + for { + collData <- loadCollective(clientId, account) + userData <- loadUser(clientId, account) + mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData) + } yield mergedData + + implicit def jsonSemigroup: Semigroup[Json] = + Semigroup.instance((a1, a2) => a1.deepMerge(a2)) }) } diff --git a/modules/restapi/src/main/resources/docspell-openapi.yml b/modules/restapi/src/main/resources/docspell-openapi.yml index 11deb901..f6dc38ef 100644 --- a/modules/restapi/src/main/resources/docspell-openapi.yml +++ b/modules/restapi/src/main/resources/docspell-openapi.yml @@ -2356,13 +2356,7 @@ paths: summary: Return the client settings of current user description: | Returns the settings for the share. This is the settings of - the user who created the share. It is created by merging the - client settings for the collective and the user's own client - settings into one json structure. - - Null, Array, Boolean, String and Number are treated as values, - and values from the users settings completely replace values - from the collective's settings. + the collective that belongs to the share. The `clientId` is an identifier to a client application. It returns a JSON structure. The server doesn't care about the 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 25f8a4df..96dad4b4 100644 --- a/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala +++ b/modules/restserver/src/main/scala/docspell/restserver/routes/ClientSettingsRoutes.scala @@ -6,10 +6,9 @@ package docspell.restserver.routes +import cats.data.OptionT import cats.effect._ import cats.implicits._ -import cats.{Monad, Semigroup} -import cats.data.OptionT import docspell.backend.BackendApp import docspell.backend.auth.{AuthToken, ShareToken} @@ -33,17 +32,15 @@ object ClientSettingsRoutes { val dsl = new Http4sDsl[F] {} import dsl._ - HttpRoutes.of { - case GET -> Root / Ident(clientId) => - (for { - _ <- OptionT.liftF(logger.debug(s"Get client settings for share ${token.id}")) - share <- backend.share.findActiveById(token.id) - merged <- OptionT.liftF(getMergedSettings(backend, share.user.accountId, clientId)) - res <- OptionT.liftF(merged match { - case Some(j) => Ok(j) - case None => NotFound() - }) - } yield res) + HttpRoutes.of { case GET -> Root / Ident(clientId) => + (for { + _ <- OptionT.liftF(logger.debug(s"Get client settings for share ${token.id}")) + share <- backend.share.findActiveById(token.id) + sett <- OptionT( + backend.clientSettings.loadCollective(clientId, share.user.accountId) + ) + res <- OptionT.liftF(Ok(sett.settingsData)) + } yield res) .getOrElseF(Ok(Map.empty[String, String])) } } @@ -55,11 +52,10 @@ object ClientSettingsRoutes { HttpRoutes.of { case GET -> Root / Ident(clientId) => for { - mergedData <- getMergedSettings(backend, user.account, clientId) - + mergedData <- backend.clientSettings.loadMerged(clientId, user.account) res <- mergedData match { case Some(j) => Ok(j) - case None => NotFound() + case None => Ok(Map.empty[String, String]) } } yield res @@ -118,17 +114,4 @@ object ClientSettingsRoutes { } yield res } } - - - def getMergedSettings[F[_]: Monad](backend:BackendApp[F], account: AccountId, clientId: Ident) = - for { - collData <- backend.clientSettings.loadCollective(clientId, account) - userData <- backend.clientSettings.loadUser(clientId, account) - - mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData) - } yield mergedData - - - implicit def jsonSemigroup: Semigroup[Json] = - Semigroup.instance((a1, a2) => a1.deepMerge(a2)) } diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm index 16eb1851..cdae2f20 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm @@ -580,7 +580,6 @@ update flags sett msg model = TagsTab -> { sett | tagCategoryColors = Nothing } - -- no reset here FieldsTab -> { sett | formFields = Nothing } @@ -658,7 +657,7 @@ settingFormTabs texts flags _ model = , onClick (ResetTab tab) ] [ i [ class "fa fa-eraser mr-1" ] [] - , text "Reset" + , text texts.resetLabel ] in [ { name = akkordionTabName GeneralTab diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm index 9bd02222..2f749f77 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm @@ -28,7 +28,9 @@ import Html.Attributes exposing (..) import Http import Messages.Comp.UiSettingsManage exposing (Texts) import Page.Search.Data exposing (Msg(..)) +import Process import Styles as S +import Task type alias Model = @@ -67,6 +69,7 @@ type Msg | ReceiveServerSettings (Result Http.Error ( StoredUiSettings, StoredUiSettings )) | ToggleExpandCollapse | SwitchForm AccountScope + | ResetFormState init : Flags -> ( Model, Cmd Msg ) @@ -134,12 +137,6 @@ update flags settings msg model = | collSettings = Maybe.withDefault data.collSettings sett , collModel = m_ } - , formResult = - if sett /= Nothing then - FormInit - - else - model.formResult } Data.AccountScope.User -> @@ -155,12 +152,6 @@ update flags settings msg model = | userSettings = Maybe.withDefault data.userSettings sett , userModel = m_ } - , formResult = - if sett /= Nothing then - FormInit - - else - model.formResult } Submit -> @@ -198,9 +189,13 @@ update flags settings msg model = update flags settings (ReceiveServerSettings (Ok ( data.collSettings, data.userSettings ))) - model + { model | formResult = FormSaved } + + cmd = + Process.sleep 2000 + |> Task.perform (\_ -> ResetFormState) in - { result | appEvent = AppReloadUiSettings } + { result | appEvent = AppReloadUiSettings, cmd = Cmd.batch [ cmd, result.cmd ] } _ -> unit { model | formResult = FormUnknownError } @@ -231,7 +226,13 @@ update flags settings msg model = , collSettings = coll , collModel = cm } - , formModel = ViewUser + , formModel = + case model.formModel of + ViewLoading -> + ViewUser + + _ -> + model.formModel } cmds = @@ -262,6 +263,14 @@ update flags settings msg model = in Data.AccountScope.fold forUser forColl scope + ResetFormState -> + case model.formResult of + FormSaved -> + unit { model | formResult = FormInit } + + _ -> + unit model + isError : Model -> Bool isError model = @@ -364,6 +373,9 @@ view2 texts flags _ classes model = [ h2 [ class S.header2 ] [ text texts.collectiveHeader ] + , div [ class "py-1 opacity-80" ] + [ text texts.collectiveInfo + ] , Html.map (UiFormMsg scope) (Comp.UiSettingsForm.view2 texts.uiSettingsForm diff --git a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsForm.elm b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsForm.elm index a09151a3..771bab51 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsForm.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsForm.elm @@ -56,6 +56,7 @@ type alias Texts = , fieldLabel : Field -> String , templateHelpMessage : String , pdfMode : PdfMode -> String + , resetLabel : String } @@ -131,6 +132,7 @@ and if that is not present the person. If both are absent a dash `-` is rendered. """ , pdfMode = Messages.Data.PdfMode.gb + , resetLabel = "Reset" } @@ -208,4 +210,5 @@ oder, wenn diese leer ist, die Person. Sind beide leer wird ein `-` dargestellt. """ , pdfMode = Messages.Data.PdfMode.de + , resetLabel = "Zurücksetzen" } diff --git a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm index 409b0c4b..dd7d56f2 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm @@ -30,6 +30,7 @@ type alias Texts = , userHeader : String , userInfo : String , collectiveHeader : String + , collectiveInfo : String , expandCollapse : String } @@ -45,8 +46,9 @@ gb = , unknownSaveError = "Unknown error while trying to save settings." , httpError = Messages.Comp.HttpError.gb , userHeader = "Personal settings" - , userInfo = "Your personal settings override those of the collective. On reset, settings are set back to those of the collective." + , userInfo = "Your personal settings override those of the collective. On reset, settings are taken from the collective settings." , collectiveHeader = "Collective settings" + , collectiveInfo = "These settings apply to all users, unless overriden by personal ones. A reset loads the provided default values of the application." , expandCollapse = "Expand/collapse all" } @@ -62,7 +64,8 @@ de = , unknownSaveError = "Unbekannter Fehler beim Speichern der Einstellungen." , httpError = Messages.Comp.HttpError.de , userHeader = "Persönliche Einstellungen" - , userInfo = "Die persönlichen Einstellungen überschreiben die des Kollektivs. Wenn Einstellungen zurückgesetzt werden, werden sie auf die Werte des Kollektivs gesetzt." + , userInfo = "Die persönlichen Einstellungen überschreiben die des Kollektivs. Wenn Einstellungen zurückgesetzt werden, gelten automatisch die Werte des Kollektivs." , collectiveHeader = "Kollektiv Einstellungen" + , collectiveInfo = "Diese Einstellungen sind für alle Benutzer, können aber in den persönlichen Einstellungen überschrieben werden. Durch ein Zurücksetzen erhält man die bereitgestellten Standardwerte der Anwendung." , expandCollapse = "Alle ein-/ausklappen" }