From 1baa8a1b927fbc792f642435f64710efeb1e0f5f Mon Sep 17 00:00:00 2001
From: Taylor Hanayik <hanayik@gmail.com>
Date: Wed, 2 Oct 2024 23:35:17 +0100
Subject: [PATCH] allow drag and drop using new dcm2niix build

---
 index.html        | 16 ++++++----
 main.js           | 77 ++++++++++++++++++++++++++++++++++++++++++++++-
 niivue.css        | 14 ++++++++-
 package-lock.json |  8 ++---
 package.json      |  4 +--
 5 files changed, 105 insertions(+), 14 deletions(-)

diff --git a/index.html b/index.html
index ff800bf..dc7c97b 100644
--- a/index.html
+++ b/index.html
@@ -10,14 +10,18 @@
 
 <body>
   <header id="header">
+    <div id="dropTarget" class="drop-target">
+      <p>Drop a folder of DICOM files here</p>
+      <p>Or use the file input button below</p>
+    </div>
     <!-- ordered list for instructions -->
     <ol id="instructions">
       <li>
-      <!-- directory input -->
-      <label for="fileInput" id="fileInputLabel" class="file-input-label">
-        Choose a folder of DICOM files
-      </label>
-      <input type="file" id="fileInput" webkitdirectory multiple />
+        <!-- directory input -->
+        <label for="fileInput" id="fileInputLabel" class="file-input-label">
+          Choose a folder of DICOM files
+        </label>
+        <input type="file" id="fileInput" webkitdirectory multiple />
       </li>
       <li>
         <!-- file select for converted files returned from dcm2niix -->
@@ -26,7 +30,7 @@
       </li>
       <li>
         <!-- optional: save nii file in the niivue canvas from conversion -->
-         <label for="saveButton" id="saveButtonLabel">Optional: save the nifti file you are viewing</label>
+        <label for="saveButton" id="saveButtonLabel">Optional: save the nifti file you are viewing</label>
         <button id="saveButton" class="hidden">Save nifti</button>
       </li>
     </ol>
diff --git a/main.js b/main.js
index c39d63a..24b41c5 100644
--- a/main.js
+++ b/main.js
@@ -121,6 +121,11 @@ const runDcm2niix = async (files) => {
     console.log(resultFileList);
     hideLoadingCircle()
     showFileSelect()
+    // set the first file as the selected file
+    fileSelect.value = 0
+    // trigger the change event
+    const event = new Event('change')
+    fileSelect.dispatchEvent(event)
   } catch (error) {
     console.error(error);
     resultFileList = []
@@ -139,6 +144,72 @@ const ensureObjectOfObjects = (obj) => {
   }
 }
 
