Merge pull request #1327 from eikek/collective-settings

Collective settings
This commit is contained in:
mergify[bot]
2022-01-29 16:50:55 +00:00
committed by GitHub
35 changed files with 974 additions and 579 deletions

View File

@ -6,6 +6,7 @@
package docspell.backend.ops package docspell.backend.ops
import cats.Semigroup
import cats.data.OptionT import cats.data.OptionT
import cats.effect.{Async, Resource} import cats.effect.{Async, Resource}
import cats.implicits._ import cats.implicits._
@ -32,6 +33,7 @@ trait OClientSettings[F[_]] {
account: AccountId account: AccountId
): F[Option[RClientSettingsCollective]] ): F[Option[RClientSettingsCollective]]
def loadMerged(clientId: Ident, account: AccountId): F[Option[Json]]
} }
object OClientSettings { object OClientSettings {
@ -108,5 +110,14 @@ object OClientSettings {
data <- OptionT(store.transact(RClientSettingsUser.find(clientId, userId))) data <- OptionT(store.transact(RClientSettingsUser.find(clientId, userId)))
} yield data).value } 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))
}) })
} }

View File

@ -48,6 +48,8 @@ trait OShare[F[_]] {
// --- // ---
def findActiveById(id: Ident): OptionT[F, ShareData]
/** Verifies the given id and password and returns a authorization token on success. */ /** Verifies the given id and password and returns a authorization token on success. */
def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult] def verify(key: ByteVector)(id: Ident, password: Option[Password]): F[VerifyResult]
@ -277,6 +279,9 @@ object OShare {
VerifyResult.invalidToken.pure[F] VerifyResult.invalidToken.pure[F]
} }
def findActiveById(id: Ident): OptionT[F, ShareData] =
RShare.findCurrentActive(id).mapK(store.transform).map(ShareData.tupled)
def findShareQuery(id: Ident): OptionT[F, ShareQuery] = def findShareQuery(id: Ident): OptionT[F, ShareQuery] =
RShare RShare
.findCurrentActive(id) .findCurrentActive(id)

View File

@ -2304,7 +2304,7 @@ paths:
/share/attachment/{id}/preview: /share/attachment/{id}/preview:
head: head:
operationId: "share-attach-check-preview" operationId: "share-attach-check-preview"
tags: [ Attachment ] tags: [ Share ]
summary: Get the headers to a preview image of an attachment file. summary: Get the headers to a preview image of an attachment file.
description: | description: |
Checks if an image file showing a preview of the attachment is Checks if an image file showing a preview of the attachment is
@ -2320,7 +2320,7 @@ paths:
description: NotFound description: NotFound
get: get:
operationId: "share-attach-get-preview" operationId: "share-attach-get-preview"
tags: [ Attachment ] tags: [ Share ]
summary: Get a preview image of an attachment file. summary: Get a preview image of an attachment file.
description: | description: |
Gets a image file showing a preview of the attachment. Usually Gets a image file showing a preview of the attachment. Usually
@ -2347,6 +2347,31 @@ paths:
schema: schema:
type: string type: string
format: binary format: binary
/share/clientSettings/{clientId}:
parameters:
- $ref: "#/components/parameters/clientId"
get:
operationId: "share-clientsettings-get"
tags: [ Share ]
summary: Return the client settings of current user
description: |
Returns the settings for the share. This is the settings of
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
actual data, since it is meant to be interpreted by clients.
security:
- shareTokenHeader: []
responses:
422:
description: BadRequest
200:
description: Ok
content:
application/json:
schema: {}
/admin/user/resetPassword: /admin/user/resetPassword:
post: post:

View File

@ -213,7 +213,8 @@ object RestServer {
Router( Router(
"search" -> ShareSearchRoutes(restApp.backend, cfg, token), "search" -> ShareSearchRoutes(restApp.backend, cfg, token),
"attachment" -> ShareAttachmentRoutes(restApp.backend, token), "attachment" -> ShareAttachmentRoutes(restApp.backend, token),
"item" -> ShareItemRoutes(restApp.backend, token) "item" -> ShareItemRoutes(restApp.backend, token),
"clientSettings" -> ClientSettingsRoutes.share(restApp.backend, token)
) )
def redirectTo[F[_]: Async](path: String): HttpRoutes[F] = { def redirectTo[F[_]: Async](path: String): HttpRoutes[F] = {

View File

@ -6,12 +6,12 @@
package docspell.restserver.routes package docspell.restserver.routes
import cats.data.OptionT
import cats.effect._ import cats.effect._
import cats.implicits._ import cats.implicits._
import cats.kernel.Semigroup
import docspell.backend.BackendApp import docspell.backend.BackendApp
import docspell.backend.auth.AuthToken import docspell.backend.auth.{AuthToken, ShareToken}
import docspell.common._ import docspell.common._
import docspell.restapi.model._ import docspell.restapi.model._
@ -23,6 +23,28 @@ import org.http4s.dsl.Http4sDsl
object ClientSettingsRoutes { object ClientSettingsRoutes {
def share[F[_]: Async](
backend: BackendApp[F],
token: ShareToken
): HttpRoutes[F] = {
val logger = Logger.log4s[F](org.log4s.getLogger)
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)
sett <- OptionT(
backend.clientSettings.loadCollective(clientId, share.user.accountId)
)
res <- OptionT.liftF(Ok(sett.settingsData))
} yield res)
.getOrElseF(Ok(Map.empty[String, String]))
}
}
def apply[F[_]: Async](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = { def apply[F[_]: Async](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
val dsl = new Http4sDsl[F] {} val dsl = new Http4sDsl[F] {}
import dsl._ import dsl._
@ -30,14 +52,10 @@ object ClientSettingsRoutes {
HttpRoutes.of { HttpRoutes.of {
case GET -> Root / Ident(clientId) => case GET -> Root / Ident(clientId) =>
for { for {
collData <- backend.clientSettings.loadCollective(clientId, user.account) mergedData <- backend.clientSettings.loadMerged(clientId, user.account)
userData <- backend.clientSettings.loadUser(clientId, user.account)
mergedData = collData.map(_.settingsData) |+| userData.map(_.settingsData)
res <- mergedData match { res <- mergedData match {
case Some(j) => Ok(j) case Some(j) => Ok(j)
case None => NotFound() case None => Ok(Map.empty[String, String])
} }
} yield res } yield res
@ -96,7 +114,4 @@ object ClientSettingsRoutes {
} yield res } yield res
} }
} }
implicit def jsonSemigroup: Semigroup[Json] =
Semigroup.instance((a1, a2) => a1.deepMerge(a2))
} }

View File

@ -22,6 +22,7 @@ module Api exposing
, changeFolderName , changeFolderName
, changePassword , changePassword
, checkCalEvent , checkCalEvent
, clientSettingsShare
, confirmMultiple , confirmMultiple
, confirmOtp , confirmOtp
, createChannel , createChannel
@ -64,6 +65,7 @@ module Api exposing
, getChannels , getChannels
, getChannelsIgnoreError , getChannelsIgnoreError
, getClientSettings , getClientSettings
, getClientSettingsRaw
, getCollective , getCollective
, getCollectiveSettings , getCollectiveSettings
, getContacts , getContacts
@ -136,6 +138,7 @@ module Api exposing
, restoreItem , restoreItem
, sampleEvent , sampleEvent
, saveClientSettings , saveClientSettings
, saveUserClientSettingsBy
, searchShare , searchShare
, searchShareStats , searchShareStats
, sendMail , sendMail
@ -296,7 +299,7 @@ import Data.OrganizationOrder exposing (OrganizationOrder)
import Data.PersonOrder exposing (PersonOrder) import Data.PersonOrder exposing (PersonOrder)
import Data.Priority exposing (Priority) import Data.Priority exposing (Priority)
import Data.TagOrder exposing (TagOrder) import Data.TagOrder exposing (TagOrder)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (StoredUiSettings, UiSettings)
import File exposing (File) import File exposing (File)
import Http import Http
import Json.Decode as JsonDecode import Json.Decode as JsonDecode
@ -2335,6 +2338,13 @@ getItemProposals flags item receive =
--- Client Settings --- Client Settings
uiSettingsPath : AccountScope -> String
uiSettingsPath scope =
Data.AccountScope.fold "/api/v1/sec/clientSettings/user/webClient"
"/api/v1/sec/clientSettings/collective/webClient"
scope
getClientSettings : Flags -> (Result Http.Error UiSettings -> msg) -> Cmd msg getClientSettings : Flags -> (Result Http.Error UiSettings -> msg) -> Cmd msg
getClientSettings flags receive = getClientSettings flags receive =
let let
@ -2346,29 +2356,91 @@ getClientSettings flags receive =
Data.UiSettings.storedUiSettingsDecoder Data.UiSettings.storedUiSettingsDecoder
in in
Http2.authGet Http2.authGet
{ url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClient" { url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/webClient"
, account = getAccount flags , account = getAccount flags
, expect = Http.expectJson receive decoder , expect = Http.expectJson receive decoder
} }
saveClientSettings : Flags -> UiSettings -> (Result Http.Error BasicResult -> msg) -> Cmd msg getClientSettingsTaskFor : Flags -> AccountScope -> Task.Task Http.Error StoredUiSettings
saveClientSettings flags settings receive = getClientSettingsTaskFor flags scope =
let let
storedSettings = path =
Data.UiSettings.toStoredUiSettings settings uiSettingsPath scope
encode =
Data.UiSettings.storedUiSettingsEncode storedSettings
in in
Http2.authPut Http2.authTask
{ url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClient" { method = "GET"
, url = flags.config.baseUrl ++ path
, account = getAccount flags , account = getAccount flags
, body = Http.jsonBody encode , body = Http.emptyBody
, expect = Http.expectJson receive Api.Model.BasicResult.decoder , resolver = Http2.jsonResolver Data.UiSettings.storedUiSettingsDecoder
, headers = []
, timeout = Nothing
} }
getClientSettingsRaw : Flags -> (Result Http.Error ( StoredUiSettings, StoredUiSettings ) -> msg) -> Cmd msg
getClientSettingsRaw flags receive =
let
coll =
getClientSettingsTaskFor flags Data.AccountScope.Collective
user =
getClientSettingsTaskFor flags Data.AccountScope.User
in
Task.map2 Tuple.pair coll user |> Task.attempt receive
saveClientSettingsTask :
Flags
-> StoredUiSettings
-> AccountScope
-> Task.Task Http.Error BasicResult
saveClientSettingsTask flags settings scope =
let
encoded =
Data.UiSettings.storedUiSettingsEncode settings
path =
uiSettingsPath scope
in
Http2.authTask
{ method = "PUT"
, url = flags.config.baseUrl ++ path
, account = getAccount flags
, body = Http.jsonBody encoded
, resolver = Http2.jsonResolver Api.Model.BasicResult.decoder
, headers = []
, timeout = Nothing
}
saveClientSettings :
Flags
-> StoredUiSettings
-> AccountScope
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
saveClientSettings flags settings scope receive =
saveClientSettingsTask flags settings scope |> Task.attempt receive
saveUserClientSettingsBy :
Flags
-> (StoredUiSettings -> StoredUiSettings)
-> (Result Http.Error BasicResult -> msg)
-> Cmd msg
saveUserClientSettingsBy flags modify receive =
let
readTask =
getClientSettingsTaskFor flags Data.AccountScope.User
save s =
saveClientSettingsTask flags s Data.AccountScope.User
in
Task.andThen (modify >> save) readTask |> Task.attempt receive
--- Dashboards --- Dashboards
@ -2749,6 +2821,23 @@ itemDetailShare flags token itemId receive =
} }
clientSettingsShare : Flags -> String -> (Result Http.Error UiSettings -> msg) -> Cmd msg
clientSettingsShare flags token receive =
let
defaults =
Data.UiSettings.defaults
decoder =
JsonDecode.map (\s -> Data.UiSettings.merge s defaults)
Data.UiSettings.storedUiSettingsDecoder
in
Http2.shareGet
{ url = flags.config.baseUrl ++ "/api/v1/share/clientSettings/webClient"
, token = token
, expect = Http.expectJson receive decoder
}
shareSendMail : shareSendMail :
Flags Flags
-> { conn : String, mail : SimpleShareMail } -> { conn : String, mail : SimpleShareMail }

View File

