packages feed

matterhorn-50200.16.0: src/Matterhorn/Events/ManageAttachments.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Matterhorn.Events.ManageAttachments
  ( onEventManageAttachments
  , attachmentListKeybindings
  , attachmentBrowseKeyHandlers
  , attachmentBrowseKeybindings
  , attachmentListKeyHandlers
  )
where

import           Prelude ()
import           Matterhorn.Prelude

import qualified Brick.Widgets.FileBrowser as FB
import qualified Brick.Widgets.List as L
import qualified Data.Text as T
import qualified Data.Vector as Vector
import qualified Graphics.Vty as V
import           Lens.Micro.Platform ( (?=), (%=), to )

import           Network.Mattermost.Types ( TeamId )

import           Matterhorn.Types
import           Matterhorn.Types.KeyEvents
import           Matterhorn.Events.Keybindings
import           Matterhorn.State.Attachments
import           Matterhorn.State.Common


onEventManageAttachments :: TeamId -> V.Event -> MH ()
onEventManageAttachments tId e = do
    mode <- use (csTeam(tId).tsMode)
    case mode of
        ManageAttachments -> void $ onEventAttachmentList tId e
        ManageAttachmentsBrowseFiles -> onEventBrowseFile tId e
        _ -> error "BUG: onEventManageAttachments called in invalid mode"

onEventAttachmentList :: TeamId -> V.Event -> MH Bool
onEventAttachmentList tId =
    handleKeyboardEvent (attachmentListKeybindings tId) $
        mhHandleEventLensed (csTeam(tId).tsEditState.cedAttachmentList) L.handleListEvent

attachmentListKeybindings :: TeamId -> KeyConfig -> KeyHandlerMap
attachmentListKeybindings tId = mkKeybindings (attachmentListKeyHandlers tId)

attachmentListKeyHandlers :: TeamId -> [KeyEventHandler]
attachmentListKeyHandlers tId =
    [ mkKb CancelEvent "Close attachment list" $
          setMode tId Main
    , mkKb SelectUpEvent "Move cursor up" $
          mhHandleEventLensed (csTeam(tId).tsEditState.cedAttachmentList) L.handleListEvent (V.EvKey V.KUp [])
    , mkKb SelectDownEvent "Move cursor down" $
          mhHandleEventLensed (csTeam(tId).tsEditState.cedAttachmentList) L.handleListEvent (V.EvKey V.KDown [])
    , mkKb AttachmentListAddEvent "Add a new attachment to the attachment list" $
          showAttachmentFileBrowser tId
    , mkKb AttachmentOpenEvent "Open the selected attachment using the URL open command" $
          openSelectedAttachment tId
    , mkKb AttachmentListDeleteEvent "Delete the selected attachment from the attachment list" $
          deleteSelectedAttachment tId
    ]

attachmentBrowseKeybindings :: TeamId -> KeyConfig -> KeyHandlerMap
attachmentBrowseKeybindings tId = mkKeybindings (attachmentBrowseKeyHandlers tId)

attachmentBrowseKeyHandlers :: TeamId -> [KeyEventHandler]
attachmentBrowseKeyHandlers tId =
    [ mkKb CancelEvent "Cancel attachment file browse" $
      cancelAttachmentBrowse tId
    , mkKb AttachmentOpenEvent "Open the selected file using the URL open command" $
      openSelectedBrowserEntry tId
    , mkKb FileBrowserBeginSearchEvent "Begin search for name in list" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserBeginSearch
    , mkKb FileBrowserSelectEnterEvent "Select file or enter directory" $ do
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserSelectEnter
      withFileBrowser tId (tryAddAttachment tId . FB.fileBrowserSelection)
    , mkKb FileBrowserSelectCurrentEvent "Select file" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserSelectCurrent
    , mkKb FileBrowserListPageUpEvent "Move cursor one page up" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListPageUp
    , mkKb FileBrowserListPageDownEvent "Move cursor one page down" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListPageDown
    , mkKb FileBrowserListHalfPageUpEvent "Move cursor one-half page up" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListHalfPageUp
    , mkKb FileBrowserListHalfPageDownEvent "Move cursor one-half page down" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListHalfPageDown
    , mkKb FileBrowserListTopEvent "Move cursor to top of list" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListTop
    , mkKb FileBrowserListBottomEvent "Move cursor to bottom of list" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListBottom
    , mkKb FileBrowserListNextEvent "Move cursor down" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListNext
    , mkKb FileBrowserListPrevEvent "Move cursor up" $
      mhHandleEventLensed' (csTeam(tId).tsEditState.unsafeCedFileBrowser)
        FB.actionFileBrowserListPrev
    ]

withFileBrowser :: TeamId -> ((FB.FileBrowser Name) -> MH ()) -> MH ()
withFileBrowser tId f = do
    use (csTeam(tId).tsEditState.cedFileBrowser) >>= \case
        Nothing -> do
            -- The widget has not been created yet.  This should
            -- normally not occur, because the ManageAttachments
            -- events should not fire when there is no FileBrowser
            -- Widget active to cause Brick to generate these events.
            -- This could therefore be implemented as an `error "BUG:
            -- ..."` handler, but the more benign approach is to
            -- simply create an available FileBrowser at this stage.
            new_b <- liftIO $ FB.newFileBrowser FB.selectNonDirectories (AttachmentFileBrowser tId) Nothing
            csTeam(tId).tsEditState.cedFileBrowser ?= new_b
            f new_b
        Just b -> f b

openSelectedAttachment :: TeamId -> MH ()
openSelectedAttachment tId = do
    cur <- use (csTeam(tId).tsEditState.cedAttachmentList.to L.listSelectedElement)
    case cur of
        Nothing -> return ()
        Just (_, entry) -> void $ openFilePath (FB.fileInfoFilePath $
                                                attachmentDataFileInfo entry)

openSelectedBrowserEntry :: TeamId -> MH ()
openSelectedBrowserEntry tId = withFileBrowser tId $ \b ->
    case FB.fileBrowserCursor b of
        Nothing -> return ()
        Just entry -> void $ openFilePath (FB.fileInfoFilePath entry)

onEventBrowseFile :: TeamId -> V.Event -> MH ()
onEventBrowseFile tId e = do
    withFileBrowser tId $ \b -> do
        case FB.fileBrowserIsSearching b of
            False ->
                void $ handleKeyboardEvent (attachmentBrowseKeybindings tId) (handleFileBrowserEvent tId) e
            True ->
                handleFileBrowserEvent tId e

    -- n.b. the FileBrowser may have been updated above, so re-acquire it
    withFileBrowser tId $ \b -> do
        case FB.fileBrowserException b of
            Nothing -> return ()
            Just ex -> do
                mhLog LogError $ T.pack $ "FileBrowser exception: " <> show ex

cancelAttachmentBrowse :: TeamId -> MH ()
cancelAttachmentBrowse tId = do
    es <- use (csTeam(tId).tsEditState.cedAttachmentList.L.listElementsL)
    case length es of
        0 -> setMode tId Main
        _ -> setMode tId ManageAttachments

handleFileBrowserEvent :: TeamId -> V.Event -> MH ()
handleFileBrowserEvent tId e = do
    let fbHandle ev = sequence . (fmap (FB.handleFileBrowserEvent ev))
    mhHandleEventLensed (csTeam(tId).tsEditState.cedFileBrowser) fbHandle e
    -- TODO: Check file browser exception state
    withFileBrowser tId $ \b ->
        tryAddAttachment tId $ FB.fileBrowserSelection b

deleteSelectedAttachment :: TeamId -> MH ()
deleteSelectedAttachment tId = do
    es <- use (csTeam(tId).tsEditState.cedAttachmentList.L.listElementsL)
    mSel <- use (csTeam(tId).tsEditState.cedAttachmentList.to L.listSelectedElement)
    case mSel of
        Nothing ->
            return ()
        Just (pos, _) -> do
            oldIdx <- use (csTeam(tId).tsEditState.cedAttachmentList.L.listSelectedL)
            let idx = if Vector.length es == 1
                      then Nothing
                      else case oldIdx of
                          Nothing -> Just 0
                          Just old -> if pos >= old
                                      then Just $ pos - 1
                                      else Just pos
            csTeam(tId).tsEditState.cedAttachmentList %= L.listReplace (deleteAt pos es) idx

deleteAt :: Int -> Vector.Vector a -> Vector.Vector a
deleteAt p as | p < 0 || p >= length as = as
              | otherwise = Vector.take p as <> Vector.drop (p + 1) as