{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE KindSignatures #-} ----------------------------------------------------------------------------- -- | -- Module : Miso -- Copyright : (C) 2016-2018 David M. Johnson -- License : BSD3-style (see the file LICENSE) -- Maintainer : David M. Johnson -- Stability : experimental -- Portability : non-portable ---------------------------------------------------------------------------- module Miso ( miso , startApp , module Miso.Effect , module Miso.Event , module Miso.Html , module Miso.Subscription , module Miso.Types , module Miso.Router , module Miso.Util ) where import Control.Concurrent import Control.Monad import Data.IORef import Data.List import Data.Sequence ((|>)) import qualified Data.Sequence as S import qualified JavaScript.Object.Internal as OI import JavaScript.Web.AnimationFrame import Miso.Concurrent import Miso.Delegate import Miso.Diff import Miso.Effect import Miso.Event import Miso.Util import Miso.Html import Miso.Router import Miso.Subscription import Miso.Types import Miso.FFI -- | Helper function to abstract out common functionality between `startApp` and `miso` common :: Eq model => App model action -> model -> (Sink action -> IO (IORef VTree)) -> IO b common App {..} m getView = do -- init Notifier Notify {..} <- newNotify -- init empty actions actionsRef <- newIORef S.empty let writeEvent a = void . forkIO $ do atomicModifyIORef' actionsRef $ \as -> (as |> a, ()) notify -- init Subs forM_ subs $ \sub -> sub writeEvent -- Hack to get around `BlockedIndefinitelyOnMVar` exception -- that occurs when no event handlers are present on a template -- and `notify` is no longer in scope void . forkIO . forever $ threadDelay (1000000 * 86400) >> notify -- Retrieves reference view viewRef <- getView writeEvent -- know thy mountElement mountEl <- mountElement mountPoint -- Begin listening for events in the virtual dom delegator mountEl viewRef events -- Process initial action of application writeEvent initialAction -- Program loop, blocking on SkipChan let loop !oldModel = wait >> do -- Apply actions to model actions <- atomicModifyIORef' actionsRef $ \actions -> (S.empty, actions) let (Acc newModel effects) = foldl' (foldEffects writeEvent update) (Acc oldModel (pure ())) actions effects when (oldModel /= newModel) $ do newVTree <- runView (view newModel) writeEvent oldVTree <- readIORef viewRef void $ waitForAnimationFrame (diff mountPoint) (Just oldVTree) (Just newVTree) atomicWriteIORef viewRef newVTree loop newModel loop m -- | Runs an isomorphic miso application -- Assumes the pre-rendered DOM is already present miso :: (HasURI model, Eq model) => App model action -> IO () miso app@App{..} = do uri <- getCurrentURI let modelWithUri = setURI uri model common app model $ \writeEvent -> do let initialView = view modelWithUri VTree (OI.Object iv) <- flip runView writeEvent initialView -- Initial diff can be bypassed, just copy DOM into VTree copyDOMIntoVTree iv let initialVTree = VTree (OI.Object iv) -- Create virtual dom, perform initial diff newIORef initialVTree -- | Runs a miso application startApp :: Eq model => App model action -> IO () startApp app@App {..} = common app model $ \writeEvent -> do let initialView = view model initialVTree <- flip runView writeEvent initialView (diff mountPoint) Nothing (Just initialVTree) newIORef initialVTree -- | Helper foldEffects :: Sink action -> (action -> model -> Effect action model) -> Acc model -> action -> Acc model foldEffects sink update = \(Acc model as) action -> case update action model of Effect newModel effs -> Acc newModel newAs where newAs = as >> do forM_ effs $ \eff -> void $ forkIO (eff sink) data Acc model = Acc !model !(IO ())