diff --git a/frontend/src/app.sass b/frontend/src/app.sass index 240c495bb..84fb13958 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -160,7 +160,7 @@ h4 --current-header-height: var(--header-height-collapsed) position: relative background-color: white - transition: padding-left .2s ease-out, margin-top 0.2s cubic-bezier(0.03, 0.43, 0.58, 1) + transition: padding-left .2s ease-out, margin-top 0.2s ease margin-top: var(--current-header-height) margin-left: 0 @@ -203,7 +203,7 @@ h4 .main__content margin-left: var(--asidenav-width-md, 50px) -@media (min-width: 769px) and (min-height: 501px) +@media (min-width: 769px) .main__content margin-left: var(--asidenav-width-lg, 20%) @@ -641,7 +641,7 @@ section .ribbon position: fixed top: calc(40px + var(--header-height)) - transition: all 0.2s cubic-bezier(0.03, 0.43, 0.58, 1) + transition: all 0.2s ease right: -63px transform: rotate(45deg) width: 250px @@ -652,7 +652,7 @@ section font-size: 1.25rem line-height: 2em box-shadow: 0 0 3px rgba(0, 0, 0, 0.4) - z-index: 19 + z-index: 21 pointer-events: none .navbar__container-radio:checked ~ & @@ -1003,9 +1003,12 @@ th, td .breadcrumbs__container position: relative color: var(--color-lightwhite) - padding: 4px 13px 4px 40px + padding: 4px 20px 4px 40px background-color: var(--color-dark) - line-height: 30px + margin: 0 -5px + + a + color: var(--color-lightwhite) @media (min-width: 426px) .breadcrumbs__container @@ -1015,45 +1018,31 @@ th, td .breadcrumbs__container padding: 7px 40px -.breadcrumbs__link - color: var(--color-lightwhite) +ul.breadcrumbs__list + display: flex + align-items: center + height: 30px - &:hover - color: var(--color-white) + & > li + display: block .breadcrumbs__item - padding-right: 14px - position: relative - line-height: 28px opacity: 0.8 - z-index: 1 - margin-right: 10px + margin: 0 5px &:hover opacity: 1 - &::after - content: '' - position: absolute - top: 5.5px - right: 0 - width: 7px - height: 7px - border-style: solid - border-width: 0 - border-bottom-width: 1px - border-right-width: 1px - border-color: var(--color-white) - transform: rotate(-45deg) - z-index: 10 - - @media (min-width: 426px) - top: 11px +.breadcrumbs__item-separator + line-height: 0 + opacity: 0.5 + margin: 0 5px + margin-top: 1px a.breadcrumbs__home position: absolute - left: 10px - top: 8px + left: 15px + top: 5px width: 20px height: 30px opacity: 0.5 @@ -1061,14 +1050,16 @@ a.breadcrumbs__home color: var(--color-lightwhite) text-align: center line-height: 30px + + @media (min-width: 426px) + top: 8px &:hover opacity: 1 .breadcrumbs__last-item - line-height: 28px - vertical-align: bottom font-weight: 600 + opacity: 1 .recipient-category max-width: 400px @@ -1144,7 +1135,7 @@ a.breadcrumbs__home text-align: center padding: 20px position: relative - margin: 40px 0 + margin: 40px 0 0 0 &::before content: '' @@ -1183,70 +1174,3 @@ a.breadcrumbs__home .checkbox display: inline-block margin-left: 7px - -.pagenav - display: flex - align-items: flex-start - padding-bottom: 15px - margin-bottom: 20px - border-bottom: 1px solid #eee - -.pagenav__list-item - flex: 1 - position: relative - display: inline-flex - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) - margin: 10px 10px 0 0 - -.pagenav__link-wrapper - flex: 1 - padding: 10px 10px 12px - text-decoration: none !important - - &:hover - background-color: var(--color-grey-light) - -@media (max-width: 1024px) - .pagenav - flex-direction: column - -@media (min-width: 1025px) - .pagenav-secondary - position: relative - overflow: visible - padding-top: 10px - - &::after - content: '\2026' - display: inline-block - padding: 10px 10px 12px - width: 40px - box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) - box-sizing: border-box - text-align: center - transition: box-shadow 0.2s ease - - &:hover - &::after - box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.8) - - .pagenav-secondary__list - display: block - - .pagenav-secondary__list - position: absolute - display: none - right: 0 - top: 50px - width: 250px - background-color: white - box-shadow: 0 0 6px 3px var(--color-grey-light) - z-index: 18 - - .pagenav__list-item--secondary - display: flex - box-shadow: none - margin: 0 - - &:hover - background-color: var(--color-grey-light) diff --git a/frontend/src/services/util-registry/util-registry.js b/frontend/src/services/util-registry/util-registry.js index e035903fa..3fcfb99d5 100644 --- a/frontend/src/services/util-registry/util-registry.js +++ b/frontend/src/services/util-registry/util-registry.js @@ -58,7 +58,7 @@ export class UtilRegistry { if (utilInstance) { const instance = utilInstance.instance; if (instance && typeof instance.start === 'function') { - instance.start(); + instance.start.bind(instance)(); startedInstances.push(instance); } } @@ -91,6 +91,7 @@ export class UtilRegistry { if (DEBUG_MODE > 0) { console.error('Error while trying to initialize a utility!', { util , element, err }); } + utilInstance = null; } if (utilInstance) { diff --git a/frontend/src/utils/asidenav/asidenav.sass b/frontend/src/utils/asidenav/asidenav.sass index 176b5ab4f..4ed526a21 100644 --- a/frontend/src/utils/asidenav/asidenav.sass +++ b/frontend/src/utils/asidenav/asidenav.sass @@ -1,7 +1,6 @@ .main__aside position: fixed box-shadow: 0 0 10px rgba(0, 0, 0, 0.3) - z-index: 1 top: 0 left: 0 width: var(--asidenav-width-lg, 20%) @@ -9,16 +8,7 @@ flex: 0 0 0 flex-basis: var(--asidenav-width-lg, 20%) transition: all .2s ease-out - - &::before - position: absolute - z-index: -1 - left: 0 - top: 0 - width: 100% - height: 100% - background-color: var(--color-dark) - opacity: 0.05 + z-index: 20 &::after content: '' @@ -233,7 +223,7 @@ // hover sub-menus .asidenav__nested-list-wrapper position: absolute - z-index: 10 + z-index: 22 display: none color: var(--color-font) background-color: var(--color-grey-light) @@ -270,6 +260,10 @@ min-height: calc(100% - var(--header-height-collapsed)) top: var(--header-height-collapsed) + .navbar__container-radio:checked ~ & + min-height: calc(100% - var(--header-height-collapsed) - 30px) + top: calc(var(--header-height-collapsed) + 30px) + .asidenav__box-title width: var(--asidenav-width-md, 50px) font-size: 18px @@ -278,6 +272,7 @@ word-break: break-all background-color: var(--color-dark) color: var(--color-lightwhite) + border: none &:hover background-color: var(--color-darker) diff --git a/frontend/src/utils/navbar/navbar.js b/frontend/src/utils/navbar/navbar.js index 28264963b..f31ba77bd 100644 --- a/frontend/src/utils/navbar/navbar.js +++ b/frontend/src/utils/navbar/navbar.js @@ -3,7 +3,7 @@ import './navbar.sass'; import * as throttle from 'lodash.throttle'; export const HEADER_CONTAINER_UTIL_SELECTOR = '.navbar__list-item--container-selector .navbar__link-wrapper'; -const HEADER_CONTAINER_INITIALIZED_CLASS = '.navbar-header-container--initialized'; +const HEADER_CONTAINER_INITIALIZED_CLASS = 'navbar-header-container--initialized'; @Utility({ selector: HEADER_CONTAINER_UTIL_SELECTOR, @@ -24,7 +24,7 @@ export class NavHeaderContainerUtil { } if (element.classList.contains(HEADER_CONTAINER_INITIALIZED_CLASS)) { - return false; + return; } this._element = element; @@ -55,6 +55,9 @@ export class NavHeaderContainerUtil { } start() { + if (!this.container) + return; + window.addEventListener('click', this.clickHandler.bind(this)); this.radioButton.addEventListener('change', this.throttleUpdateWasOpen.bind(this)); } diff --git a/frontend/src/utils/navbar/navbar.sass b/frontend/src/utils/navbar/navbar.sass index 37f234270..67094233e 100644 --- a/frontend/src/utils/navbar/navbar.sass +++ b/frontend/src/utils/navbar/navbar.sass @@ -9,7 +9,7 @@ min-height: var(--header-height) background-color: var(--color-primary) color: white - z-index: 20 + z-index: 22 box-shadow: 0 0 4px rgba(0, 0, 0, 0.2) overflow: auto transition: all 0.2s cubic-bezier(0.03, 0.43, 0.58, 1) @@ -64,6 +64,7 @@ .navbar__container-list /* margin: 10px 0 0 0 */ + position: relative padding: 0 40px overflow: hidden display: flex @@ -108,8 +109,6 @@ transform-origin: 10px 10px transform: rotate(-0.25turn) - &.navbar__container-list--left - transform: rotate(0.25turn) opacity: 0.5 transition: transform 0.2s, opacity 0.2s ease @@ -126,9 +125,12 @@ visibility: hidden &.navbar__container-list--left .navbar__container-list-closer - left: 14.5px + transform: rotate(0.25turn) right: auto + left: 10px + &:hover + transform: scale(1.4) // links .navbar__link-wrapper diff --git a/frontend/src/utils/pageactions/pageactions.js b/frontend/src/utils/pageactions/pageactions.js new file mode 100644 index 000000000..97a719ea4 --- /dev/null +++ b/frontend/src/utils/pageactions/pageactions.js @@ -0,0 +1,110 @@ +import { Utility } from '../../core/utility'; +import './pageactions.sass'; +import * as throttle from 'lodash.throttle'; + +export const PAGEACTION_SECONDARY_UTIL_SELECTOR = '.pagenav__list-item'; +const PAGEACTION_SECONDARY_INITIALIZED_CLASS = '.pagenav-list-item--initialized'; +const PAGEACTION_SECONDARY_CLASS = 'pagenav-secondary'; + +@Utility({ + selector: PAGEACTION_SECONDARY_UTIL_SELECTOR, +}) +export class PageActionSecondaryUtil { + _element; + navIdent; + radioButton; + closeButton; + container; + wasOpen; + + _throttleUpdateWasOpen; + + constructor(element) { + if (!element) { + throw new Error('Pageaction Secondary utility needs to be passed an element!'); + } + + if (element.classList.contains(PAGEACTION_SECONDARY_INITIALIZED_CLASS)) { + return false; + } + + this._element = element; + + const childContainer = this._element.querySelector('.pagenav-item__children'); + + if (!childContainer) { + return false; + } + + if (this._element.classList.contains(PAGEACTION_SECONDARY_CLASS)) { + this.navIdent = 'secondary'; + } else { + const links = Array.from(this._element.querySelectorAll('.pagenav-item__link')).filter(l => !childContainer.contains(l)); + + if (!links || Array.from(links).length !== 1) { + throw new Error('Pageaction Secondary utility could not find associated link!'); + } + this.navIdent = links[0].id; + } + + this.radioButton = document.getElementById(`pageaction-item__expand-${this.navIdent}`); + if (!this.radioButton) { + throw new Error('Pageaction Secondary utility could not find associated radio button!'); + } + + this.closeButton = document.getElementById('pageaction-item__expand-none'); + if (!this.closeButton) { + throw new Error('Pageaction Secondary utility could not find radio button for closing!'); + } + + this.container = document.querySelector('.pagenav-item__children-wrapper'); + if (!this.container) { + throw new Error('Pageaction Secondary utility could not find associated container!'); + } + + const closer = this._element.querySelector('.pagenav-item__close-label'); + if (closer) { + closer.classList.add('pagenav-item__close-label--hidden'); + } + + this.updateWasOpen(); + this.throttleUpdateWasOpen = throttle(this.updateWasOpen.bind(this), 100, { leading: false, trailing: true }); + + this._element.classList.add(PAGEACTION_SECONDARY_INITIALIZED_CLASS); + } + + start() { + if (!this.container) + return; + + window.addEventListener('click', this.clickHandler.bind(this)); + this.radioButton.addEventListener('change', this.throttleUpdateWasOpen.bind(this)); + } + + clickHandler() { + if (!this.container.contains(event.target) && window.document.contains(event.target) && this.wasOpen) { + this.close(); + } + } + + close() { + this.radioButton.checked = false; + this.throttleUpdateWasOpen(); + } + + isOpen() { + return this.radioButton.checked; + } + + updateWasOpen() { + this.wasOpen = this.isOpen(); + } + + destroy() { /* TODO */ } +} + + + +export const PageActionsUtils = [ + PageActionSecondaryUtil, +]; diff --git a/frontend/src/utils/pageactions/pageactions.sass b/frontend/src/utils/pageactions/pageactions.sass new file mode 100644 index 000000000..9dbdad870 --- /dev/null +++ b/frontend/src/utils/pageactions/pageactions.sass @@ -0,0 +1,190 @@ + +.pagenav + display: flex + align-content: flex-start + align-items: flex-start + flex-flow: row wrap + padding: 0 0 10px 0 + margin: -5px -5px 20px -5px + border-bottom: 1px solid #eee + list-style: none + +.pagenav-item__expand-radio + display: none + +.pagenav-item__link + display: block + padding: 6px 10px + background-color: white + + &:hover + background-color: var(--color-grey-light) + +a.pagenav-item__link, .pagenav-item__link a + text-decoration: none + +.pagenav__list-item + position: relative + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) + margin: 5px + padding: 0 + display: grid + grid-template-areas: "label expand" + grid-template-columns: auto minmax(0, auto) + flex: 0 0 auto + + & > * + grid-area: label + place-self: stretch / stretch + line-height: 20px + + &.pagenav-item__children-wrapper + grid-column: label-start / expand-end + + & > .pagenav-item__expand-label + display: flex + justify-content: center + align-items: center + grid-area: expand + background-color: white + transition: background-color 0.2s ease + padding: 6px 10px + cursor: pointer + + .fas + line-height: 20px + + &:hover + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.9) + + & > .pagenav-item__expand-label + background-color: var(--color-grey-light) + + .pagenav-item__link:hover ~ .pagenav-item__expand-label + background-color: white + + .pagenav-item__expand-radio:checked ~ .pagenav-item__expand-label + background-color: var(--color-grey-light) + + .pagenav-secondary & + grid-template-areas: "expand" + & > .pagenav-item__children-wrapper + grid-column: exand-start / exand-end + + +.pagenav-item__children + flex: 1 0 auto + list-style: none + margin: 0 + padding: 0 + display: grid + grid-template-rows: auto + justify-items: stretch + grid-auto-columns: max-content + + .pagenav-item__link + max-width: 250px + + & > li + display: flex + + & > * + flex: 1 0 auto + +.pagenav-item__close-label + display: none + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) + padding: 6px 10px + transition: all 0.2s ease + position: absolute + top: 100% + right: 0 + background-color: white + z-index: -1 + cursor: pointer + + .fas + transition: all 0.2s ease + opacity: 0.5 + transform: rotate(0.5turn) + line-height: 20px + + &:hover + background-color: var(--color-grey-light) + .fas + transform: rotate(0) + opacity: 1 + +.pagenav-item__children-wrapper + display: none + position: absolute + right: 0 + top: 100% + background-color: white + z-index: 21 + margin: 0 + padding: 0 + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) + + .pagenav-item__expand-radio:checked ~ &, .pagenav-item__expand-label:hover ~ &, &:hover + display: flex + + .pagenav__list-item:hover & + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.9) + + .pagenav-item__expand-radio:checked ~ & + .pagenav-item__close-label:not(.pagenav-item__close-label--hidden) + display: block + +/* .pagenav__link-wrapper +/* flex: 1 +/* padding: 10px 10px 12px +/* text-decoration: none !important + +/* &:hover +/* background-color: var(--color-grey-light) + +/* @media (max-width: 1024px) +/* .pagenav +/* flex-direction: column + +/* @media (min-width: 1025px) +/* .pagenav-secondary +/* position: relative +/* overflow: visible +/* padding-top: 10px + +/* &::after +/* content: '\2026' +/* display: inline-block +/* padding: 10px 10px 12px +/* width: 40px +/* box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.6) +/* box-sizing: border-box +/* text-align: center +/* transition: box-shadow 0.2s ease + +/* &:hover +/* &::after +/* box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.8) + +/* .pagenav-secondary__list +/* display: block + +/* .pagenav-secondary__list +/* position: absolute +/* display: none +/* right: 0 +/* top: 50px +/* width: 250px +/* background-color: white +/* box-shadow: 0 0 6px 3px var(--color-grey-light) +/* z-index: 18 + +/* .pagenav__list-item--secondary +/* display: flex +/* box-shadow: none +/* margin: 0 + +/* &:hover +/* background-color: var(--color-grey-light) diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index 00f3e5ec0..ebcb76c5b 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -11,6 +11,7 @@ import { Modal } from './modal/modal'; import { Tooltip } from './tooltips/tooltips'; import { CourseTeaser } from './course-teaser/course-teaser'; import { NavbarUtils } from './navbar/navbar'; +import { PageActionsUtils } from './pageactions/pageactions'; import { HideColumns } from './hide-columns/hide-columns'; export const Utils = [ @@ -28,5 +29,6 @@ export const Utils = [ Tooltip, CourseTeaser, ...NavbarUtils, + ...PageActionsUtils, HideColumns, ]; diff --git a/records.json b/records.json index f65dcfbb3..ebc168844 100644 --- a/records.json +++ b/records.json @@ -765,5 +765,18 @@ "usedIds": [] } } + ], + "mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--6-1!node_modules/postcss-loader/src/index.js??ref--6-2!node_modules/resolve-url-loader/index.js??ref--6-3!node_modules/sass-loader/dist/cjs.js??ref--6-4!frontend/src/utils/pageactions/pageactions.sass": [ + { + "modules": { + "byIdentifier": {}, + "usedIds": {} + }, + "chunks": { + "byName": {}, + "bySource": {}, + "usedIds": [] + } + } ] } \ No newline at end of file diff --git a/src/Foundation.hs b/src/Foundation.hs index f5db6a93d..e99edcb05 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -78,7 +78,7 @@ import Handler.Utils.ExamOffice.ExternalExam import Handler.Utils.Profile import Handler.Utils.Routes import Utils.Form --- import Utils.Sheet +import Utils.Sheet import Utils.SystemMessage import Text.Shakespeare.Text (st) @@ -117,6 +117,16 @@ instance RenderMessage UniWorX (UnsupportedAuthPredicate AuthTag (Route UniWorX) mr = renderMessage f ls (pieces, _) = renderRoute route +data NavQuickView + = NavQuickViewFavourite + | NavQuickViewPageActionSecondary + deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) +instance Universe NavQuickView +instance Finite NavQuickView + +navQuick :: NavQuickView -> (NavQuickView -> Any) +navQuick x x' = Any $ x == x' + data NavType = NavTypeLink { navModal :: Bool @@ -140,7 +150,7 @@ data NavLink = forall msg route. (RenderMessage UniWorX msg, HasRoute UniWorX ro , navRoute :: route , navAccess' :: Handler Bool , navType :: NavType - , navQuick :: Bool + , navQuick' :: NavQuickView -> Any , navForceActive :: Bool } @@ -1670,9 +1680,7 @@ siteLayout' headingOverride widget = do -> let courseRoute = CourseR courseTerm courseSchool courseShorthand CShowR favouriteReason = fromMaybe FavouriteCurrent mFavourite in do - items''' <- pageActions courseRoute - items'' <- catMaybes <$> mapM (runMaybeT . navAccess) items''' - items' <- filterM navLinkAccess $ items'' ^.. typesUsing @NavChildren @NavLink . filtered navQuick + items' <- pageQuickActions NavQuickViewFavourite courseRoute items <- forM items' $ \n -> (n,) <$> toTextUrl n return (c, courseRoute, items, favouriteReason) @@ -1720,7 +1728,7 @@ siteLayout' headingOverride widget = do -- you to use normal widget features in default-layout. navWidget :: (Nav, Text, Maybe Text, [(NavLink, Text, Text)]) -> Widget - navWidget (n, navIdent, navRoute', _navChildren') = case n of + navWidget (n, navIdent, navRoute', navChildren') = case n of NavHeader{ navLink = navLink@NavLink{..}, .. } | NavTypeLink{..} <- navType , navModal @@ -1735,18 +1743,24 @@ siteLayout' headingOverride widget = do ident = navIdent in $(widgetFile "widgets/navbar/item") NavPageActionPrimary{ navLink = navLink@NavLink{..}, .. } - | NavTypeLink{..} <- navType - , navModal - -> customModal Modal - { modalTriggerId = Just navIdent - , modalId = Nothing - , modalTrigger = \(Just route) ident -> $(widgetFile "widgets/pageaction/primary") - , modalContent = Left $ SomeRoute navLink - } - | NavTypeLink{} <- navType - -> let route = navRoute' - ident = navIdent - in $(widgetFile "widgets/pageaction/primary") + -> let pWidget + | NavTypeLink{..} <- navType + , navModal + = customModal Modal + { modalTriggerId = Just navIdent + , modalId = Nothing + , modalTrigger = \(Just route) ident -> $(widgetFile "widgets/pageaction/primary") + , modalContent = Left $ SomeRoute navLink + } + | NavTypeLink{} <- navType + = let route = navRoute' + ident = navIdent + in $(widgetFile "widgets/pageaction/primary") + | otherwise + = error "not implemented" + sWidgets = navChildren' + & map (\(l, i, r) -> navWidget (NavPageActionSecondary l, i, Just r, [])) + in $(widgetFile "widgets/pageaction/primary-wrapper") NavPageActionSecondary{ navLink = navLink@NavLink{..}, .. } | NavTypeLink{..} <- navType , navModal @@ -1821,10 +1835,13 @@ siteLayout' headingOverride widget = do hasPageActions = hasPrimaryPageActions || hasSecondaryPageActions hasSecondaryPageActions = has (folded . _1 . _NavPageActionSecondary) nav hasPrimaryPageActions = has (folded . _1 . _NavPageActionPrimary ) nav + hasPrimarySubActions = has (folded . _1 . filtered (is _NavPageActionPrimary) . _navChildren . folded) nav contentRibbon :: Maybe Widget contentRibbon = fmap toWidget appRibbon isNavHeaderContainer = has $ _1 . _NavHeaderContainer + isPageActionPrimary = has $ _1 . _NavPageActionPrimary + isPageActionSecondary = has $ _1 . _NavPageActionSecondary MsgRenderer mr <- getMsgRenderer let @@ -2171,7 +2188,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AuthR LogoutR , navAccess' = is _Just <$> maybeAuthId , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2183,7 +2200,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AuthR LoginR , navAccess' = is _Nothing <$> maybeAuthId , navType = NavTypeLink { navModal = True } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2195,7 +2212,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = ProfileR , navAccess' = is _Just <$> maybeAuthId , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2212,7 +2229,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the { navMethod = POST , navData = [(toPathPiece PostLanguage, lang)] } - , navQuick = False + , navQuick' = mempty , navForceActive = lang == activeLang } @@ -2235,7 +2252,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = (HelpR, [(toPathPiece GetReferer, toPathPiece currentRoute) | let Just currentRoute = mCurrentRoute ]) , navAccess' = return True , navType = NavTypeLink { navModal = True } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2244,7 +2261,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = LegalR :#: ("data-protection" :: Text) , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return $ NavFooter NavLink @@ -2252,7 +2269,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = LegalR :#: ("terms-of-use" :: Text) , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return $ NavFooter NavLink @@ -2260,7 +2277,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = LegalR :#: ("copyright" :: Text) , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return $ NavFooter NavLink @@ -2268,7 +2285,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = LegalR :#: ("imprint" :: Text) , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return $ NavFooter NavLink @@ -2276,7 +2293,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = InfoR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return $ NavFooter NavLink @@ -2284,7 +2301,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = GlossaryR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , return NavHeader @@ -2295,7 +2312,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = NewsR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2307,7 +2324,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = CourseListR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2319,7 +2336,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = CorrectionsR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2331,7 +2348,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = ExamOfficeR EOExamsR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } } @@ -2345,7 +2362,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = UsersR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2353,7 +2370,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = SchoolListR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2361,7 +2378,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AdminFeaturesR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2369,7 +2386,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = MessageListR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2377,7 +2394,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AdminErrMsgR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2385,7 +2402,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AdminTestR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } ] @@ -2400,7 +2417,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = CourseNewR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2408,7 +2425,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = EExamListR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2416,7 +2433,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = TermShowR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2424,7 +2441,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = AllocationListR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } , NavLink @@ -2432,7 +2449,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navRoute = InfoLecturerR , navAccess' = return True , navType = NavTypeLink { navModal = False } - , navQuick = False + , navQuick' = mempty , navForceActive = False } ] @@ -2440,26 +2457,267 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the ] --- pageActions :: (MonadHandler m, HandlerSite m ~ UniWorX) => Route UniWorX -> m [Nav] -pageActions :: _ +pageActions :: ( MonadHandler m + , HandlerSite m ~ UniWorX + , MonadCatch m + ) + => Route UniWorX -> m [Nav] +pageActions NewsR = return + [ NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuOpenCourses + , navRoute = (CourseListR, [("courses-openregistration", toPathPiece True)]) + , navAccess' = return True + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + , navChildren = [] + } + , NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuOpenAllocations + , navRoute = (AllocationListR, [("allocations-active", toPathPiece True)]) + , navAccess' = return True + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + , navChildren = [] + } + ] +pageActions (CourseR tid ssh csh CShowR) = do + sheetListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh SheetListR + + return + [ NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuMaterialList + , navRoute = CourseR tid ssh csh MaterialListR + , navAccess' = + let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh MaterialNewR -- Always show for lecturers to create new material + materialAccess mnm = hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR -- otherwise show only if the user can see at least one of the contents + existsVisible = do + matNames <- E.select . E.from $ \(course `E.InnerJoin` material) -> do + E.on $ course E.^. CourseId E.==. material E.^. MaterialCourse + E.where_ $ course E.^. CourseTerm E.==. E.val tid + E.&&. course E.^. CourseSchool E.==. E.val ssh + E.&&. course E.^. CourseShorthand E.==. E.val csh + return $ material E.^. MaterialName + anyM matNames (materialAccess . E.unValue) + in runDB $ lecturerAccess `or2M` existsVisible + , navType = NavTypeLink { navModal = False } + , navQuick' = navQuick NavQuickViewFavourite + , navForceActive = False + } + , navChildren = [] -- TODO: MaterialNewR + } + , NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuSheetList + , navRoute = CourseR tid ssh csh SheetListR + , navAccess' = + let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh SheetNewR -- Always show for lecturers to create new sheets + sheetAccess shn = hasReadAccessTo $ CSheetR tid ssh csh shn SShowR -- othwerwise show only if the user can see at least one of the contents + existsVisible = do + sheetNames <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do + E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse + E.where_ $ course E.^. CourseTerm E.==. E.val tid + E.&&. course E.^. CourseSchool E.==. E.val ssh + E.&&. course E.^. CourseShorthand E.==. E.val csh + return $ sheet E.^. SheetName + anyM sheetNames $ sheetAccess . E.unValue + in runDB $ lecturerAccess `or2M` existsVisible + , navType = NavTypeLink { navModal = False } + , navQuick' = navQuick NavQuickViewFavourite + , navForceActive = False + } + , navChildren = sheetListSecondary + } + , NavPageActionSecondary + { navLink = NavLink + { navLabel = MsgMenuCourseDelete + , navRoute = CourseR tid ssh csh CDeleteR + , navAccess' = return True + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + } -- TODO + ] +pageActions (CourseR tid ssh csh SheetListR) = return + [ NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuSheetCurrent + , navRoute = CourseR tid ssh csh SheetCurrentR + , navAccess' = runDB . maybeT (return False) $ + True <$ MaybeT (sheetCurrent tid ssh csh) + , navType = NavTypeLink { navModal = False } + , navQuick' = navQuick NavQuickViewFavourite <> navQuick NavQuickViewPageActionSecondary + , navForceActive = False + } + , navChildren = [] + } -- TODO + ] pageActions _ = return [] --- pageActions (NewsR) = +-- pageActions (CourseR tid ssh csh CShowR) = -- [ MenuItem -- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuOpenCourses +-- , menuItemLabel = MsgMenuMaterialList -- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute (CourseListR, [("courses-openregistration", "True")]) +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh MaterialListR +-- , menuItemModal = False +-- , menuItemAccessCallback' = +-- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh MaterialNewR -- Always show for lecturers to create new material +-- materialAccess mnm = hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR -- otherwise show only if the user can see at least one of the contents +-- existsVisible = do +-- matNames <- E.select . E.from $ \(course `E.InnerJoin` material) -> do +-- E.on $ course E.^. CourseId E.==. material E.^. MaterialCourse +-- E.where_ $ course E.^. CourseTerm E.==. E.val tid +-- E.&&. course E.^. CourseSchool E.==. E.val ssh +-- E.&&. course E.^. CourseShorthand E.==. E.val csh +-- return $ material E.^. MaterialName +-- anyM matNames (materialAccess . E.unValue) +-- in runDB $ lecturerAccess `or2M` existsVisible +-- } +-- , MenuItem +-- { menuItemType = PageActionPrime +-- , menuItemLabel = MsgMenuSheetList +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetListR +-- , menuItemModal = False +-- , menuItemAccessCallback' = +-- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh SheetNewR -- Always show for lecturers to create new sheets +-- sheetAccess shn = hasReadAccessTo $ CSheetR tid ssh csh shn SShowR -- othwerwise show only if the user can see at least one of the contents +-- existsVisible = do +-- sheetNames <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do +-- E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse +-- E.where_ $ course E.^. CourseTerm E.==. E.val tid +-- E.&&. course E.^. CourseSchool E.==. E.val ssh +-- E.&&. course E.^. CourseShorthand E.==. E.val csh +-- return $ sheet E.^. SheetName +-- anyM sheetNames $ sheetAccess . E.unValue +-- in runDB $ lecturerAccess `or2M` existsVisible +-- } +-- ] ++ pageActions (CourseR tid ssh csh SheetListR) ++ +-- [ MenuItem +-- { menuItemType = PageActionPrime +-- , menuItemLabel = MsgMenuTutorialList +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CTutorialListR -- , menuItemModal = False -- , menuItemAccessCallback' = return True -- } -- , MenuItem -- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuOpenAllocations +-- , menuItemLabel = MsgMenuExamList -- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute (AllocationListR, [("allocations-active", "True")]) +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamListR +-- , menuItemModal = False +-- , menuItemAccessCallback' = +-- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh CExamNewR +-- examAccess examn = hasReadAccessTo $ CExamR tid ssh csh examn EShowR +-- existsVisible = do +-- examNames <- E.select . E.from $ \(course `E.InnerJoin` exam) -> do +-- E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse +-- E.where_ $ course E.^. CourseTerm E.==. E.val tid +-- E.&&. course E.^. CourseSchool E.==. E.val ssh +-- E.&&. course E.^. CourseShorthand E.==. E.val csh +-- return $ exam E.^. ExamName +-- anyM examNames $ examAccess . E.unValue +-- in runDB $ lecturerAccess `or2M` existsVisible +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseApplications +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CApplicationsR +-- , menuItemModal = False +-- , menuItemAccessCallback' = +-- let courseWhere course = course <$ do +-- E.where_ $ course E.^. CourseTerm E.==. E.val tid +-- E.&&. course E.^. CourseSchool E.==. E.val ssh +-- E.&&. course E.^. CourseShorthand E.==. E.val csh +-- existsApplications = E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do +-- E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse +-- void $ courseWhere course +-- courseApplications = fmap (any E.unValue) . E.select . E.from $ \course -> do +-- void $ courseWhere course +-- return $ course E.^. CourseApplicationsRequired +-- courseAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse) -> do +-- E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse +-- void $ courseWhere course +-- in runDB $ courseAllocation `or2M` courseApplications `or2M` existsApplications +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseMembers +-- , menuItemIcon = Just "user-graduate" +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CUsersR +-- , menuItemModal = False +-- , menuItemAccessCallback' = do +-- now <- liftIO getCurrentTime +-- let courseWhere course = course <$ do +-- E.where_ $ course E.^. CourseTerm E.==. E.val tid +-- E.&&. course E.^. CourseSchool E.==. E.val ssh +-- E.&&. course E.^. CourseShorthand E.==. E.val csh +-- hasActiveAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse `E.InnerJoin` allocation) -> do +-- E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation +-- E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId +-- void $ courseWhere course +-- E.where_ $ E.maybe E.false (E.<=. E.val now) (allocation E.^. AllocationRegisterByStaffFrom) +-- E.||. E.maybe E.false (E.<=. E.val now) (allocation E.^. AllocationRegisterByCourse) +-- hasParticipants = E.selectExists . E.from $ \(course `E.InnerJoin` courseParticipant) -> do +-- E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse +-- void $ courseWhere course +-- runDB $ (not <$> hasActiveAllocation) `or2M` hasParticipants +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseCommunication +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CCommR -- , menuItemModal = False -- , menuItemAccessCallback' = return True -- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseEdit +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CEditR +-- , menuItemModal = False +-- , menuItemAccessCallback' = return True +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseClone +-- , menuItemIcon = Just "copy" +-- , menuItemRoute = SomeRoute (CourseNewR, [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)]) +-- , menuItemModal = False +-- , menuItemAccessCallback' = return True +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseDelete +-- , menuItemIcon = Just "trash" +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CDeleteR +-- , menuItemModal = False +-- , menuItemAccessCallback' = return True +-- } +-- , MenuItem +-- { menuItemType = PageActionSecondary +-- , menuItemLabel = MsgMenuCourseExamOffice +-- , menuItemIcon = Nothing +-- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamOfficeR +-- , menuItemModal = True +-- , menuItemAccessCallback' = do +-- uid <- requireAuthId +-- runDB $ do +-- cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh +-- E.selectExists $ do +-- (_school, isForced) <- courseExamOfficeSchools (E.val uid) (E.val cid) +-- E.where_ $ E.not_ isForced +-- } -- ] -- pageActions (AdminR) = -- [ MenuItem @@ -2746,165 +3004,6 @@ pageActions _ = return [] -- , menuItemAccessCallback' = return True -- } -- ] --- pageActions (CourseR tid ssh csh CShowR) = --- [ MenuItem --- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuMaterialList --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh MaterialListR --- , menuItemModal = False --- , menuItemAccessCallback' = --- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh MaterialNewR -- Always show for lecturers to create new material --- materialAccess mnm = hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR -- otherwise show only if the user can see at least one of the contents --- existsVisible = do --- matNames <- E.select . E.from $ \(course `E.InnerJoin` material) -> do --- E.on $ course E.^. CourseId E.==. material E.^. MaterialCourse --- E.where_ $ course E.^. CourseTerm E.==. E.val tid --- E.&&. course E.^. CourseSchool E.==. E.val ssh --- E.&&. course E.^. CourseShorthand E.==. E.val csh --- return $ material E.^. MaterialName --- anyM matNames (materialAccess . E.unValue) --- in runDB $ lecturerAccess `or2M` existsVisible --- } --- , MenuItem --- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuSheetList --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetListR --- , menuItemModal = False --- , menuItemAccessCallback' = --- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh SheetNewR -- Always show for lecturers to create new sheets --- sheetAccess shn = hasReadAccessTo $ CSheetR tid ssh csh shn SShowR -- othwerwise show only if the user can see at least one of the contents --- existsVisible = do --- sheetNames <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do --- E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse --- E.where_ $ course E.^. CourseTerm E.==. E.val tid --- E.&&. course E.^. CourseSchool E.==. E.val ssh --- E.&&. course E.^. CourseShorthand E.==. E.val csh --- return $ sheet E.^. SheetName --- anyM sheetNames $ sheetAccess . E.unValue --- in runDB $ lecturerAccess `or2M` existsVisible --- } --- ] ++ pageActions (CourseR tid ssh csh SheetListR) ++ --- [ MenuItem --- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuTutorialList --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CTutorialListR --- , menuItemModal = False --- , menuItemAccessCallback' = return True --- } --- , MenuItem --- { menuItemType = PageActionPrime --- , menuItemLabel = MsgMenuExamList --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamListR --- , menuItemModal = False --- , menuItemAccessCallback' = --- let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh CExamNewR --- examAccess examn = hasReadAccessTo $ CExamR tid ssh csh examn EShowR --- existsVisible = do --- examNames <- E.select . E.from $ \(course `E.InnerJoin` exam) -> do --- E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse --- E.where_ $ course E.^. CourseTerm E.==. E.val tid --- E.&&. course E.^. CourseSchool E.==. E.val ssh --- E.&&. course E.^. CourseShorthand E.==. E.val csh --- return $ exam E.^. ExamName --- anyM examNames $ examAccess . E.unValue --- in runDB $ lecturerAccess `or2M` existsVisible --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseApplications --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CApplicationsR --- , menuItemModal = False --- , menuItemAccessCallback' = --- let courseWhere course = course <$ do --- E.where_ $ course E.^. CourseTerm E.==. E.val tid --- E.&&. course E.^. CourseSchool E.==. E.val ssh --- E.&&. course E.^. CourseShorthand E.==. E.val csh --- existsApplications = E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do --- E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse --- void $ courseWhere course --- courseApplications = fmap (any E.unValue) . E.select . E.from $ \course -> do --- void $ courseWhere course --- return $ course E.^. CourseApplicationsRequired --- courseAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse) -> do --- E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse --- void $ courseWhere course --- in runDB $ courseAllocation `or2M` courseApplications `or2M` existsApplications --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseMembers --- , menuItemIcon = Just "user-graduate" --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CUsersR --- , menuItemModal = False --- , menuItemAccessCallback' = do --- now <- liftIO getCurrentTime --- let courseWhere course = course <$ do --- E.where_ $ course E.^. CourseTerm E.==. E.val tid --- E.&&. course E.^. CourseSchool E.==. E.val ssh --- E.&&. course E.^. CourseShorthand E.==. E.val csh --- hasActiveAllocation = E.selectExists . E.from $ \(course `E.InnerJoin` allocationCourse `E.InnerJoin` allocation) -> do --- E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation --- E.on $ allocationCourse E.^. AllocationCourseCourse E.==. course E.^. CourseId --- void $ courseWhere course --- E.where_ $ E.maybe E.false (E.<=. E.val now) (allocation E.^. AllocationRegisterByStaffFrom) --- E.||. E.maybe E.false (E.<=. E.val now) (allocation E.^. AllocationRegisterByCourse) --- hasParticipants = E.selectExists . E.from $ \(course `E.InnerJoin` courseParticipant) -> do --- E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse --- void $ courseWhere course --- runDB $ (not <$> hasActiveAllocation) `or2M` hasParticipants --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseCommunication --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CCommR --- , menuItemModal = False --- , menuItemAccessCallback' = return True --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseEdit --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CEditR --- , menuItemModal = False --- , menuItemAccessCallback' = return True --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseClone --- , menuItemIcon = Just "copy" --- , menuItemRoute = SomeRoute (CourseNewR, [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)]) --- , menuItemModal = False --- , menuItemAccessCallback' = return True --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseDelete --- , menuItemIcon = Just "trash" --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CDeleteR --- , menuItemModal = False --- , menuItemAccessCallback' = return True --- } --- , MenuItem --- { menuItemType = PageActionSecondary --- , menuItemLabel = MsgMenuCourseExamOffice --- , menuItemIcon = Nothing --- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamOfficeR --- , menuItemModal = True --- , menuItemAccessCallback' = do --- uid <- requireAuthId --- runDB $ do --- cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh --- E.selectExists $ do --- (_school, isForced) <- courseExamOfficeSchools (E.val uid) (E.val cid) --- E.where_ $ E.not_ isForced --- } --- ] -- pageActions (CourseR tid ssh csh CCorrectionsR) = -- [ MenuItem -- { menuItemType = PageActionPrime @@ -3464,6 +3563,16 @@ pageActions _ = return [] -- ] -- pageActions _ = [] +pageQuickActions :: ( MonadCatch m + , MonadHandler m + , HandlerSite m ~ UniWorX + ) + => NavQuickView -> Route UniWorX -> m [NavLink] +pageQuickActions qView route = do + items'' <- pageActions route + items' <- catMaybes <$> mapM (runMaybeT . navAccess) items'' + filterM navLinkAccess $ items' ^.. typesUsing @NavChildren @NavLink . filtered (getAny . ($ qView) . navQuick') + i18nHeading :: (MonadWidget m, RenderMessage site msg, HandlerSite m ~ site) => msg -> m () i18nHeading msg = liftWidget $ toWidget =<< getMessageRender <*> pure msg diff --git a/src/Utils/Icon.hs b/src/Utils/Icon.hs index 309857823..37b29ec16 100644 --- a/src/Utils/Icon.hs +++ b/src/Utils/Icon.hs @@ -69,7 +69,7 @@ data Icon | IconNotificationError | IconFavourite | IconLanguage - | IconNavContainerClose + | IconNavContainerClose | IconPageActionChildrenClose | IconMenuNews | IconMenuHelp | IconMenuProfile @@ -80,62 +80,68 @@ data Icon | IconMenuCorrections | IconMenuExams | IconMenuAdmin + | IconPageActionPrimaryExpand | IconPageActionSecondary + | IconBreadcrumbSeparator deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic, Typeable) iconText :: Icon -> Text iconText = \case - IconNew -> "seedling" - IconOK -> "check" - IconNotOK -> "times" - IconWarning -> "exclamation" - IconProblem -> "bolt" - IconVisible -> "eye" - IconInvisible -> "eye-slash" - IconCourse -> "graduation-cap" - IconEnrolTrue -> "user-plus" - IconEnrolFalse -> "user-slash" - IconPlanned -> "cog" - IconAnnounce -> "bullhorn" - IconExam -> "poll-h" - IconExamRegisterTrue -> "calendar-check" - IconExamRegisterFalse -> "calendar-times" - IconCommentTrue -> "comment-alt" - IconCommentFalse -> "comment-alt-slash" - IconLink -> "link" - IconFileDownload -> "file-download" - IconFileUpload -> "file-upload" - IconFileZip -> "file-archive" - IconFileCSV -> "file-csv" - IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar) - IconSFTHint -> "life-ring" -- for SheetFileType only - IconSFTSolution -> "exclamation-circle" -- for SheetFileType only - IconSFTMarking -> "check-circle" -- for SheetFileType only - IconEmail -> "envelope" - IconRegisterTemplate -> "file-alt" - IconApplyTrue -> "file-alt" - IconApplyFalse -> "trash" - IconNoCorrectors -> "user-slash" - IconApplicationVeto -> "times" - IconApplicationFiles -> "file-alt" - IconTooltipDefault -> "question-circle" - IconNotificationSuccess -> "check-circle" - IconNotificationInfo -> "info-circle" - IconNotificationWarning -> "exclamation-circle" - IconNotificationError -> "exclamation-triangle" - IconFavourite -> "star" - IconLanguage -> "flag-alt" - IconNavContainerClose -> "chevron-up" - IconMenuNews -> "megaphone" - IconMenuHelp -> "question" - IconMenuProfile -> "cogs" - IconMenuLogin -> "sign-in-alt" - IconMenuLogout -> "sign-out-alt" - IconBreadcrumbsHome -> "home" - IconMenuExtra -> "ellipsis-h" - IconMenuCourseList -> "graduation-cap" - IconMenuCorrections -> "check" - IconMenuExams -> "poll-h" - IconMenuAdmin -> "screwdriver" + IconNew -> "seedling" + IconOK -> "check" + IconNotOK -> "times" + IconWarning -> "exclamation" + IconProblem -> "bolt" + IconVisible -> "eye" + IconInvisible -> "eye-slash" + IconCourse -> "graduation-cap" + IconEnrolTrue -> "user-plus" + IconEnrolFalse -> "user-slash" + IconPlanned -> "cog" + IconAnnounce -> "bullhorn" + IconExam -> "poll-h" + IconExamRegisterTrue -> "calendar-check" + IconExamRegisterFalse -> "calendar-times" + IconCommentTrue -> "comment-alt" + IconCommentFalse -> "comment-alt-slash" + IconLink -> "link" + IconFileDownload -> "file-download" + IconFileUpload -> "file-upload" + IconFileZip -> "file-archive" + IconFileCSV -> "file-csv" + IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar) + IconSFTHint -> "life-ring" -- for SheetFileType only + IconSFTSolution -> "exclamation-circle" -- for SheetFileType only + IconSFTMarking -> "check-circle" -- for SheetFileType only + IconEmail -> "envelope" + IconRegisterTemplate -> "file-alt" + IconApplyTrue -> "file-alt" + IconApplyFalse -> "trash" + IconNoCorrectors -> "user-slash" + IconApplicationVeto -> "times" + IconApplicationFiles -> "file-alt" + IconTooltipDefault -> "question-circle" + IconNotificationSuccess -> "check-circle" + IconNotificationInfo -> "info-circle" + IconNotificationWarning -> "exclamation-circle" + IconNotificationError -> "exclamation-triangle" + IconFavourite -> "star" + IconLanguage -> "flag-alt" + IconNavContainerClose -> "chevron-up" + IconPageActionChildrenClose -> "chevron-up" + IconMenuNews -> "megaphone" + IconMenuHelp -> "question" + IconMenuProfile -> "cogs" + IconMenuLogin -> "sign-in-alt" + IconMenuLogout -> "sign-out-alt" + IconBreadcrumbsHome -> "home" + IconMenuExtra -> "ellipsis-h" + IconMenuCourseList -> "graduation-cap" + IconMenuCorrections -> "check" + IconMenuExams -> "poll-h" + IconMenuAdmin -> "screwdriver" + IconPageActionPrimaryExpand -> "bars" + IconPageActionSecondary -> "ellipsis-h" + IconBreadcrumbSeparator -> "angle-right" instance Universe Icon instance Finite Icon diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index b59084d7e..2f8a66ebb 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -32,7 +32,7 @@ $if not isModal #{snd back} --> ^{headline} - $if not isModal && hasPageActions + $if hasPageActions ^{pageaction} diff --git a/templates/widgets/breadcrumbs/breadcrumbs.hamlet b/templates/widgets/breadcrumbs/breadcrumbs.hamlet index 30c0b2acd..320f42b8a 100644 --- a/templates/widgets/breadcrumbs/breadcrumbs.hamlet +++ b/templates/widgets/breadcrumbs/breadcrumbs.hamlet @@ -6,7 +6,12 @@ $newline never $forall (bcRoute, bcTitle, hasAccess) <- parents
  • $if hasAccess - #{bcTitle} + + #{bcTitle} $else - #{bcTitle} -
  • #{title} + + #{bcTitle} +
  • + #{iconBreadcrumbSeparator} +
  • + #{title} diff --git a/templates/widgets/pageaction/pageaction.hamlet b/templates/widgets/pageaction/pageaction.hamlet index f5d4b3600..a7bba4210 100644 --- a/templates/widgets/pageaction/pageaction.hamlet +++ b/templates/widgets/pageaction/pageaction.hamlet @@ -1,13 +1,19 @@ $newline never -
    - $if hasPrimaryPageActions -
    - $forall n@(NavPageActionPrimary{}, _, _, _) <- nav -
    - ^{navWidget n} +$if hasSecondaryPageActions || hasPrimarySubActions + +