{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DuplicateRecordFields #-}
module Purview
(
run
, Configuration (..)
, defaultConfiguration
, simpleHandler
, messageHandler
, effectHandler
, div
, span
, p
, h1
, h2
, h3
, h4
, text
, button
, form
, input
, style
, onClick
, onSubmit
, render
, Attributes (..)
, DirectedEvent (..)
, Purview (..)
)
where
import Prelude hiding (div, log, span)
import qualified Web.Scotty as Sc
import Data.Text (pack, Text, all)
import qualified Data.Text.Lazy as LazyText
import qualified Network.Wai.Middleware.Gzip as Sc
import qualified Network.Wai.Handler.WebSockets as WaiWebSocket
import qualified Network.WebSockets as WebSocket
import qualified Network.Wai as Wai
import qualified Network.Wai.Handler.Warp as Warp
import Data.Aeson
import Control.Monad (when)
import Control.Concurrent.STM.TChan
import Control.Monad.STM
import Control.Concurrent
import Component
import EventLoop
import Events
import PrepareTree
import Rendering
import Wrapper
import Network.Wai.Middleware.RequestLogger (mkRequestLogger)
type Log m = String -> m ()
data Configuration parentAction action m = Configuration
{ forall parentAction action (m :: * -> *).
Configuration parentAction action m
-> Purview parentAction action m
component :: Purview parentAction action m
, forall parentAction action (m :: * -> *).
Configuration parentAction action m -> m [Event] -> IO [Event]
interpreter :: m [Event] -> IO [Event]
, forall parentAction action (m :: * -> *).
Configuration parentAction action m -> String -> IO ()
logger :: String -> IO ()
, forall parentAction action (m :: * -> *).
Configuration parentAction action m -> [HtmlEventHandler]
htmlEventHandlers :: [HtmlEventHandler]
, forall parentAction action (m :: * -> *).
Configuration parentAction action m -> Text
htmlHead :: Text
, forall parentAction action (m :: * -> *).
Configuration parentAction action m -> Bool
devMode :: Bool
}
defaultConfiguration :: Configuration parentAction action IO
defaultConfiguration :: forall parentAction action. Configuration parentAction action IO
defaultConfiguration = Configuration
{ $sel:component:Configuration :: Purview parentAction action IO
component = [Purview parentAction action IO] -> Purview parentAction action IO
forall parentAction action (m :: * -> *).
[Purview parentAction action m] -> Purview parentAction action m
div []
, $sel:interpreter:Configuration :: IO [Event] -> IO [Event]
interpreter = IO [Event] -> IO [Event]
forall a. a -> a
id
, $sel:logger:Configuration :: String -> IO ()
logger = String -> IO ()
forall a. Show a => a -> IO ()
print
, $sel:htmlEventHandlers:Configuration :: [HtmlEventHandler]
htmlEventHandlers = [HtmlEventHandler
clickEventHandler, HtmlEventHandler
submitEventHandler]
, $sel:htmlHead:Configuration :: Text
htmlHead = Text
""
, $sel:devMode:Configuration :: Bool
devMode = Bool
False
}
run :: Monad m => Configuration () any m -> IO ()
run :: forall (m :: * -> *) any.
Monad m =>
Configuration () any m -> IO ()
run Configuration { Bool
devMode :: Bool
$sel:devMode:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m -> Bool
devMode, Purview () any m
component :: Purview () any m
$sel:component:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m
-> Purview parentAction action m
component, String -> IO ()
logger :: String -> IO ()
$sel:logger:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m -> String -> IO ()
logger, m [Event] -> IO [Event]
interpreter :: m [Event] -> IO [Event]
$sel:interpreter:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m -> m [Event] -> IO [Event]
interpreter, [HtmlEventHandler]
htmlEventHandlers :: [HtmlEventHandler]
$sel:htmlEventHandlers:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m -> [HtmlEventHandler]
htmlEventHandlers, Text
htmlHead :: Text
$sel:htmlHead:Configuration :: forall parentAction action (m :: * -> *).
Configuration parentAction action m -> Text
htmlHead } = do
let port :: Port
port = Port
8001
let settings :: Settings
settings = Port -> Settings -> Settings
Warp.setPort Port
port Settings
Warp.defaultSettings
Application
requestHandler' <- Purview () any m -> Text -> [HtmlEventHandler] -> IO Application
forall parentAction action (m :: * -> *).
Purview parentAction action m
-> Text -> [HtmlEventHandler] -> IO Application
requestHandler Purview () any m
component Text
htmlHead [HtmlEventHandler]
htmlEventHandlers
Settings -> Application -> IO ()
Warp.runSettings Settings
settings
(Application -> IO ()) -> Application -> IO ()
forall a b. (a -> b) -> a -> b
$ ConnectionOptions -> ServerApp -> Application -> Application
WaiWebSocket.websocketsOr
ConnectionOptions
WebSocket.defaultConnectionOptions
(Bool
-> (m [Event] -> IO [Event])
-> (String -> IO ())
-> Purview () any m
-> ServerApp
forall (m :: * -> *) parentAction action.
Monad m =>
Bool
-> (m [Event] -> IO [Event])
-> (String -> IO ())
-> Purview parentAction action m
-> ServerApp
webSocketHandler Bool
devMode m [Event] -> IO [Event]
interpreter String -> IO ()
logger Purview () any m
component)
Application
requestHandler'
requestHandler :: Purview parentAction action m -> Text -> [HtmlEventHandler] -> IO Wai.Application
requestHandler :: forall parentAction action (m :: * -> *).
Purview parentAction action m
-> Text -> [HtmlEventHandler] -> IO Application
requestHandler Purview parentAction action m
routes Text
htmlHead [HtmlEventHandler]
htmlEventHandlers =
ScottyM () -> IO Application
Sc.scottyApp (ScottyM () -> IO Application) -> ScottyM () -> IO Application
forall a b. (a -> b) -> a -> b
$ do
(Application -> Application) -> ScottyM ()
Sc.middleware ((Application -> Application) -> ScottyM ())
-> (Application -> Application) -> ScottyM ()
forall a b. (a -> b) -> a -> b
$ GzipSettings -> Application -> Application
Sc.gzip (GzipSettings -> Application -> Application)
-> GzipSettings -> Application -> Application
forall a b. (a -> b) -> a -> b
$ GzipSettings
forall a. Default a => a
Sc.def { gzipFiles :: GzipFiles
Sc.gzipFiles = GzipFiles
Sc.GzipCompress }
RoutePattern -> ActionM () -> ScottyM ()
Sc.get RoutePattern
"/"
(ActionM () -> ScottyM ()) -> ActionM () -> ScottyM ()
forall a b. (a -> b) -> a -> b
$ Text -> ActionM ()
Sc.html
(Text -> ActionM ()) -> Text -> ActionM ()
forall a b. (a -> b) -> a -> b
$ Text -> Text
LazyText.fromStrict
(Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ Text -> [HtmlEventHandler] -> Text -> Text
wrapHtml Text
htmlHead [HtmlEventHandler]
htmlEventHandlers
(Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ String -> Text
Data.Text.pack
(String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Purview parentAction action m -> String
forall parentAction action (m :: * -> *).
Purview parentAction action m -> String
render (Purview parentAction action m -> String)
-> ((Purview parentAction action m, [Event])
-> Purview parentAction action m)
-> (Purview parentAction action m, [Event])
-> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Purview parentAction action m, [Event])
-> Purview parentAction action m
forall a b. (a, b) -> a
fst
((Purview parentAction action m, [Event]) -> String)
-> (Purview parentAction action m, [Event]) -> String
forall a b. (a -> b) -> a -> b
$ Purview parentAction action m
-> (Purview parentAction action m, [Event])
forall parentAction action (m :: * -> *).
Purview parentAction action m
-> (Purview parentAction action m, [Event])
prepareTree Purview parentAction action m
routes
webSocketMessageHandler :: TChan Event -> WebSocket.Connection -> IO ()
webSocketMessageHandler :: TChan Event -> Connection -> IO ()
webSocketMessageHandler TChan Event
eventBus Connection
websocketConnection = do
ByteString
message' <- Connection -> IO ByteString
forall a. WebSocketsData a => Connection -> IO a
WebSocket.receiveData Connection
websocketConnection
case ByteString -> Maybe Event
forall a. FromJSON a => ByteString -> Maybe a
decode ByteString
message' of
Just Event
fromEvent -> STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TChan Event -> Event -> STM ()
forall a. TChan a -> a -> STM ()
writeTChan TChan Event
eventBus Event
fromEvent
Maybe Event
Nothing -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
TChan Event -> Connection -> IO ()
webSocketMessageHandler TChan Event
eventBus Connection
websocketConnection
webSocketHandler
:: Monad m
=> Bool
-> (m [Event] -> IO [Event])
-> Log IO
-> Purview parentAction action m
-> WebSocket.ServerApp
webSocketHandler :: forall (m :: * -> *) parentAction action.
Monad m =>
Bool
-> (m [Event] -> IO [Event])
-> (String -> IO ())
-> Purview parentAction action m
-> ServerApp
webSocketHandler Bool
devMode m [Event] -> IO [Event]
runner String -> IO ()
log Purview parentAction action m
component PendingConnection
pending = do
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
devMode (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
putStrLn String
"ws connected"
Connection
conn <- PendingConnection -> IO Connection
WebSocket.acceptRequest PendingConnection
pending
TChan Event
eventBus <- IO (TChan Event)
forall a. IO (TChan a)
newTChanIO
STM () -> IO ()
forall a. STM a -> IO a
atomically (STM () -> IO ()) -> STM () -> IO ()
forall a b. (a -> b) -> a -> b
$ TChan Event -> Event -> STM ()
forall a. TChan a -> a -> STM ()
writeTChan TChan Event
eventBus (Event -> STM ()) -> Event -> STM ()
forall a b. (a -> b) -> a -> b
$ Event { $sel:event:Event :: Text
event = Text
"init", $sel:message:Event :: Value
message = Value
"init", $sel:location:Event :: Maybe [Port]
location = Maybe [Port]
forall a. Maybe a
Nothing }
Connection -> Port -> IO () -> IO () -> IO ()
forall a. Connection -> Port -> IO () -> IO a -> IO a
WebSocket.withPingThread Connection
conn Port
30 (() -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()) (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ do
ThreadId
_ <- IO () -> IO ThreadId
forkIO (IO () -> IO ThreadId) -> IO () -> IO ThreadId
forall a b. (a -> b) -> a -> b
$ TChan Event -> Connection -> IO ()
webSocketMessageHandler TChan Event
eventBus Connection
conn
Bool
-> (m [Event] -> IO [Event])
-> (String -> IO ())
-> TChan Event
-> Connection
-> Purview parentAction action m
-> IO ()
forall (m :: * -> *) parentAction action.
Monad m =>
Bool
-> (m [Event] -> IO [Event])
-> (String -> IO ())
-> TChan Event
-> Connection
-> Purview parentAction action m
-> IO ()
eventLoop Bool
devMode m [Event] -> IO [Event]
runner String -> IO ()
log TChan Event
eventBus Connection
conn Purview parentAction action m
component