@ -18,18 +18,15 @@ import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.VersionInfo exposing (VersionInfo) import Api.Model.VersionInfo exposing (VersionInfo)
import Browser exposing (UrlRequest) import Browser exposing (UrlRequest)
import Browser.Navigation exposing (Key) import Browser.Navigation exposing (Key)
import Data.Dashboard exposing (Dashboard)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.ServerEvent exposing (ServerEvent) import Data.ServerEvent exposing (ServerEvent)
import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Data.UiSettings exposing (UiSettings)
import Data.UiTheme exposing (UiTheme) import Data.UiTheme exposing (UiTheme)
import Http import Http
import Messages
import Messages.UiLanguage exposing (UiLanguage) import Messages.UiLanguage exposing (UiLanguage)
import Page exposing (Page(..)) import Page exposing (Page(..))
import Page.CollectiveSettings.Data import Page.CollectiveSettings.Data
import Page.Dashboard.Data import Page.Dashboard.Data
import Page.Dashboard.DefaultDashboard
import Page.ItemDetail.Data import Page.ItemDetail.Data
import Page.Login.Data import Page.Login.Data
import Page.ManageData.Data import Page.ManageData.Data
@ -107,11 +104,7 @@ init key url flags_ settings =
Page.Dashboard.Data.init flags Page.Dashboard.Data.init flags
searchViewMode = searchViewMode =
if settings.searchMenuVisible then
Page.Search.Data.SearchView Page.Search.Data.SearchView
else
Page.Search.Data.SimpleView
in in
( { flags = flags ( { flags = flags
, key = key , key = key
@ -204,8 +197,7 @@ type Msg
| ToggleDarkMode | ToggleDarkMode
| ToggleLangMenu | ToggleLangMenu
| SetLanguage UiLanguage | SetLanguage UiLanguage
| ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) | ClientSettingsSaveResp (Result Http.Error BasicResult)
| ReceiveBrowserSettings StoredUiSettings
| ReceiveWsMessage (Result String ServerEvent) | ReceiveWsMessage (Result String ServerEvent)
| ToggleShowNewItemsArrived | ToggleShowNewItemsArrived

View File

@ -14,6 +14,7 @@ import Api
import App.Data exposing (..) import App.Data exposing (..)
import Browser exposing (UrlRequest(..)) import Browser exposing (UrlRequest(..))
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Data.AppEvent exposing (AppEvent(..))
import Data.Flags import Data.Flags
import Data.ServerEvent exposing (ServerEvent(..)) import Data.ServerEvent exposing (ServerEvent(..))
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
@ -81,15 +82,15 @@ updateWithSub msg model =
next = next =
Data.UiTheme.cycle settings.uiTheme Data.UiTheme.cycle settings.uiTheme
newSettings = newSettings s =
{ settings | uiTheme = next } { s | uiTheme = Just (Data.UiTheme.toString next) }
in in
-- when authenticated, store it in settings only -- when authenticated, store it in settings only
-- once new settings are successfully saved (the -- once new settings are successfully saved (the
-- response is arrived), the ui is updated. so it -- response is arrived), the ui is updated. so it
-- is also updated on page refresh -- is also updated on page refresh
( { model | userMenuOpen = False } ( { model | userMenuOpen = False }
, Api.saveClientSettings model.flags newSettings (ClientSettingsSaveResp newSettings) , Api.saveUserClientSettingsBy model.flags newSettings ClientSettingsSaveResp
, Sub.none , Sub.none
) )
@ -104,14 +105,14 @@ updateWithSub msg model =
, Sub.none , Sub.none
) )
ClientSettingsSaveResp settings (Ok res) -> ClientSettingsSaveResp (Ok res) ->
if res.success then if res.success then
applyClientSettings texts model settings ( model, Api.getClientSettings model.flags GetUiSettings, Sub.none )
else else
( model, Cmd.none, Sub.none ) ( model, Cmd.none, Sub.none )
ClientSettingsSaveResp _ (Err _) -> ClientSettingsSaveResp (Err _) ->
( model, Cmd.none, Sub.none ) ( model, Cmd.none, Sub.none )
ToggleLangMenu -> ToggleLangMenu ->
@ -307,13 +308,6 @@ updateWithSub msg model =
GetUiSettings (Err _) -> GetUiSettings (Err _) ->
( model, Cmd.none, Sub.none ) ( model, Cmd.none, Sub.none )
ReceiveBrowserSettings sett ->
let
lm =
Page.UserSettings.Data.ReceiveBrowserSettings sett
in
updateUserSettings texts lm model
ReceiveWsMessage data -> ReceiveWsMessage data ->
case data of case data of
Ok (JobDone task) -> Ok (JobDone task) ->
@ -342,7 +336,7 @@ updateWithSub msg model =
Ok (JobsWaiting n) -> Ok (JobsWaiting n) ->
( { model | jobsWaiting = max 0 n }, Cmd.none, Sub.none ) ( { model | jobsWaiting = max 0 n }, Cmd.none, Sub.none )
Err err -> Err _ ->
( model, Cmd.none, Sub.none ) ( model, Cmd.none, Sub.none )
ToggleShowNewItemsArrived -> ToggleShowNewItemsArrived ->
@ -368,7 +362,6 @@ applyClientSettings texts model settings =
, Sub.none , Sub.none
) )
, updateDashboard texts Page.Dashboard.Data.reloadUiSettings , updateDashboard texts Page.Dashboard.Data.reloadUiSettings
, updateUserSettings texts Page.UserSettings.Data.UpdateSettings
, updateSearch texts Page.Search.Data.UiSettingsUpdated , updateSearch texts Page.Search.Data.UiSettingsUpdated
, updateItemDetail texts Page.ItemDetail.Data.UiSettingsUpdated , updateItemDetail texts Page.ItemDetail.Data.UiSettingsUpdated
] ]
@ -524,22 +517,21 @@ updateUserSettings texts lmsg model =
model_ = model_ =
{ model | userSettingsModel = result.model } { model | userSettingsModel = result.model }
( lm2, lc2, s2 ) = lc =
case result.newSettings of case result.appEvent of
Just sett -> AppReloadUiSettings ->
applyClientSettings texts model_ sett Api.getClientSettings model.flags GetUiSettings
Nothing -> AppNothing ->
( model_, Cmd.none, Sub.none ) Cmd.none
in in
( lm2 ( model_
, Cmd.batch , Cmd.batch
[ Cmd.map UserSettingsMsg result.cmd [ Cmd.map UserSettingsMsg result.cmd
, lc2 , lc
] ]
, Sub.batch , Sub.batch
[ Sub.map UserSettingsMsg result.sub [ Sub.map UserSettingsMsg result.sub
, s2
] ]
) )
@ -595,22 +587,21 @@ updateSearch texts lmsg model =
model_ = model_ =
{ model | searchModel = result.model } { model | searchModel = result.model }
( lm, lc, ls ) = lc =
case result.newSettings of case result.appEvent of
Just sett -> AppReloadUiSettings ->
applyClientSettings texts model_ sett Api.getClientSettings model.flags GetUiSettings
Nothing -> AppNothing ->
( model_, Cmd.none, Sub.none ) Cmd.none
in in
( lm ( model_
, Cmd.batch , Cmd.batch
[ Cmd.map SearchMsg result.cmd [ Cmd.map SearchMsg result.cmd
, lc , lc
] ]
, Sub.batch , Sub.batch
[ Sub.map SearchMsg result.sub [ Sub.map SearchMsg result.sub
, ls
] ]
) )

View File

