packages feed

classy-miso-0.0.0.2: src/Miso/Classy/App.hs

{-# LANGUAGE InstanceSigs         #-}
{-# LANGUAGE NamedFieldPuns       #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UnicodeSyntax        #-}

module Miso.Classy.App
	( MisoApp(..)
	, appToRecord
	, startClientApp
	, startIsoClientApp
	, ViewSpec(..)
	, vsRoutePath
	, specToView
	, defaultSubscriptions
	, applyRoute
	, module Miso.Classy
	) where

import           Control.Lens hiding ( view )
import           Miso         hiding ( App (..) )
import qualified Miso
import           Miso.Classy
import           Network.URI  ( URI (..), parseURI )
import           RFC.Prelude  hiding ( init )

class (Component model) => MisoApp model where
  initialAction :: model -> Action model
  notFoundView :: model -> WrappedComponent
  viewSpec :: ALens' model ViewSpec

  subscriptions :: model -> [Sub WrappedAction]
  subscriptions = const defaultSubscriptions
  {-# INLINE subscriptions #-}

-- | These are the default subscriptions that we automatically connect to.
defaultSubscriptions :: [Sub WrappedAction]
defaultSubscriptions = []
{-# INLINE defaultSubscriptions #-}

applyRoute :: (MisoApp model) => model -> RoutePath -> Maybe ViewSpec
applyRoute model routePath =
    foldr foldImpl (parseToVs model) subs
  where
    parseToVs :: forall c. Component c => c -> Maybe ViewSpec
    parseToVs it = (\result -> ViewSpec (result, routePath)) <$> routeParser it routePath
    foldImpl wrapped rest = mapWrappedComponent parseToVs wrapped <|> rest
    subs = model^.(cloneLens subcomponents)
{-# INLINE applyRoute #-}

-- | Converts a 'MisoApp' into a 'Miso.App' record with reasonable defaults
--   for 'Miso.events' and 'Miso.mountPoint', and everything else derived
--   from the 'MisoApp' class.
appToRecord :: (MisoApp model) => model -> Miso.App model WrappedAction
appToRecord model = Miso.App
  { Miso.model = model
  , Miso.update = update'
  , Miso.view = fmap WrappedAction . view
  , Miso.subs = subscriptions model
  , Miso.events = Miso.defaultEvents
  , Miso.initialAction = WrappedAction $ initialAction model
  , Miso.mountPoint = Nothing
  }
{-# INLINE appToRecord #-}

-- | Starts an app with the default 'appToRecord' conversion and without
--   expecting there to be a DOM in existence.
startClientApp :: (MisoApp model, MonadIO m) => InitArgs model -> m ()
startClientApp args = liftIO $ do
  app <- init args
  Miso.startApp $ appToRecord app
{-# INLINE startClientApp #-}

-- | Starts an app with the default 'appToRecord' conversion, and assuming
--   that there is an isomorphic DOM in existence already.
startIsoClientApp :: (MisoApp model, MonadIO m) => InitArgs model -> m ()
startIsoClientApp args = liftIO $ do
  app <- init args
  Miso.startApp $ appToRecord app
{-# INLINE startIsoClientApp #-}

-- | This contains the information necessary to render a view.
newtype ViewSpec = ViewSpec (WrappedComponent, RoutePath) deriving (Eq)

-- | Get the route path which matched for the 'ViewSpec'
vsRoutePath :: ViewSpec -> RoutePath
vsRoutePath (ViewSpec (_,rp)) = rp
{-# INLINE vsRoutePath #-}

-- | Renders a view based on the viewspec
specToView :: ViewSpec -> View WrappedAction
specToView (ViewSpec (wrapped,_)) = mapWrappedComponent (fmap WrappedAction . view) wrapped
{-# INLINE specToView #-}