Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
{% partialdef case-series %}
|
{% partialdef case-series %}
|
||||||
{% for series in ordered_series %}
|
{% for series in ordered_series %}
|
||||||
<article class="series-block series-block-interactive"
|
<article class="series-block series-block-interactive series-block-draggable"
|
||||||
id="series-block-{{ series.pk }}"
|
id="series-block-{{ series.pk }}"
|
||||||
data-series-id="{{series.pk}}"
|
data-series-id="{{series.pk}}"
|
||||||
data-series-label="Series {{ forloop.counter }}"
|
data-series-label="Series {{ forloop.counter }}"
|
||||||
data-series-name="{{ series|escape }}"
|
data-series-name="{{ series|escape }}"
|
||||||
|
data-image-ids='{{ series.get_image_url_array_and_count.0 }}'
|
||||||
data-series-detail-url="{% url 'atlas:series_detail' pk=series.pk %}"
|
data-series-detail-url="{% url 'atlas:series_detail' pk=series.pk %}"
|
||||||
data-series-popup-url="/atlas/series/{{series.pk}}">
|
data-series-popup-url="/atlas/series/{{series.pk}}"
|
||||||
|
draggable="true">
|
||||||
<input type="checkbox" class="hide" name="series-ids" value="{{series.pk}}">
|
<input type="checkbox" class="hide" name="series-ids" value="{{series.pk}}">
|
||||||
<div class="series-card-shell">
|
<div class="series-card-shell">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -216,6 +218,29 @@
|
|||||||
border-color: rgba(13, 110, 253, 0.3);
|
border-color: rgba(13, 110, 253, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Draggable series block */
|
||||||
|
.series-block-draggable {
|
||||||
|
cursor: grab;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.series-block-draggable:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
.series-block-draggable::after {
|
||||||
|
content: '\2807';
|
||||||
|
position: absolute;
|
||||||
|
top: 0.35rem;
|
||||||
|
right: 0.35rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: rgba(120, 150, 200, 0.45);
|
||||||
|
pointer-events: none;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.series-block-draggable.dragging {
|
||||||
|
opacity: 0.55;
|
||||||
|
box-shadow: 0 4px 16px rgba(25, 118, 210, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
.series-block-actions {
|
.series-block-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1836,6 +1861,106 @@
|
|||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// ── Series-block drag-and-drop to viewer viewports ──────────────────
|
||||||
|
(function initSeriesBlockDrag() {
|
||||||
|
if (window.__seriesBlockDragBound) return;
|
||||||
|
window.__seriesBlockDragBound = true;
|
||||||
|
|
||||||
|
function getApiKey(viewerEl) {
|
||||||
|
if (!viewerEl) return null;
|
||||||
|
return viewerEl.id ? viewerEl.id.replace(/[^a-zA-Z0-9_]/g, '_') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMainViewer() {
|
||||||
|
return document.getElementById('main_viewer') || document.querySelector('.dicom-viewer-root');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.series-block-draggable[data-image-ids]').forEach(function (block) {
|
||||||
|
block.addEventListener('dragstart', function (e) {
|
||||||
|
const rawIds = block.getAttribute('data-image-ids') || '[]';
|
||||||
|
const name = block.getAttribute('data-series-name') || block.getAttribute('data-series-label') || 'Series';
|
||||||
|
try {
|
||||||
|
// Validate it parses
|
||||||
|
JSON.parse(rawIds);
|
||||||
|
} catch (_) {
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.dataTransfer.setData('text/dv3d-imageids', rawIds);
|
||||||
|
e.dataTransfer.setData('text/dv3d-stack-name', name);
|
||||||
|
e.dataTransfer.effectAllowed = 'copy';
|
||||||
|
block.classList.add('dragging');
|
||||||
|
|
||||||
|
// Ensure viewer is open so viewports can receive the drop.
|
||||||
|
const viewerDetails = document.getElementById('dicom-viewer-details');
|
||||||
|
if (viewerDetails && !viewerDetails.hasAttribute('open')) {
|
||||||
|
viewerDetails.setAttribute('open', 'open');
|
||||||
|
viewerDetails.dispatchEvent(new Event('toggle', { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
block.addEventListener('dragend', function () {
|
||||||
|
block.classList.remove('dragging');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update View / View-in-new-pane buttons to use the direct external
|
||||||
|
// stack API when image IDs are embedded on the series block, falling
|
||||||
|
// back to the legacy state-import path when they are not.
|
||||||
|
document.querySelectorAll('.series-open-primary-btn').forEach(function (btn) {
|
||||||
|
btn._origClick = btn.onclick;
|
||||||
|
btn.addEventListener('click', async function (e) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
const seriesId = btn.getAttribute('data-series-id');
|
||||||
|
const block = document.querySelector(`.series-block-draggable[data-series-id='${seriesId}']`);
|
||||||
|
const rawIds = block ? block.getAttribute('data-image-ids') : null;
|
||||||
|
if (!rawIds) return; // fall through to legacy handler
|
||||||
|
let imageIds;
|
||||||
|
try { imageIds = JSON.parse(rawIds); } catch (_) { return; }
|
||||||
|
if (!Array.isArray(imageIds) || imageIds.length === 0) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const viewerEl = getMainViewer();
|
||||||
|
const apiKey = getApiKey(viewerEl);
|
||||||
|
const loadFn = apiKey ? window[`loadExternalStack_${apiKey}`] : null;
|
||||||
|
if (loadFn) {
|
||||||
|
const name = btn.getAttribute('data-series-name') || 'Series';
|
||||||
|
// Ensure viewer is visible first
|
||||||
|
const viewerDetails = document.getElementById('dicom-viewer-details');
|
||||||
|
if (viewerDetails && !viewerDetails.hasAttribute('open')) {
|
||||||
|
viewerDetails.setAttribute('open', 'open');
|
||||||
|
viewerDetails.dispatchEvent(new Event('toggle', { bubbles: true }));
|
||||||
|
}
|
||||||
|
try { await loadFn(imageIds, name); } catch (err) { console.error('loadExternalStack failed', err); }
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.series-open-new-viewport-btn').forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', async function (e) {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
const seriesId = btn.getAttribute('data-series-id');
|
||||||
|
const block = document.querySelector(`.series-block-draggable[data-series-id='${seriesId}']`);
|
||||||
|
const rawIds = block ? block.getAttribute('data-image-ids') : null;
|
||||||
|
if (!rawIds) return;
|
||||||
|
let imageIds;
|
||||||
|
try { imageIds = JSON.parse(rawIds); } catch (_) { return; }
|
||||||
|
if (!Array.isArray(imageIds) || imageIds.length === 0) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const viewerEl = getMainViewer();
|
||||||
|
const apiKey = getApiKey(viewerEl);
|
||||||
|
const loadFn = apiKey ? window[`loadExternalStackNewViewport_${apiKey}`] : null;
|
||||||
|
if (loadFn) {
|
||||||
|
const name = btn.getAttribute('data-series-name') || 'Series';
|
||||||
|
const viewerDetails = document.getElementById('dicom-viewer-details');
|
||||||
|
if (viewerDetails && !viewerDetails.hasAttribute('open')) {
|
||||||
|
viewerDetails.setAttribute('open', 'open');
|
||||||
|
viewerDetails.dispatchEvent(new Event('toggle', { bubbles: true }));
|
||||||
|
}
|
||||||
|
try { await loadFn(imageIds, name); } catch (err) { console.error('loadExternalStackNewViewport failed', err); }
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+175
-175
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user