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:
Eike Kettner
2021-01-29 20:48:27 +01:00
parent 442b76c5af
commit dd935454c9
140 changed files with 15077 additions and 214 deletions

View File

@ -0,0 +1,144 @@
module Comp.ItemDetail.AddFilesForm exposing (view)
import Comp.Dropzone
import Comp.ItemDetail.Model exposing (..)
import Comp.Progress
import Data.DropdownStyle
import Dict
import File exposing (File)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onCheck, onClick, onInput)
import Set
import Styles as S
import Util.File exposing (makeFileId)
import Util.Size
view : Model -> Html Msg
view model =
div
[ classList
[ ( "hidden", not model.addFilesOpen )
]
, class "flex flex-col px-2 py-2 mb-4"
, class S.box
]
[ div [ class "text-lg font-bold" ]
[ text "Add more files to this item"
]
, Html.map AddFilesMsg
(Comp.Dropzone.view2 model.addFilesModel)
, div [ class "flex flex-row space-x-2 mt-2" ]
[ button
[ class S.primaryButton
, href "#"
, onClick AddFilesSubmitUpload
]
[ text "Submit"
]
, button
[ class S.secondaryButton
, href "#"
, onClick AddFilesReset
]
[ text "Reset"
]
]
, div
[ classList
[ ( S.successMessage, True )
, ( "hidden", model.selectedFiles == [] || not (isSuccessAll model) )
]
, class "mt-2"
]
[ text "All files have been uploaded. They are being processed, some data "
, text "may not be available immediately. "
, a
[ class S.successMessageLink
, href "#"
, onClick ReloadItem
]
[ text "Refresh now"
]
]
, div
[ class "flex flex-col mt-2"
, classList [ ( "hidden", List.isEmpty model.selectedFiles || isSuccessAll model ) ]
]
(List.map (renderFileItem model) model.selectedFiles)
]
renderFileItem : Model -> File -> Html Msg
renderFileItem model file =
let
name =
File.name file
size =
File.size file
|> toFloat
|> Util.Size.bytesReadable Util.Size.B
getProgress =
let
key =
makeFileId file
in
Dict.get key model.loading
|> Maybe.withDefault 0
in
div [ class "flex flex-col" ]
[ div [ class "flex flex-row items-center" ]
[ div [ class "inline-flex items-center" ]
[ i
[ classList
[ ( "mr-2 text-lg", True )
, ( "fa fa-file font-thin", isIdle model file )
, ( "fa fa-spinner animate-spin ", isLoading model file )
, ( "fa fa-check ", isCompleted model file )
, ( "fa fa-bolt", isError model file )
]
]
[]
, div [ class "middle aligned content" ]
[ div [ class "header" ]
[ text name
]
]
]
, div [ class "flex-grow inline-flex justify-end" ]
[ text size
]
]
, div [ class "h-4" ]
[ Comp.Progress.progress2 getProgress
]
]
isSuccessAll : Model -> Bool
isSuccessAll model =
List.map makeFileId model.selectedFiles
|> List.all (\id -> Set.member id model.completed)
isIdle : Model -> File -> Bool
isIdle model file =
not (isLoading model file || isCompleted model file || isError model file)
isLoading : Model -> File -> Bool
isLoading model file =
Dict.member (makeFileId file) model.loading
isCompleted : Model -> File -> Bool
isCompleted model file =
Set.member (makeFileId file) model.completed
isError : Model -> File -> Bool
isError model file =
Set.member (makeFileId file) model.errored

View File

