Merge remote-tracking branch 'origin/placeholder-refactor' into develop

# Conflicts:
#	.gitignore
#	reflex-dom.cabal
#	src-ghc/Reflex/Dom/WebSocket/Foreign.hs
#	src-ghcjs/Reflex/Dom/WebSocket/Foreign.hs
#	src/Reflex/Dom/WebSocket.hs
This commit is contained in:
Ryan Trinkle 2016-11-11 18:04:37 -05:00
commit 7b9f101573
14 changed files with 338 additions and 207 deletions

2
.gitignore vendored
View File

@ -36,3 +36,5 @@ hsenv.log
cabal.sandbox.config
.stack-work
codex.tags
*.dump-*
*.verbose-core2core

View File

@ -20,39 +20,45 @@ extra-source-files: src-ghc/Reflex/Dom/Internal/Foreign.hs
src/Reflex/Dom/Xhr/ResponseType.hs
src/Reflex/Dom/Xhr/Exception.hs
flag use-reflex-optimizer
description: Use the GHC plugin Reflex.Optimizer on some of the modules in the package. This is still experimental.
default: False
manual: True
library
hs-source-dirs: src
build-depends:
base >= 4.7 && < 4.10,
template-haskell,
stm == 2.4.*,
reflex == 0.5.*,
dependent-sum == 0.3.*,
dependent-map == 0.2.*,
semigroups >= 0.16 && < 0.19,
mtl >= 2.1 && < 2.3,
containers == 0.5.*,
these >= 0.4 && < 0.8,
ref-tf == 0.4.*,
random == 1.1.*,
ghcjs-dom >= 0.2.1 && < 0.3,
transformers >= 0.3 && < 0.6,
lens >= 4.7 && < 5,
text == 1.2.*,
blaze-builder,
bytestring == 0.10.*,
data-default >= 0.5 && < 0.8,
aeson >= 0.8 && < 1.1,
time >= 1.4 && < 1.7,
exception-transformers == 0.4.*,
directory == 1.2.*,
dependent-sum-template >= 0.0.0.4 && < 0.1,
zenc == 0.1.*,
base >= 4.7 && < 4.10,
bifunctors >= 4.2 && < 6,
bimap >= 0.3 && < 0.4,
monad-control >= 1.0.1 && < 1.1,
blaze-builder,
bytestring == 0.10.*,
containers == 0.5.*,
data-default >= 0.5 && < 0.8,
dependent-map == 0.2.*,
dependent-sum == 0.3.*,
dependent-sum-template >= 0.0.0.4 && < 0.1,
directory == 1.2.*,
exception-transformers == 0.4.*,
ghcjs-dom >= 0.2.1 && < 0.3,
keycode == 0.2.*,
unbounded-delays >= 0.1.0.9 && < 0.2
lens >= 4.7 && < 5,
monad-control >= 1.0.1 && < 1.1,
mtl >= 2.1 && < 2.3,
primitive >= 0.5 && < 0.7,
random == 1.1.*,
ref-tf == 0.4.*,
reflex == 0.5.*,
semigroups >= 0.16 && < 0.19,
stm == 2.4.*,
template-haskell,
text == 1.2.*,
these >= 0.4 && < 0.8,
time >= 1.4 && < 1.7,
transformers >= 0.3 && < 0.6,
unbounded-delays >= 0.1.0.9 && < 0.2,
zenc == 0.1.*
if impl(ghcjs)
hs-source-dirs: src-ghcjs
@ -101,6 +107,8 @@ library
other-extensions: TemplateHaskell
ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2 -ferror-spans
if flag(use-reflex-optimizer)
ghc-options: -fplugin=Reflex.Optimizer
test-suite hlint
build-depends: base, hlint == 1.9.*

View File

@ -77,7 +77,7 @@ closeWebSocket (JSWebSocket ws c) code reason = do
newWebSocket
:: WebView
-> Text -- url
-> (ByteString -> IO ()) -- onmessage
-> (Either ByteString a -> IO ()) -- onmessage
-> IO () -- onopen
-> IO () -- onerror
-> ((Bool, Word, Text) -> IO ()) -- onclose
@ -94,7 +94,7 @@ newWebSocket wv url onMessage onOpen onError onClose = withWebViewContext wv $ \
msg' <- fromJSStringMaybe c msg
case msg' of
Nothing -> return ()
Just m -> onMessage $ encodeUtf8 m
Just m -> onMessage $ Left $ encodeUtf8 m
jsvaluemakeundefined c
onMessageCb <- jsobjectmakefunctionwithcallback c nullPtr onMessage'
onOpen' <- wrapper $ \_ _ _ _ _ _ -> do
@ -123,3 +123,8 @@ newWebSocket wv url onMessage onOpen onError onClose = withWebViewContext wv $ \
addCbs <- jsstringcreatewithutf8cstring "this[0]['onmessage'] = this[1]; this[0]['onopen'] = this[2]; this[0]['onerror'] = this[3]; this[0]['onclose'] = this[4];"
_ <- jsevaluatescript c addCbs o nullPtr 1 nullPtr
return $ JSWebSocket ws c
onBSMessage :: Either ByteString b -> ByteString
onBSMessage = either id (error "onBSMessage: ghc env expects ByteString.")
type JSVal = ()

View File

@ -1,7 +1,10 @@
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE JavaScriptFFI #-}
module Reflex.Dom.WebSocket.Foreign where
module Reflex.Dom.WebSocket.Foreign
( module Reflex.Dom.WebSocket.Foreign
, JSVal
) where
import Prelude hiding (all, concat, concatMap, div, mapM, mapM_, sequence, span)
@ -54,7 +57,7 @@ closeEvent = unsafeEventName (toJSString "close")
newWebSocket
:: a
-> Text -- url
-> (ByteString -> IO ()) -- onmessage
-> (Either ByteString JSVal -> IO ()) -- onmessage
-> IO () -- onopen
-> IO () -- onerror
-> ((Bool, Word, Text) -> IO ()) -- onclose
@ -74,10 +77,13 @@ newWebSocket _ url onMessage onOpen onError onClose = do
e <- ask
d <- getData e
liftIO $ case jsTypeOf d of
String -> onMessage $ encodeUtf8 $ pFromJSVal d
String -> onMessage $ Right d
_ -> do
ab <- unsafeFreeze $ pFromJSVal d
onMessage $ toByteString 0 Nothing $ createFromArrayBuffer ab
onMessage $ Left $ toByteString 0 Nothing $ createFromArrayBuffer ab
return $ JSWebSocket ws
foreign import javascript safe "new DataView($3,$1,$2)" js_dataView :: Int -> Int -> JSVal -> JSVal
onBSMessage :: Either ByteString JSVal -> ByteString
onBSMessage = either id (encodeUtf8 . pFromJSVal)

View File

