packages feed

nomyx-web-1.0.0: src/Nomyx/Web/Common.hs

{-# LANGUAGE DoAndIfThenElse      #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE GADTs                #-}
{-# LANGUAGE NamedFieldPuns       #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE QuasiQuotes          #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeSynonymInstances #-}

module Nomyx.Web.Common where

import           Control.Concurrent.STM
import           Control.Lens
import           Control.Monad.State
import           Data.Acid.Advanced                  (query')
import qualified Data.ByteString.Char8               as C
import           Data.Char
import           Data.Maybe
import           Data.Monoid
import           Data.String
import           Data.Text                           (Text, append, tail,
                                                      unpack)
import           Happstack.Authenticate.Core         (AuthenticateURL (..),
                                                      GetUserByUserId (..),
                                                      User, UserId (..),
                                                      getUserId)
import           Happstack.Server                    as HS
import           Language.Haskell.HsColour.Colourise (defaultColourPrefs)
import           Language.Haskell.HsColour.HTML      (hscolour)
import           Language.Javascript.JMacro          (JStat (..), jLam, jVarTy,
                                                      jhFromList, jmacro,
                                                      toJExpr)
import           Network.HTTP.Types                  (urlEncode)
import           Nomyx.Language
import           Nomyx.Core.Engine
import           Nomyx.Core.Profile
import           Nomyx.Core.Session
import           Nomyx.Core.Types                    as T
import           Nomyx.Web.Types
import qualified Nomyx.Auth                          as Auth
import           Numeric
import           Prelude                             hiding (div)
import           Safe
import           Text.Blaze.Html.Renderer.Utf8       (renderHtml)
import           Text.Blaze.Html5                    hiding (base, map, output)
import qualified Text.Blaze.Html5                    as H
import           Text.Blaze.Html5.Attributes         hiding (dir, id)
import qualified Text.Blaze.Html5.Attributes         as A
import           Text.Printf
import           Text.Reform                         (ErrorInputType, Form,
                                                      FormError (..), FormInput)
import           Text.Reform.Blaze.String            ()
import qualified Text.Reform.Generalized             as G
import           Text.Reform.Happstack               ()
import           Web.Routes.Base
import           Web.Routes.Happstack                ()
import           Web.Routes.RouteT
import           Web.Routes.PathInfo
import           Imprevu.Happstack.Types hiding (updateSession)


ruleFormAnchor, inputAnchor :: Text
ruleFormAnchor = "RuleForm"
inputAnchor = "Input"

blazeResponse :: Html -> Response
blazeResponse html = toResponseBS (C.pack "text/html;charset=UTF-8") $ renderHtml html

blazeForm :: Html -> Text -> Html
blazeForm html link =
    H.form ! A.action (toValue link)
         ! A.method "POST"
         ! A.enctype "multipart/form-data" $
            do html
               input ! A.type_ "submit" ! A.value "Submit"

mainPage' :: String -> Html -> Bool -> Html -> RoutedNomyxServer Response
mainPage' title header footer body = do
   html <- mainPage title header body footer False
   return $ toResponse html

mainPage :: String -> Html -> Html -> Bool -> Bool -> RoutedNomyxServer Html
mainPage title header body footer backLink = do
   routeFn <- askRouteFn
   ok $ pageTemplate title header body footer routeFn

pageTemplate ::
       String -- ^ title
    -> Html   -- ^ extra tags to include in \<head\>
    -> Html   -- ^ contents to put inside \<body\>
    -> Bool   -- ^ include footer
    -> (URL (RouteT PlayerCommand (ServerPartT IO))
                          -> [(Text, Maybe Text)] -> Text)
    -> Html
pageTemplate title headers body footer routeFn = do
 H.html $ do  
   H.head $ do
      H.title (fromString title)
      H.link ! rel "stylesheet" ! type_ "text/css" ! href "/static/css/nomyx.css"
      H.script ! A.type_ "text/JavaScript" ! A.src "/static/js/jquery.min.js" $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src "/static/js/bootstrap.min.js" $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src "/static/js/angular.min.js" $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src "/static/js/angular-route.min.js" $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src "/static/js/nomyx.js" $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src (textValue $ routeFn NomyxJS []) $ ""
      H.script ! A.type_ "text/JavaScript" ! A.src (textValue $ routeFn (Auth Controllers) []) $ ""
      H.meta ! A.httpEquiv "Content-Type" ! content "text/html;charset=utf-8"
      H.meta ! A.name "keywords" ! A.content "Nomyx, game, rules, Haskell, auto-reference"
   H.body ! onload "loadDivVisibility()"  ! customAttribute "ng-controller" "AuthenticationCtrl" ! customAttribute "ng-app" "NomyxApp" $ H.div ! A.id "container" $ do
      H.div ! A.id "header" $ headers
      body ! A.id "multi" 
      when footer $ H.div ! A.id "footer" $ "Copyright Corentin Dupont 2012-2013"

-- | return the player number (user ID) based on the session cookie.
getPlayerNumber :: RoutedNomyxServer (Maybe PlayerNumber)
getPlayerNumber = do
  auth <- use authState
  liftRouteT $ lift $ Auth.getPlayerNumber auth

getUser :: RoutedNomyxServer (Maybe User)
getUser = do
  auth <- use authState
  liftRouteT $ lift $ Auth.getUser auth

getProfile' :: PlayerNumber -> RoutedNomyxServer (Maybe ProfileData)
getProfile' pn = do
  s <- getSession
  getProfile s pn

getSession :: RoutedNomyxServer Session
getSession = do
  ts <- use webSession
  liftIO $ atomically $ readTVar ts

--update the session using the command and saves it
webCommand :: StateT Session IO () -> RoutedNomyxServer ()
webCommand ss = do
  ts <- use webSession
  liftIO $ updateSession ts ss

isAdmin :: RoutedNomyxServer Bool
isAdmin = do
   mpn <- getPlayerNumber
   case mpn of
      Just pn -> do
         mpf <- getProfile' pn
         case mpf of
            Just pf -> return $ _pIsAdmin pf
            Nothing -> return False
      Nothing -> return False

isGameAdmin :: GameInfo -> RoutedNomyxServer Bool
isGameAdmin gi = do
   mpn <- getPlayerNumber
   admin <- isAdmin
   return $ case mpn of
      Just pn -> admin || (_ownedBy gi == Just pn)
      Nothing -> False

getPublicGames :: RoutedNomyxServer [GameInfo]
getPublicGames = do
   s <- getSession
   return $ filter ((== True) . _isPublic) (_gameInfos $ _multi s)

fieldRequired :: NomyxError -> String -> Either NomyxError String
fieldRequired a []  = Left a
fieldRequired _ str = Right str

idEncode :: String -> String
idEncode t = map (\c -> if c==' ' then '+'; else c) t

displayCode :: String -> Html
displayCode s = preEscapedToHtml $ hscolour defaultColourPrefs False 0 s

getGame :: GameInfo -> Game
getGame = _game . _loggedGame

numberOfGamesOwned :: [GameInfo] -> PlayerNumber -> Int
numberOfGamesOwned gis pn = length $ filter (maybe False (==pn) . _ownedBy) gis

getFirstGame :: Session -> Maybe GameInfo
getFirstGame = headMay . filter _isPublic ._gameInfos . _multi

titleWithHelpIcon :: Html -> String -> Html
titleWithHelpIcon myTitle help = table ! width "100%" $ tr $ do
   td ! A.style "text-align:left;" $ myTitle
   td ! A.style "text-align:right;" $ img ! src "/static/pictures/help.jpg" ! A.title (toValue help)

defLink :: PlayerCommand -> Bool -> Text
defLink a logged = if logged then showRelURL a else showRelURL Login

trim = unwords . words

showRelURL :: PlayerCommand -> Text
showRelURL c = "/Nomyx" <> (toPathInfo c)

showRelURLParams :: PlayerCommand -> [(Text, Maybe Text)] -> Text
showRelURLParams c ps = "/Nomyx" <> (toPathInfoParams c ps)

viewRuleCode :: RuleCode -> Html
viewRuleCode rc = do
  let code = lines rc
  let codeCutLines = 7
  div ! A.class_ "codeDiv" $ displayCode $ unlines $ take codeCutLines code
  div $ when (length code >= codeCutLines) $ fromString "(...)"

viewRuleHead :: String -> Maybe FilePath -> String -> String -> Html
viewRuleHead name picture desc author = do
    let pic = fromMaybe "/static/pictures/democracy.png" picture
    div ! A.class_ "ruleTitle" $ fromString $ name
    div ! A.class_ "ruleImg" $ img ! (A.src $ toValue $ pic)
    div ! A.class_ "ruleDescr" $ fromString $ desc
    div ! A.class_ "ruleAuthor" $ h2 $ fromString $ "authored by " ++ author

-- | app module for angulasjs
--
-- We just depend on the usernamePassword module
nomyxJS :: JStat
nomyxJS = [jmacro|
 {
   var demoApp = angular.module('NomyxApp', [
     'happstackAuthentication',
     'usernamePassword',
     'openId',
     'ngRoute'
   ]);

   demoApp.config(['$routeProvider',
     function($routeProvider) {
       $routeProvider.when('/resetPassword',
                            { templateUrl: '/authenticate/authentication-methods/password/partial/reset-password-form',
                              controller: 'UsernamePasswordCtrl'
                            });
     }]);

   demoApp.controller('NomyxCtrl', ['$scope', '$http',function($scope, $http) {
     $scope.message = '';

     $scope.callRestricted = function (url) {
       $http({url: url, method: 'GET'}).
       success(function (datum, status, headers, config) {
         $scope.message = datum.name;
       }).
       error(function (datum, status, headers, config) {
         alert(datum);
       });
     };
   }]);
 }
|]