@ -0,0 +1,429 @@
module Comp.ItemDetail.EditForm exposing (formTabs, view2)
import Comp.CustomFieldMultiInput
import Comp.DatePicker
import Comp.Dropdown
import Comp.ItemDetail.FieldTabState as FTabState
import Comp.ItemDetail.Model
exposing
( Model
, Msg(..)
, NotesField(..)
, SaveNameState(..)
, personMatchesOrg
)
import Comp.KeyInput
import Comp.Tabs as TB
import Data.DropdownStyle
import Data.Fields
import Data.Icons as Icons
import Data.UiSettings exposing (UiSettings)
import Dict
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Markdown
import Page exposing (Page(..))
import Set exposing (Set)
import Styles as S
import Util.Folder
import Util.Time
view2 : UiSettings -> Model -> Html Msg
view2 settings model =
let
keyAttr =
if settings.itemDetailShortcuts then
Comp.KeyInput.eventsM KeyInputMsg
else
[]
tabStyle =
TB.searchMenuStyle
tabs =
formTabs settings model
allTabNames =
List.map .title tabs
|> Set.fromList
in
div (class "flex flex-col relative" :: keyAttr)
[ TB.akkordion tabStyle
(tabState settings allTabNames model)
tabs
]
formTabs : UiSettings -> Model -> List (TB.Tab Msg)
formTabs settings model =
let
dds =
Data.DropdownStyle.sidebarStyle
addIconLink tip m =
a
[ class "float-right"
, href "#"
, title tip
, onClick m
, class S.link
]
[ i [ class "fa fa-plus" ] []
]
editIconLink tip dm m =
a
[ classList
[ ( "hidden", Comp.Dropdown.notSelected dm )
]
, href "#"
, class "float-right mr-2"
, class S.link
, title tip
, onClick m
]
[ i [ class "fa fa-pencil-alt" ] []
]
fieldVisible field =
Data.UiSettings.fieldVisible settings field
customFieldSettings =
Comp.CustomFieldMultiInput.ViewSettings
True
"field"
(\f -> Dict.get f.id model.customFieldSavingIcon)
optional fields html =
if
List.map fieldVisible fields
|> List.foldl (||) False
then
html
else
span [ class "invisible hidden" ] []
in
[ { title = "Name"
, info = Nothing
, body =
[ div [ class "relative mb-4" ]
[ input
[ type_ "text"
, value model.nameModel
, onInput SetName
, class S.textInputSidebar
, class "pr-10"
]
[]
, span [ class S.inputLeftIconOnly ]
[ i
[ classList
[ ( "text-green-500 fa fa-check", model.nameState == SaveSuccess )
, ( "text-red-500 fa fa-exclamation-triangle", model.nameState == SaveFailed )
, ( "sync fa fa-circle-notch animate-spin", model.nameState == Saving )
]
]
[]
]
]
]
}
, { title = "Date"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ div [ class "relative" ]
[ Html.map ItemDatePickerMsg
(Comp.DatePicker.viewTimeDefault
model.itemDate
model.itemDatePicker
)
, a
[ class "ui icon button"
, href "#"
, class S.inputLeftIconLinkSidebar
, onClick RemoveDate
]
[ i [ class "fa fa-trash-alt font-thin" ] []
]
, Icons.dateIcon2 S.dateInputIcon
]
, renderItemDateSuggestions model
]
]
}
, { title = "Tags"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ Html.map TagDropdownMsg (Comp.Dropdown.view2 dds settings model.tagModel)
]
]
}
, { title = "Folder"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ Html.map FolderDropdownMsg
(Comp.Dropdown.view2
dds
settings
model.folderModel
)
, div
[ classList
[ ( S.message, True )
, ( "hidden", isFolderMember model )
]
]
[ Markdown.toHtml [] """
You are **not a member** of this folder. This item will be **hidden**
from any search now. Use a folder where you are a member of to make this
item visible. This message will disappear then.
"""
]
]
]
}
, { title = "Custom Fields"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ Html.map CustomFieldMsg
(Comp.CustomFieldMultiInput.view2
dds
customFieldSettings
model.customFieldsModel
)
]
]
}
, { title = "Due Date"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ div [ class "relative" ]
[ Html.map DueDatePickerMsg
(Comp.DatePicker.viewTimeDefault
model.dueDate
model.dueDatePicker
)
, a
[ class "ui icon button"
, href "#"
, class S.inputLeftIconLinkSidebar
, onClick RemoveDueDate
]
[ i [ class "fa fa-trash-alt font-thin" ] []
]
, Icons.dueDateIcon2 S.dateInputIcon
]
, renderDueDateSuggestions model
]
]
}
, { title = "Correspondent"
, info = Nothing
, body =
[ optional [ Data.Fields.CorrOrg ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.organizationIcon2 "mr-2"
, text "Organization"
, addIconLink "Add new organization" StartCorrOrgModal
, editIconLink "Edit organization" model.corrOrgModel StartEditCorrOrgModal
]
, Html.map OrgDropdownMsg (Comp.Dropdown.view2 dds settings model.corrOrgModel)
, renderOrgSuggestions model
]
, optional [ Data.Fields.CorrPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 "mr-2"
, text "Person"
, addIconLink "Add new correspondent person" StartCorrPersonModal
, editIconLink "Edit person"
model.corrPersonModel
(StartEditPersonModal model.corrPersonModel)
]
, Html.map CorrPersonMsg (Comp.Dropdown.view2 dds settings model.corrPersonModel)
, renderCorrPersonSuggestions model
, div
[ classList
[ ( "hidden", personMatchesOrg model )
]
, class S.message
, class "my-2"
]
[ i [ class "fa fa-info mr-2 " ] []
, text "The selected person doesn't belong to the selected organization."
]
]
]
}
, { title = "Concerning"
, info = Nothing
, body =
[ optional [ Data.Fields.ConcPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 "mr-2"
, text "Person"
, addIconLink "Add new concerning person" StartConcPersonModal
, editIconLink "Edit person"
model.concPersonModel
(StartEditPersonModal model.concPersonModel)
]
, Html.map ConcPersonMsg
(Comp.Dropdown.view2
dds
settings
model.concPersonModel
)
, renderConcPersonSuggestions model
]
, optional [ Data.Fields.ConcEquip ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.equipmentIcon2 "mr-2"
, text "Equipment"
, addIconLink "Add new equipment" StartEquipModal
, editIconLink "Edit equipment"
model.concEquipModel
StartEditEquipModal
]
, Html.map ConcEquipMsg
(Comp.Dropdown.view2
dds
settings
model.concEquipModel
)
, renderConcEquipSuggestions model
]
]
}
, { title = "Direction"
, info = Nothing
, body =
[ div [ class "mb-4" ]
[ Html.map DirDropdownMsg
(Comp.Dropdown.view2
dds
settings
model.directionModel
)
]
]
}
]
renderSuggestions : Model -> (a -> String) -> List a -> (a -> Msg) -> Html Msg
renderSuggestions model mkName idnames tagger =
div
[ classList
[ ( "hidden", model.item.state /= "created" )
]
, class "flex flex-col text-sm"
]
[ div [ class "font-bold my-1" ]
[ text "Suggestions"
]
, ul [ class "list-disc ml-6" ] <|
(idnames
|> List.map
(\p ->
li []
[ a
[ class S.link
, href "#"
, onClick (tagger p)
]
[ text (mkName p) ]
]
)
)
]
renderOrgSuggestions : Model -> Html Msg
renderOrgSuggestions model =
renderSuggestions model
.name
(List.take 6 model.itemProposals.corrOrg)
SetCorrOrgSuggestion
renderCorrPersonSuggestions : Model -> Html Msg
renderCorrPersonSuggestions model =
renderSuggestions model
.name
(List.take 6 model.itemProposals.corrPerson)
SetCorrPersonSuggestion
renderConcPersonSuggestions : Model -> Html Msg
renderConcPersonSuggestions model =
renderSuggestions model
.name
(List.take 6 model.itemProposals.concPerson)
SetConcPersonSuggestion
renderConcEquipSuggestions : Model -> Html Msg
renderConcEquipSuggestions model =
renderSuggestions model
.name
(List.take 6 model.itemProposals.concEquipment)
SetConcEquipSuggestion
renderItemDateSuggestions : Model -> Html Msg
renderItemDateSuggestions model =
renderSuggestions model
Util.Time.formatDate
(List.take 6 model.itemProposals.itemDate)
SetItemDateSuggestion
renderDueDateSuggestions : Model -> Html Msg
renderDueDateSuggestions model =
renderSuggestions model
Util.Time.formatDate
(List.take 6 model.itemProposals.dueDate)
SetDueDateSuggestion
--- Helpers
isFolderMember : Model -> Bool
isFolderMember model =
let
selected =
Comp.Dropdown.getSelected model.folderModel
|> List.head
|> Maybe.map .id
in
Util.Folder.isFolderMember model.allFolders selected
tabState : UiSettings -> Set String -> Model -> TB.Tab Msg -> ( TB.State, Msg )
tabState settings allNames model =
let
openTabs =
if model.item.state == "created" then
allNames
else
model.editMenuTabsOpen
in
FTabState.tabState settings
openTabs
model.customFieldsModel
(.title >> ToggleAkkordionTab)

View File

@ -0,0 +1,62 @@
module Comp.ItemDetail.FieldTabState exposing (tabState)
import Comp.CustomFieldMultiInput
import Comp.Tabs as TB
import Data.Fields
import Data.UiSettings exposing (UiSettings)
import Set exposing (Set)
tabState :
UiSettings
-> Set String
-> Comp.CustomFieldMultiInput.Model
-> (TB.Tab msg -> msg)
-> TB.Tab msg
-> ( TB.State, msg )
tabState settings openTabs cfmodel toggle tab =
let
isHidden f =
Data.UiSettings.fieldHidden settings f
hidden =
case tab.title of
"Tags" ->
isHidden Data.Fields.Tag
"Folder" ->
isHidden Data.Fields.Folder
"Correspondent" ->
isHidden Data.Fields.CorrOrg && isHidden Data.Fields.CorrPerson
"Concerning" ->
isHidden Data.Fields.ConcEquip && isHidden Data.Fields.ConcPerson
"Custom Fields" ->
isHidden Data.Fields.CustomFields
|| Comp.CustomFieldMultiInput.isEmpty cfmodel
"Date" ->
isHidden Data.Fields.Date
"Due Date" ->
isHidden Data.Fields.DueDate
"Direction" ->
isHidden Data.Fields.Direction
_ ->
False
state =
if hidden then
TB.Hidden
else if Set.member tab.title openTabs then
TB.Open
else
TB.Closed
in
( state, toggle tab )

