Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX

This commit is contained in:
SJost 2018-06-07 17:04:33 +02:00
commit ae64b10d5c
21 changed files with 232 additions and 143 deletions

View File

@ -50,3 +50,7 @@ SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem
EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer.
NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet. NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet.
HomeHeading: Startseite
TermsHeading: Semesterübersicht
NumCourses n@Int64: #{tshow n} Kurse

View File

@ -1,7 +1,7 @@
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
@ -445,11 +445,14 @@ instance Yesod UniWorX where
-- value passed to hamletToRepHtml cannot be a widget, this allows -- value passed to hamletToRepHtml cannot be a widget, this allows
-- you to use normal widget features in default-layout. -- you to use normal widget features in default-layout.
let let
navbar :: Widget navbar :: Widget
navbar = $(widgetFile "widgets/navbar") navbar = $(widgetFile "widgets/navbar")
asidenav :: Widget asidenav :: Widget
asidenav = $(widgetFile "widgets/asidenav") asidenav = $(widgetFile "widgets/asidenav")
contentHeadline :: Maybe Widget
contentHeadline = pageHeading =<< mcurrentRoute
breadcrumbs :: Widget breadcrumbs :: Widget
breadcrumbs = $(widgetFile "widgets/breadcrumbs") breadcrumbs = $(widgetFile "widgets/breadcrumbs")
pageactionprime :: Widget pageactionprime :: Widget
@ -476,7 +479,7 @@ instance Yesod UniWorX where
$(widgetFile "standalone/showHide") $(widgetFile "standalone/showHide")
$(widgetFile "standalone/inputs") $(widgetFile "standalone/inputs")
withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet") withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")
-- The page to be redirected to when authentication is required. -- The page to be redirected to when authentication is required.
authRoute _ = Just $ AuthR LoginR authRoute _ = Just $ AuthR LoginR
@ -606,6 +609,14 @@ pageActions (TermCourseListR _) =
pageActions _ = [] pageActions _ = []
pageHeading :: Route UniWorX -> Maybe Widget
pageHeading HomeR
= Just [whamlet|_{MsgHomeHeading}|]
pageHeading TermShowR
= Just [whamlet|_{MsgTermsHeading}|]
pageHeading _
= Nothing
defaultLinks :: [MenuTypes] defaultLinks :: [MenuTypes]
defaultLinks = -- Define the menu items of the header. defaultLinks = -- Define the menu items of the header.
[ NavbarRight $ MenuItem [ NavbarRight $ MenuItem
@ -633,21 +644,15 @@ defaultLinks = -- Define the menu items of the header.
, menuItemAccessCallback' = isJust <$> maybeAuthPair , menuItemAccessCallback' = isJust <$> maybeAuthPair
} }
, NavbarAside $ MenuItem , NavbarAside $ MenuItem
{ menuItemLabel = "Aktuelle Veranstaltungen" { menuItemLabel = "Veranstaltungen"
, menuItemIcon = Just "book" , menuItemIcon = Just "book"
, menuItemRoute = CourseListR -- should be CourseListActiveR or similar in the future , menuItemRoute = CourseListR -- should be CourseListActiveR or similar in the future
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
} }
, NavbarAside $ MenuItem , NavbarAside $ MenuItem
{ menuItemLabel = "Alte Veranstaltungen" { menuItemLabel = "Semester"
, menuItemIcon = Just "book" , menuItemIcon = Nothing
, menuItemRoute = CourseListR -- should be CourseListInactiveR or similar in the future , menuItemRoute = CourseListR -- should be TermListR ,,,
, menuItemAccessCallback' = return True
}
, NavbarAside $ MenuItem
{ menuItemLabel = "Veranstaltungen"
, menuItemIcon = Just "book"
, menuItemRoute = CourseListR
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
} }
, NavbarAside $ MenuItem , NavbarAside $ MenuItem

View File

