packages feed

typed-admin-0.1.0.0: src/TypedAdmin/Instance.hs

{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE UndecidableInstances  #-}

module TypedAdmin.Instance where

import           Control.Applicative
import           Control.Monad
import           Control.Monad.State.Class
import           Control.Monad.Trans.Class
import qualified Data.ByteString.UTF8          as BS
import           Data.Generics.Product.Subtype
import qualified Data.HashMap.Strict           as M
import           Data.Maybe
import           Data.Proxy
import           Data.Text                     as T (Text, dropWhile, pack)
import           Data.Time
import           Data.Yaml                     as Y
import           GHC.Generics
import           GHC.Int
import           Lucid
import           Network.HTTP.Types.URI        (Query, renderQuery)
import           TypedAdmin.Class
import           TypedAdmin.Router
import           TypedAdmin.Util

pushH :: MonadState Context m => Text -> m a -> m a
pushH k f = do
  modify (\x -> x { localeHierarchy = k:localeHierarchy x})
  r <- f
  modify (\x -> x { localeHierarchy = Prelude.tail (localeHierarchy x)})
  return r

toLocal :: MonadState Context m => Text -> m Text
toLocal k = do
  dic <- dic <$> get
  h <- localeHierarchy <$> get
  let k' = T.dropWhile (== '_') k
  let f d [] h k'' =
        case (M.lookup k'' =<< d, h) of
          (Just (Y.String x), _)     -> return x
          (_, _:h')                  -> f dic h' h' k''
          (Just (Y.Object dic'), []) -> f (Just dic') [] [] "."
          (_, [])                    -> return (if k'' == "." then k' else k'')
      f d (h':hs) h k'' =
        case M.lookup h' =<< d of
          Just (Y.Object dic') -> f (Just dic') hs h k''
          _                    -> f dic (tail h) (tail h) k''
  f dic h h k'

-- instance {-# OVERLAPS #-} (Show a) => ToHtml a where
--   toHtml = toHtml . show
--   toHtmlRaw = toHtmlRaw .show

-- instance ToHtml a => ToHtml (Maybe a) where
--   toHtml x = Data.Maybe.fromMaybe (toHtml ("" :: String)) $ toHtml <$> x
--   toHtmlRaw = toHtmlRaw

instance ToDetailField String where
  toDetailField x = span_ [] (toHtml x)

instance ToDetailField Bool where
  toDetailField x = span_ [] (toHtml (show x))

instance ToDetailField Integer where
  toDetailField x = span_ [] (toHtml (show x))
instance ToDetailField Int where
  toDetailField x = toDetailField (fromIntegral x :: Integer)
instance ToDetailField Int32 where
  toDetailField x = toDetailField (fromIntegral x :: Integer)
instance ToDetailField Int64 where
  toDetailField x = toDetailField (fromIntegral x :: Integer)

instance ToDetailField Day where
  toDetailField x = span_ [] (toHtml (show x))

instance ToDetailField UTCTime where
  toDetailField x = span_ [] (toHtml (show x))

instance ToDetailField LocalTime where
  toDetailField x = span_ [] (toHtml (show x))

instance ToDetailField a => ToDetailField (Maybe a) where
  toDetailField x = fromMaybe (span_ [] "")$ toDetailField <$> x

instance {-# OVERLAPPABLE #-} ToDetailField a => ToDetailField [a] where
  toDetailField xs = do
    ul_ [] $
      forM_ (toDetailField <$> xs) $ \x -> do
        li_ [] x

instance {-# OVERLAPPABLE #-} (Show a) =>  ToDetailField a where
  toDetailField x = span_ [] (toHtml $ show x)

-- instance GToDetail V1 where
--   gToDetail _ = error "toRow V1"

-- instance GToDetail U1 where
--   gToDetail _ = error "toRow U1"

instance (GToDetail f, GToDetail g) => GToDetail (f :*: g) where
  gToDetail (f :*: g) = do
    x <- gToDetail f
    y <- gToDetail g
    return $ x <> y
  gDetailTitle _ = pure "*"

-- instance (GToDetail f, GToDetail g) => GToDetail (f :+: g) where
--   gToDetail _ = error "toRow sum type"

instance (ToDetailField c, Selector s) => GToDetail (M1 S s (K1 i c)) where
  gToDetail x =
    let (M1 (K1 c)) = x
    in return [(pack (selName x), toDetailField c)]
  gDetailTitle _ = pure "--"

instance {-# OVERLAPPABLE #-} (GToDetail f, Constructor c) => GToDetail (M1 C c f) where
  gToDetail (M1 x) = do
    gToDetail x
  gDetailTitle x = pure $ pack $ conName (undefined :: M1 C c rep ())

instance {-# OVERLAPPABLE #-} (GToDetail f) => GToDetail (M1 i t f) where
  gToDetail (M1 x) = do
    gToDetail x
  gDetailTitle _ = gDetailTitle (Proxy :: Proxy f)
  -- gDetailTitle x = pure $ pack $ conName (undefined :: M1 C t f ())

-- instance  {-# OVERLAPS #-} (ToDetail a, HasHeader a) => ToHtml [a] where
--   toHtml xs = do
--     table_ $ do
--       tr_ $ mapM_ th_ $ hasHeader (Proxy :: Proxy a)
--       forM_ xs $ (tr_ []) . toRow
--   toHtmlRaw = toHtml

-- instance GHasHeader V1 where
--   gHasHeader _ = error "hasHeader V1"

-- instance GHasHeader U1 where
--   gHasHeader _ = error "hasHeader U1"

instance (GHasHeader f, GHasHeader g) => GHasHeader (f :*: g) where
  gHasHeader _ = gHasHeader (Proxy :: Proxy f) ++ gHasHeader (Proxy :: Proxy g)

-- instance GHasHeader (f :+: g) where
--   gHasHeader _ = error "hasHeader sum type"

-- instance GHasHeader (K1 i c) where
--   gHasHeader (K1 x) = error "K1"

instance (Selector s) => GHasHeader (M1 S s k) where
  gHasHeader _ =
    [ do
        l <- lift $ toLocal $ pack $ selName (undefined :: t s k a)
        toHtml l
    ]
instance GHasHeader f => GHasHeader (M1 C t f) where
  gHasHeader _ = gHasHeader (Proxy :: Proxy f)

instance GHasHeader f => GHasHeader (M1 D t f) where
  gHasHeader _ = gHasHeader (Proxy :: Proxy f)

instance (GToForm m f, GToForm m g) => GToForm m (f :*: g) where
  gToForm (Just (f :*: g)) = do
    x <- gToForm (Just f)
    y <- gToForm (Just g)
    return $ x ++ y
  gToForm _ = do
    x <- gToForm (Nothing :: Maybe (f a))
    y <- gToForm (Nothing :: Maybe (g a))
    return $ x ++ y
  gFromForm ps = do
    x <- (gFromForm ps)
    y <- (gFromForm ps)
    pure $ liftA2 (:*:) x y

instance GToForm m f => GToForm m (M1 D t f) where
  gToForm (Just (M1 x)) = gToForm (Just x)
  gToForm _             = gToForm (Nothing :: Maybe (f a))
  gFromForm ps = do
    x <- gFromForm ps
    pure $ M1 <$> x

instance GToForm m f => GToForm m (M1 C t f) where
  gToForm (Just (M1 x)) = gToForm (Just x)
  gToForm _             = gToForm (Nothing :: Maybe (f a))
  gFromForm ps = do
    x <- gFromForm ps
    pure $ M1 <$> x

instance forall s m c i. (Selector s, FormField m c) => GToForm m (M1 S s (K1 i c)) where
  gToForm x = do
    let k = (\(M1 (K1 k)) -> k) <$> x
        sname = selName (undefined :: t s (K1 i (Maybe a)) p)
    pure [(pack sname, toFormField (pack $ sname) k)]
  gFromForm ps = do
    let sname = selName (undefined :: t s (K1 i (Maybe a)) p)
    mx <- fromFormField ps (BS.fromString $ sname)
    pure $ case mx of
      Just x  -> Right $ M1 $ K1 x
      Nothing -> Left ("--GToForm--" <> show ps <> sname)

instance Monad m => FormField m String where
  toFormField n x = input_ [type_ "text", name_ n, value_ (pack $ fromMaybe "" x), required_ ""]
  fromFormField ps k = pure (BS.toString <$> join (lookup k ps))

instance {-# OVERLAPPING #-} Monad m => FormField m (Maybe String) where
  toFormField n x = input_ [type_ "text", name_ n, value_ (pack $ fromMaybe "" $ join x)
                           , class_ "maybe"]
  fromFormField ps k =
    let x = BS.toString <$> join (lookup k ps)
    in pure $ Just $ memptyToNothing =<< x

instance Monad m => FormField m Bool where
  toFormField n x =
    let chkd = if (fromMaybe False x) then [checked_] else []
    in input_ $ [type_ "checkbox", name_ n] ++ chkd
  fromFormField ps k = pure $ Just $ isJust $ join (lookup k ps)

-- instance {-# OVERLAPS #-} (Integral a, Show a, Read a) => FormField a where
--   toFormField n x = input_ [type_ "number", name_ n, value_ (pack $ fromMaybe "" (show <$> x))]
--   fromFormField ps k = lookupMaybe k ps

toHtmlInput t n x = input_ [type_ t, name_ n, value_ (pack $ fromMaybe "" (show <$> x))]
fromHtmlInput ps k = pure $ lookupMaybe k ps

-- todo: Read,ShowなものはすべてtoTypeAttr実装し共通実装では?
instance Monad m => FormField m Int where
  toFormField = toHtmlInput "number"
  fromFormField = fromHtmlInput

instance Monad m =>  FormField m Int32 where
  toFormField = toHtmlInput "number"
  fromFormField = fromHtmlInput

instance Monad m => FormField m Int64 where
  toFormField = toHtmlInput "number"
  fromFormField = fromHtmlInput

instance Monad m => FormField m Double where
  toFormField = toHtmlInput "text"
  fromFormField = fromHtmlInput

instance Monad m => FormField m Day where
  toFormField = toHtmlInput "date"
  fromFormField = fromHtmlInput

instance FormField m a => FormField m (Maybe a) where
  toFormField n x = toFormField n (join x)
  fromFormField ps k = do
    x <- fromFormField ps k
     -- todo: Justが出てくる箇所多分型間違ってるので、見直す
    pure $ Just x

newtype SelectForm a = SelectForm { unSelectForm :: a}
  deriving (Show)


selectFormField' :: forall a m.
  (Enum a, Bounded a, Show a, Read a, Monad m) =>
  Bool -> Text -> Maybe (SelectForm a) -> HtmlT m ()
selectFormField' required n x = do
  let min = minBound :: a
      max = maxBound :: a
  select_ [name_ n] $ do
    when (not required) $ option_ [value_ ""] ""
    forM_ [min .. max] $ \o -> do
      let sed = case x of
            Just (SelectForm x)
              | fromEnum o == fromEnum x -> [selected_ ""]
              | otherwise -> []
            _ -> []
      option_ ([value_ $ pack $ show $ fromEnum o] ++ sed) $ toHtml $ show o

fromSelectFormField ps k = pure $ fmap (SelectForm . toEnum) $ lookupMaybe k ps

instance {-# OVERLAPPING #-} (Enum a, Bounded a, Show a, Read a, Monad m) => FormField m (Maybe (SelectForm a)) where
  toFormField n x = selectFormField' False n (join x)
  fromFormField ps k = Just <$> fromSelectFormField ps k

instance (Enum a, Bounded a, Show a, Read a, Monad m) => FormField m (SelectForm a) where
  toFormField n x = selectFormField' True n x
  fromFormField = fromSelectFormField


instance Monad m => ToForm m () where
  toForm _ = pure mempty
  fromForm _ = pure $ pure mempty

renderListHtml ::
  forall a b c d m . (Monad m, ListConsole m a b, PathParam c d, MonadState Context m)
  => [a]
  -> Maybe b
  -> (c, d, Query)
  -> (Int, Maybe Int)
  -> HtmlT m ()
renderListHtml xs p (path, param, query) (page, total) = do
  let pathText = toText $ renderPath path param
  -- liftIO $ print $ show (Proxy :: Proxy a)
  let hs = hasHeader (Proxy :: Proxy a)
  title <- lift $ detailTitle (Proxy :: Proxy a)
  title' <- toLocal title
  h1_ [] $ toHtml $ title'
  listSublayout p xs $ do
    pushH title $ do
      form_ [method_ "GET", action_ pathText, class_ "_typed_admin_search_form"] $ do
        form <- lift $ toForm p
        div_ [class_ "_typed_admin_fields"] $ do
          forM_ form $ \(name, fld) -> do
            div_ [] $ do
              dt_ [] $ do
                name' <- toLocal name
                label_ [] (toHtml name')
              dd_ [] $
                  fld
        div_ [class_ "_typed_admin_actions"] $ do
          dt_ [] "action"
          dd_ [] $ do
            lblSearch <- toLocal "search"
            input_ [type_ "submit", value_ lblSearch]
      table_ [] $ do
        thead_ [] $
          tr_ $ mapM_ th_ hs
        tbody_ [] $ do
          forM_ xs $ \row -> do
            detail <- lift $ toDetail row
            tr_ [] $ do
              forM_ detail $ \(n, cell) -> do
                td_ [class_ ("_typed_admin_td_" <> n)] cell
              td_ [class_ "_typed_admin_actions"] $
                ul_ [] $ do
                  editLbl <- toLocal "edit"
                  forM_ [("detail", linkDetail), (editLbl, linkEdit)] $ \(l, g) -> do
                    mp <- lift $ g row
                    ll <- toLocal l
                    whenJust mp $ \p -> li_ [] $ a_ [href_ p] $ toHtml ll
              --     forM_ (catMaybes
              --            [ (\u -> a_ [href_ u] "detail") <$> linkDetail row
              --            , (\u -> a_ [href_ u] "edit") <$> linkEdit row
              --            ]
              --           ) $ \a -> li_ [] a
      div_ [class_ "_typed_admin_pager"] $ do
        let rQuery q = pack (BS.toString (renderQuery True q))
            pageQ d = rQuery (setPageQuery (page + d) query)
        when (page > 0) $
          a_ [href_ (pathText <> pageQ (-1))] "prev"
        whenJust total $ \t ->
          forM_ [0..(t - 1)] $ \p -> do
            let sp = toHtml $ show (p + 1)
            if page == p
            then
              span_ $ sp
            else
              a_ [href_ (pathText <> rQuery (setPageQuery p query))] sp
        a_ [href_ (pathText <> pageQ 1)] "next"

setPageQuery page query =
  let p = ("page", Just $ BS.fromString $ show (page))
  in p:(filter ((/= "page") . fst) query)

renderDetailHtml :: forall a b m .
  (Monad m, DetailConsole m a, MonadState Context m)
  => a -> HtmlT m ()
renderDetailHtml x = do
  title <- lift $ detailTitle (Proxy :: Proxy a)
  title' <- toLocal title
  detail <- lift $ toDetail x
  div_ [class_ ("_typed_admin_detail_" <> title)] $ do
    h1_ [] $ toHtml $ title'
    detailSublayout x $ do
      pushH title $ do
        dl_ [] $ do
          forM_ detail $ \(name, field) -> do
            dt_ [] (toHtml name)
            dd_ [] field

toCreateForm :: forall a b c d proxy m .
  (Monad m, CreateConsole m a b, PathParam c d)
  => a
  -> proxy b
  -> (c, d)
  -> HtmlT m ()
toCreateForm x _ (path, param) = do
  detail <- lift $ toDetail x
  form <- lift $ toForm (Just (upcast x :: b))
  let pathText = toText $ renderPath path param
  let r =
        form_ [method_ "POST", action_ pathText] $ do
          dl_ $ do
            forM_ detail $ \(name, detailField) -> do
              dt_ [] $
                label_ [] (toHtml name)
              dd_ [] $
                fromMaybe (detailField) $ lookup name form
            tr_ $
              p_ [] "actions"
            dd_ $
              input_ [type_ "submit"]
  createSublayout (Proxy :: Proxy b) x r

renderEditHtml :: forall a b c d proxy m .
  (Monad m, EditConsole m a b, PathParam c d, MonadState Context m)
  => a
  -> proxy b
  -> EditIdent a b
  -> (c, d)
  -> HtmlT m ()
renderEditHtml x _ ident (path, param) = do
  title <- lift $ detailTitle (Proxy :: Proxy a)
  title' <- toLocal title
  div_ [class_ ("_typed_admin_detail_" <> title)] $ do
    h1_ [] $ toHtml $ title'
    detail <- lift $ toDetail x
    form <- lift $ toForm (Just (upcast x :: b))
    let pathText = toText $ renderPath path param
    sublayout (Proxy :: Proxy b) x $ do
      form_ [method_ "POST", action_ pathText] $ do
        dl_ $ do
          forM_ detail $ \(name, detailField) -> do
            dt_ [] $ do
              name' <- toLocal name
              label_ [] (toHtml name')
            dd_ [] $
              fromMaybe (detailField) $ lookup name form
          tr_ $ do
            lblActions <- toLocal "actions"
            p_ [] (toHtml lblActions)
          dd_ $
            input_ [type_ "submit"]

renderDeleteHtml :: forall a b c d proxy m .
  (Monad m, DeleteConsole m a, PathParam c d, MonadState Context m)
  => DeleteIdent a
  -> a
  -> (c, d)
  -> HtmlT m ()
renderDeleteHtml ident x (path, param) = do
  form <- lift $ toForm (Just x)
  let pathText = toText $ renderPath path param
  form_ [method_ "POST", action_ pathText] $ do
    forM_ form snd
    input_ [type_ "submit"]

-- instance (ToHtml c) => GToEditForm (K1 i c) where
--   gToEditForm (K1 x) = do
--     td_ (toHtml x)

-- instance (GToEditForm f) => GToEditForm (M1 i t f) where
--   gToEditForm (M1 x) = do
--     gToEditForm x


-- todo: create router library, and refactor listPath andcreatePath, etc.