on master help sign in
Merge pull request #104 from obsidiansystems/sr-mainwidgetwithhead

obelisk-run: allow custom <head>

commit8b69a0b0aadaf7a218aa46de6f3572be0f872e73 authorLuigy Leon <luichi.19@gmail.com> dateSat 19 May 2018, 20:09 (8 years ago) parentc87d93decd567b
13 files changed+80−115
lib/asset/serve-snap/obelisk-asset-serve.cabal → lib/asset/serve-snap/obelisk-asset-serve-snap.cabal lib/backend/obelisk-backend.cabal+6−3 lib/backend/src/Obelisk/Backend.hs+12−4 lib/run/obelisk-run.cabal+1 lib/run/src/Obelisk/Run.hs+29−8 lib/snap/default.nix−17 lib/snap/obelisk-snap.cabal+2−1 lib/snap/src/Obelisk/Snap.hs+12−71 skeleton/backend/backend.cabal+2 skeleton/backend/frontendJs/frontend.jsexe+1 skeleton/backend/src/Backend.hs+3 skeleton/frontend/src/Frontend.hs+11−10 skeleton/frontend/src/main.hs+1−1
lib/asset/serve-snap/obelisk-asset-serve.cabal → lib/asset/serve-snap/obelisk-asset-serve-snap.cabalrenamed

The content is the same.