View File

@ -0,0 +1,198 @@
module Comp.ItemDetail.ItemInfoHeader exposing (view)
import Api.Model.IdName exposing (IdName)
import Comp.ItemDetail.Model
exposing
( Model
, Msg(..)
, NotesField(..)
, SaveNameState(..)
)
import Comp.LinkTarget
import Data.Direction
import Data.Fields
import Data.Icons as Icons
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Page exposing (Page(..))
import Styles as S
import Util.Maybe
import Util.Time
view : UiSettings -> Model -> Html Msg
view settings model =
let
date =
( div
[ class "ml-2 sm:ml-0 whitespace-nowrap py-1 whitespace-nowrap opacity-75"
, title "Item Date"
]
[ Icons.dateIcon2 "mr-2"
, Maybe.withDefault model.item.created model.item.itemDate
|> Util.Time.formatDate
|> text
]
, Data.UiSettings.fieldVisible settings Data.Fields.Date
)
itemStyle =
"ml-2 sm:ml-4 py-1 whitespace-nowrap "
linkStyle =
"opacity-75 hover:opacity-100"
duedate =
( div
[ class "ml-2 sm:ml-4 py-1 max-w-min whitespace-nowrap opacity-100"
, class S.basicLabel
, title "Due Date"
]
[ Icons.dueDateIcon2 "mr-2"
, Maybe.map Util.Time.formatDate model.item.dueDate
|> Maybe.withDefault ""
|> text
]
, Data.UiSettings.fieldVisible settings Data.Fields.DueDate
&& Util.Maybe.nonEmpty model.item.dueDate
)
corr =
( div
[ class itemStyle
, title "Correspondent"
]
(Icons.correspondentIcon2 "mr-2"
:: Comp.LinkTarget.makeCorrLink model.item
[ ( linkStyle, True ) ]
SetLinkTarget
)
, Data.UiSettings.fieldVisible settings Data.Fields.CorrOrg
|| Data.UiSettings.fieldVisible settings Data.Fields.CorrPerson
)
conc =
( div
[ class itemStyle
, title "Concerning"
]
(Icons.concernedIcon2 "mr-2"
:: Comp.LinkTarget.makeConcLink model.item
[ ( linkStyle, True ) ]
SetLinkTarget
)
, Data.UiSettings.fieldVisible settings Data.Fields.ConcEquip
|| Data.UiSettings.fieldVisible settings Data.Fields.ConcPerson
)
itemfolder =
( div
[ class itemStyle
, title "Folder"
]
[ Icons.folderIcon2 "mr-2"
, Comp.LinkTarget.makeFolderLink model.item
[ ( linkStyle, True ) ]
SetLinkTarget
]
, Data.UiSettings.fieldVisible settings Data.Fields.Folder
)
src =
( div
[ class itemStyle
, title "Source"
]
[ Icons.sourceIcon2 "mr-2"
, Comp.LinkTarget.makeSourceLink [ ( linkStyle, True ) ]
SetLinkTarget
model.item.source
]
, True
)
in
div [ class "flex flex-col pb-2" ]
[ div [ class "flex flex-row items-center text-2xl" ]
[ i
[ classList
[ ( "hidden", Data.UiSettings.fieldHidden settings Data.Fields.Direction )
]
, class (Data.Direction.iconFromString2 model.item.direction)
, class "mr-2"
, title model.item.direction
]
[]
, div [ class "flex-grow ml-1 flex flex-col" ]
[ div [ class "flex flex-row items-center font-semibold" ]
[ text model.item.name
, div
[ classList
[ ( "hidden", model.item.state /= "created" )
]
, class "ml-3 text-base label bg-blue-500 dark:bg-lightblue-500 text-white rounded-lg"
]
[ text "New"
, i [ class "fa fa-exclamation ml-2" ] []
]
]
]
]
, ul [ class "flex flex-col sm:flex-row flex-wrap text-base " ]
(List.filter Tuple.second
[ date
, corr
, conc
, itemfolder
, src
, duedate
]
|> List.map Tuple.first
)
, div [ class "font-semibold mb-2 mt-3 pr-3" ]
(renderTagsAndFields settings model)
]
renderTagsAndFields : UiSettings -> Model -> List (Html Msg)
renderTagsAndFields settings model =
[ div [ class "flex flex-row flex-wrap items-center sm:justify-end" ]
(renderTags settings model ++ renderCustomValues settings model)
]
renderTags : UiSettings -> Model -> List (Html Msg)
renderTags settings model =
let
tagView t =
Comp.LinkTarget.makeTagLink
(IdName t.id t.name)
[ ( "label inline-flex ml-2 hover:opacity-90 mt-1 items-center", True )
, ( Data.UiSettings.tagColorString2 t settings, True )
]
SetLinkTarget
in
if Data.UiSettings.fieldHidden settings Data.Fields.Tag || model.item.tags == [] then
[]
else
List.map tagView model.item.tags
renderCustomValues : UiSettings -> Model -> List (Html Msg)
renderCustomValues settings model =
let
fieldView cv =
Comp.LinkTarget.makeCustomFieldLink2
cv
[ ( "ml-2 hover:opacity-90 mt-1 " ++ S.basicLabel, True ) ]
SetLinkTarget
labelThenName cv =
Maybe.withDefault cv.name cv.label
in
if Data.UiSettings.fieldHidden settings Data.Fields.CustomFields || model.item.customfields == [] then
[]
else
List.map fieldView (List.sortBy labelThenName model.item.customfields)

View File

@ -103,6 +103,8 @@ type alias Model =
, customFieldThrottle : Throttle Msg
, allTags : List Tag
, allPersons : Dict String Person
, attachmentDropdownOpen : Bool
, editMenuTabsOpen : Set String
}
@ -134,7 +136,7 @@ emptyModel =
, attachMenuOpen = False
, menuOpen = False
, tagModel =
Util.Tag.makeDropdownModel
Util.Tag.makeDropdownModel2
, directionModel =
Comp.Dropdown.makeSingleList
{ makeOption =
@ -195,7 +197,7 @@ emptyModel =
, pdfNativeView = Nothing
, deleteAttachConfirm = Comp.YesNoDimmer.emptyModel
, addFilesOpen = False
, addFilesModel = Comp.Dropzone.init Comp.Dropzone.defaultSettings
, addFilesModel = Comp.Dropzone.init []
, selectedFiles = []
, completed = Set.empty
, errored = Set.empty
@ -209,6 +211,8 @@ emptyModel =
, customFieldThrottle = Throttle.create 1
, allTags = []
, allPersons = Dict.empty
, attachmentDropdownOpen = False
, editMenuTabsOpen = Set.empty
}
@ -297,6 +301,9 @@ type Msg
| CustomFieldMsg Comp.CustomFieldMultiInput.Msg
| CustomFieldSaveResp CustomField String (Result Http.Error BasicResult)
| CustomFieldRemoveResp String (Result Http.Error BasicResult)
| ToggleAttachmentDropdown
| ToggleAkkordionTab String
| ToggleOpenAllAkkordionTabs
type SaveNameState

