mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-22 18:38:26 +00:00
Save and load dashboards
This commit is contained in:
26
modules/webapp/src/main/elm/Data/AccountScope.elm
Normal file
26
modules/webapp/src/main/elm/Data/AccountScope.elm
Normal file
@ -0,0 +1,26 @@
|
||||
module Data.AccountScope exposing (..)
|
||||
|
||||
|
||||
type AccountScope
|
||||
= User
|
||||
| Collective
|
||||
|
||||
|
||||
fold : a -> a -> AccountScope -> a
|
||||
fold user coll scope =
|
||||
case scope of
|
||||
User ->
|
||||
user
|
||||
|
||||
Collective ->
|
||||
coll
|
||||
|
||||
|
||||
isUser : AccountScope -> Bool
|
||||
isUser scope =
|
||||
fold True False scope
|
||||
|
||||
|
||||
isCollective : AccountScope -> Bool
|
||||
isCollective scope =
|
||||
fold False True scope
|
@ -1,6 +1,8 @@
|
||||
module Data.Box exposing (Box, boxIcon, empty, messageBox, queryBox, statsBox, uploadBox)
|
||||
module Data.Box exposing (Box, boxIcon, decoder, empty, encode, messageBox, queryBox, statsBox, uploadBox)
|
||||
|
||||
import Data.BoxContent exposing (BoxContent(..))
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type alias Box =
|
||||
@ -45,3 +47,28 @@ messageBox =
|
||||
uploadBox : Box
|
||||
uploadBox =
|
||||
empty (BoxUpload Data.BoxContent.emptyUploadData)
|
||||
|
||||
|
||||
|
||||
--- JSON
|
||||
|
||||
|
||||
decoder : D.Decoder Box
|
||||
decoder =
|
||||
D.map5 Box
|
||||
(D.field "name" D.string)
|
||||
(D.field "visible" D.bool)
|
||||
(D.field "decoration" D.bool)
|
||||
(D.field "colspan" D.int)
|
||||
(D.field "content" Data.BoxContent.boxContentDecoder)
|
||||
|
||||
|
||||
encode : Box -> E.Value
|
||||
encode box =
|
||||
E.object
|
||||
[ ( "name", E.string box.name )
|
||||
, ( "visible", E.bool box.visible )
|
||||
, ( "decoration", E.bool box.decoration )
|
||||
, ( "colspan", E.int box.colspan )
|
||||
, ( "content", Data.BoxContent.boxContentEncode box.content )
|
||||
]
|
||||
|
@ -6,6 +6,8 @@ module Data.BoxContent exposing
|
||||
, StatsData
|
||||
, SummaryShow(..)
|
||||
, UploadData
|
||||
, boxContentDecoder
|
||||
, boxContentEncode
|
||||
, boxContentIcon
|
||||
, emptyMessageData
|
||||
, emptyQueryData
|
||||
@ -14,6 +16,9 @@ module Data.BoxContent exposing
|
||||
)
|
||||
|
||||
import Data.ItemColumn exposing (ItemColumn)
|
||||
import Html exposing (datalist)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type BoxContent
|
||||
@ -89,6 +94,28 @@ type SearchQuery
|
||||
| SearchQueryBookmark String
|
||||
|
||||
|
||||
searchQueryAsString : SearchQuery -> String
|
||||
searchQueryAsString q =
|
||||
case q of
|
||||
SearchQueryBookmark id ->
|
||||
"bookmark:" ++ id
|
||||
|
||||
SearchQueryString str ->
|
||||
"query:" ++ str
|
||||
|
||||
|
||||
searchQueryFromString : String -> Maybe SearchQuery
|
||||
searchQueryFromString str =
|
||||
if String.startsWith "bookmark:" str then
|
||||
Just (SearchQueryBookmark <| String.dropLeft 9 str)
|
||||
|
||||
else if String.startsWith "query:" str then
|
||||
Just (SearchQueryString <| String.dropLeft 6 str)
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
boxContentIcon : BoxContent -> String
|
||||
boxContentIcon content =
|
||||
case content of
|
||||
@ -103,3 +130,183 @@ boxContentIcon content =
|
||||
|
||||
BoxStats _ ->
|
||||
"fa fa-chart-bar font-thin"
|
||||
|
||||
|
||||
|
||||
--- JSON
|
||||
|
||||
|
||||
boxContentDecoder : D.Decoder BoxContent
|
||||
boxContentDecoder =
|
||||
let
|
||||
from discr =
|
||||
case String.toLower discr of
|
||||
"message" ->
|
||||
D.field "data" <|
|
||||
D.map BoxMessage messageDataDecoder
|
||||
|
||||
"upload" ->
|
||||
D.field "data" <|
|
||||
D.map BoxUpload uploadDataDecoder
|
||||
|
||||
"query" ->
|
||||
D.field "data" <|
|
||||
D.map BoxQuery queryDataDecoder
|
||||
|
||||
"stats" ->
|
||||
D.field "data" <|
|
||||
D.map BoxStats statsDataDecoder
|
||||
|
||||
_ ->
|
||||
D.fail ("Unknown box content: " ++ discr)
|
||||
in
|
||||
D.andThen from (D.field discriminator D.string)
|
||||
|
||||
|
||||
boxContentEncode : BoxContent -> E.Value
|
||||
boxContentEncode cnt =
|
||||
case cnt of
|
||||
BoxMessage data ->
|
||||
E.object
|
||||
[ ( discriminator, E.string "message" )
|
||||
, ( "data", messageDataEncode data )
|
||||
]
|
||||
|
||||
BoxUpload data ->
|
||||
E.object
|
||||
[ ( discriminator, E.string "upload" )
|
||||
, ( "data", uploadDataEncode data )
|
||||
]
|
||||
|
||||
BoxQuery data ->
|
||||
E.object
|
||||
[ ( discriminator, E.string "query" )
|
||||
, ( "data", queryDataEncode data )
|
||||
]
|
||||
|
||||
BoxStats data ->
|
||||
E.object
|
||||
[ ( discriminator, E.string "stats" )
|
||||
, ( "data", statsDataEncode data )
|
||||
]
|
||||
|
||||
|
||||
messageDataDecoder : D.Decoder MessageData
|
||||
messageDataDecoder =
|
||||
D.map2 MessageData
|
||||
(D.field "title" D.string)
|
||||
(D.field "body" D.string)
|
||||
|
||||
|
||||
messageDataEncode : MessageData -> E.Value
|
||||
messageDataEncode data =
|
||||
E.object
|
||||
[ ( "title", E.string data.title )
|
||||
, ( "body", E.string data.body )
|
||||
]
|
||||
|
||||
|
||||
uploadDataDecoder : D.Decoder UploadData
|
||||
uploadDataDecoder =
|
||||
D.map UploadData
|
||||
(D.maybe (D.field "sourceId" D.string))
|
||||
|
||||
|
||||
uploadDataEncode : UploadData -> E.Value
|
||||
uploadDataEncode data =
|
||||
E.object
|
||||
[ ( "sourceId", Maybe.map E.string data.sourceId |> Maybe.withDefault E.null )
|
||||
]
|
||||
|
||||
|
||||
queryDataDecoder : D.Decoder QueryData
|
||||
queryDataDecoder =
|
||||
D.map5 QueryData
|
||||
(D.field "query" searchQueryDecoder)
|
||||
(D.field "limit" D.int)
|
||||
(D.field "details" D.bool)
|
||||
(D.field "columns" <| D.list Data.ItemColumn.decode)
|
||||
(D.field "showHeaders" D.bool)
|
||||
|
||||
|
||||
queryDataEncode : QueryData -> E.Value
|
||||
queryDataEncode data =
|
||||
E.object
|
||||
[ ( "query", searchQueryEncode data.query )
|
||||
, ( "limit", E.int data.limit )
|
||||
, ( "details", E.bool data.details )
|
||||
, ( "columns", E.list Data.ItemColumn.encode data.columns )
|
||||
, ( "showHeaders", E.bool data.showHeaders )
|
||||
]
|
||||
|
||||
|
||||
statsDataDecoder : D.Decoder StatsData
|
||||
statsDataDecoder =
|
||||
D.map2 StatsData
|
||||
(D.field "query" searchQueryDecoder)
|
||||
(D.field "show" summaryShowDecoder)
|
||||
|
||||
|
||||
statsDataEncode : StatsData -> E.Value
|
||||
statsDataEncode data =
|
||||
E.object
|
||||
[ ( "query", searchQueryEncode data.query )
|
||||
, ( "show", summaryShowEncode data.show )
|
||||
]
|
||||
|
||||
|
||||
searchQueryDecoder : D.Decoder SearchQuery
|
||||
searchQueryDecoder =
|
||||
let
|
||||
fromString str =
|
||||
case searchQueryFromString str of
|
||||
Just q ->
|
||||
D.succeed q
|
||||
|
||||
Nothing ->
|
||||
D.fail ("Invalid search query: " ++ str)
|
||||
in
|
||||
D.andThen fromString D.string
|
||||
|
||||
|
||||
searchQueryEncode : SearchQuery -> E.Value
|
||||
searchQueryEncode q =
|
||||
E.string (searchQueryAsString q)
|
||||
|
||||
|
||||
summaryShowDecoder : D.Decoder SummaryShow
|
||||
summaryShowDecoder =
|
||||
let
|
||||
decode discr =
|
||||
case String.toLower discr of
|
||||
"fields" ->
|
||||
D.field "showItemCount" D.bool
|
||||
|> D.map SummaryShowFields
|
||||
|
||||
"general" ->
|
||||
D.succeed SummaryShowGeneral
|
||||
|
||||
_ ->
|
||||
D.fail ("Unknown summary show for: " ++ discr)
|
||||
in
|
||||
D.andThen decode (D.field discriminator D.string)
|
||||
|
||||
|
||||
summaryShowEncode : SummaryShow -> E.Value
|
||||
summaryShowEncode show =
|
||||
case show of
|
||||
SummaryShowFields flag ->
|
||||
E.object
|
||||
[ ( discriminator, E.string "fields" )
|
||||
, ( "showItemCount", E.bool flag )
|
||||
]
|
||||
|
||||
SummaryShowGeneral ->
|
||||
E.object
|
||||
[ ( "discriminator", E.string "general" )
|
||||
]
|
||||
|
||||
|
||||
discriminator : String
|
||||
discriminator =
|
||||
"discriminator"
|
||||
|
@ -1,10 +1,50 @@
|
||||
module Data.Dashboard exposing (Dashboard)
|
||||
module Data.Dashboard exposing (Dashboard, decoder, empty, encode, isEmpty)
|
||||
|
||||
import Data.Box exposing (Box)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type alias Dashboard =
|
||||
{ name : String
|
||||
, columns : Int
|
||||
, gap : Int
|
||||
, boxes : List Box
|
||||
}
|
||||
|
||||
|
||||
empty : Dashboard
|
||||
empty =
|
||||
{ name = ""
|
||||
, columns = 1
|
||||
, gap = 2
|
||||
, boxes = []
|
||||
}
|
||||
|
||||
|
||||
isEmpty : Dashboard -> Bool
|
||||
isEmpty board =
|
||||
List.isEmpty board.boxes
|
||||
|
||||
|
||||
|
||||
--- JSON
|
||||
|
||||
|
||||
encode : Dashboard -> E.Value
|
||||
encode b =
|
||||
E.object
|
||||
[ ( "name", E.string b.name )
|
||||
, ( "columns", E.int b.columns )
|
||||
, ( "gap", E.int b.gap )
|
||||
, ( "boxes", E.list Data.Box.encode b.boxes )
|
||||
]
|
||||
|
||||
|
||||
decoder : D.Decoder Dashboard
|
||||
decoder =
|
||||
D.map4 Dashboard
|
||||
(D.field "name" D.string)
|
||||
(D.field "columns" D.int)
|
||||
(D.field "gap" D.int)
|
||||
(D.field "boxes" <| D.list Data.Box.decoder)
|
||||
|
289
modules/webapp/src/main/elm/Data/Dashboards.elm
Normal file
289
modules/webapp/src/main/elm/Data/Dashboards.elm
Normal file
@ -0,0 +1,289 @@
|
||||
module Data.Dashboards exposing
|
||||
( AllDashboards
|
||||
, Dashboards
|
||||
, countAll
|
||||
, decoder
|
||||
, empty
|
||||
, emptyAll
|
||||
, encode
|
||||
, exists
|
||||
, existsAll
|
||||
, find
|
||||
, findInAll
|
||||
, foldl
|
||||
, getAllDefault
|
||||
, getDefault
|
||||
, getScope
|
||||
, insert
|
||||
, insertIn
|
||||
, isDefaultAll
|
||||
, isEmpty
|
||||
, isEmptyAll
|
||||
, map
|
||||
, remove
|
||||
, removeFromAll
|
||||
, selectBoards
|
||||
, setDefaultAll
|
||||
, singleton
|
||||
, singletonAll
|
||||
, unsetDefaultAll
|
||||
)
|
||||
|
||||
import Data.AccountScope exposing (AccountScope)
|
||||
import Data.Dashboard exposing (Dashboard)
|
||||
import Dict exposing (Dict)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
import Util.Maybe
|
||||
|
||||
|
||||
type Dashboards
|
||||
= Dashboards Info
|
||||
|
||||
|
||||
empty : Dashboards
|
||||
empty =
|
||||
Dashboards { default = "", boards = Dict.empty }
|
||||
|
||||
|
||||
isEmpty : Dashboards -> Bool
|
||||
isEmpty (Dashboards info) =
|
||||
Dict.isEmpty info.boards
|
||||
|
||||
|
||||
insert : Dashboard -> Dashboards -> Dashboards
|
||||
insert board (Dashboards info) =
|
||||
let
|
||||
nb =
|
||||
Dict.insert (String.toLower board.name) board info.boards
|
||||
in
|
||||
Dashboards { info | boards = nb }
|
||||
|
||||
|
||||
singleton : Dashboard -> Dashboards
|
||||
singleton board =
|
||||
insert board empty
|
||||
|
||||
|
||||
remove : String -> Dashboards -> Dashboards
|
||||
remove name (Dashboards info) =
|
||||
let
|
||||
nb =
|
||||
Dict.remove (String.toLower name) info.boards
|
||||
in
|
||||
Dashboards { info | boards = nb }
|
||||
|
||||
|
||||
map : (Dashboard -> a) -> Dashboards -> List a
|
||||
map f (Dashboards info) =
|
||||
List.map f (Dict.values info.boards)
|
||||
|
||||
|
||||
find : String -> Dashboards -> Maybe Dashboard
|
||||
find name (Dashboards info) =
|
||||
Dict.get (String.toLower name) info.boards
|
||||
|
||||
|
||||
foldl : (Dashboard -> a -> a) -> a -> Dashboards -> a
|
||||
foldl f init (Dashboards info) =
|
||||
List.foldl f init (Dict.values info.boards)
|
||||
|
||||
|
||||
exists : String -> Dashboards -> Bool
|
||||
exists name (Dashboards info) =
|
||||
Dict.member (String.toLower name) info.boards
|
||||
|
||||
|
||||
getDefault : Dashboards -> Maybe Dashboard
|
||||
getDefault (Dashboards info) =
|
||||
Dict.get (String.toLower info.default) info.boards
|
||||
|
||||
|
||||
isDefault : String -> Dashboards -> Bool
|
||||
isDefault name (Dashboards info) =
|
||||
String.toLower name == String.toLower info.default
|
||||
|
||||
|
||||
setDefault : String -> Dashboards -> Dashboards
|
||||
setDefault name (Dashboards info) =
|
||||
Dashboards { info | default = String.toLower name }
|
||||
|
||||
|
||||
unsetDefault : String -> Dashboards -> Dashboards
|
||||
unsetDefault name dbs =
|
||||
if isDefault name dbs then
|
||||
setDefault "" dbs
|
||||
|
||||
else
|
||||
dbs
|
||||
|
||||
|
||||
getFirst : Dashboards -> Maybe Dashboard
|
||||
getFirst (Dashboards info) =
|
||||
List.head (Dict.values info.boards)
|
||||
|
||||
|
||||
|
||||
--- AllDashboards
|
||||
|
||||
|
||||
type alias AllDashboards =
|
||||
{ collective : Dashboards
|
||||
, user : Dashboards
|
||||
}
|
||||
|
||||
|
||||
emptyAll : AllDashboards
|
||||
emptyAll =
|
||||
AllDashboards empty empty
|
||||
|
||||
|
||||
isEmptyAll : AllDashboards -> Bool
|
||||
isEmptyAll all =
|
||||
isEmpty all.collective && isEmpty all.user
|
||||
|
||||
|
||||
insertIn : AccountScope -> Dashboard -> AllDashboards -> AllDashboards
|
||||
insertIn scope board all =
|
||||
Data.AccountScope.fold
|
||||
{ user = insert board all.user
|
||||
, collective = all.collective
|
||||
}
|
||||
{ user = all.user
|
||||
, collective = insert board all.collective
|
||||
}
|
||||
scope
|
||||
|
||||
|
||||
selectBoards : AccountScope -> AllDashboards -> Dashboards
|
||||
selectBoards scope all =
|
||||
Data.AccountScope.fold all.user all.collective scope
|
||||
|
||||
|
||||
getAllDefault : AllDashboards -> Maybe Dashboard
|
||||
getAllDefault boards =
|
||||
Util.Maybe.or
|
||||
[ getDefault boards.user
|
||||
, getDefault boards.collective
|
||||
, getFirst boards.user
|
||||
, getFirst boards.collective
|
||||
]
|
||||
|
||||
|
||||
existsAll : String -> AllDashboards -> Bool
|
||||
existsAll name boards =
|
||||
exists name boards.collective || exists name boards.user
|
||||
|
||||
|
||||
singletonAll : Dashboard -> AllDashboards
|
||||
singletonAll board =
|
||||
AllDashboards empty (singleton board)
|
||||
|
||||
|
||||
isDefaultAll : String -> AllDashboards -> Bool
|
||||
isDefaultAll name all =
|
||||
isDefault name all.user || isDefault name all.collective
|
||||
|
||||
|
||||
findInAll : String -> AllDashboards -> Maybe Dashboard
|
||||
findInAll name all =
|
||||
Util.Maybe.or
|
||||
[ find name all.user
|
||||
, find name all.collective
|
||||
]
|
||||
|
||||
|
||||
removeFromAll : String -> AllDashboards -> AllDashboards
|
||||
removeFromAll name all =
|
||||
{ user = remove name all.user
|
||||
, collective = remove name all.collective
|
||||
}
|
||||
|
||||
|
||||
setDefaultAll : String -> AllDashboards -> AllDashboards
|
||||
setDefaultAll name all =
|
||||
if isDefaultAll name all then
|
||||
all
|
||||
|
||||
else
|
||||
{ user = setDefault name all.user
|
||||
, collective = setDefault name all.collective
|
||||
}
|
||||
|
||||
|
||||
unsetDefaultAll : String -> AllDashboards -> AllDashboards
|
||||
unsetDefaultAll name all =
|
||||
if isDefaultAll name all then
|
||||
{ user = unsetDefault name all.user
|
||||
, collective = unsetDefault name all.collective
|
||||
}
|
||||
|
||||
else
|
||||
all
|
||||
|
||||
|
||||
getScope : String -> AllDashboards -> Maybe AccountScope
|
||||
getScope name all =
|
||||
if exists name all.user then
|
||||
Just Data.AccountScope.User
|
||||
|
||||
else if exists name all.collective then
|
||||
Just Data.AccountScope.Collective
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
countAll : AllDashboards -> Int
|
||||
countAll all =
|
||||
List.sum
|
||||
[ foldl (\_ -> \n -> n + 1) 0 all.user
|
||||
, foldl (\_ -> \n -> n + 1) 0 all.collective
|
||||
]
|
||||
|
||||
|
||||
|
||||
--- Helper
|
||||
|
||||
|
||||
type alias Info =
|
||||
{ boards : Dict String Dashboard
|
||||
, default : String
|
||||
}
|
||||
|
||||
|
||||
|
||||
--- JSON
|
||||
|
||||
|
||||
decoder : D.Decoder Dashboards
|
||||
decoder =
|
||||
D.oneOf
|
||||
[ D.map Dashboards infoDecoder
|
||||
, emptyObjectDecoder
|
||||
]
|
||||
|
||||
|
||||
encode : Dashboards -> E.Value
|
||||
encode (Dashboards info) =
|
||||
infoEncode info
|
||||
|
||||
|
||||
infoDecoder : D.Decoder Info
|
||||
infoDecoder =
|
||||
D.map2 Info
|
||||
(D.field "boards" <| D.dict Data.Dashboard.decoder)
|
||||
(D.field "default" D.string)
|
||||
|
||||
|
||||
emptyObjectDecoder : D.Decoder Dashboards
|
||||
emptyObjectDecoder =
|
||||
D.dict (D.fail "non-empty") |> D.map (\_ -> empty)
|
||||
|
||||
|
||||
infoEncode : Info -> E.Value
|
||||
infoEncode info =
|
||||
E.object
|
||||
[ ( "boards", E.dict identity Data.Dashboard.encode info.boards )
|
||||
, ( "default", E.string info.default )
|
||||
]
|
@ -2,6 +2,8 @@ module Data.ItemColumn exposing (..)
|
||||
|
||||
import Api.Model.ItemLight exposing (ItemLight)
|
||||
import Data.ItemTemplate as IT exposing (TemplateContext)
|
||||
import Json.Decode as D
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
type ItemColumn
|
||||
@ -75,7 +77,7 @@ asString col =
|
||||
"folder"
|
||||
|
||||
Correspondent ->
|
||||
"correspodnent"
|
||||
"correspondent"
|
||||
|
||||
Concerning ->
|
||||
"concerning"
|
||||
@ -105,7 +107,7 @@ fromString str =
|
||||
"folder" ->
|
||||
Just Folder
|
||||
|
||||
"correspodnent" ->
|
||||
"correspondent" ->
|
||||
Just Correspondent
|
||||
|
||||
"concerning" ->
|
||||
@ -116,3 +118,22 @@ fromString str =
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
|
||||
|
||||
encode : ItemColumn -> E.Value
|
||||
encode col =
|
||||
asString col |> E.string
|
||||
|
||||
|
||||
decode : D.Decoder ItemColumn
|
||||
decode =
|
||||
let
|
||||
from str =
|
||||
case fromString str of
|
||||
Just col ->
|
||||
D.succeed col
|
||||
|
||||
Nothing ->
|
||||
D.fail ("Invalid column: " ++ str)
|
||||
in
|
||||
D.andThen from D.string
|
||||
|
Reference in New Issue
Block a user