Lorem ipsum
.heated
--hotness: 0
@@ -625,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
@@ -636,15 +652,21 @@ 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
-@media (max-width: 768px)
+ .navbar__container-radio:checked ~ &
+ top: calc(84px + var(--header-height))
+
+@media (max-width: 768px), (max-height: 500px)
.ribbon
top: calc(20px + var(--header-height-collapsed))
right: -83px
transform: rotate(45deg) scale(0.6)
+ .navbar__container-radio:checked ~ &
+ top: calc(50px + var(--header-height-collapsed))
+
#admin-studyterms
select, option, input
min-width: 50px
@@ -981,54 +1003,63 @@ th, td
.breadcrumbs__container
position: relative
color: var(--color-lightwhite)
- padding: 4px 13px
+ padding: 4px 20px 4px 40px
background-color: var(--color-dark)
- line-height: 30px
+
+ a
+ color: var(--color-lightwhite)
@media (min-width: 426px)
.breadcrumbs__container
- padding: 7px 20px
+ padding: 7px 20px 7px 40px
@media (min-width: 769px)
.breadcrumbs__container
padding: 7px 40px
-.breadcrumbs__link
- color: var(--color-lightwhite)
+ul.breadcrumbs__list
+ display: flex
+ align-items: center
+ height: 30px
+ margin: 0 -5px
- &: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: 11px
- 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
+.breadcrumbs__item-separator
+ line-height: 0
+ opacity: 0.5
+ margin: 0 5px
+ margin-top: 1px
+
+a.breadcrumbs__home
+ position: absolute
+ left: 10px
+ top: 5px
+ width: 20px
+ height: 30px
+ opacity: 0.5
+ text-decoration: none
+ 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
@@ -1096,10 +1127,15 @@ th, td
overflow: auto
.footer
+ display: flex
+ flex-flow: row wrap
+ justify-content: center
+ align-items: baseline
+ align-content: flex-start
text-align: center
padding: 20px
position: relative
- margin: 40px 0
+ margin: 40px 0 0 0
&::before
content: ''
@@ -1110,9 +1146,9 @@ th, td
height: 2px
background-color: var(--color-grey-light)
-.footer-links *
+.footer-links > *
margin-right: 0.5em
- display: inline-block
+ display: block
&:last
margin-right: 0
@@ -1138,70 +1174,3 @@ th, td
.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 b9beb15b4..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: ''
@@ -85,6 +75,7 @@
padding: 10px 13px
margin: 0
border-bottom: 1px solid var(--color-grey)
+ height: 44px
.asidenav-term-identifier--long
display: inherit
@@ -232,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)
@@ -269,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
@@ -277,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/check-all/check-all.js b/frontend/src/utils/check-all/check-all.js
index d196aae51..7922cd3ee 100644
--- a/frontend/src/utils/check-all/check-all.js
+++ b/frontend/src/utils/check-all/check-all.js
@@ -5,7 +5,7 @@ const CHECKBOX_SELECTOR = '[type="checkbox"]';
const CHECK_ALL_INITIALIZED_CLASS = 'check-all--initialized';
@Utility({
- selector: 'table',
+ selector: 'table:not([uw-no-check-all])',
})
export class CheckAll {
diff --git a/frontend/src/utils/exam-correct/exam-correct.js b/frontend/src/utils/exam-correct/exam-correct.js
index 7e99ac5e4..2731d66c9 100644
--- a/frontend/src/utils/exam-correct/exam-correct.js
+++ b/frontend/src/utils/exam-correct/exam-correct.js
@@ -23,6 +23,7 @@ const EXAM_CORRECT_USER_INPUT_CANDIDATES_ID = 'exam-correct__user-candidates';
const EXAM_CORRECT_INPUT_BODY_ID = 'exam-correct__new';
const EXAM_CORRECT_USER_ATTR = 'exam-correct--user-id';
const EXAM_CORRECT_USER_DNAME_ATTR = 'exam-correct--user-dname';
+const EXAM_CORRECT_STATUS_CELL_CLASS = 'exam-correct--status-cell';
const STATUS = {
NONE: null,
@@ -162,7 +163,7 @@ export class ExamCorrect {
}).then(
(response) => response.json()
).then(
- (response) => this._processResponse(response, body.user)
+ (response) => this._processResponse(body, response, body.user)
).catch((error) => {
console.error('Error while validating user input', error);
});
@@ -203,14 +204,18 @@ export class ExamCorrect {
}
}
+ const result = this._resultSelect.value !== 'none' && this._resultSelect.value;
+
// abort send if there are no results (after validation)
if (Object.keys(results).length <= 0) return;
const rowInfo = {
- users: [userId || user],
+ users: [user],
results: results,
status: STATUS.LOADING,
};
+ if (results) rowInfo.results = results;
+ if (result) rowInfo.result = result === 'delete' ? null : result;
this._addRow(rowInfo);
// clear inputs on validation success
@@ -219,8 +224,11 @@ export class ExamCorrect {
const body = {
user: userId || user,
- results: results,
};
+ if (results) body.results = results;
+ if (result) body.grade = result === 'result' ? this._resultGradeSelect.value : (result === 'delete' ? null : result);
+
+ console.log('request body', body);
this._app.httpClient.post({
url: EXAM_CORRECT_URL_POST,
@@ -229,13 +237,13 @@ export class ExamCorrect {
}).then(
(response) => response.json()
).then(
- (response) => this._processResponse(response, body.user, results)
+ (response) => this._processResponse(body, response, user, undefined, { results: results, result: result })
).catch((error) => {
console.error('Error while processing response', error);
});
}
- _processResponse(response, user, results) {
+ _processResponse(request, response, user, targetRow, ...results) {
if (response) {
if (response.status === 'no-op') {
if (response.users) {
@@ -292,19 +300,22 @@ export class ExamCorrect {
let newEntry = {
users: null,
results: null,
+ result: null,
status: STATUS.FAILURE,
+ message: null,
date: null,
};
console.log('response', response);
- for (let row of [...this._element.rows]) {
+ const candidateRows = (targetRow && [targetRow]) || [...this._element.rows];
+ for (let row of candidateRows) {
let userElem = row.cells.item(this._cIndices.get('user'));
const userIdent = userElem && userElem.getAttribute(EXAM_CORRECT_USER_ATTR); // TODO use other attribute identifier
if (userIdent === user) {
+ console.log('response-update', row);
let status = STATUS.FAILURE;
switch (response.status) {
- // TODO fetch update time from response and replace
case 'success':
status = STATUS.SUCCESS;
if (response.user) {
@@ -315,24 +326,27 @@ export class ExamCorrect {
timeElem.classList.remove('exam-correct--local-time');
newEntry.users = [response.user];
newEntry.results = response.results;
+ newEntry.result = response.grade;
}
- // TODO replace results with results from response
// TODO set edit button visibility
break;
case 'ambiguous':
- // TODO show tooltip with error message
// TODO set edit button visibility
status = STATUS.AMBIGUOUS;
if (response.users) {
userElem = this._showUserList(row, response.users, results);
newEntry.users = response.users;
- newEntry.results = results;
+ newEntry.results = results.partResults;
+ newEntry.result = results.result;
}
+ newEntry.message = response.message || null;
break;
case 'failure':
status = STATUS.FAILURE;
- newEntry.users = [user];
+ newEntry.users = (response.user && [response.user]) || null;
newEntry.results = results;
+ newEntry.message = response.message || null;
+ newEntry.result = results.result;
break;
default:
// TODO show tooltip with 'invalid response'
@@ -344,6 +358,36 @@ export class ExamCorrect {
});
newEntry.status = status || STATUS.FAILURE;
newEntry.date = response.time || moment().utc().format();
+
+ const statusCell = row.querySelector(`.${EXAM_CORRECT_STATUS_CELL_CLASS}`);
+ const messageElem = statusCell.querySelector('.uw-exam-correct--message');
+ if (messageElem) {
+ statusCell.removeChild(messageElem);
+ }
+
+ if (newEntry.message) {
+ const messageElem = document.createElement('SPAN');
+ messageElem.classList.add('uw-exam-correct--message');
+ const messageText = document.createTextNode(newEntry.message);
+ messageElem.appendChild(messageText);
+ statusCell.appendChild(messageElem);
+ }
+
+ const userCell = row.querySelector('.uw-exam-correct--user-cell');
+ if (userCell && newEntry.users && newEntry.users.length === 1) {
+ const user = newEntry.users[0];
+ userCell.innerHTML = userToHTML(user);
+ userCell.setAttribute(EXAM_CORRECT_USER_ATTR, user);
+ } else if (userCell && newEntry.users) {
+ row.replaceChild(userCell, this._showUserList(row, newEntry.users, request.results));
+ }
+
+ for (let [k, v] of Object.entries(newEntry.results)) {
+ const resultCell = row.cells.item(this._cIndices.get(k));
+ if (v.result)
+ resultCell.innerHTML = v.result;
+ }
+
savedEntries.push(newEntry);
this._storageManager.save('entries', savedEntries);
return;
@@ -392,14 +436,15 @@ export class ExamCorrect {
const timeElem = row.cells.item(0);
timeElem.innerHTML = now.format(this._dateFormat);
timeElem.classList.add('exam-correct--local-time');
- const userElem = row.cells.item(1);
+ const userElem = row.cells.item(this._cIndices.get('user'));
const statusElem = row.querySelector('.exam-correct--ambiguous');
setStatus(statusElem, STATUS.LOADING);
const body = {
user: listItem.getAttribute(EXAM_CORRECT_USER_ATTR),
- results: results,
+ results: results.partResults,
+ grade: results.result,
};
this._app.httpClient.post({
@@ -408,37 +453,8 @@ export class ExamCorrect {
body: JSON.stringify(body),
}).then(
(response) => response.json()
- ).then((response) => {
- switch (response.status) {
- case 'success': {
- userElem.innerHTML = userToHTML(response.user);
- // TODO replace part results with results from server
- timeElem.innerHTML = moment(response.time).format(this._dateFormat);
- timeElem.classList.remove('exam-correct--local-time');
- setStatus(statusElem, STATUS.SUCCESS);
- const savedEntries = this._storageManager.load('entries');
- for (let i = 0; i < savedEntries.length; i++) {
- for (let user of savedEntries[i].users) {
- if (user.id === response.user.id) {
- savedEntries[i] = {
- users: [response.user],
- results: response.results,
- status: STATUS.SUCCESS,
- date: response.time,
- };
- break;
- }
- }
- }
- this._storageManager.save('entries', savedEntries);
- break;
- }
- default:
- // non-success response on request with a uuid => panic and ignore for now
- }
- }).catch((error) => {
- console.error(error);
- });
+ ).then((response) => this._processResponse(body, response, userElem.getAttribute(EXAM_CORRECT_USER_ATTR), row, { results: results.partResults, result: results.result })
+ ).catch(console.error);
}
_addRow(rowInfo) {
@@ -458,6 +474,7 @@ export class ExamCorrect {
cells.set(this._cIndices.get('date'), dateCell);
let userCell = document.createElement('TD');
+ userCell.classList.add('uw-exam-correct--user-cell');
if (!rowInfo.users || rowInfo.users.length === 0) {
console.error('Found rowInfo without users info!');
} else if (rowInfo.users.length === 1) {
@@ -465,7 +482,7 @@ export class ExamCorrect {
userCell.innerHTML = userToHTML(user);
userCell.setAttribute(EXAM_CORRECT_USER_ATTR, user);
} else {
- userCell = this._showUserList(newRow, rowInfo.users, rowInfo.results);
+ userCell = this._showUserList(newRow, rowInfo.users, { partResults: rowInfo.results, result: rowInfo.result });
}
cells.set(this._cIndices.get('user'), userCell);
@@ -483,12 +500,15 @@ export class ExamCorrect {
const resultCell = document.createElement('TD');
resultCell.colSpan = 2;
+ if (rowInfo.result)
+ resultCell.innerHTML = rowInfo.result;
cells.set(this._cIndices.get('result'), resultCell);
const statusCell = document.createElement('TD');
- const statusDiv = document.createElement('DIV');
- setStatus(statusDiv, rowInfo.status);
- statusCell.appendChild(statusDiv);
+ statusCell.classList.add(EXAM_CORRECT_STATUS_CELL_CLASS);
+ const statusSymbol = document.createElement('I');
+ setStatus(statusSymbol, rowInfo.status);
+ statusCell.appendChild(statusSymbol);
cells.set(this._cIndices.get('status'), statusCell);
for (let i = 0; i <= this._lastColumnIndex; i++) {
diff --git a/frontend/src/utils/exam-correct/exam-correct.sass b/frontend/src/utils/exam-correct/exam-correct.sass
index 95836f0f6..44655052d 100644
--- a/frontend/src/utils/exam-correct/exam-correct.sass
+++ b/frontend/src/utils/exam-correct/exam-correct.sass
@@ -8,13 +8,26 @@ table[uw-exam-correct]
th.uw-exam-correct--user-cell, td.uw-exam-correct--user-cell
min-width: 200px
+
th.uw-exam-correct--part-cell, td.uw-exam-correct--part-cell
- width: 85px
+ width: min-content
text-align: center
+ white-space: nowrap
+
input
width: 70px
padding: 4px 8px
+ .uw-exam-correct--delete-exam-part ~ .fa-trash
+ opacity: .5
+ cursor: pointer
+ margin-left: 5px
+ .uw-exam-correct--delete-exam-part ~ .fa-trash:hover
+ opacity: 1
+ .uw-exam-correct--delete-exam-part:checked ~ .fa-trash
+ opacity: 1
+ color: var(--color-error)
+
td#uw-exam-correct__result
width: min-content
select
@@ -34,6 +47,16 @@ table[uw-exam-correct]
td#uw-exam-correct__result__grade select.grade-hidden
visibility: hidden
+ td.exam-correct--status-cell
+ font-size: .9rem
+ font-weight: 600
+ color: var(--color-fontsec)
+ font-style: italic
+
+ .fas
+ font-size: 1rem
+ text-align: center
+ padding-right: .25rem
[uw-exam-correct] input:invalid:not(.no-value)
diff --git a/frontend/src/utils/navbar/navbar.js b/frontend/src/utils/navbar/navbar.js
index 0bb6fb029..f31ba77bd 100644
--- a/frontend/src/utils/navbar/navbar.js
+++ b/frontend/src/utils/navbar/navbar.js
@@ -1,48 +1,91 @@
import { Utility } from '../../core/utility';
import './navbar.sass';
+import * as throttle from 'lodash.throttle';
-
-export const LANGUAGE_SELECT_UTIL_SELECTOR = '[uw-language-select]';
-const LANGUAGE_SELECT_INITIALIZED_CLASS = 'language-select--initialized';
-
+export const HEADER_CONTAINER_UTIL_SELECTOR = '.navbar__list-item--container-selector .navbar__link-wrapper';
+const HEADER_CONTAINER_INITIALIZED_CLASS = 'navbar-header-container--initialized';
@Utility({
- selector: LANGUAGE_SELECT_UTIL_SELECTOR,
+ selector: HEADER_CONTAINER_UTIL_SELECTOR,
})
-export class LanguageSelectUtil {
+export class NavHeaderContainerUtil {
_element;
- checkbox;
-
+ radioButton;
+ closeButton;
+ container;
+
+ wasOpen;
+
+ _throttleUpdateWasOpen;
+
constructor(element) {
if (!element) {
- throw new Error('Language Select utility needs to be passed an element!');
+ throw new Error('Navbar Header Container utility needs to be passed an element!');
}
- if (element.classList.contains(LANGUAGE_SELECT_INITIALIZED_CLASS)) {
- return false;
+ if (element.classList.contains(HEADER_CONTAINER_INITIALIZED_CLASS)) {
+ return;
}
this._element = element;
- this.checkbox = element.querySelector('#lang-checkbox');
+ this.radioButton = document.getElementById(`${this._element.id}-radio`);
+ if (!this.radioButton) {
+ throw new Error('Navbar Header Container utility could not find associated radio button!');
+ }
- window.addEventListener('click', event => this.close(event));
+ this.closeButton = document.getElementById('container-radio-none');
+ if (!this.closeButton) {
+ throw new Error('Navbar Header Container utility could not find radio button for closing!');
+ }
+
+ this.container = document.getElementById(`${this._element.id}-container`);
+ if (!this.container) {
+ throw new Error('Navbar Header Container utility could not find associated container!');
+ }
- element.classList.add(LANGUAGE_SELECT_INITIALIZED_CLASS);
+ const closer = this.container.querySelector('.navbar__container-list-closer');
+ if (closer) {
+ closer.classList.add('navbar__container-list-closer--hidden');
+ }
+
+ this.updateWasOpen();
+ this.throttleUpdateWasOpen = throttle(this.updateWasOpen.bind(this), 100, { leading: false, trailing: true });
+
+ this._element.classList.add(HEADER_CONTAINER_INITIALIZED_CLASS);
}
- close(event) {
- if (!this._element.contains(event.target) && window.document.contains(event.target)) {
- this.checkbox.checked = false;
+ 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();
}
}
- destroy() {
- // TODO
+ close() {
+ this.radioButton.checked = false;
+ this.throttleUpdateWasOpen();
}
+ isOpen() {
+ return this.radioButton.checked;
+ }
+
+ updateWasOpen() {
+ this.wasOpen = this.isOpen();
+ }
+
+ destroy() { /* TODO */ }
}
+
export const NavbarUtils = [
- LanguageSelectUtil,
+ NavHeaderContainerUtil,
];
diff --git a/frontend/src/utils/navbar/navbar.sass b/frontend/src/utils/navbar/navbar.sass
index 05e3e23ab..67094233e 100644
--- a/frontend/src/utils/navbar/navbar.sass
+++ b/frontend/src/utils/navbar/navbar.sass
@@ -1,49 +1,137 @@
.navbar-container
position: relative
-.navbar-shadow
- position: fixed
- right: 0
- top: 0
- height: var(--header-height-collapsed)
- width: 20px
- z-index: 50
- background-image: linear-gradient(to left, rgba(0, 0, 0, 0.4), transparent)
- transition: height 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
-
-@media (min-width: 768px)
- .navbar-shadow
- height: var(--header-height)
-
-@media (min-width: 1025px)
- .navbar-shadow
- display: none
-
.navbar
position: fixed
- display: flex
- flex-direction: row
- align-items: center
- justify-content: flex-start
right: 0
top: 0
left: var(--asidenav-width-xl)
- height: var(--header-height)
+ 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)
+ margin: 0
+ padding: 10px 0
-@media (max-width: 1199px)
- .navbar
+ @media (max-width: 1199px)
left: var(--asidenav-width-lg)
-@media (max-width: 768px)
- .navbar
+ @media (max-width: 768px), (max-height: 500px)
+ min-height: var(--header-height-collapsed)
+ padding: 0
+
+ @media (max-width: 768px)
left: 0
+ display: flex
+ & > *
+ flex-grow: 1
+
+.navbar__stack
+ display: flex
+ flex-flow: column nowrap
+
+ & > *
+ flex-grow: 1
+
+.navbar__list-wrapper
+ display: flex
+ flex-flow: row nowrap
+ justify-content: space-between
+ align-items: center
+ margin: 0
+
+ @media (min-width: 769px)
+ margin: 0 40px
+
+.navbar__list
+ display: flex
+ flex-flow: row nowrap
+ justify-content: flex-end
+ align-items: center
+ list-style-type: none
+ margin: 0
+
+ &.navbar__list-left
+ justify-content: flex-start
+ margin-right: 40px
+
+ & > *
+ display: block
+
+.navbar__container-list
+ /* margin: 10px 0 0 0 */
+ position: relative
+ padding: 0 40px
+ overflow: hidden
+ display: flex
+ flex-grow: 1
+
+ & > ul
+ display: flex
+ flex-grow: 1
+ flex-flow: row nowrap
+ align-items: center
+ overflow: overlay
+ list-style-type: none
+ justify-content: flex-end
+ margin: 0
+
+ & > *
+ display: block
+
+ @media (min-width: 501px)
+ margin-right: 12px
+
+ &:last-child
+ margin-right: 0
+
+ &.navbar__container-list--left > ul
+ justify-content: flex-start
+
+ @media (max-width: 768px)
+ padding: 0
+
+ margin: 0
+ height: 0
+ transition: all 0.2s cubic-bezier(0.03, 0.43, 0.58, 1)
+
+ .navbar__container-list-closer
+ position: absolute
+ top: 5px
+ right: 10px
+ width: 20px
+ height: 20px
+ text-align: center
+
+ transform-origin: 10px 10px
+ transform: rotate(-0.25turn)
+
+ opacity: 0.5
+ transition: transform 0.2s, opacity 0.2s ease
+
+ &:hover
+ opacity: 1
+
+ transform: scale(1.4)
+
+ &.navbar__container-list-closer--hidden
+ visibility: hidden
+
+ @media (max-width: 768px)
+ visibility: hidden
+
+ &.navbar__container-list--left .navbar__container-list-closer
+ transform: rotate(0.25turn)
+ right: auto
+ left: 10px
+
+ &:hover
+ transform: scale(1.4)
+
// links
.navbar__link-wrapper
display: flex
@@ -57,6 +145,10 @@
overflow: hidden
cursor: pointer
+ @media (max-width: 768px), (max-height: 500px)
+ height: var(--header-height-collapsed)
+
+
.navbar__link-icon
opacity: 0.7
transition: opacity 0.2s ease
@@ -67,12 +159,13 @@
padding: 2px 4px
text-transform: uppercase
font-weight: 600
+ font-size: 16px
-@media (min-width: 769px)
+@media (min-width: 769px) and (min-height: 501px)
.navbar__link-wrapper
border: 1px solid rgba(255, 255, 255, 0.7)
-@media (max-width: 768px)
+@media (max-width: 768px), (max-height: 500px)
.navbar__link-wrapper
box-shadow: none
min-width: 0
@@ -86,72 +179,56 @@
transform: scale(0.65)
margin-bottom: 0
-// navbar list
-.navbar__list
+.navbar__container-link
+ display: block
+
+ @media (min-width: 769px) and (min-height: 501px)
+ border: 1px solid rgba(255, 255, 255, 0.7)
+
+ height: 30px
+ color: var(--color-lightwhite) !important
+ background-color: rgba(0, 0, 0, 0) !important
+ padding: 5px 10px
+ text-transform: uppercase
+ font-weight: 600
+ font-size: 16px
+ outline: 0
+ min-width: 0
+ transition: none
+ cursor: pointer
white-space: nowrap
- + .navbar__list
- margin-left: 12px
+ &:not(.navbar__container-link--active):hover
+ background-color: var(--color-dark) !important
+ color: var(--color-lightwhite) !important
-@media (min-width: 769px)
- .navbar__list:last-of-type
- padding-right: 40px
+ &.navbar__container-link--active
+ background-color: var(--color-lightwhite) !important
+ color: var(--color-dark) !important
-@media (max-width: 768px)
- .navbar__list
- + .navbar__list
- margin-left: 0
- padding-right: 40px
// list item
.navbar__list-item
position: relative
transition: background-color .1s ease
- &:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper
- margin-left: 12px
-
- &:not(.navbar__list-item--favorite) + .navbar__list-item
+ & + .navbar__list-item
margin-left: 12px
@media (max-width: 500px)
.navbar__list-item
min-width: 60px
- &:not(.navbar__list-item--favorite) + .navbar__list-item
+ & + .navbar__list-item
margin-left: 0
- &:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper
- margin-left: 0
-
-.navbar__list-left
- flex: 5
- padding-left: 40px
-
-@media (max-width: 768px)
- .navbar__list-left
- padding-left: 0
-
// "Favorites" list item, only visible on small screens and logged in
-.navbar__list-item
- &.navbar__list-item--favorite
- display: none
-
-.navbar__list-item--favorite
- display: none
- background-color: var(--color-primary)
-
-.logged-in
- .navbar__list
- li.navbar__list-item--favorite,
- .navbar__list-item--favorite
- display: inline-block
-
@media (min-width: 426px)
- .logged-in
- .navbar__list
- .navbar__list-item--favorite
- display: none !important
+ .navbar__list-item--favorite
+ display: none !important
+
+ & + .navbar__list-item
+ margin-left: 0
.navbar__list-item--active
background-color: var(--color-lightwhite)
@@ -163,7 +240,7 @@
.navbar__list-item--active .navbar__link-wrapper
color: var(--color-dark)
-.navbar .navbar__list-item:not(.navbar__list-item--active):not(.navbar__list-item--favorite):hover .navbar__link-wrapper, #lang-checkbox:checked ~ * .navbar__link-wrapper
+.navbar__list-item:not(.navbar__list-item--active):hover .navbar__link-wrapper
background-color: var(--color-dark)
color: var(--color-lightwhite)
@@ -186,43 +263,5 @@
display: block
height: var(--header-height-collapsed)
-@media (max-width: 768px)
- .navbar,
- .navbar__pushdown
- height: var(--header-height-collapsed)
-
- .navbar__link-wrapper
- height: var(--header-height-collapsed)
-
-@media (max-height: 500px)
- .navbar,
- .navbar__pushdown
- height: var(--header-height-collapsed)
-
- .navbar__link-wrapper
- height: var(--header-height-collapsed)
-
-#lang-dropdown
+.navbar__container-radio--none, .navbar__container-radio
display: none
- position: fixed
- top: var(--header-height)
- right: 0
- min-width: 200px
- z-index: 10
- background-color: white
- border-radius: 2px
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
-
- select
- display: block
-
- button
- display: block
- width: 100%
-
-#lang-checkbox:checked ~ #lang-dropdown
- display: block
-
-@media (max-width: 768px)
- #lang-dropdown
- top: var(--header-height-collapsed)
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 215134503..8727b1844 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';
import { ExamCorrect } from './exam-correct/exam-correct';
import { SortTable } from './sort-table/sort-table';
@@ -30,6 +31,7 @@ export const Utils = [
Tooltip,
CourseTeaser,
...NavbarUtils,
+ ...PageActionsUtils,
HideColumns,
ExamCorrect,
SortTable,
diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg
index c92ffab3c..e79cff87f 100644
--- a/messages/uniworx/de-de-formal.msg
+++ b/messages/uniworx/de-de-formal.msg
@@ -494,7 +494,7 @@ CorrectorsPlaceholder: Korrektoren...
CorrectorsDefaulted: Korrektoren-Liste wurde aus bisherigen Übungsblättern diesen Kurses generiert. Es sind keine Daten gespeichert.
Users: Benutzer
-HomeHeading: Aktuelle Termine
+NewsHeading: Aktuelles
LoginHeading: Authentifizierung
LoginTitle: Authentifizierung
ProfileHeading: Benutzereinstellungen
@@ -509,9 +509,9 @@ NotificationSettingsHeading displayName@Text: Benachrichtigungs-Einstellungen f
TokensLastReset: Tokens zuletzt invalidiert
TokensResetSuccess: Authorisierungs-Tokens invalidiert
-HomeOpenAllocations: Offene Zentralanmeldungen
-HomeUpcomingSheets: Anstehende Übungsblätter
-HomeUpcomingExams: Bevorstehende Prüfungen
+NewsOpenAllocations: Offene Zentralanmeldungen
+NewsUpcomingSheets: Anstehende Übungsblätter
+NewsUpcomingExams: Bevorstehende Prüfungen
NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"}
CloseAlert: Schliessen
@@ -886,9 +886,9 @@ MailSubjectSubmissionsUnassigned csh@CourseShorthand sheetName@SheetName: Abgabe
MailSubmissionsUnassignedIntro n@Int courseName@Text termDesc@Text sheetName@SheetName: #{n} Abgaben zu #{sheetName} im Kurs #{courseName} (#{termDesc}) konnten nicht automatisiert verteilt werden.
MailSubjectSheetSoonInactive csh@CourseShorthand sheetName@SheetName: #{sheetName} in #{csh} kann nur noch kurze Zeit abgegeben werden
-MailSheetSoonInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze.
+MailSheetSoonInactiveIntro courseName@Text termDesc@Text sheetName@SheetName: Abgabefrist für #{sheetName} im Kurs #{courseName} (#{termDesc}) endet in Kürze.
MailSubjectSheetInactive csh@CourseShorthand sheetName@SheetName: Abgabezeitraum für #{sheetName} in #{csh} abgelaufen
-MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName n@Int num@Int64: Die Abgabefirst für #{sheetName} im Kurs #{courseName} (#{termDesc}) beendet. Es gab #{noneOneMoreDE n "Keine Abgaben" "Nur eine Abgabe von " (toMessage n <> " Abgaben von ")}#{noneOneMoreDE num "" "einem Teilnehmer" (toMessage num <> " Teilnehmern")}.
+MailSheetInactiveIntro courseName@Text termDesc@Text sheetName@SheetName n@Int num@Int64: Die Abgabefrist für #{sheetName} im Kurs #{courseName} (#{termDesc}) beendet. Es gab #{noneOneMoreDE n "Keine Abgaben" "Nur eine Abgabe von " (toMessage n <> " Abgaben von ")}#{noneOneMoreDE num "" "einem Teilnehmer" (toMessage num <> " Teilnehmern")}.
MailSubjectCorrectionsAssigned csh@CourseShorthand sheetName@SheetName: Ihnen wurden Korrekturen zu #{sheetName} in #{csh} zugeteilt
MailCorrectionsAssignedIntro courseName@Text termDesc@Text sheetName@SheetName n@Int: #{n} #{pluralDE n "Abgabe wurde" "Abgaben wurden"} Ihnen zur Korrektur für #{sheetName} im Kurs #{courseName} (#{termDesc}) zugeteilt.
@@ -1115,7 +1115,7 @@ InvalidRoute: Konnte URL nicht interpretieren
MenuOpenCourses: Kurse mit offener Registrierung
MenuOpenAllocations: Aktive Zentralanmeldungen
-MenuHome: Aktuell
+MenuNews: Aktuell
MenuInformation: Informationen
MenuLegal: Rechtliche Informationen
MenuDataProt: Datenschutzerklärung
@@ -1186,7 +1186,7 @@ MenuTutorialEdit: Tutorium editieren
MenuTutorialComm: Mitteilung an Teilnehmer
MenuExamList: Prüfungen
MenuExamNew: Neue Prüfung anlegen
-MenuExamEdit: Bearbeiten
+MenuExamEdit: Prüfung bearbeiten
MenuExamUsers: Teilnehmer
MenuExamGrades: Prüfungsleistungen
MenuExamAddMembers: Prüfungsteilnehmer hinzufügen
@@ -1272,8 +1272,8 @@ BreadcrumbExternalExamGrades: Prüfungsleistungen
BreadcrumbExternalExamStaffInvite: Einladung zum Prüfer
BreadcrumbParticipantsList: Kursteilnehmerlisten
BreadcrumbParticipants: Kursteilnehmerliste
-BreadcrumbStorageKey: Lokalen Schlüssel generieren
BreadcrumbExamAutoOccurrence: Automatische Termin-/Raumverteilung
+BreadcrumbStorageKey: Lokalen Schlüssel generieren
ExternalExamEdit coursen@CourseName examn@ExamName: Bearbeiten: #{coursen}, #{examn}
ExternalExamGrades coursen@CourseName examn@ExamName: Prüfungsleistungen: #{coursen}, #{examn}
@@ -1399,10 +1399,10 @@ ExamRegistrationInviteHeading examn@ExamName: Einladung zum Teilnehmer für #{ex
ExamRegistrationInviteExplanation: Sie wurden eingeladen, Prüfungsteilnehmer zu sein.
ExamCorrectHeading examname@Text: Prüfungsergebnisse für #{examname} eintragen
+ExamCorrectExamResultDelete: Prüfungsergebnis löschen
ExamCorrectHeadDate: Zeit
ExamCorrectHeadParticipant: Teilnehmer
-ExamCorrectHeadParticipantTooltip: Geben Sie hier einen beliebigen eindeutigen Identifikator des Teilnehmers an. Definitiv eindeutig ist die Matrikelnummer des Teilnehmers, aber auch der Name oder ein Teil der Matrikelnummer können unter Umständen (je nach Liste aller Prüfungsteilnehmer) bereits eindeutig sein.
ExamCorrectHeadPart exampartnum@ExamPartNumber: #{exampartnum}
ExamCorrectHeadPartName exampartname@ExamPartName: #{exampartname}
ExamCorrectHeadStatus: Status
@@ -1411,6 +1411,8 @@ ExamCorrectButtonSend: Senden
ExamCorrectErrorMultipleMatchingParticipants: Dem Identifikator konnten mehrere Prüfungsteilnehmer zugeordnet werden.
ExamCorrectErrorNoMatchingParticipants: Dem Identifikator konnte kein Prüfungsteilnehmer zugeordnet werden.
+ExamCorrectErrorPartResultOutOfBounds examPartNumber@ExamPartNumber: Prüfungsergebnis für Teil #{examPartNumber} ist nicht größer Null.
+ExamCorrectErrorPartResultOutOfBoundsMax examPartNumber@ExamPartNumber maxPoints@Points: Prüfungsergebnis für Teil #{examPartNumber} liegt nicht zwischen 0 und #{maxPoints}.
SubmissionUserInvitationAccepted shn@SheetName: Sie wurden als Mitabgebende(r) für eine Abgabe zu #{shn} eingetragen
SubmissionUserInvitationDeclined shn@SheetName: Sie haben die Einladung, Mitabgebende(r) für #{shn} zu werden, abgelehnt
@@ -1553,9 +1555,9 @@ ExamDeregisterUntil: Abmeldung bis
ExamPublishOccurrenceAssignments: Termin- bzw. Raumzuteilung den Teilnehmern mitteilen um
ExamPublishOccurrenceAssignmentsTip: Ab diesem Zeitpunkt Teilnehmer einsehen zu welcher Teilprüfung bzw. welchen Raum sie angemeldet sind
ExamPublishOccurrenceAssignmentsParticipant: Termin- bzw. Raumzuteilung einsehbar ab
-ExamFinished: Bewertung abgeschlossen ab
+ExamFinished: Ergebnisse sichtbar ab
ExamFinishedOffice: Noten bekannt gegeben
-ExamFinishedParticipant: Bewertung vorrausichtlich abgeschlossen
+ExamFinishedParticipant: Bewertung voraussichtlich abgeschlossen
ExamFinishedTip: Zeitpunkt zu dem Prüfungergebnisse den Teilnehmern gemeldet werden
ExamClosed: Noten gemeldet
ExamClosedTip: Prüfungsbeauftraget, die im System Noten einsehen, werden zu diesem Zeitpunkt benachrichtigt und danach bei Änderungen informiert
@@ -1580,7 +1582,6 @@ ExamBonusRule: Prüfungsbonus aus Übungsbetrieb
ExamNoBonus': Kein automatischer Bonus
ExamBonusPoints': Umrechnung von Übungspunkten
ExamBonusManual': Manuelle Berechnung
-ExamGradesExplanation: Diese Ansicht zeigt die selben Daten an, wie die Tabelle von Prüfungsteilnehmern. Anpassen der Teilnehmerdaten und Ergebnisse ist nur dort möglich. Hier können Sie vor Allem einsehen und markieren, welche Prüfungsleistungen von den zuständigen Prüfungsbeauftragten bereits vollständig bearbeitet wurden.
ExamRegisterForOccurrence: Anmeldung zur Klausur erfolgt durch Anmeldung zu einem Termin/Raum
@@ -1673,12 +1674,12 @@ ExamLoginToRegister: Um sich zum Kurs anzumelden müssen Sie zunächst in Uni2wo
ExamRegisterToMustBeAfterRegisterFrom: "Anmeldung ab" muss vor "Anmeldung bis" liegen
ExamDeregisterUntilMustBeAfterRegisterFrom: "Abmeldung bis" muss nach "Anmeldung bis" liegen
-ExamStartMustBeAfterPublishOccurrenceAssignments: Start muss nach Veröffentlichung der Termin- bzw. Raumzuordnung liegen
+ExamStartMustBeAfterPublishOccurrenceAssignments: Beginn muss nach Veröffentlichung der Termin- bzw. Raumzuordnung liegen
ExamEndMustBeAfterStart: Beginn der Prüfung muss vor ihrem Ende liegen
-ExamFinishedMustBeAfterEnd: "Bewertung abgeschlossen ab" muss nach Ende liegen
-ExamFinishedMustBeAfterStart: "Bewertung abgeschlossen ab" muss nach Start liegen
-ExamClosedMustBeAfterFinished: "Noten stehen fest ab" muss nach "Bewertung abgeschlossen ab" liegen
-ExamClosedMustBeAfterStart: "Noten stehen fest ab" muss nach Start liegen
+ExamFinishedMustBeAfterEnd: "Ergebnisse sichtbar ab" muss nach Ende liegen
+ExamFinishedMustBeAfterStart: "Ergebnisse sichtbar ab" muss nach Beginn liegen
+ExamClosedMustBeAfterFinished: "Noten stehen fest ab" muss nach "Ergebnisse sichtbar ab" liegen
+ExamClosedMustBeAfterStart: "Noten stehen fest ab" muss nach Beginn liegen
ExamClosedMustBeAfterEnd: "Noten stehen fest ab" muss nach Ende liegen
ExamOccurrenceEndMustBeAfterStart eoName@ExamOccurrenceName: Beginn des Termins #{eoName} muss vor seinem Ende liegen
diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg
index c9c87345a..8f829c850 100644
--- a/messages/uniworx/en-eu.msg
+++ b/messages/uniworx/en-eu.msg
@@ -492,7 +492,7 @@ CorrectorsPlaceholder: Correctors...
CorrectorsDefaulted: List of correctors was automatically generated based on those of preceding sheets for this course. No data has been saved, yet.
Users: Users
-HomeHeading: Home
+NewsHeading: News
LoginHeading: Authentication
LoginTitle: Authentication
ProfileHeading: Settings
@@ -507,9 +507,9 @@ NotificationSettingsHeading displayName: Notification settings for #{displayName
TokensLastReset: Tokens last reset
TokensResetSuccess: Successfully invalidated all authorisation tokens
-HomeOpenAllocations: Active central allocations
-HomeUpcomingSheets: Upcoming exercise sheets
-HomeUpcomingExams: Upcoming exams
+NewsOpenAllocations: Active central allocations
+NewsUpcomingSheets: Upcoming exercise sheets
+NewsUpcomingExams: Upcoming exams
NumCourses num: #{num} #{pluralEN num "course" "courses"}
CloseAlert: Close
@@ -1114,7 +1114,7 @@ InvalidRoute: Could not interpret url
MenuOpenCourses: Courses with open registration
MenuOpenAllocations: Active central allocations
-MenuHome: Home
+MenuNews: News
MenuInformation: Information
MenuLegal: Legal
MenuDataProt: Data protection
@@ -1185,7 +1185,7 @@ MenuTutorialEdit: Edit tutorial
MenuTutorialComm: Send course message
MenuExamList: Exams
MenuExamNew: Create new exam
-MenuExamEdit: Edit
+MenuExamEdit: Edit exam
MenuExamUsers: Participants
MenuExamGrades: Exam results
MenuExamAddMembers: Add exam participants
@@ -1271,8 +1271,8 @@ BreadcrumbExternalExamGrades: Exam results
BreadcrumbExternalExamStaffInvite: Invitation
BreadcrumbParticipantsList: Lists of course participants
BreadcrumbParticipants: Course participants
-BreadcrumbStorageKey: Generate storage key
BreadcrumbExamAutoOccurrence: Automatic occurrence/room distribution
+BreadcrumbStorageKey: Generate storage key
ExternalExamEdit coursen examn: Edit: #{coursen}, #{examn}
ExternalExamGrades coursen examn: Exam achievements: #{coursen}, #{examn}
@@ -1400,7 +1400,6 @@ ExamCorrectHeading examname: Submit corrections for #{examname}
ExamCorrectHeadDate: Time
ExamCorrectHeadParticipant: Participant
-ExamCorrectHeadParticipantTooltip: Enter any string that uniquely identifies the participant. Their matriculation number is definitely unique, but also their name or a part of their matriculation number may already be unique for this participant (depending on the list of all participants).
ExamCorrectHeadPart exampartnum: #{exampartnum}
ExamCorrectHeadPartName exampartname: #{exampartname}
ExamCorrectHeadStatus: Status
@@ -1409,6 +1408,10 @@ ExamCorrectButtonSend: Submit
ExamCorrectErrorMultipleMatchingParticipants: This identifier matches on multiple exam participants.
ExamCorrectErrorNoMatchingParticipants: This identifier does not match on any exam participant.
+ExamCorrectErrorPartResultOutOfBounds examPartNumber: Exam part result for #{examPartNumber} ist not greater zero.
+ExamCorrectErrorPartResultOutOfBoundsMax examPartNumber maxPoints: Exam part result for #{examPartNumber} is not between 0 and #{maxPoints}.
+
+ExamCorrectExamResultDelete: Delete exam result
SubmissionUserInvitationAccepted shn: You now participate in a submission for #{shn}
SubmissionUserInvitationDeclined shn: You have declined the invitation to participate in a submission for #{shn}
@@ -1551,7 +1554,7 @@ ExamDeregisterUntil: Deregister until
ExamPublishOccurrenceAssignments: Publish occurrence/room-assignments
ExamPublishOccurrenceAssignmentsTip: At this time participants are informed to which occurrence/room they are assigned
ExamPublishOccurrenceAssignmentsParticipant: Occurrence/room-assignments published
-ExamFinished: Marking finished
+ExamFinished: Results visible from
ExamFinishedOffice: Exam achievements published
ExamFinishedParticipant: Marking expected to be finished
ExamFinishedTip: At this participants are informed of their exam achievements
@@ -1578,7 +1581,6 @@ ExamBonusRule: Bonus points from exercises
ExamNoBonus': No automatic exam bonus
ExamBonusPoints': Compute from exercise achievements
ExamBonusManual': Manual computation
-ExamGradesExplanation: This view shows the same data as the table of exam participants. Changing participant's data and achievements is only possible via the table of exam participants. Primarily, this view allows you to check and adjust which exam achievements were properly handled by the relevant exam offices.
ExamRegisterForOccurrence: Registration for this exam is done by registering for an occurrence/room
@@ -1631,6 +1633,7 @@ ExamFormCorrection: Correction
ExamFormParts: Exam parts
ExamCorrectors: Correctors
+ExamCorrectorsTip: Correctors configured here may, after the start of the exam and until "Results visible from", enter exam part results for all exam parts and participants.
ExamCorrectorAlreadyAdded: A corrector with this email address already exists
ExamParts: Exam parts/questions
@@ -1672,9 +1675,9 @@ ExamRegisterToMustBeAfterRegisterFrom: "Register to" must be after "register fro
ExamDeregisterUntilMustBeAfterRegisterFrom: "Deregister until" must be after "register from"
ExamStartMustBeAfterPublishOccurrenceAssignments: "Start" must be after "publish occurrence/room-assignments"
ExamEndMustBeAfterStart: "End" must be after "start"
-ExamFinishedMustBeAfterEnd: "Marking finished" must be after "end"
-ExamFinishedMustBeAfterStart: "Marking finished" must be after "start"
-ExamClosedMustBeAfterFinished: "Exam achievements registered" must be after "marking finished"
+ExamFinishedMustBeAfterEnd: "Results visible from" must be after "end"
+ExamFinishedMustBeAfterStart: "Results visible from" must be after "start"
+ExamClosedMustBeAfterFinished: "Exam achievements registered" must be after "results visible from"
ExamClosedMustBeAfterStart: "Exam achievements registered" must be after "start"
ExamClosedMustBeAfterEnd: "Exam achievements registered" must be after "end"
diff --git a/records.json b/records.json
index a2a5ae297..d84014b37 100644
--- a/records.json
+++ b/records.json
@@ -789,5 +789,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/routes b/routes
index 9f7a5b92f..753ea445a 100644
--- a/routes
+++ b/routes
@@ -41,7 +41,7 @@
/metrics MetricsR GET
-/ HomeR GET !free
+/ NewsR GET !free
/users UsersR GET POST -- no tags, i.e. admins only
/users/#CryptoUUIDUser AdminUserR GET POST
/users/#CryptoUUIDUser/delete AdminUserDeleteR POST
@@ -189,8 +189,8 @@
/register ERegisterR POST !timeANDcourse-registeredAND¬exam-registered !timeANDexam-registeredAND¬exam-result
/register/#ExamOccurrenceName ERegisterOccR POST !exam-occurrence-registrationANDtimeANDcapacityANDcourse-registeredAND¬exam-occurrence-registered !exam-occurrence-registrationANDtimeANDexam-occurrence-registeredAND¬exam-result
/grades EGradesR GET POST !exam-office
- /correct ECorrectR GET POST !exam-correctorANDtime
/assign-occurrences EAutoOccurrenceR POST
+ /correct ECorrectR GET POST !exam-correctorANDtime
/apps CApplicationsR GET POST
!/apps/files CAppsFilesR GET
/apps/#CryptoFileNameCourseApplication CourseApplicationR:
diff --git a/src/Application.hs b/src/Application.hs
index f61813796..c2274a508 100644
--- a/src/Application.hs
+++ b/src/Application.hs
@@ -101,7 +101,7 @@ import Data.List (cycle)
-- Import all relevant handler modules here.
-- (HPack takes care to add new modules to our cabal file nowadays.)
-import Handler.Home
+import Handler.News
import Handler.Info
import Handler.Help
import Handler.Profile
diff --git a/src/Foundation.hs b/src/Foundation.hs
index 7c8878a40..ec9808928 100644
--- a/src/Foundation.hs
+++ b/src/Foundation.hs
@@ -1,5 +1,7 @@
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE OverloadedLabels #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} -- MonadCrypto
module Foundation
@@ -63,6 +65,7 @@ import Control.Monad.Except (MonadError(..), ExceptT)
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Trans.Reader (runReader, mapReaderT)
import Control.Monad.Trans.Writer (WriterT(..), runWriterT)
+import Control.Monad.Trans.State (execStateT)
import Control.Monad.Writer.Class (MonadWriter(..))
import Control.Monad.Memo.Class (MonadMemo(..), for4)
import qualified Control.Monad.Catch as C
@@ -79,6 +82,7 @@ import Utils.Sheet
import Utils.SystemMessage
import Text.Shakespeare.Text (st)
+import Text.Cassius (cassiusFile)
import Yesod.Form.I18n.German
import Yesod.Form.I18n.English
@@ -113,64 +117,99 @@ 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
--- Menus and Favourites
-data MenuType = NavbarAside | NavbarRight | NavbarSecondary | PageActionPrime | PageActionSecondary | Footer
- deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
+navQuick :: NavQuickView -> (NavQuickView -> Any)
+navQuick x x' = Any $ x == x'
-instance Universe MenuType
-instance Finite MenuType
+data NavType
+ = NavTypeLink
+ { navModal :: Bool
+ }
+ | NavTypeButton
+ { navMethod :: StdMethod
+ , navData :: [(Text, Text)]
+ } deriving (Eq, Ord, Read, Show, Generic, Typeable)
-makePrisms ''MenuType
+makeLenses_ ''NavType
+makePrisms ''NavType
-data MenuItem = MenuItem
- { menuItemLabel :: UniWorXMessage
- , menuItemIcon :: Maybe Text -- currently from: https://fontawesome.com/icons?d=gallery
- , menuItemRoute :: SomeRoute UniWorX
- , menuItemAccessCallback' :: Handler Bool -- Check whether action is shown in ADDITION to authorization (which is always checked)
- , menuItemModal :: Bool
- , menuItemType :: MenuType
+data NavLevel = NavLevelTop | NavLevelInner
+ deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
+
+data NavHeaderRole = NavHeaderPrimary | NavHeaderSecondary
+ deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
+
+data NavLink = forall msg route. (RenderMessage UniWorX msg, HasRoute UniWorX route, RedirectUrl UniWorX route) => NavLink
+ { navLabel :: msg
+ , navRoute :: route
+ , navAccess' :: Handler Bool
+ , navType :: NavType
+ , navQuick' :: NavQuickView -> Any
+ , navForceActive :: Bool
}
-makeLenses_ ''MenuItem
+makeLenses_ ''NavLink
-instance RedirectUrl UniWorX MenuItem where
- toTextUrl MenuItem{..} = toTextUrl menuItemRoute
-instance HasRoute UniWorX MenuItem where
- urlRoute MenuItem{..} = urlRoute menuItemRoute
+instance HasRoute UniWorX NavLink where
+ urlRoute NavLink{..} = urlRoute navRoute
+instance RedirectUrl UniWorX NavLink where
+ toTextUrl NavLink{..} = toTextUrl navRoute
+instance RenderMessage UniWorX NavLink where
+ renderMessage app ls NavLink{..} = renderMessage app ls navLabel
-menuItemAccessCallback :: MenuItem -> Handler Bool
-menuItemAccessCallback MenuItem{..} = and2M ((==) Authorized <$> authCheck) menuItemAccessCallback'
+data Nav
+ = NavHeader
+ { navHeaderRole :: NavHeaderRole
+ , navIcon :: Icon
+ , navLink :: NavLink
+ }
+ | NavHeaderContainer
+ { navHeaderRole :: NavHeaderRole
+ , navLabel :: SomeMessage UniWorX
+ , navIcon :: Icon
+ , navChildren :: [NavLink]
+ }
+ | NavPageActionPrimary
+ { navLink :: NavLink
+ , navChildren :: [NavLink]
+ }
+ | NavPageActionSecondary
+ { navLink :: NavLink
+ }
+ | NavFooter
+ { navLink :: NavLink
+ } deriving (Generic, Typeable)
+
+makeLenses_ ''Nav
+makePrisms ''Nav
+
+data NavChildren
+type instance Children NavChildren a = ChildrenNavChildren a
+type family ChildrenNavChildren a where
+ ChildrenNavChildren (SomeMessage UniWorX) = '[]
+
+ ChildrenNavChildren a = Children ChGeneric a
+
+navAccess :: (MonadHandler m, HandlerSite m ~ UniWorX, MonadCatch m) => Nav -> MaybeT m Nav
+navAccess = execStateT $ do
+ guardM $ preuse _navLink >>= maybe (return True) navLinkAccess
+
+ _navChildren <~ (filterM navLinkAccess =<< use _navChildren)
+ whenM (hasn't _navLink <$> use id) $
+ guardM $ not . null <$> use _navChildren
+
+navLinkAccess :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX, MonadCatch m) => NavLink -> m Bool
+navLinkAccess NavLink{..} = liftHandler navAccess' `and2M` accessCheck navType navRoute
where
- authCheck = handleAny (\_ -> return . Unauthorized $ error "authCheck caught exception") $ isAuthorized (urlRoute menuItemRoute) False
-
-$(return [])
-
-
-data instance ButtonClass UniWorX
- = BCIsButton
- | BCDefault
- | BCPrimary
- | BCSuccess
- | BCInfo
- | BCWarning
- | BCDanger
- | BCLink
- | BCMassInputAdd | BCMassInputDelete
- deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
-instance Universe (ButtonClass UniWorX)
-instance Finite (ButtonClass UniWorX)
-
-instance PathPiece (ButtonClass UniWorX) where
- toPathPiece BCIsButton = "btn"
- toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass
- fromPathPiece = finiteFromPathPiece
-
-
-embedRenderMessage ''UniWorX ''ButtonSubmit id
-instance Button UniWorX ButtonSubmit where
- btnClasses BtnSubmit = [BCIsButton, BCPrimary]
-
+ accessCheck :: HasRoute UniWorX route => NavType -> route -> m Bool
+ accessCheck nt (urlRoute -> route) = handleAll (\_ -> return False) $ bool hasWriteAccessTo hasReadAccessTo (is _NavTypeLink nt) route
+
getTimeLocale' :: [Lang] -> TimeLocale
getTimeLocale' = $(timeLocaleMap [("de-de", "de_DE.utf8"), ("en-GB", "en_GB.utf8")])
@@ -186,9 +225,9 @@ appLanguagesOpts :: ( MonadHandler m
) => m (OptionList Lang)
-- ^ Authoritive list of supported Languages
appLanguagesOpts = do
- mr <- getsYesod renderMessage
+ MsgRenderer mr <- getMsgRenderer
let mkOption l = Option
- { optionDisplay = mr (l : filter (/= l) (optionInternalValue <$> langOptions)) (MsgLanguage l)
+ { optionDisplay = mr $ MsgLanguage l
, optionInternalValue = l
, optionExternalValue = l
}
@@ -573,8 +612,6 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
| otherwise
-> guard $ visible
&& NTop (Just cTime) <= NTop examDeregisterUntil
- ECorrectR -> guard $ NTop (Just cTime) >= NTop examStart
- && NTop (Just cTime) <= NTop examFinished
ERegisterOccR occn -> do
occId <- (>>= hoistMaybe) . $cachedHereBinary (eId, occn) . lift . getKeyBy $ UniqueExamOccurrence eId occn
if
@@ -585,6 +622,8 @@ tagAccessPredicate AuthTime = APDB $ \mAuthId route _ -> case route of
-> guard $ visible
&& NTop examRegisterFrom <= NTop (Just cTime)
&& NTop (Just cTime) <= NTop examRegisterTo
+ ECorrectR -> guard $ NTop (Just cTime) >= NTop examStart
+ && NTop (Just cTime) <= NTop examFinished
_ -> return ()
return Authorized
@@ -1350,6 +1389,30 @@ evalAccessCorrector :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX)
evalAccessCorrector tid ssh csh = evalAccess (CourseR tid ssh csh CNotesR) False
+data instance ButtonClass UniWorX
+ = BCIsButton
+ | BCDefault
+ | BCPrimary
+ | BCSuccess
+ | BCInfo
+ | BCWarning
+ | BCDanger
+ | BCLink
+ | BCMassInputAdd | BCMassInputDelete
+ deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
+instance Universe (ButtonClass UniWorX)
+instance Finite (ButtonClass UniWorX)
+
+instance PathPiece (ButtonClass UniWorX) where
+ toPathPiece BCIsButton = "btn"
+ toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass
+ fromPathPiece = finiteFromPathPiece
+
+embedRenderMessage ''UniWorX ''ButtonSubmit id
+instance Button UniWorX ButtonSubmit where
+ btnClasses BtnSubmit = [BCIsButton, BCPrimary]
+
+
updateFavourites :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX)
=> Maybe (TermId, SchoolId, CourseShorthand) -- ^ Insert course into favourites, as appropriate
-> ReaderT SqlBackend m ()
@@ -1523,16 +1586,16 @@ instance Yesod UniWorX where
makeLogger = readTVarIO . snd . appLogger
-langForm :: Form (Lang, Route UniWorX)
-langForm csrf = do
- lang <- selectLanguage appLanguages
- route <- getCurrentRoute
- (urlRes, urlView) <- mreq hiddenField ("" & addName ("referer" :: Text)) route
- (langBoxRes, langBoxView) <- mreq
- (selectField appLanguagesOpts)
- ("" & addAttr "multiple" "multiple" & addAttr "size" (tshow . min 10 $ length appLanguages) & addAutosubmit & addName ("lang" :: Text))
- (Just lang)
- return ((,) <$> langBoxRes <*> urlRes, toWidget csrf <> fvInput urlView <> fvInput langBoxView)
+-- langForm :: Form (Lang, Route UniWorX)
+-- langForm csrf = do
+-- lang <- selectLanguage appLanguages
+-- route <- getCurrentRoute
+-- (urlRes, urlView) <- mreq hiddenField ("" & addName ("referer" :: Text)) route
+-- (langBoxRes, langBoxView) <- mreq
+-- (selectField appLanguagesOpts)
+-- ("" & addAttr "multiple" "multiple" & addAttr "size" (tshow . min 10 $ length appLanguages) & addAutosubmit & addName ("lang" :: Text))
+-- (Just lang)
+-- return ((,) <$> langBoxRes <*> urlRes, toWidget csrf <> fvInput urlView <> fvInput langBoxView)
siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX) => msg -> Widget -> Handler Html
siteLayoutMsg msg widget = do
@@ -1579,12 +1642,6 @@ siteLayout' headingOverride widget = do
-- let isParent :: Route UniWorX -> Bool
-- isParent r = r == (fst parents)
- defaultLinks' <- defaultLinks
- let menu :: [MenuItem]
- menu = defaultLinks' ++ maybe [] pageActions mcurrentRoute
-
- menuTypes <- mapM (\x -> (,,) <$> pure x <*> newIdent <*> toTextUrl x) =<< filterM menuItemAccessCallback menu
-
isAuth <- isJust <$> maybeAuthId
-- Lookup Favourites & Theme if possible
@@ -1639,9 +1696,16 @@ siteLayout' headingOverride widget = do
-> let courseRoute = CourseR courseTerm courseSchool courseShorthand CShowR
favouriteReason = fromMaybe FavouriteCurrent mFavourite
in do
- items <- filterM menuItemAccessCallback (pageActions courseRoute)
- items' <- forM items $ \i -> (i, ) <$> toTextUrl i
- return (c, courseRoute, items', favouriteReason)
+ items' <- pageQuickActions NavQuickViewFavourite courseRoute
+ items <- forM items' $ \n -> (n,) <$> toTextUrl n
+ return (c, courseRoute, items, favouriteReason)
+
+ nav'' <- mconcat <$> sequence
+ [ defaultLinks
+ , maybe (return []) pageActions mcurrentRoute
+ ]
+ nav' <- catMaybes <$> mapM (runMaybeT . navAccess) nav''
+ nav <- forM nav' $ \n -> (n,,,) <$> newIdent <*> traverse toTextUrl (n ^? _navLink) <*> traverse (\nc -> (nc,, ) <$> newIdent <*> toTextUrl nc) (n ^. _navChildren)
mmsgs <- if
| isModal -> getMessages
@@ -1652,21 +1716,23 @@ siteLayout' headingOverride widget = do
\authTag -> addMessageWidget Info $ msgModal [whamlet|_{MsgUnauthorizedDisabledTag authTag}|] (Left $ SomeRoute (AuthPredsR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mcurrentRoute]))
getMessages
- (langFormView, langFormEnctype) <- generateFormPost $ identifyForm FIDLanguage langForm
- let langFormView' = wrapForm langFormView def
- { formAction = Just $ SomeRoute LangR
- , formSubmit = FormAutoSubmit
- , formEncoding = langFormEnctype
- }
+ -- (langFormView, langFormEnctype) <- generateFormPost $ identifyForm FIDLanguage langForm
+ -- let langFormView' = wrapForm langFormView def
+ -- { formAction = Just $ SomeRoute LangR
+ -- , formSubmit = FormAutoSubmit
+ -- , formEncoding = langFormEnctype
+ -- }
- let highlight :: Route UniWorX -> Bool -- highlight last route in breadcrumbs, favorites taking priority
- highlight = let crumbs = mcons mcurrentRoute $ view _1 <$> reverse parents
- navItems = map (view _2) favourites ++ map (urlRoute . menuItemRoute . view _1) menuTypes
- highR = find (`elem` navItems) . uncurry (++) $ partition (`elem` map (view _2) favourites) crumbs
- in \r -> Just r == highR
+ let highlight :: HasRoute UniWorX url => url -> Bool
+ -- ^ highlight last route in breadcrumbs, favorites taking priority
+ highlight = (highR ==) . Just . urlRoute
+ where crumbs = mcons mcurrentRoute $ view _1 <$> reverse parents
+ navItems = map (view _2) favourites ++ toListOf (folded . typesUsing @NavChildren @NavLink . to urlRoute) nav
+ highR = find (`elem` navItems) . uncurry (++) $ partition (`elem` map (view _2) favourites) crumbs
+ highlightNav = (||) <$> navForceActive <*> highlight
favouriteTerms :: [TermIdentifier]
favouriteTerms = take maxFavouriteTerms . Set.toDescList $ foldMap (\(Course{..}, _, _, _) -> Set.singleton $ unTermKey courseTerm) favourites
- favouriteTermReason :: TermIdentifier -> FavouriteReason -> [(Course, Route UniWorX, [(MenuItem, Text)], FavouriteReason)]
+ favouriteTermReason :: TermIdentifier -> FavouriteReason -> [(Course, Route UniWorX, [(NavLink, Text)], FavouriteReason)]
favouriteTermReason tid favReason' = favourites
& filter (\(Course{..}, _, _, favReason) -> unTermKey courseTerm == tid && favReason == favReason')
& sortOn (\(Course{..}, _, _, _) -> courseName)
@@ -1677,24 +1743,101 @@ siteLayout' headingOverride widget = do
-- value passed to hamletToRepHtml cannot be a widget, this allows
-- you to use normal widget features in default-layout.
- navbarModal (MenuItem{..}, menuIdent') = customModal Modal
- { modalTriggerId = Just menuIdent'
- , modalId = Nothing
- , modalTrigger = \(Just route) menuIdent -> $(widgetFile "widgets/navbar/item")
- , modalContent = Left menuItemRoute
- }
+ navWidget :: (Nav, Text, Maybe Text, [(NavLink, Text, Text)]) -> Widget
+ navWidget (n, navIdent, navRoute', navChildren') = case n of
+ NavHeader{ navLink = navLink@NavLink{..}, .. }
+ | NavTypeLink{..} <- navType
+ , navModal
+ -> customModal Modal
+ { modalTriggerId = Just navIdent
+ , modalId = Nothing
+ , modalTrigger = \(Just route) ident -> $(widgetFile "widgets/navbar/item")
+ , modalContent = Left $ SomeRoute navLink
+ }
+ | NavTypeLink{} <- navType
+ -> let route = navRoute'
+ ident = navIdent
+ in $(widgetFile "widgets/navbar/item")
+ NavPageActionPrimary{ navLink = navLink@NavLink{..}, .. }
+ -> 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
+ -> customModal Modal
+ { modalTriggerId = Just navIdent
+ , modalId = Nothing
+ , modalTrigger = \(Just route) ident -> $(widgetFile "widgets/pageaction/secondary")
+ , modalContent = Left $ SomeRoute navLink
+ }
+ | NavTypeLink{} <- navType
+ -> let route = navRoute'
+ ident = navIdent
+ in $(widgetFile "widgets/pageaction/secondary")
+ NavHeaderContainer{..} -> $(widgetFile "widgets/navbar/container")
+ NavFooter{ navLink = navLink@NavLink{..} }
+ | NavTypeLink{..} <- navType
+ , not navModal
+ -> let route = navRoute'
+ ident = navIdent
+ in $(widgetFile "widgets/footer/link")
+ _other -> error "not implemented"
- navbarItem (MenuItem{..}, menuIdent) = do
- route <- toTextUrl menuItemRoute
- $(widgetFile "widgets/navbar/item")
+ navContainerItemWidget :: (Nav, Text, Maybe Text, [(NavLink, Text, Text)])
+ -> (NavLink, Text, Text)
+ -> Widget
+ navContainerItemWidget (n, _navIdent, _navRoute', _navChildren') (iN@NavLink{..}, iNavIdent, iNavRoute) = case n of
+ NavHeaderContainer{}
+ | NavTypeLink{..} <- navType
+ , navModal
+ -> customModal Modal
+ { modalTriggerId = Just iNavIdent
+ , modalId = Nothing
+ , modalTrigger = \(Just route) ident -> $(widgetFile "widgets/navbar/navbar-container-item--link")
+ , modalContent = Left $ SomeRoute iN
+ }
+ | NavTypeLink{} <- navType
+ -> let route = iNavRoute
+ ident = iNavIdent
+ in $(widgetFile "widgets/navbar/navbar-container-item--link")
+ | NavTypeButton{..} <- navType -> do
+ csrfToken <- reqToken <$> getRequest
+ wrapForm $(widgetFile "widgets/navbar/navbar-container-item--button") def
+ { formMethod = navMethod
+ , formSubmit = FormNoSubmit
+ , formAction = Just $ SomeRoute iN
+ }
+ _other -> error "not implemented"
navbar :: Widget
- navbar = $(widgetFile "widgets/navbar/navbar")
+ navbar = do
+ $(widgetFile "widgets/navbar/navbar")
+ forM_ (filter isNavHeaderContainer nav) $ \(_, containerIdent, _, _) ->
+ toWidget $(cassiusFile "templates/widgets/navbar/container-radio.cassius")
+ where isNavHeaderPrimary = has $ _1 . _navHeaderRole . only NavHeaderPrimary
+ isNavHeaderSecondary = has $ _1 . _navHeaderRole . only NavHeaderSecondary
asidenav :: Widget
asidenav = $(widgetFile "widgets/asidenav/asidenav")
where logo = preEscapedToMarkup $ decodeUtf8 $(embedFile "assets/lmu/logo.svg")
footer :: Widget
footer = $(widgetFile "widgets/footer/footer")
+ where isNavFooter = has $ _1 . _NavFooter
alerts :: Widget
alerts = $(widgetFile "widgets/alerts/alerts")
contentHeadline :: Maybe Widget
@@ -1706,11 +1849,16 @@ siteLayout' headingOverride widget = do
-- functions to determine if there are page-actions (primary or secondary)
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
+ 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
-- See Utils.Frontend.I18n and files in messages/frontend for message definitions
@@ -1769,12 +1917,12 @@ i18nCrumb msg mbR = do
-- i.e. information might be leaked by not performing permission checks if the
-- breadcrumb value depends on sensitive content (like an user's name).
instance YesodBreadcrumbs UniWorX where
- breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
+ breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just NewsR
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
- breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
+ breadcrumb NewsR = i18nCrumb MsgMenuNews Nothing
breadcrumb UsersR = i18nCrumb MsgMenuUsers $ Just AdminR
breadcrumb AdminUserAddR = i18nCrumb MsgMenuUserAdd $ Just UsersR
breadcrumb (AdminUserR cID) = maybeT (i18nCrumb MsgBreadcrumbUser $ Just UsersR) $ do
@@ -1839,7 +1987,7 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb StorageKeyR = i18nCrumb MsgBreadcrumbStorageKey Nothing
- breadcrumb TermShowR = i18nCrumb MsgMenuTermShow $ Just HomeR
+ breadcrumb TermShowR = i18nCrumb MsgMenuTermShow $ Just NewsR
breadcrumb TermCurrentR = i18nCrumb MsgMenuTermCurrent $ Just TermShowR
breadcrumb TermEditR = i18nCrumb MsgMenuTermCreate $ Just TermShowR
breadcrumb (TermEditExistR tid) = i18nCrumb MsgMenuTermEdit . Just $ TermCourseListR tid
@@ -1853,7 +2001,7 @@ instance YesodBreadcrumbs UniWorX where
<*> fmap isJust (get tid)
return (CI.original $ unSchoolKey ssh, Just $ TermCourseListR tid)
- breadcrumb AllocationListR = i18nCrumb MsgAllocationListTitle $ Just HomeR
+ breadcrumb AllocationListR = i18nCrumb MsgAllocationListTitle $ Just NewsR
breadcrumb (AllocationR tid ssh ash AShowR) = maybeT (i18nCrumb MsgBreadcrumbAllocation $ Just AllocationListR) $ do
mr <- getMessageRender
Entity _ Allocation{allocationName} <- MaybeT . runDB . getBy $ TermSchoolAllocationShort tid ssh ash
@@ -1940,9 +2088,9 @@ instance YesodBreadcrumbs UniWorX where
ECInviteR -> i18nCrumb MsgBreadcrumbExamCorrectorInvite . Just $ CExamR tid ssh csh examn EShowR
EInviteR -> i18nCrumb MsgBreadcrumbExamParticipantInvite . Just $ CExamR tid ssh csh examn EShowR
ERegisterR -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
- ECorrectR -> i18nCrumb MsgBreadcrumbExamCorrect . Just $ CExamR tid ssh csh examn EShowR
ERegisterOccR _occn -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
EAutoOccurrenceR -> i18nCrumb MsgBreadcrumbExamAutoOccurrence . Just $ CExamR tid ssh csh examn EUsersR
+ ECorrectR -> i18nCrumb MsgMenuExamCorrect . Just $ CExamR tid ssh csh examn EShowR
breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
TUsersR -> maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
@@ -2008,7 +2156,7 @@ instance YesodBreadcrumbs UniWorX where
mayList <- (== Authorized) <$> evalAccess MessageListR False
if
| mayList -> i18nCrumb MsgBreadcrumbSystemMessage $ Just MessageListR
- | otherwise -> i18nCrumb MsgBreadcrumbSystemMessage $ Just HomeR
+ | otherwise -> i18nCrumb MsgBreadcrumbSystemMessage $ Just NewsR
breadcrumb MessageListR = i18nCrumb MsgMenuMessageList $ Just AdminR
breadcrumb GlossaryR = i18nCrumb MsgMenuGlossary $ Just InfoR
@@ -2049,701 +2197,856 @@ submissionList tid csh shn uid = E.select . E.from $ \(course `E.InnerJoin` shee
-defaultLinks :: (MonadHandler m, HandlerSite m ~ UniWorX) => m [MenuItem]
+defaultLinks :: (MonadHandler m, HandlerSite m ~ UniWorX) => m [Nav]
defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header.
- [ return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuHome
- , menuItemIcon = Just "home"
- , menuItemRoute = SomeRoute HomeR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ [ return NavHeader
+ { navHeaderRole = NavHeaderSecondary
+ , navIcon = IconMenuLogout
+ , navLink = NavLink
+ { navLabel = MsgMenuLogout
+ , navRoute = AuthR LogoutR
+ , navAccess' = is _Just <$> maybeAuthId
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuDataProt
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ LegalR :#: ("data-protection" :: Text)
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return NavHeader
+ { navHeaderRole = NavHeaderSecondary
+ , navIcon = IconMenuLogin
+ , navLink = NavLink
+ { navLabel = MsgMenuLogin
+ , navRoute = AuthR LoginR
+ , navAccess' = is _Nothing <$> maybeAuthId
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuTermsUse
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ LegalR :#: ("terms-of-use" :: Text)
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuCopyright
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ LegalR :#: ("copyright" :: Text)
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuImprint
- , menuItemIcon = Just "file-signature"
- , menuItemRoute = SomeRoute $ LegalR :#: ("imprint" :: Text)
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuInformation
- , menuItemIcon = Just "info"
- , menuItemRoute = SomeRoute InfoR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , return MenuItem
- { menuItemType = Footer
- , menuItemLabel = MsgMenuGlossary
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute GlossaryR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return NavHeader
+ { navHeaderRole = NavHeaderSecondary
+ , navIcon = IconMenuProfile
+ , navLink = NavLink
+ { navLabel = MsgMenuProfile
+ , navRoute = ProfileR
+ , navAccess' = is _Just <$> maybeAuthId
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
, do
mCurrentRoute <- getCurrentRoute
- return MenuItem
- { menuItemType = NavbarRight
- , menuItemLabel = MsgMenuHelp
- , menuItemIcon = Just "question"
- , menuItemRoute = SomeRoute (HelpR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mCurrentRoute])
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+ activeLang <- selectLanguage appLanguages
+
+ let navChildren = flip map (toList appLanguages) $ \lang -> NavLink
+ { navLabel = MsgLanguage lang
+ , navRoute = (LangR, [(toPathPiece GetReferer, toPathPiece currentRoute) | let Just currentRoute = mCurrentRoute ])
+ , navAccess' = return True
+ , navType = NavTypeButton
+ { navMethod = POST
+ , navData = [(toPathPiece PostLanguage, lang)]
+ }
+ , navQuick' = mempty
+ , navForceActive = lang == activeLang
+ }
+
+ guard $ length navChildren > 1
+
+ return NavHeaderContainer
+ { navHeaderRole = NavHeaderSecondary
+ , navLabel = SomeMessage MsgMenuLanguage
+ , navIcon = IconLanguage
+ , navChildren
}
- , return MenuItem
- { menuItemType = NavbarRight
- , menuItemLabel = MsgMenuProfile
- , menuItemIcon = Just "cogs"
- , menuItemRoute = SomeRoute ProfileR
- , menuItemModal = False
- , menuItemAccessCallback' = isJust <$> maybeAuthPair
+ , do
+ mCurrentRoute <- getCurrentRoute
+
+ return NavHeader
+ { navHeaderRole = NavHeaderSecondary
+ , navIcon = IconMenuHelp
+ , navLink = NavLink
+ { navLabel = MsgMenuHelp
+ , navRoute = (HelpR, [(toPathPiece GetReferer, toPathPiece currentRoute) | let Just currentRoute = mCurrentRoute ])
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , return $ NavFooter NavLink
+ { navLabel = MsgMenuDataProt
+ , navRoute = LegalR :#: ("data-protection" :: Text)
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarSecondary
- , menuItemLabel = MsgMenuLogin
- , menuItemIcon = Just "sign-in-alt"
- , menuItemRoute = SomeRoute $ AuthR LoginR
- , menuItemModal = True
- , menuItemAccessCallback' = isNothing <$> maybeAuthPair
+ , return $ NavFooter NavLink
+ { navLabel = MsgMenuTermsUse
+ , navRoute = LegalR :#: ("terms-of-use" :: Text)
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarSecondary
- , menuItemLabel = MsgMenuLogout
- , menuItemIcon = Just "sign-out-alt"
- , menuItemRoute = SomeRoute $ AuthR LogoutR
- , menuItemModal = False
- , menuItemAccessCallback' = isJust <$> maybeAuthPair
+ , return $ NavFooter NavLink
+ { navLabel = MsgMenuCopyright
+ , navRoute = LegalR :#: ("copyright" :: Text)
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuTermShow
- , 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 $ NavFooter NavLink
+ { navLabel = MsgMenuImprint
+ , navRoute = LegalR :#: ("imprint" :: Text)
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuCourseList
- , menuItemIcon = Just "graduation-cap"
- , menuItemRoute = SomeRoute CourseListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return $ NavFooter NavLink
+ { navLabel = MsgMenuInformation
+ , navRoute = InfoR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuCorrections
- , menuItemIcon = Just "check"
- , menuItemRoute = SomeRoute CorrectionsR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return $ NavFooter NavLink
+ { navLabel = MsgMenuGlossary
+ , navRoute = GlossaryR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuExamOfficeExams
- , menuItemIcon = Just "poll-h"
- , menuItemRoute = SomeRoute $ ExamOfficeR EOExamsR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return NavHeader
+ { navHeaderRole = NavHeaderPrimary
+ , navIcon = IconMenuNews
+ , navLink = NavLink
+ { navLabel = MsgMenuNews
+ , navRoute = NewsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgMenuUsers
- , menuItemIcon = Just "users"
- , menuItemRoute = SomeRoute UsersR
- , menuItemModal = False
- , menuItemAccessCallback' = return True -- Creates a LOOP: (Authorized ==) <$> isAuthorized UsersR False
+ , return NavHeader
+ { navHeaderRole = NavHeaderPrimary
+ , navIcon = IconMenuCourseList
+ , navLink = NavLink
+ { navLabel = MsgMenuCourseList
+ , navRoute = CourseListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , return MenuItem
- { menuItemType = NavbarAside
- , menuItemLabel = MsgAdminHeading
- , menuItemIcon = Just "screwdriver"
- , menuItemRoute = SomeRoute AdminR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , return NavHeader
+ { navHeaderRole = NavHeaderPrimary
+ , navIcon = IconMenuCorrections
+ , navLink = NavLink
+ { navLabel = MsgMenuCorrections
+ , navRoute = CorrectionsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , return NavHeader
+ { navHeaderRole = NavHeaderPrimary
+ , navIcon = IconMenuExams
+ , navLink = NavLink
+ { navLabel = MsgMenuExamOfficeExams
+ , navRoute = ExamOfficeR EOExamsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , return NavHeaderContainer
+ { navHeaderRole = NavHeaderPrimary
+ , navLabel = SomeMessage MsgAdminHeading
+ , navIcon = IconMenuAdmin
+ , navChildren =
+ [ NavLink
+ { navLabel = MsgMenuUsers
+ , navRoute = UsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuSchoolList
+ , navRoute = SchoolListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgAdminFeaturesHeading
+ , navRoute = AdminFeaturesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuMessageList
+ , navRoute = MessageListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuAdminErrMsg
+ , navRoute = AdminErrMsgR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuAdminTest
+ , navRoute = AdminTestR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ ]
+ }
+ , return NavHeaderContainer
+ { navHeaderRole = NavHeaderPrimary
+ , navLabel = SomeMessage (mempty :: Text)
+ , navIcon = IconMenuExtra
+ , navChildren =
+ [ NavLink
+ { navLabel = MsgMenuCourseNew
+ , navRoute = CourseNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuExternalExamList
+ , navRoute = EExamListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuTermShow
+ , navRoute = TermShowR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgMenuAllocationList
+ , navRoute = AllocationListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , NavLink
+ { navLabel = MsgInfoLecturerTitle
+ , navRoute = InfoLecturerR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ ]
}
]
-pageActions :: Route UniWorX -> [MenuItem]
-{-
- Icons: https://fontawesome.com/icons?d=gallery
- Guideline: use icons without boxes/frames, only non-pro
+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
+ materialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh MaterialListR
+ tutorialListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CTutorialListR
+ sheetListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh SheetListR
+ examListSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CExamListR
+ membersSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CUsersR
- Please keep sorted according to routes
--}
-pageActions (HomeR) =
- [
- MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgInfoLecturerTitle
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute InfoLecturerR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ let examListBound :: Num a => a
+ examListBound = 4 -- guaranteed random; chosen by fair dice roll
+ examListExams <- liftHandler . runDB $ do
+ examNames <- E.select . E.from $ \(course `E.InnerJoin` exam) -> do
+ E.on $ exam E.^. ExamCourse E.==. course E.^. CourseId
+ 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
+ E.limit $ succ examListBound
+ return $ exam E.^. ExamName
+ return $ do
+ E.Value examn <- examNames
+ return NavLink
+ { navLabel = examn
+ , navRoute = CExamR tid ssh csh examn EShowR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ let showExamList = length examListExams <= examListBound
+
+ let
+ navMembers = NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseMembers
+ , navRoute = CourseR tid ssh csh CUsersR
+ , navAccess' =
+ 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
+ hasParticipants = E.selectExists . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
+ E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
+ void $ courseWhere course
+ mayRegister = hasWriteAccessTo $ CourseR tid ssh csh CAddUserR
+ in runDB $ mayRegister `or2M` hasParticipants
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = membersSecondary
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseNew
- , menuItemIcon = Just "book"
- , menuItemRoute = SomeRoute CourseNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ showMembers <- maybeT (return False) $ True <$ navAccess navMembers
+
+ 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 = materialListSecondary
}
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuExternalExamList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute EExamListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , 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
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuOpenCourses
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute (CourseListR, [("courses-openregistration", "True")])
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialList
+ , navRoute = CourseR tid ssh csh CTutorialListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = tutorialListSecondary
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuOpenAllocations
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute (AllocationListR, [("allocations-active", "True")])
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamList
+ , navRoute = CourseR tid ssh csh CExamListR
+ , navAccess' =
+ 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
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = bool (navQuick NavQuickViewFavourite) mempty showExamList
+ , navForceActive = False
+ }
+ , navChildren = examListSecondary ++ guardOnM showExamList examListExams
}
+ , navMembers
+ ] ++ guardOnM (not showMembers) [ NavPageActionPrimary{ navLink, navChildren = [] } | navLink <- membersSecondary ] ++
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseCommunication
+ , navRoute = CourseR tid ssh csh CCommR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseExamOffice
+ , navRoute = CourseR tid ssh csh CExamOfficeR
+ , navAccess' = 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
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseEdit
+ , navRoute = CourseR tid ssh csh CEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseClone
+ , navRoute = ( CourseNewR
+ , [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)]
+ )
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseDelete
+ , navRoute = CourseR tid ssh csh CDeleteR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
+ ]
+pageActions (ExamOfficeR EOExamsR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamOfficeFields
+ , navRoute = ExamOfficeR EOFieldsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamOfficeUsers
+ , navRoute = ExamOfficeR EOUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions (AdminR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSchoolList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute SchoolListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgAdminFeaturesHeading
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AdminFeaturesR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuMessageList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute MessageListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuAdminErrMsg
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AdminErrMsgR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuAdminTest
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AdminTestR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions SchoolListR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSchoolNew
+ , navRoute = SchoolNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (ExamOfficeR EOExamsR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamOfficeFields
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ ExamOfficeR EOFieldsR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+pageActions UsersR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuLecturerInvite
+ , navRoute = AdminNewFunctionaryInviteR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamOfficeUsers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ ExamOfficeR EOUsersR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuUserAdd
+ , navRoute = AdminUserAddR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (SchoolListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSchoolNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute SchoolNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (AdminUserR cID) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuUserNotifications
+ , navRoute = UserNotificationR cID
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
- ]
-pageActions (UsersR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuLecturerInvite
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AdminNewFunctionaryInviteR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuUserAdd
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AdminUserAddR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions (AdminUserR cID) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuUserNotifications
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ UserNotificationR cID
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuUserPassword
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ UserPasswordR cID
- , menuItemModal = True
- , menuItemAccessCallback' = do
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuUserPassword
+ , navRoute = UserPasswordR cID
+ , navAccess' = do
uid <- decrypt cID
User{userAuthentication} <- runDB $ get404 uid
return $ is _AuthPWHash userAuthentication
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (InfoR) = [
- MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgInfoLecturerTitle
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute InfoLecturerR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions InfoR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgInfoLecturerTitle
+ , navRoute = InfoLecturerR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuLegal
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute LegalR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuLegal
+ , navRoute = LegalR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuGlossary
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute GlossaryR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuGlossary
+ , navRoute = GlossaryR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (VersionR) = [
- MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgInfoLecturerTitle
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute InfoLecturerR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions VersionR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgInfoLecturerTitle
+ , navRoute = InfoLecturerR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuLegal
+ , navRoute = LegalR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuGlossary
+ , navRoute = GlossaryR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions HealthR = [
- MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuInstance
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute InstanceR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions HealthR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuInstance
+ , navRoute = InstanceR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions InstanceR = [
- MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuHealth
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute HealthR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions InstanceR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuHealth
+ , navRoute = HealthR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (HelpR) = [
- -- MenuItem
- -- { menuItemType = PageActionPrime
- -- , menuItemLabel = MsgInfoLecturerTitle
- -- , menuItemIcon = Nothing
- -- , menuItemRoute = SomeRoute InfoLecturerR
- -- , menuItemModal = False
- -- , menuItemAccessCallback' = return True
- -- }
+pageActions HelpR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgInfoLecturerTitle
+ , navRoute = InfoLecturerR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuGlossary
+ , navRoute = GlossaryR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions (ProfileR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuProfileData
- , menuItemIcon = Just "book"
- , menuItemRoute = SomeRoute ProfileDataR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions ProfileR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuProfileData
+ , navRoute = ProfileDataR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuAuthPreds
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AuthPredsR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuAuthPreds
+ , navRoute = AuthPredsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgCsvOptions
+ , navRoute = CsvOptionsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions TermShowR =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuTermCreate
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute TermEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions TermShowR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuTermCreate
+ , navRoute = TermEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuParticipantsList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute ParticipantsListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuParticipantsList
+ , navRoute = ParticipantsListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (TermCourseListR tid) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseNew
- , menuItemIcon = Just "book"
- , menuItemRoute = SomeRoute CourseNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (AllocationR _tid _ssh _ash AShowR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuAllocationInfo
+ , navRoute = InfoAllocationR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuTermEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ TermEditExistR tid
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ ]
+pageActions CourseListR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseNew
+ , navRoute = CourseNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuAllocationList
+ , navRoute = AllocationListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuParticipantsList
+ , navRoute = ParticipantsListR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ ]
+pageActions CourseNewR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgInfoLecturerTitle
+ , navRoute = InfoLecturerR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions (TermSchoolCourseListR _tid _ssh) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseNew
- , menuItemIcon = Just "book"
- , menuItemRoute = SomeRoute CourseNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CourseR tid ssh csh CCorrectionsR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsAssign
+ , navRoute = CourseR tid ssh csh CAssignR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
}
- ]
-pageActions (AllocationR _tid _ssh _ash AShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuAllocationInfo
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute InfoAllocationR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions (CourseListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseNew
- , menuItemIcon = Just "book"
- , menuItemRoute = SomeRoute CourseNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuAllocationList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute AllocationListR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuParticipantsList
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute ParticipantsListR
- , menuItemModal = False
- , 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
- , 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
- , menuItemLabel = MsgMenuCorrectionsAssign
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CAssignR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions (CourseR tid ssh csh SheetListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSheetCurrent
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetCurrentR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
- void . MaybeT $ sheetCurrent tid ssh csh
- return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSheetOldUnassigned
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetOldUnassignedR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
- void . MaybeT $ sheetOldUnassigned tid ssh csh
- return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSubmissions
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CCorrectionsR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsAssign
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CAssignR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsOwn
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute (CorrectionsR, [ ("corrections-term" , termToText $ unTermKey tid)
- , ("corrections-school", CI.original $ unSchoolKey ssh)
- , ("corrections-course", CI.original csh)
- ])
- , menuItemModal = False
- , menuItemAccessCallback' = do
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsOwn
+ , navRoute = ( CorrectionsR
+ , [ ("corrections-term", toPathPiece tid)
+ , ("corrections-school", toPathPiece ssh)
+ , ("corrections-course", toPathPiece csh)
+ ]
+ )
+ , navAccess' = do
muid <- maybeAuthId
case muid of
Nothing -> return False
@@ -2756,32 +3059,90 @@ pageActions (CourseR tid ssh csh SheetListR) =
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
return ok
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSheetNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh SheetNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
+ , navChildren = []
+ }
]
-pageActions (CourseR tid ssh csh CUsersR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseAddMembers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CAddUserR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+pageActions (CourseR tid ssh csh SheetListR) = do
+ correctionsSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CourseR tid ssh csh CCorrectionsR
+
+ let
+ navCorrections = NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissions
+ , navRoute = CourseR tid ssh csh CCorrectionsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = correctionsSecondary
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseApplications
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CApplicationsR
- , menuItemModal = False
- , menuItemAccessCallback' =
+ showCorrections <- maybeT (return False) $ True <$ navAccess navCorrections
+
+ return $
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetCurrent
+ , navRoute = CourseR tid ssh csh SheetCurrentR
+ , navAccess' =
+ runDB . maybeT (return False) $ do
+ void . MaybeT $ sheetCurrent tid ssh csh
+ return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetOldUnassigned
+ , navRoute = CourseR tid ssh csh SheetOldUnassignedR
+ , navAccess' =
+ runDB . maybeT (return False) $ do
+ void . MaybeT $ sheetOldUnassigned tid ssh csh
+ return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , navCorrections
+ ] ++ guardOnM (not showCorrections) [ NavPageActionPrimary{ navLink, navChildren = [] } | navLink <- correctionsSecondary ] ++
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetNew
+ , navRoute = CourseR tid ssh csh SheetNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ ]
+pageActions (CourseR tid ssh csh CUsersR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseAddMembers
+ , navRoute = CourseR tid ssh csh CAddUserR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseApplications
+ , navRoute = CourseR tid ssh csh CApplicationsR
+ , navAccess' =
let courseWhere course = course <$ do
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
@@ -2796,295 +3157,442 @@ pageActions (CourseR tid ssh csh CUsersR) =
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
void $ courseWhere course
in runDB $ courseAllocation `or2M` courseApplications `or2M` existsApplications
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CourseR tid ssh csh MaterialListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuMaterialNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh MaterialNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CourseR tid ssh csh MaterialListR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuMaterialNew
+ , navRoute = CourseR tid ssh csh MaterialNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CMaterialR tid ssh csh mnm MShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuMaterialEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CMaterialR tid ssh csh mnm MEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CMaterialR tid ssh csh mnm MShowR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuMaterialEdit
+ , navRoute = CMaterialR tid ssh csh mnm MEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuMaterialDelete
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CMaterialR tid ssh csh mnm MDelR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuMaterialDelete
+ , navRoute = CMaterialR tid ssh csh mnm MDelR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ }
]
-pageActions (CourseR tid ssh csh CTutorialListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuTutorialNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CTutorialNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CourseR tid ssh csh CTutorialListR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialNew
+ , navRoute = CourseR tid ssh csh CTutorialNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CTutorialR tid ssh csh tutn TEditR) =
- [ MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuTutorialDelete
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CTutorialR tid ssh csh tutn TDeleteR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CTutorialR tid ssh csh tutn TEditR) = return
+ [ NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialDelete
+ , navRoute = CTutorialR tid ssh csh tutn TDeleteR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ }
]
-pageActions (CTutorialR tid ssh csh tutn TUsersR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuTutorialComm
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CTutorialR tid ssh csh tutn TUsersR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialComm
+ , navRoute = CTutorialR tid ssh csh tutn TCommR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuTutorialEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CTutorialR tid ssh csh tutn TEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialEdit
+ , navRoute = CTutorialR tid ssh csh tutn TEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuTutorialDelete
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CTutorialR tid ssh csh tutn TDeleteR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuTutorialDelete
+ , navRoute = CTutorialR tid ssh csh tutn TDeleteR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ }
]
-pageActions (CourseR tid ssh csh CExamListR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CExamNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CourseR tid ssh csh CExamListR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamNew
+ , navRoute = CourseR tid ssh csh CExamNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CExamR tid ssh csh examn EShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CExamR tid ssh csh examn EShowR) = do
+ usersSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CExamR tid ssh csh examn EUsersR
+
+ return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamEdit
+ , navRoute = CExamR tid ssh csh examn EEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamUsers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EUsersR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamUsers
+ , navRoute = CExamR tid ssh csh examn EUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = usersSecondary
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamGrades
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EGradesR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamGrades
+ , navRoute = CExamR tid ssh csh examn EGradesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamCorrect
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn ECorrectR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamCorrect
+ , navRoute = CExamR tid ssh csh examn ECorrectR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
+ ]
+pageActions (CExamR tid ssh csh examn ECorrectR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamUsers
+ , navRoute = CExamR tid ssh csh examn EUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamGrades
+ , navRoute = CExamR tid ssh csh examn EGradesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamEdit
+ , navRoute = CExamR tid ssh csh examn EEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
]
-pageActions (CExamR tid ssh csh examn EUsersR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamAddMembers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EAddUserR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+pageActions (CExamR tid ssh csh examn EUsersR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamAddMembers
+ , navRoute = CExamR tid ssh csh examn EAddUserR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamGrades
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EGradesR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamGrades
+ , navRoute = CExamR tid ssh csh examn EGradesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamCorrect
+ , navRoute = CExamR tid ssh csh examn ECorrectR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions (CExamR tid ssh csh examn EGradesR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExamUsers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CExamR tid ssh csh examn EUsersR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CExamR tid ssh csh examn EGradesR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamUsers
+ , navRoute = CExamR tid ssh csh examn EUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExamCorrect
+ , navRoute = CExamR tid ssh csh examn ECorrectR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
]
-pageActions (CSheetR tid ssh csh shn SShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSubmissionNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SubmissionNewR
- , menuItemModal = True
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
- uid <- MaybeT $ liftHandler maybeAuthId
- submissions <- lift $ submissionList tid csh shn uid
- guard $ null submissions
- return True
+pageActions (CSheetR tid ssh csh shn SShowR) = do
+ subsSecondary <- pageQuickActions NavQuickViewPageActionSecondary $ CSheetR tid ssh csh shn SSubsR
+ let
+ navSubmissions = NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissions
+ , navRoute = CSheetR tid ssh csh shn SSubsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = subsSecondary
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSubmissionOwn
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SubmissionOwnR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
- uid <- MaybeT $ liftHandler maybeAuthId
- submissions <- lift $ submissionList tid csh shn uid
- guard . not $ null submissions
- return True
+ showSubmissions <- maybeT (return False) $ True <$ navAccess navSubmissions
+
+ return $
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissionOwn
+ , navRoute = CSheetR tid ssh csh shn SubmissionOwnR
+ , navAccess' =
+ runDB . maybeT (return False) $ do
+ uid <- MaybeT $ liftHandler maybeAuthId
+ submissions <- lift $ submissionList tid csh shn uid
+ guard . not $ null submissions
+ return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsOwn
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute (CorrectionsR, [ ("corrections-term" , termToText $ unTermKey tid)
- , ("corrections-school", CI.original $ unSchoolKey ssh)
- , ("corrections-course", CI.original csh)
- , ("corrections-sheet" , CI.original shn)
- ])
- , menuItemModal = False
- , menuItemAccessCallback' = (== Authorized) <$> evalAccessCorrector tid ssh csh
+ , navSubmissions
+ ] ++ guardOnM (not showSubmissions) [ NavPageActionPrimary{ navLink, navChildren = [] } | navLink <- subsSecondary ] ++
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetEdit
+ , navRoute = CSheetR tid ssh csh shn SEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSubmissions
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SSubsR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetClone
+ , navRoute = (CourseR tid ssh csh SheetNewR, [("shn", toPathPiece shn)])
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsAssign
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SAssignR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuSheetDelete
+ , navRoute = CSheetR tid ssh csh shn SDelR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSheetEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ ]
+pageActions (CSheetR tid ssh csh shn SSubsR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissionNew
+ , navRoute = CSheetR tid ssh csh shn SubmissionNewR
+ , navAccess' =
+ let submissionAccess = hasWriteAccessTo $ CSheetR tid ssh csh shn SSubsR
+ hasNoSubmission = maybeT (return False) $ do
+ uid <- MaybeT $ liftHandler maybeAuthId
+ submissions <- lift $ submissionList tid csh shn uid
+ guard $ null submissions
+ return True
+ in runDB $ hasNoSubmission `or2M` submissionAccess
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuSheetClone
- , menuItemIcon = Just "copy"
- , menuItemRoute = SomeRoute (CourseR tid ssh csh SheetNewR, [("shn", toPathPiece shn)])
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsOwn
+ , navRoute = ( CorrectionsR
+ , [ ("corrections-term", toPathPiece tid)
+ , ("corrections-school", toPathPiece ssh)
+ , ("corrections-course", toPathPiece csh)
+ , ("corrections-sheet", toPathPiece shn)
+ ]
+ )
+ , navAccess' = (== Authorized) <$> evalAccessCorrector tid ssh csh
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuSheetDelete
- , menuItemIcon = Just "trash"
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SDelR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsAssign
+ , navRoute = CSheetR tid ssh csh shn SAssignR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CSheetR tid ssh csh shn SSubsR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuSubmissionNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SubmissionNewR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+pageActions (CSubmissionR tid ssh csh shn cid SubShowR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrection
+ , navRoute = CSubmissionR tid ssh csh shn cid CorrectionR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsAssign
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSheetR tid ssh csh shn SAssignR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgCorrectorAssignTitle
+ , navRoute = CSubmissionR tid ssh csh shn cid SubAssignR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissionDelete
+ , navRoute = CSubmissionR tid ssh csh shn cid SubDelR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ }
]
-pageActions (CSubmissionR tid ssh csh shn cid SubShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrection
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid CorrectionR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgCorrectorAssignTitle
+ , navRoute = CSubmissionR tid ssh csh shn cid SubAssignR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgCorrectorAssignTitle
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubAssignR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuSubmissionDelete
- , menuItemIcon = Just "trash"
- , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubDelR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionSecondary
+ { navLink = NavLink
+ { navLabel = MsgMenuSubmissionDelete
+ , navRoute = CSubmissionR tid ssh csh shn cid SubDelR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ }
]
-pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) =
- [ MenuItem
- { menuItemType = PageActionSecondary
- , menuItemLabel = MsgMenuSubmissionDelete
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CSubmissionR tid ssh csh shn cid SubDelR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions (CourseR tid ssh csh CApplicationsR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseApplicationsFiles
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CAppsFilesR
- , menuItemModal = False
- , menuItemAccessCallback'
- = let appAccess (E.Value appId) = do
+pageActions (CourseR tid ssh csh CApplicationsR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseApplicationsFiles
+ , navRoute = CourseR tid ssh csh CAppsFilesR
+ , navAccess' =
+ let appAccess (E.Value appId) = do
cID <- encrypt appId
hasReadAccessTo $ CApplicationR tid ssh csh cID CAFilesR
appSource = E.selectSource . E.from $ \(course `E.InnerJoin` courseApplication) -> do
@@ -3096,42 +3604,55 @@ pageActions (CourseR tid ssh csh CApplicationsR) =
E.where_ $ courseApplicationFile E.^. CourseApplicationFileApplication E.==. courseApplication E.^. CourseApplicationId
return $ courseApplication E.^. CourseApplicationId
in runDB . runConduit $ appSource .| anyMC appAccess
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCourseMembers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ CourseR tid ssh csh CUsersR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB $ do
- cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
- exists [ CourseParticipantCourse ==. cid ]
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCourseMembers
+ , navRoute = CourseR tid ssh csh CUsersR
+ , navAccess' =
+ runDB $ do
+ cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
+ exists [ CourseParticipantCourse ==. cid ]
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
}
+ , navChildren = []
+ }
]
-pageActions (CorrectionsR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsDownload
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsDownloadR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions CorrectionsR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsDownload
+ , navRoute = CorrectionsDownloadR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsUpload
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsUploadR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsUpload
+ , navRoute = CorrectionsUploadR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsCreate
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsCreateR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsCreate
+ , navRoute = CorrectionsCreateR
+ , navAccess' = runDB . maybeT (return False) $ do
uid <- MaybeT $ liftHandler maybeAuthId
sheets <- lift . E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
@@ -3145,114 +3666,137 @@ pageActions (CorrectionsR) =
E.where_ $ isCorrector' E.||. isLecturer
return $ sheet E.^. SheetSubmissionMode
return $ orOf (traverse . _Value . _submissionModeCorrector) sheets
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = navQuick NavQuickViewPageActionSecondary
+ , navForceActive = False
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsGrade
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsGradeR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrectionsGrade
+ , navRoute = CorrectionsGradeR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ ]
+pageActions CorrectionsGradeR = do
+ correctionsSecondary <- pageQuickActions NavQuickViewPageActionSecondary CorrectionsR
+ return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuCorrections
+ , navRoute = CorrectionsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = correctionsSecondary
+ }
+ ]
+pageActions EExamListR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamNew
+ , navRoute = EExamNewR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
]
-pageActions (CorrectionsGradeR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsUpload
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsUploadR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
+pageActions (EExamR tid ssh coursen examn EEShowR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamEdit
+ , navRoute = EExamR tid ssh coursen examn EEEditR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuCorrectionsCreate
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CorrectionsCreateR
- , menuItemModal = False
- , menuItemAccessCallback' = runDB . maybeT (return False) $ do
- uid <- MaybeT $ liftHandler maybeAuthId
- sheets <- lift . E.select . E.from $ \(course `E.InnerJoin` sheet) -> do
- E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
- let
- isCorrector' = E.exists . E.from $ \sheetCorrector -> E.where_
- $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val uid
- E.&&. sheetCorrector E.^. SheetCorrectorSheet E.==. sheet E.^. SheetId
- isLecturer = E.exists . E.from $ \lecturer -> E.where_
- $ lecturer E.^. LecturerUser E.==. E.val uid
- E.&&. lecturer E.^. LecturerCourse E.==. course E.^. CourseId
- E.where_ $ isCorrector' E.||. isLecturer
- return $ sheet E.^. SheetSubmissionMode
- return $ orOf (traverse . _Value . _submissionModeCorrector) sheets
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamUsers
+ , navRoute = EExamR tid ssh coursen examn EEUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
+ }
+ , NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamGrades
+ , navRoute = EExamR tid ssh coursen examn EEGradesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
]
-pageActions EExamListR =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamNew
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute EExamNewR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (EExamR tid ssh coursen examn EEGradesR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamUsers
+ , navRoute = EExamR tid ssh coursen examn EEUsersR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
]
-pageActions (EExamR tid ssh coursen examn EEShowR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamEdit
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ EExamR tid ssh coursen examn EEEditR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamUsers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ EExamR tid ssh coursen examn EEUsersR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- , MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamGrades
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ EExamR tid ssh coursen examn EEGradesR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions (EExamR tid ssh coursen examn EEUsersR) = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgMenuExternalExamGrades
+ , navRoute = EExamR tid ssh coursen examn EEGradesR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = False }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
]
-pageActions (EExamR tid ssh coursen examn EEGradesR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamUsers
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ EExamR tid ssh coursen examn EEUsersR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
+pageActions ParticipantsListR = return
+ [ NavPageActionPrimary
+ { navLink = NavLink
+ { navLabel = MsgCsvOptions
+ , navRoute = CsvOptionsR
+ , navAccess' = return True
+ , navType = NavTypeLink { navModal = True }
+ , navQuick' = mempty
+ , navForceActive = False
+ }
+ , navChildren = []
}
]
-pageActions (EExamR tid ssh coursen examn EEUsersR) =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgMenuExternalExamGrades
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute $ EExamR tid ssh coursen examn EEGradesR
- , menuItemModal = False
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions ParticipantsListR =
- [ MenuItem
- { menuItemType = PageActionPrime
- , menuItemLabel = MsgCsvOptions
- , menuItemIcon = Nothing
- , menuItemRoute = SomeRoute CsvOptionsR
- , menuItemModal = True
- , menuItemAccessCallback' = return True
- }
- ]
-pageActions _ = []
+pageActions _ = return []
+
+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 ()
@@ -3262,8 +3806,8 @@ i18nHeading msg = liftWidget $ toWidget =<< getMessageRender <*> pure msg
pageHeading :: Route UniWorX -> Maybe Widget
pageHeading (AuthR _)
= Just $ i18nHeading MsgLoginHeading
-pageHeading HomeR
- = Just $ i18nHeading MsgHomeHeading
+pageHeading NewsR
+ = Just $ i18nHeading MsgNewsHeading
pageHeading UsersR
= Just $ i18nHeading MsgUsers
pageHeading (AdminUserR _)
@@ -3889,9 +4433,9 @@ instance YesodAuth UniWorX where
type AuthId UniWorX = UserId
-- Where to send a user after successful login
- loginDest _ = HomeR
+ loginDest _ = NewsR
-- Where to send a user after logout
- logoutDest _ = HomeR
+ logoutDest _ = NewsR
-- Override the above two destinations when a Referer: header is present
redirectToReferer _ = True
diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs
index 99406decd..028be1e3a 100644
--- a/src/Foundation/I18n.hs
+++ b/src/Foundation/I18n.hs
@@ -168,7 +168,7 @@ instance RenderMessage UniWorX MsgLanguage where
| ("en" : _) <- lang' = mr MsgEnglish
| otherwise = lang
where
- mr = renderMessage foundation ls
+ mr = renderMessage foundation $ lang : filter (/= lang) ls
embedRenderMessage ''UniWorX ''MessageStatus ("Message" <>)
embedRenderMessage ''UniWorX ''NotificationTrigger $ ("NotificationTrigger" <>) . concat . drop 1 . splitCamel
diff --git a/src/Handler/Exam.hs b/src/Handler/Exam.hs
index ef54cc1ef..0e3c209dd 100644
--- a/src/Handler/Exam.hs
+++ b/src/Handler/Exam.hs
@@ -11,5 +11,5 @@ import Handler.Exam.Edit as Handler.Exam
import Handler.Exam.Show as Handler.Exam
import Handler.Exam.Users as Handler.Exam
import Handler.Exam.AddUser as Handler.Exam
-import Handler.Exam.Correct as Handler.Exam
import Handler.Exam.AutoOccurrence as Handler.Exam
+import Handler.Exam.Correct as Handler.Exam
diff --git a/src/Handler/Exam/Correct.hs b/src/Handler/Exam/Correct.hs
index 70996ab93..447a4c301 100644
--- a/src/Handler/Exam/Correct.hs
+++ b/src/Handler/Exam/Correct.hs
@@ -38,7 +38,8 @@ data CorrectInterfaceResponse
, ciraMessage :: Text
}
| CorrectInterfaceResponseFailure
- { cirfMessage :: Text
+ { cirfUser :: Maybe CorrectInterfaceUser
+ , cirfMessage :: Text
}
| CorrectInterfaceResponseNoOp
{ cirnUsers :: Set CorrectInterfaceUser
@@ -84,8 +85,6 @@ getECorrectR tid ssh csh examn = do
name <- newIdent
fieldView (pointsField :: Field Handler Points) ("exam-correct__" <> toPathPiece n) name [("uw-exam-correct--part-input", toPathPiece n)] (Left "") False
- participantHeadTooltip = [whamlet| _{MsgExamCorrectHeadParticipantTooltip} |]
-
examGrades :: [ExamGrade]
examGrades = universeF
@@ -93,6 +92,7 @@ getECorrectR tid ssh csh examn = do
siteLayoutMsg heading $ do
setTitleI heading
+ let examCorrectExplanation = $(i18nWidgetFile "exam-correct-explanation")
$(widgetFile "exam-correct")
@@ -102,11 +102,11 @@ postECorrectR tid ssh csh examn = do
CorrectInterfaceRequest{..} <- requireCheckJsonBody
- response <- runDB $ do
- Entity eId Exam{..} <- fetchExam tid ssh csh examn
+ response <- exceptT return return . hoist runDB $ do
+ Entity eId Exam{..} <- lift $ fetchExam tid ssh csh examn
euid <- traverse decrypt ciqUser
- participantMatches <- E.select . E.from $ \(examRegistration `E.InnerJoin` user) -> do
+ participantMatches <- lift . E.select . E.from $ \(examRegistration `E.InnerJoin` user) -> do
E.on $ examRegistration E.^. ExamRegistrationUser E.==. user E.^. UserId
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
@@ -149,10 +149,10 @@ postECorrectR tid ssh csh examn = do
now <- liftIO getCurrentTime
newExamPartResults <- if
| Just results <- ciqResults -> iforM (toNullable results) $ \partNumber mPartResult -> do
- (Entity examPartId ExamPart{..}) <- getBy404 $ UniqueExamPartNumber eId partNumber
- mOldResult <- getBy $ UniqueExamPartResult examPartId uid
+ (Entity examPartId ExamPart{..}) <- lift . getBy404 $ UniqueExamPartNumber eId partNumber
+ mOldResult <- lift . getBy $ UniqueExamPartResult examPartId uid
if
- | Just (Entity oldId _) <- mOldResult, is _Nothing mPartResult -> do
+ | Just (Entity oldId _) <- mOldResult, is _Nothing mPartResult -> lift $ do
delete oldId
audit $ TransactionExamPartResultDeleted examPartId uid
return Nothing
@@ -161,29 +161,36 @@ postECorrectR tid ssh csh examn = do
mNew = ExamAttended <$> mPartResult
resultVal = _entityVal . _examPartResultResult
in if
- | mOld /= mNew -> let
- -- cut off part results that exceed the maximum number of points for this exam part for now
- -- TODO answer with new failure response type instead
- partResult' = if
- | Just maxPts <- examPartMaxPoints, maxPts < partResult -> maxPts
- | otherwise -> partResult
- in do
- newExamPartResult <- upsert ExamPartResult
+ | mOld /= mNew -> do
+ let
+ partResultAcceptable = 0 <= partResult
+ && maybe True (partResult <=) examPartMaxPoints
+ guardMExceptT partResultAcceptable $
+ let
+ msg | Just maxPoints <- examPartMaxPoints
+ = MsgExamCorrectErrorPartResultOutOfBoundsMax partNumber maxPoints
+ | otherwise
+ = MsgExamCorrectErrorPartResultOutOfBounds partNumber
+ in CorrectInterfaceResponseFailure
+ <$> (Just <$> userToResponse match)
+ <*> (getMessageRender <*> pure msg)
+
+ newExamPartResult <- lift $ upsert ExamPartResult
{ examPartResultExamPart = examPartId
, examPartResultUser = uid
- , examPartResultResult = ExamAttended partResult'
+ , examPartResultResult = ExamAttended partResult
, examPartResultLastChanged = now
}
- [ ExamPartResultResult =. ExamAttended partResult'
+ [ ExamPartResultResult =. ExamAttended partResult
, ExamPartResultLastChanged =. now
]
- audit $ TransactionExamPartResultEdit examPartId uid
+ lift . audit $ TransactionExamPartResultEdit examPartId uid
return $ newExamPartResult ^? resultVal
| otherwise -> return $ mOldResult ^? _Just . resultVal
| otherwise -> return Nothing
| otherwise -> return mempty
- newExamResult <- do
+ newExamResult <- lift $ do
mOldResult <- getBy $ UniqueExamResult eId uid
if
| Just (Entity oldId _) <- mOldResult, is _Nothing ciqGrade -> do
@@ -221,7 +228,8 @@ postECorrectR tid ssh csh examn = do
-- on match with no exam participant, answer with 400
| [] <- participantMatches -> return CorrectInterfaceResponseFailure
- { cirfMessage = mr MsgExamCorrectErrorNoMatchingParticipants
+ { cirfUser = Nothing
+ , cirfMessage = mr MsgExamCorrectErrorNoMatchingParticipants
}
-- on match with multiple exam participants, answer with 400 and a set of all matches
diff --git a/src/Handler/Exam/Show.hs b/src/Handler/Exam/Show.hs
index e1bec059e..8a01b551b 100644
--- a/src/Handler/Exam/Show.hs
+++ b/src/Handler/Exam/Show.hs
@@ -82,6 +82,7 @@ getEShowR tid ssh csh examn = do
examClosedShown = lecturerInfoShown
showCloseWidget = lecturerInfoShown
showAutoOccurrenceCalculateWidget = lecturerInfoShown
+ examFinishedMsg = if lecturerInfoShown then MsgExamFinished else MsgExamFinishedParticipant
sumMaxPoints = sum [ fromRational examPartWeight * mPoints | Entity _ ExamPart{..} <- examParts, mPoints <- examPartMaxPoints ^.. _Just ]
diff --git a/src/Handler/Exam/Users.hs b/src/Handler/Exam/Users.hs
index eee9a53b0..2d82115c7 100644
--- a/src/Handler/Exam/Users.hs
+++ b/src/Handler/Exam/Users.hs
@@ -1089,5 +1089,6 @@ postEUsersR tid ssh csh examn = do
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamUsersHeading) $ do
setTitleI $ prependCourseTitle tid ssh csh MsgExamUsersHeading
- let computedValuesTip = $(i18nWidgetFile "exam-users/computed-values-tip")
+ let computedValuesTip = notificationWidget NotificationBroad Warning
+ $(i18nWidgetFile "exam-users/computed-values-tip")
$(widgetFile "exam-users")
diff --git a/src/Handler/ExamOffice/Exam.hs b/src/Handler/ExamOffice/Exam.hs
index fbb18a591..c92ec1e62 100644
--- a/src/Handler/ExamOffice/Exam.hs
+++ b/src/Handler/ExamOffice/Exam.hs
@@ -441,4 +441,5 @@ postEGradesR tid ssh csh examn = do
siteLayoutMsg (prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading) $ do
setTitleI $ prependCourseTitle tid ssh csh MsgExamOfficeExamUsersHeading
+ let examGradesExplanation = notificationWidget NotificationBroad Info $(i18nWidgetFile "exam-office/exam-grades-explanation")
$(widgetFile "exam-office/exam-results")
diff --git a/src/Handler/ExamOffice/ExternalExam.hs b/src/Handler/ExamOffice/ExternalExam.hs
index 2d7978fbc..db65d9c8c 100644
--- a/src/Handler/ExamOffice/ExternalExam.hs
+++ b/src/Handler/ExamOffice/ExternalExam.hs
@@ -30,4 +30,5 @@ postEEGradesR tid ssh coursen examn = do
siteLayoutMsg (MsgExternalExamGrades coursen examn) $ do
setTitleI MsgBreadcrumbExternalExamGrades
+ let examGradesExplanation = notificationWidget NotificationBroad Info $(i18nWidgetFile "exam-office/exam-grades-explanation")
$(widgetFile "exam-office/externalExamGrades")
diff --git a/src/Handler/ExamOffice/Users.hs b/src/Handler/ExamOffice/Users.hs
index fd03b912b..ab5588677 100644
--- a/src/Handler/ExamOffice/Users.hs
+++ b/src/Handler/ExamOffice/Users.hs
@@ -80,7 +80,7 @@ examOfficeUserInvitationConfig = InvitationConfig{..}
return res
invitationSuccessMsg _ _ =
return $ SomeMessage MsgExamOfficeUserInvitationAccepted
- invitationUltDest _ _ = return $ SomeRoute HomeR
+ invitationUltDest _ _ = return $ SomeRoute NewsR
makeExamOfficeUsersForm :: Maybe (Set (Either UserEmail UserId)) -> Form (Set (Either UserEmail UserId))
diff --git a/src/Handler/Home.hs b/src/Handler/News.hs
similarity index 96%
rename from src/Handler/Home.hs
rename to src/Handler/News.hs
index 532326ec3..e5eab1715 100644
--- a/src/Handler/Home.hs
+++ b/src/Handler/News.hs
@@ -1,4 +1,4 @@
-module Handler.Home where
+module Handler.News where
import Import
@@ -9,21 +9,25 @@ import Database.Esqueleto.Utils.TH
import qualified Database.Esqueleto as E
import qualified Database.Esqueleto.Utils as E
-getHomeR :: Handler Html
-getHomeR = do
+getNewsR :: Handler Html
+getNewsR = do
muid <- maybeAuthId
defaultLayout $ do
- setTitleI MsgHomeHeading
+ setTitleI MsgNewsHeading
+
+ when (is _Nothing muid) $
+ notificationWidget NotificationBroad Info $(i18nWidgetFile "pitch")
+
case muid of
Just uid -> do
- homeUpcomingExams uid
- homeUpcomingSheets uid
+ newsUpcomingExams uid
+ newsUpcomingSheets uid
Nothing ->
- $(i18nWidgetFile "unauth-home")
+ $(i18nWidgetFile "unauth-news")
-homeUpcomingSheets :: UserId -> Widget
-homeUpcomingSheets uid = do
+newsUpcomingSheets :: UserId -> Widget
+newsUpcomingSheets uid = do
cTime <- liftIO getCurrentTime
let tableData :: E.LeftOuterJoin
(E.InnerJoin (E.InnerJoin (E.SqlExpr (Entity CourseParticipant)) (E.SqlExpr (Entity Course))) (E.SqlExpr (Entity Sheet)))
@@ -121,11 +125,11 @@ homeUpcomingSheets uid = do
, dbtCsvEncode = noCsvEncode
, dbtCsvDecode = Nothing
}
- $(widgetFile "home/upcomingSheets")
+ $(widgetFile "news/upcomingSheets")
-homeUpcomingExams :: UserId -> Widget
-homeUpcomingExams uid = do
+newsUpcomingExams :: UserId -> Widget
+newsUpcomingExams uid = do
now <- liftIO getCurrentTime
((Any hasExams, examTable), warningDays) <- liftHandler . runDB $ do
User {userWarningDays} <- get404 uid
@@ -255,6 +259,6 @@ homeUpcomingExams uid = do
(, userWarningDays) <$> dbTable examDBTableValidator examDBTable
- $(widgetFile "home/upcomingExams")
+ $(widgetFile "news/upcomingExams")
diff --git a/src/Handler/Profile.hs b/src/Handler/Profile.hs
index c13c3f13f..b34ce9cb2 100644
--- a/src/Handler/Profile.hs
+++ b/src/Handler/Profile.hs
@@ -850,17 +850,14 @@ postCsvOptionsR = do
, formAttrs = [ asyncSubmitAttr | isModal ]
}
-postLangR :: Handler ()
+postLangR :: Handler Void
postLangR = do
- ((langRes, _), _) <- runFormPost $ identifyForm FIDLanguage langForm
+ requestedLang <- selectLanguage' appLanguages . hoistMaybe <$> lookupGlobalPostParam PostLanguage
+ lang' <- runDB . updateUserLanguage $ Just requestedLang
- formResult langRes $ \(lang, route) -> do
- lang' <- runDB . updateUserLanguage $ Just lang
-
- app <- getYesod
- let mr | Just lang'' <- lang' = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang''
- | otherwise = renderMessage app []
- addMessage Success . toHtml $ mr MsgLanguageChanged
- redirect route
+ app <- getYesod
+ let mr | Just lang'' <- lang' = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang''
+ | otherwise = renderMessage app []
+ addMessage Success . toHtml $ mr MsgLanguageChanged
- invalidArgs ["Language form required"]
+ redirect . fromMaybe NewsR =<< lookupGlobalGetParam GetReferer
diff --git a/src/Handler/Utils/Invitations.hs b/src/Handler/Utils/Invitations.hs
index 5e3489ac0..983ea7b3c 100644
--- a/src/Handler/Utils/Invitations.hs
+++ b/src/Handler/Utils/Invitations.hs
@@ -399,7 +399,7 @@ invitationR' InvitationConfig{..} = liftHandler $ do
Nothing -> do
addMessageI Info MsgInvitationDeclined
deleteBy . UniqueInvitation itEmail $ invRef @junction fid
- return . Just $ SomeRoute HomeR
+ return . Just $ SomeRoute NewsR
Just (jData, formCtx) -> do
let junction = review _InvitableJunction (invitee, fid, jData)
mResult <- invitationInsertHook itEmail fEnt iData junction formCtx $ insertUniqueEntity junction
diff --git a/src/Import/NoFoundation.hs b/src/Import/NoFoundation.hs
index b722fb338..c2f083f37 100644
--- a/src/Import/NoFoundation.hs
+++ b/src/Import/NoFoundation.hs
@@ -10,6 +10,7 @@ import Model.Submission as Import
import Model.Tokens as Import
import Utils.Tokens as Import
import Utils.Frontend.Modal as Import
+import Utils.Frontend.Notification as Import
import Utils.Lens as Import
import Settings as Import
diff --git a/src/Utils.hs b/src/Utils.hs
index f924d3141..c1a05222d 100644
--- a/src/Utils.hs
+++ b/src/Utils.hs
@@ -630,6 +630,15 @@ assertM' f x = x <$ guard (f x)
guardOn :: Alternative m => Bool -> a -> m a
guardOn b x = x <$ guard b
+guardOnM :: Alternative m => Bool -> m a -> m a
+guardOnM b x = guard b *> x
+
+guardMOn :: MonadPlus m => m Bool -> a -> m a
+guardMOn b x = x <$ guardM b
+
+guardMOnM :: MonadPlus m => m Bool -> m a -> m a
+guardMOnM b x = guardM b *> x
+
-- Some Utility Functions from Agda.Utils.Monad
-- | Monadic if-then-else.
ifM :: Monad m => m Bool -> m a -> m a -> m a
diff --git a/src/Utils/Form.hs b/src/Utils/Form.hs
index 78af6cfaf..3a3e2e8f6 100644
--- a/src/Utils/Form.hs
+++ b/src/Utils/Form.hs
@@ -46,6 +46,7 @@ import Data.Scientific
import Data.Time.Clock (NominalDiffTime, nominalDay)
import Utils
+import Utils.Frontend.Notification
-- import Utils.Message
-- import Utils.PathPiece
-- import Utils.Route
@@ -869,26 +870,15 @@ wformMessage :: (MonadHandler m) => Message -> WForm m ()
wformMessage = void . aFormToWForm . aformMessage
formMessage :: (MonadHandler m) => Message -> MForm m (FormResult (), FieldView site)
-formMessage Message{..} = do
+formMessage msg = do
return (FormSuccess (), FieldView
{ fvLabel = mempty
, fvTooltip = Nothing
, fvId = idFormMessageNoinput
, fvErrors = Nothing
, fvRequired = False
- , fvInput = [whamlet|
- $newline never
-