View File

@ -1,4 +1,4 @@
module Comp.ItemDetail.EditMenu exposing
module Comp.ItemDetail.MultiEditMenu exposing
( Model
, Msg
, SaveNameState(..)
@ -7,6 +7,7 @@ module Comp.ItemDetail.EditMenu exposing
, loadModel
, update
, view
, view2
)
import Api
@ -14,7 +15,6 @@ import Api.Model.EquipmentList exposing (EquipmentList)
import Api.Model.FolderItem exposing (FolderItem)
import Api.Model.FolderList exposing (FolderList)
import Api.Model.IdName exposing (IdName)
import Api.Model.ItemProposals exposing (ItemProposals)
import Api.Model.PersonList exposing (PersonList)
import Api.Model.ReferenceList exposing (ReferenceList)
import Api.Model.Tag exposing (Tag)
@ -23,9 +23,12 @@ import Comp.CustomFieldMultiInput
import Comp.DatePicker
import Comp.DetailEdit
import Comp.Dropdown exposing (isDropdownChangeMsg)
import Comp.ItemDetail.FieldTabState as FTabState
import Comp.ItemDetail.FormChange exposing (FormChange(..))
import Comp.Tabs as TB
import Data.CustomFieldChange exposing (CustomFieldChange(..))
import Data.Direction exposing (Direction)
import Data.DropdownStyle
import Data.Fields
import Data.Flags exposing (Flags)
import Data.Icons as Icons
@ -37,6 +40,8 @@ import Html.Events exposing (onClick, onInput)
import Http
import Markdown
import Page exposing (Page(..))
import Set exposing (Set)
import Styles as S
import Task
import Throttle exposing (Throttle)
import Time
@ -71,7 +76,6 @@ type alias Model =
, directionModel : Comp.Dropdown.Model Direction
, itemDatePicker : DatePicker
, itemDate : Maybe Int
, itemProposals : ItemProposals
, dueDate : Maybe Int
, dueDatePicker : DatePicker
, corrOrgModel : Comp.Dropdown.Model IdName
@ -81,6 +85,7 @@ type alias Model =
, modalEdit : Maybe Comp.DetailEdit.Model
, tagEditMode : TagEditMode
, customFieldModel : Comp.CustomFieldMultiInput.Model
, openTabs : Set String
}
@ -107,12 +112,13 @@ type Msg
| GetEquipResp (Result Http.Error EquipmentList)
| GetFolderResp (Result Http.Error FolderList)
| CustomFieldMsg Comp.CustomFieldMultiInput.Msg
| ToggleAkkordionTab String
init : Model
init =
{ tagModel =
Util.Tag.makeDropdownModel
Util.Tag.makeDropdownModel2
, directionModel =
Comp.Dropdown.makeSingleList
{ makeOption =
@ -155,12 +161,12 @@ init =
, nameSaveThrottle = Throttle.create 1
, itemDatePicker = Comp.DatePicker.emptyModel
, itemDate = Nothing
, itemProposals = Api.Model.ItemProposals.empty
, dueDate = Nothing
, dueDatePicker = Comp.DatePicker.emptyModel
, modalEdit = Nothing
, tagEditMode = AddTags
, customFieldModel = Comp.CustomFieldMultiInput.initWith []
, openTabs = Set.empty
}
@ -563,7 +569,7 @@ update flags msg model =
CustomFieldMsg lm ->
let
res =
Comp.CustomFieldMultiInput.update lm model.customFieldModel
Comp.CustomFieldMultiInput.update flags lm model.customFieldModel
model_ =
{ model | customFieldModel = res.model }
@ -587,6 +593,17 @@ update flags msg model =
in
UpdateResult model_ cmd_ Sub.none change
ToggleAkkordionTab title ->
let
tabs =
if Set.member title model.openTabs then
Set.remove title model.openTabs
else
Set.insert title model.openTabs
in
UpdateResult { model | openTabs = tabs } Cmd.none Sub.none NoFormChange
nameThrottleSub : Model -> Sub Msg
nameThrottleSub model =
@ -857,3 +874,286 @@ actionInputDatePicker =
Comp.DatePicker.defaultSettings
in
{ ds | containerClassList = [ ( "ui action input", True ) ] }
--- View2
view2 : ViewConfig -> UiSettings -> Model -> Html Msg
view2 =
renderEditForm2
renderEditForm2 : ViewConfig -> UiSettings -> Model -> Html Msg
renderEditForm2 cfg settings model =
let
fieldVisible field =
Data.UiSettings.fieldVisible settings field
optional fields html =
if
List.map fieldVisible fields
|> List.foldl (||) False
then
html
else
span [ class "hidden" ] []
tagModeIcon =
case model.tagEditMode of
AddTags ->
i [ class "fa fa-plus" ] []
RemoveTags ->
i [ class "fa fa-eraser" ] []
ReplaceTags ->
i [ class "fa fa-redo-alt" ] []
tagModeMsg =
case model.tagEditMode of
AddTags ->
"Tags chosen here are *added* to all selected items."
RemoveTags ->
"Tags chosen here are *removed* from all selected items."
ReplaceTags ->
"Tags chosen here *replace* those on selected items."
customFieldIcon field =
case cfg.customFieldState field.id of
SaveSuccess ->
Nothing
SaveFailed ->
Just "text-red-500 fa fa-exclamation-triangle"
Saving ->
Just "fa fa-sync-alt animate-spin"
customFieldSettings =
Comp.CustomFieldMultiInput.ViewSettings
False
"mb-4"
customFieldIcon
dds =
Data.DropdownStyle.sidebarStyle
tabStyle =
TB.searchMenuStyle
in
div [ class cfg.menuClass, class "mt-2" ]
[ TB.akkordion
tabStyle
(tabState settings model)
[ { title = "Confirm/Unconfirm item metadata"
, info = Nothing
, body =
[ div
[ class "flex flex-row space-x-4"
]
[ button
[ class S.primaryButton
, class "flex-grow"
, onClick (ConfirmMsg True)
]
[ text "Confirm"
]
, button
[ class S.secondaryButton
, class "flex-grow"
, onClick (ConfirmMsg False)
]
[ text "Unconfirm"
]
]
]
}
, { title = "Tags"
, info = Nothing
, body =
[ div [ class "field" ]
[ label [ class S.inputLabel ]
[ Icons.tagsIcon2 ""
, text "Tags"
, a
[ class "float-right"
, class S.link
, href "#"
, title "Change tag edit mode"
, onClick ToggleTagEditMode
]
[ tagModeIcon
]
]
, Html.map TagDropdownMsg (Comp.Dropdown.view2 dds settings model.tagModel)
, Markdown.toHtml [ class "opacity-50 text-sm" ] tagModeMsg
]
]
}
, { title = "Folder"
, info = Nothing
, body =
[ Html.map FolderDropdownMsg (Comp.Dropdown.view2 dds settings model.folderModel)
, div
[ classList
[ ( S.message, True )
, ( "hidden", isFolderMember model )
]
]
[ Markdown.toHtml [] """
You are **not a member** of this folder. This item will be **hidden**
from any search now. Use a folder where you are a member of to make this
item visible. This message will disappear then.
"""
]
]
}
, { title = "Custom Fields"
, info = Nothing
, body =
[ Html.map CustomFieldMsg
(Comp.CustomFieldMultiInput.view2 dds customFieldSettings model.customFieldModel)
]
}
, { title = "Date"
, info = Nothing
, body =
[ div [ class "relative" ]
[ Html.map ItemDatePickerMsg
(Comp.DatePicker.viewTime
model.itemDate
actionInputDatePicker2
model.itemDatePicker
)
, a
[ class S.inputLeftIconLinkSidebar
, href "#"
, onClick RemoveDate
]
[ i [ class "fa fa-trash-alt font-thin" ] []
]
, Icons.dateIcon2 S.dateInputIcon
]
]
}
, { title = "Due Date"
, info = Nothing
, body =
[ div [ class "relative" ]
[ Html.map DueDatePickerMsg
(Comp.DatePicker.viewTime
model.dueDate
actionInputDatePicker2
model.dueDatePicker
)
, a
[ class S.inputLeftIconLinkSidebar
, href "#"
, onClick RemoveDueDate
]
[ i [ class "fa fa-trash-alt font-thin" ] []
]
, Icons.dueDateIcon2 S.dateInputIcon
]
]
}
, { title = "Correspondent"
, info = Nothing
, body =
[ optional [ Data.Fields.CorrOrg ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.organizationIcon2 ""
, span [ class "ml-2" ]
[ text "Organization"
]
]
, Html.map OrgDropdownMsg (Comp.Dropdown.view2 dds settings model.corrOrgModel)
]
, optional [ Data.Fields.CorrPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 ""
, span [ class "ml-2" ]
[ text "Person"
]
]
, Html.map CorrPersonMsg (Comp.Dropdown.view2 dds settings model.corrPersonModel)
]
]
}
, { title =
"Concerning"
, info = Nothing
, body =
[ optional [ Data.Fields.ConcPerson ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.personIcon2 ""
, span [ class "ml-2" ]
[ text "Person" ]
]
, Html.map ConcPersonMsg (Comp.Dropdown.view2 dds settings model.concPersonModel)
]
, optional [ Data.Fields.ConcEquip ] <|
div [ class "mb-4" ]
[ label [ class S.inputLabel ]
[ Icons.equipmentIcon2 ""
, span [ class "ml-2" ]
[ text "Equipment" ]
]
, Html.map ConcEquipMsg (Comp.Dropdown.view2 dds settings model.concEquipModel)
]
]
}
, { title = "Direction"
, info = Nothing
, body =
[ Html.map DirDropdownMsg (Comp.Dropdown.view2 dds settings model.directionModel)
]
}
, { title = "Name"
, info = Nothing
, body =
[ div [ class "relative" ]
[ input
[ type_ "text"
, value model.nameModel
, onInput SetName
, class S.textInputSidebar
]
[]
, span [ class S.inputLeftIconOnly ]
[ i
[ classList
[ ( "text-green-500 fa fa-check", cfg.nameState == SaveSuccess )
, ( "text-red-500 fa fa-exclamation-triangle", cfg.nameState == SaveFailed )
, ( "sync fa fa-circle-notch animate-spin", cfg.nameState == Saving )
]
]
[]
]
]
]
}
]
]
tabState : UiSettings -> Model -> TB.Tab Msg -> ( TB.State, Msg )
tabState settings model tab =
FTabState.tabState settings
model.openTabs
model.customFieldModel
(.title >> ToggleAkkordionTab)
tab
actionInputDatePicker2 : DatePicker.Settings
actionInputDatePicker2 =
Comp.DatePicker.defaultSettings

