Save and load dashboards

This commit is contained in:
eikek
2022-01-26 21:23:48 +01:00
parent e83bf6b750
commit 3ff7e255b4
24 changed files with 1647 additions and 192 deletions

View 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

View File

@ -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 )
]

View File

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

View File

@ -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)

View 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 )
]

View File

@ -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