@ -144,7 +144,12 @@ viewItemRow texts settings meta item =
getColumns meta getColumns meta
render col = render col =
Comp.ItemColumnView.renderDiv texts.templateCtx settings col [ class "flex flex-row flex-wrap space-x-1" ] item Comp.ItemColumnView.renderDiv
texts.templateCtx
settings
col
[ class "flex flex-row flex-wrap space-x-1 space-y-1" ]
item
td1 = td1 =
td [ class "py-2 px-1" ] td [ class "py-2 px-1" ]
@ -158,7 +163,7 @@ viewItemRow texts settings meta item =
tdRem index col = tdRem index col =
td td
[ class "py-2 px-1" [ class "py-1 px-1"
, classList [ ( "hidden md:table-cell", index > 1 ) ] , classList [ ( "hidden md:table-cell", index > 1 ) ]
] ]
[ render col [ render col

View File

@ -1411,7 +1411,7 @@ update key flags inav settings msg model =
let let
model_ = model_ =
{ model { model
| menuOpen = settings.editMenuVisible | menuOpen = settings.sideMenuVisible
} }
in in
resultModel model_ resultModel model_

View File

@ -26,6 +26,7 @@ import Styles as S
type Item msg type Item msg
= TextInput (TextInputData msg) = TextInput (TextInputData msg)
| Checkbox (CheckboxData msg) | Checkbox (CheckboxData msg)
| RadioButton (CheckboxData msg)
| PrimaryButton (ButtonData msg) | PrimaryButton (ButtonData msg)
| SecondaryButton (ButtonData msg) | SecondaryButton (ButtonData msg)
| DeleteButton (ButtonData msg) | DeleteButton (ButtonData msg)
@ -119,7 +120,7 @@ view1 classes mb =
(List.map viewItem mb.start) (List.map viewItem mb.start)
right = right =
div [ class "flex-grow flex-row flex justify-end space-x-2 w-full" ] div [ class "flex-grow flex-row items-center flex justify-end space-x-2 w-full" ]
(List.map viewItem mb.end) (List.map viewItem mb.end)
in in
div div
@ -139,7 +140,10 @@ viewItem item =
makeInput model makeInput model
Checkbox model -> Checkbox model ->
makeCheckbox model makeCheckbox False model
RadioButton model ->
makeCheckbox True model
PrimaryButton model -> PrimaryButton model ->
makeButton [ ( S.primaryButton, True ) ] model makeButton [ ( S.primaryButton, True ) ] model
@ -306,8 +310,8 @@ makeButton btnType model =
(icon ++ label) (icon ++ label)
makeCheckbox : CheckboxData msg -> Html msg makeCheckbox : Bool -> CheckboxData msg -> Html msg
makeCheckbox model = makeCheckbox radio model =
let let
withId list = withId list =
if model.id == "" then if model.id == "" then
@ -315,6 +319,13 @@ makeCheckbox model =
else else
id model.id :: list id model.id :: list
fold rd ck =
if radio then
rd
else
ck
in in
div [ class "" ] div [ class "" ]
[ label [ label
@ -323,10 +334,10 @@ makeCheckbox model =
] ]
[ input [ input
(withId (withId
[ type_ "checkbox" [ type_ (fold "radio" "checkbox")
, onCheck model.tagger , onCheck model.tagger
, checked model.value , checked model.value
, class S.checkboxInput , class (fold S.radioInput S.checkboxInput)
] ]
) )
[] []

View File

@ -9,6 +9,8 @@ module Comp.UiSettingsForm exposing
( Model ( Model
, Msg , Msg
, init , init
, initData
, toggleAllTabs
, update , update
, view2 , view2
) )
@ -30,7 +32,7 @@ import Data.Flags exposing (Flags)
import Data.ItemTemplate as IT exposing (ItemTemplate) import Data.ItemTemplate as IT exposing (ItemTemplate)
import Data.Pdf exposing (PdfMode) import Data.Pdf exposing (PdfMode)
import Data.TagOrder import Data.TagOrder
import Data.UiSettings exposing (ItemPattern, Pos(..), UiSettings) import Data.UiSettings exposing (ItemPattern, StoredUiSettings, UiSettings)
import Dict exposing (Dict) import Dict exposing (Dict)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@ -73,6 +75,7 @@ type alias Model =
, uiLangModel : Comp.FixedDropdown.Model UiLanguage , uiLangModel : Comp.FixedDropdown.Model UiLanguage
, uiLang : UiLanguage , uiLang : UiLanguage
, openTabs : Set String , openTabs : Set String
, defaults : UiSettings
} }
@ -111,9 +114,13 @@ updatePatternModel pm str =
} }
init : Flags -> UiSettings -> ( Model, Cmd Msg ) initModel : Flags -> StoredUiSettings -> UiSettings -> Model
init flags settings = initModel flags storedSettings defaults =
( { itemSearchPageSize = Just settings.itemSearchPageSize let
settings =
Data.UiSettings.merge storedSettings defaults
in
{ itemSearchPageSize = Just settings.itemSearchPageSize
, searchPageSizeModel = , searchPageSizeModel =
Comp.IntField.init Comp.IntField.init
(Just 10) (Just 10)
@ -163,9 +170,22 @@ init flags settings =
, uiLangModel = , uiLangModel =
Comp.FixedDropdown.init Messages.UiLanguage.all Comp.FixedDropdown.init Messages.UiLanguage.all
, openTabs = Set.empty , openTabs = Set.empty
, defaults = defaults
} }
, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp
)
init : Flags -> StoredUiSettings -> UiSettings -> ( Model, Cmd Msg )
init flags storedSettings defaults =
( initModel flags storedSettings defaults, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp )
initData : Flags -> StoredUiSettings -> UiSettings -> Model -> ( Model, Cmd Msg )
initData flags storedSettings defaults model =
let
( m, c ) =
init flags storedSettings defaults
in
( { m | openTabs = model.openTabs }, c )
type Msg type Msg
@ -188,14 +208,61 @@ type Msg
| TogglePowerSearch | TogglePowerSearch
| UiLangMsg (Comp.FixedDropdown.Msg UiLanguage) | UiLangMsg (Comp.FixedDropdown.Msg UiLanguage)
| PdfModeMsg (Comp.FixedDropdown.Msg PdfMode) | PdfModeMsg (Comp.FixedDropdown.Msg PdfMode)
| ToggleAllTabs
| ResetTab AkkordionTab
toggleAllTabs : Msg
toggleAllTabs =
ToggleAllTabs
type AkkordionTab
= GeneralTab
| SearchTab
| CardsTab
| SearchMenuTab
| DetailTab
| TagsTab
| FieldsTab
allTabs : List AkkordionTab
allTabs =
[ GeneralTab, SearchTab, CardsTab, SearchMenuTab, DetailTab, TagsTab, FieldsTab ]
akkordionTabName : AkkordionTab -> String
akkordionTabName tab =
case tab of
GeneralTab ->
"general"
SearchTab ->
"search"
CardsTab ->
"item-cards"
SearchMenuTab ->
"search-menu"
DetailTab ->
"item-detail"
TagsTab ->
"tags"
FieldsTab ->
"fields"
--- Update --- Update
update : UiSettings -> Msg -> Model -> ( Model, Maybe UiSettings ) update : Flags -> StoredUiSettings -> Msg -> Model -> ( Model, Maybe StoredUiSettings )
update sett msg model = update flags sett msg model =
case msg of case msg of
SearchPageSizeMsg lm -> SearchPageSizeMsg lm ->
let let
@ -203,7 +270,7 @@ update sett msg model =
Comp.IntField.update lm model.searchPageSizeModel Comp.IntField.update lm model.searchPageSizeModel
nextSettings = nextSettings =
Maybe.map (\sz -> { sett | itemSearchPageSize = sz }) n Maybe.map (\sz -> { sett | itemSearchPageSize = Just sz }) n
model_ = model_ =
{ model { model
@ -219,7 +286,7 @@ update sett msg model =
Comp.IntField.update lm model.searchNoteLengthModel Comp.IntField.update lm model.searchNoteLengthModel
nextSettings = nextSettings =
Maybe.map (\len -> { sett | itemSearchNoteLength = len }) n Maybe.map (\len -> { sett | itemSearchNoteLength = Just len }) n
model_ = model_ =
{ model { model
@ -235,7 +302,7 @@ update sett msg model =
Comp.IntField.update lm model.searchMenuFolderCountModel Comp.IntField.update lm model.searchMenuFolderCountModel
nextSettings = nextSettings =
Maybe.map (\len -> { sett | searchMenuFolderCount = len }) n Maybe.map (\len -> { sett | searchMenuFolderCount = Just len }) n
model_ = model_ =
{ model { model
@ -251,7 +318,7 @@ update sett msg model =
Comp.IntField.update lm model.searchMenuTagCountModel Comp.IntField.update lm model.searchMenuTagCountModel
nextSettings = nextSettings =
Maybe.map (\len -> { sett | searchMenuTagCount = len }) n Maybe.map (\len -> { sett | searchMenuTagCount = Just len }) n
model_ = model_ =
{ model { model
@ -267,7 +334,7 @@ update sett msg model =
Comp.IntField.update lm model.searchMenuTagCatCountModel Comp.IntField.update lm model.searchMenuTagCatCountModel
nextSettings = nextSettings =
Maybe.map (\len -> { sett | searchMenuTagCatCount = len }) n Maybe.map (\len -> { sett | searchMenuTagCatCount = Just len }) n
model_ = model_ =
{ model { model
@ -282,8 +349,13 @@ update sett msg model =
( m_, d_ ) = ( m_, d_ ) =
Comp.ColorTagger.update lm model.tagColorModel Comp.ColorTagger.update lm model.tagColorModel
colors dict =
Dict.map (\_ -> Data.Color.toString) dict
|> Dict.toList
|> Just
nextSettings = nextSettings =
Maybe.map (\tc -> { sett | tagCategoryColors = tc }) d_ Maybe.map (\tc -> { sett | tagCategoryColors = colors tc }) d_
model_ = model_ =
{ model { model
@ -316,7 +388,11 @@ update sett msg model =
Comp.FieldListSelect.update lm model.formFields Comp.FieldListSelect.update lm model.formFields
newSettings = newSettings =
{ sett | formFields = selected } { sett
| formFields =
List.map Data.Fields.toString selected
|> Just
}
in in
( { model | formFields = selected } ( { model | formFields = selected }
, if selected /= model.formFields then , if selected /= model.formFields then
@ -332,7 +408,7 @@ update sett msg model =
not model.itemDetailShortcuts not model.itemDetailShortcuts
in in
( { model | itemDetailShortcuts = flag } ( { model | itemDetailShortcuts = flag }
, Just { sett | itemDetailShortcuts = flag } , Just { sett | itemDetailShortcuts = Just flag }
) )
CardPreviewSizeMsg lm -> CardPreviewSizeMsg lm ->
@ -343,7 +419,13 @@ update sett msg model =
newSettings = newSettings =
if next /= model.cardPreviewSize then if next /= model.cardPreviewSize then
Just { sett | cardPreviewSize = next } Just
{ sett
| cardPreviewSize =
next
|> Data.BasicSize.asString
|> Just
}
else else
Nothing Nothing
@ -361,14 +443,8 @@ update sett msg model =
updatePatternModel pm str updatePatternModel pm str
newSettings = newSettings =
if pm_.pattern /= Just sett.cardTitleTemplate.pattern then if pm_.pattern /= sett.cardTitleTemplate then
Just Just { sett | cardTitleTemplate = pm_.pattern }
{ sett
| cardTitleTemplate =
ItemPattern
(Maybe.withDefault "" pm_.pattern)
pm_.current
}
else else
Nothing Nothing
@ -384,14 +460,8 @@ update sett msg model =
updatePatternModel pm str updatePatternModel pm str
newSettings = newSettings =
if pm_.pattern /= Just sett.cardSubtitleTemplate.pattern then if pm_.pattern /= sett.cardSubtitleTemplate then
Just Just { sett | cardSubtitleTemplate = pm_.pattern }
{ sett
| cardSubtitleTemplate =
ItemPattern
(Maybe.withDefault "" pm_.pattern)
pm_.current
}
else else
Nothing Nothing
@ -407,9 +477,21 @@ update sett msg model =
not model.searchStatsVisible not model.searchStatsVisible
in in
( { model | searchStatsVisible = flag } ( { model | searchStatsVisible = flag }
, Just { sett | searchStatsVisible = flag } , Just { sett | searchStatsVisible = Just flag }
) )
ToggleAllTabs ->
let
tabs =
if Set.isEmpty model.openTabs then
List.map akkordionTabName allTabs
|> Set.fromList
else
Set.empty
in
( { model | openTabs = tabs }, Nothing )
ToggleAkkordionTab name -> ToggleAkkordionTab name ->
let let
tabs = tabs =
@ -429,7 +511,7 @@ update sett msg model =
not model.sideMenuVisible not model.sideMenuVisible
in in
( { model | sideMenuVisible = next } ( { model | sideMenuVisible = next }
, Just { sett | sideMenuVisible = next } , Just { sett | sideMenuVisible = Just next }
) )
TogglePowerSearch -> TogglePowerSearch ->
@ -438,7 +520,7 @@ update sett msg model =
not model.powerSearchEnabled not model.powerSearchEnabled
in in
( { model | powerSearchEnabled = next } ( { model | powerSearchEnabled = next }
, Just { sett | powerSearchEnabled = next } , Just { sett | powerSearchEnabled = Just next }
) )
UiLangMsg lm -> UiLangMsg lm ->
@ -454,7 +536,7 @@ update sett msg model =
Nothing Nothing
else else
Just { sett | uiLang = newLang } Just { sett | uiLang = Just (Messages.toIso2 newLang) }
) )
PdfModeMsg lm -> PdfModeMsg lm ->
@ -470,9 +552,52 @@ update sett msg model =
Nothing Nothing
else else
Just { sett | pdfMode = newMode } Just { sett | pdfMode = Just (Data.Pdf.asString newMode) }
) )
ResetTab tab ->
let
newSettings =
case tab of
GeneralTab ->
{ sett | uiLang = Nothing, sideMenuVisible = Nothing }
SearchTab ->
{ sett
| itemSearchPageSize = Nothing
, searchStatsVisible = Nothing
, powerSearchEnabled = Nothing
}
CardsTab ->
{ sett
| itemSearchNoteLength = Nothing
, cardPreviewSize = Nothing
, cardTitleTemplate = Nothing
, cardSubtitleTemplate = Nothing
}
SearchMenuTab ->
{ sett
| searchMenuTagCount = Nothing
, searchMenuTagCatCount = Nothing
, searchMenuFolderCount = Nothing
}
DetailTab ->
{ sett | pdfMode = Nothing, itemDetailShortcuts = Nothing }
TagsTab ->
{ sett | tagCategoryColors = Nothing }
FieldsTab ->
{ sett | formFields = Nothing }
nm =
initModel flags newSettings model.defaults
in
( { nm | openTabs = model.openTabs }, Just newSettings )
--- View2 --- View2
@ -495,7 +620,7 @@ tagColorViewOpts2 texts =
} }
view2 : Texts -> Flags -> UiSettings -> Model -> Html Msg view2 : Texts -> Flags -> StoredUiSettings -> Model -> Html Msg
view2 texts flags settings model = view2 texts flags settings model =
let let
state tab = state tab =
@ -517,7 +642,7 @@ view2 texts flags settings model =
] ]
settingFormTabs : Texts -> Flags -> UiSettings -> Model -> List (Comp.Tabs.Tab Msg) settingFormTabs : Texts -> Flags -> StoredUiSettings -> Model -> List (Comp.Tabs.Tab Msg)
settingFormTabs texts flags _ model = settingFormTabs texts flags _ model =
let let
langCfg = langCfg =
@ -533,10 +658,21 @@ settingFormTabs texts flags _ model =
, style = DS.mainStyle , style = DS.mainStyle
, selectPlaceholder = texts.basics.selectPlaceholder , selectPlaceholder = texts.basics.selectPlaceholder
} }
resetLink tab =
a
[ href "#"
, class S.link
, class "text-sm"
, onClick (ResetTab tab)
]
[ i [ class "fa fa-eraser mr-1" ] []
, text texts.resetLabel
]
in in
[ { name = "general" [ { name = akkordionTabName GeneralTab
, title = texts.general , title = texts.general
, titleRight = [] , titleRight = [ resetLink GeneralTab ]
, info = Nothing , info = Nothing
, body = , body =
[ div [ class "mb-4 " ] [ div [ class "mb-4 " ]
@ -560,9 +696,9 @@ settingFormTabs texts flags _ model =
] ]
] ]
} }
, { name = "item-search" , { name = akkordionTabName SearchTab
, title = texts.itemSearch , title = texts.itemSearch
, titleRight = [] , titleRight = [ resetLink SearchTab ]
, info = Nothing , info = Nothing
, body = , body =
[ Html.map SearchPageSizeMsg [ Html.map SearchPageSizeMsg
@ -594,9 +730,9 @@ settingFormTabs texts flags _ model =
] ]
] ]
} }
, { name = "item-cards" , { name = akkordionTabName CardsTab
, title = texts.itemCards , title = texts.itemCards
, titleRight = [] , titleRight = [ resetLink CardsTab ]
, info = Nothing , info = Nothing
, body = , body =
[ Html.map NoteLengthMsg [ Html.map NoteLengthMsg
@ -666,9 +802,9 @@ settingFormTabs texts flags _ model =
texts.templateHelpMessage texts.templateHelpMessage
] ]
} }
, { name = "search-menu" , { name = akkordionTabName SearchMenuTab
, title = texts.searchMenu , title = texts.searchMenu
, titleRight = [] , titleRight = [ resetLink SearchMenuTab ]
, info = Nothing , info = Nothing
, body = , body =
[ Html.map SearchMenuTagMsg [ Html.map SearchMenuTagMsg
@ -700,9 +836,9 @@ settingFormTabs texts flags _ model =
) )
] ]
} }
, { name = "item-detail" , { name = akkordionTabName DetailTab
, title = texts.itemDetail , title = texts.itemDetail
, titleRight = [] , titleRight = [ resetLink DetailTab ]
, info = Nothing , info = Nothing
, body = , body =
[ div [ class "mb-4" ] [ div [ class "mb-4" ]
@ -726,9 +862,9 @@ settingFormTabs texts flags _ model =
] ]
] ]
} }
, { name = "tag-category-colors" , { name = akkordionTabName TagsTab
, title = texts.tagCategoryColors , title = texts.tagCategoryColors
, titleRight = [] , titleRight = [ resetLink TagsTab ]
, info = Nothing , info = Nothing
, body = , body =
[ Html.map TagColorMsg [ Html.map TagColorMsg
@ -739,9 +875,9 @@ settingFormTabs texts flags _ model =
) )
] ]
} }
, { name = "fields" , { name = akkordionTabName FieldsTab
, title = texts.fields , title = texts.fields
, titleRight = [] , titleRight = [ resetLink FieldsTab ]
, info = Nothing , info = Nothing
, body = , body =
[ span [ class "opacity-50 text-sm" ] [ span [ class "opacity-50 text-sm" ]

View File

@ -16,26 +16,44 @@ module Comp.UiSettingsManage exposing
import Api import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Comp.Basic
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.UiSettingsForm import Comp.UiSettingsForm
import Comp.UiSettingsMigrate import Data.AccountScope exposing (AccountScope)
import Data.AppEvent exposing (AppEvent(..))
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Data.UiSettings exposing (StoredUiSettings, UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Http import Http
import Messages.Comp.UiSettingsManage exposing (Texts) import Messages.Comp.UiSettingsManage exposing (Texts)
import Page.Search.Data exposing (Msg(..))
import Process
import Styles as S import Styles as S
import Task
type alias Model = type alias Model =
{ formModel : Comp.UiSettingsForm.Model { formModel : FormView
, settings : Maybe UiSettings
, formResult : FormResult , formResult : FormResult
, settingsMigrate : Comp.UiSettingsMigrate.Model , formData : Maybe FormData
} }
type alias FormData =
{ userSettings : StoredUiSettings
, userModel : Comp.UiSettingsForm.Model
, collSettings : StoredUiSettings
, collModel : Comp.UiSettingsForm.Model
}
type FormView
= ViewLoading
| ViewUser
| ViewCollective
type FormResult type FormResult
= FormInit = FormInit
| FormUnchanged | FormUnchanged
@ -45,35 +63,40 @@ type FormResult
type Msg type Msg
= UiSettingsFormMsg Comp.UiSettingsForm.Msg = UiFormMsg AccountScope Comp.UiSettingsForm.Msg
| UiSettingsMigrateMsg Comp.UiSettingsMigrate.Msg
| Submit | Submit
| UpdateSettings | SaveSettingsResp (Result Http.Error BasicResult)
| SaveSettingsResp UiSettings (Result Http.Error BasicResult) | ReceiveServerSettings (Result Http.Error ( StoredUiSettings, StoredUiSettings ))
| ReceiveBrowserSettings StoredUiSettings | ToggleExpandCollapse
| SwitchForm AccountScope
| ResetFormState
init : Flags -> UiSettings -> ( Model, Cmd Msg ) init : Flags -> ( Model, Cmd Msg )
init flags settings = init flags =
let ( { formModel = ViewLoading
( fm, fc ) = , formData = Nothing
Comp.UiSettingsForm.init flags settings
( mm, mc ) =
Comp.UiSettingsMigrate.init flags
in
( { formModel = fm
, settings = Nothing
, formResult = FormInit , formResult = FormInit
, settingsMigrate = mm
} }
, Cmd.batch , Cmd.batch
[ Cmd.map UiSettingsFormMsg fc [ Api.getClientSettingsRaw flags ReceiveServerSettings
, Cmd.map UiSettingsMigrateMsg mc
] ]
) )
getViewScope : Model -> AccountScope
getViewScope model =
case model.formModel of
ViewCollective ->
Data.AccountScope.Collective
ViewUser ->
Data.AccountScope.User
_ ->
Data.AccountScope.User
--- update --- update
@ -82,108 +105,177 @@ type alias UpdateResult =
{ model : Model { model : Model
, cmd : Cmd Msg , cmd : Cmd Msg
, sub : Sub Msg , sub : Sub Msg
, newSettings : Maybe UiSettings , appEvent : AppEvent
} }
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none AppNothing
update : Flags -> UiSettings -> Msg -> Model -> UpdateResult update : Flags -> UiSettings -> Msg -> Model -> UpdateResult
update flags settings msg model = update flags settings msg model =
case msg of case msg of
UiSettingsFormMsg lm -> UiFormMsg scope lm ->
case model.formData of
Nothing ->
unit model
Just data ->
case scope of
Data.AccountScope.Collective ->
let let
inSettings =
Maybe.withDefault settings model.settings
( m_, sett ) = ( m_, sett ) =
Comp.UiSettingsForm.update inSettings lm model.formModel Comp.UiSettingsForm.update flags data.collSettings lm data.collModel
in in
{ model = unit
{ model { model
| formModel = m_ | formData =
, settings = Just
if sett == Nothing then { data
model.settings | collSettings = Maybe.withDefault data.collSettings sett
, collModel = m_
else
sett
, formResult =
if sett /= Nothing then
FormInit
else
model.formResult
} }
, cmd = Cmd.none }
Data.AccountScope.User ->
let
( m_, sett ) =
Comp.UiSettingsForm.update flags data.userSettings lm data.userModel
in
unit
{ model
| formData =
Just
{ data
| userSettings = Maybe.withDefault data.userSettings sett
, userModel = m_
}
}
Submit ->
case ( model.formModel, model.formData ) of
( ViewCollective, Just data ) ->
{ model = { model | formResult = FormInit }
, cmd =
Api.saveClientSettings flags
data.collSettings
Data.AccountScope.Collective
SaveSettingsResp
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
UiSettingsMigrateMsg lm -> ( ViewUser, Just data ) ->
{ model = { model | formResult = FormInit }
, cmd =
Api.saveClientSettings flags
data.userSettings
Data.AccountScope.User
SaveSettingsResp
, sub = Sub.none
, appEvent = AppNothing
}
_ ->
unit model
SaveSettingsResp (Ok res) ->
case ( res.success, model.formData ) of
( True, Just data ) ->
let let
result = result =
Comp.UiSettingsMigrate.update flags lm model.settingsMigrate update flags
settings
(ReceiveServerSettings (Ok ( data.collSettings, data.userSettings )))
{ model | formResult = FormSaved }
cmd =
Process.sleep 2000
|> Task.perform (\_ -> ResetFormState)
in in
{ model = { model | settingsMigrate = result.model } { result | appEvent = AppReloadUiSettings, cmd = Cmd.batch [ cmd, result.cmd ] }
, cmd = Cmd.map UiSettingsMigrateMsg result.cmd
, sub = Sub.map UiSettingsMigrateMsg result.sub _ ->
, newSettings = result.newSettings unit { model | formResult = FormUnknownError }
SaveSettingsResp (Err err) ->
UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none AppNothing
ReceiveServerSettings (Ok ( coll, user )) ->
let
collDefaults =
Data.UiSettings.defaults
userDefaults =
Data.UiSettings.merge coll collDefaults
( ( um, uc ), ( cm, cc ) ) =
case model.formData of
Just data ->
( Comp.UiSettingsForm.initData flags user userDefaults data.userModel
, Comp.UiSettingsForm.initData flags coll collDefaults data.collModel
)
Nothing ->
( Comp.UiSettingsForm.init flags user userDefaults
, Comp.UiSettingsForm.init flags coll collDefaults
)
model_ =
{ model
| formData =
Just
{ userSettings = user
, userModel = um
, collSettings = coll
, collModel = cm
}
, formModel =
case model.formModel of
ViewLoading ->
ViewUser
_ ->
model.formModel
} }
ReceiveBrowserSettings sett -> cmds =
Cmd.batch
[ Cmd.map (UiFormMsg Data.AccountScope.User) uc
, Cmd.map (UiFormMsg Data.AccountScope.Collective) cc
]
in
UpdateResult model_ cmds Sub.none AppNothing
ReceiveServerSettings (Err err) ->
unit { model | formResult = FormHttpError err }
ToggleExpandCollapse ->
let let
lm = lm =
UiSettingsMigrateMsg (Comp.UiSettingsMigrate.receiveBrowserSettings sett) UiFormMsg (getViewScope model) Comp.UiSettingsForm.toggleAllTabs
in in
update flags settings lm model update flags settings lm model
Submit -> SwitchForm scope ->
case model.settings of
Just s ->
{ model = { model | formResult = FormInit }
, cmd = Api.saveClientSettings flags s (SaveSettingsResp s)
, sub = Sub.none
, newSettings = Nothing
}
Nothing ->
{ model = { model | formResult = FormUnchanged }
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing
}
SaveSettingsResp newSettings (Ok res) ->
if res.success then
{ model = { model | formResult = FormSaved }
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Just newSettings
}
else
{ model = { model | formResult = FormUnknownError }
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing
}
SaveSettingsResp _ (Err err) ->
UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none Nothing
UpdateSettings ->
let let
( fm, fc ) = forUser =
Comp.UiSettingsForm.init flags settings unit { model | formModel = ViewUser }
forColl =
unit { model | formModel = ViewCollective }
in in
{ model = { model | formModel = fm } Data.AccountScope.fold forUser forColl scope
, cmd = Cmd.map UiSettingsFormMsg fc
, sub = Sub.none
, newSettings = Nothing
}
ResetFormState ->
case model.formResult of
FormSaved ->
unit { model | formResult = FormInit }
_ ->
--- View2 unit model
isError : Model -> Bool isError : Model -> Bool
@ -211,7 +303,11 @@ isSuccess model =
view2 : Texts -> Flags -> UiSettings -> String -> Model -> Html Msg view2 : Texts -> Flags -> UiSettings -> String -> Model -> Html Msg
view2 texts flags settings classes model = view2 texts flags _ classes model =
let
scope =
getViewScope model
in
div [ class classes ] div [ class classes ]
[ MB.view [ MB.view
{ start = { start =
@ -221,14 +317,29 @@ view2 texts flags settings classes model =
, title = texts.saveSettings , title = texts.saveSettings
, icon = Just "fa fa-save" , icon = Just "fa fa-save"
} }
, MB.SecondaryButton
{ tagger = ToggleExpandCollapse
, label = ""
, title = texts.expandCollapse
, icon = Just "fa fa-compress"
}
]
, end =
[ MB.RadioButton
{ tagger = \_ -> SwitchForm Data.AccountScope.User
, label = texts.accountScope Data.AccountScope.User
, value = Data.AccountScope.fold True False scope
, id = "ui-settings-chooser-user"
}
, MB.RadioButton
{ tagger = \_ -> SwitchForm Data.AccountScope.Collective
, label = texts.accountScope Data.AccountScope.Collective
, value = Data.AccountScope.fold False True scope
, id = "ui-settings-chooser-collective"
}
] ]
, end = []
, rootClasses = "mb-4" , rootClasses = "mb-4"
} }
, div []
[ Html.map UiSettingsMigrateMsg
(Comp.UiSettingsMigrate.view model.settingsMigrate)
]
, div , div
[ classList [ classList
[ ( S.successMessage, isSuccess model ) [ ( S.successMessage, isSuccess model )
@ -252,11 +363,55 @@ view2 texts flags settings classes model =
FormUnknownError -> FormUnknownError ->
text texts.unknownSaveError text texts.unknownSaveError
] ]
, Html.map UiSettingsFormMsg , case model.formModel of
ViewLoading ->
div [ class "h-24 md:relative" ]
[ Comp.Basic.loadingDimmer
{ label = ""
, active = True
}
]
ViewCollective ->
case model.formData of
Just data ->
div []
[ h2 [ class S.header2 ]
[ text texts.collectiveHeader
]
, div [ class "py-1 opacity-80" ]
[ text texts.collectiveInfo
]
, Html.map (UiFormMsg scope)
(Comp.UiSettingsForm.view2 (Comp.UiSettingsForm.view2
texts.uiSettingsForm texts.uiSettingsForm
flags flags
settings data.collSettings
model.formModel data.collModel
) )
] ]
Nothing ->
span [ class "hidden" ] []
ViewUser ->
case model.formData of
Just data ->
div []
[ h2 [ class S.header2 ]
[ text texts.userHeader
]
, div [ class "py-1 opacity-80" ]
[ text texts.userInfo
]
, Html.map (UiFormMsg scope)
(Comp.UiSettingsForm.view2 texts.uiSettingsForm
flags
data.userSettings
data.userModel
)
]
Nothing ->
span [ class "hidden" ] []
]

View File

@ -17,6 +17,7 @@ module Comp.UiSettingsMigrate exposing
import Api import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Data.AccountScope
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Data.UiSettings exposing (StoredUiSettings, UiSettings)
import Html exposing (..) import Html exposing (..)
@ -132,7 +133,10 @@ update flags msg model =
Data.UiSettings.merge settings Data.UiSettings.defaults Data.UiSettings.merge settings Data.UiSettings.defaults
cmd = cmd =
Api.saveClientSettings flags uiSettings (SaveSettingsResp uiSettings) Api.saveClientSettings flags
(Data.UiSettings.convert uiSettings)
Data.AccountScope.Collective
(SaveSettingsResp uiSettings)
in in
{ empty | model = MigrateRequestRunning, cmd = cmd } { empty | model = MigrateRequestRunning, cmd = cmd }

View File

@ -23,6 +23,11 @@ fold user coll scope =
coll coll
all : List AccountScope
all =
[ Collective, User ]
isUser : AccountScope -> Bool isUser : AccountScope -> Bool
isUser scope = isUser scope =
fold True False scope fold True False scope

View File

@ -0,0 +1,13 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.AppEvent exposing (AppEvent(..))
type AppEvent
= AppNothing
| AppReloadUiSettings

View File

@ -7,7 +7,6 @@
module Data.UiSettings exposing module Data.UiSettings exposing
( ItemPattern ( ItemPattern
, Pos(..)
, StoredUiSettings , StoredUiSettings
, UiSettings , UiSettings
, cardPreviewSize , cardPreviewSize
@ -15,22 +14,21 @@ module Data.UiSettings exposing
, catColor , catColor
, catColorFg2 , catColorFg2
, catColorString2 , catColorString2
, convert
, defaults , defaults
, documentationSite , documentationSite
, emptyStoredSettings
, fieldHidden , fieldHidden
, fieldVisible , fieldVisible
, getUiLanguage , getUiLanguage
, merge , merge
, mergeDefaults , mergeDefaults
, pdfUrl , pdfUrl
, posFromString
, posToString
, storedUiSettingsDecoder , storedUiSettingsDecoder
, storedUiSettingsEncode , storedUiSettingsEncode
, tagColor , tagColor
, tagColorFg2 , tagColorFg2
, tagColorString2 , tagColorString2
, toStoredUiSettings
) )
import Api.Model.Tag exposing (Tag) import Api.Model.Tag exposing (Tag)
@ -62,31 +60,53 @@ force default settings.
-} -}
type alias StoredUiSettings = type alias StoredUiSettings =
{ itemSearchPageSize : Maybe Int { itemSearchPageSize : Maybe Int
, tagCategoryColors : List ( String, String ) , tagCategoryColors : Maybe (List ( String, String ))
, pdfMode : Maybe String , pdfMode : Maybe String
, itemSearchNoteLength : Maybe Int , itemSearchNoteLength : Maybe Int
, itemDetailNotesPosition : Maybe String
, searchMenuFolderCount : Maybe Int , searchMenuFolderCount : Maybe Int
, searchMenuTagCount : Maybe Int , searchMenuTagCount : Maybe Int
, searchMenuTagCatCount : Maybe Int , searchMenuTagCatCount : Maybe Int
, formFields : Maybe (List String) , formFields : Maybe (List String)
, itemDetailShortcuts : Bool , itemDetailShortcuts : Maybe Bool
, searchMenuVisible : Bool
, editMenuVisible : Bool
, cardPreviewSize : Maybe String , cardPreviewSize : Maybe String
, cardTitleTemplate : Maybe String , cardTitleTemplate : Maybe String
, cardSubtitleTemplate : Maybe String , cardSubtitleTemplate : Maybe String
, searchStatsVisible : Bool , searchStatsVisible : Maybe Bool
, cardPreviewFullWidth : Bool , cardPreviewFullWidth : Maybe Bool
, uiTheme : Maybe String , uiTheme : Maybe String
, sideMenuVisible : Bool , sideMenuVisible : Maybe Bool
, powerSearchEnabled : Bool , powerSearchEnabled : Maybe Bool
, uiLang : Maybe String , uiLang : Maybe String
, itemSearchShowGroups : Bool , itemSearchShowGroups : Maybe Bool
, itemSearchArrange : Maybe String , itemSearchArrange : Maybe String
} }
emptyStoredSettings : StoredUiSettings
emptyStoredSettings =
{ itemSearchPageSize = Nothing
, tagCategoryColors = Nothing
, pdfMode = Nothing
, itemSearchNoteLength = Nothing
, searchMenuFolderCount = Nothing
, searchMenuTagCount = Nothing
, searchMenuTagCatCount = Nothing
, formFields = Nothing
, itemDetailShortcuts = Nothing
, cardPreviewSize = Nothing
, cardTitleTemplate = Nothing
, cardSubtitleTemplate = Nothing
, searchStatsVisible = Nothing
, cardPreviewFullWidth = Nothing
, uiTheme = Nothing
, sideMenuVisible = Nothing
, powerSearchEnabled = Nothing
, uiLang = Nothing
, itemSearchShowGroups = Nothing
, itemSearchArrange = Nothing
}
storedUiSettingsDecoder : Decode.Decoder StoredUiSettings storedUiSettingsDecoder : Decode.Decoder StoredUiSettings
storedUiSettingsDecoder = storedUiSettingsDecoder =
let let
@ -96,65 +116,62 @@ storedUiSettingsDecoder =
maybeString = maybeString =
Decode.maybe Decode.string Decode.maybe Decode.string
def = maybeBool =
defaults Decode.maybe Decode.bool
in in
Decode.succeed StoredUiSettings Decode.succeed StoredUiSettings
|> P.optional "itemSearchPageSize" maybeInt Nothing |> P.optional "itemSearchPageSize" maybeInt Nothing
|> P.optional "tagCategoryColors" (Decode.keyValuePairs Decode.string) [] |> P.optional "tagCategoryColors" (Decode.maybe <| Decode.keyValuePairs Decode.string) Nothing
|> P.optional "pdfMode" maybeString Nothing |> P.optional "pdfMode" maybeString Nothing
|> P.optional "itemSearchNoteLength" maybeInt Nothing |> P.optional "itemSearchNoteLength" maybeInt Nothing
|> P.optional "itemDetailNotesPosition" maybeString Nothing
|> P.optional "searchMenuFolderCount" maybeInt Nothing |> P.optional "searchMenuFolderCount" maybeInt Nothing
|> P.optional "searchMenuTagCount" maybeInt Nothing |> P.optional "searchMenuTagCount" maybeInt Nothing
|> P.optional "searchMenuTagCatCount" maybeInt Nothing |> P.optional "searchMenuTagCatCount" maybeInt Nothing
|> P.optional "formFields" (Decode.maybe <| Decode.list Decode.string) Nothing |> P.optional "formFields" (Decode.maybe <| Decode.list Decode.string) Nothing
|> P.optional "itemDetailShortcuts" Decode.bool def.itemDetailShortcuts |> P.optional "itemDetailShortcuts" maybeBool Nothing
|> P.optional "searchMenuVisible" Decode.bool def.searchMenuVisible
|> P.optional "editMenuVisible" Decode.bool def.editMenuVisible
|> P.optional "cardPreviewSize" maybeString Nothing |> P.optional "cardPreviewSize" maybeString Nothing
|> P.optional "cardTitleTemplate" maybeString Nothing |> P.optional "cardTitleTemplate" maybeString Nothing
|> P.optional "cardSubtitleTemplate" maybeString Nothing |> P.optional "cardSubtitleTemplate" maybeString Nothing
|> P.optional "searchStatsVisible" Decode.bool def.searchStatsVisible |> P.optional "searchStatsVisible" maybeBool Nothing
|> P.optional "cardPreviewFullWidth" Decode.bool def.cardPreviewFullWidth |> P.optional "cardPreviewFullWidth" maybeBool Nothing
|> P.optional "uiTheme" maybeString Nothing |> P.optional "uiTheme" maybeString Nothing
|> P.optional "sideMenuVisible" Decode.bool def.sideMenuVisible |> P.optional "sideMenuVisible" maybeBool Nothing
|> P.optional "powerSearchEnabled" Decode.bool def.powerSearchEnabled |> P.optional "powerSearchEnabled" maybeBool Nothing
|> P.optional "uiLang" maybeString Nothing |> P.optional "uiLang" maybeString Nothing
|> P.optional "itemSearchShowGroups" Decode.bool def.itemSearchShowGroups |> P.optional "itemSearchShowGroups" maybeBool Nothing
|> P.optional "itemSearchArrange" maybeString Nothing |> P.optional "itemSearchArrange" maybeString Nothing
storedUiSettingsEncode : StoredUiSettings -> Encode.Value storedUiSettingsEncode : StoredUiSettings -> Encode.Value
storedUiSettingsEncode value = storedUiSettingsEncode value =
let let
maybeEnc enca ma = maybeEnc field enca ma =
Maybe.map enca ma |> Maybe.withDefault Encode.null Maybe.map (\a -> ( field, enca a )) ma
in in
Encode.object Encode.object <|
[ ( "itemSearchPageSize", maybeEnc Encode.int value.itemSearchPageSize ) List.filterMap identity
, ( "tagCategoryColors", Encode.dict identity Encode.string (Dict.fromList value.tagCategoryColors) ) [ maybeEnc "itemSearchPageSize" Encode.int value.itemSearchPageSize
, ( "pdfMode", maybeEnc Encode.string value.pdfMode ) , maybeEnc "tagCategoryColors"
, ( "itemSearchNoteLength", maybeEnc Encode.int value.itemSearchNoteLength ) (Encode.dict identity Encode.string)
, ( "itemDetailNotesPosition", maybeEnc Encode.string value.itemDetailNotesPosition ) (Maybe.map Dict.fromList value.tagCategoryColors)
, ( "searchMenuFolderCount", maybeEnc Encode.int value.searchMenuFolderCount ) , maybeEnc "pdfMode" Encode.string value.pdfMode
, ( "searchMenuTagCount", maybeEnc Encode.int value.searchMenuTagCount ) , maybeEnc "itemSearchNoteLength" Encode.int value.itemSearchNoteLength
, ( "searchMenuTagCatCount", maybeEnc Encode.int value.searchMenuTagCatCount ) , maybeEnc "searchMenuFolderCount" Encode.int value.searchMenuFolderCount
, ( "formFields", maybeEnc (Encode.list Encode.string) value.formFields ) , maybeEnc "searchMenuTagCount" Encode.int value.searchMenuTagCount
, ( "itemDetailShortcuts", Encode.bool value.itemDetailShortcuts ) , maybeEnc "searchMenuTagCatCount" Encode.int value.searchMenuTagCatCount
, ( "searchMenuVisible", Encode.bool value.searchMenuVisible ) , maybeEnc "formFields" (Encode.list Encode.string) value.formFields
, ( "editMenuVisible", Encode.bool value.editMenuVisible ) , maybeEnc "itemDetailShortcuts" Encode.bool value.itemDetailShortcuts
, ( "cardPreviewSize", maybeEnc Encode.string value.cardPreviewSize ) , maybeEnc "cardPreviewSize" Encode.string value.cardPreviewSize
, ( "cardTitleTemplate", maybeEnc Encode.string value.cardTitleTemplate ) , maybeEnc "cardTitleTemplate" Encode.string value.cardTitleTemplate
, ( "cardSubtitleTemplate", maybeEnc Encode.string value.cardSubtitleTemplate ) , maybeEnc "cardSubtitleTemplate" Encode.string value.cardSubtitleTemplate
, ( "searchStatsVisible", Encode.bool value.searchStatsVisible ) , maybeEnc "searchStatsVisible" Encode.bool value.searchStatsVisible
, ( "cardPreviewFullWidth", Encode.bool value.cardPreviewFullWidth ) , maybeEnc "cardPreviewFullWidth" Encode.bool value.cardPreviewFullWidth
, ( "uiTheme", maybeEnc Encode.string value.uiTheme ) , maybeEnc "uiTheme" Encode.string value.uiTheme
, ( "sideMenuVisible", Encode.bool value.sideMenuVisible ) , maybeEnc "sideMenuVisible" Encode.bool value.sideMenuVisible
, ( "powerSearchEnabled", Encode.bool value.powerSearchEnabled ) , maybeEnc "powerSearchEnabled" Encode.bool value.powerSearchEnabled
, ( "uiLang", maybeEnc Encode.string value.uiLang ) , maybeEnc "uiLang" Encode.string value.uiLang
, ( "itemSearchShowGroups", Encode.bool value.itemSearchShowGroups ) , maybeEnc "itemSearchShowGroups" Encode.bool value.itemSearchShowGroups
, ( "itemSearchArrange", maybeEnc Encode.string value.itemSearchArrange ) , maybeEnc "itemSearchArrange" Encode.string value.itemSearchArrange
] ]
@ -171,14 +188,11 @@ type alias UiSettings =
, tagCategoryColors : Dict String Color , tagCategoryColors : Dict String Color
, pdfMode : PdfMode , pdfMode : PdfMode
, itemSearchNoteLength : Int , itemSearchNoteLength : Int
, itemDetailNotesPosition : Pos
, searchMenuFolderCount : Int , searchMenuFolderCount : Int
, searchMenuTagCount : Int , searchMenuTagCount : Int
, searchMenuTagCatCount : Int , searchMenuTagCatCount : Int
, formFields : List Field , formFields : List Field
, itemDetailShortcuts : Bool , itemDetailShortcuts : Bool
, searchMenuVisible : Bool
, editMenuVisible : Bool
, cardPreviewSize : BasicSize , cardPreviewSize : BasicSize
, cardTitleTemplate : ItemPattern , cardTitleTemplate : ItemPattern
, cardSubtitleTemplate : ItemPattern , cardSubtitleTemplate : ItemPattern
@ -205,48 +219,17 @@ readPattern str =
|> Maybe.map (ItemPattern str) |> Maybe.map (ItemPattern str)
type Pos
= Top
| Bottom
posToString : Pos -> String
posToString pos =
case pos of
Top ->
"top"
Bottom ->
"bottom"
posFromString : String -> Maybe Pos
posFromString str =
case str of
"top" ->
Just Top
"bottom" ->
Just Bottom
_ ->
Nothing
defaults : UiSettings defaults : UiSettings
defaults = defaults =
{ itemSearchPageSize = 60 { itemSearchPageSize = 60
, tagCategoryColors = Dict.empty , tagCategoryColors = Dict.empty
, pdfMode = Data.Pdf.Detect , pdfMode = Data.Pdf.Detect
, itemSearchNoteLength = 0 , itemSearchNoteLength = 0
, itemDetailNotesPosition = Bottom
, searchMenuFolderCount = 3 , searchMenuFolderCount = 3
, searchMenuTagCount = 6 , searchMenuTagCount = 6
, searchMenuTagCatCount = 3 , searchMenuTagCatCount = 3
, formFields = Data.Fields.all , formFields = Data.Fields.all
, itemDetailShortcuts = False , itemDetailShortcuts = False
, searchMenuVisible = False
, editMenuVisible = False
, cardPreviewSize = Data.BasicSize.Medium , cardPreviewSize = Data.BasicSize.Medium
, cardTitleTemplate = , cardTitleTemplate =
{ template = Data.ItemTemplate.name { template = Data.ItemTemplate.name
@ -273,7 +256,8 @@ merge given fallback =
choose given.itemSearchPageSize fallback.itemSearchPageSize choose given.itemSearchPageSize fallback.itemSearchPageSize
, tagCategoryColors = , tagCategoryColors =
Dict.union Dict.union
(Dict.fromList given.tagCategoryColors (Maybe.map Dict.fromList given.tagCategoryColors
|> Maybe.withDefault Dict.empty
|> Dict.map (\_ -> Data.Color.fromString) |> Dict.map (\_ -> Data.Color.fromString)
|> Dict.filter (\_ -> \mc -> mc /= Nothing) |> Dict.filter (\_ -> \mc -> mc /= Nothing)
|> Dict.map (\_ -> Maybe.withDefault Data.Color.Grey) |> Dict.map (\_ -> Maybe.withDefault Data.Color.Grey)
@ -285,9 +269,6 @@ merge given fallback =
|> Maybe.withDefault fallback.pdfMode |> Maybe.withDefault fallback.pdfMode
, itemSearchNoteLength = , itemSearchNoteLength =
choose given.itemSearchNoteLength fallback.itemSearchNoteLength choose given.itemSearchNoteLength fallback.itemSearchNoteLength
, itemDetailNotesPosition =
choose (Maybe.andThen posFromString given.itemDetailNotesPosition)
fallback.itemDetailNotesPosition
, searchMenuFolderCount = , searchMenuFolderCount =
choose given.searchMenuFolderCount choose given.searchMenuFolderCount
fallback.searchMenuFolderCount fallback.searchMenuFolderCount
@ -299,9 +280,7 @@ merge given fallback =
choose choose
(Maybe.map Data.Fields.fromList given.formFields) (Maybe.map Data.Fields.fromList given.formFields)
fallback.formFields fallback.formFields
, itemDetailShortcuts = given.itemDetailShortcuts , itemDetailShortcuts = choose given.itemDetailShortcuts fallback.itemDetailShortcuts
, searchMenuVisible = given.searchMenuVisible
, editMenuVisible = given.editMenuVisible
, cardPreviewSize = , cardPreviewSize =
given.cardPreviewSize given.cardPreviewSize
|> Maybe.andThen Data.BasicSize.fromString |> Maybe.andThen Data.BasicSize.fromString
@ -312,17 +291,17 @@ merge given fallback =
, cardSubtitleTemplate = , cardSubtitleTemplate =
Maybe.andThen readPattern given.cardSubtitleTemplate Maybe.andThen readPattern given.cardSubtitleTemplate
|> Maybe.withDefault fallback.cardSubtitleTemplate |> Maybe.withDefault fallback.cardSubtitleTemplate
, searchStatsVisible = given.searchStatsVisible , searchStatsVisible = choose given.searchStatsVisible fallback.searchStatsVisible
, cardPreviewFullWidth = given.cardPreviewFullWidth , cardPreviewFullWidth = choose given.cardPreviewFullWidth fallback.cardPreviewFullWidth
, uiTheme = , uiTheme =
Maybe.andThen Data.UiTheme.fromString given.uiTheme Maybe.andThen Data.UiTheme.fromString given.uiTheme
|> Maybe.withDefault fallback.uiTheme |> Maybe.withDefault fallback.uiTheme
, sideMenuVisible = given.sideMenuVisible , sideMenuVisible = choose given.sideMenuVisible fallback.sideMenuVisible
, powerSearchEnabled = given.powerSearchEnabled , powerSearchEnabled = choose given.powerSearchEnabled fallback.powerSearchEnabled
, uiLang = , uiLang =
Maybe.map Messages.fromIso2 given.uiLang Maybe.map Messages.fromIso2 given.uiLang
|> Maybe.withDefault Messages.UiLanguage.English |> Maybe.withDefault fallback.uiLang
, itemSearchShowGroups = given.itemSearchShowGroups , itemSearchShowGroups = choose given.itemSearchShowGroups fallback.itemSearchShowGroups
, itemSearchArrange = , itemSearchArrange =
Maybe.andThen Data.ItemArrange.fromString given.itemSearchArrange Maybe.andThen Data.ItemArrange.fromString given.itemSearchArrange
|> Maybe.withDefault fallback.itemSearchArrange |> Maybe.withDefault fallback.itemSearchArrange
@ -334,37 +313,35 @@ mergeDefaults given =
merge given defaults merge given defaults
toStoredUiSettings : UiSettings -> StoredUiSettings convert : UiSettings -> StoredUiSettings
toStoredUiSettings settings = convert settings =
{ itemSearchPageSize = Just settings.itemSearchPageSize { itemSearchPageSize = Just settings.itemSearchPageSize
, tagCategoryColors = , tagCategoryColors =
Dict.map (\_ -> Data.Color.toString) settings.tagCategoryColors Dict.map (\_ -> Data.Color.toString) settings.tagCategoryColors
|> Dict.toList |> Dict.toList
|> Just
, pdfMode = Just (Data.Pdf.asString settings.pdfMode) , pdfMode = Just (Data.Pdf.asString settings.pdfMode)
, itemSearchNoteLength = Just settings.itemSearchNoteLength , itemSearchNoteLength = Just settings.itemSearchNoteLength
, itemDetailNotesPosition = Just (posToString settings.itemDetailNotesPosition)
, searchMenuFolderCount = Just settings.searchMenuFolderCount , searchMenuFolderCount = Just settings.searchMenuFolderCount
, searchMenuTagCount = Just settings.searchMenuTagCount , searchMenuTagCount = Just settings.searchMenuTagCount
, searchMenuTagCatCount = Just settings.searchMenuTagCatCount , searchMenuTagCatCount = Just settings.searchMenuTagCatCount
, formFields = , formFields =
List.map Data.Fields.toString settings.formFields List.map Data.Fields.toString settings.formFields
|> Just |> Just
, itemDetailShortcuts = settings.itemDetailShortcuts , itemDetailShortcuts = Just settings.itemDetailShortcuts
, searchMenuVisible = settings.searchMenuVisible
, editMenuVisible = settings.editMenuVisible
, cardPreviewSize = , cardPreviewSize =
settings.cardPreviewSize settings.cardPreviewSize
|> Data.BasicSize.asString |> Data.BasicSize.asString
|> Just |> Just
, cardTitleTemplate = settings.cardTitleTemplate.pattern |> Just , cardTitleTemplate = settings.cardTitleTemplate.pattern |> Just
, cardSubtitleTemplate = settings.cardSubtitleTemplate.pattern |> Just , cardSubtitleTemplate = settings.cardSubtitleTemplate.pattern |> Just
, searchStatsVisible = settings.searchStatsVisible , searchStatsVisible = Just settings.searchStatsVisible
, cardPreviewFullWidth = settings.cardPreviewFullWidth , cardPreviewFullWidth = Just settings.cardPreviewFullWidth
, uiTheme = Just (Data.UiTheme.toString settings.uiTheme) , uiTheme = Just (Data.UiTheme.toString settings.uiTheme)
, sideMenuVisible = settings.sideMenuVisible , sideMenuVisible = Just settings.sideMenuVisible
, powerSearchEnabled = settings.powerSearchEnabled , powerSearchEnabled = Just settings.powerSearchEnabled
, uiLang = Just <| Messages.toIso2 settings.uiLang , uiLang = Just <| Messages.toIso2 settings.uiLang
, itemSearchShowGroups = settings.itemSearchShowGroups , itemSearchShowGroups = Just settings.itemSearchShowGroups
, itemSearchArrange = Data.ItemArrange.asString settings.itemSearchArrange |> Just , itemSearchArrange = Data.ItemArrange.asString settings.itemSearchArrange |> Just
} }

View File

@ -14,7 +14,6 @@ import App.View2
import Browser exposing (Document) import Browser exposing (Document)
import Browser.Navigation exposing (Key) import Browser.Navigation exposing (Key)
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.NotificationChannel
import Data.UiSettings import Data.UiSettings
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@ -93,6 +92,5 @@ subscriptions : Model -> Sub Msg
subscriptions model = subscriptions model =
Sub.batch Sub.batch
[ model.subs [ model.subs
, Ports.receiveUiSettings ReceiveBrowserSettings
, Ports.receiveServerEvent ReceiveWsMessage , Ports.receiveServerEvent ReceiveWsMessage
] ]

View File

@ -56,6 +56,7 @@ type alias Texts =
, fieldLabel : Field -> String , fieldLabel : Field -> String
, templateHelpMessage : String , templateHelpMessage : String
, pdfMode : PdfMode -> 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. is rendered.
""" """
, pdfMode = Messages.Data.PdfMode.gb , pdfMode = Messages.Data.PdfMode.gb
, resetLabel = "Reset"
} }
@ -208,4 +210,5 @@ oder, wenn diese leer ist, die Person. Sind beide leer wird ein `-`
dargestellt. dargestellt.
""" """
, pdfMode = Messages.Data.PdfMode.de , pdfMode = Messages.Data.PdfMode.de
, resetLabel = "Zurücksetzen"
} }

View File

@ -15,16 +15,23 @@ import Http
import Messages.Basics import Messages.Basics
import Messages.Comp.HttpError import Messages.Comp.HttpError
import Messages.Comp.UiSettingsForm import Messages.Comp.UiSettingsForm
import Messages.Data.AccountScope
type alias Texts = type alias Texts =
{ basics : Messages.Basics.Texts { basics : Messages.Basics.Texts
, uiSettingsForm : Messages.Comp.UiSettingsForm.Texts , uiSettingsForm : Messages.Comp.UiSettingsForm.Texts
, accountScope : Messages.Data.AccountScope.Texts
, saveSettings : String , saveSettings : String
, settingsUnchanged : String , settingsUnchanged : String
, settingsSaved : String , settingsSaved : String
, unknownSaveError : String , unknownSaveError : String
, httpError : Http.Error -> String , httpError : Http.Error -> String
, userHeader : String
, userInfo : String
, collectiveHeader : String
, collectiveInfo : String
, expandCollapse : String
} }
@ -32,11 +39,17 @@ gb : Texts
gb = gb =
{ basics = Messages.Basics.gb { basics = Messages.Basics.gb
, uiSettingsForm = Messages.Comp.UiSettingsForm.gb , uiSettingsForm = Messages.Comp.UiSettingsForm.gb
, accountScope = Messages.Data.AccountScope.gb
, saveSettings = "Save settings" , saveSettings = "Save settings"
, settingsUnchanged = "Settings unchanged or invalid." , settingsUnchanged = "Settings unchanged or invalid."
, settingsSaved = "Settings saved." , settingsSaved = "Settings saved."
, unknownSaveError = "Unknown error while trying to save settings." , unknownSaveError = "Unknown error while trying to save settings."
, httpError = Messages.Comp.HttpError.gb , httpError = Messages.Comp.HttpError.gb
, userHeader = "Personal settings"
, 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"
} }
@ -44,9 +57,15 @@ de : Texts
de = de =
{ basics = Messages.Basics.de { basics = Messages.Basics.de
, uiSettingsForm = Messages.Comp.UiSettingsForm.de , uiSettingsForm = Messages.Comp.UiSettingsForm.de
, accountScope = Messages.Data.AccountScope.de
, saveSettings = "Einstellungen speichern" , saveSettings = "Einstellungen speichern"
, settingsUnchanged = "Einstellungen nicht verändert oder ungültig." , settingsUnchanged = "Einstellungen nicht verändert oder ungültig."
, settingsSaved = "Einstellungen gespeichert" , settingsSaved = "Einstellungen gespeichert"
, unknownSaveError = "Unbekannter Fehler beim Speichern der Einstellungen." , unknownSaveError = "Unbekannter Fehler beim Speichern der Einstellungen."
, httpError = Messages.Comp.HttpError.de , httpError = Messages.Comp.HttpError.de
, userHeader = "Persönliche Einstellungen"
, 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"
} }

View File

@ -80,8 +80,7 @@ gb =
, changePassword = "Change Password" , changePassword = "Change Password"
, channelSettings = "Notification Channels" , channelSettings = "Notification Channels"
, uiSettingsInfo = , uiSettingsInfo =
"These settings only affect the web ui. They are stored in the browser, " "These settings only affect the web ui. Settings can be stored to the collective or to your personal user. Personal settings are prefered when both values exist."
++ "so they are separated between browsers and devices."
, scanMailboxInfo1 = , scanMailboxInfo1 =
"Docspell can scan folders of your mailbox to import your mails. " "Docspell can scan folders of your mailbox to import your mails. "
++ "You need to provide a connection in " ++ "You need to provide a connection in "
@ -144,7 +143,7 @@ de =
, channelSettings = "Benachrichtigungskanäle" , channelSettings = "Benachrichtigungskanäle"
, changePassword = "Passwort ändern" , changePassword = "Passwort ändern"
, uiSettingsInfo = , uiSettingsInfo =
"Diese Einstellungen sind für die Web-Oberfläche." "Diese Einstellungen sind für die Web-Oberfläche. Es kann entweder für das ganze Kollektiv Einstellungen gemacht werden oder persönliche. Die persönlichen Einstellungen werden bevorzugt, falls beide gesetzt sind."
, scanMailboxInfo1 = , scanMailboxInfo1 =
"""Docspell kann Postfächer durchsuchen und E-Mails importieren. Dafür sind """Docspell kann Postfächer durchsuchen und E-Mails importieren. Dafür sind
E-Mail-Einstellungen (IMAP) notwendig.""" E-Mail-Einstellungen (IMAP) notwendig."""

View File

@ -109,8 +109,7 @@ initSelectViewModel flags =
type ViewMode type ViewMode
= SimpleView = SearchView
| SearchView
| SelectView SelectViewModel | SelectView SelectViewModel
| PublishView Comp.PublishItems.Model | PublishView Comp.PublishItems.Model
@ -149,9 +148,6 @@ init flags viewMode =
menuCollapsed : Model -> Bool menuCollapsed : Model -> Bool
menuCollapsed model = menuCollapsed model =
case model.viewMode of case model.viewMode of
SimpleView ->
True
SearchView -> SearchView ->
False False
@ -165,9 +161,6 @@ menuCollapsed model =
selectActive : Model -> Bool selectActive : Model -> Bool
selectActive model = selectActive model =
case model.viewMode of case model.viewMode of
SimpleView ->
False
SearchView -> SearchView ->
False False
@ -181,9 +174,6 @@ selectActive model =
editActive : Model -> Bool editActive : Model -> Bool
editActive model = editActive model =
case model.viewMode of case model.viewMode of
SimpleView ->
False
SearchView -> SearchView ->
False False
@ -203,7 +193,6 @@ type Msg
| ItemSearchResp Bool (Result Http.Error ItemLightList) | ItemSearchResp Bool (Result Http.Error ItemLightList)
| ItemSearchAddResp (Result Http.Error ItemLightList) | ItemSearchAddResp (Result Http.Error ItemLightList)
| DoSearch SearchType | DoSearch SearchType
| ToggleSearchMenu
| ToggleSelectView | ToggleSelectView
| LoadMore | LoadMore
| SetBasicSearch String | SetBasicSearch String
@ -231,7 +220,7 @@ type Msg
| KeyUpPowerSearchbarMsg (Maybe KeyCode) | KeyUpPowerSearchbarMsg (Maybe KeyCode)
| RequestReprocessSelected | RequestReprocessSelected
| ReprocessSelectedConfirmed | ReprocessSelectedConfirmed
| ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) | ClientSettingsSaveResp (Result Http.Error BasicResult)
| RemoveItem String | RemoveItem String
| MergeSelectedItems | MergeSelectedItems
| MergeItemsMsg Comp.ItemMerge.Msg | MergeItemsMsg Comp.ItemMerge.Msg

View File

@ -22,7 +22,9 @@ import Comp.LinkTarget exposing (LinkTarget)
import Comp.PowerSearchInput import Comp.PowerSearchInput
import Comp.PublishItems import Comp.PublishItems
import Comp.SearchMenu import Comp.SearchMenu
import Data.AppEvent exposing (AppEvent(..))
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.ItemArrange
import Data.ItemQuery as Q import Data.ItemQuery as Q
import Data.ItemSelection import Data.ItemSelection
import Data.Items import Data.Items
@ -44,7 +46,7 @@ type alias UpdateResult =
{ model : Model { model : Model
, cmd : Cmd Msg , cmd : Cmd Msg
, sub : Sub Msg , sub : Sub Msg
, newSettings : Maybe UiSettings , appEvent : AppEvent
} }
@ -74,7 +76,7 @@ update bookmarkId mId key flags texts settings msg model =
model model
DoNothing -> DoNothing ->
UpdateResult model Cmd.none Sub.none Nothing UpdateResult model Cmd.none Sub.none AppNothing
ResetSearch -> ResetSearch ->
let let
@ -274,34 +276,10 @@ update bookmarkId mId key flags texts settings msg model =
else else
doSearch param model doSearch param model
ToggleSearchMenu ->
let
nextView =
case model.viewMode of
SimpleView ->
SearchView
SearchView ->
SimpleView
SelectView _ ->
SimpleView
PublishView q ->
PublishView q
in
withSub
( { model | viewMode = nextView }
, Cmd.none
)
ToggleSelectView -> ToggleSelectView ->
let let
( nextView, cmd ) = ( nextView, cmd ) =
case model.viewMode of case model.viewMode of
SimpleView ->
( SelectView <| initSelectViewModel flags, loadEditModel flags )
SearchView -> SearchView ->
( SelectView <| initSelectViewModel flags, loadEditModel flags ) ( SelectView <| initSelectViewModel flags, loadEditModel flags )
@ -642,12 +620,8 @@ update bookmarkId mId key flags texts settings msg model =
SelectView { svm | mergeModel = result.model } SelectView { svm | mergeModel = result.model }
Comp.ItemMerge.OutcomeMerged -> Comp.ItemMerge.OutcomeMerged ->
if settings.searchMenuVisible then
SearchView SearchView
else
SimpleView
model_ = model_ =
{ model | viewMode = nextView } { model | viewMode = nextView }
in in
@ -834,17 +808,10 @@ update bookmarkId mId key flags texts settings msg model =
UiSettingsUpdated -> UiSettingsUpdated ->
let let
defaultViewMode = defaultViewMode =
if settings.searchMenuVisible then
SearchView SearchView
else
SimpleView
viewMode = viewMode =
case model.viewMode of case model.viewMode of
SimpleView ->
defaultViewMode
SearchView -> SearchView ->
defaultViewMode defaultViewMode
@ -868,26 +835,26 @@ update bookmarkId mId key flags texts settings msg model =
TogglePreviewFullWidth -> TogglePreviewFullWidth ->
let let
newSettings = newSettings s =
{ settings | cardPreviewFullWidth = not settings.cardPreviewFullWidth } { s | cardPreviewFullWidth = Just (not settings.cardPreviewFullWidth) }
cmd = cmd =
Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp
in in
noSub ( { model | viewMenuOpen = False }, cmd ) noSub ( { model | viewMenuOpen = False }, cmd )
ClientSettingsSaveResp newSettings (Ok res) -> ClientSettingsSaveResp (Ok res) ->
if res.success then if res.success then
{ model = model { model = model
, cmd = Cmd.none , cmd = Cmd.none
, sub = Sub.none , sub = Sub.none
, newSettings = Just newSettings , appEvent = AppReloadUiSettings
} }
else else
noSub ( model, Cmd.none ) noSub ( model, Cmd.none )
ClientSettingsSaveResp _ (Err _) -> ClientSettingsSaveResp (Err _) ->
noSub ( model, Cmd.none ) noSub ( model, Cmd.none )
PowerSearchMsg lm -> PowerSearchMsg lm ->
@ -1015,21 +982,21 @@ update bookmarkId mId key flags texts settings msg model =
ToggleShowGroups -> ToggleShowGroups ->
let let
newSettings = newSettings s =
{ settings | itemSearchShowGroups = not settings.itemSearchShowGroups } { s | itemSearchShowGroups = Just (not settings.itemSearchShowGroups) }
cmd = cmd =
Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp
in in
noSub ( { model | viewMenuOpen = False }, cmd ) noSub ( { model | viewMenuOpen = False }, cmd )
ToggleArrange am -> ToggleArrange am ->
let let
newSettings = newSettings s =
{ settings | itemSearchArrange = am } { s | itemSearchArrange = Data.ItemArrange.asString am |> Just }
cmd = cmd =
Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp
in in
noSub ( { model | viewMenuOpen = False }, cmd ) noSub ( { model | viewMenuOpen = False }, cmd )
@ -1201,5 +1168,5 @@ makeResult ( m, c, s ) =
{ model = m { model = m
, cmd = c , cmd = c
, sub = s , sub = s
, newSettings = Nothing , appEvent = AppNothing
} }

View File

@ -93,9 +93,6 @@ mainView texts flags settings model =
(publishResults texts settings flags model pm) (publishResults texts settings flags model pm)
] ]
SimpleView ->
Nothing
SearchView -> SearchView ->
Nothing Nothing
in in
@ -109,7 +106,7 @@ mainView texts flags settings model =
bookmarkQueryWidget : Texts -> UiSettings -> Flags -> Model -> List (Html Msg) bookmarkQueryWidget : Texts -> UiSettings -> Flags -> Model -> List (Html Msg)
bookmarkQueryWidget texts settings flags model = bookmarkQueryWidget texts _ _ model =
case model.topWidgetModel of case model.topWidgetModel of
BookmarkQuery m -> BookmarkQuery m ->
[ div [ class "px-2 mb-4 border-l border-r border-b dark:border-slate-600" ] [ div [ class "px-2 mb-4 border-l border-r border-b dark:border-slate-600" ]
@ -136,7 +133,7 @@ itemMergeView texts settings svm =
publishResults : Texts -> UiSettings -> Flags -> Model -> Comp.PublishItems.Model -> List (Html Msg) publishResults : Texts -> UiSettings -> Flags -> Model -> Comp.PublishItems.Model -> List (Html Msg)
publishResults texts settings flags model pm = publishResults texts settings flags _ pm =
[ Html.map PublishViewMsg [ Html.map PublishViewMsg
(Comp.PublishItems.view texts.publishItems settings flags pm) (Comp.PublishItems.view texts.publishItems settings flags pm)
] ]
@ -188,9 +185,6 @@ confirmModal texts model =
itemsBar : Texts -> Flags -> UiSettings -> Model -> List (Html Msg) itemsBar : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
itemsBar texts flags settings model = itemsBar texts flags settings model =
case model.viewMode of case model.viewMode of
SimpleView ->
[ defaultMenuBar texts flags settings model ]
SearchView -> SearchView ->
[ defaultMenuBar texts flags settings model ] [ defaultMenuBar texts flags settings model ]

View File

@ -18,6 +18,7 @@ import Comp.SearchMenu
import Comp.SharePasswordForm import Comp.SharePasswordForm
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.ItemArrange exposing (ItemArrange) import Data.ItemArrange exposing (ItemArrange)
import Data.UiSettings exposing (UiSettings)
import Http import Http
import Page.Search.Data exposing (Msg(..)) import Page.Search.Data exposing (Msg(..))
import Set exposing (Set) import Set exposing (Set)
@ -53,6 +54,7 @@ type alias Model =
, initialized : Bool , initialized : Bool
, contentSearch : Maybe String , contentSearch : Maybe String
, searchMode : SearchBarMode , searchMode : SearchBarMode
, uiSettings : UiSettings
, viewMode : , viewMode :
{ menuOpen : Bool { menuOpen : Bool
, showGroups : Bool , showGroups : Bool
@ -75,6 +77,7 @@ emptyModel flags =
, initialized = False , initialized = False
, contentSearch = Nothing , contentSearch = Nothing
, searchMode = SearchBarContent , searchMode = SearchBarContent
, uiSettings = Data.UiSettings.defaults
, viewMode = , viewMode =
{ menuOpen = False { menuOpen = False
, showGroups = True , showGroups = True
@ -107,6 +110,7 @@ type Msg
= VerifyResp (Result Http.Error ShareVerifyResult) = VerifyResp (Result Http.Error ShareVerifyResult)
| SearchResp (Result Http.Error ItemLightList) | SearchResp (Result Http.Error ItemLightList)
| StatsResp Bool (Result Http.Error SearchStats) | StatsResp Bool (Result Http.Error SearchStats)
| UiSettingsResp (Result Http.Error UiSettings)
| PasswordMsg Comp.SharePasswordForm.Msg | PasswordMsg Comp.SharePasswordForm.Msg
| SearchMenuMsg Comp.SearchMenu.Msg | SearchMenuMsg Comp.SearchMenu.Msg
| PowerSearchMsg Comp.PowerSearchInput.Msg | PowerSearchMsg Comp.PowerSearchInput.Msg

View File

@ -10,7 +10,6 @@ module Page.Share.Sidebar exposing (..)
import Comp.SearchMenu import Comp.SearchMenu
import Comp.Tabs import Comp.Tabs
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Messages.Page.Share exposing (Texts) import Messages.Page.Share exposing (Texts)
@ -18,8 +17,8 @@ import Page.Share.Data exposing (Model, Msg(..))
import Util.ItemDragDrop import Util.ItemDragDrop
view : Texts -> Flags -> UiSettings -> Model -> Html Msg view : Texts -> Flags -> Model -> Html Msg
view texts flags settings model = view texts flags model =
let let
hideTrashTab tab default = hideTrashTab tab default =
case tab of case tab of
@ -41,7 +40,7 @@ view texts flags settings model =
ddDummy ddDummy
flags flags
searchMenuCfg searchMenuCfg
settings model.uiSettings
model.searchMenuModel model.searchMenuModel
) )
] ]

View File

@ -43,7 +43,10 @@ update flags settings shareId msg model =
, verifyResult = res , verifyResult = res
, searchInProgress = True , searchInProgress = True
} }
, makeSearchCmd flags True model , Cmd.batch
[ makeSearchCmd flags True model
, Api.clientSettingsShare flags res.token UiSettingsResp
]
) )
else if res.passwordRequired then else if res.passwordRequired then
@ -242,6 +245,12 @@ update flags settings shareId msg model =
in in
noSub ( { model | viewMode = next }, Cmd.none ) noSub ( { model | viewMode = next }, Cmd.none )
UiSettingsResp (Ok s) ->
noSub ( { model | uiSettings = s }, Cmd.none )
UiSettingsResp (Err _) ->
noSub ( model, Cmd.none )
noSub : ( Model, Cmd Msg ) -> UpdateResult noSub : ( Model, Cmd Msg ) -> UpdateResult
noSub ( m, c ) = noSub ( m, c ) =

View File

@ -11,11 +11,9 @@ import Api.Model.VersionInfo exposing (VersionInfo)
import Comp.Basic as B import Comp.Basic as B
import Comp.SharePasswordForm import Comp.SharePasswordForm
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.Items
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onInput, onSubmit)
import Messages.Page.Share exposing (Texts) import Messages.Page.Share exposing (Texts)
import Page.Share.Data exposing (..) import Page.Share.Data exposing (..)
import Page.Share.Menubar as Menubar import Page.Share.Menubar as Menubar
@ -25,19 +23,19 @@ import Styles as S
viewSidebar : Texts -> Bool -> Flags -> UiSettings -> Model -> Html Msg viewSidebar : Texts -> Bool -> Flags -> UiSettings -> Model -> Html Msg
viewSidebar texts visible flags settings model = viewSidebar texts visible flags _ model =
div div
[ id "sidebar" [ id "sidebar"
, class S.sidebar , class S.sidebar
, class S.sidebarBg , class S.sidebarBg
, classList [ ( "hidden", not visible || model.mode /= ModeShare ) ] , classList [ ( "hidden", not visible || model.mode /= ModeShare ) ]
] ]
[ Sidebar.view texts flags settings model [ Sidebar.view texts flags model
] ]
viewContent : Texts -> Flags -> VersionInfo -> UiSettings -> String -> Model -> Html Msg viewContent : Texts -> Flags -> VersionInfo -> UiSettings -> String -> Model -> Html Msg
viewContent texts flags versionInfo uiSettings shareId model = viewContent texts flags versionInfo _ shareId model =
case model.mode of case model.mode of
ModeInitial -> ModeInitial ->
div div
@ -60,15 +58,15 @@ viewContent texts flags versionInfo uiSettings shareId model =
passwordContent texts flags versionInfo model passwordContent texts flags versionInfo model
ModeShare -> ModeShare ->
mainContent texts flags uiSettings shareId model mainContent texts flags shareId model
--- Helpers --- Helpers
mainContent : Texts -> Flags -> UiSettings -> String -> Model -> Html Msg mainContent : Texts -> Flags -> String -> Model -> Html Msg
mainContent texts flags settings shareId model = mainContent texts flags shareId model =
div div
[ id "content" [ id "content"
, class "h-full flex flex-col" , class "h-full flex flex-col"
@ -82,7 +80,7 @@ mainContent texts flags settings shareId model =
] ]
, Menubar.view texts flags model , Menubar.view texts flags model
, errorMessage texts model , errorMessage texts model
, Results.view texts settings flags shareId model , Results.view texts model.uiSettings flags shareId model
] ]

View File

@ -14,6 +14,7 @@ import Api.Model.ShareVerifyResult exposing (ShareVerifyResult)
import Comp.SharePasswordForm import Comp.SharePasswordForm
import Comp.UrlCopy import Comp.UrlCopy
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Http import Http
@ -37,12 +38,14 @@ type alias Model =
, pageError : PageError , pageError : PageError
, attachMenuOpen : Bool , attachMenuOpen : Bool
, visibleAttach : Int , visibleAttach : Int
, uiSettings : UiSettings
} }
type Msg type Msg
= VerifyResp (Result Http.Error ShareVerifyResult) = VerifyResp (Result Http.Error ShareVerifyResult)
| GetItemResp (Result Http.Error ItemDetail) | GetItemResp (Result Http.Error ItemDetail)
| UiSettingsResp (Result Http.Error UiSettings)
| PasswordMsg Comp.SharePasswordForm.Msg | PasswordMsg Comp.SharePasswordForm.Msg
| SelectActiveAttachment Int | SelectActiveAttachment Int
| ToggleSelectAttach | ToggleSelectAttach
@ -58,6 +61,7 @@ emptyModel vm =
, pageError = PageErrorNone , pageError = PageErrorNone
, attachMenuOpen = False , attachMenuOpen = False
, visibleAttach = 0 , visibleAttach = 0
, uiSettings = Data.UiSettings.defaults
} }

View File

@ -25,7 +25,10 @@ update shareId itemId flags msg model =
, viewMode = ViewLoading , viewMode = ViewLoading
, verifyResult = res , verifyResult = res
} }
, Api.itemDetailShare flags res.token itemId GetItemResp , Cmd.batch
[ Api.itemDetailShare flags res.token itemId GetItemResp
, Api.clientSettingsShare flags res.token UiSettingsResp
]
) )
else if res.passwordRequired then else if res.passwordRequired then
@ -93,3 +96,9 @@ update shareId itemId flags msg model =
Comp.UrlCopy.update lm Comp.UrlCopy.update lm
in in
( model, cmd ) ( model, cmd )
UiSettingsResp (Ok s) ->
( { model | uiSettings = s }, Cmd.none )
UiSettingsResp (Err _) ->
( model, Cmd.none )

View File

@ -16,7 +16,7 @@ import Comp.UrlCopy
import Data.Fields import Data.Fields
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.Icons as Icons import Data.Icons as Icons
import Data.ItemTemplate as IT exposing (ItemTemplate) import Data.ItemTemplate as IT
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@ -33,7 +33,7 @@ import Util.String
viewSidebar : Texts -> Bool -> Flags -> UiSettings -> String -> String -> Model -> Html Msg viewSidebar : Texts -> Bool -> Flags -> UiSettings -> String -> String -> Model -> Html Msg
viewSidebar texts visible flags settings shareId itemId model = viewSidebar texts visible flags _ shareId itemId model =
div div
[ id "sidebar" [ id "sidebar"
, classList [ ( "hidden", not visible || model.viewMode /= ViewNormal ) ] , classList [ ( "hidden", not visible || model.viewMode /= ViewNormal ) ]
@ -103,6 +103,7 @@ itemData texts flags model shareId itemId =
div div
[ class "flex ml-2 mt-1 font-semibold hover:opacity-75" [ class "flex ml-2 mt-1 font-semibold hover:opacity-75"
, class S.basicLabel , class S.basicLabel
, class (Data.UiSettings.tagColorString2 tag model.uiSettings)
] ]
[ i [ class "fa fa-tag mr-2" ] [] [ i [ class "fa fa-tag mr-2" ] []
, text tag.name , text tag.name

View File

@ -42,10 +42,10 @@ type alias Model =
init : Flags -> UiSettings -> ( Model, Cmd Msg ) init : Flags -> UiSettings -> ( Model, Cmd Msg )
init flags settings = init flags _ =
let let
( um, uc ) = ( um, uc ) =
Comp.UiSettingsManage.init flags settings Comp.UiSettingsManage.init flags
( otpm, otpc ) = ( otpm, otpc ) =
Comp.OtpSetup.init flags Comp.OtpSetup.init flags
@ -107,5 +107,3 @@ type Msg
| NotificationHookMsg Comp.NotificationHookManage.Msg | NotificationHookMsg Comp.NotificationHookManage.Msg
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg | PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
| ChannelMsg Comp.NotificationChannelManage.Msg | ChannelMsg Comp.NotificationChannelManage.Msg
| UpdateSettings
| ReceiveBrowserSettings StoredUiSettings

View File

@ -17,6 +17,7 @@ import Comp.OtpSetup
import Comp.PeriodicQueryTaskManage import Comp.PeriodicQueryTaskManage
import Comp.ScanMailboxManage import Comp.ScanMailboxManage
import Comp.UiSettingsManage import Comp.UiSettingsManage
import Data.AppEvent exposing (AppEvent(..))
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (UiSettings)
import Page.UserSettings.Data exposing (..) import Page.UserSettings.Data exposing (..)
@ -26,10 +27,15 @@ type alias UpdateResult =
{ model : Model { model : Model
, cmd : Cmd Msg , cmd : Cmd Msg
, sub : Sub Msg , sub : Sub Msg
, newSettings : Maybe UiSettings , appEvent : AppEvent
} }
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none AppNothing
update : Flags -> UiSettings -> Msg -> Model -> UpdateResult update : Flags -> UiSettings -> Msg -> Model -> UpdateResult
update flags settings msg model = update flags settings msg model =
case msg of case msg of
@ -47,7 +53,7 @@ update flags settings msg model =
{ model = { m | emailSettingsModel = em } { model = { m | emailSettingsModel = em }
, cmd = Cmd.map EmailSettingsMsg c , cmd = Cmd.map EmailSettingsMsg c
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
ImapSettingsTab -> ImapSettingsTab ->
@ -58,18 +64,14 @@ update flags settings msg model =
{ model = { m | imapSettingsModel = em } { model = { m | imapSettingsModel = em }
, cmd = Cmd.map ImapSettingsMsg c , cmd = Cmd.map ImapSettingsMsg c
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
ChangePassTab -> ChangePassTab ->
UpdateResult m Cmd.none Sub.none Nothing unit m
NotificationTab -> NotificationTab ->
{ model = m unit m
, cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing
}
NotificationWebhookTab -> NotificationWebhookTab ->
let let
@ -79,7 +81,7 @@ update flags settings msg model =
{ model = m { model = m
, cmd = Cmd.map NotificationHookMsg nc , cmd = Cmd.map NotificationHookMsg nc
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
NotificationQueriesTab -> NotificationQueriesTab ->
@ -88,7 +90,7 @@ update flags settings msg model =
Cmd.map NotificationMsg Cmd.map NotificationMsg
(Tuple.second (Comp.DueItemsTaskManage.init flags)) (Tuple.second (Comp.DueItemsTaskManage.init flags))
in in
UpdateResult m initCmd Sub.none Nothing UpdateResult m initCmd Sub.none AppNothing
NotificationDueItemsTab -> NotificationDueItemsTab ->
let let
@ -96,7 +98,7 @@ update flags settings msg model =
Cmd.map NotificationMsg Cmd.map NotificationMsg
(Tuple.second (Comp.DueItemsTaskManage.init flags)) (Tuple.second (Comp.DueItemsTaskManage.init flags))
in in
UpdateResult m initCmd Sub.none Nothing UpdateResult m initCmd Sub.none AppNothing
ScanMailboxTab -> ScanMailboxTab ->
let let
@ -104,16 +106,24 @@ update flags settings msg model =
Cmd.map ScanMailboxMsg Cmd.map ScanMailboxMsg
(Tuple.second (Comp.ScanMailboxManage.init flags)) (Tuple.second (Comp.ScanMailboxManage.init flags))
in in
UpdateResult m initCmd Sub.none Nothing UpdateResult m initCmd Sub.none AppNothing
UiSettingsTab -> UiSettingsTab ->
UpdateResult m Cmd.none Sub.none Nothing let
( um, uc ) =
Comp.UiSettingsManage.init flags
in
{ model = { m | uiSettingsModel = um }
, cmd = Cmd.map UiSettingsMsg uc
, sub = Sub.none
, appEvent = AppNothing
}
OtpTab -> OtpTab ->
UpdateResult m Cmd.none Sub.none Nothing unit m
ChannelTab -> ChannelTab ->
UpdateResult m Cmd.none Sub.none Nothing unit m
ChangePassMsg m -> ChangePassMsg m ->
let let
@ -123,7 +133,7 @@ update flags settings msg model =
{ model = { model | changePassModel = m2 } { model = { model | changePassModel = m2 }
, cmd = Cmd.map ChangePassMsg c2 , cmd = Cmd.map ChangePassMsg c2
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
EmailSettingsMsg m -> EmailSettingsMsg m ->
@ -134,7 +144,7 @@ update flags settings msg model =
{ model = { model | emailSettingsModel = m2 } { model = { model | emailSettingsModel = m2 }
, cmd = Cmd.map EmailSettingsMsg c2 , cmd = Cmd.map EmailSettingsMsg c2
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
ImapSettingsMsg m -> ImapSettingsMsg m ->
@ -145,7 +155,7 @@ update flags settings msg model =
{ model = { model | imapSettingsModel = m2 } { model = { model | imapSettingsModel = m2 }
, cmd = Cmd.map ImapSettingsMsg c2 , cmd = Cmd.map ImapSettingsMsg c2
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
NotificationMsg lm -> NotificationMsg lm ->
@ -156,7 +166,7 @@ update flags settings msg model =
{ model = { model | notificationModel = m2 } { model = { model | notificationModel = m2 }
, cmd = Cmd.map NotificationMsg c2 , cmd = Cmd.map NotificationMsg c2
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
ScanMailboxMsg lm -> ScanMailboxMsg lm ->
@ -167,7 +177,7 @@ update flags settings msg model =
{ model = { model | scanMailboxModel = m2 } { model = { model | scanMailboxModel = m2 }
, cmd = Cmd.map ScanMailboxMsg c2 , cmd = Cmd.map ScanMailboxMsg c2
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
UiSettingsMsg lm -> UiSettingsMsg lm ->
@ -178,7 +188,7 @@ update flags settings msg model =
{ model = { model | uiSettingsModel = res.model } { model = { model | uiSettingsModel = res.model }
, cmd = Cmd.map UiSettingsMsg res.cmd , cmd = Cmd.map UiSettingsMsg res.cmd
, sub = Sub.map UiSettingsMsg res.sub , sub = Sub.map UiSettingsMsg res.sub
, newSettings = res.newSettings , appEvent = res.appEvent
} }
OtpSetupMsg lm -> OtpSetupMsg lm ->
@ -189,7 +199,7 @@ update flags settings msg model =
{ model = { model | otpSetupModel = otpm } { model = { model | otpSetupModel = otpm }
, cmd = Cmd.map OtpSetupMsg otpc , cmd = Cmd.map OtpSetupMsg otpc
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
NotificationHookMsg lm -> NotificationHookMsg lm ->
@ -200,7 +210,7 @@ update flags settings msg model =
{ model = { model | notificationHookModel = hm } { model = { model | notificationHookModel = hm }
, cmd = Cmd.map NotificationHookMsg hc , cmd = Cmd.map NotificationHookMsg hc
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
ChannelMsg lm -> ChannelMsg lm ->
@ -211,22 +221,9 @@ update flags settings msg model =
{ model = { model | channelModel = cm } { model = { model | channelModel = cm }
, cmd = Cmd.map ChannelMsg cc , cmd = Cmd.map ChannelMsg cc
, sub = Sub.none , sub = Sub.none
, newSettings = Nothing , appEvent = AppNothing
} }
UpdateSettings ->
update flags
settings
(UiSettingsMsg Comp.UiSettingsManage.UpdateSettings)
model
ReceiveBrowserSettings sett ->
let
lm =
Comp.UiSettingsManage.ReceiveBrowserSettings sett
in
update flags settings (UiSettingsMsg lm) model
PeriodicQueryMsg lm -> PeriodicQueryMsg lm ->
let let
( pqm, pqc, pqs ) = ( pqm, pqc, pqs ) =
@ -235,5 +232,5 @@ update flags settings msg model =
{ model = { model | periodicQueryModel = pqm } { model = { model | periodicQueryModel = pqm }
, cmd = Cmd.map PeriodicQueryMsg pqc , cmd = Cmd.map PeriodicQueryMsg pqc
, sub = Sub.map PeriodicQueryMsg pqs , sub = Sub.map PeriodicQueryMsg pqs
, newSettings = Nothing , appEvent = AppNothing
} }

View File

@ -11,9 +11,7 @@ port module Ports exposing
, printElement , printElement
, receiveCheckQueryResult , receiveCheckQueryResult
, receiveServerEvent , receiveServerEvent
, receiveUiSettings
, removeAccount , removeAccount
, requestUiSettings
, setAccount , setAccount
, setUiTheme , setUiTheme
) )
@ -21,7 +19,6 @@ port module Ports exposing
import Api.Model.AuthResult exposing (AuthResult) import Api.Model.AuthResult exposing (AuthResult)
import Data.QueryParseResult exposing (QueryParseResult) import Data.QueryParseResult exposing (QueryParseResult)
import Data.ServerEvent exposing (ServerEvent) import Data.ServerEvent exposing (ServerEvent)
import Data.UiSettings exposing (StoredUiSettings)
import Data.UiTheme exposing (UiTheme) import Data.UiTheme exposing (UiTheme)
import Json.Decode as D import Json.Decode as D
@ -46,12 +43,6 @@ port receiveCheckQueryResult : (QueryParseResult -> msg) -> Sub msg
port initClipboard : ( String, String ) -> Cmd msg port initClipboard : ( String, String ) -> Cmd msg
port receiveUiSettings : (StoredUiSettings -> msg) -> Sub msg
port requestUiSettings : AuthResult -> Cmd msg
{-| Creates a new window/tab, writes the contents of the given element {-| Creates a new window/tab, writes the contents of the given element
and calls the print dialog. and calls the print dialog.
-} -}

View File

@ -55,25 +55,6 @@ elmApp.ports.removeAccount.subscribe(function() {
closeWS(); closeWS();
}); });
elmApp.ports.requestUiSettings.subscribe(function(args) {
console.log("Requesting ui settings");
var account = args;
var collective = account ? account.collective : null;
var user = account ? account.user : null;
if (collective && user) {
var key = collective + "/" + user + "/uiSettings";
var settings = localStorage.getItem(key);
try {
var data = settings ? JSON.parse(settings) : null;
if (data) {
console.log("Sending browser ui settings");
elmApp.ports.receiveUiSettings.send(data);
}
} catch (error) {
console.log(error);
}
}
});
var docspell_clipboards = {}; var docspell_clipboards = {};