1{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE DataKinds #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE OverloadedStrings #-}
9{-# LANGUAGE RankNTypes #-}
10{-# LANGUAGE RecursiveDo #-}
11{-# LANGUAGE ScopedTypeVariables #-}
12{-# LANGUAGE TypeFamilies #-}
13module Reflex.Dom.Widget.Basic
18 -- * Displaying Values
26 -- * Working with Maps
33 -- * Widgets on Collections
36 , listWithKeyShallowDiff
38 , selectViewListWithKey
39 , selectViewListWithKey_
41 -- * Creating DOM Elements
48 -- ** With Element Results
55 , dynamicAttributesToModifyAttributes
61 -- * Specific DOM Elements
82import Reflex.Class as Reflex
83import Reflex.Dom.Builder.Class
84import Reflex.Dom.Class
85import Reflex.Dom.Internal.Foreign ()
87import Reflex.PostBuild.Class
90import Control.Lens hiding (children, element)
91import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_, sequence, sequence_)
96import Data.Functor.Misc
98import qualified Data.Map as Map
102import qualified Data.Set as Set
103import Data.Text (Text)
104import qualified Data.Text as T
106import Data.Traversable
107import Prelude hiding (mapM, mapM_, sequence, sequence_)
109widgetHoldInternal :: DomBuilder t m => m a -> Event t (m b) -> m (a, Event t b)
110widgetHoldInternal child0 child' = do
111 childResult0 <- deletable (void child') child0
112 childResult' <- placeholder $ def & placeholderConfig_insertAbove .~ fmap (deletable (void child')) child'
113 return (childResult0, _placeholder_insertedAbove childResult')
115-- | Breaks the given Map into pieces based on the given Set. Each piece will contain only keys that are less than the key of the piece, and greater than or equal to the key of the piece with the next-smaller key. There will be one additional piece containing all keys from the original Map that are larger or equal to the largest key in the Set.
116-- Either k () is used instead of Maybe k so that the resulting map of pieces is sorted so that the additional piece has the largest key.
117-- No empty pieces will be included in the output.
119--TODO: This can probably be done more efficiently by dividing and conquering, re-using the structure of the Set instead of going through the Set linearally
120partitionMapBySetLT :: forall k v. Ord k => Set k -> Map k v -> Map (Either k ()) (Map k v)
121partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0
122 where go :: [k] -> Map k v -> [(Either k (), Map k v)]
123 go [] m = if Map.null m
126 go (h:t) m = let (lt, eq, gt) = Map.splitLookup h m
127 geq = maybe id (Map.insert h) eq gt
130 else (Left h, lt) : go t geq
132newtype ChildResult t k a = ChildResult { unChildResult :: (a, Event t (Map k (Maybe (ChildResult t k a)))) }
134listHoldWithKey :: forall t m k v a. (Ord k, DomBuilder t m, MonadHold t m, MonadFix m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> m a) -> m (Dynamic t (Map k a))
135listHoldWithKey initialChildren modifyChildren buildChild = do
136 let deleteChildSelector = fanMap $ fmap void modifyChildren
137 liveChildren :: Dynamic t (Set k) <- foldDyn applyMapKeysSet (Map.keysSet initialChildren) modifyChildren
138 let placeChildSelector = fanMap $ attachWith partitionMapBySetLT (current liveChildren) $ fmap (Map.mapMaybe id) modifyChildren
139 buildAugmentedChild :: k -> v -> m (ChildResult t k a)
140 buildAugmentedChild k v = do
141 let delete = select deleteChildSelector $ Const2 k
143 & insertAbove .~ fmap (imapM buildAugmentedChild) (select placeChildSelector $ Const2 $ Left k)
144 & deleteSelf .~ delete
145 ph <- placeholder myCfg
146 result <- deletable delete $ buildChild k v
147 return $ ChildResult (result, (fmap Just <$> _placeholder_insertedAbove ph) <> (Map.singleton k Nothing <$ _placeholder_deletedSelf ph)) --Note: we could also use the "deleted" output on deletable, if it had one; we're using this so that everything changes all at once, instead of deletions being prompt and insertions being delayed
148 rec initialAugmentedResults <- iforM initialChildren buildAugmentedChild
149 augmentedResults <- foldDyn applyMap initialAugmentedResults $ newInsertedBelow <> newInsertedAbove
150 let newInsertedAbove = switch $ mconcat . reverse . fmap (snd . unChildResult) . Map.elems <$> current augmentedResults
151 belowAll <- placeholder $ def & placeholderConfig_insertAbove .~ fmap (imapM buildAugmentedChild) (select placeChildSelector $ Const2 $ Right ())
152 let newInsertedBelow = fmap Just <$> _placeholder_insertedAbove belowAll
153 return $ fmap (fmap (fst . unChildResult)) augmentedResults
155text :: DomBuilder t m => Text -> m ()
156text t = void $ textNode $ def & textNodeConfig_initialContents .~ t
158dynText :: forall t m. (PostBuild t m, DomBuilder t m) => Dynamic t Text -> m ()
160 postBuild <- getPostBuild
161 void $ textNode $ (def :: TextNodeConfig t) & textNodeConfig_setContents .~ leftmost
163 , tag (current t) postBuild
166display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m ()
167display = dynText . fmap (T.pack . show)
169button :: DomBuilder t m => Text -> m (Event t ())
171 (e, _) <- element "button" def $ text t
172 return $ domEvent Click e
174--TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'?
175-- | Given a Dynamic of widget-creating actions, create a widget that is recreated whenever the Dynamic updates.
176-- The returned Event of widget results occurs when the Dynamic does.
177-- Note: Often, the type 'a' is an Event, in which case the return value is an Event-of-Events that would typically be flattened (via 'switchPromptly').
178dyn :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a)
180 postBuild <- getPostBuild
181 let newChild = leftmost [updated child, tag (current child) postBuild]
182 snd <$> widgetHoldInternal (return ()) newChild
184-- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires.
185-- The returned Dynamic of widget results occurs when the Event does.
186-- Note: Often, the type 'a' is an Event, in which case the return value is a Dynamic-of-Events that would typically be flattened.
187widgetHold :: (DomBuilder t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a)
188widgetHold child0 newChild = do
189 (result0, newResult) <- widgetHoldInternal child0 newChild
190 holdDyn result0 newResult
192diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v)
193diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case
194 This _ -> Just Nothing
195 These _ new -> Just $ Just new
196 That new -> Just $ Just new
198diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v)
199diffMap olds news = flip Map.mapMaybe (align olds news) $ \case
200 This _ -> Just Nothing
202 | old == new -> Nothing
203 | otherwise -> Just $ Just new
204 That new -> Just $ Just new
206applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v
207applyMap patch old = insertions `Map.union` (old `Map.difference` deletions)
208 where (deletions, insertions) = mapPartitionEithers $ maybeToEither <$> patch
209 maybeToEither = \case
213mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b)
214mapPartitionEithers m = (fromLeft <$> ls, fromRight <$> rs)
215 where (ls, rs) = Map.partition isLeft m
216 fromLeft (Left l) = l
217 fromLeft _ = error "mapPartitionEithers: fromLeft received a Right value; this should be impossible"
218 fromRight (Right r) = r
219 fromRight _ = error "mapPartitionEithers: fromRight received a Left value; this should be impossible"
221-- | Apply a map patch to a set
222-- > applyMapKeysSet patch (Map.keysSet m) == Map.keysSet (applyMap patch m)
223applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k
224applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions)
225 where (insertions, deletions) = Map.partition isJust patch
227--TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value
228listWithKey :: 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))
229listWithKey vals mkChild = do
230 postBuild <- getPostBuild
231 rec sentVals :: Dynamic t (Map k v) <- foldDyn applyMap Map.empty changeVals
232 let changeVals :: Event t (Map k (Maybe v))
233 changeVals = attachWith diffMapNoEq (current sentVals) $ leftmost
235 , 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
237 listWithKeyShallowDiff Map.empty changeVals $ \k v0 dv -> do
238 mkChild k =<< holdDyn v0 dv
240{-# DEPRECATED listWithKey' "listWithKey' has been renamed to listWithKeyShallowDiff; also, its behavior has changed to fix a bug where children were always rebuilt (never updated)" #-}
241listWithKey' :: (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))
242listWithKey' = listWithKeyShallowDiff
244-- | 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.
245listWithKeyShallowDiff :: (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))
246listWithKeyShallowDiff initialVals valsChanged mkChild = do
247 let childValChangedSelector = fanMap $ fmap (Map.mapMaybe id) valsChanged
248 sentVals <- foldDyn applyMap Map.empty $ fmap (fmap void) valsChanged
249 let relevantPatch patch _ = case patch of
250 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
251 Just _ -> Nothing -- We don't want to let spurious re-creations of items through
252 listHoldWithKey initialVals (attachWith (flip (Map.differenceWith relevantPatch)) (current sentVals) valsChanged) $ \k v ->
253 mkChild k v $ Reflex.select childValChangedSelector $ Const2 k
255--TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value
256-- | Create a dynamically-changing set of Event-valued widgets.
257-- 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'.
258listViewWithKey :: (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))
259listViewWithKey vals mkChild = switch . fmap mergeMap <$> listViewWithKey' vals mkChild
261listViewWithKey' :: (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))
262listViewWithKey' vals mkChild = current <$> listWithKey vals mkChild
264-- | Create a dynamically-changing set of widgets, one of which is selected at any time.
265selectViewListWithKey :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m)
266 => Dynamic t k -- ^ Current selection key
267 -> Dynamic t (Map k v) -- ^ Dynamic key/value map
268 -> (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
269 -> m (Event t (k, a)) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget.
270selectViewListWithKey selection vals mkChild = do
271 let selectionDemux = demux selection -- For good performance, this value must be shared across all children
272 selectChild <- listWithKey vals $ \k v -> do
273 let selected = demuxed selectionDemux k
274 selectSelf <- mkChild k v selected
275 return $ fmap ((,) k) selectSelf
276 return $ switchPromptlyDyn $ leftmost . Map.elems <$> selectChild
278selectViewListWithKey_ :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m)
279 => Dynamic t k -- ^ Current selection key
280 -> Dynamic t (Map k v) -- ^ Dynamic key/value map
281 -> (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
282 -> m (Event t k) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget.
283selectViewListWithKey_ selection vals mkChild = fmap fst <$> selectViewListWithKey selection vals mkChild
285-- | Create a DOM element
286-- > el "div" (text "Hello World")
287-- <div>Hello World</div>
289el :: forall t m a. DomBuilder t m => Text -> m a -> m a
290el elementTag child = snd <$> el' elementTag child
292-- | Create a DOM element with attributes
293-- > elAttr "a" ("href" =: "http://google.com") (text "Google!")
294-- <a href="http://google.com">Google!</a>
295{-# INLINABLE elAttr #-}
296elAttr :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m a
297elAttr elementTag attrs child = snd <$> elAttr' elementTag attrs child
299-- | Create a DOM element with classes
300-- > elClass "div" "row" (return ())
301-- <div class="row"></div>
302{-# INLINABLE elClass #-}
303elClass :: forall t m a. DomBuilder t m => Text -> Text -> m a -> m a
304elClass elementTag c child = snd <$> elClass' elementTag c child
306-- | Create a DOM element with Dynamic Attributes
307-- > elClass "div" (constDyn ("class" =: "row")) (return ())
308-- <div class="row"></div>
309{-# INLINABLE elDynAttr #-}
310elDynAttr :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t (Map Text Text) -> m a -> m a
311elDynAttr elementTag attrs child = snd <$> elDynAttr' elementTag attrs child
313-- | Create a DOM element with a Dynamic Class
314-- > elDynClass "div" (constDyn "row") (return ())
315-- <div class="row"></div>
316{-# INLINABLE elDynClass #-}
317elDynClass :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t Text -> m a -> m a
318elDynClass elementTag c child = snd <$> elDynClass' elementTag c child
320-- | Create a DOM element and return the element
321-- > do (e, _) <- el' "div" (text "Click")
322-- > return $ domEvent Click e
324el' :: forall t m a. DomBuilder t m => Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
325el' elementTag = element elementTag def
327-- | Create a DOM element with attributes and return the element
328{-# INLINABLE elAttr' #-}
329elAttr' :: forall t m a. DomBuilder t m => Text -> Map Text Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
330elAttr' elementTag attrs = element elementTag $ def
331 & initialAttributes .~ Map.mapKeys (AttributeName Nothing) attrs
333-- | Create a DOM element with a class and return the element
334{-# INLINABLE elClass' #-}
335elClass' :: forall t m a. DomBuilder t m => Text -> Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
336elClass' elementTag c = elAttr' elementTag ("class" =: c)
338-- | Create a DOM element with Dynamic Attributes and return the element
339{-# INLINABLE elDynAttr' #-}
340elDynAttr' :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t (Map Text Text) -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
341elDynAttr' = elDynAttrNS' Nothing
343-- | Create a DOM element with a Dynamic class and return the element
344{-# INLINABLE elDynClass' #-}
345elDynClass' :: forall t m a. (DomBuilder t m, PostBuild t m) => Text -> Dynamic t Text -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
346elDynClass' elementTag c = elDynAttr' elementTag (fmap ("class" =:) c)
348{-# INLINABLE elDynAttrNS' #-}
349elDynAttrNS' :: forall t m a. (DomBuilder t m, PostBuild t m) => Maybe Text -> Text -> Dynamic t (Map Text Text) -> m a -> m (Element EventResult (DomBuilderSpace m) t, a)
350elDynAttrNS' mns elementTag attrs child = do
351 modifyAttrs <- dynamicAttributesToModifyAttributes attrs
353 & elementConfig_namespace .~ mns
354 & modifyAttributes .~ fmap mapKeysToAttributeName modifyAttrs
355 element elementTag cfg child
357dynamicAttributesToModifyAttributes :: (Ord k, PostBuild t m) => Dynamic t (Map k Text) -> m (Event t (Map k (Maybe Text)))
358dynamicAttributesToModifyAttributes = dynamicAttributesToModifyAttributesWithInitial mempty
360dynamicAttributesToModifyAttributesWithInitial :: (Ord k, PostBuild t m) => Map k Text -> Dynamic t (Map k Text) -> m (Event t (Map k (Maybe Text)))
361dynamicAttributesToModifyAttributesWithInitial attrs0 d = do
362 postBuild <- getPostBuild
363 let modificationsNeeded = flip pushAlways (align postBuild $ updated d) $ \case
365 new <- sample $ current d
366 return $ diffMap attrs0 new
367 These () new -> return $ diffMap attrs0 new
369 old <- sample $ current d
370 return $ diffMap old new
371 return modificationsNeeded
373--------------------------------------------------------------------------------
374-- Copied and pasted from Reflex.Widget.Class
375--------------------------------------------------------------------------------
377-- | Create a dynamically-changing set of widgets from a Dynamic key/value map.
378-- Unlike the 'withKey' variants, the child widgets are insensitive to which key they're associated with.
379list :: (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))
380list dm mkChild = listWithKey dm (\_ dv -> mkChild dv)
382-- | Create a dynamically-changing set of widgets from a Dynamic list.
383simpleList :: (DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m) => Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a])
384simpleList xs mkChild = fmap (fmap (map snd . Map.toList)) $ flip list mkChild $ fmap (Map.fromList . zip [(1::Int)..]) xs
387schedulePostBuild x = performEvent_ . (x <$) =<< getPostBuild
389elDynHtml' :: DomBuilder t m => Text -> Dynamic t Text -> m (El t)
390elDynHtml' elementTag html = do
391 e <- buildEmptyElement elementTag (Map.empty :: Map Text Text)
392 schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
393 performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
394 wrapElement defaultDomEventHandler e
396elDynHtmlAttr' :: DomBuilder t m => Text -> Map Text Text -> Dynamic t Text -> m (El t)
397elDynHtmlAttr' elementTag attrs html = do
398 e <- buildEmptyElement elementTag attrs
399 schedulePostBuild $ setInnerHTML e . Just =<< sample (current html)
400 performEvent_ $ fmap (setInnerHTML e . Just) $ updated html
401 wrapElement defaultDomEventHandler e
405 = Link { _link_clicked :: Event t ()
408linkClass :: DomBuilder t m => Text -> Text -> m (Link t)
410 (l,_) <- elAttr' "a" ("class" =: c) $ text s
411 return $ Link $ domEvent Click l
413link :: DomBuilder t m => Text -> m (Link t)
414link s = linkClass s ""
416divClass :: forall t m a. DomBuilder t m => Text -> m a -> m a
417divClass = elClass "div"
419dtdd :: forall t m a. DomBuilder t m => Text -> m a -> m a
424blank :: forall m. Monad m => m ()
427newtype Workflow t m a = Workflow { unWorkflow :: m (a, Event t (Workflow t m a)) }
429workflow :: forall t m a. (DomBuilder t m, MonadFix m, MonadHold t m) => Workflow t m a -> m (Dynamic t a)
431 rec eResult <- widgetHold (unWorkflow w0) $ fmap unWorkflow $ switch $ snd <$> current eResult
432 return $ fmap fst eResult
434workflowView :: forall t m a. (DomBuilder t m, MonadFix m, MonadHold t m, PostBuild t m) => Workflow t m a -> m (Event t a)
436 rec eResult <- dyn . fmap unWorkflow =<< holdDyn w0 eReplace
437 eReplace <- fmap switch $ hold never $ fmap snd eResult
438 return $ fmap fst eResult
440mapWorkflow :: (DomBuilder t m) => (a -> b) -> Workflow t m a -> Workflow t m b
441mapWorkflow f (Workflow x) = Workflow (fmap (f *** fmap (mapWorkflow f)) x)
443-- | A widget to display a table with static columns and dynamic rows.
444tableDynAttr :: forall t m r k v. (Ord k, DomBuilder t m, MonadHold t m, PostBuild t m, MonadFix m)
445 => Text -- ^ Class applied to <table> element
446 -> [(Text, k -> Dynamic t r -> m v)] -- ^ Columns of (header, row key -> row value -> child widget)
447 -> Dynamic t (Map k r) -- ^ Map from row key to row value
448 -> (k -> m (Dynamic t (Map Text Text))) -- ^ Function to compute <tr> element attributes from row key
449 -> m (Dynamic t (Map k (Element EventResult (DomBuilderSpace m) t, [v]))) -- ^ Map from row key to (El, list of widget return values)
450tableDynAttr klass cols dRows rowAttrs = elAttr "div" (Map.singleton "style" "zoom: 1; overflow: auto; background: white;") $ do
451 elAttr "table" (Map.singleton "class" klass) $ do
452 el "thead" $ el "tr" $ do
453 mapM_ (\(h, _) -> el "th" $ text h) cols
455 listWithKey dRows (\k r -> do
457 elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols)
459-- | A widget to construct a tabbed view that shows only one of its child widgets at a time.
460-- Creates a header bar containing a <ul> with one <li> per child; clicking a <li> displays
461-- the corresponding child and hides all others.
462tabDisplay :: forall t m k. (MonadFix m, DomBuilder t m, MonadHold t m, PostBuild t m, Ord k)
463 => Text -- ^ Class applied to <ul> element
464 -> Text -- ^ Class applied to currently active <li> element
465 -> Map k (Text, m ()) -- ^ Map from (arbitrary) key to (tab label, child widget)
467tabDisplay ulClass activeClass tabItems = do
468 let t0 = listToMaybe $ Map.keys tabItems
469 rec currentTab :: Demux t (Maybe k) <- elAttr "ul" ("class" =: ulClass) $ do
470 tabClicksList :: [Event t k] <- Map.elems <$> imapM (\k (s,_) -> headerBarLink s k $ demuxed currentTab (Just k)) tabItems
471 let eTabClicks :: Event t k = leftmost tabClicksList
472 fmap demux $ holdDyn t0 $ fmap Just eTabClicks
474 iforM_ tabItems $ \k (_, w) -> do
475 let isSelected = demuxed currentTab $ Just k
476 attrs = ffor isSelected $ \s -> if s then Map.empty else Map.singleton "style" "display:none;"
477 elDynAttr "div" attrs w
480 headerBarLink :: Text -> k -> Dynamic t Bool -> m (Event t k)
481 headerBarLink x k isSelected = do
482 let attrs = fmap (\b -> if b then Map.singleton "class" activeClass else Map.empty) isSelected
483 elDynAttr "li" attrs $ do
485 return $ fmap (const k) (_link_clicked a)
487class HasAttributes a where
489 attributes :: Lens' a (Attrs a)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list