Add series images endpoint and template for HTMX integration

This commit is contained in:
Ross
2025-12-01 14:50:11 +00:00
parent e9a04e823c
commit 590e0b76ed
5 changed files with 378 additions and 309 deletions
@@ -0,0 +1,31 @@
{% load static %}
<div class="series-images-list">
{% for image in images %}
<div class="d-flex align-items-center mb-2">
{% if image.image %}
<a href="{% url 'atlas:series_image_dicom' pk=image.pk %}" target="_blank" class="me-2">
<img src="{{ image.image.url }}" alt="image-{{ image.pk }}" style="width:96px;height:auto;object-fit:cover;border-radius:.25rem;" class="img-thumbnail">
</a>
{% else %}
<div class="me-2 bg-secondary" style="width:96px;height:72px;border-radius:.25rem;"></div>
{% endif %}
<div class="flex-grow-1">
<div><strong>{{ image.image.name|default:"(no file)" }}</strong></div>
{% if image.image %}
<div class="small text-muted">{{ image.image.size|filesizeformat }}</div>
{% endif %}
{% if image.image_blake3_hash %}
<div class="small text-muted">hash: {{ image.image_blake3_hash|truncatechars:12 }}</div>
{% endif %}
</div>
<div class="ms-auto text-end">
<a href="{% url 'atlas:series_image_dicom' pk=image.pk %}" class="btn btn-sm btn-outline-secondary">View DICOM</a>
</div>
</div>
{% empty %}
<p class="text-muted">No images found for this series.</p>
{% endfor %}
</div>
+332 -308
View File
@@ -1,337 +1,361 @@
{% load crispy_forms_tags %}
{% with image_url_array_and_count=series.get_image_url_array_and_count %}
<div class="container my-4">
<div class="row">
<div class="col-12 mb-3">
<div class="card shadow-sm">
<div class="card-body d-flex flex-column flex-md-row justify-content-between align-items-start gap-3">
<div>
<h4 class="card-title mb-1">Series: {{ series.modality }} - {{ series.examination }}</h4>
<div class="text-muted small">{{ series.plane }}, {{ series.contrast }}</div>
<div class="mt-2">Description: <span class="text-break">{{ series.description }}</span></div>
<div class="mt-2 text-muted">Author: {{ series.get_author_display }}</div>
<div class="container my-4">
<div class="row">
<div class="col-12 mb-3">
<div class="card shadow-sm">
<div class="card-body d-flex flex-column flex-md-row justify-content-between align-items-start gap-3">
<div>
<h4 class="card-title mb-1">Series: {{ series.modality }} - {{ series.examination }}</h4>
<div class="text-muted small">{{ series.plane }}, {{ series.contrast }}</div>
<div class="mt-2">Description: <span class="text-break">{{ series.description }}</span></div>
<div class="mt-2 text-muted">Author: {{ series.get_author_display }}</div>
</div>
<div class="ms-auto d-flex gap-2">
<a class="btn btn-outline-secondary btn-sm" href="{% url 'atlas:series_detail' pk=series.pk %}">View series</a>
{% if can_edit %}
<button id="reset-viewport-button" class="btn btn-secondary btn-sm">Reset viewport</button>
{% else %}
<button id="reset-viewport-button" class="btn btn-secondary btn-sm">Reset viewport</button>
{% endif %}
</div>
</div>
<div class="ms-auto d-flex gap-2">
<a class="btn btn-outline-secondary btn-sm" href="{% url 'atlas:series_detail' pk=series.pk %}">View series</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 mb-3">
<div class="card">
<div class="card-body p-2">
{% with image_url_array_and_count=series.get_image_url_array_and_count %}
<div id="root" class="dicom-viewer-root w-100" data-images="{{ image_url_array_and_count.0 }}"
style="height: 600px; box-sizing: border-box; background: #222;" data-auto-cache-stack=false>
</div>
{% endwith %}
</div>
<div class="card-footer bg-white d-flex flex-wrap gap-2 align-items-center">
{% if can_edit and editing_finding < 1 %}
<button id="add-finding-button" class="btn btn-primary btn-sm">Add finding</button>
<button id="clone-finding-button" class="btn btn-outline-primary btn-sm"
title="Clone an existing finding"
hx-get="{% url 'atlas:series_finding_related' series.pk %}"
hx-target="#clone-findings-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#clone-findings-modal">Clone finding</button>
{% endif %}
<div class="ms-auto text-muted small">{{ series.get_image_count }} images</div>
</div>
</div>
<div class="mt-3">
<!-- Finding form moved below the viewport -->
<div class="card mt-3">
<div class="card-header">Finding form</div>
<div class="card-body">
<div id="finding-form">
<div class="hide" id="hidden-form">
{% if editing_finding > 0 %}
<h5>Editing Finding</h5>
<p class="small">Editing finding with ID: {{editing_finding}}</p>
{% else %}
<h5>Add Finding</h5>
<p class="small">Click the button below to add a new finding.</p>
{% endif %}
<form method="post" id="series_finding_form">
{% csrf_token %}
{{series_finding_form|crispy}}
<div class="d-flex gap-2 mt-2">
<input type="submit" value="Submit" class="btn btn-success btn-sm">
<button id="cancel-add-finding-button" class="btn btn-secondary btn-sm">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
<details class="styled-detail open">
<summary>Findings</summary>
<div class="mt-2">
{% for finding in series.findings.all %}
<div class="card mb-2">
<div class="card-body d-flex flex-column flex-md-row justify-content-between gap-2">
<div>
<button id="finding-{{finding.pk}}-3d" class="btn btn-sm btn-outline-secondary view-finding-button-3d me-2"
data-annotationjson3d='{{finding.annotation_json_3d}}' data-viewerstatejson='{{finding.viewer_state_3d}}' data-findingid='{{finding.pk}}'>View 3D</button>
<strong>Finding(s):</strong>
{% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
<strong>Structure(s):</strong> {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
<strong>Condition(s):</strong> {% for s in finding.conditions.all %}{{s.get_link}}{% endfor %}<br />
<div class="mt-2">{{finding.description}}</div>
</div>
<div class="text-end">
{% if request.user.is_superuser %}
<button class="btn btn-sm btn-outline-dark mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#extra-{{finding.pk}}">Raw JSON</button>
<div class="collapse" id="extra-{{finding.pk}}">
<pre class="small bg-light p-2">{{finding.annotation_json}}</pre>
<pre class="small bg-light p-2">{{finding.viewport_json}}</pre>
</div>
{% endif %}
{% if can_edit %}
<div class="d-flex flex-column">
<a href="{% url 'atlas:series_edit_finding' pk=series.pk finding_pk=finding.pk %}" class="btn btn-sm btn-outline-primary mb-1">Edit</a>
<a href="{% url 'atlas:delete_finding' pk=finding.pk %}" class="btn btn-sm btn-outline-danger">Delete</a>
</div>
{% endif %}
</div>
</div>
</div>
{% empty %}
<p class="text-muted">No findings available.</p>
{% endfor %}
</div>
</details>
</div>
<!-- Images (loaded on demand via HTMX) -->
<div class="card mt-3">
<div class="card-header d-flex justify-content-between align-items-center">
<span>Images</span>
<div>
<button id="load-images-button" class="btn btn-sm btn-outline-primary"
hx-get="{% url 'atlas:series_images' series.pk %}"
hx-target="#series-images-container"
hx-swap="innerHTML"
hx-trigger="click"
hx-on="htmx:beforeRequest: document.getElementById('images-loading-indicator').style.display='inline-block'; htmx:afterSwap: document.getElementById('images-loading-indicator').style.display='none'; document.getElementById('load-images-button').disabled = true">
Load images
</button>
<a href="{% url 'atlas:series_download' series.pk %}" class="btn btn-sm btn-outline-success ms-2" target="_blank" rel="noopener">Download</a>
<span id="images-loading-indicator" class="spinner-border spinner-border-sm ms-2" role="status" aria-hidden="true" style="display:none"></span>
</div>
</div>
<div class="card-body">
<div id="series-images-container">
<p class="text-muted small">Click 'Load images' to fetch.</p>
</div>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card mb-3">
<div class="card-header">Cases</div>
<div class="card-body">
<div id="cases-list">
{% include 'atlas/partials/series_cases_list.html' %}
</div>
{% if can_edit %}
<button id="reset-viewport-button" class="btn btn-secondary btn-sm">Reset viewport</button>
{% else %}
<button id="reset-viewport-button" class="btn btn-secondary btn-sm">Reset viewport</button>
<details id="add-series-to-case-details" class="mt-2">
<summary>Add Series to Case</summary>
<form hx-post="{% url 'atlas:add_series_to_case' series.pk %}" hx-target="#cases-list" hx-swap="innerHTML" class="mt-2">
{% csrf_token %}
<div class="mb-2">
<label for="case-select" class="form-label">Select Case</label>
<select name="case_id" id="case-select" class="form-select" required>
{% for case in available_cases %}
<option value="{{ case.pk }}">{{ case.title }}</option>
{% endfor %}
</select>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm">Add to Case</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="document.getElementById('add-series-to-case-details').open = false">Close</button>
</div>
</form>
</details>
{% endif %}
</div>
</div>
<div class="card mb-3">
<div class="card-header">Actions</div>
<div class="card-body d-flex flex-column gap-2">
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}">Anonymise dicoms</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom' pk=series.pk %}">Order by slice location</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}">Order by instance number</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}">Order by SeriesInstanceUID</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}">Order by uploaded filename</a>
<a class="btn btn-outline-warning btn-sm" href="{% url 'api-1:series_split_by_tag' series.pk 'ImageType' %}">Split by ImageType tag</a>
</div>
</div>
{% if can_edit %}
<div class="card">
<div class="card-header">Truncate series</div>
<div class="card-body">
<p class="small">Limit the series to the selected bounds (the rest of the images will be deleted). This action is irreversible on site.</p>
<div class="alert alert-warning"><strong>Warning:</strong> If you have reordered the series this may remove the wrong images.</div>
<div class="mb-2">
<label class="form-label">Start</label>
<div class="input-group">
<input id="lower-truncation-bound-input" type="number" class="form-control form-control-sm" value="1">
<button id="set-lower-truncation-bound-button" class="btn btn-outline-secondary btn-sm">Set</button>
</div>
</div>
<div class="mb-2">
<label class="form-label">End</label>
<div class="input-group">
<input id="upper-truncation-bound-input" type="number" class="form-control form-control-sm" value="{{image_url_array_and_count.1}}">
<button id="set-upper-truncation-bound-button" class="btn btn-outline-secondary btn-sm">Set</button>
</div>
</div>
<div class="d-flex gap-2">
<button id="truncate-test-button" class="btn btn-sm btn-outline-primary">Test truncate</button>
<button id="truncate-button" class="btn btn-sm btn-danger">Truncate series</button>
</div>
<div id="truncate-output" class="mt-2"></div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-md-8 mb-3">
<div class="card">
<div class="card-body p-2">
{% with image_url_array_and_count=series.get_image_url_array_and_count %}
<div id="root" class="dicom-viewer-root w-100" data-images="{{ image_url_array_and_count.0 }}"
style="height: 600px; box-sizing: border-box; background: #222;" data-auto-cache-stack=false>
</div>
{% endwith %}
</div>
<div class="card-footer bg-white d-flex flex-wrap gap-2 align-items-center">
{% if can_edit and editing_finding < 1 %}
<button id="add-finding-button" class="btn btn-primary btn-sm">Add finding</button>
<button id="clone-finding-button" class="btn btn-outline-primary btn-sm"
title="Clone an existing finding"
hx-get="{% url 'atlas:series_finding_related' series.pk %}"
hx-target="#clone-findings-modal"
hx-trigger="click"
data-bs-toggle="modal"
data-bs-target="#clone-findings-modal">Clone finding</button>
{% endif %}
<div class="ms-auto text-muted small">{{ series.get_image_count }} images</div>
</div>
</div>
<div class="mt-3">
<!-- Finding form moved below the viewport -->
<div class="card mt-3">
<div class="card-header">Finding form</div>
<div class="card-body">
<div id="finding-form">
<div class="hide" id="hidden-form">
{% if editing_finding > 0 %}
<h5>Editing Finding</h5>
<p class="small">Editing finding with ID: {{editing_finding}}</p>
{% else %}
<h5>Add Finding</h5>
<p class="small">Click the button below to add a new finding.</p>
{% endif %}
<form method="post" id="series_finding_form">
{% csrf_token %}
{{series_finding_form|crispy}}
<div class="d-flex gap-2 mt-2">
<input type="submit" value="Submit" class="btn btn-success btn-sm">
<button id="cancel-add-finding-button" class="btn btn-secondary btn-sm">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
<details class="styled-detail open">
<summary>Findings</summary>
<div class="mt-2">
{% for finding in series.findings.all %}
<div class="card mb-2">
<div class="card-body d-flex flex-column flex-md-row justify-content-between gap-2">
<div>
<button id="finding-{{finding.pk}}-3d" class="btn btn-sm btn-outline-secondary view-finding-button-3d me-2"
data-annotationjson3d='{{finding.annotation_json_3d}}' data-viewerstatejson='{{finding.viewer_state_3d}}' data-findingid='{{finding.pk}}'>View 3D</button>
<strong>Finding(s):</strong>
{% for f in finding.findings.all %}{{f.get_link}}{% endfor %}<br />
<strong>Structure(s):</strong> {% for s in finding.structures.all %}{{s.get_link}}{% endfor %}<br />
<strong>Condition(s):</strong> {% for s in finding.conditions.all %}{{s.get_link}}{% endfor %}<br />
<div class="mt-2">{{finding.description}}</div>
</div>
<div class="text-end">
{% if request.user.is_superuser %}
<button class="btn btn-sm btn-outline-dark mb-2" type="button" data-bs-toggle="collapse" data-bs-target="#extra-{{finding.pk}}">Raw JSON</button>
<div class="collapse" id="extra-{{finding.pk}}">
<pre class="small bg-light p-2">{{finding.annotation_json}}</pre>
<pre class="small bg-light p-2">{{finding.viewport_json}}</pre>
</div>
{% endif %}
{% if can_edit %}
<div class="d-flex flex-column">
<a href="{% url 'atlas:series_edit_finding' pk=series.pk finding_pk=finding.pk %}" class="btn btn-sm btn-outline-primary mb-1">Edit</a>
<a href="{% url 'atlas:delete_finding' pk=finding.pk %}" class="btn btn-sm btn-outline-danger">Delete</a>
</div>
{% endif %}
</div>
</div>
</div>
{% empty %}
<p class="text-muted">No findings available.</p>
{% endfor %}
</div>
</details>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card mb-3">
<div class="card-header">Cases</div>
<div class="card-body">
<div id="cases-list">
{% include 'atlas/partials/series_cases_list.html' %}
</div>
{% if can_edit %}
<details id="add-series-to-case-details" class="mt-2">
<summary>Add Series to Case</summary>
<form hx-post="{% url 'atlas:add_series_to_case' series.pk %}" hx-target="#cases-list" hx-swap="innerHTML" class="mt-2">
{% csrf_token %}
<div class="mb-2">
<label for="case-select" class="form-label">Select Case</label>
<select name="case_id" id="case-select" class="form-select" required>
{% for case in available_cases %}
<option value="{{ case.pk }}">{{ case.title }}</option>
{% endfor %}
</select>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary btn-sm">Add to Case</button>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="document.getElementById('add-series-to-case-details').open = false">Close</button>
</div>
</form>
</details>
{% endif %}
</div>
</div>
<div class="card mb-3">
<div class="card-header">Actions</div>
<div class="card-body d-flex flex-column gap-2">
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_anonymise_dicom' pk=series.pk %}">Anonymise dicoms</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom' pk=series.pk %}">Order by slice location</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom_instance' pk=series.pk %}">Order by instance number</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_dicom_SeriesInstanceUID' pk=series.pk %}">Order by SeriesInstanceUID</a>
<a class="btn btn-outline-primary btn-sm" href="{% url 'atlas:series_order_upload_filename' pk=series.pk %}">Order by uploaded filename</a>
<a class="btn btn-outline-warning btn-sm" href="{% url 'api-1:series_split_by_tag' series.pk 'ImageType' %}">Split by ImageType tag</a>
</div>
</div>
{% if can_edit %}
<div class="card">
<div class="card-header">Truncate series</div>
<div class="card-body">
<p class="small">Limit the series to the selected bounds (the rest of the images will be deleted). This action is irreversible on site.</p>
<div class="alert alert-warning"><strong>Warning:</strong> If you have reordered the series this may remove the wrong images.</div>
<div class="mb-2">
<label class="form-label">Start</label>
<div class="input-group">
<input id="lower-truncation-bound-input" type="number" class="form-control form-control-sm" value="1">
<button id="set-lower-truncation-bound-button" class="btn btn-outline-secondary btn-sm">Set</button>
</div>
</div>
<div class="mb-2">
<label class="form-label">End</label>
<div class="input-group">
<input id="upper-truncation-bound-input" type="number" class="form-control form-control-sm" value="{{image_url_array_and_count.1}}">
<button id="set-upper-truncation-bound-button" class="btn btn-outline-secondary btn-sm">Set</button>
</div>
</div>
<div class="d-flex gap-2">
<button id="truncate-test-button" class="btn btn-sm btn-outline-primary">Test truncate</button>
<button id="truncate-button" class="btn btn-sm btn-danger">Truncate series</button>
</div>
<div id="truncate-output" class="mt-2"></div>
</div>
</div>
{% endif %}
<div id="clone-findings-modal" class="modal modal-blur fade" tabindex="-1">
<div class="modal-dialog modal-dialog-centered related-finding-modal" style="max-width: 700px;">
<div class="modal-content"></div>
</div>
</div>
</div>
<div id="clone-findings-modal" class="modal modal-blur fade" tabindex="-1">
<div class="modal-dialog modal-dialog-centered related-finding-modal" style="max-width: 700px;">
<div class="modal-content"></div>
</div>
</div>
<script>
$(document).ready(function () {
const apiKey = "root"
$("#set-lower-truncation-bound-button").click(() => {
$("#lower-truncation-bound-input").val(getCurrentStackPosition_root(0) + 1);
});
$("#set-upper-truncation-bound-button").click(() => {
$("#upper-truncation-bound-input").val(getCurrentStackPosition_root(0) + 1);
});
$("#truncate-test-button").click(() => {
const lower = parseInt($("#lower-truncation-bound-input").val()) - 1;
const upper = parseInt($("#upper-truncation-bound-input").val()) - 1;
const ok = window[`truncateStack_${apiKey}`](lower, upper);
if (ok) {
$("#truncate-output").text("The truncated images are shown above.");
}
});
$("#truncate-button").click(() => {
lower_bound = parseInt($("#lower-truncation-bound-input").val()) - 1;
upper_bound = parseInt($("#upper-truncation-bound-input").val()) - 1;
if (lower_bound >= upper_bound) {
alert("The lower bound must be less than the upper bound.");
return;
}
if(confirm(`Trucated series. This will leave ${upper_bound - lower_bound + 1} images.`) == true) {
htmx.ajax("GET", `{% url 'api-1:series_truncate' series.id '****' '++++' %}`.replace("****", lower_bound).replace("++++", upper_bound), "#truncate-output")
}
<script>
$(document).ready(function () {
const apiKey = "root"
})
$("#set-lower-truncation-bound-button").click(() => {
$("#lower-truncation-bound-input").val(getCurrentStackPosition_root(0) + 1);
});
$("#set-upper-truncation-bound-button").click(() => {
$("#upper-truncation-bound-input").val(getCurrentStackPosition_root(0) + 1);
});
$("#truncate-test-button").click(() => {
const lower = parseInt($("#lower-truncation-bound-input").val()) - 1;
const upper = parseInt($("#upper-truncation-bound-input").val()) - 1;
const ok = window[`truncateStack_${apiKey}`](lower, upper);
if (ok) {
$("#truncate-output").text("The truncated images are shown above.");
}
});
$("#truncate-button").click(() => {
lower_bound = parseInt($("#lower-truncation-bound-input").val()) - 1;
upper_bound = parseInt($("#upper-truncation-bound-input").val()) - 1;
if (lower_bound >= upper_bound) {
alert("The lower bound must be less than the upper bound.");
return;
}
if(confirm(`Trucated series. This will leave ${upper_bound - lower_bound + 1} images.`) == true) {
htmx.ajax("GET", `{% url 'api-1:series_truncate' series.id '****' '++++' %}`.replace("****", lower_bound).replace("++++", upper_bound), "#truncate-output")
}
})
$("#add-finding-button").click(() => {
$("#hidden-form").show()
$("#add-finding-button").click(() => {
$("#hidden-form").show()
//dicom_element = $(".cornerstone-element").get(0);
//cornerstoneTools.globalImageIdSpecificToolStateManager.clear(dicom_element);
//cornerstone.reset(dicom_element);
////cornerstone.resize(dicom_element, true);
$("#add-finding-button").hide()
$("#clone-finding-button").hide()
$("#add-finding-button").hide()
$("#clone-finding-button").hide()
//$("#finding-form").empty().append(
// $("#hidden-form form").clone()
//);
});
$("#cancel-add-finding-button").click((e) => {
$("#hidden-form").hide()
$("#add-finding-button").show()
$("#clone-finding-button").show()
e.preventDefault();
});
$("#reset-viewport-button").click(() => {
resetViewer_root();
});
$(".view-finding-button-3d").each((n, el) => {
$(el).click((e) => {
dicom_element = $(".cornerstone-element").get(0);
annotationjson3d = JSON.parse(e.currentTarget.dataset.annotationjson3d);
viewer_state_json = JSON.parse(e.currentTarget.dataset.viewerstatejson);
console.log("Loading 3D annotation and viewer state", annotationjson3d, viewer_state_json);
importAnnotations_root(annotationjson3d);
importViewerState_root(viewer_state_json);
$(el).addClass("active");
});
});
{% if editing_finding > 0 %}
$("#hidden-form").show()
setTimeout(function() {
$(".view-finding-button-3d[data-findingid={{editing_finding}}]").trigger("click");
}, 1000)
{% endif %}
{% if request.GET.show_finding %}
function show_finding() {
console.log("show finding")
$("#finding-{{ request.GET.show_finding }}").trigger("click");
}
setTimeout(show_finding, 500);
{% endif %}
});
$("#cancel-add-finding-button").click((e) => {
$("#hidden-form").hide()
$("#add-finding-button").show()
$("#clone-finding-button").show()
$(document).on('submit', '#series_finding_form', function (e) {
e.preventDefault();
});
$("#reset-viewport-button").click(() => {
resetViewer_root();
});
$(".view-finding-button-3d").each((n, el) => {
$(el).click((e) => {
dicom_element = $(".cornerstone-element").get(0);
annotationjson3d = JSON.parse(e.currentTarget.dataset.annotationjson3d);
viewer_state_json = JSON.parse(e.currentTarget.dataset.viewerstatejson);
console.log("Loading 3D annotation and viewer state", annotationjson3d, viewer_state_json);
importAnnotations_root(annotationjson3d);
importViewerState_root(viewer_state_json);
$(el).addClass("active");
$.ajax({
type: 'POST',
url: '{% url "atlas:add_finding" %}',
data: {
series_finding_id: {{ editing_finding }},
description: $('#id_description').val(),
series: {{ series.pk }},
annotation_json_3d: window.exportAnnotations_root(),
viewer_state_3d: window.exportViewerState_root(),
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
conditions: JSON.stringify($('#finding-form select[name="conditions"]').find(
":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'post'
},
success: function (json) {
$("#finding-form").empty();
toastr.info('Finding added.');
location.href='{{series.get_absolute_url}}';
},
error: function (xhr, errmsg, err) {
console.log(xhr.status + ": " + xhr
.responseText); // provide a bit more info about the error to the console
}
});
});
{% if editing_finding > 0 %}
$("#hidden-form").show()
setTimeout(function() {
$(".view-finding-button-3d[data-findingid={{editing_finding}}]").trigger("click");
}, 1000)
{% endif %}
{% if request.GET.show_finding %}
function show_finding() {
console.log("show finding")
$("#finding-{{ request.GET.show_finding }}").trigger("click");
}
setTimeout(show_finding, 500);
{% endif %}
});
$(document).on('submit', '#series_finding_form', function (e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: '{% url "atlas:add_finding" %}',
data: {
series_finding_id: {{ editing_finding }},
description: $('#id_description').val(),
series: {{ series.pk }},
annotation_json_3d: window.exportAnnotations_root(),
viewer_state_3d: window.exportViewerState_root(),
findings: JSON.stringify($('#finding-form select[name="findings"]').find(":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
structures: JSON.stringify($('#finding-form select[name="structures"]').find(
":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
conditions: JSON.stringify($('#finding-form select[name="conditions"]').find(
":selected")
.map((i, el) => {
return $(el).val()
}).toArray()),
csrfmiddlewaretoken: $('input[name=csrfmiddlewaretoken]').val(),
action: 'post'
},
success: function (json) {
$("#finding-form").empty();
toastr.info('Finding added.');
location.href='{{series.get_absolute_url}}';
},
error: function (xhr, errmsg, err) {
console.log(xhr.status + ": " + xhr
.responseText); // provide a bit more info about the error to the console
}
});
});
</script>
</script>
{% endwith %}
@@ -402,14 +426,14 @@
transition: background 0.2s, font-size 0.2s;
}
details.styled-detail:not([open]) {
padding-bottom: 0;
min-height: 0;
}
details.styled-detail:not([open]) {
padding-bottom: 0;
min-height: 0;
}
details.styled-detail:not([open]) > *:not(summary) {
display: none;
}
details.styled-detail:not([open]) > *:not(summary) {
display: none;
}
details.styled-detail:not([open]) > summary {
padding: 0.7em 1.2em 0.3em 1.2em;