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