Add directory loading support and enhance file handling in the viewer
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Load Directory Test</title>
|
||||
<style>
|
||||
body { font-family: system-ui, Arial; padding: 18px; background: #f6f6f8; color: #111 }
|
||||
.controls { margin: 12px 0 }
|
||||
.file-row { display:flex; gap:8px; align-items:center; padding:6px 8px; border-bottom:1px solid #eee }
|
||||
.path { font-family: monospace; color:#444; flex:1 }
|
||||
button { padding:6px 10px }
|
||||
#fileList { border:1px solid #ddd; background:#fff; border-radius:6px; max-height:60vh; overflow:auto }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Directory Loader (recursive)</h2>
|
||||
<p>Select a folder (uses `webkitdirectory`) — files will be read recursively.
|
||||
This page will call <code>window.loadDicomStackFromFiles(files)</code> if available in the viewer.</p>
|
||||
|
||||
<div class="controls">
|
||||
<input id="dirInput" type="file" webkitdirectory directory multiple />
|
||||
<label style="margin-left:12px"><input id="onlyDcm" type="checkbox" checked /> Show only <code>.dcm</code></label>
|
||||
<button id="loadSelected">Load Selected</button>
|
||||
<button id="loadAll">Load All</button>
|
||||
<button id="openViewerAndSend">Open Viewer & Send</button>
|
||||
<button id="clear">Clear</button>
|
||||
</div>
|
||||
|
||||
<div id="fileList"></div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
const input = document.getElementById('dirInput');
|
||||
const fileListDiv = document.getElementById('fileList');
|
||||
const onlyDcmChk = document.getElementById('onlyDcm');
|
||||
|
||||
function asArray(fileList){ return Array.prototype.slice.call(fileList || []); }
|
||||
|
||||
function renderFiles(files){
|
||||
fileListDiv.innerHTML = '';
|
||||
if (!files || files.length === 0) {
|
||||
fileListDiv.innerHTML = '<div style="padding:12px;color:#666">No files selected</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const frag = document.createDocumentFragment();
|
||||
files.forEach((f, idx) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'file-row';
|
||||
const cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.checked = true;
|
||||
cb.dataset.idx = idx;
|
||||
|
||||
const pathEl = document.createElement('div');
|
||||
pathEl.className = 'path';
|
||||
// webkitRelativePath shows the folder structure when directory input used
|
||||
pathEl.textContent = f.webkitRelativePath || f.name;
|
||||
|
||||
const sizeEl = document.createElement('div');
|
||||
sizeEl.style.width = '90px';
|
||||
sizeEl.style.textAlign = 'right';
|
||||
sizeEl.style.color = '#666';
|
||||
sizeEl.textContent = (f.size/1024).toFixed(1)+' kB';
|
||||
|
||||
row.appendChild(cb);
|
||||
row.appendChild(pathEl);
|
||||
row.appendChild(sizeEl);
|
||||
|
||||
frag.appendChild(row);
|
||||
});
|
||||
fileListDiv.appendChild(frag);
|
||||
}
|
||||
|
||||
function filterFiles(files){
|
||||
const onlyDcm = onlyDcmChk.checked;
|
||||
if (!onlyDcm) return files;
|
||||
return files.filter(f => f.name.toLowerCase().endsWith('.dcm'));
|
||||
}
|
||||
|
||||
input.addEventListener('change', ()=>{
|
||||
const all = asArray(input.files);
|
||||
const filtered = filterFiles(all);
|
||||
// Save filtered list on the element for retrieval
|
||||
input._currentFiles = filtered;
|
||||
renderFiles(filtered);
|
||||
});
|
||||
|
||||
document.getElementById('loadSelected').addEventListener('click', ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
// collect checked indices
|
||||
const checked = Array.from(fileListDiv.querySelectorAll('input[type=checkbox]'))
|
||||
.filter(cb=>cb.checked)
|
||||
.map(cb=>parseInt(cb.dataset.idx,10))
|
||||
.filter(n=>!Number.isNaN(n));
|
||||
const chosen = checked.map(i=>current[i]).filter(Boolean);
|
||||
if (!chosen.length) return alert('No files checked.');
|
||||
sendToViewer(chosen);
|
||||
});
|
||||
|
||||
document.getElementById('loadAll').addEventListener('click', ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
sendToViewer(current);
|
||||
});
|
||||
|
||||
document.getElementById('openViewerAndSend').addEventListener('click', async ()=>{
|
||||
const current = input._currentFiles || [];
|
||||
if (!current.length) return alert('No files selected. Choose a folder first.');
|
||||
// Open the viewer root in a new tab/window
|
||||
const viewerWin = window.open('/', '_blank');
|
||||
if (!viewerWin) return alert('Failed to open viewer window (popup blocked).');
|
||||
|
||||
// Wait for the viewer window to expose loadDicomStackFromFiles
|
||||
const timeoutMs = 15000;
|
||||
const start = Date.now();
|
||||
const poll = () => new Promise((resolve, reject) => {
|
||||
const iv = setInterval(() => {
|
||||
try {
|
||||
if (viewerWin && (viewerWin.loadDicomStackFromFiles || viewerWin.loadDicomStackFromWadouriList)) {
|
||||
clearInterval(iv);
|
||||
resolve(true);
|
||||
}
|
||||
if (Date.now() - start > timeoutMs) {
|
||||
clearInterval(iv);
|
||||
reject(new Error('Timed out waiting for viewer API'));
|
||||
}
|
||||
} catch (e) {
|
||||
// cross-origin or not ready
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
|
||||
try {
|
||||
await poll();
|
||||
// call the function on the opened viewer window
|
||||
if (viewerWin.loadDicomStackFromFiles) {
|
||||
viewerWin.loadDicomStackFromFiles(current);
|
||||
alert('Sent '+current.length+' file(s) to the newly opened viewer.');
|
||||
} else if (viewerWin.loadDicomStackFromWadouriList) {
|
||||
// fallback for remote URLs
|
||||
const wadouriList = current.map(f => 'wadouri:' + URL.createObjectURL(f));
|
||||
viewerWin.loadDicomStackFromWadouriList(wadouriList);
|
||||
alert('Sent '+current.length+' wadouri image(s) to the newly opened viewer.');
|
||||
} else {
|
||||
alert('Viewer opened but API not available.');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('openViewerAndSend failed', e);
|
||||
alert('Timed out waiting for viewer to be ready. Check the viewer tab.');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('clear').addEventListener('click', ()=>{
|
||||
input.value = null;
|
||||
input._currentFiles = [];
|
||||
renderFiles([]);
|
||||
});
|
||||
|
||||
function sendToViewer(files){
|
||||
// Prefer calling the viewer global if present
|
||||
if (window.loadDicomStackFromFiles && typeof window.loadDicomStackFromFiles === 'function') {
|
||||
try {
|
||||
// The viewer accepts FileList or File[] per typing; pass array
|
||||
window.loadDicomStackFromFiles(files);
|
||||
alert('Sent '+files.length+' file(s) to viewer (window.loadDicomStackFromFiles).');
|
||||
return;
|
||||
} catch (e) {
|
||||
console.error('loadDicomStackFromFiles failed', e);
|
||||
alert('Viewer call failed (see console).');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try posting to parent (if embedded)
|
||||
if (window.parent && window.parent !== window) {
|
||||
try {
|
||||
window.parent.postMessage({ type: 'loadFiles', filesCount: files.length }, '*');
|
||||
alert('Posted message to parent with '+files.length+' files.');
|
||||
return;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
alert('Viewer integration function `window.loadDicomStackFromFiles` not found on this page.');
|
||||
}
|
||||
|
||||
// Initial empty render
|
||||
renderFiles([]);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user