Shpadoinkle-router 0.2.0.0 → 0.2.0.1
raw patch · 6 files changed
+220/−116 lines, 6 filesdep +jsaddle-warpdep ~warpPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: jsaddle-warp
Dependency ranges changed: warp
API changes (from Hackage documentation)
- Shpadoinkle.Router.HTML: data HTML
- Shpadoinkle.Router.HTML: instance Servant.API.ContentTypes.Accept Shpadoinkle.Router.HTML.HTML
- Shpadoinkle.Router.HTML: instance Servant.API.ContentTypes.MimeRender Shpadoinkle.Router.HTML.HTML (Shpadoinkle.Core.Html m a)
- Shpadoinkle.Router.Server: instance (Shpadoinkle.Router.Server.ServeRouter x r, Shpadoinkle.Router.Server.ServeRouter y r) => Shpadoinkle.Router.Server.ServeRouter (x Servant.API.Alternative.:<|> y) r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter Servant.API.Raw.Raw r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r => Shpadoinkle.Router.Server.ServeRouter (Servant.API.Capture.Capture sym x Servant.API.Sub.:> sub) r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryFlag sym Servant.API.Sub.:> sub) r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryParam sym x Servant.API.Sub.:> sub) r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryParams sym x Servant.API.Sub.:> sub) r
- Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r => Shpadoinkle.Router.Server.ServeRouter (path Servant.API.Sub.:> sub) r
+ Shpadoinkle.Router: data HTML :: Type
+ Shpadoinkle.Router: data View :: (Type -> Type) -> Type -> Type
+ Shpadoinkle.Router: fullPageSPA' :: forall layout b a r m. HasRouter layout => Backend b m a => Monad (b m) => Eq a => Functor m => (m ~> JSM) -> (TVar a -> b m ~> m) -> TVar a -> (r -> m a) -> (a -> Html (b m) a) -> b m RawNode -> (r -> m (Continuation m a)) -> (layout :>> r) -> JSM ()
+ Shpadoinkle.Router: instance Servant.API.ContentTypes.Accept Shpadoinkle.Router.HTML
+ Shpadoinkle.Router: instance Servant.API.ContentTypes.MimeRender Shpadoinkle.Router.HTML (Shpadoinkle.Core.Html m a)
+ Shpadoinkle.Router: instance Servant.Links.HasLink (Shpadoinkle.Router.View m a)
+ Shpadoinkle.Router: instance Servant.Server.Internal.HasServer (Shpadoinkle.Router.View m a) context
+ Shpadoinkle.Router: instance Shpadoinkle.Router.HasRouter (Shpadoinkle.Router.View m b)
+ Shpadoinkle.Router: instance Shpadoinkle.Router.HasRouter (f '[Shpadoinkle.Router.HTML] (Shpadoinkle.Core.Html m b))
+ Shpadoinkle.Router.Client: BaseUrl :: Scheme -> String -> Int -> String -> BaseUrl
+ Shpadoinkle.Router.Client: ClientEnv :: BaseUrl -> ClientEnv
+ Shpadoinkle.Router.Client: ClientM :: ReaderT ClientEnv (ExceptT ClientError JSM) a -> ClientM a
+ Shpadoinkle.Router.Client: ConnectionError :: SomeException -> ClientError
+ Shpadoinkle.Router.Client: DecodeFailure :: Text -> Response -> ClientError
+ Shpadoinkle.Router.Client: EmptyClient :: EmptyClient
+ Shpadoinkle.Router.Client: FailureResponse :: RequestF () (BaseUrl, ByteString) -> Response -> ClientError
+ Shpadoinkle.Router.Client: Http :: Scheme
+ Shpadoinkle.Router.Client: Https :: Scheme
+ Shpadoinkle.Router.Client: InvalidContentTypeHeader :: Response -> ClientError
+ Shpadoinkle.Router.Client: Response :: Status -> Seq Header -> HttpVersion -> a -> ResponseF a
+ Shpadoinkle.Router.Client: UnsupportedContentType :: MediaType -> Response -> ClientError
+ Shpadoinkle.Router.Client: [baseUrlHost] :: BaseUrl -> String
+ Shpadoinkle.Router.Client: [baseUrlPath] :: BaseUrl -> String
+ Shpadoinkle.Router.Client: [baseUrlPort] :: BaseUrl -> Int
+ Shpadoinkle.Router.Client: [baseUrlScheme] :: BaseUrl -> Scheme
+ Shpadoinkle.Router.Client: [baseUrl] :: ClientEnv -> BaseUrl
+ Shpadoinkle.Router.Client: [responseBody] :: ResponseF a -> a
+ Shpadoinkle.Router.Client: [responseHeaders] :: ResponseF a -> Seq Header
+ Shpadoinkle.Router.Client: [responseHttpVersion] :: ResponseF a -> HttpVersion
+ Shpadoinkle.Router.Client: [responseStatusCode] :: ResponseF a -> Status
+ Shpadoinkle.Router.Client: [runClientM'] :: ClientM a -> ReaderT ClientEnv (ExceptT ClientError JSM) a
+ Shpadoinkle.Router.Client: class RunClient m => HasClient (m :: Type -> Type) api where {
+ Shpadoinkle.Router.Client: client :: HasClient ClientM api => Proxy api -> Client ClientM api
+ Shpadoinkle.Router.Client: clientWithRoute :: HasClient m api => Proxy m -> Proxy api -> Request -> Client m api
+ Shpadoinkle.Router.Client: data BaseUrl
+ Shpadoinkle.Router.Client: data ClientError
+ Shpadoinkle.Router.Client: data EmptyClient
+ Shpadoinkle.Router.Client: data InvalidBaseUrlException
+ Shpadoinkle.Router.Client: data ResponseF a
+ Shpadoinkle.Router.Client: data Scheme
+ Shpadoinkle.Router.Client: hoistClientMonad :: HasClient m api => Proxy m -> Proxy api -> (forall x. () => mon x -> mon' x) -> Client mon api -> Client mon' api
+ Shpadoinkle.Router.Client: newtype ClientEnv
+ Shpadoinkle.Router.Client: newtype ClientM a
+ Shpadoinkle.Router.Client: parseBaseUrl :: MonadThrow m => String -> m BaseUrl
+ Shpadoinkle.Router.Client: runClientM :: () => ClientM a -> ClientEnv -> JSM (Either ClientError a)
+ Shpadoinkle.Router.Client: showBaseUrl :: BaseUrl -> String
+ Shpadoinkle.Router.Client: type Response = ResponseF ByteString
+ Shpadoinkle.Router.Client: type StreamingResponse = ResponseF SourceIO ByteString
+ Shpadoinkle.Router.Client: type family Client (m :: Type -> Type) api :: Type;
+ Shpadoinkle.Router.Client: withStreamingRequestJSM :: () => Request -> (StreamingResponse -> JSM a) -> ClientM a
+ Shpadoinkle.Router.Client: }
+ Shpadoinkle.Router.Server: instance (Shpadoinkle.Router.Server.ServeRouter x r m a, Shpadoinkle.Router.Server.ServeRouter y r m a) => Shpadoinkle.Router.Server.ServeRouter (x Servant.API.Alternative.:<|> y) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter (Shpadoinkle.Router.View n b) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter Servant.API.Raw.Raw r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r m a => Shpadoinkle.Router.Server.ServeRouter (Servant.API.Capture.Capture sym x Servant.API.Sub.:> sub) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r m a => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryFlag sym Servant.API.Sub.:> sub) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r m a => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryParam sym x Servant.API.Sub.:> sub) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r m a => Shpadoinkle.Router.Server.ServeRouter (Servant.API.QueryParam.QueryParams sym x Servant.API.Sub.:> sub) r m a
+ Shpadoinkle.Router.Server: instance Shpadoinkle.Router.Server.ServeRouter sub r m a => Shpadoinkle.Router.Server.ServeRouter (path Servant.API.Sub.:> sub) r m a
+ Shpadoinkle.Router.Server: serveDirectoryWithSpa :: StaticSettings -> ServerT (View m a) n
- Shpadoinkle.Router.Server: class ServeRouter layout route
+ Shpadoinkle.Router.Server: class ServeRouter layout route m a
- Shpadoinkle.Router.Server: serveUI :: ServeRouter layout route => FilePath -> (route -> IO (Html m a)) -> (layout :>> route) -> Server layout
+ Shpadoinkle.Router.Server: serveUI :: ServeRouter layout route m a => FilePath -> (route -> IO (Html m a)) -> (layout :>> route) -> Server layout
Files
- README.md +5/−5
- Shpadoinkle-router.cabal +5/−5
- Shpadoinkle/Router.hs +147/−21
- Shpadoinkle/Router/Client.hs +23/−13
- Shpadoinkle/Router/HTML.hs +0/−47
- Shpadoinkle/Router/Server.hs +40/−25
README.md view
@@ -24,10 +24,10 @@ ```haskell -- The accepted URIs-type SPA- = "echo" :> QueryParam "echo" Text :> Raw- :<|> "v2" :> "echo" :> QueryParam "echo" Text :> Raw- :<|> "home" :> Raw+type SPA m+ = "echo" :> QueryParam "echo" Text :> View m ()+ :<|> "v2" :> "echo" :> QueryParam "echo" Text :> View m ()+ :<|> "home" :> View m () -- The routes that can be rendered data Route@@ -35,7 +35,7 @@ | Home -- Surjection from URIs to routes-routes :: SPA :>> Route+routes :: SPA m :>> Route routes = REcho :<|> REcho
Shpadoinkle-router.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.32.0.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 1f447eca9bd32966123dd6a67d22331ec3365eaf01daa425e4da52100d935f37+-- hash: 287b71ef93760ce3e427d0d14f772ed6bee4c147edd12b6879f767ff727d2f7b name: Shpadoinkle-router-version: 0.2.0.0+version: 0.2.0.1 synopsis: A single page application rounter for Shpadoinkle based on Servant. description: Surjective single page application routing with Servant. Surjectivity in this context means routes can be backward compatible, allowing URLs to evolve. Since routes are specified as Servant combinators, serving these routes from the backend is trivial. For an example of leveraging the client-server isomorphism via Servant specification, check the servant-crud example. category: Web@@ -28,7 +28,6 @@ exposed-modules: Shpadoinkle.Router Shpadoinkle.Router.Client- Shpadoinkle.Router.HTML Shpadoinkle.Router.Server other-modules: Paths_Shpadoinkle_router@@ -57,10 +56,11 @@ else build-depends: Shpadoinkle-backend-static+ , jsaddle-warp , servant-client >=0.16.0 && <0.18 , servant-client-js >=0.1 && <0.2 , servant-server >=0.16 && <0.18 , wai >=3.2.2 && <3.3 , wai-app-static >=3.1.6 && <3.2- , warp >=3.2.28 && <3.3+ , warp >=3.2.28 && <3.4 default-language: Haskell2010
Shpadoinkle/Router.hs view
@@ -11,7 +11,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}@@ -33,9 +32,9 @@ -- * Classes HasRouter(..), Routed(..) -- * Types- , Redirect(..), Router(..)+ , Redirect(..), Router(..), View, HTML -- * Shpadoinkle with SPA- , fullPageSPAC, fullPageSPA+ , fullPageSPAC, fullPageSPA, fullPageSPA' -- * Navigation , navigate -- * Rehydration@@ -45,41 +44,75 @@ ) where -import Control.Applicative-import Control.Compactable as C-import Control.Monad-import Control.Monad.IO.Class-import Data.Aeson+import Control.Applicative (Alternative ((<|>)))+import Control.Compactable as C (Compactable (compact, filter))+import Control.Monad (forever)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Aeson (FromJSON, ToJSON, decode,+ encode) import Data.ByteString.Lazy (fromStrict, toStrict)-import Data.Kind+import Data.Kind (Type) import Data.Maybe (isJust)-import Data.Proxy+import Data.Proxy (Proxy (..)) import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding (decodeUtf8, encodeUtf8)-import GHC.TypeLits-import GHCJS.DOM+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)+import GHCJS.DOM (currentWindowUnchecked,+ syncPoint) import GHCJS.DOM.EventM (on)-import GHCJS.DOM.EventTargetClosures-import GHCJS.DOM.History+import GHCJS.DOM.EventTargetClosures (EventName, unsafeEventName)+import GHCJS.DOM.History (pushState) import GHCJS.DOM.Location (getPathname, getSearch)-import GHCJS.DOM.PopStateEvent+import GHCJS.DOM.PopStateEvent (PopStateEvent) import GHCJS.DOM.Types (JSM, MonadJSM, liftJSM)-import GHCJS.DOM.Window+import GHCJS.DOM.Window (Window, getHistory, getLocation) import Language.Javascript.JSaddle (fromJSVal, jsg)-import Servant.API hiding (uriPath, uriQuery)+#ifndef ghcjs_HOST_OS+import Servant.API (Accept (contentTypes), Capture,+ FromHttpApiData, HasLink (..),+ IsElem, MimeRender (..),+ QueryFlag, QueryParam,+ QueryParam', QueryParams, Raw,+ Required, type (:<|>) (..),+ type (:>))+#else+import Servant.API (Capture, FromHttpApiData,+ HasLink (..), IsElem, QueryFlag,+ QueryParam, QueryParam',+ QueryParams, Raw, Required,+ type (:<|>) (..), type (:>))+#endif import Servant.Links (Link, URI (..), linkURI, safeLink)-import System.IO.Unsafe+import System.IO.Unsafe (unsafePerformIO) import UnliftIO.Concurrent (MVar, forkIO, newEmptyMVar, putMVar, takeMVar)-import UnliftIO.STM (TVar, newTVarIO)+import UnliftIO.STM (TVar, atomically, newTVarIO,+ writeTVar) import Web.HttpApiData (parseQueryParamMaybe, parseUrlPieceMaybe) -import Shpadoinkle+import Shpadoinkle (Backend, Continuation, Html,+ RawNode, h, hoist, kleisli, pur,+ shpadoinkle, text, type (~>),+ writeUpdate) +#ifndef ghcjs_HOST_OS ++import qualified Data.ByteString.Lazy as BSL+import qualified Data.List.NonEmpty as NE+import qualified Network.HTTP.Media as M+import Servant (Application, HasServer, Tagged)+import qualified Servant as S++import Shpadoinkle.Backend.Static (renderStatic)+++#endif++ default (Text) @@ -122,6 +155,7 @@ Just fe -> return fe _ -> s r + -- | When using server-side rendering, you may benefit from seeding the page with -- data. This function returns a script tag that makes a global variable "initState" -- containing a JSON representation to be used as the initial state of the application@@ -212,7 +246,48 @@ fullPageSPA a b c v g s = fullPageSPAC @layout a b c v g (fmap (pur . const) . s) +-- | This method wraps @shpadoinkle@ providing for a convenient entrypoint+-- for single page applications. It wires together your normal @shpadoinkle@+-- app components with a function to respond to route changes, and the route mapping+-- itself.+{-# ANN fullPageSPA' ("HLint: ignore Reduce duplication" :: String) #-}+fullPageSPA' :: forall layout b a r m+ . HasRouter layout+ => Backend b m a+ => Monad (b m)+ => Eq a+ => Functor m+ => (m ~> JSM)+ -- ^ how do we get to JSM?+ -> (TVar a -> b m ~> m)+ -- ^ what backend are we running?+ -> TVar a+ -- ^ where do we store the state?+ -> (r -> m a)+ -- ^ what is the initial state?+ -> (a -> Html (b m) a)+ -- ^ how should the html look?+ -> b m RawNode+ -- ^ where do we render?+ -> (r -> m (Continuation m a))+ -- ^ listen for route changes+ -> layout :>> r+ -- ^ how shall we relate urls to routes?+ -> JSM ()+fullPageSPA' toJSM backend model i' view getStage onRoute routes = do+ let router = route @layout @r routes+ window <- currentWindowUnchecked+ getRoute window router $ \case+ Nothing -> return ()+ Just r -> do+ i <- toJSM $ i' r+ atomically $ writeTVar model i+ _ <- listenStateChange router $ writeUpdate model . kleisli . const+ . (fmap (hoist toJSM) . toJSM) . onRoute+ shpadoinkle toJSM backend i model view getStage+ syncPoint + -- | ?foo=bar&baz=qux -> [("foo","bar"),("baz","qux")] parseQuery :: Text -> [(Text,Text)] parseQuery = (=<<) toKVs . T.splitOn "&" . T.drop 1@@ -249,6 +324,9 @@ _ <- forkIO . forever $ do liftIO $ takeMVar syncRoute getRoute w router $ maybe (return ()) handle+ syncPoint+ () <- liftIO $ return ()+ return () return () @@ -266,7 +344,7 @@ RQueryParamR sym f -> case lookup (T.pack $ symbolVal sym) queries of Nothing -> Nothing- Just t -> fromRouter queries segs =<< f <$> parseQueryParamMaybe t+ Just t -> fromRouter queries segs . f =<< parseQueryParamMaybe t RQueryParams sym f -> fromRouter queries segs . f . compact $ parseQueryParamMaybe . snd <$> C.filter (\(k, _) -> k == T.pack (symbolVal sym))@@ -360,3 +438,51 @@ route = RView {-# INLINABLE route #-} +instance HasRouter (f '[HTML] (Html m b)) where+ type f '[HTML] (Html m b) :>> a = a++ route :: r -> Router r+ route = RView+ {-# INLINABLE route #-}++instance HasRouter (View m b) where+ type View m b :>> a = a++ route :: r -> Router r+ route = RView+ {-# INLINABLE route #-}+++#ifndef ghcjs_HOST_OS+++instance Accept HTML where+ contentTypes _ =+ "text" M.// "html" M./: ("charset", "utf-8") NE.:|+ ["text" M.// "html"]+++instance MimeRender HTML (Html m a) where+ mimeRender _ = BSL.fromStrict . encodeUtf8 . renderStatic+++instance HasServer (View m a) context where+ type ServerT (View m a) m' = Tagged m' Application+ route _ = S.route (Proxy @Raw)+ hoistServerWithContext _ = S.hoistServerWithContext (Proxy @Raw)+++#endif+++-- | A Mime type for rendering Html as "text/html"+data HTML :: Type+++-- | Servant terminal for Shpadoinkle views (recommended)+data View :: (Type -> Type) -> Type -> Type+++instance HasLink (View m a) where+ type MkLink (View m a) b = b+ toLink toA _ = toA
Shpadoinkle/Router/Client.hs view
@@ -1,13 +1,14 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-redundant-constraints #-} {-# OPTIONS_GHC -Wno-type-defaults #-} + -- | Helper for querying the server from client side code using a derived client. -- This module exists to save you from having to use CPP yourself. + module Shpadoinkle.Router.Client ( runXHR , runXHR'@@ -15,16 +16,24 @@ ) where -import Control.Monad (join)-import Control.Monad.Catch-import Data.Maybe-import Data.Text-import Language.Javascript.JSaddle hiding (JSM)-import Servant.Client.JS-import Text.Read-import GHCJS.DOM.Types hiding (Text)-import UnliftIO+import Control.Monad.Catch (MonadThrow (throwM))+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import GHCJS.DOM.Types (JSM)+import Language.Javascript.JSaddle (FromJSVal (fromJSVal), jsg, (!))+import Servant.Client.JS (BaseUrl (..), ClientEnv (..),+ ClientError (..), ClientM (..),+ EmptyClient (..), HasClient (..),+ InvalidBaseUrlException, Response,+ ResponseF (..), Scheme (..),+ StreamingResponse, client,+ parseBaseUrl, runClientM,+ showBaseUrl,+ withStreamingRequestJSM)+import Text.Read (readMaybe)+import UnliftIO (MonadIO (liftIO)) + default (Text) @@ -34,15 +43,16 @@ loc <- jsg ("window" :: Text) >>= (! ("location" :: Text)) protocol <- mapProtocol <$> (loc ! ("protocol" :: Text) >>= fromJSVal) hostname <- fromMaybe "localhost" <$> (loc ! ("hostname" :: Text) >>= fromJSVal)- port <- fromMaybe (defaultPort protocol) . join . fmap readMaybe <$> (loc ! ("port" :: Text) >>= fromJSVal)+ port <- fromMaybe (defaultPort protocol) . (readMaybe =<<) <$> (loc ! ("port" :: Text) >>= fromJSVal) runXHR' m . ClientEnv $ BaseUrl protocol hostname port "" where mapProtocol :: Maybe String -> Scheme mapProtocol (Just "https:") = Https- mapProtocol _ = Http+ mapProtocol _ = Http defaultPort :: Scheme -> Int defaultPort Https = 443- defaultPort Http = 80+ defaultPort Http = 80+ -- | Run the ClientM from Servant as an XHR request with a customized base URL. runXHR' :: ClientM a -> ClientEnv -> JSM a
− Shpadoinkle/Router/HTML.hs
@@ -1,47 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}---{-|- HTML is a Servant combinator for the "text/html" MIME type, equipped with instances- for serving Shpadoinkle's 'Html' type.--}---module Shpadoinkle.Router.HTML (HTML) where---#ifndef ghcjs_HOST_OS---import qualified Data.ByteString.Lazy as BSL-import qualified Data.List.NonEmpty as NE-import Data.Text.Encoding-import qualified Network.HTTP.Media as M-import Servant.API (Accept (..), MimeRender (..))--import Shpadoinkle-import Shpadoinkle.Backend.Static (renderStatic)---data HTML---instance Accept HTML where- contentTypes _ =- "text" M.// "html" M./: ("charset", "utf-8") NE.:|- ["text" M.// "html"]---instance MimeRender HTML (Html m a) where- mimeRender _ = BSL.fromStrict . encodeUtf8 . renderStatic---#else---data HTML---#endif
Shpadoinkle/Router/Server.hs view
@@ -25,19 +25,26 @@ #ifndef ghcjs_HOST_OS -import Data.ByteString.Lazy as BS-import Data.Text.Encoding-import GHC.TypeLits-import Network.Wai-import Network.Wai.Application.Static+import Data.ByteString.Lazy as BS (ByteString, fromStrict,+ length)+import Data.Text.Encoding (encodeUtf8)+import GHC.TypeLits (Symbol)+import Network.Wai (responseLBS)+import Network.Wai.Application.Static (StaticSettings (ssLookupFile, ssMaxAge),+ defaultWebAppSettings,+ staticApp) import Servant.API-import Servant.Server-import Servant.Server.StaticFiles-import WaiAppStatic.Types+import Servant.Server (HasServer (ServerT), Server,+ Tagged (Tagged))+import Servant.Server.StaticFiles (serveDirectoryWith)+import WaiAppStatic.Types (File (..),+ LookupResult (LRFile, LRNotFound),+ MaxAge (MaxAgeSeconds), Piece,+ toPieces) -import Shpadoinkle-import Shpadoinkle.Backend.Static-import Shpadoinkle.Router+import Shpadoinkle (Html)+import Shpadoinkle.Backend.Static (renderStatic)+import Shpadoinkle.Router (HasRouter (..), View) -- | Helper to serve a 'ByteString' as a file from the web application interface.@@ -74,7 +81,7 @@ -- | Serve the UI by generating a Servant Server from the SPA URIs-class ServeRouter layout route where+class ServeRouter layout route m a where serveUI :: FilePath -- ^ Where should we look for static assets?@@ -85,51 +92,59 @@ -> Server layout -instance (ServeRouter x r, ServeRouter y r)- => ServeRouter (x :<|> y) r where+instance (ServeRouter x r m a, ServeRouter y r m a)+ => ServeRouter (x :<|> y) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> (x :<|> y) :>> r -> Server (x :<|> y) serveUI root view (x :<|> y) = serveUI @x root view x :<|> serveUI @y root view y {-# INLINABLE serveUI #-} -instance ServeRouter sub r- => ServeRouter (Capture sym x :> sub) r where+instance ServeRouter sub r m a+ => ServeRouter (Capture sym x :> sub) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> (x -> sub :>> r) -> Server (Capture sym x :> sub) serveUI root view = (serveUI @sub root view .) {-# INLINABLE serveUI #-} -instance ServeRouter sub r- => ServeRouter (QueryParam sym x :> sub) r where+instance ServeRouter sub r m a+ => ServeRouter (QueryParam sym x :> sub) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> (Maybe x -> sub :>> r) -> Server (QueryParam sym x :> sub) serveUI root view = (serveUI @sub root view .) {-# INLINABLE serveUI #-} -instance ServeRouter sub r- => ServeRouter (QueryParams sym x :> sub) r where+instance ServeRouter sub r m a+ => ServeRouter (QueryParams sym x :> sub) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> ([x] -> sub :>> r) -> Server (QueryParams sym x :> sub) serveUI root view = (serveUI @sub root view .) {-# INLINABLE serveUI #-} -instance ServeRouter sub r- => ServeRouter (QueryFlag sym :> sub) r where+instance ServeRouter sub r m a+ => ServeRouter (QueryFlag sym :> sub) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> (Bool -> sub :>> r) -> Server (QueryFlag sym :> sub) serveUI root view = (serveUI @sub root view .) {-# INLINABLE serveUI #-} -instance ServeRouter sub r- => ServeRouter ((path :: Symbol) :> sub) r where+instance ServeRouter sub r m a+ => ServeRouter ((path :: Symbol) :> sub) r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> (path :> sub) :>> r -> Server (path :> sub) serveUI = serveUI @sub {-# INLINABLE serveUI #-} -instance ServeRouter Raw r where+instance ServeRouter Raw r m a where serveUI :: FilePath -> (r -> IO (Html m a)) -> Raw :>> r -> Server Raw serveUI root view = serveDirectoryWith . defaultSPAServerSettings root . view {-# INLINABLE serveUI #-} +instance ServeRouter (View n b) r m a where+ serveUI :: FilePath -> (r -> IO (Html m a)) -> View n b :>> r -> Server (View n b)+ serveUI root view = serveDirectoryWithSpa . defaultSPAServerSettings root . view+ {-# INLINABLE serveUI #-}+++serveDirectoryWithSpa :: StaticSettings -> ServerT (View m a) n+serveDirectoryWithSpa = Tagged . staticApp #endif