From b94211df7729b979e231c3e50ce8516902b624a2 Mon Sep 17 00:00:00 2001 From: Niklas Mohrin Date: Mon, 17 Jul 2023 22:32:42 +0200 Subject: [PATCH] Vision More realistic alternative to `current` Add custom elements scaffold looks right, doesn't open found a way to use import, but not have icons modal texts displayed before script is loaded fix usage of custom elements prototype before it was loaded make it open the modal dont know some progess with native dialogs make it look somewhat like bootstrap progress? make animations work implement confirmation logic ough, now the slots inherit values from the .card-header bar Use `:defined` instead of `.loaded` class more modals and fix blocktrans Allow putting extra stuff under `confirmation-modal` more modals format more modals --- .../contributor_evaluation_form.html | 31 ++-- .../templates/contributor_index.html | 73 +++------ evap/evaluation/templates/base.html | 2 + .../confirmation_modal_template.html | 33 +++++ .../evaluation/templates/custom_elements.html | 9 ++ evap/grades/templates/grades_course_view.html | 59 +++++--- .../templates/grades_semester_view.html | 76 +++++----- evap/staff/templates/staff_semester_view.html | 138 +++++++++++------- evap/staff/templates/staff_user_import.html | 33 ++--- evap/staff/views.py | 3 +- evap/static/scss/_components.scss | 1 + .../scss/components/_confirmation-modal.scss | 77 ++++++++++ evap/static/ts/src/confirmation-modal.ts | 62 ++++++++ evap/static/ts/src/utils.ts | 2 +- 14 files changed, 391 insertions(+), 208 deletions(-) create mode 100644 evap/evaluation/templates/confirmation_modal_template.html create mode 100644 evap/evaluation/templates/custom_elements.html create mode 100644 evap/static/scss/components/_confirmation-modal.scss create mode 100644 evap/static/ts/src/confirmation-modal.ts diff --git a/evap/contributor/templates/contributor_evaluation_form.html b/evap/contributor/templates/contributor_evaluation_form.html index ecd2e22c51..9fb2b90d48 100644 --- a/evap/contributor/templates/contributor_evaluation_form.html +++ b/evap/contributor/templates/contributor_evaluation_form.html @@ -90,9 +90,18 @@
{% trans 'Evaluation data' %}
{% 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 @@ {% 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 %} +
+
+ + + + +
{% endif %} {% elif evaluation.state == evaluation.State.EDITOR_APPROVED or evaluation.state == evaluation.State.APPROVED %} + + {% endif %} {% endfor %} {% endblock %} - -{% block modals %} - {{ block.super }} - - {% with modal_id='delegateSelectionModal' %} - - - - {% endwith %} -{% endblock %} diff --git a/evap/evaluation/templates/base.html b/evap/evaluation/templates/base.html index cdb785d8e1..4c2d8346c3 100644 --- a/evap/evaluation/templates/base.html +++ b/evap/evaluation/templates/base.html @@ -28,6 +28,8 @@ + {% include "custom_elements.html" %} + {% block modals %} {% if user.is_authenticated %} {% trans 'Feedback' as title %} diff --git a/evap/evaluation/templates/confirmation_modal_template.html b/evap/evaluation/templates/confirmation_modal_template.html new file mode 100644 index 0000000000..bf13e90727 --- /dev/null +++ b/evap/evaluation/templates/confirmation_modal_template.html @@ -0,0 +1,33 @@ +{% load static %} + + diff --git a/evap/evaluation/templates/custom_elements.html b/evap/evaluation/templates/custom_elements.html new file mode 100644 index 0000000000..f2093a71a7 --- /dev/null +++ b/evap/evaluation/templates/custom_elements.html @@ -0,0 +1,9 @@ +{% load static %} + +{% include "confirmation_modal_template.html" %} + + diff --git a/evap/grades/templates/grades_course_view.html b/evap/grades/templates/grades_course_view.html index 7ab7b60fcb..bb2f36c43b 100644 --- a/evap/grades/templates/grades_course_view.html +++ b/evap/grades/templates/grades_course_view.html @@ -1,4 +1,5 @@ {% extends 'grades_course_base.html' %} +{% load static %} {% block content %} {{ block.super }} @@ -30,13 +31,45 @@

{{ course.name }} ({{ semester.name }})

{% 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' %}
- - +
+ {% csrf_token %} + + + {% trans 'Activate reward points' %} + {% trans 'Activate reward points' %} + + {% blocktrans with semester_name=semester.name %} + Do you want to activate the reward points for the semester {{ semester_name }}? + 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 %} + + + + + + +

@@ -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;