mirror of
https://github.com/C4illin/ConvertX.git
synced 2026-06-27 22:45:48 +00:00
add auto refresh
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
"@elysiajs/html": "^1.0.2",
|
||||
"@elysiajs/jwt": "^1.0.2",
|
||||
"@elysiajs/static": "^1.0.3",
|
||||
"@picocss/pico": "^2.0.6",
|
||||
"elysia": "^1.0.22",
|
||||
"sharp": "^0.33.4"
|
||||
},
|
||||
|
||||
@@ -24,7 +24,6 @@ export const BaseHtml = ({ children, title = "ConvertX" }) => (
|
||||
href="/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<script src="https://unpkg.com/htmx.org@1.9.12" />
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
convert as convertGraphicsmagick,
|
||||
} 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: {
|
||||
[key: string]: {
|
||||
properties: {
|
||||
|
||||
@@ -6,6 +6,8 @@ export const normalizeFiletype = (filetype: string): string => {
|
||||
return "jpeg";
|
||||
case "htm":
|
||||
return "html";
|
||||
case "tex":
|
||||
return "latex";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
@@ -17,6 +19,8 @@ export const normalizeOutputFiletype = (filetype: string): string => {
|
||||
switch (lowercaseFiletype) {
|
||||
case "jpeg":
|
||||
return "jpg";
|
||||
case "latex":
|
||||
return "tex";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
|
||||
118
src/index.tsx
118
src/index.tsx
@@ -1,5 +1,5 @@
|
||||
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 { html } from "@elysiajs/html";
|
||||
import { jwt } from "@elysiajs/jwt";
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
normalizeFiletype,
|
||||
normalizeOutputFiletype,
|
||||
} from "./helpers/normalizeFiletype";
|
||||
import { rmSync } from "node:fs";
|
||||
|
||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||
const uploadsDir = "./data/uploads/";
|
||||
@@ -641,7 +642,7 @@ const app = new Elysia()
|
||||
);
|
||||
|
||||
// delete all uploaded files in userUploadsDir
|
||||
rmdir(userUploadsDir, { recursive: true });
|
||||
rmSync(userUploadsDir, { recursive: true, force: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error in conversion process:", error);
|
||||
@@ -763,7 +764,8 @@ const app = new Elysia()
|
||||
<button
|
||||
type="button"
|
||||
style={{ width: "10rem", float: "right" }}
|
||||
onclick="downloadAll()">
|
||||
onclick="downloadAll()"
|
||||
{...(files.length !== job.num_files && { disabled: true })}>
|
||||
Download All
|
||||
</button>
|
||||
</div>
|
||||
@@ -801,11 +803,93 @@ const app = new Elysia()
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script src="/downloadAll.js" defer />
|
||||
<script src="/results.js" defer />
|
||||
</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(
|
||||
"/download/:userId/:jobId/:fileName",
|
||||
async ({ params, jwt, redirect, cookie: { auth } }) => {
|
||||
@@ -930,3 +1014,29 @@ const app = new Elysia()
|
||||
console.log(
|
||||
`🦊 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();
|
||||
@@ -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
src/public/results.js
Normal file
35
src/public/results.js
Normal file
@@ -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();
|
||||
@@ -5,11 +5,11 @@ let fileType;
|
||||
|
||||
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
|
||||
fileInput.addEventListener("change", (e) => {
|
||||
console.log(e.target.files);
|
||||
// console.log(e.target.files);
|
||||
// Get the selected files from the event target
|
||||
const files = e.target.files;
|
||||
|
||||
@@ -28,18 +28,16 @@ fileInput.addEventListener("change", (e) => {
|
||||
|
||||
if (!fileType) {
|
||||
fileType = file.name.split(".").pop();
|
||||
console.log(file.type);
|
||||
fileInput.setAttribute("accept", `.${fileType}`);
|
||||
setTitle();
|
||||
|
||||
// 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", {
|
||||
method: "POST",
|
||||
@@ -50,7 +48,6 @@ fileInput.addEventListener("change", (e) => {
|
||||
})
|
||||
.then((res) => res.text()) // Convert the response to text
|
||||
.then((html) => {
|
||||
console.log(html);
|
||||
selectElem.outerHTML = html; // Set the HTML
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
|
||||
Reference in New Issue
Block a user