mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-04-05 10:59:33 +00:00
Merge pull request #834 from eikek/feature/usersettings-in-db
Feature/usersettings in db
This commit is contained in:
commit
de19b1d216
@ -38,6 +38,7 @@ trait BackendApp[F[_]] {
|
||||
def folder: OFolder[F]
|
||||
def customFields: OCustomFields[F]
|
||||
def simpleSearch: OSimpleSearch[F]
|
||||
def clientSettings: OClientSettings[F]
|
||||
}
|
||||
|
||||
object BackendApp {
|
||||
@ -73,26 +74,28 @@ object BackendApp {
|
||||
folderImpl <- OFolder(store)
|
||||
customFieldsImpl <- OCustomFields(store)
|
||||
simpleSearchImpl = OSimpleSearch(fulltextImpl, itemSearchImpl)
|
||||
clientSettingsImpl <- OClientSettings(store)
|
||||
} yield new BackendApp[F] {
|
||||
val login = loginImpl
|
||||
val signup = signupImpl
|
||||
val collective = collImpl
|
||||
val source = sourceImpl
|
||||
val tag = tagImpl
|
||||
val equipment = equipImpl
|
||||
val organization = orgImpl
|
||||
val upload = uploadImpl
|
||||
val node = nodeImpl
|
||||
val job = jobImpl
|
||||
val item = itemImpl
|
||||
val itemSearch = itemSearchImpl
|
||||
val fulltext = fulltextImpl
|
||||
val mail = mailImpl
|
||||
val joex = joexImpl
|
||||
val userTask = userTaskImpl
|
||||
val folder = folderImpl
|
||||
val customFields = customFieldsImpl
|
||||
val simpleSearch = simpleSearchImpl
|
||||
val login = loginImpl
|
||||
val signup = signupImpl
|
||||
val collective = collImpl
|
||||
val source = sourceImpl
|
||||
val tag = tagImpl
|
||||
val equipment = equipImpl
|
||||
val organization = orgImpl
|
||||
val upload = uploadImpl
|
||||
val node = nodeImpl
|
||||
val job = jobImpl
|
||||
val item = itemImpl
|
||||
val itemSearch = itemSearchImpl
|
||||
val fulltext = fulltextImpl
|
||||
val mail = mailImpl
|
||||
val joex = joexImpl
|
||||
val userTask = userTaskImpl
|
||||
val folder = folderImpl
|
||||
val customFields = customFieldsImpl
|
||||
val simpleSearch = simpleSearchImpl
|
||||
val clientSettings = clientSettingsImpl
|
||||
}
|
||||
|
||||
def apply[F[_]: ConcurrentEffect: ContextShift](
|
||||
|
@ -0,0 +1,78 @@
|
||||
package docspell.backend.ops
|
||||
|
||||
import cats.data.OptionT
|
||||
import cats.effect.{Effect, Resource}
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common.AccountId
|
||||
import docspell.common._
|
||||
import docspell.common.syntax.all._
|
||||
import docspell.store.Store
|
||||
import docspell.store.records.RClientSettings
|
||||
import docspell.store.records.RUser
|
||||
|
||||
import io.circe.Json
|
||||
import org.log4s._
|
||||
|
||||
trait OClientSettings[F[_]] {
|
||||
|
||||
def delete(clientId: Ident, account: AccountId): F[Boolean]
|
||||
def save(clientId: Ident, account: AccountId, data: Json): F[Unit]
|
||||
def load(clientId: Ident, account: AccountId): F[Option[RClientSettings]]
|
||||
|
||||
}
|
||||
|
||||
object OClientSettings {
|
||||
private[this] val logger = getLogger
|
||||
|
||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OClientSettings[F]] =
|
||||
Resource.pure[F, OClientSettings[F]](new OClientSettings[F] {
|
||||
|
||||
private def getUserId(account: AccountId): OptionT[F, Ident] =
|
||||
OptionT(store.transact(RUser.findByAccount(account))).map(_.uid)
|
||||
|
||||
def delete(clientId: Ident, account: AccountId): F[Boolean] =
|
||||
(for {
|
||||
_ <- OptionT.liftF(
|
||||
logger.fdebug(
|
||||
s"Deleting client settings for client ${clientId.id} and account $account"
|
||||
)
|
||||
)
|
||||
userId <- getUserId(account)
|
||||
n <- OptionT.liftF(
|
||||
store.transact(
|
||||
RClientSettings.delete(clientId, userId)
|
||||
)
|
||||
)
|
||||
} yield n > 0).getOrElse(false)
|
||||
|
||||
def save(clientId: Ident, account: AccountId, data: Json): F[Unit] =
|
||||
(for {
|
||||
_ <- OptionT.liftF(
|
||||
logger.fdebug(
|
||||
s"Storing client settings for client ${clientId.id} and account $account"
|
||||
)
|
||||
)
|
||||
userId <- getUserId(account)
|
||||
n <- OptionT.liftF(
|
||||
store.transact(RClientSettings.upsert(clientId, userId, data))
|
||||
)
|
||||
_ <- OptionT.liftF(
|
||||
if (n <= 0) Effect[F].raiseError(new Exception("No rows updated!"))
|
||||
else ().pure[F]
|
||||
)
|
||||
} yield ()).getOrElse(())
|
||||
|
||||
def load(clientId: Ident, account: AccountId): F[Option[RClientSettings]] =
|
||||
(for {
|
||||
_ <- OptionT.liftF(
|
||||
logger.fdebug(
|
||||
s"Loading client settings for client ${clientId.id} and account $account"
|
||||
)
|
||||
)
|
||||
userId <- getUserId(account)
|
||||
data <- OptionT(store.transact(RClientSettings.find(clientId, userId)))
|
||||
} yield data).value
|
||||
|
||||
})
|
||||
}
|
@ -1185,6 +1185,68 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
|
||||
/sec/clientSettings/{clientId}:
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/clientId"
|
||||
get:
|
||||
tags: [ Client Settings ]
|
||||
summary: Return the current user settings
|
||||
description: |
|
||||
Returns the settings for the current user. The `clientId` is
|
||||
an identifier to a client application. It returns a JSON
|
||||
structure. The server doesn't care about the actual data,
|
||||
since it is meant to be interpreted by clients.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
put:
|
||||
tags: [ Client Settings ]
|
||||
summary: Update current user settings
|
||||
description: |
|
||||
Updates (replaces or creates) the current user's settings with
|
||||
the given data. The `clientId` is an identifier to a client
|
||||
application. The request body is expected to be JSON, the
|
||||
structure is not important to the server.
|
||||
|
||||
The data is stored for the current user and given `clientId`.
|
||||
|
||||
The data is only saved without being checked in any way
|
||||
(besides being valid JSON). It is returned "as is" to the
|
||||
client in the corresponding GET endpoint.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema: {}
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
delete:
|
||||
tags: [ Client Settings ]
|
||||
summary: Clears the current user settings
|
||||
description: |
|
||||
Removes all stored user settings for the client identified by
|
||||
`clientId`.
|
||||
security:
|
||||
- authTokenHeader: []
|
||||
responses:
|
||||
200:
|
||||
description: Ok
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BasicResult"
|
||||
|
||||
/admin/user/resetPassword:
|
||||
post:
|
||||
tags: [ Collective, Admin ]
|
||||
@ -5571,3 +5633,11 @@ components:
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
clientId:
|
||||
name: clientId
|
||||
in: path
|
||||
required: true
|
||||
description: |
|
||||
some identifier for a client application
|
||||
schema:
|
||||
type: string
|
||||
|
@ -91,7 +91,8 @@ object RestServer {
|
||||
"calevent/check" -> CalEventCheckRoutes(),
|
||||
"fts" -> FullTextIndexRoutes.secured(cfg, restApp.backend, token),
|
||||
"folder" -> FolderRoutes(restApp.backend, token),
|
||||
"customfield" -> CustomFieldRoutes(restApp.backend, token)
|
||||
"customfield" -> CustomFieldRoutes(restApp.backend, token),
|
||||
"clientSettings" -> ClientSettingsRoutes(restApp.backend, token)
|
||||
)
|
||||
|
||||
def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
|
||||
|
@ -0,0 +1,52 @@
|
||||
package docspell.restserver.routes
|
||||
|
||||
import cats.effect._
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.backend.BackendApp
|
||||
import docspell.backend.auth.AuthToken
|
||||
import docspell.common._
|
||||
import docspell.restapi.model._
|
||||
|
||||
import io.circe.Json
|
||||
import org.http4s.HttpRoutes
|
||||
import org.http4s.circe.CirceEntityDecoder._
|
||||
import org.http4s.circe.CirceEntityEncoder._
|
||||
import org.http4s.dsl.Http4sDsl
|
||||
|
||||
object ClientSettingsRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case req @ PUT -> Root / Ident(clientId) =>
|
||||
for {
|
||||
data <- req.as[Json]
|
||||
_ <- backend.clientSettings.save(clientId, user.account, data)
|
||||
res <- Ok(BasicResult(true, "Settings stored"))
|
||||
} yield res
|
||||
|
||||
case GET -> Root / Ident(clientId) =>
|
||||
for {
|
||||
data <- backend.clientSettings.load(clientId, user.account)
|
||||
res <- data match {
|
||||
case Some(d) => Ok(d.settingsData)
|
||||
case None => NotFound()
|
||||
}
|
||||
} yield res
|
||||
|
||||
case DELETE -> Root / Ident(clientId) =>
|
||||
for {
|
||||
flag <- backend.clientSettings.delete(clientId, user.account)
|
||||
res <- Ok(
|
||||
BasicResult(
|
||||
flag,
|
||||
if (flag) "Settings deleted" else "Deleting settings failed"
|
||||
)
|
||||
)
|
||||
} yield res
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
CREATE TABLE "client_settings" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"client_id" varchar(254) not null,
|
||||
"user_id" varchar(254) not null,
|
||||
"settings_data" text not null,
|
||||
"created" timestamp not null,
|
||||
"updated" timestamp not null,
|
||||
foreign key ("user_id") references "user_"("uid") on delete cascade,
|
||||
unique ("client_id", "user_id")
|
||||
);
|
@ -0,0 +1,10 @@
|
||||
CREATE TABLE `client_settings` (
|
||||
`id` varchar(254) not null primary key,
|
||||
`client_id` varchar(254) not null,
|
||||
`user_id` varchar(254) not null,
|
||||
`settings_data` longtext not null,
|
||||
`created` timestamp not null,
|
||||
`updated` timestamp not null,
|
||||
foreign key (`user_id`) references `user_`(`uid`) on delete cascade,
|
||||
unique (`client_id`, `user_id`)
|
||||
);
|
@ -0,0 +1,10 @@
|
||||
CREATE TABLE "client_settings" (
|
||||
"id" varchar(254) not null primary key,
|
||||
"client_id" varchar(254) not null,
|
||||
"user_id" varchar(254) not null,
|
||||
"settings_data" text not null,
|
||||
"created" timestamp not null,
|
||||
"updated" timestamp not null,
|
||||
foreign key ("user_id") references "user_"("uid") on delete cascade,
|
||||
unique ("client_id", "user_id")
|
||||
);
|
@ -11,6 +11,7 @@ import doobie._
|
||||
import doobie.implicits.legacy.instant._
|
||||
import doobie.util.log.Success
|
||||
import emil.doobie.EmilDoobieMeta
|
||||
import io.circe.Json
|
||||
import io.circe.{Decoder, Encoder}
|
||||
|
||||
trait DoobieMeta extends EmilDoobieMeta {
|
||||
@ -112,10 +113,18 @@ trait DoobieMeta extends EmilDoobieMeta {
|
||||
|
||||
implicit val metaOrgUse: Meta[OrgUse] =
|
||||
Meta[String].timap(OrgUse.unsafeFromString)(_.name)
|
||||
|
||||
implicit val metaJsonString: Meta[Json] =
|
||||
Meta[String].timap(DoobieMeta.parseJsonUnsafe)(_.noSpaces)
|
||||
}
|
||||
|
||||
object DoobieMeta extends DoobieMeta {
|
||||
import org.log4s._
|
||||
private val logger = getLogger
|
||||
|
||||
private def parseJsonUnsafe(str: String): Json =
|
||||
io.circe.parser
|
||||
.parse(str)
|
||||
.fold(throw _, identity)
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,79 @@
|
||||
package docspell.store.records
|
||||
|
||||
import cats.data.NonEmptyList
|
||||
import cats.implicits._
|
||||
|
||||
import docspell.common._
|
||||
import docspell.store.qb.DSL._
|
||||
import docspell.store.qb._
|
||||
|
||||
import doobie._
|
||||
import doobie.implicits._
|
||||
import io.circe.Json
|
||||
|
||||
case class RClientSettings(
|
||||
id: Ident,
|
||||
clientId: Ident,
|
||||
userId: Ident,
|
||||
settingsData: Json,
|
||||
updated: Timestamp,
|
||||
created: Timestamp
|
||||
) {}
|
||||
|
||||
object RClientSettings {
|
||||
|
||||
final case class Table(alias: Option[String]) extends TableDef {
|
||||
val tableName = "client_settings"
|
||||
|
||||
val id = Column[Ident]("id", this)
|
||||
val clientId = Column[Ident]("client_id", this)
|
||||
val userId = Column[Ident]("user_id", this)
|
||||
val settingsData = Column[Json]("settings_data", this)
|
||||
val updated = Column[Timestamp]("updated", this)
|
||||
val created = Column[Timestamp]("created", this)
|
||||
val all =
|
||||
NonEmptyList.of[Column[_]](id, clientId, userId, settingsData, updated, created)
|
||||
}
|
||||
|
||||
def as(alias: String): Table = Table(Some(alias))
|
||||
val T = Table(None)
|
||||
|
||||
def insert(v: RClientSettings): ConnectionIO[Int] = {
|
||||
val t = Table(None)
|
||||
DML.insert(
|
||||
t,
|
||||
t.all,
|
||||
fr"${v.id},${v.clientId},${v.userId},${v.settingsData},${v.updated},${v.created}"
|
||||
)
|
||||
}
|
||||
|
||||
def updateSettings(
|
||||
clientId: Ident,
|
||||
userId: Ident,
|
||||
data: Json,
|
||||
updateTs: Timestamp
|
||||
): ConnectionIO[Int] =
|
||||
DML.update(
|
||||
T,
|
||||
T.clientId === clientId && T.userId === userId,
|
||||
DML.set(T.settingsData.setTo(data), T.updated.setTo(updateTs))
|
||||
)
|
||||
|
||||
def upsert(clientId: Ident, userId: Ident, data: Json): ConnectionIO[Int] =
|
||||
for {
|
||||
id <- Ident.randomId[ConnectionIO]
|
||||
now <- Timestamp.current[ConnectionIO]
|
||||
nup <- updateSettings(clientId, userId, data, now)
|
||||
nin <-
|
||||
if (nup <= 0) insert(RClientSettings(id, clientId, userId, data, now, now))
|
||||
else 0.pure[ConnectionIO]
|
||||
} yield nup + nin
|
||||
|
||||
def delete(clientId: Ident, userId: Ident): ConnectionIO[Int] =
|
||||
DML.delete(T, T.clientId === clientId && T.userId === userId)
|
||||
|
||||
def find(clientId: Ident, userId: Ident): ConnectionIO[Option[RClientSettings]] =
|
||||
run(select(T.all), from(T), T.clientId === clientId && T.userId === userId)
|
||||
.query[RClientSettings]
|
||||
.option
|
||||
}
|
@ -37,6 +37,7 @@ module Api exposing
|
||||
, deleteUser
|
||||
, fileURL
|
||||
, getAttachmentMeta
|
||||
, getClientSettings
|
||||
, getCollective
|
||||
, getCollectiveSettings
|
||||
, getContacts
|
||||
@ -51,7 +52,6 @@ module Api exposing
|
||||
, getJobQueueState
|
||||
, getJobQueueStateIn
|
||||
, getMailSettings
|
||||
, getNewUi
|
||||
, getNotifyDueItems
|
||||
, getOrgFull
|
||||
, getOrgLight
|
||||
@ -92,6 +92,7 @@ module Api exposing
|
||||
, removeTagsMultiple
|
||||
, reprocessItem
|
||||
, reprocessMultiple
|
||||
, saveClientSettings
|
||||
, sendMail
|
||||
, setAttachmentName
|
||||
, setCollectiveSettings
|
||||
@ -124,7 +125,6 @@ module Api exposing
|
||||
, startOnceScanMailbox
|
||||
, startReIndex
|
||||
, submitNotifyDueItems
|
||||
, toggleNewUi
|
||||
, toggleTags
|
||||
, unconfirmMultiple
|
||||
, updateNotifyDueItems
|
||||
@ -206,8 +206,10 @@ import Api.Model.VersionInfo exposing (VersionInfo)
|
||||
import Data.ContactType exposing (ContactType)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.Priority exposing (Priority)
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import File exposing (File)
|
||||
import Http
|
||||
import Json.Decode as JsonDecode
|
||||
import Json.Encode as JsonEncode
|
||||
import Set exposing (Set)
|
||||
import Task
|
||||
@ -1982,21 +1984,40 @@ getItemProposals flags item receive =
|
||||
}
|
||||
|
||||
|
||||
toggleNewUi : Flags -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
toggleNewUi flags receive =
|
||||
Http2.authPost
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/newui"
|
||||
|
||||
--- Client Settings
|
||||
|
||||
|
||||
getClientSettings : Flags -> (Result Http.Error UiSettings -> msg) -> Cmd msg
|
||||
getClientSettings flags receive =
|
||||
let
|
||||
defaults =
|
||||
Data.UiSettings.defaults
|
||||
|
||||
decoder =
|
||||
JsonDecode.map (\s -> Data.UiSettings.merge s defaults)
|
||||
Data.UiSettings.storedUiSettingsDecoder
|
||||
in
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/webClient"
|
||||
, account = getAccount flags
|
||||
, body = Http.emptyBody
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
, expect = Http.expectJson receive decoder
|
||||
}
|
||||
|
||||
|
||||
getNewUi : Flags -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
getNewUi flags receive =
|
||||
Http2.authGet
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/newui"
|
||||
saveClientSettings : Flags -> UiSettings -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||
saveClientSettings flags settings receive =
|
||||
let
|
||||
storedSettings =
|
||||
Data.UiSettings.toStoredUiSettings settings
|
||||
|
||||
encode =
|
||||
Data.UiSettings.storedUiSettingsEncode storedSettings
|
||||
in
|
||||
Http2.authPut
|
||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/clientSettings/webClient"
|
||||
, account = getAccount flags
|
||||
, body = Http.jsonBody encode
|
||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,12 @@ module App.Data exposing
|
||||
)
|
||||
|
||||
import Api.Model.AuthResult exposing (AuthResult)
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
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)
|
||||
@ -155,11 +156,13 @@ type Msg
|
||||
| SessionCheckResp (Result Http.Error AuthResult)
|
||||
| ToggleNavMenu
|
||||
| ToggleUserMenu
|
||||
| GetUiSettings UiSettings
|
||||
| GetUiSettings (Result Http.Error UiSettings)
|
||||
| ToggleSidebar
|
||||
| ToggleDarkMode
|
||||
| ToggleLangMenu
|
||||
| SetLanguage UiLanguage
|
||||
| ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult)
|
||||
| ReceiveBrowserSettings StoredUiSettings
|
||||
|
||||
|
||||
defaultPage : Flags -> Page
|
||||
|
@ -8,6 +8,7 @@ import App.Data exposing (..)
|
||||
import Browser exposing (UrlRequest(..))
|
||||
import Browser.Navigation as Nav
|
||||
import Data.Flags
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Data.UiTheme
|
||||
import Page exposing (Page(..))
|
||||
import Page.CollectiveSettings.Data
|
||||
@ -65,11 +66,11 @@ updateWithSub msg model =
|
||||
{ settings | uiTheme = next }
|
||||
in
|
||||
-- when authenticated, store it in settings only
|
||||
-- once new settings arrive via a subscription,
|
||||
-- the ui is updated. so it is also updated on
|
||||
-- page refresh
|
||||
-- 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 }
|
||||
, Ports.storeUiSettings model.flags newSettings
|
||||
, Api.saveClientSettings model.flags newSettings (ClientSettingsSaveResp newSettings)
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
@ -84,6 +85,16 @@ updateWithSub msg model =
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
ClientSettingsSaveResp settings (Ok res) ->
|
||||
if res.success then
|
||||
applyClientSettings model settings
|
||||
|
||||
else
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
ClientSettingsSaveResp _ (Err _) ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
ToggleLangMenu ->
|
||||
( { model | langMenuOpen = not model.langMenuOpen }
|
||||
, Cmd.none
|
||||
@ -258,22 +269,37 @@ updateWithSub msg model =
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
GetUiSettings settings ->
|
||||
GetUiSettings (Ok settings) ->
|
||||
applyClientSettings model settings
|
||||
|
||||
GetUiSettings (Err _) ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
ReceiveBrowserSettings sett ->
|
||||
let
|
||||
setTheme =
|
||||
Ports.setUiTheme settings.uiTheme
|
||||
lm =
|
||||
Page.UserSettings.Data.ReceiveBrowserSettings sett
|
||||
in
|
||||
Util.Update.andThen2
|
||||
[ \m ->
|
||||
( { m | sidebarVisible = settings.sideMenuVisible }
|
||||
, setTheme
|
||||
, Sub.none
|
||||
)
|
||||
, updateUserSettings Page.UserSettings.Data.UpdateSettings
|
||||
, updateHome Page.Home.Data.UiSettingsUpdated
|
||||
, updateItemDetail Page.ItemDetail.Data.UiSettingsUpdated
|
||||
]
|
||||
{ model | uiSettings = settings }
|
||||
updateUserSettings lm model
|
||||
|
||||
|
||||
applyClientSettings : Model -> UiSettings -> ( Model, Cmd Msg, Sub Msg )
|
||||
applyClientSettings model settings =
|
||||
let
|
||||
setTheme =
|
||||
Ports.setUiTheme settings.uiTheme
|
||||
in
|
||||
Util.Update.andThen2
|
||||
[ \m ->
|
||||
( { m | sidebarVisible = settings.sideMenuVisible }
|
||||
, setTheme
|
||||
, Sub.none
|
||||
)
|
||||
, updateUserSettings Page.UserSettings.Data.UpdateSettings
|
||||
, updateHome Page.Home.Data.UiSettingsUpdated
|
||||
, updateItemDetail Page.ItemDetail.Data.UiSettingsUpdated
|
||||
]
|
||||
{ model | uiSettings = settings }
|
||||
|
||||
|
||||
updateItemDetail : Page.ItemDetail.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
@ -360,14 +386,29 @@ updateQueue lmsg model =
|
||||
updateUserSettings : Page.UserSettings.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
updateUserSettings lmsg model =
|
||||
let
|
||||
( lm, lc, ls ) =
|
||||
result =
|
||||
Page.UserSettings.Update.update model.flags model.uiSettings lmsg model.userSettingsModel
|
||||
|
||||
model_ =
|
||||
{ model | userSettingsModel = result.model }
|
||||
|
||||
( lm2, lc2, s2 ) =
|
||||
case result.newSettings of
|
||||
Just sett ->
|
||||
applyClientSettings model_ sett
|
||||
|
||||
Nothing ->
|
||||
( model_, Cmd.none, Sub.none )
|
||||
in
|
||||
( { model
|
||||
| userSettingsModel = lm
|
||||
}
|
||||
, Cmd.map UserSettingsMsg lc
|
||||
, Sub.map UserSettingsMsg ls
|
||||
( lm2
|
||||
, Cmd.batch
|
||||
[ Cmd.map UserSettingsMsg result.cmd
|
||||
, lc2
|
||||
]
|
||||
, Sub.batch
|
||||
[ Sub.map UserSettingsMsg result.sub
|
||||
, s2
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -415,14 +456,29 @@ updateHome lmsg model =
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
( lm, lc, ls ) =
|
||||
result =
|
||||
Page.Home.Update.update mid model.key model.flags model.uiSettings lmsg model.homeModel
|
||||
|
||||
model_ =
|
||||
{ model | homeModel = result.model }
|
||||
|
||||
( lm, lc, ls ) =
|
||||
case result.newSettings of
|
||||
Just sett ->
|
||||
applyClientSettings model_ sett
|
||||
|
||||
Nothing ->
|
||||
( model_, Cmd.none, Sub.none )
|
||||
in
|
||||
( { model
|
||||
| homeModel = lm
|
||||
}
|
||||
, Cmd.map HomeMsg lc
|
||||
, Sub.map HomeMsg ls
|
||||
( lm
|
||||
, Cmd.batch
|
||||
[ Cmd.map HomeMsg result.cmd
|
||||
, lc
|
||||
]
|
||||
, Sub.batch
|
||||
[ Sub.map HomeMsg result.sub
|
||||
, ls
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
@ -463,9 +463,13 @@ update sett msg model =
|
||||
tagColorViewOpts2 : Texts -> Comp.ColorTagger.ViewOpts
|
||||
tagColorViewOpts2 texts =
|
||||
{ renderItem =
|
||||
\( _, v ) ->
|
||||
span [ class (" label " ++ Data.Color.toString2 v) ]
|
||||
[ text (texts.colorLabel v) ]
|
||||
\( name, v ) ->
|
||||
span [ class "flex inline-flex items-center" ]
|
||||
[ span [ class "mr-2" ] [ text name ]
|
||||
, span [ class (" label " ++ Data.Color.toString2 v) ]
|
||||
[ text (texts.colorLabel v)
|
||||
]
|
||||
]
|
||||
, colorLabel = texts.colorLabel
|
||||
, label = texts.chooseTagColorLabel
|
||||
, description = Just texts.tagColorDescription
|
||||
|
@ -1,36 +1,49 @@
|
||||
module Comp.UiSettingsManage exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, UpdateResult
|
||||
, init
|
||||
, update
|
||||
, view2
|
||||
)
|
||||
|
||||
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 Html.Events exposing (onClick)
|
||||
import Http
|
||||
import Messages.Comp.UiSettingsManage exposing (Texts)
|
||||
import Ports
|
||||
import Styles as S
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ formModel : Comp.UiSettingsForm.Model
|
||||
, settings : Maybe UiSettings
|
||||
, message : Maybe BasicResult
|
||||
, formResult : FormResult
|
||||
, settingsMigrate : Comp.UiSettingsMigrate.Model
|
||||
}
|
||||
|
||||
|
||||
type FormResult
|
||||
= FormInit
|
||||
| FormUnchanged
|
||||
| FormSaved
|
||||
| FormHttpError Http.Error
|
||||
| FormUnknownError
|
||||
|
||||
|
||||
type Msg
|
||||
= UiSettingsFormMsg Comp.UiSettingsForm.Msg
|
||||
| UiSettingsMigrateMsg Comp.UiSettingsMigrate.Msg
|
||||
| Submit
|
||||
| SettingsSaved
|
||||
| UpdateSettings
|
||||
| SaveSettingsResp UiSettings (Result Http.Error BasicResult)
|
||||
| ReceiveBrowserSettings StoredUiSettings
|
||||
|
||||
|
||||
init : Flags -> UiSettings -> ( Model, Cmd Msg )
|
||||
@ -38,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
|
||||
, message = Nothing
|
||||
, formResult = FormInit
|
||||
, settingsMigrate = mm
|
||||
}
|
||||
, Cmd.map UiSettingsFormMsg fc
|
||||
, Cmd.batch
|
||||
[ Cmd.map UiSettingsFormMsg fc
|
||||
, Cmd.map UiSettingsMigrateMsg mc
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@ -51,7 +71,15 @@ init flags settings =
|
||||
--- update
|
||||
|
||||
|
||||
update : Flags -> UiSettings -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
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
|
||||
UiSettingsFormMsg lm ->
|
||||
@ -62,54 +90,89 @@ update flags settings msg model =
|
||||
( m_, sett ) =
|
||||
Comp.UiSettingsForm.update inSettings lm model.formModel
|
||||
in
|
||||
( { model
|
||||
| formModel = m_
|
||||
, settings =
|
||||
if sett == Nothing then
|
||||
model.settings
|
||||
{ model =
|
||||
{ model
|
||||
| formModel = m_
|
||||
, settings =
|
||||
if sett == Nothing then
|
||||
model.settings
|
||||
|
||||
else
|
||||
sett
|
||||
, message =
|
||||
if sett /= Nothing then
|
||||
Nothing
|
||||
else
|
||||
sett
|
||||
, formResult =
|
||||
if sett /= Nothing then
|
||||
FormInit
|
||||
|
||||
else
|
||||
model.message
|
||||
}
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
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 ->
|
||||
let
|
||||
lm =
|
||||
UiSettingsMigrateMsg (Comp.UiSettingsMigrate.receiveBrowserSettings sett)
|
||||
in
|
||||
update flags settings lm model
|
||||
|
||||
Submit ->
|
||||
case model.settings of
|
||||
Just s ->
|
||||
( { model | message = Nothing }
|
||||
, Ports.storeUiSettings flags s
|
||||
, Ports.onUiSettingsSaved SettingsSaved
|
||||
)
|
||||
{ model = { model | formResult = FormInit }
|
||||
, cmd = Api.saveClientSettings flags s (SaveSettingsResp s)
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
( { model | message = Just (BasicResult False "Settings unchanged or invalid.") }
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
{ model = { model | formResult = FormUnchanged }
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
SettingsSaved ->
|
||||
( { model | message = Just (BasicResult True "Settings saved.") }
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
SaveSettingsResp newSettings (Ok res) ->
|
||||
if res.success then
|
||||
{ model = { model | formResult = FormSaved }
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
, newSettings = Just newSettings
|
||||
}
|
||||
|
||||
else
|
||||
{ model = { model | formResult = FormUnknownError }
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
SaveSettingsResp _ (Err err) ->
|
||||
UpdateResult { model | formResult = FormHttpError err } Cmd.none Sub.none Nothing
|
||||
|
||||
UpdateSettings ->
|
||||
let
|
||||
( fm, fc ) =
|
||||
Comp.UiSettingsForm.init flags settings
|
||||
in
|
||||
( { model | formModel = fm }
|
||||
, Cmd.map UiSettingsFormMsg fc
|
||||
, Sub.none
|
||||
)
|
||||
{ model = { model | formModel = fm }
|
||||
, cmd = Cmd.map UiSettingsFormMsg fc
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -118,12 +181,26 @@ update flags settings msg model =
|
||||
|
||||
isError : Model -> Bool
|
||||
isError model =
|
||||
Maybe.map .success model.message == Just False
|
||||
case model.formResult of
|
||||
FormSaved ->
|
||||
False
|
||||
|
||||
FormInit ->
|
||||
False
|
||||
|
||||
FormUnchanged ->
|
||||
True
|
||||
|
||||
FormHttpError _ ->
|
||||
True
|
||||
|
||||
FormUnknownError ->
|
||||
True
|
||||
|
||||
|
||||
isSuccess : Model -> Bool
|
||||
isSuccess model =
|
||||
Maybe.map .success model.message == Just True
|
||||
not (isError model)
|
||||
|
||||
|
||||
view2 : Texts -> Flags -> UiSettings -> String -> Model -> Html Msg
|
||||
@ -141,16 +218,32 @@ 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 )
|
||||
, ( S.errorMessage, isError model )
|
||||
, ( "hidden", model.message == Nothing )
|
||||
, ( "hidden", model.formResult == FormInit )
|
||||
]
|
||||
]
|
||||
[ Maybe.map .message model.message
|
||||
|> Maybe.withDefault ""
|
||||
|> text
|
||||
[ case model.formResult of
|
||||
FormInit ->
|
||||
text ""
|
||||
|
||||
FormUnchanged ->
|
||||
text texts.settingsUnchanged
|
||||
|
||||
FormHttpError err ->
|
||||
text (texts.httpError err)
|
||||
|
||||
FormSaved ->
|
||||
text texts.settingsSaved
|
||||
|
||||
FormUnknownError ->
|
||||
text texts.unknownSaveError
|
||||
]
|
||||
, Html.map UiSettingsFormMsg
|
||||
(Comp.UiSettingsForm.view2
|
||||
|
186
modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm
Normal file
186
modules/webapp/src/main/elm/Comp/UiSettingsMigrate.elm
Normal 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" ] []
|
@ -15,6 +15,8 @@ module Data.UiSettings exposing
|
||||
, mergeDefaults
|
||||
, posFromString
|
||||
, posToString
|
||||
, storedUiSettingsDecoder
|
||||
, storedUiSettingsEncode
|
||||
, tagColor
|
||||
, tagColorFg2
|
||||
, tagColorString2
|
||||
@ -30,6 +32,9 @@ import Data.UiTheme exposing (UiTheme)
|
||||
import Dict exposing (Dict)
|
||||
import Html exposing (Attribute)
|
||||
import Html.Attributes as HA
|
||||
import Json.Decode as Decode
|
||||
import Json.Decode.Pipeline as P
|
||||
import Json.Encode as Encode
|
||||
import Messages
|
||||
import Messages.UiLanguage exposing (UiLanguage)
|
||||
|
||||
@ -67,6 +72,70 @@ type alias StoredUiSettings =
|
||||
}
|
||||
|
||||
|
||||
storedUiSettingsDecoder : Decode.Decoder StoredUiSettings
|
||||
storedUiSettingsDecoder =
|
||||
let
|
||||
maybeInt =
|
||||
Decode.maybe Decode.int
|
||||
|
||||
maybeString =
|
||||
Decode.maybe Decode.string
|
||||
in
|
||||
Decode.succeed StoredUiSettings
|
||||
|> P.optional "itemSearchPageSize" maybeInt Nothing
|
||||
|> P.optional "tagCategoryColors" (Decode.keyValuePairs Decode.string) []
|
||||
|> P.optional "nativePdfPreview" Decode.bool False
|
||||
|> P.optional "itemSearchNoteLength" maybeInt Nothing
|
||||
|> P.optional "itemDetailNotesPosition" maybeString Nothing
|
||||
|> P.optional "searchMenuFolderCount" maybeInt Nothing
|
||||
|> 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 False
|
||||
|> P.optional "searchMenuVisible" Decode.bool False
|
||||
|> P.optional "editMenuVisible" Decode.bool False
|
||||
|> P.optional "cardPreviewSize" maybeString Nothing
|
||||
|> P.optional "cardTitleTemplate" maybeString Nothing
|
||||
|> P.optional "cardSubtitleTemplate" maybeString Nothing
|
||||
|> P.optional "searchStatsVisible" Decode.bool False
|
||||
|> P.optional "cardPreviewFullWidth" Decode.bool False
|
||||
|> P.optional "uiTheme" maybeString Nothing
|
||||
|> P.optional "sideMenuVisible" Decode.bool False
|
||||
|> P.optional "powerSearchEnabled" Decode.bool False
|
||||
|> P.optional "uiLang" maybeString Nothing
|
||||
|
||||
|
||||
storedUiSettingsEncode : StoredUiSettings -> Encode.Value
|
||||
storedUiSettingsEncode value =
|
||||
let
|
||||
maybeEnc enca ma =
|
||||
Maybe.map enca ma |> Maybe.withDefault Encode.null
|
||||
in
|
||||
Encode.object
|
||||
[ ( "itemSearchPageSize", maybeEnc Encode.int value.itemSearchPageSize )
|
||||
, ( "tagCategoryColors", Encode.dict identity Encode.string (Dict.fromList value.tagCategoryColors) )
|
||||
, ( "nativePdfPreview", Encode.bool value.nativePdfPreview )
|
||||
, ( "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 )
|
||||
]
|
||||
|
||||
|
||||
{-| Settings for the web ui. These fields are all mandatory, since
|
||||
there is always a default value.
|
||||
|
||||
|
@ -55,7 +55,7 @@ init flags url key =
|
||||
|
||||
else
|
||||
Cmd.none
|
||||
, Ports.getUiSettings flags
|
||||
, Api.getClientSettings flags GetUiSettings
|
||||
]
|
||||
)
|
||||
|
||||
@ -85,5 +85,5 @@ subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.batch
|
||||
[ model.subs
|
||||
, Ports.loadUiSettings GetUiSettings
|
||||
, Ports.receiveUiSettings ReceiveBrowserSettings
|
||||
]
|
||||
|
@ -1,6 +1,8 @@
|
||||
module Messages.Comp.UiSettingsManage exposing (Texts, gb)
|
||||
|
||||
import Http
|
||||
import Messages.Basics
|
||||
import Messages.Comp.HttpError
|
||||
import Messages.Comp.UiSettingsForm
|
||||
|
||||
|
||||
@ -8,6 +10,10 @@ type alias Texts =
|
||||
{ basics : Messages.Basics.Texts
|
||||
, uiSettingsForm : Messages.Comp.UiSettingsForm.Texts
|
||||
, saveSettings : String
|
||||
, settingsUnchanged : String
|
||||
, settingsSaved : String
|
||||
, unknownSaveError : String
|
||||
, httpError : Http.Error -> String
|
||||
}
|
||||
|
||||
|
||||
@ -16,4 +22,8 @@ gb =
|
||||
{ basics = Messages.Basics.gb
|
||||
, uiSettingsForm = Messages.Comp.UiSettingsForm.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
|
||||
}
|
||||
|
@ -193,6 +193,7 @@ type Msg
|
||||
| KeyUpPowerSearchbarMsg (Maybe KeyCode)
|
||||
| RequestReprocessSelected
|
||||
| ReprocessSelectedConfirmed
|
||||
| ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult)
|
||||
|
||||
|
||||
type SearchType
|
||||
|
@ -1,4 +1,7 @@
|
||||
module Page.Home.Update exposing (update)
|
||||
module Page.Home.Update exposing
|
||||
( UpdateResult
|
||||
, update
|
||||
)
|
||||
|
||||
import Api
|
||||
import Api.Model.ItemLightList exposing (ItemLightList)
|
||||
@ -16,7 +19,6 @@ import Data.Items
|
||||
import Data.UiSettings exposing (UiSettings)
|
||||
import Page exposing (Page(..))
|
||||
import Page.Home.Data exposing (..)
|
||||
import Ports
|
||||
import Process
|
||||
import Scroll
|
||||
import Set exposing (Set)
|
||||
@ -28,7 +30,15 @@ import Util.ItemDragDrop as DD
|
||||
import Util.Update
|
||||
|
||||
|
||||
update : Maybe String -> Nav.Key -> Flags -> UiSettings -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
type alias UpdateResult =
|
||||
{ model : Model
|
||||
, cmd : Cmd Msg
|
||||
, sub : Sub Msg
|
||||
, newSettings : Maybe UiSettings
|
||||
}
|
||||
|
||||
|
||||
update : Maybe String -> Nav.Key -> Flags -> UiSettings -> Msg -> Model -> UpdateResult
|
||||
update mId key flags settings msg model =
|
||||
case msg of
|
||||
Init ->
|
||||
@ -41,11 +51,12 @@ update mId key flags settings msg model =
|
||||
, scroll = True
|
||||
}
|
||||
in
|
||||
Util.Update.andThen2
|
||||
[ update mId key flags settings (SearchMenuMsg Comp.SearchMenu.Init)
|
||||
, doSearch searchParam
|
||||
]
|
||||
model
|
||||
makeResult <|
|
||||
Util.Update.andThen3
|
||||
[ update mId key flags settings (SearchMenuMsg Comp.SearchMenu.Init)
|
||||
, doSearch searchParam
|
||||
]
|
||||
model
|
||||
|
||||
ResetSearch ->
|
||||
let
|
||||
@ -79,21 +90,21 @@ update mId key flags settings msg model =
|
||||
BasicSearch
|
||||
}
|
||||
|
||||
( m2, c2, s2 ) =
|
||||
result =
|
||||
if nextState.stateChange && not model.searchInProgress then
|
||||
doSearch (SearchParam flags BasicSearch settings.itemSearchPageSize 0 False) newModel
|
||||
|
||||
else
|
||||
withSub ( newModel, Cmd.none )
|
||||
in
|
||||
( m2
|
||||
, Cmd.batch
|
||||
[ c2
|
||||
, Cmd.map SearchMenuMsg nextState.cmd
|
||||
, dropCmd
|
||||
]
|
||||
, s2
|
||||
)
|
||||
{ result
|
||||
| cmd =
|
||||
Cmd.batch
|
||||
[ result.cmd
|
||||
, Cmd.map SearchMenuMsg nextState.cmd
|
||||
, dropCmd
|
||||
]
|
||||
}
|
||||
|
||||
SetLinkTarget lt ->
|
||||
case linkTargetMsg lt of
|
||||
@ -101,7 +112,7 @@ update mId key flags settings msg model =
|
||||
update mId key flags settings m model
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
makeResult ( model, Cmd.none, Sub.none )
|
||||
|
||||
ItemCardListMsg m ->
|
||||
let
|
||||
@ -144,15 +155,16 @@ update mId key flags settings msg model =
|
||||
, moreAvailable = list.groups /= []
|
||||
}
|
||||
in
|
||||
Util.Update.andThen2
|
||||
[ update mId key flags settings (ItemCardListMsg (Comp.ItemCardList.SetResults list))
|
||||
, if scroll then
|
||||
scrollToCard mId
|
||||
makeResult <|
|
||||
Util.Update.andThen3
|
||||
[ update mId key flags settings (ItemCardListMsg (Comp.ItemCardList.SetResults list))
|
||||
, if scroll then
|
||||
scrollToCard mId
|
||||
|
||||
else
|
||||
\next -> ( next, Cmd.none, Sub.none )
|
||||
]
|
||||
m
|
||||
else
|
||||
\next -> makeResult ( next, Cmd.none, Sub.none )
|
||||
]
|
||||
m
|
||||
|
||||
ItemSearchAddResp (Ok list) ->
|
||||
let
|
||||
@ -167,10 +179,7 @@ update mId key flags settings msg model =
|
||||
, moreAvailable = list.groups /= []
|
||||
}
|
||||
in
|
||||
Util.Update.andThen2
|
||||
[ update mId key flags settings (ItemCardListMsg (Comp.ItemCardList.AddResults list))
|
||||
]
|
||||
m
|
||||
update mId key flags settings (ItemCardListMsg (Comp.ItemCardList.AddResults list)) m
|
||||
|
||||
ItemSearchAddResp (Err _) ->
|
||||
withSub
|
||||
@ -514,10 +523,11 @@ update mId key flags settings msg model =
|
||||
res.change
|
||||
(MultiUpdateResp res.change)
|
||||
in
|
||||
( { model | viewMode = SelectView svm_ }
|
||||
, Cmd.batch [ cmd_, upCmd ]
|
||||
, sub_
|
||||
)
|
||||
makeResult
|
||||
( { model | viewMode = SelectView svm_ }
|
||||
, Cmd.batch [ cmd_, upCmd ]
|
||||
, sub_
|
||||
)
|
||||
|
||||
_ ->
|
||||
noSub ( model, Cmd.none )
|
||||
@ -540,10 +550,11 @@ update mId key flags settings msg model =
|
||||
noSub ( nm, Cmd.none )
|
||||
|
||||
MultiUpdateResp change (Err _) ->
|
||||
( updateSelectViewNameState False model change
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
makeResult
|
||||
( updateSelectViewNameState False model change
|
||||
, Cmd.none
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
ReplaceChangedItemsResp (Ok items) ->
|
||||
noSub ( replaceItems model items, Cmd.none )
|
||||
@ -592,10 +603,24 @@ update mId key flags settings msg model =
|
||||
{ settings | cardPreviewFullWidth = not settings.cardPreviewFullWidth }
|
||||
|
||||
cmd =
|
||||
Ports.storeUiSettings flags newSettings
|
||||
Api.saveClientSettings flags newSettings (ClientSettingsSaveResp newSettings)
|
||||
in
|
||||
noSub ( model, cmd )
|
||||
|
||||
ClientSettingsSaveResp newSettings (Ok res) ->
|
||||
if res.success then
|
||||
{ model = model
|
||||
, cmd = Cmd.none
|
||||
, sub = Sub.none
|
||||
, newSettings = Just newSettings
|
||||
}
|
||||
|
||||
else
|
||||
noSub ( model, Cmd.none )
|
||||
|
||||
ClientSettingsSaveResp _ (Err _) ->
|
||||
noSub ( model, Cmd.none )
|
||||
|
||||
PowerSearchMsg lm ->
|
||||
let
|
||||
result =
|
||||
@ -609,7 +634,7 @@ update mId key flags settings msg model =
|
||||
in
|
||||
case result.action of
|
||||
Comp.PowerSearchInput.NoAction ->
|
||||
( model_, cmd_, Sub.map PowerSearchMsg result.subs )
|
||||
makeResult ( model_, cmd_, Sub.map PowerSearchMsg result.subs )
|
||||
|
||||
Comp.PowerSearchInput.SubmitSearch ->
|
||||
update mId key flags settings (DoSearch model_.searchTypeDropdownValue) model_
|
||||
@ -703,21 +728,22 @@ loadChangedItems flags ids =
|
||||
Api.itemSearch flags search ReplaceChangedItemsResp
|
||||
|
||||
|
||||
scrollToCard : Maybe String -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
scrollToCard : Maybe String -> Model -> UpdateResult
|
||||
scrollToCard mId model =
|
||||
let
|
||||
scroll id =
|
||||
Scroll.scrollElementY "item-card-list" id 0.5 0.5
|
||||
in
|
||||
case mId of
|
||||
Just id ->
|
||||
( { model | scrollToCard = mId }
|
||||
, Task.attempt ScrollResult (scroll id)
|
||||
, Sub.none
|
||||
)
|
||||
makeResult <|
|
||||
case mId of
|
||||
Just id ->
|
||||
( { model | scrollToCard = mId }
|
||||
, Task.attempt ScrollResult (scroll id)
|
||||
, Sub.none
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
Nothing ->
|
||||
( model, Cmd.none, Sub.none )
|
||||
|
||||
|
||||
loadEditModel : Flags -> Cmd Msg
|
||||
@ -725,7 +751,7 @@ loadEditModel flags =
|
||||
Cmd.map EditMenuMsg (Comp.ItemDetail.MultiEditMenu.loadModel flags)
|
||||
|
||||
|
||||
doSearch : SearchParam -> Model -> ( Model, Cmd Msg, Sub Msg )
|
||||
doSearch : SearchParam -> Model -> UpdateResult
|
||||
doSearch param model =
|
||||
let
|
||||
param_ =
|
||||
@ -798,16 +824,26 @@ doSearchMore flags settings model =
|
||||
)
|
||||
|
||||
|
||||
withSub : ( Model, Cmd Msg ) -> ( Model, Cmd Msg, Sub Msg )
|
||||
withSub : ( Model, Cmd Msg ) -> UpdateResult
|
||||
withSub ( m, c ) =
|
||||
( m
|
||||
, c
|
||||
, Throttle.ifNeeded
|
||||
(Time.every 500 (\_ -> UpdateThrottle))
|
||||
m.throttle
|
||||
)
|
||||
makeResult
|
||||
( m
|
||||
, c
|
||||
, Throttle.ifNeeded
|
||||
(Time.every 500 (\_ -> UpdateThrottle))
|
||||
m.throttle
|
||||
)
|
||||
|
||||
|
||||
noSub : ( Model, Cmd Msg ) -> ( Model, Cmd Msg, Sub Msg )
|
||||
noSub : ( Model, Cmd Msg ) -> UpdateResult
|
||||
noSub ( m, c ) =
|
||||
( m, c, Sub.none )
|
||||
makeResult ( m, c, Sub.none )
|
||||
|
||||
|
||||
makeResult : ( Model, Cmd Msg, Sub Msg ) -> UpdateResult
|
||||
makeResult ( m, c, s ) =
|
||||
{ model = m
|
||||
, cmd = c
|
||||
, sub = s
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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, Sub Msg )
|
||||
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,17 +33,25 @@ update flags settings msg model =
|
||||
( em, c ) =
|
||||
Comp.EmailSettingsManage.init flags
|
||||
in
|
||||
( { m | emailSettingsModel = em }, Cmd.map EmailSettingsMsg c, Sub.none )
|
||||
{ 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, Sub.none )
|
||||
{ model = { m | imapSettingsModel = em }
|
||||
, cmd = Cmd.map ImapSettingsMsg c
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
ChangePassTab ->
|
||||
( m, Cmd.none, Sub.none )
|
||||
UpdateResult m Cmd.none Sub.none Nothing
|
||||
|
||||
NotificationTab ->
|
||||
let
|
||||
@ -43,7 +59,7 @@ update flags settings msg model =
|
||||
Cmd.map NotificationMsg
|
||||
(Tuple.second (Comp.NotificationManage.init flags))
|
||||
in
|
||||
( m, initCmd, Sub.none )
|
||||
UpdateResult m initCmd Sub.none Nothing
|
||||
|
||||
ScanMailboxTab ->
|
||||
let
|
||||
@ -51,64 +67,86 @@ update flags settings msg model =
|
||||
Cmd.map ScanMailboxMsg
|
||||
(Tuple.second (Comp.ScanMailboxManage.init flags))
|
||||
in
|
||||
( m, initCmd, Sub.none )
|
||||
UpdateResult m initCmd Sub.none Nothing
|
||||
|
||||
UiSettingsTab ->
|
||||
( m, Cmd.none, Sub.none )
|
||||
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, Sub.none )
|
||||
{ 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, Sub.none )
|
||||
{ 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, Sub.none )
|
||||
{ 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
|
||||
, Sub.none
|
||||
)
|
||||
{ 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
|
||||
, Sub.none
|
||||
)
|
||||
{ model = { model | scanMailboxModel = m2 }
|
||||
, cmd = Cmd.map ScanMailboxMsg c2
|
||||
, sub = Sub.none
|
||||
, newSettings = Nothing
|
||||
}
|
||||
|
||||
UiSettingsMsg lm ->
|
||||
let
|
||||
( m2, c2, s2 ) =
|
||||
res =
|
||||
Comp.UiSettingsManage.update flags settings lm model.uiSettingsModel
|
||||
in
|
||||
( { model | uiSettingsModel = m2 }
|
||||
, Cmd.map UiSettingsMsg c2
|
||||
, Sub.map UiSettingsMsg s2
|
||||
)
|
||||
{ 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
|
||||
|
@ -1,21 +1,17 @@
|
||||
port module Ports exposing
|
||||
( checkSearchQueryString
|
||||
, getUiSettings
|
||||
, initClipboard
|
||||
, loadUiSettings
|
||||
, onUiSettingsSaved
|
||||
, receiveCheckQueryResult
|
||||
, receiveUiSettings
|
||||
, removeAccount
|
||||
, requestUiSettings
|
||||
, setAccount
|
||||
, setUiTheme
|
||||
, storeUiSettings
|
||||
)
|
||||
|
||||
import Api.Model.AuthResult exposing (AuthResult)
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Data.QueryParseResult exposing (QueryParseResult)
|
||||
import Data.UiSettings exposing (StoredUiSettings, UiSettings)
|
||||
import Data.UiSettings exposing (StoredUiSettings)
|
||||
import Data.UiTheme exposing (UiTheme)
|
||||
|
||||
|
||||
@ -27,18 +23,6 @@ port setAccount : AuthResult -> Cmd msg
|
||||
port removeAccount : () -> Cmd msg
|
||||
|
||||
|
||||
port saveUiSettings : ( AuthResult, StoredUiSettings ) -> Cmd msg
|
||||
|
||||
|
||||
port receiveUiSettings : (StoredUiSettings -> msg) -> Sub msg
|
||||
|
||||
|
||||
port requestUiSettings : ( AuthResult, StoredUiSettings ) -> Cmd msg
|
||||
|
||||
|
||||
port uiSettingsSaved : (() -> msg) -> Sub msg
|
||||
|
||||
|
||||
port internalSetUiTheme : String -> Cmd msg
|
||||
|
||||
|
||||
@ -48,45 +32,15 @@ port checkSearchQueryString : String -> Cmd msg
|
||||
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)
|
||||
|
||||
|
||||
onUiSettingsSaved : msg -> Sub msg
|
||||
onUiSettingsSaved m =
|
||||
uiSettingsSaved (\_ -> m)
|
||||
|
||||
|
||||
storeUiSettings : Flags -> UiSettings -> Cmd msg
|
||||
storeUiSettings flags settings =
|
||||
case flags.account of
|
||||
Just ar ->
|
||||
saveUiSettings
|
||||
( ar
|
||||
, Data.UiSettings.toStoredUiSettings settings
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
|
||||
loadUiSettings : (UiSettings -> msg) -> Sub msg
|
||||
loadUiSettings tagger =
|
||||
receiveUiSettings (Data.UiSettings.mergeDefaults >> tagger)
|
||||
|
||||
|
||||
getUiSettings : Flags -> Cmd msg
|
||||
getUiSettings flags =
|
||||
case flags.account of
|
||||
Just ar ->
|
||||
requestUiSettings
|
||||
( ar
|
||||
, Data.UiSettings.toStoredUiSettings Data.UiSettings.defaults
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Cmd.none
|
||||
|
||||
|
||||
port initClipboard : ( String, String ) -> Cmd msg
|
||||
|
@ -1,6 +1,7 @@
|
||||
module Util.Update exposing
|
||||
( andThen1
|
||||
, andThen2
|
||||
, andThen3
|
||||
, cmdUnit
|
||||
)
|
||||
|
||||
@ -44,6 +45,21 @@ andThen2 fs m =
|
||||
|> combine
|
||||
|
||||
|
||||
andThen3 :
|
||||
List (model -> { x | model : model, cmd : Cmd msg, sub : Sub msg })
|
||||
-> model
|
||||
-> ( model, Cmd msg, Sub msg )
|
||||
andThen3 list m =
|
||||
let
|
||||
mkTuple r =
|
||||
( r.model, r.cmd, r.sub )
|
||||
|
||||
list2 =
|
||||
List.map (\e -> e >> mkTuple) list
|
||||
in
|
||||
andThen2 list2 m
|
||||
|
||||
|
||||
cmdUnit : a -> Cmd a
|
||||
cmdUnit a =
|
||||
Task.perform (\_ -> a) (Task.succeed ())
|
||||
|
@ -50,40 +50,22 @@ 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 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 && defaults) {
|
||||
var defaults = extend(defaults, data);
|
||||
elmApp.ports.receiveUiSettings.send(defaults);
|
||||
} else if (defaults) {
|
||||
elmApp.ports.receiveUiSettings.send(defaults);
|
||||
if (data) {
|
||||
console.log("Sending browser ui settings");
|
||||
elmApp.ports.receiveUiSettings.send(data);
|
||||
}
|
||||
} else if (defaults) {
|
||||
elmApp.ports.receiveUiSettings.send(defaults);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user