View File

@ -0,0 +1,101 @@
module Comp.ItemDetail.Notes exposing (view)
import Comp.ItemDetail.Model
exposing
( Model
, Msg(..)
, NotesField(..)
, SaveNameState(..)
)
import Comp.MarkdownInput
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Markdown
import Page exposing (Page(..))
import Styles as S
import Util.String
view : Model -> Html Msg
view model =
case model.notesField of
ViewNotes ->
div [ class "flex flex-col ds-item-detail-notes" ]
[ div [ class "flex flex-row items-center border-b dark:border-bluegray-600" ]
[ div [ class "flex-grow font-bold text-lg" ]
[ text "Notes"
]
, div [ class "" ]
[ a
[ class S.link
, onClick ToggleEditNotes
, href "#"
]
[ i [ class "fa fa-edit mr-2" ] []
, text "Edit"
]
]
]
, div [ class "" ]
[ Markdown.toHtml [ class "markdown-preview" ]
(Maybe.withDefault "" model.item.notes)
]
]
EditNotes mm ->
let
classes act =
classList
[ ( "opacity-100", act )
, ( "opacity-50", not act )
]
in
div [ class "flex flex-col ds-item-detail-notes" ]
[ div [ class "flex flex-col" ]
[ div [ class "flex flex-row items-center" ]
[ div [ class "font-bold text-lg" ]
[ text "Notes"
]
, div [ class "flex flex-grow justify-end text-sm" ]
[ Html.map NotesEditMsg
(Comp.MarkdownInput.viewEditLink2 classes mm)
, span [ class "px-3" ] [ text "" ]
, Html.map NotesEditMsg
(Comp.MarkdownInput.viewPreviewLink2 classes mm)
]
]
]
, div [ class "flex flex-col h-64" ]
[ Html.map NotesEditMsg
(Comp.MarkdownInput.viewContent2
(Maybe.withDefault "" model.notesModel)
mm
)
, div [ class "text-sm flex justify-end" ]
[ Comp.MarkdownInput.viewCheatLink2 S.link mm
]
, div [ class "flex flex-row mt-1" ]
[ a
[ class S.primaryButton
, href "#"
, onClick SaveNotes
]
[ i [ class "fa fa-save font-thin mr-2" ] []
, text "Save"
]
, a
[ classList
[ ( "invisible hidden", Util.String.isNothingOrBlank model.item.notes )
]
, class S.secondaryButton
, class "ml-2"
, href "#"
, onClick ToggleEditNotes
]
[ i [ class "fa fa-times mr-2" ] []
, text "Cancel"
]
]
]
]

View File

