Merge branch 'master' into pageactions

This commit is contained in:
Felix Hamann 2019-02-04 22:06:22 +01:00
commit 8110405534
36 changed files with 547 additions and 432 deletions

View File

@ -1,3 +1,7 @@
* Version 30.01.2019
Designänderungen
* Version 16.01.2019 * Version 16.01.2019
Links für Bequemlichkeiten hinzugefügt (z.B. aktuelles Übungsblatt) Links für Bequemlichkeiten hinzugefügt (z.B. aktuelles Übungsblatt)

View File

@ -7,6 +7,7 @@ BtnHijack: Sitzung übernehmen
Aborted: Abgebrochen Aborted: Abgebrochen
Registered: Angemeldet Registered: Angemeldet
RegisteredSince date@Text: Angemeldet seit #{date}
RegisterFrom: Anmeldungen von RegisterFrom: Anmeldungen von
RegisterTo: Anmeldungen bis RegisterTo: Anmeldungen bis
DeRegUntil: Abmeldungen bis DeRegUntil: Abmeldungen bis
@ -69,7 +70,7 @@ CourseSemester: Semester
CourseSchool: Institut CourseSchool: Institut
CourseSchoolShort: Fach CourseSchoolShort: Fach
CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gesetzt CourseSecretTip: Anmeldung zum Kurs erfordert Eingabe des Passworts, sofern gesetzt
CourseRegisterFromTip: Ohne Datum ist keine eigenständige Anmeldung von Studierenden möglich CourseRegisterFromTip: Ohne Datum ist KEINE eigenständige Anmeldung von Studierenden möglich
CourseRegisterToTip: Anmeldung darf auch ohne Begrenzung möglich sein CourseRegisterToTip: Anmeldung darf auch ohne Begrenzung möglich sein
CourseDeregisterUntilTip: Abmeldung darf auch ohne Begrenzung möglich sein CourseDeregisterUntilTip: Abmeldung darf auch ohne Begrenzung möglich sein
CourseFilterSearch: Volltext-Suche CourseFilterSearch: Volltext-Suche
@ -108,7 +109,7 @@ SheetSolutionFrom: Lösung ab
SheetMarking: Hinweise für Korrektoren SheetMarking: Hinweise für Korrektoren
SheetType: Wertung SheetType: Wertung
SheetInvisible: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar! SheetInvisible: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar!
SheetInvisibleUntil mFrom@Text: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar bis #{mFrom}! SheetInvisibleUntil date@Text: Dieses Übungsblatt ist für Teilnehmer momentan unsichtbar bis #{date}!
SheetName: Name SheetName: Name
SheetDescription: Hinweise für Teilnehmer SheetDescription: Hinweise für Teilnehmer
SheetGroup: Gruppenabgabe SheetGroup: Gruppenabgabe
@ -207,6 +208,7 @@ CorByProportionOnly proportion@Rational: #{display proportion} Anteile
CorByProportionIncludingTutorial proportion@Rational: #{display proportion} Anteile - Tutorium CorByProportionIncludingTutorial proportion@Rational: #{display proportion} Anteile - Tutorium
CorByProportionExcludingTutorial proportion@Rational: #{display proportion} Anteile + Tutorium CorByProportionExcludingTutorial proportion@Rational: #{display proportion} Anteile + Tutorium
RowCount count@Int64: #{display count} #{pluralDE count "Eintrag" "Einträge"} insgesamt
DeleteRow: Zeile entfernen DeleteRow: Zeile entfernen
ProportionNegative: Anteile dürfen nicht negativ sein ProportionNegative: Anteile dürfen nicht negativ sein
CorrectorUpdated: Korrektor erfolgreich aktualisiert CorrectorUpdated: Korrektor erfolgreich aktualisiert
@ -240,7 +242,7 @@ MultiFileUploadInfo: (Mehrere Dateien mit Shift oder Strg auswählen)
NrColumn: Nr NrColumn: Nr
SelectColumn: Auswahl SelectColumn: Auswahl
DBTablePagesize: Einträge DBTablePagesize: Einträge pro Seite
DBTablePagesizeAll: Alle DBTablePagesizeAll: Alle
CorrDownload: Herunterladen CorrDownload: Herunterladen
@ -568,15 +570,16 @@ MenuSubmissions: Abgaben
MenuSheetList: Übungsblätter MenuSheetList: Übungsblätter
MenuSheetNew: Neues Übungsblatt anlegen MenuSheetNew: Neues Übungsblatt anlegen
MenuSheetCurrent: Aktuelles Übungsblatt MenuSheetCurrent: Aktuelles Übungsblatt
MenuSheetLastInactive: Zuletzt abgegebenes Übungsblatt MenuSheetOldUnassigned: Abgaben ohne Korrektor
MenuCourseEdit: Kurs editieren MenuCourseEdit: Kurs editieren
MenuCourseNewTemplate: Als neuen Kurs klonen MenuCourseClone: Als neuen Kurs klonen
MenuCourseDelete: Kurs löschen MenuCourseDelete: Kurs löschen
MenuSubmissionNew: Abgabe anlegen MenuSubmissionNew: Abgabe anlegen
MenuSubmissionOwn: Abgabe MenuSubmissionOwn: Abgabe
MenuCorrectors: Korrektoren MenuCorrectors: Korrektoren
MenuSheetEdit: Übungsblatt editieren MenuSheetEdit: Übungsblatt editieren
MenuSheetDelete: Übungsblatt löschen MenuSheetDelete: Übungsblatt löschen
MenuSheetClone: Als neues Übungsblatt klonen
MenuCorrectionsUpload: Korrekturen hochladen MenuCorrectionsUpload: Korrekturen hochladen
MenuCorrectionsCreate: Abgaben registrieren MenuCorrectionsCreate: Abgaben registrieren
MenuCorrectionsGrade: Abgaben bewerten MenuCorrectionsGrade: Abgaben bewerten
@ -608,4 +611,4 @@ DeleteCopyStringIfSure n@Int: Wenn Sie sich sicher sind, dass Sie #{pluralDE n "
DeleteConfirmation: Bestätigung DeleteConfirmation: Bestätigung
DeleteConfirmationWrong: Bestätigung muss genau dem angezeigten Text entsprechen. DeleteConfirmationWrong: Bestätigung muss genau dem angezeigten Text entsprechen.
DBTIRowsMissing n@Int: #{pluralDE n "Eine Zeile ist" "Einige Zeile sind"} aus der Datenbank verschwunden, seit das Formular für Sie generiert wurde DBTIRowsMissing n@Int: #{pluralDE n "Eine Zeile ist" "Einige Zeile sind"} aus der Datenbank verschwunden, seit das Formular für Sie generiert wurde

6
routes
View File

@ -72,9 +72,9 @@
/notes CNotesR GET POST !corrector /notes CNotesR GET POST !corrector
/subs CCorrectionsR GET POST /subs CCorrectionsR GET POST
/ex SheetListR GET !registered !materials !corrector /ex SheetListR GET !registered !materials !corrector
!/ex/new SheetNewR GET POST /ex/new SheetNewR GET POST
!/ex/current SheetCurrentR GET !free -- just a redirect /ex/current SheetCurrentR GET !registered !materials !corrector
!/ex/lastinactive SheetLastInactiveR GET !free -- just a redirect /ex/unassigned SheetOldUnassigned GET
/ex/#SheetName SheetR: /ex/#SheetName SheetR:
/show SShowR GET !timeANDregistered !timeANDmaterials !corrector /show SShowR GET !timeANDregistered !timeANDmaterials !corrector
/edit SEditR GET POST /edit SEditR GET POST

View File

@ -14,15 +14,14 @@ import qualified Data.CaseInsensitive as CI
data DummyMessage = MsgDummyIdent data DummyMessage = MsgDummyIdent
| MsgDummyNoFormData | MsgDummyNoFormData
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
dummyForm :: ( RenderMessage site FormMessage dummyForm :: ( RenderMessage site FormMessage
, RenderMessage site DummyMessage , RenderMessage site DummyMessage
, RenderMessage site ButtonMessage
, YesodPersist site , YesodPersist site
, SqlBackendCanRead (YesodPersistBackend site) , SqlBackendCanRead (YesodPersistBackend site)
, Button site SubmitButton , Button site ButtonSubmit
, Show (ButtonCssClass site)
) => AForm (HandlerT site IO) (CI Text) ) => AForm (HandlerT site IO) (CI Text)
dummyForm = areq (selectField userList) (fslI MsgDummyIdent) Nothing dummyForm = areq (selectField userList) (fslI MsgDummyIdent) Nothing
<* submitButton <* submitButton
@ -35,9 +34,7 @@ dummyLogin :: ( YesodAuth site
, SqlBackendCanRead (YesodPersistBackend site) , SqlBackendCanRead (YesodPersistBackend site)
, RenderMessage site FormMessage , RenderMessage site FormMessage
, RenderMessage site DummyMessage , RenderMessage site DummyMessage
, RenderMessage site ButtonMessage , Button site ButtonSubmit
, Button site SubmitButton
, Show (ButtonCssClass site)
) => AuthPlugin site ) => AuthPlugin site
dummyLogin = AuthPlugin{..} dummyLogin = AuthPlugin{..}
where where

View File

