add auto refresh
This commit is contained in:
parent
d5628dcfab
commit
dcf360b7d2
9 changed files with 164 additions and 29 deletions
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
|
@ -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"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
118
src/index.tsx
118
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();
|
||||||
|
|
@ -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 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) {
|
// for (const option of convertFromSelect.children) {
|
||||||
console.log(option.value);
|
// console.log(option.value);
|
||||||
if (option.value === fileType) {
|
// if (option.value === fileType) {
|
||||||
option.selected = true;
|
// 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));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue