packages feed

restman-0.7.4.0: src/UI.hs

{-# language OverloadedStrings #-}
{-|
Module: UI

Top-level Brick application wiring for RESTman.

'mkInitialState' builds the initial 'AppS' from the values parsed on the command line.
'startUI' creates the HTTP manager, optionally performs an initial request when a URI is
supplied, and hands control to the Brick main loop.

The Brick 'App' record is assembled here; the individual draw, event-handling, and cursor
functions live in "Lib".
-}
module UI
    ( -- * Application entry points
      mkInitialState
    , startUI
    ) where

-- base
import Data.Maybe (fromMaybe)

-- brick
import Brick.AttrMap (AttrMap)
import qualified Brick.AttrMap as Brick
import Brick.Main (App(..), defaultMain)
import Brick.Widgets.Border (borderAttr)
import Brick.Widgets.Edit (editor)
import Brick.Widgets.List (listSelectedFocusedAttr)

-- bytestring
import qualified Data.ByteString.Lazy as Lazy (ByteString)

-- http-client
import qualified Network.HTTP.Client as HC

-- text
import Data.Text (Text)
import qualified Data.Text as T

-- vty
import Graphics.Vty.Attributes
  ( Attr(Attr, attrBackColor, attrForeColor, attrStyle, attrURL)
  , MaybeDefault(Default, SetTo)
  , currentAttr
  , reverseVideo
  )
import Graphics.Vty.Image (emptyImage)

-- local libs
import HTTP.Client (Header, UseDefaultHeaders, robustSettings)
import Lib
import Types


-- | Attribute map for the application.  Sets reverse-video for the focused list item and
-- resets the border attribute to terminal defaults so borders inherit the terminal colour scheme.
attrMap :: AppS -> AttrMap
attrMap _ =
  Brick.attrMap currentAttr
    [ (listSelectedFocusedAttr, currentAttr {attrStyle = SetTo reverseVideo})
    , (borderAttr, termDefaults)
    ]
 where
  termDefaults = Attr
    { attrStyle = Default
    , attrForeColor = Default
    , attrBackColor = Default
    , attrURL = Default
    }

-- | Build the initial 'AppS' from command-line arguments and an HTTP manager.
mkInitialState
  :: HC.Manager            -- ^ HTTP connection manager
  -> String                -- ^ Initial HTTP method
  -> UseDefaultHeaders     -- ^ How to handle default headers
  -> [Header]              -- ^ Additional or replacement headers
  -> Maybe Lazy.ByteString -- ^ Payload or Nothing
  -> Maybe Text            -- ^ URI or Nothing
  -> AppS
mkInitialState m method useDefault custHeaders payloadLbs uri = AppS
  { focus = UrlEditor
  , methodEditor = editor MethodEditor (Just 1) method
  , overlayState = Nothing
  , urlEditor = editor UrlEditor (Just 1) (T.unpack $ fromMaybe "" uri)
  , lastResponse = emptyImage
  , useDefaultHeaders = toEnum $ fromEnum useDefault
  , customHeaders = map (True,) custHeaders
  , headerEditor = Nothing
  , payload = payloadLbs
  , connManager = m
  }

{-|
Launch the RESTman TUI.

Creates a new HTTP 'HC.Manager' using 'robustSettings', builds the initial 'AppS' via
'mkInitialState', and — if a URI was supplied — fires an initial request before entering
the Brick event loop.
-}
startUI
  :: String -- ^ Initial HTTP method
  -> UseDefaultHeaders -- ^ how to handle default headers
  -> [Header] -- ^ additional or replacement headers
  -> Maybe Lazy.ByteString -- ^ payload or Nothing
  -> Maybe Text -- ^ uri or Nothing
  -> IO ()
startUI method useDefault custHeaders payloadLbs uri = do
  manager <- HC.newManager robustSettings
  uiState <- case uri of
    Nothing -> pure $ mkInitialState manager method useDefault custHeaders payloadLbs uri
    Just{} -> doRequest $ mkInitialState manager method useDefault custHeaders payloadLbs uri
  _ <- defaultMain app uiState
  pure ()
 where
  app = App
    { appDraw = draw
    , appChooseCursor = chooseCursor
    , appHandleEvent = handleEvent
    , appStartEvent = pure ()
    , appAttrMap = attrMap
    }