{-# 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 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 Config { tpPort = 10000 , tpCustomHTML = Just "chat.html" , tpStatic = static } $ setup messages type Message = (UTCTime, String, String) setup :: Chan Message -> Window -> IO () setup globalMsgs w = do msgs <- Chan.dupChan globalMsgs return w # set title "Chat" (nick, nickname) <- mkNickname messageArea <- mkMessageArea msgs nick getBody w #+ [ UI.div #. "header" #+ [string "Threepenny Chat"] , UI.div #. "gradient" , viewSource , element nickname , element messageArea ] void $ forkIO $ receiveMessages w msgs messageArea -- io $ catch (runTP session handleEvents) -- (\e -> do killThread messageReceiver -- throw (e :: SomeException)) 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 -> Element -> 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 <- trim <$> get value 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 (Element, Element) mkNickname = do i <- UI.input #. "name-input" el <- UI.div #. "name-area" #+ [ UI.span #. "name-label" #+ [string "Your name "] , element i ] UI.setFocus i return (i,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"