Add a way to migrate settings stored at the browser to the server

This commit is contained in:
Eike Kettner 2021-05-27 01:07:36 +02:00
parent 5a4f6c0595
commit e406718cb7
9 changed files with 338 additions and 85 deletions

View File

@ -12,7 +12,7 @@ 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.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (StoredUiSettings, UiSettings)
import Data.UiTheme exposing (UiTheme) import Data.UiTheme exposing (UiTheme)
import Http import Http
import Messages.UiLanguage exposing (UiLanguage) import Messages.UiLanguage exposing (UiLanguage)
@ -162,6 +162,7 @@ type Msg
| ToggleLangMenu | ToggleLangMenu
| SetLanguage UiLanguage | SetLanguage UiLanguage
| ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) | ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult)
| ReceiveBrowserSettings StoredUiSettings
defaultPage : Flags -> Page defaultPage : Flags -> Page

View File

@ -275,6 +275,13 @@ 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 lm model
applyClientSettings : Model -> UiSettings -> ( Model, Cmd Msg, Sub Msg ) applyClientSettings : Model -> UiSettings -> ( Model, Cmd Msg, Sub Msg )
applyClientSettings model settings = applyClientSettings model settings =
@ -379,14 +386,14 @@ updateQueue lmsg model =
updateUserSettings : Page.UserSettings.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg ) updateUserSettings : Page.UserSettings.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
updateUserSettings lmsg model = updateUserSettings lmsg model =
let let
( lm, lc, newClientSettings ) = result =
Page.UserSettings.Update.update model.flags model.uiSettings lmsg model.userSettingsModel Page.UserSettings.Update.update model.flags model.uiSettings lmsg model.userSettingsModel
model_ = model_ =
{ model | userSettingsModel = lm } { model | userSettingsModel = result.model }
( lm2, lc2, s2 ) = ( lm2, lc2, s2 ) =
case newClientSettings of case result.newSettings of
Just sett -> Just sett ->
applyClientSettings model_ sett applyClientSettings model_ sett
@ -395,10 +402,13 @@ updateUserSettings lmsg model =
in in
( lm2 ( lm2
, Cmd.batch , Cmd.batch
[ Cmd.map UserSettingsMsg lc [ Cmd.map UserSettingsMsg result.cmd
, lc2 , lc2
] ]
, s2 , Sub.batch
[ Sub.map UserSettingsMsg result.sub
, s2
]
) )

View File

@ -11,8 +11,9 @@ import Api
import Api.Model.BasicResult exposing (BasicResult) import Api.Model.BasicResult exposing (BasicResult)
import Comp.MenuBar as MB import Comp.MenuBar as MB
import Comp.UiSettingsForm import Comp.UiSettingsForm
import Comp.UiSettingsMigrate
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (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
@ -24,6 +25,7 @@ type alias Model =
{ formModel : Comp.UiSettingsForm.Model { formModel : Comp.UiSettingsForm.Model
, settings : Maybe UiSettings , settings : Maybe UiSettings
, formResult : FormResult , formResult : FormResult
, settingsMigrate : Comp.UiSettingsMigrate.Model
} }
@ -37,9 +39,11 @@ type FormResult
type Msg type Msg
= UiSettingsFormMsg Comp.UiSettingsForm.Msg = UiSettingsFormMsg Comp.UiSettingsForm.Msg
| UiSettingsMigrateMsg Comp.UiSettingsMigrate.Msg
| Submit | Submit
| UpdateSettings | UpdateSettings
| SaveSettingsResp UiSettings (Result Http.Error BasicResult) | SaveSettingsResp UiSettings (Result Http.Error BasicResult)
| ReceiveBrowserSettings StoredUiSettings
init : Flags -> UiSettings -> ( Model, Cmd Msg ) init : Flags -> UiSettings -> ( Model, Cmd Msg )
@ -47,12 +51,19 @@ init flags settings =
let let
( fm, fc ) = ( fm, fc ) =
Comp.UiSettingsForm.init flags settings Comp.UiSettingsForm.init flags settings
( mm, mc ) =
Comp.UiSettingsMigrate.init flags
in in
( { formModel = fm ( { formModel = fm
, settings = Nothing , settings = Nothing
, formResult = FormInit , 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 = type alias UpdateResult =
{ model : Model { model : Model
, cmd : Cmd Msg , cmd : Cmd Msg
, sub : Sub Msg
, newSettings : Maybe UiSettings , newSettings : Maybe UiSettings
} }
@ -95,20 +107,41 @@ update flags settings msg model =
model.formResult model.formResult
} }
, cmd = Cmd.none , cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing , 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 -> Submit ->
case model.settings of case model.settings of
Just s -> Just s ->
{ model = { model | formResult = FormInit } { model = { model | formResult = FormInit }
, cmd = Api.saveClientSettings flags s (SaveSettingsResp s) , cmd = Api.saveClientSettings flags s (SaveSettingsResp s)
, sub = Sub.none
, newSettings = Nothing , newSettings = Nothing
} }
Nothing -> Nothing ->
{ model = { model | formResult = FormUnchanged } { model = { model | formResult = FormUnchanged }
, cmd = Cmd.none , cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing , newSettings = Nothing
} }
@ -116,17 +149,19 @@ update flags settings msg model =
if res.success then if res.success then
{ model = { model | formResult = FormSaved } { model = { model | formResult = FormSaved }
, cmd = Cmd.none , cmd = Cmd.none
, sub = Sub.none
, newSettings = Just newSettings , newSettings = Just newSettings
} }
else else
{ model = { model | formResult = FormUnknownError } { model = { model | formResult = FormUnknownError }
, cmd = Cmd.none , cmd = Cmd.none
, sub = Sub.none
, newSettings = Nothing , newSettings = Nothing
} }
SaveSettingsResp _ (Err err) -> SaveSettingsResp _ (Err err) ->
UpdateResult { model | formResult = FormHttpError err } Cmd.none Nothing UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none Nothing
UpdateSettings -> UpdateSettings ->
let let
@ -135,6 +170,7 @@ update flags settings msg model =
in in
{ model = { model | formModel = fm } { model = { model | formModel = fm }
, cmd = Cmd.map UiSettingsFormMsg fc , cmd = Cmd.map UiSettingsFormMsg fc
, sub = Sub.none
, newSettings = Nothing , newSettings = Nothing
} }
@ -182,6 +218,10 @@ view2 texts flags settings classes model =
, end = [] , 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 )

View File

@ -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" ] []

View File

@ -85,4 +85,5 @@ subscriptions : Model -> Sub Msg
subscriptions model = subscriptions model =
Sub.batch Sub.batch
[ model.subs [ model.subs
, Ports.receiveUiSettings ReceiveBrowserSettings
] ]

View File

@ -12,7 +12,7 @@ import Comp.NotificationManage
import Comp.ScanMailboxManage import Comp.ScanMailboxManage
import Comp.UiSettingsManage import Comp.UiSettingsManage
import Data.Flags exposing (Flags) import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings) import Data.UiSettings exposing (StoredUiSettings, UiSettings)
type alias Model = type alias Model =
@ -62,3 +62,4 @@ type Msg
| ScanMailboxMsg Comp.ScanMailboxManage.Msg | ScanMailboxMsg Comp.ScanMailboxManage.Msg
| UiSettingsMsg Comp.UiSettingsManage.Msg | UiSettingsMsg Comp.UiSettingsManage.Msg
| UpdateSettings | UpdateSettings
| ReceiveBrowserSettings StoredUiSettings

View File

@ -1,4 +1,4 @@
module Page.UserSettings.Update exposing (update) module Page.UserSettings.Update exposing (UpdateResult, update)
import Comp.ChangePasswordForm import Comp.ChangePasswordForm
import Comp.EmailSettingsManage import Comp.EmailSettingsManage
@ -11,7 +11,15 @@ import Data.UiSettings exposing (UiSettings)
import Page.UserSettings.Data exposing (..) 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 = update flags settings msg model =
case msg of case msg of
SetTab t -> SetTab t ->
@ -25,23 +33,25 @@ update flags settings msg model =
( em, c ) = ( em, c ) =
Comp.EmailSettingsManage.init flags Comp.EmailSettingsManage.init flags
in in
( { m | emailSettingsModel = em } { model = { m | emailSettingsModel = em }
, Cmd.map EmailSettingsMsg c , cmd = Cmd.map EmailSettingsMsg c
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
ImapSettingsTab -> ImapSettingsTab ->
let let
( em, c ) = ( em, c ) =
Comp.ImapSettingsManage.init flags Comp.ImapSettingsManage.init flags
in in
( { m | imapSettingsModel = em } { model = { m | imapSettingsModel = em }
, Cmd.map ImapSettingsMsg c , cmd = Cmd.map ImapSettingsMsg c
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
ChangePassTab -> ChangePassTab ->
( m, Cmd.none, Nothing ) UpdateResult m Cmd.none Sub.none Nothing
NotificationTab -> NotificationTab ->
let let
@ -49,7 +59,7 @@ update flags settings msg model =
Cmd.map NotificationMsg Cmd.map NotificationMsg
(Tuple.second (Comp.NotificationManage.init flags)) (Tuple.second (Comp.NotificationManage.init flags))
in in
( m, initCmd, Nothing ) UpdateResult m initCmd Sub.none Nothing
ScanMailboxTab -> ScanMailboxTab ->
let let
@ -57,73 +67,86 @@ 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
( m, initCmd, Nothing ) UpdateResult m initCmd Sub.none Nothing
UiSettingsTab -> UiSettingsTab ->
( m, Cmd.none, Nothing ) UpdateResult m Cmd.none Sub.none Nothing
ChangePassMsg m -> ChangePassMsg m ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.ChangePasswordForm.update flags m model.changePassModel Comp.ChangePasswordForm.update flags m model.changePassModel
in in
( { model | changePassModel = m2 } { model = { model | changePassModel = m2 }
, Cmd.map ChangePassMsg c2 , cmd = Cmd.map ChangePassMsg c2
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
EmailSettingsMsg m -> EmailSettingsMsg m ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.EmailSettingsManage.update flags m model.emailSettingsModel Comp.EmailSettingsManage.update flags m model.emailSettingsModel
in in
( { model | emailSettingsModel = m2 } { model = { model | emailSettingsModel = m2 }
, Cmd.map EmailSettingsMsg c2 , cmd = Cmd.map EmailSettingsMsg c2
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
ImapSettingsMsg m -> ImapSettingsMsg m ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.ImapSettingsManage.update flags m model.imapSettingsModel Comp.ImapSettingsManage.update flags m model.imapSettingsModel
in in
( { model | imapSettingsModel = m2 } { model = { model | imapSettingsModel = m2 }
, Cmd.map ImapSettingsMsg c2 , cmd = Cmd.map ImapSettingsMsg c2
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
NotificationMsg lm -> NotificationMsg lm ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.NotificationManage.update flags lm model.notificationModel Comp.NotificationManage.update flags lm model.notificationModel
in in
( { model | notificationModel = m2 } { model = { model | notificationModel = m2 }
, Cmd.map NotificationMsg c2 , cmd = Cmd.map NotificationMsg c2
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
ScanMailboxMsg lm -> ScanMailboxMsg lm ->
let let
( m2, c2 ) = ( m2, c2 ) =
Comp.ScanMailboxManage.update flags lm model.scanMailboxModel Comp.ScanMailboxManage.update flags lm model.scanMailboxModel
in in
( { model | scanMailboxModel = m2 } { model = { model | scanMailboxModel = m2 }
, Cmd.map ScanMailboxMsg c2 , cmd = Cmd.map ScanMailboxMsg c2
, Nothing , sub = Sub.none
) , newSettings = Nothing
}
UiSettingsMsg lm -> UiSettingsMsg lm ->
let let
res = res =
Comp.UiSettingsManage.update flags settings lm model.uiSettingsModel Comp.UiSettingsManage.update flags settings lm model.uiSettingsModel
in in
( { model | uiSettingsModel = res.model } { model = { model | uiSettingsModel = res.model }
, Cmd.map UiSettingsMsg res.cmd , cmd = Cmd.map UiSettingsMsg res.cmd
, res.newSettings , sub = Sub.map UiSettingsMsg res.sub
) , newSettings = res.newSettings
}
UpdateSettings -> UpdateSettings ->
update flags update flags
settings settings
(UiSettingsMsg Comp.UiSettingsManage.UpdateSettings) (UiSettingsMsg Comp.UiSettingsManage.UpdateSettings)
model model
ReceiveBrowserSettings sett ->
let
lm =
Comp.UiSettingsManage.ReceiveBrowserSettings sett
in
update flags settings (UiSettingsMsg lm) model

View File

@ -2,13 +2,16 @@ port module Ports exposing
( checkSearchQueryString ( checkSearchQueryString
, initClipboard , initClipboard
, receiveCheckQueryResult , receiveCheckQueryResult
, receiveUiSettings
, removeAccount , removeAccount
, requestUiSettings
, setAccount , setAccount
, setUiTheme , setUiTheme
) )
import Api.Model.AuthResult exposing (AuthResult) import Api.Model.AuthResult exposing (AuthResult)
import Data.QueryParseResult exposing (QueryParseResult) import Data.QueryParseResult exposing (QueryParseResult)
import Data.UiSettings exposing (StoredUiSettings)
import Data.UiTheme exposing (UiTheme) import Data.UiTheme exposing (UiTheme)
@ -32,6 +35,12 @@ 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
setUiTheme : UiTheme -> Cmd msg setUiTheme : UiTheme -> Cmd msg
setUiTheme theme = setUiTheme theme =
internalSetUiTheme (Data.UiTheme.toString theme) internalSetUiTheme (Data.UiTheme.toString theme)

View File

@ -50,43 +50,25 @@ elmApp.ports.removeAccount.subscribe(function() {
localStorage.removeItem("account"); localStorage.removeItem("account");
}); });
elmApp.ports.requestUiSettings.subscribe(function(args) {
// elmApp.ports.saveUiSettings.subscribe(function(args) { console.log("Requesting ui settings");
// if (Array.isArray(args) && args.length == 2) { var account = args;
// var authResult = args[0]; var collective = account ? account.collective : null;
// var settings = args[1]; var user = account ? account.user : null;
// if (authResult && settings) { if (collective && user) {
// var key = authResult.collective + "/" + authResult.user + "/uiSettings"; var key = collective + "/" + user + "/uiSettings";
// console.log("Save ui settings to local storage"); var settings = localStorage.getItem(key);
// localStorage.setItem(key, JSON.stringify(settings)); try {
// elmApp.ports.receiveUiSettings.send(settings); var data = settings ? JSON.parse(settings) : null;
// elmApp.ports.uiSettingsSaved.send(null); if (data) {
// } console.log("Sending browser ui settings");
// } elmApp.ports.receiveUiSettings.send(data);
// }); }
} catch (error) {
// elmApp.ports.requestUiSettings.subscribe(function(args) { console.log(error);
// 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);
// }
// }
// });
var docspell_clipboards = {}; var docspell_clipboards = {};