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
117 changed files with 7943 additions and 941 deletions

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