@ -0,0 +1,343 @@
module Comp.ItemDetail.SingleAttachment exposing (view)
import Api
import Api.Model.Attachment exposing (Attachment)
import Comp.AttachmentMeta
import Comp.ItemDetail.Model
exposing
( Model
, Msg(..)
, NotesField(..)
, SaveNameState(..)
)
import Comp.MenuBar as MB
import Comp.YesNoDimmer
import Data.UiSettings exposing (UiSettings)
import Dict
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Html5.DragDrop as DD
import Page exposing (Page(..))
import Styles as S
import Util.Maybe
import Util.Size
import Util.String
view : UiSettings -> Model -> Int -> Attachment -> Html Msg
view settings model pos attach =
let
fileUrl =
Api.fileURL attach.id
in
div
[ class "flex flex-col md:relative h-full mb-2"
, classList
[ ( "hidden", not (attachmentVisible model pos) )
]
]
[ Html.map (DeleteAttachConfirm attach.id)
(Comp.YesNoDimmer.viewN
True
(Comp.YesNoDimmer.defaultSettings2 "Really delete this file?")
model.deleteAttachConfirm
)
, div
[ class "flex flex-row px-2 py-2 text-sm"
, class S.border
]
[ attachHeader settings model pos attach
]
, editAttachmentName model attach
, attachmentSelect model pos attach
, if isAttachMetaOpen model attach.id then
case Dict.get attach.id model.attachMeta of
Just am ->
Html.map (AttachMetaMsg attach.id)
(Comp.AttachmentMeta.view2 am)
Nothing ->
span [ class "hidden" ] []
else
div
[ class "flex flex-col relative px-2 pt-2 h-full"
, class "border-r border-l border-b dark:border-bluegray-600"
, id "ds-pdf-view-parent"
, style "max-height" "calc(100vh - 140px)"
, style "min-height" "500px"
]
[ iframe
[ if Maybe.withDefault settings.nativePdfPreview model.pdfNativeView then
src fileUrl
else
src (fileUrl ++ "/view")
, class "absolute h-full w-full top-0 left-0 mx-0 py-0"
, id "ds-pdf-view-iframe"
]
[]
]
]
{-| attachment header
- toggle thumbs
- name + size
- eye icon to open it
- menu
- rename
- meta data
- download archive
- download
- delete
- native view
-}
attachHeader : UiSettings -> Model -> Int -> Attachment -> Html Msg
attachHeader settings model _ attach =
let
attachName =
Maybe.withDefault "No name" attach.name
fileUrl =
Api.fileURL attach.id
hasArchive =
List.map .id model.item.archives
|> List.member attach.id
multiAttach =
List.length model.item.attachments > 1
attachSelectToggle mobile =
a
[ href "#"
, onClick ToggleAttachMenu
, class S.secondaryBasicButton
, classList
[ ( "bg-gray-200 dark:bg-bluegray-600 ", model.attachMenuOpen )
, ( "hidden", not multiAttach )
, ( "sm:hidden", multiAttach && mobile )
, ( "hidden sm:block", multiAttach && not mobile )
]
]
[ i [ class "fa fa-images font-thin" ] []
]
in
div [ class "flex flex-col sm:flex-row items-center w-full" ]
[ attachSelectToggle False
, div [ class "ml-2 text-base font-bold flex-grow w-full text-center sm:text-left" ]
[ text attachName
, text " ("
, text (Util.Size.bytesReadable Util.Size.B (toFloat attach.size))
, text ")"
]
, div [ class "flex flex-row justify-end items-center" ]
[ attachSelectToggle True
, a
[ href fileUrl
, target "_new"
, title "Open file in new tab"
, class S.secondaryBasicButton
, class "ml-2"
]
[ i [ class "fa fa-eye font-thin" ] []
]
, MB.viewItem <|
MB.Dropdown
{ linkIcon = "fa fa-bars"
, linkClass =
[ ( "ml-2", True )
, ( S.secondaryBasicButton, True )
]
, toggleMenu = ToggleAttachmentDropdown
, menuOpen = model.attachmentDropdownOpen
, items =
[ { icon = "fa fa-download"
, label = "Download file"
, attrs =
[ download attachName
, href fileUrl
]
}
, { icon = "fa fa-file"
, label = "Rename file"
, attrs =
[ href "#"
, onClick (EditAttachNameStart attach.id)
]
}
, { icon = "fa fa-file-archive"
, label = "Download original archive"
, attrs =
[ href (fileUrl ++ "/archive")
, target "_new"
, classList [ ( "hidden", not hasArchive ) ]
]
}
, { icon = "fa fa-external-link-alt"
, label = "Original file"
, attrs =
[ href (fileUrl ++ "/original")
, target "_new"
, classList [ ( "hidden", not attach.converted ) ]
]
}
, { icon =
if Maybe.withDefault settings.nativePdfPreview model.pdfNativeView then
"fa fa-toggle-on"
else
"fa fa-toggle-off"
, label = "Render pdf by browser"
, attrs =
[ onClick (TogglePdfNativeView settings.nativePdfPreview)
, href "#"
]
}
, { icon =
if isAttachMetaOpen model attach.id then
"fa fa-toggle-on"
else
"fa fa-toggle-off"
, label = "View extracted data"
, attrs =
[ onClick (AttachMetaClick attach.id)
, href "#"
]
}
, { icon = "fa fa-trash"
, label = "Delete this file"
, attrs =
[ onClick (RequestDeleteAttachment attach.id)
, href "#"
]
}
]
}
]
]
attachmentVisible : Model -> Int -> Bool
attachmentVisible model pos =
not model.sentMailsOpen
&& (if model.visibleAttach >= List.length model.item.attachments then
pos == 0
else
model.visibleAttach == pos
)
isAttachMetaOpen : Model -> String -> Bool
isAttachMetaOpen model id =
model.attachMetaOpen && (Dict.get id model.attachMeta /= Nothing)
editAttachmentName : Model -> Attachment -> Html Msg
editAttachmentName model attach =
let
am =
Util.Maybe.filter (\m -> m.id == attach.id) model.attachRename
in
case am of
Just m ->
div [ class "flex flex-row border-l border-r px-2 py-2 dark:border-bluegray-600" ]
[ input
[ type_ "text"
, value m.newName
, onInput EditAttachNameSet
, class S.textInput
, class "mr-2"
]
[]
, button
[ class S.primaryButton
, onClick EditAttachNameSubmit
]
[ i [ class "fa fa-check" ] []
]
, button
[ class S.secondaryButton
, onClick EditAttachNameCancel
]
[ i [ class "fa fa-times" ] []
]
]
Nothing ->
span [ class "hidden" ] []
attachmentSelect : Model -> Int -> Attachment -> Html Msg
attachmentSelect model _ _ =
div
[ class "flex flex-row border-l border-r px-2 py-2 dark:border-bluegray-600 "
, class "overflow-x-auto overflow-y-none"
, classList
[ ( "hidden", not model.attachMenuOpen )
]
]
(List.indexedMap (menuItem model) model.item.attachments)
menuItem : Model -> Int -> Attachment -> Html Msg
menuItem model pos attach =
let
highlight =
let
dropId =
DD.getDropId model.attachDD
dragId =
DD.getDragId model.attachDD
enable =
Just attach.id == dropId && dropId /= dragId
in
[ ( "bg-gray-300 dark:bg-bluegray-700 current-drop-target", enable )
]
active =
model.visibleAttach == pos
in
a
([ classList <|
[ ( "border-blue-500 dark:border-lightblue-500", pos == 0 )
, ( "dark:border-bluegray-600", pos /= 0 )
]
++ highlight
, class "block flex-col relative border rounded px-1 py-1 mr-2"
, class " hover:shadow dark:hover:border-bluegray-500"
, href "#"
, onClick (SetActiveAttachment pos)
]
++ DD.draggable AttachDDMsg attach.id
++ DD.droppable AttachDDMsg attach.id
)
[ div
[ classList
[ ( "hidden", not active )
]
, class "absolute right-1 top-1 text-blue-400 dark:text-lightblue-400 text-xl"
]
[ i [ class "fa fa-check-circle ml-1" ] []
]
, div [ class "" ]
[ img
[ src (Api.attachmentPreviewURL attach.id)
, class "block w-20 mx-auto"
]
[]
]
, div [ class "mt-1 text-sm break-all w-28 text-center" ]
[ Maybe.map (Util.String.ellipsis 36) attach.name
|> Maybe.withDefault "No Name"
|> text
]
]