@ -28,13 +28,14 @@ import qualified Yesod.Auth.Message as Msg
data CampusLogin = CampusLogin data CampusLogin = CampusLogin
{ campusIdent :: CI Text { campusIdent :: CI Text
, campusPassword :: Text , campusPassword :: Text
} } deriving (Generic, Typeable)
data CampusMessage = MsgCampusIdentNote data CampusMessage = MsgCampusIdentNote
| MsgCampusIdent | MsgCampusIdent
| MsgCampusPassword | MsgCampusPassword
| MsgCampusSubmit | MsgCampusSubmit
| MsgCampusInvalidCredentials | MsgCampusInvalidCredentials
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
findUser :: LdapConf -> Ldap -> Text -> [Ldap.Attr] -> IO [Ldap.SearchEntry] findUser :: LdapConf -> Ldap -> Text -> [Ldap.Attr] -> IO [Ldap.SearchEntry]
@ -53,9 +54,7 @@ userPrincipalName = Ldap.Attr "userPrincipalName"
campusForm :: ( RenderMessage site FormMessage campusForm :: ( RenderMessage site FormMessage
, RenderMessage site CampusMessage , RenderMessage site CampusMessage
, RenderMessage site ButtonMessage , Button site ButtonSubmit
, Button site SubmitButton
, Show (ButtonCssClass site)
) => AForm (HandlerT site IO) CampusLogin ) => AForm (HandlerT site IO) CampusLogin
campusForm = CampusLogin campusForm = CampusLogin
<$> areq ciField (fslpI MsgCampusIdent "user.name@campus.lmu.de" & setTooltip MsgCampusIdentNote) Nothing <$> areq ciField (fslpI MsgCampusIdent "user.name@campus.lmu.de" & setTooltip MsgCampusIdentNote) Nothing
@ -66,9 +65,7 @@ campusLogin :: forall site.
( YesodAuth site ( YesodAuth site
, RenderMessage site FormMessage , RenderMessage site FormMessage
, RenderMessage site CampusMessage , RenderMessage site CampusMessage
, RenderMessage site ButtonMessage , Button site ButtonSubmit
, Button site SubmitButton
, Show (ButtonCssClass site)
) => LdapConf -> LdapPool -> AuthPlugin site ) => LdapConf -> LdapPool -> AuthPlugin site
campusLogin conf@LdapConf{..} pool = AuthPlugin{..} campusLogin conf@LdapConf{..} pool = AuthPlugin{..}
where where
@ -116,7 +113,7 @@ data CampusUserException = CampusUserLdapError LdapPoolError
| CampusUserHostCannotConnect String [IOException] | CampusUserHostCannotConnect String [IOException]
| CampusUserNoResult | CampusUserNoResult
| CampusUserAmbiguous | CampusUserAmbiguous
deriving (Show, Eq, Typeable) deriving (Show, Eq, Generic, Typeable)
instance Exception CampusUserException instance Exception CampusUserException

View File

@ -19,17 +19,16 @@ import qualified Yesod.Auth.Message as Msg
data HashLogin = HashLogin data HashLogin = HashLogin
{ hashIdent :: CI Text { hashIdent :: CI Text
, hashPassword :: Text , hashPassword :: Text
} } deriving (Generic, Typeable)
data PWHashMessage = MsgPWHashIdent data PWHashMessage = MsgPWHashIdent
| MsgPWHashPassword | MsgPWHashPassword
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
hashForm :: ( RenderMessage site FormMessage hashForm :: ( RenderMessage site FormMessage
, RenderMessage site PWHashMessage , RenderMessage site PWHashMessage
, RenderMessage site ButtonMessage , Button site ButtonSubmit
, Button site SubmitButton
, Show (ButtonCssClass site)
) => AForm (HandlerT site IO) HashLogin ) => AForm (HandlerT site IO) HashLogin
hashForm = HashLogin hashForm = HashLogin
<$> areq ciField (fslpI MsgPWHashIdent "Identifikation") Nothing <$> areq ciField (fslpI MsgPWHashIdent "Identifikation") Nothing
@ -42,9 +41,7 @@ hashLogin :: ( YesodAuth site
, SqlBackendCanRead (YesodPersistBackend site) , SqlBackendCanRead (YesodPersistBackend site)
, RenderMessage site FormMessage , RenderMessage site FormMessage
, RenderMessage site PWHashMessage , RenderMessage site PWHashMessage
, RenderMessage site ButtonMessage , Button site ButtonSubmit
, Button site SubmitButton
, Show (ButtonCssClass site)
) => PWHashAlgorithm -> AuthPlugin site ) => PWHashAlgorithm -> AuthPlugin site
hashLogin pwHashAlgo = AuthPlugin{..} hashLogin pwHashAlgo = AuthPlugin{..}
where where

View File

