Merge pull request #1320 from eikek/dashboard

Dashboard
This commit is contained in:
mergify[bot] 2022-01-26 22:40:40 +00:00 committed by GitHub
commit b701d25c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
117 changed files with 7943 additions and 941 deletions

View File

@ -1961,7 +1961,7 @@ paths:
- $ref: "#/components/parameters/bookmarkId"
delete:
operationId: "sec-querybookmark-delete"
tags: [Query Bookmark]
tags: [Query Bookmarks]
summary: Delete a bookmark.
description: |
Deletes a bookmarks by its id.

View File

@ -11,6 +11,7 @@ module Api exposing
, addConcPerson
, addCorrOrg
, addCorrPerson
, addDashboard
, addMember
, addShare
, addTag
@ -39,6 +40,7 @@ module Api exposing
, deleteCustomField
, deleteCustomValue
, deleteCustomValueMultiple
, deleteDashboard
, deleteEquip
, deleteFolder
, deleteHook
@ -56,6 +58,7 @@ module Api exposing
, deleteUser
, disableOtp
, fileURL
, getAllDashboards
, getAttachmentMeta
, getBookmarks
, getChannels
@ -101,7 +104,9 @@ module Api exposing
, itemDetailShare
, itemIndexSearch
, itemSearch
, itemSearchBookmark
, itemSearchStats
, itemSearchStatsBookmark
, login
, loginSession
, logout
@ -124,6 +129,7 @@ module Api exposing
, register
, removeMember
, removeTagsMultiple
, replaceDashboard
, reprocessItem
, reprocessMultiple
, restoreAllItems
@ -275,9 +281,12 @@ import Api.Model.User exposing (User)
import Api.Model.UserList exposing (UserList)
import Api.Model.UserPass exposing (UserPass)
import Api.Model.VersionInfo exposing (VersionInfo)
import Data.AccountScope exposing (AccountScope)
import Data.Bookmarks exposing (AllBookmarks, Bookmarks)
import Data.ContactType exposing (ContactType)
import Data.CustomFieldOrder exposing (CustomFieldOrder)
import Data.Dashboard exposing (Dashboard)
import Data.Dashboards exposing (AllDashboards, Dashboards)
import Data.EquipmentOrder exposing (EquipmentOrder)
import Data.EventType exposing (EventType)
import Data.Flags exposing (Flags)
@ -297,6 +306,7 @@ import Task
import Url
import Util.File
import Util.Http as Http2
import Util.Result
@ -2028,24 +2038,70 @@ itemIndexSearch flags query receive =
}
itemSearch : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
itemSearch flags search receive =
Http2.authPost
itemSearchTask : Flags -> ItemQuery -> Task.Task Http.Error ItemLightList
itemSearchTask flags search =
Http2.authTask
{ url = flags.config.baseUrl ++ "/api/v1/sec/item/search"
, method = "POST"
, headers = []
, account = getAccount flags
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
, expect = Http.expectJson receive Api.Model.ItemLightList.decoder
, resolver = Http2.jsonResolver Api.Model.ItemLightList.decoder
, timeout = Nothing
}
itemSearch : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
itemSearch flags search receive =
itemSearchTask flags search |> Task.attempt receive
{-| Same as `itemSearch` but interprets the `query` field as a bookmark id.
-}
itemSearchBookmark : Flags -> ItemQuery -> (Result Http.Error ItemLightList -> msg) -> Cmd msg
itemSearchBookmark flags bmSearch receive =
let
getBookmark =
getBookmarkByIdTask flags bmSearch.query
|> Task.map (\bm -> { bmSearch | query = bm.query })
search q =
itemSearchTask flags q
in
Task.andThen search getBookmark
|> Task.attempt receive
itemSearchStatsTask : Flags -> ItemQuery -> Task.Task Http.Error SearchStats
itemSearchStatsTask flags search =
Http2.authTask
{ url = flags.config.baseUrl ++ "/api/v1/sec/item/searchStats"
, method = "POST"
, headers = []
, account = getAccount flags
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
, resolver = Http2.jsonResolver Api.Model.SearchStats.decoder
, timeout = Nothing
}
itemSearchStats : Flags -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg
itemSearchStats flags search receive =
Http2.authPost
{ url = flags.config.baseUrl ++ "/api/v1/sec/item/searchStats"
, account = getAccount flags
, body = Http.jsonBody (Api.Model.ItemQuery.encode search)
, expect = Http.expectJson receive Api.Model.SearchStats.decoder
}
itemSearchStatsTask flags search |> Task.attempt receive
itemSearchStatsBookmark : Flags -> ItemQuery -> (Result Http.Error SearchStats -> msg) -> Cmd msg
itemSearchStatsBookmark flags search receive =
let
getBookmark =
getBookmarkByIdTask flags search.query
|> Task.map (\bm -> { search | query = bm.query })
getStats q =
itemSearchStatsTask flags q
in
Task.andThen getStats getBookmark
|> Task.attempt receive
itemDetail : Flags -> String -> (Result Http.Error ItemDetail -> msg) -> Cmd msg
@ -2314,6 +2370,132 @@ saveClientSettings flags settings receive =
--- Dashboards
dashboardsUrl : Flags -> AccountScope -> String
dashboardsUrl flags scope =
let
part =
Data.AccountScope.fold "user" "collective" scope
in
flags.config.baseUrl ++ "/api/v1/sec/clientSettings/" ++ part ++ "/webClientDashboards"
getDashboardsScopeTask : Flags -> AccountScope -> Task.Task Http.Error Dashboards
getDashboardsScopeTask flags scope =
Http2.authTask
{ method = "GET"
, url = dashboardsUrl flags scope
, account = getAccount flags
, body = Http.emptyBody
, resolver = Http2.jsonResolver Data.Dashboards.decoder
, headers = []
, timeout = Nothing
}
pushDashbordsScopeTask : Flags -> AccountScope -> Dashboards -> Task.Task Http.Error BasicResult
pushDashbordsScopeTask flags scope boards =
Http2.authTask
{ method = "PUT"
, url = dashboardsUrl flags scope
, account = getAccount flags
, body = Http.jsonBody (Data.Dashboards.encode boards)
, resolver = Http2.jsonResolver Api.Model.BasicResult.decoder
, headers = []
, timeout = Nothing
}
getAllDashboardsTask : Flags -> Task.Task Http.Error AllDashboards
getAllDashboardsTask flags =
let
coll =
getDashboardsScopeTask flags Data.AccountScope.Collective
user =
getDashboardsScopeTask flags Data.AccountScope.User
in
Task.map2 AllDashboards coll user
getAllDashboards : Flags -> (Result Http.Error AllDashboards -> msg) -> Cmd msg
getAllDashboards flags receive =
getAllDashboardsTask flags |> Task.attempt receive
saveDashboardTask : Flags -> String -> Dashboard -> AccountScope -> Bool -> Task.Task Http.Error BasicResult
saveDashboardTask flags original board scope isDefault =
let
boardsTask =
getAllDashboardsTask flags
setDefault all =
if isDefault then
Data.Dashboards.setDefaultAll board.name all
else
Data.Dashboards.unsetDefaultAll board.name all
removeOriginal boards =
Data.Dashboards.removeFromAll original boards
insert all =
Data.Dashboards.insertIn scope board all
update all =
let
next =
(removeOriginal >> insert >> setDefault) all
saveU =
if all.user == next.user then
Task.succeed (BasicResult True "")
else
pushDashbordsScopeTask flags Data.AccountScope.User next.user
saveC =
if all.collective == next.collective then
Task.succeed (BasicResult True "")
else
pushDashbordsScopeTask flags Data.AccountScope.Collective next.collective
in
Task.map2 Util.Result.combine saveU saveC
in
Task.andThen update boardsTask
addDashboard : Flags -> Dashboard -> AccountScope -> Bool -> (Result Http.Error BasicResult -> msg) -> Cmd msg
addDashboard flags board scope isDefault receive =
saveDashboardTask flags board.name board scope isDefault |> Task.attempt receive
replaceDashboard : Flags -> String -> Dashboard -> AccountScope -> Bool -> (Result Http.Error BasicResult -> msg) -> Cmd msg
replaceDashboard flags originalName board scope isDefault receive =
saveDashboardTask flags originalName board scope isDefault |> Task.attempt receive
deleteDashboardTask : Flags -> String -> AccountScope -> Task.Task Http.Error BasicResult
deleteDashboardTask flags name scope =
let
boardsTask =
getDashboardsScopeTask flags scope
remove boards =
Data.Dashboards.remove name boards
in
Task.andThen (remove >> pushDashbordsScopeTask flags scope) boardsTask
deleteDashboard : Flags -> String -> AccountScope -> (Result Http.Error BasicResult -> msg) -> Cmd msg
deleteDashboard flags name scope receive =
deleteDashboardTask flags name scope |> Task.attempt receive
--- Query Bookmarks
@ -2335,6 +2517,21 @@ getBookmarksTask flags =
}
getBookmarkByIdTask : Flags -> String -> Task.Task Http.Error BookmarkedQuery
getBookmarkByIdTask flags id =
let
findBm all =
Data.Bookmarks.findById id all
mapNotFound maybeBookmark =
Maybe.map Task.succeed maybeBookmark
|> Maybe.withDefault (Task.fail (Http.BadStatus 404))
in
getBookmarksTask flags
|> Task.map findBm
|> Task.andThen mapNotFound
getBookmarks : Flags -> (Result Http.Error AllBookmarks -> msg) -> Cmd msg
getBookmarks flags receive =
let

View File

