_{MsgErrorResponseNotAuthenticated}|] PermissionDenied err' -> [whamlet|
#{err'}|] BadMethod method -> [whamlet|
_{MsgErrorResponseBadMethod (decodeUtf8 method)}|] - fmap toTypedContent . siteLayout (Just . toHtml . mr $ ErrorResponseTitle err) $ do + fmap toTypedContent . siteLayout (toWgt . mr $ ErrorResponseTitle err) $ do toWidget [cassius| .errMsg @@ -837,7 +860,7 @@ instance Yesod UniWorX where |] errPage - defaultLayout = siteLayout Nothing + defaultLayout = siteLayout' Nothing -- The page to be redirected to when authentication is required. authRoute _ = Just $ AuthR LoginR @@ -870,6 +893,8 @@ instance Yesod UniWorX where . runIdentity $ sourceList (Lazy.ByteString.toChunks content) $$ sinkHash + fileUpload _site _length = FileUploadMemory lbsBackEnd + -- What messages should be logged. The following includes all messages when -- in development, and warnings and errors in production. shouldLog _ _ _ = error "Must use shouldLogIO" @@ -880,9 +905,21 @@ instance Yesod UniWorX where makeLogger = readTVarIO . snd . appLogger -siteLayout :: Maybe Html -- ^ Optionally override `pageHeading` +siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX) => msg -> Widget -> Handler Html +siteLayoutMsg msg widget = do + mr <- getMessageRender + siteLayout (toWgt $ mr msg) widget + +siteLayoutMsg' :: (RenderMessage site msg, site ~ UniWorX) => msg -> Widget -> Handler Html +siteLayoutMsg' = siteLayout . i18nHeading + +siteLayout :: Widget -- ^ `pageHeading` -> Widget -> Handler Html -siteLayout headingOverride widget = do +siteLayout = siteLayout' . Just + +siteLayout' :: Maybe Widget -- ^ Optionally override `pageHeading` + -> Widget -> Handler Html +siteLayout' headingOverride widget = do master <- getYesod let AppSettings { appUserDefaults = UserDefaultConf{..}, .. } = appSettings master @@ -949,45 +986,65 @@ siteLayout headingOverride widget = do -- you to use normal widget features in default-layout. navbar :: Widget - navbar = $(widgetFile "widgets/navbar") + navbar = $(widgetFile "widgets/navbar/navbar") asidenav :: Widget - asidenav = $(widgetFile "widgets/asidenav") + asidenav = $(widgetFile "widgets/asidenav/asidenav") footer :: Widget - footer = $(widgetFile "widgets/footer") + footer = $(widgetFile "widgets/footer/footer") + alerts :: Widget + alerts = $(widgetFile "widgets/alerts/alerts") contentHeadline :: Maybe Widget - contentHeadline = (toWidget <$> headingOverride) <|> (pageHeading =<< mcurrentRoute) + contentHeadline = headingOverride <|> (pageHeading =<< mcurrentRoute) breadcrumbsWgt :: Widget - breadcrumbsWgt = $(widgetFile "widgets/breadcrumbs") - pageactionprime :: Widget - pageactionprime = $(widgetFile "widgets/pageactionprime") -- TODO: rename, since it also shows secondary pageActions now + breadcrumbsWgt = $(widgetFile "widgets/breadcrumbs/breadcrumbs") + pageaction :: Widget + pageaction = $(widgetFile "widgets/pageaction/pageaction") -- functions to determine if there are page-actions (primary or secondary) - isPageAction :: MenuType -> Bool - isPageAction PageActionPrime = True - isPageAction PageActionSecondary = True - isPageAction _ = False - hasPageActions :: Bool - hasPageActions = any (isPageAction . menuItemType . view _1) menuTypes + hasPageActions, hasSecondaryPageActions, hasPrimaryPageActions :: Bool + hasPageActions = hasPrimaryPageActions || hasSecondaryPageActions + hasSecondaryPageActions = any (is _PageActionSecondary) $ toListOf (traverse . _1 . _menuItemType) menuTypes + hasPrimaryPageActions = any (is _PageActionPrime) $ toListOf (traverse . _1 . _menuItemType) menuTypes pc <- widgetToPageContent $ do - addStylesheetRemote "https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,800,900|Roboto:300,400,600" - addScript $ StaticR js_zepto_js - addScript $ StaticR js_fetchPolyfill_js - addScript $ StaticR js_urlPolyfill_js - addScript $ StaticR js_featureChecker_js - addScript $ StaticR js_flatpickr_js - addScript $ StaticR js_tabber_js - addStylesheet $ StaticR css_flatpickr_css - addStylesheet $ StaticR css_tabber_css + -- 3rd party + addScript $ StaticR js_vendor_flatpickr_js + addScript $ StaticR js_vendor_zepto_js + addStylesheet $ StaticR css_vendor_flatpickr_css + addStylesheet $ StaticR css_vendor_fontawesome_css + -- fonts addStylesheet $ StaticR css_fonts_css - addStylesheet $ StaticR css_fontawesome_css + -- polyfills + addScript $ StaticR js_polyfills_fetchPolyfill_js + addScript $ StaticR js_polyfills_urlPolyfill_js + -- JavaScript utils + addScript $ StaticR js_utils_alerts_js + addScript $ StaticR js_utils_asidenav_js + addScript $ StaticR js_utils_asyncForm_js + addScript $ StaticR js_utils_asyncTable_js + addScript $ StaticR js_utils_asyncTableFilter_js + addScript $ StaticR js_utils_checkAll_js + addScript $ StaticR js_utils_httpClient_js + addScript $ StaticR js_utils_form_js + addScript $ StaticR js_utils_inputs_js + addScript $ StaticR js_utils_modal_js + addScript $ StaticR js_utils_setup_js + addScript $ StaticR js_utils_showHide_js + addScript $ StaticR js_utils_tabber_js + addStylesheet $ StaticR css_utils_alerts_scss + addStylesheet $ StaticR css_utils_asidenav_scss + addStylesheet $ StaticR css_utils_asyncForm_scss + addStylesheet $ StaticR css_utils_asyncTable_scss + addStylesheet $ StaticR css_utils_asyncTableFilter_scss + addStylesheet $ StaticR css_utils_checkbox_scss + addStylesheet $ StaticR css_utils_form_scss + addStylesheet $ StaticR css_utils_inputs_scss + addStylesheet $ StaticR css_utils_modal_scss + addStylesheet $ StaticR css_utils_radio_scss + addStylesheet $ StaticR css_utils_showHide_scss + addStylesheet $ StaticR css_utils_tabber_scss + addStylesheet $ StaticR css_utils_tooltip_scss + -- widgets $(widgetFile "default-layout") - $(widgetFile "standalone/modal") - $(widgetFile "standalone/showHide") - $(widgetFile "standalone/inputs") - $(widgetFile "standalone/tooltip") - $(widgetFile "standalone/tabber") - $(widgetFile "standalone/alerts") - $(widgetFile "standalone/datepicker") withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet") @@ -1018,25 +1075,35 @@ applySystemMessages = liftHandlerT . runDB . runConduit $ selectSource [] [] .| -- Define breadcrumbs. instance YesodBreadcrumbs UniWorX where - breadcrumb (AuthR _) = return ("Login" , Just HomeR) - breadcrumb HomeR = return ("Uni2work", Nothing) - breadcrumb UsersR = return ("Benutzer", Just HomeR) - breadcrumb AdminTestR = return ("Test" , Just HomeR) - breadcrumb (AdminUserR _) = return ("Users" , Just UsersR) - breadcrumb VersionR = return ("Impressum" , Just HomeR) + breadcrumb (AuthR _) = return ("Login" , Just HomeR) + breadcrumb HomeR = return ("Uni2work" , Nothing) + breadcrumb UsersR = return ("Benutzer" , Just HomeR) + breadcrumb AdminTestR = return ("Test" , Just HomeR) + breadcrumb (AdminUserR _) = return ("Users" , Just UsersR) - breadcrumb ProfileR = return ("Profile" , Just HomeR) - breadcrumb ProfileDataR = return ("Data" , Just ProfileR) + breadcrumb InfoR = return ("Information" , Nothing) + breadcrumb InfoLecturerR = return ("Veranstalter" , Just InfoR) + breadcrumb DataProtR = return ("Datenschutz" , Just InfoR) + breadcrumb ImpressumR = return ("Impressum" , Just InfoR) + breadcrumb VersionR = return ("Versionsgeschichte", Just InfoR) + + + breadcrumb HelpR = return ("Hilfe" , Just HomeR) + + + breadcrumb ProfileR = return ("User" , Just HomeR) + breadcrumb ProfileDataR = return ("Profile" , Just ProfileR) + breadcrumb AuthPredsR = return ("Authentifizierung", Just ProfileR) breadcrumb TermShowR = return ("Semester" , Just HomeR) breadcrumb TermCurrentR = return ("Aktuell" , Just TermShowR) breadcrumb TermEditR = return ("Neu" , Just TermCurrentR) breadcrumb (TermEditExistR tid) = return ("Editieren" , Just $ TermCourseListR tid) - breadcrumb (TermCourseListR (unTermKey -> tid)) = getMessageRender <&> \mr -> (mr $ ShortTermIdentifier tid, Nothing) + breadcrumb (TermCourseListR (unTermKey -> tid)) = getMessageRender <&> \mr -> (mr $ ShortTermIdentifier tid, Just CourseListR) breadcrumb (TermSchoolCourseListR tid ssh) = return (CI.original $ unSchoolKey ssh, Just $ TermCourseListR tid) - breadcrumb CourseListR = return ("Kurse" , Just HomeR) + breadcrumb CourseListR = return ("Kurse" , Nothing) breadcrumb CourseNewR = return ("Neu" , Just CourseListR) breadcrumb (CourseR tid ssh csh CShowR) = return (CI.original csh, Just $ TermSchoolCourseListR tid ssh) -- (CourseR tid ssh csh CRegisterR) -- is POST only @@ -1086,18 +1153,34 @@ submissionList tid csh shn uid = E.select . E.from $ \(course `E.InnerJoin` shee defaultLinks :: (MonadHandler m, HandlerSite m ~ UniWorX) => m [MenuItem] defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header. [ return MenuItem - { menuItemType = NavbarAside + { menuItemType = NavbarAside , menuItemLabel = MsgMenuHome - , menuItemIcon = Just "home" + , menuItemIcon = Just "home" , menuItemRoute = SomeRoute HomeR , menuItemModal = False , menuItemAccessCallback' = return True } , return MenuItem - { menuItemType = Footer - , menuItemLabel = MsgMenuVersion - , menuItemIcon = Just "book" - , menuItemRoute = SomeRoute VersionR + { menuItemType = Footer + , menuItemLabel = MsgMenuDataProt + , menuItemIcon = Just "shield" + , menuItemRoute = SomeRoute DataProtR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , return MenuItem + { menuItemType = Footer + , menuItemLabel = MsgMenuImpressum + , menuItemIcon = Just "file-signature" + , menuItemRoute = SomeRoute ImpressumR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , return MenuItem + { menuItemType = Footer + , menuItemLabel = MsgMenuInformation + , menuItemIcon = Just "info" + , menuItemRoute = SomeRoute InfoR , menuItemModal = False , menuItemAccessCallback' = return True } @@ -1105,7 +1188,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the mCurrentRoute <- getCurrentRoute return MenuItem - { menuItemType = NavbarRight + { menuItemType = NavbarRight , menuItemLabel = MsgMenuHelp , menuItemIcon = Just "question" , menuItemRoute = SomeRoute (HelpR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mCurrentRoute]) @@ -1113,57 +1196,57 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , menuItemAccessCallback' = return True } , return MenuItem - { menuItemType = NavbarRight + { menuItemType = NavbarRight , menuItemLabel = MsgMenuProfile - , menuItemIcon = Just "cogs" + , menuItemIcon = Just "cogs" , menuItemRoute = SomeRoute ProfileR , menuItemModal = False , menuItemAccessCallback' = isJust <$> maybeAuthPair } , return MenuItem - { menuItemType = NavbarSecondary + { menuItemType = NavbarSecondary , menuItemLabel = MsgMenuLogin - , menuItemIcon = Just "sign-in-alt" + , menuItemIcon = Just "sign-in-alt" , menuItemRoute = SomeRoute $ AuthR LoginR , menuItemModal = True , menuItemAccessCallback' = isNothing <$> maybeAuthPair } , return MenuItem - { menuItemType = NavbarSecondary + { menuItemType = NavbarSecondary , menuItemLabel = MsgMenuLogout - , menuItemIcon = Just "sign-out-alt" + , menuItemIcon = Just "sign-out-alt" , menuItemRoute = SomeRoute $ AuthR LogoutR , menuItemModal = False , menuItemAccessCallback' = isJust <$> maybeAuthPair } , return MenuItem - { menuItemType = NavbarAside - , menuItemLabel = MsgMenuCourseList - , menuItemIcon = Just "calendar-alt" - , menuItemRoute = SomeRoute CourseListR - , menuItemModal = False - , menuItemAccessCallback' = return True - } - , return MenuItem - { menuItemType = NavbarAside + { menuItemType = NavbarAside , menuItemLabel = MsgMenuTermShow - , menuItemIcon = Just "graduation-cap" + , menuItemIcon = Just "calendar-alt" -- SJ wrote: calendar icon, since Term will be repleaced with TimeTable in the future; arguably Term is more calendar-like than courses anyway!!! , menuItemRoute = SomeRoute TermShowR , menuItemModal = False , menuItemAccessCallback' = return True } , return MenuItem - { menuItemType = NavbarAside + { menuItemType = NavbarAside + , menuItemLabel = MsgMenuCourseList + , menuItemIcon = Just "graduation-cap" + , menuItemRoute = SomeRoute CourseListR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , return MenuItem + { menuItemType = NavbarAside , menuItemLabel = MsgMenuCorrections - , menuItemIcon = Just "check" + , menuItemIcon = Just "check" , menuItemRoute = SomeRoute CorrectionsR , menuItemModal = False , menuItemAccessCallback' = return True } , return MenuItem - { menuItemType = NavbarAside + { menuItemType = NavbarAside , menuItemLabel = MsgMenuUsers - , menuItemIcon = Just "users" + , menuItemIcon = Just "users" , menuItemRoute = SomeRoute UsersR , menuItemModal = False , menuItemAccessCallback' = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False @@ -1180,14 +1263,15 @@ pageActions :: Route UniWorX -> [MenuItem] -} pageActions (HomeR) = [ --- NavbarAside $ MenuItem --- { menuItemLabel = "Benutzer" --- , menuItemIcon = Just "users" --- , menuItemRoute = UsersR --- , menuItemAccessCallback' = return True --- } --- , MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgInfoLecturerTitle + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute InfoLecturerR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , MenuItem { menuItemType = PageActionPrime , menuItemLabel = MsgMenuAdminTest , menuItemIcon = Just "screwdriver" @@ -1212,6 +1296,36 @@ pageActions (HomeR) = , menuItemAccessCallback' = return True } ] +pageActions (InfoR) = [ + MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgInfoLecturerTitle + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute InfoLecturerR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + ] +pageActions (VersionR) = [ + MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgInfoLecturerTitle + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute InfoLecturerR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + ] +pageActions (HelpR) = [ + -- MenuItem + -- { menuItemType = PageActionPrime + -- , menuItemLabel = MsgInfoLecturerTitle + -- , menuItemIcon = Nothing + -- , menuItemRoute = SomeRoute InfoLecturerR + -- , menuItemModal = False + -- , menuItemAccessCallback' = return True + -- } + ] pageActions (ProfileR) = [ MenuItem { menuItemType = PageActionPrime @@ -1221,6 +1335,14 @@ pageActions (ProfileR) = , menuItemModal = False , menuItemAccessCallback' = return True } + , MenuItem + { menuItemType = PageActionSecondary + , menuItemLabel = MsgMenuAuthPreds + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute AuthPredsR + , menuItemModal = True + , menuItemAccessCallback' = return True + } ] pageActions TermShowR = [ MenuItem @@ -1260,6 +1382,16 @@ pageActions (CourseListR) = , menuItemAccessCallback' = return True } ] +pageActions (CourseNewR) = [ + MenuItem + { menuItemType = PageActionPrime + , menuItemLabel = MsgInfoLecturerTitle + , menuItemIcon = Nothing + , menuItemRoute = SomeRoute InfoLecturerR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + ] pageActions (CourseR tid ssh csh CShowR) = [ MenuItem { menuItemType = PageActionPrime @@ -1281,6 +1413,14 @@ pageActions (CourseR tid ssh csh CShowR) = } ] ++ pageActions (CourseR tid ssh csh SheetListR) ++ [ MenuItem + { menuItemType = PageActionSecondary + , menuItemLabel = MsgMenuCourseMembers + , menuItemIcon = Just "user-graduate" + , menuItemRoute = SomeRoute $ CourseR tid ssh csh CUsersR + , menuItemModal = False + , menuItemAccessCallback' = return True + } + , MenuItem { menuItemType = PageActionSecondary , menuItemLabel = MsgMenuCourseEdit , menuItemIcon = Nothing @@ -1474,7 +1614,7 @@ pageActions (CSubmissionR tid ssh csh shn cid SubShowR) = , MenuItem { menuItemType = PageActionSecondary , menuItemLabel = MsgMenuSubmissionDelete - , menuItemIcon = Nothing + , menuItemIcon = Just "trash" , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubDelR , menuItemModal = False , menuItemAccessCallback' = return True @@ -1586,6 +1726,7 @@ pageActions _ = [] i18nHeading :: (MonadWidget m, RenderMessage site msg, HandlerSite m ~ site) => msg -> m () i18nHeading msg = liftWidgetT $ toWidget =<< getMessageRender <*> pure msg +-- | only used in defaultLayout; better use siteLayout instead! pageHeading :: Route UniWorX -> Maybe Widget pageHeading (AuthR _) = Just $ i18nHeading MsgLoginHeading @@ -1593,14 +1734,22 @@ pageHeading HomeR = Just $ i18nHeading MsgHomeHeading pageHeading UsersR = Just $ i18nHeading MsgUsers +pageHeading (AdminUserR _) + = Just $ i18nHeading MsgAdminUserHeading pageHeading (AdminTestR) = Just $ [whamlet|Internal Code Demonstration Page|] -pageHeading (AdminUserR _) - = Just $ [whamlet|User Display for Admin|] pageHeading (AdminErrMsgR) = Just $ i18nHeading MsgErrMsgHeading -pageHeading (VersionR) + +pageHeading (InfoR) + = Just $ i18nHeading MsgInfoHeading +pageHeading (DataProtR) + = Just $ i18nHeading MsgDataProtHeading +pageHeading (ImpressumR) = Just $ i18nHeading MsgImpressumHeading +pageHeading (VersionR) + = Just $ i18nHeading MsgVersionHeading + pageHeading (HelpR) = Just $ i18nHeading MsgHelpRequest diff --git a/src/Handler/Admin.hs b/src/Handler/Admin.hs index 17bc943b9..501cc97b9 100644 --- a/src/Handler/Admin.hs +++ b/src/Handler/Admin.hs @@ -54,6 +54,24 @@ emailTestForm = (,) SelFormatDate -> d SelFormatTime -> t +makeDemoForm :: Int -> Form (Int,Bool,Double) +makeDemoForm n = identifyForm "adminTestForm" $ \html -> do -- Important: used identForm instead! + (result, widget) <- flip (renderAForm FormStandard) html $ (,,) + <$> areq (minIntField n "Zahl") (fromString $ "Ganzzahl > " ++ show n) Nothing + <* aformSection MsgFormBehaviour + <*> areq checkBoxField "Muss nächste Zahl größer sein?" (Just True) + <*> areq doubleField "Fliesskommazahl" Nothing + <* submitButton + return $ case result of + FormSuccess fsres + | errorMsgs <- validateResult fsres + , not $ null errorMsgs -> (FormFailure errorMsgs, widget) + _otherwise -> (result, widget) + where + validateResult :: (Int,Bool,Double) -> [Text] + validateResult (i,True,d) | fromIntegral i >= d = [tshow d <> " ist nicht größer als " <> tshow i, "Zweite Fehlermeldung", "Dritte Fehlermeldung"] + validateResult _other = [] + getAdminTestR, postAdminTestR :: Handler Html -- Demo Page. Referenzimplementierungen sollte hier gezeigt werden! getAdminTestR = postAdminTestR @@ -66,7 +84,7 @@ postAdminTestR = do _other -> addMessage Warning "KEIN Knopf erkannt" ((emailResult, emailWidget), emailEnctype) <- runFormPost . identifyForm "email" $ renderAForm FormStandard emailTestForm - case emailResult of + case emailResult of (FormSuccess (email, ls)) -> do jId <- runDB $ do jId <- queueJob $ JobSendTestEmail email ls @@ -77,24 +95,47 @@ postAdminTestR = do (FormFailure errs) -> forM_ errs $ addMessage Error . toHtml let emailWidget' = [whamlet| -