From 239141152bce5f863cbec9628196f8d1854fb336 Mon Sep 17 00:00:00 2001 From: Dave Laing Date: Tue, 15 Aug 2017 17:03:09 +1000 Subject: [PATCH 01/11] Moves the Time module to `reflex` --- reflex-dom-core/reflex-dom-core.cabal | 1 - reflex-dom-core/src/Reflex/Dom/Core.hs | 1 - reflex-dom-core/src/Reflex/Dom/Time.hs | 218 ------------------ .../src/Reflex/Dom/Widget/Resize.hs | 2 +- reflex-dom/reflex-dom.cabal | 1 - 5 files changed, 1 insertion(+), 222 deletions(-) delete mode 100644 reflex-dom-core/src/Reflex/Dom/Time.hs diff --git a/reflex-dom-core/reflex-dom-core.cabal b/reflex-dom-core/reflex-dom-core.cabal index 0c75bda7..c0f82c6f 100644 --- a/reflex-dom-core/reflex-dom-core.cabal +++ b/reflex-dom-core/reflex-dom-core.cabal @@ -98,7 +98,6 @@ library Reflex.Dom.Modals.Class Reflex.Dom.Old Reflex.Dom.Prerender - Reflex.Dom.Time Reflex.Dom.WebSocket Reflex.Dom.Widget Reflex.Dom.Widget.Basic diff --git a/reflex-dom-core/src/Reflex/Dom/Core.hs b/reflex-dom-core/src/Reflex/Dom/Core.hs index fe4a371f..6d3976f1 100644 --- a/reflex-dom-core/src/Reflex/Dom/Core.hs +++ b/reflex-dom-core/src/Reflex/Dom/Core.hs @@ -12,7 +12,6 @@ import Reflex.Dom.Modals.Base as X import Reflex.Dom.Modals.Class as X import Reflex.Dom.Old as X import Reflex.Dom.Prerender as X -import Reflex.Dom.Time as X import Reflex.Dom.WebSocket as X import Reflex.Dom.Widget as X import Reflex.Dom.Xhr as X diff --git a/reflex-dom-core/src/Reflex/Dom/Time.hs b/reflex-dom-core/src/Reflex/Dom/Time.hs deleted file mode 100644 index e085fc6d..00000000 --- a/reflex-dom-core/src/Reflex/Dom/Time.hs +++ /dev/null @@ -1,218 +0,0 @@ -{-# LANGUAGE DeriveDataTypeable #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE RecursiveDo #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -module Reflex.Dom.Time where - -import Reflex.Class -import Reflex.Dynamic -import Reflex.PerformEvent.Class -import Reflex.PostBuild.Class -import Reflex.TriggerEvent.Class - -import Control.Concurrent -import qualified Control.Concurrent.Thread.Delay as Concurrent -import Control.Monad -import Control.Monad.Fix -import Control.Monad.IO.Class -import Data.Align -import Data.Fixed -import Data.Sequence (Seq, (|>)) -import qualified Data.Sequence as Seq -import Data.These -import Data.Time.Clock -import Data.Typeable -import System.Random - -data TickInfo - = TickInfo { _tickInfo_lastUTC :: UTCTime - -- ^ UTC time immediately after the last tick. - , _tickInfo_n :: Integer - -- ^ Number of time periods since t0 - , _tickInfo_alreadyElapsed :: NominalDiffTime - -- ^ Amount of time already elapsed in the current tick period. - } - deriving (Eq, Ord, Show, Typeable) - --- | Special case of tickLossyFrom that uses the post-build event to start the --- tick thread. -tickLossy :: (PostBuild t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m) => NominalDiffTime -> UTCTime -> m (Event t TickInfo) -tickLossy dt t0 = tickLossyFrom dt t0 =<< getPostBuild - --- | Send events over time with the given basis time and interval --- If the system starts running behind, occurrences will be dropped rather than buffered --- Each occurrence of the resulting event will contain the index of the current interval, with 0 representing the basis time -tickLossyFrom - :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m) - => NominalDiffTime - -> UTCTime - -> Event t a - -- ^ Event that starts a tick generation thread. Usually you want this to - -- be something like the result of getPostBuild that only fires once. But - -- there could be uses for starting multiple timer threads. - -> m (Event t TickInfo) -tickLossyFrom dt t0 e = tickLossyFrom' $ (dt, t0) <$ e - --- | Generalization of tickLossyFrom that takes dt and t0 in the event. -tickLossyFrom' - :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), MonadFix m) - => Event t (NominalDiffTime, UTCTime) - -- ^ Event that starts a tick generation thread. Usually you want this to - -- be something like the result of getPostBuild that only fires once. But - -- there could be uses for starting multiple timer threads. - -> m (Event t TickInfo) -tickLossyFrom' e = do - rec result <- performEventAsync $ callAtNextInterval <$> leftmost [e, snd <$> result] - return $ fst <$> result - where callAtNextInterval pair cb = void $ liftIO $ forkIO $ do - tick <- uncurry getCurrentTick pair - Concurrent.delay $ ceiling $ (fst pair - _tickInfo_alreadyElapsed tick) * 1000000 - cb (tick, pair) - -clockLossy :: (MonadIO m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m), PostBuild t m, MonadHold t m, MonadFix m) => NominalDiffTime -> UTCTime -> m (Dynamic t TickInfo) -clockLossy dt t0 = do - initial <- liftIO $ getCurrentTick dt t0 - e <- tickLossy dt t0 - holdDyn initial e - -getCurrentTick :: NominalDiffTime -> UTCTime -> IO TickInfo -getCurrentTick dt t0 = do - t <- getCurrentTime - let offset = t `diffUTCTime` t0 - (n, alreadyElapsed) = offset `divMod'` dt - return $ TickInfo t n alreadyElapsed - --- | Delay an Event's occurrences by a given amount in seconds. -delay :: (PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t a) -delay dt e = performEventAsync $ ffor e $ \a cb -> liftIO $ void $ forkIO $ do - Concurrent.delay $ ceiling $ dt * 1000000 - cb a - --- | Send events with Poisson timing with the given basis and rate --- Each occurence of the resulting event will contain the index of --- the current interval, with 0 representing the basis time -poissonLossyFrom - :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m) - => g - -> Double - -- ^ Poisson event rate (Hz) - -> UTCTime - -- ^ Baseline time for events - -> Event t a - -- ^ Event that starts a tick generation thread. Usually you want this to - -- be something like the result of getPostBuild that only fires once. But - -- there could be uses for starting multiple timer threads. - -- Start sending events in response to the event parameter. - -> m (Event t TickInfo) -poissonLossyFrom rnd rate = inhomogeneousPoissonFrom rnd (constant rate) rate - - --- | Send events with Poisson timing with the given basis and rate --- Each occurence of the resulting event will contain the index of --- the current interval, with 0 representing the basis time. --- Automatically begin sending events when the DOM is built -poissonLossy - :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, PostBuild t m) - => g - -> Double - -- ^ Poisson event rate (Hz) - -> UTCTime - -- ^ Baseline time for events - -> m (Event t TickInfo) -poissonLossy rnd rate t0 = poissonLossyFrom rnd rate t0 =<< getPostBuild - --- | Send events with inhomogeneous Poisson timing with the given basis --- and variable rate. Provide a maxRate that you expect to support. -inhomogeneousPoissonFrom - :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m) - => g - -> Behavior t Double - -> Double - -> UTCTime - -> Event t a - -> m (Event t TickInfo) -inhomogeneousPoissonFrom rnd rate maxRate t0 e = do - - -- Create a thread for producing homogeneous poisson events - -- along with random Doubles (usage of Double's explained below) - ticksWithRateRand <- performEventAsync $ - fmap callAtNextInterval e - - -- Filter homogeneous events according to associated - -- random values and the current rate parameter - return $ attachWithMaybe filterFun rate ticksWithRateRand - - where - - -- Inhomogeneous poisson processes are built from faster - -- homogeneous ones by randomly dropping events from the - -- fast process. For each fast homogeneous event, choose - -- a uniform random sample from (0, rMax). If the - -- inhomogeneous rate at this moment is greater than the - -- random sample, then keep this event, otherwise drop it - filterFun :: Double -> (TickInfo, Double) -> Maybe TickInfo - filterFun r (tInfo, p) - | r >= p = Just tInfo - | otherwise = Nothing - - callAtNextInterval _ cb = void $ liftIO $ forkIO $ go t0 rnd cb 0 - - go tTargetLast lastGen cb lastN = do - t <- getCurrentTime - - -- Generate random numbers for this poisson interval (u) - -- and sample-retention likelihood (p) - let (u, nextGen) = randomR (0,1) lastGen - (p :: Double, nextGen') = randomR (0,maxRate) nextGen - - -- Inter-event interval is drawn from exponential - -- distribution accourding to u - let dt = realToFrac $ (-1) * log u / maxRate :: NominalDiffTime - nEvents = lastN + 1 - alreadyElapsed = diffUTCTime t tTargetLast - tTarget = addUTCTime dt tTargetLast - thisDelay = realToFrac $ diffUTCTime tTarget t :: Double - Concurrent.delay $ ceiling $ thisDelay * 1000000 - _ <- cb (TickInfo t nEvents alreadyElapsed, p) - go tTarget nextGen' cb nEvents - --- | Send events with inhomogeneous Poisson timing with the given basis --- and variable rate. Provide a maxRate that you expect to support -inhomogeneousPoisson - :: (RandomGen g, MonadIO (Performable m), PerformEvent t m, TriggerEvent t m, PostBuild t m) - => g - -> Behavior t Double - -> Double - -> UTCTime - -> m (Event t TickInfo) -inhomogeneousPoisson rnd rate maxRate t0 = - inhomogeneousPoissonFrom rnd rate maxRate t0 =<< getPostBuild - --- | Block occurrences of an Event until the given number of seconds elapses without --- the Event firing, at which point the last occurrence of the Event will fire. -debounce :: (MonadFix m, MonadHold t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t a) -debounce dt e = do - n :: Dynamic t Integer <- count e - let tagged = attachPromptlyDynWith (,) n e - delayed <- delay dt tagged - return $ attachWithMaybe (\n' (t, v) -> if n' == t then Just v else Nothing) (current n) delayed - --- | When the given 'Event' occurs, wait the given amount of time and collect --- all occurrences during that time. Then, fire the output 'Event' with the --- collected output. -batchOccurrences :: (MonadFix m, MonadHold t m, PerformEvent t m, TriggerEvent t m, MonadIO (Performable m)) => NominalDiffTime -> Event t a -> m (Event t (Seq a)) -batchOccurrences t newValues = do - let f s x = (Just newState, out) - where newState = case x of - This a -> s |> a - That _ -> mempty - These a _ -> Seq.singleton a - out = case x of - This _ -> if Seq.null s then Just () else Nothing - That _ -> Nothing - These _ _ -> Just () - rec (buffer, toDelay) <- mapAccumMaybe f mempty $ align newValues delayed - delayed <- delay t toDelay - return $ tag buffer delayed diff --git a/reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs b/reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs index 6fa5095c..744579af 100644 --- a/reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs +++ b/reflex-dom-core/src/Reflex/Dom/Widget/Resize.hs @@ -7,10 +7,10 @@ module Reflex.Dom.Widget.Resize where import Reflex.Class +import Reflex.Time import Reflex.Dom.Builder.Class import Reflex.Dom.Builder.Immediate import Reflex.Dom.Class -import Reflex.Dom.Time import Reflex.Dom.Widget.Basic import Reflex.PerformEvent.Class import Reflex.PostBuild.Class diff --git a/reflex-dom/reflex-dom.cabal b/reflex-dom/reflex-dom.cabal index 5813e9c5..6e575f4d 100644 --- a/reflex-dom/reflex-dom.cabal +++ b/reflex-dom/reflex-dom.cabal @@ -71,7 +71,6 @@ library , Reflex.Dom.Location , Reflex.Dom.Main , Reflex.Dom.Old - , Reflex.Dom.Time , Reflex.Dom.WebSocket , Reflex.Dom.Widget , Reflex.Dom.Widget.Basic From d1a3fd3758b4d16c75c7c6531d60beee6b9724d2 Mon Sep 17 00:00:00 2001 From: 3noch Date: Tue, 29 Aug 2017 14:55:25 -0400 Subject: [PATCH 02/11] WIP: Fuller API for browser history --- reflex-dom-core/src/Reflex/Dom/Location.hs | 87 ++++++++++++++++++++-- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/reflex-dom-core/src/Reflex/Dom/Location.hs b/reflex-dom-core/src/Reflex/Dom/Location.hs index b3f1ef7b..c91a1082 100644 --- a/reflex-dom-core/src/Reflex/Dom/Location.hs +++ b/reflex-dom-core/src/Reflex/Dom/Location.hs @@ -1,13 +1,86 @@ -module Reflex.Dom.Location (getLocationHost, getLocationProtocol) where +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +module Reflex.Dom.Location + ( browserHistoryWith + , getLocationAfterHost + , getLocationFragment + , getLocationHost + , getLocationPath + , getLocationProtocol + , getLocationUrl + ) where + +import Reflex +import Reflex.Dom.Builder.Immediate (wrapDomEvent) + +import Control.Lens ((^.)) +import Control.Monad ((>=>)) import Data.Text (Text) -import GHCJS.DOM (currentWindowUnchecked) -import GHCJS.DOM.Location (getHost, getProtocol) -import GHCJS.DOM.Types (MonadJSM) -import GHCJS.DOM.Window (getLocation) +import qualified GHCJS.DOM as DOM +import qualified GHCJS.DOM.EventM as DOM +import qualified GHCJS.DOM.Location as Location +import GHCJS.DOM.Types (Location) +import qualified GHCJS.DOM.Types as DOM +import qualified GHCJS.DOM.Window as Window +import qualified GHCJS.DOM.WindowEventHandlers as DOM +import Language.Javascript.JSaddle (FromJSString, MonadJSM, ToJSString, fromJSValUnchecked, js1, toJSVal) + +withLocation :: (MonadJSM m) => (Location -> m a) -> m a +withLocation f = DOM.currentWindowUnchecked >>= Window.getLocation >>= f + +-- | Returns the full URI-decoded URL of the current window location. +getLocationUrl :: (MonadJSM m) => m Text +getLocationUrl = withLocation (Location.getHref >=> decodeURIText) + +-- | Returns the host of the current window location getLocationHost :: (MonadJSM m) => m Text -getLocationHost = currentWindowUnchecked >>= getLocation >>= getHost +getLocationHost = withLocation Location.getHost +-- | Returns the protocol/scheme (e.g. @http:@ or @https:@) of the current window location getLocationProtocol :: (MonadJSM m) => m Text -getLocationProtocol = currentWindowUnchecked >>= getLocation >>= getProtocol +getLocationProtocol = withLocation Location.getProtocol + +-- | Returns the URI-decoded location after the host and port; i.e. returns the path, query, and fragment of the location. +getLocationAfterHost :: (MonadJSM m) => m Text +getLocationAfterHost = withLocation $ \loc -> do + pathname <- Location.getPathname loc + search <- Location.getSearch loc + hash <- Location.getHash loc + decodeURI (mconcat [pathname, search, hash] :: Text) + +-- | Returns the URI-decoded path of the current window location. +getLocationPath :: (MonadJSM m) => m Text +getLocationPath = withLocation (Location.getPathname >=> decodeURIText) + +-- | Returns the URI-decoded fragment/hash of the current window location. +getLocationFragment :: (MonadJSM m) => m Text +getLocationFragment = withLocation (Location.getHash >=> decodeURIText) + + +-- | Decodes a URI with JavaScript's @decodeURI@ function. +-- +-- FIXME: @decodeURI@ will throw when URI is malformed +decodeURI :: (MonadJSM m, ToJSString a, FromJSString b) => a -> m b +decodeURI input = do + window <- DOM.currentWindowUnchecked + window' <- DOM.liftJSM $ toJSVal window + DOM.liftJSM $ window' ^. js1 ("decodeURI"::Text) input >>= fromJSValUnchecked + +decodeURIText :: (MonadJSM m) => Text -> m Text +decodeURIText = decodeURI + +-- | Builds a Dynamic carrying the current window location. +browserHistoryWith :: (MonadJSM m, TriggerEvent t m, MonadHold t m) + => (forall jsm. MonadJSM jsm => Location -> jsm a) + -- ^ A function to encode the window location in a more useful form (e.g. @getLocationAfterHost@). + -> m (Dynamic t a) +browserHistoryWith f = do + window <- DOM.currentWindowUnchecked + location <- Window.getLocation window + loc0 <- f location + locEv <- wrapDomEvent window (`DOM.on` DOM.popState) $ f location + holdDyn loc0 locEv From 55b79188d538ec4a89f68496d5399a6925cfefe2 Mon Sep 17 00:00:00 2001 From: Dave Laing Date: Wed, 4 Oct 2017 10:53:05 +1000 Subject: [PATCH 03/11] Some cleanups after moving the Time module to `reflex` --- reflex-dom-core/default.nix | 14 +++++++------- reflex-dom-core/reflex-dom-core.cabal | 4 +--- reflex-dom-core/test/gc.hs | 1 + 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/reflex-dom-core/default.nix b/reflex-dom-core/default.nix index 248375b1..3f8463b4 100644 --- a/reflex-dom-core/default.nix +++ b/reflex-dom-core/default.nix @@ -2,10 +2,10 @@ , bytestring, constraints, containers, contravariant, data-default , dependent-map, dependent-sum, dependent-sum-template, directory , exception-transformers, ghcjs-dom, hlint, jsaddle, keycode, lens -, monad-control, mtl, primitive, random, ref-tf, reflex, semigroups -, stdenv, stm, template-haskell, temporary, text, these, time -, transformers, unbounded-delays, unix, zenc, hashable -, chromium, process, jsaddle-warp, linux-namespaces, iproute +, monad-control, mtl, primitive, ref-tf, reflex, semigroups, stdenv +, stm, template-haskell, temporary, text, these, transformers +, unix, zenc, hashable, chromium, process, jsaddle-warp +, linux-namespaces, iproute }: let addGcTestDepends = drv: if stdenv.system != "x86_64-linux" then drv else drv // { testHaskellDepends = (drv.testHaskellDepends or []) ++ [ temporary jsaddle-warp process linux-namespaces ]; @@ -19,9 +19,9 @@ in mkDerivation (addGcTestDepends { aeson base bifunctors bimap blaze-builder bytestring constraints containers contravariant data-default dependent-map dependent-sum dependent-sum-template directory exception-transformers ghcjs-dom - jsaddle keycode lens monad-control mtl primitive random ref-tf - reflex semigroups stm template-haskell text these time transformers - unbounded-delays unix zenc + jsaddle keycode lens monad-control mtl primitive ref-tf + reflex semigroups stm template-haskell text these transformers + unix zenc ] ++ (if ghc.isGhcjs or false then [ hashable ] else []); diff --git a/reflex-dom-core/reflex-dom-core.cabal b/reflex-dom-core/reflex-dom-core.cabal index c0f82c6f..473881f1 100644 --- a/reflex-dom-core/reflex-dom-core.cabal +++ b/reflex-dom-core/reflex-dom-core.cabal @@ -59,16 +59,13 @@ library 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.*, text == 1.2.*, these >= 0.4 && < 0.8, - time >= 1.4 && < 1.8, transformers >= 0.3 && < 0.6, - unbounded-delays >= 0.1.0.9 && < 0.2, zenc == 0.1.* if impl(ghcjs) @@ -142,6 +139,7 @@ test-suite hlint test-suite gc build-depends: base + , reflex , reflex-dom-core , jsaddle , jsaddle-warp diff --git a/reflex-dom-core/test/gc.hs b/reflex-dom-core/test/gc.hs index 583339a5..8f5f8cc6 100644 --- a/reflex-dom-core/test/gc.hs +++ b/reflex-dom-core/test/gc.hs @@ -8,6 +8,7 @@ import Data.Int import GHC.Stats import Language.Javascript.JSaddle.Warp import Reflex.Dom.Core +import Reflex.Time import System.Exit import System.IO.Temp import System.Linux.Namespaces From 83a05cd1f865970fb67c04b45ac3fcb578493db1 Mon Sep 17 00:00:00 2001 From: "Robert J. Macomber" Date: Wed, 4 Oct 2017 19:27:45 -0700 Subject: [PATCH 04/11] Enable DOM storage in the Android WebView --- reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java | 1 + 1 file changed, 1 insertion(+) diff --git a/reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java b/reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java index 2a793207..8fa01c3b 100644 --- a/reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java +++ b/reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java @@ -26,6 +26,7 @@ private static Object startMainWidget(Activity a, String url, long jsaddleCallba ws.setJavaScriptEnabled(true); ws.setAllowFileAccessFromFileURLs(true); ws.setAllowUniversalAccessFromFileURLs(true); + ws.setDomStorageEnabled(true); wv.setWebContentsDebuggingEnabled(true); // allow video to play without user interaction wv.getSettings().setMediaPlaybackRequiresUserGesture(false); From ab47b38ebf0aa924278da88c780d8832f9c1d52f Mon Sep 17 00:00:00 2001 From: Ryan Trinkle Date: Sat, 7 Oct 2017 17:49:33 -0400 Subject: [PATCH 05/11] Change 'krausest' from a benchmark to an executable --- reflex-dom/reflex-dom.cabal | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/reflex-dom/reflex-dom.cabal b/reflex-dom/reflex-dom.cabal index 77d6a38e..0d43f0eb 100644 --- a/reflex-dom/reflex-dom.cabal +++ b/reflex-dom/reflex-dom.cabal @@ -135,13 +135,14 @@ executable benchmark if !impl(ghcjs) buildable: False -benchmark krausest +executable krausest build-depends: base, reflex, reflex-dom, text, prim-uniq, dependent-map, containers, transformers, mtl, ghcjs-dom, ghc-prim, random, dependent-sum, vector hs-source-dirs: benchmarks main-is: krausest.hs - ghc-options: -O2 -fspecialise-aggressively -DGHCJS_GC_INTERVAL=60000 + ghc-options: -O2 -fspecialise-aggressively if impl(ghcjs) ghc-options: -dedupe + cpp-options: -DGHCJS_BROWSER -DGHCJS_GC_INTERVAL=60000 type: exitcode-stdio-1.0 source-repository head From a08d82ca8f17a80c0e13d627b0de5f5304a80816 Mon Sep 17 00:00:00 2001 From: Ryan Trinkle Date: Mon, 9 Oct 2017 15:02:18 -0400 Subject: [PATCH 06/11] Only build krausest benchmark when building for ghcjs --- reflex-dom/reflex-dom.cabal | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex-dom/reflex-dom.cabal b/reflex-dom/reflex-dom.cabal index 0d43f0eb..bada592e 100644 --- a/reflex-dom/reflex-dom.cabal +++ b/reflex-dom/reflex-dom.cabal @@ -143,6 +143,8 @@ executable krausest if impl(ghcjs) ghc-options: -dedupe cpp-options: -DGHCJS_BROWSER -DGHCJS_GC_INTERVAL=60000 + else + buildable: False type: exitcode-stdio-1.0 source-repository head From 0dff2c368f3cfd588e5625544c5f11e2b998ab69 Mon Sep 17 00:00:00 2001 From: Ryan Trinkle Date: Mon, 16 Oct 2017 17:48:47 -0400 Subject: [PATCH 07/11] Fix 32-bit android --- reflex-dom/cbits/MainWidget.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/reflex-dom/cbits/MainWidget.c b/reflex-dom/cbits/MainWidget.c index 69b10ef2..9b9ff424 100644 --- a/reflex-dom/cbits/MainWidget.c +++ b/reflex-dom/cbits/MainWidget.c @@ -20,7 +20,7 @@ jobject Reflex_Dom_Android_MainWidget_start(jobject activity, const char *url, c jstring jurl = (*env)->NewStringUTF(env, url); assert(jurl); jstring initialJS = (*env)->NewStringUTF(env, jsaddleCallbacks->jsaddleJsData); - jobject result = (*env)->CallStaticObjectMethod(env, cls, startMainWidget, activity, jurl, (long)jsaddleCallbacks, initialJS); + jobject result = (*env)->CallStaticObjectMethod(env, cls, startMainWidget, activity, jurl, (jlong)jsaddleCallbacks, initialJS); (*env)->DeleteLocalRef(env, initialJS); if((*env)->ExceptionOccurred(env)) { __android_log_write(ANDROID_LOG_DEBUG, "MainWidget", "startMainWidget exception"); @@ -51,13 +51,13 @@ void Reflex_Dom_Android_MainWidget_runJS(jobject jsExecutor, const char* js) { (*env)->PopLocalFrame(env, 0); } -JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_startProcessing (JNIEnv *env, jobject thisObj, long callbacksLong) { +JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_startProcessing (JNIEnv *env, jobject thisObj, jlong callbacksLong) { const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong; (*(callbacks->jsaddleStart))(); return; } -JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processMessage (JNIEnv *env, jobject thisObj, long callbacksLong, jstring msg) { +JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jstring msg) { const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong; const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL); (*(callbacks->jsaddleResult))(msg_str); @@ -65,7 +65,7 @@ JNIEXPORT void JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallb return; } -JNIEXPORT jstring JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processSyncMessage (JNIEnv *env, jobject thisObj, long callbacksLong, jstring msg) { +JNIEXPORT jstring JNICALL Java_org_reflexfrp_reflexdom_MainWidget_00024JSaddleCallbacks_processSyncMessage (JNIEnv *env, jobject thisObj, jlong callbacksLong, jstring msg) { const JSaddleCallbacks *callbacks = (const JSaddleCallbacks *)callbacksLong; const char *msg_str = (*env)->GetStringUTFChars(env, msg, NULL); char *next_str = (*(callbacks->jsaddleSyncResult))(msg_str); From 986e1cfb514e8a8832479924166920f783787742 Mon Sep 17 00:00:00 2001 From: Dave Laing Date: Sat, 8 Jul 2017 17:55:29 +1000 Subject: [PATCH 08/11] Moves the collection management functions to `reflex`. Also deals with the renaming of `MonadAdjust`. --- reflex-dom-core/src/Foreign/JavaScript/TH.hs | 2 +- .../src/Reflex/Dom/Builder/Class.hs | 2 +- .../src/Reflex/Dom/Builder/Immediate.hs | 14 +- .../src/Reflex/Dom/Builder/InputDisabled.hs | 2 +- .../src/Reflex/Dom/Builder/Static.hs | 8 +- reflex-dom-core/src/Reflex/Dom/Modals/Base.hs | 2 +- .../src/Reflex/Dom/Widget/Basic.hs | 141 +----------------- .../src/Reflex/Dom/Widget/Input.hs | 1 + reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs | 1 + reflex-dom/examples/sortableList.hs | 2 +- 10 files changed, 20 insertions(+), 155 deletions(-) diff --git a/reflex-dom-core/src/Foreign/JavaScript/TH.hs b/reflex-dom-core/src/Foreign/JavaScript/TH.hs index 8f01ce74..a7edf8b2 100644 --- a/reflex-dom-core/src/Foreign/JavaScript/TH.hs +++ b/reflex-dom-core/src/Foreign/JavaScript/TH.hs @@ -136,7 +136,7 @@ instance PrimMonad m => PrimMonad (WithJSContextSingleton x m) where type PrimState (WithJSContextSingleton x m) = PrimState m primitive = lift . primitive -instance MonadAdjust t m => MonadAdjust t (WithJSContextSingleton x m) where +instance Adjustable t m => Adjustable t (WithJSContextSingleton x m) where runWithReplace a0 a' = WithJSContextSingleton $ runWithReplace (coerce a0) (coerceEvent a') traverseIntMapWithKeyWithAdjust f dm0 dm' = WithJSContextSingleton $ traverseIntMapWithKeyWithAdjust (\k v -> unWithJSContextSingleton $ f k v) (coerce dm0) (coerceEvent dm') traverseDMapWithKeyWithAdjust f dm0 dm' = WithJSContextSingleton $ traverseDMapWithKeyWithAdjust (\k v -> unWithJSContextSingleton $ f k v) (coerce dm0) (coerceEvent dm') diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs b/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs index 27958941..ebc9af37 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs @@ -70,7 +70,7 @@ class Default (EventSpec d EventResult) => DomSpace d where -- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building -- dynamic DOM in the 'Reflex' timeline @t@ -class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), MonadAdjust t m) => DomBuilder t m | m -> t where +class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), Adjustable t m) => DomBuilder t m | m -> t where type DomBuilderSpace m :: * textNode :: TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t) default textNode :: ( MonadTrans f diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs b/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs index e526b38c..d484df51 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs @@ -325,7 +325,7 @@ extractUpTo df s e = liftJSM $ do void $ call f f (df, s, e) #endif -type SupportsImmediateDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, MonadAdjust t m, PrimMonad m) +type SupportsImmediateDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m) {-# INLINABLE collectUpTo #-} collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment @@ -388,7 +388,7 @@ wrap e cfg = do } {-# INLINABLE makeElement #-} -makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, MonadAdjust t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element) +makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element) makeElement elementTag cfg child = do doc <- askDocument e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of @@ -640,7 +640,7 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after) liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after) -instance (Reflex t, MonadAdjust t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => MonadAdjust t (ImmediateDomBuilderT t m) where +instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where {-# INLINABLE runWithReplace #-} runWithReplace a0 a' = do initialEnv <- ImmediateDomBuilderT ask @@ -753,7 +753,7 @@ instance (Reflex t, MonadAdjust t m, MonadJSM m, MonadHold t m, MonadFix m, Prim liftIO $ writeIORef placeholders $! phsAfter {-# INLINABLE traverseDMapWithKeyWithAdjust' #-} -traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (MonadAdjust t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v')) +traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v')) traverseDMapWithKeyWithAdjust' = do let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do let new :: forall a. k a -> ComposeMaybe (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a) @@ -781,7 +781,7 @@ traverseDMapWithKeyWithAdjust' = do liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs {-# INLINABLE traverseIntMapWithKeyWithAdjust' #-} -traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (MonadAdjust t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v')) +traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v')) traverseIntMapWithKeyWithAdjust' = do let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt)) @@ -824,7 +824,7 @@ data ChildReadyStateInt {-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-} hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p. - ( MonadAdjust t m + ( Adjustable t m , MonadIO m , MonadJSM m , MonadFix m @@ -908,7 +908,7 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ {-# INLINABLE hoistTraverseWithKeyWithAdjust #-} hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p. - ( MonadAdjust t m + ( Adjustable t m , MonadHold t m , DMap.GCompare k , MonadIO m diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs b/reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs index 06bd0a18..d5f9aa36 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs @@ -76,7 +76,7 @@ instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (InputDisabl newEventWithTrigger = lift . newEventWithTrigger newFanEventWithTrigger f = lift $ newFanEventWithTrigger f -instance MonadAdjust t m => MonadAdjust t (InputDisabledT m) where +instance Adjustable t m => Adjustable t (InputDisabledT m) where runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a') traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm') traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm') diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/Static.hs b/reflex-dom-core/src/Reflex/Dom/Builder/Static.hs index 6c283eea..6d352bf2 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/Static.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/Static.hs @@ -33,6 +33,7 @@ import Data.Dependent.Sum (DSum (..)) import Data.Functor.Compose import Data.Functor.Constant import qualified Data.Map as Map +import Data.Map.Misc (applyMap) import Data.Monoid import qualified Data.Set as Set import Data.Text (Text) @@ -41,7 +42,6 @@ import Data.Tuple import GHC.Generics import Reflex.Class import Reflex.Dom.Builder.Class -import Reflex.Dom.Widget.Basic (applyMap) import Reflex.Dynamic import Reflex.Host.Class import Reflex.PerformEvent.Base @@ -122,7 +122,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, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref IO, MonadAdjust t m) +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, Adjustable t m) data StaticDomSpace @@ -146,7 +146,7 @@ instance DomSpace StaticDomSpace where type RawSelectElement StaticDomSpace = () addEventSpecFlags _ _ _ _ = StaticEventSpec -instance (Reflex t, MonadAdjust t m, MonadHold t m) => MonadAdjust t (StaticDomBuilderT t m) where +instance (Reflex t, Adjustable t m, MonadHold t m) => Adjustable t (StaticDomBuilderT t m) where runWithReplace a0 a' = do e <- StaticDomBuilderT ask (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a') @@ -157,7 +157,7 @@ instance (Reflex t, MonadAdjust t m, MonadHold t m) => MonadAdjust t (StaticDomB traverseDMapWithKeyWithAdjustWithMove = hoistDMapWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove hoistDMapWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p. - ( MonadAdjust t m + ( Adjustable t m , MonadHold t m , PatchTarget (p k (Constant (Behavior t Builder))) ~ DMap k (Constant (Behavior t Builder)) , Patch (p k (Constant (Behavior t Builder))) diff --git a/reflex-dom-core/src/Reflex/Dom/Modals/Base.hs b/reflex-dom-core/src/Reflex/Dom/Modals/Base.hs index 6814b747..2ac0f086 100644 --- a/reflex-dom-core/src/Reflex/Dom/Modals/Base.hs +++ b/reflex-dom-core/src/Reflex/Dom/Modals/Base.hs @@ -106,7 +106,7 @@ instance MonadAtomicRef m => MonadAtomicRef (ModalsT t m) where {-# INLINABLE atomicModifyRef #-} atomicModifyRef r = lift . atomicModifyRef r -instance (MonadAdjust t m, MonadHold t m, MonadFix m) => MonadAdjust t (ModalsT t m) where +instance (Adjustable t m, MonadHold t m, MonadFix m) => Adjustable t (ModalsT t m) where runWithReplace a0 a' = ModalsT $ runWithReplace (unModalsT a0) (fmapCheap unModalsT a') traverseDMapWithKeyWithAdjust f dm0 dm' = ModalsT $ traverseDMapWithKeyWithAdjust (coerce f) dm0 dm' traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = ModalsT $ traverseDMapWithKeyWithAdjustWithMove (coerce f) dm0 dm' diff --git a/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs b/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs index 42260c40..774d954b 100644 --- a/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs +++ b/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs @@ -3,7 +3,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -12,7 +11,6 @@ {-# LANGUAGE TypeFamilies #-} module Reflex.Dom.Widget.Basic ( partitionMapBySetLT - , listHoldWithKey , ChildResult (..) -- * Displaying Values @@ -24,21 +22,6 @@ module Reflex.Dom.Widget.Basic , widgetHold , untilReady - -- * Working with Maps - , diffMapNoEq - , diffMap - , applyMap - , mapPartitionEithers - , applyMapKeysSet - - -- * Widgets on Collections - , listWithKey - , listWithKey' - , listWithKeyShallowDiff - , listViewWithKey - , selectViewListWithKey - , selectViewListWithKey_ - -- * Creating DOM Elements , el , elAttr @@ -56,10 +39,6 @@ module Reflex.Dom.Widget.Basic , dynamicAttributesToModifyAttributes , dynamicAttributesToModifyAttributesWithInitial - -- * List Utils - , list - , simpleList - -- * Specific DOM Elements , Link (..) , linkClass @@ -83,6 +62,7 @@ module Reflex.Dom.Widget.Basic ) where import Reflex.Class +import Reflex.Collection import Reflex.Dom.Builder.Class import Reflex.Dom.Class import Reflex.Dynamic @@ -95,9 +75,9 @@ import Data.Align import Data.Default import Data.Either import Data.Foldable -import Data.Functor.Misc import Data.Map (Map) import qualified Data.Map as Map +import Data.Map.Misc import Data.Maybe import Data.Set (Set) import qualified Data.Set as Set @@ -129,13 +109,6 @@ 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) => 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 @@ -182,107 +155,6 @@ untilReady a b = do postBuild <- getPostBuild runWithReplace a $ b <$ postBuild -diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v) -diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case - This _ -> Just Nothing - These _ new -> Just $ Just new - That new -> Just $ Just new - -diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v) -diffMap olds news = flip Map.mapMaybe (align olds news) $ \case - This _ -> Just Nothing - These old new - | old == new -> Nothing - | otherwise -> Just $ Just new - That new -> Just $ Just new - -applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v -applyMap patch old = insertions `Map.union` (old `Map.difference` deletions) - where (deletions, insertions) = mapPartitionEithers $ maybeToEither <$> patch - maybeToEither = \case - Nothing -> Left () - Just r -> Right r - -mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b) -mapPartitionEithers m = (fromLeft <$> ls, fromRight <$> rs) - where (ls, rs) = Map.partition isLeft m - fromLeft (Left l) = l - fromLeft _ = error "mapPartitionEithers: fromLeft received a Right value; this should be impossible" - fromRight (Right r) = r - fromRight _ = error "mapPartitionEithers: fromRight received a Left value; this should be impossible" - --- | Apply a map patch to a set --- > applyMapKeysSet patch (Map.keysSet m) == Map.keysSet (applyMap patch m) -applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k -applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions) - where (insertions, deletions) = Map.partition isJust patch - ---TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value -listWithKey :: forall t k v m a. (Ord k, DomBuilder t m, PostBuild t m, MonadFix m, MonadHold t m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Dynamic t (Map k a)) -listWithKey vals mkChild = do - postBuild <- getPostBuild - let childValChangedSelector = fanMap $ updated vals - -- We keep track of changes to children values in the mkChild function we pass to listHoldWithKey - -- The other changes we need to keep track of are child insertions and deletions. diffOnlyKeyChanges - -- keeps track of insertions and deletions but ignores value changes, since they're already accounted for. - diffOnlyKeyChanges olds news = flip Map.mapMaybe (align olds news) $ \case - This _ -> Just Nothing - These _ _ -> Nothing - That new -> Just $ Just new - rec sentVals :: Dynamic t (Map k v) <- foldDyn applyMap Map.empty changeVals - let changeVals :: Event t (Map k (Maybe v)) - changeVals = attachWith diffOnlyKeyChanges (current sentVals) $ leftmost - [ updated vals - , tag (current vals) postBuild --TODO: This should probably be added to the attachWith, not to the updated; if we were using diffMap instead of diffMapNoEq, I think it might not work - ] - listHoldWithKey Map.empty changeVals $ \k v -> - mkChild k =<< holdDyn v (select childValChangedSelector $ Const2 k) - -{-# DEPRECATED listWithKey' "listWithKey' has been renamed to listWithKeyShallowDiff; also, its behavior has changed to fix a bug where children were always rebuilt (never updated)" #-} -listWithKey' :: (Ord k, DomBuilder t m, MonadFix m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> Event t v -> m a) -> m (Dynamic t (Map k a)) -listWithKey' = listWithKeyShallowDiff - --- | Display the given map of items (in key order) using the builder function provided, and update it with the given event. 'Nothing' update entries will delete the corresponding children, and 'Just' entries will create them if they do not exist or send an update event to them if they do. -listWithKeyShallowDiff :: (Ord k, DomBuilder t m, MonadFix m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> Event t v -> m a) -> m (Dynamic t (Map k a)) -listWithKeyShallowDiff initialVals valsChanged mkChild = do - let childValChangedSelector = fanMap $ fmap (Map.mapMaybe id) valsChanged - sentVals <- foldDyn applyMap Map.empty $ fmap (fmap void) valsChanged - let relevantPatch patch _ = case patch of - Nothing -> Just Nothing -- Even if we let a Nothing through when the element doesn't already exist, this doesn't cause a problem because it is ignored - Just _ -> Nothing -- We don't want to let spurious re-creations of items through - listHoldWithKey initialVals (attachWith (flip (Map.differenceWith relevantPatch)) (current sentVals) valsChanged) $ \k v -> - mkChild k v $ select childValChangedSelector $ Const2 k - ---TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value --- | Create a dynamically-changing set of Event-valued widgets. --- This is like listWithKey, specialized for widgets returning (Event t a). listWithKey would return 'Dynamic t (Map k (Event t a))' in this scenario, but listViewWithKey flattens this to 'Event t (Map k a)' via 'switch'. -listViewWithKey :: (Ord k, DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m (Event t a)) -> m (Event t (Map k a)) -listViewWithKey vals mkChild = switch . fmap mergeMap <$> listViewWithKey' vals mkChild - -listViewWithKey' :: (Ord k, DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Behavior t (Map k a)) -listViewWithKey' vals mkChild = current <$> listWithKey vals mkChild - --- | Create a dynamically-changing set of widgets, one of which is selected at any time. -selectViewListWithKey :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m) - => Dynamic t k -- ^ Current selection key - -> Dynamic t (Map k v) -- ^ Dynamic key/value map - -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected - -> m (Event t (k, a)) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget. -selectViewListWithKey selection vals mkChild = do - let selectionDemux = demux selection -- For good performance, this value must be shared across all children - selectChild <- listWithKey vals $ \k v -> do - let selected = demuxed selectionDemux k - selectSelf <- mkChild k v selected - return $ fmap ((,) k) selectSelf - return $ switchPromptlyDyn $ leftmost . Map.elems <$> selectChild - -selectViewListWithKey_ :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m) - => Dynamic t k -- ^ Current selection key - -> Dynamic t (Map k v) -- ^ Dynamic key/value map - -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected - -> m (Event t k) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget. -selectViewListWithKey_ selection vals mkChild = fmap fst <$> selectViewListWithKey selection vals mkChild - -- | Create a DOM element -- > el "div" (text "Hello World") --
Hello World
@@ -380,15 +252,6 @@ dynamicAttributesToModifyAttributesWithInitial attrs0 d = do -- Copied and pasted from Reflex.Widget.Class -------------------------------------------------------------------------------- --- | Create a dynamically-changing set of widgets from a Dynamic key/value map. --- Unlike the 'withKey' variants, the child widgets are insensitive to which key they're associated with. -list :: (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m) => Dynamic t (Map k v) -> (Dynamic t v -> m a) -> m (Dynamic t (Map k a)) -list dm mkChild = listWithKey dm (\_ dv -> mkChild dv) - --- | Create a dynamically-changing set of widgets from a Dynamic list. -simpleList :: (DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m) => Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a]) -simpleList xs mkChild = fmap (fmap (map snd . Map.toList)) $ flip list mkChild $ fmap (Map.fromList . zip [(1::Int)..]) xs - {- schedulePostBuild x = performEvent_ . (x <$) =<< getPostBuild diff --git a/reflex-dom-core/src/Reflex/Dom/Widget/Input.hs b/reflex-dom-core/src/Reflex/Dom/Widget/Input.hs index 51682fab..b7ccb575 100644 --- a/reflex-dom-core/src/Reflex/Dom/Widget/Input.hs +++ b/reflex-dom-core/src/Reflex/Dom/Widget/Input.hs @@ -41,6 +41,7 @@ import GHCJS.DOM.HTMLTextAreaElement (HTMLTextAreaElement) import GHCJS.DOM.Types (MonadJSM, File, uncheckedCastTo) import qualified GHCJS.DOM.Types as DOM (HTMLElement(..), EventTarget(..)) import Reflex.Class +import Reflex.Collection import Reflex.Dom.Builder.Class import Reflex.Dom.Builder.Immediate import Reflex.Dom.Class diff --git a/reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs b/reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs index 86243bfc..6c01e2e2 100644 --- a/reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs +++ b/reflex-dom-core/src/Reflex/Dom/Widget/Lazy.hs @@ -7,6 +7,7 @@ module Reflex.Dom.Widget.Lazy where import Reflex.Class +import Reflex.Collection import Reflex.Dom.Builder.Class import Reflex.Dom.Builder.Immediate import Reflex.Dom.Class diff --git a/reflex-dom/examples/sortableList.hs b/reflex-dom/examples/sortableList.hs index 92404150..55e1ee70 100644 --- a/reflex-dom/examples/sortableList.hs +++ b/reflex-dom/examples/sortableList.hs @@ -67,7 +67,7 @@ displayRedrawTime e = do text "Time: " dynText =<< holdDyn "not yet run" (T.pack . show <$> diff) -simpleSortableList :: forall t m k v. (MonadHold t m, MonadFix m, MonadAdjust t m, Ord k) => (k -> v -> m ()) -> Map k v -> Event t (v -> v -> Ordering) -> Event t (v -> v -> Ordering) -> m () +simpleSortableList :: forall t m k v. (MonadHold t m, MonadFix m, Adjustable t m, Ord k) => (k -> v -> m ()) -> Map k v -> Event t (v -> v -> Ordering) -> Event t (v -> v -> Ordering) -> m () simpleSortableList f m0 resortFunc resortSlowFunc = do rec let resortPatchFast = attachWith (flip patchThatSortsMapWith) (currentIncremental m) resortFunc redrawPatch :: Map k v -> (v -> v -> Ordering) -> PatchMapWithMove k v From 941e28f93105f847814f499d5f6a7efac69d9be7 Mon Sep 17 00:00:00 2001 From: Dave Laing Date: Thu, 26 Oct 2017 09:02:12 +1000 Subject: [PATCH 09/11] Re-exports the collection code --- reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs b/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs index 774d954b..65112c18 100644 --- a/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs +++ b/reflex-dom-core/src/Reflex/Dom/Widget/Basic.hs @@ -59,6 +59,8 @@ module Reflex.Dom.Widget.Basic , tabDisplay , HasAttributes (..) + , module Reflex.Collection + , module Data.Map.Misc ) where import Reflex.Class From 88851b5c9a9f3a3e1ad1d61b3fa919081ac51976 Mon Sep 17 00:00:00 2001 From: Dave Laing Date: Tue, 31 Oct 2017 11:50:47 +1000 Subject: [PATCH 10/11] Adjusts Quickref.md to reflect recent changes --- Quickref.md | 57 ++++------------------------------------------------- 1 file changed, 4 insertions(+), 53 deletions(-) diff --git a/Quickref.md b/Quickref.md index 3299fbcb..5f4b5f99 100644 --- a/Quickref.md +++ b/Quickref.md @@ -76,30 +76,7 @@ Note the "list" functions do not imply particular HTML tags (ul, li, etc), thoug -- Same as dyn, but takes initial value and an update Event instead of a Dynamic. [W] widgetHold :: m a -> Event (m a) -> m (Dynamic a) --- Turn a Dynamic key/value map into a set of dynamically-changing widgets. -[W] listWithKey :: Ord k => - Dynamic (Map k v) -> (k -> Dynamic v -> m a ) -> m (Dynamic (Map k a)) - --- Same as above where the widget constructor doesn't care about the key. -[W] list :: Ord k => - Dynamic (Map k v) -> ( Dynamic v -> m a ) -> m (Dynamic (Map k a)) - --- Even simpler version where there are no keys and we just use a list. -[W] simpleList :: - Dynamic [v] -> ( Dynamic v -> m a ) -> m (Dynamic [a]) - --- Like listWithKey specialized for widgets returning (Event a). -[W] listViewWithKey :: Ord k => - Dynamic (Map k v) -> (k -> Dynamic v -> m (Event a)) -> m (Event (Map k a)) - --- Create a dynamically-changing set of widgets, one of which is selected at any time. -[W] selectViewListWithKey_ :: Ord k => Dynamic k -> - Dynamic (Map k v) -> (k -> Dynamic v -> Dynamic Bool -> m (Event a)) -> m (Event k) - --- Same as listWithKey, but takes initial values and an updates Event instead of a Dynamic. -[W] listWithKey' :: Ord k => - Map k v -> Event (Map k (Maybe v)) -> (k -> v -> Event v -> m a) -> m (Dynamic (Map k a)) -``` +Also see the "Collection management functions" section in the `reflex` Quick Reference. ### Utility widgets @@ -135,27 +112,12 @@ Some of these widget builders take a configuration record and return a record co ## Connecting to the real world (I/O) -### Connecting to DOM events - ```haskell -- Extract the specified Event from an 'El'. See Reflex.Dom.Widget.Basic [ ] domEvent :: EventName en -> El -> Event (EventResultType en) ``` -### Performing arbitrary I/O in response to Events - -```haskell --- Run side-effecting actions in Event when it occurs; returned Event contains --- results. Side effects run in (WidgetHost m) monad, which includes [S] and [H] --- and can also do I/O via liftIO -[W] performEvent :: Event ( WidgetHost m a) -> m (Event a) - --- Just run side-effects; no return Event -[W] performEvent_ :: Event ( WidgetHost m ()) -> m () - --- Actions run asynchronously; actions are given a callback to send return values -[W] performEventAsync :: Event ((a -> IO ()) -> WidgetHost m ()) -> m (Event a) -``` +Also see the "Connection to the real world" and "Time" sections in the `reflex` Quick Reference. ### XMLHttpRequest @@ -178,16 +140,6 @@ Convenience functions for XMLHttpRequest. see Reflex.Dom.Xhr [W] getAndDecode :: FromJSON a => Event Text -> m (Event (Maybe a)) ``` -### Time - -```haskell --- Create Event at given interval with given basis time. -[W] tickLossy :: NominalDiffTime -> UTCTime -> m (Event t TickInfo) - --- Delay an Event's occurrences by a given amount in seconds. -[W] delay :: NominalDiffTime -> Event t a -> m (Event t a) -``` - ## Startup ```haskell @@ -201,7 +153,6 @@ Convenience functions for XMLHttpRequest. see Reflex.Dom.Xhr [I] mainWidgetWithCss :: ByteString -> Widget Spider (Gui Spider (WithWebView SpiderHost) (HostFrame Spider)) () -> IO () - --- One-shot Event that is triggered once all initial widgets are built -[W] getPostBuild :: m (Event ()) ``` + +Also see the corresponding section in the `reflex` Quick Reference. From 30a487e0bf6ac234c8a5bbc2de1f1ddf3d84bd8b Mon Sep 17 00:00:00 2001 From: Ryan Trinkle Date: Sat, 11 Nov 2017 13:39:58 -0500 Subject: [PATCH 11/11] Reconcile conflicts for PR #152 --- .../src/Reflex/Dom/Builder/Class.hs | 34 ++ .../src/Reflex/Dom/Builder/Immediate.hs | 489 ++++++++++++------ reflex-dom-core/src/Reflex/Dom/Main.hs | 22 +- reflex-dom-core/src/Reflex/Dom/Old.hs | 3 +- .../src/Reflex/Dom/Specializations.hs | 10 +- test/prebuild.hs | 2 +- 6 files changed, 381 insertions(+), 179 deletions(-) diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs b/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs index ebc9af37..95cb2d2e 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/Class.hs @@ -155,11 +155,41 @@ class (Monad m, Reflex t, DomSpace (DomBuilderSpace m), Adjustable t m) => DomBu => m () notReady = lift notReady +{- 2017-05-19 dridus: Commented out per Ryan Trinkle. Note that this does not support mount state and possibly has issues with unready child handling. + class DomBuilder t m => MountableDomBuilder t m where type DomFragment m :: * buildDomFragment :: m a -> m (DomFragment m, a) mountDomFragment :: DomFragment m -> Event t (DomFragment m) -> m () +-} + +-- |'HasMountStatus' represents a widget that can be aware of whether the corresponding DOM built by the widget is present within the document yet or not. +-- Its primary use is to integrate with external libraries which need to be invoked only when DOM structures are installed in the document. +-- +-- ___Note:___ once the current scope is replaced, any 'performEvent's in the scope will be cancelled and so if you want to observe the 'Unmounted' status +-- you have to plumb the mount state dynamic back out of the current scope so the parent scope can react to it. +class Monad m => HasMountStatus t m | m -> t where + -- |Get a 'Dynamic' representing the current 'MountState' of DOM elements created in the current scope. + getMountStatus :: m (Dynamic t MountState) + +instance HasMountStatus t m => HasMountStatus t (ReaderT r m) where + getMountStatus = lift getMountStatus +instance HasMountStatus t m => HasMountStatus t (PostBuildT t m) where + getMountStatus = lift getMountStatus + +-- |Type representing the current mount status of a DOM structure. Mount status refers to whether the DOM structure is currently within the document tree, not +-- in the document tree, or transitioning. +data MountState + -- note: order of these constructors is important, because Ord is derived and employed when combining parent and child mount states + = Unmounted + -- ^DOM structures have been removed from the document. + | Mounting + -- ^DOM structures are not yet installed in the document. + | Mounted + -- ^DOM structures are now in the document. + deriving (Eq, Ord, Show) + type Namespace = Text data TextNodeConfig t @@ -522,11 +552,15 @@ instance (DomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => DomBui type DomBuilderSpace (PostBuildT t m) = DomBuilderSpace m wrapRawElement e = lift . wrapRawElement e +{- 2017-05-19 dridus: Commented out per Ryan Trinkle. Note that this does not support mount state and possibly has issues with unready child handling. + instance (MountableDomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => MountableDomBuilder t (PostBuildT t m) where type DomFragment (PostBuildT t m) = DomFragment m buildDomFragment = liftThrough buildDomFragment mountDomFragment f0 f' = lift $ mountDomFragment f0 f' +-} + instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m textNode = liftTextNode diff --git a/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs b/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs index d484df51..a886c693 100644 --- a/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs +++ b/reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs @@ -63,6 +63,8 @@ module Reflex.Dom.Builder.Immediate , drawChildUpdate , ChildReadyState (..) , ChildReadyStateInt (..) + , Child + , ChildInstallation , mkHasFocus , insertBefore , EventType @@ -122,6 +124,7 @@ import Data.Default import Data.Dependent.Map (DMap) import qualified Data.Dependent.Map as DMap import Data.Dependent.Sum +import Data.Foldable (for_) import Data.Functor.Compose import Data.Functor.Constant import Data.Functor.Misc @@ -133,7 +136,7 @@ import Data.Monoid hiding (Product) import Data.Some (Some) import qualified Data.Some as Some import Data.Text (Text) -import qualified Data.Text as T +import Data.Traversable (for) import qualified GHCJS.DOM as DOM import GHCJS.DOM.RequestAnimationFrameCallback import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode) @@ -179,13 +182,22 @@ instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where liftJSM' = ImmediateDomBuilderT . liftJSM' #endif -data ImmediateDomBuilderEnv t - = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document - , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node - , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered - , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it - } +-- |Structure forming the environment in which a DOM builder runs, tracking what document, where in that document is being built, and so on. +data ImmediateDomBuilderEnv t = ImmediateDomBuilderEnv + { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document -- TODO: is this really necessary? it's denormalized with respect to getOwnerDocument. + -- ^The document in which building is being performed. + , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node + -- ^The DOM node where building is occurring within, often a document fragment. + , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) + -- ^Number of children who still aren't fully rendered. While a DOM builder action has unready children, the parent being built into will typically be an + -- unmounted document fragment. As each child becomes ready, this count is decremented until finally it reaches zero and the commit action is triggered. + , _immediateDomBuilderEnv_commitAction :: JSM () + -- ^Action to take when the unready children all become ready, usually to install the document fragment. + , _immediateDomBuilderEnv_mountState :: Dynamic t MountState + -- ^'Dynamic' representing the current state of DOM nodes within the parent node with respect to the document as a whole. See 'MountState' for more. + } +-- |Implementation of a 'DomBuilder' monad which manipualtes a DOM tree live, as used in the browser. newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException #if MIN_VERSION_base(4,9,1) @@ -399,6 +411,7 @@ makeElement elementTag cfg child = do Just ans -> lift $ setAttributeNS e (Just ans) n v result <- flip localEnv child $ \env -> env { _immediateDomBuilderEnv_parent = toNode e + -- plain element contents are always as mounted as their parent, so don't derive a new mount state } append $ toNode e wrapped <- wrap e $ extractRawElementConfig cfg @@ -599,6 +612,9 @@ instance SupportsImmediateDomBuilder t m => DomBuilder t (ImmediateDomBuilderT t let unreadyChildren = _immediateDomBuilderEnv_unreadyChildren env liftIO $ modifyIORef' unreadyChildren succ + +{- 2017-05-19 dridus: Commented out per Ryan Trinkle. Note that this does not support mount state and possibly has issues with unready child handling. + data FragmentState = FragmentState_Unmounted | FragmentState_Mounted (DOM.Text, DOM.Text) @@ -639,15 +655,72 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB insertBefore (_immediateDomFragment_document childFragment) after liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after) liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after) +-} + +instance Monad m => HasMountStatus t (ImmediateDomBuilderT t m) where + getMountStatus = ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_mountState + +-- |Structure which keeps track of which generation of a 'runWithReplace' action is currently installed in the DOM. +data Cohort = Cohort + { _cohort_generation :: !Int + -- ^Sequentially assigned generation number for this cohort + , _cohort_triggerUpdateMountState :: !(MountState -> IO ()) + -- ^IO action which updates the '_immediateDomBuilderEnv_mountState' for this generation, so that a previous generation can be informed that it's + -- unmounted. + } + +-- |Structure keeping track of an individual DOM child managed by 'traverseDMapWithKeyWithAdjust', 'traverseIntMapWithKeyWithAdjust', +-- 'traverseDMapWithKeyWithAdjustWithMove', or 'traverseIntMapWithKeyWithAdjustWithMove' +data Child rs a = Child + { _child_documentFragment :: !DOM.DocumentFragment + -- ^The document fragment installed in the DOM which should contain the intended DOM child tree for the entry in the 'DMap'. + , _child_readyState :: !(IORef rs) + -- ^Reference cell containing the current readiness of the child. + , _child_installation :: !ChildInstallation + -- ^Substructure with details that need to be maintained as long as the child is installed in the DOM. + , _child_result :: a + -- ^The result of running the builder action for the child. + } + +-- |Structure keeping track of aspects of a child installed in the DOM by 'traverseDMapWithKeyWithAdjust' or 'traverseDMapWithKeyWithAdjustWithMove' +data ChildInstallation = ChildInstallation + { _childInstallation_placeholder :: !DOM.Text + -- ^The placeholder text node which gets used until the real content is available, at which point the placeholder is replaced with the document fragment. + , _childInstallation_triggerUpdateLocalMountState :: !(MountState -> IO ()) + -- ^IO trigger to update the local mount state of the child, which is then zipped with the parent mount state. + } + +-- |Enumerated state of a 'Child' being used with a 'DMap', representing whether it's installed in the DOM or not. +#if MIN_VERSION_base(4,9,0) +data ChildReadyState k +#else +data ChildReadyState (k :: * -> *) +#endif + = ChildReadyState_Ready + -- ^The child is installed in the DOM because it's become ready at some point. + | ChildReadyState_Unready !(Maybe (Some k)) + -- ^The child is not yet installed in the DOM and if it has unready children then the key into the unready count tracking 'DMap' is given. + deriving (Show, Read, Eq, Ord) -instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where +-- |Enumerated state of a 'Child' being used with an 'IntMap', representing whether it's installed in the DOM or not. +data ChildReadyStateInt + = ChildReadyStateInt_Ready + -- ^The child is installed in the DOM because it's become ready at some point. + | ChildReadyStateInt_Unready !(Maybe Int) + -- ^The child is not yet installed in the DOM and if it has unready children then the key into the unready count tracking 'DMap' is given. + deriving (Show, Read, Eq, Ord) + +instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, MonadRef m, Ref m ~ IORef, MonadReflexCreateTrigger t m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where {-# INLINABLE runWithReplace #-} runWithReplace a0 a' = do initialEnv <- ImmediateDomBuilderT ask before <- textNodeInternal ("" :: Text) let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv + parentMountState = _immediateDomBuilderEnv_mountState initialEnv haveEverBeenReady <- liftIO $ newIORef False - currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM + (updateFirstLocalMountState, triggerUpdateFirstLocalMountState) <- newTriggerEvent + firstLocalMountState <- holdDyn Mounting updateFirstLocalMountState + currentCohort <- liftIO $ newIORef $ Cohort (-1) triggerUpdateFirstLocalMountState -- Equal to the cohort currently in the DOM let myCommitAction = do liftIO (readIORef haveEverBeenReady) >>= \case True -> return () @@ -669,6 +742,7 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = myCommitAction + , _immediateDomBuilderEnv_mountState = zipDynWith min parentMountState firstLocalMountState } liftIO $ readIORef unreadyChildren >>= \case 0 -> writeIORef haveEverBeenReady True @@ -678,17 +752,23 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM (result0, child') <- ImmediateDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do df <- createDocumentFragment doc unreadyChildren <- liftIO $ newIORef 0 + (updateLocalMountState, triggerUpdateLocalMountState) <- newTriggerEvent + localMountState <- holdDyn Mounting updateLocalMountState let commitAction = do - c <- liftIO $ readIORef currentCohort + Cohort c lastCohortTriggerUpdateMountState <- liftIO $ readIORef currentCohort when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this deleteBetweenExclusive before after insertBefore df after - liftIO $ writeIORef currentCohort cohortId + liftIO $ do + lastCohortTriggerUpdateMountState Unmounted + triggerUpdateLocalMountState Mounted + writeIORef currentCohort $ Cohort cohortId triggerUpdateLocalMountState myCommitAction result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv { _immediateDomBuilderEnv_parent = toNode df , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = commitAction + , _immediateDomBuilderEnv_mountState = zipDynWith min parentMountState localMountState } uc <- liftIO $ readIORef unreadyChildren let commitActionToRunNow = if uc == 0 @@ -697,161 +777,185 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM return (commitActionToRunNow, result) requestDomAction_ $ fmapMaybe fst child' return (result0, snd <$> child') + {-# INLINABLE traverseIntMapWithKeyWithAdjust #-} traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust' + {-# INLINABLE traverseDMapWithKeyWithAdjust #-} traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust' - {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-} - traverseDMapWithKeyWithAdjustWithMove = do - let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do - let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState k))) a) - new k = PatchDMapWithMove.nodeInfoMapFromM $ \case - PatchDMapWithMove.From_Insert (Compose (_, _, sRef, _)) -> do - readIORef sRef >>= \case - ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete - ChildReadyState_Unready _ -> do - writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k - return $ PatchDMapWithMove.From_Insert $ Constant sRef - PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete - PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey - deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState k))) (ComposeMaybe k) a -> IO (Constant () a) - deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do - writeIORef sRef $ ChildReadyState_Unready $ Some.This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases - return $ Constant () - p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p - _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old - return $ applyAlways p' old - hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholderRef (p_ :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) -> do - let p = unPatchDMapWithMove p_ - phsBefore <- liftIO $ readIORef placeholders - lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef - let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a) - collectIfMoved k e = do - let mThisPlaceholder = Map.lookup (Some.This k) phsBefore -- Will be Nothing if this element wasn't present before - nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsBefore - case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of - False -> do - mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder - return $ Constant Nothing - True -> do - Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder - collected <- DMap.traverseWithKey collectIfMoved p - let !phsAfter = fromMaybe phsBefore $ apply (weakenPatchDMapWithMoveWith (\(Compose (_, ph, _, _)) -> ph) p_) phsBefore --TODO: Don't recompute this - let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant () a) - placeFragment k e = do - let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsAfter - case PatchDMapWithMove._nodeInfo_from e of - PatchDMapWithMove.From_Insert (Compose (df, _, _, _)) -> do - df `insertBefore` nextPlaceholder - PatchDMapWithMove.From_Delete -> do - return () - PatchDMapWithMove.From_Move fromKey -> do - Just (Constant mdf) <- return $ DMap.lookup fromKey collected - mapM_ (`insertBefore` nextPlaceholder) mdf - return $ Constant () - mapM_ (\(k :=> v) -> void $ placeFragment k v) $ DMap.toDescList p -- We need to go in reverse order here, to make sure the placeholders are in the right spot at the right time - liftIO $ writeIORef placeholders $! phsAfter -{-# INLINABLE traverseDMapWithKeyWithAdjust' #-} -traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v')) -traverseDMapWithKeyWithAdjust' = do - let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do - let new :: forall a. k a -> ComposeMaybe (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a) - new k (ComposeMaybe m) = ComposeMaybe <$> case m of - Nothing -> return Nothing - Just (Compose (_, _, sRef, _)) -> do + {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-} + traverseDMapWithKeyWithAdjustWithMove = + hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness applyDomUpdate_ + where + updateChildUnreadiness (p :: PatchDMapWithMove k (Compose (Child (ChildReadyState k)) v')) old = do + let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (Child (ChildReadyState k)) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState k))) a) + new k = PatchDMapWithMove.nodeInfoMapFromM $ \case + PatchDMapWithMove.From_Insert (Compose Child { _child_readyState = sRef }) -> do readIORef sRef >>= \case - ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready + ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete ChildReadyState_Unready _ -> do writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k - return $ Just $ Constant sRef - delete _ (Constant sRef) = do - writeIORef sRef $ ChildReadyState_Unready Nothing + return $ PatchDMapWithMove.From_Insert $ Constant sRef + PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete + PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey + deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState k))) (ComposeMaybe k) a -> IO (Constant () a) + deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do + writeIORef sRef $ ChildReadyState_Unready $ Some.This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases return $ Constant () - p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p - _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old + p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p + _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old return $ applyAlways p' old - hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchDMap p) -> do - phs <- liftIO $ readIORef placeholders - forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do - lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef - let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs - forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder - forM_ mv $ \(Compose (df, _, _, _)) -> df `insertBefore` nextPlaceholder - liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs - -{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-} -traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v')) -traverseIntMapWithKeyWithAdjust' = do - let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do - let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt)) - new k m = case m of - Nothing -> return Nothing - Just (_, _, sRef, _) -> do - readIORef sRef >>= \case - ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready - ChildReadyStateInt_Unready _ -> do - writeIORef sRef $ ChildReadyStateInt_Unready $ Just k - return $ Just sRef - delete _ sRef = do - writeIORef sRef $ ChildReadyStateInt_Unready Nothing - return () - p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner - _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old - return $ applyAlways p' old - hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchIntMap p) -> do - phs <- liftIO $ readIORef placeholders - forM_ (IntMap.toList p) $ \(k, mv) -> do - lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef - let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs - forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder - forM_ mv $ \(df, _, _, _) -> df `insertBefore` nextPlaceholder - liftIO $ writeIORef placeholders $! fromMaybe phs $ apply ((\(_, ph, _, _) -> ph) <$> PatchIntMap p) phs - -#if MIN_VERSION_base(4,9,0) -data ChildReadyState k -#else -data ChildReadyState (k :: * -> *) -#endif - = ChildReadyState_Ready - | ChildReadyState_Unready !(Maybe (Some k)) - deriving (Show, Read, Eq, Ord) + applyDomUpdate_ currentChildInstallations lastPlaceholderRef (p_ :: PatchDMapWithMove k (Compose (Child (ChildReadyState k)) v')) = do + let p = unPatchDMapWithMove p_ + childInstallationsBefore <- liftIO $ readIORef currentChildInstallations + lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef + let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (Child (ChildReadyState k)) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a) + collectIfMoved k e = do + let mThisChildInstallation = Map.lookup (Some.This k) childInstallationsBefore -- Will be Nothing if this element wasn't present before + nextPlaceholder = maybe lastPlaceholder (_childInstallation_placeholder . snd) $ Map.lookupGT (Some.This k) childInstallationsBefore + case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of + False -> do + for_ mThisChildInstallation $ \ ci -> do + _childInstallation_placeholder ci `deleteUpTo` nextPlaceholder + liftIO $ _childInstallation_triggerUpdateLocalMountState ci Unmounted + return $ Constant Nothing + True -> do + fmap Constant . for mThisChildInstallation $ \ ci -> + -- no need to change mount state here since it's just moving around in the DOM + _childInstallation_placeholder ci `collectUpTo` nextPlaceholder + collected <- DMap.traverseWithKey collectIfMoved p + let !childInstallationsAfter = applyAlways (weakenPatchDMapWithMoveWith (_child_installation . getCompose) p_) childInstallationsBefore --TODO: Don't recompute this + let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose (Child (ChildReadyState k)) v') a -> JSM (Constant () a) + placeFragment k e = do + let nextPlaceholder = maybe lastPlaceholder (_childInstallation_placeholder . snd) $ Map.lookupGT (Some.This k) childInstallationsAfter + case PatchDMapWithMove._nodeInfo_from e of + PatchDMapWithMove.From_Insert (Compose child) -> do + _child_documentFragment child `insertBefore` nextPlaceholder + liftIO $ (_childInstallation_triggerUpdateLocalMountState . _child_installation) child Mounted + PatchDMapWithMove.From_Delete -> do + -- umount already triggered in collectIfMoved + return () + PatchDMapWithMove.From_Move fromKey -> do + Just (Constant mdf) <- return $ DMap.lookup fromKey collected + mapM_ (`insertBefore` nextPlaceholder) mdf + return $ Constant () + mapM_ (\(k :=> v) -> void $ placeFragment k v) $ DMap.toDescList p -- We need to go in reverse order here, to make sure the placeholders are in the right spot at the right time + liftIO $ writeIORef currentChildInstallations $! childInstallationsAfter -data ChildReadyStateInt - = ChildReadyStateInt_Ready - | ChildReadyStateInt_Unready !(Maybe Int) - deriving (Show, Read, Eq, Ord) +{-# INLINABLE traverseDMapWithKeyWithAdjust' #-} +traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, MonadRef m, Ref m ~ IORef, MonadReflexCreateTrigger t m, DMap.GCompare k) => (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> ImmediateDomBuilderT t m (DMap k v', Event t (PatchDMap k v')) +traverseDMapWithKeyWithAdjust' = + hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness applyDomUpdate_ + where + updateChildUnreadiness (p :: PatchDMap k (Compose (Child (ChildReadyState k)) v')) old = do + let new :: forall a. k a -> ComposeMaybe (Compose (Child (ChildReadyState k)) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a) + new k (ComposeMaybe m) = ComposeMaybe <$> case m of + Nothing -> return Nothing + Just (Compose Child { _child_readyState = sRef }) -> do + readIORef sRef >>= \case + ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready + ChildReadyState_Unready _ -> do + writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k + return $ Just $ Constant sRef + delete _ (Constant sRef) = do + writeIORef sRef $ ChildReadyState_Unready Nothing + return $ Constant () + p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p + _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old + return $ applyAlways p' old + applyDomUpdate_ currentChildInstallations lastPlaceholderRef (PatchDMap p) = do + childInstallations <- liftIO $ readIORef currentChildInstallations + forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do + lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef + let nextPlaceholder = maybe lastPlaceholder (_childInstallation_placeholder . snd) $ Map.lookupGT (Some.This k) childInstallations + forM_ (Map.lookup (Some.This k) childInstallations) $ \ ci -> do + _childInstallation_placeholder ci `deleteUpTo` nextPlaceholder + liftIO $ _childInstallation_triggerUpdateLocalMountState ci Unmounted + forM_ mv $ \ (Compose child) -> do + _child_documentFragment child `insertBefore` nextPlaceholder + liftIO $ (_childInstallation_triggerUpdateLocalMountState . _child_installation) child Mounted + liftIO $ writeIORef currentChildInstallations $! applyAlways (weakenPatchDMapWith (_child_installation . getCompose) $ PatchDMap p) childInstallations -{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-} +{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-} +traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, MonadRef m, Ref m ~ IORef, MonadReflexCreateTrigger t m) => (IntMap.Key -> v -> ImmediateDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> ImmediateDomBuilderT t m (IntMap v', Event t (PatchIntMap v')) +traverseIntMapWithKeyWithAdjust' = + hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness applyDomUpdate_ + where + updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (Child ChildReadyStateInt v')) old = do + let new :: IntMap.Key -> Maybe (Child ChildReadyStateInt v') -> IO (Maybe (IORef ChildReadyStateInt)) + new k m = case m of + Nothing -> return Nothing + Just Child { _child_readyState = sRef } -> do + readIORef sRef >>= \case + ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready + ChildReadyStateInt_Unready _ -> do + writeIORef sRef $ ChildReadyStateInt_Unready $ Just k + return $ Just sRef + delete _ sRef = do + writeIORef sRef $ ChildReadyStateInt_Unready Nothing + return () + p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner + _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old + return $ applyAlways p' old + applyDomUpdate_ currentChildInstallations lastPlaceholderRef (PatchIntMap p) = do + childInstallations <- liftIO $ readIORef currentChildInstallations + forM_ (IntMap.toList p) $ \ (k, mv) -> do + lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef + let nextPlaceholder = maybe lastPlaceholder (_childInstallation_placeholder . snd) $ IntMap.lookupGT k childInstallations + forM_ (IntMap.lookup k childInstallations) $ \ ci -> do + _childInstallation_placeholder ci `deleteUpTo` nextPlaceholder + liftIO $ _childInstallation_triggerUpdateLocalMountState ci Unmounted + forM_ mv $ \ child -> do + _child_documentFragment child `insertBefore` nextPlaceholder + liftIO $ (_childInstallation_triggerUpdateLocalMountState . _child_installation) child Mounted + liftIO $ writeIORef currentChildInstallations $! applyAlways (_child_installation <$> PatchIntMap p) childInstallations + +-- |Hoist a 'traverseIntMapWithKeyWithAdjust' or 'traverseIntMapWithKeyWithAdjustWithMove' in the base monad that underlies a +-- @'ImmediateDomBuilderT' m ~ 'RequesterT' t 'JSM' 'Identity' ('TriggerEventT' m)@. This is used to implement both 'traverseIntMapWithKeyWithAdjust' and +-- 'traverseIntMapWithKeyWithAdjustWithMove' for 'ImmediateDomBuilderT'. +-- +-- This is the core work which manages setting up the DOM structure and updating it over time on incoming patch events (@p v@), deferring the specifics of +-- adjust only versus adjust and move to the caller by way of its @applyDomUpdate_@ parameter. +{-# INLINABLE hoistTraverseIntMapWithKeyWithAdjust #-} hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p. ( Adjustable t m + , MonadHold t m , MonadIO m , MonadJSM m - , MonadFix m , PrimMonad m - , Monoid (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) + , MonadFix m + , MonadRef m, Ref m ~ IORef + , MonadReflexCreateTrigger t m + , Monoid (p (Child ChildReadyStateInt v')) , Functor p ) - => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) + => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (Child ChildReadyStateInt v')) -> IntMap v -> Event t (p v) - -> RequesterT t JSM Identity (TriggerEventT t m) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event t (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))) - ) -- ^ The base monad's traversal - -> (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state - -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) -- ^ Apply a patch to the DOM + -> RequesterT t JSM Identity (TriggerEventT t m) (IntMap (Child ChildReadyStateInt v'), Event t (p (Child ChildReadyStateInt v'))) + ) + -- ^@base@: The base monad's 'Adjustable' traversal function to augment with DOM handling. + -> (p (Child ChildReadyStateInt v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) + -- ^@updateChildUnreadiness@: Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state + -> (IORef (IntMap ChildInstallation) -> IORef DOM.Text -> p (Child ChildReadyStateInt v') -> JSM ()) + -- ^@applyDomUpdate_@: Apply a patch to the DOM -> (IntMap.Key -> v -> ImmediateDomBuilderT t m v') + -- ^@f@: The function to apply to each pair in the initial 'DMap' or each pair that gets updated to produce the updated DOM structure for the value. -> IntMap v + -- ^@im0@: The initial 'IntMap' to traverse, which will be successively updated by patch events. -> Event t (p v) + -- ^@im'@: The stream of patch events to update @dm0@ with, applying @f@ for each change to build a new DOM structure. -> ImmediateDomBuilderT t m (IntMap v', Event t (p v')) -hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do +hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f im0 im' = do initialEnv <- ImmediateDomBuilderT ask let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv - pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) <- liftIO $ newIORef mempty + pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (Child ChildReadyStateInt v')) <- liftIO $ newIORef mempty haveEverBeenReady <- liftIO $ newIORef False - placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized" + currentChildInstallations <- liftIO $ newIORef $ error "currentChildInstallations not yet initialized" lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized" let applyDomUpdate p = do - applyDomUpdate_ placeholders lastPlaceholderRef p + applyDomUpdate_ currentChildInstallations lastPlaceholderRef p markSelfReady liftIO $ writeIORef pendingChange $! mempty markSelfReady = do @@ -878,8 +982,8 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ liftIO $ writeIORef pendingChange (newUnready, p) when (IntMap.null newUnready) $ do applyDomUpdate p - (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm' - let processChild k (_, _, sRef, _) = do + (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) im0 im' + let processChild k Child { _child_readyState = sRef } = do readIORef sRef >>= \case ChildReadyStateInt_Ready -> return Nothing ChildReadyStateInt_Unready _ -> do @@ -891,11 +995,15 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ else do modifyIORef' parentUnreadyChildren succ writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time - let result0 = IntMap.map (\(_, _, _, v) -> v) children0 - placeholders0 = fmap (\(_, ph, _, _) -> ph) children0 - result' = ffor children' $ fmap $ \(_, _, _, r) -> r - liftIO $ writeIORef placeholders $! placeholders0 - _ <- IntMap.traverseWithKey (\_ (df, _, _, _) -> void $ append $ toNode df) children0 + let result0 = IntMap.map _child_result children0 + childInstallations0 = fmap (\ Child { _child_installation = ci } -> ci) children0 + result' = ffor children' $ fmap _child_result + liftIO $ writeIORef currentChildInstallations $! childInstallations0 + let placeInitialChild _ child = do + append . toNode $ _child_documentFragment child + liftIO $ (_childInstallation_triggerUpdateLocalMountState . _child_installation) child Mounted + return () + _ <- IntMap.traverseWithKey placeInitialChild children0 liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text) requestDomAction_ $ ffor children' $ \p -> do (oldUnready, oldP) <- liftIO $ readIORef pendingChange @@ -906,6 +1014,12 @@ hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ applyDomUpdate newP return (result0, result') +-- |Hoist a 'traverseWithKeyWithAdjust' or 'traverseWithKeyWithAdjustWithMove' in the base monad that underlies a +-- @'ImmediateDomBuilderT' m ~ 'RequesterT' t 'JSM' 'Identity' ('TriggerEventT' m)@. This is used to implement both 'traverseWithKeyWithAdjust' and +-- 'traverseWithKeyWithAdjustWithMove' for 'ImmediateDomBuilderT'. +-- +-- This is the core work which manages setting up the DOM structure and updating it over time on incoming patch events (@p k v@), deferring the specifics of +-- adjust only versus adjust and move to the caller by way of its @applyDomUpdate_@ parameter. {-# INLINABLE hoistTraverseWithKeyWithAdjust #-} hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p. ( Adjustable t m @@ -915,9 +1029,11 @@ hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p. , MonadJSM m , PrimMonad m , MonadFix m + , MonadRef m, Ref m ~ IORef + , MonadReflexCreateTrigger t m , Patch (p k v) , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int) - , Monoid (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) + , Monoid (p k (Compose (Child (ChildReadyState k)) v')) , Patch (p k (Constant Int)) ) => (forall vv vv'. @@ -925,23 +1041,30 @@ hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p. -> DMap k vv -> Event t (p k vv) -> RequesterT t JSM Identity (TriggerEventT t m) (DMap k vv', Event t (p k vv')) - ) -- ^ The base monad's traversal - -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv') -- ^ A way of mapping over the patch type - -> (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> DMap k (Constant (IORef (ChildReadyState k))) -> IO (DMap k (Constant (IORef (ChildReadyState k))))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state - -> (IORef (Map.Map (Some.Some k) DOM.Text) -> IORef DOM.Text -> p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> JSM ()) -- ^ Apply a patch to the DOM + ) + -- ^@base@: The base monad's 'Adjustable' traversal function to augment with DOM handling. + -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv') + -- ^@mapPatch@: A way of mapping over the patch type, e.g. 'mapPatchDMap' or 'mapPatchDMapWithMove' + -> (p k (Compose (Child (ChildReadyState k)) v') -> DMap k (Constant (IORef (ChildReadyState k))) -> IO (DMap k (Constant (IORef (ChildReadyState k))))) + -- ^@updateChildUnreadiness@: Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state + -> (IORef (Map.Map (Some.Some k) ChildInstallation) -> IORef DOM.Text -> p k (Compose (Child (ChildReadyState k)) v') -> JSM ()) + -- ^@applyDomUpdate_@: Apply a patch to the DOM -> (forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) + -- ^@f@: The function to apply to each pair in the initial 'DMap' or each pair that gets updated to produce the updated DOM structure for the value. -> DMap k v + -- ^@dm0@: The initial 'DMap' to traverse, which will be successively updated by patch events. -> Event t (p k v) + -- ^@dm'@: The stream of patch events to update @dm0@ with, applying @f@ for each change to build a new DOM structure. -> ImmediateDomBuilderT t m (DMap k v', Event t (p k v')) hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ (f :: forall a. k a -> v a -> ImmediateDomBuilderT t m (v' a)) (dm0 :: DMap k v) dm' = do initialEnv <- ImmediateDomBuilderT ask let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv - pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState k))), p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) <- liftIO $ newIORef mempty + pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState k))), p k (Compose (Child (ChildReadyState k)) v')) <- liftIO $ newIORef mempty haveEverBeenReady <- liftIO $ newIORef False - placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized" + currentChildInstallations <- liftIO $ newIORef $ error "currentChildInstallations not yet initialized" lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized" let applyDomUpdate p = do - applyDomUpdate_ placeholders lastPlaceholderRef p + applyDomUpdate_ currentChildInstallations lastPlaceholderRef p markSelfReady liftIO $ writeIORef pendingChange $! mempty markSelfReady = do @@ -969,7 +1092,7 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda when (DMap.null newUnready) $ do applyDomUpdate p (children0, children') <- ImmediateDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm' - let processChild k (Compose (_, _, sRef, _)) = ComposeMaybe <$> do + let processChild k (Compose Child { _child_readyState = sRef }) = ComposeMaybe <$> do readIORef sRef >>= \case ChildReadyState_Ready -> return Nothing ChildReadyState_Unready _ -> do @@ -981,11 +1104,15 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda else do modifyIORef' parentUnreadyChildren succ writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time - let result0 = DMap.map (\(Compose (_, _, _, v)) -> v) children0 - placeholders0 = weakenDMapWith (\(Compose (_, ph, _, _)) -> ph) children0 - result' = ffor children' $ mapPatch $ \(Compose (_, _, _, r)) -> r - liftIO $ writeIORef placeholders $! placeholders0 - _ <- DMap.traverseWithKey (\_ (Compose (df, _, _, _)) -> Constant () <$ append (toNode df)) children0 + let result0 = DMap.map (_child_result . getCompose) children0 + childInstallations0 = weakenDMapWith (\ (Compose Child { _child_installation = ci }) -> ci) children0 + result' = ffor children' $ mapPatch $ _child_result . getCompose + liftIO $ writeIORef currentChildInstallations $! childInstallations0 + let placeInitialChild _ (Compose child) = do + append . toNode $ _child_documentFragment child + liftIO $ (_childInstallation_triggerUpdateLocalMountState . _child_installation) child Mounted + return $ Constant () + _ <- DMap.traverseWithKey placeInitialChild children0 liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text) requestDomAction_ $ ffor children' $ \p -> do (oldUnready, oldP) <- liftIO $ readIORef pendingChange @@ -996,43 +1123,69 @@ hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpda applyDomUpdate newP return (result0, result') +-- |Helper which is used by 'hoistTraverseWithKeyWithAdjust' to create a 'Child' for some value in a 'DMap' which a DOM structure will be created for. +-- This is invoked for each value in the input 'DMap' as well as any updated or inserted value patched in. {-# INLINABLE drawChildUpdate #-} -drawChildUpdate :: (MonadIO m, MonadJSM m) +drawChildUpdate :: (MonadIO m, MonadJSM m, MonadHold t m, MonadRef m, MonadReflexCreateTrigger t m, Ref m ~ IORef, Reflex t, MonadHold t m) => ImmediateDomBuilderEnv t - -> (IORef (ChildReadyState k) -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready + -- ^@initialEnv@: The builder environment that the child will be placed in. + -> (IORef (ChildReadyState k) -> JSM ()) + -- ^@markReady@: Action to take when the child becomes ready, installed as the commit action when evaluating the DOM building function @child@. + -- This will NOT be called if the child is ready at initialization time; instead, the 'ChildReadyState' return value will be 'ChildReadyState_Ready'. This + -- is a natural consequence of this being installed as the commit action; commit actions are only called when the unready children count is larger than + -- zero and then hits zero. If the unready count starts at zero, they're not called. -> ImmediateDomBuilderT t m (v' a) - -> RequesterT t JSM Identity (TriggerEventT t m) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a) + -- ^@child@: Action to build the DOM for @v' a@ + -> RequesterT t JSM Identity (TriggerEventT t m) (Compose (Child (ChildReadyState k)) v' a) drawChildUpdate initialEnv markReady child = do childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing unreadyChildren <- liftIO $ newIORef 0 df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv - (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv - { _immediateDomBuilderEnv_parent = toNode df + let parentMountState = _immediateDomBuilderEnv_mountState initialEnv + (updateLocalMountState, triggerUpdateLocalMountState) <- newTriggerEvent + localMountState <- holdDyn Mounting updateLocalMountState + (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ ImmediateDomBuilderEnv + { _immediateDomBuilderEnv_document = _immediateDomBuilderEnv_document initialEnv + , _immediateDomBuilderEnv_parent = toNode df , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = markReady childReadyState + , _immediateDomBuilderEnv_mountState = zipDynWith min parentMountState localMountState } u <- liftIO $ readIORef unreadyChildren when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready - return $ Compose (df, placeholder, childReadyState, result) + return $ Compose $ Child df childReadyState (ChildInstallation placeholder triggerUpdateLocalMountState) result +-- |Helper which is used by 'hoistTraverseIntMapWithKeyWithAdjust' to create a 'Child' for some value in an 'IntMap' which a DOM structure will be created for. +-- This is invoked for each value in the input 'IntMap' as well as any updated or inserted value patched in. {-# INLINABLE drawChildUpdateInt #-} -drawChildUpdateInt :: (MonadIO m, MonadJSM m) +drawChildUpdateInt :: (MonadIO m, MonadJSM m, MonadRef m, Ref m ~ IORef, MonadReflexCreateTrigger t m, Reflex t, MonadHold t m) => ImmediateDomBuilderEnv t + -- ^@initialEnv@: The builder environment that the child will be placed in. -> (IORef ChildReadyStateInt -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready + -- ^@markReady@: Action to take when the child becomes ready, installed as the commit action when evaluating the DOM building function @child@. + -- This will NOT be called if the child is ready at initialization time; instead, the 'ChildReadyStateInt' return value will be 'ChildReadyStateInt_Ready'. This + -- is a natural consequence of this being installed as the commit action; commit actions are only called when the unready children count is larger than + -- zero and then hits zero. If the unready count starts at zero, they're not called. -> ImmediateDomBuilderT t m v' - -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') + -- ^@child@: Action to build the DOM for @v'@ + -> RequesterT t JSM Identity (TriggerEventT t m) (Child ChildReadyStateInt v') drawChildUpdateInt initialEnv markReady child = do childReadyState <- liftIO $ newIORef $ ChildReadyStateInt_Unready Nothing unreadyChildren <- liftIO $ newIORef 0 df <- createDocumentFragment $ _immediateDomBuilderEnv_document initialEnv - (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv - { _immediateDomBuilderEnv_parent = toNode df + let parentMountState = _immediateDomBuilderEnv_mountState initialEnv + (updateLocalMountState, triggerUpdateLocalMountState) <- newTriggerEvent + localMountState <- holdDyn Mounting updateLocalMountState + (placeholder, result) <- runReaderT (unImmediateDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ ImmediateDomBuilderEnv + { _immediateDomBuilderEnv_document = _immediateDomBuilderEnv_document initialEnv + , _immediateDomBuilderEnv_parent = toNode df , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = markReady childReadyState + , _immediateDomBuilderEnv_mountState = zipDynWith min parentMountState localMountState } u <- liftIO $ readIORef unreadyChildren when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready - return (df, placeholder, childReadyState, result) + return $ Child df childReadyState (ChildInstallation placeholder triggerUpdateLocalMountState) result mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool) mkHasFocus e = do diff --git a/reflex-dom-core/src/Reflex/Dom/Main.hs b/reflex-dom-core/src/Reflex/Dom/Main.hs index 2d8709d4..10c8162e 100644 --- a/reflex-dom-core/src/Reflex/Dom/Main.hs +++ b/reflex-dom-core/src/Reflex/Dom/Main.hs @@ -17,6 +17,8 @@ module Reflex.Dom.Main where import Prelude hiding (concat, mapM, mapM_, sequence, sequence_) +import Reflex.Class (holdDyn) +import Reflex.Dom.Builder.Class (MountState (Mounted, Mounting)) import Reflex.Dom.Builder.Immediate import Reflex.Dom.Class import Reflex.Host.Class @@ -117,9 +119,11 @@ mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do headFragment <- createDocumentFragment doc bodyElement <- getBodyUnchecked doc bodyFragment <- createDocumentFragment doc - (events, fc) <- liftIO . attachWidget'' $ \events -> do + ((events, postMountTriggerRef), fc) <- liftIO . attachWidget'' $ \events -> do let (headWidget, bodyWidget) = widgets (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef + (postMount, postMountTriggerRef) <- newEventWithTriggerRef + mountState <- holdDyn Mounting (Mounted <$ postMount) let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c go w df = do unreadyChildren <- liftIO $ newIORef 0 @@ -128,13 +132,17 @@ mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do , _immediateDomBuilderEnv_parent = toNode df , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = return () --TODO + , _immediateDomBuilderEnv_mountState = mountState } runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing rec b <- go (headWidget a) headFragment a <- go (bodyWidget b) bodyFragment - return (events, postBuildTriggerRef) + return ((events, postMountTriggerRef), postBuildTriggerRef) replaceElementContents headElement headFragment replaceElementContents bodyElement bodyFragment + liftIO . runSpiderHost $ do + mPostMountTrigger <- readRef postMountTriggerRef + forM_ mPostMountTrigger $ \ postMountTrigger -> runFireCommand fc [postMountTrigger :=> Identity ()] $ return () liftIO $ processAsyncEvents events fc replaceElementContents :: DOM.IsElement e => e -> DOM.DocumentFragment -> JSM () @@ -148,18 +156,24 @@ attachWidget' :: DOM.IsElement e => e -> JSContextSingleton x -> Widget x a -> J attachWidget' rootElement jsSing w = do doc <- getOwnerDocumentUnchecked rootElement df <- createDocumentFragment doc - ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do + ((a, events, postMountTriggerRef), fc) <- liftIO . attachWidget'' $ \events -> do (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef + (postMount, postMountTriggerRef) <- newEventWithTriggerRef + mountState <- holdDyn Mounting (Mounted <$ postMount) unreadyChildren <- liftIO $ newIORef 0 let builderEnv = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document = toDocument doc , _immediateDomBuilderEnv_parent = toNode df , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren , _immediateDomBuilderEnv_commitAction = return () --TODO + , _immediateDomBuilderEnv_mountState = mountState } a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing - return ((a, events), postBuildTriggerRef) + return ((a, events, postMountTriggerRef), postBuildTriggerRef) replaceElementContents rootElement df + liftIO . runSpiderHost $ do + mPostMountTrigger <- liftIO $ readRef postMountTriggerRef + forM_ mPostMountTrigger $ \ postMountTrigger -> runFireCommand fc [postMountTrigger :=> Identity ()] $ return () liftIO $ processAsyncEvents events fc return (a, fc) diff --git a/reflex-dom-core/src/Reflex/Dom/Old.hs b/reflex-dom-core/src/Reflex/Dom/Old.hs index ee9463c4..37ee8e8a 100644 --- a/reflex-dom-core/src/Reflex/Dom/Old.hs +++ b/reflex-dom-core/src/Reflex/Dom/Old.hs @@ -14,6 +14,7 @@ #endif module Reflex.Dom.Old ( MonadWidget + , MonadWidgetConstraints , El , ElConfig (..) , elConfig_namespace @@ -103,7 +104,6 @@ elConfig_attributes f (ElConfig a b) = (\b' -> ElConfig a b') <$> f b {-# INLINE elConfig_attributes #-} #endif ---TODO: HasDocument is still not accounted for type MonadWidgetConstraints t m = ( DomBuilder t m , DomBuilderSpace m ~ GhcjsDomSpace @@ -112,6 +112,7 @@ type MonadWidgetConstraints t m = , MonadSample t (Performable m) , MonadReflexCreateTrigger t m , PostBuild t m + , HasMountStatus t m , PerformEvent t m , MonadIO m , MonadIO (Performable m) diff --git a/reflex-dom-core/src/Reflex/Dom/Specializations.hs b/reflex-dom-core/src/Reflex/Dom/Specializations.hs index b60ffd34..7f46ea34 100644 --- a/reflex-dom-core/src/Reflex/Dom/Specializations.hs +++ b/reflex-dom-core/src/Reflex/Dom/Specializations.hs @@ -43,18 +43,18 @@ import qualified GHCJS.DOM.Types as DOM {-# SPECIALIZE append :: DOM.Node -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) () #-} -{-# SPECIALIZE drawChildUpdate :: forall k v' a. ImmediateDomBuilderEnv (SpiderTimeline Global) -> (IORef (ChildReadyState k) -> JSM ()) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (v' a) -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a) #-} +{-# SPECIALIZE drawChildUpdate :: forall k v' a. ImmediateDomBuilderEnv (SpiderTimeline Global) -> (IORef (ChildReadyState k) -> JSM ()) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (v' a) -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (Compose (Child (ChildReadyState k)) v' a) #-} {-# SPECIALIZE traverseIntMapWithKeyWithAdjust' :: forall v v'. (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v') -> IntMap v -> Event (SpiderTimeline Global) (PatchIntMap v) -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) (IntMap v', Event (SpiderTimeline Global) (PatchIntMap v')) #-} {-# SPECIALIZE hoistTraverseIntMapWithKeyWithAdjust :: forall v v'. - ( (IntMap.Key -> v -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) + ( (IntMap.Key -> v -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (Child ChildReadyStateInt v')) -> IntMap v -> Event (SpiderTimeline Global) (PatchIntMap v) - -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event (SpiderTimeline Global) (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))) + -> RequesterT (SpiderTimeline Global) JSM Identity (TriggerEventT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global)))) (IntMap (Child ChildReadyStateInt v'), Event (SpiderTimeline Global) (PatchIntMap (Child ChildReadyStateInt v'))) ) - -> (PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) - -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) + -> (PatchIntMap (Child ChildReadyStateInt v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) + -> (IORef (IntMap ChildInstallation) -> IORef DOM.Text -> PatchIntMap (Child ChildReadyStateInt v') -> JSM ()) -> (IntMap.Key -> v -> ImmediateDomBuilderT (SpiderTimeline Global) (WithJSContextSingleton () (PerformEventT Spider (SpiderHost Global))) v') -> IntMap v -> Event (SpiderTimeline Global) (PatchIntMap v) diff --git a/test/prebuild.hs b/test/prebuild.hs index 14f4e608..aa8401ed 100644 --- a/test/prebuild.hs +++ b/test/prebuild.hs @@ -24,7 +24,7 @@ import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove main :: IO () main = mainWidget w -w :: forall t m. (MonadWidget t m, DomRenderHook t m, MountableDomBuilder t m) => m () +w :: forall t m. (MonadWidget t m, DomRenderHook t m) => m () w = do let slow :: forall m'. (MonadWidget t m', DomRenderHook t m') => m' () {-