ghcjs-dom-0.9.9.2: src/GHCJS/DOM/Debug.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module GHCJS.DOM.Debug (
DomHasCallStack
, debugEnabled
, getElementStack
, addDebugMenu
, addDebugMenu'
) where
import Control.Arrow (Arrow(..))
import Control.Monad.IO.Class (MonadIO(..))
import Data.Char (toLower)
import Data.Foldable (forM_)
import Data.Monoid ((<>))
import System.IO (stderr, hPutStrLn)
import GHC.Stack
(srcLocStartLine, srcLocFile, SrcLoc(..), HasCallStack)
import GHC.Exts (Constraint)
import GHCJS.DOM (currentDocumentUnchecked)
import GHCJS.DOM.Types
(HTMLDivElement(..), HTMLStyleElement(..), uncheckedCastTo,
HTMLUListElement(..), MouseEvent(..), Document(..), Element(..),
HTMLAnchorElement(..), MonadJSM, JSM, liftJSM, JSString)
import GHCJS.DOM.Debug.Internal (DomHasCallStack, getElementStack, debugEnabled)
import GHCJS.DOM.Document
(createTextNode, getBodyUnchecked,
getElementsByTagName, createElement)
import GHCJS.DOM.DocumentOrShadowRoot (elementFromPoint)
import GHCJS.DOM.Element
(getTagName, setAttribute, getAttribute, setInnerHTML)
import GHCJS.DOM.EventM
(addListener, uiPageXY, mouseClientXY, mouseShiftKey, target,
newListener, on, preventDefault)
import GHCJS.DOM.EventTargetClosures (SaferEventListener(..))
import GHCJS.DOM.GlobalEventHandlers (click, contextMenu)
import GHCJS.DOM.HTMLCollection (itemUnchecked)
import GHCJS.DOM.HTMLStyleElement (setType)
import GHCJS.DOM.Node (getParentElement, appendChild_)
import GHCJS.DOM.NonElementParentNode (getElementById)
addDebugMenu :: MonadJSM m => m (JSM ())
addDebugMenu = addDebugMenu' (const Nothing)
addDebugMenu' :: MonadJSM m => (SrcLoc -> Maybe String) -> m (JSM ())
addDebugMenu' getSourceLink = liftJSM $ do
let menuId = "ghcjs-dom-debug" :: JSString
doc <- currentDocumentUnchecked
style <- uncheckedCastTo HTMLStyleElement <$> createElement doc ("style" :: JSString)
setType style ("text/css" :: JSString)
setInnerHTML style $ mconcat [
"#", menuId, ", #", menuId, " ul ul{"
, " display: block;"
, " position: absolute;"
, " box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);"
, " padding: 2px;"
, " z-index:1000;"
, " margin: 0;"
, " list-style-type: none;"
, " list-style: none;"
, " background-color:#fff;"
, " color:#000;"
, "}\n"
, "#", menuId, " ul{"
, " list-style-type: none;"
, " margin: 0;"
, " padding: 2px;"
, " cursor: pointer;"
, "}\n"
, "#", menuId, " li{"
, " white-space: nowrap;"
, "}\n"
, "#", menuId, " ul ul{"
, " display: none;"
, "}\n"
, "#", menuId, ">li:hover{"
, " background-color:#ddd;"
, "}\n"
, "#", menuId, " li:hover ul{"
, " display: inline-block;"
, " position: absolute;"
, " z-index:1001;"
, "}\n"
]
getElementsByTagName doc ("head" :: JSString) >>= (`itemUnchecked` 0) >>= (`appendChild_` style)
body <- getBodyUnchecked doc
clickListener <- newListener $ do
target >>= \case
Just (t :: HTMLAnchorElement) ->
getAttribute t ("hs-srcloc" :: JSString)
>>= mapM_ (liftIO . hPutStrLn stderr . ("OPEN " <>))
Nothing -> return ()
getElementById doc menuId >>= mapM_
(\e -> setAttribute e ("style" :: JSString) ("display: none;" :: JSString))
on doc contextMenu $ mouseShiftKey >>= \case
True -> ((fromIntegral *** fromIntegral) <$> mouseClientXY) >>= uncurry (elementFromPoint doc) >>= \case
Just e -> do
preventDefault
menu <- getElementById doc menuId >>= \case
Just menu -> setInnerHTML menu ("" :: JSString) >> return (uncheckedCastTo HTMLDivElement menu)
Nothing -> do
menu <- uncheckedCastTo HTMLDivElement <$> createElement doc ("div" :: JSString)
setAttribute menu ("id" :: JSString) menuId
appendChild_ body menu
return menu
if debugEnabled
then do
ul <- uncheckedCastTo HTMLUListElement <$> createElement doc ("ul" :: JSString)
appendChild_ menu ul
addMenu doc clickListener ul e
else do
a <- uncheckedCastTo HTMLAnchorElement <$> createElement doc ("a" :: JSString)
appendChild_ menu a
createTextNode doc ("The ghcjs-dom debug cabal flag is switched off" :: JSString)
>>= appendChild_ a
setAttribute a ("href" :: JSString)
("https://github.com/ghcjs/ghcjs-dom/blob/master/README.md#debug" :: JSString)
setAttribute a ("target" :: JSString) ("_blank" :: JSString)
liftJSM $ addListener a click clickListener False
(pageX, pageY) <- uiPageXY
setAttribute menu ("style" :: JSString) ("display: block; left: " <> show pageX <> "px; top:" <> show pageY <> "px;" )
Nothing -> return ()
False -> return ()
where
addMenu :: MonadJSM m => Document -> SaferEventListener HTMLAnchorElement MouseEvent -> HTMLUListElement -> Element -> m ()
addMenu doc clickListener parentMenu = loop
where
loop e = do
getElementStack e >>= mapM_ (addElementMenu doc clickListener parentMenu e)
getParentElement e >>= mapM_ loop
addElementMenu
:: MonadJSM m
=> Document
-> SaferEventListener HTMLAnchorElement MouseEvent
-> HTMLUListElement
-> Element
-> [(String, SrcLoc)]
-> m ()
addElementMenu doc clickListener parentMenu e cs = do
tagName <- map toLower <$> getTagName e
parentLi <- createElement doc ("li" :: JSString)
appendChild_ parentMenu parentLi
createTextNode doc tagName >>= appendChild_ parentLi
ul <- createElement doc ("ul" :: JSString)
appendChild_ parentLi ul
forM_ (reverse cs) $ \(f, loc) -> do
li <- createElement doc ("li" :: JSString)
appendChild_ ul li
a <- uncheckedCastTo HTMLAnchorElement <$> createElement doc ("a" :: JSString)
appendChild_ li a
createTextNode doc (f <> " " <> srcLocFile loc <> ":" <> show (srcLocStartLine loc))
>>= appendChild_ a
forM_ (getSourceLink loc) $ \link -> do
setAttribute a ("href" :: JSString) link
setAttribute a ("target" :: JSString) ("_blank" :: JSString)
setAttribute a ("hs-srcloc" :: JSString) $ prettySrcLoc loc
liftJSM $ addListener a click clickListener False
prettySrcLoc :: SrcLoc -> String
prettySrcLoc SrcLoc {..} = mconcat
[ srcLocFile , ":("
, show srcLocStartLine, ","
, show srcLocStartCol , ")-("
, show srcLocEndLine , ","
, show srcLocEndCol , ") in "
, srcLocPackage, ":", srcLocModule
]