script.js 7.7 KB

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