feature: add download all file by file alongside the tar download
This commit is contained in:
parent
27e9b7eb35
commit
48e5e8a71c
20 changed files with 79 additions and 45 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "ConvertX Development Environment",
|
"name": "ConvertX",
|
||||||
"build": {
|
"build": {
|
||||||
"dockerfile": "Dockerfile"
|
"dockerfile": "Dockerfile"
|
||||||
},
|
},
|
||||||
|
|
@ -46,5 +46,8 @@
|
||||||
},
|
},
|
||||||
"postCreateCommand": "bun install",
|
"postCreateCommand": "bun install",
|
||||||
"remoteUser": "root",
|
"remoteUser": "root",
|
||||||
"mounts": ["source=${localWorkspaceFolder}/data,target=/app/data,type=bind"]
|
"mounts": ["source=${localWorkspaceFolder}/data,target=/app/data,type=bind"],
|
||||||
|
"containerEnv": {
|
||||||
|
"JWT_SECRET": "jwt_secret_only_used_in_testing_for_easier_hot_reloading"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,17 @@ const refreshData = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshData();
|
refreshData();
|
||||||
|
|
||||||
|
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 * 300);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
||||||
1
reset.d.ts
vendored
1
reset.d.ts
vendored
|
|
@ -1 +0,0 @@
|
||||||
import "@total-typescript/ts-reset";
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { version } from "../../package.json";
|
import { version } from "../../package.json";
|
||||||
|
|
||||||
export const BaseHtml = ({
|
export const BaseHtml = ({
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { Html } from "@kitajs/html";
|
|
||||||
|
|
||||||
export const Header = ({
|
export const Header = ({
|
||||||
loggedIn,
|
loggedIn,
|
||||||
accountRegistration,
|
accountRegistration,
|
||||||
|
|
|
||||||
18
src/icons/download.tsx
Normal file
18
src/icons/download.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
export function DownloadIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class={`size-6`}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
src/icons/eye.tsx
Normal file
19
src/icons/eye.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
export function EyeIcon() {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
class={`size-6`}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z"
|
||||||
|
/>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import Elysia, { t } from "elysia";
|
import Elysia, { t } from "elysia";
|
||||||
import { getPossibleTargets } from "../converters/main";
|
import { getPossibleTargets } from "../converters/main";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import Elysia from "elysia";
|
import Elysia from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
import { Header } from "../components/header";
|
import { Header } from "../components/header";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
|
|
@ -6,6 +5,8 @@ import { Header } from "../components/header";
|
||||||
import db from "../db/db";
|
import db from "../db/db";
|
||||||
import { Filename, Jobs } from "../db/types";
|
import { Filename, Jobs } from "../db/types";
|
||||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||||
|
import { DownloadIcon } from "../icons/download";
|
||||||
|
import { EyeIcon } from "../icons/eye";
|
||||||
import { userService } from "./user";
|
import { userService } from "./user";
|
||||||
|
|
||||||
function ResultsArticle({
|
function ResultsArticle({
|
||||||
|
|
@ -25,25 +26,24 @@ function ResultsArticle({
|
||||||
<article class="article">
|
<article class="article">
|
||||||
<div class="mb-4 flex items-center justify-between">
|
<div class="mb-4 flex items-center justify-between">
|
||||||
<h1 class="text-xl">Results</h1>
|
<h1 class="text-xl">Results</h1>
|
||||||
<div>
|
<div class="flex flex-row gap-4">
|
||||||
<a
|
<a
|
||||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||||
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
||||||
download={`converted_files_${job.id}.tar`}
|
download={`converted_files_${job.id}.tar`}
|
||||||
>
|
class="flex btn-primary flex-row gap-2 text-contrast"
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="float-right w-40 btn-primary"
|
|
||||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||||
>
|
>
|
||||||
{files.length === job.num_files ? "Download All" : "Converting..."}
|
<DownloadIcon /> <p>Tar</p>
|
||||||
</button>
|
|
||||||
</a>
|
</a>
|
||||||
|
<button class="flex btn-primary flex-row gap-2 text-contrast" onclick="downloadAll()">
|
||||||
|
<DownloadIcon /> <p>All</p>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<progress
|
<progress
|
||||||
max={job.num_files}
|
max={job.num_files}
|
||||||
value={files.length}
|
{...(files.length === job.num_files ? { value: files.length } : "")}
|
||||||
class={`
|
class={`
|
||||||
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
||||||
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
||||||
|
|
@ -84,15 +84,7 @@ function ResultsArticle({
|
||||||
sm:px-4
|
sm:px-4
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
View
|
Actions
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
class={`
|
|
||||||
px-2 py-2
|
|
||||||
sm:px-4
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
Download
|
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -103,7 +95,7 @@ function ResultsArticle({
|
||||||
{file.output_file_name}
|
{file.output_file_name}
|
||||||
</td>
|
</td>
|
||||||
<td safe>{file.status}</td>
|
<td safe>{file.status}</td>
|
||||||
<td>
|
<td class="flex flex-row gap-4">
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-500 underline
|
text-accent-500 underline
|
||||||
|
|
@ -111,10 +103,8 @@ function ResultsArticle({
|
||||||
`}
|
`}
|
||||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||||
>
|
>
|
||||||
View
|
<EyeIcon />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a
|
<a
|
||||||
class={`
|
class={`
|
||||||
text-accent-500 underline
|
text-accent-500 underline
|
||||||
|
|
@ -123,7 +113,7 @@ function ResultsArticle({
|
||||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||||
download={file.output_file_name}
|
download={file.output_file_name}
|
||||||
>
|
>
|
||||||
Download
|
<DownloadIcon />
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { randomInt } from "node:crypto";
|
import { randomInt } from "node:crypto";
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import { Html } from "@elysiajs/html";
|
|
||||||
import { jwt } from "@elysiajs/jwt";
|
import { jwt } from "@elysiajs/jwt";
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { BaseHtml } from "../components/base";
|
import { BaseHtml } from "../components/base";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/dvisvgm";
|
import { convert } from "../../src/converters/dvisvgm";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { expect } from "bun:test";
|
import { expect } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { ConvertFnWithExecFile, ExecFileFn } from "../../../src/converters/types";
|
import { ConvertFnWithExecFile, ExecFileFn } from "../../../src/converters/types";
|
||||||
|
|
||||||
export async function runConvertSuccessTest(convertFn: ConvertFnWithExecFile) {
|
export async function runConvertSuccessTest(convertFn: ConvertFnWithExecFile) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/imagemagick";
|
import { convert } from "../../src/converters/imagemagick";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/libjxl";
|
import { convert } from "../../src/converters/libjxl";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { expect, test } from "bun:test";
|
import { expect, test } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { convert } from "../../src/converters/msgconvert";
|
import { convert } from "../../src/converters/msgconvert";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ExecFileException } from "node:child_process";
|
|
||||||
import { beforeEach, expect, test } from "bun:test";
|
import { beforeEach, expect, test } from "bun:test";
|
||||||
|
import type { ExecFileException } from "node:child_process";
|
||||||
import { ExecFileFn } from "../../src/converters/types";
|
import { ExecFileFn } from "../../src/converters/types";
|
||||||
import { convert } from "../../src/converters/vips";
|
import { convert } from "../../src/converters/vips";
|
||||||
import { runCommonTests } from "./helpers/commonTests";
|
import { runCommonTests } from "./helpers/commonTests";
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,8 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "react",
|
"jsx": "react-jsx",
|
||||||
"jsxFactory": "Html.createElement",
|
"jsxImportSource": "@kitajs/html",
|
||||||
"jsxFragmentFactory": "Html.Fragment",
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
@ -30,5 +29,5 @@
|
||||||
"esModuleInterop": true
|
"esModuleInterop": true
|
||||||
// "noImplicitReturns": true
|
// "noImplicitReturns": true
|
||||||
},
|
},
|
||||||
"include": ["src", "tests", "package.json", "reset.d.ts"]
|
"include": ["src", "tests", "package.json"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue