Add files via upload
This commit is contained in:
parent
2075e51c44
commit
fdb0c0d7eb
1 changed files with 42 additions and 33 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
|
|
@ -12,31 +14,21 @@ import { userService } from "./user";
|
||||||
import { outputDir } from "..";
|
import { outputDir } from "..";
|
||||||
import { sendFileToErugo } from "../helpers/erugo";
|
import { sendFileToErugo } from "../helpers/erugo";
|
||||||
|
|
||||||
type JobsWithOptionalNumFiles = Jobs & {
|
|
||||||
num_files?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
function ResultsArticle({
|
function ResultsArticle({
|
||||||
job,
|
job,
|
||||||
files,
|
files,
|
||||||
outputPath,
|
outputPath,
|
||||||
}: {
|
}: {
|
||||||
job: JobsWithOptionalNumFiles;
|
job: Jobs;
|
||||||
files: Filename[];
|
files: Filename[];
|
||||||
outputPath: string;
|
outputPath: string;
|
||||||
}) {
|
}) {
|
||||||
const maxFiles =
|
const maxFiles = Number((job as any).num_files ?? 0);
|
||||||
typeof job.num_files === "number" && Number.isFinite(job.num_files)
|
const doneFiles = Number(files.filter((f: any) => String((f as any).status || '').toLowerCase() === 'done').length);
|
||||||
? Number(job.num_files)
|
const isDone = doneFiles === maxFiles;
|
||||||
: Number(files.length);
|
|
||||||
|
|
||||||
const doneFiles = Number(
|
|
||||||
files.filter((f) => String(f.status ?? "").toLowerCase() === "done").length,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDone = maxFiles > 0 ? doneFiles >= maxFiles : true;
|
|
||||||
|
|
||||||
const disabledLinkClass = "pointer-events-none opacity-50";
|
const disabledLinkClass = "pointer-events-none opacity-50";
|
||||||
|
const busyAttrs = { disabled: true, "aria-busy": "true" } as const;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article class="article">
|
<article class="article">
|
||||||
|
|
@ -49,7 +41,7 @@ function ResultsArticle({
|
||||||
!isDone ? disabledLinkClass : ""
|
!isDone ? disabledLinkClass : ""
|
||||||
}`}
|
}`}
|
||||||
href={`${WEBROOT}/delete/${job.id}`}
|
href={`${WEBROOT}/delete/${job.id}`}
|
||||||
aria-disabled={!isDone ? "true" : undefined}
|
{...(!isDone ? busyAttrs : {})}
|
||||||
>
|
>
|
||||||
<DeleteIcon /> <p>Delete</p>
|
<DeleteIcon /> <p>Delete</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -59,8 +51,8 @@ function ResultsArticle({
|
||||||
!isDone ? disabledLinkClass : ""
|
!isDone ? disabledLinkClass : ""
|
||||||
}`}
|
}`}
|
||||||
href={`${WEBROOT}/archive/${job.id}`}
|
href={`${WEBROOT}/archive/${job.id}`}
|
||||||
download
|
download={`converted_files_${job.id}.tar`}
|
||||||
aria-disabled={!isDone ? "true" : undefined}
|
{...(!isDone ? busyAttrs : {})}
|
||||||
>
|
>
|
||||||
<DownloadIcon /> <p>Tar</p>
|
<DownloadIcon /> <p>Tar</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -69,8 +61,7 @@ function ResultsArticle({
|
||||||
id="cxDownloadAll"
|
id="cxDownloadAll"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex btn-primary flex-row gap-2 text-contrast"
|
class="flex btn-primary flex-row gap-2 text-contrast"
|
||||||
disabled={!isDone ? "true" : undefined}
|
{...(!isDone ? busyAttrs : {})}
|
||||||
aria-busy={!isDone ? "true" : undefined}
|
|
||||||
>
|
>
|
||||||
<DownloadIcon /> <p>All</p>
|
<DownloadIcon /> <p>All</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -124,7 +115,7 @@ function ResultsArticle({
|
||||||
<a
|
<a
|
||||||
class="text-accent-500 hover:text-accent-400"
|
class="text-accent-500 hover:text-accent-400"
|
||||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||||
download
|
download={file.output_file_name}
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -205,7 +196,7 @@ function ResultsArticle({
|
||||||
<textarea
|
<textarea
|
||||||
id="cxShareDescription"
|
id="cxShareDescription"
|
||||||
class="w-full rounded border border-neutral-700 bg-neutral-950 px-3 py-2 text-neutral-100 outline-none focus:border-accent-500"
|
class="w-full rounded border border-neutral-700 bg-neutral-950 px-3 py-2 text-neutral-100 outline-none focus:border-accent-500"
|
||||||
rows="3"
|
rows={3}
|
||||||
placeholder="Message to share recipients (optional)"
|
placeholder="Message to share recipients (optional)"
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,7 +205,7 @@ function ResultsArticle({
|
||||||
<button
|
<button
|
||||||
id="cxShareSubmit"
|
id="cxShareSubmit"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex btn-primary items-center gap-2"
|
class="btn-primary flex items-center gap-2"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -252,6 +243,11 @@ function ResultsArticle({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMPORTANT FIX:
|
||||||
|
* Results HTML is re-rendered via /progress/:jobId and replaces the article + modal.
|
||||||
|
* So we MUST NOT keep stale DOM references. We re-query elements on each action.
|
||||||
|
*/
|
||||||
const shareJs = `
|
const shareJs = `
|
||||||
(function () {
|
(function () {
|
||||||
const WEBROOT = ${JSON.stringify(WEBROOT)};
|
const WEBROOT = ${JSON.stringify(WEBROOT)};
|
||||||
|
|
@ -310,11 +306,13 @@ const shareJs = `
|
||||||
currentFileName = null;
|
currentFileName = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delegated: always works even after /progress replaces the article
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
const t = e.target;
|
const t = e.target;
|
||||||
const btn = t && (t.closest ? t.closest('[data-share="true"]') : null);
|
const btn = t && (t.closest ? t.closest('[data-share="true"]') : null);
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
|
// kill old handlers (e.g. alert() from older results.js)
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
|
||||||
|
|
@ -324,6 +322,7 @@ const shareJs = `
|
||||||
openModal(jobId, fileName);
|
openModal(jobId, fileName);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// Delegated close (because modal DOM is replaced during progress polling)
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
const id = e.target && e.target.id;
|
const id = e.target && e.target.id;
|
||||||
if (id === "cxShareClose" || id === "cxShareCancel") {
|
if (id === "cxShareClose" || id === "cxShareCancel") {
|
||||||
|
|
@ -331,6 +330,7 @@ const shareJs = `
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
if (id === "cxShareModal") {
|
if (id === "cxShareModal") {
|
||||||
|
// click outside dialog closes
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
@ -340,6 +340,7 @@ const shareJs = `
|
||||||
if (e.key === "Escape" && r.modal && !r.modal.classList.contains("hidden")) closeModal();
|
if (e.key === "Escape" && r.modal && !r.modal.classList.contains("hidden")) closeModal();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Delegated copy
|
||||||
document.addEventListener("click", async (e) => {
|
document.addEventListener("click", async (e) => {
|
||||||
const t = e.target;
|
const t = e.target;
|
||||||
if (!t || t.id !== "cxShareCopy") return;
|
if (!t || t.id !== "cxShareCopy") return;
|
||||||
|
|
@ -358,6 +359,7 @@ const shareJs = `
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// Delegated submit
|
||||||
document.addEventListener("click", async (e) => {
|
document.addEventListener("click", async (e) => {
|
||||||
const t = e.target;
|
const t = e.target;
|
||||||
if (!t || t.id !== "cxShareSubmit") return;
|
if (!t || t.id !== "cxShareSubmit") return;
|
||||||
|
|
@ -401,13 +403,13 @@ const shareJs = `
|
||||||
}
|
}
|
||||||
|
|
||||||
const url =
|
const url =
|
||||||
(json && typeof json === "object" && json.share_url) ||
|
json?.share_url ||
|
||||||
(json && typeof json === "object" && json.share_link) ||
|
json?.share_link ||
|
||||||
(json && typeof json === "object" && json.data && json.data.url) ||
|
json?.data?.url ||
|
||||||
(json && typeof json === "object" && json.data && json.data.share && json.data.share.url) ||
|
json?.data?.share?.url ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
if (url && typeof url === "string") {
|
if (url) {
|
||||||
r.linkEl.value = url;
|
r.linkEl.value = url;
|
||||||
r.linkBlock.classList.remove("hidden");
|
r.linkBlock.classList.remove("hidden");
|
||||||
}
|
}
|
||||||
|
|
@ -424,6 +426,7 @@ const shareJs = `
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
// Download All: delegated, because button is replaced during progress polling
|
||||||
document.addEventListener("click", (e) => {
|
document.addEventListener("click", (e) => {
|
||||||
const t = e.target;
|
const t = e.target;
|
||||||
const btn = t && (t.closest ? t.closest("#cxDownloadAll") : null);
|
const btn = t && (t.closest ? t.closest("#cxDownloadAll") : null);
|
||||||
|
|
@ -445,6 +448,7 @@ const shareJs = `
|
||||||
|
|
||||||
export const results = new Elysia()
|
export const results = new Elysia()
|
||||||
.use(userService)
|
.use(userService)
|
||||||
|
|
||||||
.get(
|
.get(
|
||||||
"/results-share.js",
|
"/results-share.js",
|
||||||
() =>
|
() =>
|
||||||
|
|
@ -456,6 +460,7 @@ export const results = new Elysia()
|
||||||
}),
|
}),
|
||||||
{ auth: true },
|
{ auth: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
.get(
|
.get(
|
||||||
"/results/:jobId",
|
"/results/:jobId",
|
||||||
async ({ params, set, cookie: { job_id }, user }) => {
|
async ({ params, set, cookie: { job_id }, user }) => {
|
||||||
|
|
@ -464,7 +469,7 @@ export const results = new Elysia()
|
||||||
const job = db
|
const job = db
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
.as(Jobs)
|
.as(Jobs)
|
||||||
.get(user.id, params.jobId) as JobsWithOptionalNumFiles | null;
|
.get(user.id, params.jobId);
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
set.status = 404;
|
set.status = 404;
|
||||||
|
|
@ -491,10 +496,10 @@ export const results = new Elysia()
|
||||||
<ResultsArticle job={job} files={files} outputPath={outputPath} />
|
<ResultsArticle job={job} files={files} outputPath={outputPath} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* existing file */}
|
{/* keep existing file */}
|
||||||
<script src={`${WEBROOT}/results.js`} defer />
|
<script src={`${WEBROOT}/results.js`} defer />
|
||||||
|
|
||||||
{/* override handlers */}
|
{/* our override must also load */}
|
||||||
<script src={`${WEBROOT}/results-share.js`} defer />
|
<script src={`${WEBROOT}/results-share.js`} defer />
|
||||||
</>
|
</>
|
||||||
</BaseHtml>
|
</BaseHtml>
|
||||||
|
|
@ -502,6 +507,7 @@ export const results = new Elysia()
|
||||||
},
|
},
|
||||||
{ auth: true },
|
{ auth: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
.post(
|
.post(
|
||||||
"/progress/:jobId",
|
"/progress/:jobId",
|
||||||
async ({ set, params, cookie: { job_id }, user }) => {
|
async ({ set, params, cookie: { job_id }, user }) => {
|
||||||
|
|
@ -510,7 +516,7 @@ export const results = new Elysia()
|
||||||
const job = db
|
const job = db
|
||||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||||
.as(Jobs)
|
.as(Jobs)
|
||||||
.get(user.id, params.jobId) as JobsWithOptionalNumFiles | null;
|
.get(user.id, params.jobId);
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
set.status = 404;
|
set.status = 404;
|
||||||
|
|
@ -528,6 +534,7 @@ export const results = new Elysia()
|
||||||
},
|
},
|
||||||
{ auth: true },
|
{ auth: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
.post(
|
.post(
|
||||||
"/share-to-erugo/:jobId",
|
"/share-to-erugo/:jobId",
|
||||||
async ({ params, body, user, set }) => {
|
async ({ params, body, user, set }) => {
|
||||||
|
|
@ -570,7 +577,7 @@ export const results = new Elysia()
|
||||||
|
|
||||||
const result = await sendFileToErugo(payload);
|
const result = await sendFileToErugo(payload);
|
||||||
return result;
|
return result;
|
||||||
} catch (err: unknown) {
|
} catch (err: any) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
set.status = 500;
|
set.status = 500;
|
||||||
return { message: "Failed to share with Erugo" };
|
return { message: "Failed to share with Erugo" };
|
||||||
|
|
@ -586,3 +593,5 @@ export const results = new Elysia()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue