{-# LANGUAGE CPP #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE RankNTypes #-} #ifdef DEVELOPMENT {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} #endif {-# OPTIONS_GHC -fno-warn-redundant-constraints #-} module Shpadoinkle.DeveloperTools (withDeveloperTools) where import Language.Javascript.JSaddle import UnliftIO #ifdef DEVELOPMENT import Control.Lens import Control.Monad import Control.Monad.STM (retry) import UnliftIO.Concurrent #endif default (JSString) #ifdef DEVELOPMENT withDeveloperTools :: forall a. Eq a => Read a => Show a => TVar a -> JSM () withDeveloperTools x = do i' <- readTVarIO x y <- newTVarIO i' outputState i' syncPoint listenForSetState x () <$ forkIO (f y) where f y = do x' <- atomically $ do y' :: a <- readTVar y x' :: a <- readTVar x if x' == y' then retry else x' <$ writeTVar y x' outputState x' f y outputState :: forall a. Show a => a -> JSM () outputState x = void . (try :: forall b. JSM b -> JSM (Either SomeException b)) $ do o <- obj (o <# "type") "shpadoinkle_output_state" (o <# "msg") $ toJSString $ show x jsg "window" ^. js2 "postMessage" o "*" listenForSetState :: forall a. Read a => TVar a -> JSM () listenForSetState model = void $ jsg "window" ^. js2 "addEventListener" "message" (fun $ \_ _ args -> do let e = Prelude.head args isWindow <- strictEqual (e ^. js "source") (jsg "window") d <- e ^. js "data" isRightType <- strictEqual (d ^. js "type") "shpadoinkle_set_state" msg <- fromJSVal =<< (d ^. js "msg") case msg of Just msg' | isWindow && isRightType -> atomically . writeTVar model $ read msg' _ -> return ()) #else withDeveloperTools :: forall a. Eq a => Read a => Show a => TVar a -> JSM () withDeveloperTools = const $ pure () #endif