@ -63,6 +63,7 @@ import Handler.Utils.StudyFeatures
import Handler.Utils.Templates import Handler.Utils.Templates
import Utils.Lens import Utils.Lens
import Utils.Form import Utils.Form
import Utils.Sheet
import Utils.SystemMessage import Utils.SystemMessage
import Text.Shakespeare.Text (st) import Text.Shakespeare.Text (st)
@ -275,20 +276,35 @@ instance HasRoute UniWorX MenuItem where
urlRoute MenuItem{..} = urlRoute menuItemRoute urlRoute MenuItem{..} = urlRoute menuItemRoute
menuItemAccessCallback :: MenuItem -> Handler Bool menuItemAccessCallback :: MenuItem -> Handler Bool
menuItemAccessCallback MenuItem{..} = (&&) <$> ((==) Authorized <$> authCheck) <*> menuItemAccessCallback' menuItemAccessCallback MenuItem{..} = and2M ((==) Authorized <$> authCheck) menuItemAccessCallback'
where where
authCheck = handleAny (\_ -> return . Unauthorized $ error "authCheck caught exception") $ isAuthorized (urlRoute menuItemRoute) False authCheck = handleAny (\_ -> return . Unauthorized $ error "authCheck caught exception") $ isAuthorized (urlRoute menuItemRoute) False
$(return []) $(return [])
data instance ButtonCssClass UniWorX = BCDefault | BCPrimary | BCSuccess | BCInfo | BCWarning | BCDanger | BCLink data instance ButtonClass UniWorX
deriving (Enum, Eq, Ord, Bounded, Read, Show) = BCIsButton
| BCDefault
| BCPrimary
| BCSuccess
| BCInfo
| BCWarning
| BCDanger
| BCLink
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe (ButtonClass UniWorX)
instance Finite (ButtonClass UniWorX)
instance Button UniWorX SubmitButton where instance PathPiece (ButtonClass UniWorX) where
label BtnSubmit = [whamlet|_{MsgBtnSubmit}|] toPathPiece BCIsButton = "btn"
toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass
fromPathPiece = finiteFromPathPiece
cssClass BtnSubmit = BCPrimary
embedRenderMessage ''UniWorX ''ButtonSubmit id
instance Button UniWorX ButtonSubmit where
btnClasses BtnSubmit = [BCIsButton, BCPrimary]
getTimeLocale' :: [Lang] -> TimeLocale getTimeLocale' :: [Lang] -> TimeLocale
@ -469,12 +485,23 @@ tagAccessPredicate AuthTime = APDB $ \route _ -> case route of
return Authorized return Authorized
CourseR tid ssh csh CRegisterR -> maybeT (unauthorizedI MsgUnauthorizedCourseTime) $ do CourseR tid ssh csh CRegisterR -> do
Entity _ Course{courseRegisterFrom, courseRegisterTo} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh now <- liftIO getCurrentTime
cTime <- (NTop . Just) <$> liftIO getCurrentTime mbc <- getBy $ TermSchoolCourseShort tid ssh csh
guard $ NTop courseRegisterFrom <= cTime mAid <- lift maybeAuthId
&& NTop courseRegisterTo >= cTime registered <- case (mbc,mAid) of
return Authorized (Just (Entity cid _), Just uid) -> isJust <$> (getBy $ UniqueParticipant uid cid)
_ -> return False
case mbc of
(Just (Entity _ Course{courseRegisterFrom, courseRegisterTo}))
| not registered
, Just regFrom <- courseRegisterFrom -- Nothing = no registration
, regFrom <= now
, maybe True (now <=) courseRegisterTo -> return Authorized
(Just (Entity _ Course{courseDeregisterUntil}))
| registered
, maybe True (now <=) courseDeregisterUntil -> return Authorized
_other -> unauthorizedI MsgUnauthorizedCourseTime
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
smId <- decrypt cID smId <- decrypt cID
@ -1269,8 +1296,8 @@ pageActions (CourseR tid ssh csh CShowR) =
} }
, MenuItem , MenuItem
{ menuItemType = PageActionSecondary { menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuCourseNewTemplate , menuItemLabel = MsgMenuCourseClone
, menuItemIcon = Nothing , menuItemIcon = Just "copy"
, menuItemRoute = SomeRoute (CourseNewR, [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)]) , menuItemRoute = SomeRoute (CourseNewR, [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)])
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
@ -1278,7 +1305,7 @@ pageActions (CourseR tid ssh csh CShowR) =
, MenuItem , MenuItem
{ menuItemType = PageActionSecondary { menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuCourseDelete , menuItemLabel = MsgMenuCourseDelete
, menuItemIcon = Nothing , menuItemIcon = Just "trash"
, menuItemRoute = SomeRoute $ CourseR tid ssh csh CDeleteR , menuItemRoute = SomeRoute $ CourseR tid ssh csh CDeleteR
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
@ -1291,29 +1318,19 @@ pageActions (CourseR tid ssh csh SheetListR) =
, menuItemIcon = Nothing , menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetCurrentR , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetCurrentR
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = do , menuItemAccessCallback' = runDB . maybeT (return False) $ do
now <- liftIO getCurrentTime void . MaybeT $ sheetCurrent tid ssh csh
sheets <- runDB . E.select . E.from $ \(course `E.InnerJoin` sheet) -> do return True
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheet E.^. SheetActiveTo E.>. E.val now
E.&&. sheet E.^. SheetActiveFrom E.<=. E.val now
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.orderBy [E.asc $ sheet E.^. SheetActiveTo]
E.limit 1
return $ sheet E.^. SheetName
case sheets of
(E.Value shn):_ -> (== Authorized) <$> isAuthorized (CSheetR tid ssh csh shn SShowR) False
_ -> return False
} }
, MenuItem , MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime
, menuItemLabel = MsgMenuSheetLastInactive , menuItemLabel = MsgMenuSheetOldUnassigned
, menuItemIcon = Nothing , menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetLastInactiveR , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetOldUnassigned
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = (== Authorized) <$> evalAccessCorrector tid ssh csh , menuItemAccessCallback' = runDB . maybeT (return False) $ do
void . MaybeT $ sheetOldUnassigned tid ssh csh
return True
} }
, MenuItem , MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime
@ -1368,18 +1385,6 @@ pageActions (CSheetR tid ssh csh shn SShowR) =
guard $ null submissions guard $ null submissions
return True return True
} }
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuCorrectionsOwn
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute (CorrectionsR, [ ("corrections-term" , termToText $ unTermKey tid)
, ("corrections-school", CI.original $ unSchoolKey ssh)
, ("corrections-course", CI.original csh)
, ("corrections-sheet" , CI.original shn)
])
, menuItemModal = False
, menuItemAccessCallback' = (== Authorized) <$> evalAccessCorrector tid ssh csh
}
, MenuItem , MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime
, menuItemLabel = MsgMenuSubmissionOwn , menuItemLabel = MsgMenuSubmissionOwn
@ -1392,6 +1397,18 @@ pageActions (CSheetR tid ssh csh shn SShowR) =
guard . not $ null submissions guard . not $ null submissions
return True return True
} }
, MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuCorrectionsOwn
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute (CorrectionsR, [ ("corrections-term" , termToText $ unTermKey tid)
, ("corrections-school", CI.original $ unSchoolKey ssh)
, ("corrections-course", CI.original csh)
, ("corrections-sheet" , CI.original shn)
])
, menuItemModal = False
, menuItemAccessCallback' = (== Authorized) <$> evalAccessCorrector tid ssh csh
}
, MenuItem , MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime
, menuItemLabel = MsgMenuCorrectors , menuItemLabel = MsgMenuCorrectors
@ -1416,10 +1433,18 @@ pageActions (CSheetR tid ssh csh shn SShowR) =
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
} }
, MenuItem
{ menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuSheetClone
, menuItemIcon = Just "copy"
, menuItemRoute = SomeRoute (CourseR tid ssh csh SheetNewR, [("shn", toPathPiece shn)])
, menuItemModal = False
, menuItemAccessCallback' = return True
}
, MenuItem , MenuItem
{ menuItemType = PageActionSecondary { menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuSheetDelete , menuItemLabel = MsgMenuSheetDelete
, menuItemIcon = Nothing , menuItemIcon = Just "trash"
, menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SDelR , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SDelR
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
@ -1455,7 +1480,7 @@ pageActions (CSubmissionR tid ssh csh shn cid SubShowR) =
, MenuItem , MenuItem
{ menuItemType = PageActionSecondary { menuItemType = PageActionSecondary
, menuItemLabel = MsgMenuSubmissionDelete , menuItemLabel = MsgMenuSubmissionDelete
, menuItemIcon = Nothing , menuItemIcon = Just "trash"
, menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubDelR , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubDelR
, menuItemModal = False , menuItemModal = False
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
@ -1474,7 +1499,7 @@ pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) =
pageActions (CSheetR tid ssh csh shn SCorrR) = pageActions (CSheetR tid ssh csh shn SCorrR) =
[ MenuItem [ MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime
, menuItemLabel = MsgMenuCorrections , menuItemLabel = MsgMenuSubmissions
, menuItemIcon = Nothing , menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SSubsR , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SSubsR
, menuItemModal = False , menuItemModal = False

View File

@ -13,8 +13,6 @@ import Control.Monad.Trans.Except
-- import Data.Function ((&)) -- import Data.Function ((&))
-- import Yesod.Form.Bootstrap3 -- import Yesod.Form.Bootstrap3
import Web.PathPieces (showToPathPiece, readFromPathPiece)
import Database.Persist.Sql (fromSqlKey) import Database.Persist.Sql (fromSqlKey)
-- import Colonnade hiding (fromMaybe) -- import Colonnade hiding (fromMaybe)
@ -23,19 +21,19 @@ import Database.Persist.Sql (fromSqlKey)
-- import qualified Data.UUID.Cryptographic as UUID -- import qualified Data.UUID.Cryptographic as UUID
-- BEGIN - Buttons needed only here -- BEGIN - Buttons needed only here
data CreateButton = CreateMath | CreateInf -- Dummy for Example data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonCreate
instance Finite ButtonCreate
instance PathPiece CreateButton where -- for displaying the button only, not really for paths nullaryPathPiece ''ButtonCreate camelToPathPiece
toPathPiece = showToPathPiece
fromPathPiece = readFromPathPiece
instance Button UniWorX CreateButton where instance Button UniWorX ButtonCreate where
label CreateMath = [whamlet|Ma<i>thema</i>tik|] btnLabel CreateMath = [whamlet|Ma<i>thema</i>tik|]
label CreateInf = "Informatik" btnLabel CreateInf = "Informatik"
cssClass CreateMath = BCInfo btnClasses CreateMath = [BCIsButton, BCInfo]
cssClass CreateInf = BCPrimary btnClasses CreateInf = [BCIsButton, BCPrimary]
-- END Button needed here -- END Button needed here
emailTestForm :: AForm (HandlerT UniWorX IO) (Email, MailContext) emailTestForm :: AForm (HandlerT UniWorX IO) (Email, MailContext)
@ -60,7 +58,7 @@ emailTestForm = (,)
getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden! getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden!
getAdminTestR = postAdminTestR getAdminTestR = postAdminTestR
postAdminTestR = do postAdminTestR = do
((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm "buttons" (buttonForm :: Form CreateButton) ((btnResult, btnWdgt), btnEnctype) <- runFormPost $ identifyForm "buttons" (buttonForm :: Form ButtonCreate)
case btnResult of case btnResult of
(FormSuccess CreateInf) -> addMessage Info "Informatik-Knopf gedrückt" (FormSuccess CreateInf) -> addMessage Info "Informatik-Knopf gedrückt"
(FormSuccess CreateMath) -> addMessage Warning "Knopf Mathematik erkannt" (FormSuccess CreateMath) -> addMessage Warning "Knopf Mathematik erkannt"

View File

@ -479,11 +479,9 @@ assignAction selId = ( CorrSetCorrector
E.distinct $ return user E.distinct $ return user
mr <- getMessageRender correctors' <- forM correctors $ \Entity{ entityKey, entityVal = User{..} } -> (SomeMessage userDisplayName, ) <$> encrypt entityKey
correctors' <- fmap ((mr MsgNoCorrector, Nothing) :) . forM correctors $ \Entity{ entityKey, entityVal = User{..} } -> (display userDisplayName, ) . Just <$> encrypt entityKey cId <- wopt (selectFieldList correctors' :: Field (HandlerT UniWorX IO) CryptoUUIDUser) (fslI MsgCorrector) Nothing
cId <- wpreq (selectFieldList correctors' :: Field (HandlerT UniWorX IO) (Maybe CryptoUUIDUser)) (fslI MsgCorrector) Nothing
fmap CorrSetCorrectorData <$> (traverse.traverse) decrypt cId fmap CorrSetCorrectorData <$> (traverse.traverse) decrypt cId
) )

View File

@ -259,25 +259,31 @@ getTermCourseListR tid = do
getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getCShowR tid ssh csh = do getCShowR tid ssh csh = do
mbAid <- maybeAuthId mbAid <- maybeAuthId
(courseEnt,(schoolMB,participants,registered),lecturers) <- runDB $ do (course,schoolName,participants,registered,lecturers) <- runDB . maybeT notFound $ do
courseEnt@(Entity cid course) <- getBy404 $ TermSchoolCourseShort tid ssh csh [(E.Entity cid course, E.Value schoolName, E.Value participants, E.Value registered)]
dependent <- (,,) <- lift . E.select . E.from $
<$> get (courseSchool course) -- join -- just fetch full school name here \((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do
<*> count [CourseParticipantCourse ==. cid] -- join E.on $ E.just (course E.^. CourseId) E.==. participant E.?. CourseParticipantCourse
<*> (case mbAid of -- TODO: Someone please refactor this late-night mess here! E.&&. E.val mbAid E.==. participant E.?. CourseParticipantUser
Nothing -> return False E.on $ course E.^. CourseSchool E.==. school E.^. SchoolId
(Just aid) -> do regL <- getBy (UniqueParticipant aid cid) E.where_ $ course E.^. CourseTerm E.==. E.val tid
return $ isJust regL) E.&&. course E.^. CourseSchool E.==. E.val ssh
lecturers <- E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do E.&&. course E.^. CourseShorthand E.==. E.val csh
E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId let numParticipants = E.sub_select . E.from $ \part -> do
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid E.where_ $ part E.^. CourseParticipantCourse E.==. course E.^. CourseId
return $ user E.^. UserDisplayName return ( E.countRows :: E.SqlExpr (E.Value Int64))
return (courseEnt,dependent,E.unValue <$> lecturers) return (course,school E.^. SchoolName, numParticipants, participant E.?. CourseParticipantRegistration)
let course = entityVal courseEnt lecturers <- lift . E.select $ E.from $ \(lecturer `E.InnerJoin` user) -> do
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm registered $ courseRegisterSecret course E.on $ lecturer E.^. LecturerUser E.==. user E.^. UserId
E.where_ $ lecturer E.^. LecturerCourse E.==. E.val cid
return $ user E.^. UserDisplayName
return (course,schoolName,participants,registered,map E.unValue lecturers)
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
mDereg <- traverse (formatTime SelFormatDateTime) $ courseDeregisterUntil course
mRegAt <- traverse (formatTime SelFormatDateTime) registered
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm (isJust mRegAt) $ courseRegisterSecret course
registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid ssh csh CRegisterR) True registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid ssh csh CRegisterR) True
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
defaultLayout $ do defaultLayout $ do
setTitle [shamlet| #{toPathPiece tid} - #{csh}|] setTitle [shamlet| #{toPathPiece tid} - #{csh}|]
$(widgetFile "course") $(widgetFile "course")

View File

@ -222,7 +222,7 @@ getProfileDataR = do
let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|] let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
-- Delete Button -- Delete Button
(btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form BtnDelete) (btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form ButtonDelete)
defaultLayout $ do defaultLayout $ do
let delWdgt = $(widgetFile "widgets/data-delete") let delWdgt = $(widgetFile "widgets/data-delete")
$(widgetFile "profileData") $(widgetFile "profileData")

View File

@ -3,6 +3,7 @@ module Handler.Sheet where
import Import import Import
import System.FilePath (takeFileName) import System.FilePath (takeFileName)
import Utils.Sheet
import Handler.Utils import Handler.Utils
-- import Handler.Utils.Zip -- import Handler.Utils.Zip
import Handler.Utils.Table.Cells import Handler.Utils.Table.Cells
@ -142,38 +143,15 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do
getSheetCurrentR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getSheetCurrentR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getSheetCurrentR tid ssh csh = runDB $ do getSheetCurrentR tid ssh csh = runDB $ do
now <- liftIO getCurrentTime let redi shn = redirectAccess $ CSheetR tid ssh csh shn SShowR
sheets <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do shn <- sheetCurrent tid ssh csh
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId maybe notFound redi shn
E.where_ $ sheet E.^. SheetActiveTo E.>. E.val now
E.&&. sheet E.^. SheetActiveFrom E.<=. E.val now
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.orderBy [E.asc $ sheet E.^. SheetActiveFrom]
E.limit 1
return $ sheet E.^. SheetName
case sheets of
(E.Value shn):_ -> redirectAccess $ CSheetR tid ssh csh shn SShowR
_ -> notFound
getSheetLastInactiveR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getSheetLastInactiveR tid ssh csh = runDB $ do
-- TODO: deliver oldest sheet with unassigned submissions instead!!!
now <- liftIO getCurrentTime
sheets <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheet E.^. SheetActiveTo E.<=. E.val now
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.orderBy [E.desc $ sheet E.^. SheetActiveTo]
E.limit 1
return $ sheet E.^. SheetName
case sheets of
(E.Value shn):_ -> redirectAccess $ CSheetR tid ssh csh shn SShowR
_ -> notFound
getSheetOldUnassigned :: TermId -> SchoolId -> CourseShorthand -> Handler ()
getSheetOldUnassigned tid ssh csh = runDB $ do
let redi shn = redirectAccess $ CSheetR tid ssh csh shn SSubsR
shn <- sheetOldUnassigned tid ssh csh
maybe notFound redi shn
getSheetListR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getSheetListR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getSheetListR tid ssh csh = do getSheetListR tid ssh csh = do
@ -299,15 +277,15 @@ getSheetListR tid ssh csh = do
$(widgetFile "sheetList") $(widgetFile "sheetList")
data ButtonGeneratePseudonym = BtnGenerate data ButtonGeneratePseudonym = BtnGenerate
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonGeneratePseudonym instance Universe ButtonGeneratePseudonym
instance Finite ButtonGeneratePseudonym instance Finite ButtonGeneratePseudonym
nullaryPathPiece ''ButtonGeneratePseudonym (camelToPathPiece' 1) nullaryPathPiece ''ButtonGeneratePseudonym (camelToPathPiece' 1)
instance Button UniWorX ButtonGeneratePseudonym where instance Button UniWorX ButtonGeneratePseudonym where
label BtnGenerate = [whamlet|_{MsgSheetGeneratePseudonym}|] btnLabel BtnGenerate = [whamlet|_{MsgSheetGeneratePseudonym}|]
cssClass BtnGenerate = BCDefault btnClasses BtnGenerate = [BCIsButton, BCDefault]
-- Show single sheet -- Show single sheet
getSShowR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler Html getSShowR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler Html
@ -447,11 +425,17 @@ getSFileR tid ssh csh shn typ title = do
getSheetNewR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getSheetNewR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
getSheetNewR tid ssh csh = do getSheetNewR tid ssh csh = do
parShn <- runInputGetResult $ iopt ciField "shn"
let searchShn sheet = case parShn of
(FormSuccess (Just shn)) -> E.where_ $ sheet E.^. SheetName E.==. E.val shn
-- (FormFailure msgs) -> -- not in MonadHandler anymore -- forM_ msgs (addMessage Error . toHtml)
_other -> return ()
lastSheets <- runDB $ E.select . E.from $ \(course `E.InnerJoin` sheet) -> do lastSheets <- runDB $ E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.where_ $ course E.^. CourseTerm E.==. E.val tid E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. course E.^. CourseShorthand E.==. E.val csh
searchShn sheet
-- let lastSheetEdit = E.sub_select . E.from $ \sheetEdit -> do -- let lastSheetEdit = E.sub_select . E.from $ \sheetEdit -> do
-- E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId -- E.where_ $ sheetEdit E.^. SheetEditSheet E.==. sheet E.^. SheetId
-- return . E.max_ $ sheetEdit E.^. SheetEditTime -- return . E.max_ $ sheetEdit E.^. SheetEditTime

View File

@ -69,3 +69,8 @@ warnTermDays tid times = do
forM_ warnholidays $ warnI MsgDayIsAHoliday forM_ warnholidays $ warnI MsgDayIsAHoliday
forM_ outoflecture $ warnI MsgDayIsOutOfLecture forM_ outoflecture $ warnI MsgDayIsOutOfLecture
forM_ outoftermdays $ warnI MsgDayIsOutOfTerm forM_ outoftermdays $ warnI MsgDayIsOutOfTerm
visibleWidget :: Bool -> Widget
-- ^ @visibleWidget False@ is an icon that denotes that something™ is not visible
visibleWidget True = mempty
visibleWidget False = [whamlet|<i .fas .fa-eye-slash>|]

View File

@ -15,8 +15,6 @@ import qualified Data.Char as Char
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
import qualified Data.Foldable as Foldable
-- import Yesod.Core -- import Yesod.Core
import qualified Data.Text as T import qualified Data.Text as T
-- import Yesod.Form.Types -- import Yesod.Form.Types
@ -51,64 +49,55 @@ import Data.Aeson.Text (encodeToLazyText)
-- Buttons (new version ) -- -- Buttons (new version ) --
---------------------------- ----------------------------
data BtnDelete = BtnDelete data ButtonDelete = BtnDelete
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonDelete
instance Finite ButtonDelete
instance Universe BtnDelete nullaryPathPiece ''ButtonDelete $ camelToPathPiece' 1
instance Finite BtnDelete
nullaryPathPiece ''BtnDelete $ camelToPathPiece' 1 embedRenderMessage ''UniWorX ''ButtonDelete id
instance Button UniWorX ButtonDelete where
btnClasses BtnDelete = [BCIsButton, BCDanger]
instance Button UniWorX BtnDelete where data ButtonRegister = BtnRegister | BtnDeregister
label BtnDelete = [whamlet|_{MsgBtnDelete}|] deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonRegister
instance Finite ButtonRegister
cssClass BtnDelete = BCDanger nullaryPathPiece ''ButtonRegister $ camelToPathPiece' 1
data RegisterButton = BtnRegister | BtnDeregister embedRenderMessage ''UniWorX ''ButtonRegister id
deriving (Enum, Eq, Ord, Bounded, Read, Show) instance Button UniWorX ButtonRegister where
btnClasses BtnRegister = [BCIsButton, BCPrimary]
btnClasses BtnDeregister = [BCIsButton, BCDanger]
instance Universe RegisterButton data ButtonHijack = BtnHijack
instance Finite RegisterButton deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe ButtonHijack
instance Finite ButtonHijack
nullaryPathPiece ''RegisterButton $ camelToPathPiece' 1 nullaryPathPiece ''ButtonHijack $ camelToPathPiece' 1
instance Button UniWorX RegisterButton where embedRenderMessage ''UniWorX ''ButtonHijack id
label BtnRegister = [whamlet|_{MsgBtnRegister}|] instance Button UniWorX ButtonHijack where
label BtnDeregister = [whamlet|_{MsgBtnDeregister}|] btnClasses BtnHijack = [BCIsButton, BCDefault]
cssClass BtnRegister = BCPrimary data ButtonSubmitDelete = BtnSubmit' | BtnDelete'
cssClass BtnDeregister = BCDanger deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
data AdminHijackUserButton = BtnHijack instance Universe ButtonSubmitDelete
deriving (Enum, Eq, Ord, Bounded, Read, Show) instance Finite ButtonSubmitDelete
instance Universe AdminHijackUserButton embedRenderMessage ''UniWorX ''ButtonSubmitDelete $ dropSuffix "'"
instance Finite AdminHijackUserButton instance Button UniWorX ButtonSubmitDelete where
btnClasses BtnSubmit' = [BCIsButton, BCPrimary]
nullaryPathPiece ''AdminHijackUserButton $ camelToPathPiece' 1 btnClasses BtnDelete' = [BCIsButton, BCDanger]
instance Button UniWorX AdminHijackUserButton where
label BtnHijack = [whamlet|_{MsgBtnHijack}|]
cssClass BtnHijack = BCDefault
data BtnSubmitDelete = BtnSubmit' | BtnDelete'
deriving (Enum, Eq, Ord, Bounded, Read, Show)
instance Universe BtnSubmitDelete
instance Finite BtnSubmitDelete
instance Button UniWorX BtnSubmitDelete where
label BtnSubmit' = [whamlet|_{MsgBtnSubmit}|]
label BtnDelete' = [whamlet|_{MsgBtnDelete}|]
cssClass BtnSubmit' = BCPrimary
cssClass BtnDelete' = BCDanger
btnValidate _ BtnSubmit' = True btnValidate _ BtnSubmit' = True
btnValidate _ BtnDelete' = False btnValidate _ BtnDelete' = False
nullaryPathPiece ''BtnSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'" nullaryPathPiece ''ButtonSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'"
-- -- Looks like a button, but is just a link (e.g. for create course, etc.) -- -- Looks like a button, but is just a link (e.g. for create course, etc.)
@ -118,8 +107,14 @@ nullaryPathPiece ''BtnSubmitDelete $ camelToPathPiece' 1 . dropSuffix "'"
-- instance PathPiece LinkButton where -- instance PathPiece LinkButton where
-- LinkButton route = ??? -- LinkButton route = ???
linkButton :: Widget -> ButtonCssClass UniWorX -> Route UniWorX -> Widget -- Alternative: Handler.Utils.simpleLink linkButton :: Widget -> [ButtonClass UniWorX] -> SomeRoute UniWorX -> Widget -- Alternative: Handler.Utils.simpleLink
linkButton lbl cls url = [whamlet| <a href=@{url} .btn .#{bcc2txt cls} role=button>^{lbl} |] linkButton lbl cls url = do
url' <- toTextUrl url
[whamlet|
$newline never
<a href=#{url'} class=#{unwords $ map toPathPiece cls} role=button>
^{lbl}
|]
-- [whamlet| -- [whamlet|
-- <form method=post action=@{url}> -- <form method=post action=@{url}>
-- <input type="hidden" name="_formid" value="identify-linkButton"> -- <input type="hidden" name="_formid" value="identify-linkButton">
@ -128,31 +123,16 @@ 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}>
-- buttonForm :: Button a => Markup -> MForm (HandlerT UniWorX IO) (FormResult a, (WidgetT UniWorX IO ())) -- buttonForm :: (Button UniWorX a, Finite a) => Markup -> MForm (HandlerT UniWorX IO) (FormResult a, Widget)
buttonForm :: (Button UniWorX a, Show a) => Form a buttonForm :: (Button UniWorX a, Finite a) => Form a
buttonForm csrf = do buttonForm csrf = do
buttonIdent <- newFormIdent (res, ($ []) -> fViews) <- aFormToForm . disambiguateButtons $ combinedButtonFieldF ""
let button b = mopt (buttonField b) ("n/a"{ fsName = Just buttonIdent }) Nothing return (res, [whamlet|
(results, btnViews) <- unzip <$> mapM button [minBound..maxBound] $newline never
let widget = #{csrf}
[whamlet| $forall bView <- fViews
#{csrf} ^{fvInput bView}
$forall bView <- btnViews |])
^{fvInput bView}
|]
return (accResult results,widget)
where
accResult :: Foldable f => f (FormResult (Maybe a)) -> FormResult a
accResult = Foldable.foldr accResult' FormMissing
accResult' :: FormResult (Maybe a) -> FormResult a -> FormResult a
-- Find the single FormSuccess Just _; Expected behaviour: all buttons deliver FormFailure, except for one.
accResult' (FormSuccess (Just _)) (FormSuccess _) = FormFailure ["Ambiguous button parse"]
accResult' (FormSuccess (Just x)) _ = FormSuccess x
accResult' _ x@(FormSuccess _) = x --Safe: most buttons deliver FormFailure, one delivers FormSuccess
accResult' (FormSuccess Nothing) x = x
accResult' FormMissing _ = FormMissing
accResult' (FormFailure errs) _ = FormFailure errs

View File

@ -130,7 +130,9 @@ assignSubmissions sid restriction = do
props = getSum $ foldMap (Sum . fst) assignments props = getSum $ foldMap (Sum . fst) assignments
toDeficit' (prop, assigned) = let toDeficit' (prop, assigned) = let
target = round $ fromInteger assigned' * (prop / props) target
| props == 0 = 0
| otherwise = round $ fromInteger assigned' * (prop / props)
in target - assigned in target - assigned
$logDebugS "assignSubmissions" $ "Previous submissions: " <> tshow prevSubs' $logDebugS "assignSubmissions" $ "Previous submissions: " <> tshow prevSubs'

View File

@ -519,7 +519,7 @@ dbParamsFormWrap DBParamsForm{..} tableForm frag = do
return . (res,) $ do return . (res,) $ do
btnId <- newIdent btnId <- newIdent
act <- traverse toTextUrl dbParamsFormAction act <- traverse toTextUrl dbParamsFormAction
let submitField :: Field Handler SubmitButton let submitField :: Field Handler ButtonSubmit
submitField = buttonField BtnSubmit submitField = buttonField BtnSubmit
submitView :: Widget submitView :: Widget
submitView = fieldView submitField btnId "" mempty (Right BtnSubmit) False submitView = fieldView submitField btnId "" mempty (Right BtnSubmit) False
@ -602,11 +602,11 @@ dbTable PSValidator{..} dbtable@DBTable{ dbtIdent = dbtIdent'@(toPathPiece -> db
| otherwise | otherwise
= def = def
referencePagesize = psLimit . snd . runPSValidator dbtable $ Just prevPi
(((filterRes, filterWdgt), filterEnc), ((pagesizeRes, pagesizeWdgt), pagesizeEnc)) <- mdo (((filterRes, filterWdgt), filterEnc), ((pagesizeRes, pagesizeWdgt), pagesizeEnc)) <- mdo
(filterRes'@((filterRes, _), _)) <- runFormGet . identForm FIDDBTableFilter . addPIHiddenField dbtable (prevPi & _piFilter .~ Nothing & _piPage .~ Nothing & _piLimit .~ (formResult' pagesizeRes <|> piLimit prevPi)) . renderAForm FormDBTableFilter $ dbtFilterUI (piFilter prevPi) (filterRes'@((filterRes, _), _)) <- runFormGet . identForm FIDDBTableFilter . addPIHiddenField dbtable (prevPi & _piFilter .~ Nothing & _piPage .~ Nothing & _piLimit .~ (formResult' pagesizeRes <|> piLimit prevPi)) . renderAForm FormDBTableFilter $ dbtFilterUI (piFilter prevPi)
let referencePagesize = psLimit . snd . runPSValidator dbtable $ Just prevPi
(pagesizeRes'@((pagesizeRes, _), _)) <- lift . runFormGet . identForm FIDDBTablePagesize . addPIHiddenField dbtable (prevPi & _piPage .~ Nothing & _piLimit .~ Nothing & _piFilter .~ (formResult' filterRes <|> piFilter prevPi)) . renderAForm FormDBTablePagesize $ (pagesizeRes'@((pagesizeRes, _), _)) <- lift . runFormGet . identForm FIDDBTablePagesize . addPIHiddenField dbtable (prevPi & _piPage .~ Nothing & _piLimit .~ Nothing & _piFilter .~ (formResult' filterRes <|> piFilter prevPi)) . renderAForm FormDBTablePagesize $
areq (pagesizeField referencePagesize) (fslI MsgDBTablePagesize & addAutosubmit & addName (wIdent "pagesize") & addClass "select--pagesize") (Just referencePagesize) areq (pagesizeField referencePagesize) (fslI MsgDBTablePagesize & addAutosubmit & addName (wIdent "pagesize") & addClass "select--pagesize") (Just referencePagesize)
<* autosubmitButton <* autosubmitButton
@ -760,6 +760,15 @@ dbColonnade :: (Headedness h, Monoid x)
-> Colonnade h r (DBCell (ReaderT SqlBackend (HandlerT UniWorX IO)) x) -> Colonnade h r (DBCell (ReaderT SqlBackend (HandlerT UniWorX IO)) x)
dbColonnade = id 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' = [ 10^n | n <- [1..3]]
pagesizeField :: PagesizeLimit -> Field Handler PagesizeLimit pagesizeField :: PagesizeLimit -> Field Handler PagesizeLimit
pagesizeField psLim = selectField $ do pagesizeField psLim = selectField $ do
MsgRenderer mr <- getMsgRenderer MsgRenderer mr <- getMsgRenderer
@ -767,16 +776,9 @@ pagesizeField psLim = selectField $ do
optText (PagesizeLimit l) = tshow l optText (PagesizeLimit l) = tshow l
optText PagesizeAll = mr MsgDBTablePagesizeAll optText PagesizeAll = mr MsgDBTablePagesizeAll
toOptionList = flip OptionList fromPathPiece . map (\o -> Option (optText o) o $ toPathPiece o) . Set.toAscList . Set.fromList toOptionList = flip OptionList fromPathPiece . map (\o -> Option (optText o) o $ toPathPiece o)
return $ toOptionList limOpts return . toOptionList . toNullable $ pagesizeOptions psLim
where
limOpts :: [PagesizeLimit]
limOpts = psLim : PagesizeAll : map PagesizeLimit opts
opts :: [Int64]
opts = filter (> 0) $ opts' <> map (`div` 2) opts'
opts' = [ 10^n | n <- [1..3]]
--- DBCell utility functions --- DBCell utility functions

View File

@ -59,6 +59,7 @@ import Ldap.Client.Pool as Import
import Database.Esqueleto.Instances as Import () import Database.Esqueleto.Instances as Import ()
import Database.Persist.Sql.Instances as Import () import Database.Persist.Sql.Instances as Import ()
import Database.Persist.Sql as Import (SqlReadT,SqlWriteT)
import Control.Monad.Trans.RWS (RWST) import Control.Monad.Trans.RWS (RWST)

View File

@ -364,7 +364,7 @@ mcons :: Maybe a -> [a] -> [a]
mcons Nothing xs = xs mcons Nothing xs = xs
mcons (Just x) xs = x:xs mcons (Just x) xs = x:xs
newtype NTop a = NTop a -- treat Nothing as Top for Ord (Maybe a); default implementation treats Nothing as bottom newtype NTop a = NTop { nBot :: a } -- treat Nothing as Top for Ord (Maybe a); default implementation treats Nothing as bottom
instance Eq a => Eq (NTop (Maybe a)) where instance Eq a => Eq (NTop (Maybe a)) where
(NTop x) == (NTop y) = x == y (NTop x) == (NTop y) = x == y

View File

@ -7,7 +7,6 @@ import Settings
import qualified Text.Blaze.Internal as Blaze (null) import qualified Text.Blaze.Internal as Blaze (null)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Char as Char
import Data.CaseInsensitive (CI) import Data.CaseInsensitive (CI)
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -200,37 +199,36 @@ identForm = identifyForm . toPathPiece
-- Buttons (new version ) -- -- Buttons (new version ) --
---------------------------- ----------------------------
data family ButtonCssClass site :: * data family ButtonClass site :: *
bcc2txt :: Show (ButtonCssClass site) => ButtonCssClass site -> Text -- a Hack; maybe define Read/Show manually class (PathPiece a, PathPiece (ButtonClass site), RenderMessage site ButtonMessage) => Button site a where
bcc2txt bcc = T.pack $ "btn-" ++ (Char.toLower <$> drop 2 (show bcc)) btnLabel :: a -> WidgetT site IO ()
class (Enum a, Bounded a, Ord a, PathPiece a) => Button site a where default btnLabel :: RenderMessage site a => a -> WidgetT site IO ()
label :: a -> WidgetT site IO () btnLabel = toWidget <=< ap getMessageRender . return
label = toWidget . toPathPiece
btnValidate :: forall p. p site -> a -> Bool btnValidate :: forall p. p site -> a -> Bool
btnValidate _ _ = True btnValidate _ _ = True
cssClass :: a -> ButtonCssClass site btnClasses :: a -> [ButtonClass site]
btnClasses _ = []
data ButtonMessage = MsgAmbiguousButtons data ButtonMessage = MsgAmbiguousButtons
| MsgWrongButtonValue | MsgWrongButtonValue
| MsgMultipleButtonValues | MsgMultipleButtonValues
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
data SubmitButton = BtnSubmit data ButtonSubmit = BtnSubmit
deriving (Enum, Eq, Ord, Bounded, Read, Show) deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe SubmitButton instance Universe ButtonSubmit
instance Finite SubmitButton instance Finite ButtonSubmit
nullaryPathPiece ''SubmitButton $ camelToPathPiece' 1 nullaryPathPiece ''ButtonSubmit $ camelToPathPiece' 1
buttonField :: forall a m. buttonField :: forall a m.
( Button (HandlerSite m) a ( Button (HandlerSite m) a
, Show (ButtonCssClass (HandlerSite m)) , MonadHandler m
, RenderMessage (HandlerSite m) ButtonMessage
, Monad m
) => a -> Field m a ) => a -> Field m a
-- | Already validates that the correct button press was received (result only neccessary for combinedButtonField) -- | Already validates that the correct button press was received (result only neccessary for combinedButtonField)
buttonField btn = Field{..} buttonField btn = Field{..}
@ -239,12 +237,12 @@ buttonField btn = Field{..}
fieldView :: FieldViewFunc m a fieldView :: FieldViewFunc m a
fieldView fid name attrs _val _ = let fieldView fid name attrs _val _ = let
cssClass' :: ButtonCssClass (HandlerSite m)
cssClass' = cssClass btn
validate = btnValidate (Proxy @(HandlerSite m)) btn validate = btnValidate (Proxy @(HandlerSite m)) btn
classes :: [ButtonClass (HandlerSite m)]
classes = btnClasses btn
in [whamlet| in [whamlet|
$newline never $newline never
<button .btn .#{bcc2txt cssClass'} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid} :not validate:formnovalidate>^{label btn} <button class=#{unwords $ map toPathPiece classes} type=submit name=#{name} value=#{toPathPiece btn} *{attrs} ##{fid} :not validate:formnovalidate>^{btnLabel btn}
|] |]
fieldParse [] [] = return $ Right Nothing fieldParse [] [] = return $ Right Nothing
@ -255,8 +253,6 @@ buttonField btn = Field{..}
combinedButtonField :: forall a m. combinedButtonField :: forall a m.
( Button (HandlerSite m) a ( Button (HandlerSite m) a
, Show (ButtonCssClass (HandlerSite m))
, RenderMessage (HandlerSite m) ButtonMessage
, MonadHandler m , MonadHandler m
) => [a] -> FieldSettings (HandlerSite m) -> AForm m [Maybe a] ) => [a] -> FieldSettings (HandlerSite m) -> AForm m [Maybe a]
combinedButtonField bs FieldSettings{..} = formToAForm $ do combinedButtonField bs FieldSettings{..} = formToAForm $ do
@ -280,8 +276,6 @@ combinedButtonField bs FieldSettings{..} = formToAForm $ do
combinedButtonFieldF :: forall m a. combinedButtonFieldF :: forall m a.
( Button (HandlerSite m) a ( Button (HandlerSite m) a
, Show (ButtonCssClass (HandlerSite m))
, RenderMessage (HandlerSite m) ButtonMessage
, Finite a , Finite a
, MonadHandler m , MonadHandler m
) => FieldSettings (HandlerSite m) -> AForm m [Maybe a] ) => FieldSettings (HandlerSite m) -> AForm m [Maybe a]
@ -298,26 +292,22 @@ disambiguateButtons = traverseAForm $ \case
combinedButtonField_ :: forall a m. combinedButtonField_ :: forall a m.
( Button (HandlerSite m) a ( Button (HandlerSite m) a
, Show (ButtonCssClass (HandlerSite m))
, RenderMessage (HandlerSite m) ButtonMessage
, MonadHandler m , MonadHandler m
) => [a] -> FieldSettings (HandlerSite m) -> AForm m () ) => [a] -> FieldSettings (HandlerSite m) -> AForm m ()
combinedButtonField_ = (void .) . combinedButtonField combinedButtonField_ = (void .) . combinedButtonField
combinedButtonFieldF_ :: forall m a p. combinedButtonFieldF_ :: forall m a p.
( Button (HandlerSite m) a ( Button (HandlerSite m) a
, Show (ButtonCssClass (HandlerSite m))
, RenderMessage (HandlerSite m) ButtonMessage
, MonadHandler m , MonadHandler m
, Finite a , Finite a
) => p a -> FieldSettings (HandlerSite m) -> AForm m () ) => p a -> FieldSettings (HandlerSite m) -> AForm m ()
combinedButtonFieldF_ _ = void . combinedButtonFieldF @m @a combinedButtonFieldF_ _ = void . combinedButtonFieldF @m @a
submitButton :: (Button (HandlerSite m) SubmitButton, Show (ButtonCssClass (HandlerSite m)), MonadHandler m, RenderMessage (HandlerSite m) ButtonMessage) => AForm m () submitButton :: (Button (HandlerSite m) ButtonSubmit, MonadHandler m) => AForm m ()
submitButton = combinedButtonFieldF_ (Proxy @SubmitButton) "" submitButton = combinedButtonFieldF_ (Proxy @ButtonSubmit) ""
autosubmitButton :: (Button (HandlerSite m) SubmitButton, Show (ButtonCssClass (HandlerSite m)), MonadHandler m, RenderMessage (HandlerSite m) ButtonMessage) => AForm m () autosubmitButton :: (Button (HandlerSite m) ButtonSubmit, MonadHandler m) => AForm m ()
autosubmitButton = combinedButtonFieldF_ (Proxy @SubmitButton) $ "" & addAutosubmit autosubmitButton = combinedButtonFieldF_ (Proxy @ButtonSubmit) $ "" & addAutosubmit
------------------- -------------------
-- Custom Fields -- -- Custom Fields --

46
src/Utils/Sheet.hs Normal file
View File

@ -0,0 +1,46 @@
module Utils.Sheet where
import Import.NoFoundation
import qualified Database.Esqueleto as E
-- DB Queries for Sheets that are used in several places
sheetCurrent :: MonadIO m => TermId -> SchoolId -> CourseShorthand -> SqlReadT m (Maybe SheetName)
sheetCurrent tid ssh csh = do
now <- liftIO getCurrentTime
sheets <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheet E.^. SheetActiveTo E.>. E.val now
E.&&. sheet E.^. SheetActiveFrom E.<=. E.val now
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.orderBy [E.asc $ sheet E.^. SheetActiveTo]
E.limit 1
return $ sheet E.^. SheetName
return $ case sheets of
[] -> Nothing
[E.Value shn] -> Just shn
_ -> error "SQL Query with limit 1 returned more than one result"
sheetOldUnassigned :: MonadIO m => TermId -> SchoolId -> CourseShorthand -> SqlReadT m (Maybe SheetName)
sheetOldUnassigned tid ssh csh = do
now <- liftIO getCurrentTime
sheets <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheet E.^. SheetActiveTo E.<=. E.val now
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.where_ . E.exists . E.from $ \submission ->
E.where_ $ submission E.^. SubmissionSheet E.==. sheet E.^. SheetId
E.&&. E.isNothing (submission E.^. SubmissionRatingBy)
E.orderBy [E.asc $ sheet E.^. SheetActiveTo]
E.limit 1
return $ sheet E.^. SheetName
return $ case sheets of
[] -> Nothing
[E.Value shn] -> Just shn
_ -> error "SQL Query with limit 1 returned more than one result"

View File

@ -36,3 +36,5 @@
^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")} ^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")}
<li> <li>
^{modal "Email-Test" (Right emailWidget')} ^{modal "Email-Test" (Right emailWidget')}
<li>
^{visibleWidget False}

View File

@ -1,10 +1,9 @@
<div .container> <div .container>
<dl .deflist> <dl .deflist>
$maybe school <- schoolMB <dt .deflist__dt>Fakultät/Institut
<dt .deflist__dt>Fakultät/Institut <dd .deflist__dd>
<dd .deflist__dd> <div>
<div> #{schoolName}
#{schoolName school}
$maybe descr <- courseDescription course $maybe descr <- courseDescription course
<dt .deflist__dt>_{MsgCourseDescription} <dt .deflist__dt>_{MsgCourseDescription}
@ -33,20 +32,27 @@ $# $if NTop (Just 0) < NTop (courseCapacity course)
#{participants} #{participants}
$maybe capacity <- courseCapacity course $maybe capacity <- courseCapacity course
\ von #{capacity} \ von #{capacity}
$maybe regFrom <- mRegFrom $maybe regFrom <- mRegFrom
<dt .deflist__dt>Anmeldezeitraum <dt .deflist__dt>Anmeldezeitraum
<dd .deflist__dd> <dd .deflist__dd>
<div>
Ab #{regFrom}
$maybe regTo <- mRegTo
\ bis #{regTo}
$maybe dereg <- mDereg
<div> <div>
Ab #{regFrom} \ <em>Achtung:</em>
$maybe regTo <- mRegTo \ Abmeldung nur bis #{dereg} erlaubt.
\ bis #{regTo} $if registrationOpen || isJust mRegAt
$if registrationOpen
<dt .deflist__dt> <dt .deflist__dt>
<dd .deflist__dd> <dd .deflist__dd>
<div .course__registration> <div .course__registration>
<form method=post action=@{CourseR tid ssh csh CRegisterR} enctype=#{regEnctype}> $if registrationOpen
$# regWidget is defined through templates/widgets/registerForm <form method=post action=@{CourseR tid ssh csh CRegisterR} enctype=#{regEnctype}>
^{regWidget} $# regWidget is defined through templates/widgets/registerForm
^{regWidget}
$maybe date <- mRegAt
_{MsgRegisteredSince date}
<dt .deflist__dt> <dt .deflist__dt>
Material Material
<dd .deflist__dd> <dd .deflist__dd>

View File

@ -1,3 +1,2 @@
<div .container> <div .container>
<div .scrolltable> ^{coursesTable}
^{coursesTable}

View File

@ -335,6 +335,8 @@ input[type="button"].btn-info:hover,
/* SCROLLTABLE */ /* SCROLLTABLE */
.scrolltable { .scrolltable {
overflow: auto; overflow: auto;
box-shadow: 0 0 3px 0 var(--color-grey);
margin-bottom: 15px;
} }
@media (max-width: 425px) { @media (max-width: 425px) {

View File

@ -6,7 +6,7 @@
.checkbox { .checkbox {
display: inline-block; display: inline-block;
margin-left: 5px; margin-left: 7px;
} }
} }

View File

@ -234,7 +234,8 @@ input[type="checkbox"]:checked::after {
height: 24px; height: 24px;
width: 24px; width: 24px;
background-color: #f3f3f3; background-color: #f3f3f3;
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05); box-shadow: inset 0 1px 2px 1px rgba(50, 50, 50, 0.05);
border: 2px solid var(--color-primary);
border-radius: 4px; border-radius: 4px;
color: white; color: white;
cursor: pointer; cursor: pointer;
@ -242,39 +243,43 @@ input[type="checkbox"]:checked::after {
label::before, label::before,
label::after { label::after {
content: '';
position: absolute; position: absolute;
top: 11px;
left: 3px;
display: block; display: block;
width: 18px; top: 12px;
left: 8px;
height: 2px; height: 2px;
width: 8px;
background-color: var(--color-font); background-color: var(--color-font);
transition: all .2s;
transform: scale(0.5, 0.1);
} }
:checked + label { :checked + label {
background-color: var(--color-primary); background-color: var(--color-primary);
text-decoration: underline; }
:checked + label::before,
:checked + label::after {
content: '';
} }
:checked + label::before { :checked + label::before {
background-color: white; background-color: white;
transform: scale(1, 1) rotate(45deg); transform: rotate(45deg);
left: 4px;
} }
:checked + label::after { :checked + label::after {
background-color: white; background-color: white;
transform: scale(1, 1) rotate(-45deg); transform: rotate(-45deg);
top: 11px;
width: 13px;
} }
}
.radio label::before { [disabled] + label {
transform: scale(0.01, 0.01) rotate(45deg); pointer-events: none;
} border: none;
.radio label::after { opacity: 0.6;
transform: scale(0.01, 0.01) rotate(-45deg); filter: grayscale(1);
}
} }
.radio::before { .radio::before {

View File

@ -3,6 +3,6 @@ $newline never
<form method=GET action=#{filterAction} enctype=#{filterEnctype}> <form method=GET action=#{filterAction} enctype=#{filterEnctype}>
^{filterWgdt} ^{filterWgdt}
<button> <button>
^{label BtnSubmit} ^{btnLabel BtnSubmit}
<section> <section>
^{scrolltable} ^{scrolltable}

View File

@ -3,10 +3,17 @@ $if null rows && (dbsEmptyStyle == DBESNoHeading)
_{dbsEmptyMessage} _{dbsEmptyMessage}
$else $else
^{table} ^{table}
<div .table-footer>
<div .table__row-count>
_{MsgRowCount rowCount}
$# Since the current pagesize is always a member of pagesizeOptions we don't need to check `pageCount > 1`
$if toEnum (fromIntegral rowCount) > minimum (pagesizeOptions referencePagesize)
<form .pagesize ##{wIdent "pagesize-form"} method=GET enctype=#{pagesizeEnc} action=#{rawAction}>
^{pagesizeWdgt}
$if pageCount > 1 $if pageCount > 1
<div .pagination> <div .pagination>
<form .pagesize method=GET enctype=#{pagesizeEnc} action=#{rawAction}>
^{pagesizeWdgt}
<ul ##{wIdent "pagination"} .pages> <ul ##{wIdent "pagination"} .pages>
$forall p <- pageNumbers $forall p <- pageNumbers
<li .page-link :p == psPage:.current> <li .page-link :p == psPage:.current>

View File

@ -1,94 +1,148 @@
(function collonadeClosure() { (function collonadeClosure() {
'use strict'; 'use strict';
document.addEventListener('setup', function DOMContentLoaded(e) { window.utils = window.utils || {};
console.log('dbtable', e); window.utils.asyncTable = function(wrapper) {
if (e.detail.module && e.detail.module !== 'dbtable') var tableIdent = #{String dbtIdent};
return; var shortCircuitHeader = #{String (toPathPiece HeaderDBTableShortcircuit)};
function setupAsync(wrapper) { var ths = [];
var pagination;
var pagesizeForm;
var table = wrapper.querySelector('#' + #{String dbtIdent}); function init() {
if (!table) var table = wrapper.querySelector('#' + tableIdent);
if (!table) {
return; return;
}
var ths = Array.from(table.querySelectorAll('th.sortable'));
var pagination = wrapper.querySelector('#' + #{String dbtIdent} + '-pagination');
ths = Array.from(table.querySelectorAll('th.sortable'));
pagination = wrapper.querySelector('#' + tableIdent + '-pagination');
pagesizeForm = wrapper.querySelector('#' + tableIdent + '-pagesize-form');
setupListeners();
wrapper.classList.add('js-initialized');
}
function setupListeners() {
ths.forEach(function(th) { ths.forEach(function(th) {
th.addEventListener('click', clickHandler); th.addEventListener('click', clickHandler);
}); });
if (pagination) { if (pagination) {
Array.from(pagination.querySelectorAll('.page-link')) var pageLinks = Array.from(pagination.querySelectorAll('.page-link'));
.forEach(function(p) { pageLinks.forEach(function(p) {
p.addEventListener('click', clickHandler); p.addEventListener('click', clickHandler);
});
}
function clickHandler(event) {
event.preventDefault();
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
updateTableFrom(url);
}
function getClickDestination(el) {
console.log(el);
if (!el.querySelector('a')) {
return false;
}
return el.querySelector('a').getAttribute('href');
}
// fetches new sorted table from url with params and replaces contents of current table
function updateTableFrom(url) {
fetch(url, {
credentials: 'same-origin',
headers: {
'Accept': 'text/html',
#{String (toPathPiece HeaderDBTableShortcircuit)}: #{String dbtIdent}
}
}).then(function(response) {
if (!response.ok) {
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
}
return response.text();
}).then(function(data) {
// remove listeners
ths.forEach(function(th) {
th.removeEventListener('click', clickHandler);
});
// replace contents of table body
wrapper.innerHTML = data;
// set up async functionality again
wrapper.classList.remove("js-initialized");
document.dispatchEvent(new CustomEvent('setup', {
detail: { scope: wrapper },
bubbles: true,
cancelable: true
}));
// table.querySelector('tbody').innerHTML = data;
}).catch(function(err) {
console.error(err);
}); });
} }
wrapper.classList.add("js-initialized"); if (pagesizeForm) {
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]')
pagesizeSelect.addEventListener('change', changeHandler);
}
} }
var selector = '#' + #{String $ dbtIdent} + '-table-wrapper:not(.js-initialized)'; function removeListeners() {
var wrapperEl = e.detail.scope.querySelector(selector); ths.forEach(function(th) {
if (wrapperEl) th.removeEventListener('click', clickHandler);
setupAsync(wrapperEl); });
else if (e.detail.scope.matches(selector))
setupAsync(e.detail.scope); if (pagination) {
}); var pageLinks = Array.from(pagination.querySelectorAll('.page-link'));
pageLinks.forEach(function(p) {
p.removeEventListener('click', clickHandler);
});
}
if (pagesizeForm) {
var pagesizeSelect = pagesizeForm.querySelector('[name=' + tableIdent + '-pagesize]')
pagesizeSelect.removeEventListener('change', changeHandler);
}
}
function clickHandler(event) {
event.preventDefault();
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
updateTableFrom(url);
}
function getClickDestination(el) {
if (!el.querySelector('a')) {
return '';
}
return el.querySelector('a').getAttribute('href');
}
function changeHandler(event) {
var currentTableUrl = wrapper.dataset.currentUrl || window.location.href;
var url = getUrlWithUpdatedPagesize(currentTableUrl, event.target.value);
url = getUrlWithResetPagenumber(url);
updateTableFrom(url);
}
function getUrlWithUpdatedPagesize(url, pagesize) {
if (url.indexOf('pagesize') >= 0) {
return url.replace(/pagesize=(\d+)/, 'pagesize=' + pagesize);
} else if (url.indexOf('?') >= 0) {
return url += '&' + tableIdent + '-pagesize=' + pagesize;
}
return url += '?' + tableIdent + '-pagesize=' + pagesize;
}
function getUrlWithResetPagenumber(url) {
return url.replace(/-page=\d+/, '-page=0');
}
function updateWrapperContents(newHtml) {
wrapper.innerHTML = newHtml;
wrapper.classList.remove("js-initialized");
// setup the wrapper and its components to behave async again
window.utils.asyncTable(wrapper);
// make sure to hide any new submit buttons
document.dispatchEvent(new CustomEvent('setup', {
detail: {
scope: wrapper,
module: 'autoSubmit'
}
}));
}
// fetches new sorted table from url with params and replaces contents of current table
function updateTableFrom(url) {
fetch(url, {
credentials: 'same-origin',
headers: {
'Accept': 'text/html',
[shortCircuitHeader]: tableIdent
}
}).then(function(response) {
if (!response.ok) {
throw new Error('Looks like there was a problem fetching ' + url + '. Status Code: ' + response.status);
}
return response.text();
}).then(function(data) {
wrapper.dataset.currentUrl = url;
removeListeners();
updateWrapperContents(data);
}).catch(function(err) {
console.error(err);
});
}
init();
};
})(); })();
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'dbtable' }, bubbles: true, cancelable: true })); var selector = '#' + #{String $ dbtIdent} + '-table-wrapper:not(.js-initialized)';
var wrapper = document.querySelector(selector);
if (wrapper) {
window.utils.asyncTable(wrapper);
}
}); });