@ -21,7 +21,6 @@ module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
) where
import Reflex.Class
import Reflex.Deletable.Class
import Reflex.DynamicWriter
import Reflex.PerformEvent.Base
import Reflex.PerformEvent.Class
@ -69,6 +68,7 @@ import Control.Monad
import Control.Monad.Exception
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Primitive
import Control.Monad.Reader
import Control.Monad.Ref
import Control.Monad.State
@ -109,8 +109,20 @@ instance HasWebView m => HasWebView (DynamicWriterT t w m) where
type WebViewPhantom (DynamicWriterT t w m) = WebViewPhantom m
askWebView = lift askWebView
instance HasWebView m => HasWebView (RequestT t request response m) where
type WebViewPhantom (RequestT t request response m) = WebViewPhantom m
askWebView = lift askWebView
newtype WithWebView x m a = WithWebView { unWithWebView :: ReaderT (WebViewSingleton x) m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadTrans, MonadException, MonadAsyncException)
instance PrimMonad m => PrimMonad (WithWebView x m) where
type PrimState (WithWebView x m) = PrimState m
primitive = lift . primitive
instance MonadAdjust t m => MonadAdjust t (WithWebView x m) where
runWithReplace a0 a' = WithWebView $ runWithReplace (coerce a0) (coerceEvent a')
sequenceDMapWithAdjust dm0 dm' = WithWebView $ sequenceDMapWithAdjust (coerce dm0) (coerceEvent dm')
instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (WithWebView x m) where
{-# INLINABLE newEventWithTrigger #-}
newEventWithTrigger = lift . newEventWithTrigger
@ -154,10 +166,6 @@ instance PerformEvent t m => PerformEvent t (WithWebView x m) where
{-# INLINABLE performEvent #-}
performEvent e = liftWith $ \run -> performEvent $ fmap run e
instance Deletable t m => Deletable t (WithWebView x m) where
{-# INLINABLE deletable #-}
deletable = liftThrough . deletable
runWithWebView :: WithWebView x m a -> WebViewSingleton x -> m a
runWithWebView = runReaderT . unWithWebView
@ -218,6 +226,10 @@ instance HasJS x m => HasJS x (DynamicWriterT t w m) where
type JSM (DynamicWriterT t w m) = JSM m
liftJS = lift . liftJS
instance HasJS x m => HasJS x (RequestT t request response m) where
type JSM (RequestT t request response m) = JSM m
liftJS = lift . liftJS
-- | A Monad that is capable of executing JavaScript
class Monad m => MonadJS x m | m -> x where
runJS :: JSFFI -> [JSRef x] -> m (JSRef x)

View File

@ -18,14 +18,13 @@
module Reflex.Dom.Builder.Class
( module Reflex.Dom.Builder.Class
, module Reflex.Dom.Builder.Class.Events
, module Reflex.Deletable.Class
) where
import Reflex.Class as Reflex
import Reflex.Dom.Builder.Class.Events
import Reflex.Deletable.Class
import Reflex.DynamicWriter
import Reflex.PerformEvent.Class
import Reflex.PerformEvent.Base
import Reflex.PostBuild.Class
import qualified Control.Category
@ -34,16 +33,13 @@ import Control.Monad.Reader
import Control.Monad.State.Strict
import Control.Monad.Trans.Control
import Data.Default
import Data.Foldable
import Data.Functor.Misc
import Data.List.NonEmpty (NonEmpty, nonEmpty)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Proxy
import Data.Semigroup
import Data.String
import Data.Text (Text)
import Data.Traversable
import Data.Type.Coercion
import GHC.Exts (Constraint)
@ -65,7 +61,7 @@ liftElementConfig cfg = cfg
-- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building
-- dynamic DOM in the 'Reflex' timeline @t@
class (Monad m, Reflex t, Deletable t m, DomSpace (DomBuilderSpace m)) => DomBuilder t m | m -> t where
class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), MonadAdjust t m) => DomBuilder t m | m -> t where
type DomBuilderSpace m :: *
textNode :: TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
default textNode :: ( MonadTrans f
@ -86,9 +82,11 @@ class (Monad m, Reflex t, Deletable t m, DomSpace (DomBuilderSpace m)) => DomBui
=> Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
element t cfg child = liftWith $ \run -> element t (liftElementConfig cfg) $ run child
{-# INLINABLE element #-}
{-
-- | Create a placeholder in the DOM, with the ability to insert new DOM before it
-- The provided DOM will be executed after the current frame, so it will not be affected by any occurrences that are concurrent with the occurrence that created it
placeholder :: PlaceholderConfig above t m -> m (Placeholder above t)
-}
inputElement :: InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
default inputElement :: ( MonadTransControl f
, m ~ f m'
@ -403,13 +401,6 @@ instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (ElementConfig
instance (DomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => DomBuilder t (PostBuildT t m) where
type DomBuilderSpace (PostBuildT t m) = DomBuilderSpace m
{-# INLINABLE placeholder #-}
placeholder cfg = lift $ do
rec childPostBuild <- deletable (_placeholder_deletedSelf p) $ performEvent $ return () <$ _placeholder_insertedAbove p
p <- placeholder $ cfg
{ _placeholderConfig_insertAbove = ffor (_placeholderConfig_insertAbove cfg) $ \a -> runPostBuildT a =<< headE childPostBuild
}
return p
wrapRawElement e cfg = liftWith $ \run -> wrapRawElement e $ fmap1 run cfg
instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
@ -421,32 +412,6 @@ instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (
(el, (a, newS)) <- lift $ element elementTag cfg' $ runStateT child s
put newS
return (el, a)
placeholder cfg = do
let cfg' = cfg
{ _placeholderConfig_insertAbove = runDynamicWriterTInternal <$> _placeholderConfig_insertAbove cfg
}
let manageChildren :: Event t (NonEmpty (Replaceable t (Dynamic t w))) -- ^ Add nodes on the right; these are in reverse order
-> Event t () -- ^ No more nodes will be added after this event fires
-> m (Replaceable t (Dynamic t w))
manageChildren newChildren additionsCeased = do
rec nextId <- hold (0 :: Int) newNextId -- We assume this will never wrap around
let numberedNewChildren :: Event t (Int, PatchMap (Map Int (Replaceable t (Dynamic t w))))
numberedNewChildren = flip pushAlways newChildren $ \rcs -> do
let cs = reverse $ toList rcs
myFirstId <- sample nextId
let (myNextId, numbered) = mapAccumL (\n v -> (succ n, (n, Just v))) myFirstId cs
return (myNextId, PatchMap $ Map.fromList numbered)
newNextId = fst <$> numberedNewChildren
mconcatIncrementalReplaceableDynMap Map.empty (snd <$> numberedNewChildren) additionsCeased
rec children <- lift $ manageChildren childOutputs $ cfg ^. deleteSelf
p <- DynamicWriterT $ do
modify (children:)
lift $ placeholder cfg'
let result = fst <$> _placeholder_insertedAbove p
childOutputs = fmapMaybe (nonEmpty . snd) $ _placeholder_insertedAbove p
return $ p
{ _placeholder_insertedAbove = result
}
inputElement cfg = lift $ inputElement $ cfg & inputElementConfig_elementConfig %~ liftElementConfig
textAreaElement cfg = lift $ textAreaElement $ cfg & textAreaElementConfig_elementConfig %~ liftElementConfig
selectElement cfg (DynamicWriterT child) = DynamicWriterT $ do
@ -460,6 +425,31 @@ instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (
{ _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
}
instance (DomBuilder t m, MonadHold t m, MonadFix m) => DomBuilder t (RequestT t request response m) where
type DomBuilderSpace (RequestT t request response m) = DomBuilderSpace m
textNode = liftTextNode
element elementTag cfg (RequestT child) = RequestT $ do
r <- ask
s <- get
let cfg' = liftElementConfig cfg
(el, (a, newS)) <- lift $ lift $ element elementTag cfg' $ runReaderT (runStateT child s) r
put newS
return (el, a)
inputElement cfg = lift $ inputElement $ cfg & inputElementConfig_elementConfig %~ liftElementConfig
textAreaElement cfg = lift $ textAreaElement $ cfg & textAreaElementConfig_elementConfig %~ liftElementConfig
selectElement cfg (RequestT child) = RequestT $ do
r <- ask
s <- get
let cfg' = cfg & selectElementConfig_elementConfig %~ liftElementConfig
(el, (a, newS)) <- lift $ lift $ selectElement cfg' $ runReaderT (runStateT child s) r
put newS
return (el, a)
placeRawElement = lift . placeRawElement
wrapRawElement e cfg = lift $ wrapRawElement e $ cfg
{ _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
}
-- * Convenience functions
--TODO: Move/replace
@ -521,7 +511,6 @@ instance Reflex t => HasDomEvent t (TextAreaElement EventResult d t) en where
instance DomBuilder t m => DomBuilder t (ReaderT r m) where
type DomBuilderSpace (ReaderT r m) = DomBuilderSpace m
placeholder = liftPlaceholder
type LiftDomBuilder t f m =
( Reflex t
@ -555,8 +544,10 @@ liftTextNode = lift . textNode
liftElement :: LiftDomBuilder t f m => Text -> ElementConfig er t (f m) -> f m a -> f m (Element er (DomBuilderSpace m) t, a)
liftElement elementTag cfg child = liftWithStateless $ \run -> element elementTag (fmap1 run cfg) $ run child
{-
liftPlaceholder :: LiftDomBuilder t f m => PlaceholderConfig above t (f m) -> f m (Placeholder above t)
liftPlaceholder cfg = liftWithStateless $ \run -> placeholder $ fmap1 run cfg
-}
liftInputElement :: LiftDomBuilder t f m => InputElementConfig er t (f m) -> f m (InputElement er (DomBuilderSpace m) t)
liftInputElement cfg = liftWithStateless $ \run -> inputElement $ fmap1 run cfg

View File

@ -108,7 +108,7 @@ newtype EventResult en = EventResult { unEventResult :: EventResultType en }
type family EventResultType (en :: EventTag) :: * where
EventResultType 'ClickTag = ()
EventResultType 'DblclickTag = ()
EventResultType 'DblclickTag = (Int, Int)
EventResultType 'KeypressTag = Int
EventResultType 'KeydownTag = Int
EventResultType 'KeyupTag = Int

View File

@ -9,6 +9,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
@ -27,6 +28,7 @@ import Reflex.Host.Class
import Control.Concurrent.Chan
import Control.Lens hiding (element)
import Control.Monad.Exception
import Control.Monad.Primitive
import Control.Monad.Reader
import Control.Monad.Ref
import Control.Monad.Trans.Control
@ -37,8 +39,12 @@ import qualified Data.Dependent.Map as DMap
import Data.Dependent.Sum
import Data.Functor.Misc
import Data.IORef
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Monoid
import Data.Some (Some)
import qualified Data.Some as Some
import Data.Text (Text)
import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode)
import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
@ -51,8 +57,9 @@ import qualified GHCJS.DOM.HTMLInputElement as Input
import qualified GHCJS.DOM.HTMLSelectElement as Select
import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
import GHCJS.DOM.MouseEvent
import GHCJS.DOM.Node (appendChild, getOwnerDocument, getParentNode, getPreviousSibling, insertBefore,
import GHCJS.DOM.Node (appendChild, getOwnerDocument, getParentNode, getPreviousSibling,
removeChild, setNodeValue, toNode)
import qualified GHCJS.DOM.Node as DOM
import GHCJS.DOM.Types (FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node, ToDOMString, TouchEvent,
WheelEvent, castToHTMLInputElement, castToHTMLSelectElement,
castToHTMLTextAreaElement)
@ -74,6 +81,10 @@ data ImmediateDomBuilderEnv t
newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) m a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
instance PrimMonad m => PrimMonad (ImmediateDomBuilderT x m) where
type PrimState (ImmediateDomBuilderT x m) = PrimState m
primitive = lift . primitive
instance MonadTransControl (ImmediateDomBuilderT t) where
type StT (ImmediateDomBuilderT t) a = StT (ReaderT (ImmediateDomBuilderEnv t)) a
liftWith = defaultLiftWith ImmediateDomBuilderT unImmediateDomBuilderT
@ -121,15 +132,42 @@ deleteBetweenInclusive s e = do
case mCurrentParent of
Nothing -> return () --TODO: Is this the right behavior?
Just currentParent -> do
let go = do
Just x <- getPreviousSibling e -- This can't be Nothing because we should hit 's' first
_ <- removeChild currentParent $ Just x
when (toNode s /= toNode x) go
go
deleteUpToGivenParent currentParent s e
_ <- removeChild currentParent $ Just e
return ()
type SupportsImmediateDomBuilder t m = (Reflex t, MonadIO m, MonadHold t m, MonadFix m, PerformEvent t m, Performable m ~ m, MonadReflexCreateTrigger t m, Deletable t m, MonadRef m, Ref m ~ Ref IO)
-- | s and e must both be children of the same node and s must precede e
deleteBetweenExclusive :: (MonadIO m, IsNode start, IsNode end) => start -> end -> m ()
deleteBetweenExclusive s e = do
mCurrentParent <- getParentNode e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment
case mCurrentParent of
Nothing -> return () --TODO: Is this the right behavior?
Just currentParent -> do
let go = do
Just x <- getPreviousSibling e -- This can't be Nothing because we should hit 's' first
when (toNode s /= toNode x) $ do
_ <- removeChild currentParent $ Just x
go
go
-- | s and e must both be children of the same node and s must precede e; s and all nodes between s and e will be removed, but e will not be removed
{-# INLINABLE deleteUpTo #-}
deleteUpTo :: (MonadIO m, IsNode start, IsNode end) => start -> end -> m ()
deleteUpTo s e = do
mCurrentParent <- getParentNode e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment
case mCurrentParent of
Nothing -> return () --TODO: Is this the right behavior?
Just currentParent -> deleteUpToGivenParent currentParent s e
{-# INLINABLE deleteUpToGivenParent #-}
deleteUpToGivenParent :: (MonadIO m, IsNode parent, IsNode start, IsNode end) => parent -> start -> end -> m ()
deleteUpToGivenParent currentParent s e = do
fix $ \loop -> do
Just x <- getPreviousSibling e -- This can't be Nothing because we should hit 's' first
_ <- removeChild currentParent $ Just x
when (toNode s /= toNode x) loop
type SupportsImmediateDomBuilder t m = (Reflex t, MonadIO m, MonadIO (Performable m), MonadHold t m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref IO, MonadAdjust t m)
newtype EventFilterTriggerRef t er (en :: EventTag) = EventFilterTriggerRef (IORef (Maybe (EventTrigger t (er en))))
@ -248,6 +286,7 @@ instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t
return $ TextNode n
{-# INLINABLE element #-}
element elementTag cfg child = fst <$> makeElement elementTag cfg child
{-
{-# INLINABLE placeholder #-}
placeholder (PlaceholderConfig toInsertAbove delete) = liftThrough (deletable delete) $ do
n <- textNodeInternal ("" :: Text)
@ -257,6 +296,7 @@ instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t
mp <- getParentNode n
forM_ mp $ \p -> removeChild p $ Just n
return $ Placeholder insertedAbove deleted
-}
{-# INLINABLE inputElement #-}
inputElement cfg = do
((e, _), domElement) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
@ -354,6 +394,57 @@ instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t
placeRawElement = append
wrapRawElement = wrap
instance (Reflex t, MonadAdjust t m, MonadIO m, MonadHold t m, PerformEvent t m, MonadIO (Performable m)) => MonadAdjust t (ImmediateDomBuilderT t m) where
runWithReplace a0 a' = do
initialEnv <- ImmediateDomBuilderT ask
before <- textNodeInternal ("" :: Text)
-- We draw 'after' in this roundabout way to avoid using MonadFix
Just after <- createTextNode (_immediateDomBuilderEnv_document initialEnv) ("" :: Text)
let drawInitialChild = do
result <- a0
append after
return result
(result0, result') <- lift $ runWithReplace (runImmediateDomBuilderT drawInitialChild initialEnv) $ ffor a' $ \child -> do
Just df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
result <- runImmediateDomBuilderT child $ initialEnv
{ _immediateDomBuilderEnv_parent = toNode df
}
deleteBetweenExclusive before after
insertBefore df after
return result
return (result0, result')
sequenceDMapWithAdjust (dm0 :: DMap k (ImmediateDomBuilderT t m)) dm' = do
initialEnv <- ImmediateDomBuilderT ask
let drawChildInitial :: ImmediateDomBuilderT t m a -> m (DOM.DocumentFragment, DOM.Text, a)
drawChildInitial child = runImmediateDomBuilderT ((,,) <$> pure (error "sequenceDMapWithAdjust{ImmediateDomBuilderT}: drawChildInitial: DocumentFragment evaluated") <*> textNodeInternal ("" :: Text) <*> child) initialEnv --TODO: Don't return a DocumentFragment at all here; this will require that sequenceDMapWithAdjust accept differently-typed values for the constant and Event arguments.
drawChildUpdate :: ImmediateDomBuilderT t m a -> m (DOM.DocumentFragment, DOM.Text, a)
drawChildUpdate child = do
Just df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv
runImmediateDomBuilderT ((,,) <$> pure df <*> textNodeInternal ("" :: Text) <*> child) $ initialEnv
{ _immediateDomBuilderEnv_parent = toNode df
}
updateChildren = ffor dm' $ \(PatchDMap p) -> PatchDMap $
mapKeyValuePairsMonotonic (\(k :=> ComposeMaybe mv) -> WrapArg k :=> ComposeMaybe (fmap drawChildUpdate mv)) p
(children0, children') <- lift $ sequenceDMapWithAdjust (mapKeyValuePairsMonotonic (\(k :=> v) -> WrapArg k :=> drawChildInitial v) dm0) (updateChildren :: Event t (PatchDMap (WrapArg ((,,) DOM.DocumentFragment DOM.Text) k) m)) --TODO: Update stuff
let result0 = mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (_, _, v)) -> k :=> Identity v) children0
placeholders0 = dmapToMap $ mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (_, ph, _)) -> Const2 (Some.This k) :=> Identity ph) children0
result' = ffor children' $ \(PatchDMap p) -> PatchDMap $
mapKeyValuePairsMonotonic (\(WrapArg k :=> v) -> k :=> fmap (\(_, _, r) -> r) v) p
placeholders' = ffor children' $ \(PatchDMap p) -> PatchMap $
dmapToMap $ mapKeyValuePairsMonotonic (\(WrapArg k :=> v) -> Const2 (Some.This k) :=> Identity (fmap ((\(_, ph, _) -> ph) . runIdentity) (getComposeMaybe v))) p
placeholders :: Behavior t (Map (Some k) DOM.Text) <- current . incrementalToDynamic <$> holdIncremental placeholders0 placeholders'
lastPlaceholder <- textNodeInternal ("" :: Text)
performEvent_ $ flip push children' $ \(PatchDMap p) -> do
phs <- sample placeholders
if DMap.null p then return Nothing else return $ Just $ do
let f :: DSum (WrapArg ((,,) DOM.DocumentFragment DOM.Text) k) (ComposeMaybe Identity) -> Performable m ()
f (WrapArg k :=> ComposeMaybe mv) = do
let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs
forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
forM_ mv $ \(Identity (df, _, _)) -> df `insertBefore` nextPlaceholder
mapM_ f $ DMap.toList p
return (result0, result')
mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool)
mkHasFocus e = do
let initialFocus = False --TODO: Actually get the initial focus of the element
@ -363,7 +454,7 @@ mkHasFocus e = do
]
{-# INLINABLE insertImmediateAbove #-}
insertImmediateAbove :: (Reflex t, IsNode placeholder, PerformEvent t m, MonadIO (Performable m)) => placeholder -> Event t (ImmediateDomBuilderT t (Performable m) a) -> ImmediateDomBuilderT t m (Event t a)
insertImmediateAbove :: (IsNode placeholder, PerformEvent t m, MonadIO (Performable m)) => placeholder -> Event t (ImmediateDomBuilderT t (Performable m) a) -> ImmediateDomBuilderT t m (Event t a)
insertImmediateAbove n toInsertAbove = do
events <- askEvents
lift $ performEvent $ ffor toInsertAbove $ \new -> do
@ -374,21 +465,15 @@ insertImmediateAbove n toInsertAbove = do
, _immediateDomBuilderEnv_document = doc
, _immediateDomBuilderEnv_events = events
}
mp <- getParentNode n
case mp of
Nothing -> trace "ImmediateDomBuilderT: placeholder: Warning: (getParentNode n) returned Nothing" $ return ()
Just p -> void $ insertBefore p (Just df) (Just n) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
df `insertBefore` n
return result
instance SupportsImmediateDomBuilder t m => Deletable t (ImmediateDomBuilderT t m) where
{-# INLINABLE deletable #-}
deletable delete child = liftThrough (deletable delete) $ do
top <- textNodeInternal ("" :: Text)
result <- child
bottom <- textNodeInternal ("" :: Text)
lift $ performEvent_ $ ffor delete $ \_ -> do
deleteBetweenInclusive top bottom
return result
insertBefore :: (MonadIO m, IsNode new, IsNode existing) => new -> existing -> m ()
insertBefore new existing = do
mp <- getParentNode existing
case mp of
Nothing -> trace "ImmediateDomBuilderT: placeholder: Warning: (getParentNode n) returned Nothing" $ return ()
Just p -> void $ DOM.insertBefore p (Just new) (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
instance PerformEvent t m => PerformEvent t (ImmediateDomBuilderT t m) where
type Performable (ImmediateDomBuilderT t m) = Performable m
@ -497,7 +582,7 @@ type family EventType en where
defaultDomEventHandler :: IsElement e => e -> EventName en -> EventM e (EventType en) (Maybe (EventResult en))
defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
Click -> return ()
Dblclick -> return ()
Dblclick -> getMouseEventCoords
Keypress -> getKeyEvent
Scroll -> getScrollTop e
Keydown -> getKeyEvent
@ -547,7 +632,7 @@ defaultDomEventHandler e evt = fmap (Just . EventResult) $ case evt of
defaultDomWindowEventHandler :: DOM.Window -> EventName en -> EventM DOM.Window (EventType en) (Maybe (EventResult en))
defaultDomWindowEventHandler w evt = fmap (Just . EventResult) $ case evt of
Click -> return ()
Dblclick -> return ()
Dblclick -> getMouseEventCoords
Keypress -> getKeyEvent
Scroll -> Window.getScrollY w
Keydown -> getKeyEvent
@ -796,7 +881,7 @@ getKeyEvent = do
getMouseEventCoords :: EventM e MouseEvent (Int, Int)
getMouseEventCoords = do
e <- event
bisequence (getX e, getY e)
bisequence (getClientX e, getClientY e)
instance MonadSample t m => MonadSample t (ImmediateDomBuilderT t m) where
{-# INLINABLE sample #-}

View File

@ -7,15 +7,15 @@
{-# LANGUAGE UndecidableInstances #-}
module Reflex.Dom.Builder.InputDisabled where
import Control.Lens
import Control.Monad.Fix
import Control.Monad.Primitive
import Control.Monad.Ref
import Control.Monad.Trans
import Control.Monad.Trans.Control
import Data.Coerce
import qualified Data.Map as Map
import Foreign.JavaScript.TH
import Reflex
import Reflex.Deletable.Class
import Reflex.Dom.Builder.Class
import Reflex.Host.Class
@ -47,15 +47,16 @@ instance PerformEvent t m => PerformEvent t (InputDisabledT m) where
performEvent_ = lift . performEvent_
performEvent = lift . performEvent
instance PrimMonad m => PrimMonad (InputDisabledT m) where
type PrimState (InputDisabledT m) = PrimState m
primitive = lift . primitive
disableElementConfig :: Reflex t => ElementConfig er t m -> ElementConfig er t m
disableElementConfig cfg = cfg
{ _elementConfig_initialAttributes = Map.insert "disabled" "disabled" $ _elementConfig_initialAttributes cfg
, _elementConfig_modifyAttributes = Map.delete "disabled" <$> _elementConfig_modifyAttributes cfg
}
instance Deletable t m => Deletable t (InputDisabledT m) where
deletable d = liftThrough $ deletable d
instance PostBuild t m => PostBuild t (InputDisabledT m) where
getPostBuild = lift getPostBuild
@ -65,10 +66,16 @@ instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (InputDisabl
newEventWithTrigger = lift . newEventWithTrigger
newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
instance MonadAdjust t m => MonadAdjust t (InputDisabledT m) where
runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
sequenceDMapWithAdjust dm0 dm' = InputDisabledT $ sequenceDMapWithAdjust (coerce dm0) (coerceEvent dm')
instance DomBuilder t m => DomBuilder t (InputDisabledT m) where
type DomBuilderSpace (InputDisabledT m) = DomBuilderSpace m
{-
placeholder cfg = lift $ placeholder $ cfg
& placeholderConfig_insertAbove %~ fmap runInputDisabledT
-}
inputElement cfg = lift $ inputElement $ cfg
{ _inputElementConfig_elementConfig = liftElementConfig $ disableElementConfig $ _inputElementConfig_elementConfig cfg
}

View File

@ -20,18 +20,24 @@ import Blaze.ByteString.Builder.Html.Utf8
import Control.Lens hiding (element)
import Control.Monad.Exception
import Control.Monad.Identity
import Control.Monad.Primitive
import Control.Monad.Ref
import Control.Monad.State.Strict
import Control.Monad.Trans.Control
import Data.ByteString (ByteString)
import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Lazy as BL
import Data.ByteString.Builder (Builder, byteString, toLazyByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Default
import Data.Dependent.Map (DMap)
import qualified Data.Dependent.Map as DMap
import Data.Dependent.Sum (DSum (..))
import Data.Functor.Constant
import Data.Functor.Misc
import qualified Data.Map as Map
import Data.Monoid
import qualified Data.Set as Set
import Data.Text.Encoding
import Data.Tuple
import GHC.Generics
import Reflex.Class
import Reflex.Dom.Builder.Class
@ -43,21 +49,24 @@ import Reflex.PerformEvent.Class
import Reflex.PostBuild.Class
import Reflex.Spider
newtype StaticDomBuilderT t m a = StaticDomBuilderT
{ unStaticDomBuilderT :: StateT [Behavior t ByteString] m a -- Accumulated Html will be in revesed order
{ unStaticDomBuilderT :: StateT [Behavior t Builder] m a -- Accumulated Html will be in reversed order
}
deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
instance PrimMonad m => PrimMonad (StaticDomBuilderT x m) where
type PrimState (StaticDomBuilderT x m) = PrimState m
primitive = lift . primitive
instance MonadTransControl (StaticDomBuilderT t) where
type StT (StaticDomBuilderT t) a = StT (StateT [Behavior t ByteString]) a
type StT (StaticDomBuilderT t) a = StT (StateT [Behavior t Builder]) a
liftWith = defaultLiftWith StaticDomBuilderT unStaticDomBuilderT
restoreT = defaultRestoreT StaticDomBuilderT
instance MonadTrans (StaticDomBuilderT t) where
lift = StaticDomBuilderT . lift
runStaticDomBuilderT :: (Monad m, Reflex t) => StaticDomBuilderT t m a -> m (a, Behavior t ByteString)
runStaticDomBuilderT :: (Monad m, Reflex t) => StaticDomBuilderT t m a -> m (a, Behavior t Builder)
runStaticDomBuilderT (StaticDomBuilderT a) = do
(result, a') <- runStateT a []
return (result, mconcat $ reverse a')
@ -87,15 +96,6 @@ instance MonadHold t m => MonadHold t (StaticDomBuilderT t m) where
{-# INLINABLE holdIncremental #-}
holdIncremental v0 v' = lift $ holdIncremental v0 v'
instance (Reflex t, Monad m, MonadHold t (StateT [Behavior t ByteString] m)) => Deletable t (StaticDomBuilderT t m) where
{-# INLINABLE deletable #-}
deletable delete (StaticDomBuilderT a) = StaticDomBuilderT $ do
(result, a') <- lift $ runStateT a []
let html = mconcat $ reverse a'
b <- hold html (mempty <$ delete)
modify (join b:)
return result
instance (Monad m, Ref m ~ Ref IO, Reflex t) => TriggerEvent t (StaticDomBuilderT t m) where
{-# INLINABLE newTriggerEvent #-}
newTriggerEvent = return (never, const $ return ())
@ -113,7 +113,7 @@ instance MonadRef m => MonadRef (StaticDomBuilderT t m) where
instance MonadAtomicRef m => MonadAtomicRef (StaticDomBuilderT t m) where
atomicModifyRef r = lift . atomicModifyRef r
type SupportsStaticDomBuilder t m = (Reflex t, MonadIO m, MonadHold t m, MonadFix m, PerformEvent t m, Performable m ~ m, MonadReflexCreateTrigger t m, Deletable t m, MonadRef m, Ref m ~ Ref IO)
type SupportsStaticDomBuilder t m = (Reflex t, MonadIO m, MonadHold t m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref IO, MonadAdjust t m)
data StaticDomSpace
@ -137,19 +137,45 @@ instance DomSpace StaticDomSpace where
type RawSelectElement StaticDomSpace = ()
addEventSpecFlags _ _ _ _ = StaticEventSpec
instance (Reflex t, MonadAdjust t m, MonadHold t m) => MonadAdjust t (StaticDomBuilderT t m) where
runWithReplace a0 a' = do
(result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0) (runStaticDomBuilderT <$> a')
o <- hold (snd result0) $ snd <$> result'
StaticDomBuilderT $ modify $ (:) $ join o
return (fst result0, fst <$> result')
sequenceDMapWithAdjust (dm0 :: DMap k (StaticDomBuilderT t m)) dm' = do
let loweredDm0 = mapKeyValuePairsMonotonic (\(k :=> v) -> WrapArg k :=> fmap swap (runStaticDomBuilderT v)) dm0
loweredDm' = ffor dm' $ \(PatchDMap p) -> PatchDMap $
mapKeyValuePairsMonotonic (\(k :=> ComposeMaybe mv) -> WrapArg k :=> ComposeMaybe (fmap (fmap swap . runStaticDomBuilderT) mv)) p
(children0, children') <- lift $ sequenceDMapWithAdjust loweredDm0 loweredDm'
let result0 = mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (_, v)) -> k :=> Identity v) children0
result' = ffor children' $ \(PatchDMap p) -> PatchDMap $
mapKeyValuePairsMonotonic (\(WrapArg k :=> mv) -> k :=> fmap snd mv) p
outputs0 :: DMap k (Constant (Behavior t Builder))
outputs0 = mapKeyValuePairsMonotonic (\(WrapArg k :=> Identity (o, _)) -> k :=> Constant o) children0
outputs' :: Event t (PatchDMap k (Constant (Behavior t Builder)))
outputs' = ffor children' $ \(PatchDMap p) -> PatchDMap $
mapKeyValuePairsMonotonic (\(WrapArg k :=> ComposeMaybe mv) -> k :=> ComposeMaybe (fmap (Constant . fst . runIdentity) mv)) p
outputs <- holdIncremental outputs0 outputs'
StaticDomBuilderT $ modify $ (:) $ pull $ do
os <- sample $ currentIncremental outputs
fmap mconcat $ forM (DMap.toList os) $ \(_ :=> Constant o) -> do
sample o
return (result0, result')
instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) where
type DomBuilderSpace (StaticDomBuilderT t m) = StaticDomSpace
{-# INLINABLE textNode #-}
textNode (TextNodeConfig initialContents setContents) = StaticDomBuilderT $ do
--TODO: Do not escape quotation marks; see https://stackoverflow.com/questions/25612166/what-characters-must-be-escaped-in-html-5
let escape = BL.toStrict . toLazyByteString . fromHtmlEscapedText
let escape = fromHtmlEscapedText
modify . (:) <=< hold (escape initialContents) $ fmap escape setContents
return $ TextNode ()
{-# INLINABLE element #-}
element elementTag cfg child = do
-- https://www.w3.org/TR/html-markup/syntax.html#syntax-elements
let voidElements = Set.fromList ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"]
let toAttr (AttributeName _mns k) v = encodeUtf8 k <> "=\"" <> BL.toStrict (toLazyByteString $ fromHtmlEscapedText v) <> "\""
let toAttr (AttributeName _mns k) v = byteString (encodeUtf8 k) <> byteString "=\"" <> fromHtmlEscapedText v <> byteString "\""
es <- newFanEventWithTrigger $ \_ _ -> return (return ())
StaticDomBuilderT $ do
(result, innerHtml) <- lift $ runStaticDomBuilderT child
@ -157,18 +183,12 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
let attrs1 = ffor (current attrs0) $ mconcat . fmap (\(k, v) -> " " <> toAttr k v) . Map.toList
let tagBS = encodeUtf8 elementTag
if Set.member elementTag voidElements
then modify $ (:) $ mconcat [constant ("<" <> tagBS), attrs1, constant " />"]
then modify $ (:) $ mconcat [constant ("<" <> byteString tagBS), attrs1, constant (byteString " />")]
else do
let open = mconcat [constant ("<" <> tagBS <> " "), attrs1, constant ">"]
let close = constant $ "</" <> tagBS <> ">"
let open = mconcat [constant ("<" <> byteString tagBS <> " "), attrs1, constant (byteString ">")]
let close = constant $ byteString $ "</" <> tagBS <> ">"
modify $ (:) $ mconcat [open, innerHtml, close]
return (Element es (), result)
{-# INLINABLE placeholder #-}
placeholder (PlaceholderConfig toInsertAbove _delete) = StaticDomBuilderT $ do
result <- lift $ performEvent (fmap runStaticDomBuilderT toInsertAbove)
acc <- foldDyn (:) [] (fmap snd result)
modify $ (:) $ join $ mconcat . reverse <$> current acc
return $ Placeholder (fmap fst result) never
{-# INLINABLE inputElement #-}
inputElement cfg = do
(e, _result) <- element "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
@ -215,6 +235,7 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
--TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
type StaticWidget x = PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global)))
{-# INLINE renderStatic #-}
renderStatic :: StaticWidget x a -> IO (a, ByteString)
renderStatic w = do
runSpiderHost $ do
@ -223,4 +244,4 @@ renderStatic w = do
mPostBuildTrigger <- readRef postBuildTriggerRef
forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
bs' <- sample bs
return (res, bs')
return (res, LBS.toStrict $ toLazyByteString bs')

View File

@ -15,6 +15,7 @@ module Reflex.Dom.Internal where
import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
import qualified Reflex as R
import Reflex.Dom.Builder.Immediate
import Reflex.Dom.Class
import Reflex.Dom.Internal.Foreign
@ -74,23 +75,49 @@ type Widget x = PostBuildT Spider (ImmediateDomBuilderT Spider (WithWebView x (P
attachWidget :: DOM.IsElement e => e -> WebViewSingleton x -> Widget x a -> IO a
attachWidget rootElement wv w = fst <$> attachWidget' rootElement wv w
{-# INLINABLE attachWidget' #-}
attachWidget' :: DOM.IsElement e => e -> WebViewSingleton x -> Widget x a -> IO (a, FireCommand Spider (SpiderHost Global))
attachWidget' rootElement wv w = do
mainWidgetWithHead' :: (forall x. (a -> Widget x b, b -> Widget x a)) -> IO ()
mainWidgetWithHead' widgets = runWebGUI $ \webView -> withWebViewSingleton webView $ \wv -> fmap fst $ attachWidget'' $ \events -> do
let (headWidget, bodyWidget) = widgets
Just doc <- liftIO $ fmap DOM.castToHTMLDocument <$> webViewGetDomDocument webView
Just headElement <- getHead doc
Just bodyElement <- getBody doc
(postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
rec b <- unsafeReplaceElementContentsWithWidget events postBuild headElement wv $ headWidget a
a <- unsafeReplaceElementContentsWithWidget events postBuild bodyElement wv $ bodyWidget b
return ((), postBuildTriggerRef)
unsafeReplaceElementContentsWithWidget :: DOM.IsElement e => EventChannel -> R.Event Spider () -> e -> WebViewSingleton x -> Widget x a -> PerformEventT Spider (SpiderHost Global) a
unsafeReplaceElementContentsWithWidget events postBuild rootElement wv w = do
Just doc <- getOwnerDocument rootElement
Just df <- createDocumentFragment doc
let builderEnv = ImmediateDomBuilderEnv
{ _immediateDomBuilderEnv_document = doc
, _immediateDomBuilderEnv_parent = toNode df
, _immediateDomBuilderEnv_events = events
}
result <- runWithWebView (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv) wv
setInnerHTML rootElement $ Just ("" :: String)
_ <- appendChild rootElement $ Just df
return result
{-# INLINABLE attachWidget' #-}
attachWidget' :: DOM.IsElement e => e -> WebViewSingleton x -> Widget x a -> IO (a, FireCommand Spider (SpiderHost Global))
attachWidget' rootElement wv w = attachWidget'' $ \events -> do
(postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
result <- unsafeReplaceElementContentsWithWidget events postBuild rootElement wv w
return (result, postBuildTriggerRef)
type EventChannel = Chan [DSum (TriggerRef Spider) TriggerInvocation]
{-# INLINABLE attachWidget'' #-}
attachWidget'' :: (EventChannel -> PerformEventT Spider (SpiderHost Global) (a, IORef (Maybe (EventTrigger Spider ())))) -> IO (a, FireCommand Spider (SpiderHost Global))
attachWidget'' w = do
events <- newChan
(result, fc@(FireCommand fire)) <- runSpiderHost $ do
(postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
let builderEnv = ImmediateDomBuilderEnv
{ _immediateDomBuilderEnv_document = doc
, _immediateDomBuilderEnv_parent = toNode df
, _immediateDomBuilderEnv_events = events
}
results@(_, FireCommand fire) <- hostPerformEventT $ runWithWebView (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv) wv
((result, postBuildTriggerRef), fc@(FireCommand fire)) <- hostPerformEventT $ w events
mPostBuildTrigger <- readRef postBuildTriggerRef
forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
return results
return (result, fc)
void $ forkIO $ forever $ do
ers <- readChan events
_ <- postGUISync $ runSpiderHost $ do
@ -100,8 +127,6 @@ attachWidget' rootElement wv w = do
_ <- fire (catMaybes mes) $ return ()
liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
return ()
setInnerHTML rootElement $ Just (""::String)
_ <- appendChild rootElement $ Just df
return (result, fc)
-- | Run a reflex-dom application inside of an existing DOM element with the given ID

View File

@ -24,7 +24,6 @@ module Reflex.Dom.Old
, buildElementNS
, buildEmptyElement
, buildEmptyElementNS
, deleteBetweenExclusive
, elDynHtml'
, elDynHtmlAttr'
, elStopPropagationNS
@ -46,7 +45,6 @@ module Reflex.Dom.Old
import Control.Arrow (first)
import Control.Lens (makeLenses, (%~), (&), (.~), (^.))
import Control.Monad
import Control.Monad.Exception
import Control.Monad.Fix
import Control.Monad.IO.Class
import Control.Monad.Reader
@ -64,7 +62,7 @@ import Foreign.JavaScript.TH
import qualified GHCJS.DOM.Element as Element
import GHCJS.DOM.EventM (EventM)
import GHCJS.DOM.NamedNodeMap as NNM
import GHCJS.DOM.Node (getFirstChild, getNodeName, getParentNode, getPreviousSibling, removeChild, toNode)
import GHCJS.DOM.Node (getFirstChild, getNodeName, removeChild)
import GHCJS.DOM.Types (IsElement, IsNode)
import qualified GHCJS.DOM.Types as DOM
import Reflex.Class
@ -104,8 +102,6 @@ type MonadWidgetConstraints t m =
, TriggerEvent t m
, HasWebView m
, HasWebView (Performable m)
, MonadAsyncException m
, MonadAsyncException (Performable m)
, MonadRef m
, Ref m ~ Ref IO
, MonadRef (Performable m)
@ -162,20 +158,6 @@ addDynamicAttributes attrs cfg = do
buildElementCommon :: MonadWidget t m => Text -> m a -> ElementConfig er t m -> m (Element er (DomBuilderSpace m) t, a)
buildElementCommon elementTag child cfg = element elementTag cfg child
-- | s and e must both be children of the same node and s must precede e
deleteBetweenExclusive :: (IsNode start, IsNode end) => start -> end -> IO ()
deleteBetweenExclusive s e = do
mCurrentParent <- getParentNode e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment
case mCurrentParent of
Nothing -> return () --TODO: Is this the right behavior?
Just currentParent -> do
let go = do
Just x <- getPreviousSibling e -- This can't be Nothing because we should hit 's' first
when (toNode s /= toNode x) $ do
_ <- removeChild currentParent $ Just x
go
go
onEventName :: IsElement e => EventName en -> e -> EventM e (EventType en) () -> IO (IO ())
onEventName = elementOnEventName

View File

@ -51,17 +51,19 @@ data WebSocketConfig t a
instance Reflex t => Default (WebSocketConfig t a) where
def = WebSocketConfig never never True
data WebSocket t
= WebSocket { _webSocket_recv :: Event t ByteString
, _webSocket_open :: Event t ()
, _webSocket_error :: Event t () -- eror event does not carry any data and is always
-- followed by termination of the connection
-- for details see the close event
, _webSocket_close :: Event t ( Bool -- wasClean
, Word -- code
, Text -- reason
)
}
type WebSocket t = RawWebSocket t ByteString
data RawWebSocket t a
= RawWebSocket { _webSocket_recv :: Event t a
, _webSocket_open :: Event t ()
, _webSocket_error :: Event t () -- eror event does not carry any data and is always
-- followed by termination of the connection
-- for details see the close event
, _webSocket_close :: Event t ( Bool -- wasClean
, Word -- code
, Text -- reason
)
}
-- This can be used to send either binary or text messages for the same websocket connection
instance (IsWebSocketMessage a, IsWebSocketMessage b) => IsWebSocketMessage (Either a b) where
@ -70,7 +72,10 @@ instance (IsWebSocketMessage a, IsWebSocketMessage b) => IsWebSocketMessage (Eit
webSocket :: (MonadIO m, MonadIO (Performable m), HasWebView m, PerformEvent t m, TriggerEvent t m, PostBuild t m, IsWebSocketMessage a) => Text -> WebSocketConfig t a -> m (WebSocket t)
webSocket url config = do
webSocket url config = webSocket' url config onBSMessage
webSocket' :: (MonadIO m, MonadIO (Performable m), HasWebView m, PerformEvent t m, TriggerEvent t m, PostBuild t m, IsWebSocketMessage a) => Text -> WebSocketConfig t a -> (Either ByteString JSVal -> b) -> m (RawWebSocket t b)
webSocket' url config onRawMessage = do
wv <- fmap unWebViewSingleton askWebView
(eRecv, onMessage) <- newTriggerEvent
currentSocketRef <- liftIO $ newIORef Nothing
@ -91,7 +96,7 @@ webSocket url config = do
liftIO $ threadDelay 1000000
start
start = do
ws <- liftIO $ newWebSocket wv url onMessage onOpen onError onClose
ws <- liftIO $ newWebSocket wv url (onMessage . onRawMessage) onOpen onError onClose
liftIO $ writeIORef currentSocketRef $ Just ws
return ()
performEvent_ . (liftIO start <$) =<< getPostBuild
@ -116,6 +121,6 @@ webSocket url config = do
`catch`
(\(_ :: SomeException) -> return False))
unless success $ atomically $ unGetTQueue payloadQueue payload
return $ WebSocket eRecv eOpen eError eClose
return $ RawWebSocket eRecv eOpen eError eClose
makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig

View File

@ -97,7 +97,6 @@ import Data.Functor.Misc
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Semigroup
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
@ -106,11 +105,8 @@ import Data.These
import Data.Traversable
import Prelude hiding (mapM, mapM_, sequence, sequence_)
widgetHoldInternal :: DomBuilder t m => m a -> Event t (m b) -> m (a, Event t b)
widgetHoldInternal child0 child' = do
childResult0 <- deletable (void child') child0
childResult' <- placeholder $ def & placeholderConfig_insertAbove .~ fmap (deletable (void child')) child'
return (childResult0, _placeholder_insertedAbove childResult')
widgetHoldInternal :: forall t m a b. DomBuilder t m => m a -> Event t (m b) -> m (a, Event t b)
widgetHoldInternal = runWithReplace
-- | Breaks the given Map into pieces based on the given Set. Each piece will contain only keys that are less than the key of the piece, and greater than or equal to the key of the piece with the next-smaller key. There will be one additional piece containing all keys from the original Map that are larger or equal to the largest key in the Set.
-- Either k () is used instead of Maybe k so that the resulting map of pieces is sorted so that the additional piece has the largest key.
@ -131,26 +127,12 @@ partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
newtype ChildResult t k a = ChildResult { unChildResult :: (a, Event t (Map k (Maybe (ChildResult t k a)))) }
listHoldWithKey :: forall t m k v a. (Ord k, DomBuilder t m, MonadHold t m, MonadFix m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> m a) -> m (Dynamic t (Map k a))
listHoldWithKey initialChildren modifyChildren buildChild = do
let deleteChildSelector = fanMap $ fmap void modifyChildren
liveChildren :: Dynamic t (Set k) <- foldDyn applyMapKeysSet (Map.keysSet initialChildren) modifyChildren
let placeChildSelector = fanMap $ attachWith partitionMapBySetLT (current liveChildren) $ fmap (Map.mapMaybe id) modifyChildren
buildAugmentedChild :: k -> v -> m (ChildResult t k a)
buildAugmentedChild k v = do
let delete = select deleteChildSelector $ Const2 k
myCfg = def
& insertAbove .~ fmap (imapM buildAugmentedChild) (select placeChildSelector $ Const2 $ Left k)
& deleteSelf .~ delete
ph <- placeholder myCfg
result <- deletable delete $ buildChild k v
return $ ChildResult (result, (fmap Just <$> _placeholder_insertedAbove ph) <> (Map.singleton k Nothing <$ _placeholder_deletedSelf ph)) --Note: we could also use the "deleted" output on deletable, if it had one; we're using this so that everything changes all at once, instead of deletions being prompt and insertions being delayed
rec initialAugmentedResults <- iforM initialChildren buildAugmentedChild
augmentedResults <- foldDyn applyMap initialAugmentedResults $ newInsertedBelow <> newInsertedAbove
let newInsertedAbove = switch $ mconcat . reverse . fmap (snd . unChildResult) . Map.elems <$> current augmentedResults
belowAll <- placeholder $ def & placeholderConfig_insertAbove .~ fmap (imapM buildAugmentedChild) (select placeChildSelector $ Const2 $ Right ())
let newInsertedBelow = fmap Just <$> _placeholder_insertedAbove belowAll
return $ fmap (fmap (fst . unChildResult)) augmentedResults
listHoldWithKey :: forall t m k v a. (Ord k, DomBuilder t m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> m a) -> m (Dynamic t (Map k a))
listHoldWithKey m0 m' f = do
let dm0 = mapWithFunctorToDMap $ Map.mapWithKey f m0
dm' = fmap (PatchDMap . mapWithFunctorToDMap . Map.mapWithKey (\k v -> ComposeMaybe $ fmap (f k) v)) m'
(a0, a') <- sequenceDMapWithAdjust dm0 dm'
fmap dmapToMap . incrementalToDynamic <$> holdIncremental a0 a' --TODO: Move the dmapToMap to the righthand side so it doesn't get fully redone every time
text :: DomBuilder t m => Text -> m ()
text t = void $ textNode $ def & textNodeConfig_initialContents .~ t