feat(reconstruction): Add asynchronous series reconstruction task with progress tracking

This commit is contained in:
Ross
2026-05-17 11:32:48 +01:00
parent c8d05818a4
commit 7ed0a8f378
4 changed files with 421 additions and 198 deletions
+61 -1
View File
@@ -294,10 +294,19 @@
<div class="d-flex flex-wrap gap-2 mb-3">
<button id="truncate-test-modal-btn" class="btn btn-sm btn-outline-primary" type="button">Test preview</button>
<button class="btn btn-sm btn-danger" type="submit" name="operation" value="truncate">Apply truncate</button>
<button id="truncate-apply-btn" class="btn btn-sm btn-danger" type="submit" name="operation" value="truncate">Apply truncate</button>
<button class="btn btn-sm btn-outline-warning" type="submit" name="operation" value="remove_empty">Remove empty DICOMs</button>
</div>
<div id="truncate-progress" class="d-none mb-3">
<div class="small text-warning mb-1">Applying truncate. Please wait...</div>
<div class="progress" style="height: 8px;">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-warning" style="width: 100%" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</div>
<div class="small text-muted mb-3">Truncate is a dedicated step. Downsample and reconstruction always run on the full active series.</div>
<hr>
<div class="mb-3">
@@ -443,6 +452,19 @@
endHidden.value = endInput.value;
}
function updateBoundsInputs(startValue, endValue) {
const startInput = document.getElementById('truncate-lower-input');
const endInput = document.getElementById('truncate-upper-input');
if (!startInput || !endInput) {
return;
}
startInput.value = String(startValue);
endInput.value = String(endValue);
startInput.max = String(endValue);
endInput.max = String(endValue);
syncOptimizeBounds();
}
function getTruncateViewerElement() {
return document.getElementById(truncateApiKey);
}
@@ -559,8 +581,46 @@
syncOptimizeBounds();
});
document.body.addEventListener('htmx:beforeRequest', function (event) {
const form = document.getElementById('series-optimize-form');
if (!form || event.target !== form) {
return;
}
const op = event.detail?.parameters?.operation || '';
const truncateProgress = document.getElementById('truncate-progress');
const truncateApplyBtn = document.getElementById('truncate-apply-btn');
if (op === 'truncate') {
truncateProgress?.classList.remove('d-none');
if (truncateApplyBtn) {
truncateApplyBtn.disabled = true;
}
}
});
document.body.addEventListener('htmx:afterRequest', function (event) {
const form = document.getElementById('series-optimize-form');
if (!form || event.target !== form) {
return;
}
const truncateProgress = document.getElementById('truncate-progress');
const truncateApplyBtn = document.getElementById('truncate-apply-btn');
truncateProgress?.classList.add('d-none');
if (truncateApplyBtn) {
truncateApplyBtn.disabled = false;
}
});
document.body.addEventListener('htmx:afterSwap', function (event) {
if (event.target && event.target.id === 'series-optimize-feedback') {
const truncatePayload = document.getElementById('truncate-range-payload');
if (truncatePayload) {
const start = parseInt(truncatePayload.dataset.start || '1', 10);
const end = parseInt(truncatePayload.dataset.end || '1', 10);
if (Number.isFinite(start) && Number.isFinite(end) && end >= start) {
updateBoundsInputs(start, end);
}
}
const payload = document.getElementById('downsample-compare-payload');
if (!payload) {
return;