Refactor DICOM viewer initialization to use safe mounting helper for improved reliability and error handling

This commit is contained in:
Ross
2026-03-23 10:46:28 +00:00
parent b17774aa7c
commit 7526f6a902
3 changed files with 116 additions and 33 deletions
+53 -21
View File
@@ -1016,7 +1016,8 @@
let dicomViewerLoaded = false;
dicomDetails.addEventListener("toggle", function() {
if (!dicomViewerLoaded && dicomDetails.open) {
window.mountDicomViewers();
// Use safe mount helper to avoid arbitrary timeouts
try { mountDicomViewersSafely(); } catch (e) { if (window.mountDicomViewers) try { window.mountDicomViewers(); } catch (er) { console.warn(er); } }
dicomViewerLoaded = true;
}
});
@@ -1162,25 +1163,23 @@
} else {
viewer_element_3d = null;
}
// We have a slight delay to allow the viewer to load
setTimeout(function () {
// Then we load the annotation and viewport
window.mountDicomViewers();
if (load_3d) {
console.log("loading 3d")
//setTimeout(function () {
// console.log("loading 3d annotation")
// importAnnotations_root(annotationjson3d)
// importViewerState_root(viewerState);
//}, 1000);
} else {
console.log("not loading 3d")
// Initialize viewer deterministically after injecting HTML
(async function(){
try {
await mountDicomViewersSafely();
if (load_3d) {
console.log("loading 3d");
// deferred 3D import can be handled here if needed
} else {
console.log("not loading 3d");
}
} catch(e){
console.warn('Viewer init error:', e);
}
}, 200);
})();
document.getElementById("reload-finding").addEventListener("click", function () {
window.mountDicomViewers();
mountDicomViewersSafely();
});
// Initialize the DICOM viewer with the images and annotations
@@ -1201,6 +1200,36 @@
const dsFullscreenModal = new bootstrap.Modal(document.getElementById("displaysetFullscreenModal"));
// Loader functions: can be called directly on reload
// Helper: wait for mount function and mount the viewers safely
function waitForMountFn(timeout = 5000) {
if (window.mountDicomViewers) return Promise.resolve(window.mountDicomViewers);
return new Promise((resolve, reject) => {
const start = Date.now();
function poll() {
if (window.mountDicomViewers) return resolve(window.mountDicomViewers);
if (Date.now() - start > timeout) return reject(new Error('mountDicomViewers not available'));
requestAnimationFrame(poll);
}
poll();
});
}
async function mountDicomViewersSafely(timeout = 5000) {
try {
// wait for layout to stabilise
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
const mountFn = await waitForMountFn(timeout);
try { mountFn(); } catch (e) { console.warn('mountDicomViewers failed', e); }
// allow any async render to settle then force a resize event
requestAnimationFrame(() => {
try { window.dispatchEvent(new Event('resize')); } catch (e) {}
if (typeof window.resizeDicomViewers === 'function') try { window.resizeDicomViewers(); } catch (e) {}
});
} catch (err) {
console.warn('Viewer initialization deferred/fallback:', err);
}
}
async function loadDisplaysetModal(url, dsId) {
if (!url && dsId) url = `/atlas/${dsId}/display_sets/modal`;
if (!url) return;
@@ -1217,7 +1246,8 @@
}
} catch (e) { console.warn(e); }
try { setUrlParam('displayset', dsId); } catch (e) {}
setTimeout(function () { try { window.mountDicomViewers(); } catch (e) { console.warn('mountDicomViewers not available', e); } }, 500);
// Initialize viewer deterministically after injecting HTML
mountDicomViewersSafely();
dsModal.show();
} catch (error) {
console.error('Error loading display set details:', error);
@@ -1249,7 +1279,8 @@
document.getElementById('displaysetFullscreenRight').innerHTML = tmp.innerHTML;
try { if (dsId) { setUrlParam('displayset', dsId); setUrlParam('displayset_full', '1'); } } catch (e) {}
dsFullscreenModal.show();
setTimeout(function () { try { window.mountDicomViewers(); } catch (e) { console.warn('mountDicomViewers not available for fullscreen', e); } }, 500);
// Ensure viewer is mounted and resized after moving into fullscreen
mountDicomViewersSafely();
} catch (error) {
console.error('Error loading fullscreen display set:', error);
document.getElementById('displaysetFullscreenLeft').innerHTML = '<p style="color:#fff; padding:1rem;">Failed to load viewer</p>';
@@ -1266,11 +1297,12 @@
const resp = await fetch(url);
const html = await resp.text();
modalBody.innerHTML = html;
setTimeout(function () { try { window.mountDicomViewers(); } catch (e) { console.warn('mountDicomViewers not available', e); } }, 200);
// Ensure viewers mount after content injection
mountDicomViewersSafely();
try { setUrlParam('finding', findingId); } catch (e) {}
const modal = new bootstrap.Modal(document.getElementById('findingModal'));
modal.show();
document.getElementById('reload-finding')?.addEventListener('click', function () { window.mountDicomViewers(); });
document.getElementById('reload-finding')?.addEventListener('click', function () { mountDicomViewersSafely(); });
} catch (e) {
console.error('Error loading finding details:', e);
modalBody.innerHTML = '<p>Failed to load finding details.</p>';
+39 -11
View File
@@ -38,20 +38,48 @@
{% block js %}
<script>
document.addEventListener("DOMContentLoaded", function () {
// Delay mounting slightly to ensure viewer assets and DOM are ready
setTimeout(function() { window.mountDicomViewers(); }, 500);
// Helper: wait for mount function and mount the viewers safely
function waitForMountFn(timeout = 5000) {
if (window.mountDicomViewers) return Promise.resolve(window.mountDicomViewers);
return new Promise((resolve, reject) => {
const start = Date.now();
function poll() {
if (window.mountDicomViewers) return resolve(window.mountDicomViewers);
if (Date.now() - start > timeout) return reject(new Error('mountDicomViewers not available'));
requestAnimationFrame(poll);
}
poll();
});
}
async function mountDicomViewersSafely(timeout = 5000) {
try {
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
const mountFn = await waitForMountFn(timeout);
try { mountFn(); } catch (e) { console.warn('mountDicomViewers failed', e); }
requestAnimationFrame(() => { try { window.dispatchEvent(new Event('resize')); } catch (e) {} });
} catch (err) {
console.warn('Viewer initialization deferred/fallback:', err);
}
}
// Initialize viewers deterministically
mountDicomViewersSafely();
{% if selected_displayset %}
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);
(async function(){
try {
await mountDicomViewersSafely();
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";
}
} catch (e) { console.warn('failed to import selected displayset state', e); }
})();
{% if edit %}
var editButton = document.getElementById("displayset-{{ selected_displayset.pk }}-edit-btn");
+24 -1
View File
@@ -91,6 +91,29 @@
function hideResourceLocal(){ const disp=document.getElementById('resourceDisplay'); const inner=document.getElementById('resourceInner'); if(disp) disp.style.display='none'; if(inner) inner.innerHTML=''; }
(function(){ const disp=document.getElementById('resourceDisplay'); if(!disp) return; disp.addEventListener('click', function(e){ const close = e.target.closest && e.target.closest('#resourceClose'); if (close) hideResourceLocal(); }); })();
// Helper to safely mount the DICOM viewers (waits for bundle and layout)
function waitForMountFn(timeout = 5000) {
if (window.mountDicomViewers) return Promise.resolve(window.mountDicomViewers);
return new Promise((resolve, reject) => {
const start = Date.now();
function poll() {
if (window.mountDicomViewers) return resolve(window.mountDicomViewers);
if (Date.now() - start > timeout) return reject(new Error('mountDicomViewers not available'));
requestAnimationFrame(poll);
}
poll();
});
}
async function mountDicomViewersSafely(timeout = 5000) {
try {
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
const mountFn = await waitForMountFn(timeout);
try { mountFn(); } catch (e) { console.warn('mountDicomViewers failed', e); }
requestAnimationFrame(() => { try { window.dispatchEvent(new Event('resize')); } catch (e) {} });
} catch (err) { console.warn('Viewer initialization deferred/fallback:', err); }
}
bc.onmessage = (event) => {
// Ensure any existing overlay is closed on new load
try { hideResourceLocal(); } catch(_) {}
@@ -182,7 +205,7 @@
delete document.getElementById("root").dataset.annotationjson;
}
window.mountDicomViewers();
mountDicomViewersSafely();
bc.postMessage({"type": "open-complete-local"});
}
};