{- Copyright 2020 Docspell Contributors SPDX-License-Identifier: GPL-3.0-or-later -} module Comp.ItemMerge exposing ( Model , Msg , Outcome(..) , init , initQuery , update , view ) import Api import Api.Model.BasicResult exposing (BasicResult) import Api.Model.ItemLight exposing (ItemLight) import Api.Model.ItemLightList exposing (ItemLightList) import Comp.Basic import Comp.MenuBar as MB import Data.Direction import Data.Fields import Data.Flags exposing (Flags) import Data.Icons as Icons import Data.ItemQuery exposing (ItemQuery) import Data.ItemTemplate as IT import Data.SearchMode exposing (SearchMode) import Data.UiSettings exposing (UiSettings) import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (onClick) import Html5.DragDrop as DD import Http import Messages.Comp.ItemMerge exposing (Texts) import Styles as S import Util.CustomField import Util.Item import Util.List type alias Model = { items : List ItemLight , showInfoText : Bool , dragDrop : DDModel , formState : FormState } init : List ItemLight -> Model init items = { items = items , showInfoText = False , dragDrop = DD.init , formState = FormStateInitial } initQuery : Flags -> SearchMode -> ItemQuery -> ( Model, Cmd Msg ) initQuery flags searchMode query = let itemQuery = { offset = Just 0 , limit = Just 50 , withDetails = Just True , searchMode = Just (Data.SearchMode.asString searchMode) , query = Data.ItemQuery.render query } in ( init [], Api.itemSearch flags itemQuery ItemResp ) type alias Dropped = { sourceIdx : Int , targetIdx : Int } type alias DDModel = DD.Model Int Int type alias DDMsg = DD.Msg Int Int type FormState = FormStateInitial | FormStateHttp Http.Error | FormStateMergeSuccessful | FormStateError String | FormStateMergeInProcess --- Update type Outcome = OutcomeCancel | OutcomeMerged | OutcomeNotYet type alias UpdateResult = { model : Model , cmd : Cmd Msg , outcome : Outcome } notDoneResult : ( Model, Cmd Msg ) -> UpdateResult notDoneResult t = { model = Tuple.first t , cmd = Tuple.second t , outcome = OutcomeNotYet } type Msg = ItemResp (Result Http.Error ItemLightList) | ToggleInfoText | DragDrop (DD.Msg Int Int) | SubmitMerge | CancelMerge | MergeResp (Result Http.Error BasicResult) update : Flags -> Msg -> Model -> UpdateResult update flags msg model = case msg of ItemResp (Ok list) -> notDoneResult ( init (flatten list), Cmd.none ) ItemResp (Err err) -> notDoneResult ( { model | formState = FormStateHttp err }, Cmd.none ) MergeResp (Ok result) -> if result.success then { model = { model | formState = FormStateMergeSuccessful } , cmd = Cmd.none , outcome = OutcomeMerged } else { model = { model | formState = FormStateError result.message } , cmd = Cmd.none , outcome = OutcomeNotYet } MergeResp (Err err) -> { model = { model | formState = FormStateHttp err } , cmd = Cmd.none , outcome = OutcomeNotYet } ToggleInfoText -> notDoneResult ( { model | showInfoText = not model.showInfoText } , Cmd.none ) DragDrop lmsg -> let ( m, res ) = DD.update lmsg model.dragDrop dropped = Maybe.map (\( idx1, idx2, _ ) -> Dropped idx1 idx2) res model_ = { model | dragDrop = m } in case dropped of Just data -> let items = Util.List.changePosition data.sourceIdx data.targetIdx model.items in notDoneResult ( { model_ | items = items }, Cmd.none ) Nothing -> notDoneResult ( model_, Cmd.none ) SubmitMerge -> let ids = List.map .id model.items in notDoneResult ( { model | formState = FormStateMergeInProcess } , Api.mergeItems flags ids MergeResp ) CancelMerge -> { model = model , cmd = Cmd.none , outcome = OutcomeCancel } flatten : ItemLightList -> List ItemLight flatten list = list.groups |> List.concatMap .items --- View view : Texts -> UiSettings -> Model -> Html Msg view texts settings model = div [ class "px-2 mb-4" ] [ h1 [ class S.header1 ] [ text texts.title , a [ class "ml-2" , class S.link , href "#" , onClick ToggleInfoText ] [ i [ class "fa fa-info-circle" ] [] ] ] , p [ class S.infoMessage , classList [ ( "hidden", not model.showInfoText ) ] ] [ text texts.infoText ] , p [ class S.warnMessage , class "mt-2" ] [ text texts.deleteWarn ] , MB.view <| { start = [ MB.PrimaryButton { tagger = SubmitMerge , title = texts.submitMergeTitle , icon = Just "fa fa-less-than" , label = texts.submitMerge } , MB.SecondaryButton { tagger = CancelMerge , title = texts.cancelMergeTitle , icon = Just "fa fa-times" , label = texts.cancelMerge } ] , end = [] , rootClasses = "my-4" } , renderFormState texts model , div [ class "flex-col px-2" ] (List.indexedMap (itemCard texts settings model) model.items) ] itemCard : Texts -> UiSettings -> Model -> Int -> ItemLight -> Html Msg itemCard texts settings model index item = let previewUrl = Api.itemBasePreviewURL item.id fieldHidden f = Data.UiSettings.fieldHidden settings f 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 ] [] titlePattern = settings.cardTitleTemplate.template subtitlePattern = settings.cardSubtitleTemplate.template dropActive = let currentDrop = getDropId model currentDrag = getDragId model in currentDrop == Just index && currentDrag /= Just index && currentDrag /= Just (index - 1) in div ([ classList [ ( "pt-12 mx-2", dropActive ) ] ] ++ droppable DragDrop index ) [ div ([ class "flex flex-col sm:flex-row rounded" , class "cursor-pointer items-center" , classList [ ( "border-2 border-blue-500 dark:border-blue-500", index == 0 ) , ( "bg-blue-100 dark:bg-lightblue-900", index == 0 ) , ( "border border-gray-400 dark:border-bluegray-600 dark:hover:border-bluegray-500 bg-white dark:bg-bluegray-700 mt-2", index /= 0 ) , ( "bg-yellow-50 dark:bg-lime-900 mt-4", dropActive ) ] , id ("merge-" ++ item.id) ] ++ draggable DragDrop index ) [ div [ class "mr-2 sm:rounded-l w-16 bg-white" , classList [ ( "hidden", fieldHidden Data.Fields.PreviewImage ) ] ] [ img [ class "preview-image mx-auto pt-1" , classList [ ( "sm:rounded-l", True ) ] , src previewUrl ] [] ] , div [ class "flex-grow flex flex-col py-1 px-2" ] [ div [ class "flex flex-col sm:flex-row items-center" ] [ div [ class "font-bold text-lg" , classList [ ( "hidden", IT.render titlePattern (templateCtx texts) item == "" ) ] ] [ dirIcon , IT.render titlePattern (templateCtx texts) item |> text ] , div [ classList [ ( "opacity-75 sm:ml-2", True ) , ( "hidden", IT.render subtitlePattern (templateCtx texts) item == "" ) ] ] [ IT.render subtitlePattern (templateCtx texts) item |> text ] ] , mainData texts settings item , mainTagsAndFields2 settings item ] ] ] mainData : Texts -> UiSettings -> ItemLight -> Html Msg mainData texts settings item = let ctx = templateCtx texts corr = IT.render (Util.Item.corrTemplate settings) ctx item conc = IT.render (Util.Item.concTemplate settings) ctx item in div [ class "flex flex-row space-x-2" ] [ div [ classList [ ( "hidden", corr == "" ) ] ] [ Icons.correspondentIcon2 "mr-1" , text corr ] , div [ classList [ ( "hidden", conc == "" ) ] , class "ml-2" ] [ Icons.concernedIcon2 "mr-1" , text conc ] ] 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 = div [ class "label mt-1 font-semibold" , class (Data.UiSettings.tagColorString2 tag settings) ] [ i [ class "fa fa-tag mr-2" ] [] , span [] [ text tag.name ] ] showField fv = Util.CustomField.renderValue2 [ ( S.basicLabel, True ) , ( "mt-1 font-semibold", True ) ] Nothing fv 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 text-xs font-medium my-1 space-x-2", True ) , ( "hidden", hideTags && hideFields ) ] ] (renderFields ++ renderTags) renderFormState : Texts -> Model -> Html Msg renderFormState texts model = case model.formState of FormStateInitial -> span [ class "hidden" ] [] FormStateError msg -> div [ class S.errorMessage , class "my-2" ] [ text msg ] FormStateHttp err -> div [ class S.errorMessage , class "my-2" ] [ text (texts.httpError err) ] FormStateMergeSuccessful -> div [ class S.successMessage , class "my-2" ] [ text texts.mergeSuccessful ] FormStateMergeInProcess -> Comp.Basic.loadingDimmer { active = True , label = texts.mergeInProcess } templateCtx : Texts -> IT.TemplateContext templateCtx texts = { dateFormatLong = texts.formatDateLong , dateFormatShort = texts.formatDateShort , directionLabel = \_ -> "" } droppable : (DDMsg -> msg) -> Int -> List (Attribute msg) droppable tagger dropId = DD.droppable tagger dropId draggable : (DDMsg -> msg) -> Int -> List (Attribute msg) draggable tagger itemId = DD.draggable tagger itemId getDropId : Model -> Maybe Int getDropId model = DD.getDropId model.dragDrop getDragId : Model -> Maybe Int getDragId model = DD.getDragId model.dragDrop