lots of improvements
This commit is contained in:
@@ -118,6 +118,16 @@
|
||||
ID: {{ case.id }}
|
||||
</div>
|
||||
|
||||
<details id="dicom-viewer-details">
|
||||
<summary>Viewer</summary>
|
||||
|
||||
<div id="main_viewer" class="dicom-viewer-root"
|
||||
style="box-sizing: border-box; background: #222; width: 100%; height: 600px;"
|
||||
data-auto-cache-stack="false"
|
||||
data-images='{{case.get_series_images_nested}}'
|
||||
></div>
|
||||
</details>
|
||||
|
||||
<p class="pre-whitespace"><b>Description:</b> {{ case.description }}</p>
|
||||
|
||||
<p>
|
||||
@@ -227,8 +237,10 @@
|
||||
</details>
|
||||
{% endif %}
|
||||
|
||||
<!-- Series Findings -->
|
||||
<h5>Series Findings</h5>
|
||||
{% if case.series.all %}
|
||||
{% for detail in case.get_ordered_series %}
|
||||
{% for series in case.get_ordered_series %}
|
||||
{% for finding in series.findings.all %}
|
||||
<div class="finding-box" id="finding-box-{{ finding.pk }}" data-series="{{ series.pk }}"
|
||||
_="on mouseenter
|
||||
@@ -237,7 +249,9 @@
|
||||
until mouseleave"
|
||||
>
|
||||
<span>
|
||||
<a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}"><button class="btn btn-primary btn-sm">View</button></a>
|
||||
<a href="{{series.get_absolute_url}}?show_finding={{finding.pk}}">
|
||||
<button class="btn btn-primary btn-sm">View</button>
|
||||
</a>
|
||||
<button class="btn btn-secondary btn-sm view-finding-modal" data-finding-id="{{ finding.pk }}" data-series-id="{{ series.pk }}">
|
||||
View in Modal
|
||||
</button>
|
||||
@@ -274,6 +288,75 @@
|
||||
{% else %}
|
||||
No series associated with case.
|
||||
{% endif %}
|
||||
|
||||
<!-- Case Display Sets -->
|
||||
<h5 class="mt-3">Case Display Sets</h5>
|
||||
{% if case.display_sets %}
|
||||
<ul class="list-group">
|
||||
{% for ds in case.display_sets.all %}
|
||||
<li class="list-group-item">
|
||||
<b>Name: {{ ds.name }}</b>
|
||||
{% if ds.description %}
|
||||
<span class="text-muted">({{ ds.description }})</span>
|
||||
{% endif %}
|
||||
<span>
|
||||
<a href="{% url 'atlas:case_displaysets_detail' ds.pk %}">View Display Set</a>
|
||||
</span>
|
||||
<div>
|
||||
<strong>Findings:</strong>
|
||||
{% if ds.findings.all %}
|
||||
<ul>
|
||||
{% for finding in ds.findings.all %}
|
||||
<li>{{ finding }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Structures:</strong>
|
||||
{% if ds.structures.all %}
|
||||
<ul>
|
||||
{% for structure in ds.structures.all %}
|
||||
<li>{{ structure }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Conditions:</strong>
|
||||
{% if ds.conditions.all %}
|
||||
<ul>
|
||||
{% for condition in ds.conditions.all %}
|
||||
<li>{{ condition }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<details>
|
||||
<summary class="small text-muted" style="cursor:pointer;">Show advanced (viewer state & annotations)</summary>
|
||||
<div>
|
||||
<strong>Viewer State:</strong>
|
||||
<pre class="small">{{ ds.viewerstate|default:"{}" }}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Annotations:</strong>
|
||||
<pre class="small">{{ ds.annotations|default:"{}" }}</pre>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">No display sets for this case.</span>
|
||||
{% endif %}
|
||||
</details>
|
||||
</div>
|
||||
<div>
|
||||
@@ -439,7 +522,18 @@
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
// Move up/down logic
|
||||
|
||||
const dicomDetails = document.getElementById("dicom-viewer-details");
|
||||
let dicomViewerLoaded = false;
|
||||
dicomDetails.addEventListener("toggle", function() {
|
||||
if (!dicomViewerLoaded && dicomDetails.open) {
|
||||
window.mountDicomViewers();
|
||||
dicomViewerLoaded = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Move up/down logic
|
||||
document.querySelectorAll("#series-reorder-list .move-up, #series-reorder-list .move-down").forEach(function(btn) {
|
||||
btn.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
{% extends 'atlas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
{% include 'atlas/question_link_header.html' %}
|
||||
|
||||
<h2>Display Sets: {{ case.title }}</h2>
|
||||
|
||||
<details class="help-text">
|
||||
<summary><i class="bi bi-info-circle"></i> Help</summary>
|
||||
<p>This page shows the display sets associated with the case.</p>
|
||||
<p>If you have access you can add and edit display sets.</p>
|
||||
</details>
|
||||
|
||||
|
||||
<div id="main_viewer" class="dicom-viewer-root"
|
||||
style="box-sizing: border-box; background: #222; width: 100%; height: 600px;"
|
||||
data-auto-cache-stack="false"
|
||||
data-images='{{case.get_series_images_nested}}'
|
||||
></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button hx-get="{% url 'atlas:case_displaysets_add' case.pk %}" hx-target="#displayset-add-form" hx-swap="innerHTML">Add Display Set</button>
|
||||
<div id="displayset-add-form"></div>
|
||||
</div>
|
||||
|
||||
<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 %}
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
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 %}
|
||||
|
||||
|
||||
});
|
||||
document.addEventListener("htmx:beforeRequest", function(evt) {
|
||||
console.log("htmx:beforeRequest", evt.detail);
|
||||
// Only for the display set add form
|
||||
var form = evt.target;
|
||||
console.log("Form:", form);
|
||||
console.log("Form matches:", form.matches("form[hx-post*='{% url 'atlas:case_displaysets_add' case.pk %}']"));
|
||||
if (form && form.matches("form[hx-post*='{% url 'atlas:case_displaysets_add' case.pk %}']")) {
|
||||
// Set hidden fields from JS functions
|
||||
var viewerState = window.exportViewerState_main_viewer ? window.exportViewerState_main_viewer() : "{}";
|
||||
var annotations = window.exportAnnotations_main_viewer ? window.exportAnnotations_main_viewer() : "{}";
|
||||
evt.detail.requestConfig.parameters["annotations"] = annotations;
|
||||
evt.detail.requestConfig.parameters["viewerstate"] = viewerState;
|
||||
console.log(evt)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -29,7 +29,7 @@ document.getElementById('save-viewerstate').addEventListener('click', function()
|
||||
document.getElementById('viewerstate-save-response').innerHTML = "<span class='text-danger'>Viewer state could not be retrieved.</span>";
|
||||
return;
|
||||
}
|
||||
fetch("{% url 'atlas:collection_case_display_setup' case_detail.collection.pk case_detail.case.pk %}", {
|
||||
fetch("{% url 'atlas:collection_case_displaysetup' case_detail.collection.pk case_detail.case.pk %}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -46,7 +46,7 @@ document.getElementById('save-viewerstate').addEventListener('click', function()
|
||||
});
|
||||
});
|
||||
document.getElementById('reset-viewerstate').addEventListener('click', function() {
|
||||
fetch("{% url 'atlas:collection_case_display_setup' case_detail.collection.pk case_detail.case.pk %}", {
|
||||
fetch("{% url 'atlas:collection_case_displaysetup' case_detail.collection.pk case_detail.case.pk %}", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<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}}
|
||||
|
||||
(<a href="{% url 'atlas:collection_case_display_setup' casedetail.collection.pk casedetail.case.pk %}">setup default display</a>
|
||||
(<a href="{% url 'atlas:collection_case_displaysetup' casedetail.collection.pk casedetail.case.pk %}">setup default display</a>
|
||||
{% if casedetail.default_viewerstate %}
|
||||
<i class="bi bi-check text-success" title="This case has a default viewerstate defined"></i>
|
||||
{% endif %}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<form
|
||||
hx-post="{% if form.instance.pk %}{% url 'atlas:case_displaysets_edit' form.instance.pk %}{% else %}{% url 'atlas:case_displaysets_add' case.pk %}{% endif %}"
|
||||
{% if form.instance.pk %}
|
||||
hx-swap="outerHTML"
|
||||
hx-target="closest li"
|
||||
{% else %}
|
||||
hx-target="#display-set-list"
|
||||
hx-swap="beforeend"
|
||||
hx-on::after-request= "this.remove()"
|
||||
{% endif %}
|
||||
|
||||
>
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
{{ form.media }}
|
||||
<input type="hidden" name="viewerstate" id="viewerstate-input">
|
||||
<input type="hidden" name="annotations" id="annotations-input">
|
||||
<button type="submit">Save</button>
|
||||
</form>
|
||||
@@ -0,0 +1,84 @@
|
||||
<li class="list-group-item d-flex align-items-start" id="displayset-{{ ds.pk }}">
|
||||
<div class="me-3">
|
||||
{% if ds.viewerstate and ds.annotations %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary btn-lg"
|
||||
style="min-width: 100px;"
|
||||
onclick='importAnnotations_main_viewer({{ ds.annotations|safe }}); importViewerState_main_viewer({{ ds.viewerstate|safe }});'
|
||||
>Load</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<b>{{ ds.name }}</b>
|
||||
{% if ds.description %}
|
||||
<span class="text-muted">({{ ds.description }})</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-2">
|
||||
<strong>Findings:</strong>
|
||||
{% if ds.findings.all %}
|
||||
<ul>
|
||||
{% for finding in ds.findings.all %}
|
||||
<li>{{ finding }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Structures:</strong>
|
||||
{% if ds.structures.all %}
|
||||
<ul>
|
||||
{% for structure in ds.structures.all %}
|
||||
<li>{{ structure }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Conditions:</strong>
|
||||
{% if ds.conditions.all %}
|
||||
<ul>
|
||||
{% for condition in ds.conditions.all %}
|
||||
<li>{{ condition }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<details>
|
||||
<summary class="small text-muted" style="cursor:pointer;">Show advanced (viewer state & annotations)</summary>
|
||||
<div>
|
||||
<strong>Viewer State:</strong>
|
||||
<pre class="small">{{ ds.viewerstate|default:"{}" }}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Annotations:</strong>
|
||||
<pre class="small">{{ ds.annotations|default:"{}" }}</pre>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button
|
||||
hx-get="{% url 'atlas:case_displaysets_edit' ds.pk %}"
|
||||
hx-target="#displayset-edit-form-{{ ds.pk }}"
|
||||
hx-swap="innerHTML"
|
||||
class="btn btn-warning btn-sm"
|
||||
>Edit</button>
|
||||
<button
|
||||
hx-delete="{% url 'atlas:case_displaysets_delete' ds.pk %}"
|
||||
hx-target="closest li"
|
||||
hx-swap="outerHTML"
|
||||
class="btn btn-danger btn-sm"
|
||||
onclick="return confirm('Are you sure you want to delete this display set?');"
|
||||
>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="displayset-edit-form-{{ ds.pk }}"></div>
|
||||
</li>
|
||||
@@ -2,6 +2,7 @@
|
||||
<a href="{% url 'atlas:case_detail' pk=case.pk %}" title="View the Case">View</a>
|
||||
<a href="{% url 'atlas:case_update' pk=case.pk %}" title="Edit the Case">Edit</a>
|
||||
<a href="{% url 'atlas:case_series_update' pk=case.pk %}" title="Edit the Cases Series">Series</a>
|
||||
<a href="{% url 'atlas:case_displaysets' pk=case.pk %}" title="View and edit case display sets">Display Sets</a>
|
||||
<a href="{% url 'atlas:case_clone' pk=case.pk %}"
|
||||
title="Clone the Case (duplicate everything but the images)">Clone</a>
|
||||
<a href="{% url 'atlas:case_delete' pk=case.pk %}" title="Delete the Case">Delete</a>
|
||||
|
||||
Reference in New Issue
Block a user