mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 19:08:26 +00:00
First version of new ui based on tailwind
This drops fomantic-ui as css toolkit and introduces tailwindcss. With tailwind there are no predefined components, but it's very easy to create those. So customizing the look&feel is much simpler, most of the time no additional css is needed. This requires a complete rewrite of the markup + styles. Luckily all logic can be kept as is. The now old ui is not removed, it is still available by using a request header `Docspell-Ui` with a value of `1` for the old ui and `2` for the new ui. Another addition is "dev mode", where docspell serves assets with a no-cache header, to disable browser caching. This makes developing a lot easier.
This commit is contained in:
@ -6,6 +6,7 @@ module Comp.ItemCard exposing
|
||||
, init
|
||||
, update
|
||||
, view
|
||||
, view2
|
||||
)
|
||||
|
||||
import Api
|
||||
@ -25,6 +26,7 @@ import Html.Events exposing (onClick)
|
||||
import Markdown
|
||||
import Page exposing (Page(..))
|
||||
import Set exposing (Set)
|
||||
import Styles as S
|
||||
import Util.CustomField
|
||||
import Util.ItemDragDrop as DD
|
||||
import Util.List
|
||||
@ -91,6 +93,10 @@ currentPosition model item =
|
||||
1
|
||||
|
||||
|
||||
|
||||
--- Update
|
||||
|
||||
|
||||
update : DD.Model -> Msg -> Model -> UpdateResult
|
||||
update ddm msg model =
|
||||
case msg of
|
||||
@ -129,6 +135,10 @@ update ddm msg model =
|
||||
UpdateResult model ddm Data.ItemSelection.Inactive target
|
||||
|
||||
|
||||
|
||||
--- View
|
||||
|
||||
|
||||
view : ViewConfig -> UiSettings -> Model -> ItemLight -> Html Msg
|
||||
view cfg settings model item =
|
||||
let
|
||||
@ -236,7 +246,7 @@ metaDataContent settings item =
|
||||
, title "Correspondent"
|
||||
]
|
||||
(Icons.correspondentIcon ""
|
||||
:: Comp.LinkTarget.makeCorrLink item SetLinkTarget
|
||||
:: Comp.LinkTarget.makeCorrLink item [] SetLinkTarget
|
||||
)
|
||||
, div
|
||||
[ classList
|
||||
@ -249,7 +259,7 @@ metaDataContent settings item =
|
||||
, title "Concerning"
|
||||
]
|
||||
(Icons.concernedIcon
|
||||
:: Comp.LinkTarget.makeConcLink item SetLinkTarget
|
||||
:: Comp.LinkTarget.makeConcLink item [] SetLinkTarget
|
||||
)
|
||||
, div
|
||||
[ classList
|
||||
@ -259,7 +269,7 @@ metaDataContent settings item =
|
||||
, title "Folder"
|
||||
]
|
||||
[ Icons.folderIcon ""
|
||||
, Comp.LinkTarget.makeFolderLink item SetLinkTarget
|
||||
, Comp.LinkTarget.makeFolderLink item [] SetLinkTarget
|
||||
]
|
||||
]
|
||||
, div [ class "right floated meta" ]
|
||||
@ -545,6 +555,435 @@ renderHighlightEntry entry =
|
||||
]
|
||||
|
||||
|
||||
|
||||
--- View2
|
||||
|
||||
|
||||
view2 : ViewConfig -> UiSettings -> Model -> ItemLight -> Html Msg
|
||||
view2 cfg settings model item =
|
||||
let
|
||||
isConfirmed =
|
||||
item.state /= "created"
|
||||
|
||||
cardColor =
|
||||
if not isConfirmed then
|
||||
"text-blue-500 dark:text-lightblue-500"
|
||||
|
||||
else
|
||||
""
|
||||
|
||||
fieldHidden f =
|
||||
Data.UiSettings.fieldHidden settings f
|
||||
|
||||
cardAction =
|
||||
case cfg.selection of
|
||||
Data.ItemSelection.Inactive ->
|
||||
[ Page.href (ItemDetailPage item.id)
|
||||
]
|
||||
|
||||
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"
|
||||
, 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 settings cardAction model item
|
||||
]
|
||||
)
|
||||
++ [ mainContent2 cardAction cardColor isConfirmed settings cfg item
|
||||
, metaDataContent2 settings item
|
||||
, notesContent2 settings item
|
||||
, fulltextResultsContent2 item
|
||||
, previewMenu2 settings 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)
|
||||
|
||||
|
||||
metaDataContent2 : UiSettings -> ItemLight -> Html Msg
|
||||
metaDataContent2 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 "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 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 : List (Attribute Msg) -> String -> Bool -> UiSettings -> ViewConfig -> ItemLight -> Html Msg
|
||||
mainContent2 cardAction cardColor isConfirmed 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 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 "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 "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 item == "" ) ]
|
||||
]
|
||||
[ IT.render titlePattern item |> text
|
||||
]
|
||||
, div
|
||||
[ classList
|
||||
[ ( "absolute right-1 top-1 text-4xl", True )
|
||||
, ( cardColor, True )
|
||||
, ( "hidden", isConfirmed )
|
||||
]
|
||||
, title "New"
|
||||
]
|
||||
[ i [ class "ml-2 fa fa-exclamation-circle" ] []
|
||||
]
|
||||
, div
|
||||
[ classList
|
||||
[ ( "opacity-75", True )
|
||||
, ( "hidden", IT.render subtitlePattern item == "" )
|
||||
]
|
||||
]
|
||||
[ dirIcon
|
||||
, IT.render subtitlePattern 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 : UiSettings -> List (Attribute Msg) -> Model -> ItemLight -> Html Msg
|
||||
previewImage2 settings cardAction model item =
|
||||
let
|
||||
mainAttach =
|
||||
currentAttachment model item
|
||||
|
||||
previewUrl =
|
||||
Maybe.map .id mainAttach
|
||||
|> Maybe.map Api.attachmentPreviewURL
|
||||
|> Maybe.withDefault (Api.itemBasePreviewURL item.id)
|
||||
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 : UiSettings -> Model -> ItemLight -> Maybe AttachmentLight -> Html Msg
|
||||
previewMenu2 settings 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 id =
|
||||
if settings.nativePdfPreview then
|
||||
Api.fileURL id
|
||||
|
||||
else
|
||||
Api.fileURL id ++ "/view"
|
||||
|
||||
attachUrl =
|
||||
Maybe.map .id mainAttach
|
||||
|> Maybe.map mkAttachUrl
|
||||
|> Maybe.withDefault "/api/v1/sec/attachment/none"
|
||||
|
||||
dueDate =
|
||||
IT.render IT.dueDateShort 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 ("Due on " ++ 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 "Open attachment file"
|
||||
]
|
||||
[ i [ class "fa fa-eye" ] []
|
||||
]
|
||||
, a
|
||||
[ class S.secondaryBasicButtonPlain
|
||||
, class "px-2 py-1 border rounded ml-2"
|
||||
, Page.href (ItemDetailPage item.id)
|
||||
, title "Go to detail view"
|
||||
]
|
||||
[ 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 "Cycle attachments"
|
||||
, 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
|
||||
@ -553,3 +992,13 @@ isSelected cfg id =
|
||||
|
||||
Data.ItemSelection.Inactive ->
|
||||
False
|
||||
|
||||
|
||||
isMultiSelectMode : ViewConfig -> Bool
|
||||
isMultiSelectMode cfg =
|
||||
case cfg.selection of
|
||||
Data.ItemSelection.Active _ ->
|
||||
True
|
||||
|
||||
Data.ItemSelection.Inactive ->
|
||||
False
|
||||
|
Reference in New Issue
Block a user