{-# LANGUAGE CPP, PackageImports #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
import Control.Concurrent
import qualified Control.Concurrent.Chan as Chan
import Control.Exception
import Control.Monad
import Data.Functor
import Data.List.Extra
import Data.Time
import Data.IORef
import Prelude hiding (catch)
import Control.Monad.Trans.Reader as Reader
import Control.Monad.IO.Class
#ifdef CABAL
import qualified "threepenny-gui" Graphics.UI.Threepenny as UI
import "threepenny-gui" Graphics.UI.Threepenny.Core hiding (text)
#else
import qualified Graphics.UI.Threepenny as UI
import Graphics.UI.Threepenny.Core hiding (text)
#endif
import Paths
{-----------------------------------------------------------------------------
Chat
------------------------------------------------------------------------------}
main :: IO ()
main = do
static <- getStaticDir
messages <- Chan.newChan
startGUI defaultConfig
{ tpPort = 10000
, tpCustomHTML = Just "chat.html"
, tpStatic = Just static
} $ setup messages
type Message = (UTCTime, String, String)
setup :: Chan Message -> Window -> IO ()
setup globalMsgs window = do
msgs <- Chan.dupChan globalMsgs
return window # set title "Chat"
(nickRef, nickname) <- mkNickname
messageArea <- mkMessageArea msgs nickRef
getBody window #+
[ UI.div #. "header" #+ [string "Threepenny Chat"]
, UI.div #. "gradient"
, viewSource
, element nickname
, element messageArea
]
messageReceiver <- forkIO $ receiveMessages window msgs messageArea
on UI.disconnect window $ const $ do
putStrLn "Disconnected!"
killThread messageReceiver
now <- getCurrentTime
nick <- readIORef nickRef
Chan.writeChan msgs (now,nick,"( left the conversation )")
receiveMessages w msgs messageArea = do
messages <- Chan.getChanContents msgs
forM_ messages $ \msg -> do
atomic w $ do
element messageArea #+ [mkMessage msg]
UI.scrollToBottom messageArea
mkMessageArea :: Chan Message -> IORef String -> IO Element
mkMessageArea msgs nickname = do
input <- UI.textarea #. "send-textarea"
on UI.sendValue input $ (. trim) $ \content -> do
when (not (null content)) $ do
now <- getCurrentTime
nick <- readIORef nickname
element input # set value ""
when (not (null nick)) $
Chan.writeChan msgs (now,nick,content)
UI.div #. "message-area" #+ [UI.div #. "send-area" #+ [element input]]
mkNickname :: IO (IORef String, Element)
mkNickname = do
input <- UI.input #. "name-input"
el <- UI.div #. "name-area" #+
[ UI.span #. "name-label" #+ [string "Your name "]
, element input
]
UI.setFocus input
nick <- newIORef ""
on UI.keyup input $ \_ -> writeIORef nick . trim =<< get value input
return (nick,el)
mkMessage :: Message -> IO Element
mkMessage (timestamp, nick, content) =
UI.div #. "message" #+
[ UI.div #. "timestamp" #+ [string $ show timestamp]
, UI.div #. "name" #+ [string $ nick ++ " says:"]
, UI.div #. "content" #+ [string content]
]
viewSource :: IO Element
viewSource =
UI.anchor #. "view-source" # set UI.href url #+ [string "View source code"]
where
url = "https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/src/Chat.hs"