From e406718cb7e71df1ee5759098f4792d66b42a6f0 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Thu, 27 May 2021 01:07:36 +0200 Subject: [PATCH] Add a way to migrate settings stored at the browser to the server --- modules/webapp/src/main/elm/App/Data.elm | 3 +- modules/webapp/src/main/elm/App/Update.elm | 20 +- .../src/main/elm/Comp/UiSettingsManage.elm | 46 ++++- .../src/main/elm/Comp/UiSettingsMigrate.elm | 186 ++++++++++++++++++ modules/webapp/src/main/elm/Main.elm | 1 + .../src/main/elm/Page/UserSettings/Data.elm | 3 +- .../src/main/elm/Page/UserSettings/Update.elm | 99 ++++++---- modules/webapp/src/main/elm/Ports.elm | 9 + modules/webapp/src/main/webjar/docspell.js | 56 ++---- 9 files changed, 338 insertions(+), 85 deletions(-) create mode 100644 modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm diff --git a/modules/webapp/src/main/elm/App/Data.elm b/modules/webapp/src/main/elm/App/Data.elm index 8b608bce..784d8493 100644 --- a/modules/webapp/src/main/elm/App/Data.elm +++ b/modules/webapp/src/main/elm/App/Data.elm @@ -12,7 +12,7 @@ import Api.Model.VersionInfo exposing (VersionInfo) import Browser exposing (UrlRequest) import Browser.Navigation exposing (Key) import Data.Flags exposing (Flags) -import Data.UiSettings exposing (UiSettings) +import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Data.UiTheme exposing (UiTheme) import Http import Messages.UiLanguage exposing (UiLanguage) @@ -162,6 +162,7 @@ type Msg | ToggleLangMenu | SetLanguage UiLanguage | ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) + | ReceiveBrowserSettings StoredUiSettings defaultPage : Flags -> Page diff --git a/modules/webapp/src/main/elm/App/Update.elm b/modules/webapp/src/main/elm/App/Update.elm index 93f6d03a..fa92a709 100644 --- a/modules/webapp/src/main/elm/App/Update.elm +++ b/modules/webapp/src/main/elm/App/Update.elm @@ -275,6 +275,13 @@ updateWithSub msg model = GetUiSettings (Err _) -> ( model, Cmd.none, Sub.none ) + ReceiveBrowserSettings sett -> + let + lm = + Page.UserSettings.Data.ReceiveBrowserSettings sett + in + updateUserSettings lm model + applyClientSettings : Model -> UiSettings -> ( Model, Cmd Msg, Sub Msg ) applyClientSettings model settings = @@ -379,14 +386,14 @@ updateQueue lmsg model = updateUserSettings : Page.UserSettings.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) updateUserSettings lmsg model = let - ( lm, lc, newClientSettings ) = + result = Page.UserSettings.Update.update model.flags model.uiSettings lmsg model.userSettingsModel model_ = - { model | userSettingsModel = lm } + { model | userSettingsModel = result.model } ( lm2, lc2, s2 ) = - case newClientSettings of + case result.newSettings of Just sett -> applyClientSettings model_ sett @@ -395,10 +402,13 @@ updateUserSettings lmsg model = in ( lm2 , Cmd.batch - [ Cmd.map UserSettingsMsg lc + [ Cmd.map UserSettingsMsg result.cmd , lc2 ] - , s2 + , Sub.batch + [ Sub.map UserSettingsMsg result.sub + , s2 + ] ) diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm index 2d0d9d95..178a287d 100644 --- a/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm +++ b/modules/webapp/src/main/elm/Comp/UiSettingsManage.elm @@ -11,8 +11,9 @@ import Api import Api.Model.BasicResult exposing (BasicResult) import Comp.MenuBar as MB import Comp.UiSettingsForm +import Comp.UiSettingsMigrate import Data.Flags exposing (Flags) -import Data.UiSettings exposing (UiSettings) +import Data.UiSettings exposing (StoredUiSettings, UiSettings) import Html exposing (..) import Html.Attributes exposing (..) import Http @@ -24,6 +25,7 @@ type alias Model = { formModel : Comp.UiSettingsForm.Model , settings : Maybe UiSettings , formResult : FormResult + , settingsMigrate : Comp.UiSettingsMigrate.Model } @@ -37,9 +39,11 @@ type FormResult type Msg = UiSettingsFormMsg Comp.UiSettingsForm.Msg + | UiSettingsMigrateMsg Comp.UiSettingsMigrate.Msg | Submit | UpdateSettings | SaveSettingsResp UiSettings (Result Http.Error BasicResult) + | ReceiveBrowserSettings StoredUiSettings init : Flags -> UiSettings -> ( Model, Cmd Msg ) @@ -47,12 +51,19 @@ init flags settings = let ( fm, fc ) = Comp.UiSettingsForm.init flags settings + + ( mm, mc ) = + Comp.UiSettingsMigrate.init flags in ( { formModel = fm , settings = Nothing , formResult = FormInit + , settingsMigrate = mm } - , Cmd.map UiSettingsFormMsg fc + , Cmd.batch + [ Cmd.map UiSettingsFormMsg fc + , Cmd.map UiSettingsMigrateMsg mc + ] ) @@ -63,6 +74,7 @@ init flags settings = type alias UpdateResult = { model : Model , cmd : Cmd Msg + , sub : Sub Msg , newSettings : Maybe UiSettings } @@ -95,20 +107,41 @@ update flags settings msg model = 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 -> + let + lm = + UiSettingsMigrateMsg (Comp.UiSettingsMigrate.receiveBrowserSettings sett) + 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 } @@ -116,17 +149,19 @@ update flags settings msg model = 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 Nothing + UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none Nothing UpdateSettings -> let @@ -135,6 +170,7 @@ update flags settings msg model = in { model = { model | formModel = fm } , cmd = Cmd.map UiSettingsFormMsg fc + , sub = Sub.none , newSettings = Nothing } @@ -182,6 +218,10 @@ view2 texts flags settings classes model = , end = [] , rootClasses = "mb-4" } + , div [] + [ Html.map UiSettingsMigrateMsg + (Comp.UiSettingsMigrate.view model.settingsMigrate) + ] , div [ classList [ ( S.successMessage, isSuccess model ) diff --git a/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm b/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm new file mode 100644 index 00000000..58fdad67 --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm @@ -0,0 +1,186 @@ +module Comp.UiSettingsMigrate exposing + ( Model + , Msg + , UpdateResult + , init + , receiveBrowserSettings + , update + , view + ) + +import Api +import Api.Model.BasicResult exposing (BasicResult) +import Data.Flags exposing (Flags) +import Data.UiSettings exposing (StoredUiSettings, UiSettings) +import Html exposing (..) +import Html.Attributes exposing (class, href, title) +import Html.Events exposing (onClick) +import Http +import Messages.Comp.HttpError +import Ports +import Styles as S + + +init : Flags -> ( Model, Cmd Msg ) +init flags = + ( Initialized + , Cmd.batch + [ Api.getClientSettings flags GetClientSettingsResp + , requestBrowserSettings flags + ] + ) + + +type Model + = Initialized + | WaitingForHttp StoredUiSettings + | WaitingForBrowser + | MigrateActive StoredUiSettings + | MigrateDone + | MigrateRequestRunning + | MigrateRequestFailed String + + +type Msg + = GetClientSettingsResp (Result Http.Error UiSettings) + | GetBrowserSettings StoredUiSettings + | MigrateSettings StoredUiSettings + | SaveSettingsResp UiSettings (Result Http.Error BasicResult) + + +receiveBrowserSettings : StoredUiSettings -> Msg +receiveBrowserSettings sett = + GetBrowserSettings sett + + + +--- Update + + +requestBrowserSettings : Flags -> Cmd Msg +requestBrowserSettings flags = + case flags.account of + Just acc -> + Ports.requestUiSettings acc + + Nothing -> + Cmd.none + + +type alias UpdateResult = + { model : Model + , cmd : Cmd Msg + , sub : Sub Msg + , newSettings : Maybe UiSettings + } + + +update : Flags -> Msg -> Model -> UpdateResult +update flags msg model = + let + empty = + { model = model + , cmd = Cmd.none + , sub = Sub.none + , newSettings = Nothing + } + in + case msg of + GetClientSettingsResp (Err (Http.BadStatus 404)) -> + case model of + Initialized -> + { model = WaitingForBrowser + , cmd = requestBrowserSettings flags + , sub = Sub.none + , newSettings = Nothing + } + + WaitingForHttp sett -> + { empty | model = MigrateActive sett } + + _ -> + { empty + | sub = Sub.none + , cmd = requestBrowserSettings flags + , model = model + } + + GetBrowserSettings sett -> + case model of + Initialized -> + { empty | model = WaitingForHttp sett } + + WaitingForBrowser -> + { empty | model = MigrateActive sett } + + _ -> + empty + + GetClientSettingsResp _ -> + { empty | model = MigrateDone } + + MigrateSettings settings -> + let + uiSettings = + Data.UiSettings.merge settings Data.UiSettings.defaults + + cmd = + Api.saveClientSettings flags uiSettings (SaveSettingsResp uiSettings) + in + { empty | model = MigrateRequestRunning, cmd = cmd } + + SaveSettingsResp settings (Ok res) -> + if res.success then + { empty | model = MigrateDone, newSettings = Just settings } + + else + { empty | model = MigrateRequestFailed "Unknown error saving settings." } + + SaveSettingsResp _ (Err err) -> + { empty | model = MigrateRequestFailed <| Messages.Comp.HttpError.gb err } + + + +--- View +{- + Note: this module will be removed later, it only exists for the + transition from storing ui settings at the server. Therefore + strings here are not externalized; translation is not necessary. + +-} + + +view : Model -> Html Msg +view model = + case model of + MigrateActive sett -> + div + [ class (S.box ++ " px-2 py-2") + , class S.infoMessage + , class "flex flex-col" + ] + [ div [ class S.header2 ] [ text "Migrate your settings" ] + , p [ class " mb-3" ] + [ text "The UI settings are now stored at the server. You have " + , text "settings stored at the browser that you can now move to the " + , text "server by clicking below." + ] + , p [ class " mb-2" ] + [ text "Alternatively, change the default settings here and submit " + , text "this form. This message will disappear as soon as there are " + , text "settings at the server." + ] + , div [ class "flex flex-row items-center justify-center" ] + [ a + [ href "#" + , title "Move current settings to the server" + , onClick (MigrateSettings sett) + , class S.primaryButton + ] + [ text "Migrate current settings" + ] + ] + ] + + _ -> + span [ class "hidden" ] [] diff --git a/modules/webapp/src/main/elm/Main.elm b/modules/webapp/src/main/elm/Main.elm index 37cca8fa..3e4b3030 100644 --- a/modules/webapp/src/main/elm/Main.elm +++ b/modules/webapp/src/main/elm/Main.elm @@ -85,4 +85,5 @@ subscriptions : Model -> Sub Msg subscriptions model = Sub.batch [ model.subs + , Ports.receiveUiSettings ReceiveBrowserSettings ] diff --git a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm index 69c8f974..88545c60 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Data.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Data.elm @@ -12,7 +12,7 @@ import Comp.NotificationManage import Comp.ScanMailboxManage import Comp.UiSettingsManage import Data.Flags exposing (Flags) -import Data.UiSettings exposing (UiSettings) +import Data.UiSettings exposing (StoredUiSettings, UiSettings) type alias Model = @@ -62,3 +62,4 @@ type Msg | ScanMailboxMsg Comp.ScanMailboxManage.Msg | UiSettingsMsg Comp.UiSettingsManage.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 5e4ea74f..d9e07609 100644 --- a/modules/webapp/src/main/elm/Page/UserSettings/Update.elm +++ b/modules/webapp/src/main/elm/Page/UserSettings/Update.elm @@ -1,4 +1,4 @@ -module Page.UserSettings.Update exposing (update) +module Page.UserSettings.Update exposing (UpdateResult, update) import Comp.ChangePasswordForm import Comp.EmailSettingsManage @@ -11,7 +11,15 @@ import Data.UiSettings exposing (UiSettings) import Page.UserSettings.Data exposing (..) -update : Flags -> UiSettings -> Msg -> Model -> ( Model, Cmd Msg, Maybe UiSettings ) +type alias UpdateResult = + { model : Model + , cmd : Cmd Msg + , sub : Sub Msg + , newSettings : Maybe UiSettings + } + + +update : Flags -> UiSettings -> Msg -> Model -> UpdateResult update flags settings msg model = case msg of SetTab t -> @@ -25,23 +33,25 @@ update flags settings msg model = ( em, c ) = Comp.EmailSettingsManage.init flags in - ( { m | emailSettingsModel = em } - , Cmd.map EmailSettingsMsg c - , Nothing - ) + { model = { m | emailSettingsModel = em } + , cmd = Cmd.map EmailSettingsMsg c + , sub = Sub.none + , newSettings = Nothing + } ImapSettingsTab -> let ( em, c ) = Comp.ImapSettingsManage.init flags in - ( { m | imapSettingsModel = em } - , Cmd.map ImapSettingsMsg c - , Nothing - ) + { model = { m | imapSettingsModel = em } + , cmd = Cmd.map ImapSettingsMsg c + , sub = Sub.none + , newSettings = Nothing + } ChangePassTab -> - ( m, Cmd.none, Nothing ) + UpdateResult m Cmd.none Sub.none Nothing NotificationTab -> let @@ -49,7 +59,7 @@ update flags settings msg model = Cmd.map NotificationMsg (Tuple.second (Comp.NotificationManage.init flags)) in - ( m, initCmd, Nothing ) + UpdateResult m initCmd Sub.none Nothing ScanMailboxTab -> let @@ -57,73 +67,86 @@ update flags settings msg model = Cmd.map ScanMailboxMsg (Tuple.second (Comp.ScanMailboxManage.init flags)) in - ( m, initCmd, Nothing ) + UpdateResult m initCmd Sub.none Nothing UiSettingsTab -> - ( m, Cmd.none, Nothing ) + UpdateResult m Cmd.none Sub.none Nothing ChangePassMsg m -> let ( m2, c2 ) = Comp.ChangePasswordForm.update flags m model.changePassModel in - ( { model | changePassModel = m2 } - , Cmd.map ChangePassMsg c2 - , Nothing - ) + { model = { model | changePassModel = m2 } + , cmd = Cmd.map ChangePassMsg c2 + , sub = Sub.none + , newSettings = Nothing + } EmailSettingsMsg m -> let ( m2, c2 ) = Comp.EmailSettingsManage.update flags m model.emailSettingsModel in - ( { model | emailSettingsModel = m2 } - , Cmd.map EmailSettingsMsg c2 - , Nothing - ) + { model = { model | emailSettingsModel = m2 } + , cmd = Cmd.map EmailSettingsMsg c2 + , sub = Sub.none + , newSettings = Nothing + } ImapSettingsMsg m -> let ( m2, c2 ) = Comp.ImapSettingsManage.update flags m model.imapSettingsModel in - ( { model | imapSettingsModel = m2 } - , Cmd.map ImapSettingsMsg c2 - , Nothing - ) + { model = { model | imapSettingsModel = m2 } + , cmd = Cmd.map ImapSettingsMsg c2 + , sub = Sub.none + , newSettings = Nothing + } NotificationMsg lm -> let ( m2, c2 ) = Comp.NotificationManage.update flags lm model.notificationModel in - ( { model | notificationModel = m2 } - , Cmd.map NotificationMsg c2 - , Nothing - ) + { model = { model | notificationModel = m2 } + , cmd = Cmd.map NotificationMsg c2 + , sub = Sub.none + , newSettings = Nothing + } ScanMailboxMsg lm -> let ( m2, c2 ) = Comp.ScanMailboxManage.update flags lm model.scanMailboxModel in - ( { model | scanMailboxModel = m2 } - , Cmd.map ScanMailboxMsg c2 - , Nothing - ) + { model = { model | scanMailboxModel = m2 } + , cmd = Cmd.map ScanMailboxMsg c2 + , sub = Sub.none + , newSettings = Nothing + } UiSettingsMsg lm -> let res = Comp.UiSettingsManage.update flags settings lm model.uiSettingsModel in - ( { model | uiSettingsModel = res.model } - , Cmd.map UiSettingsMsg res.cmd - , res.newSettings - ) + { model = { model | uiSettingsModel = res.model } + , cmd = Cmd.map UiSettingsMsg res.cmd + , sub = Sub.map UiSettingsMsg res.sub + , newSettings = res.newSettings + } UpdateSettings -> update flags settings (UiSettingsMsg Comp.UiSettingsManage.UpdateSettings) model + + ReceiveBrowserSettings sett -> + let + lm = + Comp.UiSettingsManage.ReceiveBrowserSettings sett + in + update flags settings (UiSettingsMsg lm) model diff --git a/modules/webapp/src/main/elm/Ports.elm b/modules/webapp/src/main/elm/Ports.elm index af3dad91..0e9ad372 100644 --- a/modules/webapp/src/main/elm/Ports.elm +++ b/modules/webapp/src/main/elm/Ports.elm @@ -2,13 +2,16 @@ port module Ports exposing ( checkSearchQueryString , initClipboard , receiveCheckQueryResult + , receiveUiSettings , removeAccount + , requestUiSettings , setAccount , setUiTheme ) import Api.Model.AuthResult exposing (AuthResult) import Data.QueryParseResult exposing (QueryParseResult) +import Data.UiSettings exposing (StoredUiSettings) import Data.UiTheme exposing (UiTheme) @@ -32,6 +35,12 @@ port receiveCheckQueryResult : (QueryParseResult -> msg) -> Sub msg port initClipboard : ( String, String ) -> Cmd msg +port receiveUiSettings : (StoredUiSettings -> msg) -> Sub msg + + +port requestUiSettings : AuthResult -> Cmd msg + + setUiTheme : UiTheme -> Cmd msg setUiTheme theme = internalSetUiTheme (Data.UiTheme.toString theme) diff --git a/modules/webapp/src/main/webjar/docspell.js b/modules/webapp/src/main/webjar/docspell.js index 4c82861a..d13f6889 100644 --- a/modules/webapp/src/main/webjar/docspell.js +++ b/modules/webapp/src/main/webjar/docspell.js @@ -50,43 +50,25 @@ elmApp.ports.removeAccount.subscribe(function() { localStorage.removeItem("account"); }); - -// elmApp.ports.saveUiSettings.subscribe(function(args) { -// if (Array.isArray(args) && args.length == 2) { -// var authResult = args[0]; -// var settings = args[1]; -// if (authResult && settings) { -// var key = authResult.collective + "/" + authResult.user + "/uiSettings"; -// console.log("Save ui settings to local storage"); -// localStorage.setItem(key, JSON.stringify(settings)); -// elmApp.ports.receiveUiSettings.send(settings); -// elmApp.ports.uiSettingsSaved.send(null); -// } -// } -// }); - -// elmApp.ports.requestUiSettings.subscribe(function(args) { -// console.log("Requesting ui settings"); -// if (Array.isArray(args) && args.length == 2) { -// var account = args[0]; -// var defaults = args[1]; -// 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); -// var data = settings ? JSON.parse(settings) : null; -// if (data && defaults) { -// var defaults = extend(defaults, data); -// elmApp.ports.receiveUiSettings.send(defaults); -// } else if (defaults) { -// elmApp.ports.receiveUiSettings.send(defaults); -// } -// } else if (defaults) { -// elmApp.ports.receiveUiSettings.send(defaults); -// } -// } -// }); +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 = {};