glazier-react 0.2.0.0 → 0.3.0.0
raw patch · 9 files changed
+327/−108 lines, 9 files
Files
- README.md +174/−2
- glazier-react.cabal +2/−1
- jsbits/registry.js +3/−3
- src/Glazier/React/Command/Run.hs +3/−3
- src/Glazier/React/Event.hs +5/−4
- src/Glazier/React/Maker.hs +11/−23
- src/Glazier/React/Maker/Run.hs +2/−2
- src/Glazier/React/Model.hs +84/−0
- src/Glazier/React/Widget.hs +43/−70
README.md view
@@ -1,5 +1,177 @@ [](https://hackage.haskell.org/package/glazier-react)+[`Glazier.React`](https://github.com/louispan/glazier-react) contains efficient haskell bindings to React JS where render will only be called for the react components with changed states. -See https://github.com/louispan/glazier-react-examples for a example apps, include a fully-featured TodoMVC.+It uses the haskell [glazier](https://github.com/louispan/glazier) library to enable composable windows -See https://github.com/louispan/glazier-react-widget for reusable widget library.+# Features++## Efficient rendering+Using `React.PureComponent` and react `render` will only be called for component who's state actually changed, instead of requiring react to diff the entire DOM.++## Composable widgets+[`Glazier`](https://github.com/louispan/glazier) allows disciplined and lawful ways of creating composable widgets. Larger can be created out of other widgets without modifying existing widget code, or manual ["lifting state"](https://facebook.github.io/react/docs/lifting-state-up.html) into larger widgets.++For example, [List Widget](https://github.com/louispan/glazier-react-widget/blob/master/src/Glazier/React/Widgets/List.hs) creates a list of any other widget.++## Isolation of IO+The stateful effects are pure and do not involve IO. This has the benefit of allowing better testing of the intention of gadgets; increasing confidence of the behaviour of the gadget, reducing the surface area of IO misbehaviour.++There are only two places where IO is allowed:+1. in the gadget `Command` interpreter,+2. in the event callback handlers, due to the need to read properties from `javascript` and dispatching `Actions`. Besides dispatching `Actions`, it is bad practice to create any other observable side effects in event handlers.++## Combine multiple concurrent stateful effects+AFAIK, Haskell is the only language where you can [combine multiple concurrent stateful effects consistently.](https://github.com/louispan/glazier#combine-multiple-concurrent-stateful-effects)++## Compile using GHC as well as GHCJS+`Glazier.React` uses [ghcjs-base-stub](https://github.com/louispan/ghcjs-base-stub) allows compiling GHCJS projects using GHC, which means you can develop using intero.++## Easier management of GHCJS callbacks+`Glazier.React` uses [disposable](https://github.com/louispan/disposable) to ease cleanup of GHCJS callbacks. It also uses a Free Monad [`Maker`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Maker.hs) DSL to ease creation of callbacks for widgets.++## blaze/lucid style do notation+React elements can be coded using blaze/lucid-style `do` notation using [`ReactMlT`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Markup.hs)++## Haskell-driven rendering+All state and processing is in Haskell, meaning only a simple shim `React.Component` is required. This reduces the amount of `javascript` required and reduces the need for complex stateful integration with `React`.++# Examples++## TodoMVC+This is a fully featured TodoMVC in in Haskell and ReactJS using the [glazier-react](https://github.com/louispan/glazier-react) library.++For a live demo, see https://louispan.github.io/glazier-react-examples/++For more details, see the [todo example README.md](https://github.com/louispan/glazier-react-examples/tree/master/examples/todo)++# Documentation++## React+* Please refer to [react docs](https://facebook.github.io/react/docs). You only need to read up to [handling events](https://facebook.github.io/react/docs/handling-events.html).+* Also read [Lists and Keys](https://facebook.github.io/react/docs/lists-and-keys.html), and [Refs and the DOM](https://facebook.github.io/react/docs/refs-and-the-dom.html).+* Ignore controlled input in [Forms](https://facebook.github.io/react/docs/forms.html). In my experience, controlled input is error-prone and it is better to use it uncontrolled.+ * Using uncontrolled input doesn't stop you from subscribing to onChange and obtaining the latest value of the input. Just do not force a render with react[`setState`](https://facebook.github.io/react/docs/react-component.html#setstate).++## Glazier+Please read the [README.md](https://github.com/louispan/glazier) for a brief overview of glazier.++## Markup+[`Glazier.React.Markup`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Markup.hs) is a StateT monad that enables blaze/lucid style `do` notation to markup React elements to render.++```+bh (strJS "footer") [("className", strJS "footer")] $ do+ bh (strJS "span") [ ("className", strJS "todo-count")+ , ("key", strJS "todo-count")] $ do+ bh (strJS "strong") [("key", strJS "items")]+ (s ^. activeCount . to (txt . pack . show))+ txt " items left"+```++## Event handling+React re-uses SyntheticEvent from a pool, which means it may no longer be valid if we lazily parse it. However, we still want lazy parsing so we don't parse unnecessary fields.++Additionally, we don't want to block during the event handling.The reason this is a problem is because Javascript is single threaded, but Haskell is lazy.+Therefore GHCJS threads are a strange mixture of synchronous and asynchronous threads, where a synchronous thread might be converted to an asynchronous thread if a "black hole" is encountered.+See https://github.com/ghcjs/ghcjs-base/blob/master/GHCJS/Concurrent.hs++[`Glazier.React.Event`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Event.hs) uses the event handling idea from the haskell [`react-flux`](https://hackage.haskell.org/package/react-flux/docs/React-Flux-PropertiesAndEvents.html) library to allow lazy parsing of event safely.++Event handling should only be done via [`eventHandler` or `eventHandlerM`](https://github.com/louispan/glazier-react/blob/33f4e244cff1a3e98ee1845f9ae2392818b9e512/src/Glazier/React/Event.hs#L90).++```+eventHandlerM :: (Monad m, NFData a) => (evt -> m a) -> (a -> m b) -> (evt -> m b)+```++This safe interface requires two input functions:+1. a function to reduce SyntheticEvent to a NFData. The mkEventCallback will ensure that the NFData is forced which will ensure all the required fields from Synthetic event has been parsed. This function must not block.+2. a second function that uses the NFData. This function is allowed to block.++ mkEventHandler results in a function that you can safely pass into 'GHC.Foreign.Callback.syncCallback1' with 'GHCJS.Foreign.Callback.ContinueAsync'.++## Simple and efficient React.Component integration+`Glazier.React` only uses `ReactJS` as a thin layer for rendering and registering event handlers. All state and event processing are performed in Haskell, which means only a simple shim `React.PureComponent` is required.++Only [one shim React component](https://github.com/louispan/glazier-react/blob/master/jsbits/react.js) is ever used and the only methods are required are [`setState`](https://facebook.github.io/react/docs/react-component.html#setstate), [`render`](https://facebook.github.io/react/docs/react-component.html#render) and [`componentDidUpdate`](https://facebook.github.io/react/docs/react-component.html#componentdidupdate),++The shim component only has one thing in it's state, a sequence number. This sequence number is only changed with [`setState`](https://facebook.github.io/react/docs/react-component.html#setstate) when the `Glazier.Gadget` determined that there is a need for re-rendering. This is easy and efficient to determine since `Gadget` is the `StateT` responsible for changing the state in the first place.++This has the benefits of:+* Only the react shim components with changed haskell state will be re-rendered.+* React is able to efficiently determine if state has changed (just a single integer comparison)+* The shim React component is very simple.++## Modelling+[`Glazier.React.Model`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Model.hs) contain many nuanced concepts of Model.++### Model+The `Model` is the pure data used for rendering and stateful logic (the nouns).+It may contain `SuperModel` (see below) of other widgets.++### Plan+The `Plan` contains the callbacks for integrating with React (the verbs). It also contains a javascript reference to the instance of shim component used for the widget. This reference is used to trigger rendering with [`setState`](https://facebook.github.io/react/docs/react-component.html#setstate).++### Design+`Design` is basically a tuple of `Model` and `Plan`. It is a separate data type in order to generate convenient lenses to the fields.+`Design` is all that a `Window` needs to purely generate rendering instructions.++### Frame+`Frame` is a type synonym of `MVar Design`. It is a mutable holder of a copy of `Design`. This is so how the official state from Haskell is communicated to the React [`render`](https://facebook.github.io/react/docs/react-component.html#render) callback. The [`render`](https://facebook.github.io/react/docs/react-component.html#render) callback will read the latest copy of `Design` from the `MVar` and pass it to the widget `Window` for rendering.++### SuperModel+`SuperModel` is basically a tuple of `Design` and `Frame`. It is a separate data type in order to generate convenient lenses to the fields.+This contains everything a widget needs for rendering and state processing.+Most state processing is performed using the pure `Design`. The `Frame` is only used for the `RenderCommand`, to copy the latest `Design` into the `Frame` when re-rendering is required.++## Maker+`MVars` for `Frame`s and `Callback`s for `Plan`s may only be created in IO. Using Free Monads, [`Glazier.React.Maker`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Maker.hs) provides a safe way to create them without allowing other arbitrary IO.++The `Maker` can also be used create the initial `SuperModel` state for the widgets.+The `Maker` DSL has an `action` type parameter which indicated the type of action that is dispatched by the widget.+The `action` type can be mapped and hoisted to a larger `action` type, allow for embedding the smaller widget action in larger widget actions.++For example, the [TodoMVC application](https://github.com/louispan/glazier-react-examples/blob/32b5b077faa499e7501cb8e5417105b340de9ad3/examples/todo/haskell/app/Main.hs#L44) uses `Maker` to create the initial application `SuperModel` which involves making and hoisting the `SuperModel` of the input, list of todos widget, and footer widget.++## Disposable+GHCJS `Callback`s has resources that are not automatically collected by the garbage collector. `Callback`s need to be released manually. The [disposable](https://github.com/louispan/disposable) library provides a safe and easy way to convert the `Callback` into a storable `SomeDisposable` that can be queued up to be released after the next rendering frame.++[disposable](https://github.com/louispan/disposable) allows generic instances of `Disposing` to be easily created, which make it easy to create instances of `Disposing`+for a `Plan` of `Callback`s, and therefore the parent container`Design`, `SuperModel`, and `Model` (which may contain other widget `SuperModel`s)++The [`List` widget](https://github.com/louispan/glazier-react-widget/blob/54a771f492b864ff422e31949284ea4b23aa02c6/src/Glazier/React/Widgets/List.hs#L181) shows how the disposables can be queued for destruction after the next rendered frame.++## Widget+A [`Glazier.React.Widget`](https://github.com/louispan/glazier-react/blob/master/src/Glazier/React/Widget.hs) is the combination of:++The `Maker` instruction on how to create the `Plan` of that widget:+```+mkPlan :: Frame Model Plan -> F (Maker Action) Plan+```+The rendering instructions for that widget:+```+window:: WindowT (Design Model Plan) (ReactMlT Identity) ()+```+The state changes from `Action` events:+```+gadget :: GadgetT Action (SuperModel Model Plan) Identity (DList Command)+```+This is everything you need in order to create, render and interact with a widget.++`Glazier.React.IsWidget` is a typeclass that provides handy XXXOf type functions to get to the type of `Command`, `Action`, `Model`, `Plan` of the Widget. It also ensures that the `Model` and `Plan` is an instance of `Disposing`.++```+instance (CD.Disposing m, CD.Disposing p) =>+ IsWidget (Widget c a m p) where+ type CommandOf (Widget c a m p) = c+ type ActionOf (Widget c a m p) = a+ type ModelOf (Widget c a m p) = m+ type PlanOf (Widget c a m p) = p+ mkPlan (Widget f _ _) = f+ window (Widget _ f _) = f+ gadget (Widget _ _ f) = f+```++This is useful for creating widgets that is composed of other Widgets. For example:+The [List widget](https://github.com/louispan/glazier-react-widget/blob/54a771f492b864ff422e31949284ea4b23aa02c6/src/Glazier/React/Widgets/List.hs#L122) uses the IsWidget typeclass in order to ensure that the `itemWidget` can be disposed.++## Widget best practices+Please refer to [`glazier-react-widget`](https://github.com/louispan/glazier-react-widget) for documentation on the best practices for creating `Glazier.React.Widgets`
glazier-react.cabal view
@@ -1,5 +1,5 @@ name: glazier-react-version: 0.2.0.0+version: 0.3.0.0 synopsis: ReactJS binding using Glazier and Pipes.Fluid description: ReactJS binding using Glazier and Pipes.Fluid, which is more functional and composable than Elm/Flux.@@ -26,6 +26,7 @@ Glazier.React.Maker Glazier.React.Maker.Run Glazier.React.Markup+ Glazier.React.Model Glazier.React.ReactDOM Glazier.React.Widget build-depends: base >= 4.7 && < 5
jsbits/registry.js view
@@ -29,10 +29,10 @@ if (!handlers[name]) handlers[name] = { nextIndex: 0, listeners: {} }; - var i = handlers[name].nextIndex;+ const i = handlers[name].nextIndex; handlers[name].nextIndex += 1; handlers[name].listeners[i] = listener;- var unregister = function() {+ const unregister = function() { handlers[name] = omit(handlers[name], i); }; return unregister;@@ -45,7 +45,7 @@ if (handlers[name]) { // iterate using copy of keys to safeguard against listeners added/removed // during callbacks.- for (const key of Object.keys(handlers[name].listeners)) {+ for (var key of Object.keys(handlers[name].listeners)) { const listener = handlers[name].listeners[key]; if (listener) { ret.push(listener(data));
src/Glazier/React/Command/Run.hs view
@@ -10,15 +10,15 @@ import Control.Lens import Control.Monad import qualified GHCJS.Types as J-import qualified Glazier.React.Widget as R+import qualified Glazier.React.Model as R import qualified JavaScript.Extras as JE import qualified JavaScript.Object as JO componentSetState :: R.HasSuperModel sm mdl pln => sm -> [JE.Property] -> J.JSVal -> IO () componentSetState sm props j = do let dsn = sm ^. R.design- rep = sm ^. R.replica- void $ swapMVar rep dsn+ frm = sm ^. R.frame+ void $ swapMVar frm dsn js_componentSetState (JE.fromProperties props) j #ifdef __GHCJS__
src/Glazier/React/Event.hs view
@@ -71,6 +71,11 @@ -- | Using the NFData idea from React/Flux/PropertiesAndEvents.hs -- React re-uses SyntheticEvent from a pool, which means it may no longer be valid if we lazily -- parse it. However, we still want lazy parsing so we don't parse unnecessary fields.+-- Additionally, we don't want to block during the event handling.The reason this is a problem is+-- because Javascript is single threaded, but Haskell is lazy.+-- Therefore GHCJS threads are a strange mixture of synchronous and asynchronous threads,+-- where a synchronous thread might be converted to an asynchronous thread if a "black hole" is encountered.+-- See https://github.com/ghcjs/ghcjs-base/blob/master/GHCJS/Concurrent.hs -- This safe interface requires two input functions: -- 1. a function to reduce SyntheticEvent to a NFData. The mkEventCallback will ensure that the -- NFData is forced which will ensure all the required fields from Synthetic event has been parsed.@@ -78,10 +83,6 @@ -- 2. a second function that uses the NFData. This function is allowed to block. -- mkEventHandler results in a function that you can safely pass into 'GHC.Foreign.Callback.syncCallback1' -- with 'GHCJS.Foreign.Callback.ContinueAsync'.--- The reason of this is because Javascript is single threaded, but Haskell is lazy.--- Therefore GHCJS threads are a strange mixture of synchronous and asynchronous threads,--- where a synchronous thread might be converted to an asynchronous thread if a "black hole" is encountered.--- See https://github.com/ghcjs/ghcjs-base/blob/master/GHCJS/Concurrent.hs eventHandler :: NFData a => (evt -> a) -> (a -> b) -> (evt -> b) eventHandler goStrict goLazy evt = goLazy $!! goStrict evt
src/Glazier/React/Maker.hs view
@@ -15,7 +15,7 @@ import qualified Glazier as G import qualified Glazier.React.Component as R import qualified Glazier.React.Markup as R-import qualified Glazier.React.Widget as R+import qualified Glazier.React.Model as R -- | DSL for IO effects required during making widget models and callbacks -- 'Maker' remembers the action type to allow 'mapAction' for changing the action type by parent widgets.@@ -25,16 +25,16 @@ :: (J.JSVal -> MaybeT IO [act]) -> (J.Callback (J.JSVal -> IO ()) -> nxt) -> Maker act nxt- MkEmptyReplica- :: (R.Replica mdl pln -> nxt)+ MkEmptyFrame+ :: (R.Frame mdl pln -> nxt) -> Maker act nxt MkRenderer- :: R.Replica mdl pln- -> (J.JSVal -> G.WindowT (R.Design mdl pln) (R.ReactMl) ())+ :: R.Frame mdl pln+ -> (J.JSVal -> G.WindowT (R.Design mdl pln) R.ReactMl ()) -> (J.Callback (J.JSVal -> IO J.JSVal) -> nxt) -> Maker act nxt- PutReplica- :: R.Replica mdl pln+ PutFrame+ :: R.Frame mdl pln -> R.Design mdl pln -> nxt -> Maker act nxt@@ -44,9 +44,9 @@ instance Functor (Maker act) where fmap f (MkHandler handler g) = MkHandler handler (f . g)- fmap f (MkEmptyReplica g) = MkEmptyReplica (f . g)+ fmap f (MkEmptyFrame g) = MkEmptyFrame (f . g) fmap f (MkRenderer ms render g) = MkRenderer ms render (f . g)- fmap f (PutReplica rep dsn x) = PutReplica rep dsn (f x)+ fmap f (PutFrame frm dsn x) = PutFrame frm dsn (f x) fmap f (GetComponent g) = GetComponent (f . g) makeFree ''Maker@@ -54,19 +54,7 @@ -- | Allows changing the action type of Maker mapAction :: (act -> act') -> Maker act a -> Maker act' a mapAction f (MkHandler handler g) = MkHandler (\v -> fmap f <$> handler v) g-mapAction _ (MkEmptyReplica g) = MkEmptyReplica g+mapAction _ (MkEmptyFrame g) = MkEmptyFrame g mapAction _ (MkRenderer ms render g) = MkRenderer ms render g-mapAction _ (PutReplica ms s x) = PutReplica ms s x+mapAction _ (PutFrame frm dsn x) = PutFrame frm dsn x mapAction _ (GetComponent g) = GetComponent g--mkSuperModel- :: MonadFree (Maker act) m- => (R.Replica mdl pln -> m pln)- -> (pln -> R.Design mdl pln)- -> m (R.SuperModel mdl pln)-mkSuperModel makePlan toDesign = do- rep <- mkEmptyReplica- pln <- makePlan rep- let dsn = toDesign pln- putReplica rep dsn- pure (R.SuperModel dsn rep)
src/Glazier/React/Maker/Run.hs view
@@ -35,12 +35,12 @@ run :: R.ReactComponent -> PC.Output act -> R.Maker act (IO a) -> IO a run _ output (R.MkHandler handler g) = mkActionCallback output handler >>= g -run _ _ (R.MkEmptyReplica g) = newEmptyMVar >>= g+run _ _ (R.MkEmptyFrame g) = newEmptyMVar >>= g run _ _ (R.MkRenderer ms render g) = J.syncCallback1' (onRender ms render') >>= g where render' v = hoist (hoist generalize) (render v) -run _ _ (R.PutReplica rep dsn g) = putMVar rep dsn >> g+run _ _ (R.PutFrame frm dsn g) = putMVar frm dsn >> g run component _ (R.GetComponent g) = g component
+ src/Glazier/React/Model.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Glazier.React.Model where++import Control.Concurrent.MVar+import qualified Control.Disposable as CD+import Control.Lens+import qualified GHC.Generics as G++-- | Lens to the callbacks and interactions with React+class HasPlan c pln | c -> pln where+ plan :: Lens' c pln++-- | Lens to the pure model for state and rendering.+class HasModel c mdl | c -> mdl where+ model :: Lens' c mdl++-- | A record of Model and Plan+data Design mdl pln = Design+ { _model :: mdl+ , _plan :: pln+ } deriving (G.Generic)++-- | All designs should be disposable to make it easier for cleanup of callbacks.+instance (CD.Disposing pln, CD.Disposing mdl) => CD.Disposing (Design mdl pln)++instance HasPlan (Design mdl pln) pln where+ plan f (Design mdl pln) = fmap (\pln' -> Design mdl pln') (f pln)+ {-# INLINE plan #-}++instance HasModel (Design mdl pln) mdl where+ model f (Design mdl pln) = fmap (\mdl' -> Design mdl' pln) (f mdl)+ {-# INLINE model #-}++class HasDesign c mdl pln | c -> mdl pln where+ design :: Lens' c (Design mdl pln)++instance HasDesign (Design mdl pln) mdl pln where+ design = id++-- | Frame is a Mvar of Design. React rendering callback uses this MVar for rendering.+type Frame mdl pln = MVar (Design mdl pln)++class HasFrame c mdl pln | c -> mdl pln where+ frame :: Lens' c (Frame mdl pln)++instance HasFrame (Frame mdl pln) mdl pln where+ frame = id++-- | A record of Design and Frame.+data SuperModel mdl pln = SuperModel+ { _design :: Design mdl pln+ , _frame :: Frame mdl pln+ } deriving (G.Generic)++-- Undecidableinstances!+instance CD.Disposing (Design mdl pln) => CD.Disposing (SuperModel mdl pln) where+ disposing s = CD.disposing $ s ^. design++class (HasDesign c mdl pln, HasFrame c mdl pln) => HasSuperModel c mdl pln | c -> mdl pln where+ superModel :: Lens' c (SuperModel mdl pln)++instance HasSuperModel (SuperModel mdl pln) mdl pln where+ superModel = id++instance HasFrame (SuperModel mdl pln) mdl pln where+ frame f (SuperModel dsn frm) = fmap (\frm' -> SuperModel dsn frm') (f frm)+ {-# INLINE frame #-}++instance HasDesign (SuperModel mdl pln) mdl pln where+ design f (SuperModel dsn frm) = fmap (\dsn' -> SuperModel dsn' frm) (f dsn)+ {-# INLINE design #-}++instance HasPlan (SuperModel mdl pln) pln where+ plan = design . plan++instance HasModel (SuperModel mdl pln) mdl where+ model = design . model
src/Glazier/React/Widget.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-}@@ -8,81 +7,55 @@ module Glazier.React.Widget where -import Control.Concurrent.MVar import qualified Control.Disposable as CD import Control.Lens-import qualified GHC.Generics as G--class HasWidgetPlan c pln | c -> pln where- widgetPlan :: Lens' c pln--class HasWidgetModel c mdl | c -> mdl where- widgetModel :: Lens' c mdl--data Design mdl pln = Design- { _widgetModel :: mdl- , _widgetPlan :: pln- } deriving (G.Generic)--instance (CD.Disposing pln, CD.Disposing mdl) => CD.Disposing (Design mdl pln)--instance HasWidgetPlan (Design mdl pln) pln where- widgetPlan f (Design mdl pln) = fmap (\pln' -> Design mdl pln') (f pln)- {-# INLINE widgetPlan #-}--instance HasWidgetModel (Design mdl pln) mdl where- widgetModel f (Design mdl pln) = fmap (\mdl' -> Design mdl' pln) (f mdl)- {-# INLINE widgetModel #-}--class HasDesign c mdl pln | c -> mdl pln where- design :: Lens' c (Design mdl pln)--instance HasDesign (Design mdl pln) mdl pln where- design = id--type Replica mdl pln = MVar (Design mdl pln)--class HasReplica c mdl pln | c -> mdl pln where- replica :: Lens' c (Replica mdl pln)--instance HasReplica (Replica mdl pln) mdl pln where- replica = id--data SuperModel mdl pln = SuperModel- { _design :: Design mdl pln- , _replica :: Replica mdl pln- } deriving (G.Generic)--instance CD.Disposing (Design mdl pln) => CD.Disposing (SuperModel mdl pln) where- disposing s = CD.disposing $ s ^. design--class (HasDesign c mdl pln, HasReplica c mdl pln) => HasSuperModel c mdl pln | c -> mdl pln where- superModel :: Lens' c (SuperModel mdl pln)--instance HasSuperModel (SuperModel mdl pln) mdl pln where- superModel = id+import Control.Monad.Free.Church+import qualified Data.DList as D+import qualified Glazier as G+import qualified Glazier.React.Maker as R+import qualified Glazier.React.Markup as R+import qualified Glazier.React.Model as R -instance HasReplica (SuperModel mdl pln) mdl pln where- replica f (SuperModel dsn rep) = fmap (\rep' -> SuperModel dsn rep') (f rep)- {-# INLINE replica #-}+class (CD.Disposing (ModelOf w)+ , CD.Disposing (PlanOf w)) => IsWidget w where+ -- | The pure model for state and rendering+ type CommandOf w :: *+ type ActionOf w :: *+ type ModelOf w :: *+ type PlanOf w :: *+ mkPlan :: w -> R.Frame (ModelOf w) (PlanOf w) -> F (R.Maker (ActionOf w)) (PlanOf w)+ window :: w -> G.WindowT (R.Design (ModelOf w) (PlanOf w)) (R.ReactMlT Identity) ()+ gadget :: w -> G.GadgetT (ActionOf w) (R.SuperModel (ModelOf w) (PlanOf w)) Identity (D.DList (CommandOf w)) -instance HasDesign (SuperModel mdl pln) mdl pln where- design f (SuperModel dsn rep) = fmap (\dsn' -> SuperModel dsn' rep) (f dsn)- {-# INLINE design #-}+type family DesignOf w where+ DesignOf w = R.Design (ModelOf w) (PlanOf w) -class IsWidget w where- -- The input to Gadget- type WidgetAction w :: *+type family FrameOf w where+ FrameOf w = R.Frame (ModelOf w) (PlanOf w) - -- The output of Gadget- type WidgetCommand w :: *+type family SuperModelOf w where+ SuperModelOf w = R.SuperModel (ModelOf w) (PlanOf w) - -- The pure model for state and rendering- type WidgetModel w :: *+-- | Contains everything you need to make the model,+-- render, and run the event processing.+data Widget c a m p = Widget+ (R.Frame m p -> F (R.Maker a) p)+ (G.WindowT (R.Design m p) (R.ReactMlT Identity) ())+ (G.GadgetT a (R.SuperModel m p) Identity (D.DList c)) - -- Callbacks and data required for interfacing with react.- type WidgetPlan w :: *+instance (CD.Disposing m, CD.Disposing p) =>+ IsWidget (Widget c a m p) where+ type CommandOf (Widget c a m p) = c+ type ActionOf (Widget c a m p) = a+ type ModelOf (Widget c a m p) = m+ type PlanOf (Widget c a m p) = p+ mkPlan (Widget f _ _) = f+ window (Widget _ f _) = f+ gadget (Widget _ _ f) = f -type WidgetDesign w = Design (WidgetModel w) (WidgetPlan w)-type WidgetReplica w = Replica (WidgetModel w) (WidgetPlan w)-type WidgetSuperModel w = SuperModel (WidgetModel w) (WidgetPlan w) +mkSuperModel :: IsWidget w => w -> ModelOf w -> F (R.Maker (ActionOf w)) (R.SuperModel (ModelOf w) (PlanOf w))+mkSuperModel w mdl = do+ frm <- R.mkEmptyFrame+ dsn <- R.Design mdl <$> mkPlan w frm+ R.putFrame frm dsn+ pure (R.SuperModel dsn frm)