_{MsgDBCsvException}
$if not (Text.null dbCsvException)
#{dbCsvException}
^{offendingCsv}
^{csvReImport}
|]
, Catch.Handler $ \(csvParseError :: CsvParseError)
-> liftHandler $ sendResponseStatus badRequest400 =<< do
mr <- getMessageRender
let heading = ErrorResponseTitle $ InvalidArgs [mr MsgDBCsvParseError]
siteLayoutMsg heading $ do
setTitleI heading
[whamlet|
$newline never
_{MsgDBCsvParseErrorTip}
$case csvParseError
$of CsvParseError _ errMsg
#{errMsg}
$of IncrementalError errMsg
#{errMsg}
^{csvReImport}
|]
]
_other -> return ()
let
rowCount
| selectPagesize = fromMaybe 0 $ rows' ^? _head . _1 . _Value
| otherwise = olength64 rows
rawAction = tblLink
$ setParam (wIdent "sorting") Nothing
. setParam (wIdent "pagesize") Nothing
. setParam (wIdent "page") Nothing
. setParam (wIdent "pagination") Nothing
table' :: WriterT x m Widget
table' = let
columnCount :: Int64
columnCount = olength64 . getColonnade . discard $ dbtColonnade ^. _Cornice
numberColumn = case dbsTemplate of
DBSTDefault{..} -> dbstmNumber rowCount
_other -> False
genHeaders :: forall h. Cornice h _ _ (DBCell m x) -> SortableP h -> WriterT x m Widget
genHeaders cornice SortableP{..} = execWriterT . go mempty $ annotate cornice
where
go :: forall (p' :: Pillar) r'.
[(Int, Int, Int)]
-> AnnotatedCornice (Maybe Int) h p' r' (DBCell m x)
-> WriterT Widget (WriterT x m) ()
go rowspanAcc (AnnotatedCorniceBase _ (Colonnade (toList -> v))) = censor wrap . forM_ (zip (inits v) v) $ \(before, OneColonnade Sized{..} _) -> do
let (_, cellSize') = compCellSize rowspanAcc (map oneColonnadeHead before) Sized{..}
whenIsJust cellSize' $ \cellSize -> tellM $ fromContent Sized { sizedSize = cellSize, sizedContent }
go rowspanAcc (AnnotatedCorniceCap _ v@(toList -> oneCornices)) = do
rowspanAcc' <- (execStateT ?? rowspanAcc) . hoist (censor wrap) . forM_ (zip (inits oneCornices) oneCornices) $ \(before, OneCornice h (size -> sz')) -> do
let sz = Sized sz' h
let (beforeSize, cellSize') = compCellSize rowspanAcc (concatMap (map oneColonnadeHead . toList . getColonnade . uncapAnnotated . oneCorniceBody) before) sz
whenIsJust cellSize' $ \cellSize -> do
let Sized{..} = sz
lift . tellM $ fromContent Sized { sizedSize = cellSize, sizedContent }
if | [n] <- mapMaybe (\(key, val) -> guardOnM (is _Rowspan key) $ readMay val) (toSortable sizedContent ^. _sortableContent . cellAttrs)
-> State.modify $ (:) (n, beforeSize, cellSize)
| otherwise -> return ()
let rowspanAcc'' = rowspanAcc'
& traverse . _1 %~ pred
whenIsJust (flattenAnnotated v) $ go rowspanAcc''
compCellSize :: forall h' c. [(Int, Int, Int)] -> [Sized (Maybe Int) h' c] -> Sized (Maybe Int) h' c -> (Int, Maybe Int)
compCellSize rowspanAcc before Sized{..} = (beforeSize,) . assertM' (> 0) $ fromMaybe 1 sizedSize - shadowed
where Sum beforeSize = foldMap (\(Sized sz _) -> Sum $ fromMaybe 1 sz) before
Sum shadowed = flip foldMap rowspanAcc $ \(rowsRem, firstCol, sz) -> fromMaybe mempty $ do
guard $ rowsRem > 0
guard $ firstCol <= beforeSize
guard $ beforeSize < firstCol + sz
return . Sum $ sz - (beforeSize - firstCol)
wrap :: Widget -> Widget
wrap row = case dbsTemplate of
DBSTCourse{} -> row
DBSTDefault{} -> $(widgetFile "table/header")
fromContent :: Sized Int h (DBCell m x) -> WriterT x m Widget
fromContent Sized{ sizedSize = cellSize, sizedContent = toSortable -> Sortable{..} } = do
widget <- sortableContent ^. cellContents
let
directions = [dir | SortingSetting k dir <- psSorting, Just k == sortableKey ]
isSortable = isJust sortableKey
isSorted dir = fromMaybe False $ (==) <$> (SortingSetting <$> sortableKey <*> pure dir) <*> listToMaybe psSorting
attrs = sortableContent ^. cellAttrs
piSorting' = [ sSet | sSet <- fromMaybe [] piSorting, Just (sortKey sSet) /= sortableKey ]
case dbsTemplate of
DBSTCourse{} -> return $(widgetFile "table/course/header")
DBSTDefault{} -> return $(widgetFile "table/cell/header")
in do
wHeaders <- maybe (return Nothing) (fmap Just . genHeaders (dbtColonnade ^. _Cornice)) pSortable
now <- liftIO getCurrentTime
case dbsTemplate of
DBSTCourse c l r s a e -> do
wRows <- forM rows $ \row' -> let
Course{..} = row' ^. c . _entityVal
lecturerUsers = row' ^. l
courseLecturers = userSurname . entityVal <$> lecturerUsers
isRegistered = row' ^. r
mayEdit = row' ^. e
nmnow = NTop $ Just now
courseIsVisible = NTop courseVisibleFrom <= nmnow && nmnow <= NTop courseVisibleTo
courseSchoolName = schoolName $ row' ^. s . _entityVal
courseSemester = (termToText . unTermKey) courseTerm
courseAllocation = row' ^? a
in return $(widgetFile "table/course/course-teaser")
return $(widgetFile "table/course/colonnade")
DBSTDefault{..} -> do
let colonnade = discard $ dbtColonnade ^. _Cornice
wRows' <- forM rows $ \row' -> forM (oneColonnadeEncode <$> getColonnade colonnade) $ \(($ row') -> cell') -> do
widget <- cell' ^. cellContents
let attrs = cell' ^. cellAttrs
return $(widgetFile "table/cell/body")
let wRows = zip [firstRow..] wRows'
return $(widgetFile "table/colonnade")
pageCount
| PagesizeLimit l <- psLimit'
= max 1 . ceiling $ rowCount % l
| otherwise
= 1
pageNumbers = [0..pred pageCount]
pagesizeWdgt' = wrapForm pagesizeWdgt FormSettings
{ formMethod = GET
, formAction = Just . SomeRoute $ rawAction :#: wIdent "table-wrapper"
, formEncoding = pagesizeEnc
, formAttrs = [("class", "pagesize"), ("autocomplete", "off")]
, formSubmit = FormAutoSubmit
, formAnchor = Just $ wIdent "pagesize-form"
}
showPagesizeWdgt = toEnum (fromIntegral rowCount) > minimum (pagesizeOptions referencePagesize)
&& selectPagesize
csvWdgt = $(widgetFile "table/csv-transcode")
uiLayout :: Widget -> Widget
uiLayout table = dbsFilterLayout filterWdgt filterEnc (SomeRoute $ rawAction :#: wIdent "table-wrapper") $(widgetFile "table/layout")
dbInvalidateResult' = foldr (<=<) return . catMaybes $
[ do
pKeys <- previousKeys
guard $ pKeys /= currentKeys
return . dbInvalidateResult dbtParams . DBTIRowsMissing $ length previousKeys - length currentKeys
]
((csvImportConfirmRes, _confirmView), _enctype) <- case dbtCsvDecode of
Just (DBTCsvDecode{dbtCsvExecuteActions} :: DBTCsvDecode r' k' csv) -> do
lift . runFormPost . withButtonForm' [BtnCsvImportConfirm, BtnCsvImportAbort] . identifyForm (FIDDBTableCsvImportConfirm dbtIdent) $ \_csrf -> do
acts <- globalPostParamFields PostDBCsvImportAction secretJsonField
return . (, mempty) $ if
| null acts -> FormSuccess $ do
addMessageI Info MsgCsvImportAborted
redirect $ tblLink id
| otherwise -> FormSuccess $ do
finalDest <- runDBJobs' . runConduit $ C.sourceList acts .| dbtCsvExecuteActions
addMessageI Success . MsgCsvImportSuccessful $ length acts
E.transactionSave
redirect finalDest
_other -> return ((FormMissing, mempty), mempty)
formResult csvImportConfirmRes $ \case
(_, BtnCsvImportAbort) -> do
addMessageI Info MsgCsvImportAborted
redirect $ tblLink id
(act, _) -> act
let
wrapLayout :: DBResult m x -> DB (DBResult m x)
wrapLayout = dbHandler (Proxy @m) (Proxy @x) $ (\table -> $(widgetFile "table/layout-wrapper")) . uiLayout
shortcircuit :: forall void. DBResult m x -> DB void
shortcircuit res = do
addCustomHeader HeaderDBTableCanonicalURL =<< toTextUrl (tblLink substPi)
sendResponse =<< tblLayout . uiLayout =<< dbWidget (Proxy @m) (Proxy @x) res
dbInvalidateResult' <=< bool wrapLayout shortcircuit psShortcircuit <=< runDBTable dbtable paginationInput currentKeys . fmap swap $ runWriterT table'
where
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
tblLayout tbl' = do
tbl <- liftHandler $ widgetToPageContent tbl'
withUrlRenderer $(hamletFile "templates/table/layout-standalone.hamlet")
setParams :: Text -> [Text] -> QueryText -> QueryText
setParams key vs qt = map ((key, ) . Just) vs ++ [ i | i@(key', _) <- qt, key' /= key ]
setParam :: Text -> Maybe Text -> QueryText -> QueryText
setParam key = setParams key . maybeToList
dbTableWidget :: Monoid x
=> PSValidator (HandlerFor UniWorX) x
-> DBTable (HandlerFor UniWorX) x
-> DB (DBResult (HandlerFor UniWorX) x)
dbTableWidget = dbTable
dbTableWidget' :: PSValidator (HandlerFor UniWorX) ()
-> DBTable (HandlerFor UniWorX) ()
-> DB Widget
dbTableWidget' = fmap (fmap snd) . dbTable
dbTableDB :: Monoid x
=> PSValidator DB x
-> DBTable DB x
-> DB (DBResult DB x)
dbTableDB = dbTable
dbTableDB' :: PSValidator DB ()
-> DBTable DB ()
-> DB Widget
dbTableDB' = fmap (fmap snd) . dbTable
widgetColonnade :: Colonnade h r (DBCell (HandlerFor UniWorX) x)
-> Colonnade h r (DBCell (HandlerFor UniWorX) x)
widgetColonnade = id
formColonnade :: Colonnade h r (DBCell (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerFor UniWorX)) (FormResult a))
-> Colonnade h r (DBCell (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enctype Ints (HandlerFor UniWorX)) (FormResult a))
formColonnade = id
dbColonnade :: Colonnade h r (DBCell DB x)
-> Colonnade h r (DBCell DB x)
dbColonnade = id
pagesizeOptions :: PagesizeLimit -- ^ Current/previous value
-> NonNull [PagesizeLimit]
pagesizeOptions psLim = impureNonNull . Set.toAscList . Set.fromList $ psLim : PagesizeAll : map PagesizeLimit opts
where
opts :: [Int64]
opts = filter (> 0) $ opts' <> map (`div` 2) opts'
opts' :: [Int64]
opts' = [ 10^n | n <- [1..3]]
pagesizeField :: PagesizeLimit -> Field Handler PagesizeLimit
pagesizeField psLim = selectField $ do
MsgRenderer mr <- getMsgRenderer
let
optText (PagesizeLimit l) = tshow l
optText PagesizeAll = mr MsgDBTablePagesizeAll
toOptionList = flip OptionList fromPathPiece . map (\o -> Option (optText o) o $ toPathPiece o)
return . toOptionList . toNullable $ pagesizeOptions psLim
---------------------------------------------------------------
--- DBCell utility functions, more in Handler.Utils.Table.Cells
cell :: IsDBTable m a => Widget -> DBCell m a
cell wgt = dbCell # ([], return wgt)
textCell, stringCell :: (MonoFoldable msg, Element msg ~ Char, IsDBTable m a) => msg -> DBCell m a
textCell = cell . toWidget . (pack :: String -> Text) . otoList
stringCell = textCell
i18nCell :: (RenderMessage UniWorX msg, IsDBTable m a) => msg -> DBCell m a
i18nCell msg = cell $ do
mr <- getMessageRender
toWidget $ mr msg
cellTooltip :: (RenderMessage UniWorX msg, IsDBTable m a) => msg -> DBCell m a -> DBCell m a
cellTooltip msg = cellContents.mapped %~ (<> tipWdgt)
where
tipWdgt = [whamlet|
_{msg}
|]
-- | Always display widget; maybe a link if user is Authorized.
-- Also see variant `linkEmptyCell`
anchorCell :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a) => url -> wgt -> DBCell m a
anchorCell = anchorCellM . return
anchorCellC :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a, Binary cache) => cache -> url -> wgt -> DBCell m a
anchorCellC cache = anchorCellCM cache . return
anchorCell' :: ( IsDBTable m a
, ToWidget UniWorX wgt
, HasRoute UniWorX url
)
=> (r -> url)
-> (r -> wgt)
-> (r -> DBCell m a)
anchorCell' mkRoute mkWidget val = anchorCell (mkRoute val) (mkWidget val)
anchorCellM :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a) => WidgetFor UniWorX url -> wgt -> DBCell m a
anchorCellM routeM widget = anchorCellM' routeM id (const widget)
anchorCellCM :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a, Binary cache) => cache -> WidgetFor UniWorX url -> wgt -> DBCell m a
anchorCellCM cache routeM widget = anchorCellCM' cache routeM id (const widget)
anchorCellM' :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a) => WidgetFor UniWorX x -> (x -> url) -> (x -> wgt) -> DBCell m a
anchorCellM' xM x2route x2widget = linkEitherCellM' xM x2route (x2widget, x2widget)
anchorCellCM' :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a, Binary cache) => cache -> WidgetFor UniWorX x -> (x -> url) -> (x -> wgt) -> DBCell m a
anchorCellCM' cache xM x2route x2widget = linkEitherCellCM' cache xM x2route (x2widget, x2widget)
maybeAnchorCellM :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a) => MaybeT (WidgetFor UniWorX) url -> wgt -> DBCell m a
maybeAnchorCellM routeM widget = maybeAnchorCellM' routeM id (const widget)
maybeAnchorCellM' :: (HasRoute UniWorX url, ToWidget UniWorX wgt, IsDBTable m a) => MaybeT (WidgetFor UniWorX) x -> (x -> url) -> (Maybe x -> wgt) -> DBCell m a
maybeAnchorCellM' xM x2route x2widget = maybeLinkEitherCellM' xM x2route (x2widget . Just, x2widget)
-- | Variant of `anchorCell` that displays different widgets depending whether the route is authorized for current user
linkEitherCell :: (HasRoute UniWorX url, ToWidget UniWorX wgt, ToWidget UniWorX wgt', IsDBTable m a) => url -> (wgt, wgt') -> DBCell m a
linkEitherCell = linkEitherCellM . return
linkEitherCellM :: (HasRoute UniWorX url, ToWidget UniWorX wgt, ToWidget UniWorX wgt', IsDBTable m a) => WidgetFor UniWorX url -> (wgt, wgt') -> DBCell m a
linkEitherCellM routeM (widgetAuth,widgetUnauth) = linkEitherCellM' routeM id (const widgetAuth, const widgetUnauth)
linkEitherCellM' :: forall m url wgt wgt' a x.
( HasRoute UniWorX url
, ToWidget UniWorX wgt
, ToWidget UniWorX wgt'
, IsDBTable m a
)
=> WidgetFor UniWorX x -> (x -> url) -> (x -> wgt, x -> wgt') -> DBCell m a
linkEitherCellM' xM x2route (x2widgetAuth,x2widgetUnauth) = maybeLinkEitherCellM' (lift xM) x2route (x2widgetAuth, x2widgetUnauth . fromJust)
linkEitherCellCM' :: forall m url wgt wgt' a x cache.
( HasRoute UniWorX url
, ToWidget UniWorX wgt
, ToWidget UniWorX wgt'
, IsDBTable m a
, Binary cache
)
=> cache -> WidgetFor UniWorX x -> (x -> url) -> (x -> wgt, x -> wgt') -> DBCell m a
linkEitherCellCM' cache xM x2route (x2widgetAuth,x2widgetUnauth) = maybeLinkEitherCellCM' (Just . toStrict $ B.encode cache) (lift xM) x2route (x2widgetAuth, x2widgetUnauth . fromJust)
maybeLinkEitherCellM' :: forall m url wgt wgt' a x.
( HasRoute UniWorX url
, ToWidget UniWorX wgt
, ToWidget UniWorX wgt'
, IsDBTable m a
)
=> MaybeT (WidgetFor UniWorX) x -> (x -> url) -> (x -> wgt, Maybe x -> wgt') -> DBCell m a
maybeLinkEitherCellM' = maybeLinkEitherCellCM' Nothing
maybeLinkEitherCellCM' :: forall m url wgt wgt' a x.
( HasRoute UniWorX url
, ToWidget UniWorX wgt
, ToWidget UniWorX wgt'
, IsDBTable m a
)
=> Maybe ByteString -> MaybeT (WidgetFor UniWorX) x -> (x -> url) -> (x -> wgt, Maybe x -> wgt') -> DBCell m a
maybeLinkEitherCellCM' mCache xM x2route (x2widgetAuth,x2widgetUnauth) = cell $ do
x' <- runMaybeT xM
case x' of
Just x -> do
let route = x2route x
widget, widgetUnauth :: Widget
widget = toWidget $ x2widgetAuth x
widgetUnauth = toWidget . x2widgetUnauth $ Just x
authResult <- liftHandler . maybe id $cachedHereBinary mCache . hasReadAccessTo $ urlRoute route
linkUrl <- toTextUrl route
if
| authResult -> $(widgetFile "table/cell/link") -- show allowed link
| otherwise -> widgetUnauth
_otherwise -> do
toWidget $ x2widgetUnauth Nothing
listCell :: (IsDBTable m a, Traversable f) => f r' -> (r' -> DBCell m a) -> DBCell m a
listCell = listCell' . return
listCell' :: (IsDBTable m a, Traversable f) => WriterT a m (f r') -> (r' -> DBCell m a) -> DBCell m a
listCell' mkXS mkCell = review dbCell . ([], ) $ do
xs <- mkXS
cells <- forM xs $
\(view dbCell . mkCell -> (attrs, mkWidget)) -> (attrs, ) <$> mkWidget
return $(widgetFile "table/cell/list")
newtype DBFormResult i a r = DBFormResult (Map i (r, a -> a))
instance Functor (DBFormResult i a) where
f `fmap` (DBFormResult resMap) = DBFormResult $ fmap (over _1 f) resMap
instance Ord i => Sem.Semigroup (DBFormResult i a r) where
(DBFormResult m1) <> (DBFormResult m2) = DBFormResult $ Map.unionWith (\(r, f1) (_, f2) -> (r, f2 . f1)) m1 m2
instance Ord i => Monoid (DBFormResult i a r) where
mempty = DBFormResult Map.empty
mappend = (<>)
getDBFormResult :: forall r i a. (r -> a) -> DBFormResult i a r -> Map i a
getDBFormResult initial (DBFormResult m) = Map.map (\(r, f) -> f $ initial r) m
formCell :: forall x r i a. Monoid x
=> Lens' x (FormResult (DBFormResult i a (DBRow r))) -- ^ lens focussing on the form result within the larger DBResult; @id@ iff the form delivers the only result of the table
-> (DBRow r -> MForm (HandlerFor UniWorX) i) -- ^ generate row identfifiers for use in form result
-> (DBRow r -> (forall p. PathPiece p => p -> Text) -> MForm (HandlerFor UniWorX) (FormResult (a -> a), Widget)) -- ^ Given the row data and a callback to make an input name suitably unique generate the `MForm`
-> (DBRow r -> DBCell (MForm (HandlerFor UniWorX)) x)
formCell formCellLens genIndex genForm input@(DBRow{dbrKey}) = FormCell
{ formCellAttrs = []
, formCellContents = do -- MForm (HandlerFor UniWorX) (FormResult (Map i (Endo a)), Widget)
i <- lift $ genIndex input
hashKey <- LBS.toStrict . B.encode <$> cryptoIDKey return
let
mkUnique :: PathPiece p => p -> Text
mkUnique (toPathPiece -> name) = name <> "-" <> decodeUtf8 (Base64.encode rowKeyHash)
where
rowKeyHash = (BA.convert :: HMAC (SHAKE256 264) -> ByteString) . hmac hashKey . LBS.toStrict $ B.encode dbrKey
(edit, w) <- lift $ genForm input mkUnique
return (DBFormResult . Map.singleton i . (input,) <$> edit, w)
, formCellLens
}
-- Predefined colonnades
dbSelect :: forall x h r i a. (Headedness h, Monoid' x)
=> Lens' x (FormResult (DBFormResult i a (DBRow r)))
-> Setter' a Bool
-> (DBRow r -> MForm (HandlerFor UniWorX) i)
-> Colonnade h (DBRow r) (DBCell (MForm (HandlerFor UniWorX)) x)
-- dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ i18nCell MsgSelectColumn) $ formCell resLens genIndex genForm
dbSelect resLens selLens genIndex = Colonnade.singleton (headednessPure $ mempty & cellAttrs <>~ pure ("uw-hide-columns--no-hide","")) $ formCell resLens genIndex genForm
where
genForm _ mkUnique = do
(selResult, selWidget) <- mreq checkBoxField (fsUniq mkUnique "select") (Just False)
return (set selLens <$> selResult, [whamlet|^{fvWidget selWidget}|])
cap' :: ( AsCornice Sortable p r' (DBCell m x) colonnade
, IsDBTable m x
)
=> colonnade
-> Cornice Sortable ('Cap p) r' (DBCell m x)
cap' (view _Cornice -> cornice) = case cornice of
CorniceBase Colonnade{..}
| [OneColonnade{..}] <- toList getColonnade
-> recap (oneColonnadeHead & _sortableContent . cellAttrs %~ incRowspan) cornice
CorniceCap cornices
-> CorniceCap $ fmap (\OneCornice{..} -> OneCornice { oneCorniceHead = oneCorniceHead & _sortableContent . cellAttrs %~ incRowspan, oneCorniceBody = cap' oneCorniceBody }) cornices
other
-> recap (fromSortable . Sortable Nothing $ cell mempty) other
where
incRowspan :: [(Text, Text)] -> [(Text, Text)]
incRowspan attrs
| [n] <- mapMaybe (\(key, val) -> guardOnM (is _Rowspan key) $ readMay val) attrs
= (_Rowspan # (), tshow (succ n :: Natural)) : filter (hasn't $ _1 . _Rowspan) attrs
| otherwise = (_Rowspan # (), "2") : filter (hasn't $ _1 . _Rowspan) attrs
_Rowspan :: Prism' Text ()
_Rowspan = prism' (\() -> "rowspan") $ flip guardOn () . ((==) `on` CI.mk) "rowspan"