@ -8,6 +8,7 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
module Handler.Sheet where module Handler.Sheet where
@ -154,7 +155,7 @@ getSheetList courseEnt = do
rated <- count $ (SubmissionRatingTime !=. Nothing):sheetsub rated <- count $ (SubmissionRatingTime !=. Nothing):sheetsub
return (sid, sheet, (submissions, rated)) return (sid, sheet, (submissions, rated))
let colBase = mconcat let colBase = mconcat
[ headed "Blatt" $ \(sid,sheet,_) -> linkButton (toWgt $ sheetName sheet) BCLink $ CSheetR tid csh (sheetName sheet) SShowR [ headed "Blatt" $ \(sid,sheet,_) -> simpleLink (toWgt $ sheetName sheet) $ CSheetR tid csh (sheetName sheet) SShowR
, headed "Abgabe ab" $ toWgt . formatTimeGerWD . sheetActiveFrom . snd3 , headed "Abgabe ab" $ toWgt . formatTimeGerWD . sheetActiveFrom . snd3
, headed "Abgabe bis" $ toWgt . formatTimeGerWD . sheetActiveTo . snd3 , headed "Abgabe bis" $ toWgt . formatTimeGerWD . sheetActiveTo . snd3
, headed "Bewertung" $ toWgt . show . sheetType . snd3 , headed "Bewertung" $ toWgt . show . sheetType . snd3
@ -162,8 +163,8 @@ getSheetList courseEnt = do
let colAdmin = mconcat -- only show edit button for allowed course assistants let colAdmin = mconcat -- only show edit button for allowed course assistants
[ headed "Korrigiert" $ toWgt . snd . trd3 [ headed "Korrigiert" $ toWgt . snd . trd3
, headed "Eingereicht" $ toWgt . fst . trd3 , headed "Eingereicht" $ toWgt . fst . trd3
, headed "" $ \s -> linkButton "Edit" BCLink $ CSheetR tid csh (sheetName $ snd3 s) SEditR , headed "" $ \s -> simpleLink "Edit" $ CSheetR tid csh (sheetName $ snd3 s) SEditR
, headed "" $ \s -> linkButton "Delete" BCLink $ CSheetR tid csh (sheetName $ snd3 s) SDelR , headed "" $ \s -> simpleLink "Delete" $ CSheetR tid csh (sheetName $ snd3 s) SDelR
] ]
showAdmin <- case sheets of showAdmin <- case sheets of
((_,firstSheet,_):_) -> do ((_,firstSheet,_):_) -> do
@ -209,10 +210,10 @@ getSShowR tid csh shn = do
-- return desired columns -- return desired columns
return $ (file E.^. FileTitle, file E.^. FileModified, sheetFile E.^. SheetFileType) return $ (file E.^. FileTitle, file E.^. FileModified, sheetFile E.^. SheetFileType)
let colonnadeFiles = mconcat let colonnadeFiles = mconcat
[ sortable (Just "type") "Typ" $ \(_, (_,_, E.Value ftype)) -> textCell $ toPathPiece ftype [ sortable (Just "type") "Typ" $ \(_,_, E.Value ftype) -> textCell $ toPathPiece ftype
, sortable (Just "path") "Dateiname" $ anchorCell (\(_, (E.Value fName,_,E.Value fType)) -> CSheetR tid csh shn (SFileR fType fName)) , sortable (Just "path") "Dateiname" $ anchorCell (\(E.Value fName,_,E.Value fType) -> CSheetR tid csh shn (SFileR fType fName))
(\(_, (E.Value fName,_,_)) -> str2widget fName) (\(E.Value fName,_,_) -> str2widget fName)
, sortable (Just "time") "Modifikation" $ \(_, (_,E.Value modified,_)) -> stringCell $ formatTimeGerWDT modified , sortable (Just "time") "Modifikation" $ \(_,E.Value modified,_) -> stringCell $ formatTimeGerWDT (modified :: UTCTime)
] ]
fileTable <- dbTable def $ DBTable fileTable <- dbTable def $ DBTable
{ dbtSQLQuery = fileData { dbtSQLQuery = fileData

View File

@ -238,9 +238,9 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
-- Maybe construct a table to display uploaded archive files -- Maybe construct a table to display uploaded archive files
let colonnadeFiles cid = mconcat let colonnadeFiles cid = mconcat
-- [ sortable (Just "type") "Typ" $ \(_,_, E.Value ftype) -> textCell $ toPathPiece ftype -- [ sortable (Just "type") "Typ" $ \(_,_, E.Value ftype) -> textCell $ toPathPiece ftype
[ sortable (Just "path") "Dateiname" $ anchorCell (\(_, (Entity _ File{..})) -> SubmissionDownloadSingleR cid fileTitle) [ sortable (Just "path") "Dateiname" $ anchorCell (\(Entity _ File{..}) -> SubmissionDownloadSingleR cid fileTitle)
(\(_, (Entity _ File{..})) -> str2widget fileTitle) (\(Entity _ File{..}) -> str2widget fileTitle)
, sortable (Just "time") "Modifikation" $ \(_, (Entity _ File{..})) -> stringCell $ formatTimeGerWDT fileModified , sortable (Just "time") "Modifikation" $ \(Entity _ File{..}) -> stringCell $ formatTimeGerWDT fileModified
] ]
smid2ArchiveTable (smid,cid) = DBTable smid2ArchiveTable (smid,cid) = DBTable
{ dbtSQLQuery = submissionFileQuery smid { dbtSQLQuery = submissionFileQuery smid
@ -254,6 +254,7 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
, SortColumn $ \(sf `E.InnerJoin` f) -> f E.^. FileModified , SortColumn $ \(sf `E.InnerJoin` f) -> f E.^. FileModified
) )
] ]
, dbtFilter = []
} }
mFileTable <- traverse (dbTable def) . fmap smid2ArchiveTable $ (,) <$> msmid <*> mcid mFileTable <- traverse (dbTable def) . fmap smid2ArchiveTable $ (,) <$> msmid <*> mcid

View File

@ -31,10 +31,10 @@ getTermShowR = do
-- return term -- return term
-- --
let let
termData :: E.SqlExpr (Entity Term) -> E.SqlQuery (E.SqlExpr (Entity Term), E.SqlExpr (E.Value Int64))
termData term = do termData term = do
-- E.orderBy [E.desc $ term E.^. TermStart ] -- E.orderBy [E.desc $ term E.^. TermStart ]
let courseCount :: E.SqlExpr (E.Value Int) let courseCount = E.sub_select . E.from $ \course -> do
courseCount = E.sub_select . E.from $ \course -> do
E.where_ $ term E.^. TermId E.==. course E.^. CourseTerm E.where_ $ term E.^. TermId E.==. course E.^. CourseTerm
return E.countRows return E.countRows
return (term, courseCount) return (term, courseCount)
@ -42,7 +42,7 @@ getTermShowR = do
provideRep $ toJSON . map fst <$> runDB (E.select $ E.from termData) provideRep $ toJSON . map fst <$> runDB (E.select $ E.from termData)
provideRep $ do provideRep $ do
let colonnadeTerms = mconcat let colonnadeTerms = mconcat
[ sortable Nothing "Kürzel" $ \(_, (Entity tid Term{..},_)) -> cell $ do [ sortable Nothing "Kürzel" $ \(Entity tid Term{..},_) -> cell $ do
-- Scrap this if to slow, create term edit page instead -- Scrap this if to slow, create term edit page instead
adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False adminLink <- handlerToWidget $ isAuthorized (TermEditExistR tid) False
[whamlet| [whamlet|
@ -52,22 +52,20 @@ getTermShowR = do
$else $else
#{termToText termName} #{termToText termName}
|] |]
, sortable (Just "lecture-start") "Beginn Vorlesungen" $ \(_, (Entity _ Term{..},_)) -> , sortable (Just "lecture-start") "Beginn Vorlesungen" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureStart stringCell $ formatTimeGerWD termLectureStart
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(_, (Entity _ Term{..},_)) -> , sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureEnd stringCell $ formatTimeGerWD termLectureEnd
, sortable Nothing "Aktiv" $ \(_, (Entity _ Term{..},_)) -> , sortable Nothing "Aktiv" $ \(Entity _ Term{..},_) ->
textCell $ bool "" tickmark termActive textCell $ bool "" tickmark termActive
, sortable Nothing "Kursliste" $ \(_, (Entity tid Term{..}, E.Value numCourses)) -> , sortable Nothing "Kursliste" $ anchorCell
cell [whamlet| (\(Entity tid _, _) -> TermCourseListR tid)
<a href=@{TermCourseListR tid}> (\(_, E.Value numCourses) -> [whamlet|_{MsgNumCourses numCourses}|])
#{show numCourses} Kurse , sortable (Just "start") "Semesteranfang" $ \(Entity _ Term{..},_) ->
|]
, sortable (Just "start") "Semesteranfang" $ \(_, (Entity _ Term{..},_)) ->
stringCell $ formatTimeGerWD termStart stringCell $ formatTimeGerWD termStart
, sortable (Just "end") "Semesterende" $ \(_, (Entity _ Term{..},_)) -> , sortable (Just "end") "Semesterende" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termEnd stringCell $ formatTimeGerWD termEnd
, sortable Nothing "Feiertage im Semester" $ \(_, (Entity _ Term{..},_)) -> , sortable Nothing "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
stringCell $ (intercalate ", ") $ map formatTimeGerWD termHolidays stringCell $ (intercalate ", ") $ map formatTimeGerWD termHolidays
] ]
table <- dbTable def $ DBTable table <- dbTable def $ DBTable
@ -86,6 +84,10 @@ getTermShowR = do
, SortColumn $ \term -> term E.^. TermLectureEnd , SortColumn $ \term -> term E.^. TermLectureEnd
) )
] ]
, dbtFilter = [ ( "active"
, FilterColumn $ \term -> (term E.^. TermActive :: E.SqlExpr (E.Value Bool))
)
]
, dbtAttrs = tableDefault , dbtAttrs = tableDefault
, dbtIdent = "terms" :: Text , dbtIdent = "terms" :: Text
} }

