浏览代码

add auto refresh

C4illin 1 年之前
父节点
当前提交
dcf360b7d2
共有 9 个文件被更改,包括 164 次插入29 次删除
  1. 二进制
      bun.lockb
  2. 1 0
      package.json
  3. 0 1
      src/components/base.tsx
  4. 2 0
      src/converters/main.ts
  5. 4 0
      src/helpers/normalizeFiletype.ts
  6. 114 4
      src/index.tsx
  7. 0 13
      src/public/downloadAll.js
  8. 35 0
      src/public/results.js
  9. 8 11
      src/public/script.js

二进制
bun.lockb


+ 1 - 0
package.json

@@ -10,6 +10,7 @@
     "@elysiajs/html": "^1.0.2",
     "@elysiajs/html": "^1.0.2",
     "@elysiajs/jwt": "^1.0.2",
     "@elysiajs/jwt": "^1.0.2",
     "@elysiajs/static": "^1.0.3",
     "@elysiajs/static": "^1.0.3",
+    "@picocss/pico": "^2.0.6",
     "elysia": "^1.0.22",
     "elysia": "^1.0.22",
     "sharp": "^0.33.4"
     "sharp": "^0.33.4"
   },
   },

+ 0 - 1
src/components/base.tsx

@@ -24,7 +24,6 @@ export const BaseHtml = ({ children, title = "ConvertX" }) => (
         href="/favicon-16x16.png"
         href="/favicon-16x16.png"
       />
       />
       <link rel="manifest" href="/site.webmanifest" />
       <link rel="manifest" href="/site.webmanifest" />
-      <script src="https://unpkg.com/htmx.org@1.9.12" />
     </head>
     </head>
     <body>{children}</body>
     <body>{children}</body>
   </html>
   </html>

+ 2 - 0
src/converters/main.ts

@@ -18,6 +18,8 @@ import {
   convert as convertGraphicsmagick,
   convert as convertGraphicsmagick,
 } from "./graphicsmagick";
 } from "./graphicsmagick";
 
 
+// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
+
 const properties: {
 const properties: {
   [key: string]: {
   [key: string]: {
     properties: {
     properties: {

+ 4 - 0
src/helpers/normalizeFiletype.ts

@@ -6,6 +6,8 @@ export const normalizeFiletype = (filetype: string): string => {
       return "jpeg";
       return "jpeg";
     case "htm":
     case "htm":
       return "html";
       return "html";
+    case "tex":
+      return "latex";
     default:
     default:
       return lowercaseFiletype;
       return lowercaseFiletype;
   }
   }
@@ -17,6 +19,8 @@ export const normalizeOutputFiletype = (filetype: string): string => {
   switch (lowercaseFiletype) {
   switch (lowercaseFiletype) {
     case "jpeg":
     case "jpeg":
       return "jpg";
       return "jpg";
+    case "latex":
+      return "tex";
     default:
     default:
       return lowercaseFiletype;
       return lowercaseFiletype;
   }
   }

+ 114 - 4
src/index.tsx

@@ -1,5 +1,5 @@
 import { randomUUID } from "node:crypto";
 import { randomUUID } from "node:crypto";
-import { mkdir, unlink, rmdir } from "node:fs/promises";
+import { mkdir, unlink } from "node:fs/promises";
 import cookie from "@elysiajs/cookie";
 import cookie from "@elysiajs/cookie";
 import { html } from "@elysiajs/html";
 import { html } from "@elysiajs/html";
 import { jwt } from "@elysiajs/jwt";
 import { jwt } from "@elysiajs/jwt";
@@ -19,6 +19,7 @@ import {
   normalizeFiletype,
   normalizeFiletype,
   normalizeOutputFiletype,
   normalizeOutputFiletype,
 } from "./helpers/normalizeFiletype";
 } from "./helpers/normalizeFiletype";
+import { rmSync } from "node:fs";
 
 
 const db = new Database("./data/mydb.sqlite", { create: true });
 const db = new Database("./data/mydb.sqlite", { create: true });
 const uploadsDir = "./data/uploads/";
 const uploadsDir = "./data/uploads/";
@@ -641,7 +642,7 @@ const app = new Elysia()
           );
           );
 
 
           // delete all uploaded files in userUploadsDir
           // delete all uploaded files in userUploadsDir
-          rmdir(userUploadsDir, { recursive: true });
+          rmSync(userUploadsDir, { recursive: true, force: true });
         })
         })
         .catch((error) => {
         .catch((error) => {
           console.error("Error in conversion process:", error);
           console.error("Error in conversion process:", error);
@@ -763,7 +764,8 @@ const app = new Elysia()
                   <button
                   <button
                     type="button"
                     type="button"
                     style={{ width: "10rem", float: "right" }}
                     style={{ width: "10rem", float: "right" }}
-                    onclick="downloadAll()">
+                    onclick="downloadAll()"
+                    {...(files.length !== job.num_files && { disabled: true })}>
                     Download All
                     Download All
                   </button>
                   </button>
                 </div>
                 </div>
@@ -801,11 +803,93 @@ const app = new Elysia()
               </table>
               </table>
             </article>
             </article>
           </main>
           </main>
-          <script src="/downloadAll.js" defer />
+          <script src="/results.js" defer />
         </BaseHtml>
         </BaseHtml>
       );
       );
     },
     },
   )
   )