View File

@ -1,24 +1,29 @@
/* TABLE FOOTER */
.table-footer {
display: flex;
flex-flow: row-reverse;
justify-content: space-between;
}
/* PAGINATION */ /* PAGINATION */
.pagination { .pagination {
margin-top: 20px; margin-top: 20px;
display: flex; overflow: auto;
flex-direction: row;
.pagesize {
float: left;
flex-grow: 0;
}
.pages { .pages {
text-align: center; text-align: center;
flex-grow: 1; white-space: nowrap;
margin: 0; margin: 0;
.page-link { .page-link {
margin: 0 7px; margin-top: 7px;
display: inline-block; display: inline-block;
background-color: var(--color-grey); background-color: var(--color-grey);
+ .page-link {
margin-left: 7px;
}
a { a {
color: var(--color-lightwhite); color: var(--color-lightwhite);
padding: 7px 13px; padding: 7px 13px;
@ -42,10 +47,6 @@
pointer-events: none; pointer-events: none;
} }
} }
&:last-child {
margin-right: 0;
}
} }
} }
} }

View File

@ -17,16 +17,7 @@
}; };
})(); })();
document.addEventListener('setup', function(e) {
if (e.detail.module && e.detail.module !== 'asidenav')
return;
var asidenavEl = e.detail.scope.querySelector('.main__aside');
window.utils.aside(asidenavEl);
});
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'asidenav' }, bubbles: true, cancelable: true })) var asidenavEl = document.querySelector('.main__aside');
window.utils.aside(asidenavEl);
}); });

