diff --git a/modules/webapp/src/main/elm/Api.elm b/modules/webapp/src/main/elm/Api.elm index 5a5cff99..bb989e83 100644 --- a/modules/webapp/src/main/elm/Api.elm +++ b/modules/webapp/src/main/elm/Api.elm @@ -64,6 +64,7 @@ module Api exposing , getChannels , getChannelsIgnoreError , getClientSettings + , getClientSettingsRaw , getCollective , getCollectiveSettings , getContacts @@ -136,6 +137,7 @@ module Api exposing , restoreItem , sampleEvent , saveClientSettings + , saveUserClientSettingsBy , searchShare , searchShareStats , sendMail @@ -296,7 +298,7 @@ import Data.OrganizationOrder exposing (OrganizationOrder) import Data.PersonOrder exposing (PersonOrder) import Data.Priority exposing (Priority) import Data.TagOrder exposing (TagOrder) -import Data.UiSettings exposing (UiSettings) +import Data.UiSettings exposing (StoredUiSettings, UiSettings) import File exposing (File) import Http import Json.Decode as JsonDecode @@ -2335,6 +2337,13 @@ getItemProposals flags item receive = --- 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 receive = let @@ -2346,29 +2355,91 @@ getClientSettings flags receive = Data.UiSettings.storedUiSettingsDecoder in Http2.authGet - { url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClient" + { url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/webClient" , account = getAccount flags , expect = Http.expectJson receive decoder } -saveClientSettings : Flags -> UiSettings -> (Result Http.Error BasicResult -> msg) -> Cmd msg -saveClientSettings flags settings receive = +getClientSettingsTaskFor : Flags -> AccountScope -> Task.Task Http.Error StoredUiSettings +getClientSettingsTaskFor flags scope = let - storedSettings = - Data.UiSettings.toStoredUiSettings settings - - encode = - Data.UiSettings.storedUiSettingsEncode storedSettings + path = + uiSettingsPath scope in - Http2.authPut - { url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/user/webClient" + Http2.authTask + { method = "GET" + , url = flags.config.baseUrl ++ path , account = getAccount flags - , body = Http.jsonBody encode - , expect = Http.expectJson receive Api.Model.BasicResult.decoder + , body = Http.emptyBody + , 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 diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index 0facd89b..aef87620 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -204,8 +204,7 @@ type Msg | ToggleDarkMode | ToggleLangMenu | SetLanguage UiLanguage - | ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) - | ReceiveBrowserSettings StoredUiSettings + | ClientSettingsSaveResp (Result Http.Error BasicResult) | ReceiveWsMessage (Result String ServerEvent) | ToggleShowNewItemsArrived diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index 268e05cf..48e2621d 100644 --- a/modules/webapp/src/main/elm/App/Update.elm +++ b/modules/webapp/src/main/elm/App/Update.elm @@ -14,6 +14,7 @@ import Api import App.Data exposing (..) import Browser exposing (UrlRequest(..)) import Browser.Navigation as Nav +import Data.AppEvent exposing (AppEvent(..)) import Data.Flags import Data.ServerEvent exposing (ServerEvent(..)) import Data.UiSettings exposing (UiSettings) @@ -81,15 +82,15 @@ updateWithSub msg model = next = Data.UiTheme.cycle settings.uiTheme - newSettings = - { settings | uiTheme = next } + newSettings s = + { s | uiTheme = Just (Data.UiTheme.toString next) } in -- when authenticated, store it in settings only -- once new settings are successfully saved (the -- response is arrived), the ui is updated. so it -- is also updated on page refresh ( { model | userMenuOpen = False } - , Api.saveClientSettings model.flags newSettings (ClientSettingsSaveResp newSettings) + , Api.saveUserClientSettingsBy model.flags newSettings ClientSettingsSaveResp , Sub.none ) @@ -104,14 +105,14 @@ updateWithSub msg model = , Sub.none ) - ClientSettingsSaveResp settings (Ok res) -> + ClientSettingsSaveResp (Ok res) -> if res.success then - applyClientSettings texts model settings + ( model, Api.getClientSettings model.flags GetUiSettings, Sub.none ) else ( model, Cmd.none, Sub.none ) - ClientSettingsSaveResp _ (Err _) -> + ClientSettingsSaveResp (Err _) -> ( model, Cmd.none, Sub.none ) ToggleLangMenu -> @@ -307,13 +308,6 @@ updateWithSub msg model = GetUiSettings (Err _) -> ( model, Cmd.none, Sub.none ) - ReceiveBrowserSettings sett -> - let - lm = - Page.UserSettings.Data.ReceiveBrowserSettings sett - in - updateUserSettings texts lm model - ReceiveWsMessage data -> case data of Ok (JobDone task) -> @@ -342,7 +336,7 @@ updateWithSub msg model = Ok (JobsWaiting n) -> ( { model | jobsWaiting = max 0 n }, Cmd.none, Sub.none ) - Err err -> + Err _ -> ( model, Cmd.none, Sub.none ) ToggleShowNewItemsArrived -> @@ -368,7 +362,6 @@ applyClientSettings texts model settings = , Sub.none ) , updateDashboard texts Page.Dashboard.Data.reloadUiSettings - , updateUserSettings texts Page.UserSettings.Data.UpdateSettings , updateSearch texts Page.Search.Data.UiSettingsUpdated , updateItemDetail texts Page.ItemDetail.Data.UiSettingsUpdated ] @@ -524,22 +517,21 @@ updateUserSettings texts lmsg model = model_ = { model | userSettingsModel = result.model } - ( lm2, lc2, s2 ) = - case result.newSettings of - Just sett -> - applyClientSettings texts model_ sett + lc = + case result.appEvent of + AppReloadUiSettings -> + Api.getClientSettings model.flags GetUiSettings - Nothing -> - ( model_, Cmd.none, Sub.none ) + AppNothing -> + Cmd.none in - ( lm2 + ( model_ , Cmd.batch [ Cmd.map UserSettingsMsg result.cmd - , lc2 + , lc ] , Sub.batch [ Sub.map UserSettingsMsg result.sub - , s2 ] ) @@ -595,22 +587,21 @@ updateSearch texts lmsg model = model_ = { model | searchModel = result.model } - ( lm, lc, ls ) = - case result.newSettings of - Just sett -> - applyClientSettings texts model_ sett + lc = + case result.appEvent of + AppReloadUiSettings -> + Api.getClientSettings model.flags GetUiSettings - Nothing -> - ( model_, Cmd.none, Sub.none ) + AppNothing -> + Cmd.none in - ( lm + ( model_ , Cmd.batch [ Cmd.map SearchMsg result.cmd , lc ] , Sub.batch [ Sub.map SearchMsg result.sub - , ls ] ) diff --git a/modules/webapp/src/main/elm/Comp/MenuBar.elm b/modules/webapp/src/main/elm/Comp/MenuBar.elm index b5819ae2..825b9bcd 100644 --- a/modules/webapp/src/main/elm/Comp/MenuBar.elm +++ b/modules/webapp/src/main/elm/Comp/MenuBar.elm @@ -26,6 +26,7 @@ import Styles as S type Item msg = TextInput (TextInputData msg) | Checkbox (CheckboxData msg) + | RadioButton (CheckboxData msg) | PrimaryButton (ButtonData msg) | SecondaryButton (ButtonData msg) | DeleteButton (ButtonData msg) @@ -119,7 +120,7 @@ view1 classes mb = (List.map viewItem mb.start) 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) in div @@ -139,7 +140,10 @@ viewItem item = makeInput model Checkbox model -> - makeCheckbox model + makeCheckbox False model + + RadioButton model -> + makeCheckbox True model PrimaryButton model -> makeButton [ ( S.primaryButton, True ) ] model @@ -306,8 +310,8 @@ makeButton btnType model = (icon ++ label) -makeCheckbox : CheckboxData msg -> Html msg -makeCheckbox model = +makeCheckbox : Bool -> CheckboxData msg -> Html msg +makeCheckbox radio model = let withId list = if model.id == "" then @@ -315,6 +319,13 @@ makeCheckbox model = else id model.id :: list + + fold rd ck = + if radio then + rd + + else + ck in div [ class "" ] [ label @@ -323,10 +334,10 @@ makeCheckbox model = ] [ input (withId - [ type_ "checkbox" + [ type_ (fold "radio" "checkbox") , onCheck model.tagger , checked model.value - , class S.checkboxInput + , class (fold S.radioInput S.checkboxInput) ] ) [] diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm index ecfb6781..16eb1851 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsForm.elm @@ -9,6 +9,7 @@ module Comp.UiSettingsForm exposing ( Model , Msg , init + , toggleAllTabs , update , view2 ) @@ -30,7 +31,7 @@ import Data.Flags exposing (Flags) import Data.ItemTemplate as IT exposing (ItemTemplate) import Data.Pdf exposing (PdfMode) import Data.TagOrder -import Data.UiSettings exposing (ItemPattern, Pos(..), UiSettings) +import Data.UiSettings exposing (ItemPattern, Pos(..), StoredUiSettings, UiSettings) import Dict exposing (Dict) import Html exposing (..) import Html.Attributes exposing (..) @@ -73,6 +74,7 @@ type alias Model = , uiLangModel : Comp.FixedDropdown.Model UiLanguage , uiLang : UiLanguage , openTabs : Set String + , defaults : UiSettings } @@ -111,61 +113,69 @@ updatePatternModel pm str = } -init : Flags -> UiSettings -> ( Model, Cmd Msg ) -init flags settings = - ( { itemSearchPageSize = Just settings.itemSearchPageSize - , searchPageSizeModel = - Comp.IntField.init - (Just 10) - (Just flags.config.maxPageSize) - False - , tagColors = settings.tagCategoryColors - , tagColorModel = - Comp.ColorTagger.init - [] - Data.Color.all - , pdfMode = settings.pdfMode - , pdfModeModel = Comp.FixedDropdown.init Data.Pdf.allModes - , itemSearchNoteLength = Just settings.itemSearchNoteLength - , searchNoteLengthModel = - Comp.IntField.init - (Just 0) - (Just flags.config.maxNoteLength) - False - , searchMenuFolderCount = Just settings.searchMenuFolderCount - , searchMenuFolderCountModel = - Comp.IntField.init - (Just 0) - (Just 2000) - False - , searchMenuTagCount = Just settings.searchMenuTagCount - , searchMenuTagCountModel = - Comp.IntField.init - (Just 0) - (Just 2000) - False - , searchMenuTagCatCount = Just settings.searchMenuTagCatCount - , searchMenuTagCatCountModel = - Comp.IntField.init - (Just 0) - (Just 2000) - False - , formFields = settings.formFields - , itemDetailShortcuts = settings.itemDetailShortcuts - , cardPreviewSize = settings.cardPreviewSize - , cardTitlePattern = initPatternModel settings.cardTitleTemplate - , cardSubtitlePattern = initPatternModel settings.cardSubtitleTemplate - , showPatternHelp = False - , searchStatsVisible = settings.searchStatsVisible - , sideMenuVisible = settings.sideMenuVisible - , powerSearchEnabled = settings.powerSearchEnabled - , uiLang = settings.uiLang - , uiLangModel = - Comp.FixedDropdown.init Messages.UiLanguage.all - , openTabs = Set.empty - } - , Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp - ) +initModel : Flags -> StoredUiSettings -> UiSettings -> Model +initModel flags storedSettings defaults = + let + settings = + Data.UiSettings.merge storedSettings defaults + in + { itemSearchPageSize = Just settings.itemSearchPageSize + , searchPageSizeModel = + Comp.IntField.init + (Just 10) + (Just flags.config.maxPageSize) + False + , tagColors = settings.tagCategoryColors + , tagColorModel = + Comp.ColorTagger.init + [] + Data.Color.all + , pdfMode = settings.pdfMode + , pdfModeModel = Comp.FixedDropdown.init Data.Pdf.allModes + , itemSearchNoteLength = Just settings.itemSearchNoteLength + , searchNoteLengthModel = + Comp.IntField.init + (Just 0) + (Just flags.config.maxNoteLength) + False + , searchMenuFolderCount = Just settings.searchMenuFolderCount + , searchMenuFolderCountModel = + Comp.IntField.init + (Just 0) + (Just 2000) + False + , searchMenuTagCount = Just settings.searchMenuTagCount + , searchMenuTagCountModel = + Comp.IntField.init + (Just 0) + (Just 2000) + False + , searchMenuTagCatCount = Just settings.searchMenuTagCatCount + , searchMenuTagCatCountModel = + Comp.IntField.init + (Just 0) + (Just 2000) + False + , formFields = settings.formFields + , itemDetailShortcuts = settings.itemDetailShortcuts + , cardPreviewSize = settings.cardPreviewSize + , cardTitlePattern = initPatternModel settings.cardTitleTemplate + , cardSubtitlePattern = initPatternModel settings.cardSubtitleTemplate + , showPatternHelp = False + , searchStatsVisible = settings.searchStatsVisible + , sideMenuVisible = settings.sideMenuVisible + , powerSearchEnabled = settings.powerSearchEnabled + , uiLang = settings.uiLang + , uiLangModel = + Comp.FixedDropdown.init Messages.UiLanguage.all + , openTabs = Set.empty + , defaults = defaults + } + + +init : Flags -> StoredUiSettings -> UiSettings -> ( Model, Cmd Msg ) +init flags storedSettings defaults = + ( initModel flags storedSettings defaults, Api.getTags flags "" Data.TagOrder.NameAsc GetTagsResp ) type Msg @@ -188,14 +198,61 @@ type Msg | TogglePowerSearch | UiLangMsg (Comp.FixedDropdown.Msg UiLanguage) | 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 : UiSettings -> Msg -> Model -> ( Model, Maybe UiSettings ) -update sett msg model = +update : Flags -> StoredUiSettings -> Msg -> Model -> ( Model, Maybe StoredUiSettings ) +update flags sett msg model = case msg of SearchPageSizeMsg lm -> let @@ -203,7 +260,7 @@ update sett msg model = Comp.IntField.update lm model.searchPageSizeModel nextSettings = - Maybe.map (\sz -> { sett | itemSearchPageSize = sz }) n + Maybe.map (\sz -> { sett | itemSearchPageSize = Just sz }) n model_ = { model @@ -219,7 +276,7 @@ update sett msg model = Comp.IntField.update lm model.searchNoteLengthModel nextSettings = - Maybe.map (\len -> { sett | itemSearchNoteLength = len }) n + Maybe.map (\len -> { sett | itemSearchNoteLength = Just len }) n model_ = { model @@ -235,7 +292,7 @@ update sett msg model = Comp.IntField.update lm model.searchMenuFolderCountModel nextSettings = - Maybe.map (\len -> { sett | searchMenuFolderCount = len }) n + Maybe.map (\len -> { sett | searchMenuFolderCount = Just len }) n model_ = { model @@ -251,7 +308,7 @@ update sett msg model = Comp.IntField.update lm model.searchMenuTagCountModel nextSettings = - Maybe.map (\len -> { sett | searchMenuTagCount = len }) n + Maybe.map (\len -> { sett | searchMenuTagCount = Just len }) n model_ = { model @@ -267,7 +324,7 @@ update sett msg model = Comp.IntField.update lm model.searchMenuTagCatCountModel nextSettings = - Maybe.map (\len -> { sett | searchMenuTagCatCount = len }) n + Maybe.map (\len -> { sett | searchMenuTagCatCount = Just len }) n model_ = { model @@ -282,8 +339,13 @@ update sett msg model = ( m_, d_ ) = Comp.ColorTagger.update lm model.tagColorModel + colors dict = + Dict.map (\_ -> Data.Color.toString) dict + |> Dict.toList + |> Just + nextSettings = - Maybe.map (\tc -> { sett | tagCategoryColors = tc }) d_ + Maybe.map (\tc -> { sett | tagCategoryColors = colors tc }) d_ model_ = { model @@ -316,7 +378,11 @@ update sett msg model = Comp.FieldListSelect.update lm model.formFields newSettings = - { sett | formFields = selected } + { sett + | formFields = + List.map Data.Fields.toString selected + |> Just + } in ( { model | formFields = selected } , if selected /= model.formFields then @@ -332,7 +398,7 @@ update sett msg model = not model.itemDetailShortcuts in ( { model | itemDetailShortcuts = flag } - , Just { sett | itemDetailShortcuts = flag } + , Just { sett | itemDetailShortcuts = Just flag } ) CardPreviewSizeMsg lm -> @@ -343,7 +409,13 @@ update sett msg model = newSettings = if next /= model.cardPreviewSize then - Just { sett | cardPreviewSize = next } + Just + { sett + | cardPreviewSize = + next + |> Data.BasicSize.asString + |> Just + } else Nothing @@ -361,14 +433,8 @@ update sett msg model = updatePatternModel pm str newSettings = - if pm_.pattern /= Just sett.cardTitleTemplate.pattern then - Just - { sett - | cardTitleTemplate = - ItemPattern - (Maybe.withDefault "" pm_.pattern) - pm_.current - } + if pm_.pattern /= sett.cardTitleTemplate then + Just { sett | cardTitleTemplate = pm_.pattern } else Nothing @@ -384,14 +450,8 @@ update sett msg model = updatePatternModel pm str newSettings = - if pm_.pattern /= Just sett.cardSubtitleTemplate.pattern then - Just - { sett - | cardSubtitleTemplate = - ItemPattern - (Maybe.withDefault "" pm_.pattern) - pm_.current - } + if pm_.pattern /= sett.cardSubtitleTemplate then + Just { sett | cardSubtitleTemplate = pm_.pattern } else Nothing @@ -407,9 +467,21 @@ update sett msg model = not model.searchStatsVisible in ( { 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 -> let tabs = @@ -429,7 +501,7 @@ update sett msg model = not model.sideMenuVisible in ( { model | sideMenuVisible = next } - , Just { sett | sideMenuVisible = next } + , Just { sett | sideMenuVisible = Just next } ) TogglePowerSearch -> @@ -438,7 +510,7 @@ update sett msg model = not model.powerSearchEnabled in ( { model | powerSearchEnabled = next } - , Just { sett | powerSearchEnabled = next } + , Just { sett | powerSearchEnabled = Just next } ) UiLangMsg lm -> @@ -454,7 +526,7 @@ update sett msg model = Nothing else - Just { sett | uiLang = newLang } + Just { sett | uiLang = Just (Messages.toIso2 newLang) } ) PdfModeMsg lm -> @@ -470,9 +542,53 @@ update sett msg model = Nothing 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 } + + -- no reset here + FieldsTab -> + { sett | formFields = Nothing } + + nm = + initModel flags newSettings model.defaults + in + ( { nm | openTabs = model.openTabs }, Just newSettings ) + --- View2 @@ -495,7 +611,7 @@ tagColorViewOpts2 texts = } -view2 : Texts -> Flags -> UiSettings -> Model -> Html Msg +view2 : Texts -> Flags -> StoredUiSettings -> Model -> Html Msg view2 texts flags settings model = let state tab = @@ -517,7 +633,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 = let langCfg = @@ -533,10 +649,21 @@ settingFormTabs texts flags _ model = , style = DS.mainStyle , 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 "Reset" + ] in - [ { name = "general" + [ { name = akkordionTabName GeneralTab , title = texts.general - , titleRight = [] + , titleRight = [ resetLink GeneralTab ] , info = Nothing , body = [ div [ class "mb-4 " ] @@ -560,9 +687,9 @@ settingFormTabs texts flags _ model = ] ] } - , { name = "item-search" + , { name = akkordionTabName SearchTab , title = texts.itemSearch - , titleRight = [] + , titleRight = [ resetLink SearchTab ] , info = Nothing , body = [ Html.map SearchPageSizeMsg @@ -594,9 +721,9 @@ settingFormTabs texts flags _ model = ] ] } - , { name = "item-cards" + , { name = akkordionTabName CardsTab , title = texts.itemCards - , titleRight = [] + , titleRight = [ resetLink CardsTab ] , info = Nothing , body = [ Html.map NoteLengthMsg @@ -666,9 +793,9 @@ settingFormTabs texts flags _ model = texts.templateHelpMessage ] } - , { name = "search-menu" + , { name = akkordionTabName SearchMenuTab , title = texts.searchMenu - , titleRight = [] + , titleRight = [ resetLink SearchMenuTab ] , info = Nothing , body = [ Html.map SearchMenuTagMsg @@ -700,9 +827,9 @@ settingFormTabs texts flags _ model = ) ] } - , { name = "item-detail" + , { name = akkordionTabName DetailTab , title = texts.itemDetail - , titleRight = [] + , titleRight = [ resetLink DetailTab ] , info = Nothing , body = [ div [ class "mb-4" ] @@ -726,9 +853,9 @@ settingFormTabs texts flags _ model = ] ] } - , { name = "tag-category-colors" + , { name = akkordionTabName TagsTab , title = texts.tagCategoryColors - , titleRight = [] + , titleRight = [ resetLink TagsTab ] , info = Nothing , body = [ Html.map TagColorMsg @@ -739,9 +866,9 @@ settingFormTabs texts flags _ model = ) ] } - , { name = "fields" + , { name = akkordionTabName FieldsTab , title = texts.fields - , titleRight = [] + , titleRight = [ resetLink FieldsTab ] , info = Nothing , body = [ span [ class "opacity-50 text-sm" ] diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm index c1a96ecc..9bd02222 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm @@ -16,26 +16,42 @@ module Comp.UiSettingsManage exposing import Api import Api.Model.BasicResult exposing (BasicResult) +import Comp.Basic import Comp.MenuBar as MB import Comp.UiSettingsForm -import Comp.UiSettingsMigrate +import Data.AccountScope exposing (AccountScope) +import Data.AppEvent exposing (AppEvent(..)) import Data.Flags exposing (Flags) import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Html exposing (..) import Html.Attributes exposing (..) import Http import Messages.Comp.UiSettingsManage exposing (Texts) +import Page.Search.Data exposing (Msg(..)) import Styles as S type alias Model = - { formModel : Comp.UiSettingsForm.Model - , settings : Maybe UiSettings + { formModel : FormView , 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 = FormInit | FormUnchanged @@ -45,35 +61,39 @@ type FormResult type Msg - = UiSettingsFormMsg Comp.UiSettingsForm.Msg - | UiSettingsMigrateMsg Comp.UiSettingsMigrate.Msg + = UiFormMsg AccountScope Comp.UiSettingsForm.Msg | Submit - | UpdateSettings - | SaveSettingsResp UiSettings (Result Http.Error BasicResult) - | ReceiveBrowserSettings StoredUiSettings + | SaveSettingsResp (Result Http.Error BasicResult) + | ReceiveServerSettings (Result Http.Error ( StoredUiSettings, StoredUiSettings )) + | ToggleExpandCollapse + | SwitchForm AccountScope -init : Flags -> UiSettings -> ( Model, Cmd Msg ) -init flags settings = - let - ( fm, fc ) = - Comp.UiSettingsForm.init flags settings - - ( mm, mc ) = - Comp.UiSettingsMigrate.init flags - in - ( { formModel = fm - , settings = Nothing +init : Flags -> ( Model, Cmd Msg ) +init flags = + ( { formModel = ViewLoading + , formData = Nothing , formResult = FormInit - , settingsMigrate = mm } , Cmd.batch - [ Cmd.map UiSettingsFormMsg fc - , Cmd.map UiSettingsMigrateMsg mc + [ Api.getClientSettingsRaw flags ReceiveServerSettings ] ) +getViewScope : Model -> AccountScope +getViewScope model = + case model.formModel of + ViewCollective -> + Data.AccountScope.Collective + + ViewUser -> + Data.AccountScope.User + + _ -> + Data.AccountScope.User + + --- update @@ -82,108 +102,165 @@ type alias UpdateResult = { model : Model , cmd : Cmd 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 settings msg model = case msg of - UiSettingsFormMsg lm -> + UiFormMsg scope lm -> + case model.formData of + Nothing -> + unit model + + Just data -> + case scope of + Data.AccountScope.Collective -> + let + ( m_, sett ) = + Comp.UiSettingsForm.update flags data.collSettings lm data.collModel + in + unit + { model + | formData = + Just + { data + | collSettings = Maybe.withDefault data.collSettings sett + , collModel = m_ + } + , formResult = + if sett /= Nothing then + FormInit + + else + model.formResult + } + + 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_ + } + , formResult = + if sett /= Nothing then + FormInit + + else + model.formResult + } + + 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 + , appEvent = AppNothing + } + + ( 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 + result = + update flags + settings + (ReceiveServerSettings (Ok ( data.collSettings, data.userSettings ))) + model + in + { result | appEvent = AppReloadUiSettings } + + _ -> + unit { model | formResult = FormUnknownError } + + SaveSettingsResp (Err err) -> + UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none AppNothing + + ReceiveServerSettings (Ok ( coll, user )) -> let - inSettings = - Maybe.withDefault settings model.settings + collDefaults = + Data.UiSettings.defaults - ( m_, sett ) = - Comp.UiSettingsForm.update inSettings lm model.formModel + userDefaults = + Data.UiSettings.merge coll collDefaults + + ( um, uc ) = + Comp.UiSettingsForm.init flags user userDefaults + + ( cm, cc ) = + Comp.UiSettingsForm.init flags coll collDefaults + + model_ = + { model + | formData = + Just + { userSettings = user + , userModel = um + , collSettings = coll + , collModel = cm + } + , formModel = ViewUser + } + + cmds = + Cmd.batch + [ Cmd.map (UiFormMsg Data.AccountScope.User) uc + , Cmd.map (UiFormMsg Data.AccountScope.Collective) cc + ] in - { model = - { model - | formModel = m_ - , settings = - if sett == Nothing then - model.settings + UpdateResult model_ cmds Sub.none AppNothing - else - sett - , formResult = - if sett /= Nothing then - FormInit + ReceiveServerSettings (Err err) -> + unit { model | formResult = FormHttpError err } - else - model.formResult - } - , cmd = Cmd.none - , sub = Sub.none - , newSettings = Nothing - } - - UiSettingsMigrateMsg lm -> - let - result = - Comp.UiSettingsMigrate.update flags lm model.settingsMigrate - in - { model = { model | settingsMigrate = result.model } - , cmd = Cmd.map UiSettingsMigrateMsg result.cmd - , sub = Sub.map UiSettingsMigrateMsg result.sub - , newSettings = result.newSettings - } - - ReceiveBrowserSettings sett -> + ToggleExpandCollapse -> let lm = - UiSettingsMigrateMsg (Comp.UiSettingsMigrate.receiveBrowserSettings sett) + UiFormMsg (getViewScope model) Comp.UiSettingsForm.toggleAllTabs in update flags settings lm model - Submit -> - 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 -> + SwitchForm scope -> let - ( fm, fc ) = - Comp.UiSettingsForm.init flags settings + forUser = + unit { model | formModel = ViewUser } + + forColl = + unit { model | formModel = ViewCollective } in - { model = { model | formModel = fm } - , cmd = Cmd.map UiSettingsFormMsg fc - , sub = Sub.none - , newSettings = Nothing - } - - - ---- View2 + Data.AccountScope.fold forUser forColl scope isError : Model -> Bool @@ -211,7 +288,11 @@ isSuccess model = 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 ] [ MB.view { start = @@ -221,14 +302,29 @@ view2 texts flags settings classes model = , title = texts.saveSettings , 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" } - , div [] - [ Html.map UiSettingsMigrateMsg - (Comp.UiSettingsMigrate.view model.settingsMigrate) - ] , div [ classList [ ( S.successMessage, isSuccess model ) @@ -252,11 +348,52 @@ view2 texts flags settings classes model = FormUnknownError -> text texts.unknownSaveError ] - , Html.map UiSettingsFormMsg - (Comp.UiSettingsForm.view2 - texts.uiSettingsForm - flags - settings - model.formModel - ) + , 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 + ] + , Html.map (UiFormMsg scope) + (Comp.UiSettingsForm.view2 + texts.uiSettingsForm + flags + data.collSettings + 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" ] [] ] diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm b/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm index a5b39ad3..d1aeed8e 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm @@ -17,6 +17,7 @@ module Comp.UiSettingsMigrate exposing import Api import Api.Model.BasicResult exposing (BasicResult) +import Data.AccountScope import Data.Flags exposing (Flags) import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Html exposing (..) @@ -132,7 +133,10 @@ update flags msg model = Data.UiSettings.merge settings Data.UiSettings.defaults cmd = - Api.saveClientSettings flags uiSettings (SaveSettingsResp uiSettings) + Api.saveClientSettings flags + (Data.UiSettings.convert uiSettings) + Data.AccountScope.Collective + (SaveSettingsResp uiSettings) in { empty | model = MigrateRequestRunning, cmd = cmd } diff --git a/modules/webapp/src/main/elm/Data/AccountScope.elm b/modules/webapp/src/main/elm/Data/AccountScope.elm index 1fe287ec..07772f38 100644 --- a/modules/webapp/src/main/elm/Data/AccountScope.elm +++ b/modules/webapp/src/main/elm/Data/AccountScope.elm @@ -23,6 +23,11 @@ fold user coll scope = coll +all : List AccountScope +all = + [ Collective, User ] + + isUser : AccountScope -> Bool isUser scope = fold True False scope diff --git a/modules/webapp/src/main/elm/Data/AppEvent.elm b/modules/webapp/src/main/elm/Data/AppEvent.elm new file mode 100644 index 00000000..c15e912f --- /dev/null +++ b/modules/webapp/src/main/elm/Data/AppEvent.elm @@ -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 diff --git a/modules/webapp/src/main/elm/Data/UiSettings.elm b/modules/webapp/src/main/elm/Data/UiSettings.elm index 30a6c129..43f27216 100644 --- a/modules/webapp/src/main/elm/Data/UiSettings.elm +++ b/modules/webapp/src/main/elm/Data/UiSettings.elm @@ -15,8 +15,10 @@ module Data.UiSettings exposing , catColor , catColorFg2 , catColorString2 + , convert , defaults , documentationSite + , emptyStoredSettings , fieldHidden , fieldVisible , getUiLanguage @@ -30,7 +32,6 @@ module Data.UiSettings exposing , tagColor , tagColorFg2 , tagColorString2 - , toStoredUiSettings ) import Api.Model.Tag exposing (Tag) @@ -62,7 +63,7 @@ force default settings. -} type alias StoredUiSettings = { itemSearchPageSize : Maybe Int - , tagCategoryColors : List ( String, String ) + , tagCategoryColors : Maybe (List ( String, String )) , pdfMode : Maybe String , itemSearchNoteLength : Maybe Int , itemDetailNotesPosition : Maybe String @@ -70,23 +71,51 @@ type alias StoredUiSettings = , searchMenuTagCount : Maybe Int , searchMenuTagCatCount : Maybe Int , formFields : Maybe (List String) - , itemDetailShortcuts : Bool - , searchMenuVisible : Bool - , editMenuVisible : Bool + , itemDetailShortcuts : Maybe Bool + , searchMenuVisible : Maybe Bool + , editMenuVisible : Maybe Bool , cardPreviewSize : Maybe String , cardTitleTemplate : Maybe String , cardSubtitleTemplate : Maybe String - , searchStatsVisible : Bool - , cardPreviewFullWidth : Bool + , searchStatsVisible : Maybe Bool + , cardPreviewFullWidth : Maybe Bool , uiTheme : Maybe String - , sideMenuVisible : Bool - , powerSearchEnabled : Bool + , sideMenuVisible : Maybe Bool + , powerSearchEnabled : Maybe Bool , uiLang : Maybe String - , itemSearchShowGroups : Bool + , itemSearchShowGroups : Maybe Bool , itemSearchArrange : Maybe String } +emptyStoredSettings : StoredUiSettings +emptyStoredSettings = + { itemSearchPageSize = Nothing + , tagCategoryColors = Nothing + , pdfMode = Nothing + , itemSearchNoteLength = Nothing + , itemDetailNotesPosition = Nothing + , searchMenuFolderCount = Nothing + , searchMenuTagCount = Nothing + , searchMenuTagCatCount = Nothing + , formFields = Nothing + , itemDetailShortcuts = Nothing + , searchMenuVisible = Nothing + , editMenuVisible = 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 = let @@ -96,12 +125,12 @@ storedUiSettingsDecoder = maybeString = Decode.maybe Decode.string - def = - defaults + maybeBool = + Decode.maybe Decode.bool in Decode.succeed StoredUiSettings |> 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 "itemSearchNoteLength" maybeInt Nothing |> P.optional "itemDetailNotesPosition" maybeString Nothing @@ -109,53 +138,56 @@ storedUiSettingsDecoder = |> P.optional "searchMenuTagCount" maybeInt Nothing |> P.optional "searchMenuTagCatCount" maybeInt Nothing |> P.optional "formFields" (Decode.maybe <| Decode.list Decode.string) Nothing - |> P.optional "itemDetailShortcuts" Decode.bool def.itemDetailShortcuts - |> P.optional "searchMenuVisible" Decode.bool def.searchMenuVisible - |> P.optional "editMenuVisible" Decode.bool def.editMenuVisible + |> P.optional "itemDetailShortcuts" maybeBool Nothing + |> P.optional "searchMenuVisible" maybeBool Nothing + |> P.optional "editMenuVisible" maybeBool Nothing |> P.optional "cardPreviewSize" maybeString Nothing |> P.optional "cardTitleTemplate" maybeString Nothing |> P.optional "cardSubtitleTemplate" maybeString Nothing - |> P.optional "searchStatsVisible" Decode.bool def.searchStatsVisible - |> P.optional "cardPreviewFullWidth" Decode.bool def.cardPreviewFullWidth + |> P.optional "searchStatsVisible" maybeBool Nothing + |> P.optional "cardPreviewFullWidth" maybeBool Nothing |> P.optional "uiTheme" maybeString Nothing - |> P.optional "sideMenuVisible" Decode.bool def.sideMenuVisible - |> P.optional "powerSearchEnabled" Decode.bool def.powerSearchEnabled + |> P.optional "sideMenuVisible" maybeBool Nothing + |> P.optional "powerSearchEnabled" maybeBool Nothing |> P.optional "uiLang" maybeString Nothing - |> P.optional "itemSearchShowGroups" Decode.bool def.itemSearchShowGroups + |> P.optional "itemSearchShowGroups" maybeBool Nothing |> P.optional "itemSearchArrange" maybeString Nothing storedUiSettingsEncode : StoredUiSettings -> Encode.Value storedUiSettingsEncode value = let - maybeEnc enca ma = - Maybe.map enca ma |> Maybe.withDefault Encode.null + maybeEnc field enca ma = + Maybe.map (\a -> ( field, enca a )) ma in - Encode.object - [ ( "itemSearchPageSize", maybeEnc Encode.int value.itemSearchPageSize ) - , ( "tagCategoryColors", Encode.dict identity Encode.string (Dict.fromList value.tagCategoryColors) ) - , ( "pdfMode", maybeEnc Encode.string value.pdfMode ) - , ( "itemSearchNoteLength", maybeEnc Encode.int value.itemSearchNoteLength ) - , ( "itemDetailNotesPosition", maybeEnc Encode.string value.itemDetailNotesPosition ) - , ( "searchMenuFolderCount", maybeEnc Encode.int value.searchMenuFolderCount ) - , ( "searchMenuTagCount", maybeEnc Encode.int value.searchMenuTagCount ) - , ( "searchMenuTagCatCount", maybeEnc Encode.int value.searchMenuTagCatCount ) - , ( "formFields", maybeEnc (Encode.list Encode.string) value.formFields ) - , ( "itemDetailShortcuts", Encode.bool value.itemDetailShortcuts ) - , ( "searchMenuVisible", Encode.bool value.searchMenuVisible ) - , ( "editMenuVisible", Encode.bool value.editMenuVisible ) - , ( "cardPreviewSize", maybeEnc Encode.string value.cardPreviewSize ) - , ( "cardTitleTemplate", maybeEnc Encode.string value.cardTitleTemplate ) - , ( "cardSubtitleTemplate", maybeEnc Encode.string value.cardSubtitleTemplate ) - , ( "searchStatsVisible", Encode.bool value.searchStatsVisible ) - , ( "cardPreviewFullWidth", Encode.bool value.cardPreviewFullWidth ) - , ( "uiTheme", maybeEnc Encode.string value.uiTheme ) - , ( "sideMenuVisible", Encode.bool value.sideMenuVisible ) - , ( "powerSearchEnabled", Encode.bool value.powerSearchEnabled ) - , ( "uiLang", maybeEnc Encode.string value.uiLang ) - , ( "itemSearchShowGroups", Encode.bool value.itemSearchShowGroups ) - , ( "itemSearchArrange", maybeEnc Encode.string value.itemSearchArrange ) - ] + Encode.object <| + List.filterMap identity + [ maybeEnc "itemSearchPageSize" Encode.int value.itemSearchPageSize + , maybeEnc "tagCategoryColors" + (Encode.dict identity Encode.string) + (Maybe.map Dict.fromList value.tagCategoryColors) + , maybeEnc "pdfMode" Encode.string value.pdfMode + , maybeEnc "itemSearchNoteLength" Encode.int value.itemSearchNoteLength + , maybeEnc "itemDetailNotesPosition" Encode.string value.itemDetailNotesPosition + , maybeEnc "searchMenuFolderCount" Encode.int value.searchMenuFolderCount + , maybeEnc "searchMenuTagCount" Encode.int value.searchMenuTagCount + , maybeEnc "searchMenuTagCatCount" Encode.int value.searchMenuTagCatCount + , maybeEnc "formFields" (Encode.list Encode.string) value.formFields + , maybeEnc "itemDetailShortcuts" Encode.bool value.itemDetailShortcuts + , maybeEnc "searchMenuVisible" Encode.bool value.searchMenuVisible + , maybeEnc "editMenuVisible" Encode.bool value.editMenuVisible + , maybeEnc "cardPreviewSize" Encode.string value.cardPreviewSize + , maybeEnc "cardTitleTemplate" Encode.string value.cardTitleTemplate + , maybeEnc "cardSubtitleTemplate" Encode.string value.cardSubtitleTemplate + , maybeEnc "searchStatsVisible" Encode.bool value.searchStatsVisible + , maybeEnc "cardPreviewFullWidth" Encode.bool value.cardPreviewFullWidth + , maybeEnc "uiTheme" Encode.string value.uiTheme + , maybeEnc "sideMenuVisible" Encode.bool value.sideMenuVisible + , maybeEnc "powerSearchEnabled" Encode.bool value.powerSearchEnabled + , maybeEnc "uiLang" Encode.string value.uiLang + , maybeEnc "itemSearchShowGroups" Encode.bool value.itemSearchShowGroups + , maybeEnc "itemSearchArrange" Encode.string value.itemSearchArrange + ] {-| Settings for the web ui. These fields are all mandatory, since @@ -273,7 +305,8 @@ merge given fallback = choose given.itemSearchPageSize fallback.itemSearchPageSize , tagCategoryColors = Dict.union - (Dict.fromList given.tagCategoryColors + (Maybe.map Dict.fromList given.tagCategoryColors + |> Maybe.withDefault Dict.empty |> Dict.map (\_ -> Data.Color.fromString) |> Dict.filter (\_ -> \mc -> mc /= Nothing) |> Dict.map (\_ -> Maybe.withDefault Data.Color.Grey) @@ -299,9 +332,9 @@ merge given fallback = choose (Maybe.map Data.Fields.fromList given.formFields) fallback.formFields - , itemDetailShortcuts = given.itemDetailShortcuts - , searchMenuVisible = given.searchMenuVisible - , editMenuVisible = given.editMenuVisible + , itemDetailShortcuts = choose given.itemDetailShortcuts fallback.itemDetailShortcuts + , searchMenuVisible = choose given.searchMenuVisible fallback.searchMenuVisible + , editMenuVisible = choose given.editMenuVisible fallback.editMenuVisible , cardPreviewSize = given.cardPreviewSize |> Maybe.andThen Data.BasicSize.fromString @@ -312,17 +345,17 @@ merge given fallback = , cardSubtitleTemplate = Maybe.andThen readPattern given.cardSubtitleTemplate |> Maybe.withDefault fallback.cardSubtitleTemplate - , searchStatsVisible = given.searchStatsVisible - , cardPreviewFullWidth = given.cardPreviewFullWidth + , searchStatsVisible = choose given.searchStatsVisible fallback.searchStatsVisible + , cardPreviewFullWidth = choose given.cardPreviewFullWidth fallback.cardPreviewFullWidth , uiTheme = Maybe.andThen Data.UiTheme.fromString given.uiTheme |> Maybe.withDefault fallback.uiTheme - , sideMenuVisible = given.sideMenuVisible - , powerSearchEnabled = given.powerSearchEnabled + , sideMenuVisible = choose given.sideMenuVisible fallback.sideMenuVisible + , powerSearchEnabled = choose given.powerSearchEnabled fallback.powerSearchEnabled , uiLang = Maybe.map Messages.fromIso2 given.uiLang - |> Maybe.withDefault Messages.UiLanguage.English - , itemSearchShowGroups = given.itemSearchShowGroups + |> Maybe.withDefault fallback.uiLang + , itemSearchShowGroups = choose given.itemSearchShowGroups fallback.itemSearchShowGroups , itemSearchArrange = Maybe.andThen Data.ItemArrange.fromString given.itemSearchArrange |> Maybe.withDefault fallback.itemSearchArrange @@ -334,12 +367,13 @@ mergeDefaults given = merge given defaults -toStoredUiSettings : UiSettings -> StoredUiSettings -toStoredUiSettings settings = +convert : UiSettings -> StoredUiSettings +convert settings = { itemSearchPageSize = Just settings.itemSearchPageSize , tagCategoryColors = Dict.map (\_ -> Data.Color.toString) settings.tagCategoryColors |> Dict.toList + |> Just , pdfMode = Just (Data.Pdf.asString settings.pdfMode) , itemSearchNoteLength = Just settings.itemSearchNoteLength , itemDetailNotesPosition = Just (posToString settings.itemDetailNotesPosition) @@ -349,22 +383,22 @@ toStoredUiSettings settings = , formFields = List.map Data.Fields.toString settings.formFields |> Just - , itemDetailShortcuts = settings.itemDetailShortcuts - , searchMenuVisible = settings.searchMenuVisible - , editMenuVisible = settings.editMenuVisible + , itemDetailShortcuts = Just settings.itemDetailShortcuts + , searchMenuVisible = Just settings.searchMenuVisible + , editMenuVisible = Just settings.editMenuVisible , cardPreviewSize = settings.cardPreviewSize |> Data.BasicSize.asString |> Just , cardTitleTemplate = settings.cardTitleTemplate.pattern |> Just , cardSubtitleTemplate = settings.cardSubtitleTemplate.pattern |> Just - , searchStatsVisible = settings.searchStatsVisible - , cardPreviewFullWidth = settings.cardPreviewFullWidth + , searchStatsVisible = Just settings.searchStatsVisible + , cardPreviewFullWidth = Just settings.cardPreviewFullWidth , uiTheme = Just (Data.UiTheme.toString settings.uiTheme) - , sideMenuVisible = settings.sideMenuVisible - , powerSearchEnabled = settings.powerSearchEnabled + , sideMenuVisible = Just settings.sideMenuVisible + , powerSearchEnabled = Just settings.powerSearchEnabled , uiLang = Just <| Messages.toIso2 settings.uiLang - , itemSearchShowGroups = settings.itemSearchShowGroups + , itemSearchShowGroups = Just settings.itemSearchShowGroups , itemSearchArrange = Data.ItemArrange.asString settings.itemSearchArrange |> Just } diff --git a/modules/webapp/src/main/elm/Main.elm b/modules/webapp/src/main/elm/Main.elm index 3569ad5c..d972f35b 100644 --- a/modules/webapp/src/main/elm/Main.elm +++ b/modules/webapp/src/main/elm/Main.elm @@ -14,7 +14,6 @@ import App.View2 import Browser exposing (Document) import Browser.Navigation exposing (Key) import Data.Flags exposing (Flags) -import Data.NotificationChannel import Data.UiSettings import Html exposing (..) import Html.Attributes exposing (..) @@ -93,6 +92,5 @@ subscriptions : Model -> Sub Msg subscriptions model = Sub.batch [ model.subs - , Ports.receiveUiSettings ReceiveBrowserSettings , Ports.receiveServerEvent ReceiveWsMessage ] diff --git a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm index 77eb7037..409b0c4b 100644 --- a/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm +++ b/modules/webapp/src/main/elm/Messages/Comp/UiSettingsManage.elm @@ -15,16 +15,22 @@ import Http import Messages.Basics import Messages.Comp.HttpError import Messages.Comp.UiSettingsForm +import Messages.Data.AccountScope type alias Texts = { basics : Messages.Basics.Texts , uiSettingsForm : Messages.Comp.UiSettingsForm.Texts + , accountScope : Messages.Data.AccountScope.Texts , saveSettings : String , settingsUnchanged : String , settingsSaved : String , unknownSaveError : String , httpError : Http.Error -> String + , userHeader : String + , userInfo : String + , collectiveHeader : String + , expandCollapse : String } @@ -32,11 +38,16 @@ gb : Texts gb = { basics = Messages.Basics.gb , uiSettingsForm = Messages.Comp.UiSettingsForm.gb + , accountScope = Messages.Data.AccountScope.gb , saveSettings = "Save settings" , settingsUnchanged = "Settings unchanged or invalid." , settingsSaved = "Settings saved." , unknownSaveError = "Unknown error while trying to save settings." , httpError = Messages.Comp.HttpError.gb + , userHeader = "Personal settings" + , userInfo = "Your personal settings override those of the collective. On reset, settings are set back to those of the collective." + , collectiveHeader = "Collective settings" + , expandCollapse = "Expand/collapse all" } @@ -44,9 +55,14 @@ de : Texts de = { basics = Messages.Basics.de , uiSettingsForm = Messages.Comp.UiSettingsForm.de + , accountScope = Messages.Data.AccountScope.de , saveSettings = "Einstellungen speichern" , settingsUnchanged = "Einstellungen nicht verändert oder ungültig." , settingsSaved = "Einstellungen gespeichert" , unknownSaveError = "Unbekannter Fehler beim Speichern der Einstellungen." , httpError = Messages.Comp.HttpError.de + , userHeader = "Persönliche Einstellungen" + , userInfo = "Die persönlichen Einstellungen überschreiben die des Kollektivs. Wenn Einstellungen zurückgesetzt werden, werden sie auf die Werte des Kollektivs gesetzt." + , collectiveHeader = "Kollektiv Einstellungen" + , expandCollapse = "Alle ein-/ausklappen" } diff --git a/modules/webapp/src/main/elm/Messages/Page/UserSettings.elm b/modules/webapp/src/main/elm/Messages/Page/UserSettings.elm index 99879774..e030fdda 100644 --- a/modules/webapp/src/main/elm/Messages/Page/UserSettings.elm +++ b/modules/webapp/src/main/elm/Messages/Page/UserSettings.elm @@ -80,8 +80,7 @@ gb = , changePassword = "Change Password" , channelSettings = "Notification Channels" , uiSettingsInfo = - "These settings only affect the web ui. They are stored in the browser, " - ++ "so they are separated between browsers and devices." + "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." , scanMailboxInfo1 = "Docspell can scan folders of your mailbox to import your mails. " ++ "You need to provide a connection in " @@ -144,7 +143,7 @@ de = , channelSettings = "Benachrichtigungskanäle" , changePassword = "Passwort ändern" , 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 = """Docspell kann Postfächer durchsuchen und E-Mails importieren. Dafür sind E-Mail-Einstellungen (IMAP) notwendig.""" diff --git a/modules/webapp/src/main/elm/Page/Search/Data.elm b/modules/webapp/src/main/elm/Page/Search/Data.elm index 06d6bc05..e24b2e2a 100644 --- a/modules/webapp/src/main/elm/Page/Search/Data.elm +++ b/modules/webapp/src/main/elm/Page/Search/Data.elm @@ -231,7 +231,7 @@ type Msg | KeyUpPowerSearchbarMsg (Maybe KeyCode) | RequestReprocessSelected | ReprocessSelectedConfirmed - | ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) + | ClientSettingsSaveResp (Result Http.Error BasicResult) | RemoveItem String | MergeSelectedItems | MergeItemsMsg Comp.ItemMerge.Msg diff --git a/modules/webapp/src/main/elm/Page/Search/Update.elm b/modules/webapp/src/main/elm/Page/Search/Update.elm index 6db4874d..fa25cdb2 100644 --- a/modules/webapp/src/main/elm/Page/Search/Update.elm +++ b/modules/webapp/src/main/elm/Page/Search/Update.elm @@ -22,7 +22,9 @@ import Comp.LinkTarget exposing (LinkTarget) import Comp.PowerSearchInput import Comp.PublishItems import Comp.SearchMenu +import Data.AppEvent exposing (AppEvent(..)) import Data.Flags exposing (Flags) +import Data.ItemArrange import Data.ItemQuery as Q import Data.ItemSelection import Data.Items @@ -44,7 +46,7 @@ type alias UpdateResult = { model : Model , cmd : Cmd Msg , sub : Sub Msg - , newSettings : Maybe UiSettings + , appEvent : AppEvent } @@ -74,7 +76,7 @@ update bookmarkId mId key flags texts settings msg model = model DoNothing -> - UpdateResult model Cmd.none Sub.none Nothing + UpdateResult model Cmd.none Sub.none AppNothing ResetSearch -> let @@ -868,26 +870,26 @@ update bookmarkId mId key flags texts settings msg model = TogglePreviewFullWidth -> let - newSettings = - { settings | cardPreviewFullWidth = not settings.cardPreviewFullWidth } + newSettings s = + { s | cardPreviewFullWidth = Just (not settings.cardPreviewFullWidth) } cmd = - Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) + Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp in noSub ( { model | viewMenuOpen = False }, cmd ) - ClientSettingsSaveResp newSettings (Ok res) -> + ClientSettingsSaveResp (Ok res) -> if res.success then { model = model , cmd = Cmd.none , sub = Sub.none - , newSettings = Just newSettings + , appEvent = AppReloadUiSettings } else noSub ( model, Cmd.none ) - ClientSettingsSaveResp _ (Err _) -> + ClientSettingsSaveResp (Err _) -> noSub ( model, Cmd.none ) PowerSearchMsg lm -> @@ -1015,21 +1017,21 @@ update bookmarkId mId key flags texts settings msg model = ToggleShowGroups -> let - newSettings = - { settings | itemSearchShowGroups = not settings.itemSearchShowGroups } + newSettings s = + { s | itemSearchShowGroups = Just (not settings.itemSearchShowGroups) } cmd = - Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) + Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp in noSub ( { model | viewMenuOpen = False }, cmd ) ToggleArrange am -> let - newSettings = - { settings | itemSearchArrange = am } + newSettings s = + { s | itemSearchArrange = Data.ItemArrange.asString am |> Just } cmd = - Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings) + Api.saveUserClientSettingsBy flags newSettings ClientSettingsSaveResp in noSub ( { model | viewMenuOpen = False }, cmd ) @@ -1201,5 +1203,5 @@ makeResult ( m, c, s ) = { model = m , cmd = c , sub = s - , newSettings = Nothing + , appEvent = AppNothing } diff --git a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm index 2c71dda6..141e86e6 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm @@ -42,10 +42,10 @@ type alias Model = init : Flags -> UiSettings -> ( Model, Cmd Msg ) -init flags settings = +init flags _ = let ( um, uc ) = - Comp.UiSettingsManage.init flags settings + Comp.UiSettingsManage.init flags ( otpm, otpc ) = Comp.OtpSetup.init flags @@ -107,5 +107,3 @@ type Msg | NotificationHookMsg Comp.NotificationHookManage.Msg | PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg | ChannelMsg Comp.NotificationChannelManage.Msg - | UpdateSettings - | ReceiveBrowserSettings StoredUiSettings diff --git a/modules/webapp/src/main/elm/Page/UserSettings/Update.elm b/modules/webapp/src/main/elm/Page/UserSettings/Update.elm index 656674ad..eec01a4e 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Update.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Update.elm @@ -17,6 +17,7 @@ import Comp.OtpSetup import Comp.PeriodicQueryTaskManage import Comp.ScanMailboxManage import Comp.UiSettingsManage +import Data.AppEvent exposing (AppEvent(..)) import Data.Flags exposing (Flags) import Data.UiSettings exposing (UiSettings) import Page.UserSettings.Data exposing (..) @@ -26,10 +27,15 @@ type alias UpdateResult = { model : Model , cmd : Cmd 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 settings msg model = case msg of @@ -47,7 +53,7 @@ update flags settings msg model = { model = { m | emailSettingsModel = em } , cmd = Cmd.map EmailSettingsMsg c , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } ImapSettingsTab -> @@ -58,18 +64,14 @@ update flags settings msg model = { model = { m | imapSettingsModel = em } , cmd = Cmd.map ImapSettingsMsg c , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } ChangePassTab -> - UpdateResult m Cmd.none Sub.none Nothing + unit m NotificationTab -> - { model = m - , cmd = Cmd.none - , sub = Sub.none - , newSettings = Nothing - } + unit m NotificationWebhookTab -> let @@ -79,7 +81,7 @@ update flags settings msg model = { model = m , cmd = Cmd.map NotificationHookMsg nc , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } NotificationQueriesTab -> @@ -88,7 +90,7 @@ update flags settings msg model = Cmd.map NotificationMsg (Tuple.second (Comp.DueItemsTaskManage.init flags)) in - UpdateResult m initCmd Sub.none Nothing + UpdateResult m initCmd Sub.none AppNothing NotificationDueItemsTab -> let @@ -96,7 +98,7 @@ update flags settings msg model = Cmd.map NotificationMsg (Tuple.second (Comp.DueItemsTaskManage.init flags)) in - UpdateResult m initCmd Sub.none Nothing + UpdateResult m initCmd Sub.none AppNothing ScanMailboxTab -> let @@ -104,16 +106,24 @@ update flags settings msg model = Cmd.map ScanMailboxMsg (Tuple.second (Comp.ScanMailboxManage.init flags)) in - UpdateResult m initCmd Sub.none Nothing + UpdateResult m initCmd Sub.none AppNothing 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 -> - UpdateResult m Cmd.none Sub.none Nothing + unit m ChannelTab -> - UpdateResult m Cmd.none Sub.none Nothing + unit m ChangePassMsg m -> let @@ -123,7 +133,7 @@ update flags settings msg model = { model = { model | changePassModel = m2 } , cmd = Cmd.map ChangePassMsg c2 , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } EmailSettingsMsg m -> @@ -134,7 +144,7 @@ update flags settings msg model = { model = { model | emailSettingsModel = m2 } , cmd = Cmd.map EmailSettingsMsg c2 , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } ImapSettingsMsg m -> @@ -145,7 +155,7 @@ update flags settings msg model = { model = { model | imapSettingsModel = m2 } , cmd = Cmd.map ImapSettingsMsg c2 , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } NotificationMsg lm -> @@ -156,7 +166,7 @@ update flags settings msg model = { model = { model | notificationModel = m2 } , cmd = Cmd.map NotificationMsg c2 , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } ScanMailboxMsg lm -> @@ -167,7 +177,7 @@ update flags settings msg model = { model = { model | scanMailboxModel = m2 } , cmd = Cmd.map ScanMailboxMsg c2 , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } UiSettingsMsg lm -> @@ -178,7 +188,7 @@ update flags settings msg model = { model = { model | uiSettingsModel = res.model } , cmd = Cmd.map UiSettingsMsg res.cmd , sub = Sub.map UiSettingsMsg res.sub - , newSettings = res.newSettings + , appEvent = res.appEvent } OtpSetupMsg lm -> @@ -189,7 +199,7 @@ update flags settings msg model = { model = { model | otpSetupModel = otpm } , cmd = Cmd.map OtpSetupMsg otpc , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } NotificationHookMsg lm -> @@ -200,7 +210,7 @@ update flags settings msg model = { model = { model | notificationHookModel = hm } , cmd = Cmd.map NotificationHookMsg hc , sub = Sub.none - , newSettings = Nothing + , appEvent = AppNothing } ChannelMsg lm -> @@ -211,22 +221,9 @@ update flags settings msg model = { model = { model | channelModel = cm } , cmd = Cmd.map ChannelMsg cc , 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 -> let ( pqm, pqc, pqs ) = @@ -235,5 +232,5 @@ update flags settings msg model = { model = { model | periodicQueryModel = pqm } , cmd = Cmd.map PeriodicQueryMsg pqc , sub = Sub.map PeriodicQueryMsg pqs - , newSettings = Nothing + , appEvent = AppNothing } diff --git a/modules/webapp/src/main/elm/Ports.elm b/modules/webapp/src/main/elm/Ports.elm index 8a348b85..09ff2b44 100644 --- a/modules/webapp/src/main/elm/Ports.elm +++ b/modules/webapp/src/main/elm/Ports.elm @@ -11,9 +11,7 @@ port module Ports exposing , printElement , receiveCheckQueryResult , receiveServerEvent - , receiveUiSettings , removeAccount - , requestUiSettings , setAccount , setUiTheme ) @@ -21,7 +19,6 @@ port module Ports exposing import Api.Model.AuthResult exposing (AuthResult) import Data.QueryParseResult exposing (QueryParseResult) import Data.ServerEvent exposing (ServerEvent) -import Data.UiSettings exposing (StoredUiSettings) import Data.UiTheme exposing (UiTheme) import Json.Decode as D @@ -46,12 +43,6 @@ port receiveCheckQueryResult : (QueryParseResult -> msg) -> Sub 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 and calls the print dialog. -} diff --git a/modules/webapp/src/main/webjar/docspell.js b/modules/webapp/src/main/webjar/docspell.js index 4b32212e..0b81ff02 100644 --- a/modules/webapp/src/main/webjar/docspell.js +++ b/modules/webapp/src/main/webjar/docspell.js @@ -55,25 +55,6 @@ elmApp.ports.removeAccount.subscribe(function() { 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 = {};