+  .get(
+    "/progress/:jobId",
+    async ({ jwt, set, params, redirect, cookie: { auth, job_id } }) => {
+      if (!auth?.value) {
+        return redirect("/login");
+      }
+
+      if (job_id?.value) {
+        // clear the job_id cookie since we are viewing the results
+        job_id.remove();
+      }
+
+      const user = await jwt.verify(auth.value);
+      if (!user) {
+        return redirect("/login");
+      }
+
+      const job = (await db
+        .query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
+        .get(user.id, params.jobId)) as IJobs;
+
+      if (!job) {
+        set.status = 404;
+        return {
+          message: "Job not found.",
+        };
+      }
+
+      const outputPath = `${user.id}/${params.jobId}/`;
+
+      const files = db
+        .query("SELECT * FROM file_names WHERE job_id = ?")
+        .all(params.jobId) as IFileNames[];
+
+      return (
+        <article>
+          <div class="grid">
+            <h1>Results</h1>
+            <div>
+              <button
+                type="button"
+                style={{ width: "10rem", float: "right" }}
+                onclick="downloadAll()"
+                {...(files.length !== job.num_files && { disabled: true })}>
+                Download All
+              </button>
+            </div>
+          </div>
+          <progress max={job.num_files} value={files.length} />
+          <table>
+            <thead>
+              <tr>
+                <th>Converted File Name</th>
+                <th>View</th>
+                <th>Download</th>
+              </tr>
+            </thead>
+            <tbody>
+              {files.map((file) => (
+                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
+                <tr>
+                  <td>{file.output_file_name}</td>
+                  <td>
+                    <a href={`/download/${outputPath}${file.output_file_name}`}>
+                      View
+                    </a>
+                  </td>
+                  <td>
+                    <a
+                      href={`/download/${outputPath}${file.output_file_name}`}
+                      download={file.output_file_name}>
+                      Download
+                    </a>
+                  </td>
+                </tr>
+              ))}
+            </tbody>
+          </table>
+        </article>
+      );
+    },
+  )
   .get(
   .get(
     "/download/:userId/:jobId/:fileName",
     "/download/:userId/:jobId/:fileName",
     async ({ params, jwt, redirect, cookie: { auth } }) => {
     async ({ params, jwt, redirect, cookie: { auth } }) => {
@@ -930,3 +1014,29 @@ const app = new Elysia()
 console.log(
 console.log(
   `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`,
   `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`,
 );
 );
+
+const clearJobs = () => {
+  // clear all jobs older than 24 hours
+  // get all files older than 24 hours
+  const jobs = db
+    .query("SELECT * FROM jobs WHERE date_created < ?")
+    .all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString()) as IJobs[];
+  
+  for (const job of jobs) {
+    const files = db
+      .query("SELECT * FROM file_names WHERE job_id = ?")
+      .all(job.id) as IFileNames[];
+
+    for (const file of files) {
+      // delete the file
+      unlink(`${outputDir}${job.user_id}/${job.id}/${file.output_file_name}`);
+    }
+
+    // delete the job
+    db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
+  }
+
+  // run every 24 hours
+  setTimeout(clearJobs, 24 * 60 * 60 * 1000);
+}
+clearJobs();

+ 0 - 13
src/public/downloadAll.js

@@ -1,13 +0,0 @@
-window.downloadAll = function () {
-  // Get all download links
-  const downloadLinks = document.querySelectorAll("a[download]");
-
-  // Trigger download for each link
-  downloadLinks.forEach((link, index) => {
-    // We add a delay for each download to prevent them from starting at the same time
-    setTimeout(() => {
-      const event = new MouseEvent("click");
-      link.dispatchEvent(event);
-    }, index * 100);
-  });
-};

+ 35 - 0
src/public/results.js

@@ -0,0 +1,35 @@
+window.downloadAll = function () {
+  // Get all download links
+  const downloadLinks = document.querySelectorAll("a[download]");
+
+  // Trigger download for each link
+  downloadLinks.forEach((link, index) => {
+    // We add a delay for each download to prevent them from starting at the same time
+    setTimeout(() => {
+      const event = new MouseEvent("click");
+      link.dispatchEvent(event);
+    }, index * 100);
+  });
+};
+const jobId = window.location.pathname.split("/").pop();
+const main = document.querySelector("main");
+const progressElem = document.querySelector("progress");
+
+const refreshData = () => {
+  console.log("Refreshing data...");
+  console.log(progressElem.value);
+  console.log(progressElem.max);
+
+  if (progressElem.value !== progressElem.max) {
+    fetch(`/progress/${jobId}`)
+      .then((res) => res.text())
+      .then((html) => {
+        main.innerHTML = html;
+      })
+      .catch((err) => console.log(err));
+
+    setTimeout(refreshData, 1000);
+  }
+};
+
+refreshData();

+ 8 - 11
src/public/script.js

@@ -5,11 +5,11 @@ let fileType;
 
 
 const selectElem = document.querySelector("select[name='convert_to']");
 const selectElem = document.querySelector("select[name='convert_to']");
 
 
-const convertFromSelect = document.querySelector("select[name='convert_from']");
+// const convertFromSelect = document.querySelector("select[name='convert_from']");
 
 
 // Add a 'change' event listener to the file input element
 // Add a 'change' event listener to the file input element
 fileInput.addEventListener("change", (e) => {
 fileInput.addEventListener("change", (e) => {
-  console.log(e.target.files);
+  // console.log(e.target.files);
   // Get the selected files from the event target
   // Get the selected files from the event target
   const files = e.target.files;
   const files = e.target.files;
 
 
@@ -28,18 +28,16 @@ fileInput.addEventListener("change", (e) => {
 
 
     if (!fileType) {
     if (!fileType) {
       fileType = file.name.split(".").pop();
       fileType = file.name.split(".").pop();
-      console.log(file.type);
       fileInput.setAttribute("accept", `.${fileType}`);
       fileInput.setAttribute("accept", `.${fileType}`);
       setTitle();
       setTitle();
 
 
       // choose the option that matches the file type
       // choose the option that matches the file type
-      for (const option of convertFromSelect.children) {
-        console.log(option.value);
-        if (option.value === fileType) {
-          option.selected = true;
-        }
-      }
-
+      // for (const option of convertFromSelect.children) {
+      //   console.log(option.value);
+      //   if (option.value === fileType) {
+      //     option.selected = true;
+      //   }
+      // }
 
 
       fetch("/conversions", {
       fetch("/conversions", {
         method: "POST",
         method: "POST",
@@ -50,7 +48,6 @@ fileInput.addEventListener("change", (e) => {
       })
       })
         .then((res) => res.text()) // Convert the response to text
         .then((res) => res.text()) // Convert the response to text
         .then((html) => {
         .then((html) => {
-          console.log(html);
           selectElem.outerHTML = html; // Set the HTML
           selectElem.outerHTML = html; // Set the HTML
         })
         })
         .catch((err) => console.log(err));
         .catch((err) => console.log(err));