View File

@ -3,6 +3,7 @@ $newline never
$case formLayout $case formLayout
$of FormDBTablePagesize $of FormDBTablePagesize
$forall view <- fieldViews $forall view <- fieldViews
<label .form-group__label.label-pagesize for=#{fvId view}>#{fvLabel view}
^{fvInput view} ^{fvInput view}
$of _ $of _
$forall view <- fieldViews $forall view <- fieldViews

View File

@ -55,8 +55,6 @@
function addEventListeners() { function addEventListeners() {
fields.forEach(function(field) { fields.forEach(function(field) {
console.log('interactiveFieldset', 'addEventListeners', field);
field.condEl.addEventListener('input', updateFields) field.condEl.addEventListener('input', updateFields)
}); });
} }
@ -72,8 +70,6 @@ document.addEventListener('setup', function(e) {
if (e.detail.module && e.detail.module !== 'showHide') if (e.detail.module && e.detail.module !== 'showHide')
return; return;
console.log('form setup', e.detail.scope);
var forms = e.detail.scope.querySelectorAll('form'); var forms = e.detail.scope.querySelectorAll('form');
Array.from(forms).forEach(function(form) { Array.from(forms).forEach(function(form) {
// auto reactiveButton submit-buttons with required fields // auto reactiveButton submit-buttons with required fields
@ -114,11 +110,9 @@ document.addEventListener('setup', function(e) {
var target = ancestor || elem; var target = ancestor || elem;
target.classList.add('hidden'); target.classList.add('hidden');
} else if (elem.form) {
elem.addEventListener('change', function () { elem.form.submit() })
} }
elem.classList.add('.js-initalized'); elem.classList.add('js-initialized');
}); });
}); });

