script.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. const webroot = document.querySelector("meta[name='webroot']").content;
  2. const fileInput = document.querySelector('input[type="file"]');
  3. const dropZone = document.getElementById("dropzone");
  4. const convertButton = document.querySelector("input[type='submit']");
  5. const fileNames = [];
  6. let fileType;
  7. let pendingFiles = 0;
  8. let formatSelected = false;
  9. dropZone.addEventListener("dragover", (e) => {
  10. e.preventDefault();
  11. dropZone.classList.add("dragover");
  12. });
  13. dropZone.addEventListener("dragleave", () => {
  14. dropZone.classList.remove("dragover");
  15. });
  16. dropZone.addEventListener("drop", (e) => {
  17. e.preventDefault();
  18. dropZone.classList.remove("dragover");
  19. const files = e.dataTransfer.files;
  20. if (files.length === 0) {
  21. console.warn("No files dropped — likely a URL or unsupported source.");
  22. return;
  23. }
  24. for (const file of files) {
  25. console.log("Handling dropped file:", file.name);
  26. handleFile(file);
  27. }
  28. });
  29. // Extracted handleFile function for reusability in drag-and-drop and file input
  30. function handleFile(file) {
  31. const fileList = document.querySelector("#file-list");
  32. const row = document.createElement("tr");
  33. row.innerHTML = `
  34. <td>${file.name}</td>
  35. <td><progress max="100"></progress></td>
  36. <td>${(file.size / 1024).toFixed(2)} kB</td>
  37. <td><a onclick="deleteRow(this)">Remove</a></td>
  38. `;
  39. if (!fileType) {
  40. fileType = file.name.split(".").pop();
  41. fileInput.setAttribute("accept", `.${fileType}`);
  42. setTitle();
  43. fetch(`${webroot}/conversions`, {
  44. method: "POST",
  45. body: JSON.stringify({ fileType }),
  46. headers: { "Content-Type": "application/json" },
  47. })
  48. .then((res) => res.text())
  49. .then((html) => {
  50. selectContainer.innerHTML = html;
  51. updateSearchBar();
  52. })
  53. .catch(console.error);
  54. }
  55. fileList.appendChild(row);
  56. file.htmlRow = row;
  57. fileNames.push(file.name);
  58. uploadFile(file);
  59. }
  60. const selectContainer = document.querySelector("form .select_container");
  61. const updateSearchBar = () => {
  62. const convertToInput = document.querySelector(
  63. "input[name='convert_to_search']",
  64. );
  65. const convertToPopup = document.querySelector(".convert_to_popup");
  66. const convertToGroupElements = document.querySelectorAll(".convert_to_group");
  67. const convertToGroups = {};
  68. const convertToElement = document.querySelector("select[name='convert_to']");
  69. const showMatching = (search) => {
  70. for (const [targets, groupElement] of Object.values(convertToGroups)) {
  71. let matchingTargetsFound = 0;
  72. for (const target of targets) {
  73. if (target.dataset.target.includes(search)) {
  74. matchingTargetsFound++;
  75. target.classList.remove("hidden");
  76. target.classList.add("flex");
  77. } else {
  78. target.classList.add("hidden");
  79. target.classList.remove("flex");
  80. }
  81. }
  82. if (matchingTargetsFound === 0) {
  83. groupElement.classList.add("hidden");
  84. groupElement.classList.remove("flex");
  85. } else {
  86. groupElement.classList.remove("hidden");
  87. groupElement.classList.add("flex");
  88. }
  89. }
  90. };
  91. for (const groupElement of convertToGroupElements) {
  92. const groupName = groupElement.dataset.converter;
  93. const targetElements = groupElement.querySelectorAll(".target");
  94. const targets = Array.from(targetElements);
  95. for (const target of targets) {
  96. target.onmousedown = () => {
  97. convertToElement.value = target.dataset.value;
  98. convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
  99. formatSelected = true;
  100. if (pendingFiles === 0 && fileNames.length > 0) {
  101. convertButton.disabled = false;
  102. }
  103. showMatching("");
  104. };
  105. }
  106. convertToGroups[groupName] = [targets, groupElement];
  107. }
  108. convertToInput.addEventListener("input", (e) => {
  109. showMatching(e.target.value.toLowerCase());
  110. });
  111. convertToInput.addEventListener("search", () => {
  112. // when the user clears the search bar using the 'x' button
  113. convertButton.disabled = true;
  114. formatSelected = false;
  115. });
  116. convertToInput.addEventListener("blur", (e) => {
  117. // Keep the popup open even when clicking on a target button
  118. // for a split second to allow the click to go through
  119. if (e?.relatedTarget?.classList?.contains("target")) {
  120. convertToPopup.classList.add("hidden");
  121. convertToPopup.classList.remove("flex");
  122. return;
  123. }
  124. convertToPopup.classList.add("hidden");
  125. convertToPopup.classList.remove("flex");
  126. });
  127. convertToInput.addEventListener("focus", () => {
  128. convertToPopup.classList.remove("hidden");
  129. convertToPopup.classList.add("flex");
  130. });
  131. };
  132. // Add a 'change' event listener to the file input element
  133. fileInput.addEventListener("change", (e) => {
  134. const files = e.target.files;
  135. for (const file of files) {
  136. handleFile(file);
  137. }
  138. });
  139. const setTitle = () => {
  140. const title = document.querySelector("h1");
  141. title.textContent = `Convert ${fileType ? `.${fileType}` : ""}`;
  142. };
  143. // Add a onclick for the delete button
  144. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  145. const deleteRow = (target) => {
  146. const filename = target.parentElement.parentElement.children[0].textContent;
  147. const row = target.parentElement.parentElement;
  148. row.remove();
  149. // remove from fileNames
  150. const index = fileNames.indexOf(filename);
  151. fileNames.splice(index, 1);
  152. // reset fileInput
  153. fileInput.value = "";
  154. // if fileNames is empty, reset fileType
  155. if (fileNames.length === 0) {
  156. fileType = null;
  157. fileInput.removeAttribute("accept");
  158. convertButton.disabled = true;
  159. setTitle();
  160. }
  161. fetch(`${webroot}/delete`, {
  162. method: "POST",
  163. body: JSON.stringify({ filename: filename }),
  164. headers: {
  165. "Content-Type": "application/json",
  166. },
  167. })
  168. .catch((err) => console.log(err));
  169. };
  170. const uploadFile = (file) => {
  171. convertButton.disabled = true;
  172. convertButton.textContent = "Uploading...";
  173. pendingFiles += 1;
  174. const formData = new FormData();
  175. formData.append("file", file, file.name);
  176. let xhr = new XMLHttpRequest();
  177. xhr.open("POST", `${webroot}/upload`, true);
  178. xhr.onload = () => {
  179. let data = JSON.parse(xhr.responseText);
  180. pendingFiles -= 1;
  181. if (pendingFiles === 0) {
  182. if (formatSelected) {
  183. convertButton.disabled = false;
  184. }
  185. convertButton.textContent = "Convert";
  186. }
  187. //Remove the progress bar when upload is done
  188. let progressbar = file.htmlRow.getElementsByTagName("progress");
  189. progressbar[0].parentElement.remove();
  190. console.log(data);
  191. };
  192. xhr.upload.onprogress = (e) => {
  193. let sent = e.loaded;
  194. let total = e.total;
  195. console.log(`upload progress (${file.name}):`, (100 * sent) / total);
  196. let progressbar = file.htmlRow.getElementsByTagName("progress");
  197. progressbar[0].value = ((100 * sent) / total);
  198. };
  199. xhr.onerror = (e) => {
  200. console.log(e);
  201. };
  202. xhr.send(formData);
  203. };
  204. const formConvert = document.querySelector(`form[action='${webroot}/convert']`);
  205. formConvert.addEventListener("submit", () => {
  206. const hiddenInput = document.querySelector("input[name='file_names']");
  207. hiddenInput.value = JSON.stringify(fileNames);
  208. });
  209. updateSearchBar();