packages feed

Shpadoinkle 0.2.0.0 → 0.3.0.0

raw patch · 7 files changed

+351/−182 lines, 7 filesdep +containersdep +waidep +wai-app-staticsetup-changedPVP ok

version bump matches the API change (PVP)

Dependencies added: containers, wai, wai-app-static, warp

API changes (from Hackage documentation)

- Shpadoinkle.Continuation: forgetC' :: Monad m => Continuous f => f m a -> f m b
- Shpadoinkle.Core: fullPage :: Backend b m a => Monad (b m) => Eq a => (m ~> JSM) -> (TVar a -> b m ~> m) -> a -> (a -> Html (b m) a) -> b m RawNode -> JSM ()
- Shpadoinkle.Core: fullPageJSM :: Backend b JSM a => Monad (b JSM) => Eq a => (TVar a -> b JSM ~> JSM) -> a -> (a -> Html (b JSM) a) -> b JSM RawNode -> JSM ()
- Shpadoinkle.Core: runJSorWarp :: Int -> JSM () -> IO ()
- Shpadoinkle.Core: simple :: Backend b JSM a => Monad (b JSM) => Eq a => (TVar a -> b JSM ~> JSM) -> a -> (a -> Html (b JSM) a) -> b JSM RawNode -> JSM ()
+ Shpadoinkle.Core: UnliftIO :: (forall a. () => m a -> IO a) -> UnliftIO
+ Shpadoinkle.Core: [PData] :: JSVal -> Prop m a
+ Shpadoinkle.Core: [PPotato] :: (RawNode -> JSM (STM (Continuation m a))) -> Prop m a
+ Shpadoinkle.Core: [unliftIO] :: UnliftIO -> forall a. () => m a -> IO a
+ Shpadoinkle.Core: askJSM :: MonadJSM m => m JSContextRef
+ Shpadoinkle.Core: askUnliftIO :: MonadUnliftIO m => m (UnliftIO m)
+ Shpadoinkle.Core: atomically :: MonadIO m => STM a -> m a
+ Shpadoinkle.Core: bakedProp :: (RawNode -> JSM (STM (Continuation m a))) -> Prop m a
+ Shpadoinkle.Core: class MonadIO m => MonadUnliftIO (m :: Type -> Type)
+ Shpadoinkle.Core: data STM a
+ Shpadoinkle.Core: dataProp :: JSVal -> Prop m a
+ Shpadoinkle.Core: liftJSM :: MonadJSM m => JSM a -> m a
+ Shpadoinkle.Core: modifyTVar :: () => TVar a -> (a -> a) -> STM ()
+ Shpadoinkle.Core: newtype UnliftIO (m :: Type -> Type)
+ Shpadoinkle.Core: readTVar :: () => TVar a -> STM a
+ Shpadoinkle.Core: retrySTM :: () => STM a
+ Shpadoinkle.Core: runJSM :: MonadIO m => JSM a -> JSContextRef -> m a
+ Shpadoinkle.Core: withRunInIO :: MonadUnliftIO m => ((forall a. () => m a -> IO a) -> IO b) -> m b
+ Shpadoinkle.Core: writeTVar :: () => TVar a -> a -> STM ()
+ Shpadoinkle.Run: Dev :: Env
+ Shpadoinkle.Run: Prod :: Env
+ Shpadoinkle.Run: data Env
+ Shpadoinkle.Run: entrypoint :: Env -> Text
+ Shpadoinkle.Run: fullPage :: Backend b m a => Monad (b m) => Eq a => (m ~> JSM) -> (TVar a -> b m ~> m) -> a -> (a -> Html (b m) a) -> b m RawNode -> JSM ()
+ Shpadoinkle.Run: fullPageJSM :: Backend b JSM a => Monad (b JSM) => Eq a => (TVar a -> b JSM ~> JSM) -> a -> (a -> Html (b JSM) a) -> b JSM RawNode -> JSM ()
+ Shpadoinkle.Run: live :: Port -> JSM () -> IO ()
+ Shpadoinkle.Run: liveWithBackend :: Port -> JSM () -> IO Application -> IO ()
+ Shpadoinkle.Run: liveWithStatic :: Port -> JSM () -> FilePath -> IO ()
+ Shpadoinkle.Run: runJSorWarp :: Int -> JSM () -> IO ()
+ Shpadoinkle.Run: simple :: Backend b JSM a => Monad (b JSM) => Eq a => (TVar a -> b JSM ~> JSM) -> a -> (a -> Html (b JSM) a) -> b JSM RawNode -> JSM ()
+ Shpadoinkle.Run: type Port = Int
- Shpadoinkle.Continuation: forgetC :: Monad m => Monad n => Continuous f => f m a -> f n b
+ Shpadoinkle.Continuation: forgetC :: Continuous f => f m a -> f m b
- Shpadoinkle.Continuation: mapC :: (Continuous f, Functor m) => Functor n => (Continuation m a -> Continuation n b) -> f m a -> f n b
+ Shpadoinkle.Continuation: mapC :: Continuous f => (Continuation m a -> Continuation m b) -> f m a -> f m b
- Shpadoinkle.Core: cataProp :: (Text -> b) -> ((RawNode -> RawEvent -> JSM (Continuation m a)) -> b) -> (Bool -> b) -> Prop m a -> b
+ Shpadoinkle.Core: cataProp :: (JSVal -> b) -> (Text -> b) -> (Bool -> b) -> ((RawNode -> RawEvent -> JSM (Continuation m a)) -> b) -> ((RawNode -> JSM (STM (Continuation m a))) -> b) -> Prop m a -> b
- Shpadoinkle.Core: hoistHtml :: Functor m => (m ~> n) -> Html m a -> Html n a
+ Shpadoinkle.Core: hoistHtml :: Functor m => Functor n => (m ~> n) -> Html m a -> Html n a
- Shpadoinkle.Core: listen :: Text -> a -> (Text, Prop m a)
+ Shpadoinkle.Core: listen :: Text -> (a -> a) -> (Text, Prop m a)

