react-flux-0.9.2: example/routing/TabbedApps.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
-- |
module TabbedApps (TabbedAction(..)
, TabbedState(..)
, Tab(..)
, ParentRouter
, dispatch
, newStore
, view
, view_) where
import React.Flux hiding (view)
import qualified React.Flux as RF
import Router
import Types
import Control.Applicative ((<|>))
import Control.DeepSeq
import qualified Data.Aeson as A
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import qualified Data.Text.Read as TR
import Data.Typeable (Typeable)
import GHC.Generics (Generic)
import qualified Web.Routes as WR
type ParentRouter = Maybe ([T.Text] -> T.Text)
data Tab = Tab {tabName :: AppName
, tabView :: ParentRouter -> AppView ()
, tabRouter :: Maybe AppRouter
}
data TabbedState =
TabbedState { tsFocus :: !Int
, tsTabs :: ![Tab]
}
deriving Typeable
data TabbedAction = SwitchApp !Int (Maybe [T.Text])
| TabbedInit
deriving (Show, Typeable, Generic, NFData)
instance WR.PathInfo TabbedAction where
toPathSegments (SwitchApp aidx apath) =
"switchapp":WR.toPathSegments aidx ++ fromMaybe [] apath
toPathSegments TabbedInit = ["tabbedinit"]
fromPathSegments = SwitchApp <$ WR.segment "switchapp"
<*> WR.pToken ("app-num"::String) intParser
<*> WR.patternParse subRouteParser
<|> TabbedInit <$ WR.segment "tabbedinit"
where
intParser v =
case TR.decimal v of
Right (aidx, "") -> Just aidx
_ -> Nothing
subRouteParser apath =
Right $ if null apath then Nothing else Just apath
instance Show TabbedState where
showsPrec prec TabbedState{..} =
showsPrec prec ("TabbedState" :: String, tsFocus, map tabName tsTabs)
instance StoreData TabbedState where
type StoreAction TabbedState = TabbedAction
transform action st@TabbedState{..} = do
putStrLn $ "Action: " ++ show action
case action of
TabbedInit ->
return st
SwitchApp idx tabRoute
| idx >= 0 && idx <= length tsTabs -> do
let Tab{..} = tsTabs !! idx
case (tabRouter, tabRoute) of
(Just tr, Just rt) ->
tr rt
_ ->
return ()
return $ st{tsFocus = idx}
| otherwise ->
error $ "Application index is out of range " ++ show idx
newStore :: [Tab] -> ReactStore TabbedState
newStore tabs = mkStore $ TabbedState 0 tabs
view :: ReactStore TabbedState -> ParentRouter -> ReactView TabbedState
view _ prouter = defineView "tabbed" $ \TabbedState{..} ->
div_ $ do
div_ ["className" $= "tabbed-app-picker"] $
mapM_ (tabItem_ . ((prouter,tsFocus),)) $ zip [0..] $ map tabName tsTabs
div_ ["className" $= "tabbed-internal-app"] $
if tsFocus < length tsTabs
then tabView (tsTabs !! tsFocus) (Just $ router tsFocus)
else mempty
where
router cur action =
actionRoute prouter $ SwitchApp cur (Just $ childRoutePath action)
tabItem :: ReactView ((ParentRouter, Int), (Int, AppName))
tabItem =
defineView "tab-item" $ \((prouter, cur), (aidx, aname)) ->
span_ ["style" @= A.object ["color" A..= ("#eee"::String)] | cur == aidx] $
if cur == aidx
then elemText aname
else a_ ["href" $= actionRoute prouter (SwitchApp aidx Nothing)] $ elemText aname
tabItem_ :: ((ParentRouter, Int), (Int, AppName)) -> ReactElementM eventHandler ()
tabItem_ tab =
viewWithKey tabItem (fst $ snd tab) tab mempty
view_ :: ReactStore TabbedState -> ParentRouter -> TabbedState -> ReactElementM eventHandler ()
view_ rst pr st =
RF.view (view rst pr) st mempty
dispatch :: ReactStore TabbedState -> TabbedAction -> [SomeStoreAction]
dispatch rst a = [SomeStoreAction rst a]