diff --git a/modules/webapp/src/main/elm/Comp/ItemMerge.elm b/modules/webapp/src/main/elm/Comp/ItemMerge.elm new file mode 100644 index 00000000..5498b5ba --- /dev/null +++ b/modules/webapp/src/main/elm/Comp/ItemMerge.elm @@ -0,0 +1,479 @@ +{- + Copyright 2020 Docspell Contributors + + SPDX-License-Identifier: GPL-3.0-or-later +-} + + +module Comp.ItemMerge exposing + ( Model + , Msg + , 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.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 + + + +--- Update + + +type alias UpdateResult = + { model : Model + , cmd : Cmd Msg + , done : Bool + } + + +notDoneResult : ( Model, Cmd Msg ) -> UpdateResult +notDoneResult t = + { model = Tuple.first t + , cmd = Tuple.second t + , done = False + } + + +type Msg + = ItemResp (Result Http.Error ItemLightList) + | ToggleInfoText + | DragDrop (DD.Msg Int Int) + | SubmitMerge + | CancelMerge + | MergeResp (Result Http.Error BasicResult) + + +update : Msg -> Model -> UpdateResult +update 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 + , done = True + } + + else + { model = { model | formState = FormStateError result.message } + , cmd = Cmd.none + , done = False + } + + MergeResp (Err err) -> + { model = { model | formState = FormStateHttp err } + , cmd = Cmd.none + , done = False + } + + 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 -> + notDoneResult ( model, Cmd.none ) + + CancelMerge -> + { model = model + , cmd = Cmd.none + , done = True + } + + +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 "py-2" + ] + [ text msg + ] + + FormStateHttp err -> + div + [ class S.errorMessage + , class "py-2" + ] + [ text (texts.httpError err) + ] + + FormStateMergeSuccessful -> + div + [ class S.successMessage + , class "py-2" + ] + [ text texts.mergeSuccessful + ] + + +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 diff --git a/modules/webapp/src/main/elm/Messages/Comp/ItemMerge.elm b/modules/webapp/src/main/elm/Messages/Comp/ItemMerge.elm new file mode 100644 index 00000000..2aa9d4c5 --- /dev/null +++ b/modules/webapp/src/main/elm/Messages/Comp/ItemMerge.elm @@ -0,0 +1,68 @@ +{- + Copyright 2020 Docspell Contributors + + SPDX-License-Identifier: GPL-3.0-or-later +-} + + +module Messages.Comp.ItemMerge exposing + ( Texts + , de + , gb + ) + +import Http +import Messages.Basics +import Messages.Comp.HttpError +import Messages.DateFormat +import Messages.UiLanguage + + +type alias Texts = + { basics : Messages.Basics.Texts + , httpError : Http.Error -> String + , title : String + , infoText : String + , deleteWarn : String + , formatDateLong : Int -> String + , formatDateShort : Int -> String + , submitMerge : String + , cancelMerge : String + , submitMergeTitle : String + , cancelMergeTitle : String + , mergeSuccessful : String + } + + +gb : Texts +gb = + { basics = Messages.Basics.gb + , httpError = Messages.Comp.HttpError.gb + , title = "Merge Items" + , infoText = "When merging items the first item in the list acts as the target. Every other items metadata is copied into the target item. If the property is a single value (like correspondent), it is only set if not already present. Tags, custom fields and attachments are added. The items can be reordered using drag&drop." + , deleteWarn = "Note that all items but the first one is deleted after a successful merge!" + , formatDateLong = Messages.DateFormat.formatDateLong Messages.UiLanguage.English + , formatDateShort = Messages.DateFormat.formatDateShort Messages.UiLanguage.English + , submitMerge = "Merge" + , submitMergeTitle = "Merge the documents now" + , cancelMerge = "Cancel" + , cancelMergeTitle = "Back to select view" + , mergeSuccessful = "Items merged successfully" + } + + +de : Texts +de = + { basics = Messages.Basics.de + , httpError = Messages.Comp.HttpError.de + , title = "Dokumente zusammenführen" + , infoText = "Beim Zusammenführen der Dokumente, wird das erste in der Liste als Zieldokument verwendet. Die Metadaten der anderen Dokumente werden der Reihe nach auf des Zieldokument geschrieben. Metadaten die nur einen Wert haben, werden nur gesetzt falls noch kein Wert existiert. Tags, Benutzerfelder und Anhänge werden zu dem Zieldokument hinzugefügt. Die Einträge können mit Drag&Drop umgeordnet werden." + , deleteWarn = "Bitte beachte, dass nach erfolgreicher Zusammenführung alle anderen Dokumente gelöscht werden!" + , formatDateLong = Messages.DateFormat.formatDateLong Messages.UiLanguage.German + , formatDateShort = Messages.DateFormat.formatDateShort Messages.UiLanguage.German + , submitMerge = "Zusammenführen" + , submitMergeTitle = "Dokumente jetzt zusammenführen" + , cancelMerge = "Abbrechen" + , cancelMergeTitle = "Zurück zur Auswahl" + , mergeSuccessful = "Die Dokumente wurden erfolgreich zusammengeführt." + } diff --git a/modules/webapp/src/main/elm/Messages/Page/Home.elm b/modules/webapp/src/main/elm/Messages/Page/Home.elm index 5aa8ecbf..05e4f962 100644 --- a/modules/webapp/src/main/elm/Messages/Page/Home.elm +++ b/modules/webapp/src/main/elm/Messages/Page/Home.elm @@ -13,6 +13,7 @@ module Messages.Page.Home exposing import Messages.Basics import Messages.Comp.ItemCardList +import Messages.Comp.ItemMerge import Messages.Comp.SearchStatsView import Messages.Page.HomeSideMenu @@ -22,6 +23,7 @@ type alias Texts = , itemCardList : Messages.Comp.ItemCardList.Texts , searchStatsView : Messages.Comp.SearchStatsView.Texts , sideMenu : Messages.Page.HomeSideMenu.Texts + , itemMerge : Messages.Comp.ItemMerge.Texts , contentSearch : String , searchInNames : String , selectModeTitle : String @@ -39,6 +41,7 @@ type alias Texts = , selectNone : String , resetSearchForm : String , exitSelectMode : String + , mergeItemsTitle : Int -> String } @@ -48,6 +51,7 @@ gb = , itemCardList = Messages.Comp.ItemCardList.gb , searchStatsView = Messages.Comp.SearchStatsView.gb , sideMenu = Messages.Page.HomeSideMenu.gb + , itemMerge = Messages.Comp.ItemMerge.gb , contentSearch = "Content search…" , searchInNames = "Search in names…" , selectModeTitle = "Select Mode" @@ -65,6 +69,7 @@ gb = , selectNone = "Select none" , resetSearchForm = "Reset search form" , exitSelectMode = "Exit Select Mode" + , mergeItemsTitle = \n -> "Merge " ++ String.fromInt n ++ " selected items" } @@ -74,6 +79,7 @@ de = , itemCardList = Messages.Comp.ItemCardList.de , searchStatsView = Messages.Comp.SearchStatsView.de , sideMenu = Messages.Page.HomeSideMenu.de + , itemMerge = Messages.Comp.ItemMerge.de , contentSearch = "Volltextsuche…" , searchInNames = "Suche in Namen…" , selectModeTitle = "Auswahlmodus" @@ -91,4 +97,5 @@ de = , selectNone = "Wähle alle Dokumente ab" , resetSearchForm = "Suchformular zurücksetzen" , exitSelectMode = "Auswahlmodus verlassen" + , mergeItemsTitle = \n -> String.fromInt n ++ " gewählte Dokumente zusammenführen" } diff --git a/modules/webapp/src/main/elm/Page/Home/Data.elm b/modules/webapp/src/main/elm/Page/Home/Data.elm index 8d8455f9..dfa6eb78 100644 --- a/modules/webapp/src/main/elm/Page/Home/Data.elm +++ b/modules/webapp/src/main/elm/Page/Home/Data.elm @@ -26,12 +26,14 @@ module Page.Home.Data exposing import Api import Api.Model.BasicResult exposing (BasicResult) +import Api.Model.ItemLight exposing (ItemLight) import Api.Model.ItemLightList exposing (ItemLightList) import Api.Model.SearchStats exposing (SearchStats) import Browser.Dom as Dom import Comp.ItemCardList import Comp.ItemDetail.FormChange exposing (FormChange) import Comp.ItemDetail.MultiEditMenu exposing (SaveNameState(..)) +import Comp.ItemMerge import Comp.LinkTarget exposing (LinkTarget) import Comp.PowerSearchInput import Comp.SearchMenu @@ -76,6 +78,7 @@ type alias SelectViewModel = , action : SelectActionMode , confirmModal : Maybe ConfirmModalValue , editModel : Comp.ItemDetail.MultiEditMenu.Model + , mergeModel : Comp.ItemMerge.Model , saveNameState : SaveNameState , saveCustomFieldState : Set String } @@ -87,6 +90,7 @@ initSelectViewModel = , action = NoneAction , confirmModal = Nothing , editModel = Comp.ItemDetail.MultiEditMenu.init + , mergeModel = Comp.ItemMerge.init [] , saveNameState = SaveSuccess , saveCustomFieldState = Set.empty } @@ -205,6 +209,8 @@ type Msg | ReprocessSelectedConfirmed | ClientSettingsSaveResp UiSettings (Result Http.Error BasicResult) | RemoveItem String + | MergeSelectedItems + | MergeItemsMsg Comp.ItemMerge.Msg type SearchType @@ -218,6 +224,7 @@ type SelectActionMode | EditSelected | ReprocessSelected | RestoreSelected + | MergeSelected type alias SearchParam = diff --git a/modules/webapp/src/main/elm/Page/Home/Update.elm b/modules/webapp/src/main/elm/Page/Home/Update.elm index 7c68f082..15ec2b7c 100644 --- a/modules/webapp/src/main/elm/Page/Home/Update.elm +++ b/modules/webapp/src/main/elm/Page/Home/Update.elm @@ -16,6 +16,7 @@ import Browser.Navigation as Nav import Comp.ItemCardList import Comp.ItemDetail.FormChange exposing (FormChange(..)) import Comp.ItemDetail.MultiEditMenu exposing (SaveNameState(..)) +import Comp.ItemMerge import Comp.LinkTarget exposing (LinkTarget) import Comp.PowerSearchInput import Comp.SearchMenu @@ -361,6 +362,7 @@ update mId key flags settings msg model = _ -> noSub ( model, Cmd.none ) + RestoreSelectedConfirmed -> case model.viewMode of SelectView svm -> @@ -383,7 +385,6 @@ update mId key flags settings msg model = _ -> noSub ( model, Cmd.none ) - DeleteAllResp (Ok res) -> if res.success then let @@ -535,6 +536,70 @@ update mId key flags settings msg model = _ -> noSub ( model, Cmd.none ) + MergeSelectedItems -> + case model.viewMode of + SelectView svm -> + if svm.action == MergeSelected then + noSub + ( { model + | viewMode = + SelectView + { svm + | action = NoneAction + , mergeModel = Comp.ItemMerge.init [] + } + } + , Cmd.none + ) + + else if svm.ids == Set.empty then + noSub ( model, Cmd.none ) + + else + let + ( mm, mc ) = + Comp.ItemMerge.initQuery + flags + model.searchMenuModel.searchMode + (Q.ItemIdIn (Set.toList svm.ids)) + in + noSub + ( { model + | viewMode = + SelectView + { svm + | action = MergeSelected + , mergeModel = mm + } + } + , Cmd.map MergeItemsMsg mc + ) + + _ -> + noSub ( model, Cmd.none ) + + MergeItemsMsg lmsg -> + case model.viewMode of + SelectView svm -> + let + result = + Comp.ItemMerge.update lmsg svm.mergeModel + + nextView = + if result.done then + SelectView { svm | action = NoneAction } + + else + SelectView { svm | mergeModel = result.model } + in + noSub + ( { model | viewMode = nextView } + , Cmd.map MergeItemsMsg result.cmd + ) + + _ -> + noSub ( model, Cmd.none ) + EditMenuMsg lmsg -> case model.viewMode of SelectView svm -> diff --git a/modules/webapp/src/main/elm/Page/Home/View2.elm b/modules/webapp/src/main/elm/Page/Home/View2.elm index ca0bc60a..ca287b23 100644 --- a/modules/webapp/src/main/elm/Page/Home/View2.elm +++ b/modules/webapp/src/main/elm/Page/Home/View2.elm @@ -10,6 +10,7 @@ module Page.Home.View2 exposing (viewContent, viewSidebar) import Comp.Basic as B import Comp.ConfirmModal import Comp.ItemCardList +import Comp.ItemMerge import Comp.MenuBar as MB import Comp.PowerSearchInput import Comp.SearchMenu @@ -50,7 +51,11 @@ viewContent texts flags settings model = ] (searchStats texts flags settings model ++ itemsBar texts flags settings model - ++ itemCardList texts flags settings model + ++ [ div [ class "relative" ] + (itemMergeView texts settings model + ++ itemCardList texts flags settings model + ) + ] ++ confirmModal texts model ) @@ -59,6 +64,27 @@ viewContent texts flags settings model = --- Helpers +itemMergeView : Texts -> UiSettings -> Model -> List (Html Msg) +itemMergeView texts settings model = + case model.viewMode of + SelectView svm -> + case svm.action of + MergeSelected -> + [ div + [ class S.dimmerMerge + ] + [ Html.map MergeItemsMsg + (Comp.ItemMerge.view texts.itemMerge settings svm.mergeModel) + ] + ] + + _ -> + [] + + _ -> + [] + + confirmModal : Texts -> Model -> List (Html Msg) confirmModal texts model = let @@ -251,6 +277,7 @@ editMenuBar texts model svm = , inputClass = [ ( btnStyle, True ) , ( "bg-gray-200 dark:bg-bluegray-600", svm.action == EditSelected ) + , ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Trashed ) ] } , MB.CustomButton @@ -261,6 +288,7 @@ editMenuBar texts model svm = , inputClass = [ ( btnStyle, True ) , ( "bg-gray-200 dark:bg-bluegray-600", svm.action == ReprocessSelected ) + , ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Trashed ) ] } , MB.CustomButton @@ -285,6 +313,17 @@ editMenuBar texts model svm = , ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Normal ) ] } + , MB.CustomButton + { tagger = MergeSelectedItems + , label = "" + , icon = Just "fa fa-less-than" + , title = texts.mergeItemsTitle selectCount + , inputClass = + [ ( btnStyle, True ) + , ( "bg-gray-200 dark:bg-bluegray-600", svm.action == MergeSelected ) + , ( "hidden", model.searchMenuModel.searchMode == Data.SearchMode.Trashed ) + ] + } ] , end = [ MB.CustomButton diff --git a/modules/webapp/src/main/elm/Styles.elm b/modules/webapp/src/main/elm/Styles.elm index e92b29fe..7191bd27 100644 --- a/modules/webapp/src/main/elm/Styles.elm +++ b/modules/webapp/src/main/elm/Styles.elm @@ -343,6 +343,11 @@ dimmerCard = " absolute top-0 left-0 w-full h-full bg-black bg-opacity-60 dark:bg-lightblue-900 dark:bg-opacity-60 z-30 flex flex-col items-center justify-center px-4 py-2 " +dimmerMerge : String +dimmerMerge = + " absolute top-0 left-0 w-full h-full bg-white bg-opacity-100 dark:bg-bluegray-800 dark:bg-opacity-100 z-40 flex flex-col" + + tableMain : String tableMain = "border-collapse table w-full" diff --git a/modules/webapp/src/main/elm/Util/Item.elm b/modules/webapp/src/main/elm/Util/Item.elm new file mode 100644 index 00000000..93572c84 --- /dev/null +++ b/modules/webapp/src/main/elm/Util/Item.elm @@ -0,0 +1,62 @@ +{- + Copyright 2020 Docspell Contributors + + SPDX-License-Identifier: GPL-3.0-or-later +-} + + +module Util.Item exposing + ( concTemplate + , corrTemplate + ) + +import Api.Model.ItemLight exposing (ItemLight) +import Data.Fields +import Data.ItemTemplate as IT exposing (ItemTemplate) +import Data.UiSettings exposing (UiSettings) + + +corrTemplate : UiSettings -> ItemTemplate +corrTemplate settings = + let + fieldHidden f = + Data.UiSettings.fieldHidden settings f + + hiddenTuple = + ( fieldHidden Data.Fields.CorrOrg, fieldHidden Data.Fields.CorrPerson ) + in + case hiddenTuple of + ( True, True ) -> + IT.empty + + ( True, False ) -> + IT.corrPerson + + ( False, True ) -> + IT.corrOrg + + ( False, False ) -> + IT.correspondent + + +concTemplate : UiSettings -> ItemTemplate +concTemplate settings = + let + fieldHidden f = + Data.UiSettings.fieldHidden settings f + + hiddenTuple = + ( fieldHidden Data.Fields.ConcPerson, fieldHidden Data.Fields.ConcEquip ) + in + case hiddenTuple of + ( True, True ) -> + IT.empty + + ( True, False ) -> + IT.concEquip + + ( False, True ) -> + IT.concPerson + + ( False, False ) -> + IT.concerning diff --git a/modules/webapp/src/main/elm/Util/List.elm b/modules/webapp/src/main/elm/Util/List.elm index 2063b538..03909fbf 100644 --- a/modules/webapp/src/main/elm/Util/List.elm +++ b/modules/webapp/src/main/elm/Util/List.elm @@ -6,7 +6,8 @@ module Util.List exposing - ( distinct + ( changePosition + , distinct , dropRight , find , findIndexed @@ -16,6 +17,47 @@ module Util.List exposing , sliding ) +import Html.Attributes exposing (list) + + +changePosition : Int -> Int -> List a -> List a +changePosition source target list = + let + len = + List.length list + + noChange = + source == target || source + 1 == target + + outOfBounds n = + n < 0 || n >= len + + concat el acc = + let + idx = + Tuple.first el + + ela = + Tuple.second el + in + if idx == source then + ( target, ela ) :: acc + + else if idx >= target then + ( idx + 1, ela ) :: acc + + else + ( idx, ela ) :: acc + in + if noChange || outOfBounds source || outOfBounds target then + list + + else + List.indexedMap Tuple.pair list + |> List.foldl concat [] + |> List.sortBy Tuple.first + |> List.map Tuple.second + get : List a -> Int -> Maybe a get list index =