@ -18,21 +18,25 @@ import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.VersionInfo exposing (VersionInfo)
import Browser exposing (UrlRequest)
import Browser.Navigation exposing (Key)
import Data.Dashboard exposing (Dashboard)
import Data.Flags exposing (Flags)
import Data.ServerEvent exposing (ServerEvent)
import Data.UiSettings exposing (StoredUiSettings, UiSettings)
import Data.UiTheme exposing (UiTheme)
import Http
import Messages
import Messages.UiLanguage exposing (UiLanguage)
import Page exposing (Page(..))
import Page.CollectiveSettings.Data
import Page.Home.Data
import Page.Dashboard.Data
import Page.Dashboard.DefaultDashboard
import Page.ItemDetail.Data
import Page.Login.Data
import Page.ManageData.Data
import Page.NewInvite.Data
import Page.Queue.Data
import Page.Register.Data
import Page.Search.Data
import Page.Share.Data
import Page.ShareDetail.Data
import Page.Upload.Data
@ -45,7 +49,7 @@ type alias Model =
, key : Key
, page : Page
, version : VersionInfo
, homeModel : Page.Home.Data.Model
, searchModel : Page.Search.Data.Model
, loginModel : Page.Login.Data.Model
, manageDataModel : Page.ManageData.Data.Model
, collSettingsModel : Page.CollectiveSettings.Data.Model
@ -57,6 +61,7 @@ type alias Model =
, itemDetailModel : Page.ItemDetail.Data.Model
, shareModel : Page.Share.Data.Model
, shareDetailModel : Page.ShareDetail.Data.Model
, dashboardModel : Page.Dashboard.Data.Model
, navMenuOpen : Bool
, userMenuOpen : Bool
, subs : Sub Msg
@ -98,18 +103,21 @@ init key url flags_ settings =
( sdm, sdc ) =
Page.ShareDetail.Data.init (Page.pageShareDetail page) flags
homeViewMode =
( dbm, dbc ) =
Page.Dashboard.Data.init flags
searchViewMode =
if settings.searchMenuVisible then
Page.Home.Data.SearchView
Page.Search.Data.SearchView
else
Page.Home.Data.SimpleView
Page.Search.Data.SimpleView
in
( { flags = flags
, key = key
, page = page
, version = Api.Model.VersionInfo.empty
, homeModel = Page.Home.Data.init flags homeViewMode
, searchModel = Page.Search.Data.init flags searchViewMode
, loginModel = loginm
, manageDataModel = mdm
, collSettingsModel = csm
@ -121,6 +129,7 @@ init key url flags_ settings =
, itemDetailModel = Page.ItemDetail.Data.emptyModel
, shareModel = shm
, shareDetailModel = sdm
, dashboardModel = dbm
, navMenuOpen = False
, userMenuOpen = False
, subs = Sub.none
@ -133,7 +142,8 @@ init key url flags_ settings =
, jobsWaiting = 0
}
, Cmd.batch
[ Cmd.map UserSettingsMsg uc
[ Cmd.map DashboardMsg dbc
, Cmd.map UserSettingsMsg uc
, Cmd.map ManageDataMsg mdc
, Cmd.map CollSettingsMsg csc
, Cmd.map LoginMsg loginc
@ -171,7 +181,7 @@ type Msg
= NavRequest UrlRequest
| NavChange Url
| VersionResp (Result Http.Error VersionInfo)
| HomeMsg Page.Home.Data.Msg
| SearchMsg Page.Search.Data.Msg
| LoginMsg Page.Login.Data.Msg
| ManageDataMsg Page.ManageData.Data.Msg
| CollSettingsMsg Page.CollectiveSettings.Data.Msg
@ -183,6 +193,7 @@ type Msg
| ItemDetailMsg Page.ItemDetail.Data.Msg
| ShareMsg Page.Share.Data.Msg
| ShareDetailMsg Page.ShareDetail.Data.Msg
| DashboardMsg Page.Dashboard.Data.Msg
| Logout
| LogoutResp (Result Http.Error ())
| SessionCheckResp (Result Http.Error AuthResult)
@ -201,14 +212,9 @@ type Msg
defaultPage : Flags -> Page
defaultPage _ =
HomePage
DashboardPage
getUiLanguage : Model -> UiLanguage
getUiLanguage model =
case model.flags.account of
Just _ ->
model.uiSettings.uiLang
Nothing ->
model.anonymousUiLang
Data.UiSettings.getUiLanguage model.flags model.uiSettings model.anonymousUiLang

View File

@ -14,7 +14,7 @@ import Api
import App.Data exposing (..)
import Browser exposing (UrlRequest(..))
import Browser.Navigation as Nav
import Data.Flags exposing (Flags)
import Data.Flags
import Data.ServerEvent exposing (ServerEvent(..))
import Data.UiSettings exposing (UiSettings)
import Data.UiTheme
@ -22,8 +22,8 @@ import Messages exposing (Messages)
import Page exposing (Page(..))
import Page.CollectiveSettings.Data
import Page.CollectiveSettings.Update
import Page.Home.Data
import Page.Home.Update
import Page.Dashboard.Data
import Page.Dashboard.Update
import Page.ItemDetail.Data
import Page.ItemDetail.Update
import Page.Login.Data
@ -36,6 +36,8 @@ import Page.Queue.Data
import Page.Queue.Update
import Page.Register.Data
import Page.Register.Update
import Page.Search.Data
import Page.Search.Update
import Page.Share.Data
import Page.Share.Update
import Page.ShareDetail.Data
@ -121,8 +123,8 @@ updateWithSub msg model =
SetLanguage lang ->
( { model | anonymousUiLang = lang, langMenuOpen = False }, Cmd.none, Sub.none )
HomeMsg lm ->
updateHome texts lm model
SearchMsg lm ->
updateSearch texts lm model
ShareMsg lm ->
updateShare lm model
@ -157,6 +159,9 @@ updateWithSub msg model =
ItemDetailMsg m ->
updateItemDetail texts m model
DashboardMsg m ->
updateDashboard texts m model
VersionResp (Ok info) ->
( { model | version = info }, Cmd.none, Sub.none )
@ -318,12 +323,15 @@ updateWithSub msg model =
newModel =
{ model
| showNewItemsArrived = isProcessItem && model.page /= HomePage
| showNewItemsArrived = isProcessItem && not (Page.isSearchPage model.page)
, jobsWaiting = max 0 (model.jobsWaiting - 1)
}
in
if model.page == HomePage && isProcessItem then
updateHome texts Page.Home.Data.RefreshView newModel
if Page.isSearchPage model.page && isProcessItem then
updateSearch texts Page.Search.Data.RefreshView newModel
else if Page.isDashboardPage model.page && isProcessItem then
updateDashboard texts Page.Dashboard.Data.reloadDashboardData newModel
else
( newModel, Cmd.none, Sub.none )
@ -359,13 +367,31 @@ applyClientSettings texts model settings =
, setTheme
, Sub.none
)
, updateDashboard texts Page.Dashboard.Data.reloadUiSettings
, updateUserSettings texts Page.UserSettings.Data.UpdateSettings
, updateHome texts Page.Home.Data.UiSettingsUpdated
, updateSearch texts Page.Search.Data.UiSettingsUpdated
, updateItemDetail texts Page.ItemDetail.Data.UiSettingsUpdated
]
{ model | uiSettings = settings }
updateDashboard : Messages -> Page.Dashboard.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
updateDashboard texts lmsg model =
let
( dbm, dbc, dbs ) =
Page.Dashboard.Update.update texts.dashboard
model.uiSettings
model.key
model.flags
lmsg
model.dashboardModel
in
( { model | dashboardModel = dbm }
, Cmd.map DashboardMsg dbc
, Sub.map DashboardMsg dbs
)
updateShareDetail : Page.ShareDetail.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
updateShareDetail lmsg model =
case Page.pageShareDetail model.page of
@ -404,7 +430,7 @@ updateItemDetail : Messages -> Page.ItemDetail.Data.Msg -> Model -> ( Model, Cmd
updateItemDetail texts lmsg model =
let
inav =
Page.Home.Data.itemNav model.itemDetailModel.detail.item.id model.homeModel
Page.Search.Data.itemNav model.itemDetailModel.detail.item.id model.searchModel
result =
Page.ItemDetail.Update.update
@ -421,12 +447,12 @@ updateItemDetail texts lmsg model =
}
( hm, hc, hs ) =
updateHome texts (Page.Home.Data.SetLinkTarget result.linkTarget) model_
updateSearch texts (Page.Search.Data.SetLinkTarget result.linkTarget) model_
( hm1, hc1, hs1 ) =
case result.removedItem of
Just removedId ->
updateHome texts (Page.Home.Data.RemoveItem removedId) hm
updateSearch texts (Page.Search.Data.RemoveItem removedId) hm
Nothing ->
( hm, hc, hs )
@ -552,22 +578,22 @@ updateLogin lmsg model =
)
updateHome : Messages -> Page.Home.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
updateHome texts lmsg model =
updateSearch : Messages -> Page.Search.Data.Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
updateSearch texts lmsg model =
let
mid =
( mid, bmId ) =
case model.page of
HomePage ->
Util.Maybe.fromString model.itemDetailModel.detail.item.id
SearchPage bId ->
( Util.Maybe.fromString model.itemDetailModel.detail.item.id, bId )
_ ->
Nothing
( Nothing, Nothing )
result =
Page.Home.Update.update mid model.key model.flags texts.home model.uiSettings lmsg model.homeModel
Page.Search.Update.update bmId mid model.key model.flags texts.search model.uiSettings lmsg model.searchModel
model_ =
{ model | homeModel = result.model }
{ model | searchModel = result.model }
( lm, lc, ls ) =
case result.newSettings of
@ -579,11 +605,11 @@ updateHome texts lmsg model =
in
( lm
, Cmd.batch
[ Cmd.map HomeMsg result.cmd
[ Cmd.map SearchMsg result.cmd
, lc
]
, Sub.batch
[ Sub.map HomeMsg result.sub
[ Sub.map SearchMsg result.sub
, ls
]
)
@ -611,9 +637,9 @@ initPage model_ page =
Messages.get <| App.Data.getUiLanguage model
in
case page of
HomePage ->
SearchPage _ ->
Util.Update.andThen2
[ updateHome texts Page.Home.Data.Init
[ updateSearch texts Page.Search.Data.Init
, updateQueue Page.Queue.Data.StopRefresh
]
model
@ -646,7 +672,7 @@ initPage model_ page =
UploadPage _ ->
Util.Update.andThen2
[ updateQueue Page.Queue.Data.StopRefresh
, updateUpload Page.Upload.Data.Clear
, updateUpload Page.Upload.Data.reset
]
model
@ -685,3 +711,6 @@ initPage model_ page =
_ ->
( model, Cmd.none, Sub.none )
DashboardPage ->
( model, Cmd.map DashboardMsg (Page.Dashboard.Data.reinitCmd model.flags), Sub.none )

View File

@ -11,6 +11,8 @@ import Api.Model.AuthResult exposing (AuthResult)
import App.Data exposing (..)
import Comp.Basic as B
import Data.Flags
import Data.Icons as Icons
import Data.UiSettings
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
@ -19,14 +21,15 @@ import Messages.App exposing (Texts)
import Messages.UiLanguage
import Page exposing (Page(..))
import Page.CollectiveSettings.View2 as CollectiveSettings
import Page.Home.Data
import Page.Home.View2 as Home
import Page.Dashboard.View as Dashboard
import Page.ItemDetail.View2 as ItemDetail
import Page.Login.View2 as Login
import Page.ManageData.View2 as ManageData
import Page.NewInvite.View2 as NewInvite
import Page.Queue.View2 as Queue
import Page.Register.View2 as Register
import Page.Search.Data
import Page.Search.View2 as Search
import Page.Share.View as Share
import Page.ShareDetail.View as ShareDetail
import Page.Upload.View2 as Upload
@ -76,7 +79,11 @@ topNavUser auth model =
[ class S.infoMessageBase
, class "my-2 px-1 py-1 rounded-lg inline-block hover:opacity-50"
, classList [ ( "hidden", not model.showNewItemsArrived ) ]
, Page.href HomePage
, if Page.isSearchPage model.page || Page.isDashboardPage model.page then
href "#"
else
Page.href (SearchPage Nothing)
, onClick ToggleShowNewItemsArrived
]
[ i [ class "fa fa-exclamation-circle mr-1" ] []
@ -133,7 +140,7 @@ headerNavItem authenticated model =
[ class "inline-flex font-bold items-center px-4"
, classList [ ( "hover:bg-blue-200 dark:hover:bg-slate-800", authenticated ) ]
, if authenticated then
Page.href HomePage
Page.href DashboardPage
else
href "#"
@ -160,8 +167,11 @@ mainContent model =
, class styleMain
]
(case model.page of
HomePage ->
viewHome texts model
DashboardPage ->
viewDashboard texts model
SearchPage bmId ->
viewSearch texts bmId model
CollectiveSettingPage ->
viewCollectiveSettings texts model
@ -280,7 +290,7 @@ dataMenu texts _ model =
, classList [ ( "hidden", not model.navMenuOpen ) ]
]
[ dataPageLink model
HomePage
DashboardPage
[]
[ img
[ class "w-4 inline-block"
@ -288,14 +298,22 @@ dataMenu texts _ model =
]
[]
, div [ class "inline-block ml-2" ]
[ text texts.items
[ text texts.dashboard
]
]
, div [ class "py-1" ] [ hr [ class S.border ] [] ]
, dataPageLink model
(SearchPage Nothing)
[]
[ Icons.searchIcon "w-6"
, span [ class "ml-1" ]
[ text texts.items
]
]
, dataPageLink model
ManageDataPage
[]
[ i [ class "fa fa-cubes w-6" ] []
[ Icons.metadataIcon "w-6"
, span [ class "ml-1" ]
[ text texts.manageData
]
@ -304,7 +322,7 @@ dataMenu texts _ model =
, dataPageLink model
(UploadPage Nothing)
[]
[ i [ class "fa fa-upload w-6" ] []
[ Icons.fileUploadIcon "w-6"
, span [ class "ml-1" ]
[ text texts.uploadFiles
]
@ -345,11 +363,11 @@ dataMenu texts _ model =
]
, a
[ class dropdownItem
, href "https://docspell.org/docs"
, href Data.UiSettings.documentationSite
, target "_new"
, title "Opens https://docspell.org/docs"
, title ("Opens " ++ Data.UiSettings.documentationSite)
]
[ i [ class "fa fa-question-circle w-6" ] []
[ Icons.documentationIcon "w-6"
, span [ class "ml-1" ] [ text texts.help ]
, span [ class "float-right" ]
[ i [ class "fa fa-external-link-alt w-6" ] []
@ -467,6 +485,25 @@ dropdownMenu =
" absolute right-0 bg-white dark:bg-slate-800 border dark:border-slate-700 dark:text-slate-300 shadow-lg opacity-1 transition duration-200 min-w-max "
viewDashboard : Messages -> Model -> List (Html Msg)
viewDashboard texts model =
[ Html.map DashboardMsg
(Dashboard.viewSidebar texts.dashboard
model.sidebarVisible
model.flags
model.version
model.uiSettings
model.dashboardModel
)
, Html.map DashboardMsg
(Dashboard.viewContent texts.dashboard
model.flags
model.uiSettings
model.dashboardModel
)
]
viewShare : Messages -> String -> Model -> List (Html Msg)
viewShare texts shareId model =
[ Html.map ShareMsg
@ -510,20 +547,20 @@ viewShareDetail texts shareId itemId model =
]
viewHome : Messages -> Model -> List (Html Msg)
viewHome texts model =
[ Html.map HomeMsg
(Home.viewSidebar texts.home
viewSearch : Messages -> Maybe String -> Model -> List (Html Msg)
viewSearch texts bmId model =
[ Html.map SearchMsg
(Search.viewSidebar texts.search
model.sidebarVisible
model.flags
model.uiSettings
model.homeModel
model.searchModel
)
, Html.map HomeMsg
(Home.viewContent texts.home
, Html.map SearchMsg
(Search.viewContent texts.search
model.flags
model.uiSettings
model.homeModel
model.searchModel
)
]
@ -647,7 +684,7 @@ viewItemDetail : Messages -> String -> Model -> List (Html Msg)
viewItemDetail texts id model =
let
inav =
Page.Home.Data.itemNav id model.homeModel
Page.Search.Data.itemNav id model.searchModel
in
[ Html.map ItemDetailMsg
(ItemDetail.viewSidebar texts.itemDetail

View File

@ -207,7 +207,7 @@ loadingDimmer : { label : String, active : Bool } -> Html msg
loadingDimmer cfg =
let
content =
div [ class "text-gray-200" ]
div [ class "text-gray-200 " ]
[ i [ class "fa fa-circle-notch animate-spin" ] []
, span [ class "ml-2" ]
[ text cfg.label

View File

@ -16,6 +16,7 @@ module Comp.BookmarkChooser exposing
, isEmptySelection
, update
, view
, viewWith
)
import Api.Model.BookmarkedQuery exposing (BookmarkedQuery)
@ -114,33 +115,43 @@ update msg model current =
--- View
view : Texts -> Model -> Selection -> Html Msg
view texts model selection =
type alias ViewSettings =
{ showUser : Bool
, showCollective : Bool
, showShares : Bool
}
viewWith : ViewSettings -> Texts -> Model -> Selection -> Html Msg
viewWith cfg texts model selection =
let
( user, coll ) =
List.partition .personal model.all.bookmarks
in
div [ class "flex flex-col" ]
[ userBookmarks texts user selection
, collBookmarks texts coll selection
, shares texts model selection
[ userBookmarks cfg.showUser texts user selection
, collBookmarks cfg.showCollective texts coll selection
, shares cfg.showShares texts model selection
]
view : Texts -> Model -> Selection -> Html Msg
view =
viewWith { showUser = True, showCollective = True, showShares = True }
titleDiv : String -> Html msg
titleDiv label =
div [ class "text-sm opacity-75 py-0.5 italic" ]
[ text label
--, text " ──"
]
userBookmarks : Texts -> List BookmarkedQuery -> Selection -> Html Msg
userBookmarks texts model sel =
userBookmarks : Bool -> Texts -> List BookmarkedQuery -> Selection -> Html Msg
userBookmarks visible texts model sel =
div
[ class "mb-2"
, classList [ ( "hidden", model == [] ) ]
, classList [ ( "hidden", model == [] || not visible ) ]
]
[ titleDiv texts.userLabel
, div [ class "flex flex-col space-y-2 md:space-y-1" ]
@ -148,11 +159,11 @@ userBookmarks texts model sel =
]
collBookmarks : Texts -> List BookmarkedQuery -> Selection -> Html Msg
collBookmarks texts model sel =
collBookmarks : Bool -> Texts -> List BookmarkedQuery -> Selection -> Html Msg
collBookmarks visible texts model sel =
div
[ class "mb-2"
, classList [ ( "hidden", [] == model ) ]
, classList [ ( "hidden", [] == model || not visible ) ]
]
[ titleDiv texts.collectiveLabel
, div [ class "flex flex-col space-y-2 md:space-y-1" ]
@ -160,15 +171,15 @@ collBookmarks texts model sel =
]
shares : Texts -> Model -> Selection -> Html Msg
shares texts model sel =
shares : Bool -> Texts -> Model -> Selection -> Html Msg
shares visible texts model sel =
let
bms =
List.map shareToBookmark model.all.shares
in
div
[ class ""
, classList [ ( "hidden", List.isEmpty bms ) ]
, classList [ ( "hidden", List.isEmpty bms || not visible ) ]
]
[ titleDiv texts.shareLabel
, div [ class "flex flex-col space-y-2 md:space-y-1" ]

View File

@ -0,0 +1,474 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxEdit exposing
( BoxAction(..)
, Model
, Msg
, UpdateResult
, init
, update
, view
)
import Comp.Basic as B
import Comp.BoxMessageEdit
import Comp.BoxQueryEdit
import Comp.BoxStatsEdit
import Comp.BoxUploadEdit
import Comp.FixedDropdown
import Comp.MenuBar as MB
import Data.Box exposing (Box)
import Data.BoxContent exposing (BoxContent(..))
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, i, input, label, text)
import Html.Attributes exposing (class, classList, placeholder, type_, value)
import Html.Events exposing (onInput, onMouseEnter, onMouseLeave)
import Messages.Comp.BoxEdit exposing (Texts)
import Styles as S
type alias Model =
{ box : Box
, content : ContentModel
, colspanModel : Comp.FixedDropdown.Model Int
, focus : Bool
, deleteRequested : Bool
}
type ContentModel
= ContentMessage Comp.BoxMessageEdit.Model
| ContentQuery Comp.BoxQueryEdit.Model
| ContentStats Comp.BoxStatsEdit.Model
| ContentUpload Comp.BoxUploadEdit.Model
type Msg
= ToggleVisible
| ToggleDecoration
| SetName String
| ColspanMsg (Comp.FixedDropdown.Msg Int)
| MessageMsg Comp.BoxMessageEdit.Msg
| UploadMsg Comp.BoxUploadEdit.Msg
| QueryMsg Comp.BoxQueryEdit.Msg
| StatsMsg Comp.BoxStatsEdit.Msg
| SetFocus Bool
| RequestDelete
| DeleteBox
| CancelDelete
| MoveLeft
| MoveRight
init : Flags -> Box -> ( Model, Cmd Msg, Sub Msg )
init flags box =
let
( cm, cc, cs ) =
contentInit flags box.content
in
( { box = box
, content = cm
, colspanModel = Comp.FixedDropdown.init [ 1, 2, 3, 4, 5 ]
, focus = False
, deleteRequested = False
}
, cc
, cs
)
contentInit : Flags -> BoxContent -> ( ContentModel, Cmd Msg, Sub Msg )
contentInit flags content =
case content of
BoxMessage data ->
( ContentMessage (Comp.BoxMessageEdit.init data), Cmd.none, Sub.none )
BoxUpload data ->
let
( um, uc ) =
Comp.BoxUploadEdit.init flags data
in
( ContentUpload um, Cmd.map UploadMsg uc, Sub.none )
BoxQuery data ->
let
( qm, qc, qs ) =
Comp.BoxQueryEdit.init flags data
in
( ContentQuery qm, Cmd.map QueryMsg qc, Sub.map QueryMsg qs )
BoxStats data ->
let
( qm, qc, qs ) =
Comp.BoxStatsEdit.init flags data
in
( ContentStats qm, Cmd.map StatsMsg qc, Sub.map StatsMsg qs )
--- Update
type BoxAction
= BoxNoAction
| BoxMoveLeft
| BoxMoveRight
| BoxDelete
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
, action : BoxAction
}
update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
case msg of
MessageMsg lm ->
case model.content of
ContentMessage m ->
let
( mm, data ) =
Comp.BoxMessageEdit.update lm m
boxn =
model.box
box_ =
{ boxn | content = BoxMessage data }
in
{ model = { model | content = ContentMessage mm, box = box_ }
, cmd = Cmd.none
, sub = Sub.none
, action = BoxNoAction
}
_ ->
unit model
UploadMsg lm ->
case model.content of
ContentUpload m ->
let
( um, data ) =
Comp.BoxUploadEdit.update lm m
boxn =
model.box
box_ =
{ boxn | content = BoxUpload data }
in
{ model = { model | content = ContentUpload um, box = box_ }
, cmd = Cmd.none
, sub = Sub.none
, action = BoxNoAction
}
_ ->
unit model
QueryMsg lm ->
case model.content of
ContentQuery m ->
let
result =
Comp.BoxQueryEdit.update flags lm m
boxn =
model.box
box_ =
{ boxn | content = BoxQuery result.data }
in
{ model = { model | content = ContentQuery result.model, box = box_ }
, cmd = Cmd.map QueryMsg result.cmd
, sub = Sub.map QueryMsg result.sub
, action = BoxNoAction
}
_ ->
unit model
StatsMsg lm ->
case model.content of
ContentStats m ->
let
result =
Comp.BoxStatsEdit.update flags lm m
boxn =
model.box
box_ =
{ boxn | content = BoxStats result.data }
in
{ model = { model | content = ContentStats result.model, box = box_ }
, cmd = Cmd.map StatsMsg result.cmd
, sub = Sub.map StatsMsg result.sub
, action = BoxNoAction
}
_ ->
unit model
ColspanMsg lm ->
let
( cm, num ) =
Comp.FixedDropdown.update lm model.colspanModel
boxn =
model.box
box_ =
{ boxn | colspan = Maybe.withDefault boxn.colspan num }
in
unit { model | box = box_, colspanModel = cm }
ToggleVisible ->
let
box =
model.box
box_ =
{ box | visible = not box.visible }
in
unit { model | box = box_ }
ToggleDecoration ->
let
box =
model.box
box_ =
{ box | decoration = not box.decoration }
in
unit { model | box = box_ }
SetName name ->
let
box =
model.box
box_ =
{ box | name = name }
in
unit { model | box = box_ }
SetFocus flag ->
unit { model | focus = flag }
RequestDelete ->
unit { model | deleteRequested = True }
DeleteBox ->
UpdateResult model Cmd.none Sub.none BoxDelete
CancelDelete ->
unit { model | deleteRequested = False }
MoveLeft ->
UpdateResult model Cmd.none Sub.none BoxMoveLeft
MoveRight ->
UpdateResult model Cmd.none Sub.none BoxMoveRight
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none BoxNoAction
--- View
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
view texts flags settings model =
div
[ class (S.box ++ "rounded md:relative")
, class " h-full"
, classList [ ( "ring ring-opacity-50 ring-blue-600 dark:ring-sky-600", model.focus ) ]
, onMouseEnter (SetFocus True)
, onMouseLeave (SetFocus False)
]
[ B.contentDimmer model.deleteRequested
(div [ class "flex flex-col" ]
[ div [ class "text-xl" ]
[ i [ class "fa fa-info-circle mr-2" ] []
, text texts.reallyDeleteBox
]
, div [ class "mt-4 flex flex-row items-center space-x-2" ]
[ MB.viewItem <|
MB.DeleteButton
{ tagger = DeleteBox
, title = ""
, label = texts.basics.yes
, icon = Just "fa fa-check"
}
, MB.viewItem <|
MB.SecondaryButton
{ tagger = CancelDelete
, title = ""
, label = texts.basics.no
, icon = Just "fa fa-times"
}
]
]
)
, boxHeader texts model
, formHeader (texts.boxContent.forContent model.box.content)
, div [ class "mb-4 pl-2" ]
[ metaForm texts flags model
]
, formHeader texts.contentProperties
, div [ class "pl-4 pr-2 py-2 h-5/6" ]
[ boxContent texts flags settings model
]
]
formHeader : String -> Html msg
formHeader heading =
div
[ class "mx-2 border-b dark:border-slate-500 text-lg mt-1"
]
[ text heading
]
metaForm : Texts -> Flags -> Model -> Html Msg
metaForm texts _ model =
let
colspanCfg =
{ display = String.fromInt
, icon = \_ -> Nothing
, selectPlaceholder = ""
, style = DS.mainStyle
}
in
div [ class "my-1 px-2 " ]
[ div []
[ label [ class S.inputLabel ]
[ text texts.basics.name
]
, input
[ type_ "text"
, placeholder texts.namePlaceholder
, class S.textInput
, value model.box.name
, onInput SetName
]
[]
]
, div [ class "mt-1" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleVisible
, label = texts.visible
, value = model.box.visible
, id = ""
}
]
, div [ class "mt-1" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleDecoration
, label = texts.decorations
, value = model.box.decoration
, id = ""
}
]
, div [ class "mt-1" ]
[ label [ class S.inputLabel ]
[ text texts.colspan ]
, Html.map ColspanMsg
(Comp.FixedDropdown.viewStyled2
colspanCfg
False
(Just model.box.colspan)
model.colspanModel
)
]
]
boxHeader : Texts -> Model -> Html Msg
boxHeader texts model =
div
[ class "flex flex-row py-1 bg-blue-50 dark:bg-slate-700 rounded-t"
]
[ div [ class "flex flex-row items-center text-lg tracking-medium italic px-2" ]
[ i
[ class (Data.Box.boxIcon model.box)
, class "mr-2"
]
[]
, text model.box.name
]
, div [ class "flex flex-grow justify-end pr-1" ]
[ MB.viewItem <|
MB.CustomButton
{ tagger = MoveLeft
, title = texts.moveToLeft
, label = ""
, icon = Just "fa fa-arrow-left"
, inputClass =
[ ( S.secondaryBasicButton, True )
, ( "text-xs", True )
]
}
, MB.viewItem <|
MB.CustomButton
{ tagger = MoveRight
, title = texts.moveToRight
, label = ""
, icon = Just "fa fa-arrow-right"
, inputClass =
[ ( S.secondaryBasicButton, True )
, ( "text-xs mr-3", True )
]
}
, MB.viewItem <|
MB.CustomButton
{ tagger = RequestDelete
, title = texts.deleteBox
, label = ""
, icon = Just "fa fa-trash"
, inputClass =
[ ( S.deleteButton, True )
, ( "text-xs", True )
]
}
]
]
boxContent : Texts -> Flags -> UiSettings -> Model -> Html Msg
boxContent texts flags settings model =
case model.content of
ContentMessage m ->
Html.map MessageMsg
(Comp.BoxMessageEdit.view texts.messageEdit m)
ContentUpload m ->
Html.map UploadMsg
(Comp.BoxUploadEdit.view texts.uploadEdit m)
ContentQuery m ->
Html.map QueryMsg
(Comp.BoxQueryEdit.view texts.queryEdit settings m)
ContentStats m ->
Html.map StatsMsg
(Comp.BoxStatsEdit.view texts.statsEdit settings m)

View File

@ -0,0 +1,99 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxMessageEdit exposing (Model, Msg, init, update, view)
import Data.BoxContent exposing (MessageData)
import Html exposing (Html, div, input, label, text, textarea)
import Html.Attributes exposing (autocomplete, class, name, placeholder, type_, value)
import Html.Events exposing (onInput)
import Messages.Comp.BoxMessageEdit exposing (Texts)
import Styles as S
type alias Model =
{ data : MessageData
}
type Msg
= SetTitle String
| SetBody String
init : MessageData -> Model
init data =
{ data = data
}
--- Update
update : Msg -> Model -> ( Model, MessageData )
update msg model =
case msg of
SetTitle str ->
let
data =
model.data
data_ =
{ data | title = str }
in
( { model | data = data_ }, data_ )
SetBody str ->
let
data =
model.data
data_ =
{ data | body = str }
in
( { model | data = data_ }, data_ )
--- View
view : Texts -> Model -> Html Msg
view texts model =
div []
[ div []
[ label [ class S.inputLabel ]
[ text texts.titleLabel
]
, input
[ type_ "text"
, name "message-title"
, autocomplete False
, onInput SetTitle
, value model.data.title
, placeholder texts.titlePlaceholder
, class S.textInput
]
[]
]
, div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text texts.bodyLabel
]
, textarea
[ value model.data.body
, onInput SetBody
, class S.textAreaInput
, placeholder texts.bodyPlaceholder
]
[]
]
, div [ class "opacity-75 text-sm mt-1" ]
[ text texts.infoText
]
]

View File

@ -0,0 +1,197 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxQueryEdit exposing (..)
import Comp.BoxSearchQueryInput
import Comp.IntField
import Comp.ItemColumnDropdown
import Comp.MenuBar as MB
import Data.Bookmarks
import Data.BoxContent exposing (QueryData, SearchQuery(..))
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, label, text)
import Html.Attributes exposing (class)
import Messages.Comp.BoxQueryEdit exposing (Texts)
import Styles as S
type alias Model =
{ data : QueryData
, searchQueryModel : Comp.BoxSearchQueryInput.Model
, limitModel : Comp.IntField.Model
, limitValue : Maybe Int
, columnModel : Comp.ItemColumnDropdown.Model
}
type Msg
= SearchQueryMsg Comp.BoxSearchQueryInput.Msg
| LimitMsg Comp.IntField.Msg
| ColumnMsg Comp.ItemColumnDropdown.Msg
| ToggleColumnHeaders
init : Flags -> QueryData -> ( Model, Cmd Msg, Sub Msg )
init flags data =
let
( qm, qc, qs ) =
Comp.BoxSearchQueryInput.init flags data.query Data.Bookmarks.empty
emptyModel =
{ data = data
, searchQueryModel = qm
, limitModel = Comp.IntField.init (Just 1) Nothing False
, limitValue = Just data.limit
, columnModel = Comp.ItemColumnDropdown.init data.columns
}
in
( emptyModel, Cmd.map SearchQueryMsg qc, Sub.map SearchQueryMsg qs )
--- Update
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
, data : QueryData
}
update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
case msg of
SearchQueryMsg lm ->
let
result =
Comp.BoxSearchQueryInput.update flags lm model.searchQueryModel
setData data =
{ data | query = Maybe.withDefault data.query result.query }
nextModel =
withData setData { model | searchQueryModel = result.model }
in
{ model = nextModel
, cmd = Cmd.map SearchQueryMsg result.cmd
, sub = Sub.map SearchQueryMsg result.sub
, data = nextModel.data
}
LimitMsg lm ->
let
( im, n ) =
Comp.IntField.update lm model.limitModel
data =
model.data
data_ =
case n of
Just num ->
{ data | limit = num }
Nothing ->
data
in
{ model = { model | limitModel = im, limitValue = n, data = data_ }
, cmd = Cmd.none
, sub = Sub.none
, data = data_
}
ColumnMsg lm ->
let
( cm, cc ) =
Comp.ItemColumnDropdown.update lm model.columnModel
selection =
Comp.ItemColumnDropdown.getSelected cm
data =
model.data
data_ =
{ data | columns = selection }
in
{ model = { model | columnModel = cm, data = data_ }
, cmd = Cmd.map ColumnMsg cc
, sub = Sub.none
, data = data_
}
ToggleColumnHeaders ->
let
data =
model.data
data_ =
{ data | showHeaders = not data.showHeaders }
in
{ model = { model | data = data_ }
, cmd = Cmd.none
, sub = Sub.none
, data = data_
}
unit : Model -> UpdateResult
unit model =
{ model = model
, cmd = Cmd.none
, sub = Sub.none
, data = model.data
}
withData : (QueryData -> QueryData) -> Model -> Model
withData modify model =
{ model | data = modify model.data }
--- View
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
let
limitSettings =
{ label = "Limit"
, info = "Show this many results."
, number = model.limitValue
, classes = ""
}
in
div []
[ Html.map SearchQueryMsg
(Comp.BoxSearchQueryInput.view texts.searchQuery settings model.searchQueryModel)
, div [ class "mt-2" ]
[ Html.map LimitMsg
(Comp.IntField.view limitSettings model.limitModel)
]
, div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text "Columns"
]
, Html.map ColumnMsg
(Comp.ItemColumnDropdown.view texts.columnDropdown settings model.columnModel)
]
, div [ class "mt-2" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleColumnHeaders
, label = texts.showColumnHeaders
, value = model.data.showHeaders
, id = ""
}
]
]

View File

@ -0,0 +1,212 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxQueryView exposing (Model, Msg, init, reloadData, update, view)
import Api
import Api.Model.ItemLight exposing (ItemLight)
import Api.Model.ItemLightList exposing (ItemLightList)
import Api.Model.ItemQuery exposing (ItemQuery)
import Comp.Basic
import Comp.ItemColumnView
import Data.BoxContent exposing (QueryData, SearchQuery(..))
import Data.Flags exposing (Flags)
import Data.ItemColumn as IC exposing (ItemColumn)
import Data.Items
import Data.SearchMode
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, a, div, i, table, tbody, td, text, th, thead, tr)
import Html.Attributes exposing (class, classList)
import Http
import Messages.Comp.BoxQueryView exposing (Texts)
import Page exposing (Page(..))
import Styles
type alias Model =
{ results : ViewResult
, meta : QueryData
}
type ViewResult
= Loading
| Loaded ItemLightList
| Failed Http.Error
type Msg
= ItemsResp (Result Http.Error ItemLightList)
| ReloadData
init : Flags -> QueryData -> ( Model, Cmd Msg )
init flags data =
( { results = Loading
, meta = data
}
, dataCmd flags data
)
reloadData : Msg
reloadData =
ReloadData
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Bool )
update flags msg model =
case msg of
ItemsResp (Ok list) ->
( { model | results = Loaded list }, Cmd.none, False )
ItemsResp (Err err) ->
( { model | results = Failed err }, Cmd.none, False )
ReloadData ->
( model, dataCmd flags model.meta, True )
--- View
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
case model.results of
Loading ->
div [ class "h-24 " ]
[ Comp.Basic.loadingDimmer
{ label = ""
, active = True
}
]
Failed err ->
div
[ class "py-4"
, class Styles.errorMessage
]
[ text texts.errorOccurred
, text ": "
, text (texts.httpError err)
]
Loaded list ->
if list.groups == [] then
viewEmpty texts
else
viewItems texts settings model.meta list
viewItems : Texts -> UiSettings -> QueryData -> ItemLightList -> Html Msg
viewItems texts settings meta list =
let
items =
Data.Items.flatten list
in
table [ class "w-full divide-y divide-y-2 dark:divide-slate-500" ]
(viewItemHead texts meta ++ [ tbody [ class "divide-y divide-dotted dark:divide-slate-500" ] <| List.map (viewItemRow texts settings meta) items ])
viewItemHead : Texts -> QueryData -> List (Html Msg)
viewItemHead texts meta =
let
( col1, cols ) =
getColumns meta
in
if not meta.showHeaders then
[]
else
[ thead []
[ tr []
(List.map texts.itemColumn.header (col1 :: cols)
|> List.map (\n -> th [ class "text-left text-sm" ] [ text n ])
)
]
]
viewItemRow : Texts -> UiSettings -> QueryData -> ItemLight -> Html Msg
viewItemRow texts settings meta item =
let
( col1, cols ) =
getColumns meta
render col =
Comp.ItemColumnView.renderDiv texts.templateCtx settings col [ class "flex flex-row space-x-1" ] item
td1 =
td [ class "py-2 px-1" ]
[ a
[ class Styles.link
, Page.href (ItemDetailPage item.id)
]
[ render col1
]
]
tdRem index col =
td
[ class "py-2 px-1"
, classList [ ( "hidden md:table-cell", index > 1 ) ]
]
[ render col
]
in
tr []
(td1 :: List.indexedMap tdRem cols)
viewEmpty : Texts -> Html Msg
viewEmpty texts =
div [ class "flex justify-center items-center h-full" ]
[ div [ class "px-4 py-4 text-center align-middle text-lg" ]
[ i [ class "fa fa-smile font-thin mr-2" ] []
, text texts.noResults
]
]
--- Helpers
getColumns : QueryData -> ( ItemColumn, List ItemColumn )
getColumns meta =
case meta.columns of
x :: xs ->
( x, xs )
[] ->
( IC.Name, [ IC.Correspondent, IC.DateShort ] )
mkQuery : String -> QueryData -> ItemQuery
mkQuery q meta =
{ query = q
, limit = Just meta.limit
, offset = Nothing
, searchMode = Just <| Data.SearchMode.asString Data.SearchMode.Normal
, withDetails = Just meta.details
}
dataCmd : Flags -> QueryData -> Cmd Msg
dataCmd flags data =
case data.query of
SearchQueryString q ->
Api.itemSearch flags (mkQuery q data) ItemsResp
SearchQueryBookmark bmId ->
Api.itemSearchBookmark flags (mkQuery bmId data) ItemsResp

View File

@ -0,0 +1,299 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxSearchQueryInput exposing
( Model
, Msg
, UpdateResult
, init
, switchToBookmark
, switchToQuery
, toSearchQuery
, update
, view
)
import Api
import Comp.BookmarkDropdown
import Comp.PowerSearchInput
import Data.Bookmarks exposing (AllBookmarks)
import Data.BoxContent exposing (SearchQuery(..))
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, input, label, span, text)
import Html.Attributes exposing (checked, class, type_)
import Html.Events exposing (onCheck)
import Http
import Messages.Comp.BoxSearchQueryInput exposing (Texts)
import Styles as S
type alias Model =
{ queryModel : QueryModel
, allBookmarks : AllBookmarks
}
type QueryModel
= Search Comp.PowerSearchInput.Model
| Bookmark Comp.BookmarkDropdown.Model
toSearchQuery : Model -> Maybe SearchQuery
toSearchQuery model =
case model.queryModel of
Search pm ->
let
qstr =
Maybe.withDefault "" pm.input
in
if qstr == "" || Comp.PowerSearchInput.isValid pm then
Just (SearchQueryString qstr)
else
Nothing
Bookmark bm ->
Comp.BookmarkDropdown.getSelectedId bm
|> Maybe.map SearchQueryBookmark
type Msg
= GetBookmarksResp (Result Http.Error AllBookmarks)
| BookmarkMsg Comp.BookmarkDropdown.Msg
| PowerSearchMsg Comp.PowerSearchInput.Msg
| SwitchQueryBookmark
| SwitchQuerySearch
switchToBookmark : Msg
switchToBookmark =
SwitchQueryBookmark
switchToQuery : Msg
switchToQuery =
SwitchQuerySearch
init : Flags -> SearchQuery -> AllBookmarks -> ( Model, Cmd Msg, Sub Msg )
init flags query bookmarks =
let
emptyModel =
{ queryModel = Search Comp.PowerSearchInput.init
, allBookmarks = bookmarks
}
in
case query of
SearchQueryBookmark id ->
initQueryBookmark flags emptyModel (Just id)
SearchQueryString qstr ->
initQuerySearch emptyModel qstr
initQueryBookmark : Flags -> Model -> Maybe String -> ( Model, Cmd Msg, Sub Msg )
initQueryBookmark flags model bookmarkId =
( { model
| queryModel =
Bookmark
(Comp.BookmarkDropdown.initWith model.allBookmarks bookmarkId)
}
, if model.allBookmarks == Data.Bookmarks.empty then
Api.getBookmarks flags GetBookmarksResp
else
Cmd.none
, Sub.none
)
initQuerySearch : Model -> String -> ( Model, Cmd Msg, Sub Msg )
initQuerySearch model qstr =
let
( qm, qc, qs ) =
Comp.PowerSearchInput.initWith qstr
in
( { model | queryModel = Search qm }
, Cmd.map PowerSearchMsg qc
, Sub.map PowerSearchMsg qs
)
--- Update
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
, query : Maybe SearchQuery
}
update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
case msg of
GetBookmarksResp (Ok list) ->
let
bmId =
case model.queryModel of
Bookmark bm ->
Comp.BookmarkDropdown.getSelectedId bm
Search _ ->
Nothing
nm =
{ model | allBookmarks = list }
in
case model.queryModel of
Bookmark _ ->
update flags
SwitchQueryBookmark
{ nm
| queryModel =
Bookmark (Comp.BookmarkDropdown.initWith model.allBookmarks bmId)
}
Search _ ->
unit nm
GetBookmarksResp (Err _) ->
unit model
BookmarkMsg lm ->
case model.queryModel of
Bookmark m ->
let
( bm, bc ) =
Comp.BookmarkDropdown.update lm m
nextModel =
{ model | queryModel = Bookmark bm }
in
{ model = nextModel
, cmd = Cmd.map BookmarkMsg bc
, sub = Sub.none
, query = toSearchQuery nextModel
}
_ ->
unit model
PowerSearchMsg lm ->
case model.queryModel of
Search m ->
let
result =
Comp.PowerSearchInput.update lm m
nextModel =
{ model | queryModel = Search result.model }
in
{ model = nextModel
, cmd = Cmd.map PowerSearchMsg result.cmd
, sub = Sub.map PowerSearchMsg result.subs
, query = toSearchQuery nextModel
}
_ ->
unit model
SwitchQueryBookmark ->
let
selected =
case toSearchQuery model of
Just (SearchQueryBookmark id) ->
Just id
_ ->
Nothing
( m, c, s ) =
initQueryBookmark flags model selected
in
UpdateResult m c s (toSearchQuery m)
SwitchQuerySearch ->
let
qstr =
case toSearchQuery model of
Just (SearchQueryString q) ->
q
_ ->
""
( m, c, s ) =
initQuerySearch model qstr
in
UpdateResult m c s (toSearchQuery m)
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none Nothing
--- View
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
let
( isBookmark, isQuery ) =
case model.queryModel of
Bookmark _ ->
( True, False )
Search _ ->
( False, True )
searchSettings =
{ placeholder = texts.searchPlaceholder
, extraAttrs = []
}
in
div [ class "flex flex-col" ]
[ div [ class "flex flex-row space-x-4" ]
[ label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked isBookmark
, onCheck (\_ -> SwitchQueryBookmark)
, class S.radioInput
]
[]
, span [ class "ml-2" ] [ text texts.switchToBookmark ]
]
, label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked isQuery
, onCheck (\_ -> SwitchQuerySearch)
, class S.radioInput
]
[]
, span [ class "ml-2" ] [ text texts.switchToQuery ]
]
]
, case model.queryModel of
Bookmark m ->
Html.map BookmarkMsg
(Comp.BookmarkDropdown.view texts.bookmarkDropdown settings m)
Search m ->
div [ class "relative" ]
[ Html.map PowerSearchMsg
(Comp.PowerSearchInput.viewInput searchSettings m)
, Html.map PowerSearchMsg
(Comp.PowerSearchInput.viewResult [] m)
]
]

View File

@ -0,0 +1,202 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxStatsEdit exposing (..)
import Comp.BoxSearchQueryInput
import Comp.FixedDropdown
import Comp.MenuBar as MB
import Data.Bookmarks
import Data.BoxContent exposing (QueryData, SearchQuery(..), StatsData, SummaryShow(..))
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, label, span, text)
import Html.Attributes exposing (class)
import Messages.Comp.BoxStatsEdit exposing (Texts)
import Styles as S
type alias Model =
{ data : StatsData
, searchQueryModel : Comp.BoxSearchQueryInput.Model
, showModel : Comp.FixedDropdown.Model SummaryShowLabel
, summaryShow : SummaryShow
}
type Msg
= SearchQueryMsg Comp.BoxSearchQueryInput.Msg
| ShowMsg (Comp.FixedDropdown.Msg SummaryShowLabel)
| ToggleItemCountVisible
type SummaryShowLabel
= ShowFields
| ShowGeneric
init : Flags -> StatsData -> ( Model, Cmd Msg, Sub Msg )
init flags data =
let
( qm, qc, qs ) =
Comp.BoxSearchQueryInput.init flags data.query Data.Bookmarks.empty
emptyModel =
{ data = data
, searchQueryModel = qm
, showModel =
Comp.FixedDropdown.init
[ ShowFields, ShowGeneric ]
, summaryShow = data.show
}
in
( emptyModel, Cmd.map SearchQueryMsg qc, Sub.map SearchQueryMsg qs )
--- Update
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
, data : StatsData
}
update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
case msg of
SearchQueryMsg lm ->
let
result =
Comp.BoxSearchQueryInput.update flags lm model.searchQueryModel
setData data =
{ data | query = Maybe.withDefault data.query result.query }
nextModel =
withData setData { model | searchQueryModel = result.model }
in
{ model = nextModel
, cmd = Cmd.map SearchQueryMsg result.cmd
, sub = Sub.map SearchQueryMsg result.sub
, data = nextModel.data
}
ShowMsg lm ->
let
( mm, sel ) =
Comp.FixedDropdown.update lm model.showModel
nextShow =
case ( model.summaryShow, sel ) of
( SummaryShowFields _, Just ShowGeneric ) ->
SummaryShowGeneral
( SummaryShowGeneral, Just ShowFields ) ->
SummaryShowFields False
_ ->
model.summaryShow
data =
model.data
data_ =
{ data | show = nextShow }
in
unit { model | showModel = mm, summaryShow = nextShow, data = data_ }
ToggleItemCountVisible ->
let
nextShow =
case model.summaryShow of
SummaryShowFields flag ->
SummaryShowFields (not flag)
_ ->
model.summaryShow
data =
model.data
data_ =
{ data | show = nextShow }
in
unit { model | summaryShow = nextShow, data = data_ }
unit : Model -> UpdateResult
unit model =
{ model = model
, cmd = Cmd.none
, sub = Sub.none
, data = model.data
}
withData : (StatsData -> StatsData) -> Model -> Model
withData modify model =
{ model | data = modify model.data }
--- View
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
let
showSettings =
{ display =
\a ->
case a of
ShowFields ->
texts.fieldStatistics
ShowGeneric ->
texts.basicNumbers
, icon = \_ -> Nothing
, selectPlaceholder = ""
, style = DS.mainStyle
}
showLabel =
case model.summaryShow of
SummaryShowFields _ ->
ShowFields
SummaryShowGeneral ->
ShowGeneric
in
div []
[ Html.map SearchQueryMsg
(Comp.BoxSearchQueryInput.view texts.searchQuery settings model.searchQueryModel)
, div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text texts.showLabel ]
, Html.map ShowMsg
(Comp.FixedDropdown.viewStyled2 showSettings False (Just showLabel) model.showModel)
]
, div [ class "mt-2" ]
[ case model.summaryShow of
SummaryShowGeneral ->
span [ class "hidden" ] []
SummaryShowFields itemCountVisible ->
MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleItemCountVisible
, label = texts.showItemCount
, id = ""
, value = itemCountVisible
}
]
]