lib/backend/obelisk-backend.cabal+6−3
@@ -6,9 +6,12 @@ build-type: Simple
66library
77 hs-source-dirs: src
88 build-depends: base,
9− snap,
10− snap-server,
9+ bytestring,
1110 data-default,
11+ lens,
1212 obelisk-asset-serve-snap,
13− bytestring
13+ obelisk-snap,
14+ reflex-dom,
15+ snap,
16+ snap-server
1417 exposed-modules: Obelisk.Backend
lib/backend/src/Obelisk/Backend.hs+12−4
@@ -1,14 +1,17 @@
11{-# LANGUAGE OverloadedStrings #-}
22module Obelisk.Backend
33 ( backend
4− , BackendConfig
4+ , BackendConfig (..)
55 -- * Re-exports
66 , Default (def)
77 ) where
88
9+import Control.Lens
910import qualified Data.ByteString.Char8 as BSC8
1011import Data.Default (Default (..))
1112import Obelisk.Asset.Serve.Snap (serveAssets)
13+import Obelisk.Snap
14+import Reflex.Dom
1215import System.IO (hSetBuffering, stderr, BufferMode (..))
1316import Snap (httpServe, defaultConfig, commandLineConfig, route)
1417import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog (ConfigIoLog))
@@ -17,25 +20,30 @@ import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog
1720-- | Configure the operation of the Obelisk backend. For reasonable defaults,
1821-- use 'def'.
1922data BackendConfig = BackendConfig
23+ { _backendConfig_head :: StaticWidget () ()
24+ }
2025
2126instance Default BackendConfig where
22− def = BackendConfig
27+ def = BackendConfig (return ())
2328
2429-- | Start an Obelisk backend
2530backend :: BackendConfig -> IO ()
26−backend _ = do
31+backend cfg = do
2732 -- Make output more legible by decreasing the likelihood of output from
2833 -- multiple threads being interleaved
2934 hSetBuffering stderr LineBuffering
3035
3136 -- Get the web server configuration from the command line
3237 cmdLineConf <- commandLineConfig defaultConfig
38+ headHtml <- fmap snd $ renderStatic $ _backendConfig_head cfg
3339 let httpConf = cmdLineConf
3440 { accessLog = Just $ ConfigIoLog BSC8.putStrLn
3541 , errorLog = Just $ ConfigIoLog BSC8.putStrLn
3642 }
43+ appCfg = def & appConfig_initialHead .~ headHtml
3744 -- Start the web server
3845 httpServe httpConf $ route
39− [ ("", serveAssets "frontend.jsexe.assets" "frontend.jsexe") --TODO: Can we prevent naming conflicts between frontend.jsexe and static?
46+ [ ("", serveApp "" appCfg)
47+ , ("", serveAssets "frontend.jsexe.assets" "frontend.jsexe") --TODO: Can we prevent naming conflicts between frontend.jsexe and static?
4048 , ("", serveAssets "static.assets" "static")
4149 ]
lib/run/obelisk-run.cabal+1
@@ -12,6 +12,7 @@ library
1212 , bytestring
1313 , http-client
1414 , http-reverse-proxy
15+ , http-types
1516 , jsaddle
1617 , jsaddle-warp
1718 , lens
lib/run/src/Obelisk/Run.hs+29−8
@@ -5,24 +5,28 @@ module Obelisk.Run where
55
66import Control.Concurrent
77import Control.Exception
8−import Control.Lens ((^?), _Right, _Just)
8+import Control.Lens ((^?), _Just, _Right)
99import qualified Data.Attoparsec.ByteString.Char8 as A
1010import Data.ByteString (ByteString)
1111import qualified Data.ByteString.Char8 as BSC
12+import qualified Data.ByteString.Lazy.Char8 as BSLC
1213import Data.List (uncons)
1314import Data.Maybe
1415import Data.Semigroup ((<>))
1516import Data.Streaming.Network (bindPortTCP)
1617import qualified Data.Text as T
17−import Language.Javascript.JSaddle.WebSockets
1818import Language.Javascript.JSaddle.Run (syncPoint)
19−import Network.HTTP.Client (defaultManagerSettings, newManager, Manager)
19+import Language.Javascript.JSaddle.WebSockets
20+import Network.HTTP.Client (Manager, defaultManagerSettings, newManager)
2021import qualified Network.HTTP.ReverseProxy as RP
22+import qualified Network.HTTP.Types as H
2123import Network.Socket
2224import Network.Wai (Application)
25+import qualified Network.Wai as W
2326import Network.Wai.Handler.Warp
27+import Network.Wai.Handler.Warp.Internal (settingsHost, settingsPort)
28+import Network.WebSockets (ConnectionOptions)
2429import Network.WebSockets.Connection (defaultConnectionOptions)
25−import Network.Wai.Handler.Warp.Internal (settingsPort, settingsHost)
2630import Obelisk.ExecutableConfig (get)
2731import Reflex.Dom.Core
2832import System.Environment
@@ -34,7 +38,7 @@ import Text.URI.Lens
3438
3539run :: Int -- ^ Port to run the backend
3640 -> IO () -- ^ Backend
37− -> Widget () () -- ^ Frontend widget
41+ -> (StaticWidget () (), Widget () ()) -- ^ Frontend widget (head, body)
3842 -> IO ()
3943run port backend frontend = do
4044 let handleBackendErr (_ :: SomeException) = hPutStrLn stderr "backend stopped; make a change to your code to reload"
@@ -52,8 +56,8 @@ getConfigRoute = do
5256defAppUri :: URI
5357defAppUri = fromMaybe (error "defAppUri") $ URI.mkURI "http://127.0.0.1:8000"
5458
55−runWidget :: RunConfig -> Widget () () -> IO ()
56−runWidget conf w = do
59+runWidget :: RunConfig -> (StaticWidget () (), Widget () ()) -> IO ()
60+runWidget conf (h, b) = do
5761 uri <- fromMaybe defAppUri <$> getConfigRoute
5862 let port = fromIntegral $ fromMaybe 80 $ uri ^? uriAuthority . _Right . authPort . _Just
5963 redirectHost = _runConfig_redirectHost conf
@@ -66,9 +70,26 @@ runWidget conf w = do
6670 close
6771 (\skt -> do
6872 man <- newManager defaultManagerSettings
69− app <- jsaddleWithAppOr defaultConnectionOptions (mainWidget' w >> syncPoint) (fallbackProxy redirectHost redirectPort man)
73+ app <- obeliskApp defaultConnectionOptions h b (fallbackProxy redirectHost redirectPort man)
7074 runSettingsSocket settings skt app)
7175
76+obeliskApp :: ConnectionOptions -> StaticWidget () () -> Widget () () -> Application -> IO Application
77+obeliskApp opts h b backend = do
78+ html <- BSLC.fromStrict <$> indexHtml h
79+ let entryPoint = mainWidget' b >> syncPoint
80+ jsaddleOr opts entryPoint $ \req sendResponse -> case (W.requestMethod req, W.pathInfo req) of
81+ ("GET", []) -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "text/html")] html
82+ ("GET", ["jsaddle.js"]) -> sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] $ jsaddleJs False
83+ _ -> backend req sendResponse
84+
85+indexHtml :: StaticWidget () () -> IO ByteString
86+indexHtml h = do
87+ ((), bs) <- renderStatic $ el "html" $ do
88+ el "head" $ h
89+ el "body" $ return ()
90+ elAttr "script" ("src" =: "/jsaddle.js") $ return ()
91+ return $ "<!DOCTYPE html>" <> bs
92+
7293-- | like 'bindPortTCP' but reconnects on exception
7394bindPortTCPRetry :: Settings
7495 -> (IOError -> IO ()) -- ^ Action to run the first time an exception is caught
lib/snap/default.nixdeleted−17
@@ -1,17 +0,0 @@
1−{ mkDerivation, base, bytestring, data-default, diagrams-lib
2−, diagrams-svg, filepath, lens, lucid, monad-control
3−, obelisk-asset-serve, raw-strings-qq, semigroups, snap, snap-core
4−, stdenv, svg-builder, text, transformers-base
5−}:
6−mkDerivation {
7− pname = "obelisk-snap";
8− version = "0.1";
9− src = ./.;
10− libraryHaskellDepends = [
11− base bytestring data-default diagrams-lib diagrams-svg filepath
12− lens lucid monad-control obelisk-asset-serve raw-strings-qq
13− semigroups snap snap-core svg-builder text transformers-base
14− ];
15− description = "Project Synopsis Here";
16− license = stdenv.lib.licenses.bsd3;
17−}
lib/snap/obelisk-snap.cabal+2−1
@@ -23,7 +23,8 @@ library
2323 lens,
2424 lucid,
2525 monad-control,
26− obelisk-asset-serve,
26+ obelisk-asset-serve-snap,
27+ obelisk-snap-extras,
2728 semigroups,
2829 raw-strings-qq,
2930 snap,
lib/snap/src/Obelisk/Snap.hs+12−71
@@ -4,44 +4,31 @@
44
55module Obelisk.Snap where
66
7−import Obelisk.Asset.Serve
7+import Obelisk.Asset.Serve.Snap
8+import Obelisk.Snap.Extras
89
910import Snap
1011
1112import Control.Lens
1213import Data.ByteString (ByteString)
1314import Data.Default
14−import Data.Maybe
1515import Data.Monoid
16−import Data.String
17−import Data.Text (Text)
1816import qualified Data.Text as T
19−import Diagrams.Prelude (Diagram, renderDia, mkWidth)
20−import Diagrams.Backend.SVG
21−import qualified Graphics.Svg as SVG
22−import Graphics.Svg.Attributes
2317import Lucid
2418import System.FilePath
25−import Text.RawString.QQ
2619import Control.Monad.IO.Class
2720
2821-- Data type for web app configuration
2922data AppConfig m
30− = AppConfig { _appConfig_logo :: Diagram SVG
31− , _appConfig_extraHeadMarkup :: Html ()
32− , _appConfig_initialStyles :: Maybe Text
33− , _appConfig_initialBody :: Maybe (m ByteString)
34− , _appConfig_initialHead :: Maybe ByteString
23+ = AppConfig { _appConfig_initialBody :: m ByteString
24+ , _appConfig_initialHead :: ByteString
3525 , _appConfig_serveJsexe :: Bool
3626 , _appConfig_jsexe :: FilePath
3727 }
3828
3929-- Default instance for app configuration
40−instance Default (AppConfig m) where
41− def = AppConfig { _appConfig_logo = mempty
42− , _appConfig_extraHeadMarkup = mempty
43− , _appConfig_initialStyles = mempty
44− , _appConfig_initialBody = Nothing
30+instance Monad m => Default (AppConfig m) where
31+ def = AppConfig { _appConfig_initialBody = return mempty
4532 , _appConfig_initialHead = mempty
4633 , _appConfig_serveJsexe = True
4734 , _appConfig_jsexe = "frontend.jsexe"
@@ -83,28 +70,18 @@ frontendJsAssetsPath (AppConfig { _appConfig_jsexe = jsexe }) = "frontendJs.asse
8370-- Helper funtion used in 'serveAppAt', writes a lazy bytestring to the body of Http response
8471serveStaticIndex :: MonadSnap m => AppConfig m -> m ()
8572serveStaticIndex cfg = do
86− -- Decode, pack, and append asset target to file path
73+ -- Decode, pack, and append asset target to file path
8774 appJsPath <- liftIO $ getAssetPath (frontendJsAssetsPath cfg) "/all.js"
88− -- extract initial body from 'AppConfig' or return empty string
89− initialBody <- fromMaybe (return "") $ _appConfig_initialBody cfg
90− -- extract inital head from 'AppConfig' or return empty string
91− let initialHead = fromMaybe "" $ _appConfig_initialHead cfg
92− -- extract initial styles from 'AppConfig' or return empty string
93− let initialStyles = fromMaybe "" $ _appConfig_initialStyles cfg
94−
95− -- Render and write html head and body to response
75+ initialBody <- _appConfig_initialBody cfg
76+ -- Render and write html head and body to response
9677 writeLBS $ renderBS $ doctypehtml_ $ do
9778 head_ $ do
9879 meta_ [charset_ "utf-8"] -- meta-data charset description
9980 meta_ [name_ "viewport", content_ "width=device-width, initial-scale=1.0"] -- meta-data viewport description
100− _appConfig_extraHeadMarkup cfg -- any additional <head> html from 'AppConfig' type
101− toHtmlRaw initialHead -- HtmlT monad wrapper
102− style_ initialStyles -- generate style element/attribute
81+ toHtmlRaw $ _appConfig_initialHead cfg
10382 body_ $ do -- <body>
10483 toHtmlRaw initialBody -- HtmlT monad wrapper
105− script_ [type_ "text/javascript", src_ (maybe "/all.js" T.pack appJsPath), defer_ "defer"] ("" :: String)
106− -- create <script> element
107− return ()
84+ script_ [type_ "application/javascript", src_ (maybe "/all.js" T.pack appJsPath), defer_ "defer"] ("" :: String)
10885
10986{- | Takes an AppConfig monad and uses it to generate & write Lazy ByteString
11087 to the body of the https response. This one comes with hard coded values
@@ -116,43 +93,7 @@ serveIndex cfg = do
11693 head_ $ do
11794 meta_ [charset_ "utf-8"]
11895 meta_ [name_ "viewport", content_ "width=device-width, initial-scale=1.0"]
119− _appConfig_extraHeadMarkup cfg
120− style_ [r|
121− #preload-logo {
122− position: fixed;
123− left: 25%;
124− top: 25%;
125− width: 50%;
126− height: 50%;
127− -webkit-animation: fadein 2s; /* Safari, Chrome and Opera > 12.1 */
128− -moz-animation: fadein 2s; /* Firefox < 16 */
129− -ms-animation: fadein 2s; /* Internet Explorer */
130− -o-animation: fadein 2s; /* Opera < 12.1 */
131− animation: fadein 2s;
132− }
133− @keyframes fadein {
134− from { opacity: 0; }
135− to { opacity: 1; }
136− }
137− /* Firefox < 16 */
138− @-moz-keyframes fadein {
139− from { opacity: 0; }
140− to { opacity: 1; }
141− }
142− /* Safari, Chrome and Opera > 12.1 */
143− @-webkit-keyframes fadein {
144− from { opacity: 0; }
145− to { opacity: 1; }
146− }
147− /* Internet Explorer */
148− @-ms-keyframes fadein {
149− from { opacity: 0; }
150− to { opacity: 1; }
151− }
152− |]
15396 body_ $ do
154− let svgOpts = SVGOptions (mkWidth 400) Nothing "preload-logo-" [bindAttr Id_ "preload-logo"] False
155− toHtml $ SVG.renderBS $ renderDia SVG svgOpts $ _appConfig_logo cfg
156− script_ [type_ "text/javascript", src_ (maybe "/all.js" T.pack appJsPath), defer_ "defer"] ("" :: String)
97+ script_ [type_ "application/javascript", src_ (maybe "/all.js" T.pack appJsPath), defer_ "defer"] ("" :: String)
15798
15899makeLenses ''AppConfig
skeleton/backend/backend.cabal+2
@@ -9,6 +9,7 @@ library
99 buildable: False
1010 build-depends: base
1111 , common
12+ , frontend
1213 , obelisk-backend
1314 exposed-modules:
1415 Backend
@@ -22,6 +23,7 @@ executable backend
2223 build-depends: base
2324 , backend
2425 , common
26+ , frontend
2527 , lens
2628 , modern-uri
2729 , obelisk-backend
skeleton/backend/frontendJs/frontend.jsexenew file+1
@@ -0,0 +1 @@
1+../../frontend-js/bin/frontend.jsexe
skeleton/backend/src/Backend.hs+3
@@ -1,7 +1,10 @@
11module Backend where
22
33import Common.Api
4+import Frontend
45import qualified Obelisk.Backend as Ob
56
67backend :: IO ()
78backend = Ob.backend Ob.def
9+ { Ob._backendConfig_head = fst frontend
10+ }
skeleton/frontend/src/Frontend.hs+11−10
@@ -1,18 +1,19 @@
1+{-# LANGUAGE DataKinds #-}
12{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE TypeApplications #-}
3−{-# LANGUAGE DataKinds #-}
44module Frontend where
55
6−import Reflex.Dom
7−
86import qualified Data.Text as T
7+import Reflex.Dom.Core
98
10−import Static
119import Common.Api
10+import Static
1211
13−frontend :: Widget x ()
14−frontend = do
15− text "Welcome to Obelisk!"
16− el "p" $ text $ T.pack commonStuff
17− elAttr "img" ("src" =: static @"obelisk.jpg") blank
18−
12+frontend :: (StaticWidget x (), Widget x ())
13+frontend = (head', body)
14+ where
15+ head' = el "title" $ text "Obelisk Minimal Example"
16+ body = do
17+ text "Welcome to Obelisk!"
18+ el "p" $ text $ T.pack commonStuff
19+ elAttr "img" ("src" =: static @"obelisk.jpg") blank
skeleton/frontend/src/main.hs+1−1
@@ -2,4 +2,4 @@ import Reflex.Dom
22import Frontend
33
44main :: IO ()
5−main = mainWidget frontend
5+main = mainWidget $ snd frontend