Files

Control/PseudoInverseCategory.hs view
@@ -23,8 +23,9 @@   import qualified Control.Categorical.Functor as F-import           Control.Category-import           Data.Functor.Identity+import           Control.Category            (Category (..))+import           Data.Bifunctor              (bimap, first, second)+import           Data.Functor.Identity       (Identity (..)) import           Data.Tuple                  (swap) import           Prelude                     hiding (id, (.)) @@ -178,14 +179,13 @@ instance PIArrow EndoIso where   piiso = EndoIso id   piendo f = EndoIso f id id-  pifirst (EndoIso f g h) = EndoIso (\(x,y)->(f x,y)) (\(x,y)->(g x,y)) (\(x,y)->(h x,y))-  pisecond (EndoIso f g h) = EndoIso (\(x,y)->(x,f y)) (\(x,y)->(x,g y)) (\(x,y)->(x,h y))+  pifirst (EndoIso f g h) = EndoIso (first f) (first g) (first h)+  pisecond (EndoIso f g h) = EndoIso (second f) (second g) (second h)   pisplit (EndoIso f g h) (EndoIso i j k) = EndoIso-    (\(x,y) -> (f x, i y))-    (\(x,y) -> (g x, j y))-    (\(x,y) -> (h x, k y))+    (bimap f i)+    (bimap g j)+    (bimap h k)   pifan (EndoIso f g h) (EndoIso i j _) = EndoIso-    (\x -> f (i x))+    (f . i)     (\x -> (g x, j x))     (\(x,_) -> h x) -- it shouldn't matter which side we use to go back because we have isomorphisms-
README.md view
@@ -8,8 +8,7 @@ [![Hackage Deps](https://img.shields.io/hackage-deps/v/Shpadoinkle.svg)](http://packdeps.haskellers.com/reverse/Shpadoinkle) [![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/Shpadoinkle/badge)](https://matrix.hackage.haskell.org/#/package/Shpadoinkle) -Shpadoinkle is a programming model for UI development, oriented around simplicity,-performance, and ergonomics.+Shpadoinkle is a Haskell UI programming paradigm.  ## The core concept 
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple+import           Distribution.Simple main = defaultMain
Shpadoinkle.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: 1d3c55562c8448cd288523fb62cb616caa2a301fba5f92e2ac5b61a931143bbd+-- hash: 551573dac4a13219ffcfb0a630517ffaeef58089ef1c69e2feb128417516a320  name:           Shpadoinkle-version:        0.2.0.0+version:        0.3.0.0 synopsis:       A programming model for declarative, high performance user interface. description:    Shpadoinkle is an abstract frontend programming model, with one-way data flow, and a single source of truth. This module provides a parsimonious implementation of Shpadoinkle with few implementation details. category:       Web@@ -30,6 +30,7 @@       Shpadoinkle       Shpadoinkle.Continuation       Shpadoinkle.Core+      Shpadoinkle.Run   other-modules:       Paths_Shpadoinkle   hs-source-dirs:@@ -38,14 +39,16 @@   build-depends:       base >=4.12.0 && <4.16     , category >=0.2 && <0.3+    , containers     , ghcjs-dom >=0.9.4 && <0.20     , jsaddle >=0.9.7 && <0.20     , text >=1.2.3 && <1.3     , transformers     , unliftio-  if impl(ghcjs)-    build-depends:-  else+  if !impl(ghcjs)     build-depends:         jsaddle-warp >=0.9.7 && <0.20+      , wai+      , wai-app-static+      , warp   default-language: Haskell2010
Shpadoinkle/Continuation.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE InstanceSigs          #-} {-# LANGUAGE LambdaCase            #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes            #-}@@ -24,7 +23,7 @@   -- ** Hoist   , hoist   -- * Forgetting-  , voidC', voidC, forgetC, forgetC'+  , voidC', voidC, forgetC   -- * Lifts   , liftC', liftCMay', liftC, liftCMay   -- * Utilities@@ -44,11 +43,14 @@ import           Control.Arrow                 (first) import qualified Control.Categorical.Functor   as F import           Control.Monad                 (liftM2, void)-import           Control.Monad.Trans.Class-import           Control.PseudoInverseCategory+import           Control.Monad.Trans.Class     (MonadTrans (..))+import           Control.PseudoInverseCategory (EndoIso (..))+import           Data.Maybe                    (fromMaybe) import           GHC.Conc                      (retry)-import           UnliftIO-import           UnliftIO.Concurrent+import           UnliftIO                      (MonadUnliftIO, TVar, atomically,+                                                newTVarIO, readTVar, readTVarIO,+                                                writeTVar)+import           UnliftIO.Concurrent           (forkIO)   -- | A Continuation builds up an@@ -128,7 +130,7 @@ -- | @f@ is a Functor to Hask from the category where the objects are --   Continuation types and the morphisms are functions. class Continuous f where-  mapC :: Functor m => Functor n => (Continuation m a -> Continuation n b) -> f m a -> f n b+  mapC :: (Continuation m a -> Continuation m b) -> f m a -> f m b   instance Continuous Continuation where@@ -137,8 +139,8 @@  -- | Given a natural transformation, change a Continuation's underlying functor. hoist :: Functor m => (forall b. m b -> n b) -> Continuation m a -> Continuation n a-hoist _ (Pure f) = Pure f-hoist f (Rollback r) = Rollback (hoist f r)+hoist _ (Pure f)              = Pure f+hoist f (Rollback r)          = Rollback (hoist f r) hoist f (Continuation (g, h)) = Continuation . (g,) $ \x -> f $ hoist f <$> h x  @@ -154,7 +156,7 @@ liftCMay' f g (Pure h)     = Pure $ \x -> maybe x (flip f x . h) $ g x liftCMay' f g (Rollback r) = Rollback (liftCMay' f g r) liftCMay' f g (Continuation (h, i)) =-  Continuation (\x -> maybe x (flip f x . h) $ g x, \x -> maybe (pure done) (fmap (liftCMay' f g) . i) $ g x)+  Continuation (\x -> maybe x (flip f x . h) $ g x, maybe (pure done) (fmap (liftCMay' f g) . i) . g)   -- | Given a lens, change the value type of @f@ by applying the lens in the Continuations inside @f@.@@ -180,15 +182,10 @@   -- | Forget about the Continuations.-forgetC :: Monad m => Monad n => Continuous f => f m a -> f n b+forgetC :: Continuous f => f m a -> f m b forgetC = mapC (const done)  --- | Forget about the Continuations without changing the monad. This can be easier on type inference compared to forgetC.-forgetC' :: Monad m => Continuous f => f m a -> f m b-forgetC' = forgetC-- --- | Change the type of a Continuation by applying it to the left coordinate of a tuple. leftC' :: Functor m => Continuation m a -> Continuation m (a,b) leftC' = liftC' (\x (_,y) -> (x,y)) fst@@ -211,11 +208,11 @@  -- | Transform a Continuation to work on 'Maybe's. If it encounters 'Nothing', then it cancels itself. maybeC' :: Applicative m => Continuation m a -> Continuation m (Maybe a)-maybeC' (Pure f) = (Pure (fmap f))+maybeC' (Pure f) = Pure (fmap f) maybeC' (Rollback r) = Rollback (maybeC' r) maybeC' (Continuation (f, g)) = Continuation . (fmap f,) $   \case-    Just x -> maybeC' <$> g x+    Just x  -> maybeC' <$> g x     Nothing -> pure (Rollback done)  @@ -227,9 +224,7 @@ -- | Turn a @Maybe a@ updating function into an @a@ updating function which acts as --   the identity function when the input function outputs 'Nothing'. comaybe :: (Maybe a -> Maybe a) -> (a -> a)-comaybe f x = case f (Just x) of-  Nothing -> x-  Just y  -> y+comaybe f x = fromMaybe x . f $ Just x   -- | Change the type of a Maybe-valued Continuation into the Maybe-wrapped type.@@ -288,7 +283,7 @@ --   of the coproduct selected by the input value used to create the structure. eitherC :: Monad m => Continuous f => (a -> f m a) -> (b -> f m b) -> Either a b -> f m (Either a b) eitherC l _ (Left x)  = mapC (\c -> eitherC' c (pur id)) (l x)-eitherC _ r (Right x) = mapC (\c -> eitherC' (pur id) c) (r x)+eitherC _ r (Right x) = mapC (eitherC' (pur id)) (r x)   -- | Transform the type of a Continuation using an isomorphism.@@ -314,9 +309,9 @@   (Continuation (f, g)) <> (Continuation (h, i)) =     Continuation (f.h, \x -> liftM2 (<>) (g x) (i x))   (Continuation (f, g)) <> (Rollback h) =-    Rollback (Continuation (f, (\x -> liftM2 (<>) (g x) (return h))))+    Rollback (Continuation (f, \x -> liftM2 (<>) (g x) (return h)))   (Rollback h) <> (Continuation (_, g)) =-    Rollback (Continuation (id, \x -> liftM2 (<>) (return h) (g x)))+    Rollback (Continuation (id, fmap (h <>) . g))   (Rollback f) <> (Rollback g) = Rollback (f <> g)   (Pure f) <> (Pure g) = Pure (f.g)   (Pure f) <> (Continuation (g,h)) = Continuation (f.g,h)@@ -337,7 +332,7 @@   m <- f (h i)   case m of     Continuation (g,gs) -> writeUpdate' (g.h) model gs-    Pure g -> atomically $ writeTVar model =<< g.h <$> readTVar model+    Pure g -> atomically (writeTVar model . g . h =<< readTVar model)     Rollback gs -> writeUpdate' id model (const (return gs))  @@ -347,8 +342,8 @@ writeUpdate :: MonadUnliftIO m => TVar a -> Continuation m a -> m () writeUpdate model = \case   Continuation (f,g) -> void . forkIO $ writeUpdate' f model g-  Pure f -> atomically $ writeTVar model =<< f <$> readTVar model-  Rollback f -> writeUpdate model f+  Pure f             -> atomically (writeTVar model . f =<< readTVar model)+  Rollback f         -> writeUpdate model f   -- | Execute a fold by watching a state variable and executing the next
Shpadoinkle/Core.hs view
@@ -1,16 +1,14 @@ {-# LANGUAGE AllowAmbiguousTypes    #-} {-# LANGUAGE BangPatterns           #-} {-# LANGUAGE CPP                    #-}-{-# LANGUAGE ExplicitNamespaces     #-} {-# LANGUAGE FlexibleContexts       #-} {-# LANGUAGE FlexibleInstances      #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs                  #-} {-# LANGUAGE InstanceSigs           #-}-{-# LANGUAGE KindSignatures         #-} {-# LANGUAGE LambdaCase             #-}-{-# LANGUAGE MultiParamTypeClasses  #-} {-# LANGUAGE OverloadedStrings      #-}+{-# LANGUAGE PartialTypeSignatures  #-} {-# LANGUAGE RankNTypes             #-} {-# LANGUAGE ScopedTypeVariables    #-} {-# LANGUAGE TupleSections          #-}@@ -30,7 +28,7 @@   -- * Base Types   Html(..), Prop(..)   -- ** Prop Constructors-  , textProp, listenerProp, flagProp+  , dataProp, flagProp, textProp, listenerProp, bakedProp   -- *** Listeners   , listenRaw, listen, listenM, listenM_, listenC, listener   -- ** Html Constructors@@ -50,35 +48,40 @@   , type (~>)   -- * The Shpadoinkle Primitive   , shpadoinkle-  -- ** Convenience Variants-  , runJSorWarp-  , fullPage-  , fullPageJSM-  , simple   -- * Re-Exports-  , JSM, MonadJSM, TVar, newTVarIO, readTVarIO+  , JSM, MonadJSM, askJSM, runJSM, MonadUnliftIO(..), UnliftIO(..), liftJSM+  , module UnliftIO.STM   ) where  -import           Control.Arrow                    (second)-import qualified Control.Categorical.Functor      as F-import           Control.Category                 ((.))-import           Control.PseudoInverseCategory-import           Data.Functor.Identity-import           Data.Kind-import           Data.String-import           Data.Text-import           GHCJS.DOM.Types                  (JSM, MonadJSM)-import           Language.Javascript.JSaddle      (FromJSVal (..), JSVal,-                                                   ToJSVal (..))-import           Prelude                          hiding ((.))-import           UnliftIO.STM                     (TVar, newTVarIO, readTVarIO)-#ifndef ghcjs_HOST_OS-import           Language.Javascript.JSaddle.Warp (run)-#endif+import           Control.Arrow                 (second)+import qualified Control.Categorical.Functor   as F+import           Control.Category              ((.))+import           Control.PseudoInverseCategory (EndoIso (..),+                                                HasHaskFunctors (fmapA),+                                                PIArrow (piendo, piiso, pisecond),+                                                PseudoInverseCategory (piinverse),+                                                ToHask (piapply))+import           Data.Functor.Identity         (Identity (Identity, runIdentity))+import           Data.Kind                     (Type)+import           Data.List                     (foldl')+import           Data.Map                      (alter, toList)+import           Data.String                   (IsString (..))+import           Data.Text                     (Text, pack)+import           GHCJS.DOM.Types               (JSM, MonadJSM, liftJSM)+import           Language.Javascript.JSaddle   (FromJSVal (..), JSVal,+                                                ToJSVal (..), askJSM, runJSM)+import           Prelude                       hiding ((.))+import           UnliftIO                      (MonadUnliftIO (..),+                                                UnliftIO (..))+import           UnliftIO.STM                  (STM, TVar, atomically,+                                                modifyTVar, newTVarIO, readTVar,+                                                readTVarIO, retrySTM, writeTVar)  -import           Shpadoinkle.Continuation+import           Shpadoinkle.Continuation      (Continuation, Continuous (..),+                                                causes, eitherC, hoist, impur,+                                                pur, shouldUpdate)   -- | This is the core type in Backend.@@ -105,8 +108,16 @@ -- node in JavaScript. If you wish to add attributes, you may do so -- by setting its corresponding property. data Prop :: (Type -> Type) -> Type -> Type where+  -- | A data property, these do NOT appear in static rendering+  PData :: JSVal -> Prop m a   -- | A text property   PText :: Text -> Prop m a+  -- | A boolean property+  PFlag :: Bool -> Prop m a+  -- | Bake a custom property+  -- The STM Monad will be called recursively.+  -- The semantics here is roughly an event stream of continuations.+  PPotato :: (RawNode -> JSM (STM (Continuation m a))) -> Prop m a   -- | Event listeners are provided with the 'RawNode' target, and the 'RawEvent', and may perform   -- a monadic action such as a side effect. This is the one and only place where you may   -- introduce a custom monadic action. The JSM to compute the Continuation must be@@ -116,12 +127,28 @@   -- and that may not be the case if the code to compute the Continuation of some   -- listener is blocking.   PListener :: (RawNode -> RawEvent -> JSM (Continuation m a)) -> Prop m a-  -- | A boolean property works as a flag:-  -- for example @("disabled", PFlag False)@ has no effect,-  -- while @("disabled", PFlag True)@ will add the @disabled@ attribute.-  PFlag :: Bool -> Prop m a  +-- | Ensure all prop keys are unique.+-- Collisions for Data, Text, Flags, and Potatoes are last write wins+-- Collisions for Listeners are Continuation Semigroup operations+nubProps :: Monad m => Html m a -> Html m a+nubProps = mapPropsRecursive $ toList . foldl' f mempty+  where+  f acc (t,p) = alter (Just . g t p) t acc+  g k new old = case (new, old) of+    (PText t, Just (PText t')) | k == "className" -> PText $ t <> " " <> t'+    (PListener l, Just (PListener l')) -> PListener $+      \raw evt -> mappend <$> l raw evt <*> l' raw evt+    _ -> new+++mapPropsRecursive :: ([(Text, Prop m a)] -> [(Text, Prop m a)]) -> Html m a -> Html m a+mapPropsRecursive f = \case+  Node t ps cs -> Node t (f ps) (mapPropsRecursive f <$> cs)+  x            -> x++ -- | Construct a listener from its name and a simple monadic event handler. listenM :: Monad m => Text -> m (a -> a) -> (Text, Prop m a) listenM k = listenC k . impur@@ -138,11 +165,11 @@  -- | If you can provide a Natural Transformation from one Functor to another -- then you may change the action of 'Html'.-hoistHtml :: Functor m => (m ~> n) -> Html m a -> Html n a+hoistHtml :: Functor m => Functor n => (m ~> n) -> Html m a -> Html n a hoistHtml f = \case   Node t ps cs -> Node t (fmap (hoistProp f) <$> ps) (hoistHtml f <$> cs)-  Potato p -> Potato p-  TextNode t -> TextNode t+  Potato p     -> Potato p+  TextNode t   -> TextNode t {-# INLINE hoistHtml #-}  @@ -150,9 +177,11 @@ -- then you may change the action of 'Prop'. hoistProp :: Functor m => (m ~> n) -> Prop m a -> Prop n a hoistProp f = \case-  PListener g -> PListener (\x y -> hoist f <$> g x y)+  PListener g -> PListener $ \x -> fmap (hoist f) . g x+  PData t     -> PData t   PText t     -> PText t-  PFlag b     -> PFlag b+  PFlag t     -> PFlag t+  PPotato p   -> PPotato $ fmap (fmap (hoist f)) . p {-# INLINE hoistProp #-}  @@ -193,13 +222,20 @@     where f' :: EndoIso (Continuation m a) (Continuation m b)           f' = F.map f +          mapFwd :: Prop m a -> Prop m b+          mapFwd (PData t)     = PData t           mapFwd (PText t)     = PText t-          mapFwd (PListener g) = PListener (\r e -> piapply f' <$> g r e)-          mapFwd (PFlag b)     = PFlag b+          mapFwd (PFlag t)     = PFlag t+          mapFwd (PListener g) = PListener $ \r e -> piapply f' <$> g r e+          mapFwd (PPotato p)   = PPotato $ fmap (fmap (piapply f')) . p -          mapBack (PText t) = PText t-          mapBack (PListener g) = PListener (\r e -> piapply (piinverse f') <$> g r e)-          mapBack (PFlag b) = PFlag b++          mapBack :: Prop m b -> Prop m a+          mapBack (PData t)     = PData t+          mapBack (PText t)     = PText t+          mapBack (PFlag t)     = PFlag t+          mapBack (PListener g) = PListener $ \r e -> piapply (piinverse f') <$> g r e+          mapBack (PPotato b)   = PPotato $ fmap (fmap (piapply (piinverse f'))) . b   {-# INLINE map #-}  @@ -235,39 +271,58 @@ --   lens to convert the types of the Continuations which it contains --   if it is a listener. instance Continuous Prop where+  mapC _ (PData t)     = PData t   mapC _ (PText t)     = PText t-  mapC f (PListener g) = PListener (\r e -> f <$> g r e)   mapC _ (PFlag b)     = PFlag b+  mapC f (PListener g) = PListener $ \r -> fmap f . g r+  mapC f (PPotato b)   = PPotato $ fmap (fmap f) . b   {-# INLINE mapC #-}  +-- | Create a data property.+dataProp :: JSVal -> Prop m a+dataProp = PData+{-# INLINE dataProp #-}++ -- | Create a text property. textProp :: Text -> Prop m a textProp = PText {-# INLINE textProp #-}  +flagProp :: Bool -> Prop m a+flagProp = PFlag+{-# INLINE flagProp #-}++ -- | Create an event listener property. listenerProp :: (RawNode -> RawEvent -> JSM (Continuation m a)) -> Prop m a-listenerProp f = PListener (\r e -> f r e)+listenerProp = PListener {-# INLINE listenerProp #-}  --- | Create a boolean property.-flagProp :: Bool -> Prop m a-flagProp = PFlag-{-# INLINE flagProp #-}+-- | Create a delicious proptato.+bakedProp :: (RawNode -> JSM (STM (Continuation m a))) -> Prop m a+bakedProp = PPotato+{-# INLINE bakedProp #-}   -- | Transform a p-algebra into a p-catamorphism. This is like polymorphic pattern matching.-cataProp :: (Text -> b)-         -> ((RawNode -> RawEvent -> JSM (Continuation m a)) -> b)-         -> (Bool -> b)-         -> Prop m a -> b-cataProp f g h' = \case-  PText t -> f t-  PListener l -> g l-  PFlag b -> h' b+cataProp+  :: (JSVal -> b)+  -> (Text -> b)+  -> (Bool -> b)+  -> ((RawNode -> RawEvent -> JSM (Continuation m a)) -> b)+  -> ((RawNode -> JSM (STM (Continuation m a))) -> b)+  -> Prop m a+  -> b+cataProp d t f l p = \case+  PData     x -> d x+  PText     x -> t x+  PFlag     x -> f x+  PListener x -> l x+  PPotato   x -> p x   -- | Construct an HTML element JSX-style.@@ -292,7 +347,7 @@ props :: Applicative f => ([(Text, Prop m a)] -> f [(Text, Prop m a)]) -> Html m a -> f (Html m a) props inj = \case   Node t ps cs -> (\ps' -> Node t ps' cs) <$> inj ps-  t -> pure t+  t            -> pure t {-# INLINE props #-}  @@ -300,7 +355,7 @@ children :: Applicative f => ([Html m a] -> f [Html m a]) -> Html m a -> f (Html m a) children inj = \case   Node t ps cs -> Node t ps <$> inj cs-  t -> pure t+  t            -> pure t {-# INLINE children #-}  @@ -308,7 +363,7 @@ name :: Applicative f => (Text -> f Text) -> Html m a -> f (Html m a) name inj = \case   Node t ps cs -> (\t' -> Node t' ps cs) <$> inj t-  t -> pure t+  t            -> pure t {-# INLINE name #-}  @@ -316,7 +371,7 @@ textContent :: Applicative f => (Text -> f Text) -> Html m a -> f (Html m a) textContent inj = \case   TextNode t -> TextNode <$> inj t-  n -> pure n+  n          -> pure n {-# INLINE textContent #-}  @@ -333,8 +388,8 @@       -> Html m a -> b cataH f g h' = \case   Node t ps cs -> f t ps (cataH f g h' <$> cs)-  Potato p -> g p-  TextNode t -> h' t+  Potato p     -> g p+  TextNode t   -> h' t   -- | Natural Transformation@@ -382,8 +437,8 @@   -- | Construct a listener from its 'Text' name and an output value.-listen :: Text -> a -> (Text, Prop m a)-listen k = listenC k . constUpdate+listen :: Text -> (a -> a) -> (Text, Prop m a)+listen k = listenC k . pur {-# INLINE listen #-}  @@ -468,87 +523,15 @@      go :: RawNode -> VNode b m -> a -> JSM (VNode b m)     go c n a = j $ do-      !m  <- interpret toJSM (view a)+      !m  <- interpret toJSM . nubProps $ view a       patch c (Just n) m    setup @b @m @a $ do     (c,n) <- j $ do       c <- stage-      n <- interpret toJSM (view initial)+      n <- interpret toJSM . nubProps $ view initial       _ <- patch c Nothing n       return (c,n)     _ <- shouldUpdate (go c) n model     return () ---- | Wrapper around 'shpadoinkle' for full page apps--- that do not need outside control of the territory-fullPage-  :: Backend b m a => Monad (b m) => Eq a-  => (m ~> JSM)-  -- ^ How do we get to JSM?-  -> (TVar a -> b m ~> m)-  -- ^ What backend are we running?-  -> a-  -- ^ What is the initial state?-  -> (a -> Html (b m) a)-  -- ^ How should the html look?-  -> b m RawNode-  -- ^ Where do we render?-  -> JSM ()-fullPage g f i view getStage = do-  model <- newTVarIO i-  shpadoinkle g f i model view getStage-{-# INLINE fullPage #-}----- | 'fullPageJSM' is a wrapper around 'shpadoinkle'--- for full page apps that do not need outside control--- of the territory, where actions are performed directly in JSM.------ This set of assumptions is extremely common when starting--- a new project.-fullPageJSM-  :: Backend b JSM a => Monad (b JSM) => Eq a-  => (TVar a -> b JSM ~> JSM)-  -- ^ What backend are we running?-  -> a-  -- ^ What is the initial state?-  -> (a -> Html (b JSM) a)-  -- ^ How should the html look?-  -> b JSM RawNode-  -- ^ Where do we render?-  -> JSM ()-fullPageJSM = fullPage id-{-# INLINE fullPageJSM #-}----- | Start the program!------ This function works in GHC and GHCjs. I saved you from using C preprocessor directly. You're welcome.-runJSorWarp :: Int -> JSM () -> IO ()-#ifdef ghcjs_HOST_OS-runJSorWarp _ = id-{-# INLINE runJSorWarp #-}-#else-runJSorWarp = run-{-# INLINE runJSorWarp #-}-#endif----- | Simple app------ (a good starting place)-simple-  :: Backend b JSM a => Monad (b JSM) => Eq a-  => (TVar a -> b JSM ~> JSM)-  -- ^ What backend are we running?-  -> a-  -- ^ what is the initial state?-  -> (a -> Html (b JSM) a)-  -- ^ how should the html look?-  -> b JSM RawNode-  -- ^ where do we render?-  -> JSM ()-simple = fullPageJSM-{-# INLINE simple #-}
+ Shpadoinkle/Run.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE CPP               #-}+{-# LANGUAGE FlexibleContexts  #-}+{-# LANGUAGE KindSignatures    #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes        #-}+{-# LANGUAGE TypeOperators     #-}+++module Shpadoinkle.Run (+  -- * Agnostic Run+  runJSorWarp+  -- * Live Reloads+  , Env(..), Port+  , liveWithBackend+  , liveWithStatic+  , live+  -- ** Convenience Variants+  , fullPage+  , fullPageJSM+  , simple+  , entrypoint+  ) where+++import           Data.Text                              (Text)+import           GHCJS.DOM.Types                        (JSM)+import           Shpadoinkle                            (Backend, Html, RawNode,+                                                         TVar, newTVarIO,+                                                         shpadoinkle, type (~>))+++#ifndef ghcjs_HOST_OS+++import           Language.Javascript.JSaddle.Warp       (run)+import           Language.Javascript.JSaddle.WebSockets (debug, debugOr)+import           Network.Wai                            (Application)+import           Network.Wai.Application.Static         (defaultFileServerSettings,+                                                         staticApp)+++-- | Serve a web server and a jsaddle warp frontend at the same time.+-- This is useful for live reloads for development purposes.+-- For example:+-- @+--   ghcid -c "cabal repl dev" -W -T "Main.main"+-- @+liveWithBackend+  :: Port+  -- ^ Port to server the live server+  -> JSM ()+  -- ^ Frontend application+  -> IO Application+  -- ^ Server API+  -> IO ()+liveWithBackend port frontend server = debugOr port frontend =<< server+++-- | Serve jsaddle warp frontend.+-- This is useful for live reloads for development purposes.+-- For example:+-- @+--   ghcid -c "cabal repl" -W -T "Main.dev"+-- @+live+  :: Port+  -- ^ Port to server the live server+  -> JSM ()+  -- ^ Frontend application+  -> IO ()+live = debug+++-- | Serve jsaddle warp frontend with a static file server.+liveWithStatic+  :: Port+  -- ^ Port to serve the live server+  -> JSM ()+  -- ^ Frontend application+  -> FilePath+  -- ^ Path to static files+  -> IO ()+liveWithStatic port frontend =+  liveWithBackend port frontend . pure . staticApp . defaultFileServerSettings+++#else+++data Application+++live :: Port -> JSM () -> IO ()+live = error "Live reloads require GHC"+++liveWithStatic :: Port -> JSM () -> FilePath -> IO ()+liveWithStatic = error "Live reloads require GHC"+++liveWithBackend :: Port -> JSM () -> IO Application -> IO ()+liveWithBackend = error "Live reloads require GHC"+++#endif+++data Env = Dev | Prod+++type Port = Int+++-- | Wrapper around 'shpadoinkle' for full page apps+-- that do not need outside control of the territory+fullPage+  :: Backend b m a => Monad (b m) => Eq a+  => (m ~> JSM)+  -- ^ How do we get to JSM?+  -> (TVar a -> b m ~> m)+  -- ^ What backend are we running?+  -> a+  -- ^ What is the initial state?+  -> (a -> Html (b m) a)+  -- ^ How should the html look?+  -> b m RawNode+  -- ^ Where do we render?+  -> JSM ()+fullPage g f i view getStage = do+  model <- newTVarIO i+  shpadoinkle g f i model view getStage+{-# INLINE fullPage #-}+++-- | 'fullPageJSM' is a wrapper around 'shpadoinkle'+-- for full page apps that do not need outside control+-- of the territory, where actions are performed directly in JSM.+--+-- This set of assumptions is extremely common when starting+-- a new project.+fullPageJSM+  :: Backend b JSM a => Monad (b JSM) => Eq a+  => (TVar a -> b JSM ~> JSM)+  -- ^ What backend are we running?+  -> a+  -- ^ What is the initial state?+  -> (a -> Html (b JSM) a)+  -- ^ How should the html look?+  -> b JSM RawNode+  -- ^ Where do we render?+  -> JSM ()+fullPageJSM = fullPage id+{-# INLINE fullPageJSM #-}+++-- | Start the program!+--+-- This function works in GHC and GHCjs. I saved you from using C preprocessor directly. You're welcome.+runJSorWarp :: Int -> JSM () -> IO ()+#ifdef ghcjs_HOST_OS+runJSorWarp _ = id+{-# INLINE runJSorWarp #-}+#else+runJSorWarp = run+{-# INLINE runJSorWarp #-}+#endif+++-- | Simple app+--+-- (a good starting place)+simple+  :: Backend b JSM a => Monad (b JSM) => Eq a+  => (TVar a -> b JSM ~> JSM)+  -- ^ What backend are we running?+  -> a+  -- ^ what is the initial state?+  -> (a -> Html (b JSM) a)+  -- ^ how should the html look?+  -> b JSM RawNode+  -- ^ where do we render?+  -> JSM ()+simple = fullPageJSM+{-# INLINE simple #-}+++entrypoint :: Env -> Text+entrypoint Dev  = "/jsaddle.js"+entrypoint Prod = "/all.min.js"