docspell/modules/webapp/src/main/elm/Comp/FolderDetail.elm
2021-07-25 14:00:11 +02:00

562 lines
16 KiB
Elm

{-
Copyright 2020 Docspell Contributors
SPDX-License-Identifier: GPL-3.0-or-later
-}
module Comp.FolderDetail exposing
( Model
, Msg
, init
, initEmpty
, update
, view2
)
import Api
import Api.Model.BasicResult exposing (BasicResult)
import Api.Model.FolderDetail exposing (FolderDetail)
import Api.Model.IdName exposing (IdName)
import Api.Model.IdResult exposing (IdResult)
import Api.Model.NewFolder exposing (NewFolder)
import Api.Model.User exposing (User)
import Comp.Basic as B
import Comp.FixedDropdown
import Comp.MenuBar as MB
import Comp.YesNoDimmer
import Data.DropdownStyle as DS
import Data.Flags exposing (Flags)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Messages.Comp.FolderDetail exposing (Texts)
import Styles as S
import Util.Maybe
type alias Model =
{ formState : FormState
, folder : FolderDetail
, name : Maybe String
, members : List IdName
, users : List User
, memberDropdown : Comp.FixedDropdown.Model IdName
, selectedMember : Maybe IdName
, loading : Bool
, deleteDimmer : Comp.YesNoDimmer.Model
}
type FormState
= FormStateInitial
| FormStateHttpError Http.Error
| FormStateFolderCreated
| FormStateGenericError String
| FormStateNameChangeSuccessful
| FormStateDeleteSuccessful
isError : FormState -> Bool
isError state =
case state of
FormStateInitial ->
False
FormStateHttpError _ ->
True
FormStateGenericError _ ->
True
FormStateFolderCreated ->
False
FormStateNameChangeSuccessful ->
False
FormStateDeleteSuccessful ->
False
isSuccess : FormState -> Bool
isSuccess state =
case state of
FormStateInitial ->
False
FormStateHttpError _ ->
False
FormStateGenericError _ ->
False
FormStateFolderCreated ->
True
FormStateNameChangeSuccessful ->
True
FormStateDeleteSuccessful ->
True
type Msg
= SetName String
| MemberDropdownMsg (Comp.FixedDropdown.Msg IdName)
| SaveName
| NewFolderResp (Result Http.Error IdResult)
| ChangeFolderResp (Result Http.Error BasicResult)
| ChangeNameResp (Result Http.Error BasicResult)
| FolderDetailResp (Result Http.Error FolderDetail)
| AddMember
| RemoveMember IdName
| RequestDelete
| DeleteMsg Comp.YesNoDimmer.Msg
| DeleteResp (Result Http.Error BasicResult)
| GoBack
init : List User -> FolderDetail -> Model
init users folder =
{ formState = FormStateInitial
, folder = folder
, name = Util.Maybe.fromString folder.name
, members = folder.members
, users = users
, memberDropdown =
Comp.FixedDropdown.init (makeOptions users folder)
, selectedMember = Nothing
, loading = False
, deleteDimmer = Comp.YesNoDimmer.emptyModel
}
initEmpty : List User -> Model
initEmpty users =
init users Api.Model.FolderDetail.empty
makeOptions : List User -> FolderDetail -> List IdName
makeOptions users folder =
let
toIdName u =
IdName u.id u.login
notMember idn =
List.member idn (folder.owner :: folder.members) |> not
in
List.map toIdName users
|> List.filter notMember
--- Update
update : Flags -> Msg -> Model -> ( Model, Cmd Msg, Bool )
update flags msg model =
case msg of
GoBack ->
( model, Cmd.none, True )
SetName str ->
( { model | name = Util.Maybe.fromString str }
, Cmd.none
, False
)
MemberDropdownMsg lmsg ->
let
( mm, sel ) =
Comp.FixedDropdown.update lmsg model.memberDropdown
in
( { model
| memberDropdown = mm
, selectedMember =
case sel of
Just _ ->
sel
Nothing ->
model.selectedMember
}
, Cmd.none
, False
)
SaveName ->
case model.name of
Just name ->
let
cmd =
if model.folder.id == "" then
Api.createNewFolder flags (NewFolder name) NewFolderResp
else
Api.changeFolderName flags
model.folder.id
(NewFolder name)
ChangeNameResp
in
( { model
| loading = True
, formState = FormStateInitial
}
, cmd
, False
)
Nothing ->
( model, Cmd.none, False )
NewFolderResp (Ok ir) ->
if ir.success then
( model, Api.getFolderDetail flags ir.id FolderDetailResp, False )
else
( { model
| loading = False
, formState =
if ir.success then
FormStateFolderCreated
else
FormStateGenericError ir.message
}
, Cmd.none
, False
)
NewFolderResp (Err err) ->
( { model
| loading = False
, formState = FormStateHttpError err
}
, Cmd.none
, False
)
ChangeFolderResp (Ok r) ->
if r.success then
( model
, Api.getFolderDetail flags model.folder.id FolderDetailResp
, False
)
else
( { model | loading = False, formState = FormStateGenericError r.message }
, Cmd.none
, False
)
ChangeFolderResp (Err err) ->
( { model
| loading = False
, formState = FormStateHttpError err
}
, Cmd.none
, False
)
ChangeNameResp (Ok r) ->
let
model_ =
{ model
| formState =
if r.success then
FormStateNameChangeSuccessful
else
FormStateGenericError r.message
, loading = False
}
in
( model_, Cmd.none, False )
ChangeNameResp (Err err) ->
( { model
| formState = FormStateHttpError err
, loading = False
}
, Cmd.none
, False
)
FolderDetailResp (Ok sd) ->
( init model.users sd, Cmd.none, False )
FolderDetailResp (Err err) ->
( { model
| loading = False
, formState = FormStateHttpError err
}
, Cmd.none
, False
)
AddMember ->
case model.selectedMember of
Just mem ->
( { model | loading = True }
, Api.addMember flags model.folder.id mem.id ChangeFolderResp
, False
)
Nothing ->
( model, Cmd.none, False )
RemoveMember idname ->
( { model | loading = True }
, Api.removeMember flags model.folder.id idname.id ChangeFolderResp
, False
)
RequestDelete ->
let
( dm, _ ) =
Comp.YesNoDimmer.update Comp.YesNoDimmer.activate model.deleteDimmer
in
( { model | deleteDimmer = dm }, Cmd.none, False )
DeleteMsg lm ->
let
( dm, flag ) =
Comp.YesNoDimmer.update lm model.deleteDimmer
cmd =
if flag then
Api.deleteFolder flags model.folder.id DeleteResp
else
Cmd.none
in
( { model | deleteDimmer = dm }, cmd, False )
DeleteResp (Ok r) ->
( { model
| formState =
if r.success then
FormStateDeleteSuccessful
else
FormStateGenericError r.message
}
, Cmd.none
, r.success
)
DeleteResp (Err err) ->
( { model | formState = FormStateHttpError err }
, Cmd.none
, False
)
--- View2
view2 : Texts -> Flags -> Model -> Html Msg
view2 texts flags model =
let
isOwner =
Maybe.map .user flags.account
|> Maybe.map ((==) model.folder.owner.name)
|> Maybe.withDefault False
dimmerSettings : Comp.YesNoDimmer.Settings
dimmerSettings =
Comp.YesNoDimmer.defaultSettings texts.reallyDeleteThisFolder
texts.basics.yes
texts.basics.no
in
div [ class "flex flex-col md:relative" ]
(viewButtons2 texts model
:: [ Html.map DeleteMsg
(Comp.YesNoDimmer.viewN
True
dimmerSettings
model.deleteDimmer
)
, div
[ class "py-2 text-lg opacity-75"
, classList [ ( "hidden", model.folder.id /= "" ) ]
]
[ text texts.autoOwnerInfo
]
, div
[ class "py-2 text-lg opacity-75"
, classList [ ( "hidden", model.folder.id == "" ) ]
]
[ text texts.modifyInfo
]
, div
[ class S.message
, classList [ ( "hidden", model.folder.id == "" || isOwner ) ]
]
[ text texts.notOwnerInfo
]
, div [ class "mb-4 flex flex-col" ]
[ label
[ class S.inputLabel
, for "folder-name"
]
[ text texts.basics.name
, B.inputRequired
]
, div [ class "flex flex-row space-x-2" ]
[ input
[ type_ "text"
, onInput SetName
, Maybe.withDefault "" model.name
|> value
, classList [ ( S.inputErrorBorder, model.name == Nothing ) ]
, class S.textInput
, id "folder-name"
]
[]
, a
[ class S.primaryButton
, class "rounded-r -ml-1"
, onClick SaveName
, href "#"
]
[ i [ class "fa fa-save" ] []
, span [ class "ml-2 hidden sm:inline" ]
[ text texts.basics.submit
]
]
]
]
, div
[ classList
[ ( "hidden", model.formState == FormStateInitial )
, ( S.errorMessage, isError model.formState )
, ( S.successMessage, isSuccess model.formState )
]
, class "my-4"
]
[ case model.formState of
FormStateInitial ->
text ""
FormStateHttpError err ->
text (texts.httpError err)
FormStateGenericError m ->
text m
FormStateFolderCreated ->
text texts.folderCreated
FormStateNameChangeSuccessful ->
text texts.nameChangeSuccessful
FormStateDeleteSuccessful ->
text texts.deleteSuccessful
]
]
++ viewMembers2 texts model
)
viewMembers2 : Texts -> Model -> List (Html Msg)
viewMembers2 texts model =
let
folderCfg =
{ display = .name
, icon = \_ -> Nothing
, style = DS.mainStyle
, selectPlaceholder = texts.basics.selectPlaceholder
}
in
if model.folder.id == "" then
[]
else
[ div
[ class S.header3
, class "mt-4"
]
[ text texts.members
]
, div [ class "flex flex-col space-y-2" ]
[ div [ class "flex flex-row space-x-2" ]
[ div [ class "flex-grow" ]
[ Html.map MemberDropdownMsg
(Comp.FixedDropdown.viewStyled2
folderCfg
False
model.selectedMember
model.memberDropdown
)
]
, a
[ title texts.addMember
, onClick AddMember
, class S.primaryButton
, href "#"
, class "flex-none"
]
[ i [ class "fa fa-plus" ] []
, span [ class "ml-2 hidden sm:inline" ]
[ text texts.add
]
]
]
]
, div
[ class "flex flex-col space-y-4 md:space-y-2 mt-2"
, class "px-2 border-0 border-l dark:border-bluegray-600"
]
(List.map (viewMember2 texts) model.members)
]
viewMember2 : Texts -> IdName -> Html Msg
viewMember2 texts member =
div
[ class "flex flex-row space-x-2 items-center"
]
[ a
[ class S.deleteLabel
, href "#"
, title texts.removeMember
, onClick (RemoveMember member)
]
[ i [ class "fa fa-trash " ] []
]
, span [ class "ml-2" ]
[ text member.name
]
]
viewButtons2 : Texts -> Model -> Html Msg
viewButtons2 texts model =
MB.view
{ start =
[ MB.SecondaryButton
{ tagger = GoBack
, label = texts.basics.cancel
, icon = Just "fa fa-arrow-left"
, title = texts.basics.backToList
}
]
, end =
[ MB.CustomButton
{ tagger = RequestDelete
, label = texts.basics.delete
, icon = Just "fa fa-trash"
, title = texts.deleteThisFolder
, inputClass =
[ ( S.deleteButton, True )
, ( "hidden", model.folder.id == "" )
]
}
]
, rootClasses = "mb-4"
}