+async function handleDrop(e) {
+  e.preventDefault(); // prevent navigation to open file
+  const items = e.dataTransfer.items;
+  try {
+    showLoadingCircle()
+    const files = [];
+    for (let i = 0; i < items.length; i++) {
+      const item = items[i].webkitGetAsEntry();
+      if (item) {
+        await traverseFileTree(item, '', files);
+      }
+    }
+    const dcm2niix = new Dcm2niix();
+    await dcm2niix.init()
+    resultFileList = await dcm2niix.inputFromDropItems(files).run()
+    resultFileList = resultFileList.filter(file => file.name.endsWith('.nii'))
+    updateSelectItems(resultFileList)
+    console.log(resultFileList);
+    hideLoadingCircle()
+    showFileSelect()
+    // set the first file as the selected file
+    fileSelect.value = 0
+    // trigger the change event
+    const event = new Event('change')
+    fileSelect.dispatchEvent(event)
+    showText('')
+  } catch (error) {
+    console.error(error);
+    hideLoadingCircle()
+    hideFileSelect()
+    showText('Error converting files. Check the console for more information.')
+  }
+}
+
+async function traverseFileTree(item, path = '', fileArray) {
+  return new Promise((resolve) => {
+    if (item.isFile) {
+      item.file(file => {
+        file.fullPath = path + file.name;
+        // IMPORTANT: _webkitRelativePath is required for dcm2niix to work.
+        // We need to add this property so we can parse multiple directories correctly.
+        // the "webkitRelativePath" property on File objects is read-only, so we can't set it directly, hence the underscore.
+        file._webkitRelativePath = path + file.name;
+        fileArray.push(file);
+        resolve();
+      });
+    } else if (item.isDirectory) {
+      const dirReader = item.createReader();
+      const readAllEntries = () => {
+        dirReader.readEntries(entries => {
+          if (entries.length > 0) {
+            const promises = [];
+            for (const entry of entries) {
+              promises.push(traverseFileTree(entry, path + item.name + '/', fileArray));
+            }
+            Promise.all(promises).then(readAllEntries);
+          } else {
+            resolve();
+          }
+        });
+      };
+      readAllEntries();
+    }
+  });
+}
+
 async function main() {
   fileInput.addEventListener('change', async (event) => {
     if (event.target.files.length === 0) {
@@ -147,13 +218,17 @@ async function main() {
     }
     console.log('Selected files:', event.target.files);
     const selectedFiles = event.target.files;
-    const files = ensureObjectOfObjects(selectedFiles)
+    const files = ensureObjectOfObjects(selectedFiles) // probably not needed anymore with new dcm2niix version
     await runDcm2niix(files)
   });
 
   // when user changes the file to view
   fileSelect.onchange = handleFileSelectChange
 
+  // handle drag and drop
+  dropTarget.ondrop = handleDrop;
+  dropTarget.ondragover = (e) => {e.preventDefault();}
+
   // when user clicks save
   saveButton.onclick = handleSaveButtonClick
 
diff --git a/niivue.css b/niivue.css
index f128776..567c771 100644
--- a/niivue.css
+++ b/niivue.css
@@ -16,10 +16,22 @@ body {
   background: #303030;
 }
 
+.drop-target {
+  display: flex;
+  flex-direction: column;
+  width: 100%;
+  padding: 10px;
+  border: 2px dashed white;
+  border-radius: 5px;
+  text-align: center;
+  background-color: #000000;
+  color: white;
+}
+
 header {
   display: flex;
   gap: 2rem;
-  flex-direction: row;
+  flex-direction: column;
   margin: 10px;
 }
 
diff --git a/package-lock.json b/package-lock.json
index 06e4e37..0f797fc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
             "name": "niivue-dcm2niix",
             "version": "1.0.0",
             "dependencies": {
-                "@niivue/dcm2niix": "^0.1.1-dev.1",
+                "@niivue/dcm2niix": "^0.1.1-dev.2",
                 "@niivue/niivue": "^0.45.1"
             },
             "devDependencies": {
@@ -428,9 +428,9 @@
             }
         },
         "node_modules/@niivue/dcm2niix": {
-            "version": "0.1.1-dev.1",
-            "resolved": "https://registry.npmjs.org/@niivue/dcm2niix/-/dcm2niix-0.1.1-dev.1.tgz",
-            "integrity": "sha512-cwmBMALsFhTX+YOI1nAnQKe/CfkiPmJKPly3GVGxLy7sECJOlpwjziPkj/YUeHJImmurRKUVoC8qjVXSxUrofQ==",
+            "version": "0.1.1-dev.2",
+            "resolved": "https://registry.npmjs.org/@niivue/dcm2niix/-/dcm2niix-0.1.1-dev.2.tgz",
+            "integrity": "sha512-3iUrh7hW9/ezWTG74vIrl2eQJUAC8kv0vmwQzXiE88mu7e+Z3GMkmqmLVcJ9E9LjtIwQOnPfUB+Uj2Q8lU6acA==",
             "license": "BSD-2-Clause"
         },
         "node_modules/@niivue/niivue": {
diff --git a/package.json b/package.json
index 9358603..9e4eb47 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
     "name": "niivue-dcm2niix",
     "private": true,
-    "version": "1.0.0",
+    "version": "1.0.1",
     "type": "module",
     "scripts": {
         "dev": "vite",
@@ -9,7 +9,7 @@
         "preview": "vite preview"
     },
     "dependencies": {
-        "@niivue/dcm2niix": "^0.1.1-dev.1",
+        "@niivue/dcm2niix": "^0.1.1-dev.2",
         "@niivue/niivue": "^0.45.1"
     },
     "devDependencies": {