typed-admin-0.1.0.0: src/TypedAdmin/Class.hs
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module TypedAdmin.Class where
import Control.Monad.Base (MonadBase)
import Control.Monad.Catch hiding (Handler)
import Control.Monad.IO.Class
import Control.Monad.State.Class
import Control.Monad.State.Strict
import Control.Monad.Trans.Control (MonadBaseControl)
import Data.ByteString
import Data.Generics.Product.Subtype
import Data.Proxy
import Data.Text (Text)
import qualified Data.Yaml as Y
import GHC.Generics
import Lucid (HtmlT (..))
import Network.HTTP.Types
type PathText = Text
type Page = Int
type Dic = Y.Object
data Context = Context
{ dic :: Maybe Dic
, localeHierarchy :: [Text]
}
class MonadState Context m => L18N m
-- class Monad m => MonadState' r (m :: * -> *) | m -> r where
-- ask' :: m r
-- local' :: (r -> r) -> m a -> m a
-- reader' :: (r -> a) -> m a
-- instance Monad m => MonadState' r (ReaderT r m) where
-- ask' = ask
-- local' = local
-- reader' = reader
-- instance MonadState' r m => MonadState' r (HtmlT m) where
-- ask' = lift ask'
-- local' f a = a
-- asks' :: MonadState' r m => (r -> a) -> m a
-- asks' = reader'
newtype Handler a = Handler
{ runHandler :: StateT Context IO a
} deriving
( Functor
, Applicative
, Monad
, MonadIO
, MonadState Context
, MonadBase IO
, MonadBaseControl IO
, MonadThrow
, MonadCatch
)
-- class ToName a where
-- toName :: proxy a -> Text
class HasHeader a where
hasHeader :: (Monad m, MonadState Context m) => proxy a -> [HtmlT m ()]
default hasHeader:: (Monad m, MonadState Context m, Generic a, GHasHeader (Rep a)) => proxy a -> [HtmlT m ()]
hasHeader x = gHasHeader (Proxy :: Proxy (Rep a))
class GHasHeader (f :: * -> *) where
gHasHeader :: (Monad m, MonadState Context m) => proxy f -> [HtmlT m ()]
class ToDetailField a where
toDetailField :: Monad m => a -> HtmlT m ()
class MonadIO m => ToDetail m f where
toDetail :: f -> m [(Text, HtmlT m ())]
default toDetail :: (Generic f, GToDetail (Rep f)) => f -> m [(Text, HtmlT m ())]
toDetail x = do
gToDetail (from x)
-- case linkDetail x of
-- Just p ->
-- td_ [] $ a_ [href_ p] "detail"
-- Nothing -> return ()
linkDetail :: f -> m (Maybe PathText)
linkDetail _ = return Nothing
linkEdit :: f -> m (Maybe PathText)
linkEdit _ = return Nothing
detailTitle :: proxy f -> m Text
default detailTitle :: (Generic f, GToDetail (Rep f)) => proxy f -> m Text
detailTitle x = gDetailTitle (Proxy :: Proxy (Rep f))
class GToDetail (f :: * -> *) where
gToDetail :: (MonadIO m, Monad m2) => f a -> m [(Text, HtmlT m2 ())]
gDetailTitle :: (Monad m) => proxy f -> m Text
class Monad m => ToForm m a where
toForm :: Maybe a -> m [(Text, HtmlT m ())]
default toForm :: (Generic a, GToForm m (Rep a)) => Maybe a -> m [(Text, HtmlT m ())]
toForm x = gToForm (from <$> x)
fromForm :: [(ByteString, Maybe ByteString)] -> m (Either String a)
default fromForm :: (Generic a, GToForm m (Rep a)) => [(ByteString, Maybe ByteString)] -> m (Either String a)
fromForm x = fmap to <$> gFromForm x
class Monad m => GToForm m (f :: * -> *) where
gToForm :: Maybe (f a) -> m [(Text, HtmlT m ())]
gFromForm :: [(ByteString, Maybe ByteString)] -> m (Either String (f a))
class Monad m => FormField m a where
toFormField :: Text -> Maybe a -> HtmlT m ()
fromFormField :: [(ByteString, Maybe ByteString)] -> ByteString -> m (Maybe a)
-- todo: wrap [a]
class (HasHeader a, ToDetail m a, ToForm m b) => ListConsole m a b where
list :: (Maybe b) -> Page -> m ([a])
total :: Maybe b -> proxy a -> m (Maybe Int)
total _ _ = pure Nothing
listSublayout :: Maybe b -> [a] -> HtmlT m () -> HtmlT m ()
listSublayout _ _ x = x
-- todo wrap a
class (ToDetail m a, Read (Ident a)) => DetailConsole m a where
type Ident a
detail :: Ident a -> m (Maybe a)
detailSublayout :: a -> HtmlT m () -> HtmlT m ()
detailSublayout _ x = x
-- class (ToForm m a, MonadIO m) => CreateConsole m a where
-- create :: a -> m ()
-- createPath :: proxy a -> m (PathText, PathText)
-- todo: edit, createを抽象化
-- editのみroute parameterを受け取っているのは間違い
-- note:
-- 小リソースの作成など、作成時に固定で使う値があるため、
-- 作成に使うデータbが、読み込むデータaのsubsetになっている。
-- ただし、aに値を設定する必要がない場合がほとんどであり、使いにくいかも(defで対応している)
-- フォームにできる時点で初期値を持てると言う意味かも
class (ToDetail m a, ToForm m b, Subtype b a) => CreateConsole m a b where
create :: proxy a -> b -> m ()
createdRedirectPath :: proxy a -> b -> m PathText
-- createPath :: a -> proxy b -> m PathText
detailForCreate :: Query -> proxy b -> m (Maybe a)
createSublayout :: proxy b -> a -> HtmlT m () -> HtmlT m ()
createSublayout _ _ x = x
class (ToDetail m a, ToForm m b, Read (EditIdent a b), Subtype b a) => EditConsole m a b where
type EditIdent a b
edit :: proxy a -> EditIdent a b -> b -> m () -- todo: use AllowAmbiguousTypes and TypeApplication?
-- editPath :: proxy a -> proxy b -> EditIdent a b -> m (PathText, PathText)
editedRedirectPath :: proxy a -> proxy b -> EditIdent a b -> m PathText
detailForEdit :: proxy b -> EditIdent a b -> m (Maybe a)
sublayout :: proxy b -> a -> HtmlT m () -> HtmlT m ()
sublayout _ _ x = x
class (Monad m, ToForm m a) => DeleteConsole m a where
type DeleteIdent a
delete :: DeleteIdent a -> a -> m ()
deletedRedirectPath :: DeleteIdent a -> a -> m PathText
-- todo: /storeを対応、commentとかに変えて子コードにしてみる
-- todo: layout改善
-- todo: Console間のリンクを改善