Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX into feat/multi-file-field
This commit is contained in:
commit
9a26d17c5e
@ -18,11 +18,10 @@
|
||||
- i18n der
|
||||
Links -> MenuItems verwenden wie bisher
|
||||
Page Titles -> setTitleI
|
||||
Buttons?
|
||||
Buttons? -> Kann leicht geändert werden!
|
||||
Was ist mit einfachen Text Feldern, z.B. die Beschriftung von Knöpfen wie in Handler.Course.getCourseListTermR, Zeile 66 "pageActions" für menuItemLabel?
|
||||
|
||||
** Page pageActions
|
||||
- Berechtigungen prüfen?
|
||||
** Page pageActions - Berechtigungen prüfen?
|
||||
=> Eigener Constructor statt NavbarLeft/Right?!
|
||||
|
||||
|
||||
|
||||
38
fill-db.hs
38
fill-db.hs
@ -87,19 +87,17 @@ main = db $ do
|
||||
, courseTermId = TermKey summer2018
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 20
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = gkleen
|
||||
, courseChangedBy = gkleen
|
||||
, courseHasRegistration = True
|
||||
, courseRegisterFrom = Just now
|
||||
, courseRegisterTo = Just ((3600 * 24 * 60) `addUTCTime` now )
|
||||
}
|
||||
insert_ $ CourseEdit jost now ffp
|
||||
void . insert $ DegreeCourse ifiBsc ffp
|
||||
void . insert $ DegreeCourse ifiMsc ffp
|
||||
void . insert $ Lecturer gkleen ffp
|
||||
void . insert $ Corrector gkleen ffp (ByProportion 1)
|
||||
void . insert $ Sheet ffp "Blatt 1" Nothing NotGraded Nothing now now Nothing Nothing now now gkleen gkleen
|
||||
insert_ $ Corrector gkleen ffp (ByProportion 1)
|
||||
sheetkey <- insert $ Sheet ffp "Blatt 1" Nothing NotGraded NoGroups Nothing Nothing now now Nothing Nothing
|
||||
insert_ $ SheetEdit gkleen now sheetkey
|
||||
-- EIP
|
||||
eip <- insert Course
|
||||
{ courseName = "Einführung in die Programmierung"
|
||||
@ -109,14 +107,11 @@ main = db $ do
|
||||
, courseTermId = TermKey summer2017
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 20
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = fhamann
|
||||
, courseChangedBy = fhamann
|
||||
, courseHasRegistration = False
|
||||
, courseRegisterFrom = Nothing
|
||||
, courseRegisterTo = Nothing
|
||||
}
|
||||
insert_ $ CourseEdit fhamann now eip
|
||||
void . insert $ DegreeCourse ifiBsc eip
|
||||
void . insert $ DegreeCourse ifiMsc eip
|
||||
void . insert $ Lecturer fhamann eip
|
||||
@ -129,14 +124,11 @@ main = db $ do
|
||||
, courseTermId = TermKey summer2018
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 20
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = fhamann
|
||||
, courseChangedBy = fhamann
|
||||
, courseHasRegistration = True
|
||||
, courseRegisterFrom = Just now
|
||||
, courseRegisterTo = Just ((3600 * 24 * 60) `addUTCTime` now )
|
||||
}
|
||||
insert_ $ CourseEdit fhamann now ixd
|
||||
void . insert $ DegreeCourse ifiBsc ixd
|
||||
void . insert $ Lecturer fhamann ixd
|
||||
-- concept development
|
||||
@ -148,14 +140,11 @@ main = db $ do
|
||||
, courseTermId = TermKey winter2017
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 30
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = fhamann
|
||||
, courseChangedBy = fhamann
|
||||
, courseHasRegistration = False
|
||||
, courseRegisterFrom = Nothing
|
||||
, courseRegisterTo = Nothing
|
||||
}
|
||||
insert_ $ CourseEdit fhamann now ux3
|
||||
void . insert $ DegreeCourse ifiBsc ux3
|
||||
void . insert $ Lecturer fhamann ux3
|
||||
-- promo
|
||||
@ -167,14 +156,11 @@ main = db $ do
|
||||
, courseTermId = TermKey summer2017
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 50
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = jost
|
||||
, courseChangedBy = jost
|
||||
, courseHasRegistration = False
|
||||
, courseRegisterFrom = Nothing
|
||||
, courseRegisterTo = Nothing
|
||||
}
|
||||
insert_ $ CourseEdit jost now pmo
|
||||
void . insert $ DegreeCourse ifiBsc pmo
|
||||
void . insert $ Lecturer jost pmo
|
||||
-- datenbanksysteme
|
||||
@ -186,13 +172,11 @@ main = db $ do
|
||||
, courseTermId = TermKey summer2018
|
||||
, courseSchoolId = ifi
|
||||
, courseCapacity = Just 50
|
||||
, courseCreated = now
|
||||
, courseChanged = now
|
||||
, courseCreatedBy = jost
|
||||
, courseChangedBy = jost
|
||||
, courseHasRegistration = False
|
||||
, courseRegisterFrom = Nothing
|
||||
, courseRegisterTo = Nothing
|
||||
}
|
||||
insert_ $ CourseEdit gkleen now dbs
|
||||
void . insert $ DegreeCourse ifiBsc dbs
|
||||
void . insert $ Lecturer jost dbs
|
||||
void . insert $ Lecturer gkleen dbs
|
||||
void . insert $ Lecturer jost dbs
|
||||
|
||||
47
models
47
models
@ -61,14 +61,14 @@ Course
|
||||
termId TermId
|
||||
schoolId SchoolId
|
||||
capacity Int Maybe
|
||||
created UTCTime
|
||||
changed UTCTime
|
||||
createdBy UserId
|
||||
changedBy UserId
|
||||
hasRegistration Bool -- canRegisterNow = hasRegistration && maybe False (<= currentTime) registerFrom && maybe True (>= currentTime) registerTo
|
||||
registerFrom UTCTime Maybe
|
||||
registerTo UTCTime Maybe
|
||||
CourseTermShort termId shorthand
|
||||
CourseEdit
|
||||
user UserId
|
||||
time UTCTime
|
||||
course CourseId
|
||||
Lecturer
|
||||
userId UserId
|
||||
courseId CourseId
|
||||
@ -103,15 +103,11 @@ Sheet
|
||||
activeTo UTCTime
|
||||
hintFrom UTCTime Maybe
|
||||
solutionFrom UTCTime Maybe
|
||||
created UTCTime -- delete
|
||||
changed UTCTime -- delete
|
||||
createdBy UserId -- delete
|
||||
changedBy UserId -- delete
|
||||
CourseSheet courseId name
|
||||
SheetEdit
|
||||
sheet SheetId
|
||||
user UserId
|
||||
time UTCTime
|
||||
sheet SheetId
|
||||
SheetFile
|
||||
sheetId SheetId
|
||||
fileId FileId
|
||||
@ -128,11 +124,11 @@ Submission
|
||||
ratingComment Text Maybe
|
||||
ratingBy UserId Maybe
|
||||
ratingTime UTCTime Maybe
|
||||
created UTCTime
|
||||
changed UTCTime
|
||||
createdBy UserId
|
||||
changedBy UserId
|
||||
deriving Show
|
||||
SubmissionEdit
|
||||
user UserId
|
||||
time UTCTime
|
||||
submission SubmissionId
|
||||
SubmissionFile
|
||||
submissionId SubmissionId
|
||||
fileId FileId
|
||||
@ -147,10 +143,10 @@ SubmissionUser
|
||||
SubmissionGroup
|
||||
courseId CourseId
|
||||
name Text
|
||||
created UTCTime
|
||||
changed UTCTime
|
||||
createdBy UserId
|
||||
changedBy UserId
|
||||
SubmissionGroupEdit
|
||||
user UserId
|
||||
time UTCTime
|
||||
submissionGroup SubmissionGroupId
|
||||
SubmissionGroupUser
|
||||
submissionGroupId SubmissionGroupId
|
||||
userId UserId
|
||||
@ -169,13 +165,12 @@ Booking
|
||||
end UTCTime
|
||||
weekly Bool
|
||||
exceptions [Day] -- only if weekly, begin in exception
|
||||
created UTCTime
|
||||
changed UTCTime
|
||||
createdBy UserId
|
||||
changedBy UserId
|
||||
|
||||
bookedFor RoomForId
|
||||
room RoomId
|
||||
BookingEdit
|
||||
user UserId
|
||||
time UTCTime
|
||||
boooking BookingId
|
||||
Room
|
||||
name Text
|
||||
capacity Int Maybe
|
||||
@ -201,10 +196,10 @@ Exam
|
||||
deregistrationEnd UTCTime
|
||||
ratingVisible Bool
|
||||
statisticsVisible Bool
|
||||
created UTCTime
|
||||
changed UTCTime
|
||||
createdBy UserId
|
||||
changedBy UserId
|
||||
ExamEdit
|
||||
user UserId
|
||||
time UTCTime
|
||||
exam ExamId
|
||||
ExamUser
|
||||
userId UserId
|
||||
examId ExamId
|
||||
|
||||
39
routes
39
routes
@ -6,36 +6,29 @@
|
||||
|
||||
/ HomeR GET POST
|
||||
/profile ProfileR GET
|
||||
/users UsersR GET
|
||||
/users UsersR GET !adminAny
|
||||
|
||||
/term TermShowR GET
|
||||
/term/edit TermEditR GET POST
|
||||
/term/#TermId/edit TermEditExistR GET
|
||||
/term TermShowR GET
|
||||
/term/edit TermEditR GET POST !adminAny
|
||||
/term/#TermId/edit TermEditExistR GET !adminAny
|
||||
|
||||
/course/ CourseListR GET
|
||||
!/course/new CourseNewR GET POST
|
||||
!/course/new CourseNewR GET POST !lecturerAny
|
||||
!/course/#TermId CourseListTermR GET
|
||||
/course/#TermId/#Text CourseR:
|
||||
/show CourseShowR GET POST
|
||||
/edit CourseEditR GET POST !lecturer
|
||||
|
||||
-- /course/#TermId/#Text CourseR !tag:
|
||||
-- /edit CourseEditR GET POST
|
||||
-- /show CourseShowR GET POST -- CourseR tid csh CourseShowR
|
||||
-- /ex/#Text SheetR: !registered
|
||||
-- /show
|
||||
-- /edit -- CourseR tid csg (SheetR csh SheetEditR)
|
||||
-- /delete
|
||||
|
||||
/course/#TermId/#Text/edit CourseEditR GET
|
||||
/course/#TermId/#Text/show CourseShowR GET POST
|
||||
/ex SheetR !registered:
|
||||
/ SheetListR GET
|
||||
/#Text/show SheetShowR GET !time
|
||||
/#Text/#SheetFileType/#FilePath SheetFileR GET !time
|
||||
/new SheetNewR GET POST !lecturer
|
||||
/#Text/edit SheetEditR GET POST !lecturer
|
||||
/#Text/delete SheetDelR GET POST !lecturer
|
||||
|
||||
|
||||
|
||||
/course/#TermId/#Text/ex/ SheetListR GET
|
||||
/course/#TermId/#Text/ex/#Text/show SheetShowR GET
|
||||
/course/#TermId/#Text/ex/#Text/#SheetFileType/#FilePath SheetFileR GET
|
||||
/course/#TermId/#Text/ex/new SheetNewR GET POST
|
||||
/course/#TermId/#Text/ex/#Text/edit SheetEditR GET POST
|
||||
/course/#TermId/#Text/ex/#Text/delete SheetDelR GET POST
|
||||
|
||||
-- TODO below
|
||||
/submission SubmissionListR GET POST
|
||||
/submission/#CryptoUUIDSubmission SubmissionR GET POST
|
||||
/submissions.zip SubmissionDownloadMultiArchiveR POST
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
{-# LANGUAGE PatternSynonyms #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE RecordWildCards #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
@ -53,6 +54,9 @@ import System.FilePath
|
||||
|
||||
import Handler.Utils.Templates
|
||||
|
||||
-- infixl 9 :$:
|
||||
-- pattern a :$: b = a b
|
||||
|
||||
-- | The foundation datatype for your application. This can be a good place to
|
||||
-- keep settings and values requiring initialization before your application
|
||||
-- starts running, such as database connections. Every handler will have
|
||||
@ -80,7 +84,9 @@ data UniWorX = UniWorX
|
||||
-- type Widget = WidgetT UniWorX IO ()
|
||||
mkYesodData "UniWorX" $(parseRoutesFile "routes")
|
||||
|
||||
type DB a = YesodDB UniWorX a
|
||||
-- Pattern Synonyms for convenience
|
||||
pattern CSheetR tid csh ptn = CourseR tid csh (SheetR ptn)
|
||||
|
||||
|
||||
data MenuItem = MenuItem
|
||||
{ menuItemLabel :: Text
|
||||
@ -89,13 +95,16 @@ data MenuItem = MenuItem
|
||||
, menuItemAccessCallback :: Handler Bool
|
||||
}
|
||||
|
||||
data MenuTypes
|
||||
= NavbarAside { menuItem :: MenuItem }
|
||||
| NavbarRight { menuItem :: MenuItem }
|
||||
| NavbarExtra { menuItem :: MenuItem }
|
||||
| NavbarSecondary { menuItem :: MenuItem }
|
||||
data MenuTypes -- Semantische Rolle:
|
||||
= NavbarAside { menuItem :: MenuItem } -- TODO
|
||||
| NavbarExtra { menuItem :: MenuItem } -- TODO
|
||||
| NavbarRight { menuItem :: MenuItem } -- Generell, nahezu immer sichtbar
|
||||
| NavbarSecondary { menuItem :: MenuItem } -- Generell, nahezu immer sichtbar
|
||||
| PageActionPrime { menuItem :: MenuItem } -- Seitenspezifische Aktion, häufig
|
||||
| PageActionSecondary { menuItem :: MenuItem } -- Seitenspezifische Aktion, selten
|
||||
|
||||
-- | A convenient synonym for creating forms.
|
||||
-- | Convenient Type Synonyms:
|
||||
type DB a = YesodDB UniWorX a
|
||||
type Form x = Html -> MForm (HandlerT UniWorX IO) (FormResult x, Widget)
|
||||
|
||||
mkMessage "UniWorX" "messages" "de"
|
||||
@ -151,7 +160,7 @@ instance Yesod UniWorX where
|
||||
isAuthorized TermShowR _ = return Authorized
|
||||
isAuthorized CourseListR _ = return Authorized
|
||||
isAuthorized (CourseListTermR _) _ = return Authorized
|
||||
isAuthorized (CourseShowR _ _) _ = return Authorized
|
||||
isAuthorized (CourseR _ _ CourseShowR) _ = return Authorized
|
||||
isAuthorized (CryptoUUIDDispatchR _) _ = return Authorized
|
||||
isAuthorized SubmissionListR _ = isAuthenticated
|
||||
isAuthorized SubmissionDownloadMultiArchiveR _ = isAuthenticated
|
||||
@ -198,6 +207,12 @@ instance Yesod UniWorX where
|
||||
makeLogger = return . appLogger
|
||||
|
||||
isAuthorizedDB :: Route UniWorX -> Bool -> YesodDB UniWorX AuthResult
|
||||
isAuthorizedDB route@(routeAttrs -> attrs) writeable
|
||||
| "adminAny" `member` attrs = adminAccess Nothing
|
||||
| "lecturerAny" `member` attrs = lecturerAccess Nothing
|
||||
|
||||
|
||||
|
||||
isAuthorizedDB UsersR _ = adminAccess Nothing
|
||||
isAuthorizedDB (SubmissionR cID) _ = submissionAccess $ Right cID
|
||||
isAuthorizedDB (SubmissionDownloadSingleR cID _) _ = submissionAccess $ Right cID
|
||||
@ -205,14 +220,14 @@ isAuthorizedDB (SubmissionDownloadArchiveR (splitExtension -> (baseName, _))) _
|
||||
isAuthorizedDB TermEditR _ = adminAccess Nothing
|
||||
isAuthorizedDB (TermEditExistR _) _ = adminAccess Nothing
|
||||
isAuthorizedDB CourseNewR _ = lecturerAccess Nothing
|
||||
isAuthorizedDB (CourseEditR t c) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (SheetListR t c) False = return Authorized --
|
||||
isAuthorizedDB (SheetShowR t c s) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor
|
||||
isAuthorizedDB (SheetFileR t c s _ _) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor
|
||||
isAuthorizedDB (SheetListR t c) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (SheetNewR t c) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (SheetEditR t c s) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (SheetDelR t c s) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseR t c CourseEditR) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseR t c (SheetR SheetListR)) False = return Authorized --
|
||||
isAuthorizedDB (CourseR t c (SheetR SheetListR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseR t c (SheetR (SheetShowR s))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor
|
||||
isAuthorizedDB (CourseR t c (SheetR (SheetFileR s _ _))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor
|
||||
isAuthorizedDB (CourseR t c (SheetR SheetNewR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseR t c (SheetR (SheetEditR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseR t c (SheetR (SheetDelR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c)
|
||||
isAuthorizedDB (CourseEditIDR cID) _ = do
|
||||
courseId <- decrypt cID
|
||||
courseLecturerAccess courseId
|
||||
@ -285,15 +300,15 @@ instance YesodBreadcrumbs UniWorX where
|
||||
|
||||
breadcrumb CourseListR = return ("Kurs", Just HomeR)
|
||||
breadcrumb (CourseListTermR term) = return (toPathPiece term, Just TermShowR)
|
||||
breadcrumb (CourseShowR term course) = return (course, Just $ CourseListTermR term)
|
||||
breadcrumb (CourseR term course CourseShowR) = return (course, Just $ CourseListTermR term)
|
||||
breadcrumb CourseNewR = return ("Neu", Just CourseListR)
|
||||
breadcrumb (CourseEditR _ _) = return ("Editieren", Just CourseListR)
|
||||
breadcrumb (CourseR _ _ CourseEditR) = return ("Editieren", Just CourseListR)
|
||||
|
||||
breadcrumb (SheetListR tid csh) = return ("Übungen",Just $ CourseShowR tid csh)
|
||||
breadcrumb (SheetNewR tid csh) = return ("Neu", Just $ SheetListR tid csh)
|
||||
breadcrumb (SheetShowR tid csh shn) = return (shn, Just $ SheetListR tid csh)
|
||||
breadcrumb (SheetEditR tid csh shn) = return ("Edit", Just $ SheetShowR tid csh shn)
|
||||
breadcrumb (SheetDelR tid csh shn) = return ("DELETE", Just $ SheetShowR tid csh shn)
|
||||
breadcrumb (CourseR tid csh (SheetR SheetListR)) = return ("Übungen",Just $ CourseR tid csh CourseShowR)
|
||||
breadcrumb (CourseR tid csh (SheetR SheetNewR )) = return ("Neu", Just $ CourseR tid csh $ SheetR SheetListR)
|
||||
breadcrumb (CourseR tid csh (SheetR (SheetShowR shn))) = return (shn, Just $ CourseR tid csh $ SheetR SheetListR)
|
||||
breadcrumb (CourseR tid csh (SheetR (SheetEditR shn))) = return ("Edit", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
|
||||
breadcrumb (CourseR tid csh (SheetR (SheetDelR shn))) = return ("DELETE", Just $ CourseR tid csh $ SheetR $ SheetShowR shn)
|
||||
|
||||
breadcrumb SubmissionListR = return ("Abgaben", Just HomeR)
|
||||
breadcrumb (SubmissionR _) = return ("Abgabe", Just SubmissionListR)
|
||||
@ -384,6 +399,14 @@ defaultMenuLayout menu widget = do
|
||||
asidenav = $(widgetFile "widgets/asidenav")
|
||||
breadcrumbs :: Widget
|
||||
breadcrumbs = $(widgetFile "widgets/breadcrumbs")
|
||||
pageactionprime :: Widget
|
||||
pageactionprime = $(widgetFile "widgets/pageactionprime")
|
||||
-- functions to determine if there are page-actions
|
||||
isPageActionPrime :: MenuTypes -> Bool
|
||||
isPageActionPrime (PageActionPrime _) = True
|
||||
isPageActionPrime _ = False
|
||||
hasPageActions :: Bool
|
||||
hasPageActions = any isPageActionPrime menuTypes
|
||||
|
||||
pc <- widgetToPageContent $ do
|
||||
addStylesheetRemote "https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,800,900"
|
||||
@ -391,6 +414,7 @@ defaultMenuLayout menu widget = do
|
||||
addStylesheet $ StaticR css_fonts_css
|
||||
addStylesheet $ StaticR css_icons_css
|
||||
$(widgetFile "default-layout")
|
||||
$(widgetFile "standalone/modal")
|
||||
$(widgetFile "standalone/showHide")
|
||||
$(widgetFile "standalone/sortable")
|
||||
$(widgetFile "standalone/inputs")
|
||||
|
||||
@ -40,7 +40,7 @@ getCourseListTermR tidini = do
|
||||
let c = entityVal ckv
|
||||
shd = courseShorthand c
|
||||
tid = courseTermId c
|
||||
in [whamlet| <a href=@{CourseShowR tid shd}>#{shd} |] )
|
||||
in [whamlet| <a href=@{CourseR tid shd CourseShowR}>#{shd} |] )
|
||||
-- , headed "Institut" $ [shamlet| #{course} |]
|
||||
, headed "Beginn Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterFrom.entityVal
|
||||
, headed "Ende Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterTo.entityVal
|
||||
@ -54,17 +54,17 @@ getCourseListTermR tidini = do
|
||||
shd = courseShorthand c
|
||||
tid = courseTermId c
|
||||
in do
|
||||
adminLink <- handlerToWidget $ isAuthorized (CourseEditR tid shd ) False
|
||||
adminLink <- handlerToWidget $ isAuthorized (CourseR tid shd CourseEditR) False
|
||||
-- if (adminLink==Authorized) then linkButton "Ändern" BCWarning (CourseEditR tid shd) else ""
|
||||
[whamlet|
|
||||
$if adminLink == Authorized
|
||||
<a href=@{CourseEditR tid shd}>
|
||||
<a href=@{CourseR tid shd CourseEditR}>
|
||||
editieren
|
||||
|]
|
||||
)
|
||||
]
|
||||
let pageLinks =
|
||||
[ NavbarAside $ MenuItem
|
||||
[ PageActionPrime $ MenuItem
|
||||
{ menuItemLabel = "Neuer Kurs"
|
||||
, menuItemIcon = Just "book"
|
||||
, menuItemRoute = CourseNewR
|
||||
@ -93,11 +93,11 @@ getCourseShowR tid csh = do
|
||||
let course = entityVal courseEnt
|
||||
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerButton $ mbRegistered
|
||||
let pageActions =
|
||||
[ NavbarAside $ MenuItem
|
||||
[ PageActionPrime $ MenuItem
|
||||
{ menuItemLabel = "Übungsblätter"
|
||||
, menuItemIcon = Nothing
|
||||
, menuItemRoute = SheetListR tid csh
|
||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized (SheetListR tid csh) False
|
||||
, menuItemRoute = CSheetR tid csh SheetListR
|
||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized (CSheetR tid csh SheetListR) False
|
||||
}
|
||||
]
|
||||
defaultLinkLayout pageActions $ do
|
||||
@ -145,6 +145,9 @@ getCourseEditR tid csh = do
|
||||
course <- runDB $ getBy $ CourseTermShort tid csh
|
||||
courseEditHandler course
|
||||
|
||||
postCourseEditR :: TermId -> Text -> Handler Html
|
||||
postCourseEditR = getCourseEditR
|
||||
|
||||
getCourseEditIDR :: CryptoUUIDCourse -> Handler Html
|
||||
getCourseEditIDR cID = do
|
||||
cIDKey <- getsYesod appCryptoIDKey
|
||||
@ -174,7 +177,7 @@ courseEditHandler course = do
|
||||
, cfTerm = tid
|
||||
})) -> do -- create new course
|
||||
let tident = unTermKey tid
|
||||
actTime <- liftIO getCurrentTime
|
||||
now <- liftIO getCurrentTime
|
||||
insertOkay <- runDB $ insertUnique $ Course
|
||||
{ courseName = cfName res
|
||||
, courseDescription = cfDesc res
|
||||
@ -186,14 +189,12 @@ courseEditHandler course = do
|
||||
, courseHasRegistration = cfHasReg res
|
||||
, courseRegisterFrom = cfRegFrom res
|
||||
, courseRegisterTo = cfRegTo res
|
||||
, courseCreated = actTime
|
||||
, courseChanged = actTime
|
||||
, courseCreatedBy = aid
|
||||
, courseChangedBy = aid
|
||||
}
|
||||
}
|
||||
case insertOkay of
|
||||
(Just cid) -> do
|
||||
runDB $ insert_ $ Lecturer aid cid
|
||||
runDB $ do
|
||||
insert_ $ CourseEdit aid now cid
|
||||
insert_ $ Lecturer aid cid
|
||||
addMessageI "info" $ MsgCourseNewOk tident csh
|
||||
redirect $ CourseListTermR tid
|
||||
Nothing ->
|
||||
@ -205,7 +206,7 @@ courseEditHandler course = do
|
||||
, cfTerm = tid
|
||||
})) -> do -- edit existing course
|
||||
let tident = unTermKey tid
|
||||
actTime <- liftIO getCurrentTime
|
||||
now <- liftIO getCurrentTime
|
||||
-- addMessage "debug" [shamlet| #{show res}|]
|
||||
runDB $ do
|
||||
old <- get cid
|
||||
@ -228,9 +229,9 @@ courseEditHandler course = do
|
||||
-- , CourseRegisterFrom =. cfRegFrom res
|
||||
-- , CourseRegisterTo =. cfRegTo res
|
||||
-- , CourseChangedBy =. aid
|
||||
-- , CourseChanged =. actTime
|
||||
-- , CourseChanged =. now
|
||||
-- ]
|
||||
updOkay <- replace cid ( -- TODO replaceUnique requires Eq?!
|
||||
_updOkay <- replace cid ( -- TODO replaceUnique requires Eq?!
|
||||
Course { courseName = cfName res
|
||||
, courseDescription = cfDesc res
|
||||
, courseLinkExternal = cfLink res
|
||||
@ -238,15 +239,12 @@ courseEditHandler course = do
|
||||
, courseTermId = cfTerm res
|
||||
, courseSchoolId = cfSchool res
|
||||
, courseCapacity = cfCapacity res
|
||||
, courseChanged = actTime
|
||||
, courseChangedBy = aid
|
||||
, courseCreated = courseCreated oldCourse
|
||||
, courseCreatedBy = courseCreatedBy oldCourse
|
||||
, courseHasRegistration = cfHasReg res
|
||||
, courseRegisterFrom = cfRegFrom res
|
||||
, courseRegisterTo = cfRegTo res
|
||||
}
|
||||
)
|
||||
insert_ $ CourseEdit aid now cid
|
||||
-- if (isNothing updOkay)
|
||||
-- then do
|
||||
addMessageI "info" $ MsgCourseEditOk tident csh
|
||||
@ -256,7 +254,7 @@ courseEditHandler course = do
|
||||
(FormFailure _) -> addMessageI "warning" MsgInvalidInput
|
||||
other -> addMessage "error" $ [shamlet| Error: #{show other}|]
|
||||
let formTitle = "Kurs editieren/anlegen" :: Text
|
||||
let actionUrl = CourseNewR -- CourseEditR -- TODO
|
||||
actionUrl <- fromMaybe CourseNewR <$> getCurrentRoute
|
||||
defaultLayout $ do
|
||||
setTitle [shamlet| #{formTitle} |]
|
||||
$(widgetFile "formPage")
|
||||
|
||||
@ -172,30 +172,30 @@ getSheetList courseEnt = do
|
||||
rated <- count $ (SubmissionRatingTime !=. Nothing):sheetsub
|
||||
return (sid, sheet, (submissions, rated))
|
||||
let colBase = mconcat
|
||||
[ headed "Blatt" $ \(sid,sheet,_) -> linkButton (toWgt $ sheetName sheet) BCLink $ SheetShowR tid csh (sheetName sheet)
|
||||
[ headed "Blatt" $ \(sid,sheet,_) -> linkButton (toWgt $ sheetName sheet) BCLink $ CourseR tid csh $ SheetR $ SheetShowR $ sheetName sheet
|
||||
, headed "Abgabe ab" $ toWgt . formatTimeGerWD . sheetActiveFrom . snd3
|
||||
, headed "Abgabe bis" $ toWgt . formatTimeGerWD . sheetActiveTo . snd3
|
||||
, headed "Bewertung" $ toWgt . show . sheetType . snd3
|
||||
, headed "Korrigiert" $ toWgt . snd . trd3
|
||||
, headed "Eingereicht" $ toWgt . fst . trd3
|
||||
]
|
||||
let colAdmin = mconcat -- only show edit button for allowed course assistants
|
||||
[ headed "" $ \s -> linkButton "Edit" BCLink $ SheetEditR tid csh $ sheetName $ snd3 s
|
||||
, headed "" $ \s -> linkButton "Delete" BCLink $ SheetDelR tid csh $ sheetName $ snd3 s
|
||||
[ headed "Korrigiert" $ toWgt . snd . trd3
|
||||
, headed "Eingereicht" $ toWgt . fst . trd3
|
||||
, headed "" $ \s -> linkButton "Edit" BCLink $ CourseR tid csh $ SheetR $ SheetEditR $ sheetName $ snd3 s
|
||||
, headed "" $ \s -> linkButton "Delete" BCLink $ CourseR tid csh $ SheetR $ SheetDelR $ sheetName $ snd3 s
|
||||
]
|
||||
showAdmin <- case sheets of
|
||||
((_,firstSheet,_):_) -> do
|
||||
setUltDestCurrent
|
||||
(Authorized ==) <$> isAuthorized (SheetEditR tid csh $ sheetName firstSheet) False
|
||||
(Authorized ==) <$> isAuthorized (CourseR tid csh $ SheetR $ SheetEditR $ sheetName firstSheet) False
|
||||
_otherwise -> return False
|
||||
let colSheets = if showAdmin
|
||||
then colBase `mappend` colAdmin
|
||||
else colBase
|
||||
let pageActions =
|
||||
[ NavbarAside $ MenuItem
|
||||
[ PageActionPrime $ MenuItem
|
||||
{ menuItemLabel = "Neues Übungsblatt"
|
||||
, menuItemIcon = Nothing
|
||||
, menuItemRoute = SheetNewR tid csh
|
||||
, menuItemRoute = CSheetR tid csh SheetNewR
|
||||
, menuItemAccessCallback = (== Authorized) <$> isAuthorized CourseNewR False
|
||||
}
|
||||
]
|
||||
@ -221,7 +221,7 @@ getSheetShowR tid csh shn = do
|
||||
E.where_ (sheet E.^. SheetId E.==. E.val sid )
|
||||
-- return desired columns
|
||||
return $ (file E.^. FileTitle, sheetFile E.^. SheetFileType)
|
||||
let fileLinks = map (\(E.Value fName, E.Value fType) -> SheetFileR tid csh shn fType fName) fileNameTypes
|
||||
let fileLinks = map (\(E.Value fName, E.Value fType) -> CSheetR tid csh (SheetFileR shn fType fName)) fileNameTypes
|
||||
|
||||
defaultLayout $ do
|
||||
setTitle $ toHtml $ T.append "Übung " $ sheetName sheet
|
||||
@ -295,8 +295,6 @@ getSheetEditR tid csh shn = do
|
||||
}
|
||||
let action newSheet = do
|
||||
replaceRes <- myReplaceUnique sid $ newSheet
|
||||
{ sheetCreated = sheetCreated
|
||||
, sheetCreatedBy = sheetChangedBy }
|
||||
case replaceRes of
|
||||
Nothing -> return $ Just sid
|
||||
(Just _err) -> return $ Nothing -- More specific error message for edit old sheet could go here
|
||||
@ -314,8 +312,8 @@ handleSheetEdit tid csh msId template dbAction = do
|
||||
case res of
|
||||
(FormSuccess SheetForm{..}) -> do
|
||||
saveOkay <- runDB $ do
|
||||
cid <- getKeyBy404 $ CourseTermShort tid csh
|
||||
actTime <- liftIO getCurrentTime
|
||||
cid <- getKeyBy404 $ CourseTermShort tid csh
|
||||
let newSheet = Sheet
|
||||
{ sheetCourseId = cid
|
||||
, sheetName = sfName
|
||||
@ -328,10 +326,6 @@ handleSheetEdit tid csh msId template dbAction = do
|
||||
, sheetActiveTo = sfActiveTo
|
||||
, sheetHintFrom = sfHintFrom
|
||||
, sheetSolutionFrom = sfSolutionFrom
|
||||
, sheetCreated = actTime -- dbAction adjusts this for replacement, TODO: eigene Tabelle für changedBy
|
||||
, sheetChanged = actTime
|
||||
, sheetCreatedBy = aid -- dbAction adjusts this for replacement
|
||||
, sheetChangedBy = aid
|
||||
}
|
||||
mbsid <- dbAction newSheet
|
||||
case mbsid of
|
||||
@ -340,16 +334,17 @@ handleSheetEdit tid csh msId template dbAction = do
|
||||
whenIsJust sfSheetF $ insertSheetFile' sid SheetExercise
|
||||
whenIsJust sfHintF $ insertSheetFile sid SheetHint
|
||||
whenIsJust sfSolutionF $ insertSheetFile sid SheetSolution
|
||||
insert_ $ SheetEdit aid actTime sid
|
||||
addMessageI "info" $ MsgSheetEditOk tident csh sfName
|
||||
return True
|
||||
when saveOkay $ redirect $ SheetShowR tid csh sfName -- redirect must happen outside of runDB
|
||||
when saveOkay $ redirect $ CSheetR tid csh $ SheetShowR sfName -- redirect must happen outside of runDB
|
||||
(FormFailure msgs) -> forM_ msgs $ (addMessage "warning") . toHtml
|
||||
_ -> return ()
|
||||
let pageTitle = maybe (MsgSheetTitleNew tident csh)
|
||||
(MsgSheetTitle tident csh) mbshn
|
||||
let formTitle = pageTitle
|
||||
let formText = Nothing :: Maybe UniWorXMessage
|
||||
actionUrl <- fromMaybe (SheetNewR tid csh) <$> getCurrentRoute
|
||||
actionUrl <- fromMaybe (CSheetR tid csh SheetNewR) <$> getCurrentRoute
|
||||
defaultLayout $ do
|
||||
setTitleI pageTitle
|
||||
$(widgetFile "formPageI18n")
|
||||
@ -361,19 +356,19 @@ getSheetDelR tid csh shn = do
|
||||
let tident = unTermKey tid
|
||||
((result,formWidget), formEnctype) <- runFormPost (buttonForm :: Form BtnDelete)
|
||||
case result of
|
||||
(FormSuccess BtnAbort) -> redirectUltDest $ SheetShowR tid csh shn
|
||||
(FormSuccess BtnAbort) -> redirectUltDest $ CSheetR tid csh $ SheetShowR shn
|
||||
(FormSuccess BtnDelete) -> do
|
||||
runDB $ fetchSheetId tid csh shn >>= deleteCascade
|
||||
-- TODO: deleteCascade löscht aber nicht die hochgeladenen Dateien!!!
|
||||
setMessageI $ MsgSheetDelOk tident csh shn
|
||||
redirect $ SheetListR tid csh
|
||||
redirect $ CSheetR tid csh SheetListR
|
||||
_other -> do
|
||||
submissionno <- runDB $ do
|
||||
sid <- fetchSheetId tid csh shn
|
||||
count [SubmissionSheetId ==. sid]
|
||||
let formTitle = MsgSheetDelTitle tident csh shn
|
||||
let formText = Just $ MsgSheetDelText submissionno
|
||||
let actionUrl = SheetDelR tid csh shn
|
||||
let actionUrl = CSheetR tid csh $ SheetDelR shn
|
||||
defaultLayout $ do
|
||||
setTitleI $ MsgSheetTitle tident csh shn
|
||||
$(widgetFile "formPageI18n")
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
|
||||
module Handler.Submission where
|
||||
|
||||
@ -53,7 +54,7 @@ submissionTable = do
|
||||
(,,) <$> encrypt submissionId <*> encrypt submissionId <*> pure s
|
||||
|
||||
let
|
||||
anchorCourse (_, _, (_, _, Entity _ Course{..})) = CourseShowR courseTermId courseShorthand
|
||||
anchorCourse (_, _, (_, _, Entity _ Course{..})) = CourseR courseTermId courseShorthand CourseShowR
|
||||
courseText (_, _, (_, _, Entity _ Course{..})) = toWidget courseName
|
||||
anchorSubmission (_, cUUID, _) = SubmissionR cUUID
|
||||
submissionText (cID, _, _) = toWidget . toPathPiece . CI.foldedCase $ ciphertext cID
|
||||
@ -177,8 +178,12 @@ postSubmissionDownloadMultiArchiveR = do
|
||||
|
||||
withinDirectory f@File{..} = f { fileTitle = directoryName </> fileTitle }
|
||||
|
||||
lastEditMb <- lift $ selectList [SubmissionEditSubmission ==. submissionID] [Desc SubmissionEditTime, LimitTo 1]
|
||||
lastEditTime <- case lastEditMb of
|
||||
[(submissionEditTime.entityVal -> time)] -> return time
|
||||
_other -> liftIO getCurrentTime
|
||||
yield $ File
|
||||
{ fileModified = submissionChanged
|
||||
{ fileModified = lastEditTime
|
||||
, fileTitle = directoryName
|
||||
, fileContent = Nothing
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ getTermShowR = do
|
||||
, dbtIdent = "terms" :: Text
|
||||
}
|
||||
let pageActions =
|
||||
[ NavbarAside $ MenuItem
|
||||
[ PageActionPrime $ MenuItem
|
||||
{ menuItemLabel = "Neues Semester"
|
||||
, menuItemIcon = Nothing
|
||||
, menuItemRoute = TermEditR
|
||||
|
||||
@ -70,12 +70,9 @@ sinkSubmission sheetId userId mExists = do
|
||||
submissionRatingComment = Nothing
|
||||
submissionRatingBy = Nothing
|
||||
submissionRatingTime = Nothing
|
||||
submissionCreated = now
|
||||
submissionChanged = now
|
||||
submissionCreatedBy = userId
|
||||
submissionChangedBy = userId
|
||||
|
||||
(sId, isUpdate) <- lift $ maybe ((, False) <$> insert Submission{..}) return mExists
|
||||
(sId, isUpdate) <- lift $ maybe ((, False) <$> (insert Submission{..} >>= (\sid -> sid <$ insert (SubmissionEdit userId now sid)))) return mExists
|
||||
|
||||
|
||||
sId <$ sinkSubmission' sId isUpdate
|
||||
where
|
||||
@ -184,9 +181,9 @@ sinkSubmission sheetId userId mExists = do
|
||||
alreadyTouched <- gets $ getAny . sinkSubmissionTouched
|
||||
when (not alreadyTouched) $ do
|
||||
now <- liftIO getCurrentTime
|
||||
lift . update submissionId $ case isUpdate of
|
||||
False -> [ SubmissionChangedBy =. userId, SubmissionChanged =. now ]
|
||||
True -> [ SubmissionRatingBy =. Just userId, SubmissionRatingTime =. Just now ]
|
||||
lift $ case isUpdate of
|
||||
False -> insert_ $ SubmissionEdit userId now submissionId
|
||||
True -> update submissionId [ SubmissionRatingBy =. Just userId, SubmissionRatingTime =. Just now ]
|
||||
tell $ mempty{ sinkSubmissionTouched = Any True }
|
||||
|
||||
finalize :: SubmissionSinkState -> YesodDB UniWorX ()
|
||||
|
||||
@ -6,3 +6,17 @@ import Import.NoFoundation
|
||||
|
||||
lipsum :: WidgetT site IO ()
|
||||
lipsum = $(widgetFile "widgets/lipsum")
|
||||
|
||||
modal :: [Char] -> Maybe [Char] -> WidgetT site IO ()
|
||||
modal modalTrigger (Just modalContent) = do
|
||||
let
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
$(widgetFile "widgets/modal")
|
||||
modal modalTrigger Nothing = do
|
||||
let
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
modalContent :: [Char]
|
||||
modalContent = "placeholder"
|
||||
$(widgetFile "widgets/modal")
|
||||
|
||||
@ -55,7 +55,7 @@ deriveJSON defaultOptions ''SheetType
|
||||
derivePersistFieldJSON "SheetType"
|
||||
|
||||
data SheetGroup
|
||||
= Arbitrary { maxParticipants :: Int }
|
||||
= Arbitrary { maxParticipants :: Int } -- Distinguish Limited/Arbitrary
|
||||
| RegisteredGroups
|
||||
| NoGroups
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
\ bis #{formatTimeGerWD regTo}
|
||||
|
||||
<div>
|
||||
<form method=post action=@{CourseShowR tid csh} enctype=#{regEnctype}>
|
||||
<form method=post action=@{CourseR tid csh CourseShowR} enctype=#{regEnctype}>
|
||||
^{regWidget}
|
||||
|
||||
<div .course-header__title>
|
||||
|
||||
@ -38,7 +38,12 @@ $newline never
|
||||
\ });
|
||||
}
|
||||
|
||||
<body>
|
||||
|
||||
<body .no-js>
|
||||
<!-- removes no-js class from body if client supports javascript -->
|
||||
<script>
|
||||
document.body.classList.remove('no-js');
|
||||
|
||||
^{pageBody pc}
|
||||
|
||||
$maybe analytics <- appAnalytics $ appSettings master
|
||||
|
||||
@ -13,5 +13,8 @@
|
||||
$with status2 <- bool status "info" (status == "")
|
||||
<div class="alert alert-#{status2}">#{msg}
|
||||
|
||||
<!-- prime page actions -->
|
||||
^{pageactionprime}
|
||||
|
||||
<!-- actual content -->
|
||||
^{widget}
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
--lighterbase: #5F98C2;
|
||||
--whitebase: #FCFFFA;
|
||||
--greybase: #B1B5C0;
|
||||
--lightgreybase: #D9DEDB;
|
||||
--blackbase: #1A2A36;
|
||||
--fontbase: #34303a;
|
||||
--fontsec: #5b5861;
|
||||
@ -158,7 +159,7 @@ th {
|
||||
input[type="submit"],
|
||||
input[type="button"],
|
||||
button,
|
||||
.btn {
|
||||
.btn, a.btn {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
box-shadow: 0;
|
||||
@ -174,12 +175,14 @@ button,
|
||||
}
|
||||
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)
|
||||
}
|
||||
@ -187,6 +190,7 @@ button.btn-info,
|
||||
input[type="submit"][disabled],
|
||||
input[type="button"][disabled],
|
||||
button[disabled],
|
||||
a.btn[disabled],
|
||||
.btn[disabled] {
|
||||
opacity: 0.3;
|
||||
background-color: var(--greybase);
|
||||
@ -196,14 +200,17 @@ button[disabled],
|
||||
input[type="submit"]:not([disabled]):hover,
|
||||
input[type="button"]:not([disabled]):hover,
|
||||
button:not([disabled]):hover,
|
||||
a.btn:not([disabled]):hover,
|
||||
.btn:not([disabled]):hover {
|
||||
background-color: var(--lighterbase);
|
||||
text-decoration: underline;
|
||||
color: white;
|
||||
}
|
||||
|
||||
input[type="submit"].btn-info:hover,
|
||||
input[type="button"].btn-info:hover,
|
||||
button.btn-info:hover,
|
||||
a.btn.btn-info:hover,
|
||||
.btn.btn-info:hover {
|
||||
background-color: var(--greybase)
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>0
|
||||
<td>14
|
||||
<td>NT2
|
||||
<td>CON2
|
||||
<td>3
|
||||
<tr>
|
||||
@ -57,6 +57,11 @@
|
||||
<td>43
|
||||
<td>T2C2
|
||||
<td>35
|
||||
<tr>
|
||||
<td>4
|
||||
<td>73
|
||||
<td>CA62
|
||||
<td>7
|
||||
|
||||
<hr>
|
||||
<div .container>
|
||||
@ -67,3 +72,29 @@
|
||||
Knopf-Test:
|
||||
<form .form-inline method=post action=@{HomeR} enctype=#{btnEnctype}>
|
||||
^{btnWdgt}
|
||||
<li><br>
|
||||
Modals:
|
||||
^{modal ".toggler1" Nothing}
|
||||
<a href="/" .btn.toggler1>Klick mich für Ajax-Test
|
||||
<noscript>(Für Modals bitte JS aktivieren)</noscript>
|
||||
^{modal ".toggler2" (Just "Test wegen Modal")}
|
||||
<div .btn.toggler2>Klick mich für Content-Test
|
||||
<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">
|
||||
|
||||
@ -29,7 +29,7 @@
|
||||
$forall fileLink <- fileLinks
|
||||
<li>
|
||||
$case fileLink
|
||||
$of SheetFileR _ _ _ typ name
|
||||
$of CourseR _ _ (SheetR (SheetFileR _ typ name))
|
||||
#{toPathPiece typ}
|
||||
<a href=@{fileLink}>#{name}
|
||||
$of other
|
||||
|
||||
@ -24,10 +24,12 @@
|
||||
});
|
||||
};
|
||||
|
||||
// allows for multiple file uploads with separate inputs
|
||||
window.utils.reactiveFileUpload = function(input, parent) {
|
||||
var currValidInputCount = 0;
|
||||
var addMore = false;
|
||||
var inputName = input.getAttribute('name');
|
||||
var isMulti = input.getAttribute('multiple') ? true : false;
|
||||
// FileInput PseudoClass
|
||||
function FileInput(container, input, label, remover) {
|
||||
this.container = container;
|
||||
@ -42,7 +44,7 @@
|
||||
this.remove = function() {
|
||||
this.container.remove();
|
||||
}
|
||||
this.isValid = function() {
|
||||
this.wasValid = function() {
|
||||
return this.container.classList.contains('file-input__container--valid');
|
||||
}
|
||||
}
|
||||
@ -61,7 +63,9 @@
|
||||
parent.classList.add('form-group--valid')
|
||||
}
|
||||
submitBtn.removeAttribute('disabled');
|
||||
addNextInput();
|
||||
if (isMulti) {
|
||||
addNextInput();
|
||||
}
|
||||
} else {
|
||||
if (parent.classList.contains('form-group')) {
|
||||
parent.classList.remove('form-group--valid')
|
||||
@ -78,14 +82,16 @@
|
||||
var fileName = filePath[filePath.length - 1];
|
||||
fileInput.label.innerHTML = fileName;
|
||||
// increase count if this field was empty previously
|
||||
if (!fileInput.isValid()) {
|
||||
if (!fileInput.wasValid()) {
|
||||
currValidInputCount++;
|
||||
}
|
||||
fileInput.container.classList.add('file-input__container--valid')
|
||||
// show next input
|
||||
} else {
|
||||
currValidInputCount--;
|
||||
fileInput.remove();
|
||||
if (isMulti) {
|
||||
currValidInputCount--;
|
||||
}
|
||||
clearInput(fileInput);
|
||||
}
|
||||
updateForm();
|
||||
});
|
||||
@ -95,26 +101,36 @@
|
||||
fileInput.input.addEventListener('blur', function() {
|
||||
fileInput.container.classList.remove('pseudo-focus');
|
||||
});
|
||||
fileInput.label.addEventListener('click', function() {
|
||||
fileInput.input.click();
|
||||
});
|
||||
fileInput.remover.addEventListener('click', function() {
|
||||
if (fileInput.isValid()) {
|
||||
if (fileInput.wasValid()) {
|
||||
currValidInputCount--;
|
||||
}
|
||||
fileInput.remove();
|
||||
updateForm();
|
||||
clearInput(fileInput);
|
||||
});
|
||||
}
|
||||
|
||||
// clears or removes fileinput based on multi-file or not
|
||||
function clearInput(fileInput) {
|
||||
if (isMulti) {
|
||||
fileInput.remove();
|
||||
} else {
|
||||
fileInput.container.classList.remove('file-input__container--valid')
|
||||
fileInput.label.innerHTML = '';
|
||||
}
|
||||
updateForm();
|
||||
}
|
||||
// create new wrapped input element with name name
|
||||
function makeInput(name) {
|
||||
var cont = document.createElement('div');
|
||||
var desc = document.createElement('span');
|
||||
var desc = document.createElement('label');
|
||||
var nextInput = document.createElement('input');
|
||||
var remover = document.createElement('div');
|
||||
cont.classList.add('file-input__container');
|
||||
desc.classList.add('file-input__label', 'btn');
|
||||
nextInput.classList.add('js-file-input');
|
||||
desc.setAttribute('for', name + '-' + currValidInputCount);
|
||||
remover.classList.add('file-input__remover');
|
||||
nextInput.setAttribute('id', name + '-' + currValidInputCount);
|
||||
nextInput.setAttribute('name', name);
|
||||
nextInput.setAttribute('type', 'file');
|
||||
cont.appendChild(nextInput);
|
||||
@ -132,6 +148,39 @@
|
||||
setup();
|
||||
}
|
||||
|
||||
// to remove previously uploaded files
|
||||
window.utils.reactiveFileCheckbox = function(input, label, parent) {
|
||||
// adds eventlistener(s)
|
||||
function addListener(container) {
|
||||
container.addEventListener('click', function() {
|
||||
input.click();
|
||||
});
|
||||
input.addEventListener('change', function(event) {
|
||||
container.classList.toggle('file-checkbox__container--valid', this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// initial setup
|
||||
function setup() {
|
||||
var cont = input.parentNode;
|
||||
while (cont !== document.body) {
|
||||
if (cont.classList.contains('file-checkbox__container')) {
|
||||
break;
|
||||
}
|
||||
cont = cont.parentNode;
|
||||
}
|
||||
|
||||
// take care of properly moving elements
|
||||
if (input.parentNode.classList.contains('checkbox')) {
|
||||
input.parentNode.classList.add('file-checkbox__checkbox');
|
||||
} else {
|
||||
input.classList.add('file-checkbox__checkbox');
|
||||
}
|
||||
addListener(cont);
|
||||
}
|
||||
setup();
|
||||
}
|
||||
|
||||
window.utils.reactiveFormGroup = function(formGroup, input) {
|
||||
// updates to dom
|
||||
if (input.value.length > 0) {
|
||||
@ -156,18 +205,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// setup reactive labels
|
||||
Array.from(document.querySelectorAll('.reactive-label')).forEach(function(label) {
|
||||
var input = document.querySelector('#' + label.getAttribute('for'));
|
||||
if (!input) {
|
||||
console.error('No input found for ReactiveLabel! Targeted input: \'#%s\'', label.getAttribute('for'));
|
||||
return false;
|
||||
}
|
||||
|
||||
var parent = label.parentElement;
|
||||
var type = input.getAttribute('type');
|
||||
var isFileInput = /file/i.test(type);
|
||||
var isFileUpload = /file/i.test(type);
|
||||
var isFileCheckbox = input.classList.contains('file-checkbox');
|
||||
var isListening = !RegExp(['date', 'checkbox', 'radio', 'hidden', 'file'].join('|')).test(type);
|
||||
var isInFormGroup = parent.classList.contains('form-group') && parent.classList.contains('form-group--required');
|
||||
|
||||
|
||||
if (isInFormGroup) {
|
||||
window.utils.reactiveFormGroup(parent, input);
|
||||
}
|
||||
if (isFileInput) {
|
||||
if (isFileUpload) {
|
||||
window.utils.reactiveFileUpload(input, parent);
|
||||
}
|
||||
if (isFileCheckbox) {
|
||||
window.utils.reactiveFileCheckbox(input, label, parent);
|
||||
}
|
||||
if (isListening) {
|
||||
window.utils.reactiveInputLabel(input, label);
|
||||
} else {
|
||||
|
||||
@ -3,6 +3,53 @@ form {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* FORM GROUPS */
|
||||
.form-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 25% max-content;
|
||||
grid-auto-columns: 25%;
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
padding-left: 10px;
|
||||
border-left: 8px solid transparent;
|
||||
}
|
||||
|
||||
.form-group--required {
|
||||
border-left: 8px solid var(--lighterbase);
|
||||
}
|
||||
|
||||
.form-group--valid {
|
||||
border-left: 8px solid var(--validbase);
|
||||
}
|
||||
|
||||
.form-group--has-error {
|
||||
border-left: 8px solid var(--errorbase) !important;
|
||||
}
|
||||
|
||||
.form-group__label {
|
||||
width: 25%;
|
||||
white-space: nowrap;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 999px) {
|
||||
.form-group {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: 30px;
|
||||
align-items: baseline;
|
||||
margin-top: 17px;
|
||||
flex-direction: column;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TEXT INPUTS */
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
@ -18,7 +65,7 @@ input[type="email"] {
|
||||
color: var(--fontbase);
|
||||
transition: all .1s;
|
||||
font-size: 16px;
|
||||
min-width: 300px;
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
@ -37,7 +84,7 @@ textarea {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
padding: 7px 4px;
|
||||
min-width: 300px;
|
||||
min-width: 400px;
|
||||
min-height: 100px;
|
||||
font-family: var(--fontfamilybase);
|
||||
font-size: 16px;
|
||||
@ -51,30 +98,6 @@ textarea:focus {
|
||||
background-color: transparent;
|
||||
border-bottom-color: var(--lightbase);
|
||||
}
|
||||
/* FORM GROUPS */
|
||||
.form-group {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(150px, max-content));
|
||||
grid-auto-columns: minmax(150px, max-content);
|
||||
grid-gap: 5px;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
padding-left: 10px;
|
||||
border-left: 8px solid transparent;
|
||||
}
|
||||
|
||||
.form-group--required {
|
||||
border-left: 8px solid var(--lighterbase);
|
||||
}
|
||||
|
||||
.form-group--valid {
|
||||
border-left: 8px solid var(--validbase);
|
||||
}
|
||||
|
||||
.form-group--has-error {
|
||||
border-left: 8px solid var(--errorbase) !important;
|
||||
}
|
||||
|
||||
/* CUSTOM LEGACY CHECKBOX AND RADIO BOXES */
|
||||
input[type="checkbox"] {
|
||||
@ -115,7 +138,6 @@ input[type="checkbox"]:checked::after {
|
||||
.checkbox,
|
||||
.radio {
|
||||
position: relative;
|
||||
margin: 3px;
|
||||
|
||||
> [type="checkbox"],
|
||||
> [type="radio"] {
|
||||
@ -124,8 +146,9 @@ input[type="checkbox"]:checked::after {
|
||||
|
||||
> label {
|
||||
display: block;
|
||||
padding: 7px 13px 7px 30px;
|
||||
background-color: var(--darkbase);
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
background-color: var(--greybase);
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
@ -135,8 +158,8 @@ input[type="checkbox"]:checked::after {
|
||||
> label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 4px;
|
||||
top: 14px;
|
||||
left: 5px;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@ -147,13 +170,13 @@ input[type="checkbox"]:checked::after {
|
||||
> label::before {
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
transform: scale(0.8, 0.1);
|
||||
transform: scale(0.1, 0.1);
|
||||
}
|
||||
|
||||
> label::after {
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
transform: scale(0.8, 0.1);
|
||||
transform: scale(0.1, 0.1);
|
||||
}
|
||||
|
||||
> :checked + label {
|
||||
@ -161,12 +184,27 @@ input[type="checkbox"]:checked::after {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:hover > label::before,
|
||||
&:hover > label {
|
||||
background-color: var(--lighterbase);
|
||||
}
|
||||
|
||||
&:hover > label::before {
|
||||
transform: scale(0.8, 0.4);
|
||||
}
|
||||
|
||||
> :checked + label::before {
|
||||
transform: scale(1, 1) rotate(45deg);
|
||||
}
|
||||
|
||||
&:hover > label::after,
|
||||
> :checked + label:hover::after,
|
||||
> :checked + label:hover::before {
|
||||
transform: scale(1, 1) rotate(0deg);
|
||||
}
|
||||
|
||||
&:hover > label::after {
|
||||
transform: scale(0.8, 0.4);
|
||||
}
|
||||
|
||||
> :checked + label::after {
|
||||
transform: scale(1, 1) rotate(-45deg);
|
||||
}
|
||||
@ -202,12 +240,6 @@ input[type="checkbox"]:checked::after {
|
||||
color: var(--fontbase);
|
||||
}
|
||||
@media (max-width: 999px) {
|
||||
.form-group {
|
||||
grid-template-rows: 30px;
|
||||
grid-template-columns: 1fr;
|
||||
align-items: baseline;
|
||||
margin-top: 17px;
|
||||
}
|
||||
.reactive-label {
|
||||
position: relative;
|
||||
transform: translate(2px, 30px);
|
||||
@ -220,7 +252,7 @@ input[type="checkbox"]:checked::after {
|
||||
}
|
||||
|
||||
/* CUSTOM FILE INPUT */
|
||||
input[type="file"] {
|
||||
input[type="file"].js-file-input {
|
||||
color: white;
|
||||
width: 0.1px;
|
||||
height: 0.1px;
|
||||
@ -231,25 +263,34 @@ input[type="file"] {
|
||||
outline: 0;
|
||||
border: 0;
|
||||
}
|
||||
.file-input__container {
|
||||
.file-input__container,
|
||||
.file-checkbox__container {
|
||||
grid-column-start: 2;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.file-input__label,
|
||||
.file-input__remover {
|
||||
.file-input__remover,
|
||||
.file-checkbox__label,
|
||||
.file-checkbox__remover {
|
||||
display: block;
|
||||
border-radius: 2px;
|
||||
padding: 5px 13px;
|
||||
color: var(--whitebase);
|
||||
cursor: pointer;
|
||||
}
|
||||
.file-input__label {
|
||||
.file-input__label,
|
||||
.file-checkbox__label {
|
||||
text-align: left;
|
||||
position: relative;
|
||||
height: 30px;
|
||||
}
|
||||
.file-input__label.btn {
|
||||
.file-checkbox__label {
|
||||
background-color: var(--greybase);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.file-input__label.btn,
|
||||
.file-checkbox__label.btn {
|
||||
padding: 5px 13px;
|
||||
}
|
||||
.file-input__label::after,
|
||||
@ -271,6 +312,9 @@ input[type="file"] {
|
||||
.file-input__label::before {
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.file-checkbox__checkbox {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.file-input__remover {
|
||||
display: none;
|
||||
width: 40px;
|
||||
@ -292,6 +336,15 @@ input[type="file"] {
|
||||
.file-input__container--valid > .file-input__label {
|
||||
background-color: var(--lightbase);
|
||||
}
|
||||
.file-checkbox__container--valid > .file-checkbox__label {
|
||||
text-decoration: none;
|
||||
background-color: var(--lighterbase);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--greybase);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
.file-input__container--valid > .file-input__label::before,
|
||||
.file-input__container--valid > .file-input__label::after {
|
||||
content: none;
|
||||
@ -300,7 +353,8 @@ input[type="file"] {
|
||||
display: block;
|
||||
}
|
||||
@media (max-width: 999px) {
|
||||
.file-input__container {
|
||||
.file-input__container,
|
||||
.file-checkbox__container {
|
||||
grid-column-start: 1;
|
||||
}
|
||||
}
|
||||
|
||||
1
templates/standalone/modal.hamlet
Normal file
1
templates/standalone/modal.hamlet
Normal file
@ -0,0 +1 @@
|
||||
<!-- only here to be able to include modal using `toWidget` -->
|
||||
100
templates/standalone/modal.julius
Normal file
100
templates/standalone/modal.julius
Normal file
@ -0,0 +1,100 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.modal = function(modal) {
|
||||
var overlay = document.createElement('div');
|
||||
var closer = document.createElement('div');
|
||||
var trigger = document.querySelector(modal.dataset.trigger);
|
||||
var origParent = modal.parentNode;
|
||||
|
||||
function open(event) {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
modal.classList.add('modal--open');
|
||||
overlay.classList.add('modal__overlay');
|
||||
document.body.insertBefore(modal, null);
|
||||
document.body.insertBefore(overlay, modal);
|
||||
overlay.classList.add('modal__overlay--open');
|
||||
toggleScroll(false);
|
||||
|
||||
if (modal.dataset.closeable === 'true') {
|
||||
closer.classList.add('modal__closer');
|
||||
modal.insertBefore(closer, null);
|
||||
closer.addEventListener('click', close, false);
|
||||
overlay.addEventListener('click', close, false);
|
||||
}
|
||||
}
|
||||
|
||||
// open this modal with an event:
|
||||
// document.dispatchEvent(new CustomEvent('modal-open', { dateils: {for: 'modal-13'}}))
|
||||
function openOnEvent(event) {
|
||||
if (event.detail.for === modal.getAttribute('id')) {
|
||||
open();
|
||||
}
|
||||
}
|
||||
|
||||
function close(event) {
|
||||
if (typeof event === 'undefined' || event.target === closer || event.target === overlay) {
|
||||
overlay.remove();
|
||||
origParent.insertBefore(modal, null);
|
||||
modal.classList.remove('modal--open');
|
||||
toggleScroll(true);
|
||||
closer.removeEventListener('click', close, false);
|
||||
}
|
||||
};
|
||||
|
||||
function setup() {
|
||||
// every modal can be openend via document-wide event, see openOnEvent
|
||||
document.addEventListener('modal-open', openOnEvent, false);
|
||||
// if modal has trigger assigned to it open modal on click
|
||||
if (trigger) {
|
||||
trigger.classList.add('modal__trigger');
|
||||
trigger.addEventListener('click', open, false);
|
||||
}
|
||||
// if there is no content specified for the modal we assume that
|
||||
// the content is supposed to be the page the trigger links to.
|
||||
// so we check if the trigger has a href-attribute, fetch that page
|
||||
// and replace the modal content with the response
|
||||
var replaceMe = modal.querySelector('.replace-me');
|
||||
var replaceWith = trigger ? trigger.getAttribute('href') : '';
|
||||
if (replaceMe) {
|
||||
replaceMe.classList.remove('replace-me');
|
||||
replaceMe.innerText = '...loading';
|
||||
if (replaceWith.length > 0) {
|
||||
fetch(replaceWith).then(function(response) {
|
||||
return response.text();
|
||||
}).then(function(body) {
|
||||
var modalContent = document.createElement('div');
|
||||
modalContent.innerHTML = body;
|
||||
var main = modalContent.querySelector('.main__content');
|
||||
if (main) {
|
||||
replaceMe.innerText = '';
|
||||
replaceMe.insertBefore(main, null);
|
||||
} else {
|
||||
replaceMe.innerHTML = body;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// tell further modals, that this one already got initialized
|
||||
modal.classList.add('js-modal-initialized');
|
||||
}
|
||||
setup();
|
||||
};
|
||||
|
||||
// make sure document doesn't scroll when modal is active
|
||||
function toggleScroll(scrollable) {
|
||||
document.body.classList.toggle('no-scroll', !scrollable);
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
Array.from(document.querySelectorAll('.js-modal:not(.js-modal-initialized)')).map(function(modal) {
|
||||
new utils.modal(modal);
|
||||
});
|
||||
|
||||
}, false);
|
||||
87
templates/standalone/modal.lucius
Normal file
87
templates/standalone/modal.lucius
Normal file
@ -0,0 +1,87 @@
|
||||
.modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.8, 0.8);
|
||||
display: block;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
min-width: 60vw;
|
||||
min-height: 100px;
|
||||
max-height: calc(100vh - 30px);
|
||||
border-radius: 7px;
|
||||
z-index: -1;
|
||||
color: var(--fontbase);
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
opacity: 0;
|
||||
transition: all .15s ease;
|
||||
|
||||
&.modal--open {
|
||||
opacity: 1;
|
||||
z-index: 200;
|
||||
transform: translate(-50%, -50%) scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 999px) {
|
||||
.modal {
|
||||
min-width: 80vw;
|
||||
}
|
||||
}
|
||||
@media (max-width: 666px) {
|
||||
.modal {
|
||||
min-width: 90vw;
|
||||
}
|
||||
}
|
||||
@media (max-width: 444px) {
|
||||
.modal {
|
||||
min-width: calc(100vw - 20px);
|
||||
}
|
||||
}
|
||||
|
||||
.modal__overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: transparent;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: all .2s ease;
|
||||
|
||||
&.modal__overlay--open {
|
||||
z-index: 199;
|
||||
opacity: 1;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.modal__trigger {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal__closer {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: var(--darkbase);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
z-index: 20;
|
||||
|
||||
&::before {
|
||||
content: '\e014';
|
||||
font-family: 'Glyphicons Halflings';
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.no-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -1,88 +1,107 @@
|
||||
/**
|
||||
* delcare a table as sortable by adding class 'js-sortable'
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.sortable = function(table) {
|
||||
var ASC = 1;
|
||||
var DESC = -1;
|
||||
|
||||
var trs, ths, sortBy, sortDir, trContents;
|
||||
|
||||
function setup() {
|
||||
trs = table.querySelectorAll('tr');
|
||||
ths = table.querySelectorAll('th');
|
||||
sortBy = 0;
|
||||
sortDir = ASC;
|
||||
trContents = [];
|
||||
|
||||
Array.from(trs).forEach(function(tr, rowIndex) {
|
||||
if (rowIndex === 0) {
|
||||
// register table headers as sort-listener
|
||||
Array.from(tr.querySelectorAll('th')).forEach(function(th, thIndex) {
|
||||
th.addEventListener('click', function(el) {
|
||||
sortTableBy(thIndex);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// register table rows
|
||||
trContents.push(Array.from(tr.querySelectorAll('td')).map(function(td) {
|
||||
return td.innerHTML;
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
setup();
|
||||
|
||||
function updateThs(thIndex, sortOrder) {
|
||||
Array.from(ths).forEach(function (th) {
|
||||
th.classList.remove('sorted-asc', 'sorted-desc');
|
||||
});
|
||||
var suffix = sortOrder > 0 ? 'asc' : 'desc';
|
||||
ths[thIndex].classList.add('sorted-' + suffix);
|
||||
}
|
||||
|
||||
function sortTableBy(thIndex) {
|
||||
var sortKey = thIndex;
|
||||
var sortOrder = ASC;
|
||||
if (sortBy === sortKey) {
|
||||
sortOrder = sortDir === ASC ? DESC : ASC;
|
||||
}
|
||||
|
||||
trContents.sort(dynamicSortByType(sortKey, sortOrder));
|
||||
trContents.sort(dynamicSortByKey(sortKey, sortOrder));
|
||||
sortBy = thIndex;
|
||||
sortDir = sortOrder;
|
||||
updateThs(thIndex, sortOrder);
|
||||
|
||||
Array.from(trs).forEach(function(tr, trIndex) {
|
||||
if (trIndex > 0) {
|
||||
Array.from(tr.querySelectorAll('td')).forEach(function (td, tdIndex) {
|
||||
td.innerHTML = trContents[trIndex - 1][tdIndex];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dynamicSortByKey(key, order) {
|
||||
return function (a,b) {
|
||||
var aVal = parseInt(a[key]);
|
||||
var bVal = parseInt(b[key]);
|
||||
if ((isNaN(aVal) && !isNaN(bVal)) || (!isNaN(aVal) && isNaN(bVal))) {
|
||||
return 1;
|
||||
}
|
||||
aVal = isNaN(aVal) ? a[key] : aVal;
|
||||
bVal = isNaN(bVal) ? b[key] : bVal;
|
||||
var result = (aVal < bVal) ? -1 : (aVal > bVal) ? 1 : 0;
|
||||
return result * order;
|
||||
}
|
||||
}
|
||||
|
||||
function dynamicSortByType(key, order) {
|
||||
return function (a,b) {
|
||||
var aVal = parseInt(a[key]);
|
||||
var bVal = parseInt(b[key]);
|
||||
aVal = isNaN(aVal) ? a[key] : aVal;
|
||||
bVal = isNaN(bVal) ? b[key] : bVal;
|
||||
var res = (aVal < bVal ? -1 : aVal > bVal ? 1 : 0);
|
||||
if (isNaN(aVal) && !isNaN(bVal)) {
|
||||
res = -1;
|
||||
}
|
||||
if (!isNaN(aVal) && isNaN(bVal)) {
|
||||
res = 1;
|
||||
}
|
||||
return res * order;
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
var tables = [];
|
||||
var ASC = 1;
|
||||
var DESC = -1;
|
||||
|
||||
function initTable(table, tableIndex) {
|
||||
var trs = table.querySelectorAll('tr');
|
||||
var ths = table.querySelectorAll('th');
|
||||
var trContents = [];
|
||||
|
||||
Array.from(trs).forEach(function(tr, rowIndex) {
|
||||
if (rowIndex === 0) {
|
||||
// register table headers as sort-listener
|
||||
Array.from(tr.querySelectorAll('th')).forEach(function(th, thIndex) {
|
||||
th.addEventListener('click', function(el) {
|
||||
sortTableBy(tableIndex, thIndex);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// register table rows
|
||||
trContents.push(Array.from(tr.querySelectorAll('td')).map(function(td) {
|
||||
return td.innerHTML;
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
tables.push({
|
||||
el: table,
|
||||
ths: ths,
|
||||
sortBy: 0,
|
||||
sortDir: ASC,
|
||||
trContents,
|
||||
});
|
||||
}
|
||||
|
||||
function updateThs(tableIndex, thIndex, sortOrder) {
|
||||
Array.from(tables[tableIndex].ths).forEach(function (th) {
|
||||
th.classList.remove('sorted-asc', 'sorted-desc');
|
||||
});
|
||||
var suffix = sortOrder > 0 ? 'asc' : 'desc';
|
||||
tables[tableIndex].ths[thIndex].classList.add('sorted-' + suffix);
|
||||
}
|
||||
|
||||
function sortTableBy(tableIndex, thIndex) {
|
||||
var table = tables[tableIndex];
|
||||
var sortKey = thIndex;
|
||||
var sortOrder = ASC;
|
||||
if (table.sortBy === sortKey) {
|
||||
sortOrder = table.sortDir === ASC ? DESC : ASC;
|
||||
}
|
||||
|
||||
table.trContents.sort(dynamicSort(sortKey, sortOrder));
|
||||
tables[tableIndex].sortBy = thIndex;
|
||||
tables[tableIndex].sortDir = sortOrder;
|
||||
updateThs(tableIndex, thIndex, sortOrder);
|
||||
|
||||
Array.from(table.el.querySelectorAll('tr')).forEach(function(tr, trIndex) {
|
||||
if (trIndex > 0) {
|
||||
Array.from(tr.querySelectorAll('td')).forEach(function (td, tdIndex) {
|
||||
td.innerHTML = table.trContents[trIndex - 1][tdIndex];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function dynamicSort(key, order) {
|
||||
return function (a,b) {
|
||||
var aVal = parseInt(a[key]);
|
||||
var bVal = parseInt(b[key]);
|
||||
if ((isNaN(aVal) && !isNaN(bVal)) || (!isNaN(aVal) && isNaN(bVal))) {
|
||||
console.error('trying to sort table by row with mixed content: "%s", "%s"', a[key], b[key]);
|
||||
}
|
||||
aVal = isNaN(aVal) ? a[key] : aVal;
|
||||
bVal = isNaN(bVal) ? b[key] : bVal;
|
||||
var result = (aVal < bVal) ? -1 : (aVal > bVal) ? 1 : 0;
|
||||
return result * order;
|
||||
}
|
||||
}
|
||||
|
||||
var rawTables = document.querySelectorAll('.js-sortable');
|
||||
Array.from(rawTables).forEach(function(table, i) {
|
||||
initTable(table, i);
|
||||
Array.from(document.querySelectorAll('.js-sortable')).forEach(function(table) {
|
||||
utils.sortable(table);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
$newline never
|
||||
<aside .main__aside>
|
||||
<div .asidenav>
|
||||
<div .asidenav__box--dont-hide>
|
||||
@ -20,13 +21,28 @@
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ixd/show">
|
||||
<div .asidenav__link-triple>IXD
|
||||
<div .asidenav__link-label>Interaction Design
|
||||
<ul .asidenav__nested-list>
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ixd/ex">Übungsblätter
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ixd/show">Klausuren
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ixd/show">Übungsgruppen
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ffp/show">
|
||||
<div .asidenav__link-triple>FFP
|
||||
<div .asidenav__link-label>Fortgeschrittene Funktionale Programmierung
|
||||
<ul .asidenav__nested-list>
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ffp/ex">Abgaben
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/ffp/show">Klausuren
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/dbs/show">
|
||||
<div .asidenav__link-triple>DBS
|
||||
<div .asidenav__link-label>Datenbanksysteme
|
||||
<ul .asidenav__nested-list>
|
||||
<li .asidenav__list-item>
|
||||
<a .asidenav__link-wrapper href="/course/S2018/dbs/ex">Übungsgruppen
|
||||
|
||||
<div .asidenav__toggler>
|
||||
|
||||
@ -3,9 +3,18 @@
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.aside = function(asideEl, topNav) {
|
||||
// Defines a function to turn an element into an interactive aside-navigation.
|
||||
// If the small is smaller than 999px the navigation is automatically
|
||||
// collapsed - even when dynamically resized (e.g. switching from portatit
|
||||
// to landscape).
|
||||
// The can user may also manually collapse and expand the navigation by
|
||||
// using the little arrow at the bottom.
|
||||
window.utils.aside = function(asideEl) {
|
||||
var collapsed = false;
|
||||
var collClass = 'main__aside--collapsed';
|
||||
// animClass used to enable transitions only when needed so that
|
||||
// (potentially happening) initial collapse of the asidenav
|
||||
// goes unnoticed by the user.
|
||||
var animClass = 'main__aside--transitioning';
|
||||
var aboveCollapsedNav = false;
|
||||
|
||||
@ -15,24 +24,14 @@
|
||||
if (document.body.getBoundingClientRect().width < 999 || collLS) {
|
||||
asideEl.classList.add(collClass);
|
||||
collapsed = true;
|
||||
if (topNav) {
|
||||
topNav.style.paddingLeft = '90px';
|
||||
window.setTimeout(function() {
|
||||
topNav.classList.add('navbar--animated');
|
||||
}, 200);
|
||||
}
|
||||
} else if (topNav) {
|
||||
topNav.classList.add('navbar--animated');
|
||||
}
|
||||
addListener();
|
||||
}
|
||||
|
||||
function check() {
|
||||
if (collapsed && !hasClass() || !collapsed && hasClass()) {
|
||||
asideEl.classList.add(animClass);
|
||||
asideEl.classList.toggle(collClass, collapsed);
|
||||
if (topNav) {
|
||||
topNav.style.paddingLeft = collapsed ? '90px' : '';
|
||||
}
|
||||
window.localStorage.setItem('asidenavCollapsed', collapsed);
|
||||
}
|
||||
}
|
||||
@ -41,41 +40,46 @@
|
||||
return asideEl.classList.contains(collClass);
|
||||
}
|
||||
|
||||
asideEl.querySelector('.asidenav__toggler').addEventListener('click', function(event) {
|
||||
collapsed = !collapsed;
|
||||
check();
|
||||
}, false);
|
||||
asideEl.addEventListener('transitionend', function(event) {
|
||||
if (event.propertyName === 'opacity') {
|
||||
asideEl.classList.remove(animClass);
|
||||
}
|
||||
}, false);
|
||||
window.addEventListener('resize', function() {
|
||||
collapsed = document.body.getBoundingClientRect().width < 999;
|
||||
check();
|
||||
}, false);
|
||||
function addListener() {
|
||||
|
||||
asideEl.addEventListener('mouseover', function(event) {
|
||||
if (!collapsed) {
|
||||
return false;
|
||||
}
|
||||
aboveCollapsedNav = true;
|
||||
console.log(event);
|
||||
window.setTimeout(function() {
|
||||
if (aboveCollapsedNav && !document.body.classList.contains('touch-supported')) {
|
||||
asideEl.classList.add('pseudo-hover');
|
||||
asideEl.querySelector('.asidenav__toggler').addEventListener('click', function(event) {
|
||||
collapsed = !collapsed;
|
||||
check();
|
||||
}, false);
|
||||
|
||||
asideEl.addEventListener('transitionend', function(event) {
|
||||
if (event.propertyName === 'opacity') {
|
||||
asideEl.classList.remove(animClass);
|
||||
}
|
||||
}, 430);
|
||||
}, false);
|
||||
asideEl.addEventListener('mouseleave', function(event) {
|
||||
aboveCollapsedNav = false;
|
||||
asideEl.classList.remove('pseudo-hover');
|
||||
}, false);
|
||||
}, false);
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
collapsed = document.body.getBoundingClientRect().width < 999;
|
||||
check();
|
||||
}, false);
|
||||
|
||||
asideEl.addEventListener('mouseover', function(event) {
|
||||
if (!collapsed) {
|
||||
return false;
|
||||
}
|
||||
aboveCollapsedNav = true;
|
||||
window.setTimeout(function() {
|
||||
if (aboveCollapsedNav && !document.body.classList.contains('touch-supported')) {
|
||||
asideEl.classList.add('pseudo-hover');
|
||||
}
|
||||
}, 800);
|
||||
}, false);
|
||||
|
||||
asideEl.addEventListener('mouseleave', function(event) {
|
||||
aboveCollapsedNav = false;
|
||||
asideEl.classList.remove('pseudo-hover');
|
||||
}, false);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
utils.aside(document.querySelector('.main__aside'), document.querySelector('.navbar'));
|
||||
utils.aside(document.querySelector('.main__aside'));
|
||||
|
||||
});
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1;
|
||||
flex: 0 0 300px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.main__aside--transitioning {
|
||||
transition: flex-basis .2s ease;
|
||||
@ -19,6 +18,7 @@
|
||||
.main__aside--collapsed {
|
||||
width: 50px;
|
||||
flex-basis: 50px;
|
||||
overflow: hidden;
|
||||
|
||||
.asidenav__box-title {
|
||||
width: 50px;
|
||||
@ -44,6 +44,9 @@
|
||||
margin-top: 20px;
|
||||
color: white;
|
||||
|
||||
.js-show-hide__target {
|
||||
overflow: visible;
|
||||
}
|
||||
.js-show-hide__toggle::before {
|
||||
top: 14px;
|
||||
right: 12px;
|
||||
@ -76,6 +79,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__nested-list {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: var(--fontbase);
|
||||
transform: translateX(0);
|
||||
opacity: 0;
|
||||
transition: all .2s ease;
|
||||
z-index: -1;
|
||||
|
||||
.asidenav__list-item {
|
||||
background-color: var(--darkbase);
|
||||
color: white;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper {
|
||||
padding-left: 13px;
|
||||
padding-right: 13px;
|
||||
border-left: 20px solid white;
|
||||
transition: all .2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: white;
|
||||
color: var(--darkbase) !important;
|
||||
border-left: 20px solid var(--darkbase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.asidenav__list-item {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
@ -86,6 +122,11 @@
|
||||
color: white;
|
||||
background-color: var(--darkbase);
|
||||
|
||||
.asidenav__nested-list {
|
||||
transform: translateX(100%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.asidenav__link-wrapper,
|
||||
.asidenav__link-label {
|
||||
color: white;
|
||||
|
||||
18
templates/widgets/modal.hamlet
Normal file
18
templates/widgets/modal.hamlet
Normal file
@ -0,0 +1,18 @@
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTrigger} data-closeable=true>
|
||||
$if 11 == length modalContent
|
||||
<div .replace-me>
|
||||
$else
|
||||
<h2>Neue Veranstaltung
|
||||
#{modalContent}
|
||||
<form>
|
||||
<div .form-group>
|
||||
<label .reactive-label for="inp1">Name
|
||||
<input type="text" id="inp1">
|
||||
<div .form-group>
|
||||
<label .reactive-label for="inp2">Kürzel
|
||||
<input type="text" id="inp2">
|
||||
<div .form-group>
|
||||
<label .reactive-label for="inp3">Semester
|
||||
<input type="text" id="inp3">
|
||||
<div .form-group>
|
||||
<input type="submit" value="Submit">
|
||||
@ -1,3 +1,4 @@
|
||||
$newline never
|
||||
<div .navbar-container>
|
||||
<nav .navbar.js-sticky-navbar>
|
||||
|
||||
|
||||
@ -1,28 +1,48 @@
|
||||
/**
|
||||
* .js-sticky-navbar
|
||||
* ul
|
||||
* li Item 1
|
||||
* li Item 2
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.stickynav = function(nav) {
|
||||
var ticking = false;
|
||||
|
||||
init();
|
||||
function init() {
|
||||
nav.style.paddingLeft = document.body.getBoundingClientRect().width < 999 ? '90px' : '';
|
||||
window.setTimeout(function() {
|
||||
nav.classList.add('navbar--animated');
|
||||
}, 200);
|
||||
checkScroll();
|
||||
addListener();
|
||||
}
|
||||
|
||||
// checks scroll direction and shows/hides navbar accordingly
|
||||
function checkScroll() {
|
||||
var sticky = window.scrollY > 0;
|
||||
nav.classList.toggle('navbar--sticky', sticky);
|
||||
ticking = false;
|
||||
}
|
||||
|
||||
function addListener() {
|
||||
window.addEventListener('scroll', function(e) {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(checkScroll);
|
||||
ticking = true;
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function() {
|
||||
nav.style.paddingLeft = document.body.getBoundingClientRect().width < 999 ? '90px' : '';
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
var ticking = false;
|
||||
var nav = document.querySelector('.js-sticky-navbar');
|
||||
|
||||
window.addEventListener('scroll', function(e) {
|
||||
if (!ticking) {
|
||||
window.requestAnimationFrame(checkScroll);
|
||||
ticking = true;
|
||||
}
|
||||
}, false);
|
||||
|
||||
// checks scroll direction and shows/hides navbar accordingly
|
||||
function checkScroll() {
|
||||
var sticky = window.scrollY > 0;
|
||||
nav.classList.toggle('navbar--sticky', sticky);
|
||||
ticking = false;
|
||||
}
|
||||
checkScroll();
|
||||
utils.stickynav(document.querySelector('.js-sticky-navbar'));
|
||||
|
||||
});
|
||||
|
||||
11
templates/widgets/pageactionprime.hamlet
Normal file
11
templates/widgets/pageactionprime.hamlet
Normal file
@ -0,0 +1,11 @@
|
||||
$newline never
|
||||
$if hasPageActions
|
||||
<div .page-nav-prime>
|
||||
<h3>Aktionen:
|
||||
<ul .pagenav__list>
|
||||
$forall menuType <- menuTypes
|
||||
$case menuType
|
||||
$of PageActionPrime (MenuItem label mIcon route _)
|
||||
<li .pagenav__list-item>
|
||||
<a .pagenav__link-wrapper href=@{route}>#{label}
|
||||
$of _
|
||||
21
templates/widgets/pageactionprime.lucius
Normal file
21
templates/widgets/pageactionprime.lucius
Normal file
@ -0,0 +1,21 @@
|
||||
.page-nav-prime {
|
||||
background-color: var(--lightgreybase);
|
||||
box-shadow: -20px -20px 0 20px var(--lightgreybase),
|
||||
20px -20px 0 20px var(--lightgreybase);
|
||||
padding: 13px 0;
|
||||
}
|
||||
|
||||
.page-nav-prime .pagenav__list {
|
||||
margin: 7px 0 0;
|
||||
display: block;
|
||||
}
|
||||
.page-nav-prime .pagenav__list-item {
|
||||
display: inline-block;
|
||||
border-bottom: 2px solid var(--lightbase);
|
||||
margin-right: 7px;
|
||||
transition: border-bottom-color .2s ease;
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: var(--lighterbase);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user