{% if editable %}
- {# webtest does not allow submission with value "approve" if no such button exists #}
-
-
+
+
+ {% trans 'Approve evaluation' %}
+ {% trans 'Approve evaluation' %}
+
+ {% blocktrans %}
+ Do you want to approve this evaluation? This will allow the evaluation team to proceed with the preparation, but you won't be able to make any further changes.
+ {% endblocktrans %}
+
+
+
+
{% endif %}
{% if edit %}{% trans 'Cancel' %}{% else %}{% trans 'Back' %}{% endif %}
@@ -121,22 +130,6 @@
{% trans 'Preview' %}
{% block modals %}
{{ block.super }}
- {% trans 'Approve evaluation' as title %}
- {% blocktrans asvar question%}Do you want to approve this evaluation? This will allow the evaluation team to proceed with the preparation, but you won't be able to make any further changes.{% endblocktrans %}
- {% trans 'Approve evaluation' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='approveEvaluationModal' title=title question=question action_text=action_text btn_type='primary' %}
-
{% blocktrans asvar title with evaluation_name=evaluation.full_name %}Request account creation for {{ evaluation_name }}{% endblocktrans %}
{% trans 'Please tell us which new account we should create. We need the name and email for all new accounts.' as teaser %}
diff --git a/evap/contributor/templates/contributor_index.html b/evap/contributor/templates/contributor_index.html
index e61132d2cc..d49eef391c 100644
--- a/evap/contributor/templates/contributor_index.html
+++ b/evap/contributor/templates/contributor_index.html
@@ -157,12 +157,22 @@
{% if not evaluation|has_nonresponsible_editor %}
-
-
-
+
+ {% trans 'Delegate preparation' %}
+ {% trans 'Delegate preparation' %}
+
+ {% blocktrans with evaluation_name=evaluation.full_name %}
+ Do you really want to delegate the preparation of the evaluation {{ evaluation_name }}?
+ {% endblocktrans %}
+
+ {% include 'bootstrap_form.html' with form=delegate_selection_form wide=True %}
+
{% if user.is_grade_publisher %}
-
+
+ {% trans 'Delete grade document' %}
+ {% trans 'Delete grade document' %}
+
+ {% blocktrans with description=grade_document.description %}
+ Do you really want to delete the grade document {{ description }}?
+ {% endblocktrans %}
+
+
+
+
{% endif %}
{% endfor %}
+
+
{% else %}
@@ -49,23 +82,3 @@
{{ course.name }} ({{ semester.name }})
{% trans 'Upload new final grades' %}
{% endif %}
{% endblock %}
-
-{% block modals %}
- {{ block.super }}
- {% trans 'Delete grade document' as title %}
- {% trans 'Do you really want to delete the grade document ?' as question %}
- {% trans 'Delete grade document' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='deleteGradedocumentModal' title=title question=question action_text=action_text btn_type='danger' %}
-
-{% endblock %}
diff --git a/evap/grades/templates/grades_semester_view.html b/evap/grades/templates/grades_semester_view.html
index ae9d586115..9df312be6f 100644
--- a/evap/grades/templates/grades_semester_view.html
+++ b/evap/grades/templates/grades_semester_view.html
@@ -64,17 +64,38 @@
{% if num_final_grades > 0 %}
{{ num_final_grades }}
{% elif course.gets_no_grade_documents %}
-
-
-
+
+ {% trans 'Will final grades be uploaded?' %}
+ {% trans 'Confirm' %}
+
+ {% blocktrans with course_name=course.name %}
+ Please confirm that a grade document for the course {{ course_name }} will be uploaded later on.
+ {% endblocktrans %}
+
+
+
+
+
+
{% endif %}
{% if not course.gets_no_grade_documents %}
{% if num_final_grades == 0 %}
-
+
+ {% trans 'Have final grades been submitted?' %}
+ {% trans 'Confirm' %}
+
+ {% blocktrans with course_name=course.name %}
+ Please confirm that the final grades for the course {{ course_name }} have been submitted but will not be uploaded.
+ {% endblocktrans %}
+
+
+
+
+
{% endblock %}
-{% block modals %}
- {{ block.super }}
- {% trans 'Have final grades been submitted?' as title %}
- {% trans 'Please confirm that the final grades for the course have been submitted but will not be uploaded.' as question %}
- {% trans 'Confirm' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='confirmNouploadModal' title=title question=question action_text=action_text btn_type='primary' %}
-
- {% trans 'Will final grades be uploaded?' as title %}
- {% trans 'Please confirm that a grade document for the course will be uploaded later on.' as question %}
- {% trans 'Confirm' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='confirmLateruploadModal' title=title question=question action_text=action_text btn_type='primary' %}
-
-{% endblock %}
-
{% block additional_javascript %}
+
+
+ {% endif %}
{% if request.user.is_manager %}
@@ -72,8 +116,25 @@
{% trans 'Reward points active' %}
-
-
+
@@ -464,26 +525,6 @@
}).catch(error => {window.alert("{% trans 'The server is not responding.' %}");});
}
- {% trans 'Delete semester' as title %}
- {% blocktrans asvar question %}Do you really want to delete the semester ? All courses and evaluations will be deleted as well as all results. If you are sure, enter the name of the semester below.{% endblocktrans %}
- {% trans 'Delete semester' as action_text %}
- {% include 'confirmation_text_modal.html' with modal_id='deleteSemesterModal' title=title question=question action_text=action_text btn_type='danger' %}
-
{% trans 'Archive participations' as title %}
{% blocktrans asvar question %}Do you really want to archive all participations in the semester ? Further changes to the evaluations won't be possible and you can't undo this action.{% endblocktrans %}
{% trans 'Archive participations' as action_text %}
@@ -564,15 +605,6 @@
}).catch(error => {window.alert("{% trans 'The server is not responding.' %}");});
};
- {% trans 'Activate reward points' as title %}
- {% blocktrans asvar question %}Do you want to activate the reward points for the semester ? The activation will allow participants to receive reward points when voting and will also grant all eligible points for participants who have already voted so far. The process will take a while.{% endblocktrans %}
- {% trans 'Activate reward points' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='activateRewardPointsModal' title=title question=question action_text=action_text btn_type='primary' %}
-
{% endblock %}
{% block additional_javascript %}
diff --git a/evap/staff/templates/staff_user_import.html b/evap/staff/templates/staff_user_import.html
index b316052145..6593fd1351 100644
--- a/evap/staff/templates/staff_user_import.html
+++ b/evap/staff/templates/staff_user_import.html
@@ -34,33 +34,24 @@
{% trans 'Import users' %}
{% else %}
+
+
+ {% trans 'Import Users' %}
+ {% trans 'Import Users' %}
+
+ {% blocktrans %}
+ Do you really want to import the users from the Excel file?
+ {% endblocktrans %}
+
+
+
+
{% endif %}
{% endblock %}
-{% block modals %}
-{{ block.super }}
- {% trans 'Import Users' as title %}
- {% blocktrans asvar question %}Do you really want to import the users from the Excel file?{% endblocktrans %}
- {% trans 'Import Users' as action_text %}
- {% include 'confirmation_modal.html' with modal_id='importUserModal' title=title question=question action_text=action_text btn_type='primary' %}
-
-
-{% endblock %}
-
{% block additional_javascript %}
{% endblock %}
diff --git a/evap/staff/views.py b/evap/staff/views.py
index d6f143a7b6..6a90c46146 100644
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -620,7 +620,8 @@ def semester_delete(request):
Contribution.objects.filter(evaluation__course__semester=semester).delete()
Evaluation.objects.filter(course__semester=semester).delete()
Course.objects.filter(semester=semester).delete()
- semester.delete()
+ # TODO: revert
+ # semester.delete()
return redirect("staff:index")
diff --git a/evap/static/scss/_components.scss b/evap/static/scss/_components.scss
index bc06ea8d47..f30a3f7eca 100644
--- a/evap/static/scss/_components.scss
+++ b/evap/static/scss/_components.scss
@@ -17,5 +17,6 @@
@import "components/modal";
@import "components/tooltip";
+@import "components/confirmation-modal";
@import "components/distribution-bar";
@import "components/quick-review";
diff --git a/evap/static/scss/components/_confirmation-modal.scss b/evap/static/scss/components/_confirmation-modal.scss
new file mode 100644
index 0000000000..593828e135
--- /dev/null
+++ b/evap/static/scss/components/_confirmation-modal.scss
@@ -0,0 +1,77 @@
+confirmation-modal:not(:defined) > :not([slot="submit-group"]) {
+ // Without this, the elements that are slotted into the dialog element show up as if they are just normal child
+ // elements and disappear once the custom element registration is done. To avoid a short flicker of these elements,
+ // we hide them until the constructor of the custom element has run.
+ display: none;
+}
+
+dialog.evap-modal-dialog {
+ padding: 0;
+ border: 1px solid $dark-gray;
+ border-radius: 0.5rem;
+ z-index: 1050;
+ inset-block-start: -70vh;
+
+ font-weight: initial;
+ line-height: initial;
+
+ // https://youtu.be/4prVdA7_6u0
+ &[open] {
+ animation: modal-enter 300ms forwards;
+ }
+
+ &[closing] {
+ display: block;
+ pointer-events: none;
+ animation: modal-exit 300ms forwards;
+ }
+
+ @keyframes modal-enter {
+ from {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ to {
+ transform: translateY(0%);
+ opacity: 1;
+ }
+ }
+
+ @keyframes modal-exit {
+ from {
+ transform: translateY(0%);
+ opacity: 1;
+ }
+ to {
+ transform: translateY(-100%);
+ opacity: 0;
+ }
+ }
+
+ &::backdrop {
+ background-color: black;
+ opacity: 50%;
+ }
+
+ .evap-modal-container {
+ > * {
+ padding: 1rem;
+ }
+
+ > :not(:first-child) {
+ border-top: 1px solid $light-gray;
+ }
+
+ header {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+
+ .button-area {
+ display: flex;
+ flex-flow: wrap;
+ justify-content: center;
+ }
+ }
+}
diff --git a/evap/static/ts/src/confirmation-modal.ts b/evap/static/ts/src/confirmation-modal.ts
new file mode 100644
index 0000000000..29c2d0d6e7
--- /dev/null
+++ b/evap/static/ts/src/confirmation-modal.ts
@@ -0,0 +1,62 @@
+import { selectOrError } from "./utils.js";
+
+export class ConfirmationModal extends HTMLElement {
+ static formAssociated = true;
+
+ readonly dialog: HTMLDialogElement;
+ readonly dialogForm: HTMLFormElement;
+ readonly type: string;
+ readonly internals: ElementInternals;
+
+ constructor() {
+ super();
+
+ const template = selectOrError("#confirmation-modal-template").content;
+ const shadowRoot = this.attachShadow({ mode: "open" });
+ shadowRoot.appendChild(template.cloneNode(true));
+
+ this.type = this.getAttribute("type") ?? "button";
+ this.internals = this.attachInternals();
+
+ this.dialog = selectOrError("dialog", shadowRoot);
+ this.dialogForm = selectOrError("form[method=dialog]", this.dialog);
+
+ const confirmButton = selectOrError("[data-event-type=confirm]", this.dialog);
+ const confirmButtonExtraClass = this.getAttribute("confirm-button-class") ?? "btn-primary";
+ confirmButton.className += " " + confirmButtonExtraClass;
+
+ selectOrError("[slot=show-button]", this).addEventListener("click", () => this.dialog.showModal());
+ this.dialogForm.addEventListener("submit", this.onDialogFormSubmit);
+ }
+
+ onDialogFormSubmit = (event: SubmitEvent) => {
+ event.preventDefault();
+
+ this.closeDialogSlowly();
+
+ if (event.submitter?.dataset?.eventType === "confirm") {
+ this.dispatchEvent(new CustomEvent("confirmed", { detail: new FormData(this.dialogForm) }));
+
+ if (this.type === "submit") {
+ // Unfortunately, `this` cannot act as the submitter of the form. Instead, we make our `value` attribute
+ // visible to the form until submission is finished (the `submit` handlers of the form might cancel the
+ // submission again, which is why we hide reset the visible value again afterwards).
+ this.internals.setFormValue(this.getAttribute("value"));
+ this.internals.form?.requestSubmit();
+ this.internals.setFormValue(null);
+ }
+ }
+ };
+
+ closeDialogSlowly = () => {
+ this.dialog.addEventListener(
+ "animationend",
+ () => {
+ this.dialog.removeAttribute("closing");
+ this.dialog.close();
+ },
+ { once: true }
+ );
+ this.dialog.setAttribute("closing", "");
+ };
+}
diff --git a/evap/static/ts/src/utils.ts b/evap/static/ts/src/utils.ts
index ecaaae5917..de9434e3b5 100644
--- a/evap/static/ts/src/utils.ts
+++ b/evap/static/ts/src/utils.ts
@@ -1,4 +1,4 @@
-export const selectOrError = (selector: string, root: Element | Document = document): T => {
+export const selectOrError = (selector: string, root: ParentNode = document): T => {
const elem = root.querySelector(selector);
assert(elem, `Element with selector ${selector} not found`);
return elem;