Enhance user experience by adding informative messages and edit mode functionality across various templates

This commit is contained in:
Ross
2025-09-01 14:47:15 +01:00
parent 16a8bf3d3a
commit 964bce8233
8 changed files with 135 additions and 46 deletions
@@ -1,4 +1,5 @@
<span id="add-case-form">
This form allows you to add an existing case to the collection. You may find it easier to do this from the individual case pages or the <a href="{% url 'atlas:case_view' %}">case overview view</a> which allows you to add multiple cases at once.
<form hx-post="{% url 'atlas:add_case_to_collection' collection.id %}" hx-swap="outerHTML">
{% csrf_token %}
<label for="case">Select Case to add:</label>
+19 -8
View File
@@ -27,9 +27,7 @@
<ul class="list-group mb-4" id="display-set-list">
{% for ds in case.display_sets.all %}
<li class="list-group-item" id="displayset-{{ ds.pk }}">
{% include 'atlas/partials/displayset_row.html' %}
</li>
{% empty %}
<li class="list-group-item text-muted">No display sets for this case.</li>
{% endfor %}
@@ -42,14 +40,27 @@
document.addEventListener("DOMContentLoaded", function () {
window.mountDicomViewers();
{% if selected_displayset %}
setTimeout(function() {
importAnnotations_main_viewer({{ selected_displayset.annotations|safe }});
importViewerState_main_viewer({{ selected_displayset.viewerstate|safe }});
}, 500);
{% endif %}
setTimeout(function() {
importAnnotations_main_viewer({{ selected_displayset.annotations|safe }});
importViewerState_main_viewer({{ selected_displayset.viewerstate|safe }});
// Highlight the selected displayset row
var selectedRow = document.getElementById("displayset-{{ selected_displayset.pk }}");
if (selectedRow) {
selectedRow.style.border = "2px solid #ffc107";
}
}, 1000);
{% if edit %}
var editButton = document.getElementById("displayset-{{ selected_displayset.pk }}-edit-btn");
if (editButton) {
editButton.click();
}
{% endif %}
{% endif %}
});
document.addEventListener("htmx:beforeRequest", function(evt) {
+28 -20
View File
@@ -20,12 +20,12 @@
{% if collection.collection_type == "VIV" %}
<p><a href='{% url "atlas:collection_viva" collection.pk %}'><button class="btn btn-primary">Start Viva</button></a>
{% else %}
<p>This collection will be available to view/take <a href='{{collection.get_take_url}}' id="take-url">here</a>{% if not collection.active %} (when active){% endif %} <button
<p>This collection will be available to view/take <a href='{{collection.get_take_url}}' id="take-url">here</a>{% if not collection.active %} (when active){% endif %} <button
_="on click
writeText(#take-url's href) on navigator.clipboard
put 'copied url!' into me
wait 1s
put 'copy url' into me"
writeText(#take-url's href) on navigator.clipboard
put 'copied url!' into me
wait 1s
put 'copy url' into me"
class="btn btn-sm">copy link</button>
<p>Review collection <a href='{% url "atlas:collection_viva" collection.pk %}'>here</a>
{% endif %}
@@ -33,30 +33,38 @@
<h3>Cases</h3>
<ol id="full-question-list" class="sortable">
{% for casedetail in casesdetails %}
<li data-question_pk={{casedetail.case.pk}}><a title="sort_order: {{casedetail.sort_order}}" href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a>
: {{casedetail.case.title}}
<li data-question_pk={{casedetail.case.pk}}>
(<a href="{% url 'atlas:collection_case_displaysetup' casedetail.collection.pk casedetail.case.pk %}"><i class="bi bi-display" title="Setup default display"></i></a>
<a href="{% url 'atlas:collection_case_view' pk=collection.pk case_number=forloop.counter0 %}">Case {{forloop.counter}}</a>
:
{% if casedetail.case.series.count == 0 %}
&nbsp;<i class="bi bi-exclamation-circle text-warning" title="No series attached to this case"></i>&nbsp;
{% endif %}
{{casedetail.case.title}}
(<a href="{% url 'atlas:collection_case_displaysetup' casedetail.collection.pk casedetail.case.pk %}"><i class="bi bi-display" title="Setup default display"></i></a>
{% if casedetail.default_viewerstate %}
<i class="bi bi-check text-success" title="This case has a default viewerstate defined"></i>
{% endif %}
)
)
{% if collection.collection_type == "QUE" %}
(<a href='{% url "atlas:collection_case_details" casedetail.collection.pk casedetail.case.pk %}'>
{% if casedetail.question_schema %}
<i class="bi bi-question-square text-success" title="This case has questions defined."></i>
{% else %}
<i class="bi bi-question-square text-danger" title="This case has no questions defined."></i>
{% endif %}
</a>
{% if casedetail.question_schema %}
<i class="bi bi-question-square text-success" title="This case has questions defined."></i>
{% else %}
<i class="bi bi-question-square text-danger" title="This case has no questions defined."></i>
{% endif %}
</a>
)
{% endif %}
{% if casedetail.case.previous_case %}
(<a href='{% url "atlas:collection_case_priors" casedetail.collection.pk casedetail.case.pk %}'>
<i class="bi bi-link-45deg" title="Manage priors"></i></a>)
{% endif %}
{% if casedetail.case.previous_case %}
(<a href='{% url "atlas:collection_case_priors" casedetail.collection.pk casedetail.case.pk %}'>
<i class="bi bi-link-45deg" title="Manage priors"></i></a>)
{% endif %}
</li>
@@ -67,7 +75,7 @@
{% if can_edit %}
<p><button id='button-edit-order' title='click and drag questions to change order' data-posturl="{% url 'atlas:exam_json_edit' pk=collection.pk %}">Edit case order / Delete cases</button></p>
<p><button id='button-edit-order' title='click and drag questions to change order' data-posturl="{% url 'atlas:exam_json_edit' pk=collection.pk %}">Edit case order / Remove cases</button></p>
<p><button id='button-add-case'
title='click to add a case to this collection'
hx-get="{% url 'atlas:add_case_to_collection' collection.pk %}" hx-swap="innerHTML" hx-target="#case-list">
+44 -12
View File
@@ -12,6 +12,7 @@
<div class="mb-2">
<button id="toggle-load-mode" class="btn btn-secondary">Switch to Load OHIF</button>
<button id="toggle-edit-mode" class="btn btn-warning ms-2">Enable Edit Mode</button>
</div>
</details>
<div class="container">
@@ -24,14 +25,18 @@
<ol id="" class="sortable">
{% for casedetail in casedetails %}
{% with case=casedetail.case %}
<li data-question_pk={{case.pk}}>
<div class="case-item" data-title="{{case.title}}" data-case={{case.pk}} data-casejson='{{case.get_viva_details_json}}' data-images='{{case.get_series_images_nested}}' data-viewerstate={{casedetail.default_viewerstate_string}}>Case {{forloop.counter}}: {{case.title}} &nbsp;
{% comment %} <button class="show-case no-padding" data-target="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">Show</button> {% endcomment %}
{% comment %} <button class="send-message no-padding" data-title="{{case.title}}" data-target="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}">Open</button> {% endcomment %}
{% comment %} <button class="open-case no-padding" data-target="{% url 'atlas:collection_viva_case' collection.pk forloop.counter0 %}" data-type="case">Load</button> {% endcomment %}
<div class="case-item" data-title="{{case.title}}" data-case={{case.pk}} data-casejson='{{case.get_viva_details_json}}' data-images='{{case.get_series_images_nested}}' data-viewerstate={{casedetail.default_viewerstate_string}}>
Case {{forloop.counter}}: {{case.title}} &nbsp;
<button class="open-case no-padding" data-target="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:case_dicom_json' case.pk %}" data-type="case">Load OHIF</button>
<button class="open-case-local no-padding" data-target=case.pk data-type="case">Load</button>
<!-- Edit mode link for case -->
<button class="view-link-case btn btn-outline-primary btn-sm ms-2" style="display:none;">
<a href="{% url 'atlas:case_detail' case.pk %}" style="color: inherit; text-decoration: none;">View Case</a>
</button>
<button class="edit-link-case btn btn-outline-warning btn-sm ms-2" style="display:none;">
<a href="{% url 'atlas:case_update' case.pk %}" style="color: inherit; text-decoration: none;">Edit Case</a>
</button>
<br />
<div class="pre-whitespace multi-image-block">
<details class="series-detail"><summary>Series:</summary>
@@ -42,10 +47,6 @@
test
{{series.get_block}}
<br />
{% comment %} <button class="show-series no-padding text-secondary border-secondary" data-case={{case.pk}} data-target="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:series_dicom_json' series.pk %}">
show
</button> {% endcomment %}
{% comment %} <button class="open-series no-padding text-secondary border-secondary" data-type="series" data-series={{forloop.counter}} data-case={{case.pk}} data-target="{% url 'atlas:collection_viva_series' collection.pk series.pk %}"> {% endcomment %}
<button class="open-series no-padding text-secondary border-secondary" data-type="series" data-series={{forloop.counter}} data-case={{case.pk}} data-target="/ohif/viewer/dicomjson?url=https://www.penracourses.org.uk{% url 'atlas:series_dicom_json' series.pk %}">
Load
</button>
@@ -56,6 +57,10 @@
data-target="{{ series.pk }}">
Load
</button>
<!-- Edit mode link for series -->
<button class="edit-link-series btn btn-outline-warning btn-sm ms-2" style="display:none;">
<a href="{% url 'atlas:series_update' series.pk %}" style="color: inherit; text-decoration: none;">Edit Series</a>
</button>
<br>
</span>
</span>
@@ -76,13 +81,17 @@
data-displayset="{{ ds.pk }}"
data-case="{{ case.pk }}"
data-title="{{ ds.name }}"
data-casejson='{{case.get_viva_details_json}}'
data-images='{{case.get_series_images_nested}}'
data-casejson='{{case.get_viva_details_json}}'
data-images='{{case.get_series_images_nested}}'
data-viewerstate={{ds.viewerstate_string}}
data-annotations='{{ds.annotations_string}}'
>
Load Display Set
</button>
<!-- Edit mode link for display set -->
<button class="edit-link-displayset btn btn-outline-warning btn-sm ms-2" style="display:none;">
<a href="{% url 'atlas:case_displaysets' case.pk %}?displayset={{ ds.pk }}&edit=true" style="color: inherit; text-decoration: none;">Edit Display Set</a>
</button>
</span>
</div>
{% empty %}
@@ -92,7 +101,6 @@
{% endif %}
</div>
</li>
{% endwith %}
{% endfor %}
</ol>
@@ -120,10 +128,30 @@
{% block js %}
<script type="text/javascript">
var win2 = false;
let editMode = false;
$(document).ready(function() {
let loadLocalMode = true;
// Edit mode toggle
$('#toggle-edit-mode').click(function() {
editMode = !editMode;
if (editMode) {
$('.edit-link-case').show();
$('.view-link-case').show();
$('.edit-link-series').show();
$('.edit-link-displayset').show();
$(this).text('Disable Edit Mode').removeClass('btn-warning').addClass('btn-danger');
} else {
$('.edit-link-case').hide();
$('.view-link-case').hide();
$('.edit-link-series').hide();
$('.edit-link-displayset').hide();
$(this).text('Enable Edit Mode').removeClass('btn-danger').addClass('btn-warning');
}
});
// Update all "Load" buttons to reflect the current mode
function updateLoadButtons() {
if (loadLocalMode) {
@@ -335,5 +363,9 @@
.title {
font-weight: bold;
}
.edit-link-case, .edit-link-series, .edit-link-displayset {
vertical-align: middle;
}
</style>
{% endblock css %}
@@ -66,10 +66,33 @@
</div>
<div class="mt-2">
<button
id="displayset-{{ ds.pk }}-edit-btn"
hx-get="{% url 'atlas:case_displaysets_edit' ds.pk %}"
hx-target="#displayset-edit-form-{{ ds.pk }}"
hx-swap="innerHTML"
hx-after
class="btn btn-warning btn-sm"
onclick="
this.style.display='none';
var li = this.closest('li');
var ul = li.parentNode;
ul.insertBefore(li, ul.firstChild);
li.scrollIntoView({behavior: 'smooth', block: 'center'});
setTimeout(function() {
var form = document.querySelector('#displayset-edit-form-{{ ds.pk }} form');
if (form) {
var cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
cancelBtn.className = 'btn btn-secondary btn-sm ms-2';
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = function() {
document.querySelector('#displayset-edit-form-{{ ds.pk }}').innerHTML = '';
document.querySelector('#displayset-{{ ds.pk }}-edit-btn').style.display = '';
};
form.appendChild(cancelBtn);
}
}, 100);
"
>Edit</button>
<button
hx-delete="{% url 'atlas:case_displaysets_delete' ds.pk %}"
@@ -80,5 +103,8 @@
>Delete</button>
</div>
</div>
<div id="displayset-edit-form-{{ ds.pk }}"></div>
<div id="displayset-edit-form-{{ ds.pk }}"></div>
<div class="align-self-start ms-3">
<span class="text-muted small">ID: {{ ds.pk }}</span>
</div>
</li>
+9 -2
View File
@@ -11,6 +11,7 @@ from django.utils import timezone
from django.views.decorators.http import require_POST
from django.views.decorators.http import require_http_methods
from django.template.loader import render_to_string
from django.db.models import Count
# from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required, user_passes_test
@@ -211,6 +212,7 @@ def case_displaysets(request, pk):
# Get displayset id from GET parameter
displayset_id = request.GET.get("displayset")
edit = request.GET.get("edit", False)
selected_displayset = None
if displayset_id:
try:
@@ -223,6 +225,7 @@ def case_displaysets(request, pk):
"atlas/case_displaysets.html",
{
"case": case,
"edit": edit,
"can_edit": can_edit,
"selected_displayset": selected_displayset,
},
@@ -2029,8 +2032,12 @@ def collection_detail(request, pk):
collection = get_object_or_404(CaseCollection, pk=pk)
# cases = collection.cases.all().order_by("casedetail__sort_order")
casedetails = CaseDetail.objects.filter(collection=collection).order_by(
"sort_order"
casedetails = (
CaseDetail.objects
.filter(collection=collection)
.select_related("case")
.annotate(series_count=Count("case__seriesdetail"))
.order_by("sort_order")
)
return render(
+6 -2
View File
@@ -15,9 +15,13 @@
{% block content %}
<h2>Edit Authors</h2>
<p>Authors have full access to access and edit the exam/collection (including the ability to add/remove other authors).</p>
<div class="alert alert-info" role="alert">
<p>
Authors have <b>full access</b> to access and edit the exam/collection (<b>including the ability to add/remove other authors</b>).
</p>
<p>If you just want a user to be able to mark questions add them as a marker instead.</p>
</div>
<form action="" method="post">
{% csrf_token %}
+1 -1
View File
@@ -180,7 +180,7 @@
// Existing delete button logic
$(el).append($(
"<span class='exam-question-delete flex-col'><button>DELETE</button></span>"
"<span class='exam-question-delete flex-col'><button title='Remove this question from the exam/collection'>Remove</button></span>"
).click(() => {
el.remove();
}));