View File

@ -0,0 +1,186 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxStatsView exposing (Model, Msg, init, reloadData, update, view)
import Api
import Api.Model.ItemQuery exposing (ItemQuery)
import Api.Model.SearchStats exposing (SearchStats)
import Comp.Basic
import Comp.SearchStatsView
import Data.BoxContent exposing (SearchQuery(..), StatsData, SummaryShow(..))
import Data.Flags exposing (Flags)
import Html exposing (Html, div, text)
import Html.Attributes exposing (class)
import Http
import Messages.Comp.BoxStatsView exposing (Texts)
import Styles
import Util.List
type alias Model =
{ results : ViewResult
, meta : StatsData
}
type ViewResult
= Loading
| Loaded SearchStats
| Failed Http.Error
type Msg
= StatsResp (Result Http.Error SearchStats)
| ReloadData
init : Flags -> StatsData -> ( Model, Cmd Msg )
init flags data =
( { results = Loading
, meta = data
}
, dataCmd flags data
)
reloadData : Msg
reloadData =
ReloadData
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Bool )
update flags msg model =
case msg of
StatsResp (Ok stats) ->
( { model | results = Loaded stats }, Cmd.none, False )
StatsResp (Err err) ->
( { model | results = Failed err }, Cmd.none, False )
ReloadData ->
( model, dataCmd flags model.meta, True )
--- View
view : Texts -> Model -> Html Msg
view texts model =
case model.results of
Loading ->
div [ class "h-24 " ]
[ Comp.Basic.loadingDimmer
{ label = ""
, active = True
}
]
Failed err ->
div
[ class "py-4"
, class Styles.errorMessage
]
[ text texts.errorOccurred
, text ": "
, text (texts.httpError err)
]
Loaded stats ->
viewStats texts model stats
viewStats : Texts -> Model -> SearchStats -> Html Msg
viewStats texts model stats =
case model.meta.show of
SummaryShowFields flag ->
Comp.SearchStatsView.view2
texts.statsView
flag
""
stats
SummaryShowGeneral ->
viewGeneral texts stats
viewGeneral : Texts -> SearchStats -> Html Msg
viewGeneral texts stats =
let
tagCount =
List.length stats.tagCloud.items
fieldCount =
List.length stats.fieldStats
orgCount =
List.length stats.corrOrgStats
persCount =
(stats.corrPersStats ++ stats.concPersStats)
|> List.map (.ref >> .id)
|> Util.List.distinct
|> List.length
equipCount =
List.length stats.concEquipStats
mklabel name =
div [ class "py-0.5 text-lg" ] [ text name ]
value num =
div [ class "py-0.5 font-mono text-lg" ] [ text <| String.fromInt num ]
in
div [ class "opacity-90" ]
[ div [ class "flex flex-row" ]
[ div [ class "flex flex-col mr-4" ]
[ mklabel texts.basics.items
, mklabel texts.basics.tags
, mklabel texts.basics.customFields
, mklabel texts.basics.organization
, mklabel texts.basics.person
, mklabel texts.basics.equipment
]
, div [ class "flex flex-col" ]
[ value stats.count
, value tagCount
, value fieldCount
, value orgCount
, value persCount
, value equipCount
]
]
]
--- Helpers
mkQuery : String -> ItemQuery
mkQuery query =
{ query = query
, limit = Nothing
, offset = Nothing
, searchMode = Nothing
, withDetails = Nothing
}
dataCmd : Flags -> StatsData -> Cmd Msg
dataCmd flags data =
case data.query of
SearchQueryString q ->
Api.itemSearchStats flags (mkQuery q) StatsResp
SearchQueryBookmark bmId ->
Api.itemSearchStatsBookmark flags (mkQuery bmId) StatsResp

View File

@ -0,0 +1,113 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxUploadEdit exposing (..)
import Api
import Api.Model.Source exposing (Source)
import Api.Model.SourceList exposing (SourceList)
import Comp.BoxUploadView exposing (Msg)
import Comp.FixedDropdown
import Data.BoxContent exposing (UploadData)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Html exposing (Html, div, label, text)
import Html.Attributes exposing (class)
import Http
import Messages.Comp.BoxUploadEdit exposing (Texts)
import Styles as S
type alias Model =
{ data : UploadData
, allSources : List Source
, sourceModel : Comp.FixedDropdown.Model Source
}
type Msg
= GetSourcesResp (Result Http.Error SourceList)
| SourceMsg (Comp.FixedDropdown.Msg Source)
init : Flags -> UploadData -> ( Model, Cmd Msg )
init flags data =
( { data = data
, allSources = []
, sourceModel = Comp.FixedDropdown.init []
}
, Api.getSources flags GetSourcesResp
)
--- Update
update : Msg -> Model -> ( Model, UploadData )
update msg model =
case msg of
GetSourcesResp (Ok list) ->
let
all =
List.map .source list.items
|> List.filter .enabled
dm =
Comp.FixedDropdown.init all
in
( { model | allSources = all, sourceModel = dm }
, model.data
)
GetSourcesResp (Err _) ->
( model, model.data )
SourceMsg lm ->
let
( dm, sel ) =
Comp.FixedDropdown.update lm model.sourceModel
ud =
model.data
ud_ =
{ ud | sourceId = Maybe.map .id sel }
in
( { model | sourceModel = dm, data = ud_ }, ud_ )
--- View
view : Texts -> Model -> Html Msg
view texts model =
let
cfg =
{ display = \s -> s.abbrev
, icon = \_ -> Nothing
, selectPlaceholder = texts.sourcePlaceholder
, style = DS.mainStyle
}
selected =
List.filter (\e -> Just e.id == model.data.sourceId) model.allSources
|> List.head
in
div []
[ div []
[ label [ class S.inputLabel ]
[ text texts.sourceLabel
]
, Html.map SourceMsg
(Comp.FixedDropdown.viewStyled2 cfg False selected model.sourceModel)
]
, div [ class "mt-1 opacity-75 text-sm" ]
[ text texts.infoText
]
]

View File

@ -0,0 +1,70 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxUploadView exposing (..)
import Comp.UploadForm
import Data.BoxContent exposing (UploadData)
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div)
import Html.Attributes exposing (class)
import Messages.Comp.BoxUploadView exposing (Texts)
type alias Model =
{ uploadForm : Comp.UploadForm.Model
, sourceId : Maybe String
}
type Msg
= UploadMsg Comp.UploadForm.Msg
init : UploadData -> Model
init data =
{ uploadForm = Comp.UploadForm.init
, sourceId = data.sourceId
}
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model =
case msg of
UploadMsg lm ->
let
( um, uc, us ) =
Comp.UploadForm.update model.sourceId flags lm model.uploadForm
in
( { model | uploadForm = um }
, Cmd.map UploadMsg uc
, Sub.map UploadMsg us
)
--- View
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
view texts flags settings model =
let
viewCfg =
{ sourceId = model.sourceId
, showForm = False
, lightForm = True
}
in
div [ class "" ]
[ Html.map UploadMsg
(Comp.UploadForm.view texts.uploadForm viewCfg flags settings model.uploadForm)
]

View File

@ -0,0 +1,247 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.BoxView exposing (..)
import Comp.BoxQueryView
import Comp.BoxStatsView
import Comp.BoxUploadView
import Data.Box exposing (Box)
import Data.BoxContent exposing (BoxContent(..), MessageData)
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, i, text)
import Html.Attributes exposing (class, classList)
import Markdown
import Messages.Comp.BoxView exposing (Texts)
import Styles as S
type alias Model =
{ box : Box
, content : ContentModel
, reloading : Bool
}
type ContentModel
= ContentMessage Data.BoxContent.MessageData
| ContentUpload Comp.BoxUploadView.Model
| ContentQuery Comp.BoxQueryView.Model
| ContentStats Comp.BoxStatsView.Model
type Msg
= QueryMsg Comp.BoxQueryView.Msg
| StatsMsg Comp.BoxStatsView.Msg
| UploadMsg Comp.BoxUploadView.Msg
| ReloadData
init : Flags -> Box -> ( Model, Cmd Msg )
init flags box =
let
( cm, cc ) =
contentInit flags box.content
in
( { box = box
, content = cm
, reloading = False
}
, cc
)
reloadData : Msg
reloadData =
ReloadData
contentInit : Flags -> BoxContent -> ( ContentModel, Cmd Msg )
contentInit flags content =
case content of
BoxMessage data ->
( ContentMessage data, Cmd.none )
BoxUpload data ->
let
qm =
Comp.BoxUploadView.init data
in
( ContentUpload qm, Cmd.none )
BoxQuery data ->
let
( qm, qc ) =
Comp.BoxQueryView.init flags data
in
( ContentQuery qm, Cmd.map QueryMsg qc )
BoxStats data ->
let
( sm, sc ) =
Comp.BoxStatsView.init flags data
in
( ContentStats sm, Cmd.map StatsMsg sc )
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model =
case msg of
QueryMsg lm ->
case model.content of
ContentQuery qm ->
let
( cm, cc, reloading ) =
Comp.BoxQueryView.update flags lm qm
in
( { model | content = ContentQuery cm, reloading = reloading }
, Cmd.map QueryMsg cc
, Sub.none
)
_ ->
unit model
StatsMsg lm ->
case model.content of
ContentStats qm ->
let
( cm, cc, reloading ) =
Comp.BoxStatsView.update flags lm qm
in
( { model | content = ContentStats cm, reloading = reloading }
, Cmd.map StatsMsg cc
, Sub.none
)
_ ->
unit model
UploadMsg lm ->
case model.content of
ContentUpload qm ->
let
( cm, cc, cs ) =
Comp.BoxUploadView.update flags lm qm
in
( { model | content = ContentUpload cm }
, Cmd.map UploadMsg cc
, Sub.map UploadMsg cs
)
_ ->
unit model
ReloadData ->
case model.content of
ContentQuery _ ->
update flags (QueryMsg Comp.BoxQueryView.reloadData) model
ContentStats _ ->
update flags (StatsMsg Comp.BoxStatsView.reloadData) model
_ ->
unit model
unit : Model -> ( Model, Cmd Msg, Sub Msg )
unit model =
( model, Cmd.none, Sub.none )
--- View
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
view texts flags settings model =
div
[ classList [ ( S.box ++ "rounded", model.box.decoration ) ]
, class (spanStyle model.box)
, class "relative h-full"
, classList [ ( "hidden", not model.box.visible ) ]
]
[ boxLoading model
, boxHeader model
, div [ class "px-2 py-1 h-5/6" ]
[ boxContent texts flags settings model
]
]
boxLoading : Model -> Html Msg
boxLoading model =
if not model.reloading then
div [ class "hidden" ] []
else
div [ class "absolute right-0 top-1 h-6 w-6" ]
[ i [ class "fa fa-spinner animate-spin" ] []
]
boxHeader : Model -> Html Msg
boxHeader model =
div
[ class "flex flex-row py-1 bg-blue-50 dark:bg-slate-700 rounded-t"
, classList [ ( "hidden", not model.box.decoration || model.box.name == "" ) ]
]
[ div [ class "flex text-lg tracking-medium italic px-2" ]
[ text model.box.name
]
]
boxContent : Texts -> Flags -> UiSettings -> Model -> Html Msg
boxContent texts flags settings model =
case model.content of
ContentMessage m ->
messageContent m
ContentUpload qm ->
Html.map UploadMsg
(Comp.BoxUploadView.view texts.uploadView flags settings qm)
ContentQuery qm ->
Html.map QueryMsg
(Comp.BoxQueryView.view texts.queryView settings qm)
ContentStats qm ->
Html.map StatsMsg
(Comp.BoxStatsView.view texts.statsView qm)
spanStyle : Box -> String
spanStyle box =
case box.colspan of
1 ->
""
2 ->
"col-span-1 md:col-span-2"
3 ->
"col-span-1 md:col-span-3"
4 ->
"col-span-1 md:col-span-4"
_ ->
"col-span-1 md:col-span-5"
messageContent : MessageData -> Html msg
messageContent data =
div [ class "markdown-preview" ]
[ Markdown.toHtml [] data.title
, Markdown.toHtml [] data.body
]

View File

@ -0,0 +1,512 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.DashboardEdit exposing (Model, Msg, getBoard, init, update, view, viewBox)
import Comp.Basic as B
import Comp.BoxEdit
import Comp.FixedDropdown
import Comp.MenuBar as MB
import Data.AccountScope exposing (AccountScope)
import Data.Box exposing (Box)
import Data.Dashboard exposing (Dashboard)
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Dict exposing (Dict)
import Html exposing (Html, div, i, input, label, span, text)
import Html.Attributes exposing (checked, class, classList, href, placeholder, type_, value)
import Html.Events exposing (onCheck, onClick, onInput)
import Html5.DragDrop as DD
import Messages.Comp.DashboardEdit exposing (Texts)
import Styles as S
import Util.Maybe
type alias Model =
{ dashboard : Dashboard
, boxModels : Dict Int Comp.BoxEdit.Model
, nameValue : String
, columnsModel : Comp.FixedDropdown.Model Int
, columnsValue : Maybe Int
, gapModel : Comp.FixedDropdown.Model Int
, gapValue : Maybe Int
, defaultDashboard : Bool
, scope : AccountScope
, newBoxMenuOpen : Bool
, boxDragDrop : DD.Model Int Int
}
type Msg
= BoxMsg Int Comp.BoxEdit.Msg
| SetName String
| ColumnsMsg (Comp.FixedDropdown.Msg Int)
| GapMsg (Comp.FixedDropdown.Msg Int)
| ToggleNewBoxMenu
| SetScope AccountScope
| ToggleDefault
| PrependNew Box
| DragDropMsg (DD.Msg Int Int)
init : Flags -> Dashboard -> AccountScope -> Bool -> ( Model, Cmd Msg, Sub Msg )
init flags db scope default =
let
( boxModels, cmdsAndSubs ) =
List.map (Comp.BoxEdit.init flags) db.boxes
|> List.indexedMap
(\a ->
\( bm, bc, bs ) ->
( bm, ( Cmd.map (BoxMsg a) bc, Sub.map (BoxMsg a) bs ) )
)
|> List.unzip
( cmds, subs ) =
List.unzip cmdsAndSubs
in
( { dashboard = db
, nameValue = db.name
, columnsModel = Comp.FixedDropdown.init [ 1, 2, 3, 4, 5 ]
, columnsValue = Just db.columns
, gapModel = Comp.FixedDropdown.init (List.range 0 12)
, gapValue = Just db.gap
, defaultDashboard = default
, scope = scope
, newBoxMenuOpen = False
, boxModels =
List.indexedMap Tuple.pair boxModels
|> Dict.fromList
, boxDragDrop = DD.init
}
, Cmd.batch cmds
, Sub.batch subs
)
getBoard : Model -> ( Dashboard, AccountScope, Bool )
getBoard model =
( model.dashboard, model.scope, model.defaultDashboard )
--- Update
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
}
update : Flags -> Msg -> Model -> UpdateResult
update flags msg model =
case msg of
BoxMsg index lm ->
case Dict.get index model.boxModels of
Just bm ->
let
result =
Comp.BoxEdit.update flags lm bm
newBoxes =
applyBoxAction index result.action <|
Dict.insert index result.model model.boxModels
db =
model.dashboard
db_ =
{ db | boxes = List.map .box (Dict.values newBoxes) }
in
{ model = { model | boxModels = newBoxes, dashboard = db_ }
, cmd = Cmd.map (BoxMsg index) result.cmd
, sub = Sub.map (BoxMsg index) result.sub
}
Nothing ->
unit model
SetName str ->
let
db =
model.dashboard
db_ =
{ db | name = String.trim str }
in
unit { model | dashboard = db_, nameValue = str }
ColumnsMsg lm ->
let
( cm, value ) =
Comp.FixedDropdown.update lm model.columnsModel
db =
model.dashboard
db_ =
{ db | columns = Maybe.withDefault db.columns value }
in
unit
{ model
| columnsValue = Util.Maybe.or [ value, model.columnsValue ]
, columnsModel = cm
, dashboard = db_
}
GapMsg lm ->
let
( gm, value ) =
Comp.FixedDropdown.update lm model.gapModel
db =
model.dashboard
db_ =
{ db | gap = Maybe.withDefault db.gap value }
in
unit
{ model
| gapModel = gm
, gapValue = Util.Maybe.or [ value, model.gapValue ]
, dashboard = db_
}
ToggleNewBoxMenu ->
unit { model | newBoxMenuOpen = not model.newBoxMenuOpen }
PrependNew box ->
let
min =
Dict.keys model.boxModels
|> List.minimum
|> Maybe.withDefault 1
index =
min - 1
db =
model.dashboard
db_ =
{ db | boxes = box :: db.boxes }
( bm, bc, bs ) =
Comp.BoxEdit.init flags box
newBoxes =
Dict.insert index bm model.boxModels
in
{ model = { model | boxModels = newBoxes, dashboard = db_, newBoxMenuOpen = False }
, cmd = Cmd.map (BoxMsg index) bc
, sub = Sub.map (BoxMsg index) bs
}
DragDropMsg lm ->
let
( dm, dropped ) =
DD.update lm model.boxDragDrop
m_ =
{ model | boxDragDrop = dm }
nextModel =
case dropped of
Just ( dragId, dropId, _ ) ->
applyDrop dragId dropId m_
Nothing ->
m_
in
unit nextModel
SetScope s ->
unit { model | scope = s }
ToggleDefault ->
unit { model | defaultDashboard = not model.defaultDashboard }
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none
applyBoxAction :
Int
-> Comp.BoxEdit.BoxAction
-> Dict Int Comp.BoxEdit.Model
-> Dict Int Comp.BoxEdit.Model
applyBoxAction index action boxes =
let
swap n1 n2 =
Maybe.map2
(\e1 -> \e2 -> Dict.insert n2 e1 boxes |> Dict.insert n1 e2)
(Dict.get n1 boxes)
(Dict.get n2 boxes)
|> Maybe.withDefault boxes
in
case action of
Comp.BoxEdit.BoxNoAction ->
boxes
Comp.BoxEdit.BoxDelete ->
Dict.remove index boxes
Comp.BoxEdit.BoxMoveLeft ->
swap (index - 1) index
Comp.BoxEdit.BoxMoveRight ->
swap index (index + 1)
applyDrop : Int -> Int -> Model -> Model
applyDrop dragId dropId model =
let
dragEl =
Dict.get dragId model.boxModels
in
if dragId == dropId then
model
else
case dragEl of
Just box ->
let
withoutDragged =
Dict.remove dragId model.boxModels
( begin, end ) =
Dict.partition (\k -> \_ -> k < dropId) withoutDragged
incKeys =
Dict.toList end
|> List.map (\( k, v ) -> ( k + 1, v ))
|> Dict.fromList
newBoxes =
Dict.insert dropId box (Dict.union begin incKeys)
db =
model.dashboard
db_ =
{ db | boxes = List.map .box (Dict.values newBoxes) }
in
{ model | boxModels = newBoxes, dashboard = db_ }
Nothing ->
model
--- View
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
view texts flags settings model =
let
boxMenuItem box =
{ icon = i [ class (Data.Box.boxIcon box) ] []
, label = texts.boxContent.forContent box.content
, disabled = False
, attrs =
[ href "#"
, onClick (PrependNew box)
]
}
in
div []
[ viewMain texts flags settings model
, div [ class S.formHeader ]
[ text texts.dashboardBoxes
]
, MB.view
{ start = []
, end =
[ MB.Dropdown
{ linkIcon = "fa fa-plus"
, label = texts.newBox
, linkClass =
[ ( S.secondaryBasicButton, True )
]
, toggleMenu = ToggleNewBoxMenu
, menuOpen = model.newBoxMenuOpen
, items =
[ boxMenuItem Data.Box.queryBox
, boxMenuItem Data.Box.statsBox
, boxMenuItem Data.Box.messageBox
, boxMenuItem Data.Box.uploadBox
]
}
]
, rootClasses = "mb-2"
}
, div
[ class (gridStyle model.dashboard)
]
(List.map
(viewBox texts flags settings model)
(Dict.toList model.boxModels)
)
]
viewBox : Texts -> Flags -> UiSettings -> Model -> ( Int, Comp.BoxEdit.Model ) -> Html Msg
viewBox texts flags settings model ( index, box ) =
let
dropId =
DD.getDropId model.boxDragDrop
dragId =
DD.getDragId model.boxDragDrop
styles =
[ classList [ ( "opacity-40", dropId == Just index && dropId /= dragId ) ]
, class (spanStyle box.box)
]
in
div
(DD.draggable DragDropMsg index ++ DD.droppable DragDropMsg index ++ styles)
[ Html.map (BoxMsg index)
(Comp.BoxEdit.view texts.boxView flags settings box)
]
viewMain : Texts -> Flags -> UiSettings -> Model -> Html Msg
viewMain texts _ _ model =
let
columnsSettings =
{ display = String.fromInt
, icon = \_ -> Nothing
, selectPlaceholder = ""
, style = DS.mainStyle
}
in
div [ class "my-2 " ]
[ div [ class "flex flex-col" ]
[ div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text texts.basics.name
, B.inputRequired
]
, input
[ type_ "text"
, placeholder texts.namePlaceholder
, class S.textInput
, classList [ ( S.inputErrorBorder, String.trim model.nameValue == "" ) ]
, value model.nameValue
, onInput SetName
]
[]
]
, div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text texts.columns
]
, Html.map ColumnsMsg
(Comp.FixedDropdown.viewStyled2 columnsSettings
False
model.columnsValue
model.columnsModel
)
]
, div [ class "mt-2" ]
[ label [ class S.inputLabel ]
[ text texts.gap
]
, Html.map GapMsg
(Comp.FixedDropdown.viewStyled2 columnsSettings
False
model.gapValue
model.gapModel
)
]
, div [ class "mt-2" ]
[ div [ class "flex flex-row space-x-4" ]
[ label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked (Data.AccountScope.isUser model.scope)
, onCheck (\_ -> SetScope Data.AccountScope.User)
, class S.radioInput
]
[]
, span [ class "ml-2" ] [ text <| texts.accountScope Data.AccountScope.User ]
]
, label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked (Data.AccountScope.isCollective model.scope)
, onCheck (\_ -> SetScope Data.AccountScope.Collective)
, class S.radioInput
]
[]
, span [ class "ml-2" ]
[ text <| texts.accountScope Data.AccountScope.Collective ]
]
]
]
, div [ class "mt-2" ]
[ MB.viewItem <|
MB.Checkbox
{ tagger = \_ -> ToggleDefault
, label = texts.defaultDashboard
, id = ""
, value = model.defaultDashboard
}
]
]
]
--- Helpers
gridStyle : Dashboard -> String
gridStyle db =
let
colStyle =
case db.columns of
1 ->
""
2 ->
"md:grid-cols-2"
3 ->
"md:grid-cols-3"
4 ->
"md:grid-cols-4"
_ ->
"md:grid-cols-5"
in
"grid gap-4 grid-cols-1 " ++ colStyle
spanStyle : Box -> String
spanStyle box =
case box.colspan of
1 ->
""
2 ->
"col-span-1 md:col-span-2"
3 ->
"col-span-1 md:col-span-3"
4 ->
"col-span-1 md:col-span-4"
_ ->
"col-span-1 md:col-span-5"

View File

