Files
docspell/modules/webapp/src/main/elm/Comp/ItemCard.elm
2021-10-23 14:33:24 +02:00

624 lines
18 KiB
Elm

{-
Copyright 2020 Eike K. & Contributors
SPDX-License-Identifier: AGPL-3.0-or-later
-}
module Comp.ItemCard exposing
( Model
, Msg
, UpdateResult
, ViewConfig
, init
, update
, view2
)
import Api
import Api.Model.AttachmentLight exposing (AttachmentLight)
import Api.Model.HighlightEntry exposing (HighlightEntry)
import Api.Model.ItemLight exposing (ItemLight)
import Comp.LinkTarget exposing (LinkTarget(..))
import Data.Direction
import Data.Fields
import Data.Flags exposing (Flags)
import Data.Icons as Icons
import Data.ItemSelection exposing (ItemSelection)
import Data.ItemTemplate as IT
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Markdown
import Messages.Comp.ItemCard exposing (Texts)
import Page exposing (Page(..))
import Set exposing (Set)
import Styles as S
import Util.CustomField
import Util.ItemDragDrop as DD
import Util.List
import Util.Maybe
import Util.String
type alias Model =
{ previewAttach : Maybe AttachmentLight
}
type Msg
= CyclePreview ItemLight
| ToggleSelectItem (Set String) String
| ItemDDMsg DD.Msg
| SetLinkTarget LinkTarget
type alias ViewConfig =
{ selection : ItemSelection
, extraClasses : String
, previewUrl : AttachmentLight -> String
, previewUrlFallback : ItemLight -> String
, attachUrl : AttachmentLight -> String
, detailPage : ItemLight -> Page
}
type alias UpdateResult =
{ model : Model
, dragModel : DD.Model
, selection : ItemSelection
, linkTarget : LinkTarget
}
init : Model
init =
{ previewAttach = Nothing
}
currentAttachment : Model -> ItemLight -> Maybe AttachmentLight
currentAttachment model item =
Util.Maybe.or
[ model.previewAttach
, List.head item.attachments
]
currentPosition : Model -> ItemLight -> Int
currentPosition model item =
let
filter cur el =
cur.id == el.id
in
case model.previewAttach of
Just a ->
case Util.List.findIndexed (filter a) item.attachments of
Just ( _, n ) ->
n + 1
Nothing ->
1
Nothing ->
1
--- Update
update : DD.Model -> Msg -> Model -> UpdateResult
update ddm msg model =
case msg of
ItemDDMsg lm ->
let
ddd =
DD.update lm ddm
in
UpdateResult model ddd.model Data.ItemSelection.Inactive LinkNone
ToggleSelectItem ids id ->
let
newSet =
if Set.member id ids then
Set.remove id ids
else
Set.insert id ids
in
UpdateResult model ddm (Data.ItemSelection.Active newSet) LinkNone
CyclePreview item ->
let
mainAttach =
currentAttachment model item
next =
Util.List.findNext (\e -> Just e.id == Maybe.map .id mainAttach) item.attachments
in
UpdateResult { model | previewAttach = next }
ddm
Data.ItemSelection.Inactive
LinkNone
SetLinkTarget target ->
UpdateResult model ddm Data.ItemSelection.Inactive target
--- View2
view2 : Texts -> ViewConfig -> UiSettings -> Flags -> Model -> ItemLight -> Html Msg
view2 texts cfg settings flags model item =
let
isCreated =
item.state == "created"
isDeleted =
item.state == "deleted"
cardColor =
if isCreated then
"text-blue-500 dark:text-lightblue-500"
else if isDeleted then
"text-red-600 dark:text-orange-600"
else
""
fieldHidden f =
Data.UiSettings.fieldHidden settings f
cardAction =
case cfg.selection of
Data.ItemSelection.Inactive ->
[ Page.href (cfg.detailPage item)
]
Data.ItemSelection.Active ids ->
[ onClick (ToggleSelectItem ids item.id)
, href "#"
]
selectedDimmer =
div
[ classList
[ ( "hidden", not (isSelected cfg item.id) )
]
, class S.dimmerCard
, class "rounded-lg"
]
[ div [ class "text-9xl text-blue-400 hover:text-blue-500 dark:text-lightblue-300 dark:hover:text-lightblue-200" ]
[ a
cardAction
[ i [ class "fa fa-check-circle font-thin" ] []
]
]
]
in
div
([ class cfg.extraClasses
, class "ds-item-card relative hover:shadow-lg rounded-lg flex flex-col break-all"
, classList
[ ( "border border-gray-400 dark:border-bluegray-600 dark:hover:border-bluegray-500", not (isMultiSelectMode cfg) )
, ( "border-2 border-gray-800 border-dashed dark:border-lightblue-500", isMultiSelectMode cfg )
]
, id item.id
]
++ DD.draggable ItemDDMsg item.id
)
((if fieldHidden Data.Fields.PreviewImage then
[]
else
[ previewImage2 cfg settings cardAction model item
]
)
++ [ mainContent2 texts cardAction cardColor isCreated isDeleted settings cfg item
, metaDataContent2 texts settings item
, notesContent2 settings item
, fulltextResultsContent2 item
, previewMenu2 texts settings flags cfg model item (currentAttachment model item)
, selectedDimmer
]
)
fulltextResultsContent2 : ItemLight -> Html Msg
fulltextResultsContent2 item =
div
[ class "ds-card-search-hl flex flex-col text-sm px-2 bg-yellow-50 dark:bg-indigo-800 dark:bg-opacity-60 "
, classList
[ ( "hidden", item.highlighting == [] )
]
]
(List.map renderHighlightEntry2 item.highlighting)
templateCtx : Texts -> IT.TemplateContext
templateCtx texts =
{ dateFormatLong = texts.formatDateLong
, dateFormatShort = texts.formatDateShort
, directionLabel = texts.directionLabel
}
metaDataContent2 : Texts -> UiSettings -> ItemLight -> Html Msg
metaDataContent2 texts settings item =
let
fieldHidden f =
Data.UiSettings.fieldHidden settings f
in
div [ class "px-2 pb-1 flex flex-row items-center justify-between text-sm opacity-80" ]
[ div [ class "flex flex-row justify-between" ]
[ div
[ classList
[ ( "hidden", fieldHidden Data.Fields.Folder )
]
, class "hover:opacity-60"
, title texts.basics.folder
]
[ Icons.folderIcon2 "mr-2"
, Comp.LinkTarget.makeFolderLink item
[ ( "hover:opacity-60", True ) ]
SetLinkTarget
]
]
, div [ class "flex-grow" ] []
, div [ class "flex flex-row items-center justify-end" ]
[ div [ class "" ]
[ Icons.sourceIcon2 "mr-2"
, Comp.LinkTarget.makeSourceLink
[ ( "hover:opacity-60", True ) ]
SetLinkTarget
(IT.render IT.source (templateCtx texts) item)
]
]
]
notesContent2 : UiSettings -> ItemLight -> Html Msg
notesContent2 settings item =
div
[ classList
[ ( "hidden"
, settings.itemSearchNoteLength
<= 0
|| Util.String.isNothingOrBlank item.notes
)
]
, class "px-2 py-2 border-t dark:border-bluegray-600 opacity-50 text-sm"
]
[ Maybe.withDefault "" item.notes
|> Util.String.ellipsis settings.itemSearchNoteLength
|> text
]
mainContent2 :
Texts
-> List (Attribute Msg)
-> String
-> Bool
-> Bool
-> UiSettings
-> ViewConfig
-> ItemLight
-> Html Msg
mainContent2 texts _ cardColor isCreated isDeleted settings _ item =
let
dirIcon =
i
[ class (Data.Direction.iconFromMaybe2 item.direction)
, class "mr-2 w-4 text-center"
, classList [ ( "hidden", fieldHidden Data.Fields.Direction ) ]
, IT.render IT.direction (templateCtx texts) item |> title
]
[]
fieldHidden f =
Data.UiSettings.fieldHidden settings f
titlePattern =
settings.cardTitleTemplate.template
subtitlePattern =
settings.cardSubtitleTemplate.template
in
div
[ class "flex flex-col px-2 py-2 mb-auto" ]
[ div
[ classList
[ ( "hidden"
, fieldHidden Data.Fields.CorrOrg
&& fieldHidden Data.Fields.CorrPerson
)
]
, title texts.basics.correspondent
]
(Icons.correspondentIcon2 "mr-2 w-4 text-center"
:: Comp.LinkTarget.makeCorrLink item [ ( "hover:opacity-75", True ) ] SetLinkTarget
)
, div
[ classList
[ ( "hidden"
, fieldHidden Data.Fields.ConcPerson
&& fieldHidden Data.Fields.ConcEquip
)
]
, title texts.basics.concerning
]
(Icons.concernedIcon2 "mr-2 w-4 text-center"
:: Comp.LinkTarget.makeConcLink item [ ( "hover:opacity-75", True ) ] SetLinkTarget
)
, div
[ class "font-bold py-1 text-lg"
, classList [ ( "hidden", IT.render titlePattern (templateCtx texts) item == "" ) ]
]
[ IT.render titlePattern (templateCtx texts) item |> text
]
, div
[ classList
[ ( "absolute right-1 top-1 text-4xl", True )
, ( cardColor, True )
, ( "hidden", not isCreated )
]
, title texts.new
]
[ i [ class "ml-2 fa fa-exclamation-circle" ] []
]
, div
[ classList
[ ( "absolute right-1 top-1 text-4xl", True )
, ( cardColor, True )
, ( "hidden", not isDeleted )
]
, title texts.basics.deleted
]
[ i [ class "ml-2 fa fa-trash-alt" ] []
]
, div
[ classList
[ ( "opacity-75", True )
, ( "hidden", IT.render subtitlePattern (templateCtx texts) item == "" )
]
]
[ dirIcon
, IT.render subtitlePattern (templateCtx texts) item |> text
]
, div [ class "" ]
[ mainTagsAndFields2 settings item
]
]
mainTagsAndFields2 : UiSettings -> ItemLight -> Html Msg
mainTagsAndFields2 settings item =
let
fieldHidden f =
Data.UiSettings.fieldHidden settings f
hideTags =
item.tags == [] || fieldHidden Data.Fields.Tag
hideFields =
item.customfields == [] || fieldHidden Data.Fields.CustomFields
showTag tag =
Comp.LinkTarget.makeTagIconLink
tag
(i [ class "fa fa-tag mr-2" ] [])
[ ( "label ml-1 mt-1 font-semibold hover:opacity-75", True )
, ( Data.UiSettings.tagColorString2 tag settings, True )
]
SetLinkTarget
showField fv =
Comp.LinkTarget.makeCustomFieldLink2 fv
[ ( S.basicLabel, True )
, ( "ml-1 mt-1 font-semibold hover:opacity-75", True )
]
SetLinkTarget
renderFields =
if hideFields then
[]
else
List.sortBy Util.CustomField.nameOrLabel item.customfields
|> List.map showField
renderTags =
if hideTags then
[]
else
List.map showTag item.tags
in
div
[ classList
[ ( "flex flex-row items-center flex-wrap justify-end text-xs font-medium my-1", True )
, ( "hidden", hideTags && hideFields )
]
]
(renderFields ++ renderTags)
previewImage2 : ViewConfig -> UiSettings -> List (Attribute Msg) -> Model -> ItemLight -> Html Msg
previewImage2 cfg settings cardAction model item =
let
mainAttach =
currentAttachment model item
previewUrl =
Maybe.map cfg.previewUrl mainAttach
|> Maybe.withDefault (cfg.previewUrlFallback item)
in
a
([ class "overflow-hidden block bg-gray-50 dark:bg-bluegray-700 dark:bg-opacity-40 border-gray-400 dark:hover:border-bluegray-500 rounded-t-lg"
, class (Data.UiSettings.cardPreviewSize2 settings)
]
++ cardAction
)
[ img
[ class "preview-image mx-auto pt-1"
, classList
[ ( "rounded-t-lg w-full -mt-1", settings.cardPreviewFullWidth )
, ( Data.UiSettings.cardPreviewSize2 settings, not settings.cardPreviewFullWidth )
]
, src previewUrl
]
[]
]
previewMenu2 : Texts -> UiSettings -> Flags -> ViewConfig -> Model -> ItemLight -> Maybe AttachmentLight -> Html Msg
previewMenu2 texts settings flags cfg model item mainAttach =
let
pageCount =
Maybe.andThen .pageCount mainAttach
|> Maybe.withDefault 0
attachCount =
List.length item.attachments
fieldHidden f =
Data.UiSettings.fieldHidden settings f
mkAttachUrl attach =
Data.UiSettings.pdfUrl settings flags (cfg.attachUrl attach)
attachUrl =
Maybe.map mkAttachUrl mainAttach
|> Maybe.withDefault "/api/v1/sec/attachment/none"
dueDate =
IT.render IT.dueDateShort (templateCtx texts) item
dueDateLabel =
div
[ classList
[ ( " hidden"
, item.dueDate
== Nothing
|| fieldHidden Data.Fields.DueDate
)
]
, class "label font-semibold text-sm border-gray-300 dark:border-bluegray-600"
, title (texts.dueOn ++ " " ++ dueDate)
]
[ Icons.dueDateIcon2 "mr-2"
, text (" " ++ dueDate)
]
in
div [ class "px-2 py-1 flex flex-row flex-wrap bg-gray-50 dark:bg-bluegray-700 dark:bg-opacity-40 border-0 rounded-b-lg md:text-sm" ]
[ a
[ class S.secondaryBasicButtonPlain
, class "px-2 py-1 border rounded "
, href attachUrl
, target "_self"
, title texts.openAttachmentFile
]
[ i [ class "fa fa-eye" ] []
]
, a
[ class S.secondaryBasicButtonPlain
, class "px-2 py-1 border rounded ml-2"
, Page.href (cfg.detailPage item)
, title texts.gotoDetail
]
[ i [ class "fa fa-edit" ] []
]
, div
[ classList [ ( "hidden", attachCount > 1 && not (fieldHidden Data.Fields.PreviewImage) ) ]
, class "ml-2"
]
[ div [ class "px-2 rounded border border-gray-300 dark:border-bluegray-600 py-1" ]
[ text (String.fromInt pageCount)
, text "p."
]
]
, div
[ class "flex flex-row items-center ml-2"
, classList [ ( "hidden", attachCount <= 1 || fieldHidden Data.Fields.PreviewImage ) ]
]
[ a
[ class S.secondaryBasicButtonPlain
, class "px-2 py-1 border rounded-l block"
, title texts.cycleAttachments
, href "#"
, onClick (CyclePreview item)
]
[ i [ class "fa fa-arrow-right" ] []
]
, div [ class "px-2 rounded-r border-t border-r border-b border-gray-500 dark:border-bluegray-500 py-1" ]
[ currentPosition model item
|> String.fromInt
|> text
, text "/"
, text (attachCount |> String.fromInt)
, text ", "
, text (String.fromInt pageCount)
, text "p."
]
]
, div [ class "flex-grow" ] []
, div [ class "flex flex-row items-center justify-end" ]
[ dueDateLabel
]
]
renderHighlightEntry2 : HighlightEntry -> Html Msg
renderHighlightEntry2 entry =
let
stripWhitespace str =
String.trim str
|> String.replace "```" ""
|> String.replace "\t" " "
|> String.replace "\n\n" "\n"
|> String.lines
|> List.map String.trim
|> String.join "\n"
in
div [ class "content" ]
(div [ class "font-semibold" ]
[ i [ class "fa fa-caret-right mr-1 " ] []
, text (entry.name ++ ":")
]
:: List.map
(\str ->
Markdown.toHtml [ class "opacity-80 " ] <|
(stripWhitespace str ++ "")
)
entry.lines
)
--- Helpers
isSelected : ViewConfig -> String -> Bool
isSelected cfg id =
case cfg.selection of
Data.ItemSelection.Active ids ->
Set.member id ids
Data.ItemSelection.Inactive ->
False
isMultiSelectMode : ViewConfig -> Bool
isMultiSelectMode cfg =
case cfg.selection of
Data.ItemSelection.Active _ ->
True
Data.ItemSelection.Inactive ->
False