|
| 1 | +module Concur.VDom where |
| 2 | + |
| 3 | +import Concur.Core (Widget, runWidget) |
| 4 | +import Concur.Core.DOM as CD |
| 5 | +import Concur.Core.LiftWidget (class LiftWidget, liftWidget) |
| 6 | +import Concur.Core.Thunk as T |
| 7 | +import Concur.Core.Types (display, result) |
| 8 | +import Control.Applicative (pure) |
| 9 | +import Control.Bind (bind, discard) |
| 10 | +import Control.MultiAlternative (class MultiAlternative) |
| 11 | +import Control.ShiftMap (class ShiftMap) |
| 12 | +import Data.Function (identity, ($)) |
| 13 | +import Data.Functor (class Functor) |
| 14 | +import Data.Maybe (Maybe(..)) |
| 15 | +import Data.Newtype (class Newtype, un, wrap) |
| 16 | +import Data.Traversable (for_) |
| 17 | +import Data.Unit (Unit, unit) |
| 18 | +import Data.Void (Void, absurd) |
| 19 | +import Effect (Effect) |
| 20 | +import Effect.Ref (Ref) |
| 21 | +import Effect.Ref as Ref |
| 22 | +import Effect.Uncurried (runEffectFn1, runEffectFn2) |
| 23 | +import Halogen.VDom as V |
| 24 | +import Halogen.VDom.DOM.Prop as P |
| 25 | +import Halogen.VDom.Machine (Step) |
| 26 | +import Halogen.VDom.Types (ElemName(..), VDom(..)) |
| 27 | +import Safe.Coerce (coerce) |
| 28 | +import Web.DOM (Node, Element) |
| 29 | +import Web.DOM.Document (Document) |
| 30 | +import Web.DOM.Element (toNode) |
| 31 | +import Web.DOM.Node (appendChild) as DOM |
| 32 | +import Web.DOM.ParentNode (querySelector) as Web |
| 33 | +import Web.Event.Event (EventType(..)) |
| 34 | +import Web.HTML (window) as Web |
| 35 | +import Web.HTML.HTMLDocument (toDocument, toParentNode) as Web |
| 36 | +import Web.HTML.Window (document) as Web |
| 37 | + |
| 38 | +-------------------------- |
| 39 | +-- Types |
| 40 | + |
| 41 | +type HTMLProps :: Type -> Type |
| 42 | +type HTMLProps a = Array (P.Prop a) |
| 43 | + |
| 44 | +type HTMLBody :: Type -> Type |
| 45 | +type HTMLBody a = T.Thunk (HTMLF a) |
| 46 | + |
| 47 | +type HTMLSpec a = V.VDomSpec (HTMLProps a) (HTMLBody a) |
| 48 | +type HTMLVDom a = VDom (HTMLProps a) (HTMLBody a) |
| 49 | +newtype HTMLF a = HTMLF (HTMLVDom a) |
| 50 | + |
| 51 | +derive instance Functor HTMLF |
| 52 | +derive instance Newtype (HTMLF a) _ |
| 53 | + |
| 54 | +type HTML1 = HTMLF (Effect Unit) |
| 55 | +type HTML = Array HTML1 |
| 56 | + |
| 57 | +-------------------------- |
| 58 | +-- Runners |
| 59 | + |
| 60 | +mkSpec :: Document -> HTMLSpec (Effect Unit) |
| 61 | +mkSpec document = V.VDomSpec |
| 62 | + { buildWidget: T.buildThunk (un HTMLF) |
| 63 | + , buildAttributes: P.buildProp identity |
| 64 | + , document |
| 65 | + } |
| 66 | + |
| 67 | +-- To monoidal append views, we just dump them into a container div |
| 68 | +unHTML :: HTML -> HTMLVDom (Effect Unit) |
| 69 | +unHTML arr = Elem Nothing (coerce "div") [] (coerce arr) |
| 70 | + |
| 71 | +mkHandler |
| 72 | + :: Ref (Maybe (Step (HTMLVDom (Effect Unit)) Node)) |
| 73 | + -> HTMLSpec (Effect Unit) |
| 74 | + -> Element |
| 75 | + -> HTML |
| 76 | + -> Effect Unit |
| 77 | +mkHandler machineRef spec body v = do |
| 78 | + mmachine <- Ref.read machineRef |
| 79 | + machine <- case mmachine of |
| 80 | + Just machine -> do |
| 81 | + machine' <- runEffectFn2 V.step machine (unHTML v) |
| 82 | + Ref.write (Just machine') machineRef |
| 83 | + pure machine' |
| 84 | + Nothing -> do |
| 85 | + machine' <- runEffectFn1 (V.buildVDom spec) (unHTML v) |
| 86 | + DOM.appendChild (V.extract machine') (toNode body) |
| 87 | + pure machine' |
| 88 | + Ref.write (Just machine) machineRef |
| 89 | + |
| 90 | +run :: Widget HTML Void -> Effect Unit |
| 91 | +run ww = do |
| 92 | + win ← Web.window |
| 93 | + doc ← Web.document win |
| 94 | + bod ← Web.querySelector (wrap "body") (Web.toParentNode doc) |
| 95 | + for_ bod \body → do |
| 96 | + machineRef <- Ref.new Nothing |
| 97 | + let |
| 98 | + spec = mkSpec (Web.toDocument doc) |
| 99 | + handler = mkHandler machineRef spec body |
| 100 | + runWidget ww $ result handler absurd |
| 101 | + |
| 102 | +------------------------------ |
| 103 | +-- Constructors |
| 104 | + |
| 105 | +el |
| 106 | + :: forall m a |
| 107 | + . ShiftMap (Widget HTML) m |
| 108 | + => MultiAlternative m |
| 109 | + => String |
| 110 | + -> Array (P.Prop a) |
| 111 | + -> Array (m a) |
| 112 | + -> m a |
| 113 | +el s = CD.el' \a c -> HTMLF (V.Elem Nothing (ElemName s) a (coerce c)) |
| 114 | + |
| 115 | +text :: forall m a. LiftWidget HTML m => String -> m a |
| 116 | +text s = liftWidget $ display ([ HTMLF (Text s) ] :: HTML) |
| 117 | + |
| 118 | +prop :: forall a. String -> String -> P.Prop a |
| 119 | +prop k v = P.Attribute Nothing k v |
| 120 | + |
| 121 | +handle :: String -> P.Prop Unit |
| 122 | +handle eventName = P.Handler (EventType eventName) (\_ -> Just unit) |
| 123 | + |
0 commit comments