Merge branch '476-interface-fur-klausurkorrekturen' into 476-interface-fur-klausurkorrekturen-dev
This commit is contained in:
commit
66317a41dc
@ -160,7 +160,7 @@ h4
|
||||
--current-header-height: var(--header-height-collapsed)
|
||||
position: relative
|
||||
background-color: white
|
||||
transition: padding-left .2s ease-out
|
||||
transition: padding-left .2s ease-out, margin-top 0.2s ease
|
||||
margin-top: var(--current-header-height)
|
||||
margin-left: 0
|
||||
|
||||
@ -173,6 +173,12 @@ h4
|
||||
> .container
|
||||
margin: 20px 0
|
||||
|
||||
.navbar__container-radio:checked ~ * &
|
||||
margin-top: calc(var(--current-header-height) + 30px)
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
margin-top: calc(var(--current-header-height) + 44px)
|
||||
|
||||
.main__content, .modal__content
|
||||
a
|
||||
text-decoration: underline
|
||||
@ -180,19 +186,25 @@ h4
|
||||
p, form, .div-p
|
||||
margin: 0.5rem 0
|
||||
|
||||
&:first-child
|
||||
margin: 0 0 0.5rem 0
|
||||
|
||||
&:last-child
|
||||
margin: 0.5rem 0 0
|
||||
margin: 0.5rem 0 0 0
|
||||
|
||||
&:first-child
|
||||
margin: 0
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
.main__content
|
||||
--current-header-height: var(--header-height)
|
||||
|
||||
@media (min-width: 426px)
|
||||
.main__content
|
||||
margin-left: var(--asidenav-width-md, 50px)
|
||||
|
||||
@media (min-width: 769px)
|
||||
.main__content
|
||||
--current-header-height: var(--header-height)
|
||||
margin-left: var(--asidenav-width-lg, 20%)
|
||||
|
||||
@media (min-width: 1200px)
|
||||
@ -228,6 +240,7 @@ input[type="submit"],
|
||||
input[type="button"],
|
||||
button,
|
||||
.btn
|
||||
font-family: var(--font-base)
|
||||
outline: 0
|
||||
border: 0
|
||||
box-shadow: 0
|
||||
@ -524,6 +537,9 @@ section
|
||||
display: grid
|
||||
grid-column: 2
|
||||
|
||||
h1 + &
|
||||
margin: 0 auto 0.5rem
|
||||
|
||||
&::before
|
||||
@extend .fas
|
||||
|
||||
@ -582,7 +598,7 @@ section
|
||||
&::before
|
||||
height: auto
|
||||
width: 45px
|
||||
font-size: 40px
|
||||
font-size: 20px
|
||||
top: 15px
|
||||
|
||||
.notification-error
|
||||
@ -598,15 +614,15 @@ section
|
||||
color: var(--color-warning)
|
||||
|
||||
// "Heated" element.
|
||||
Set custom property "--hotness" to a value from 0 to 1 to turn
|
||||
the element's background to a color on a gradient from green to red.
|
||||
// Set custom property "--hotness" to a value from 0 to 1 to turn
|
||||
// the element's background to a color on a gradient from green to red.
|
||||
|
||||
TBD:
|
||||
- move to a proper place
|
||||
- think about font-weight...
|
||||
// TBD:
|
||||
// - move to a proper place
|
||||
// - think about font-weight...
|
||||
|
||||
Example:
|
||||
<div .heated style="--hotness: 0.2">Lorem ipsum
|
||||
// Example:
|
||||
// <div .heated style="--hotness: 0.2">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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
@ -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++) {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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)
|
||||
|
||||
110
frontend/src/utils/pageactions/pageactions.js
Normal file
110
frontend/src/utils/pageactions/pageactions.js
Normal file
@ -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,
|
||||
];
|
||||
190
frontend/src/utils/pageactions/pageactions.sass
Normal file
190
frontend/src/utils/pageactions/pageactions.sass
Normal file
@ -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)
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
13
records.json
13
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": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
4
routes
4
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:
|
||||
|
||||
@ -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
|
||||
|
||||
2822
src/Foundation.hs
2822
src/Foundation.hs
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 ]
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
<div .notification .notification-#{toPathPiece messageStatus} .fa-#{maybe defaultIcon iconText messageIcon}>
|
||||
<div .notification__content>
|
||||
#{messageContent}
|
||||
|]
|
||||
, fvInput = notification NotificationNarrow msg
|
||||
})
|
||||
where
|
||||
defaultIcon = case messageStatus of
|
||||
Success -> "check-circle"
|
||||
Info -> "info-circle"
|
||||
Warning -> "exclamation-circle"
|
||||
Error -> "exclamation-triangle"
|
||||
|
||||
---------------------
|
||||
-- Form evaluation --
|
||||
|
||||
43
src/Utils/Frontend/Notification.hs
Normal file
43
src/Utils/Frontend/Notification.hs
Normal file
@ -0,0 +1,43 @@
|
||||
module Utils.Frontend.Notification
|
||||
( NotificationType(..)
|
||||
, notification
|
||||
, notificationWidget
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod
|
||||
import Settings
|
||||
|
||||
import Utils.Message
|
||||
import Utils.Icon
|
||||
|
||||
import Control.Lens
|
||||
import Control.Lens.Extras (is)
|
||||
|
||||
|
||||
data NotificationType
|
||||
= NotificationNarrow
|
||||
| NotificationBroad
|
||||
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||
|
||||
makePrisms ''NotificationType
|
||||
|
||||
|
||||
notification :: NotificationType
|
||||
-> Message
|
||||
-> WidgetFor site ()
|
||||
notification nType Message{ messageIcon = messageIcon', .. }
|
||||
= $(widgetFile "widgets/notification")
|
||||
where
|
||||
messageIcon = fromMaybe defaultIcon messageIcon'
|
||||
defaultIcon = case messageStatus of
|
||||
Success -> IconNotificationSuccess
|
||||
Info -> IconNotificationInfo
|
||||
Warning -> IconNotificationWarning
|
||||
Error -> IconNotificationError
|
||||
|
||||
notificationWidget :: Yesod site
|
||||
=> NotificationType
|
||||
-> MessageStatus
|
||||
-> WidgetFor site ()
|
||||
-> WidgetFor site ()
|
||||
notificationWidget nType ms = notification nType <=< messageWidget ms
|
||||
@ -63,44 +63,85 @@ data Icon
|
||||
| IconApplicationVeto
|
||||
| IconApplicationFiles
|
||||
| IconTooltipDefault
|
||||
deriving (Eq, Ord, Enum, Bounded, Show, Read)
|
||||
| IconNotificationSuccess
|
||||
| IconNotificationInfo
|
||||
| IconNotificationWarning
|
||||
| IconNotificationError
|
||||
| IconFavourite
|
||||
| IconLanguage
|
||||
| IconNavContainerClose | IconPageActionChildrenClose
|
||||
| IconMenuNews
|
||||
| IconMenuHelp
|
||||
| IconMenuProfile
|
||||
| IconMenuLogin | IconMenuLogout
|
||||
| IconBreadcrumbsHome
|
||||
| IconMenuExtra
|
||||
| IconMenuCourseList
|
||||
| IconMenuCorrections
|
||||
| IconMenuExams
|
||||
| IconMenuAdmin
|
||||
| IconPageActionPrimaryExpand | IconPageActionSecondary
|
||||
| IconBreadcrumbSeparator
|
||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Generic, Typeable)
|
||||
|
||||
iconText :: Icon -> Text
|
||||
iconText = \case
|
||||
IconNew -> "seedling"
|
||||
IconOK -> "check"
|
||||
IconNotOK -> "times"
|
||||
IconWarning -> "exclamation"
|
||||
IconProblem -> "bolt"
|
||||
IconVisible -> "eye"
|
||||
IconInvisible -> "eye-slash"
|
||||
IconCourse -> "graduation-cap"
|
||||
IconEnrolTrue -> "user-plus"
|
||||
IconEnrolFalse -> "user-slash"
|
||||
IconPlanned -> "cog"
|
||||
IconAnnounce -> "bullhorn"
|
||||
IconExam -> "poll-h"
|
||||
IconExamRegisterTrue -> "calendar-check"
|
||||
IconExamRegisterFalse -> "calendar-times"
|
||||
IconCommentTrue -> "comment-alt"
|
||||
IconCommentFalse -> "comment-slash" -- comment-alt-slash is not available for free
|
||||
IconLink -> "link"
|
||||
IconFileDownload -> "file-download"
|
||||
IconFileUpload -> "file-upload"
|
||||
IconFileZip -> "file-archive"
|
||||
IconFileCSV -> "file-csv"
|
||||
IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar)
|
||||
IconSFTHint -> "life-ring" -- for SheetFileType only
|
||||
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
||||
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
||||
IconEmail -> "envelope"
|
||||
IconRegisterTemplate -> "file-alt"
|
||||
IconApplyTrue -> "file-alt"
|
||||
IconApplyFalse -> "trash"
|
||||
IconNoCorrectors -> "user-slash"
|
||||
IconApplicationVeto -> "times"
|
||||
IconApplicationFiles -> "file-alt"
|
||||
IconTooltipDefault -> "question-circle"
|
||||
IconNew -> "seedling"
|
||||
IconOK -> "check"
|
||||
IconNotOK -> "times"
|
||||
IconWarning -> "exclamation"
|
||||
IconProblem -> "bolt"
|
||||
IconVisible -> "eye"
|
||||
IconInvisible -> "eye-slash"
|
||||
IconCourse -> "graduation-cap"
|
||||
IconEnrolTrue -> "user-plus"
|
||||
IconEnrolFalse -> "user-slash"
|
||||
IconPlanned -> "cog"
|
||||
IconAnnounce -> "bullhorn"
|
||||
IconExam -> "poll-h"
|
||||
IconExamRegisterTrue -> "calendar-check"
|
||||
IconExamRegisterFalse -> "calendar-times"
|
||||
IconCommentTrue -> "comment-alt"
|
||||
IconCommentFalse -> "comment-alt-slash"
|
||||
IconLink -> "link"
|
||||
IconFileDownload -> "file-download"
|
||||
IconFileUpload -> "file-upload"
|
||||
IconFileZip -> "file-archive"
|
||||
IconFileCSV -> "file-csv"
|
||||
IconSFTQuestion -> "question-circle" -- for SheetFileType only, should all be round (similar)
|
||||
IconSFTHint -> "life-ring" -- for SheetFileType only
|
||||
IconSFTSolution -> "exclamation-circle" -- for SheetFileType only
|
||||
IconSFTMarking -> "check-circle" -- for SheetFileType only
|
||||
IconEmail -> "envelope"
|
||||
IconRegisterTemplate -> "file-alt"
|
||||
IconApplyTrue -> "file-alt"
|
||||
IconApplyFalse -> "trash"
|
||||
IconNoCorrectors -> "user-slash"
|
||||
IconApplicationVeto -> "times"
|
||||
IconApplicationFiles -> "file-alt"
|
||||
IconTooltipDefault -> "question-circle"
|
||||
IconNotificationSuccess -> "check-circle"
|
||||
IconNotificationInfo -> "info-circle"
|
||||
IconNotificationWarning -> "exclamation-circle"
|
||||
IconNotificationError -> "exclamation-triangle"
|
||||
IconFavourite -> "star"
|
||||
IconLanguage -> "flag-alt"
|
||||
IconNavContainerClose -> "chevron-up"
|
||||
IconPageActionChildrenClose -> "chevron-up"
|
||||
IconMenuNews -> "megaphone"
|
||||
IconMenuHelp -> "question"
|
||||
IconMenuProfile -> "cogs"
|
||||
IconMenuLogin -> "sign-in-alt"
|
||||
IconMenuLogout -> "sign-out-alt"
|
||||
IconBreadcrumbsHome -> "home"
|
||||
IconMenuExtra -> "ellipsis-h"
|
||||
IconMenuCourseList -> "graduation-cap"
|
||||
IconMenuCorrections -> "check"
|
||||
IconMenuExams -> "poll-h"
|
||||
IconMenuAdmin -> "screwdriver"
|
||||
IconPageActionPrimaryExpand -> "bars"
|
||||
IconPageActionSecondary -> "ellipsis-h"
|
||||
IconBreadcrumbSeparator -> "angle-right"
|
||||
|
||||
instance Universe Icon
|
||||
instance Finite Icon
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
module Utils.Lens.TH
|
||||
( makeLenses_, makeClassyFor_
|
||||
( lensRules_
|
||||
, makeLenses_, makeClassyFor_
|
||||
, multifocusG, multifocusL
|
||||
) where
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ module Utils.Message
|
||||
, addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget
|
||||
, statusToUrgencyClass
|
||||
, Message(..)
|
||||
, messageIconI
|
||||
, messageIconI, messageIconIHamlet, messageIconWidget
|
||||
, messageI, messageIHamlet, messageFile, messageWidget, messageTooltip
|
||||
) where
|
||||
|
||||
@ -163,6 +163,15 @@ messageIHamlet ms iHamlet = do
|
||||
let mi = Nothing
|
||||
Message ms <$> withUrlRenderer (iHamlet $ toHtml . mr) <*> pure mi
|
||||
|
||||
messageIconIHamlet :: ( MonadHandler m
|
||||
, RenderMessage (HandlerSite m) msg
|
||||
, HandlerSite m ~ site
|
||||
) => MessageStatus -> Icon -> HtmlUrlI18n msg (Route site) -> m Message
|
||||
messageIconIHamlet messageStatus (Just -> messageIcon) iHamlet = do
|
||||
mr <- getMessageRender
|
||||
messageContent <- withUrlRenderer (iHamlet $ toHtml . mr)
|
||||
return Message{..}
|
||||
|
||||
addMessageFile :: MessageStatus -> FilePath -> ExpQ
|
||||
addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|]
|
||||
|
||||
@ -189,6 +198,15 @@ messageWidget mc wgt = do
|
||||
PageContent{pageBody} <- liftHandler $ widgetToPageContent wgt
|
||||
messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
messageIconWidget :: forall m site.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ site
|
||||
, Yesod site
|
||||
) => MessageStatus -> Icon -> WidgetFor site () -> m Message
|
||||
messageIconWidget ms mi wgt = do
|
||||
PageContent{pageBody} <- liftHandler $ widgetToPageContent wgt
|
||||
messageIconIHamlet ms mi (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
|
||||
getMessages :: MonadHandler m => m [Message]
|
||||
getMessages = fmap decodeMessage <$> ClassyPrelude.Yesod.getMessages
|
||||
|
||||
@ -58,6 +58,7 @@ data GlobalPostParam = PostFormIdentifier
|
||||
| PostDBCsvImportAction
|
||||
| PostLoginDummy
|
||||
| PostExamAutoOccurrencePrevious
|
||||
| PostLanguage
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe GlobalPostParam
|
||||
|
||||
@ -1,5 +1,11 @@
|
||||
$newline never
|
||||
$if not isModal
|
||||
$with containers <- filter isNavHeaderContainer nav
|
||||
$if not (null containers)
|
||||
<input name=nav-container type=radio .navbar__container-radio--none checked #container-radio-none>
|
||||
$forall (_, containerIdent, _, _) <- containers
|
||||
<input name=nav-container type=radio .navbar__container-radio ##{containerIdent}-radio>
|
||||
|
||||
<!-- secondary navigation at the side -->
|
||||
^{asidenav}
|
||||
<!-- navigation -->
|
||||
@ -15,7 +21,7 @@ $if not isModal
|
||||
|
||||
$if not isModal
|
||||
<!-- breadcrumbs -->
|
||||
$if not $ Just HomeR == mcurrentRoute
|
||||
$if not $ Just NewsR == mcurrentRoute
|
||||
^{breadcrumbsWgt}
|
||||
|
||||
<div .main__content-body>
|
||||
@ -26,7 +32,7 @@ $if not isModal
|
||||
<a .breadcrumbs__link href="@{fst back}">#{snd back} -->
|
||||
^{headline}
|
||||
|
||||
$if not isModal && hasPageActions
|
||||
$if hasPageActions
|
||||
<!-- page actions -->
|
||||
^{pageaction}
|
||||
|
||||
|
||||
@ -1,24 +1,18 @@
|
||||
$newline never
|
||||
<section>
|
||||
^{examCorrectExplanation}
|
||||
|
||||
<section>
|
||||
<div uw-hide-columns="exam-correct" .scrolltable .scrolltable--bordered>
|
||||
<table .table .table--striped table--hover uw-exam-correct=#{toPathPiece examCorrectIdent} uw-sort-table=exam-correct-#{toPathPiece examCorrectIdent}>
|
||||
<table .table .table--striped .table--hover uw-exam-correct=#{toPathPiece examCorrectIdent} uw-sort-table=exam-correct-#{toPathPiece examCorrectIdent} uw-no-check-all>
|
||||
<thead>
|
||||
<tr .table__row .table__row--head>
|
||||
<th .table__th .uw-exam-correct--date-cell uw-exam-correct-header="date" uw-hide-column-header="date">
|
||||
_{MsgExamCorrectHeadDate}
|
||||
<th .table__th .uw-exam-correct--user-cell uw-exam-correct-header="user" uw-hide-column-header="user">
|
||||
_{MsgExamCorrectHeadParticipant}
|
||||
^{iconTooltip participantHeadTooltip Nothing True}
|
||||
$forall ExamPart{examPartNumber,examPartName} <- examParts
|
||||
$forall ExamPart{examPartNumber} <- examParts
|
||||
<th .table__th .uw-exam-correct--part-cell uw-exam-correct-header=#{examPartNumber} uw-hide-column-header=#{examPartNumber}>
|
||||
$maybe name <- examPartName
|
||||
<span .tooltip>
|
||||
<span>
|
||||
_{MsgExamCorrectHeadPart examPartNumber}
|
||||
<span .tooltip__content>
|
||||
_{MsgExamCorrectHeadPartName name}
|
||||
$nothing
|
||||
_{MsgExamCorrectHeadPart examPartNumber}
|
||||
$if mayEditResults
|
||||
<th .table__th .uw-exam-correct--result-cell uw-exam-correct-header="result" uw-hide-column-header="result" colspan=2>
|
||||
@ -35,6 +29,8 @@ $newline never
|
||||
$forall ExamPart{examPartNumber} <- examParts
|
||||
<td .table__td .uw-exam-correct--part-cell>
|
||||
^{ptsInput examPartNumber}
|
||||
<input #exam-correct__#{examPartNumber}--delete type="checkbox" style="display:none" uw-no-checkbox .uw-exam-correct--delete-exam-part>
|
||||
<label for=exam-correct__#{examPartNumber}--delete .fas .fa-fw .fa-trash>
|
||||
$if mayEditResults
|
||||
<td .table__td #uw-exam-correct__result>
|
||||
<select>
|
||||
@ -52,6 +48,8 @@ $newline never
|
||||
_{MsgExamResultVoided}
|
||||
<option value="no-show">
|
||||
_{MsgExamResultNoShow}
|
||||
<option value="delete">
|
||||
_{MsgExamCorrectExamResultDelete}
|
||||
<td .table__td #uw-exam-correct__result__grade>
|
||||
<select>
|
||||
$forall grade <- (toPathPiece <$> examGrades)
|
||||
|
||||
@ -3,7 +3,5 @@ $newline never
|
||||
^{closeWgt}
|
||||
<section>
|
||||
$if hasUsers
|
||||
<div .notification .notification-info .fa-question .notification--broad>
|
||||
<div .notification__content>
|
||||
_{MsgExamGradesExplanation}
|
||||
^{examGradesExplanation}
|
||||
^{examUsersTable}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
$newline never
|
||||
$if hasUsers
|
||||
<div .notification .notification-info .fa-question .notification--broad>
|
||||
<div .notification__content>
|
||||
_{MsgExamGradesExplanation}
|
||||
^{examGradesExplanation}
|
||||
^{table}
|
||||
|
||||
@ -56,7 +56,7 @@ $maybe desc <- examDescription
|
||||
$maybe start <- examStart
|
||||
^{formatTimeRangeW SelFormatDateTime start examEnd}
|
||||
$maybe finished <- examFinished
|
||||
<dt .deflist__dt>_{MsgExamFinishedParticipant}
|
||||
<dt .deflist__dt>_{examFinishedMsg}
|
||||
<dd .deflist__dd>^{formatTimeW SelFormatDateTime finished}
|
||||
$if examClosedShown
|
||||
$maybe closed <- examClosed
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
$newline never
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2020 02 07}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>
|
||||
Überarbeitete Navigation
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2020 01 30}
|
||||
<dd .deflist__dd>
|
||||
@ -109,9 +116,9 @@ $newline never
|
||||
^{formatGregorianW 2019 09 16}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Modellierung der Prüfungsämter im System inkl. direkte Einsicht in relevante Prüfungsleistungen
|
||||
<li>E-Mail-Benachrichtigungen an zuständige Prüfungsämter bei Abschluss einer Klausur
|
||||
<li>Abschluss von Klausuren (d.h. Melden der Prüfungsleistungen an die Prüfungsämter) jetzt als Button, statt als voreingestellter Zeitpunkt
|
||||
<li>Prüfungsverwaltung im System inkl. direkte Einsicht in relevante Prüfungsleistungen
|
||||
<li>E-Mail-Benachrichtigungen an zuständige Prüfungsverwalter bei Abschluss einer Klausur
|
||||
<li>Abschluss von Klausuren (d.h. Melden der Prüfungsleistungen an die Prüfungsverwalter) jetzt als Button, statt als voreingestellter Zeitpunkt
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 13}
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
$newline never
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2020 02 07}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>
|
||||
Reworked navigation
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2020 01 30}
|
||||
<dd .deflist__dd>
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
$newline never
|
||||
<p>
|
||||
Hier können Sie der Meldung ihrer Prüfungsleistungen an Prüfungsämter #
|
||||
Hier können Sie der Meldung ihrer Prüfungsleistungen an Prüfungsverwalter #
|
||||
bestimmter Institute (innerhalb von Uni2work) widersprechen.
|
||||
<p>
|
||||
Bedenken Sie, dass die Meldung der Prüfungsleistungen direkt in Uni2work den #
|
||||
Verwaltungsaufwand (und die damit verbunden Dauer) für die ordnungsgemäße #
|
||||
Anrechung Ihrer Leistungen drastisch reduziert.
|
||||
<p>
|
||||
Unter Umständen können Prüfungsämter ungeachtet der Angaben, die Sie hier #
|
||||
Unter Umständen können Prüfungsverwalter ungeachtet der Angaben, die Sie hier #
|
||||
machen, Einsicht in Ihre Leistungen erlangen.<br />
|
||||
Dies geschieht nur in begründeten Einzelfällen (z.B. bei Studierenden im #
|
||||
ERASMUS-Programm).
|
||||
<p>
|
||||
Nutzer, die unabhängig von diesen Einstellungen, Einsicht in Ihre #
|
||||
Prüfungsleistungen haben (z.B. die Kursverwalter) können Ihre Note natürlich #
|
||||
außerhalb von Uni2work an Prüfungsämter melden (auch solche, die hier nicht #
|
||||
aufgeführt sind).
|
||||
außerhalb von Uni2work weitermelden
|
||||
$if hasForced
|
||||
<p>
|
||||
Wenn Sie der Meldung an einzelne Prüfungsämter nicht widersprechen können, #
|
||||
so hat das jeweilige Prüfungsamt angegeben, dass die Einsicht, entweder #
|
||||
Wenn Sie der Meldung an einzelne Prüfungsverwalter nicht widersprechen können, #
|
||||
so hat der jeweilige Prüfungsverwalter angegeben, dass die Einsicht, entweder #
|
||||
aufgrund einer Ihrer Studiengänge (z.B. aufgrund der Studienordnung) oder #
|
||||
bei Ihnen spezifisch, zwingend erforderlich ist.
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<p>
|
||||
Um eine Prüfungsleistung einzutragen können Sie in der #
|
||||
Teilnehmer-Spalte einen beliebigen eindeutigen Identifikator des #
|
||||
Teilnehmers angeben.<br />
|
||||
|
||||
Vermutlich eindeutig ist die Matrikelnummer des Teilnehmers, aber #
|
||||
auch der Name oder ein Teil der Matrikelnummer können unter #
|
||||
Umständen bereits eindeutig sein. #
|
||||
8
templates/i18n/exam-correct-explanation/en-eu.hamlet
Normal file
8
templates/i18n/exam-correct-explanation/en-eu.hamlet
Normal file
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<p>
|
||||
To enter a participant's exam achievement you can submit any string #
|
||||
that uniquely identifies the participant.<br />
|
||||
|
||||
Matriculation numbers are likely unique. #
|
||||
The participant's name or a part of their matriculation number may #
|
||||
also be sufficiently unique.
|
||||
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<p>
|
||||
Diese Ansicht zeigt die selben Daten an, wie die Tabelle von Prüfungsteilnehmern.<br />
|
||||
Anpassen der Teilnehmerdaten und Ergebnisse ist nur dort möglich.
|
||||
|
||||
<p>
|
||||
Hier können Sie vor Allem einsehen und markieren, welche #
|
||||
Prüfungsleistungen von den zuständigen Prüfungsbeauftragten bereits #
|
||||
vollständig bearbeitet wurden.
|
||||
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<p>
|
||||
This view shows the same data as the table of exam participants.<br />
|
||||
Changing participant's data and achievements is only possible via #
|
||||
the table of exam participants.
|
||||
|
||||
<p>
|
||||
Primarily, this view allows you to check and adjust which exam #
|
||||
achievements were properly handled by the relevant exam offices.
|
||||
@ -1,24 +1,22 @@
|
||||
$newline never
|
||||
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||
<div .notification__content>
|
||||
<p>
|
||||
Die Tabelle enthält Werte, die automatisch berechnet wurden.
|
||||
<p>
|
||||
Automatisch berechnete Werte (Bonus und Prüfungsergebnis) werden weder dem #
|
||||
entsprechenden Teilnehmer angezeigt, noch an das Prüfungsamt gemeldet #
|
||||
bevor sie manuell übernommen wurden.<br />
|
||||
Hierzu können Sie die Aktion „Berechnetes Prüfungsergebnis übernehmen“ #
|
||||
verwenden.
|
||||
<p>
|
||||
Sie können die automatisch berechneten Werte auch manuell (via CSV-Import) #
|
||||
überschreiben.<br />
|
||||
Wenn die so gesetzten Werte nicht den automatisch Berechneten entsprechen #
|
||||
sind sie <i>inkonsistent</i>.
|
||||
<p>
|
||||
Automatisch berechnete Werte sind gekennzeichnet wie folgt:
|
||||
<p>
|
||||
Die Tabelle enthält Werte, die automatisch berechnet wurden.
|
||||
<p>
|
||||
Automatisch berechnete Werte (Bonus und Prüfungsergebnis) werden weder dem #
|
||||
entsprechenden Teilnehmer angezeigt, noch an Prüfungsverwalter gemeldet #
|
||||
bevor sie manuell übernommen wurden.<br />
|
||||
Hierzu können Sie die Aktion „Berechnetes Prüfungsergebnis übernehmen“ #
|
||||
verwenden.
|
||||
<p>
|
||||
Sie können die automatisch berechneten Werte auch manuell (via CSV-Import) #
|
||||
überschreiben.<br />
|
||||
Wenn die so gesetzten Werte nicht den automatisch Berechneten entsprechen #
|
||||
sind sie <i>inkonsistent</i>.
|
||||
<p>
|
||||
Automatisch berechnete Werte sind gekennzeichnet wie folgt:
|
||||
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatisch berechnet
|
||||
<td style="padding: 0 7px" .table__td>Normaler Wert
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inkonsistent
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatisch berechnet
|
||||
<td style="padding: 0 7px" .table__td>Normaler Wert
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inkonsistent
|
||||
|
||||
@ -1,23 +1,21 @@
|
||||
$newline never
|
||||
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||
<div .notification__content>
|
||||
<p>
|
||||
This table contains values that were computed automatically.
|
||||
<p>
|
||||
Values computed automatically (bonus and result) are shown to neither the #
|
||||
participant nor relevant exam offices until they are manually accepted.<br />
|
||||
To do this you may use the action “Accept computed result”.
|
||||
<p>
|
||||
You are also able to override the automatically computed values manually #
|
||||
(via CSV import).<br />
|
||||
<p>
|
||||
This table contains values that were computed automatically.
|
||||
<p>
|
||||
Values computed automatically (bonus and result) are shown to neither the #
|
||||
participant nor relevant exam offices until they are manually accepted.<br />
|
||||
To do this you may use the action “Accept computed result”.
|
||||
<p>
|
||||
You are also able to override the automatically computed values manually #
|
||||
(via CSV import).<br />
|
||||
|
||||
If values thus overriden do not match the automatically computed values #
|
||||
they are considered <i>inconsistent</i>.
|
||||
<p>
|
||||
Automatically computed values are marked as follows:
|
||||
If values thus overriden do not match the automatically computed values #
|
||||
they are considered <i>inconsistent</i>.
|
||||
<p>
|
||||
Automatically computed values are marked as follows:
|
||||
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatically computed
|
||||
<td style="padding: 0 7px" .table__td>Normal value
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inconsistent
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatically computed
|
||||
<td style="padding: 0 7px" .table__td>Normal value
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inconsistent
|
||||
|
||||
@ -3,6 +3,6 @@ $newline never
|
||||
Kursverwalter (und von ihnen beauftragte Korrektoren) können die Ergebnisse #
|
||||
einer Prüfung direkt in Uni2work hinterlegen.<br />
|
||||
Dies dient sowohl der Rückmeldung an den jeweiligen Studierenden als auch #
|
||||
können Prüfungsämter, mit den notwendigen Berechtigungen, die #
|
||||
können Prüfungsverwalter, mit den notwendigen Berechtigungen, die #
|
||||
Prüfungsleistungen direkt aus Uni2work mit ihren eigenen Verwaltungssystemen #
|
||||
synchronisieren.
|
||||
|
||||
@ -5,7 +5,7 @@ $newline never
|
||||
benötigen um sicherzustellen, dass Studienmodalitäten erfüllt und Leistungen #
|
||||
korrekt anerkannt werden.<br />
|
||||
<p>
|
||||
<i>Teil des Prüfungsamts</i> ist eine Berechtigung die einzelnen Benutzern von #
|
||||
<i>Mit Prüfungsverwaltung beauftragt</i> ist eine Berechtigung die einzelnen Benutzern von #
|
||||
einem Administrator für ein bestimmtes Institut eingeräumt wird.<br />
|
||||
Diese Benutzer haben dann u.A. Zugriff auf alle beim relevanten Institut #
|
||||
erbrachten und in Uni2work hinterlegten Prüfungsleistungen.
|
||||
|
||||
@ -312,9 +312,9 @@ $newline text
|
||||
Anmeldezeitraums anmelden.
|
||||
Die Teilnehmerlisten können online oder per CSV Export/Import bearbeitet werden.
|
||||
|
||||
<dt .deflist__dt> ^{plannedFeat} Prüfungszuteilung
|
||||
<dt .deflist__dt> ^{newFeat 2020 01 29} Prüfungszuteilung
|
||||
<dd .deflist__dd>
|
||||
Auf Wunsch kann Uni2work in Zukunft die Zuteilung der Teilnehmer auf die Prüfungen (Räume bzw. Prüfungstermine)
|
||||
Auf Wunsch kann Uni2work die Zuteilung der Teilnehmer auf die Prüfungen (Räume bzw. Prüfungstermine)
|
||||
nach verschiedenen Kriterien wie Name oder Matrikelnummer vornehmen.
|
||||
|
||||
<dt .deflist__dt> ^{plannedFeat} Korrekturen
|
||||
@ -333,7 +333,7 @@ $newline text
|
||||
<p>
|
||||
Die Berechnung der Prüfungsergebnisse kann automatisch durch Uni2work erfolgen. Dabei muss ein Notenschlüssel angegeben werden, an dem die Endnote der Studenten automatisch anhand der erreichten Punktezahl abgelesen wird.
|
||||
<p>
|
||||
^{newU2WFeat} Die automatisch abgelesenen Noten werden den Kursverwaltern dabei zunächst als Vorschlag angezeigt. Die Vorschläge müssen erst von einem Kursverwalter akzeptiert werden, bevor sie als Ergebnisse den Teilnehmern angezeigt und an die Prüfungsämter gemeldet werden können.
|
||||
^{newU2WFeat} Die automatisch abgelesenen Noten werden den Kursverwaltern dabei zunächst als Vorschlag angezeigt. Die Vorschläge müssen erst von einem Kursverwalter akzeptiert werden, bevor sie als Ergebnisse den Teilnehmern angezeigt und an die Prüfungsverwaltung gemeldet werden können.
|
||||
<p>
|
||||
^{newU2WFeat} Die Vorschläge können auch unabhängig vom eingetragenen Notenschlüssel manuell überschrieben werden.
|
||||
|
||||
@ -354,15 +354,15 @@ $newline text
|
||||
<dt .deflist__dt> ^{newFeat 2019 9 16} Notenmeldung
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Endnoten können automatisiert an die Prüfungsämter gemeldet werden.
|
||||
Endnoten können automatisiert an die Prüfungsverwaltung gemeldet werden.
|
||||
<p>
|
||||
Nach dem Abschließen einer Prüfung (durch einen Knopf über der Teilnehmerliste) werden alle mit den geprüften Teilnehmern assoziierten Prüfungsämter darüber informiert, dass die Notengebung abgeschlossen ist.
|
||||
Nach dem Abschließen einer Prüfung (durch einen Knopf über der Teilnehmerliste) werden alle mit den geprüften Teilnehmern assoziierten Prüfungsverwalter darüber informiert, dass die Notengebung abgeschlossen ist.
|
||||
<p>
|
||||
^{newU2WFeat} Hat ein Prüfungsamt die Note eines Teilnehmers zur Kenntnis genommen, erscheint ein Häkchen auf der Seite "Prüfungsleistungen". Falls auch nach längerer Zeit noch kein assoziiertes Prüfungsamt die Note eines Teilnehmers zur Kenntnis genommen hat, so sollte ein Kursverwalter der Notenmeldung für den betroffenen Teilnehmer nachgehen (z.B. durch Ausstellen eines Scheins).
|
||||
^{newU2WFeat} Hat ein Prüfungsverwalter die Note eines Teilnehmers zur Kenntnis genommen, erscheint ein Häkchen auf der Seite "Prüfungsleistungen". Falls auch nach längerer Zeit noch keine assoziierte Prüfungsverwaltung die Note eines Teilnehmers zur Kenntnis genommen hat, so sollte ein Kursverwalter der Notenmeldung für den betroffenen Teilnehmer nachgehen (z.B. durch Ausstellen eines Scheins).
|
||||
<p>
|
||||
^{newU2WFeat} Dozenten dürfen nach der Übergabe an das Prüfungsamt
|
||||
^{newU2WFeat} Dozenten dürfen nach der Übergabe an die Prüfungsverwaltung
|
||||
nachträgliche Änderungen an den Prüfungen vornehmen.
|
||||
Diejenigen Prüfungsämter, die von der nachträglichen Änderung betroffen sind, erhalten
|
||||
Diejenigen Prüfungsverwalter, die von der nachträglichen Änderung betroffen sind, erhalten
|
||||
hierüber automatisiert eine Benachrichtigung.
|
||||
|
||||
|
||||
|
||||
@ -303,7 +303,7 @@ $newline text
|
||||
<p>
|
||||
^{newU2WFeat} At first, the calculated results will be shown as suggestions only.
|
||||
These suggestions then require manual approval by the course administrator before being accepted
|
||||
as exam results, which can afterwards be sent to the Prüfungsämter (Examination Offices).
|
||||
as exam results, which can afterwards be sent to the relevant exam offices.
|
||||
|
||||
<p>
|
||||
^{newU2WFeat} The calculated suggestions can also be overriden manually, independent of the grading scale.
|
||||
@ -324,19 +324,19 @@ $newline text
|
||||
<dt .deflist__dt> ^{newFeat 2019 9 16} Reporting Exam Results
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Final grades can be automatically sent to the Prüfungsämter (Exam Offices).
|
||||
Final grades can be automatically sent to the relevant exam offices.
|
||||
<p>
|
||||
After finalizing an exam (by clicking a button on the list of exam participants), all associated
|
||||
Prüfungsämter will be notified that the grading process is finished for this exam.
|
||||
exom offices will be notified that the grading process is finished for this exam.
|
||||
<p>
|
||||
^{newU2WFeat} Once a Prüfungsamt has acknowledged the exam result of a participant, a check mark of the
|
||||
^{newU2WFeat} Once an exam office has acknowledged the exam result of a participant, a check mark of the
|
||||
page "Exam Results" will be shown.<br />
|
||||
In case of this check mark being absent even after a long period of time, a course administrator should
|
||||
look into reporting the exam result for this participant (e.g. by issueing a proof of participation).
|
||||
|
||||
<p>
|
||||
^{newU2WFeat} Course administrators are allowed to edit final results even after reporting the grades
|
||||
to the Prüfungsämter. The offices that are affected by these changes will be automatically notified.
|
||||
to the exam offices. The offices that are affected by these changes will be automatically notified.
|
||||
|
||||
|
||||
<section id="allocations">
|
||||
|
||||
@ -1,380 +0,0 @@
|
||||
$newline text
|
||||
<section>
|
||||
<section>
|
||||
<h2>Courses
|
||||
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt> Course Names
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newU2WFeat} Any course needs a shorthand for identification, e.g. DBS, PXD, DM, ...
|
||||
<p>
|
||||
The combination of shorthand, department and semester needs to be unique.<br />
|
||||
Creating a course with a shorthand that is not unique for the selected department and semester will be rejected (a corresponding error message will be shown).
|
||||
<p>
|
||||
Recommendations for course shorthands:
|
||||
<ul>
|
||||
<li> Try to keep the shorthand as short as possible (10 characters max.).
|
||||
In particular, we advice against choosing the full course title as its shorthand.
|
||||
A warning will be shown if a shorthand exceeds the recommended length.
|
||||
<li> Avoid adding identifiers for the department (e.g. "MATH"), the semester (e.g. "WS19") or the type (e.g. "SEM") of a course in its shorthand.
|
||||
<p>
|
||||
^{plannedFeatInline} It is planned to enable courses to have types (e.g. "Bachelor Seminar" or "Practical Course").<br />
|
||||
Students will then be able to explicitely search for courses of a specific type.
|
||||
|
||||
<dt .deflist__dt> Clone Courses
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Lecturers are able to clone <em>any</em> course of their department for the current semester.
|
||||
|
||||
When cloning a course, its shorthand and description will be adopted;
|
||||
but not exercise sheets, exams or registrations.
|
||||
<p>
|
||||
The course description can be composed in Html and
|
||||
<em>should contain the module description!
|
||||
|
||||
<dt .deflist__dt> Support for Multiple Departments
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newU2WFeat} Uni2work supports managing multiple departments; prefixing course titles with e.g. "[MATH]" are not necessary anymore.
|
||||
Instead, there are now department filters for course lists.
|
||||
<p>
|
||||
The permissions of Uni2work administrators are limited to courses of their respective departments.
|
||||
This means that a Uni2work administrator of the Institute of Informatics cannot access grades from courses of the Department of Maths.
|
||||
|
||||
<dt .deflist__dt> Access to Material
|
||||
<dd .deflist__dd>
|
||||
The access to exercise sheets, slides and other material can be made dependent on the registration for the course.
|
||||
|
||||
<dt .deflist__dt> Publish Material
|
||||
<dd .deflist__dd>
|
||||
Slides, code bundles etc. can now be distributed to the participants of a course; also password-protected if wanted.
|
||||
|
||||
<dt .deflist__dt> Course Passwords
|
||||
<dd .deflist__dd> ^{newU2WFeat} The registration for a course can be password-protected.
|
||||
|
||||
<dt .deflist__dt> Course Lecturers and Assistants
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Course administrators can assign <em>any</em> user as administrator of the course.
|
||||
|
||||
^{newU2WFeat} Within a course, all course administrators have the same permissions. Most notably, every course administrator is allowed to edit the list of administrators for this course.
|
||||
|
||||
<p>
|
||||
^{newU2WFeat} Uni2work features "lecturer" and "assistant" as roles:
|
||||
lecturers are essentially allowed to create new courses.
|
||||
The lecturer permission is differentiated according to department.
|
||||
|
||||
Assistants have the same permissions as lecturers, but only for a particular course.
|
||||
|
||||
<p>
|
||||
In UniWorX, there was the role "assistant",
|
||||
i.e. every "administrator" also had to be "lecturer";
|
||||
there was no differentiation between departments.
|
||||
|
||||
<dt .deflist__dt> Course Participants
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Course participants are now displayed alongside information regarding their study programs.
|
||||
Students of multiple programs need to select a main subject when registering for a course.
|
||||
This can speed up the process of reporting exam results.
|
||||
|
||||
<p>
|
||||
^{probFeatInline} Normally, information regarding students' study programs is up to date;
|
||||
however, outdated data is shown in some cases.<br />
|
||||
Therefore, no decisions with severe consequences should be made without consulting the student
|
||||
first, if they are solely based on study program information (e.g. veto in a central allocation
|
||||
of practical courses and seminars).
|
||||
|
||||
<p>
|
||||
If only a number (key) is shown instead of a study program or degree, Uni2work did not yet learn a
|
||||
mapping from this key to its corresponding program.<br />
|
||||
Unfortunately, this needs to happen successively because we cannot get an up-to-date and complete
|
||||
mapping of those keys from the Studentenkanzlei (Student Office).<br />
|
||||
^{probFeatInline} It might happen that a more "general" study program is shown than the actual
|
||||
one a student is enrolled in (e.g. Media Informatics instead of Human-Computer-Interaction).
|
||||
This problem should be fixed in the near future.
|
||||
|
||||
<dt .deflist__dt> From a Student's Point of View
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newU2WFeat} UniWorX had special links to view a page from a student's point of view ("Aus Studentensicht"), which are not necessary anymore in Uni2work.
|
||||
Instead, one can now #
|
||||
<a href=@{AuthPredsR}>temporarily withdraw one's own permissions here
|
||||
. To view one's own course from a participant's point of view, one deactivates #
|
||||
the permission check "_{MsgAuthTagLecturer}" and/or "_{MsgAuthTagCorrector}".
|
||||
|
||||
<dt .deflist__dt> News
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newFeat 2019 10 7} On the course overview page, one can directly publish news concerning
|
||||
the course ("News").
|
||||
<p>
|
||||
An RSS feed and (opt-in) email notifications for course news are planned in the future.
|
||||
|
||||
<dt .deflist__dt> Dates
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newFeat 2019 10 9} On the course overview page, one can publish dates concerning the course
|
||||
(e.g. weekly date of the lecture, one-time post-exam review, ...).
|
||||
<p>
|
||||
Email notifications on changes of the dates are planned in the future.
|
||||
|
||||
<section>
|
||||
<h2>Course Exercises
|
||||
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt> Correctors
|
||||
<dd .deflist__dd>
|
||||
^{newU2WFeat} Correctors and correction method are selected by the course administrator ad-hoc per exercise sheet;
|
||||
there is no entry for correctors in the course configuration.<br />
|
||||
To grant tutors that are no correctors permission to view solutions before the submission deadline,
|
||||
they should be registered as sheet correctors with 0 correction proportions.
|
||||
|
||||
When creating a new sheet, the settings of the previous sheet are suggested automatically.
|
||||
|
||||
<dt .deflist__dt> Distribution
|
||||
<dd .deflist__dd>
|
||||
^{newU2WFeat} Correctors can be marked as Absent or Excused per exercise sheet. This way, they will not be
|
||||
automatically assigned as correctors of any submissions.
|
||||
|
||||
The difference between Absent and Excused correctors is that Absent correctors will be assigned more
|
||||
submissions in the upcoming sheets automatically, to make up for the proportion of the sheet they missed.
|
||||
For Excused correctors, no additional submissions will be assigned later on.
|
||||
|
||||
<dt .deflist__dt> Files
|
||||
<dd .deflist__dd>
|
||||
^{newU2WFeat} The exercise sheet and its solution can consist of several files of any type.
|
||||
Instead of having to upload a ZIP file in case of multiple files, they can be uploaded one-by-one in Uni2work.
|
||||
|
||||
<dt .deflist__dt> Hints
|
||||
<dd .deflist__dd>
|
||||
In addition to exercise sheet and solution, hints (e.g. solutions to tutorial exercises) can be released
|
||||
on a specific date before the submission deadline.
|
||||
|
||||
<dt .deflist__dt> Visibility
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Exercise sheets can be hidden from the participants up to a date "Visible from".
|
||||
This can be used to distribute preliminary sheets to tutors and correctors,
|
||||
i.e. when grading and deadlines are not yet fixed.
|
||||
<p>
|
||||
The participants only get to see the sheet only once it is visible.
|
||||
However, any files regarding the sheet can only be downloaded once the submission period has started,
|
||||
similar to UniWorX.
|
||||
|
||||
<dt .deflist__dt> Timestamps
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Every file of an exercise sheet is annotated with a timestamp which is visible for the participants.
|
||||
<p>
|
||||
A visual highlighting of edited/new files and corresponding notifications is planned,
|
||||
but not yet implemented.
|
||||
|
||||
<dt .deflist__dt> External Submissions
|
||||
<dd .deflist__dd>
|
||||
External submissions (e.g. of analogue form)
|
||||
can be managed using a pseudonym:
|
||||
<ul>
|
||||
<li>
|
||||
Create sheets with submission method
|
||||
<i>External Submission with Pseudonym</i>
|
||||
<li>
|
||||
On the overview page of a single sheet,
|
||||
participants can generate a pseudonym for their solution.
|
||||
Then they can mark their external submission with it.
|
||||
<p>
|
||||
The participants need to generate a new pseudonym for each exercise sheet to guarantee
|
||||
the anonymity of their submissions.
|
||||
However, no submission will be created in the system when generating a pseudonym,
|
||||
since not every participant that generates a pseudonym actually submits a solution afterwards.
|
||||
<li>
|
||||
<p>
|
||||
After the external submissions are distributed to the correctors,
|
||||
the correctors need to
|
||||
<a href="@{CorrectionsCreateR}">
|
||||
Create a Submission
|
||||
, which can then be corrected as usual.
|
||||
<p>
|
||||
Such submissions will be accounted for in the next distribution of
|
||||
submissions according to the corrector's correction proportions.
|
||||
|
||||
<section>
|
||||
<h2>Tutorials
|
||||
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt> Occurrences
|
||||
<dd .deflist__dd>
|
||||
Tutorials can consist of multiple regular occurrences which repeat on a weekly basis.
|
||||
<br />
|
||||
Additionally, an arbitrary number of <i>Exceptions</i> can be created.
|
||||
<br />
|
||||
An exception to an occurrence overrides the regular schedule. To add an exception, one enters an arbitrary date that lies inside the regular occurrence. <em>All</em> regular occurrences that contain the selected date will then be marked as exception.
|
||||
<br />
|
||||
An exception that an occurrence takes place in turn overrides exceptions that an occurrence does not take place.
|
||||
<br />
|
||||
This behavior can be used to shift single occurrences by selecting a regular occurrence as an exception ("does not take place") and another date as a second exception ("does take place").
|
||||
|
||||
<dt .deflist__dt> Tutors
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Tutors are selected ad-hoc per tutorial.
|
||||
<br />
|
||||
A tutorial can be assigned to an arbitrary number of tutors, and a tutor can be assigned to an arbitrary number of tutorials.
|
||||
|
||||
<p>
|
||||
Tutors have access to the names and study program information of all participants of their tutorials, can send messages to them (similar to course messages) and are able to remove participants from their tutorials.
|
||||
|
||||
<p>
|
||||
^{newFeat 2019 10 14} Optionally, tutors can be given full control over their tutorials (except deleting
|
||||
them), which means that they can edit rooms and dates of the tutorial.
|
||||
|
||||
<dt .deflist__dt> Registration
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Students can register themselves for tutorials via the course page.
|
||||
<br />
|
||||
The registration proceeds on a <i>first come, first served</i> basis at the moment.
|
||||
<br />
|
||||
A prior registration for the course is required.
|
||||
<p>
|
||||
The registration period for a tutorial can be limited in time.
|
||||
<p>
|
||||
Tutorials can be provided with a <i>Registration Group</i>.
|
||||
A registration group consists of an arbitrary text that is of no further significance.
|
||||
However, participants are restricted on <i>one tutorial registration per registration group</i>.<br />
|
||||
|
||||
Empty registration groups (i.e. no registration groups have been assigned) count as distinct.
|
||||
<p>
|
||||
To allow registering for an arbitrary number of tutorials, one can leave every registration group empty.
|
||||
|
||||
<dt .deflist__dt> ^{newFeat 2019 10 10} Late Registrations
|
||||
<dt .deflist__dd>
|
||||
Course administrators can assign course participants to tutorials using the list of course participants.
|
||||
|
||||
|
||||
<section id="exams">
|
||||
<h2> Exams
|
||||
<p> Large parts of the management of exams have been implemented and are already usable.<br />
|
||||
^{newU2WFeat} In addition to UniWorX, Uni2work supports more general forms of exams (e.g. oral exams, practical exams), for which participants can be grouped and examined in different rooms and on different occurrences per group.
|
||||
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt> Create / Edit
|
||||
<dd .deflist__dd>
|
||||
Exams can be created by course administrators.
|
||||
A number of optional settings can be made immediately or later on, e.g. visibility or registration period.
|
||||
|
||||
<dt .deflist__dt> Occurrences/Rooms
|
||||
<dd .deflist__dd>
|
||||
^{newU2WFeat} An exam can be divided into multiple Occurrences/Rooms; each Occurrence/Room can have an own Occurence and Room.
|
||||
<p>
|
||||
In its most simple form, there can be exams that can take place in multiple rooms.
|
||||
<p>
|
||||
There can also be exams that take place on different occurrences, e.g. oral exams for seminars or practical courses.<br />
|
||||
These occurrences will be visible for the participants in forms of a table.
|
||||
|
||||
<dt .deflist__dt> Registrations
|
||||
<dd .deflist__dd>
|
||||
Participants can register for visible exams during the given registration period.
|
||||
The list(s) of participants can be edited online or via CSV export/import.
|
||||
|
||||
<dt .deflist__dt> ^{plannedFeat} Exam Allocations
|
||||
<dd .deflist__dd>
|
||||
Upon request, Uni2work can manage the distribution of participants to exams (rooms/occurrences)
|
||||
using some criteria, such as name or matriculation number.
|
||||
|
||||
<dt .deflist__dt> ^{plannedFeat} Markings
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
It is not possible to enter single exam markings (i.e. points per exam part) at the moment.<br />
|
||||
This feature will be implemented in the near future.
|
||||
<p>
|
||||
Entering exam corrections will happen per exam part.
|
||||
<p>
|
||||
Optionally, course administrators will be able to assign exam correctors, who will then have permission to enter markings during the correction period.<br />
|
||||
The permission to enter an exam marking for single exam parts can be granted to single exam correctors.
|
||||
|
||||
<dt .deflist__dt> ^{newFeat 2019 9 25} Results
|
||||
<dt .deflist__dd>
|
||||
<p>
|
||||
The calculation of exam results can be done automatically via Uni2work.<br />
|
||||
For this, a grading scale needs to be specified. This scale will then be used to calculate
|
||||
the final results of all exam participants based on their achieved amount of points.
|
||||
<p>
|
||||
^{newU2WFeat} At first, the calculated results will be shown as suggestions only.
|
||||
These suggestions then require manual approval by the course administrator before being accepted
|
||||
as exam results, which can afterwards be sent to the Prüfungsämter (Examination Offices).
|
||||
|
||||
<p>
|
||||
^{newU2WFeat} The calculated suggestions can also be overriden manually, independent of the grading scale.
|
||||
|
||||
<dt .deflist__dt> ^{newFeat 2019 9 26} Exam Bonus
|
||||
<dd .deflist__dd>
|
||||
It is possible to calculate bonus points for an exam based on the number of exercise points achieved.<br />
|
||||
The conditions for this calculation can be adjusted (e.g. only take bonus into account if the participant
|
||||
already passed without the bonus points).<br />
|
||||
An upper bound of possible bonus points needs to be specified. The achieved bonus will then be calculated
|
||||
linearly by taking into account the proportion of achieved exercise points and the maximum number of
|
||||
bonus points.
|
||||
|
||||
<dt .deflist__dt> ^{plannedFeat} Door Signs
|
||||
<dd .deflist__dd>
|
||||
The printing of door signs ("Quiet please!") with matching exam information is a planned feature that is not yet implemented.
|
||||
|
||||
<dt .deflist__dt> ^{newFeat 2019 9 16} Reporting Exam Results
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Final grades can be automatically sent to the Prüfungsämter (Exam Offices).
|
||||
<p>
|
||||
After finalizing an exam (by clicking a button on the list of exam participants), all associated
|
||||
Prüfungsämter will be notified that the grading process is finished for this exam.
|
||||
<p>
|
||||
^{newU2WFeat} Once a Prüfungsamt has acknowledged the exam result of a participant, a check mark of the
|
||||
page "Exam Results" will be shown.<br />
|
||||
In case of this check mark being absent even after a long period of time, a course administrator should
|
||||
look into reporting the exam result for this participant (e.g. by issueing a proof of participation).
|
||||
|
||||
<p>
|
||||
^{newU2WFeat} Course administrators are allowed to edit final results even after reporting the grades
|
||||
to the Prüfungsämter. The offices that are affected by these changes will be automatically notified.
|
||||
|
||||
|
||||
<section id="allocations">
|
||||
<h2> Central Allocations
|
||||
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt> Naming Schema
|
||||
<dd .deflist__dd>
|
||||
Courses can possess an arbitrary title.<br />
|
||||
^{newU2WFeat} Makeshift shorthands such as [SB], [ZP] etc. are not necessary anymore!
|
||||
|
||||
<dt .deflist__dt> Course Settings
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
^{newU2WFeat} Where appropriate, the course settings are being overriden by necessary settings of the
|
||||
corresponding central allocation (meaning that course administrators cannot make any mistakes anymore).
|
||||
<p>
|
||||
In particular, the selected registration period will be ignored and any direct registration of students
|
||||
will be prohibited (also coming from course administrators!), until the central allocation process is
|
||||
finished and alternate students ("Nachrücker") are treated.
|
||||
|
||||
<dt .deflist__dt> Individual Applications
|
||||
<dd .deflist__dd>
|
||||
^{newU2WFeat} Students are able to apply for each course separately.
|
||||
Application documents may consist of both free text and the submission of files (possibly with predefined
|
||||
filenames).
|
||||
|
||||
<dt .deflist__dt> Feedback on Applications
|
||||
<dd .deflist__dd>
|
||||
If so desired, course administrators can send back feedback on student's applications.
|
||||
|
||||
|
||||
<section>
|
||||
<h2>Miscellaneous
|
||||
<dl .deflist>
|
||||
|
||||
<dt .deflist__dt> Planned Maintenance
|
||||
<dd .deflist__dd>
|
||||
Planned maintenance will be conducted <i>without prior notice</i>
|
||||
on 02:00 every night.<br />
|
||||
Please refrain from scheduling any critical deadlines at exactly this time or shortly afterwards.
|
||||
11
templates/i18n/pitch/de-de-formal.hamlet
Normal file
11
templates/i18n/pitch/de-de-formal.hamlet
Normal file
@ -0,0 +1,11 @@
|
||||
$newline never
|
||||
<p>
|
||||
Uni2work ist ein Lehrverwaltungssystem, welches an der #
|
||||
Ludwig-Maximilians-Universität München entwickelt und eingesetzt #
|
||||
wird.
|
||||
<p>
|
||||
Insbesondere unterstützt Uni2work teilnehmende Institute bei #
|
||||
Übungsbetrieb, Prüfungs- und Notenverwaltung und bietet vollständige #
|
||||
Kurshomepages inkl. Vorlesungsmaterial und Terminen.
|
||||
|
||||
|
||||
8
templates/i18n/pitch/en-eu.hamlet
Normal file
8
templates/i18n/pitch/en-eu.hamlet
Normal file
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<p>
|
||||
Uni2work is a teaching management system, developed and deployed at #
|
||||
Ludwig-Maximilians-Universität München.
|
||||
<p>
|
||||
Uni2work supports participating departments in managing their course #
|
||||
exercises, exams and exam achievements, and provides complete course #
|
||||
homepages including course material and dates.
|
||||
@ -1,6 +1,6 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>_{MsgHomeUpcomingExams}
|
||||
<h2>_{MsgNewsUpcomingExams}
|
||||
$if hasExams
|
||||
^{examTable}
|
||||
$else
|
||||
@ -1,4 +1,4 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>_{MsgHomeUpcomingSheets}
|
||||
<h2>_{MsgNewsUpcomingSheets}
|
||||
^{sheetTable}
|
||||
@ -28,12 +28,9 @@ $newline never
|
||||
<div .asidenav__link-label>#{courseName}
|
||||
<div .asidenav__nested-list-wrapper>
|
||||
<ul .asidenav__nested-list.list--iconless>
|
||||
$forall (MenuItem{menuItemType, menuItemLabel}, route) <- pageActions
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{menuItemLabel}
|
||||
$of _
|
||||
$forall (NavLink{navLabel}, route) <- pageActions
|
||||
<li .asidenav__nested-list-item>
|
||||
<a .asidenav__link-wrapper href=#{route}>_{navLabel}
|
||||
|
||||
<div .asidenav__sigillum>
|
||||
<img src=@{StaticR img_lmu_sigillum_svg}>
|
||||
|
||||
@ -1,10 +1,17 @@
|
||||
$newline never
|
||||
<div .breadcrumbs__container>
|
||||
<a .breadcrumbs__home href=@{NewsR}>
|
||||
<i .fas .fa-fw .fa-#{iconText IconBreadcrumbsHome}>
|
||||
<ul .breadcrumbs__list.list--inline>
|
||||
$forall (bcRoute, bcTitle, hasAccess) <- parents
|
||||
<li .breadcrumbs__item>
|
||||
$if hasAccess
|
||||
<a .breadcrumbs__link href="@{bcRoute}">#{bcTitle}
|
||||
<a .breadcrumbs__link href="@{bcRoute}">
|
||||
#{bcTitle}
|
||||
$else
|
||||
<span .breadcrumbs__link>#{bcTitle}
|
||||
<li .breadcrumbs__last-item>#{title}
|
||||
<span .breadcrumbs__link>
|
||||
#{bcTitle}
|
||||
<li .breadcrumbs__item-separator>
|
||||
#{iconBreadcrumbSeparator}
|
||||
<li .breadcrumbs__item .breadcrumbs__last-item>
|
||||
#{title}
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
.breadcrumbs__container {
|
||||
position: relative;
|
||||
color: var(--color-lightwhite);
|
||||
padding: 4px 13px;
|
||||
background-color: var(--color-dark);
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
@media (min-width: 426px) {
|
||||
.breadcrumbs__container {
|
||||
padding: 7px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.breadcrumbs__container {
|
||||
padding: 7px 40px;
|
||||
}
|
||||
}
|
||||
|
||||
a.breadcrumbs__link {
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs__item {
|
||||
padding-right: 14px;
|
||||
position: relative;
|
||||
line-height: 28px;
|
||||
opacity: 0.8;
|
||||
z-index: 1;
|
||||
margin-right: 10px;
|
||||
|
||||
&: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;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumbs__last-item {
|
||||
line-height: 28px;
|
||||
vertical-align: bottom;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -1,13 +1,5 @@
|
||||
$newline never
|
||||
<footer .footer>
|
||||
<div .footer-links>
|
||||
$forall (MenuItem{menuItemType, menuItemRoute = _, menuItemIcon = _, menuItemLabel, menuItemModal = _}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of Footer
|
||||
$# Not used but available (remove ` = _` from the pattern match above, as needed):
|
||||
$# highlight (urlRoute menuItemRoute) :: Bool -- ^ Is this menu item currently active (i.e.: are we on this page)
|
||||
$# menuItemModal :: Bool -- ^ Should this menu item open a modal instead of being a normal link
|
||||
$# menuItemIcon :: Maybe Text -- ^ Should this menu item have an icon, if yes, then the name of the icon
|
||||
<a href=#{route} ##{menuIdent}>
|
||||
_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
<ul .footer-links .list--inline>
|
||||
$forall n <- filter isNavFooter nav
|
||||
^{navWidget n}
|
||||
|
||||
4
templates/widgets/footer/link.hamlet
Normal file
4
templates/widgets/footer/link.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<li>
|
||||
<a href=#{route} ##{ident}>
|
||||
_{navLink}
|
||||
14
templates/widgets/navbar/container-radio.cassius
Normal file
14
templates/widgets/navbar/container-radio.cassius
Normal file
@ -0,0 +1,14 @@
|
||||
##{containerIdent}-radio:checked ~ * ##{containerIdent}-container
|
||||
visibility: visible
|
||||
height: 30px
|
||||
|
||||
@media (min-width: 769px) and (min-height: 501px)
|
||||
##{containerIdent}-radio:checked ~ * ##{containerIdent}-container
|
||||
margin: 14px 0 0 0
|
||||
|
||||
##{containerIdent}-radio:checked ~ * ##{containerIdent}
|
||||
background-color: var(--color-dark)
|
||||
color: var(--color-lightwhite)
|
||||
|
||||
.navbar__link-icon
|
||||
opacity: 1
|
||||
5
templates/widgets/navbar/container.hamlet
Normal file
5
templates/widgets/navbar/container.hamlet
Normal file
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<label .navbar__link-wrapper for=#{navIdent}-radio ##{navIdent}>
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{iconText navIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage navLabel}
|
||||
@ -1,6 +1,5 @@
|
||||
$newline never
|
||||
<a .navbar__link-wrapper href=#{route} ##{menuIdent}>
|
||||
$if isJust menuItemIcon
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{fromMaybe "none" menuItemIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage menuItemLabel}
|
||||
<a .navbar__link-wrapper href=#{route} ##{ident}>
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{iconText navIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage navLabel}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
$maybe csrf <- csrfToken
|
||||
<input type=hidden name=#{defaultCsrfParamName} value=#{csrf}>
|
||||
$forall (k, v) <- navData
|
||||
<input type=hidden name=#{k} value=#{v}>
|
||||
<button .navbar__container-link :highlightNav iN:.navbar__container-link--active type=submit>
|
||||
_{SomeMessage navLabel}
|
||||
@ -0,0 +1,3 @@
|
||||
$newline never
|
||||
<a .navbar__container-link :highlightNav iN:.navbar__container-link--active href=#{route} ##{ident}>
|
||||
_{SomeMessage navLabel}
|
||||
@ -1,48 +1,54 @@
|
||||
$newline never
|
||||
<div .navbar-container>
|
||||
<div .navbar-shadow>
|
||||
<nav .navbar.js-sticky-navbar>
|
||||
<nav .navbar>
|
||||
<div .navbar__stack>
|
||||
<div .navbar__list-wrapper>
|
||||
<ul .navbar__list.navbar__list-left>
|
||||
$# manually add favorites to navbar for small screens
|
||||
<li .navbar__list-item.navbar__list-item--favorite>
|
||||
<a .navbar__link-wrapper href="#">
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-#{iconText IconFavourite}>
|
||||
<div .navbar__link-label>_{MsgNavigationFavourites}
|
||||
|
||||
<ul .navbar__list.list--inline.navbar__list-left>
|
||||
$# manually add favorites to navbar for small screens
|
||||
<li .navbar__list-item.navbar__list-item--favorite>
|
||||
<a .navbar__link-wrapper href="#">
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-star>
|
||||
<div .navbar__link-label>_{MsgNavigationFavourites}
|
||||
$forall n <- filter isNavHeaderPrimary nav
|
||||
$case view _1 n
|
||||
$of NavHeader{ navLink }
|
||||
<li .navbar__list-item :highlightNav navLink:.navbar__list-item--active>
|
||||
^{navWidget n}
|
||||
$of NavHeaderContainer{}
|
||||
<li .navbar__list-item.navbar__list-item--container-selector>
|
||||
^{navWidget n}
|
||||
$of _
|
||||
|
||||
$forall (menuItem@MenuItem{menuItemType, menuItemRoute, menuItemModal}, menuIdent, _) <- menuTypes
|
||||
$case menuItemType
|
||||
$of NavbarAside
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of _
|
||||
<ul .navbar__list>
|
||||
$forall n <- filter isNavHeaderSecondary $ reverse nav
|
||||
$case view _1 n
|
||||
$of NavHeader{ navLink }
|
||||
<li .navbar__list-item :highlightNav navLink:.navbar__list-item--active>
|
||||
^{navWidget n}
|
||||
$of NavHeaderContainer{}
|
||||
<li .navbar__list-item.navbar__list-item--container-selector>
|
||||
^{navWidget n}
|
||||
$of _
|
||||
|
||||
<ul .navbar__list.list--inline>
|
||||
$forall (menuItem@MenuItem{menuItemType, menuItemRoute, menuItemModal}, menuIdent, _) <- menuTypes
|
||||
$case menuItemType
|
||||
$of NavbarRight
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of NavbarSecondary
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
^{navbarModal (menuItem, menuIdent)}
|
||||
$else
|
||||
^{navbarItem (menuItem, menuIdent)}
|
||||
$of _
|
||||
<li .navbar__list-item--lang-wrapper uw-language-select>
|
||||
<input type="checkbox" id="lang-checkbox" uw-no-checkbox>
|
||||
<div id="lang-dropdown">
|
||||
^{langFormView'}
|
||||
<div .navbar__list-item .navbar__list-item--language>
|
||||
<label .navbar__link-wrapper for="lang-checkbox">
|
||||
<div .navbar__link-icon>
|
||||
<i .fas .fa-2x .fa-flag-alt>
|
||||
<div .navbar__link-label>_{MsgMenuLanguage}
|
||||
|
||||
$forall n@(NavHeaderContainer{ navHeaderRole }, containerIdent, _, ns) <- filter isNavHeaderContainer nav
|
||||
<div .navbar__container-list :navHeaderRole == NavHeaderPrimary:.navbar__container-list--left ##{containerIdent}-container>
|
||||
<label .navbar__container-list-closer for=container-radio-none>
|
||||
<i .fas .fa-fw .fa-#{iconText IconNavContainerClose}>
|
||||
<ul>
|
||||
$forall iN@(nl, _, _) <- ns
|
||||
<li .navbar__container-list-item :highlightNav nl:.navbar__container-list-item--active>
|
||||
^{navContainerItemWidget n iN}
|
||||
|
||||
|
||||
$# <li .navbar__list-item--lang-wrapper uw-language-select>
|
||||
$# <input type="checkbox" id="lang-checkbox" uw-no-checkbox>
|
||||
$# <div id="lang-dropdown">
|
||||
$# ^{langFormView'}
|
||||
$# <div .navbar__list-item .navbar__list-item--language>
|
||||
$# <label .navbar__link-wrapper for="lang-checkbox">
|
||||
$# <div .navbar__link-icon>
|
||||
$# <i .fas .fa-2x .fa-flag-alt>
|
||||
$# <div .navbar__link-label>_{MsgMenuLanguage}
|
||||
|
||||
4
templates/widgets/notification.hamlet
Normal file
4
templates/widgets/notification.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<div .notification .notification-#{toPathPiece messageStatus} .fa-#{iconText messageIcon} :is _NotificationBroad nType:.notification--broad>
|
||||
<div .notification__content>
|
||||
#{messageContent}
|
||||
@ -1,23 +1,19 @@
|
||||
$newline never
|
||||
<div .pagenav>
|
||||
$if hasPrimaryPageActions
|
||||
<div .pagenav-prime>
|
||||
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of PageActionPrime
|
||||
<div .pagenav__list-item>
|
||||
$if menuItemModal
|
||||
<div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
|
||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
$if hasSecondaryPageActions || hasPrimarySubActions
|
||||
<input .pagenav-item__expand-radio name=pagenav-item__expand-radio id=pageaction-item__expand-none type=radio checked>
|
||||
<ul .pagenav>
|
||||
$forall n <- filter isPageActionPrimary nav
|
||||
<li .pagenav__list-item>
|
||||
^{navWidget n}
|
||||
$if hasSecondaryPageActions
|
||||
<div .pagenav-secondary>
|
||||
<div .pagenav-secondary__list>
|
||||
$forall (MenuItem{menuItemLabel, menuItemType, menuItemModal}, menuIdent, route) <- menuTypes
|
||||
$case menuItemType
|
||||
$of PageActionSecondary
|
||||
<div .pagenav__list-item.pagenav__list-item--secondary>
|
||||
$if menuItemModal
|
||||
<div uw-modal data-modal-trigger=#{menuIdent} data-modal-closeable>
|
||||
<a .pagenav__link-wrapper.pagenav__link-wrapper--secondary href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
<li .pagenav__list-item .pagenav-secondary>
|
||||
<input .pagenav-item__expand-radio name=pagenav-item__expand-radio id=pageaction-item__expand-secondary type=radio>
|
||||
<label .pagenav-item__expand-label for=pageaction-item__expand-secondary>
|
||||
<i .fas .fa-fw .fa-#{iconText IconPageActionSecondary}>
|
||||
<div .pagenav-item__children-wrapper>
|
||||
<ul .pagenav-item__children>
|
||||
$forall n <- filter isPageActionSecondary nav
|
||||
<li>
|
||||
^{navWidget n}
|
||||
<label .pagenav-item__close-label for=pageaction-item__expand-none>
|
||||
#{iconPageActionChildrenClose}
|
||||
|
||||
13
templates/widgets/pageaction/primary-wrapper.hamlet
Normal file
13
templates/widgets/pageaction/primary-wrapper.hamlet
Normal file
@ -0,0 +1,13 @@
|
||||
$newline never
|
||||
^{pWidget}
|
||||
$if not (null sWidgets)
|
||||
<input .pagenav-item__expand-radio name=pagenav-item__expand-radio id=pageaction-item__expand-#{navIdent} type=radio>
|
||||
<label .pagenav-item__expand-label for=pageaction-item__expand-#{navIdent}>
|
||||
<i .fas .fa-fw .fa-#{iconText IconPageActionPrimaryExpand}>
|
||||
<div .pagenav-item__children-wrapper>
|
||||
<ul .pagenav-item__children>
|
||||
$forall sWgt <- sWidgets
|
||||
<li>
|
||||
^{sWgt}
|
||||
<label .pagenav-item__close-label for=pageaction-item__expand-none>
|
||||
#{iconPageActionChildrenClose}
|
||||
3
templates/widgets/pageaction/primary.hamlet
Normal file
3
templates/widgets/pageaction/primary.hamlet
Normal file
@ -0,0 +1,3 @@
|
||||
$newline never
|
||||
<a .pagenav-item__link href=#{route} ##{ident}>
|
||||
_{SomeMessage navLabel}
|
||||
3
templates/widgets/pageaction/secondary.hamlet
Normal file
3
templates/widgets/pageaction/secondary.hamlet
Normal file
@ -0,0 +1,3 @@
|
||||
$newline never
|
||||
<a .pagenav-item__link href=#{route} ##{ident}>
|
||||
_{SomeMessage navLabel}
|
||||
Loading…
Reference in New Issue
Block a user