@ -0,0 +1,319 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.DashboardManage exposing (Model, Msg, SubmitAction(..), UpdateResult, init, update, view)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Comp.Basic as B
import Comp.DashboardEdit
import Comp.MenuBar as MB
import Data.AccountScope exposing (AccountScope)
import Data.Dashboard exposing (Dashboard)
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html, div, i, text)
import Html.Attributes exposing (class, classList)
import Http
import Messages.Comp.DashboardManage exposing (Texts)
import Styles as S
type alias Model =
{ edit : Comp.DashboardEdit.Model
, initData : InitData
, deleteRequested : Bool
, formError : Maybe FormError
}
type Msg
= SaveDashboard
| Cancel
| DeleteDashboard
| SetRequestDelete Bool
| EditMsg Comp.DashboardEdit.Msg
| DeleteResp (Result Http.Error BasicResult)
| SaveResp String (Result Http.Error BasicResult)
| CreateNew
| CopyCurrent
type FormError
= FormInvalid String
| FormHttpError Http.Error
| FormNameEmpty
| FormNameExists
type alias InitData =
{ flags : Flags
, dashboard : Dashboard
, scope : AccountScope
, isDefault : Bool
}
init : InitData -> ( Model, Cmd Msg, Sub Msg )
init data =
let
( em, ec, es ) =
Comp.DashboardEdit.init data.flags data.dashboard data.scope data.isDefault
model =
{ edit = em
, initData = data
, deleteRequested = False
, formError = Nothing
}
in
( model, Cmd.map EditMsg ec, Sub.map EditMsg es )
--- Update
type SubmitAction
= SubmitNone
| SubmitCancel String
| SubmitSaved String
| SubmitDeleted
type alias UpdateResult =
{ model : Model
, cmd : Cmd Msg
, sub : Sub Msg
, action : SubmitAction
}
update : Flags -> (String -> Bool) -> Msg -> Model -> UpdateResult
update flags nameExists msg model =
case msg of
EditMsg lm ->
let
result =
Comp.DashboardEdit.update flags lm model.edit
in
{ model = { model | edit = result.model }
, cmd = Cmd.map EditMsg result.cmd
, sub = Sub.map EditMsg result.sub
, action = SubmitNone
}
CreateNew ->
let
initData =
{ flags = flags
, dashboard = Data.Dashboard.empty
, scope = Data.AccountScope.User
, isDefault = False
}
( m, c, s ) =
init initData
in
UpdateResult m c s SubmitNone
CopyCurrent ->
let
( current, scope, isDefault ) =
Comp.DashboardEdit.getBoard model.edit
initData =
{ flags = flags
, dashboard = { current | name = "" }
, scope = scope
, isDefault = isDefault
}
( m, c, s ) =
init initData
in
UpdateResult m c s SubmitNone
SetRequestDelete flag ->
unit { model | deleteRequested = flag }
SaveDashboard ->
let
( tosave, scope, isDefault ) =
Comp.DashboardEdit.getBoard model.edit
saveCmd =
Api.replaceDashboard flags
model.initData.dashboard.name
tosave
scope
isDefault
(SaveResp tosave.name)
in
if tosave.name == "" then
unit { model | formError = Just FormNameEmpty }
else if tosave.name /= model.initData.dashboard.name && nameExists tosave.name then
unit { model | formError = Just FormNameExists }
else
UpdateResult model saveCmd Sub.none SubmitNone
Cancel ->
unitAction model (SubmitCancel model.initData.dashboard.name)
DeleteDashboard ->
let
deleteCmd =
Api.deleteDashboard flags model.initData.dashboard.name model.initData.scope DeleteResp
in
UpdateResult model deleteCmd Sub.none SubmitNone
SaveResp name (Ok result) ->
if result.success then
unitAction model (SubmitSaved name)
else
unit { model | formError = Just (FormInvalid result.message) }
SaveResp _ (Err err) ->
unit { model | formError = Just (FormHttpError err) }
DeleteResp (Ok result) ->
if result.success then
unitAction model SubmitDeleted
else
unit { model | formError = Just (FormInvalid result.message) }
DeleteResp (Err err) ->
unit { model | formError = Just (FormHttpError err) }
unit : Model -> UpdateResult
unit model =
UpdateResult model Cmd.none Sub.none SubmitNone
unitAction : Model -> SubmitAction -> UpdateResult
unitAction model action =
UpdateResult model Cmd.none Sub.none action
--- View
type alias ViewSettings =
{ showDeleteButton : Bool
, showCopyButton : Bool
}
view : Texts -> Flags -> ViewSettings -> UiSettings -> Model -> Html Msg
view texts flags cfg settings model =
div []
[ B.contentDimmer model.deleteRequested
(div [ class "flex flex-col" ]
[ div [ class "text-xl" ]
[ i [ class "fa fa-info-circle mr-2" ] []
, text texts.reallyDeleteDashboard
]
, div [ class "mt-4 flex flex-row items-center space-x-2" ]
[ MB.viewItem <|
MB.DeleteButton
{ tagger = DeleteDashboard
, title = ""
, label = texts.basics.yes
, icon = Just "fa fa-check"
}
, MB.viewItem <|
MB.SecondaryButton
{ tagger = SetRequestDelete False
, title = ""
, label = texts.basics.no
, icon = Just "fa fa-times"
}
]
]
)
, MB.view
{ start =
[ MB.PrimaryButton
{ tagger = SaveDashboard
, title = texts.basics.submitThisForm
, icon = Just "fa fa-save"
, label = texts.basics.submit
}
, MB.SecondaryButton
{ tagger = Cancel
, title = texts.basics.cancel
, icon = Just "fa fa-times"
, label = texts.basics.cancel
}
]
, end =
[ MB.BasicButton
{ tagger = CreateNew
, title = texts.createDashboard
, icon = Just "fa fa-plus"
, label = texts.createDashboard
}
, MB.CustomButton
{ tagger = CopyCurrent
, title = texts.copyDashboard
, icon = Just "fa fa-copy"
, label = texts.copyDashboard
, inputClass =
[ ( S.secondaryBasicButton, True )
, ( "hidden", not cfg.showCopyButton )
]
}
, MB.CustomButton
{ tagger = SetRequestDelete True
, title = texts.basics.delete
, icon = Just "fa fa-times"
, label = texts.basics.delete
, inputClass =
[ ( S.deleteButton, True )
, ( "hidden", not cfg.showDeleteButton )
]
}
]
, rootClasses = ""
}
, div
[ class S.errorMessage
, class "mt-2"
, classList [ ( "hidden", model.formError == Nothing ) ]
]
[ errorMessage texts model
]
, div []
[ Html.map EditMsg
(Comp.DashboardEdit.view texts.dashboardEdit flags settings model.edit)
]
]
errorMessage : Texts -> Model -> Html Msg
errorMessage texts model =
case model.formError of
Just (FormInvalid errMsg) ->
text errMsg
Just (FormHttpError err) ->
text (texts.httpError err)
Just FormNameEmpty ->
text texts.nameEmpty
Just FormNameExists ->
text texts.nameExists
Nothing ->
text ""

View File

@ -0,0 +1,137 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.DashboardView exposing (Model, Msg, init, reloadData, update, view, viewBox)
import Comp.BoxView
import Data.Dashboard exposing (Dashboard)
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Dict exposing (Dict)
import Html exposing (Html, div)
import Html.Attributes exposing (class)
import Messages.Comp.DashboardView exposing (Texts)
import Util.Update
type alias Model =
{ dashboard : Dashboard
, boxModels : Dict Int Comp.BoxView.Model
}
type Msg
= BoxMsg Int Comp.BoxView.Msg
| ReloadData
init : Flags -> Dashboard -> ( Model, Cmd Msg )
init flags db =
let
( boxModels, cmds ) =
List.map (Comp.BoxView.init flags) db.boxes
|> List.indexedMap (\a -> \( bm, bc ) -> ( bm, Cmd.map (BoxMsg a) bc ))
|> List.unzip
in
( { dashboard = db
, boxModels =
List.indexedMap Tuple.pair boxModels
|> Dict.fromList
}
, Cmd.batch cmds
)
reloadData : Msg
reloadData =
ReloadData
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update flags msg model =
case msg of
BoxMsg index lm ->
case Dict.get index model.boxModels of
Just bm ->
let
( cm, cc, cs ) =
Comp.BoxView.update flags lm bm
in
( { model | boxModels = Dict.insert index cm model.boxModels }
, Cmd.map (BoxMsg index) cc
, Sub.map (BoxMsg index) cs
)
Nothing ->
unit model
ReloadData ->
let
updateAll =
List.map (\index -> BoxMsg index Comp.BoxView.reloadData) (Dict.keys model.boxModels)
|> List.map (\m -> update flags m)
|> Util.Update.andThen2
in
updateAll model
unit : Model -> ( Model, Cmd Msg, Sub Msg )
unit model =
( model, Cmd.none, Sub.none )
--- View
view : Texts -> Flags -> UiSettings -> Model -> Html Msg
view texts flags settings model =
div
[ class (gridStyle model.dashboard)
]
(List.indexedMap (viewBox texts flags settings) <| Dict.values model.boxModels)
viewBox : Texts -> Flags -> UiSettings -> Int -> Comp.BoxView.Model -> Html Msg
viewBox texts flags settings index box =
Html.map (BoxMsg index)
(Comp.BoxView.view texts.boxView flags settings box)
--- Helpers
{-| note due to tailwinds purging css that is not found in source
files, need to spell them out somewhere - which is done it keep.txt in
this case.
-}
gridStyle : Dashboard -> String
gridStyle db =
let
cappedGap =
min db.gap 12
cappedCol =
min db.columns 12
gapStyle =
" gap-" ++ String.fromInt cappedGap ++ " "
colStyle =
case db.columns of
1 ->
""
_ ->
" md:grid-cols-" ++ String.fromInt cappedCol ++ " "
in
"grid grid-cols-1 " ++ gapStyle ++ colStyle

View File

@ -712,10 +712,10 @@ formHeading texts classes model =
(\_ -> texts.addCustomFieldHeader)
headIcon =
fold (\_ -> Icons.tagIcon2 "mr-2")
(\_ -> Icons.personIcon2 "mr-2")
(\_ -> Icons.organizationIcon2 "mr-2")
(\_ -> Icons.equipmentIcon2 "mt-2")
fold (\_ -> Icons.tagIcon "mr-2")
(\_ -> Icons.personIcon "mr-2")
(\_ -> Icons.organizationIcon "mr-2")
(\_ -> Icons.equipmentIcon "mt-2")
(\_ -> Icons.customFieldIcon2 "mr-2")
in
div [ class classes ]
@ -738,10 +738,10 @@ viewModal2 texts settings mm =
(\_ -> texts.addCustomFieldHeader)
headIcon =
fold (\_ -> Icons.tagIcon2 "mr-2")
(\_ -> Icons.personIcon2 "mr-2")
(\_ -> Icons.organizationIcon2 "mr-2")
(\_ -> Icons.equipmentIcon2 "mt-2")
fold (\_ -> Icons.tagIcon "mr-2")
(\_ -> Icons.personIcon "mr-2")
(\_ -> Icons.organizationIcon "mr-2")
(\_ -> Icons.equipmentIcon "mt-2")
(\_ -> Icons.customFieldIcon2 "mr-2")
in
div

View File

@ -132,15 +132,21 @@ filterMime model files =
--- View2
view2 : Texts -> Model -> Html Msg
view2 texts model =
type alias ViewSettings =
{ light : Bool
}
view2 : Texts -> ViewSettings -> Model -> Html Msg
view2 texts cfg model =
div
[ classList
[ ( "bg-opacity-100 bg-blue-100 dark:bg-sky-800", model.state.hover )
, ( "bg-blue-100 dark:bg-sky-900 bg-opacity-50", not model.state.hover )
, ( "bg-indigo-100 dark:bg-sky-900 bg-opacity-50", not model.state.hover )
, ( "disabled", not model.state.active )
]
, class "flex flex-col justify-center items-center py-2 md:py-12 border-0 border-t-2 border-blue-500 dark:border-sky-500 dropzone"
, class "flex flex-col justify-center items-center py-2 md:py-12 dropzone"
, classList [ ( " border-0 border-t-2 border-blue-500 dark:border-sky-500", not cfg.light ) ]
, onDragEnter DragEnter
, onDragOver DragEnter
, onDragLeave DragLeave
@ -168,7 +174,10 @@ view2 texts model =
, attrs = [ href "#" ]
, disabled = not model.state.active
}
, div [ class "text-center opacity-75 text-sm mt-4" ]
, div
[ class "text-center opacity-75 text-sm mt-4"
, classList [ ( "hidden", cfg.light ) ]
]
[ text texts.selectInfo
]
]

View File

@ -9,6 +9,7 @@ module Comp.EquipmentManage exposing
( Model
, Msg(..)
, emptyModel
, init
, update
, view2
)
@ -70,6 +71,11 @@ emptyModel =
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel, Api.getEquipments flags emptyModel.query emptyModel.order EquipmentResp )
type Msg
= TableMsg Comp.EquipmentTable.Msg
| FormMsg Comp.EquipmentForm.Msg

View File

