mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 10:58:26 +00:00
@ -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
|
||||
|
@ -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" ]
|
||||
|
474
modules/webapp/src/main/elm/Comp/BoxEdit.elm
Normal file
474
modules/webapp/src/main/elm/Comp/BoxEdit.elm
Normal 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)
|
99
modules/webapp/src/main/elm/Comp/BoxMessageEdit.elm
Normal file
99
modules/webapp/src/main/elm/Comp/BoxMessageEdit.elm
Normal 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
|
||||
]
|
||||
]
|
197
modules/webapp/src/main/elm/Comp/BoxQueryEdit.elm
Normal file
197
modules/webapp/src/main/elm/Comp/BoxQueryEdit.elm
Normal 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 = ""
|
||||
}
|
||||
]
|
||||
]
|
212
modules/webapp/src/main/elm/Comp/BoxQueryView.elm
Normal file
212
modules/webapp/src/main/elm/Comp/BoxQueryView.elm
Normal 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
|
299
modules/webapp/src/main/elm/Comp/BoxSearchQueryInput.elm
Normal file
299
modules/webapp/src/main/elm/Comp/BoxSearchQueryInput.elm
Normal 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)
|
||||
]
|
||||
]
|
202
modules/webapp/src/main/elm/Comp/BoxStatsEdit.elm
Normal file
202
modules/webapp/src/main/elm/Comp/BoxStatsEdit.elm
Normal 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
|
||||
}
|
||||
]
|
||||
]
|
186
modules/webapp/src/main/elm/Comp/BoxStatsView.elm
Normal file
186
modules/webapp/src/main/elm/Comp/BoxStatsView.elm
Normal 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
|
113
modules/webapp/src/main/elm/Comp/BoxUploadEdit.elm
Normal file
113
modules/webapp/src/main/elm/Comp/BoxUploadEdit.elm
Normal 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
|
||||
]
|
||||
]
|
70
modules/webapp/src/main/elm/Comp/BoxUploadView.elm
Normal file
70
modules/webapp/src/main/elm/Comp/BoxUploadView.elm
Normal 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)
|
||||
]
|
247
modules/webapp/src/main/elm/Comp/BoxView.elm
Normal file
247
modules/webapp/src/main/elm/Comp/BoxView.elm
Normal 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
|
||||
]
|
512
modules/webapp/src/main/elm/Comp/DashboardEdit.elm
Normal file
512
modules/webapp/src/main/elm/Comp/DashboardEdit.elm
Normal 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"
|
319
modules/webapp/src/main/elm/Comp/DashboardManage.elm
Normal file
319
modules/webapp/src/main/elm/Comp/DashboardManage.elm
Normal 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 ""
|
137
modules/webapp/src/main/elm/Comp/DashboardView.elm
Normal file
137
modules/webapp/src/main/elm/Comp/DashboardView.elm
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
]
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
86
modules/webapp/src/main/elm/Comp/ItemColumnDropdown.elm
Normal file
86
modules/webapp/src/main/elm/Comp/ItemColumnDropdown.elm
Normal 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)
|
43
modules/webapp/src/main/elm/Comp/ItemColumnView.elm
Normal file
43
modules/webapp/src/main/elm/Comp/ItemColumnView.elm
Normal 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)
|
||||
]
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ]
|
||||
]
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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" ] []
|
||||
|
@ -35,6 +35,7 @@ type LinkTarget
|
||||
| LinkTag IdName
|
||||
| LinkCustomField ItemFieldValue
|
||||
| LinkSource String
|
||||
| LinkBookmark String
|
||||
| LinkNone
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -85,6 +85,7 @@ init flags =
|
||||
, Cmd.batch
|
||||
[ Cmd.map FormMsg fc
|
||||
, Cmd.map MailMsg mc
|
||||
, Api.getShares flags "" True LoadSharesResp
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -69,6 +69,7 @@ init flags =
|
||||
[ Cmd.map FormMsg fc
|
||||
, Ports.initClipboard appClipboardData
|
||||
, Ports.initClipboard apiClipboardData
|
||||
, Api.getSources flags SourceResp
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
572
modules/webapp/src/main/elm/Comp/UploadForm.elm
Normal file
572
modules/webapp/src/main/elm/Comp/UploadForm.elm
Normal 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)
|
||||
]
|
||||
]
|
Reference in New Issue
Block a user