View File

@ -135,6 +135,8 @@ linkButton lbl cls url = [whamlet| <a href=@{url} .btn .#{bcc2txt cls} role=butt
-- |] -- |]
-- <input .btn .#{bcc2txt cls} type="submit" value=^{lbl}> -- <input .btn .#{bcc2txt cls} type="submit" value=^{lbl}>
simpleLink :: Widget -> Route UniWorX -> Widget
simpleLink lbl url = [whamlet| <a href=@{url}>^{lbl} |]
buttonField :: Button a => a -> Field Handler a buttonField :: Button a => a -> Field Handler a
buttonField btn = Field {fieldParse, fieldView, fieldEnctype} buttonField btn = Field {fieldParse, fieldView, fieldEnctype}

View File

@ -7,10 +7,15 @@
, LambdaCase , LambdaCase
, ViewPatterns , ViewPatterns
, FlexibleContexts , FlexibleContexts
, FlexibleInstances
, MultiParamTypeClasses
, TypeFamilies
#-} #-}
module Handler.Utils.Table.Pagination module Handler.Utils.Table.Pagination
( SortColumn(..), SortDirection(..) ( SortColumn(..), SortDirection(..)
, FilterColumn(..), IsFilterColumn
, DBRow(..), DBOutput
, DBTable(..) , DBTable(..)
, PaginationSettings(..) , PaginationSettings(..)
, PSValidator(..) , PSValidator(..)
@ -36,11 +41,14 @@ import qualified Network.Wai as Wai
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import Control.Monad.RWS hiding ((<>), Foldable(..), mapM_) import Control.Monad.RWS hiding ((<>), Foldable(..), mapM_, forM_)
import Data.Map (Map, (!)) import Data.Map (Map, (!))
import qualified Data.Map as Map
import Colonnade hiding (bool, fromMaybe) import Data.Profunctor (lmap)
import Colonnade hiding (bool, fromMaybe, singleton)
import Colonnade.Encode import Colonnade.Encode
import Yesod.Colonnade import Yesod.Colonnade
@ -64,22 +72,65 @@ instance PathPiece SortDirection where
sqlSortDirection :: t -> (SortColumn t, SortDirection) -> E.SqlExpr E.OrderBy sqlSortDirection :: t -> (SortColumn t, SortDirection) -> E.SqlExpr E.OrderBy
sqlSortDirection t (SortColumn e, SortAsc ) = E.asc $ e t sqlSortDirection t (SortColumn e, SortAsc ) = E.asc $ e t
sqlSortDirection t (SortColumn e, SortDesc) = E.desc $ e t sqlSortDirection t (SortColumn e, SortDesc) = E.desc $ e t
data DBTable = forall a r h i t.
( ToSortable h data FilterColumn t = forall a. IsFilterColumn t a => FilterColumn a
, E.SqlSelect a r
filterColumn :: FilterColumn t -> [Text] -> t -> E.SqlExpr (E.Value Bool)
filterColumn (FilterColumn f) = filterColumn' f
class IsFilterColumn t a where
filterColumn' :: a -> [Text] -> t -> E.SqlExpr (E.Value Bool)
instance IsFilterColumn t (E.SqlExpr (E.Value Bool)) where
filterColumn' fin _ _ = fin
instance IsFilterColumn t cont => IsFilterColumn t (t -> cont) where
filterColumn' cont is t = filterColumn' (cont t) is t
instance {-# OVERLAPPABLE #-} (PathPiece (Element l), IsFilterColumn t cont, MonoPointed l, Monoid l) => IsFilterColumn t (l -> cont) where
filterColumn' cont is t = filterColumn' (cont input) is' t
where
(input, ($ []) -> is') = go (mempty, id) is
go acc [] = acc
go (acc, is') (i:is)
| Just i' <- fromPathPiece i = go (acc `mappend` singleton i', is') is
| otherwise = go (acc, is' . (i:)) is
data DBRow r = DBRow
{ dbrIndex, dbrCount :: Int64
, dbrOutput :: r
}
class DBOutput r r' where
dbProj :: r -> r'
instance DBOutput r r where
dbProj = id
instance DBOutput (DBRow r) r where
dbProj = dbrOutput
instance DBOutput (DBRow r) (Int64, r) where
dbProj = (,) <$> dbrIndex <*> dbrOutput
data DBTable = forall a r r' h i t.
( ToSortable h, Functor h
, E.SqlSelect a r, DBOutput (DBRow r) r'
, PathPiece i , PathPiece i
, E.From E.SqlQuery E.SqlExpr E.SqlBackend t , E.From E.SqlQuery E.SqlExpr E.SqlBackend t
) => DBTable ) => DBTable
{ dbtSQLQuery :: t -> E.SqlQuery a { dbtSQLQuery :: t -> E.SqlQuery a
, dbtColonnade :: Colonnade h (Int64, r) (Cell UniWorX) , dbtColonnade :: Colonnade h r' (Cell UniWorX)
, dbtSorting :: Map Text (SortColumn t) , dbtSorting :: Map Text (SortColumn t)
, dbtFilter :: Map Text (FilterColumn t)
, dbtAttrs :: Attribute , dbtAttrs :: Attribute
, dbtIdent :: i , dbtIdent :: i
} }
data PaginationSettings = PaginationSettings data PaginationSettings = PaginationSettings
{ psSorting :: [(Text, SortDirection)] { psSorting :: [(Text, SortDirection)]
, psFilter :: Map Text [Text]
, psLimit :: Int64 , psLimit :: Int64
, psPage :: Int64 , psPage :: Int64
, psShortcircuit :: Bool , psShortcircuit :: Bool
@ -88,15 +139,16 @@ data PaginationSettings = PaginationSettings
instance Default PaginationSettings where instance Default PaginationSettings where
def = PaginationSettings def = PaginationSettings
{ psSorting = [] { psSorting = []
, psFilter = Map.empty
, psLimit = 50 , psLimit = 50
, psPage = 0 , psPage = 0
, psShortcircuit = False , psShortcircuit = False
} }
newtype PSValidator = PSValidator { runPSValidator :: Maybe PaginationSettings -> ([SomeMessage UniWorX], PaginationSettings) } newtype PSValidator = PSValidator { runPSValidator :: DBTable -> Maybe PaginationSettings -> ([SomeMessage UniWorX], PaginationSettings) }
instance Default PSValidator where instance Default PSValidator where
def = PSValidator $ \case def = PSValidator $ \DBTable{..} -> \case
Nothing -> def Nothing -> def
Just ps -> swap . (\act -> execRWS act () ps) $ do Just ps -> swap . (\act -> execRWS act () ps) $ do
l <- gets psLimit l <- gets psLimit
@ -106,7 +158,7 @@ instance Default PSValidator where
dbTable :: PSValidator -> DBTable -> Handler Widget dbTable :: PSValidator -> DBTable -> Handler Widget
dbTable PSValidator{..} DBTable{ dbtIdent = (toPathPiece -> dbtIdent), .. } = do dbTable PSValidator{..} dbtable@(DBTable{ dbtIdent = (toPathPiece -> dbtIdent), dbtColonnade = (lmap dbProj -> dbtColonnade), .. }) = do
let let
sortingOptions = mkOptionList sortingOptions = mkOptionList
[ Option t' (t, d) t' [ Option t' (t, d) t'
@ -114,35 +166,43 @@ dbTable PSValidator{..} DBTable{ dbtIdent = (toPathPiece -> dbtIdent), .. } = do
, d <- [SortAsc, SortDesc] , d <- [SortAsc, SortDesc]
, let t' = t <> "-" <> toPathPiece d , let t' = t <> "-" <> toPathPiece d
] ]
(_, defPS) = runPSValidator Nothing (_, defPS) = runPSValidator dbtable Nothing
wIdent n wIdent n
| not $ null dbtIdent = dbtIdent <> "-" <> n | not $ null dbtIdent = dbtIdent <> "-" <> n
| otherwise = n | otherwise = n
dbtAttrs' dbtAttrs'
| not $ null dbtIdent = Html5.id (fromString $ unpack dbtIdent) <> dbtAttrs | not $ null dbtIdent = Html5.id (fromString $ unpack dbtIdent) <> dbtAttrs
| otherwise = dbtAttrs | otherwise = dbtAttrs
multiTextField = Field
{ fieldParse = \ts _ -> return . Right $ Just ts
, fieldView = undefined
, fieldEnctype = UrlEncoded
}
psResult <- runInputGetResult $ PaginationSettings psResult <- runInputGetResult $ PaginationSettings
<$> (fromMaybe [] <$> iopt (multiSelectField $ return sortingOptions) (wIdent "sorting")) <$> (fromMaybe [] <$> iopt (multiSelectField $ return sortingOptions) (wIdent "sorting"))
<*> (Map.mapMaybe ((\args -> args <$ guard (not $ null args)) =<<) <$> Map.traverseWithKey (\k _ -> iopt multiTextField (wIdent $ "filter." <> k)) dbtFilter)
<*> (fromMaybe (psLimit defPS) <$> iopt intField (wIdent "pagesize")) <*> (fromMaybe (psLimit defPS) <$> iopt intField (wIdent "pagesize"))
<*> (fromMaybe (psPage defPS) <$> iopt intField (wIdent "page")) <*> (fromMaybe (psPage defPS) <$> iopt intField (wIdent "page"))
<*> ireq checkBoxField (wIdent "table-only") <*> ireq checkBoxField (wIdent "table-only")
$(logDebug) . tshow $ (,,,) <$> (length . psSorting <$> psResult) $(logDebug) . tshow $ (,,,,) <$> (length . psSorting <$> psResult)
<*> (psLimit <$> psResult) <*> (Map.keys . psFilter <$> psResult)
<*> (psPage <$> psResult) <*> (psLimit <$> psResult)
<*> (psShortcircuit <$> psResult) <*> (psPage <$> psResult)
<*> (psShortcircuit <$> psResult)
let let
(errs, PaginationSettings{..}) = case psResult of (errs, PaginationSettings{..}) = case psResult of
FormSuccess ps -> runPSValidator $ Just ps FormSuccess ps -> runPSValidator dbtable $ Just ps
FormFailure errs -> first (map SomeMessage errs <>) $ runPSValidator Nothing FormFailure errs -> first (map SomeMessage errs <>) $ runPSValidator dbtable Nothing
FormMissing -> runPSValidator Nothing FormMissing -> runPSValidator dbtable Nothing
psSorting' = map (first (dbtSorting !)) psSorting psSorting' = map (first (dbtSorting !)) psSorting
sqlQuery' = E.from $ \t -> dbtSQLQuery t sqlQuery' = E.from $ \t -> dbtSQLQuery t
<* E.orderBy (map (sqlSortDirection t) psSorting') <* E.orderBy (map (sqlSortDirection t) psSorting')
<* E.limit psLimit <* E.limit psLimit
<* E.offset (psPage * psLimit) <* E.offset (psPage * psLimit)
<* E.where_ (Map.foldrWithKey (\key args expr -> filterColumn (dbtFilter ! key) args t E.&&. expr) (E.val True) psFilter)
mapM_ (addMessageI "warning") errs mapM_ (addMessageI "warning") errs
@ -152,7 +212,7 @@ dbTable PSValidator{..} DBTable{ dbtIdent = (toPathPiece -> dbtIdent), .. } = do
rowCount rowCount
| ((_, E.Value n), _):_ <- rows' = n | ((_, E.Value n), _):_ <- rows' = n
| otherwise = 0 | otherwise = 0
rows = map (\((E.Value i, _), r) -> (i, r)) rows' rows = map (\((E.Value i, E.Value n), r) -> DBRow i n r) rows'
bool return (sendResponse <=< tblLayout) psShortcircuit $ do bool return (sendResponse <=< tblLayout) psShortcircuit $ do
getParams <- handlerToWidget $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest getParams <- handlerToWidget $ queryToQueryText . Wai.queryString . reqWaiRequest <$> getRequest

View File

@ -12,7 +12,7 @@ import Colonnade
import Colonnade.Encode import Colonnade.Encode
data Sortable a = Sortable data Sortable a = Sortable
{ sortableKey :: (Maybe Text) { sortableKey :: Maybe Text
, sortableContent :: a , sortableContent :: a
} }
@ -23,6 +23,9 @@ instance Headedness Sortable where
headednessPure = Sortable Nothing headednessPure = Sortable Nothing
headednessExtract = Just $ \(Sortable _ x) -> x headednessExtract = Just $ \(Sortable _ x) -> x
headednessExtractForall = Just $ ExtractForall (\(Sortable _ x) -> x) headednessExtractForall = Just $ ExtractForall (\(Sortable _ x) -> x)
instance Functor Sortable where
fmap f Sortable{..} = Sortable { sortableContent = f sortableContent, .. }
newtype SortableP s = SortableP { toSortable :: forall a. s a -> Sortable a} newtype SortableP s = SortableP { toSortable :: forall a. s a -> Sortable a}

View File

@ -32,3 +32,6 @@
.glyphicon--logout::before { .glyphicon--logout::before {
content: '\e163'; content: '\e163';
} }
.glyphicon--none::before {
content: '';
}

View File

@ -1,8 +1,6 @@
.tab-group { .tab-group {
/* box-shadow: 0 0 0 18px white, 0 0 0 20px #b3b7c1; */
border-top: 2px solid #dcdcdc; border-top: 2px solid #dcdcdc;
padding-top: 30px; padding-top: 30px;
margin-top: 40px;
} }
.tab-group-openers { .tab-group-openers {

View File

@ -30,9 +30,13 @@
$maybe regTo <- courseRegisterTo course $maybe regTo <- courseRegisterTo course
\ bis #{formatTimeGerWD regTo} \ bis #{formatTimeGerWD regTo}
<tr>
<th>
<td>
$# if allowed to register $# if allowed to register
<div .course__registration> <div .course__registration.container>
<a href="#">Anmelden <button class="btn btn-primary">
<a href="#">Anmelden
$# <form method=post action=@{CourseR tid csh CShow} enctype=#{regEnctype}> $# <form method=post action=@{CourseR tid csh CShow} enctype=#{regEnctype}>
$# ^{regWidget} $# ^{regWidget}
@ -63,11 +67,7 @@
<td>Do 18.04.18 <td>Do 18.04.18
<td>NotGraded <td>NotGraded
<tr .no-hover.no-stripe> <tr .no-hover.no-stripe>
<td>&nbsp; <td colspan="4">
<td>&nbsp;
<td>&nbsp;
<td>&nbsp;
<td>
<a href="/course/S2018/ffp/ex/new" #modal-toggler__new-sheet>Neues Übungsblatt anlegen <a href="/course/S2018/ffp/ex/new" #modal-toggler__new-sheet>Neues Übungsblatt anlegen
<div .tab data-tab-name="Übungsgruppen"> <div .tab data-tab-name="Übungsgruppen">
<h3 .tab-title>Übungsgruppen <h3 .tab-title>Übungsgruppen
@ -82,16 +82,14 @@
<th>Anmeldung bis <th>Anmeldung bis
<tbody> <tbody>
<tr> <tr>
<td> <td>Gruppe 1
<a href="#">Gruppe 1
<td>Montag 10:00 - 12:00 <td>Montag 10:00 - 12:00
<td>N/A <td>N/A
<td>2/10 <td>2/10
<td>Tutor1 Tutoren <td>Tutor1 Tutoren
<td>Do 21.02.2019, 19:00 <td>Do 21.02.2019, 19:00
<tr> <tr>
<td> <td>Gruppe 2
<a href="#">Gruppe 2
<td>Montag 12:00 - 14:00 <td>Montag 12:00 - 14:00
<td>N/A <td>N/A
<td>0/10 <td>0/10

View File

@ -0,0 +1,3 @@
.course__registration {
margin-top: 20px;
}

View File

@ -13,6 +13,10 @@
$with status2 <- bool status "info" (status == "") $with status2 <- bool status "info" (status == "")
<div class="alert alert-#{status2}">#{msg} <div class="alert alert-#{status2}">#{msg}
$maybe headline <- contentHeadline
<h1>
^{headline}
<!-- prime page actions --> <!-- prime page actions -->
^{pageactionprime} ^{pageactionprime}

View File

@ -126,12 +126,11 @@ table {
th, td { th, td {
text-align: left; text-align: left;
padding: 0 13px 0 7px; padding: 7px;
vertical-align: baseline; vertical-align: baseline;
} }
th:first-child, th:first-child,
td:first-child { td:first-child {
padding-left: 0;
border-left: 0; border-left: 0;
} }
th { th {
@ -189,21 +188,15 @@ button,
transition: all .1s; transition: all .1s;
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
border-radius: 4px;
display: inline-block; display: inline-block;
}
input.btn-primary,
button.btn-primary,
a.btn.btn-primary,
.btn.btn-primary {
background-color: var(--primarybase);
}
input.btn-info, a {
button.btn-info, color: white;
a.btn.btn-info, }
.btn.btn-info {
background-color: var(--infobase) a:hover {
color: white;
}
} }
input[type="submit"][disabled], input[type="submit"][disabled],
@ -226,6 +219,20 @@ a.btn:not([disabled]):hover,
color: white; color: white;
} }
input.btn-primary,
button.btn-primary,
a.btn.btn-primary,
.btn.btn-primary {
background-color: var(--primarybase);
}
input.btn-info,
button.btn-info,
a.btn.btn-info,
.btn.btn-info {
background-color: var(--infobase)
}
input[type="submit"].btn-info:hover, input[type="submit"].btn-info:hover,
input[type="button"].btn-info:hover, input[type="button"].btn-info:hover,
button.btn-info:hover, button.btn-info:hover,
@ -237,4 +244,3 @@ a.btn.btn-info:hover,
.alert-debug { .alert-debug {
background-color: rgb(240, 30, 240); background-color: rgb(240, 30, 240);
} }

View File

@ -43,21 +43,3 @@
^{modal ".toggler2" (Just "Test Inhalt für Modal")} ^{modal ".toggler2" (Just "Test Inhalt für Modal")}
<div .btn.toggler2>Klick mich für Content-Test <div .btn.toggler2>Klick mich für Content-Test
<noscript>(Für Modals bitte JS aktivieren)</noscript> <noscript>(Für Modals bitte JS aktivieren)</noscript>
<li><br>
Multi-File-Input für bereits hochgeladene Dateien:
<form>
<div .form-group>
<label .form-group__label>Datei(en)
$# file 1
<div .file-checkbox__container>
<label .file-checkbox__label.reactive-label.btn for="f2-1">Datenschutz.txt
<div .checkbox>
<input .file-checkbox id="f2-1" name="f2" value="Datenschutz.txt" type="checkbox">
<label for="f2-1">
$# file 2
<div .file-checkbox__container>
<label .file-checkbox__label.reactive-label.btn for="f2-2">fill-db.hs
<div .checkbox>
<input .file-checkbox id="f2-2" name="f2" value="fill-db.hs" type="checkbox">
<label for="f2-2">

View File

@ -6,13 +6,6 @@ $forall FileUploadInfo{..} <- fileInfos
<input .file-checkbox.js-file-checkbox id=#{fuiHtmlId} name=#{fieldName} :fuiChecked:checked value=#{toPathPiece fuiId} type="checkbox"> <input .file-checkbox.js-file-checkbox id=#{fuiHtmlId} name=#{fieldName} :fuiChecked:checked value=#{toPathPiece fuiId} type="checkbox">
<label for=#{fuiHtmlId}> <label for=#{fuiHtmlId}>
<div .file-checkbox__container.file-checkbox__container--checked>
<label .file-checkbox__label.reactive-label.btn for=fi1>file1.txt
<div .checkbox>
<input .file-checkbox.js-file-checkbox id=fi1 name=file checked value="file1.txt" type="checkbox">
<label for=fi1>
$# new files $# new files
<input type="file" name=#{fieldName} multiple> <input type="file" name=#{fieldName} multiple>

View File

@ -13,7 +13,7 @@ form {
grid-gap: 5px; grid-gap: 5px;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
margin: 10px 0; margin: 17px 0;
padding-left: 10px; padding-left: 10px;
border-left: 2px solid transparent; border-left: 2px solid transparent;
} }
@ -44,20 +44,37 @@ input[type="password"],
input[type="url"], input[type="url"],
input[type="number"], input[type="number"],
input[type="email"] { input[type="email"] {
background-color: rgba(0, 0, 0, 0.05); /* from bulma.css */
padding: 7px 3px 7px; background-color: #fff;
outline: 0; border-color: #dbdbdb;
border: 0; color: #363636;
border-bottom: 2px solid var(--darkbase); box-shadow: inset 0 2px 3px 1px rgba(50,50,50,.1);
box-shadow: 0 2px 13px rgba(0, 0, 0, 0.05);
color: var(--fontbase);
transition: all .1s;
font-size: 16px;
min-width: 400px; min-width: 400px;
-webkit-appearance: none;
align-items: center;
border: 1px solid transparent;
border-radius: 4px;
display: inline-flex;
font-size: 1rem;
height: 2.25em;
justify-content: flex-start;
line-height: 1.5;
padding-bottom: calc(.375em - 1px);
padding-left: calc(.625em - 1px);
padding-right: calc(.625em - 1px);
padding-top: calc(.375em - 1px);
position: relative;
vertical-align: top;
} }
.form-group--required { .form-group--required {
.form-group__label::before {
content: '*';
position: absolute;
left: -14px;
}
input, textarea { input, textarea {
border-bottom-color: var(--lighterbase); border-bottom-color: var(--lighterbase);
} }
@ -82,32 +99,42 @@ input[type="password"]:focus,
input[type="url"]:focus, input[type="url"]:focus,
input[type="number"]:focus, input[type="number"]:focus,
input[type="email"]:focus { input[type="email"]:focus {
border-bottom-color: var(--lightbase); /* border-bottom-color: var(--lightbase);
background-color: transparent; background-color: transparent;
box-shadow: 0 0 13px var(--lighterbase); box-shadow: 0 0 13px var(--lighterbase); */
border-color: #3273dc;
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
outline: 0;
} }
/* BUTTON STYLE SEE default-layout.lucius */ /* BUTTON STYLE SEE default-layout.lucius */
/* TEXTAREAS */ /* TEXTAREAS */
textarea { textarea {
outline: 0; -webkit-appearance: none;
border: 0; border: 1px solid transparent;
padding: 7px 4px; border-radius: 2px;
min-width: 400px; display: inline-flex;
min-height: 100px; font-size: 1rem;
font-family: var(--fontfamilybase); height: 170px;
font-size: 16px; width: 400px;
color: var(--fontbase); line-height: 1.5;
background-color: rgba(0, 0, 0, 0.05); padding-bottom: calc(.375em - 1px);
box-shadow: 0 2px 13px rgba(0, 0, 0, 0.05); padding-left: calc(.625em - 1px);
border-bottom: 2px solid var(--darkbase); padding-right: calc(.625em - 1px);
padding-top: calc(.375em - 1px);
position: relative;
vertical-align: top;
background-color: #fff;
border-color: #dbdbdb;
color: #363636;
box-shadow: inset 0 1px 2px rgba(10,10,10,.1);
} }
textarea:focus { textarea:focus {
background-color: transparent; border-color: #3273dc;
border-bottom-color: var(--lightbase); box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
box-shadow: 0 0 13px var(--lighterbase); outline: 0;
} }
/* CUSTOM LEGACY CHECKBOX AND RADIO BOXES */ /* CUSTOM LEGACY CHECKBOX AND RADIO BOXES */

View File

@ -1,4 +1,2 @@
<div .container> <div .container>
<h1>Semesterübersicht
^{table} ^{table}

View File

@ -8,8 +8,7 @@ $newline never
$of NavbarAside (MenuItem label mIcon route _) $of NavbarAside (MenuItem label mIcon route _)
<li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active> <li .asidenav__list-item :Just route == mcurrentRoute:.asidenav__list-item--active>
<a .asidenav__link-wrapper href=@{route}> <a .asidenav__link-wrapper href=@{route}>
$if isJust mIcon <div .glyphicon.glyphicon--#{fromMaybe "none" mIcon}>
<div .glyphicon.glyphicon--#{fromMaybe "" mIcon}>
<div .asidenav__link-label>#{label} <div .asidenav__link-label>#{label}
$of _ $of _

View File

@ -188,7 +188,7 @@
position: absolute; position: absolute;
color: var(--greybase); color: var(--greybase);
line-height: 50px; line-height: 50px;
opacity: 0.3; opacity: 0.2;
right: 10px; right: 10px;
top: 0; top: 0;
font-size: 40px; font-size: 40px;

View File

@ -1,10 +1,10 @@
.page-nav-prime { .page-nav-prime {
background-color: var(--lightgreybase); background-color: rgba(200, 200, 200, 0.2);
padding: 13px; padding: 13px;
margin-top: 30px;
} }
.page-nav-prime .pagenav__list { .page-nav-prime .pagenav__list {
margin: 7px 0 0;
display: block; display: block;
} }
.page-nav-prime .pagenav__list-item { .page-nav-prime .pagenav__list-item {