View File

@ -22,6 +22,7 @@ import Comp.DetailEdit
import Comp.Dropdown exposing (isDropdownChangeMsg)
import Comp.Dropzone
import Comp.EquipmentForm
import Comp.ItemDetail.EditForm
import Comp.ItemDetail.Model
exposing
( AttachmentRename
@ -863,7 +864,10 @@ update key flags inav settings msg model =
case Dict.get id model.attachMeta of
Just _ ->
resultModel
{ model | attachMetaOpen = not model.attachMetaOpen }
{ model
| attachMetaOpen = not model.attachMetaOpen
, attachmentDropdownOpen = False
}
Nothing ->
let
@ -874,7 +878,11 @@ update key flags inav settings msg model =
Dict.insert id am model.attachMeta
in
resultModelCmd
( { model | attachMeta = nextMeta, attachMetaOpen = True }
( { model
| attachMeta = nextMeta
, attachMetaOpen = True
, attachmentDropdownOpen = False
}
, Cmd.map (AttachMetaMsg id) ac
)
@ -901,6 +909,7 @@ update key flags inav settings msg model =
Nothing ->
Just (not default)
, attachmentDropdownOpen = False
}
DeleteAttachConfirm attachId lmsg ->
@ -933,7 +942,7 @@ update key flags inav settings msg model =
inav
settings
(DeleteAttachConfirm id Comp.YesNoDimmer.activate)
model
{ model | attachmentDropdownOpen = False }
AddFilesToggle ->
resultModel
@ -964,7 +973,7 @@ update key flags inav settings msg model =
resultModel
{ model
| selectedFiles = []
, addFilesModel = Comp.Dropzone.init Comp.Dropzone.defaultSettings
, addFilesModel = Comp.Dropzone.init []
, completed = Set.empty
, errored = Set.empty
, loading = Dict.empty
@ -1220,13 +1229,21 @@ update key flags inav settings msg model =
in
case name of
Just n ->
resultModel { model | attachRename = Just (AttachmentRename id n) }
resultModel
{ model
| attachRename = Just (AttachmentRename id n)
, attachmentDropdownOpen = False
}
Nothing ->
resultModel model
Just _ ->
resultModel { model | attachRename = Nothing }
resultModel
{ model
| attachRename = Nothing
, attachmentDropdownOpen = False
}
EditAttachNameCancel ->
resultModel { model | attachRename = Nothing }
@ -1370,7 +1387,7 @@ update key flags inav settings msg model =
CustomFieldMsg lm ->
let
result =
Comp.CustomFieldMultiInput.update lm model.customFieldsModel
Comp.CustomFieldMultiInput.update flags lm model.customFieldsModel
cmd_ =
Cmd.map CustomFieldMsg result.cmd
@ -1460,6 +1477,36 @@ update key flags inav settings msg model =
CustomFieldRemoveResp fieldId (Err _) ->
resultModel { model | customFieldSavingIcon = Dict.remove fieldId model.customFieldSavingIcon }
ToggleAttachmentDropdown ->
resultModel { model | attachmentDropdownOpen = not model.attachmentDropdownOpen }
ToggleAkkordionTab title ->
let
tabs =
if Set.member title model.editMenuTabsOpen then
Set.remove title model.editMenuTabsOpen
else
Set.insert title model.editMenuTabsOpen
in
resultModel { model | editMenuTabsOpen = tabs }
ToggleOpenAllAkkordionTabs ->
let
allNames =
Comp.ItemDetail.EditForm.formTabs settings model
|> List.map .title
|> Set.fromList
next =
if model.editMenuTabsOpen == allNames then
Set.empty
else
allNames
in
resultModel { model | editMenuTabsOpen = next }
--- Helper

View File

@ -525,7 +525,7 @@ renderItemInfo settings model =
, title "Correspondent"
]
(Icons.correspondentIcon ""
:: Comp.LinkTarget.makeCorrLink model.item SetLinkTarget
:: Comp.LinkTarget.makeCorrLink model.item [] SetLinkTarget
)
, Data.UiSettings.fieldVisible settings Data.Fields.CorrOrg
|| Data.UiSettings.fieldVisible settings Data.Fields.CorrPerson
@ -537,7 +537,7 @@ renderItemInfo settings model =
, title "Concerning"
]
(Icons.concernedIcon
:: Comp.LinkTarget.makeConcLink model.item SetLinkTarget
:: Comp.LinkTarget.makeConcLink model.item [] SetLinkTarget
)
, Data.UiSettings.fieldVisible settings Data.Fields.ConcEquip
|| Data.UiSettings.fieldVisible settings Data.Fields.ConcPerson
@ -549,7 +549,7 @@ renderItemInfo settings model =
, title "Folder"
]
[ Icons.folderIcon ""
, Comp.LinkTarget.makeFolderLink model.item SetLinkTarget
, Comp.LinkTarget.makeFolderLink model.item [] SetLinkTarget
]
, Data.UiSettings.fieldVisible settings Data.Fields.Folder
)
@ -1093,7 +1093,11 @@ renderAddFilesForm model =
[ h4 [ class "ui header" ]
[ text "Add more files to this item"
]
, Html.map AddFilesMsg (Comp.Dropzone.view model.addFilesModel)
, Html.map AddFilesMsg
(Comp.Dropzone.view
Comp.Dropzone.defaultSettings
model.addFilesModel
)
, button
[ class "ui primary button"
, href "#"

View File

@ -0,0 +1,289 @@
module Comp.ItemDetail.View2 exposing (view)
import Comp.Basic as B
import Comp.DetailEdit
import Comp.ItemDetail.AddFilesForm
import Comp.ItemDetail.ItemInfoHeader
import Comp.ItemDetail.Model
exposing
( Model
, Msg(..)
, NotesField(..)
, SaveNameState(..)
)
import Comp.ItemDetail.Notes
import Comp.ItemDetail.SingleAttachment
import Comp.ItemMail
import Comp.MenuBar as MB
import Comp.SentMails
import Comp.YesNoDimmer
import Data.Icons as Icons
import Data.ItemNav exposing (ItemNav)
import Data.UiSettings exposing (UiSettings)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
import Page exposing (Page(..))
import Styles as S
import Util.Time
view : ItemNav -> UiSettings -> Model -> Html Msg
view inav settings model =
div [ class "flex flex-col h-full" ]
[ header settings model
, menuBar inav settings model
, body inav settings model
, Html.map DeleteItemConfirm
(Comp.YesNoDimmer.viewN
True
(Comp.YesNoDimmer.defaultSettings2 "Really delete the complete item?")
model.deleteItemConfirm
)
]
header : UiSettings -> Model -> Html Msg
header settings model =
div [ class "my-3" ]
[ Comp.ItemDetail.ItemInfoHeader.view settings model ]
menuBar : ItemNav -> UiSettings -> Model -> Html Msg
menuBar inav settings model =
let
keyDescr name =
if settings.itemDetailShortcuts && model.menuOpen then
" Key '" ++ name ++ "'."
else
""
in
MB.view
{ start =
[ MB.CustomElement <|
a
[ class S.secondaryBasicButton
, Page.href HomePage
, title "Back to search results"
]
[ i [ class "fa fa-arrow-left" ] []
]
, MB.CustomElement <|
div [ class "inline-flex" ]
[ B.genericButton
{ label = ""
, icon = "fa fa-caret-left"
, baseStyle = S.secondaryBasicButtonMain ++ " px-4 py-2 border rounded-l"
, activeStyle = S.secondaryBasicButtonHover
, handler =
Maybe.map ItemDetailPage inav.prev
|> Maybe.map Page.href
|> Maybe.withDefault (href "#")
, disabled = inav.prev == Nothing
, attrs =
[ title ("Previous item." ++ keyDescr "Ctrl-,")
]
}
, B.genericButton
{ label = ""
, icon = "fa fa-caret-right"
, baseStyle =
S.secondaryBasicButtonMain
++ " px-4 py-2 border-t border-b border-r rounded-r"
, activeStyle = S.secondaryBasicButtonHover
, handler =
Maybe.map ItemDetailPage inav.next
|> Maybe.map Page.href
|> Maybe.withDefault (href "#")
, disabled = inav.next == Nothing
, attrs =
[ title ("Next item." ++ keyDescr "Ctrl-.")
]
}
]
, MB.CustomElement <|
a
[ classList
[ ( "bg-gray-200 dark:bg-bluegray-600", model.mailOpen )
]
, title "Send Mail"
, onClick ToggleMail
, class S.secondaryBasicButton
, href "#"
]
[ i [ class "fa fa-envelope font-thin" ] []
]
, MB.CustomElement <|
a
[ classList
[ ( "bg-gray-200 dark:bg-bluegray-600", model.addFilesOpen )
]
, if model.addFilesOpen then
title "Close"
else
title "Add more files to this item"
, onClick AddFilesToggle
, class S.secondaryBasicButton
, href "#"
]
[ Icons.addFilesIcon2 ""
]
, MB.CustomElement <|
a
[ class S.primaryButton
, href "#"
, onClick ConfirmItem
, title "Confirm item metadata"
, classList [ ( "hidden", model.item.state /= "created" ) ]
]
[ i [ class "fa fa-check mr-2" ] []
, text "Confirm"
]
]
, end =
[ MB.CustomElement <|
a
[ class S.secondaryBasicButton
, href "#"
, onClick UnconfirmItem
, title "Un-confirm item metadata"
, classList [ ( "hidden", model.item.state == "created" ) ]
]
[ i [ class "fa fa-eye-slash font-thin" ] []
]
, MB.CustomElement <|
a
[ class S.deleteButton
, href "#"
, onClick RequestDelete
, title "Delete this item"
]
[ i [ class "fa fa-trash" ] []
]
]
, rootClasses = "mb-2"
}
body : ItemNav -> UiSettings -> Model -> Html Msg
body inav settings model =
div [ class "grid gap-2 grid-cols-1 md:grid-cols-3 h-full" ]
[ leftArea settings model
, rightArea settings model
]
leftArea : UiSettings -> Model -> Html Msg
leftArea settings model =
div [ class "w-full md:order-first md:mr-2 flex flex-col" ]
[ addDetailForm settings model
, sendMailForm settings model
, Comp.ItemDetail.AddFilesForm.view model
, Comp.ItemDetail.Notes.view model
, div
[ classList
[ ( "hidden", Comp.SentMails.isEmpty model.sentMails )
]
, class "mt-4 "
]
[ h3 [ class "flex flex-row items-center border-b dark:border-bluegray-600 font-bold text-lg" ]
[ text "Sent E-Mails"
]
, Html.map SentMailsMsg (Comp.SentMails.view2 model.sentMails)
]
, div [ class "flex-grow" ] []
, itemIdInfo model
]
rightArea : UiSettings -> Model -> Html Msg
rightArea settings model =
div [ class "md:col-span-2 h-full" ]
(attachmentsBody settings model)
attachmentsBody : UiSettings -> Model -> List (Html Msg)
attachmentsBody settings model =
List.indexedMap (Comp.ItemDetail.SingleAttachment.view settings model)
model.item.attachments
sendMailForm : UiSettings -> Model -> Html Msg
sendMailForm settings model =
div
[ classList
[ ( "hidden", not model.mailOpen )
]
, class S.box
, class "mb-4 px-2 py-2"
]
[ div [ class "text-lg font-bold" ]
[ text "Send this item via E-Mail"
]
, B.loadingDimmer model.mailSending
, Html.map ItemMailMsg (Comp.ItemMail.view2 settings model.itemMail)
, div
[ classList
[ ( S.errorMessage
, Maybe.map .success model.mailSendResult
|> Maybe.map not
|> Maybe.withDefault False
)
, ( S.successMessage
, Maybe.map .success model.mailSendResult
|> Maybe.withDefault False
)
, ( "hidden", model.mailSendResult == Nothing )
]
, class "mt-2"
]
[ Maybe.map .message model.mailSendResult
|> Maybe.withDefault ""
|> text
]
]
itemIdInfo : Model -> Html msg
itemIdInfo model =
div [ class "flex flex-col opacity-50 text-xs pb-1 mt-3 border-t dark:border-bluegray-600" ]
[ div
[ class "inline-flex items-center"
, title "Item ID"
]
[ i [ class "fa fa-bullseye mr-2" ] []
, text model.item.id
]
, div
[ class "inline-flex items-center"
, title "Created on"
]
[ i [ class "fa fa-sun font-thin mr-2" ] []
, Util.Time.formatDateTime model.item.created |> text
]
, div
[ class "inline-flex items-center"
, title "Last update on"
]
[ i [ class "fa fa-pencil-alt mr-2" ] []
, Util.Time.formatDateTime model.item.updated |> text
]
]
addDetailForm : UiSettings -> Model -> Html Msg
addDetailForm settings model =
case model.modalEdit of
Just mm ->
div
[ class "flex flex-col px-2 py-2 mb-4"
, class S.box
]
[ Comp.DetailEdit.formHeading S.header3 mm
, Html.map ModalEditMsg (Comp.DetailEdit.view2 [] settings mm)
]
Nothing ->
span [ class "hidden" ] []