Refactor http example into Client component

- Client will have own Action and Model
    - Model is TODO but needs to hold all of the aborts
      usually a front-end client wants to abort other ongoing requests

- Network.Client will have an update function that uses effectSub to
  create a thread that will inform the app of the results of the xhr
  action. We shouldn't just do this in Main, it gets messy
This commit is contained in:
towards-a-new-leftypol 2024-01-12 18:59:20 -05:00
parent bbc6a69d57
commit 107013756b
5 changed files with 165 additions and 148 deletions

View File

@ -64,6 +64,7 @@ executable chandlr
-- Modules included in this executable, other than Main.
other-modules: Component.CatalogGrid
Action
Network.Http
Network.Client
Routes

View File

@ -13,5 +13,5 @@ data Action
, board_thread_id :: Int
}
| HaveLatest (Client.HttpResult Text)
| NewConnection Client.HttpActionResult
| ClientAction Client.Action
| NoAction

View File

@ -1,15 +1,10 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Concurrent.MVar (takeMVar)
import Data.Proxy
import Data.Text (Text)
import Control.Monad (void)
import Control.Concurrent (forkIO)
import Miso
( View
@ -27,7 +22,6 @@ import Miso
, URI
, runRoute
, getCurrentURI
, effectSub
, consoleLog
--, MisoString (..)
)
@ -36,13 +30,14 @@ import Servant.API
import Action
import Routes
import Network.Client as Client
import qualified Network.Client as Client
import qualified Component.CatalogGrid as Grid
data Model = Model
{ gridModel :: Grid.Model
, clientModel :: Client.Model
} deriving Eq
@ -63,6 +58,7 @@ initialActionFromRoute model uri = either (const NoAction) id routing_result
initialModel :: Model
initialModel = Model
{ gridModel = Grid.initialModel
, clientModel = Client.initialModel
}
@ -101,68 +97,24 @@ mainUpdate (HaveLatest Client.Error) m = noEff m
mainUpdate (HaveLatest (Client.HttpResponse {..})) m = m <# do
case body of
Nothing -> do
putStrLn "Didn't get anything back from API"
consoleLog "Didn't get anything back from API"
Just txt -> do
print txt
consoleLog $ toJSString txt
return NoAction
mainUpdate (NewConnection (_, resultVar)) m = effectSub m $ \sink -> do
void $ forkIO $ do
result :: Client.HttpResult Text <- takeMVar resultVar
sink $ HaveLatest result
mainUpdate GetLatest m = m <# do
putStrLn "Getting Latest!"
stuff <- Client.http
"http://localhost:3000/posts?limit=10"
Client.GET
Nothing
return $ NewConnection stuff
{-
mainUpdate GetLatest m =
Client.http
"/posts?limit=10"
Client.GET
Nothing
<# \(abort, resultVar) ->
effectSub m (httpSub resultVar) <> noEff m
where
httpSub :: MVar (Client.HttpResult Text) -> Sink Action -> IO ()
httpSub resultVar sink = do
result :: Client.HttpResult Text <- liftIO $ takeMVar resultVar
liftIO $ sink (HaveLatest result)
-}
{-
mainUpdate GetLatest m = do
(abort, resultVar) <- Client.http
"/posts?limit=10"
Client.GET
Nothing
-- return noEff m
return $ effectSub m (httpSub resultVar) <> noEff m -- { abortAction = Just abort }
where
httpSub :: MVar (Client.HttpResult Text) -> Sink Action -> IO ()
httpSub resultVar sink = do
result :: Client.HttpResult Text <- liftIO $ takeMVar resultVar
liftIO $ sink (HaveLatest result)
-}
mainUpdate GetLatest m = m <# Client.fetchLatest iClient
mainUpdate GetThread {..} m = noEff m
mainUpdate (GridAction ga) m =
Grid.update iGrid ga (gridModel m)
>>= \gm -> noEff (m { gridModel = gm })
mainUpdate (ClientAction ca) m =
Client.update iClient ca (clientModel m)
>>= \cm -> noEff (m { clientModel = cm })
iGrid :: Grid.Interface Action
iGrid = Grid.Interface
@ -170,6 +122,12 @@ iGrid = Grid.Interface
, Grid.selectThread = ()
}
iClient :: Client.Interface Action
iClient = Client.Interface
{ Client.passAction = ClientAction
, Client.returnResult = HaveLatest
}
{-
- TODO:
- - Create Hello World page render

View File

@ -1,105 +1,58 @@
{-# LANGUAGE ScopedTypeVariables #-}
module Network.Client
( http
, HttpActionResult
, HttpMethod (..)
, HttpResult (..)
)
where
( Http.HttpActionResult
, Http.HttpMethod (..)
, Http.HttpResult (..)
, Action (..)
, Interface (..)
, fetchLatest
, Model
, update
, initialModel
) where
import Data.Text (Text)
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar)
import GHCJS.DOM.XMLHttpRequest
( newXMLHttpRequest
, openSimple
, getStatus
, getStatusText
, getResponseText
, abort
-- , send
)
import GHCJS.DOM.JSFFI.Generated.XMLHttpRequest (send)
import GHCJS.DOM.Types (XMLHttpRequest)
import Data.JSString.Text (textToJSString)
import GHCJS.DOM.EventM (onAsync)
import GHCJS.DOM.XMLHttpRequestEventTarget (load)
import Control.Monad (void)
import Control.Concurrent (forkIO)
import Control.Concurrent.MVar (takeMVar)
-- What we actually want is to call send and not block the thread
-- - so that we can put the request into our list of ongoing requests.
-- - and have the response come back as an Action?
-- - we would need the onload event to send an Action to updateModel
import Miso (effectSub, Effect)
{-
- Okay what the hell do I even want from an http function?
-
- the js implementation has these features:
-
- - url
- - method
- - ability to abort
- - is async
- - get the result from it
- - this should be a fn (IO result), let's not expose the xhr object?
-
- Also want:
- - return the same data structure always, but that data structure should
- inform of any errors, and have the status code, response body,
- and status text.
-
-
- Do we really need some sort of isomorphic framework? I think that's
- more work than just using a simple http lib server-side... tbqh
-}
data HttpMethod = GET | PUT | POST | DELETE | PATCH
deriving Show
data HttpResult a
= Error
| HttpResponse
{ status_code :: Int
, status_text :: String
, body :: Maybe a
}
type HttpActionResult = (IO (), MVar (HttpResult Text)) -- (abort, result)
import qualified Network.Http as Http
mkResult :: XMLHttpRequest -> IO (HttpResult Text)
mkResult xhr = do
sc <- getStatus xhr
let status_code_int :: Int = fromEnum sc
st :: String <- getStatusText xhr
mBody :: Maybe Text <- getResponseText xhr
return HttpResponse
{ status_code = status_code_int
, status_text = st
, body = mBody
}
data Action = Connect Http.HttpActionResult
http
:: String
-> HttpMethod
-> Maybe Text
-> IO HttpActionResult
http url method payload = do
xhr <- newXMLHttpRequest
data Interface a = Interface
{ passAction :: Action -> a
, returnResult :: Http.HttpResult Text -> a
}
resultVar <- newEmptyMVar
_ <- onAsync xhr load $ liftIO $ do
result <- mkResult xhr
putMVar resultVar result
type Model = ()
openSimple xhr (show method) url
-- "/posts?limit=10"
initialModel :: Model
initialModel = ()
send xhr (payload >>= Just . textToJSString)
return (abort xhr, resultVar)
update
:: Interface a
-> Action
-> Model
-> Effect a Model
update iface (Connect (_, resultVar)) m = effectSub m $
\sink -> void $ forkIO $ do
result :: Http.HttpResult Text <- takeMVar resultVar
sink $ (returnResult iface) result
fetchLatest :: Interface a -> IO a
fetchLatest iface =
Http.http
"http://localhost:3000/posts?limit=10"
Http.GET
Nothing
>>= return . (passAction iface) . Connect

105
src/Network/Http.hs Normal file
View File

@ -0,0 +1,105 @@
{-# LANGUAGE ScopedTypeVariables #-}
module Network.Http
( http
, HttpActionResult
, HttpMethod (..)
, HttpResult (..)
)
where
import Data.Text (Text)
import Control.Monad.IO.Class (liftIO)
import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar)
import GHCJS.DOM.XMLHttpRequest
( newXMLHttpRequest
, openSimple
, getStatus
, getStatusText
, getResponseText
, abort
-- , send
)
import GHCJS.DOM.JSFFI.Generated.XMLHttpRequest (send)
import GHCJS.DOM.Types (XMLHttpRequest)
import Data.JSString.Text (textToJSString)
import GHCJS.DOM.EventM (onAsync)
import GHCJS.DOM.XMLHttpRequestEventTarget (load)
-- What we actually want is to call send and not block the thread
-- - so that we can put the request into our list of ongoing requests.
-- - and have the response come back as an Action?
-- - we would need the onload event to send an Action to updateModel
{-
- Okay what the hell do I even want from an http function?
-
- the js implementation has these features:
-
- - url
- - method
- - ability to abort
- - is async
- - get the result from it
- - this should be a fn (IO result), let's not expose the xhr object?
-
- Also want:
- - return the same data structure always, but that data structure should
- inform of any errors, and have the status code, response body,
- and status text.
-
-
- Do we really need some sort of isomorphic framework? I think that's
- more work than just using a simple http lib server-side... tbqh
-}
data HttpMethod = GET | PUT | POST | DELETE | PATCH
deriving Show
data HttpResult a
= Error
| HttpResponse
{ status_code :: Int
, status_text :: String
, body :: Maybe a
}
type HttpActionResult = (IO (), MVar (HttpResult Text)) -- (abort, result)
mkResult :: XMLHttpRequest -> IO (HttpResult Text)
mkResult xhr = do
sc <- getStatus xhr
let status_code_int :: Int = fromEnum sc
st :: String <- getStatusText xhr
mBody :: Maybe Text <- getResponseText xhr
return HttpResponse
{ status_code = status_code_int
, status_text = st
, body = mBody
}
http
:: String
-> HttpMethod
-> Maybe Text
-> IO HttpActionResult
http url method payload = do
xhr <- newXMLHttpRequest
resultVar <- newEmptyMVar
_ <- onAsync xhr load $ liftIO $ do
result <- mkResult xhr
putMVar resultVar result
openSimple xhr (show method) url
-- "/posts?limit=10"
send xhr (payload >>= Just . textToJSString)
return (abort xhr, resultVar)