View File

@ -11,14 +11,32 @@ fieldset {
margin-bottom: 0; margin-bottom: 0;
} }
[data-autosubmit][type="submit"] {
animation: fade-in 500ms ease-in-out backwards;
animation-delay: 500ms;
}
@keyframes fade-in {
from {
opacity: 0;
}
}
.hidden { .hidden {
visibility: hidden; visibility: hidden !important;
height: 0; height: 0 !important;
opacity: 0; width: 0 !important;
margin: 0; opacity: 0 !important;
margin: 0 !important;
padding: 0 !important;
min-width: 0 !important;
} }
.select--pagesize { .select--pagesize {
width: 5em; width: 5em;
min-width: 75px; min-width: 75px;
} }
.label-pagesize {
margin-right: 13px;
}

View File

@ -9,34 +9,35 @@ $# rowWdgts :: Liste von Widgets für jede Zeile (Normal,Bonus,KeineWert
$# -- $# --
<div> <div>
<h3>_{MsgSummaryTitle} _{title $ getSum $ numSheets $ sumSummaries} <h3>_{MsgSummaryTitle} _{title $ getSum $ numSheets $ sumSummaries}
<table .table .table--striped> <div .scrolltable>
<tr .table__row .table__row--head> <table .table .table--striped>
<th> <tr .table__row .table__row--head>
$# empty cell for row headers <th>
$maybe _ <- hasMarkedPasses $# empty cell for row headers
<th .table__th colspan=2>_{MsgCorrected}
$maybe _ <- hasPasses
<th .table__th>_{MsgSheetGradingPassing'}
$maybe _ <- hasMarkedPoints
<th .table__th colspan=2>_{MsgCorrected}
$maybe _ <- hasPoints
<th .table__th>_{MsgSheetGradingPoints'}
<th .table__th>_{MsgSheetGradingCount'}
$# Number of Sheet/Submissions used for calculating maximum passes/points
$forall row <- rowWdgts
^{row}
$maybe nrNoGrade <- positiveSum $ numNotGraded
<tr .table__row>
<th .table__th>_{MsgSheetTypeNotGraded}
$maybe _ <- hasMarkedPasses $maybe _ <- hasMarkedPasses
<td colspan=2> <th .table__th colspan=2>_{MsgCorrected}
$maybe _ <- hasPasses $maybe _ <- hasPasses
<td .table__td> <th .table__th>_{MsgSheetGradingPassing'}
$maybe _ <- hasMarkedPoints $maybe _ <- hasMarkedPoints
<td .table__td colspan=2> <th .table__th colspan=2>_{MsgCorrected}
$maybe _ <- hasPoints $maybe _ <- hasPoints
<td .table__td> <th .table__th>_{MsgSheetGradingPoints'}
<td .table__td>#{nrNoGrade} <th .table__th>_{MsgSheetGradingCount'}
$# Number of Sheet/Submissions used for calculating maximum passes/points
$forall row <- rowWdgts
^{row}
$maybe nrNoGrade <- positiveSum $ numNotGraded
<tr .table__row>
<th .table__th>_{MsgSheetTypeNotGraded}
$maybe _ <- hasMarkedPasses
<td colspan=2>
$maybe _ <- hasPasses
<td .table__td>
$maybe _ <- hasMarkedPoints
<td .table__td colspan=2>
$maybe _ <- hasPoints
<td .table__td>
<td .table__td>#{nrNoGrade}
$maybe _ <- positiveSum $ bonusSummary ^. _numSheets $maybe _ <- positiveSum $ bonusSummary ^. _numSheets
<p>_{MsgSheetTypeInfoBonus} # <p>_{MsgSheetTypeInfoBonus} #
$maybe _ <- positiveSum $ bonusSummary ^. _achievedPoints $maybe _ <- positiveSum $ bonusSummary ^. _achievedPoints

View File

@ -5,4 +5,3 @@ $maybe secretView <- msecretView
^{fvInput secretView} ^{fvInput secretView}
$# Always display register/deregister button $# Always display register/deregister button
^{fvInput btnView} ^{fvInput btnView}