@ -418,7 +418,7 @@ viewRow texts cfg settings flags model item =
, class "hover:opacity-60"
, title texts.basics.folder
]
[ Icons.folderIcon2 "mr-2"
[ Icons.folderIcon "mr-2"
, Comp.LinkTarget.makeFolderLink item
[ ( "hover:opacity-60", True ) ]
SetLinkTarget
@ -592,7 +592,7 @@ metaDataContent2 texts settings item =
, class "hover:opacity-60"
, title texts.basics.folder
]
[ Icons.folderIcon2 "mr-2"
[ Icons.folderIcon "mr-2"
, Comp.LinkTarget.makeFolderLink item
[ ( "hover:opacity-60", True ) ]
SetLinkTarget

View File

@ -0,0 +1,86 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.ItemColumnDropdown exposing (Model, Msg, getSelected, init, update, view)
import Comp.Dropdown exposing (Option)
import Data.DropdownStyle
import Data.ItemColumn exposing (ItemColumn(..))
import Data.UiSettings exposing (UiSettings)
import Html exposing (Html)
import Messages.Comp.ItemColumnDropdown exposing (Texts)
type Model
= Model (Comp.Dropdown.Model ItemColumn)
type Msg
= DropdownMsg (Comp.Dropdown.Msg ItemColumn)
init : List ItemColumn -> Model
init selected =
Model <|
Comp.Dropdown.makeMultipleList
{ options = Data.ItemColumn.all, selected = selected }
getSelected : Model -> List ItemColumn
getSelected (Model dm) =
Comp.Dropdown.getSelected dm
--- Update
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
let
dmodel =
case model of
Model a ->
a
in
case msg of
DropdownMsg lm ->
let
( dm, dc ) =
Comp.Dropdown.update lm dmodel
in
( Model dm, Cmd.map DropdownMsg dc )
--- View
itemOption : Texts -> ItemColumn -> Option
itemOption texts item =
{ text = texts.column.label item
, additional = ""
}
view : Texts -> UiSettings -> Model -> Html Msg
view texts settings model =
let
viewSettings =
{ makeOption = itemOption texts
, placeholder = texts.placeholder
, labelColor = \_ -> \_ -> ""
, style = Data.DropdownStyle.mainStyle
}
dm =
case model of
Model a ->
a
in
Html.map DropdownMsg
(Comp.Dropdown.view2 viewSettings settings dm)

View File

@ -0,0 +1,43 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.ItemColumnView exposing (..)
import Api.Model.ItemLight exposing (ItemLight)
import Data.ItemColumn exposing (ItemColumn(..))
import Data.ItemTemplate exposing (TemplateContext)
import Data.UiSettings exposing (UiSettings)
import Html exposing (Attribute, Html, div, text)
import Html.Attributes exposing (class)
renderDiv :
TemplateContext
-> UiSettings
-> ItemColumn
-> List (Attribute msg)
-> ItemLight
-> Html msg
renderDiv ctx settings col attr item =
case col of
Tags ->
div attr
(List.map
(\t ->
div
[ class "label text-sm"
, class <| Data.UiSettings.tagColorString2 t settings
]
[ text t.name ]
)
item.tags
)
_ ->
div attr
[ text (Data.ItemColumn.renderString ctx col item)
]

View File

@ -24,6 +24,11 @@ import Util.Size
view : Texts -> Model -> Html Msg
view texts model =
let
dropzoneCfg =
{ light = True
}
in
div
[ classList
[ ( "hidden", not model.addFilesOpen )
@ -35,7 +40,7 @@ view texts model =
[ text texts.addMoreFilesToItem
]
, Html.map AddFilesMsg
(Comp.Dropzone.view2 texts.dropzone model.addFilesModel)
(Comp.Dropzone.view2 texts.dropzone dropzoneCfg model.addFilesModel)
, div [ class "flex flex-row space-x-2 mt-2" ]
[ button
[ class S.primaryButton

View File

@ -298,7 +298,7 @@ formTabs texts flags settings model =
[ optional [ Data.Fields.CorrOrg ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.organizationIcon2 "mr-2"
[ Icons.organizationIcon "mr-2"
, text texts.basics.organization
, addIconLink texts.addNewOrg StartCorrOrgModal
, editIconLink texts.editOrg model.corrOrgModel StartEditCorrOrgModal
@ -314,7 +314,7 @@ formTabs texts flags settings model =
, optional [ Data.Fields.CorrPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 "mr-2"
[ Icons.personIcon "mr-2"
, text texts.basics.person
, addIconLink texts.addNewCorrespondentPerson StartCorrPersonModal
, editIconLink texts.editPerson
@ -348,7 +348,7 @@ formTabs texts flags settings model =
[ optional [ Data.Fields.ConcPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 "mr-2"
[ Icons.personIcon "mr-2"
, text texts.basics.person
, addIconLink texts.addNewConcerningPerson StartConcPersonModal
, editIconLink texts.editPerson
@ -366,7 +366,7 @@ formTabs texts flags settings model =
, optional [ Data.Fields.ConcEquip ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.equipmentIcon2 "mr-2"
[ Icons.equipmentIcon "mr-2"
, text texts.basics.equipment
, addIconLink texts.addNewEquipment StartEquipModal
, editIconLink texts.editEquipment

View File

@ -98,7 +98,7 @@ view texts settings model =
[ class itemStyle
, title texts.basics.folder
]
[ Icons.folderIcon2 "mr-2"
[ Icons.folderIcon "mr-2"
, Comp.LinkTarget.makeFolderLink model.item
[ ( linkStyle, True ) ]
SetLinkTarget

View File

@ -735,7 +735,7 @@ renderEditForm2 texts flags cfg settings model =
, body =
[ div [ class "field" ]
[ label [ class S.inputLabel ]
[ Icons.tagsIcon2 ""
[ Icons.tagsIcon ""
, text texts.basics.tags
, a
[ class "float-right"
@ -841,7 +841,7 @@ renderEditForm2 texts flags cfg settings model =
[ optional [ Data.Fields.CorrOrg ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.organizationIcon2 ""
[ Icons.organizationIcon ""
, span [ class "ml-2" ]
[ text texts.basics.organization
]
@ -856,7 +856,7 @@ renderEditForm2 texts flags cfg settings model =
, optional [ Data.Fields.CorrPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 ""
[ Icons.personIcon ""
, span [ class "ml-2" ]
[ text texts.basics.person
]
@ -878,7 +878,7 @@ renderEditForm2 texts flags cfg settings model =
[ optional [ Data.Fields.ConcPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 ""
[ Icons.personIcon ""
, span [ class "ml-2" ]
[ text texts.basics.person ]
]
@ -887,7 +887,7 @@ renderEditForm2 texts flags cfg settings model =
, optional [ Data.Fields.ConcEquip ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.equipmentIcon2 ""
[ Icons.equipmentIcon ""
, span [ class "ml-2" ]
[ text texts.basics.equipment ]
]

View File

@ -42,10 +42,7 @@ import Comp.ItemDetail.Model
, UpdateResult
, ViewMode(..)
, initSelectViewModel
, initShowQrModel
, isEditNotes
, isShowQrAttach
, isShowQrItem
, resultModel
, resultModelCmd
, resultModelCmdSub
@ -741,7 +738,7 @@ update key flags inav settings msg model =
resultModelCmd ( model, Page.set key (ItemDetailPage id) )
Nothing ->
resultModelCmd ( model, Page.set key HomePage )
resultModelCmd ( model, Page.set key (SearchPage Nothing) )
in
{ result_ | removedItem = Just removedId }

View File

@ -80,7 +80,7 @@ menuBar texts inav settings model =
[ MB.CustomElement <|
a
[ class S.secondaryBasicButton
, Page.href HomePage
, Page.href (SearchPage Nothing)
, title texts.backToSearchResults
]
[ i [ class "fa fa-arrow-left" ] []

View File

@ -35,6 +35,7 @@ type LinkTarget
| LinkTag IdName
| LinkCustomField ItemFieldValue
| LinkSource String
| LinkBookmark String
| LinkNone

View File

@ -308,18 +308,27 @@ makeButton btnType model =
makeCheckbox : CheckboxData msg -> Html msg
makeCheckbox model =
let
withId list =
if model.id == "" then
list
else
id model.id :: list
in
div [ class "" ]
[ label
[ class "inline-flex space-x-2 items-center"
, for model.id
]
[ input
[ type_ "checkbox"
, onCheck model.tagger
, checked model.value
, class S.checkboxInput
, id model.id
]
(withId
[ type_ "checkbox"
, onCheck model.tagger
, checked model.value
, class S.checkboxInput
]
)
[]
, span [ class "truncate" ]
[ text model.label

View File

@ -9,6 +9,7 @@ module Comp.OrgManage exposing
( Model
, Msg(..)
, emptyModel
, init
, update
, view2
)
@ -71,6 +72,11 @@ emptyModel =
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel, Api.getOrganizations flags emptyModel.query emptyModel.order OrgResp )
type Msg
= TableMsg Comp.OrgTable.Msg
| FormMsg Comp.OrgForm.Msg

View File

@ -9,6 +9,7 @@ module Comp.PersonManage exposing
( Model
, Msg(..)
, emptyModel
, init
, update
, view2
)
@ -72,6 +73,11 @@ emptyModel =
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel, Api.getPersons flags emptyModel.query emptyModel.order PersonResp )
type Msg
= TableMsg Comp.PersonTable.Msg
| FormMsg Comp.PersonForm.Msg

View File

@ -11,6 +11,7 @@ module Comp.PowerSearchInput exposing
, Msg
, ViewSettings
, init
, initWith
, isValid
, setSearchString
, update
@ -45,6 +46,15 @@ init =
}
initWith : String -> ( Model, Cmd Msg, Sub Msg )
initWith qstr =
let
result =
update (setSearchString qstr) init
in
( result.model, result.cmd, result.subs )
isValid : Model -> Bool
isValid model =
model.input /= Nothing && model.result.success

View File

@ -59,7 +59,7 @@ import Data.UiSettings exposing (UiSettings)
import DatePicker exposing (DatePicker)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Html.Events exposing (onInput)
import Http
import Messages.Comp.SearchMenu exposing (Texts)
import Set exposing (Set)
@ -385,6 +385,7 @@ type Msg
| SetConcEquip IdName
| SetFolder IdName
| SetTag String
| SetBookmark String
| SetCustomField ItemFieldValue
| CustomFieldMsg Comp.CustomFieldMultiInput.Msg
| SetSource String
@ -432,6 +433,9 @@ linkTargetMsg linkTarget =
Comp.LinkTarget.LinkSource str ->
Just <| ResetToSource str
Comp.LinkTarget.LinkBookmark id ->
Just <| SetBookmark id
type alias NextState =
{ model : Model
@ -556,6 +560,22 @@ updateDrop ddm flags settings msg model =
SetTag id ->
resetAndSet (TagSelectMsg (Comp.TagSelect.toggleTag id))
SetBookmark id ->
let
nextModel =
resetModel model
sel =
{ bookmarks = Set.singleton id
, shares = Set.empty
}
in
{ model = { nextModel | selectedBookmarks = sel }
, cmd = Cmd.none
, stateChange = sel /= model.selectedBookmarks
, dragDrop = DD.DragDropData ddm Nothing
}
GetAllTagsResp (Ok stats) ->
let
tagSel =
@ -1064,7 +1084,7 @@ updateDrop ddm flags settings msg model =
AllBookmarksResp (Ok bm) ->
{ model = { model | allBookmarks = Comp.BookmarkChooser.init bm }
, cmd = Cmd.none
, stateChange = False
, stateChange = model.allBookmarks /= Comp.BookmarkChooser.init bm
, dragDrop = DD.DragDropData ddm Nothing
}
@ -1082,7 +1102,7 @@ updateDrop ddm flags settings msg model =
in
{ model = { model | allBookmarks = next, selectedBookmarks = sel }
, cmd = Cmd.none
, stateChange = sel /= model.selectedBookmarks
, stateChange = sel /= model.selectedBookmarks || model.allBookmarks /= next
, dragDrop = DD.DragDropData ddm Nothing
}

View File

@ -8,6 +8,7 @@
module Comp.SearchStatsView exposing
( nameOrLabel
, sortFields
, view
, view2
)
@ -36,8 +37,13 @@ sortFields fields =
--- View2
view2 : Texts -> String -> SearchStats -> Html msg
view2 texts classes stats =
view : Texts -> String -> SearchStats -> Html msg
view texts classes stats =
view2 texts True classes stats
view2 : Texts -> Bool -> String -> SearchStats -> Html msg
view2 texts showCount classes stats =
let
isNumField f =
f.sum > 0
@ -78,7 +84,10 @@ view2 texts classes stats =
in
div [ class classes ]
[ div [ class "flex flex-col md:flex-row" ]
[ div [ class "px-8 py-4" ]
[ div
[ class "px-8 py-4"
, classList [ ( "hidden", not showCount ) ]
]
[ B.stats
{ rootClass = ""
, valueClass = "text-4xl"

View File

@ -85,6 +85,7 @@ init flags =
, Cmd.batch
[ Cmd.map FormMsg fc
, Cmd.map MailMsg mc
, Api.getShares flags "" True LoadSharesResp
]
)

View File

@ -69,6 +69,7 @@ init flags =
[ Cmd.map FormMsg fc
, Ports.initClipboard appClipboardData
, Ports.initClipboard apiClipboardData
, Api.getSources flags SourceResp
]
)

View File

@ -9,6 +9,7 @@ module Comp.TagManage exposing
( Model
, Msg(..)
, emptyModel
, init
, update
, view2
)
@ -73,6 +74,11 @@ emptyModel =
}
init : Flags -> ( Model, Cmd Msg )
init flags =
( emptyModel, Api.getTags flags emptyModel.query emptyModel.order (TagResp emptyModel.query) )
type Msg
= TableMsg Comp.TagTable.Msg
| FormMsg Comp.TagForm.Msg

View File

@ -546,7 +546,7 @@ viewTagItem2 ddm settings model tag =
Data.UiSettings.tagColorFg2 tag.tag settings
icon =
getIcon2 state color I.tagIcon2
getIcon2 state color I.tagIcon
dropActive =
DD.getDropId ddm == Just (DD.Tag tag.tag.id)
@ -587,7 +587,7 @@ viewCategoryItem2 settings model cat =
Data.UiSettings.catColorFg2 settings cat.name
icon =
getIcon2 state color I.tagsIcon2
getIcon2 state color I.tagsIcon
in
a
[ class "flex flex-row items-center"

View File

@ -0,0 +1,572 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.UploadForm exposing (Model, Msg, init, reset, update, view)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.ItemUploadMeta
import Comp.Dropzone
import Comp.FixedDropdown
import Comp.Progress
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Data.Language exposing (Language)
import Data.UiSettings exposing (UiSettings)
import Dict exposing (Dict)
import File exposing (File)
import Html exposing (Html, a, div, h2, h3, i, input, label, p, span, text)
import Html.Attributes exposing (action, checked, class, classList, href, id, type_)
import Html.Events exposing (onCheck, onClick)
import Http
import Messages.Comp.UploadForm exposing (Texts)
import Page exposing (Page(..))
import Set exposing (Set)
import Styles
import Util.File exposing (makeFileId)
import Util.Maybe
import Util.Size
type alias Model =
{ incoming : Bool
, singleItem : Bool
, files : List File
, completed : Set String
, errored : Set String
, loading : Dict String Int
, dropzone : Comp.Dropzone.Model
, skipDuplicates : Bool
, languageModel : Comp.FixedDropdown.Model Language
, language : Maybe Language
}
type Msg
= SubmitUpload
| SingleUploadResp String (Result Http.Error BasicResult)
| GotProgress String Http.Progress
| ToggleIncoming
| ToggleSingleItem
| Clear
| DropzoneMsg Comp.Dropzone.Msg
| ToggleSkipDuplicates
| LanguageMsg (Comp.FixedDropdown.Msg Language)
init : Model
init =
{ incoming = True
, singleItem = False
, files = []
, completed = Set.empty
, errored = Set.empty
, loading = Dict.empty
, dropzone = Comp.Dropzone.init []
, skipDuplicates = True
, languageModel =
Comp.FixedDropdown.init Data.Language.all
, language = Nothing
}
reset : Msg
reset =
Clear
isLoading : Model -> File -> Bool
isLoading model file =
Dict.member (makeFileId file) model.loading
isCompleted : Model -> File -> Bool
isCompleted model file =
Set.member (makeFileId file) model.completed
isError : Model -> File -> Bool
isError model file =
Set.member (makeFileId file) model.errored
isIdle : Model -> File -> Bool
isIdle model file =
not (isLoading model file || isCompleted model file || isError model file)
uploadAllTracker : String
uploadAllTracker =
"upload-all"
isDone : Model -> Bool
isDone model =
List.map makeFileId model.files
|> List.all (\id -> Set.member id model.completed || Set.member id model.errored)
isSuccessAll : Model -> Bool
isSuccessAll model =
List.map makeFileId model.files
|> List.all (\id -> Set.member id model.completed)
hasErrors : Model -> Bool
hasErrors model =
not (Set.isEmpty model.errored)
--- Update
update : Maybe String -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update sourceId flags msg model =
case msg of
ToggleIncoming ->
( { model | incoming = not model.incoming }, Cmd.none, Sub.none )
ToggleSingleItem ->
( { model | singleItem = not model.singleItem }, Cmd.none, Sub.none )
ToggleSkipDuplicates ->
( { model | skipDuplicates = not model.skipDuplicates }, Cmd.none, Sub.none )
SubmitUpload ->
let
emptyMeta =
Api.Model.ItemUploadMeta.empty
meta =
{ emptyMeta
| multiple = not model.singleItem
, skipDuplicates = Just model.skipDuplicates
, direction =
if model.incoming then
Just "incoming"
else
Just "outgoing"
, language = Maybe.map Data.Language.toIso3 model.language
}
fileids =
List.map makeFileId model.files
uploads =
if model.singleItem then
Api.uploadSingle flags
sourceId
meta
uploadAllTracker
model.files
(SingleUploadResp uploadAllTracker)
else
Cmd.batch (Api.upload flags sourceId meta model.files SingleUploadResp)
tracker =
if model.singleItem then
Http.track uploadAllTracker (GotProgress uploadAllTracker)
else
Sub.batch <| List.map (\id -> Http.track id (GotProgress id)) fileids
( cm2, _, _ ) =
Comp.Dropzone.update (Comp.Dropzone.setActive False) model.dropzone
nowLoading =
List.map (\fid -> ( fid, 0 )) fileids
|> Dict.fromList
in
( { model | loading = nowLoading, dropzone = cm2 }, uploads, tracker )
SingleUploadResp fileid (Ok res) ->
let
compl =
if res.success then
setCompleted model fileid
else
model.completed
errs =
if not res.success then
setErrored model fileid
else
model.errored
load =
if fileid == uploadAllTracker then
Dict.empty
else
Dict.remove fileid model.loading
in
( { model | completed = compl, errored = errs, loading = load }
, Cmd.none
, Sub.none
)
SingleUploadResp fileid (Err _) ->
let
errs =
setErrored model fileid
load =
if fileid == uploadAllTracker then
Dict.empty
else
Dict.remove fileid model.loading
in
( { model | errored = errs, loading = load }, Cmd.none, Sub.none )
GotProgress fileid progress ->
let
percent =
case progress of
Http.Sending p ->
Http.fractionSent p
|> (*) 100
|> round
_ ->
0
newLoading =
if model.singleItem then
Dict.insert uploadAllTracker percent model.loading
else
Dict.insert fileid percent model.loading
in
( { model | loading = newLoading }
, Cmd.none
, Sub.none
)
Clear ->
( init, Cmd.none, Sub.none )
DropzoneMsg m ->
let
( m2, c2, files ) =
Comp.Dropzone.update m model.dropzone
nextFiles =
List.append model.files files
in
( { model | files = nextFiles, dropzone = m2 }, Cmd.map DropzoneMsg c2, Sub.none )
LanguageMsg lm ->
let
( dm, sel ) =
Comp.FixedDropdown.update lm model.languageModel
in
( { model
| languageModel = dm
, language = Util.Maybe.or [ sel, model.language ]
}
, Cmd.none
, Sub.none
)
setCompleted : Model -> String -> Set String
setCompleted model fileid =
if fileid == uploadAllTracker then
List.map makeFileId model.files |> Set.fromList
else
Set.insert fileid model.completed
setErrored : Model -> String -> Set String
setErrored model fileid =
if fileid == uploadAllTracker then
List.map makeFileId model.files |> Set.fromList
else
Set.insert fileid model.errored
--- View
type alias ViewSettings =
{ showForm : Bool
, sourceId : Maybe String
, lightForm : Bool
}
view : Texts -> ViewSettings -> Flags -> UiSettings -> Model -> Html Msg
view texts viewCfg _ _ model =
let
showForm =
viewCfg.sourceId == Nothing && viewCfg.showForm
dropzoneCfg =
{ light = viewCfg.lightForm
}
in
div [ class "mx-auto" ]
[ div [ class "px-0 flex flex-col" ]
[ if showForm then
div [ class "mb-4" ]
[ renderForm texts model
]
else
span [ class "hidden" ] []
, div [ class "py-0" ]
[ Html.map DropzoneMsg
(Comp.Dropzone.view2 texts.dropzone dropzoneCfg model.dropzone)
]
, div [ class "py-4" ]
[ a
[ class Styles.primaryButton
, href "#"
, onClick SubmitUpload
]
[ text texts.basics.submit
]
, a
[ class Styles.secondaryButton
, class "ml-2"
, href "#"
, onClick Clear
]
[ text texts.reset
]
]
]
, renderErrorMsg texts model
, renderSuccessMsg texts (Util.Maybe.nonEmpty viewCfg.sourceId) model
, renderUploads texts model
]
renderForm : Texts -> Model -> Html Msg
renderForm texts model =
let
languageCfg =
{ display = texts.languageLabel
, icon = \_ -> Nothing
, style = DS.mainStyleWith "w-40"
, selectPlaceholder = texts.basics.selectPlaceholder
}
in
div [ class "row" ]
[ Html.form [ action "#" ]
[ div [ class "flex flex-col mb-3" ]
[ label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked model.incoming
, onCheck (\_ -> ToggleIncoming)
, class Styles.radioInput
]
[]
, span [ class "ml-2" ] [ text texts.basics.incoming ]
]
, label [ class "inline-flex items-center" ]
[ input
[ type_ "radio"
, checked (not model.incoming)
, onCheck (\_ -> ToggleIncoming)
, class Styles.radioInput
]
[]
, span [ class "ml-2" ] [ text texts.basics.outgoing ]
]
]
, div [ class "flex flex-col mb-3" ]
[ label [ class "inline-flex items-center" ]
[ input
[ type_ "checkbox"
, checked model.singleItem
, onCheck (\_ -> ToggleSingleItem)
, class Styles.checkboxInput
]
[]
, span [ class "ml-2" ]
[ text texts.allFilesOneItem
]
]
]
, div [ class "flex flex-col mb-3" ]
[ label [ class "inline-flex items-center" ]
[ input
[ type_ "checkbox"
, checked model.skipDuplicates
, onCheck (\_ -> ToggleSkipDuplicates)
, class Styles.checkboxInput
]
[]
, span [ class "ml-2" ]
[ text texts.skipExistingFiles
]
]
]
, div [ class "flex flex-col mb-3" ]
[ label [ class "inline-flex items-center mb-2" ]
[ span [ class "mr-2" ] [ text (texts.language ++ ":") ]
, Html.map LanguageMsg
(Comp.FixedDropdown.viewStyled2
languageCfg
False
model.language
model.languageModel
)
]
, div [ class "text-gray-400 text-xs" ]
[ text texts.languageInfo
]
]
]
]
renderErrorMsg : Texts -> Model -> Html Msg
renderErrorMsg texts model =
div
[ class "row"
, classList [ ( "hidden", not (isDone model && hasErrors model) ) ]
]
[ div [ class "mt-4" ]
[ div [ class Styles.errorMessage ]
[ text texts.uploadErrorMessage
]
]
]
renderSuccessMsg : Texts -> Bool -> Model -> Html Msg
renderSuccessMsg texts public model =
div
[ class "row"
, classList [ ( "hidden", List.isEmpty model.files || not (isSuccessAll model) ) ]
]
[ div [ class "mt-4" ]
[ div [ class Styles.successMessage ]
[ h3 [ class Styles.header2, class "text-green-800 dark:text-lime-800" ]
[ i [ class "fa fa-smile font-thin" ] []
, span [ class "ml-2" ]
[ text texts.successBox.allFilesUploaded
]
]
, p
[ classList [ ( "hidden", public ) ]
]
[ text texts.successBox.line1
, a
[ class Styles.successMessageLink
, Page.href (SearchPage Nothing)
]
[ text texts.successBox.itemsPage
]
, text texts.successBox.line2
, a
[ class Styles.successMessageLink
, Page.href QueuePage
]
[ text texts.successBox.processingPage
]
, text texts.successBox.line3
]
, p []
[ text texts.successBox.resetLine1
, a
[ class Styles.successMessageLink
, href "#"
, onClick Clear
]
[ text texts.successBox.reset
]
, text texts.successBox.resetLine2
]
]
]
]
renderUploads : Texts -> Model -> Html Msg
renderUploads texts model =
div
[ class "mt-4"
, classList [ ( "hidden", List.isEmpty model.files || isSuccessAll model ) ]
]
[ h2 [ class Styles.header2 ]
[ text texts.selectedFiles
, text (" (" ++ (List.length model.files |> String.fromInt) ++ ")")
]
, div [] <|
if model.singleItem then
List.map (renderFileItem model (Just uploadAllTracker)) model.files
else
List.map (renderFileItem model Nothing) model.files
]
getProgress : Model -> File -> Int
getProgress model file =
let
key =
if model.singleItem then
uploadAllTracker
else
makeFileId file
in
Dict.get key model.loading
|> Maybe.withDefault 0
renderFileItem : Model -> Maybe String -> File -> Html Msg
renderFileItem model _ file =
let
name =
File.name file
size =
File.size file
|> toFloat
|> Util.Size.bytesReadable Util.Size.B
in
div [ class "flex flex-col w-full mb-4" ]
[ div [ class "flex flex-row items-center" ]
[ div [ class "inline-flex items-center" ]
[ i
[ classList
[ ( "mr-2 text-lg", True )
, ( "fa fa-file font-thin", isIdle model file )
, ( "fa fa-spinner animate-spin ", isLoading model file )
, ( "fa fa-check ", isCompleted model file )
, ( "fa fa-bolt", isError model file )
]
]
[]
, div [ class "middle aligned content" ]
[ div [ class "header" ]
[ text name
]
]
]
, div [ class "flex-grow inline-flex justify-end" ]
[ text size
]
]
, div [ class "h-4" ]
[ Comp.Progress.progress2 (getProgress model file)
]
]

View File

@ -0,0 +1,33 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
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

@ -11,6 +11,7 @@ module Data.Bookmarks exposing
, bookmarksDecoder
, empty
, exists
, findById
, sort
)
@ -34,6 +35,12 @@ type alias Bookmarks =
List BookmarkedQuery
findById : String -> Bookmarks -> Maybe BookmarkedQuery
findById id all =
List.filter (\e -> e.id == id) all
|> List.head
{-| Checks wether a bookmark of this name already exists.
-}
exists : String -> Bookmarks -> Bool

View File

@ -0,0 +1,81 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
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 =
{ name : String
, visible : Bool
, decoration : Bool
, colspan : Int
, content : BoxContent
}
empty : BoxContent -> Box
empty cnt =
{ name = ""
, visible = True
, decoration = True
, colspan = 1
, content = cnt
}
boxIcon : Box -> String
boxIcon box =
Data.BoxContent.boxContentIcon box.content
queryBox : Box
queryBox =
empty (BoxQuery Data.BoxContent.emptyQueryData)
statsBox : Box
statsBox =
empty (BoxStats Data.BoxContent.emptyStatsData)
messageBox : Box
messageBox =
empty (BoxMessage Data.BoxContent.emptyMessageData)
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

@ -0,0 +1,319 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Data.BoxContent exposing
( BoxContent(..)
, MessageData
, QueryData
, SearchQuery(..)
, StatsData
, SummaryShow(..)
, UploadData
, boxContentDecoder
, boxContentEncode
, boxContentIcon
, emptyMessageData
, emptyQueryData
, emptyStatsData
, emptyUploadData
)
import Data.ItemColumn exposing (ItemColumn)
import Html exposing (datalist)
import Json.Decode as D
import Json.Encode as E
type BoxContent
= BoxUpload UploadData
| BoxMessage MessageData
| BoxQuery QueryData
| BoxStats StatsData
type alias MessageData =
{ title : String
, body : String
}
emptyMessageData : MessageData
emptyMessageData =
{ title = ""
, body = ""
}
type alias UploadData =
{ sourceId : Maybe String
}
emptyUploadData : UploadData
emptyUploadData =
{ sourceId = Nothing
}
type alias QueryData =
{ query : SearchQuery
, limit : Int
, details : Bool
, columns : List ItemColumn
, showHeaders : Bool
}
emptyQueryData : QueryData
emptyQueryData =
{ query = SearchQueryString ""
, limit = 5
, details = True
, columns = []
, showHeaders = True
}
type alias StatsData =
{ query : SearchQuery
, show : SummaryShow
}
emptyStatsData : StatsData
emptyStatsData =
{ query = SearchQueryString ""
, show = SummaryShowGeneral
}
type SummaryShow
= SummaryShowFields Bool
| SummaryShowGeneral
type SearchQuery
= SearchQueryString String
| 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
BoxMessage _ ->
"fa fa-comment-alt font-thin"
BoxUpload _ ->
"fa fa-file-upload"
BoxQuery _ ->
"fa fa-search"
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

@ -0,0 +1,57 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
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,296 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
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

@ -25,6 +25,7 @@ module Data.Icons exposing
, customFieldTypeIcon
, customFieldTypeIconString
, customFieldTypeIconString2
, dashboardIcon
, date
, date2
, dateIcon
@ -33,31 +34,30 @@ module Data.Icons exposing
, direction2
, directionIcon
, directionIcon2
, documentationIcon
, dueDate
, dueDate2
, dueDateIcon
, dueDateIcon2
, editNotes
, editNotesIcon
, editIcon
, equipment
, equipment2
, equipmentIcon
, equipmentIcon2
, fileUploadIcon
, folder
, folder2
, folderIcon
, folderIcon2
, gotifyIcon
, itemDatesIcon
, matrixIcon
, metadata
, metadataIcon
, notificationHooks
, notificationHooksIcon
, organization
, organization2
, organizationIcon
, organizationIcon2
, periodicTasks
, periodicTasksIcon
, person
, person2
, personIcon
, personIcon2
, search
, searchIcon
, share
@ -67,11 +67,9 @@ module Data.Icons exposing
, source2
, sourceIcon2
, tag
, tag2
, tagIcon
, tagIcon2
, tags2
, tagsIcon2
, tags
, tagsIcon
, trash
, trashIcon
)
@ -83,6 +81,56 @@ import Svg
import Svg.Attributes as SA
documentation : String
documentation =
"fa fa-question-circle"
documentationIcon : String -> Html msg
documentationIcon classes =
i [ class classes, class documentation ] []
dashboard : String
dashboard =
"fa fa-house-user"
dashboardIcon : String -> Html msg
dashboardIcon classes =
i [ class classes, class dashboard ] []
periodicTasks : String
periodicTasks =
"fa fa-history"
periodicTasksIcon : String -> Html msg
periodicTasksIcon classes =
i [ class classes, class periodicTasks ] []
notificationHooks : String
notificationHooks =
"fa fa-comment font-thin"
notificationHooksIcon : String -> Html msg
notificationHooksIcon classes =
i [ class classes, class notificationHooks ] []
metadata : String
metadata =
"fa fa-cubes"
metadataIcon : String -> Html msg
metadataIcon classes =
i [ class classes, class metadata ] []
trash : String
trash =
"fa fa-trash-alt text-red-500 dark:text-orange-600"
@ -112,6 +160,16 @@ source2 =
"fa fa-upload"
fileUpload : String
fileUpload =
"fa fa-file-upload"
fileUploadIcon : String -> Html msg
fileUploadIcon classes =
i [ class classes, class fileUpload ] []
sourceIcon2 : String -> Html msg
sourceIcon2 classes =
i [ class (source2 ++ " " ++ classes) ] []
@ -203,7 +261,7 @@ customFieldIcon2 classes =
search : String
search =
"search icon"
"fa fa-search"
searchIcon : String -> Html msg
@ -213,11 +271,6 @@ searchIcon classes =
folder : String
folder =
"folder outline icon"
folder2 : String
folder2 =
"fa fa-folder font-thin "
@ -226,11 +279,6 @@ folderIcon classes =
i [ class (folder ++ " " ++ classes) ] []
folderIcon2 : String -> Html msg
folderIcon2 classes =
i [ class (folder2 ++ " " ++ classes) ] []
concerned : String
concerned =
"crosshairs icon"
@ -329,14 +377,14 @@ dueDateIcon2 classes =
i [ class (dueDate2 ++ " " ++ classes) ] []
editNotes : String
editNotes =
"comment alternate outline icon"
edit : String
edit =
"fa fa-edit font-thin"
editNotesIcon : Html msg
editNotesIcon =
i [ class editNotes ] []
editIcon : String -> Html msg
editIcon classes =
i [ class edit, class classes ] []
addFiles2 : String
@ -361,11 +409,6 @@ showQrIcon classes =
tag : String
tag =
"tag icon"
tag2 : String
tag2 =
"fa fa-tag"
@ -374,19 +417,14 @@ tagIcon classes =
i [ class (tag ++ " " ++ classes) ] []
tagIcon2 : String -> Html msg
tagIcon2 classes =
i [ class (tag2 ++ " " ++ classes) ] []
tags2 : String
tags2 =
tags : String
tags =
"fa fa-tags"
tagsIcon2 : String -> Html msg
tagsIcon2 classes =
i [ class (tags2 ++ " " ++ classes) ] []
tagsIcon : String -> Html msg
tagsIcon classes =
i [ class (tags ++ " " ++ classes) ] []
direction : String
@ -411,11 +449,6 @@ directionIcon2 classes =
person : String
person =
"user icon"
person2 : String
person2 =
"fa fa-user"
@ -424,18 +457,8 @@ personIcon classes =
i [ class (person ++ " " ++ classes) ] []
personIcon2 : String -> Html msg
personIcon2 classes =
i [ class (person2 ++ " " ++ classes) ] []
organization : String
organization =
"factory icon"
organization2 : String
organization2 =
"fa fa-industry"
@ -444,18 +467,8 @@ organizationIcon classes =
i [ class (organization ++ " " ++ classes) ] []
organizationIcon2 : String -> Html msg
organizationIcon2 classes =
i [ class (organization2 ++ " " ++ classes) ] []
equipment : String
equipment =
"box icon"
equipment2 : String
equipment2 =
"fa fa-box"
@ -464,11 +477,6 @@ equipmentIcon classes =
i [ class (equipment ++ " " ++ classes) ] []
equipmentIcon2 : String -> Html msg
equipmentIcon2 classes =
i [ class (equipment2 ++ " " ++ classes) ] []
matrixIcon : String -> Html msg
matrixIcon classes =
Svg.svg

View File

@ -0,0 +1,146 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
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
= Name
| DateLong
| DateShort
| DueDateLong
| DueDateShort
| Folder
| Correspondent
| Concerning
| Tags
all : List ItemColumn
all =
[ Name, DateLong, DateShort, DueDateLong, DueDateShort, Folder, Correspondent, Concerning, Tags ]
renderString : TemplateContext -> ItemColumn -> ItemLight -> String
renderString ctx col item =
case col of
Name ->
IT.render IT.name ctx item
DateShort ->
IT.render IT.dateShort ctx item
DateLong ->
IT.render IT.dateLong ctx item
DueDateShort ->
IT.render IT.dueDateShort ctx item
DueDateLong ->
IT.render IT.dueDateLong ctx item
Folder ->
IT.render IT.folder ctx item
Correspondent ->
IT.render IT.correspondent ctx item
Concerning ->
IT.render IT.concerning ctx item
Tags ->
List.map .name item.tags
|> String.join ", "
asString : ItemColumn -> String
asString col =
case col of
Name ->
"name"
DateShort ->
"dateshort"
DateLong ->
"datelong"
DueDateShort ->
"duedateshort"
DueDateLong ->
"duedatelong"
Folder ->
"folder"
Correspondent ->
"correspondent"
Concerning ->
"concerning"
Tags ->
"tags"
fromString : String -> Maybe ItemColumn
fromString str =
case String.toLower str of
"name" ->
Just Name
"dateshort" ->
Just DateShort
"datelong" ->
Just DateLong
"duedateshort" ->
Just DueDateShort
"duedatelong" ->
Just DueDateLong
"folder" ->
Just Folder
"correspondent" ->
Just Correspondent
"concerning" ->
Just Concerning
"tags" ->
Just Tags
_ ->
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

View File

@ -16,8 +16,10 @@ module Data.UiSettings exposing
, catColorFg2
, catColorString2
, defaults
, documentationSite
, fieldHidden
, fieldVisible
, getUiLanguage
, merge
, mergeDefaults
, pdfUrl
@ -93,6 +95,9 @@ storedUiSettingsDecoder =
maybeString =
Decode.maybe Decode.string
def =
defaults
in
Decode.succeed StoredUiSettings
|> P.optional "itemSearchPageSize" maybeInt Nothing
@ -104,19 +109,19 @@ storedUiSettingsDecoder =
|> 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 "itemDetailShortcuts" Decode.bool def.itemDetailShortcuts
|> P.optional "searchMenuVisible" Decode.bool def.searchMenuVisible
|> P.optional "editMenuVisible" Decode.bool def.editMenuVisible
|> 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 "searchStatsVisible" Decode.bool def.searchStatsVisible
|> P.optional "cardPreviewFullWidth" Decode.bool def.cardPreviewFullWidth
|> P.optional "uiTheme" maybeString Nothing
|> P.optional "sideMenuVisible" Decode.bool False
|> P.optional "powerSearchEnabled" Decode.bool False
|> P.optional "sideMenuVisible" Decode.bool def.sideMenuVisible
|> P.optional "powerSearchEnabled" Decode.bool def.powerSearchEnabled
|> P.optional "uiLang" maybeString Nothing
|> P.optional "itemSearchShowGroups" Decode.bool True
|> P.optional "itemSearchShowGroups" Decode.bool def.itemSearchShowGroups
|> P.optional "itemSearchArrange" maybeString Nothing
@ -444,6 +449,21 @@ pdfUrl settings flags originalUrl =
Data.Pdf.serverUrl originalUrl
getUiLanguage : Flags -> UiSettings -> UiLanguage -> UiLanguage
getUiLanguage flags settings default =
case flags.account of
Just _ ->
settings.uiLang
Nothing ->
default
documentationSite : String
documentationSite =
"https://docspell.org/docs"
--- Helpers

View File

@ -14,13 +14,14 @@ module Messages exposing
import Messages.App
import Messages.Page.CollectiveSettings
import Messages.Page.Home
import Messages.Page.Dashboard
import Messages.Page.ItemDetail
import Messages.Page.Login
import Messages.Page.ManageData
import Messages.Page.NewInvite
import Messages.Page.Queue
import Messages.Page.Register
import Messages.Page.Search
import Messages.Page.Share
import Messages.Page.ShareDetail
import Messages.Page.Upload
@ -45,9 +46,10 @@ type alias Messages =
, queue : Messages.Page.Queue.Texts
, userSettings : Messages.Page.UserSettings.Texts
, manageData : Messages.Page.ManageData.Texts
, home : Messages.Page.Home.Texts
, search : Messages.Page.Search.Texts
, share : Messages.Page.Share.Texts
, shareDetail : Messages.Page.ShareDetail.Texts
, dashboard : Messages.Page.Dashboard.Texts
}
@ -112,9 +114,10 @@ gb =
, queue = Messages.Page.Queue.gb
, userSettings = Messages.Page.UserSettings.gb
, manageData = Messages.Page.ManageData.gb
, home = Messages.Page.Home.gb
, search = Messages.Page.Search.gb
, share = Messages.Page.Share.gb
, shareDetail = Messages.Page.ShareDetail.gb
, dashboard = Messages.Page.Dashboard.gb
}
@ -134,7 +137,8 @@ de =
, queue = Messages.Page.Queue.de
, userSettings = Messages.Page.UserSettings.de
, manageData = Messages.Page.ManageData.de
, home = Messages.Page.Home.de
, search = Messages.Page.Search.de
, share = Messages.Page.Share.de
, shareDetail = Messages.Page.ShareDetail.de
, dashboard = Messages.Page.Dashboard.de
}

View File

@ -24,6 +24,7 @@ type alias Texts =
, newInvites : String
, help : String
, newItemsArrived : String
, dashboard : String
}
@ -40,6 +41,7 @@ gb =
, newInvites = "New Invites"
, help = "Help"
, newItemsArrived = "New items arrived!"
, dashboard = "Dashboard"
}
@ -56,4 +58,5 @@ de =
, newInvites = "Neue Einladung"
, help = "Hilfe (English)"
, newItemsArrived = "Neue Dokumente eingetroffen!"
, dashboard = "Dashboard"
}

View File

@ -45,6 +45,10 @@ type alias Texts =
, customFields : String
, direction : String
, folderNotOwnerWarning : String
, shares : String
, sources : String
, periodicQueries : String
, notificationHooks : String
}
@ -87,6 +91,10 @@ You are **not a member** of this folder. This item will be **hidden**
from any search now. Use a folder where you are a member of to make this
item visible. This message will disappear then.
"""
, shares = "Shares"
, sources = "Sources"
, periodicQueries = "Periodic Queries"
, notificationHooks = "Webhooks"
}
@ -130,4 +138,8 @@ URL hochgeladen werden, sind für dich in der Suche *nicht* sichtbar.
Nutze lieber einen Ordner, dem Du als Mitglied zugeordnet bist. Diese
Nachricht verschwindet dann.
"""
, shares = "Freigaben"
, sources = "Quellen"
, periodicQueries = "Periodische Abfragen"
, notificationHooks = "Webhooks"
}

View File

@ -11,7 +11,9 @@ module Messages.Comp.BookmarkChooser exposing
, gb
)
import Data.AccountScope exposing (AccountScope(..))
import Messages.Basics
import Messages.Data.AccountScope
type alias Texts =
@ -25,8 +27,8 @@ type alias Texts =
gb : Texts
gb =
{ basics = Messages.Basics.gb
, userLabel = "Personal"
, collectiveLabel = "Collective"
, userLabel = Messages.Data.AccountScope.gb User
, collectiveLabel = Messages.Data.AccountScope.gb Collective
, shareLabel = "Shares"
}
@ -34,7 +36,7 @@ gb =
de : Texts
de =
{ basics = Messages.Basics.de
, userLabel = "Persönlich"
, collectiveLabel = "Kollektiv"
, userLabel = Messages.Data.AccountScope.de User
, collectiveLabel = Messages.Data.AccountScope.de Collective
, shareLabel = "Freigaben"
}

View File

@ -0,0 +1,74 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxEdit exposing (Texts, de, gb)
import Messages.Basics
import Messages.Comp.BoxMessageEdit
import Messages.Comp.BoxQueryEdit
import Messages.Comp.BoxStatsEdit
import Messages.Comp.BoxUploadEdit
import Messages.Data.BoxContent
type alias Texts =
{ messageEdit : Messages.Comp.BoxMessageEdit.Texts
, uploadEdit : Messages.Comp.BoxUploadEdit.Texts
, queryEdit : Messages.Comp.BoxQueryEdit.Texts
, statsEdit : Messages.Comp.BoxStatsEdit.Texts
, boxContent : Messages.Data.BoxContent.Texts
, basics : Messages.Basics.Texts
, namePlaceholder : String
, visible : String
, decorations : String
, colspan : String
, contentProperties : String
, reallyDeleteBox : String
, moveToLeft : String
, moveToRight : String
, deleteBox : String
}
gb : Texts
gb =
{ messageEdit = Messages.Comp.BoxMessageEdit.gb
, uploadEdit = Messages.Comp.BoxUploadEdit.gb
, queryEdit = Messages.Comp.BoxQueryEdit.gb
, statsEdit = Messages.Comp.BoxStatsEdit.gb
, boxContent = Messages.Data.BoxContent.gb
, basics = Messages.Basics.gb
, namePlaceholder = "Box name"
, visible = "Visible"
, decorations = "Box decorations"
, colspan = "Column span"
, contentProperties = "Content"
, reallyDeleteBox = "Really delete this box?"
, moveToLeft = "Move to left"
, moveToRight = "Move to right"
, deleteBox = "Delete box"
}
de : Texts
de =
{ messageEdit = Messages.Comp.BoxMessageEdit.de
, uploadEdit = Messages.Comp.BoxUploadEdit.de
, queryEdit = Messages.Comp.BoxQueryEdit.de
, statsEdit = Messages.Comp.BoxStatsEdit.de
, boxContent = Messages.Data.BoxContent.de
, basics = Messages.Basics.de
, namePlaceholder = "Boxname"
, visible = "Sichtbar"
, decorations = "Kachel-Dekoration anzeigen"
, colspan = "Spalten überspannen"
, contentProperties = "Inhalt"
, reallyDeleteBox = "Die Kachel wirklich entfernen?"
, moveToLeft = "Nach links verschieben"
, moveToRight = "Nach rechts verschieben"
, deleteBox = "Kachel entfernen"
}

View File

@ -0,0 +1,37 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxMessageEdit exposing (Texts, de, gb)
type alias Texts =
{ titleLabel : String
, titlePlaceholder : String
, bodyLabel : String
, bodyPlaceholder : String
, infoText : String
}
gb : Texts
gb =
{ titleLabel = "Title"
, titlePlaceholder = "Message title"
, bodyLabel = "Body"
, bodyPlaceholder = "Message body"
, infoText = "Markdown can be used in both fields for simple formatting."
}
de : Texts
de =
{ titleLabel = "Titel"
, titlePlaceholder = "Titel"
, bodyLabel = "Nachricht"
, bodyPlaceholder = "Text"
, infoText = "Markdown kann in beiden Feldern für einfache Formatierung verwendet werden."
}

View File

@ -0,0 +1,34 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxQueryEdit exposing (Texts, de, gb)
import Messages.Comp.BoxSearchQueryInput
import Messages.Comp.ItemColumnDropdown
type alias Texts =
{ columnDropdown : Messages.Comp.ItemColumnDropdown.Texts
, searchQuery : Messages.Comp.BoxSearchQueryInput.Texts
, showColumnHeaders : String
}
gb : Texts
gb =
{ columnDropdown = Messages.Comp.ItemColumnDropdown.gb
, searchQuery = Messages.Comp.BoxSearchQueryInput.gb
, showColumnHeaders = "Show column headers"
}
de : Texts
de =
{ columnDropdown = Messages.Comp.ItemColumnDropdown.de
, searchQuery = Messages.Comp.BoxSearchQueryInput.de
, showColumnHeaders = "Spaltennamen anzeigen"
}

View File

@ -0,0 +1,57 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxQueryView exposing (Texts, de, gb)
import Data.ItemTemplate as IT
import Http
import Messages.Basics
import Messages.Comp.HttpError
import Messages.Data.Direction
import Messages.Data.ItemColumn
import Messages.DateFormat as DF
import Messages.UiLanguage
type alias Texts =
{ httpError : Http.Error -> String
, errorOccurred : String
, basics : Messages.Basics.Texts
, noResults : String
, templateCtx : IT.TemplateContext
, itemColumn : Messages.Data.ItemColumn.Texts
}
gb : Texts
gb =
{ httpError = Messages.Comp.HttpError.gb
, errorOccurred = "Error retrieving data."
, basics = Messages.Basics.gb
, noResults = "No items found."
, templateCtx =
{ dateFormatLong = DF.formatDateLong Messages.UiLanguage.English
, dateFormatShort = DF.formatDateShort Messages.UiLanguage.English
, directionLabel = Messages.Data.Direction.gb
}
, itemColumn = Messages.Data.ItemColumn.gb
}
de : Texts
de =
{ httpError = Messages.Comp.HttpError.de
, errorOccurred = "Fehler beim Laden der Daten."
, basics = Messages.Basics.de
, noResults = "Keine Dokumente gefunden."
, templateCtx =
{ dateFormatLong = DF.formatDateLong Messages.UiLanguage.German
, dateFormatShort = DF.formatDateShort Messages.UiLanguage.German
, directionLabel = Messages.Data.Direction.de
}
, itemColumn = Messages.Data.ItemColumn.de
}

View File

@ -0,0 +1,36 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxSearchQueryInput exposing (Texts, de, gb)
import Messages.Comp.BookmarkDropdown
type alias Texts =
{ bookmarkDropdown : Messages.Comp.BookmarkDropdown.Texts
, switchToBookmark : String
, switchToQuery : String
, searchPlaceholder : String
}
gb : Texts
gb =
{ bookmarkDropdown = Messages.Comp.BookmarkDropdown.gb
, switchToBookmark = "Bookmarks"
, switchToQuery = "Search query"
, searchPlaceholder = "Search"
}
de : Texts
de =
{ bookmarkDropdown = Messages.Comp.BookmarkDropdown.de
, switchToBookmark = "Bookmarks"
, switchToQuery = "Suchabfrage"
, searchPlaceholder = "Abfrage"
}

View File

@ -0,0 +1,39 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxStatsEdit exposing (Texts, de, gb)
import Messages.Comp.BoxSearchQueryInput
type alias Texts =
{ searchQuery : Messages.Comp.BoxSearchQueryInput.Texts
, fieldStatistics : String
, basicNumbers : String
, showLabel : String
, showItemCount : String
}
gb : Texts
gb =
{ searchQuery = Messages.Comp.BoxSearchQueryInput.gb
, fieldStatistics = "Field statistics"
, basicNumbers = "Basic numbers"
, showLabel = "Display"
, showItemCount = "Show item count"
}
de : Texts
de =
{ searchQuery = Messages.Comp.BoxSearchQueryInput.de
, fieldStatistics = "Benutzerfeld Statistiken"
, basicNumbers = "Allgemeine Zahlen"
, showLabel = "Anzeige"
, showItemCount = "Gesamtanzahl Dokumente mit anzeigen"
}

View File

@ -0,0 +1,39 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxStatsView exposing (Texts, de, gb)
import Http
import Messages.Basics
import Messages.Comp.HttpError
import Messages.Comp.SearchStatsView
type alias Texts =
{ httpError : Http.Error -> String
, errorOccurred : String
, statsView : Messages.Comp.SearchStatsView.Texts
, basics : Messages.Basics.Texts
}
gb : Texts
gb =
{ httpError = Messages.Comp.HttpError.gb
, errorOccurred = "Error retrieving data."
, statsView = Messages.Comp.SearchStatsView.gb
, basics = Messages.Basics.gb
}
de : Texts
de =
{ httpError = Messages.Comp.HttpError.de
, errorOccurred = "Fehler beim Laden der Daten."
, statsView = Messages.Comp.SearchStatsView.de
, basics = Messages.Basics.de
}

View File

@ -0,0 +1,31 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxUploadEdit exposing (Texts, de, gb)
type alias Texts =
{ sourceLabel : String
, sourcePlaceholder : String
, infoText : String
}
gb : Texts
gb =
{ sourceLabel = "Source"
, sourcePlaceholder = "Choose source"
, infoText = "Optionally choose a source otherwise default settings apply to all uploads."
}
de : Texts
de =
{ sourceLabel = "Quelle"
, sourcePlaceholder = "Quelle"
, infoText = "Optional kann eine Quelle als Einstellung gewählt werden, sonst werden Standardeinstellungen verwendet."
}

View File

@ -0,0 +1,30 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxUploadView exposing (Texts, de, gb)
import Messages.Comp.UploadForm
type alias Texts =
{ uploadForm : Messages.Comp.UploadForm.Texts
, moreOptions : String
}
gb : Texts
gb =
{ uploadForm = Messages.Comp.UploadForm.gb
, moreOptions = "More options"
}
de : Texts
de =
{ uploadForm = Messages.Comp.UploadForm.de
, moreOptions = "More options"
}

View File

@ -0,0 +1,35 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.BoxView exposing (Texts, de, gb)
import Messages.Comp.BoxQueryView
import Messages.Comp.BoxStatsView
import Messages.Comp.BoxUploadView
type alias Texts =
{ queryView : Messages.Comp.BoxQueryView.Texts
, statsView : Messages.Comp.BoxStatsView.Texts
, uploadView : Messages.Comp.BoxUploadView.Texts
}
gb : Texts
gb =
{ queryView = Messages.Comp.BoxQueryView.gb
, statsView = Messages.Comp.BoxStatsView.gb
, uploadView = Messages.Comp.BoxUploadView.gb
}
de : Texts
de =
{ queryView = Messages.Comp.BoxQueryView.de
, statsView = Messages.Comp.BoxStatsView.de
, uploadView = Messages.Comp.BoxUploadView.de
}

View File

@ -0,0 +1,57 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.DashboardEdit exposing (Texts, de, gb)
import Messages.Basics
import Messages.Comp.BoxEdit
import Messages.Data.AccountScope
import Messages.Data.BoxContent
type alias Texts =
{ boxView : Messages.Comp.BoxEdit.Texts
, boxContent : Messages.Data.BoxContent.Texts
, basics : Messages.Basics.Texts
, accountScope : Messages.Data.AccountScope.Texts
, namePlaceholder : String
, columns : String
, dashboardBoxes : String
, newBox : String
, defaultDashboard : String
, gap : String
}
gb : Texts
gb =
{ boxView = Messages.Comp.BoxEdit.gb
, boxContent = Messages.Data.BoxContent.gb
, basics = Messages.Basics.gb
, accountScope = Messages.Data.AccountScope.gb
, namePlaceholder = "Dashboard name"
, columns = "Columns"
, dashboardBoxes = "Dashboard Boxes"
, newBox = "New box"
, defaultDashboard = "Default Dashboard"
, gap = "Gap"
}
de : Texts
de =
{ boxView = Messages.Comp.BoxEdit.de
, boxContent = Messages.Data.BoxContent.de
, basics = Messages.Basics.de
, accountScope = Messages.Data.AccountScope.de
, namePlaceholder = "Dashboardname"
, columns = "Spalten"
, dashboardBoxes = "Dashboard Kacheln"
, newBox = "Neue Kachel"
, defaultDashboard = "Standard Dashboard"
, gap = "Abstand"
}

View File

@ -0,0 +1,51 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.DashboardManage exposing (Texts, de, gb)
import Http
import Messages.Basics
import Messages.Comp.DashboardEdit
import Messages.Comp.HttpError
type alias Texts =
{ basics : Messages.Basics.Texts
, dashboardEdit : Messages.Comp.DashboardEdit.Texts
, httpError : Http.Error -> String
, reallyDeleteDashboard : String
, nameEmpty : String
, nameExists : String
, createDashboard : String
, copyDashboard : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, dashboardEdit = Messages.Comp.DashboardEdit.gb
, httpError = Messages.Comp.HttpError.gb
, reallyDeleteDashboard = "Really delete this dashboard?"
, nameEmpty = "The name must not be empty."
, nameExists = "The name is already in use."
, createDashboard = "New"
, copyDashboard = "Copy"
}
de : Texts
de =
{ basics = Messages.Basics.de
, dashboardEdit = Messages.Comp.DashboardEdit.de
, httpError = Messages.Comp.HttpError.de
, reallyDeleteDashboard = "Das Dashboard wirklich entfernen?"
, nameEmpty = "Ein Name muss angegeben werden."
, nameExists = "Der Name wird bereits verwendet."
, createDashboard = "Neu"
, copyDashboard = "Kopie"
}

View File

@ -0,0 +1,27 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.DashboardView exposing (Texts, de, gb)
import Messages.Comp.BoxView
type alias Texts =
{ boxView : Messages.Comp.BoxView.Texts
}
gb : Texts
gb =
{ boxView = Messages.Comp.BoxView.gb
}
de : Texts
de =
{ boxView = Messages.Comp.BoxView.de
}

View File

@ -0,0 +1,38 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.ItemColumnDropdown exposing
( Texts
, de
, gb
)
import Messages.Basics
import Messages.Data.ItemColumn
type alias Texts =
{ basics : Messages.Basics.Texts
, column : Messages.Data.ItemColumn.Texts
, placeholder : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, column = Messages.Data.ItemColumn.gb
, placeholder = "Choose"
}
de : Texts
de =
{ basics = Messages.Basics.de
, column = Messages.Data.ItemColumn.de
, placeholder = "Wähle"
}

View File

@ -0,0 +1,98 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Comp.UploadForm exposing (Texts, de, gb)
import Data.Language exposing (Language)
import Messages.Basics
import Messages.Comp.Dropzone
import Messages.Data.Language
type alias Texts =
{ basics : Messages.Basics.Texts
, dropzone : Messages.Comp.Dropzone.Texts
, reset : String
, allFilesOneItem : String
, skipExistingFiles : String
, language : String
, languageInfo : String
, uploadErrorMessage : String
, successBox :
{ allFilesUploaded : String
, line1 : String
, itemsPage : String
, line2 : String
, processingPage : String
, line3 : String
, resetLine1 : String
, reset : String
, resetLine2 : String
}
, selectedFiles : String
, languageLabel : Language -> String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, dropzone = Messages.Comp.Dropzone.gb
, reset = "Reset"
, allFilesOneItem = "All files are one single item"
, skipExistingFiles = "Skip files already present in docspell"
, language = "Language"
, languageInfo =
"Used for text extraction and analysis. The collective's "
++ "default language is used if not specified here."
, uploadErrorMessage = "There were errors uploading some files."
, successBox =
{ allFilesUploaded = "All files uploaded"
, line1 =
"Your files have been successfully uploaded. "
++ "They are now being processed. Check the "
, itemsPage = "Items Page"
, line2 = " later where the files will arrive eventually. Or go to the "
, processingPage = "Processing Page"
, line3 = " to view the current processing state."
, resetLine1 = " Click "
, reset = "Reset"
, resetLine2 = " to upload more files."
}
, selectedFiles = "Selected Files"
, languageLabel = Messages.Data.Language.gb
}
de : Texts
de =
{ basics = Messages.Basics.de
, dropzone = Messages.Comp.Dropzone.de
, reset = "Zurücksetzen"
, allFilesOneItem = "Alle Dateien sind ein Dokument"
, skipExistingFiles = "Lasse Dateien aus, die schon in Docspell sind"
, language = "Sprache"
, languageInfo =
"Wird für Texterkennung und -analyse verwendet. Die Standardsprache des Kollektivs "
++ "wird verwendet, falls hier nicht angegeben."
, uploadErrorMessage = "Es gab Fehler beim Hochladen der Dateien."
, successBox =
{ allFilesUploaded = "Alle Dateien hochgeladen"
, line1 =
"Deine Dateien wurden erfolgreich hochgeladen und sie werden nun verarbeitet. "
++ "Gehe nachher zur "
, itemsPage = "Hauptseite"
, line2 = " wo die Dateien als Dokumente erscheinen werden oder gehe zur "
, processingPage = "Verarbeitungsseite,"
, line3 = " welche einen Einblick in den aktuellen Status gibt."
, resetLine1 = " Klicke "
, reset = "Zurücksetzen"
, resetLine2 = " um weitere Dateien hochzuladen."
}
, selectedFiles = "Ausgewählte Dateien"
, languageLabel = Messages.Data.Language.de
}

View File

@ -0,0 +1,24 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Data.AccountScope exposing (Texts, de, gb)
import Data.AccountScope exposing (AccountScope)
type alias Texts =
AccountScope -> String
gb : Texts
gb =
Data.AccountScope.fold "Personal" "Collective"
de : Texts
de =
Data.AccountScope.fold "Persönlich" "Kollektiv"

View File

@ -0,0 +1,61 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Data.BoxContent exposing (Texts, de, gb)
import Data.BoxContent exposing (BoxContent(..))
type alias Texts =
{ forContent : BoxContent -> String
, queryBox : String
, statsBox : String
, messageBox : String
, uploadBox : String
}
gb : Texts
gb =
updateForContent
{ forContent = \_ -> ""
, queryBox = "Query box"
, statsBox = "Statistics box"
, messageBox = "Message box"
, uploadBox = "Upload box"
}
de : Texts
de =
updateForContent
{ forContent = \_ -> ""
, queryBox = "Suchabfrage Kachel"
, statsBox = "Statistik Kachel"
, messageBox = "Mitteilung Kachel"
, uploadBox = "Datei hochladen Kachel"
}
updateForContent : Texts -> Texts
updateForContent init =
{ init
| forContent =
\cnt ->
case cnt of
BoxMessage _ ->
init.messageBox
BoxUpload _ ->
init.uploadBox
BoxQuery _ ->
init.queryBox
BoxStats _ ->
init.statsBox
}

View File

@ -0,0 +1,122 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Data.ItemColumn exposing (Texts, de, gb)
import Data.ItemColumn exposing (ItemColumn(..))
type alias Texts =
{ header : ItemColumn -> String
, label : ItemColumn -> String
}
gb : Texts
gb =
let
headerName col =
case col of
Name ->
"Name"
DateLong ->
"Date"
DateShort ->
"Date"
DueDateLong ->
"Due date"
DueDateShort ->
"Due date"
Folder ->
"Folder"
Correspondent ->
"Correspondent"
Concerning ->
"Concerning"
Tags ->
"Tags"
in
{ header = headerName
, label =
\col ->
case col of
DateShort ->
headerName col ++ " (short)"
DateLong ->
headerName col ++ " (long)"
DueDateShort ->
headerName col ++ " (short)"
DueDateLong ->
headerName col ++ " (long)"
_ ->
headerName col
}
de : Texts
de =
let
headerName col =
case col of
Name ->
"Name"
DateLong ->
"Datum"
DateShort ->
"Datum"
DueDateLong ->
"Fälligkeitsdatum"
DueDateShort ->
"Fälligkeitsdatum"
Folder ->
"Ordner"
Correspondent ->
"Korrespondent"
Concerning ->
"Betreffend"
Tags ->
"Tags"
in
{ header = headerName
, label =
\col ->
case col of
DateShort ->
headerName col ++ " (kurz)"
DateLong ->
headerName col ++ " (lang)"
DueDateShort ->
headerName col ++ " (kurz)"
DueDateLong ->
headerName col ++ " (lang)"
_ ->
headerName col
}

View File

@ -10,6 +10,7 @@ module Messages.DateFormat exposing
, formatDateLong
, formatDateShort
, formatDateTimeLong
, formatDateTimeShort
)
import DateFormat exposing (Token)
@ -68,6 +69,11 @@ formatDateShort lang millis =
format lang .dateShort millis
formatDateTimeShort : UiLanguage -> Int -> String
formatDateTimeShort lang millis =
format lang .dateTimeShort millis
--- Language Definitions

View File

@ -29,10 +29,8 @@ type alias Texts =
, httpError : Http.Error -> String
, collectiveSettings : String
, insights : String
, sources : String
, settings : String
, users : String
, shares : String
, user : String
, collective : String
, size : String
@ -51,10 +49,8 @@ gb =
, httpError = Messages.Comp.HttpError.gb
, collectiveSettings = "Collective Settings"
, insights = "Insights"
, sources = "Sources"
, settings = "Settings"
, users = "Users"
, shares = "Shares"
, user = "User"
, collective = "Collective"
, size = "Size"
@ -73,10 +69,8 @@ de =
, httpError = Messages.Comp.HttpError.de
, collectiveSettings = "Kollektiveinstellungen"
, insights = "Statistiken"
, sources = "Quellen"
, settings = "Einstellungen"
, users = "Benutzer"
, shares = "Freigaben"
, user = "Benutzer"
, collective = "Kollektiv"
, size = "Größe"

View File

@ -0,0 +1,117 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Page.Dashboard exposing (Texts, de, gb)
import Messages.Basics
import Messages.Comp.BookmarkChooser
import Messages.Comp.DashboardManage
import Messages.Comp.DashboardView
import Messages.Comp.EquipmentManage
import Messages.Comp.FolderManage
import Messages.Comp.NotificationHookManage
import Messages.Comp.OrgManage
import Messages.Comp.PeriodicQueryTaskManage
import Messages.Comp.PersonManage
import Messages.Comp.ShareManage
import Messages.Comp.SourceManage
import Messages.Comp.TagManage
import Messages.Comp.UploadForm
import Messages.Data.AccountScope
import Messages.Page.DefaultDashboard
type alias Texts =
{ basics : Messages.Basics.Texts
, bookmarkChooser : Messages.Comp.BookmarkChooser.Texts
, notificationHookManage : Messages.Comp.NotificationHookManage.Texts
, periodicQueryManage : Messages.Comp.PeriodicQueryTaskManage.Texts
, sourceManage : Messages.Comp.SourceManage.Texts
, shareManage : Messages.Comp.ShareManage.Texts
, organizationManage : Messages.Comp.OrgManage.Texts
, personManage : Messages.Comp.PersonManage.Texts
, equipManage : Messages.Comp.EquipmentManage.Texts
, tagManage : Messages.Comp.TagManage.Texts
, folderManage : Messages.Comp.FolderManage.Texts
, uploadForm : Messages.Comp.UploadForm.Texts
, dashboard : Messages.Comp.DashboardView.Texts
, dashboardManage : Messages.Comp.DashboardManage.Texts
, defaultDashboard : Messages.Page.DefaultDashboard.Texts
, accountScope : Messages.Data.AccountScope.Texts
, manage : String
, dashboardLink : String
, bookmarks : String
, misc : String
, settings : String
, documentation : String
, uploadFiles : String
, editDashboard : String
, dashboards : String
, predefinedMessage : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, bookmarkChooser = Messages.Comp.BookmarkChooser.gb
, notificationHookManage = Messages.Comp.NotificationHookManage.gb
, periodicQueryManage = Messages.Comp.PeriodicQueryTaskManage.gb
, sourceManage = Messages.Comp.SourceManage.gb
, shareManage = Messages.Comp.ShareManage.gb
, organizationManage = Messages.Comp.OrgManage.gb
, personManage = Messages.Comp.PersonManage.gb
, equipManage = Messages.Comp.EquipmentManage.gb
, tagManage = Messages.Comp.TagManage.gb
, folderManage = Messages.Comp.FolderManage.gb
, uploadForm = Messages.Comp.UploadForm.gb
, dashboard = Messages.Comp.DashboardView.gb
, dashboardManage = Messages.Comp.DashboardManage.gb
, defaultDashboard = Messages.Page.DefaultDashboard.gb
, accountScope = Messages.Data.AccountScope.gb
, manage = "Manage"
, dashboardLink = "Dasbhoard"
, bookmarks = "Bookmarks"
, misc = "Misc"
, settings = "Settings"
, documentation = "Documentation"
, uploadFiles = "Upload documents"
, editDashboard = "Edit Dashboard"
, dashboards = "Dashboards"
, predefinedMessage = "This dashboard is predefined one that cannot be deleted. It is replaced with the first one you save."
}
de : Texts
de =
{ basics = Messages.Basics.de
, bookmarkChooser = Messages.Comp.BookmarkChooser.de
, notificationHookManage = Messages.Comp.NotificationHookManage.de
, periodicQueryManage = Messages.Comp.PeriodicQueryTaskManage.de
, sourceManage = Messages.Comp.SourceManage.de
, shareManage = Messages.Comp.ShareManage.de
, organizationManage = Messages.Comp.OrgManage.de
, personManage = Messages.Comp.PersonManage.de
, equipManage = Messages.Comp.EquipmentManage.de
, tagManage = Messages.Comp.TagManage.de
, folderManage = Messages.Comp.FolderManage.de
, uploadForm = Messages.Comp.UploadForm.de
, dashboard = Messages.Comp.DashboardView.de
, dashboardManage = Messages.Comp.DashboardManage.de
, defaultDashboard = Messages.Page.DefaultDashboard.de
, accountScope = Messages.Data.AccountScope.de
, manage = "Verwalten"
, dashboardLink = "Dasbhoard"
, bookmarks = "Bookmarks"
, misc = "Anderes"
, settings = "Einstellungen"
, documentation = "Dokumentation"
, uploadFiles = "Dokumente hochladen"
, editDashboard = "Dashboard ändern"
, dashboards = "Dashboards"
, predefinedMessage = "Dieses Dashboard ist vordefiniert und kann nicht entfernt werden. Es wird durch ein gespeichertes ersetzt."
}

View File

@ -0,0 +1,48 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Messages.Page.DefaultDashboard exposing (Texts, de, gb)
import Messages.Basics
type alias Texts =
{ basics : Messages.Basics.Texts
, default : String
, welcomeName : String
, welcomeTitle : String
, welcomeBody : String
, summaryName : String
, dueInDays : Int -> String
, newDocsName : String
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, default = "Default"
, welcomeName = "Welcome Message"
, welcomeTitle = "# Welcome to Docspell"
, welcomeBody = "Docspell keeps your documents organized."
, summaryName = "Summary"
, dueInDays = \n -> "Due in " ++ String.fromInt n ++ " days"
, newDocsName = "New Documents"
}
de : Texts
de =
{ basics = Messages.Basics.de
, default = "Standard"
, welcomeName = "Willkommens-Nachricht"
, welcomeTitle = "# Willkommen zu Docspell"
, welcomeBody = "Docspell behält die Übersicht über deine Dokumene."
, summaryName = "Zahlen"
, dueInDays = \n -> "Fällig in " ++ String.fromInt n ++ " Tagen"
, newDocsName = "Neue Dokumente"
}

View File

@ -5,7 +5,7 @@
-}
module Messages.Page.Home exposing
module Messages.Page.Search exposing
( Texts
, de
, gb
@ -17,14 +17,14 @@ import Messages.Comp.ItemCardList
import Messages.Comp.ItemMerge
import Messages.Comp.PublishItems
import Messages.Comp.SearchStatsView
import Messages.Page.HomeSideMenu
import Messages.Page.SearchSideMenu
type alias Texts =
{ basics : Messages.Basics.Texts
, itemCardList : Messages.Comp.ItemCardList.Texts
, searchStatsView : Messages.Comp.SearchStatsView.Texts
, sideMenu : Messages.Page.HomeSideMenu.Texts
, sideMenu : Messages.Page.SearchSideMenu.Texts
, itemMerge : Messages.Comp.ItemMerge.Texts
, publishItems : Messages.Comp.PublishItems.Texts
, bookmarkManage : Messages.Comp.BookmarkQueryManage.Texts
@ -66,7 +66,7 @@ gb =
{ basics = Messages.Basics.gb
, itemCardList = Messages.Comp.ItemCardList.gb
, searchStatsView = Messages.Comp.SearchStatsView.gb
, sideMenu = Messages.Page.HomeSideMenu.gb
, sideMenu = Messages.Page.SearchSideMenu.gb
, itemMerge = Messages.Comp.ItemMerge.gb
, publishItems = Messages.Comp.PublishItems.gb
, bookmarkManage = Messages.Comp.BookmarkQueryManage.gb
@ -108,7 +108,7 @@ de =
{ basics = Messages.Basics.de
, itemCardList = Messages.Comp.ItemCardList.de
, searchStatsView = Messages.Comp.SearchStatsView.de
, sideMenu = Messages.Page.HomeSideMenu.de
, sideMenu = Messages.Page.SearchSideMenu.de
, itemMerge = Messages.Comp.ItemMerge.de
, publishItems = Messages.Comp.PublishItems.de
, bookmarkManage = Messages.Comp.BookmarkQueryManage.de

View File

@ -5,7 +5,7 @@
-}
module Messages.Page.HomeSideMenu exposing
module Messages.Page.SearchSideMenu exposing
( Texts
, de
, gb

View File

@ -11,92 +11,21 @@ module Messages.Page.Upload exposing
, gb
)
import Data.Language exposing (Language)
import Messages.Basics
import Messages.Comp.Dropzone
import Messages.Data.Language
import Messages.Comp.UploadForm
type alias Texts =
{ basics : Messages.Basics.Texts
, dropzone : Messages.Comp.Dropzone.Texts
, reset : String
, allFilesOneItem : String
, skipExistingFiles : String
, language : String
, languageInfo : String
, uploadErrorMessage : String
, successBox :
{ allFilesUploaded : String
, line1 : String
, itemsPage : String
, line2 : String
, processingPage : String
, line3 : String
, resetLine1 : String
, reset : String
, resetLine2 : String
}
, selectedFiles : String
, languageLabel : Language -> String
{ uploadForm : Messages.Comp.UploadForm.Texts
}
gb : Texts
gb =
{ basics = Messages.Basics.gb
, dropzone = Messages.Comp.Dropzone.gb
, reset = "Reset"
, allFilesOneItem = "All files are one single item"
, skipExistingFiles = "Skip files already present in docspell"
, language = "Language"
, languageInfo =
"Used for text extraction and analysis. The collective's "
++ "default language is used if not specified here."
, uploadErrorMessage = "There were errors uploading some files."
, successBox =
{ allFilesUploaded = "All files uploaded"
, line1 =
"Your files have been successfully uploaded. "
++ "They are now being processed. Check the "
, itemsPage = "Items Page"
, line2 = " later where the files will arrive eventually. Or go to the "
, processingPage = "Processing Page"
, line3 = " to view the current processing state."
, resetLine1 = " Click "
, reset = "Reset"
, resetLine2 = " to upload more files."
}
, selectedFiles = "Selected Files"
, languageLabel = Messages.Data.Language.gb
{ uploadForm = Messages.Comp.UploadForm.gb
}
de : Texts
de =
{ basics = Messages.Basics.de
, dropzone = Messages.Comp.Dropzone.de
, reset = "Zurücksetzen"
, allFilesOneItem = "Alle Dateien sind ein Dokument"
, skipExistingFiles = "Lasse Dateien aus, die schon in Docspell sind"
, language = "Sprache"
, languageInfo =
"Wird für Texterkennung und -analyse verwendet. Die Standardsprache des Kollektivs "
++ "wird verwendet, falls hier nicht angegeben."
, uploadErrorMessage = "Es gab Fehler beim Hochladen der Dateien."
, successBox =
{ allFilesUploaded = "Alle Dateien hochgeladen"
, line1 =
"Deine Dateien wurden erfolgreich hochgeladen und sie werden nun verarbeitet. "
++ "Gehe nachher zur "
, itemsPage = "Hauptseite"
, line2 = " wo die Dateien als Dokumente erscheinen werden oder gehe zur "
, processingPage = "Verarbeitungsseite,"
, line3 = " welche einen Einblick in den aktuellen Status gibt."
, resetLine1 = " Klicke "
, reset = "Zurücksetzen"
, resetLine2 = " um weitere Dateien hochzuladen."
}
, selectedFiles = "Ausgewählte Dateien"
, languageLabel = Messages.Data.Language.de
{ uploadForm = Messages.Comp.UploadForm.de
}

View File

@ -11,6 +11,7 @@ module Messages.Page.UserSettings exposing
, gb
)
import Messages.Basics
import Messages.Comp.ChangePasswordForm
import Messages.Comp.DueItemsTaskManage
import Messages.Comp.EmailSettingsManage
@ -24,7 +25,8 @@ import Messages.Comp.UiSettingsManage
type alias Texts =
{ changePasswordForm : Messages.Comp.ChangePasswordForm.Texts
{ basics : Messages.Basics.Texts
, changePasswordForm : Messages.Comp.ChangePasswordForm.Texts
, uiSettingsManage : Messages.Comp.UiSettingsManage.Texts
, emailSettingsManage : Messages.Comp.EmailSettingsManage.Texts
, imapSettingsManage : Messages.Comp.ImapSettingsManage.Texts
@ -46,8 +48,6 @@ type alias Texts =
, scanMailboxInfo1 : String
, scanMailboxInfo2 : String
, otpMenu : String
, webhooks : String
, genericQueries : String
, dueItems : String
, notificationInfoText : String
, webhookInfoText : String
@ -60,7 +60,8 @@ type alias Texts =
gb : Texts
gb =
{ changePasswordForm = Messages.Comp.ChangePasswordForm.gb
{ basics = Messages.Basics.gb
, changePasswordForm = Messages.Comp.ChangePasswordForm.gb
, uiSettingsManage = Messages.Comp.UiSettingsManage.gb
, emailSettingsManage = Messages.Comp.EmailSettingsManage.gb
, imapSettingsManage = Messages.Comp.ImapSettingsManage.gb
@ -96,8 +97,6 @@ gb =
adjust the schedule to avoid reading over the same mails
again."""
, otpMenu = "Two Factor Authentication"
, webhooks = "Webhooks"
, genericQueries = "Generic Queries"
, dueItems = "Due Items Query"
, notificationInfoText = """
@ -125,7 +124,8 @@ must be created before.
de : Texts
de =
{ changePasswordForm = Messages.Comp.ChangePasswordForm.de
{ basics = Messages.Basics.de
, changePasswordForm = Messages.Comp.ChangePasswordForm.de
, uiSettingsManage = Messages.Comp.UiSettingsManage.de
, emailSettingsManage = Messages.Comp.EmailSettingsManage.de
, imapSettingsManage = Messages.Comp.ImapSettingsManage.de
@ -161,8 +161,6 @@ E-Mail-Einstellungen (IMAP) notwendig."""
gleichen E-Mails möglichst nicht noch einmal eingelesen
werden."""
, otpMenu = "Zwei-Faktor-Authentifizierung"
, webhooks = "Webhooks"
, genericQueries = "Periodische Abfragen"
, dueItems = "Fällige Dokumente"
, notificationInfoText = """

View File

@ -13,7 +13,9 @@ module Page exposing
, goto
, hasSidebar
, href
, isDashboardPage
, isOpen
, isSearchPage
, isSecured
, loginPage
, loginPageReferrer
@ -51,7 +53,7 @@ emptyLoginData =
type Page
= HomePage
= SearchPage (Maybe String)
| LoginPage LoginData
| ManageDataPage
| CollectiveSettingPage
@ -63,12 +65,16 @@ type Page
| ItemDetailPage String
| SharePage String
| ShareDetailPage String String
| DashboardPage
isSecured : Page -> Bool
isSecured page =
case page of
HomePage ->
DashboardPage ->
True
SearchPage _ ->
True
LoginPage _ ->
@ -138,11 +144,34 @@ loginPage p =
LoginPage { emptyLoginData | referrer = Just p }
isSearchPage : Page -> Bool
isSearchPage page =
case page of
SearchPage _ ->
True
_ ->
False
isDashboardPage : Page -> Bool
isDashboardPage page =
case page of
DashboardPage ->
True
_ ->
False
pageName : Page -> String
pageName page =
case page of
HomePage ->
"Home"
DashboardPage ->
"dashboard"
SearchPage _ ->
"Search"
LoginPage _ ->
"Login"
@ -226,8 +255,16 @@ uploadId page =
pageToString : Page -> String
pageToString page =
case page of
HomePage ->
"/app/home"
DashboardPage ->
"/app/dashboard"
SearchPage bmId ->
case bmId of
Just id ->
"/app/search?bm=" ++ id
Nothing ->
"/app/search"
LoginPage data ->
case data.referrer of
@ -312,12 +349,14 @@ pathPrefix =
parser : Parser (Page -> a) a
parser =
oneOf
[ Parser.map HomePage
[ Parser.map DashboardPage
(oneOf
[ Parser.top
, s pathPrefix </> s "home"
, s pathPrefix
, s pathPrefix </> s "dashboard"
]
)
, Parser.map SearchPage (s pathPrefix </> s "search" <?> Query.string "bm")
, Parser.map LoginPage (s pathPrefix </> s "login" <?> loginPageParser)
, Parser.map ManageDataPage (s pathPrefix </> s "managedata")
, Parser.map CollectiveSettingPage (s pathPrefix </> s "csettings")

View File

@ -59,7 +59,7 @@ viewSidebar texts visible _ _ model =
[ Icons.sourceIcon2 ""
, span
[ class "ml-3" ]
[ text texts.sources ]
[ text texts.basics.sources ]
]
, a
[ href "#"
@ -70,7 +70,7 @@ viewSidebar texts visible _ _ model =
[ Icons.shareIcon ""
, span
[ class "ml-3" ]
[ text texts.shares ]
[ text texts.basics.shares ]
]
, a
[ href "#"
@ -238,7 +238,7 @@ viewSources texts flags settings model =
]
[ Icons.sourceIcon2 ""
, div [ class "ml-3" ]
[ text texts.sources
[ text texts.basics.sources
]
]
, Html.map SourceMsg (Comp.SourceManage.view2 texts.sourceManage flags settings model.sourceModel)
@ -253,7 +253,7 @@ viewShares texts settings flags model =
]
[ Icons.shareIcon ""
, div [ class "ml-3" ]
[ text texts.shares
[ text texts.basics.shares
]
]
, Html.map ShareMsg (Comp.ShareManage.view texts.shareManage settings flags model.shareModel)

View File

@ -0,0 +1,200 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Page.Dashboard.Data exposing
( Content(..)
, Model
, Msg(..)
, PageError(..)
, SideMenuModel
, init
, isDashboardDefault
, isDashboardVisible
, isHomeContent
, reinitCmd
, reloadDashboardData
, reloadUiSettings
)
import Api
import Comp.BookmarkChooser
import Comp.DashboardManage
import Comp.DashboardView
import Comp.EquipmentManage
import Comp.FolderManage
import Comp.NotificationHookManage
import Comp.OrgManage
import Comp.PeriodicQueryTaskManage
import Comp.PersonManage
import Comp.ShareManage
import Comp.SourceManage
import Comp.TagManage
import Comp.UploadForm
import Data.Bookmarks exposing (AllBookmarks)
import Data.Dashboard exposing (Dashboard)
import Data.Dashboards exposing (AllDashboards)
import Data.Flags exposing (Flags)
import Http
type alias SideMenuModel =
{ bookmarkChooser : Comp.BookmarkChooser.Model
}
type alias Model =
{ sideMenu : SideMenuModel
, content : Content
, pageError : Maybe PageError
, dashboards : AllDashboards
, isPredefined : Bool
}
type Msg
= GetBookmarksResp AllBookmarks
| GetAllDashboardsResp (Maybe Msg) (Result Http.Error AllDashboards)
| BookmarkMsg Comp.BookmarkChooser.Msg
| NotificationHookMsg Comp.NotificationHookManage.Msg
| PeriodicQueryMsg Comp.PeriodicQueryTaskManage.Msg
| SourceMsg Comp.SourceManage.Msg
| ShareMsg Comp.ShareManage.Msg
| OrganizationMsg Comp.OrgManage.Msg
| PersonMsg Comp.PersonManage.Msg
| EquipmentMsg Comp.EquipmentManage.Msg
| TagMsg Comp.TagManage.Msg
| FolderMsg Comp.FolderManage.Msg
| UploadMsg Comp.UploadForm.Msg
| DashboardMsg Comp.DashboardView.Msg
| DashboardManageMsg Comp.DashboardManage.Msg
| InitNotificationHook
| InitPeriodicQuery
| InitSource
| InitShare
| InitOrganization
| InitPerson
| InitEquipment
| InitTags
| InitFolder
| InitUpload
| InitEditDashboard
| ReloadDashboardData
| HardReloadDashboard
| SetDashboard Dashboard
| SetDashboardByName String
| SetDefaultDashboard
init : Flags -> ( Model, Cmd Msg )
init flags =
let
( dm, dc ) =
Comp.DashboardView.init flags Data.Dashboard.empty
in
( { sideMenu =
{ bookmarkChooser = Comp.BookmarkChooser.init Data.Bookmarks.empty
}
, content = Home dm
, pageError = Nothing
, dashboards = Data.Dashboards.emptyAll
, isPredefined = True
}
, Cmd.batch
[ initCmd flags
, Cmd.map DashboardMsg dc
]
)
initCmd : Flags -> Cmd Msg
initCmd flags =
makeInitCmd flags SetDefaultDashboard
reinitCmd : Flags -> Cmd Msg
reinitCmd flags =
makeInitCmd flags ReloadDashboardData
makeInitCmd : Flags -> Msg -> Cmd Msg
makeInitCmd flags nextMsg =
let
ignoreBookmarkError r =
Result.withDefault Data.Bookmarks.empty r
|> GetBookmarksResp
in
Cmd.batch
[ Api.getBookmarks flags ignoreBookmarkError
, Api.getAllDashboards flags (GetAllDashboardsResp (Just nextMsg))
]
reloadDashboardData : Msg
reloadDashboardData =
ReloadDashboardData
reloadUiSettings : Msg
reloadUiSettings =
HardReloadDashboard
--- Content
type Content
= Home Comp.DashboardView.Model
| Webhook Comp.NotificationHookManage.Model
| PeriodicQuery Comp.PeriodicQueryTaskManage.Model
| Source Comp.SourceManage.Model
| Share Comp.ShareManage.Model
| Organization Comp.OrgManage.Model
| Person Comp.PersonManage.Model
| Equipment Comp.EquipmentManage.Model
| Tags Comp.TagManage.Model
| Folder Comp.FolderManage.Model
| Upload Comp.UploadForm.Model
| Edit Comp.DashboardManage.Model
isHomeContent : Content -> Bool
isHomeContent cnt =
case cnt of
Home _ ->
True
_ ->
False
isDashboardVisible : Model -> String -> Bool
isDashboardVisible model name =
case model.content of
Home m ->
m.dashboard.name == name
Edit m ->
m.initData.dashboard.name == name
_ ->
False
isDashboardDefault : Model -> String -> Bool
isDashboardDefault model name =
Data.Dashboards.isDefaultAll name model.dashboards
--- Errors
type PageError
= PageErrorHttp Http.Error
| PageErrorNoDashboard
| PageErrorInvalid String

View File

@ -0,0 +1,143 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Page.Dashboard.DefaultDashboard exposing (getDefaultDashboard, value)
import Data.Box exposing (Box)
import Data.BoxContent exposing (BoxContent(..), SearchQuery(..), SummaryShow(..))
import Data.Dashboard exposing (Dashboard)
import Data.Flags exposing (Flags)
import Data.ItemColumn as IC
import Data.UiSettings exposing (UiSettings)
import Messages
import Messages.Page.DefaultDashboard exposing (Texts)
import Messages.UiLanguage
value : Texts -> Dashboard
value texts =
{ name = texts.default
, columns = 4
, gap = 2
, boxes =
[ messageBox texts
, fieldStats
, newDocuments texts
, dueDocuments texts
, upload
, summary texts
]
}
getDefaultDashboard : Flags -> UiSettings -> Dashboard
getDefaultDashboard flags settings =
let
lang =
Data.UiSettings.getUiLanguage flags settings Messages.UiLanguage.English
texts =
Messages.get lang
in
value texts.dashboard.defaultDashboard
--- Boxes
messageBox : Texts -> Box
messageBox texts =
{ name = texts.welcomeName
, visible = True
, decoration = False
, colspan = 4
, content =
BoxMessage
{ title = texts.welcomeTitle
, body = texts.welcomeBody
}
}
newDocuments : Texts -> Box
newDocuments texts =
{ name = texts.newDocsName
, visible = True
, decoration = True
, colspan = 2
, content =
BoxQuery
{ query = SearchQueryString "inbox:yes"
, limit = 5
, details = True
, showHeaders = False
, columns = []
}
}
dueDocuments : Texts -> Box
dueDocuments texts =
{ name = texts.dueInDays 10
, visible = True
, decoration = True
, colspan = 2
, content =
BoxQuery
{ query = SearchQueryString "due>today;-10d due<today;+10d"
, limit = 5
, details = True
, showHeaders = True
, columns =
[ IC.Name
, IC.Correspondent
, IC.DueDateShort
]
}
}
summary : Texts -> Box
summary texts =
{ name = texts.summaryName
, visible = True
, decoration = True
, colspan = 1
, content =
BoxStats
{ query = SearchQueryString ""
, show = SummaryShowGeneral
}
}
fieldStats : Box
fieldStats =
{ name = ""
, visible = True
, decoration = False
, colspan = 4
, content =
BoxStats
{ query = SearchQueryString ""
, show = SummaryShowFields False
}
}
upload : Box
upload =
{ name = ""
, visible = True
, decoration = True
, colspan = 3
, content =
BoxUpload
{ sourceId = Nothing
}
}

View File

@ -0,0 +1,180 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Page.Dashboard.SideMenu exposing (view)
import Api.Model.VersionInfo exposing (VersionInfo)
import Comp.BookmarkChooser
import Data.AccountScope
import Data.Dashboard exposing (Dashboard)
import Data.Dashboards
import Data.Icons as Icons
import Data.UiSettings exposing (UiSettings)
import Html exposing (Attribute, Html, a, div, h3, i, span, text)
import Html.Attributes exposing (class, classList, href, target, title)
import Html.Events exposing (onClick)
import Messages.Page.Dashboard exposing (Texts)
import Page exposing (Page(..))
import Page.Dashboard.Data exposing (Model, Msg(..), isDashboardDefault, isDashboardVisible, isHomeContent)
import Styles as S
view : Texts -> VersionInfo -> UiSettings -> Model -> Html Msg
view texts versionInfo _ model =
div [ class "flex flex-col flex-grow" ]
[ div [ class "mt-2" ]
[ menuLink [ onClick SetDefaultDashboard, href "#" ] (Icons.dashboardIcon "") texts.dashboardLink
, menuLink [ Page.href (SearchPage Nothing) ] (Icons.searchIcon "") texts.basics.items
, menuLink [ onClick InitUpload, href "#" ] (Icons.fileUploadIcon "") texts.uploadFiles
]
, h3
[ class S.header3
, class "italic mt-3"
]
[ text texts.bookmarks
]
, div [ class "ml-2" ]
[ Html.map BookmarkMsg
(Comp.BookmarkChooser.viewWith
{ showUser = True, showCollective = True, showShares = False }
texts.bookmarkChooser
model.sideMenu.bookmarkChooser
Comp.BookmarkChooser.emptySelection
)
]
, h3
[ class S.header3
, class "italic mt-3"
]
[ text texts.settings
]
, div [ class "ml-2 mb-2" ]
[ menuLink [ onClick InitNotificationHook, href "#" ] (Icons.notificationHooksIcon "") texts.basics.notificationHooks
, menuLink [ onClick InitPeriodicQuery, href "#" ] (Icons.periodicTasksIcon "") texts.basics.periodicQueries
, menuLink [ onClick InitSource, href "#" ] (Icons.sourceIcon2 "") texts.basics.sources
, menuLink [ onClick InitShare, href "#" ] (Icons.shareIcon "") texts.basics.shares
]
, h3
[ class S.header3
, class "italic mt-3"
]
[ text texts.manage
]
, div [ class "ml-2 mb-2" ]
[ menuLink [ onClick InitOrganization, href "#" ] (Icons.organizationIcon "") texts.basics.organization
, menuLink [ onClick InitPerson, href "#" ] (Icons.personIcon "") texts.basics.person
, menuLink [ onClick InitEquipment, href "#" ] (Icons.equipmentIcon "") texts.basics.equipment
, menuLink [ onClick InitTags, href "#" ] (Icons.tagsIcon "") texts.basics.tags
, menuLink [ onClick InitFolder, href "#" ] (Icons.folderIcon "") texts.basics.folder
]
, h3
[ class S.header3
, class "italic mt-3"
, classList [ ( "hidden", Data.Dashboards.countAll model.dashboards <= 1 ) ]
]
[ text texts.dashboards
]
, div
[ class "ml-2"
, classList [ ( "hidden", Data.Dashboards.countAll model.dashboards <= 1 ) ]
]
[ titleDiv <| texts.accountScope Data.AccountScope.User
, div
[ classList [ ( "hidden", Data.Dashboards.isEmpty model.dashboards.user ) ]
]
(Data.Dashboards.map (dashboardLink texts model) model.dashboards.user)
, titleDiv <| texts.accountScope Data.AccountScope.Collective
, div
[ classList [ ( "hidden", Data.Dashboards.isEmpty model.dashboards.collective ) ]
]
(Data.Dashboards.map (dashboardLink texts model) model.dashboards.collective)
]
, h3
[ class S.header3
, class "italic mt-3"
]
[ text texts.misc
]
, div [ class "ml-2" ]
[ menuLink
[ onClick InitEditDashboard
, classList [ ( "hidden", not (isHomeContent model.content) ) ]
, href "#"
]
(Icons.editIcon "")
texts.editDashboard
, div [ class "mt-2 opacity-75" ]
[ menuLink [ href Data.UiSettings.documentationSite, target "_blank" ]
(Icons.documentationIcon "")
texts.documentation
]
]
, div [ class "flex flex-grow items-end" ]
[ div [ class "text-center text-xs w-full opacity-50" ]
[ text "Docspell "
, text versionInfo.version
]
]
]
titleDiv : String -> Html msg
titleDiv label =
div [ class "text-sm opacity-75 py-0.5 italic" ]
[ text label
]
menuLinkStyle : String
menuLinkStyle =
"my-1 flex flex-row items-center rounded px-1 py-1 hover:bg-blue-100 dark:hover:bg-slate-600"
menuLink : List (Attribute Msg) -> Html Msg -> String -> Html Msg
menuLink attrs icon label =
a
(attrs ++ [ class menuLinkStyle ])
[ icon
, span [ class "ml-2" ]
[ text label
]
]
dashboardLink : Texts -> Model -> Dashboard -> Html Msg
dashboardLink texts model db =
let
( visible, default ) =
( isDashboardVisible model db.name
, isDashboardDefault model db.name
)
in
a
[ class menuLinkStyle
, classList [ ( "italic", visible ) ]
, href "#"
, onClick (SetDashboard db)
]
[ if visible then
i [ class "fa fa-check mr-2" ] []
else
i [ class "fa fa-columns mr-2" ] []
, div [ class "flex flex-row flex-grow space-x-1" ]
[ div [ class "flex flex-grow" ]
[ text db.name
]
, div [ class "opacity-50" ]
[ i
[ classList [ ( "hidden", not default ) ]
, class "fa fa-house-user"
, title texts.defaultDashboard.default
]
[]
]
]
]

View File

@ -0,0 +1,451 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Page.Dashboard.Update exposing (update)
import Api
import Browser.Navigation as Nav
import Comp.BookmarkChooser
import Comp.DashboardManage
import Comp.DashboardView
import Comp.EquipmentManage
import Comp.FolderManage
import Comp.NotificationHookManage
import Comp.OrgManage
import Comp.PeriodicQueryTaskManage
import Comp.PersonManage
import Comp.ShareManage
import Comp.SourceManage
import Comp.TagManage
import Comp.UploadForm
import Data.AccountScope
import Data.Dashboards
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Messages.Page.Dashboard exposing (Texts)
import Page exposing (Page(..))
import Page.Dashboard.Data exposing (..)
import Page.Dashboard.DefaultDashboard
import Set
update : Texts -> UiSettings -> Nav.Key -> Flags -> Msg -> Model -> ( Model, Cmd Msg, Sub Msg )
update texts settings navKey flags msg model =
let
nextRun amsg =
nextRunModel amsg model
nextRunModel amsg amodel =
update texts settings navKey flags amsg amodel
in
case msg of
GetBookmarksResp list ->
let
sideMenu =
model.sideMenu
in
unit
{ model | sideMenu = { sideMenu | bookmarkChooser = Comp.BookmarkChooser.init list } }
GetAllDashboardsResp next (Ok boards) ->
let
nextModel =
if Data.Dashboards.isEmptyAll boards then
{ model
| dashboards =
Data.Dashboards.singletonAll <|
Page.Dashboard.DefaultDashboard.value texts.defaultDashboard
, isPredefined = True
, pageError = Nothing
}
else
{ model | dashboards = boards, isPredefined = False, pageError = Nothing }
in
case next of
Just nextMsg ->
nextRunModel nextMsg nextModel
Nothing ->
unit nextModel
GetAllDashboardsResp _ (Err err) ->
unit { model | pageError = Just <| PageErrorHttp err }
BookmarkMsg lm ->
let
sideMenu =
model.sideMenu
( bm, sel ) =
Comp.BookmarkChooser.update
lm
sideMenu.bookmarkChooser
Comp.BookmarkChooser.emptySelection
bmId =
Set.toList sel.bookmarks |> List.head
in
( { model | sideMenu = { sideMenu | bookmarkChooser = bm } }
, Page.set navKey (SearchPage bmId)
, Sub.none
)
ReloadDashboardData ->
let
lm =
DashboardMsg Comp.DashboardView.reloadData
in
update texts settings navKey flags lm model
HardReloadDashboard ->
case model.content of
Home dm ->
let
board =
dm.dashboard
( dm_, dc ) =
Comp.DashboardView.init flags board
in
( { model | content = Home dm_ }, Cmd.map DashboardMsg dc, Sub.none )
_ ->
unit model
SetDashboard db ->
let
isVisible =
case model.content of
Home dm ->
dm.dashboard.name == db.name
_ ->
False
in
if isVisible then
update texts settings navKey flags ReloadDashboardData model
else
let
( dbm, dbc ) =
Comp.DashboardView.init flags db
in
( { model | content = Home dbm, pageError = Nothing }
, Cmd.map DashboardMsg dbc
, Sub.none
)
SetDefaultDashboard ->
case Data.Dashboards.getAllDefault model.dashboards of
Just db ->
nextRun (SetDashboard db)
Nothing ->
unit model
SetDashboardByName name ->
case Data.Dashboards.findInAll name model.dashboards of
Just db ->
nextRun (SetDashboard db)
Nothing ->
unit model
InitNotificationHook ->
let
( nhm, nhc ) =
Comp.NotificationHookManage.init flags
in
( { model | content = Webhook nhm }, Cmd.map NotificationHookMsg nhc, Sub.none )
InitPeriodicQuery ->
let
( pqm, pqc ) =
Comp.PeriodicQueryTaskManage.init flags
in
( { model | content = PeriodicQuery pqm }, Cmd.map PeriodicQueryMsg pqc, Sub.none )
InitSource ->
let
( sm, sc ) =
Comp.SourceManage.init flags
in
( { model | content = Source sm }, Cmd.map SourceMsg sc, Sub.none )
InitShare ->
let
( sm, sc ) =
Comp.ShareManage.init flags
in
( { model | content = Share sm }, Cmd.map ShareMsg sc, Sub.none )
InitOrganization ->
let
( om, oc ) =
Comp.OrgManage.init flags
in
( { model | content = Organization om }, Cmd.map OrganizationMsg oc, Sub.none )
InitPerson ->
let
( pm, pc ) =
Comp.PersonManage.init flags
in
( { model | content = Person pm }, Cmd.map PersonMsg pc, Sub.none )
InitEquipment ->
let
( em, ec ) =
Comp.EquipmentManage.init flags
in
( { model | content = Equipment em }, Cmd.map EquipmentMsg ec, Sub.none )
InitTags ->
let
( tm, tc ) =
Comp.TagManage.init flags
in
( { model | content = Tags tm }, Cmd.map TagMsg tc, Sub.none )
InitFolder ->
let
( fm, fc ) =
Comp.FolderManage.init flags
in
( { model | content = Folder fm }, Cmd.map FolderMsg fc, Sub.none )
InitUpload ->
let
um =
Comp.UploadForm.init
in
( { model | content = Upload um }, Cmd.none, Sub.none )
InitEditDashboard ->
case model.content of
Home m ->
let
default =
Data.Dashboards.isDefaultAll m.dashboard.name model.dashboards
scope =
Data.Dashboards.getScope m.dashboard.name model.dashboards
|> Maybe.withDefault Data.AccountScope.User
( dm, dc, ds ) =
Comp.DashboardManage.init
{ flags = flags
, dashboard = m.dashboard
, scope = scope
, isDefault = default
}
in
( { model | content = Edit dm }
, Cmd.map DashboardManageMsg dc
, Sub.map DashboardManageMsg ds
)
_ ->
unit model
NotificationHookMsg lm ->
case model.content of
Webhook nhm ->
let
( nhm_, nhc ) =
Comp.NotificationHookManage.update flags lm nhm
in
( { model | content = Webhook nhm_ }, Cmd.map NotificationHookMsg nhc, Sub.none )
_ ->
unit model
PeriodicQueryMsg lm ->
case model.content of
PeriodicQuery pqm ->
let
( pqm_, pqc, pqs ) =
Comp.PeriodicQueryTaskManage.update flags lm pqm
in
( { model | content = PeriodicQuery pqm_ }
, Cmd.map PeriodicQueryMsg pqc
, Sub.map PeriodicQueryMsg pqs
)
_ ->
unit model
SourceMsg lm ->
case model.content of
Source m ->
let
( sm, sc ) =
Comp.SourceManage.update flags lm m
in
( { model | content = Source sm }, Cmd.map SourceMsg sc, Sub.none )
_ ->
unit model
ShareMsg lm ->
case model.content of
Share m ->
let
( sm, sc, subs ) =
Comp.ShareManage.update texts.shareManage flags lm m
in
( { model | content = Share sm }
, Cmd.map ShareMsg sc
, Sub.map ShareMsg subs
)
_ ->
unit model
OrganizationMsg lm ->
case model.content of
Organization m ->
let
( om, oc ) =
Comp.OrgManage.update flags lm m
in
( { model | content = Organization om }, Cmd.map OrganizationMsg oc, Sub.none )
_ ->
unit model
PersonMsg lm ->
case model.content of
Person m ->
let
( pm, pc ) =
Comp.PersonManage.update flags lm m
in
( { model | content = Person pm }, Cmd.map PersonMsg pc, Sub.none )
_ ->
unit model
EquipmentMsg lm ->
case model.content of
Equipment m ->
let
( em, ec ) =
Comp.EquipmentManage.update flags lm m
in
( { model | content = Equipment em }, Cmd.map EquipmentMsg ec, Sub.none )
_ ->
unit model
TagMsg lm ->
case model.content of
Tags m ->
let
( tm, tc ) =
Comp.TagManage.update flags lm m
in
( { model | content = Tags tm }, Cmd.map TagMsg tc, Sub.none )
_ ->
unit model
FolderMsg lm ->
case model.content of
Folder m ->
let
( fm, fc ) =
Comp.FolderManage.update flags lm m
in
( { model | content = Folder fm }, Cmd.map FolderMsg fc, Sub.none )
_ ->
unit model
UploadMsg lm ->
case model.content of
Upload m ->
let
( um, uc, us ) =
Comp.UploadForm.update Nothing flags lm m
in
( { model | content = Upload um }, Cmd.map UploadMsg uc, Sub.map UploadMsg us )
_ ->
unit model
DashboardMsg lm ->
case model.content of
Home m ->
let
( dm, dc, ds ) =
Comp.DashboardView.update flags lm m
in
( { model | content = Home dm }, Cmd.map DashboardMsg dc, Sub.map DashboardMsg ds )
_ ->
unit model
DashboardManageMsg lm ->
case model.content of
Edit m ->
let
nameExists name =
Data.Dashboards.existsAll name model.dashboards
result =
Comp.DashboardManage.update flags nameExists lm m
in
case result.action of
Comp.DashboardManage.SubmitNone ->
( { model | content = Edit result.model }
, Cmd.map DashboardManageMsg result.cmd
, Sub.map DashboardManageMsg result.sub
)
Comp.DashboardManage.SubmitSaved name ->
( { model | content = Edit result.model }
, Cmd.batch
[ Cmd.map DashboardManageMsg result.cmd
, getDashboards flags (Just <| SetDashboardByName name)
]
, Sub.map DashboardManageMsg result.sub
)
Comp.DashboardManage.SubmitCancel name ->
case Data.Dashboards.findInAll name model.dashboards of
Just db ->
update texts settings navKey flags (SetDashboard db) model
Nothing ->
( { model | content = Edit result.model }
, Cmd.map DashboardManageMsg result.cmd
, Sub.map DashboardManageMsg result.sub
)
Comp.DashboardManage.SubmitDeleted ->
( { model | content = Edit result.model }
, Cmd.batch
[ Cmd.map DashboardManageMsg result.cmd
, getDashboards flags (Just SetDefaultDashboard)
]
, Sub.map DashboardManageMsg result.sub
)
_ ->
unit model
unit : Model -> ( Model, Cmd Msg, Sub Msg )
unit m =
( m, Cmd.none, Sub.none )
getDashboards : Flags -> Maybe Msg -> Cmd Msg
getDashboards flags nextMsg =
Api.getAllDashboards flags (GetAllDashboardsResp nextMsg)

View File

@ -0,0 +1,226 @@
{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Page.Dashboard.View exposing (viewContent, viewSidebar)
import Api.Model.VersionInfo exposing (VersionInfo)
import Comp.DashboardManage
import Comp.DashboardView
import Comp.EquipmentManage
import Comp.FolderManage
import Comp.NotificationHookManage
import Comp.OrgManage
import Comp.PeriodicQueryTaskManage
import Comp.PersonManage
import Comp.ShareManage
import Comp.SourceManage
import Comp.TagManage
import Comp.UploadForm
import Data.Flags exposing (Flags)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Messages.Page.Dashboard exposing (Texts)
import Page.Dashboard.Data exposing (..)
import Page.Dashboard.SideMenu as SideMenu
import Styles as S
viewSidebar : Texts -> Bool -> Flags -> VersionInfo -> UiSettings -> Model -> Html Msg
viewSidebar texts visible _ versionInfo settings model =
div
[ id "sidebar"
, class S.sidebar
, class S.sidebarBg
, classList [ ( "hidden", not visible ) ]
]
[ SideMenu.view texts versionInfo settings model
]
viewContent : Texts -> Flags -> UiSettings -> Model -> Html Msg
viewContent texts flags settings model =
let
editSettings =
{ showDeleteButton = not model.isPredefined
, showCopyButton = not model.isPredefined
}
in
div
[ id "content"
, class S.content
]
[ case model.content of
Home m ->
div [ class "mt-1" ]
[ Html.map DashboardMsg
(Comp.DashboardView.view texts.dashboard flags settings m)
]
Edit m ->
div [ class "mt-1" ]
[ div
[ class S.infoMessage
, class "my-1"
, classList [ ( "hidden", not model.isPredefined ) ]
]
[ text texts.predefinedMessage ]
, Html.map DashboardManageMsg
(Comp.DashboardManage.view texts.dashboardManage flags editSettings settings m)
]
Webhook m ->
viewHookManage texts settings m
PeriodicQuery m ->
viewPeriodicQuery texts settings m
Source m ->
viewSource texts flags settings m
Share m ->
viewShare texts flags settings m
Organization m ->
viewOrganization texts settings m
Person m ->
viewPerson texts settings m
Equipment m ->
viewEquipment texts m
Tags m ->
viewTags texts settings m
Folder m ->
viewFolder texts flags m
Upload m ->
viewUplod texts flags settings m
]
--- Helpers
viewUplod : Texts -> Flags -> UiSettings -> Comp.UploadForm.Model -> Html Msg
viewUplod texts flags settings model =
let
viewCfg =
{ showForm = True
, sourceId = Nothing
, lightForm = False
}
in
div []
[ h1 [ class S.header1 ]
[ text texts.uploadFiles
]
, Html.map UploadMsg <|
Comp.UploadForm.view texts.uploadForm viewCfg flags settings model
]
viewFolder : Texts -> Flags -> Comp.FolderManage.Model -> Html Msg
viewFolder texts flags model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.folder
]
, Html.map FolderMsg <|
Comp.FolderManage.view2 texts.folderManage flags model
]
viewTags : Texts -> UiSettings -> Comp.TagManage.Model -> Html Msg
viewTags texts settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.tags
]
, Html.map TagMsg <|
Comp.TagManage.view2 texts.tagManage settings model
]
viewEquipment : Texts -> Comp.EquipmentManage.Model -> Html Msg
viewEquipment texts model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.equipment
]
, Html.map EquipmentMsg <|
Comp.EquipmentManage.view2 texts.equipManage model
]
viewPerson : Texts -> UiSettings -> Comp.PersonManage.Model -> Html Msg
viewPerson texts settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.person
]
, Html.map PersonMsg <|
Comp.PersonManage.view2 texts.personManage settings model
]
viewOrganization : Texts -> UiSettings -> Comp.OrgManage.Model -> Html Msg
viewOrganization texts settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.organization
]
, Html.map OrganizationMsg <|
Comp.OrgManage.view2 texts.organizationManage settings model
]
viewShare : Texts -> Flags -> UiSettings -> Comp.ShareManage.Model -> Html Msg
viewShare texts flags settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.shares
]
, Html.map ShareMsg <|
Comp.ShareManage.view texts.shareManage settings flags model
]
viewSource : Texts -> Flags -> UiSettings -> Comp.SourceManage.Model -> Html Msg
viewSource texts flags settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.sources
]
, Html.map SourceMsg <|
Comp.SourceManage.view2 texts.sourceManage flags settings model
]
viewPeriodicQuery : Texts -> UiSettings -> Comp.PeriodicQueryTaskManage.Model -> Html Msg
viewPeriodicQuery texts settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.periodicQueries
]
, Html.map PeriodicQueryMsg <|
Comp.PeriodicQueryTaskManage.view texts.periodicQueryManage settings model
]
viewHookManage : Texts -> UiSettings -> Comp.NotificationHookManage.Model -> Html Msg
viewHookManage texts settings model =
div []
[ h1 [ class S.header1 ]
[ text texts.basics.notificationHooks
]
, Html.map NotificationHookMsg <|
Comp.NotificationHookManage.view texts.notificationHookManage settings model
]

View File

@ -60,7 +60,7 @@ update key flags inav settings msg model =
Cmd.none
_ ->
Page.set key HomePage
Page.set key (SearchPage Nothing)
in
{ model = { model | detail = result.model }
, cmd = Cmd.batch [ pageSwitch, Cmd.map ItemDetailMsg result.cmd ]

View File

@ -53,7 +53,7 @@ update loginData flags msg model =
AuthResp (Ok lr) ->
let
gotoRef =
Maybe.withDefault HomePage loginData.referrer |> Page.goto
Maybe.withDefault DashboardPage loginData.referrer |> Page.goto
in
if lr.success && not lr.requireSecondFactor then
( { model | formState = AuthSuccess lr, password = "" }

View File

@ -46,7 +46,7 @@ viewSidebar texts visible _ settings model =
, class S.sidebarLink
, menuEntryActive model TagTab
]
[ Icons.tagIcon2 ""
[ Icons.tagIcon ""
, span
[ class "ml-3" ]
[ text texts.basics.tags
@ -58,7 +58,7 @@ viewSidebar texts visible _ settings model =
, menuEntryActive model EquipTab
, class S.sidebarLink
]
[ Icons.equipmentIcon2 ""
[ Icons.equipmentIcon ""
, span
[ class "ml-3" ]
[ text texts.basics.equipment
@ -70,7 +70,7 @@ viewSidebar texts visible _ settings model =
, menuEntryActive model OrgTab
, class S.sidebarLink
]
[ Icons.organizationIcon2 ""
[ Icons.organizationIcon ""
, span
[ class "ml-3" ]
[ text texts.basics.organization
@ -82,7 +82,7 @@ viewSidebar texts visible _ settings model =
, menuEntryActive model PersonTab
, class S.sidebarLink
]
[ Icons.personIcon2 ""
[ Icons.personIcon ""
, span
[ class "ml-3" ]
[ text texts.basics.person
@ -99,7 +99,7 @@ viewSidebar texts visible _ settings model =
, menuEntryActive model FolderTab
, class S.sidebarLink
]
[ Icons.folderIcon2 ""
[ Icons.folderIcon ""
, span
[ class "ml-3" ]
[ text texts.basics.folder
@ -186,7 +186,7 @@ viewTags texts settings model =
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.tagIcon2 ""
[ Icons.tagIcon ""
, div [ class "ml-2" ]
[ text texts.basics.tags
]
@ -206,7 +206,7 @@ viewEquip texts model =
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.equipmentIcon2 ""
[ Icons.equipmentIcon ""
, div [ class "ml-2" ]
[ text texts.basics.equipment
]
@ -224,7 +224,7 @@ viewOrg texts settings model =
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.organizationIcon2 ""
[ Icons.organizationIcon ""
, div [ class "ml-2" ]
[ text texts.basics.organization
]
@ -243,7 +243,7 @@ viewPerson texts settings model =
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.personIcon2 ""
[ Icons.personIcon ""
, div [ class "ml-2" ]
[ text texts.basics.person
]
@ -262,7 +262,7 @@ viewFolder texts flags _ model =
[ class S.header1
, class "inline-flex items-center"
]
[ Icons.folderIcon2 ""
[ Icons.folderIcon ""
, div
[ class "ml-2"
]

View File

@ -5,7 +5,7 @@
-}
module Page.Home.Data exposing
module Page.Search.Data exposing
( ConfirmModalValue(..)
, Model
, Msg(..)
@ -48,7 +48,6 @@ import Data.Items
import Data.UiSettings exposing (UiSettings)
import Http
import Set exposing (Set)
import Throttle exposing (Throttle)
import Util.Html exposing (KeyCode(..))
import Util.ItemDragDrop as DD
@ -61,7 +60,6 @@ type alias Model =
, searchOffset : Int
, moreAvailable : Bool
, moreInProgress : Bool
, throttle : Throttle Msg
, searchTypeDropdownValue : SearchType
, lastSearchType : SearchType
, dragDropData : DD.DragDropData
@ -129,7 +127,6 @@ init flags viewMode =
, searchOffset = 0
, moreAvailable = True
, moreInProgress = False
, throttle = Throttle.create 1
, searchTypeDropdownValue =
if Comp.SearchMenu.isFulltextSearch searchMenuModel then
ContentOnlySearch
@ -199,6 +196,7 @@ editActive model =
type Msg
= Init
| DoNothing
| SearchMenuMsg Comp.SearchMenu.Msg
| ResetSearch
| ItemCardListMsg Comp.ItemCardList.Msg
@ -208,7 +206,6 @@ type Msg
| ToggleSearchMenu
| ToggleSelectView
| LoadMore
| UpdateThrottle
| SetBasicSearch String
| ToggleSearchType
| KeyUpSearchbarMsg (Maybe KeyCode)

View File

@ -5,7 +5,7 @@
-}
module Page.Home.SideMenu exposing (view)
module Page.Search.SideMenu exposing (view)
import Comp.Basic as B
import Comp.ItemDetail.MultiEditMenu
@ -16,8 +16,8 @@ import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Messages.Page.HomeSideMenu exposing (Texts)
import Page.Home.Data exposing (..)
import Messages.Page.SearchSideMenu exposing (Texts)
import Page.Search.Data exposing (..)
import Set
import Styles as S

View File

@ -5,7 +5,7 @@
-}
module Page.Home.Update exposing
module Page.Search.Update exposing
( UpdateResult
, update
)
@ -28,15 +28,13 @@ import Data.ItemSelection
import Data.Items
import Data.SearchMode exposing (SearchMode)
import Data.UiSettings exposing (UiSettings)
import Messages.Page.Home exposing (Texts)
import Messages.Page.Search exposing (Texts)
import Page exposing (Page(..))
import Page.Home.Data exposing (..)
import Page.Search.Data exposing (..)
import Process
import Scroll
import Set exposing (Set)
import Task
import Throttle
import Time
import Util.Html exposing (KeyCode(..))
import Util.ItemDragDrop as DD
import Util.Update
@ -50,8 +48,8 @@ type alias UpdateResult =
}
update : Maybe String -> Nav.Key -> Flags -> Texts -> UiSettings -> Msg -> Model -> UpdateResult
update mId key flags texts settings msg model =
update : Maybe String -> Maybe String -> Nav.Key -> Flags -> Texts -> UiSettings -> Msg -> Model -> UpdateResult
update bookmarkId mId key flags texts settings msg model =
case msg of
Init ->
let
@ -62,20 +60,28 @@ update mId key flags texts settings msg model =
, offset = 0
, scroll = True
}
setBookmark =
Maybe.map (\bmId -> SearchMenuMsg <| Comp.SearchMenu.SetBookmark bmId) bookmarkId
|> Maybe.withDefault DoNothing
in
makeResult <|
Util.Update.andThen3
[ update mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.Init)
[ update bookmarkId mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.Init)
, update bookmarkId mId key flags texts settings setBookmark
, doSearch searchParam
]
model
DoNothing ->
UpdateResult model Cmd.none Sub.none Nothing
ResetSearch ->
let
nm =
{ model | searchOffset = 0, powerSearchInput = Comp.PowerSearchInput.init }
in
update mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
update bookmarkId mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.ResetForm) nm
SearchMenuMsg m ->
let
@ -121,7 +127,7 @@ update mId key flags texts settings msg model =
SetLinkTarget lt ->
case linkTargetMsg lt of
Just m ->
update mId key flags texts settings m model
update bookmarkId mId key flags texts settings m model
Nothing ->
makeResult ( model, Cmd.none, Sub.none )
@ -193,7 +199,7 @@ update mId key flags texts settings msg model =
in
makeResult <|
Util.Update.andThen3
[ update mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.SetResults list))
[ update bookmarkId mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.SetResults list))
, if scroll then
scrollToCard mId
@ -215,7 +221,7 @@ update mId key flags texts settings msg model =
, moreAvailable = list.groups /= []
}
in
update mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.AddResults list)) m
update bookmarkId mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.AddResults list)) m
ItemSearchAddResp (Err _) ->
withSub
@ -319,30 +325,23 @@ update mId key flags texts settings msg model =
else
withSub ( model, Cmd.none )
UpdateThrottle ->
let
( newThrottle, cmd ) =
Throttle.update model.throttle
in
withSub ( { model | throttle = newThrottle }, cmd )
SetBasicSearch str ->
let
smMsg =
SearchMenuMsg (Comp.SearchMenu.SetTextSearch str)
in
update mId key flags texts settings smMsg model
update bookmarkId mId key flags texts settings smMsg model
ToggleSearchType ->
case model.searchTypeDropdownValue of
BasicSearch ->
update mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.SetFulltextSearch) model
update bookmarkId mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.SetFulltextSearch) model
ContentOnlySearch ->
update mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.SetNamesSearch) model
update bookmarkId mId key flags texts settings (SearchMenuMsg Comp.SearchMenu.SetNamesSearch) model
KeyUpSearchbarMsg (Just Enter) ->
update mId key flags texts settings (DoSearch model.searchTypeDropdownValue) model
update bookmarkId mId key flags texts settings (DoSearch model.searchTypeDropdownValue) model
KeyUpSearchbarMsg _ ->
withSub ( model, Cmd.none )
@ -653,7 +652,8 @@ update mId key flags texts settings msg model =
{ model | viewMode = nextView }
in
if result.outcome == Comp.ItemMerge.OutcomeMerged then
update mId
update bookmarkId
mId
key
flags
texts
@ -733,7 +733,8 @@ update mId key flags texts settings msg model =
{ model | viewMode = nextView }
in
if result.outcome == Comp.PublishItems.OutcomeDone then
update mId
update bookmarkId
mId
key
flags
texts
@ -853,7 +854,7 @@ update mId key flags texts settings msg model =
model_ =
{ model | viewMode = viewMode }
in
update mId key flags texts settings (DoSearch model.lastSearchType) model_
update bookmarkId mId key flags texts settings (DoSearch model.lastSearchType) model_
SearchStatsResp result ->
let
@ -863,7 +864,7 @@ update mId key flags texts settings msg model =
stats =
Result.withDefault model.searchStats result
in
update mId key flags texts settings lm { model | searchStats = stats }
update bookmarkId mId key flags texts settings lm { model | searchStats = stats }
TogglePreviewFullWidth ->
let
@ -905,16 +906,16 @@ update mId key flags texts settings msg model =
makeResult ( model_, cmd_, Sub.map PowerSearchMsg result.subs )
Comp.PowerSearchInput.SubmitSearch ->
update mId key flags texts settings (DoSearch model_.searchTypeDropdownValue) model_
update bookmarkId mId key flags texts settings (DoSearch model_.searchTypeDropdownValue) model_
KeyUpPowerSearchbarMsg (Just Enter) ->
update mId key flags texts settings (DoSearch model.searchTypeDropdownValue) model
update bookmarkId mId key flags texts settings (DoSearch model.searchTypeDropdownValue) model
KeyUpPowerSearchbarMsg _ ->
withSub ( model, Cmd.none )
RemoveItem id ->
update mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.RemoveItem id)) model
update bookmarkId mId key flags texts settings (ItemCardListMsg (Comp.ItemCardList.RemoveItem id)) model
TogglePublishCurrentQueryView ->
case createQuery model of
@ -1146,18 +1147,14 @@ doSearch param model =
searchCmd =
doSearchCmd param_ model
( newThrottle, cmd ) =
Throttle.try searchCmd model.throttle
in
withSub
( { model
| searchInProgress = cmd /= Cmd.none
| searchInProgress = True
, searchOffset = 0
, throttle = newThrottle
, lastSearchType = param.searchType
}
, cmd
, searchCmd
)
@ -1190,9 +1187,7 @@ withSub ( m, c ) =
makeResult
( m
, c
, Throttle.ifNeeded
(Time.every 500 (\_ -> UpdateThrottle))
m.throttle
, Sub.none
)

View File

@ -5,7 +5,7 @@
-}
module Page.Home.View2 exposing (viewContent, viewSidebar)
module Page.Search.View2 exposing (viewContent, viewSidebar)
import Api
import Comp.Basic as B
@ -27,10 +27,10 @@ import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Messages.Page.Home exposing (Texts)
import Messages.Page.Search exposing (Texts)
import Page exposing (Page(..))
import Page.Home.Data exposing (..)
import Page.Home.SideMenu
import Page.Search.Data exposing (..)
import Page.Search.SideMenu
import Set
import Styles as S
import Util.Html
@ -44,7 +44,7 @@ viewSidebar texts visible flags settings model =
, class S.sidebarBg
, classList [ ( "hidden", not visible ) ]
]
[ Page.Home.SideMenu.view texts.sideMenu flags settings model
[ Page.Search.SideMenu.view texts.sideMenu flags settings model
]
@ -563,7 +563,7 @@ editMenuBar texts model svm =
searchStats : Texts -> Flags -> UiSettings -> Model -> List (Html Msg)
searchStats texts _ settings model =
if settings.searchStatsVisible then
[ Comp.SearchStatsView.view2 texts.searchStatsView "my-2" model.searchStats
[ Comp.SearchStatsView.view texts.searchStatsView "my-2" model.searchStats
]
else

View File

@ -19,7 +19,7 @@ import Comp.SharePasswordForm
import Data.Flags exposing (Flags)
import Data.ItemArrange exposing (ItemArrange)
import Http
import Page.Home.Data exposing (Msg(..))
import Page.Search.Data exposing (Msg(..))
import Set exposing (Set)
import Util.Html exposing (KeyCode)

View File

@ -143,7 +143,7 @@ itemData texts flags model shareId itemId =
]
, div [ class boxStyle ]
[ div [ class headerStyle ]
[ Icons.tagsIcon2 "mr-2 ml-2"
[ Icons.tagsIcon "mr-2 ml-2"
, text texts.tagsAndFields
]
, div [ class "flex flex-row items-center flex-wrap font-medium my-1" ]

View File

@ -9,106 +9,27 @@ module Page.Upload.Data exposing
( Model
, Msg(..)
, emptyModel
, hasErrors
, isCompleted
, isDone
, isError
, isIdle
, isLoading
, isSuccessAll
, uploadAllTracker
, reset
)
import Api.Model.BasicResult exposing (BasicResult)
import Comp.Dropzone
import Comp.FixedDropdown
import Data.Language exposing (Language)
import Dict exposing (Dict)
import File exposing (File)
import Http
import Set exposing (Set)
import Util.File exposing (makeFileId)
import Comp.UploadForm
type alias Model =
{ incoming : Bool
, singleItem : Bool
, files : List File
, completed : Set String
, errored : Set String
, loading : Dict String Int
, dropzone : Comp.Dropzone.Model
, skipDuplicates : Bool
, languageModel : Comp.FixedDropdown.Model Language
, language : Maybe Language
{ uploadForm : Comp.UploadForm.Model
}
emptyModel : Model
emptyModel =
{ incoming = True
, singleItem = False
, files = []
, completed = Set.empty
, errored = Set.empty
, loading = Dict.empty
, dropzone = Comp.Dropzone.init []
, skipDuplicates = True
, languageModel =
Comp.FixedDropdown.init Data.Language.all
, language = Nothing
{ uploadForm = Comp.UploadForm.init
}
type Msg
= SubmitUpload
| SingleUploadResp String (Result Http.Error BasicResult)
| GotProgress String Http.Progress
| ToggleIncoming
| ToggleSingleItem
| Clear
| DropzoneMsg Comp.Dropzone.Msg
| ToggleSkipDuplicates
| LanguageMsg (Comp.FixedDropdown.Msg Language)
= UploadMsg Comp.UploadForm.Msg
isLoading : Model -> File -> Bool
isLoading model file =
Dict.member (makeFileId file) model.loading
isCompleted : Model -> File -> Bool
isCompleted model file =
Set.member (makeFileId file) model.completed
isError : Model -> File -> Bool
isError model file =
Set.member (makeFileId file) model.errored
isIdle : Model -> File -> Bool
isIdle model file =
not (isLoading model file || isCompleted model file || isError model file)
uploadAllTracker : String
uploadAllTracker =
"upload-all"
isDone : Model -> Bool
isDone model =
List.map makeFileId model.files
|> List.all (\id -> Set.member id model.completed || Set.member id model.errored)
isSuccessAll : Model -> Bool
isSuccessAll model =
List.map makeFileId model.files
|> List.all (\id -> Set.member id model.completed)
hasErrors : Model -> Bool
hasErrors model =
not (Set.isEmpty model.errored)
reset : Msg
reset =
UploadMsg Comp.UploadForm.reset

Some files were not shown because too many files have changed in this diff Show More