chore: add devcontainer
This commit is contained in:
parent
78bac9c9ca
commit
d984891791
90 changed files with 8511 additions and 8368 deletions
|
|
@ -1,44 +1,44 @@
|
|||
import { Html } from "@elysiajs/html";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
export const BaseHtml = ({
|
||||
children,
|
||||
title = "ConvertX",
|
||||
webroot = "",
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
title?: string;
|
||||
webroot?: string;
|
||||
}) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="webroot" content={webroot} />
|
||||
<title safe>{title}</title>
|
||||
<link rel="stylesheet" href={`${webroot}/generated.css`} />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={`${webroot}/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${webroot}/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${webroot}/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${webroot}/site.webmanifest`} />
|
||||
</head>
|
||||
<body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}>
|
||||
{children}
|
||||
<footer class="w-full">
|
||||
<div class="p-4 text-center text-sm text-neutral-500">
|
||||
<span>Powered by </span>
|
||||
<a
|
||||
href="https://github.com/C4illin/ConvertX"
|
||||
class={`
|
||||
text-neutral-400
|
||||
hover:text-accent-500
|
||||
`}
|
||||
>
|
||||
ConvertX{" "}
|
||||
</a>
|
||||
<span safe>v{version || ""}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
export const BaseHtml = ({
|
||||
children,
|
||||
title = "ConvertX",
|
||||
webroot = "",
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
title?: string;
|
||||
webroot?: string;
|
||||
}) => (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="webroot" content={webroot} />
|
||||
<title safe>{title}</title>
|
||||
<link rel="stylesheet" href={`${webroot}/generated.css`} />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href={`${webroot}/apple-touch-icon.png`} />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href={`${webroot}/favicon-32x32.png`} />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href={`${webroot}/favicon-16x16.png`} />
|
||||
<link rel="manifest" href={`${webroot}/site.webmanifest`} />
|
||||
</head>
|
||||
<body class={`flex min-h-screen w-full flex-col bg-neutral-900 text-neutral-200`}>
|
||||
{children}
|
||||
<footer class="w-full">
|
||||
<div class="p-4 text-center text-sm text-neutral-500">
|
||||
<span>Powered by </span>
|
||||
<a
|
||||
href="https://github.com/C4illin/ConvertX"
|
||||
class={`
|
||||
text-neutral-400
|
||||
hover:text-accent-500
|
||||
`}
|
||||
>
|
||||
ConvertX{" "}
|
||||
</a>
|
||||
<span safe>v{version || ""}</span>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,106 +1,106 @@
|
|||
import { Html } from "@kitajs/html";
|
||||
|
||||
export const Header = ({
|
||||
loggedIn,
|
||||
accountRegistration,
|
||||
allowUnauthenticated,
|
||||
hideHistory,
|
||||
webroot = "",
|
||||
}: {
|
||||
loggedIn?: boolean;
|
||||
accountRegistration?: boolean;
|
||||
allowUnauthenticated?: boolean;
|
||||
hideHistory?: boolean;
|
||||
webroot?: string;
|
||||
}) => {
|
||||
let rightNav: JSX.Element;
|
||||
if (loggedIn) {
|
||||
rightNav = (
|
||||
<ul class="flex gap-4">
|
||||
{!hideHistory && (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/history`}
|
||||
>
|
||||
History
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/account`}
|
||||
>
|
||||
Account
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/logoff`}
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
rightNav = (
|
||||
<ul class="flex gap-4">
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/login`}
|
||||
>
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
{accountRegistration ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/register`}
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<header class="w-full p-4">
|
||||
<nav class={`mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4`}>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a href={`${webroot}/`}>ConvertX</a>
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
{rightNav}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
import { Html } from "@kitajs/html";
|
||||
|
||||
export const Header = ({
|
||||
loggedIn,
|
||||
accountRegistration,
|
||||
allowUnauthenticated,
|
||||
hideHistory,
|
||||
webroot = "",
|
||||
}: {
|
||||
loggedIn?: boolean;
|
||||
accountRegistration?: boolean;
|
||||
allowUnauthenticated?: boolean;
|
||||
hideHistory?: boolean;
|
||||
webroot?: string;
|
||||
}) => {
|
||||
let rightNav: JSX.Element;
|
||||
if (loggedIn) {
|
||||
rightNav = (
|
||||
<ul class="flex gap-4">
|
||||
{!hideHistory && (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/history`}
|
||||
>
|
||||
History
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/account`}
|
||||
>
|
||||
Account
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/logoff`}
|
||||
>
|
||||
Logout
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
);
|
||||
} else {
|
||||
rightNav = (
|
||||
<ul class="flex gap-4">
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/login`}
|
||||
>
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
{accountRegistration ? (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/register`}
|
||||
>
|
||||
Register
|
||||
</a>
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<header class="w-full p-4">
|
||||
<nav class={`mx-auto flex max-w-4xl justify-between rounded-sm bg-neutral-900 p-4`}>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>
|
||||
<a href={`${webroot}/`}>ConvertX</a>
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
{rightNav}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,140 +1,140 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
object: [
|
||||
"3d",
|
||||
"3ds",
|
||||
"3mf",
|
||||
"ac",
|
||||
"ac3d",
|
||||
"acc",
|
||||
"amf",
|
||||
"amj",
|
||||
"ase",
|
||||
"ask",
|
||||
"assbin",
|
||||
"b3d",
|
||||
"blend",
|
||||
"bsp",
|
||||
"bvh",
|
||||
"cob",
|
||||
"csm",
|
||||
"dae",
|
||||
"dxf",
|
||||
"enff",
|
||||
"fbx",
|
||||
"glb",
|
||||
"gltf",
|
||||
"hmb",
|
||||
"hmp",
|
||||
"ifc",
|
||||
"ifczip",
|
||||
"iqm",
|
||||
"irr",
|
||||
"irrmesh",
|
||||
"lwo",
|
||||
"lws",
|
||||
"lxo",
|
||||
"m3d",
|
||||
"md2",
|
||||
"md3",
|
||||
"md5anim",
|
||||
"md5camera",
|
||||
"md5mesh",
|
||||
"mdc",
|
||||
"mdl",
|
||||
"mesh.xml",
|
||||
"mesh",
|
||||
"mot",
|
||||
"ms3d",
|
||||
"ndo",
|
||||
"nff",
|
||||
"obj",
|
||||
"off",
|
||||
"ogex",
|
||||
"pk3",
|
||||
"ply",
|
||||
"pmx",
|
||||
"prj",
|
||||
"q3o",
|
||||
"q3s",
|
||||
"raw",
|
||||
"scn",
|
||||
"sib",
|
||||
"smd",
|
||||
"step",
|
||||
"stl",
|
||||
"stp",
|
||||
"ter",
|
||||
"uc",
|
||||
"usd",
|
||||
"usda",
|
||||
"usdc",
|
||||
"usdz",
|
||||
"vta",
|
||||
"x",
|
||||
"x3d",
|
||||
"x3db",
|
||||
"xgl",
|
||||
"xml",
|
||||
"zae",
|
||||
"zgl",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
object: [
|
||||
"3ds",
|
||||
"3mf",
|
||||
"assbin",
|
||||
"assjson",
|
||||
"assxml",
|
||||
"collada",
|
||||
"dae",
|
||||
"fbx",
|
||||
"fbxa",
|
||||
"glb",
|
||||
"glb2",
|
||||
"gltf",
|
||||
"gltf2",
|
||||
"json",
|
||||
"obj",
|
||||
"objnomtl",
|
||||
"pbrt",
|
||||
"ply",
|
||||
"plyb",
|
||||
"stl",
|
||||
"stlb",
|
||||
"stp",
|
||||
"x",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
object: [
|
||||
"3d",
|
||||
"3ds",
|
||||
"3mf",
|
||||
"ac",
|
||||
"ac3d",
|
||||
"acc",
|
||||
"amf",
|
||||
"amj",
|
||||
"ase",
|
||||
"ask",
|
||||
"assbin",
|
||||
"b3d",
|
||||
"blend",
|
||||
"bsp",
|
||||
"bvh",
|
||||
"cob",
|
||||
"csm",
|
||||
"dae",
|
||||
"dxf",
|
||||
"enff",
|
||||
"fbx",
|
||||
"glb",
|
||||
"gltf",
|
||||
"hmb",
|
||||
"hmp",
|
||||
"ifc",
|
||||
"ifczip",
|
||||
"iqm",
|
||||
"irr",
|
||||
"irrmesh",
|
||||
"lwo",
|
||||
"lws",
|
||||
"lxo",
|
||||
"m3d",
|
||||
"md2",
|
||||
"md3",
|
||||
"md5anim",
|
||||
"md5camera",
|
||||
"md5mesh",
|
||||
"mdc",
|
||||
"mdl",
|
||||
"mesh.xml",
|
||||
"mesh",
|
||||
"mot",
|
||||
"ms3d",
|
||||
"ndo",
|
||||
"nff",
|
||||
"obj",
|
||||
"off",
|
||||
"ogex",
|
||||
"pk3",
|
||||
"ply",
|
||||
"pmx",
|
||||
"prj",
|
||||
"q3o",
|
||||
"q3s",
|
||||
"raw",
|
||||
"scn",
|
||||
"sib",
|
||||
"smd",
|
||||
"step",
|
||||
"stl",
|
||||
"stp",
|
||||
"ter",
|
||||
"uc",
|
||||
"usd",
|
||||
"usda",
|
||||
"usdc",
|
||||
"usdz",
|
||||
"vta",
|
||||
"x",
|
||||
"x3d",
|
||||
"x3db",
|
||||
"xgl",
|
||||
"xml",
|
||||
"zae",
|
||||
"zgl",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
object: [
|
||||
"3ds",
|
||||
"3mf",
|
||||
"assbin",
|
||||
"assjson",
|
||||
"assxml",
|
||||
"collada",
|
||||
"dae",
|
||||
"fbx",
|
||||
"fbxa",
|
||||
"glb",
|
||||
"glb2",
|
||||
"gltf",
|
||||
"gltf2",
|
||||
"json",
|
||||
"obj",
|
||||
"objnomtl",
|
||||
"pbrt",
|
||||
"ply",
|
||||
"plyb",
|
||||
"stl",
|
||||
"stlb",
|
||||
"stp",
|
||||
"x",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,86 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: [
|
||||
"azw4",
|
||||
"chm",
|
||||
"cbr",
|
||||
"cbz",
|
||||
"cbt",
|
||||
"cba",
|
||||
"cb7",
|
||||
"djvu",
|
||||
"docx",
|
||||
"epub",
|
||||
"fb2",
|
||||
"htlz",
|
||||
"html",
|
||||
"lit",
|
||||
"lrf",
|
||||
"mobi",
|
||||
"odt",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pml",
|
||||
"rb",
|
||||
"rtf",
|
||||
"recipe",
|
||||
"snb",
|
||||
"tcr",
|
||||
"txt",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
document: [
|
||||
"azw3",
|
||||
"docx",
|
||||
"epub",
|
||||
"fb2",
|
||||
"html",
|
||||
"htmlz",
|
||||
"kepub.epub",
|
||||
"lit",
|
||||
"lrf",
|
||||
"mobi",
|
||||
"oeb",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pml",
|
||||
"rb",
|
||||
"rtf",
|
||||
"snb",
|
||||
"tcr",
|
||||
"txt",
|
||||
"txtz",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: [
|
||||
"azw4",
|
||||
"chm",
|
||||
"cbr",
|
||||
"cbz",
|
||||
"cbt",
|
||||
"cba",
|
||||
"cb7",
|
||||
"djvu",
|
||||
"docx",
|
||||
"epub",
|
||||
"fb2",
|
||||
"htlz",
|
||||
"html",
|
||||
"lit",
|
||||
"lrf",
|
||||
"mobi",
|
||||
"odt",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pml",
|
||||
"rb",
|
||||
"rtf",
|
||||
"recipe",
|
||||
"snb",
|
||||
"tcr",
|
||||
"txt",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
document: [
|
||||
"azw3",
|
||||
"docx",
|
||||
"epub",
|
||||
"fb2",
|
||||
"html",
|
||||
"htmlz",
|
||||
"kepub.epub",
|
||||
"lit",
|
||||
"lrf",
|
||||
"mobi",
|
||||
"oeb",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pml",
|
||||
"rb",
|
||||
"rtf",
|
||||
"snb",
|
||||
"tcr",
|
||||
"txt",
|
||||
"txtz",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("ebook-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
import fs from "fs";
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: ["yaml", "toml", "json", "xml", "csv"],
|
||||
},
|
||||
to: {
|
||||
document: ["yaml", "toml", "json", "csv"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const args: string[] = [];
|
||||
|
||||
args.push("--file", filePath);
|
||||
args.push("--read", fileType);
|
||||
args.push("--write", convertTo);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("dasel", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
fs.writeFile(targetPath, stdout, (err: NodeJS.ErrnoException | null) => {
|
||||
if (err) {
|
||||
reject(`Failed to write output: ${err}`);
|
||||
} else {
|
||||
resolve("Done");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
import fs from "fs";
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: ["yaml", "toml", "json", "xml", "csv"],
|
||||
},
|
||||
to: {
|
||||
document: ["yaml", "toml", "json", "csv"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const args: string[] = [];
|
||||
|
||||
args.push("--file", filePath);
|
||||
args.push("--read", fileType);
|
||||
args.push("--write", convertTo);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("dasel", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
fs.writeFile(targetPath, stdout, (err: NodeJS.ErrnoException | null) => {
|
||||
if (err) {
|
||||
reject(`Failed to write output: ${err}`);
|
||||
} else {
|
||||
resolve("Done");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,49 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["dvi", "xdv", "pdf", "eps"],
|
||||
},
|
||||
to: {
|
||||
images: ["svg", "svgz"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const inputArgs: string[] = [];
|
||||
if (fileType === "eps") {
|
||||
inputArgs.push("--eps");
|
||||
}
|
||||
if (fileType === "pdf") {
|
||||
inputArgs.push("--pdf");
|
||||
}
|
||||
if (convertTo === "svgz") {
|
||||
inputArgs.push("-z");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["dvi", "xdv", "pdf", "eps"],
|
||||
},
|
||||
to: {
|
||||
images: ["svg", "svgz"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const inputArgs: string[] = [];
|
||||
if (fileType === "eps") {
|
||||
inputArgs.push("--eps");
|
||||
}
|
||||
if (fileType === "pdf") {
|
||||
inputArgs.push("--pdf");
|
||||
}
|
||||
if (convertTo === "svgz") {
|
||||
inputArgs.push("-z");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("dvisvgm", [...inputArgs, filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,337 +1,337 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
image: [
|
||||
"3fr",
|
||||
"8bim",
|
||||
"8bimtext",
|
||||
"8bimwtext",
|
||||
"app1",
|
||||
"app1jpeg",
|
||||
"art",
|
||||
"arw",
|
||||
"avs",
|
||||
"b",
|
||||
"bie",
|
||||
"bigtiff",
|
||||
"bmp",
|
||||
"c",
|
||||
"cals",
|
||||
"caption",
|
||||
"cin",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cr2",
|
||||
"crw",
|
||||
"cur",
|
||||
"cut",
|
||||
"dcm",
|
||||
"dcr",
|
||||
"dcx",
|
||||
"dng",
|
||||
"dpx",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"erf",
|
||||
"exif",
|
||||
"fax",
|
||||
"file",
|
||||
"fits",
|
||||
"fractal",
|
||||
"ftp",
|
||||
"g",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gradient",
|
||||
"gray",
|
||||
"graya",
|
||||
"heic",
|
||||
"heif",
|
||||
"hrz",
|
||||
"http",
|
||||
"icb",
|
||||
"icc",
|
||||
"icm",
|
||||
"ico",
|
||||
"icon",
|
||||
"identity",
|
||||
"image",
|
||||
"iptc",
|
||||
"iptctext",
|
||||
"iptcwtext",
|
||||
"jbg",
|
||||
"jbig",
|
||||
"jng",
|
||||
"jnx",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"k",
|
||||
"k25",
|
||||
"kdc",
|
||||
"label",
|
||||
"m",
|
||||
"mac",
|
||||
"map",
|
||||
"mat",
|
||||
"mef",
|
||||
"miff",
|
||||
"mng",
|
||||
"mono",
|
||||
"mpc",
|
||||
"mrw",
|
||||
"msl",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"nef",
|
||||
"null",
|
||||
"o",
|
||||
"orf",
|
||||
"otb",
|
||||
"p7",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pef",
|
||||
"pfa",
|
||||
"pfb",
|
||||
"pgm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pix",
|
||||
"plasma",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"ppm",
|
||||
"ps",
|
||||
"ptif",
|
||||
"pwp",
|
||||
"r",
|
||||
"raf",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"rla",
|
||||
"rle",
|
||||
"sct",
|
||||
"sfw",
|
||||
"sgi",
|
||||
"sr2",
|
||||
"srf",
|
||||
"stegano",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"text",
|
||||
"tga",
|
||||
"tif",
|
||||
"tiff",
|
||||
"tile",
|
||||
"tim",
|
||||
"topol",
|
||||
"ttf",
|
||||
"txt",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webp",
|
||||
"wmf",
|
||||
"wpg",
|
||||
"x3f",
|
||||
"xbm",
|
||||
"xc",
|
||||
"xcf",
|
||||
"xmp",
|
||||
"xpm",
|
||||
"xv",
|
||||
"xwd",
|
||||
"y",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
image: [
|
||||
"8bim",
|
||||
"8bimtext",
|
||||
"8bimwtext",
|
||||
"app1",
|
||||
"app1jpeg",
|
||||
"art",
|
||||
"avs",
|
||||
"b",
|
||||
"bie",
|
||||
"bigtiff",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"brf",
|
||||
"c",
|
||||
"cals",
|
||||
"cin",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"dcx",
|
||||
"dpx",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"eps2",
|
||||
"eps3",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"exif",
|
||||
"fax",
|
||||
"fits",
|
||||
"g",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gray",
|
||||
"graya",
|
||||
"histogram",
|
||||
"html",
|
||||
"icb",
|
||||
"icc",
|
||||
"icm",
|
||||
"info",
|
||||
"iptc",
|
||||
"iptctext",
|
||||
"iptcwtext",
|
||||
"isobrl",
|
||||
"isobrl6",
|
||||
"jbg",
|
||||
"jbig",
|
||||
"jng",
|
||||
"jpeg",
|
||||
"k",
|
||||
"m",
|
||||
"m2v",
|
||||
"map",
|
||||
"mat",
|
||||
"matte",
|
||||
"miff",
|
||||
"mng",
|
||||
"mono",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"msl",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"null",
|
||||
"o",
|
||||
"otb",
|
||||
"p7",
|
||||
"pal",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pgm",
|
||||
"picon",
|
||||
"pict",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"ppm",
|
||||
"preview",
|
||||
"ps",
|
||||
"ps2",
|
||||
"ps3",
|
||||
"ptif",
|
||||
"r",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"sgi",
|
||||
"shtml",
|
||||
"sun",
|
||||
"text",
|
||||
"tga",
|
||||
"tiff",
|
||||
"txt",
|
||||
"ubrl",
|
||||
"ubrl6",
|
||||
"uil",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webp",
|
||||
"x",
|
||||
"xbm",
|
||||
"xmp",
|
||||
"xpm",
|
||||
"xv",
|
||||
"xwd",
|
||||
"y",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
image: [
|
||||
"3fr",
|
||||
"8bim",
|
||||
"8bimtext",
|
||||
"8bimwtext",
|
||||
"app1",
|
||||
"app1jpeg",
|
||||
"art",
|
||||
"arw",
|
||||
"avs",
|
||||
"b",
|
||||
"bie",
|
||||
"bigtiff",
|
||||
"bmp",
|
||||
"c",
|
||||
"cals",
|
||||
"caption",
|
||||
"cin",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cr2",
|
||||
"crw",
|
||||
"cur",
|
||||
"cut",
|
||||
"dcm",
|
||||
"dcr",
|
||||
"dcx",
|
||||
"dng",
|
||||
"dpx",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"erf",
|
||||
"exif",
|
||||
"fax",
|
||||
"file",
|
||||
"fits",
|
||||
"fractal",
|
||||
"ftp",
|
||||
"g",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gradient",
|
||||
"gray",
|
||||
"graya",
|
||||
"heic",
|
||||
"heif",
|
||||
"hrz",
|
||||
"http",
|
||||
"icb",
|
||||
"icc",
|
||||
"icm",
|
||||
"ico",
|
||||
"icon",
|
||||
"identity",
|
||||
"image",
|
||||
"iptc",
|
||||
"iptctext",
|
||||
"iptcwtext",
|
||||
"jbg",
|
||||
"jbig",
|
||||
"jng",
|
||||
"jnx",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"k",
|
||||
"k25",
|
||||
"kdc",
|
||||
"label",
|
||||
"m",
|
||||
"mac",
|
||||
"map",
|
||||
"mat",
|
||||
"mef",
|
||||
"miff",
|
||||
"mng",
|
||||
"mono",
|
||||
"mpc",
|
||||
"mrw",
|
||||
"msl",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"nef",
|
||||
"null",
|
||||
"o",
|
||||
"orf",
|
||||
"otb",
|
||||
"p7",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pef",
|
||||
"pfa",
|
||||
"pfb",
|
||||
"pgm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pix",
|
||||
"plasma",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"ppm",
|
||||
"ps",
|
||||
"ptif",
|
||||
"pwp",
|
||||
"r",
|
||||
"raf",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"rla",
|
||||
"rle",
|
||||
"sct",
|
||||
"sfw",
|
||||
"sgi",
|
||||
"sr2",
|
||||
"srf",
|
||||
"stegano",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"text",
|
||||
"tga",
|
||||
"tif",
|
||||
"tiff",
|
||||
"tile",
|
||||
"tim",
|
||||
"topol",
|
||||
"ttf",
|
||||
"txt",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webp",
|
||||
"wmf",
|
||||
"wpg",
|
||||
"x3f",
|
||||
"xbm",
|
||||
"xc",
|
||||
"xcf",
|
||||
"xmp",
|
||||
"xpm",
|
||||
"xv",
|
||||
"xwd",
|
||||
"y",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
image: [
|
||||
"8bim",
|
||||
"8bimtext",
|
||||
"8bimwtext",
|
||||
"app1",
|
||||
"app1jpeg",
|
||||
"art",
|
||||
"avs",
|
||||
"b",
|
||||
"bie",
|
||||
"bigtiff",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"brf",
|
||||
"c",
|
||||
"cals",
|
||||
"cin",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"dcx",
|
||||
"dpx",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"eps2",
|
||||
"eps3",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"exif",
|
||||
"fax",
|
||||
"fits",
|
||||
"g",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gray",
|
||||
"graya",
|
||||
"histogram",
|
||||
"html",
|
||||
"icb",
|
||||
"icc",
|
||||
"icm",
|
||||
"info",
|
||||
"iptc",
|
||||
"iptctext",
|
||||
"iptcwtext",
|
||||
"isobrl",
|
||||
"isobrl6",
|
||||
"jbg",
|
||||
"jbig",
|
||||
"jng",
|
||||
"jpeg",
|
||||
"k",
|
||||
"m",
|
||||
"m2v",
|
||||
"map",
|
||||
"mat",
|
||||
"matte",
|
||||
"miff",
|
||||
"mng",
|
||||
"mono",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"msl",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"null",
|
||||
"o",
|
||||
"otb",
|
||||
"p7",
|
||||
"pal",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pgm",
|
||||
"picon",
|
||||
"pict",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"ppm",
|
||||
"preview",
|
||||
"ps",
|
||||
"ps2",
|
||||
"ps3",
|
||||
"ptif",
|
||||
"r",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"sgi",
|
||||
"shtml",
|
||||
"sun",
|
||||
"text",
|
||||
"tga",
|
||||
"tiff",
|
||||
"txt",
|
||||
"ubrl",
|
||||
"ubrl6",
|
||||
"uil",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webp",
|
||||
"x",
|
||||
"xbm",
|
||||
"xmp",
|
||||
"xpm",
|
||||
"xv",
|
||||
"xwd",
|
||||
"y",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,492 +1,492 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
images: [
|
||||
"3fr",
|
||||
"3g2",
|
||||
"3gp",
|
||||
"aai",
|
||||
"ai",
|
||||
"apng",
|
||||
"art",
|
||||
"arw",
|
||||
"avci",
|
||||
"avi",
|
||||
"avif",
|
||||
"avs",
|
||||
"bayer",
|
||||
"bayera",
|
||||
"bgr",
|
||||
"bgra",
|
||||
"bgro",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"cal",
|
||||
"cals",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cin",
|
||||
"clip",
|
||||
"clipboard",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cr2",
|
||||
"cr3",
|
||||
"crw",
|
||||
"cube",
|
||||
"cur",
|
||||
"cut",
|
||||
"data",
|
||||
"dcm",
|
||||
"dcr",
|
||||
"dcraw",
|
||||
"dcx",
|
||||
"dds",
|
||||
"dfont",
|
||||
"dng",
|
||||
"dpx",
|
||||
"dxt1",
|
||||
"dxt5",
|
||||
"emf",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"erf",
|
||||
"exr",
|
||||
"farbfeld",
|
||||
"fax",
|
||||
"ff",
|
||||
"fff",
|
||||
"file",
|
||||
"fits",
|
||||
"fl32",
|
||||
"flif",
|
||||
"flv",
|
||||
"fractal",
|
||||
"ftp",
|
||||
"fts",
|
||||
"ftxt",
|
||||
"g3",
|
||||
"g4",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gradient",
|
||||
"gray",
|
||||
"graya",
|
||||
"group4",
|
||||
"hald",
|
||||
"hdr",
|
||||
"heic",
|
||||
"heif",
|
||||
"hrz",
|
||||
"http",
|
||||
"https",
|
||||
"icb",
|
||||
"ico",
|
||||
"icon",
|
||||
"iiq",
|
||||
"inline",
|
||||
"ipl",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jng",
|
||||
"jnx",
|
||||
"jp2",
|
||||
"jpc",
|
||||
"jpe",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"jpm",
|
||||
"jps",
|
||||
"jpt",
|
||||
"jxl",
|
||||
"k25",
|
||||
"kdc",
|
||||
"label",
|
||||
"m2v",
|
||||
"m4v",
|
||||
"mac",
|
||||
"map",
|
||||
"mask",
|
||||
"mat",
|
||||
"mdc",
|
||||
"mef",
|
||||
"miff",
|
||||
"mkv",
|
||||
"mng",
|
||||
"mono",
|
||||
"mos",
|
||||
"mov",
|
||||
"mp4",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"mpo",
|
||||
"mrw",
|
||||
"msl",
|
||||
"msvg",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"nef",
|
||||
"nrw",
|
||||
"null",
|
||||
"ora",
|
||||
"orf",
|
||||
"otb",
|
||||
"otf",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pango",
|
||||
"pattern",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pdfa",
|
||||
"pef",
|
||||
"pes",
|
||||
"pfa",
|
||||
"pfb",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pgx",
|
||||
"phm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pix",
|
||||
"pjpeg",
|
||||
"plasma",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"pocketmod",
|
||||
"ppm",
|
||||
"ps",
|
||||
"psb",
|
||||
"psd",
|
||||
"ptif",
|
||||
"pwp",
|
||||
"qoi",
|
||||
"radial",
|
||||
"raf",
|
||||
"ras",
|
||||
"raw",
|
||||
"rgb",
|
||||
"rgb565",
|
||||
"rgba",
|
||||
"rgbo",
|
||||
"rgf",
|
||||
"rla",
|
||||
"rle",
|
||||
"rmf",
|
||||
"rsvg",
|
||||
"rw2",
|
||||
"rwl",
|
||||
"scr",
|
||||
"screenshot",
|
||||
"sct",
|
||||
"sfw",
|
||||
"sgi",
|
||||
"six",
|
||||
"sixel",
|
||||
"sr2",
|
||||
"srf",
|
||||
"srw",
|
||||
"stegano",
|
||||
"sti",
|
||||
"strimg",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"text",
|
||||
"tga",
|
||||
"tiff",
|
||||
"tiff64",
|
||||
"tile",
|
||||
"tim",
|
||||
"tm2",
|
||||
"ttc",
|
||||
"ttf",
|
||||
"txt",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vips",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webm",
|
||||
"webp",
|
||||
"wmf",
|
||||
"wmv",
|
||||
"wpg",
|
||||
"x3f",
|
||||
"xbm",
|
||||
"xc",
|
||||
"xcf",
|
||||
"xpm",
|
||||
"xps",
|
||||
"xv",
|
||||
"ycbcr",
|
||||
"ycbcra",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"aai",
|
||||
"ai",
|
||||
"apng",
|
||||
"art",
|
||||
"ashlar",
|
||||
"avif",
|
||||
"avs",
|
||||
"bayer",
|
||||
"bayera",
|
||||
"bgr",
|
||||
"bgra",
|
||||
"bgro",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"brf",
|
||||
"cal",
|
||||
"cals",
|
||||
"cin",
|
||||
"cip",
|
||||
"clip",
|
||||
"clipboard",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cur",
|
||||
"data",
|
||||
"dcx",
|
||||
"dds",
|
||||
"dpx",
|
||||
"dxt1",
|
||||
"dxt5",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"eps2",
|
||||
"eps3",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"exr",
|
||||
"farbfeld",
|
||||
"fax",
|
||||
"ff",
|
||||
"fits",
|
||||
"fl32",
|
||||
"flif",
|
||||
"flv",
|
||||
"fts",
|
||||
"ftxt",
|
||||
"g3",
|
||||
"g4",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gray",
|
||||
"graya",
|
||||
"group4",
|
||||
"hdr",
|
||||
"histogram",
|
||||
"hrz",
|
||||
"htm",
|
||||
"html",
|
||||
"icb",
|
||||
"ico",
|
||||
"icon",
|
||||
"info",
|
||||
"inline",
|
||||
"ipl",
|
||||
"isobrl",
|
||||
"isobrl6",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jng",
|
||||
"jp2",
|
||||
"jpc",
|
||||
"jpe",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"jpm",
|
||||
"jps",
|
||||
"jpt",
|
||||
"json",
|
||||
"jxl",
|
||||
"m2v",
|
||||
"m4v",
|
||||
"map",
|
||||
"mask",
|
||||
"mat",
|
||||
"matte",
|
||||
"miff",
|
||||
"mkv",
|
||||
"mng",
|
||||
"mono",
|
||||
"mov",
|
||||
"mp4",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"msl",
|
||||
"msvg",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"null",
|
||||
"otb",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pdfa",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pgx",
|
||||
"phm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pjpeg",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"pocketmod",
|
||||
"ppm",
|
||||
"ps",
|
||||
"ps2",
|
||||
"ps3",
|
||||
"psb",
|
||||
"psd",
|
||||
"ptif",
|
||||
"qoi",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"rgbo",
|
||||
"rgf",
|
||||
"rsvg",
|
||||
"sgi",
|
||||
"shtml",
|
||||
"six",
|
||||
"sixel",
|
||||
"sparse",
|
||||
"strimg",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"tga",
|
||||
"thumbnail",
|
||||
"tiff",
|
||||
"tiff64",
|
||||
"txt",
|
||||
"ubrl",
|
||||
"ubrl6",
|
||||
"uil",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vips",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webm",
|
||||
"webp",
|
||||
"wmv",
|
||||
"wpg",
|
||||
"xbm",
|
||||
"xpm",
|
||||
"xv",
|
||||
"yaml",
|
||||
"ycbcr",
|
||||
"ycbcra",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
let outputArgs: string[] = [];
|
||||
let inputArgs: string[] = [];
|
||||
|
||||
if (convertTo === "ico") {
|
||||
outputArgs = ["-define", "icon:auto-resize=256,128,64,48,32,16", "-background", "none"];
|
||||
|
||||
if (fileType === "svg") {
|
||||
// this might be a bit too much, but it works
|
||||
inputArgs = ["-background", "none", "-density", "512"];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EMF files specifically to avoid LibreOffice delegate issues
|
||||
if (fileType === "emf") {
|
||||
// Use direct conversion without delegates for EMF files
|
||||
inputArgs.push("-define", "emf:delegate=false", "-density", "300");
|
||||
outputArgs.push("-background", "white", "-alpha", "remove");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
"magick",
|
||||
[...inputArgs, filePath, ...outputArgs, targetPath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
images: [
|
||||
"3fr",
|
||||
"3g2",
|
||||
"3gp",
|
||||
"aai",
|
||||
"ai",
|
||||
"apng",
|
||||
"art",
|
||||
"arw",
|
||||
"avci",
|
||||
"avi",
|
||||
"avif",
|
||||
"avs",
|
||||
"bayer",
|
||||
"bayera",
|
||||
"bgr",
|
||||
"bgra",
|
||||
"bgro",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"cal",
|
||||
"cals",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cin",
|
||||
"clip",
|
||||
"clipboard",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cr2",
|
||||
"cr3",
|
||||
"crw",
|
||||
"cube",
|
||||
"cur",
|
||||
"cut",
|
||||
"data",
|
||||
"dcm",
|
||||
"dcr",
|
||||
"dcraw",
|
||||
"dcx",
|
||||
"dds",
|
||||
"dfont",
|
||||
"dng",
|
||||
"dpx",
|
||||
"dxt1",
|
||||
"dxt5",
|
||||
"emf",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"erf",
|
||||
"exr",
|
||||
"farbfeld",
|
||||
"fax",
|
||||
"ff",
|
||||
"fff",
|
||||
"file",
|
||||
"fits",
|
||||
"fl32",
|
||||
"flif",
|
||||
"flv",
|
||||
"fractal",
|
||||
"ftp",
|
||||
"fts",
|
||||
"ftxt",
|
||||
"g3",
|
||||
"g4",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gradient",
|
||||
"gray",
|
||||
"graya",
|
||||
"group4",
|
||||
"hald",
|
||||
"hdr",
|
||||
"heic",
|
||||
"heif",
|
||||
"hrz",
|
||||
"http",
|
||||
"https",
|
||||
"icb",
|
||||
"ico",
|
||||
"icon",
|
||||
"iiq",
|
||||
"inline",
|
||||
"ipl",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jng",
|
||||
"jnx",
|
||||
"jp2",
|
||||
"jpc",
|
||||
"jpe",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"jpm",
|
||||
"jps",
|
||||
"jpt",
|
||||
"jxl",
|
||||
"k25",
|
||||
"kdc",
|
||||
"label",
|
||||
"m2v",
|
||||
"m4v",
|
||||
"mac",
|
||||
"map",
|
||||
"mask",
|
||||
"mat",
|
||||
"mdc",
|
||||
"mef",
|
||||
"miff",
|
||||
"mkv",
|
||||
"mng",
|
||||
"mono",
|
||||
"mos",
|
||||
"mov",
|
||||
"mp4",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"mpo",
|
||||
"mrw",
|
||||
"msl",
|
||||
"msvg",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"nef",
|
||||
"nrw",
|
||||
"null",
|
||||
"ora",
|
||||
"orf",
|
||||
"otb",
|
||||
"otf",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pango",
|
||||
"pattern",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pdfa",
|
||||
"pef",
|
||||
"pes",
|
||||
"pfa",
|
||||
"pfb",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pgx",
|
||||
"phm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pix",
|
||||
"pjpeg",
|
||||
"plasma",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"pocketmod",
|
||||
"ppm",
|
||||
"ps",
|
||||
"psb",
|
||||
"psd",
|
||||
"ptif",
|
||||
"pwp",
|
||||
"qoi",
|
||||
"radial",
|
||||
"raf",
|
||||
"ras",
|
||||
"raw",
|
||||
"rgb",
|
||||
"rgb565",
|
||||
"rgba",
|
||||
"rgbo",
|
||||
"rgf",
|
||||
"rla",
|
||||
"rle",
|
||||
"rmf",
|
||||
"rsvg",
|
||||
"rw2",
|
||||
"rwl",
|
||||
"scr",
|
||||
"screenshot",
|
||||
"sct",
|
||||
"sfw",
|
||||
"sgi",
|
||||
"six",
|
||||
"sixel",
|
||||
"sr2",
|
||||
"srf",
|
||||
"srw",
|
||||
"stegano",
|
||||
"sti",
|
||||
"strimg",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"text",
|
||||
"tga",
|
||||
"tiff",
|
||||
"tiff64",
|
||||
"tile",
|
||||
"tim",
|
||||
"tm2",
|
||||
"ttc",
|
||||
"ttf",
|
||||
"txt",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vips",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webm",
|
||||
"webp",
|
||||
"wmf",
|
||||
"wmv",
|
||||
"wpg",
|
||||
"x3f",
|
||||
"xbm",
|
||||
"xc",
|
||||
"xcf",
|
||||
"xpm",
|
||||
"xps",
|
||||
"xv",
|
||||
"ycbcr",
|
||||
"ycbcra",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"aai",
|
||||
"ai",
|
||||
"apng",
|
||||
"art",
|
||||
"ashlar",
|
||||
"avif",
|
||||
"avs",
|
||||
"bayer",
|
||||
"bayera",
|
||||
"bgr",
|
||||
"bgra",
|
||||
"bgro",
|
||||
"bmp",
|
||||
"bmp2",
|
||||
"bmp3",
|
||||
"brf",
|
||||
"cal",
|
||||
"cals",
|
||||
"cin",
|
||||
"cip",
|
||||
"clip",
|
||||
"clipboard",
|
||||
"cmyk",
|
||||
"cmyka",
|
||||
"cur",
|
||||
"data",
|
||||
"dcx",
|
||||
"dds",
|
||||
"dpx",
|
||||
"dxt1",
|
||||
"dxt5",
|
||||
"epdf",
|
||||
"epi",
|
||||
"eps",
|
||||
"eps2",
|
||||
"eps3",
|
||||
"epsf",
|
||||
"epsi",
|
||||
"ept",
|
||||
"ept2",
|
||||
"ept3",
|
||||
"exr",
|
||||
"farbfeld",
|
||||
"fax",
|
||||
"ff",
|
||||
"fits",
|
||||
"fl32",
|
||||
"flif",
|
||||
"flv",
|
||||
"fts",
|
||||
"ftxt",
|
||||
"g3",
|
||||
"g4",
|
||||
"gif",
|
||||
"gif87",
|
||||
"gray",
|
||||
"graya",
|
||||
"group4",
|
||||
"hdr",
|
||||
"histogram",
|
||||
"hrz",
|
||||
"htm",
|
||||
"html",
|
||||
"icb",
|
||||
"ico",
|
||||
"icon",
|
||||
"info",
|
||||
"inline",
|
||||
"ipl",
|
||||
"isobrl",
|
||||
"isobrl6",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jng",
|
||||
"jp2",
|
||||
"jpc",
|
||||
"jpe",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"jpm",
|
||||
"jps",
|
||||
"jpt",
|
||||
"json",
|
||||
"jxl",
|
||||
"m2v",
|
||||
"m4v",
|
||||
"map",
|
||||
"mask",
|
||||
"mat",
|
||||
"matte",
|
||||
"miff",
|
||||
"mkv",
|
||||
"mng",
|
||||
"mono",
|
||||
"mov",
|
||||
"mp4",
|
||||
"mpc",
|
||||
"mpeg",
|
||||
"mpg",
|
||||
"msl",
|
||||
"msvg",
|
||||
"mtv",
|
||||
"mvg",
|
||||
"null",
|
||||
"otb",
|
||||
"pal",
|
||||
"palm",
|
||||
"pam",
|
||||
"pbm",
|
||||
"pcd",
|
||||
"pcds",
|
||||
"pcl",
|
||||
"pct",
|
||||
"pcx",
|
||||
"pdb",
|
||||
"pdf",
|
||||
"pdfa",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pgx",
|
||||
"phm",
|
||||
"picon",
|
||||
"pict",
|
||||
"pjpeg",
|
||||
"png",
|
||||
"png00",
|
||||
"png24",
|
||||
"png32",
|
||||
"png48",
|
||||
"png64",
|
||||
"png8",
|
||||
"pnm",
|
||||
"pocketmod",
|
||||
"ppm",
|
||||
"ps",
|
||||
"ps2",
|
||||
"ps3",
|
||||
"psb",
|
||||
"psd",
|
||||
"ptif",
|
||||
"qoi",
|
||||
"ras",
|
||||
"rgb",
|
||||
"rgba",
|
||||
"rgbo",
|
||||
"rgf",
|
||||
"rsvg",
|
||||
"sgi",
|
||||
"shtml",
|
||||
"six",
|
||||
"sixel",
|
||||
"sparse",
|
||||
"strimg",
|
||||
"sun",
|
||||
"svg",
|
||||
"svgz",
|
||||
"tga",
|
||||
"thumbnail",
|
||||
"tiff",
|
||||
"tiff64",
|
||||
"txt",
|
||||
"ubrl",
|
||||
"ubrl6",
|
||||
"uil",
|
||||
"uyvy",
|
||||
"vda",
|
||||
"vicar",
|
||||
"vid",
|
||||
"viff",
|
||||
"vips",
|
||||
"vst",
|
||||
"wbmp",
|
||||
"webm",
|
||||
"webp",
|
||||
"wmv",
|
||||
"wpg",
|
||||
"xbm",
|
||||
"xpm",
|
||||
"xv",
|
||||
"yaml",
|
||||
"ycbcr",
|
||||
"ycbcra",
|
||||
"yuv",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
let outputArgs: string[] = [];
|
||||
let inputArgs: string[] = [];
|
||||
|
||||
if (convertTo === "ico") {
|
||||
outputArgs = ["-define", "icon:auto-resize=256,128,64,48,32,16", "-background", "none"];
|
||||
|
||||
if (fileType === "svg") {
|
||||
// this might be a bit too much, but it works
|
||||
inputArgs = ["-background", "none", "-density", "512"];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EMF files specifically to avoid LibreOffice delegate issues
|
||||
if (fileType === "emf") {
|
||||
// Use direct conversion without delegates for EMF files
|
||||
inputArgs.push("-define", "emf:delegate=false", "-density", "300");
|
||||
outputArgs.push("-background", "white", "-alpha", "remove");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
"magick",
|
||||
[...inputArgs, filePath, ...outputArgs, targetPath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,56 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"dxf",
|
||||
"emf",
|
||||
"eps",
|
||||
"fxg",
|
||||
"gpl",
|
||||
"hpgl",
|
||||
"html",
|
||||
"odg",
|
||||
"pdf",
|
||||
"png",
|
||||
"pov",
|
||||
"ps",
|
||||
"sif",
|
||||
"svg",
|
||||
"svgz",
|
||||
"tex",
|
||||
"wmf",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["svg", "pdf", "eps", "ps", "wmf", "emf", "png"],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"dxf",
|
||||
"emf",
|
||||
"eps",
|
||||
"fxg",
|
||||
"gpl",
|
||||
"hpgl",
|
||||
"html",
|
||||
"odg",
|
||||
"pdf",
|
||||
"png",
|
||||
"pov",
|
||||
"ps",
|
||||
"sif",
|
||||
"svg",
|
||||
"svgz",
|
||||
"tex",
|
||||
"wmf",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("inkscape", [filePath, "-o", targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
import { execFile as execFileOriginal } from "child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["avci", "avcs", "avif", "h264", "heic", "heics", "heif", "heifs", "hif", "mkv", "mp4"],
|
||||
},
|
||||
to: {
|
||||
images: ["jpeg", "png", "y4m"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["avci", "avcs", "avif", "h264", "heic", "heics", "heif", "heifs", "hif", "mkv", "mp4"],
|
||||
},
|
||||
to: {
|
||||
images: ["jpeg", "png", "y4m"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("heif-convert", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
jxl: ["jxl"],
|
||||
images: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||
},
|
||||
to: {
|
||||
jxl: ["apng", "exr", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||
images: ["jxl"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
let tool = "";
|
||||
if (fileType === "jxl") {
|
||||
tool = "djxl";
|
||||
}
|
||||
|
||||
if (convertTo === "jxl") {
|
||||
tool = "cjxl";
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
jxl: ["jxl"],
|
||||
images: ["apng", "exr", "gif", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||
},
|
||||
to: {
|
||||
jxl: ["apng", "exr", "jpeg", "pam", "pfm", "pgm", "pgx", "png", "ppm"],
|
||||
images: ["jxl"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
let tool = "";
|
||||
if (fileType === "jxl") {
|
||||
tool = "djxl";
|
||||
}
|
||||
|
||||
if (convertTo === "jxl") {
|
||||
tool = "cjxl";
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(tool, [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,177 +1,177 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: [
|
||||
"602",
|
||||
"abw",
|
||||
"csv",
|
||||
"cwk",
|
||||
"doc",
|
||||
"docm",
|
||||
"docx",
|
||||
"dot",
|
||||
"dotx",
|
||||
"dotm",
|
||||
"epub",
|
||||
"fb2",
|
||||
"fodt",
|
||||
"htm",
|
||||
"html",
|
||||
"hwp",
|
||||
"mcw",
|
||||
"mw",
|
||||
"mwd",
|
||||
"lwp",
|
||||
"lrf",
|
||||
"odt",
|
||||
"ott",
|
||||
"pages",
|
||||
"pdf",
|
||||
"psw",
|
||||
"rtf",
|
||||
"sdw",
|
||||
"stw",
|
||||
"sxw",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wn",
|
||||
"wpd",
|
||||
"wps",
|
||||
"wpt",
|
||||
"wri",
|
||||
"xhtml",
|
||||
"xml",
|
||||
"zabw",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
text: [
|
||||
"csv",
|
||||
"doc",
|
||||
"docm",
|
||||
"docx",
|
||||
"dot",
|
||||
"dotx",
|
||||
"dotm",
|
||||
"epub",
|
||||
"fodt",
|
||||
"htm",
|
||||
"html",
|
||||
"odt",
|
||||
"ott",
|
||||
"pdf",
|
||||
"rtf",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wps",
|
||||
"wpt",
|
||||
"xhtml",
|
||||
"xml",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
type FileCategories = "text" | "calc";
|
||||
|
||||
const filters: Record<FileCategories, Record<string, string>> = {
|
||||
text: {
|
||||
"602": "T602Document",
|
||||
abw: "AbiWord",
|
||||
csv: "Text",
|
||||
doc: "MS Word 97",
|
||||
docm: "MS Word 2007 XML VBA",
|
||||
docx: "MS Word 2007 XML",
|
||||
dot: "MS Word 97 Vorlage",
|
||||
dotx: "MS Word 2007 XML Template",
|
||||
dotm: "MS Word 2007 XML Template",
|
||||
epub: "EPUB",
|
||||
fb2: "Fictionbook 2",
|
||||
fodt: "OpenDocument Text Flat XML",
|
||||
htm: "HTML (StarWriter)",
|
||||
html: "HTML (StarWriter)",
|
||||
hwp: "writer_MIZI_Hwp_97",
|
||||
mcw: "MacWrite",
|
||||
mw: "MacWrite",
|
||||
mwd: "Mariner_Write",
|
||||
lwp: "LotusWordPro",
|
||||
lrf: "BroadBand eBook",
|
||||
odt: "writer8",
|
||||
ott: "writer8_template",
|
||||
pages: "Apple Pages",
|
||||
// pdf: "writer_pdf_import",
|
||||
psw: "PocketWord File",
|
||||
rtf: "Rich Text Format",
|
||||
sdw: "StarOffice_Writer",
|
||||
stw: "writer_StarOffice_XML_Writer_Template",
|
||||
sxw: "StarOffice XML (Writer)",
|
||||
tab: "Text",
|
||||
tsv: "Text",
|
||||
txt: "Text",
|
||||
wn: "WriteNow",
|
||||
wpd: "WordPerfect",
|
||||
wps: "MS Word 97",
|
||||
wpt: "MS Word 97 Vorlage",
|
||||
wri: "MS_Write",
|
||||
xhtml: "HTML (StarWriter)",
|
||||
xml: "OpenDocument Text Flat XML",
|
||||
zabw: "AbiWord",
|
||||
},
|
||||
calc: {},
|
||||
};
|
||||
|
||||
const getFilters = (fileType: string, converto: string) => {
|
||||
if (fileType in filters.text && converto in filters.text) {
|
||||
return [filters.text[fileType], filters.text[converto]];
|
||||
} else if (fileType in filters.calc && converto in filters.calc) {
|
||||
return [filters.calc[fileType], filters.calc[converto]];
|
||||
}
|
||||
return [null, null];
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "") ?? targetPath;
|
||||
|
||||
// Build arguments array
|
||||
const args: string[] = [];
|
||||
args.push("--headless");
|
||||
const [inFilter, outFilter] = getFilters(fileType, convertTo);
|
||||
|
||||
if (inFilter) {
|
||||
args.push(`--infilter="${inFilter}"`);
|
||||
}
|
||||
|
||||
if (outFilter) {
|
||||
args.push("--convert-to", `${convertTo}:${outFilter}`, "--outdir", outputPath, filePath);
|
||||
} else {
|
||||
args.push("--convert-to", convertTo, "--outdir", outputPath, filePath);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("soffice", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: [
|
||||
"602",
|
||||
"abw",
|
||||
"csv",
|
||||
"cwk",
|
||||
"doc",
|
||||
"docm",
|
||||
"docx",
|
||||
"dot",
|
||||
"dotx",
|
||||
"dotm",
|
||||
"epub",
|
||||
"fb2",
|
||||
"fodt",
|
||||
"htm",
|
||||
"html",
|
||||
"hwp",
|
||||
"mcw",
|
||||
"mw",
|
||||
"mwd",
|
||||
"lwp",
|
||||
"lrf",
|
||||
"odt",
|
||||
"ott",
|
||||
"pages",
|
||||
"pdf",
|
||||
"psw",
|
||||
"rtf",
|
||||
"sdw",
|
||||
"stw",
|
||||
"sxw",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wn",
|
||||
"wpd",
|
||||
"wps",
|
||||
"wpt",
|
||||
"wri",
|
||||
"xhtml",
|
||||
"xml",
|
||||
"zabw",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
text: [
|
||||
"csv",
|
||||
"doc",
|
||||
"docm",
|
||||
"docx",
|
||||
"dot",
|
||||
"dotx",
|
||||
"dotm",
|
||||
"epub",
|
||||
"fodt",
|
||||
"htm",
|
||||
"html",
|
||||
"odt",
|
||||
"ott",
|
||||
"pdf",
|
||||
"rtf",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wps",
|
||||
"wpt",
|
||||
"xhtml",
|
||||
"xml",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
type FileCategories = "text" | "calc";
|
||||
|
||||
const filters: Record<FileCategories, Record<string, string>> = {
|
||||
text: {
|
||||
"602": "T602Document",
|
||||
abw: "AbiWord",
|
||||
csv: "Text",
|
||||
doc: "MS Word 97",
|
||||
docm: "MS Word 2007 XML VBA",
|
||||
docx: "MS Word 2007 XML",
|
||||
dot: "MS Word 97 Vorlage",
|
||||
dotx: "MS Word 2007 XML Template",
|
||||
dotm: "MS Word 2007 XML Template",
|
||||
epub: "EPUB",
|
||||
fb2: "Fictionbook 2",
|
||||
fodt: "OpenDocument Text Flat XML",
|
||||
htm: "HTML (StarWriter)",
|
||||
html: "HTML (StarWriter)",
|
||||
hwp: "writer_MIZI_Hwp_97",
|
||||
mcw: "MacWrite",
|
||||
mw: "MacWrite",
|
||||
mwd: "Mariner_Write",
|
||||
lwp: "LotusWordPro",
|
||||
lrf: "BroadBand eBook",
|
||||
odt: "writer8",
|
||||
ott: "writer8_template",
|
||||
pages: "Apple Pages",
|
||||
// pdf: "writer_pdf_import",
|
||||
psw: "PocketWord File",
|
||||
rtf: "Rich Text Format",
|
||||
sdw: "StarOffice_Writer",
|
||||
stw: "writer_StarOffice_XML_Writer_Template",
|
||||
sxw: "StarOffice XML (Writer)",
|
||||
tab: "Text",
|
||||
tsv: "Text",
|
||||
txt: "Text",
|
||||
wn: "WriteNow",
|
||||
wpd: "WordPerfect",
|
||||
wps: "MS Word 97",
|
||||
wpt: "MS Word 97 Vorlage",
|
||||
wri: "MS_Write",
|
||||
xhtml: "HTML (StarWriter)",
|
||||
xml: "OpenDocument Text Flat XML",
|
||||
zabw: "AbiWord",
|
||||
},
|
||||
calc: {},
|
||||
};
|
||||
|
||||
const getFilters = (fileType: string, converto: string) => {
|
||||
if (fileType in filters.text && converto in filters.text) {
|
||||
return [filters.text[fileType], filters.text[converto]];
|
||||
} else if (fileType in filters.calc && converto in filters.calc) {
|
||||
return [filters.calc[fileType], filters.calc[converto]];
|
||||
}
|
||||
return [null, null];
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "") ?? targetPath;
|
||||
|
||||
// Build arguments array
|
||||
const args: string[] = [];
|
||||
args.push("--headless");
|
||||
const [inFilter, outFilter] = getFilters(fileType, convertTo);
|
||||
|
||||
if (inFilter) {
|
||||
args.push(`--infilter="${inFilter}"`);
|
||||
}
|
||||
|
||||
if (outFilter) {
|
||||
args.push("--convert-to", `${convertTo}:${outFilter}`, "--outdir", outputPath, filePath);
|
||||
} else {
|
||||
args.push("--convert-to", convertTo, "--outdir", outputPath, filePath);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("soffice", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,364 +1,364 @@
|
|||
import { Cookie } from "elysia";
|
||||
import db from "../db/db";
|
||||
import { MAX_CONVERT_PROCESS } from "../helpers/env";
|
||||
import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
|
||||
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
|
||||
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
||||
import { convert as convertDasel, properties as propertiesDasel } from "./dasel";
|
||||
import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
|
||||
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
|
||||
import {
|
||||
convert as convertGraphicsmagick,
|
||||
properties as propertiesGraphicsmagick,
|
||||
} from "./graphicsmagick";
|
||||
import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
|
||||
import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
|
||||
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
|
||||
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
|
||||
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
|
||||
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
|
||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
||||
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
|
||||
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
|
||||
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||
|
||||
// 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: Record<
|
||||
string,
|
||||
{
|
||||
properties: {
|
||||
from: Record<string, string[]>;
|
||||
to: Record<string, string[]>;
|
||||
options?: Record<
|
||||
string,
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
description: string;
|
||||
type: string;
|
||||
default: number;
|
||||
}
|
||||
>
|
||||
>;
|
||||
};
|
||||
converter: (
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
|
||||
options?: unknown,
|
||||
) => unknown;
|
||||
}
|
||||
> = {
|
||||
// Prioritize Inkscape for EMF files as it handles them better than ImageMagick
|
||||
inkscape: {
|
||||
properties: propertiesInkscape,
|
||||
converter: convertInkscape,
|
||||
},
|
||||
libjxl: {
|
||||
properties: propertiesLibjxl,
|
||||
converter: convertLibjxl,
|
||||
},
|
||||
resvg: {
|
||||
properties: propertiesresvg,
|
||||
converter: convertresvg,
|
||||
},
|
||||
vips: {
|
||||
properties: propertiesImage,
|
||||
converter: convertImage,
|
||||
},
|
||||
libheif: {
|
||||
properties: propertiesLibheif,
|
||||
converter: convertLibheif,
|
||||
},
|
||||
xelatex: {
|
||||
properties: propertiesxelatex,
|
||||
converter: convertxelatex,
|
||||
},
|
||||
calibre: {
|
||||
properties: propertiesCalibre,
|
||||
converter: convertCalibre,
|
||||
},
|
||||
dasel: {
|
||||
properties: propertiesDasel,
|
||||
converter: convertDasel,
|
||||
},
|
||||
libreoffice: {
|
||||
properties: propertiesLibreOffice,
|
||||
converter: convertLibreOffice,
|
||||
},
|
||||
pandoc: {
|
||||
properties: propertiesPandoc,
|
||||
converter: convertPandoc,
|
||||
},
|
||||
msgconvert: {
|
||||
properties: propertiesMsgconvert,
|
||||
converter: convertMsgconvert,
|
||||
},
|
||||
dvisvgm: {
|
||||
properties: propertiesDvisvgm,
|
||||
converter: convertDvisvgm,
|
||||
},
|
||||
imagemagick: {
|
||||
properties: propertiesImagemagick,
|
||||
converter: convertImagemagick,
|
||||
},
|
||||
graphicsmagick: {
|
||||
properties: propertiesGraphicsmagick,
|
||||
converter: convertGraphicsmagick,
|
||||
},
|
||||
assimp: {
|
||||
properties: propertiesassimp,
|
||||
converter: convertassimp,
|
||||
},
|
||||
ffmpeg: {
|
||||
properties: propertiesFFmpeg,
|
||||
converter: convertFFmpeg,
|
||||
},
|
||||
potrace: {
|
||||
properties: propertiesPotrace,
|
||||
converter: convertPotrace,
|
||||
},
|
||||
vtracer: {
|
||||
properties: propertiesVtracer,
|
||||
converter: convertVtracer,
|
||||
},
|
||||
};
|
||||
|
||||
function chunks<T>(arr: T[], size: number): T[][] {
|
||||
if (size <= 0) {
|
||||
return [arr];
|
||||
}
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) =>
|
||||
arr.slice(i * size, i * size + size),
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleConvert(
|
||||
fileNames: string[],
|
||||
userUploadsDir: string,
|
||||
userOutputDir: string,
|
||||
convertTo: string,
|
||||
converterName: string,
|
||||
jobId: Cookie<string | undefined>,
|
||||
) {
|
||||
const query = db.query(
|
||||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
||||
);
|
||||
|
||||
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
|
||||
const toProcess: Promise<string>[] = [];
|
||||
for (const fileName of chunk) {
|
||||
const filePath = `${userUploadsDir}${fileName}`;
|
||||
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
||||
const fileType = normalizeFiletype(fileTypeOrig);
|
||||
const newFileExt = normalizeOutputFiletype(convertTo);
|
||||
const newFileName = fileName.replace(
|
||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||
newFileExt,
|
||||
);
|
||||
const targetPath = `${userOutputDir}${newFileName}`;
|
||||
toProcess.push(
|
||||
new Promise((resolve, reject) => {
|
||||
mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
|
||||
.then((r) => {
|
||||
if (jobId.value) {
|
||||
query.run(jobId.value, fileName, newFileName, r);
|
||||
}
|
||||
resolve(r);
|
||||
})
|
||||
.catch((c) => reject(c));
|
||||
}),
|
||||
);
|
||||
}
|
||||
await Promise.all(toProcess);
|
||||
}
|
||||
}
|
||||
|
||||
async function mainConverter(
|
||||
inputFilePath: string,
|
||||
fileTypeOriginal: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
converterName?: string,
|
||||
) {
|
||||
const fileType = normalizeFiletype(fileTypeOriginal);
|
||||
|
||||
let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined;
|
||||
|
||||
if (converterName) {
|
||||
converterFunc = properties[converterName]?.converter;
|
||||
} else {
|
||||
// Iterate over each converter in properties
|
||||
for (converterName in properties) {
|
||||
const converterObj = properties[converterName];
|
||||
|
||||
if (!converterObj) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const key in converterObj.properties.from) {
|
||||
if (
|
||||
converterObj?.properties?.from[key]?.includes(fileType) &&
|
||||
converterObj?.properties?.to[key]?.includes(convertTo)
|
||||
) {
|
||||
converterFunc = converterObj.converter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!converterFunc) {
|
||||
console.log(`No available converter supports converting from ${fileType} to ${convertTo}.`);
|
||||
return "File type not supported";
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options);
|
||||
|
||||
console.log(
|
||||
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
||||
result,
|
||||
);
|
||||
|
||||
if (typeof result === "string") {
|
||||
return result;
|
||||
}
|
||||
|
||||
return "Done";
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo} using ${converterName}.`,
|
||||
error,
|
||||
);
|
||||
return "Failed, check logs";
|
||||
}
|
||||
}
|
||||
|
||||
const possibleTargets: Record<string, Record<string, string[]>> = {};
|
||||
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
if (converterProperties.from[key] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const extension of converterProperties.from[key] ?? []) {
|
||||
if (!possibleTargets[extension]) {
|
||||
possibleTargets[extension] = {};
|
||||
}
|
||||
|
||||
possibleTargets[extension][converterName] = converterProperties.to[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
||||
const fromClean = normalizeFiletype(from);
|
||||
|
||||
return possibleTargets[fromClean] || {};
|
||||
};
|
||||
|
||||
const possibleInputs: string[] = [];
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
for (const extension of converterProperties.from[key] ?? []) {
|
||||
if (!possibleInputs.includes(extension)) {
|
||||
possibleInputs.push(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
possibleInputs.sort();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const getPossibleInputs = () => {
|
||||
return possibleInputs;
|
||||
};
|
||||
|
||||
const allTargets: Record<string, string[]> = {};
|
||||
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.to) {
|
||||
if (allTargets[converterName]) {
|
||||
allTargets[converterName].push(...(converterProperties.to[key] || []));
|
||||
} else {
|
||||
allTargets[converterName] = converterProperties.to[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllTargets = () => {
|
||||
return allTargets;
|
||||
};
|
||||
|
||||
const allInputs: Record<string, string[]> = {};
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
if (allInputs[converterName]) {
|
||||
allInputs[converterName].push(...(converterProperties.from[key] || []));
|
||||
} else {
|
||||
allInputs[converterName] = converterProperties.from[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllInputs = (converter: string) => {
|
||||
return allInputs[converter] || [];
|
||||
};
|
||||
|
||||
// // count the number of unique formats
|
||||
// const uniqueFormats = new Set();
|
||||
|
||||
// for (const converterName in properties) {
|
||||
// const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
// if (!converterProperties) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// for (const key in converterProperties.from) {
|
||||
// for (const extension of converterProperties.from[key] ?? []) {
|
||||
// uniqueFormats.add(extension);
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const key in converterProperties.to) {
|
||||
// for (const extension of converterProperties.to[key] ?? []) {
|
||||
// uniqueFormats.add(extension);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // print the number of unique Inputs and Outputs
|
||||
// console.log(`Unique Formats: ${uniqueFormats.size}`);
|
||||
import { Cookie } from "elysia";
|
||||
import db from "../db/db";
|
||||
import { MAX_CONVERT_PROCESS } from "../helpers/env";
|
||||
import { normalizeFiletype, normalizeOutputFiletype } from "../helpers/normalizeFiletype";
|
||||
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
|
||||
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
||||
import { convert as convertDasel, properties as propertiesDasel } from "./dasel";
|
||||
import { convert as convertDvisvgm, properties as propertiesDvisvgm } from "./dvisvgm";
|
||||
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
|
||||
import {
|
||||
convert as convertGraphicsmagick,
|
||||
properties as propertiesGraphicsmagick,
|
||||
} from "./graphicsmagick";
|
||||
import { convert as convertImagemagick, properties as propertiesImagemagick } from "./imagemagick";
|
||||
import { convert as convertInkscape, properties as propertiesInkscape } from "./inkscape";
|
||||
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
|
||||
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
|
||||
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
|
||||
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
|
||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
||||
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
|
||||
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
|
||||
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||
|
||||
// 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: Record<
|
||||
string,
|
||||
{
|
||||
properties: {
|
||||
from: Record<string, string[]>;
|
||||
to: Record<string, string[]>;
|
||||
options?: Record<
|
||||
string,
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
description: string;
|
||||
type: string;
|
||||
default: number;
|
||||
}
|
||||
>
|
||||
>;
|
||||
};
|
||||
converter: (
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
|
||||
options?: unknown,
|
||||
) => unknown;
|
||||
}
|
||||
> = {
|
||||
// Prioritize Inkscape for EMF files as it handles them better than ImageMagick
|
||||
inkscape: {
|
||||
properties: propertiesInkscape,
|
||||
converter: convertInkscape,
|
||||
},
|
||||
libjxl: {
|
||||
properties: propertiesLibjxl,
|
||||
converter: convertLibjxl,
|
||||
},
|
||||
resvg: {
|
||||
properties: propertiesresvg,
|
||||
converter: convertresvg,
|
||||
},
|
||||
vips: {
|
||||
properties: propertiesImage,
|
||||
converter: convertImage,
|
||||
},
|
||||
libheif: {
|
||||
properties: propertiesLibheif,
|
||||
converter: convertLibheif,
|
||||
},
|
||||
xelatex: {
|
||||
properties: propertiesxelatex,
|
||||
converter: convertxelatex,
|
||||
},
|
||||
calibre: {
|
||||
properties: propertiesCalibre,
|
||||
converter: convertCalibre,
|
||||
},
|
||||
dasel: {
|
||||
properties: propertiesDasel,
|
||||
converter: convertDasel,
|
||||
},
|
||||
libreoffice: {
|
||||
properties: propertiesLibreOffice,
|
||||
converter: convertLibreOffice,
|
||||
},
|
||||
pandoc: {
|
||||
properties: propertiesPandoc,
|
||||
converter: convertPandoc,
|
||||
},
|
||||
msgconvert: {
|
||||
properties: propertiesMsgconvert,
|
||||
converter: convertMsgconvert,
|
||||
},
|
||||
dvisvgm: {
|
||||
properties: propertiesDvisvgm,
|
||||
converter: convertDvisvgm,
|
||||
},
|
||||
imagemagick: {
|
||||
properties: propertiesImagemagick,
|
||||
converter: convertImagemagick,
|
||||
},
|
||||
graphicsmagick: {
|
||||
properties: propertiesGraphicsmagick,
|
||||
converter: convertGraphicsmagick,
|
||||
},
|
||||
assimp: {
|
||||
properties: propertiesassimp,
|
||||
converter: convertassimp,
|
||||
},
|
||||
ffmpeg: {
|
||||
properties: propertiesFFmpeg,
|
||||
converter: convertFFmpeg,
|
||||
},
|
||||
potrace: {
|
||||
properties: propertiesPotrace,
|
||||
converter: convertPotrace,
|
||||
},
|
||||
vtracer: {
|
||||
properties: propertiesVtracer,
|
||||
converter: convertVtracer,
|
||||
},
|
||||
};
|
||||
|
||||
function chunks<T>(arr: T[], size: number): T[][] {
|
||||
if (size <= 0) {
|
||||
return [arr];
|
||||
}
|
||||
return Array.from({ length: Math.ceil(arr.length / size) }, (_: T, i: number) =>
|
||||
arr.slice(i * size, i * size + size),
|
||||
);
|
||||
}
|
||||
|
||||
export async function handleConvert(
|
||||
fileNames: string[],
|
||||
userUploadsDir: string,
|
||||
userOutputDir: string,
|
||||
convertTo: string,
|
||||
converterName: string,
|
||||
jobId: Cookie<string | undefined>,
|
||||
) {
|
||||
const query = db.query(
|
||||
"INSERT INTO file_names (job_id, file_name, output_file_name, status) VALUES (?1, ?2, ?3, ?4)",
|
||||
);
|
||||
|
||||
for (const chunk of chunks(fileNames, MAX_CONVERT_PROCESS)) {
|
||||
const toProcess: Promise<string>[] = [];
|
||||
for (const fileName of chunk) {
|
||||
const filePath = `${userUploadsDir}${fileName}`;
|
||||
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
||||
const fileType = normalizeFiletype(fileTypeOrig);
|
||||
const newFileExt = normalizeOutputFiletype(convertTo);
|
||||
const newFileName = fileName.replace(
|
||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||
newFileExt,
|
||||
);
|
||||
const targetPath = `${userOutputDir}${newFileName}`;
|
||||
toProcess.push(
|
||||
new Promise((resolve, reject) => {
|
||||
mainConverter(filePath, fileType, convertTo, targetPath, {}, converterName)
|
||||
.then((r) => {
|
||||
if (jobId.value) {
|
||||
query.run(jobId.value, fileName, newFileName, r);
|
||||
}
|
||||
resolve(r);
|
||||
})
|
||||
.catch((c) => reject(c));
|
||||
}),
|
||||
);
|
||||
}
|
||||
await Promise.all(toProcess);
|
||||
}
|
||||
}
|
||||
|
||||
async function mainConverter(
|
||||
inputFilePath: string,
|
||||
fileTypeOriginal: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
converterName?: string,
|
||||
) {
|
||||
const fileType = normalizeFiletype(fileTypeOriginal);
|
||||
|
||||
let converterFunc: (typeof properties)["libjxl"]["converter"] | undefined;
|
||||
|
||||
if (converterName) {
|
||||
converterFunc = properties[converterName]?.converter;
|
||||
} else {
|
||||
// Iterate over each converter in properties
|
||||
for (converterName in properties) {
|
||||
const converterObj = properties[converterName];
|
||||
|
||||
if (!converterObj) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const key in converterObj.properties.from) {
|
||||
if (
|
||||
converterObj?.properties?.from[key]?.includes(fileType) &&
|
||||
converterObj?.properties?.to[key]?.includes(convertTo)
|
||||
) {
|
||||
converterFunc = converterObj.converter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!converterFunc) {
|
||||
console.log(`No available converter supports converting from ${fileType} to ${convertTo}.`);
|
||||
return "File type not supported";
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await converterFunc(inputFilePath, fileType, convertTo, targetPath, options);
|
||||
|
||||
console.log(
|
||||
`Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
|
||||
result,
|
||||
);
|
||||
|
||||
if (typeof result === "string") {
|
||||
return result;
|
||||
}
|
||||
|
||||
return "Done";
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Failed to convert ${inputFilePath} from ${fileType} to ${convertTo} using ${converterName}.`,
|
||||
error,
|
||||
);
|
||||
return "Failed, check logs";
|
||||
}
|
||||
}
|
||||
|
||||
const possibleTargets: Record<string, Record<string, string[]>> = {};
|
||||
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
if (converterProperties.from[key] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const extension of converterProperties.from[key] ?? []) {
|
||||
if (!possibleTargets[extension]) {
|
||||
possibleTargets[extension] = {};
|
||||
}
|
||||
|
||||
possibleTargets[extension][converterName] = converterProperties.to[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
|
||||
const fromClean = normalizeFiletype(from);
|
||||
|
||||
return possibleTargets[fromClean] || {};
|
||||
};
|
||||
|
||||
const possibleInputs: string[] = [];
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
for (const extension of converterProperties.from[key] ?? []) {
|
||||
if (!possibleInputs.includes(extension)) {
|
||||
possibleInputs.push(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
possibleInputs.sort();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const getPossibleInputs = () => {
|
||||
return possibleInputs;
|
||||
};
|
||||
|
||||
const allTargets: Record<string, string[]> = {};
|
||||
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.to) {
|
||||
if (allTargets[converterName]) {
|
||||
allTargets[converterName].push(...(converterProperties.to[key] || []));
|
||||
} else {
|
||||
allTargets[converterName] = converterProperties.to[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllTargets = () => {
|
||||
return allTargets;
|
||||
};
|
||||
|
||||
const allInputs: Record<string, string[]> = {};
|
||||
for (const converterName in properties) {
|
||||
const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
if (!converterProperties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key in converterProperties.from) {
|
||||
if (allInputs[converterName]) {
|
||||
allInputs[converterName].push(...(converterProperties.from[key] || []));
|
||||
} else {
|
||||
allInputs[converterName] = converterProperties.from[key] || [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getAllInputs = (converter: string) => {
|
||||
return allInputs[converter] || [];
|
||||
};
|
||||
|
||||
// // count the number of unique formats
|
||||
// const uniqueFormats = new Set();
|
||||
|
||||
// for (const converterName in properties) {
|
||||
// const converterProperties = properties[converterName]?.properties;
|
||||
|
||||
// if (!converterProperties) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// for (const key in converterProperties.from) {
|
||||
// for (const extension of converterProperties.from[key] ?? []) {
|
||||
// uniqueFormats.add(extension);
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const key in converterProperties.to) {
|
||||
// for (const extension of converterProperties.to[key] ?? []) {
|
||||
// uniqueFormats.add(extension);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // print the number of unique Inputs and Outputs
|
||||
// console.log(`Unique Formats: ${uniqueFormats.size}`);
|
||||
|
|
|
|||
|
|
@ -1,52 +1,52 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
email: ["msg"],
|
||||
},
|
||||
to: {
|
||||
email: ["eml"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fileType === "msg" && convertTo === "eml") {
|
||||
// Convert MSG to EML using msgconvert
|
||||
// msgconvert will output to the same directory as the input file with .eml extension
|
||||
// We need to use --outfile to specify the target path
|
||||
const args = ["--outfile", targetPath, filePath];
|
||||
|
||||
execFile("msgconvert", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(new Error(`msgconvert failed: ${error.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
// Log sanitized stderr to avoid exposing sensitive paths
|
||||
const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]");
|
||||
console.warn(
|
||||
`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + "..." : sanitizedStderr}`,
|
||||
);
|
||||
}
|
||||
|
||||
resolve(targetPath);
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
email: ["msg"],
|
||||
},
|
||||
to: {
|
||||
email: ["eml"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (fileType === "msg" && convertTo === "eml") {
|
||||
// Convert MSG to EML using msgconvert
|
||||
// msgconvert will output to the same directory as the input file with .eml extension
|
||||
// We need to use --outfile to specify the target path
|
||||
const args = ["--outfile", targetPath, filePath];
|
||||
|
||||
execFile("msgconvert", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(new Error(`msgconvert failed: ${error.message}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
// Log sanitized stderr to avoid exposing sensitive paths
|
||||
const sanitizedStderr = stderr.replace(/(\/[^\s]+)/g, "[REDACTED_PATH]");
|
||||
console.warn(
|
||||
`msgconvert stderr: ${sanitizedStderr.length > 200 ? sanitizedStderr.slice(0, 200) + "..." : sanitizedStderr}`,
|
||||
);
|
||||
}
|
||||
|
||||
resolve(targetPath);
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Unsupported conversion from ${fileType} to ${convertTo}. Only MSG to EML conversion is currently supported.`,
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,163 +1,163 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: [
|
||||
"textile",
|
||||
"tikiwiki",
|
||||
"tsv",
|
||||
"twiki",
|
||||
"typst",
|
||||
"vimwiki",
|
||||
"biblatex",
|
||||
"bibtex",
|
||||
"bits",
|
||||
"commonmark",
|
||||
"commonmark_x",
|
||||
"creole",
|
||||
"csljson",
|
||||
"csv",
|
||||
"djot",
|
||||
"docbook",
|
||||
"docx",
|
||||
"dokuwiki",
|
||||
"endnotexml",
|
||||
"epub",
|
||||
"fb2",
|
||||
"gfm",
|
||||
"haddock",
|
||||
"html",
|
||||
"ipynb",
|
||||
"jats",
|
||||
"jira",
|
||||
"json",
|
||||
"latex",
|
||||
"man",
|
||||
"markdown",
|
||||
"markdown_mmd",
|
||||
"markdown_phpextra",
|
||||
"markdown_strict",
|
||||
"mediawiki",
|
||||
"muse",
|
||||
"pandoc native",
|
||||
"opml",
|
||||
"org",
|
||||
"ris",
|
||||
"rst",
|
||||
"rtf",
|
||||
"t2t",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
text: [
|
||||
"tei",
|
||||
"texinfo",
|
||||
"textile",
|
||||
"typst",
|
||||
"xwiki",
|
||||
"zimwiki",
|
||||
"asciidoc",
|
||||
"asciidoc_legacy",
|
||||
"asciidoctor",
|
||||
"beamer",
|
||||
"biblatex",
|
||||
"bibtex",
|
||||
"chunkedhtml",
|
||||
"commonmark",
|
||||
"commonmark_x",
|
||||
"context",
|
||||
"csljson",
|
||||
"djot",
|
||||
"docbook",
|
||||
"docbook4",
|
||||
"docbook5",
|
||||
"docx",
|
||||
"dokuwiki",
|
||||
"dzslides",
|
||||
"epub",
|
||||
"epub2",
|
||||
"epub3",
|
||||
"fb2",
|
||||
"gfm",
|
||||
"haddock",
|
||||
"html",
|
||||
"html4",
|
||||
"html5",
|
||||
"icml",
|
||||
"ipynb",
|
||||
"jats",
|
||||
"jats_archiving",
|
||||
"jats_articleauthoring",
|
||||
"jats_publishing",
|
||||
"jira",
|
||||
"json",
|
||||
"latex",
|
||||
"man",
|
||||
"markdown",
|
||||
"markdown_mmd",
|
||||
"markdown_phpextra",
|
||||
"markdown_strict",
|
||||
"markua",
|
||||
"mediawiki",
|
||||
"ms",
|
||||
"muse",
|
||||
"pandoc native",
|
||||
"odt",
|
||||
"opendocument",
|
||||
"opml",
|
||||
"org",
|
||||
"pdf",
|
||||
"plain",
|
||||
"pptx",
|
||||
"revealjs",
|
||||
"rst",
|
||||
"rtf",
|
||||
"s5",
|
||||
"slideous",
|
||||
"slidy",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
// set xelatex here
|
||||
const xelatex = ["pdf", "latex"];
|
||||
|
||||
// Build arguments array
|
||||
const args: string[] = [];
|
||||
|
||||
if (xelatex.includes(convertTo)) {
|
||||
args.push("--pdf-engine=xelatex");
|
||||
}
|
||||
|
||||
args.push(filePath);
|
||||
args.push("-f", fileType);
|
||||
args.push("-t", convertTo);
|
||||
args.push("-o", targetPath);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("pandoc", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: [
|
||||
"textile",
|
||||
"tikiwiki",
|
||||
"tsv",
|
||||
"twiki",
|
||||
"typst",
|
||||
"vimwiki",
|
||||
"biblatex",
|
||||
"bibtex",
|
||||
"bits",
|
||||
"commonmark",
|
||||
"commonmark_x",
|
||||
"creole",
|
||||
"csljson",
|
||||
"csv",
|
||||
"djot",
|
||||
"docbook",
|
||||
"docx",
|
||||
"dokuwiki",
|
||||
"endnotexml",
|
||||
"epub",
|
||||
"fb2",
|
||||
"gfm",
|
||||
"haddock",
|
||||
"html",
|
||||
"ipynb",
|
||||
"jats",
|
||||
"jira",
|
||||
"json",
|
||||
"latex",
|
||||
"man",
|
||||
"markdown",
|
||||
"markdown_mmd",
|
||||
"markdown_phpextra",
|
||||
"markdown_strict",
|
||||
"mediawiki",
|
||||
"muse",
|
||||
"pandoc native",
|
||||
"opml",
|
||||
"org",
|
||||
"ris",
|
||||
"rst",
|
||||
"rtf",
|
||||
"t2t",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
text: [
|
||||
"tei",
|
||||
"texinfo",
|
||||
"textile",
|
||||
"typst",
|
||||
"xwiki",
|
||||
"zimwiki",
|
||||
"asciidoc",
|
||||
"asciidoc_legacy",
|
||||
"asciidoctor",
|
||||
"beamer",
|
||||
"biblatex",
|
||||
"bibtex",
|
||||
"chunkedhtml",
|
||||
"commonmark",
|
||||
"commonmark_x",
|
||||
"context",
|
||||
"csljson",
|
||||
"djot",
|
||||
"docbook",
|
||||
"docbook4",
|
||||
"docbook5",
|
||||
"docx",
|
||||
"dokuwiki",
|
||||
"dzslides",
|
||||
"epub",
|
||||
"epub2",
|
||||
"epub3",
|
||||
"fb2",
|
||||
"gfm",
|
||||
"haddock",
|
||||
"html",
|
||||
"html4",
|
||||
"html5",
|
||||
"icml",
|
||||
"ipynb",
|
||||
"jats",
|
||||
"jats_archiving",
|
||||
"jats_articleauthoring",
|
||||
"jats_publishing",
|
||||
"jira",
|
||||
"json",
|
||||
"latex",
|
||||
"man",
|
||||
"markdown",
|
||||
"markdown_mmd",
|
||||
"markdown_phpextra",
|
||||
"markdown_strict",
|
||||
"markua",
|
||||
"mediawiki",
|
||||
"ms",
|
||||
"muse",
|
||||
"pandoc native",
|
||||
"odt",
|
||||
"opendocument",
|
||||
"opml",
|
||||
"org",
|
||||
"pdf",
|
||||
"plain",
|
||||
"pptx",
|
||||
"revealjs",
|
||||
"rst",
|
||||
"rtf",
|
||||
"s5",
|
||||
"slideous",
|
||||
"slidy",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
// set xelatex here
|
||||
const xelatex = ["pdf", "latex"];
|
||||
|
||||
// Build arguments array
|
||||
const args: string[] = [];
|
||||
|
||||
if (xelatex.includes(convertTo)) {
|
||||
args.push("--pdf-engine=xelatex");
|
||||
}
|
||||
|
||||
args.push(filePath);
|
||||
args.push("-f", fileType);
|
||||
args.push("-t", convertTo);
|
||||
args.push("-o", targetPath);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("pandoc", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,50 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["pnm", "pbm", "pgm", "bmp"],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"svg",
|
||||
"pdf",
|
||||
"pdfpage",
|
||||
"eps",
|
||||
"postscript",
|
||||
"ps",
|
||||
"dxf",
|
||||
"geojson",
|
||||
"pgm",
|
||||
"gimppath",
|
||||
"xfig",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["pnm", "pbm", "pgm", "bmp"],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"svg",
|
||||
"pdf",
|
||||
"pdfpage",
|
||||
"eps",
|
||||
"postscript",
|
||||
"ps",
|
||||
"dxf",
|
||||
"geojson",
|
||||
"pgm",
|
||||
"gimppath",
|
||||
"xfig",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("potrace", [filePath, "-o", targetPath, "-b", convertTo], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,38 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["svg"],
|
||||
},
|
||||
to: {
|
||||
images: ["png"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["svg"],
|
||||
},
|
||||
to: {
|
||||
images: ["png"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("resvg", [filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { ExecFileOptions } from "child_process";
|
||||
|
||||
export type ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
options?: ExecFileOptions,
|
||||
) => void;
|
||||
|
||||
export type ConvertFnWithExecFile = (
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options: unknown,
|
||||
execFileOverride?: ExecFileFn,
|
||||
) => Promise<string>;
|
||||
import { ExecFileOptions } from "child_process";
|
||||
|
||||
export type ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
options?: ExecFileOptions,
|
||||
) => void;
|
||||
|
||||
export type ConvertFnWithExecFile = (
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options: unknown,
|
||||
execFileOverride?: ExecFileFn,
|
||||
) => Promise<string>;
|
||||
|
|
|
|||
|
|
@ -1,139 +1,139 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
images: [
|
||||
"avif",
|
||||
"bif",
|
||||
"csv",
|
||||
"exr",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"hdr",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"img",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"mrxs",
|
||||
"ndpi",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"pdf",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pic",
|
||||
"png",
|
||||
"ppm",
|
||||
"raw",
|
||||
"scn",
|
||||
"svg",
|
||||
"svs",
|
||||
"svslide",
|
||||
"szi",
|
||||
"tif",
|
||||
"tiff",
|
||||
"v",
|
||||
"vips",
|
||||
"vms",
|
||||
"vmu",
|
||||
"webp",
|
||||
"zip",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"avif",
|
||||
"dzi",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"png",
|
||||
"tiff",
|
||||
"vips",
|
||||
"webp",
|
||||
],
|
||||
},
|
||||
options: {
|
||||
svg: {
|
||||
scale: {
|
||||
description: "Scale the image up or down",
|
||||
type: "number",
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
// if (fileType === "svg") {
|
||||
// const scale = options.scale || 1;
|
||||
// const metadata = await sharp(filePath).metadata();
|
||||
|
||||
// if (!metadata || !metadata.width || !metadata.height) {
|
||||
// throw new Error("Could not get metadata from image");
|
||||
// }
|
||||
|
||||
// const newWidth = Math.round(metadata.width * scale);
|
||||
// const newHeight = Math.round(metadata.height * scale);
|
||||
|
||||
// return await sharp(filePath)
|
||||
// .resize(newWidth, newHeight)
|
||||
// .toFormat(convertTo)
|
||||
// .toFile(targetPath);
|
||||
// }
|
||||
let action = "copy";
|
||||
if (fileType === "pdf") {
|
||||
action = "pdfload";
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("vips", [action, filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
// declare possible conversions
|
||||
export const properties = {
|
||||
from: {
|
||||
images: [
|
||||
"avif",
|
||||
"bif",
|
||||
"csv",
|
||||
"exr",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"hdr",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"img",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"mrxs",
|
||||
"ndpi",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"pdf",
|
||||
"pfm",
|
||||
"pgm",
|
||||
"pic",
|
||||
"png",
|
||||
"ppm",
|
||||
"raw",
|
||||
"scn",
|
||||
"svg",
|
||||
"svs",
|
||||
"svslide",
|
||||
"szi",
|
||||
"tif",
|
||||
"tiff",
|
||||
"v",
|
||||
"vips",
|
||||
"vms",
|
||||
"vmu",
|
||||
"webp",
|
||||
"zip",
|
||||
],
|
||||
},
|
||||
to: {
|
||||
images: [
|
||||
"avif",
|
||||
"dzi",
|
||||
"fits",
|
||||
"gif",
|
||||
"hdr.gz",
|
||||
"heic",
|
||||
"heif",
|
||||
"img.gz",
|
||||
"j2c",
|
||||
"j2k",
|
||||
"jp2",
|
||||
"jpeg",
|
||||
"jpx",
|
||||
"jxl",
|
||||
"mat",
|
||||
"nia.gz",
|
||||
"nia",
|
||||
"nii.gz",
|
||||
"nii",
|
||||
"png",
|
||||
"tiff",
|
||||
"vips",
|
||||
"webp",
|
||||
],
|
||||
},
|
||||
options: {
|
||||
svg: {
|
||||
scale: {
|
||||
description: "Scale the image up or down",
|
||||
type: "number",
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
// if (fileType === "svg") {
|
||||
// const scale = options.scale || 1;
|
||||
// const metadata = await sharp(filePath).metadata();
|
||||
|
||||
// if (!metadata || !metadata.width || !metadata.height) {
|
||||
// throw new Error("Could not get metadata from image");
|
||||
// }
|
||||
|
||||
// const newWidth = Math.round(metadata.width * scale);
|
||||
// const newHeight = Math.round(metadata.height * scale);
|
||||
|
||||
// return await sharp(filePath)
|
||||
// .resize(newWidth, newHeight)
|
||||
// .toFormat(convertTo)
|
||||
// .toFile(targetPath);
|
||||
// }
|
||||
let action = "copy";
|
||||
if (fileType === "pdf") {
|
||||
action = "pdfload";
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("vips", [action, filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +1,80 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"],
|
||||
},
|
||||
to: {
|
||||
images: ["svg"],
|
||||
},
|
||||
};
|
||||
|
||||
interface VTracerOptions {
|
||||
colormode?: string;
|
||||
hierarchical?: string;
|
||||
mode?: string;
|
||||
filter_speckle?: string | number;
|
||||
color_precision?: string | number;
|
||||
layer_difference?: string | number;
|
||||
corner_threshold?: string | number;
|
||||
length_threshold?: string | number;
|
||||
max_iterations?: string | number;
|
||||
splice_threshold?: string | number;
|
||||
path_precision?: string | number;
|
||||
}
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Build vtracer arguments
|
||||
const args = ["--input", filePath, "--output", targetPath];
|
||||
|
||||
// Add optional parameter if provided
|
||||
if (options && typeof options === "object") {
|
||||
const opts = options as VTracerOptions;
|
||||
const validOptions: Array<keyof VTracerOptions> = [
|
||||
"colormode",
|
||||
"hierarchical",
|
||||
"mode",
|
||||
"filter_speckle",
|
||||
"color_precision",
|
||||
"layer_difference",
|
||||
"corner_threshold",
|
||||
"length_threshold",
|
||||
"max_iterations",
|
||||
"splice_threshold",
|
||||
"path_precision",
|
||||
];
|
||||
|
||||
for (const option of validOptions) {
|
||||
if (opts[option] !== undefined) {
|
||||
args.push(`--${option}`, String(opts[option]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execFile("vtracer", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
images: ["jpg", "jpeg", "png", "bmp", "gif", "tiff", "tif", "webp"],
|
||||
},
|
||||
to: {
|
||||
images: ["svg"],
|
||||
},
|
||||
};
|
||||
|
||||
interface VTracerOptions {
|
||||
colormode?: string;
|
||||
hierarchical?: string;
|
||||
mode?: string;
|
||||
filter_speckle?: string | number;
|
||||
color_precision?: string | number;
|
||||
layer_difference?: string | number;
|
||||
corner_threshold?: string | number;
|
||||
length_threshold?: string | number;
|
||||
max_iterations?: string | number;
|
||||
splice_threshold?: string | number;
|
||||
path_precision?: string | number;
|
||||
}
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Build vtracer arguments
|
||||
const args = ["--input", filePath, "--output", targetPath];
|
||||
|
||||
// Add optional parameter if provided
|
||||
if (options && typeof options === "object") {
|
||||
const opts = options as VTracerOptions;
|
||||
const validOptions: Array<keyof VTracerOptions> = [
|
||||
"colormode",
|
||||
"hierarchical",
|
||||
"mode",
|
||||
"filter_speckle",
|
||||
"color_precision",
|
||||
"layer_difference",
|
||||
"corner_threshold",
|
||||
"length_threshold",
|
||||
"max_iterations",
|
||||
"splice_threshold",
|
||||
"path_precision",
|
||||
];
|
||||
|
||||
for (const option of validOptions) {
|
||||
if (opts[option] !== undefined) {
|
||||
args.push(`--${option}`, String(opts[option]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execFile("vtracer", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}${stderr ? `\nstderr: ${stderr}` : ""}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.log(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: ["tex", "latex"],
|
||||
},
|
||||
to: {
|
||||
text: ["pdf"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
|
||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "");
|
||||
|
||||
execFile(
|
||||
"latexmk",
|
||||
["-xelatex", "-interaction=nonstopmode", `-output-directory=${outputPath}`, filePath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
text: ["tex", "latex"],
|
||||
},
|
||||
to: {
|
||||
text: ["pdf"],
|
||||
},
|
||||
};
|
||||
|
||||
export function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
|
||||
const outputPath = targetPath.split("/").slice(0, -1).join("/").replace("./", "");
|
||||
|
||||
execFile(
|
||||
"latexmk",
|
||||
["-xelatex", "-interaction=nonstopmode", `-output-directory=${outputPath}`, filePath],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
82
src/db/db.ts
82
src/db/db.ts
|
|
@ -1,41 +1,41 @@
|
|||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||
|
||||
if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
output_file_name TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date_created TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
num_files INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
PRAGMA user_version = 1;`);
|
||||
}
|
||||
|
||||
const dbVersion = (db.query("PRAGMA user_version").get() as { user_version?: number }).user_version;
|
||||
if (dbVersion === 0) {
|
||||
db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';");
|
||||
db.exec("PRAGMA user_version = 1;");
|
||||
console.log("Updated database to version 1.");
|
||||
}
|
||||
|
||||
// enable WAL mode
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
|
||||
export default db;
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||
|
||||
if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
output_file_name TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date_created TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
num_files INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
PRAGMA user_version = 1;`);
|
||||
}
|
||||
|
||||
const dbVersion = (db.query("PRAGMA user_version").get() as { user_version?: number }).user_version;
|
||||
if (dbVersion === 0) {
|
||||
db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';");
|
||||
db.exec("PRAGMA user_version = 1;");
|
||||
console.log("Updated database to version 1.");
|
||||
}
|
||||
|
||||
// enable WAL mode
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
|
||||
export default db;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
export class Filename {
|
||||
id!: number;
|
||||
job_id!: number;
|
||||
file_name!: string;
|
||||
output_file_name!: string;
|
||||
status!: string;
|
||||
}
|
||||
|
||||
export class Jobs {
|
||||
finished_files!: number;
|
||||
id!: number;
|
||||
user_id!: number;
|
||||
date_created!: string;
|
||||
status!: string;
|
||||
num_files!: number;
|
||||
files_detailed!: Filename[];
|
||||
}
|
||||
|
||||
export class User {
|
||||
id!: number;
|
||||
email!: string;
|
||||
password!: string;
|
||||
}
|
||||
export class Filename {
|
||||
id!: number;
|
||||
job_id!: number;
|
||||
file_name!: string;
|
||||
output_file_name!: string;
|
||||
status!: string;
|
||||
}
|
||||
|
||||
export class Jobs {
|
||||
finished_files!: number;
|
||||
id!: number;
|
||||
user_id!: number;
|
||||
date_created!: string;
|
||||
status!: string;
|
||||
num_files!: number;
|
||||
files_detailed!: Filename[];
|
||||
}
|
||||
|
||||
export class User {
|
||||
id!: number;
|
||||
email!: string;
|
||||
password!: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
export const ACCOUNT_REGISTRATION =
|
||||
process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
|
||||
|
||||
export const HTTP_ALLOWED = process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
|
||||
|
||||
export const ALLOW_UNAUTHENTICATED =
|
||||
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
|
||||
|
||||
export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
||||
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
||||
: 24;
|
||||
|
||||
export const HIDE_HISTORY = process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
|
||||
|
||||
export const WEBROOT = process.env.WEBROOT ?? "";
|
||||
|
||||
export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en";
|
||||
|
||||
export const MAX_CONVERT_PROCESS =
|
||||
process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0
|
||||
? Number(process.env.MAX_CONVERT_PROCESS)
|
||||
: 0;
|
||||
|
||||
export const UNAUTHENTICATED_USER_SHARING =
|
||||
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;
|
||||
export const ACCOUNT_REGISTRATION =
|
||||
process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
|
||||
|
||||
export const HTTP_ALLOWED = process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
|
||||
|
||||
export const ALLOW_UNAUTHENTICATED =
|
||||
process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
|
||||
|
||||
export const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
||||
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
||||
: 24;
|
||||
|
||||
export const HIDE_HISTORY = process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
|
||||
|
||||
export const WEBROOT = process.env.WEBROOT ?? "";
|
||||
|
||||
export const LANGUAGE = process.env.LANGUAGE?.toLowerCase() || "en";
|
||||
|
||||
export const MAX_CONVERT_PROCESS =
|
||||
process.env.MAX_CONVERT_PROCESS && Number(process.env.MAX_CONVERT_PROCESS) > 0
|
||||
? Number(process.env.MAX_CONVERT_PROCESS)
|
||||
: 0;
|
||||
|
||||
export const UNAUTHENTICATED_USER_SHARING =
|
||||
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
export const normalizeFiletype = (filetype: string): string => {
|
||||
const lowercaseFiletype = filetype.toLowerCase();
|
||||
|
||||
switch (lowercaseFiletype) {
|
||||
case "jfif":
|
||||
case "jpg":
|
||||
return "jpeg";
|
||||
case "htm":
|
||||
return "html";
|
||||
case "tex":
|
||||
return "latex";
|
||||
case "md":
|
||||
return "markdown";
|
||||
case "unknown":
|
||||
return "m4a";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
};
|
||||
|
||||
export const normalizeOutputFiletype = (filetype: string): string => {
|
||||
const lowercaseFiletype = filetype.toLowerCase();
|
||||
|
||||
switch (lowercaseFiletype) {
|
||||
case "jpeg":
|
||||
return "jpg";
|
||||
case "latex":
|
||||
return "tex";
|
||||
case "markdown_phpextra":
|
||||
case "markdown_strict":
|
||||
case "markdown_mmd":
|
||||
case "markdown":
|
||||
return "md";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
};
|
||||
export const normalizeFiletype = (filetype: string): string => {
|
||||
const lowercaseFiletype = filetype.toLowerCase();
|
||||
|
||||
switch (lowercaseFiletype) {
|
||||
case "jfif":
|
||||
case "jpg":
|
||||
return "jpeg";
|
||||
case "htm":
|
||||
return "html";
|
||||
case "tex":
|
||||
return "latex";
|
||||
case "md":
|
||||
return "markdown";
|
||||
case "unknown":
|
||||
return "m4a";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
};
|
||||
|
||||
export const normalizeOutputFiletype = (filetype: string): string => {
|
||||
const lowercaseFiletype = filetype.toLowerCase();
|
||||
|
||||
switch (lowercaseFiletype) {
|
||||
case "jpeg":
|
||||
return "jpg";
|
||||
case "latex":
|
||||
return "tex";
|
||||
case "markdown_phpextra":
|
||||
case "markdown_strict":
|
||||
case "markdown_mmd":
|
||||
case "markdown":
|
||||
return "md";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,186 +1,186 @@
|
|||
import { exec } from "node:child_process";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
console.log(`ConvertX v${version}`);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
exec("cat /etc/os-release", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Not running on docker, this is not supported.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("pandoc -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Pandoc is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("ffmpeg -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("FFmpeg is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("vips -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Vips is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("magick --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ImageMagick is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]?.replace("Version: ", ""));
|
||||
}
|
||||
});
|
||||
|
||||
exec("gm version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("GraphicsMagick is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("inkscape --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Inkscape is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("djxl --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libjxl-tools is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("dasel --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("dasel is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("xelatex -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Tex Live with XeTeX is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("resvg -V", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("resvg is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`resvg v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("assimp version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("assimp is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`assimp ${stdout.split("\n")[5]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("ebook-convert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ebook-convert (calibre) is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("heif-info -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libheif is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`libheif v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("potrace -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("potrace is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("soffice --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libreoffice is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("msgconvert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("bun -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Bun is not installed. wait what");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`Bun v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
import { exec } from "node:child_process";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
console.log(`ConvertX v${version}`);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
exec("cat /etc/os-release", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Not running on docker, this is not supported.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split('PRETTY_NAME="')[1]?.split('"')[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("pandoc -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Pandoc is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("ffmpeg -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("FFmpeg is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("vips -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Vips is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("magick --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ImageMagick is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]?.replace("Version: ", ""));
|
||||
}
|
||||
});
|
||||
|
||||
exec("gm version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("GraphicsMagick is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("inkscape --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Inkscape is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("djxl --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libjxl-tools is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("dasel --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("dasel is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("xelatex -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Tex Live with XeTeX is not installed.");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("resvg -V", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("resvg is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`resvg v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("assimp version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("assimp is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`assimp ${stdout.split("\n")[5]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("ebook-convert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ebook-convert (calibre) is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("heif-info -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libheif is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`libheif v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("potrace -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("potrace is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("soffice --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libreoffice is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("msgconvert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
|
||||
exec("bun -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Bun is not installed. wait what");
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`Bun v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import tailwind from "@tailwindcss/postcss";
|
||||
import postcss from "postcss";
|
||||
|
||||
export const generateTailwind = async () => {
|
||||
const result = await Bun.file("./src/main.css")
|
||||
.text()
|
||||
.then((sourceText) => {
|
||||
return postcss([tailwind]).process(sourceText, {
|
||||
from: "./src/main.css",
|
||||
to: "./public/generated.css",
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
import tailwind from "@tailwindcss/postcss";
|
||||
import postcss from "postcss";
|
||||
|
||||
export const generateTailwind = async () => {
|
||||
const result = await Bun.file("./src/main.css")
|
||||
.text()
|
||||
.then((sourceText) => {
|
||||
return postcss([tailwind]).process(sourceText, {
|
||||
from: "./src/main.css",
|
||||
to: "./public/generated.css",
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
|||
188
src/index.tsx
188
src/index.tsx
|
|
@ -1,94 +1,94 @@
|
|||
import { rmSync } from "node:fs";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { html } from "@elysiajs/html";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
import { Elysia } from "elysia";
|
||||
import "./helpers/printVersions";
|
||||
import db from "./db/db";
|
||||
import { Jobs } from "./db/types";
|
||||
import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env";
|
||||
import { chooseConverter } from "./pages/chooseConverter";
|
||||
import { convert } from "./pages/convert";
|
||||
import { deleteFile } from "./pages/deleteFile";
|
||||
import { download } from "./pages/download";
|
||||
import { history } from "./pages/history";
|
||||
import { listConverters } from "./pages/listConverters";
|
||||
import { results } from "./pages/results";
|
||||
import { root } from "./pages/root";
|
||||
import { upload } from "./pages/upload";
|
||||
import { user } from "./pages/user";
|
||||
|
||||
mkdir("./data", { recursive: true }).catch(console.error);
|
||||
|
||||
export const uploadsDir = "./data/uploads/";
|
||||
export const outputDir = "./data/output/";
|
||||
|
||||
const app = new Elysia({
|
||||
serve: {
|
||||
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
prefix: WEBROOT,
|
||||
})
|
||||
.use(html())
|
||||
.use(
|
||||
staticPlugin({
|
||||
assets: "public",
|
||||
prefix: "",
|
||||
}),
|
||||
)
|
||||
.use(user)
|
||||
.use(root)
|
||||
.use(upload)
|
||||
.use(history)
|
||||
.use(convert)
|
||||
.use(download)
|
||||
.use(results)
|
||||
.use(deleteFile)
|
||||
.use(listConverters)
|
||||
.use(chooseConverter)
|
||||
.onError(({ error }) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
await import("./helpers/tailwind").then(async ({ generateTailwind }) => {
|
||||
const result = await generateTailwind();
|
||||
|
||||
app.get("/generated.css", ({ set }) => {
|
||||
set.headers["content-type"] = "text/css";
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`);
|
||||
|
||||
const clearJobs = () => {
|
||||
const jobs = db
|
||||
.query("SELECT * FROM jobs WHERE date_created < ?")
|
||||
.as(Jobs)
|
||||
.all(new Date(Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000).toISOString());
|
||||
|
||||
for (const job of jobs) {
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
}
|
||||
|
||||
setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
|
||||
};
|
||||
|
||||
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
|
||||
clearJobs();
|
||||
}
|
||||
import { rmSync } from "node:fs";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { html } from "@elysiajs/html";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
import { Elysia } from "elysia";
|
||||
import "./helpers/printVersions";
|
||||
import db from "./db/db";
|
||||
import { Jobs } from "./db/types";
|
||||
import { AUTO_DELETE_EVERY_N_HOURS, WEBROOT } from "./helpers/env";
|
||||
import { chooseConverter } from "./pages/chooseConverter";
|
||||
import { convert } from "./pages/convert";
|
||||
import { deleteFile } from "./pages/deleteFile";
|
||||
import { download } from "./pages/download";
|
||||
import { history } from "./pages/history";
|
||||
import { listConverters } from "./pages/listConverters";
|
||||
import { results } from "./pages/results";
|
||||
import { root } from "./pages/root";
|
||||
import { upload } from "./pages/upload";
|
||||
import { user } from "./pages/user";
|
||||
|
||||
mkdir("./data", { recursive: true }).catch(console.error);
|
||||
|
||||
export const uploadsDir = "./data/uploads/";
|
||||
export const outputDir = "./data/output/";
|
||||
|
||||
const app = new Elysia({
|
||||
serve: {
|
||||
maxRequestBodySize: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
prefix: WEBROOT,
|
||||
})
|
||||
.use(html())
|
||||
.use(
|
||||
staticPlugin({
|
||||
assets: "public",
|
||||
prefix: "",
|
||||
}),
|
||||
)
|
||||
.use(user)
|
||||
.use(root)
|
||||
.use(upload)
|
||||
.use(history)
|
||||
.use(convert)
|
||||
.use(download)
|
||||
.use(results)
|
||||
.use(deleteFile)
|
||||
.use(listConverters)
|
||||
.use(chooseConverter)
|
||||
.onError(({ error }) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
await import("./helpers/tailwind").then(async ({ generateTailwind }) => {
|
||||
const result = await generateTailwind();
|
||||
|
||||
app.get("/generated.css", ({ set }) => {
|
||||
set.headers["content-type"] = "text/css";
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(3000);
|
||||
|
||||
console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`);
|
||||
|
||||
const clearJobs = () => {
|
||||
const jobs = db
|
||||
.query("SELECT * FROM jobs WHERE date_created < ?")
|
||||
.as(Jobs)
|
||||
.all(new Date(Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000).toISOString());
|
||||
|
||||
for (const job of jobs) {
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
}
|
||||
|
||||
setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
|
||||
};
|
||||
|
||||
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
|
||||
clearJobs();
|
||||
}
|
||||
|
|
|
|||
64
src/main.css
64
src/main.css
|
|
@ -1,32 +1,32 @@
|
|||
@import "./theme/theme.css";
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwind-scrollbar";
|
||||
|
||||
@theme {
|
||||
--color-contrast: var(--contrast);
|
||||
--color-neutral-900: var(--neutral-900);
|
||||
--color-neutral-800: var(--neutral-800);
|
||||
--color-neutral-700: var(--neutral-700);
|
||||
--color-neutral-600: var(--neutral-600);
|
||||
--color-neutral-500: var(--neutral-500);
|
||||
--color-neutral-400: var(--neutral-400);
|
||||
--color-neutral-300: var(--neutral-300);
|
||||
--color-neutral-200: var(--neutral-200);
|
||||
--color-neutral-100: var(--neutral-100);
|
||||
--color-accent-600: var(--accent-600);
|
||||
--color-accent-500: var(--accent-500);
|
||||
--color-accent-400: var(--accent-400);
|
||||
}
|
||||
|
||||
@utility article {
|
||||
@apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
|
||||
}
|
||||
|
||||
@utility btn-primary {
|
||||
@apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
|
||||
}
|
||||
|
||||
@utility btn-secondary {
|
||||
@apply bg-neutral-400 text-contrast rounded-sm p-2 sm:p-4 hover:bg-neutral-300 cursor-pointer transition-colors;
|
||||
}
|
||||
@import "./theme/theme.css";
|
||||
@import "tailwindcss";
|
||||
|
||||
@plugin "tailwind-scrollbar";
|
||||
|
||||
@theme {
|
||||
--color-contrast: var(--contrast);
|
||||
--color-neutral-900: var(--neutral-900);
|
||||
--color-neutral-800: var(--neutral-800);
|
||||
--color-neutral-700: var(--neutral-700);
|
||||
--color-neutral-600: var(--neutral-600);
|
||||
--color-neutral-500: var(--neutral-500);
|
||||
--color-neutral-400: var(--neutral-400);
|
||||
--color-neutral-300: var(--neutral-300);
|
||||
--color-neutral-200: var(--neutral-200);
|
||||
--color-neutral-100: var(--neutral-100);
|
||||
--color-accent-600: var(--accent-600);
|
||||
--color-accent-500: var(--accent-500);
|
||||
--color-accent-400: var(--accent-400);
|
||||
}
|
||||
|
||||
@utility article {
|
||||
@apply px-2 sm:px-4 py-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded-sm;
|
||||
}
|
||||
|
||||
@utility btn-primary {
|
||||
@apply bg-accent-500 text-contrast rounded-sm p-2 sm:p-4 hover:bg-accent-400 cursor-pointer transition-colors;
|
||||
}
|
||||
|
||||
@utility btn-secondary {
|
||||
@apply bg-neutral-400 text-contrast rounded-sm p-2 sm:p-4 hover:bg-neutral-300 cursor-pointer transition-colors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +1,67 @@
|
|||
import { Html } from "@elysiajs/html";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { getPossibleTargets } from "../converters/main";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const chooseConverter = new Elysia().use(userService).post(
|
||||
"/conversions",
|
||||
({ body }) => {
|
||||
return (
|
||||
<>
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`convert_to_group flex w-full flex-col border-b border-neutral-700 p-4`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
);
|
||||
},
|
||||
{ body: t.Object({ fileType: t.String() }) },
|
||||
);
|
||||
import { Html } from "@elysiajs/html";
|
||||
import Elysia, { t } from "elysia";
|
||||
import { getPossibleTargets } from "../converters/main";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const chooseConverter = new Elysia().use(userService).post(
|
||||
"/conversions",
|
||||
({ body }) => {
|
||||
return (
|
||||
<>
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`convert_to_group flex w-full flex-col border-b border-neutral-700 p-4`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class="convert_to_target flex flex-row flex-wrap gap-1">
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getPossibleTargets(body.fileType)).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
);
|
||||
},
|
||||
{ body: t.Object({ fileType: t.String() }) },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,94 +1,94 @@
|
|||
import { mkdir } from "node:fs/promises";
|
||||
import { Elysia, t } from "elysia";
|
||||
import sanitize from "sanitize-filename";
|
||||
import { outputDir, uploadsDir } from "..";
|
||||
import { handleConvert } from "../converters/main";
|
||||
import db from "../db/db";
|
||||
import { Jobs } from "../db/types";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const convert = new Elysia().use(userService).post(
|
||||
"/convert",
|
||||
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
const user = await jwt.verify(auth.value);
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.as(Jobs)
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
const userOutputDir = `${outputDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
// create the output directory
|
||||
try {
|
||||
await mkdir(userOutputDir, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error(`Failed to create the output directory: ${userOutputDir}.`, error);
|
||||
}
|
||||
|
||||
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
|
||||
const converterName = body.convert_to.split(",")[1];
|
||||
|
||||
if (!converterName) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const fileNames = JSON.parse(body.file_names) as string[];
|
||||
|
||||
for (let i = 0; i < fileNames.length; i++) {
|
||||
fileNames[i] = sanitize(fileNames[i] || "");
|
||||
}
|
||||
|
||||
if (!Array.isArray(fileNames) || fileNames.length === 0) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run(
|
||||
fileNames.length,
|
||||
jobId.value,
|
||||
);
|
||||
|
||||
// Start the conversion process in the background
|
||||
handleConvert(fileNames, userUploadsDir, userOutputDir, convertTo, converterName, jobId)
|
||||
.then(() => {
|
||||
// All conversions are done, update the job status to 'completed'
|
||||
if (jobId.value) {
|
||||
db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(jobId.value);
|
||||
}
|
||||
|
||||
// Delete all uploaded files in userUploadsDir
|
||||
// rmSync(userUploadsDir, { recursive: true, force: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error in conversion process:", error);
|
||||
});
|
||||
|
||||
// Redirect the client immediately
|
||||
return redirect(`${WEBROOT}/results/${jobId.value}`, 302);
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
convert_to: t.String(),
|
||||
file_names: t.String(),
|
||||
}),
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { Elysia, t } from "elysia";
|
||||
import sanitize from "sanitize-filename";
|
||||
import { outputDir, uploadsDir } from "..";
|
||||
import { handleConvert } from "../converters/main";
|
||||
import db from "../db/db";
|
||||
import { Jobs } from "../db/types";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const convert = new Elysia().use(userService).post(
|
||||
"/convert",
|
||||
async ({ body, redirect, jwt, cookie: { auth, jobId } }) => {
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
const user = await jwt.verify(auth.value);
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.as(Jobs)
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
const userOutputDir = `${outputDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
// create the output directory
|
||||
try {
|
||||
await mkdir(userOutputDir, { recursive: true });
|
||||
} catch (error) {
|
||||
console.error(`Failed to create the output directory: ${userOutputDir}.`, error);
|
||||
}
|
||||
|
||||
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
|
||||
const converterName = body.convert_to.split(",")[1];
|
||||
|
||||
if (!converterName) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const fileNames = JSON.parse(body.file_names) as string[];
|
||||
|
||||
for (let i = 0; i < fileNames.length; i++) {
|
||||
fileNames[i] = sanitize(fileNames[i] || "");
|
||||
}
|
||||
|
||||
if (!Array.isArray(fileNames) || fileNames.length === 0) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
db.query("UPDATE jobs SET num_files = ?1, status = 'pending' WHERE id = ?2").run(
|
||||
fileNames.length,
|
||||
jobId.value,
|
||||
);
|
||||
|
||||
// Start the conversion process in the background
|
||||
handleConvert(fileNames, userUploadsDir, userOutputDir, convertTo, converterName, jobId)
|
||||
.then(() => {
|
||||
// All conversions are done, update the job status to 'completed'
|
||||
if (jobId.value) {
|
||||
db.query("UPDATE jobs SET status = 'completed' WHERE id = ?1").run(jobId.value);
|
||||
}
|
||||
|
||||
// Delete all uploaded files in userUploadsDir
|
||||
// rmSync(userUploadsDir, { recursive: true, force: true });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error in conversion process:", error);
|
||||
});
|
||||
|
||||
// Redirect the client immediately
|
||||
return redirect(`${WEBROOT}/results/${jobId.value}`, 302);
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
convert_to: t.String(),
|
||||
file_names: t.String(),
|
||||
}),
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,32 +1,32 @@
|
|||
import { unlink } from "node:fs/promises";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { uploadsDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const deleteFile = new Elysia().use(userService).post(
|
||||
"/delete",
|
||||
async ({ body, redirect, cookie: { jobId }, user }) => {
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = await db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
await unlink(`${userUploadsDir}${body.filename}`);
|
||||
|
||||
return {
|
||||
message: "File deleted successfully.",
|
||||
};
|
||||
},
|
||||
{ body: t.Object({ filename: t.String() }), auth: true },
|
||||
);
|
||||
import { unlink } from "node:fs/promises";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { uploadsDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const deleteFile = new Elysia().use(userService).post(
|
||||
"/delete",
|
||||
async ({ body, redirect, cookie: { jobId }, user }) => {
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = await db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
await unlink(`${userUploadsDir}${body.filename}`);
|
||||
|
||||
return {
|
||||
message: "File deleted successfully.",
|
||||
};
|
||||
},
|
||||
{ body: t.Object({ filename: t.String() }), auth: true },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,61 +1,61 @@
|
|||
import path from "node:path";
|
||||
import { Elysia, t } from 'elysia'
|
||||
import sanitize from "sanitize-filename";
|
||||
import * as tar from "tar";
|
||||
import { outputDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const download = new Elysia()
|
||||
.use(userService)
|
||||
.get(
|
||||
"/download/:userId/:jobId/:fileName",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
// parse from URL encoded string
|
||||
const userId = decodeURIComponent(params.userId);
|
||||
const jobId = decodeURIComponent(params.jobId);
|
||||
const fileName = sanitize(decodeURIComponent(params.fileName));
|
||||
|
||||
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
||||
return Bun.file(filePath);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
}
|
||||
)
|
||||
.get("/archive/:userId/:jobId", async ({ params, redirect, user }) => {
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
|
||||
const userId = decodeURIComponent(params.userId);
|
||||
const jobId = decodeURIComponent(params.jobId);
|
||||
const outputPath = `${outputDir}${userId}/${jobId}`;
|
||||
const outputTar = path.join(outputPath, `converted_files_${jobId}.tar`);
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
file: outputTar,
|
||||
cwd: outputPath,
|
||||
filter: (path) => {
|
||||
return !path.match(".*\\.tar");
|
||||
},
|
||||
},
|
||||
["."],
|
||||
);
|
||||
return Bun.file(outputTar);
|
||||
}, {
|
||||
auth: true,
|
||||
});
|
||||
import path from "node:path";
|
||||
import { Elysia, t } from 'elysia'
|
||||
import sanitize from "sanitize-filename";
|
||||
import * as tar from "tar";
|
||||
import { outputDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const download = new Elysia()
|
||||
.use(userService)
|
||||
.get(
|
||||
"/download/:userId/:jobId/:fileName",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
// parse from URL encoded string
|
||||
const userId = decodeURIComponent(params.userId);
|
||||
const jobId = decodeURIComponent(params.jobId);
|
||||
const fileName = sanitize(decodeURIComponent(params.fileName));
|
||||
|
||||
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
||||
return Bun.file(filePath);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
}
|
||||
)
|
||||
.get("/archive/:userId/:jobId", async ({ params, redirect, user }) => {
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
|
||||
const userId = decodeURIComponent(params.userId);
|
||||
const jobId = decodeURIComponent(params.jobId);
|
||||
const outputPath = `${outputDir}${userId}/${jobId}`;
|
||||
const outputTar = path.join(outputPath, `converted_files_${jobId}.tar`);
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
file: outputTar,
|
||||
cwd: outputPath,
|
||||
filter: (path) => {
|
||||
return !path.match(".*\\.tar");
|
||||
},
|
||||
},
|
||||
["."],
|
||||
);
|
||||
return Bun.file(outputTar);
|
||||
}, {
|
||||
auth: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,213 +1,213 @@
|
|||
import { Html } from "@elysiajs/html";
|
||||
import { Elysia } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const history = new Elysia()
|
||||
.use(userService)
|
||||
.get("/history", async ({ jwt, redirect, user }) => {
|
||||
if (HIDE_HISTORY) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
let userJobs = db.query("SELECT * FROM jobs WHERE user_id = ?").as(Jobs).all(user.id).reverse();
|
||||
|
||||
for (const job of userJobs) {
|
||||
const files = db.query("SELECT * FROM file_names WHERE job_id = ?").as(Filename).all(job.id);
|
||||
|
||||
job.finished_files = files.length;
|
||||
job.files_detailed = files;
|
||||
}
|
||||
|
||||
// Filter out jobs with no files
|
||||
userJobs = userJobs.filter((job) => job.num_files > 0);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Results">
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Results</h1>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<span class="sr-only">Expand details</span>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Time
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Files
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
max-sm:hidden
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Files Done
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
View
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="inline-block h-4 w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td safe>{new Date(job.date_created).toLocaleTimeString(LANGUAGE)}</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/results/${job.id}`}
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="6">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
<div class="flex items-center">
|
||||
<span class="w-5/12 truncate" title={file.file_name} safe>
|
||||
{file.file_name}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
||||
{file.output_file_name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script>
|
||||
{`
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
const jobId = this.dataset.jobId;
|
||||
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
||||
// The arrow SVG itself has the ID arrow-\${jobId}
|
||||
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
||||
|
||||
if (detailsRow && arrow) {
|
||||
detailsRow.classList.toggle("hidden");
|
||||
if (detailsRow.classList.contains("hidden")) {
|
||||
// Right-facing arrow (collapsed)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
||||
} else {
|
||||
// Down-facing arrow (expanded)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
`}
|
||||
</script>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
auth: true
|
||||
});
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { Elysia } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const history = new Elysia()
|
||||
.use(userService)
|
||||
.get("/history", async ({ jwt, redirect, user }) => {
|
||||
if (HIDE_HISTORY) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
let userJobs = db.query("SELECT * FROM jobs WHERE user_id = ?").as(Jobs).all(user.id).reverse();
|
||||
|
||||
for (const job of userJobs) {
|
||||
const files = db.query("SELECT * FROM file_names WHERE job_id = ?").as(Filename).all(job.id);
|
||||
|
||||
job.finished_files = files.length;
|
||||
job.files_detailed = files;
|
||||
}
|
||||
|
||||
// Filter out jobs with no files
|
||||
userJobs = userJobs.filter((job) => job.num_files > 0);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Results">
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Results</h1>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<span class="sr-only">Expand details</span>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Time
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Files
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
max-sm:hidden
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Files Done
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
View
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="inline-block h-4 w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td safe>{new Date(job.date_created).toLocaleTimeString(LANGUAGE)}</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/results/${job.id}`}
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="6">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
<div class="flex items-center">
|
||||
<span class="w-5/12 truncate" title={file.file_name} safe>
|
||||
{file.file_name}
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M12.293 5.293a1 1 0 011.414 0l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-2.293-2.293a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span class="w-5/12 truncate" title={file.output_file_name} safe>
|
||||
{file.output_file_name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
<script>
|
||||
{`
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
const jobId = this.dataset.jobId;
|
||||
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
||||
// The arrow SVG itself has the ID arrow-\${jobId}
|
||||
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
||||
|
||||
if (detailsRow && arrow) {
|
||||
detailsRow.classList.toggle("hidden");
|
||||
if (detailsRow.classList.contains("hidden")) {
|
||||
// Right-facing arrow (collapsed)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
||||
} else {
|
||||
// Down-facing arrow (expanded)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
`}
|
||||
</script>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
auth: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,73 +1,73 @@
|
|||
import { Html } from "@elysiajs/html";
|
||||
import Elysia from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import { getAllInputs, getAllTargets } from "../converters/main";
|
||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const listConverters = new Elysia()
|
||||
.use(userService)
|
||||
.get("/converters", async () => {
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Converters</h1>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
[&_ul]:list-inside [&_ul]:list-disc
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="mx-4 my-2">Converter</th>
|
||||
<th class="mx-4 my-2">From (Count)</th>
|
||||
<th class="mx-4 my-2">To (Count)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => {
|
||||
const inputs = getAllInputs(converter);
|
||||
return (
|
||||
<tr>
|
||||
<td safe>{converter}</td>
|
||||
<td>
|
||||
Count: {inputs.length}
|
||||
<ul>
|
||||
{inputs.map((input) => (
|
||||
<li safe>{input}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
Count: {targets.length}
|
||||
<ul>
|
||||
{targets.map((target) => (
|
||||
<li safe>{target}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
auth: true
|
||||
});
|
||||
import { Html } from "@elysiajs/html";
|
||||
import Elysia from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import { getAllInputs, getAllTargets } from "../converters/main";
|
||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const listConverters = new Elysia()
|
||||
.use(userService)
|
||||
.get("/converters", async () => {
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Converters">
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Converters</h1>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
[&_ul]:list-inside [&_ul]:list-disc
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="mx-4 my-2">Converter</th>
|
||||
<th class="mx-4 my-2">From (Count)</th>
|
||||
<th class="mx-4 my-2">To (Count)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => {
|
||||
const inputs = getAllInputs(converter);
|
||||
return (
|
||||
<tr>
|
||||
<td safe>{converter}</td>
|
||||
<td>
|
||||
Count: {inputs.length}
|
||||
<ul>
|
||||
{inputs.map((input) => (
|
||||
<li safe>{input}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
Count: {targets.length}
|
||||
<ul>
|
||||
{targets.map((target) => (
|
||||
<li safe>{target}</li>
|
||||
))}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
auth: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,207 +1,207 @@
|
|||
import { Html } from "@elysiajs/html";
|
||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||
import { Elysia } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
function ResultsArticle({
|
||||
user,
|
||||
job,
|
||||
files,
|
||||
outputPath,
|
||||
}: {
|
||||
user: {
|
||||
id: string;
|
||||
} & JWTPayloadSpec;
|
||||
job: Jobs;
|
||||
files: Filename[];
|
||||
outputPath: string;
|
||||
}) {
|
||||
return (
|
||||
<article class="article">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<div>
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
||||
download={`converted_files_${job.id}.tar`}
|
||||
>
|
||||
<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 ? "Download All" : "Converting..."}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<progress
|
||||
max={job.num_files}
|
||||
value={files.length}
|
||||
class={`
|
||||
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
|
||||
[&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||
`}
|
||||
/>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Converted File Name
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
View
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Download
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
<tr>
|
||||
<td safe class="max-w-[20vw] truncate">
|
||||
{file.output_file_name}
|
||||
</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
download={file.output_file_name}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export const results = new Elysia()
|
||||
.use(userService)
|
||||
.get("/results/:jobId", async ({ params, jwt, set, redirect, cookie: { job_id }, user }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
}
|
||||
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
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 = ?")
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Result">
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />
|
||||
</main>
|
||||
<script src={`${WEBROOT}/results.js`} defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, { auth: true })
|
||||
.post("/progress/:jobId", async ({ jwt, set, params, cookie: { job_id }, user }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
}
|
||||
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
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 = ?")
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return <ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />;
|
||||
}, { auth: true });
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||
import { Elysia } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
|
||||
function ResultsArticle({
|
||||
user,
|
||||
job,
|
||||
files,
|
||||
outputPath,
|
||||
}: {
|
||||
user: {
|
||||
id: string;
|
||||
} & JWTPayloadSpec;
|
||||
job: Jobs;
|
||||
files: Filename[];
|
||||
outputPath: string;
|
||||
}) {
|
||||
return (
|
||||
<article class="article">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<div>
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
href={`${WEBROOT}/archive/${user.id}/${job.id}`}
|
||||
download={`converted_files_${job.id}.tar`}
|
||||
>
|
||||
<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 ? "Download All" : "Converting..."}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<progress
|
||||
max={job.num_files}
|
||||
value={files.length}
|
||||
class={`
|
||||
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
|
||||
[&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||
`}
|
||||
/>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Converted File Name
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
View
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
Download
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
<tr>
|
||||
<td safe class="max-w-[20vw] truncate">
|
||||
{file.output_file_name}
|
||||
</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
>
|
||||
View
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
download={file.output_file_name}
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export const results = new Elysia()
|
||||
.use(userService)
|
||||
.get("/results/:jobId", async ({ params, jwt, set, redirect, cookie: { job_id }, user }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
}
|
||||
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
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 = ?")
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT} title="ConvertX | Result">
|
||||
<>
|
||||
<Header webroot={WEBROOT} allowUnauthenticated={ALLOW_UNAUTHENTICATED} loggedIn />
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />
|
||||
</main>
|
||||
<script src={`${WEBROOT}/results.js`} defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, { auth: true })
|
||||
.post("/progress/:jobId", async ({ jwt, set, params, cookie: { job_id }, user }) => {
|
||||
if (job_id?.value) {
|
||||
// Clear the job_id cookie since we are viewing the results
|
||||
job_id.remove();
|
||||
}
|
||||
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
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 = ?")
|
||||
.as(Filename)
|
||||
.all(params.jobId);
|
||||
|
||||
return <ResultsArticle user={user} job={job} files={files} outputPath={outputPath} />;
|
||||
}, { auth: true });
|
||||
|
|
|
|||
|
|
@ -1,248 +1,248 @@
|
|||
import { randomInt } from "node:crypto";
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import { getAllTargets } from "../converters/main";
|
||||
import db from "../db/db";
|
||||
import { User } from "../db/types";
|
||||
import {
|
||||
ACCOUNT_REGISTRATION,
|
||||
ALLOW_UNAUTHENTICATED,
|
||||
HIDE_HISTORY,
|
||||
HTTP_ALLOWED,
|
||||
UNAUTHENTICATED_USER_SHARING,
|
||||
WEBROOT,
|
||||
} from "../helpers/env";
|
||||
import { FIRST_RUN, userService } from "./user";
|
||||
|
||||
export const root = new Elysia()
|
||||
.use(userService)
|
||||
.get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => {
|
||||
if (!ALLOW_UNAUTHENTICATED) {
|
||||
if (FIRST_RUN) {
|
||||
return redirect(`${WEBROOT}/setup`, 302);
|
||||
}
|
||||
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
|
||||
// validate jwt
|
||||
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||
if (ALLOW_UNAUTHENTICATED) {
|
||||
const newUserId = String(
|
||||
UNAUTHENTICATED_USER_SHARING
|
||||
? 0
|
||||
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
||||
);
|
||||
const accessToken = await jwt.sign({
|
||||
id: newUserId,
|
||||
});
|
||||
|
||||
user = { id: newUserId };
|
||||
if (!auth) {
|
||||
return {
|
||||
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
||||
};
|
||||
}
|
||||
|
||||
// set cookie
|
||||
auth.set({
|
||||
value: accessToken,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
} else if (auth?.value) {
|
||||
user = await jwt.verify(auth.value);
|
||||
|
||||
if (
|
||||
user !== false &&
|
||||
user.id &&
|
||||
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
||||
) {
|
||||
// Make sure user exists in db
|
||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
||||
|
||||
if (!existingUser) {
|
||||
if (auth?.value) {
|
||||
auth.remove();
|
||||
}
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
// create a new job
|
||||
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
||||
user.id,
|
||||
new Date().toISOString(),
|
||||
);
|
||||
|
||||
const { id } = db
|
||||
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
||||
.get(user.id) as { id: number };
|
||||
|
||||
if (!jobId) {
|
||||
return { message: "Cookies should be enabled to use this app." };
|
||||
}
|
||||
|
||||
jobId.set({
|
||||
value: id,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
console.log("jobId set to:", id);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
accountRegistration={ACCOUNT_REGISTRATION}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Convert</h1>
|
||||
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900
|
||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="dropzone"
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||
border-neutral-700 transition-all
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
<b>Choose a file</b> or drag it here
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
multiple
|
||||
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
<form
|
||||
method="post"
|
||||
action={`${WEBROOT}/convert`}
|
||||
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
||||
>
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article class="article w-full">
|
||||
<input
|
||||
type="search"
|
||||
name="convert_to_search"
|
||||
placeholder="Search for conversions"
|
||||
autocomplete="off"
|
||||
class="w-full rounded-sm bg-neutral-800 p-4"
|
||||
/>
|
||||
<div class="select_container relative">
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`
|
||||
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||
`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</article>
|
||||
<input
|
||||
class={`
|
||||
w-full btn-primary opacity-100
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
type="submit"
|
||||
value="Convert"
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
cookie: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
})
|
||||
});
|
||||
import { randomInt } from "node:crypto";
|
||||
import { Html } from "@elysiajs/html";
|
||||
import { JWTPayloadSpec } from "@elysiajs/jwt";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import { getAllTargets } from "../converters/main";
|
||||
import db from "../db/db";
|
||||
import { User } from "../db/types";
|
||||
import {
|
||||
ACCOUNT_REGISTRATION,
|
||||
ALLOW_UNAUTHENTICATED,
|
||||
HIDE_HISTORY,
|
||||
HTTP_ALLOWED,
|
||||
UNAUTHENTICATED_USER_SHARING,
|
||||
WEBROOT,
|
||||
} from "../helpers/env";
|
||||
import { FIRST_RUN, userService } from "./user";
|
||||
|
||||
export const root = new Elysia()
|
||||
.use(userService)
|
||||
.get("/", async ({ jwt, redirect, cookie: { auth, jobId } }) => {
|
||||
if (!ALLOW_UNAUTHENTICATED) {
|
||||
if (FIRST_RUN) {
|
||||
return redirect(`${WEBROOT}/setup`, 302);
|
||||
}
|
||||
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
|
||||
// validate jwt
|
||||
let user: ({ id: string } & JWTPayloadSpec) | false = false;
|
||||
if (ALLOW_UNAUTHENTICATED) {
|
||||
const newUserId = String(
|
||||
UNAUTHENTICATED_USER_SHARING
|
||||
? 0
|
||||
: randomInt(2 ** 24, Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER)),
|
||||
);
|
||||
const accessToken = await jwt.sign({
|
||||
id: newUserId,
|
||||
});
|
||||
|
||||
user = { id: newUserId };
|
||||
if (!auth) {
|
||||
return {
|
||||
message: "No auth cookie, perhaps your browser is blocking cookies.",
|
||||
};
|
||||
}
|
||||
|
||||
// set cookie
|
||||
auth.set({
|
||||
value: accessToken,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
} else if (auth?.value) {
|
||||
user = await jwt.verify(auth.value);
|
||||
|
||||
if (
|
||||
user !== false &&
|
||||
user.id &&
|
||||
(Number.parseInt(user.id) < 2 ** 24 || !ALLOW_UNAUTHENTICATED)
|
||||
) {
|
||||
// Make sure user exists in db
|
||||
const existingUser = db.query("SELECT * FROM users WHERE id = ?").as(User).get(user.id);
|
||||
|
||||
if (!existingUser) {
|
||||
if (auth?.value) {
|
||||
auth.remove();
|
||||
}
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
|
||||
// create a new job
|
||||
db.query("INSERT INTO jobs (user_id, date_created) VALUES (?, ?)").run(
|
||||
user.id,
|
||||
new Date().toISOString(),
|
||||
);
|
||||
|
||||
const { id } = db
|
||||
.query("SELECT id FROM jobs WHERE user_id = ? ORDER BY id DESC")
|
||||
.get(user.id) as { id: number };
|
||||
|
||||
if (!jobId) {
|
||||
return { message: "Cookies should be enabled to use this app." };
|
||||
}
|
||||
|
||||
jobId.set({
|
||||
value: id,
|
||||
httpOnly: true,
|
||||
secure: !HTTP_ALLOWED,
|
||||
maxAge: 24 * 60 * 60,
|
||||
sameSite: "strict",
|
||||
});
|
||||
|
||||
console.log("jobId set to:", id);
|
||||
|
||||
return (
|
||||
<BaseHtml webroot={WEBROOT}>
|
||||
<>
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
accountRegistration={ACCOUNT_REGISTRATION}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
w-full flex-1 px-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Convert</h1>
|
||||
<div class="mb-4 scrollbar-thin max-h-[50vh] overflow-y-auto">
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900
|
||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="dropzone"
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||
border-neutral-700 transition-all
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
<b>Choose a file</b> or drag it here
|
||||
</span>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
multiple
|
||||
class="absolute inset-0 size-full cursor-pointer opacity-0"
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
<form
|
||||
method="post"
|
||||
action={`${WEBROOT}/convert`}
|
||||
class="relative mx-auto mb-[35vh] w-full max-w-4xl"
|
||||
>
|
||||
<input type="hidden" name="file_names" id="file_names" />
|
||||
<article class="article w-full">
|
||||
<input
|
||||
type="search"
|
||||
name="convert_to_search"
|
||||
placeholder="Search for conversions"
|
||||
autocomplete="off"
|
||||
class="w-full rounded-sm bg-neutral-800 p-4"
|
||||
/>
|
||||
<div class="select_container relative">
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<article
|
||||
class={`
|
||||
convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
|
||||
`}
|
||||
data-converter={converter}
|
||||
>
|
||||
<header class="mb-2 w-full text-xl font-bold" safe>
|
||||
{converter}
|
||||
</header>
|
||||
<ul class={`convert_to_target flex flex-row flex-wrap gap-1`}>
|
||||
{targets.map((target) => (
|
||||
<button
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
data-target={target}
|
||||
data-converter={converter}
|
||||
type="button"
|
||||
safe
|
||||
>
|
||||
{target}
|
||||
</button>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
))}
|
||||
</article>
|
||||
|
||||
{/* Hidden element which determines the format to convert the file too and the converter to use */}
|
||||
<select name="convert_to" aria-label="Convert to" required hidden>
|
||||
<option selected disabled value="">
|
||||
Convert to
|
||||
</option>
|
||||
{Object.entries(getAllTargets()).map(([converter, targets]) => (
|
||||
<optgroup label={converter}>
|
||||
{targets.map((target) => (
|
||||
<option value={`${target},${converter}`} safe>
|
||||
{target}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</article>
|
||||
<input
|
||||
class={`
|
||||
w-full btn-primary opacity-100
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
type="submit"
|
||||
value="Convert"
|
||||
disabled
|
||||
/>
|
||||
</form>
|
||||
</main>
|
||||
<script src="script.js" defer />
|
||||
</>
|
||||
</BaseHtml>
|
||||
);
|
||||
}, {
|
||||
cookie: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,39 +1,39 @@
|
|||
import { Elysia, t } from "elysia";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { uploadsDir } from "../index";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const upload = new Elysia().use(userService).post(
|
||||
"/upload",
|
||||
async ({ body, redirect, user, cookie: { jobId } }) => {
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = await db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
if (body?.file) {
|
||||
if (Array.isArray(body.file)) {
|
||||
for (const file of body.file) {
|
||||
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
||||
}
|
||||
} else {
|
||||
await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Files uploaded successfully.",
|
||||
};
|
||||
},
|
||||
{ body: t.Object({ file: t.Files() }), auth: true },
|
||||
);
|
||||
import { Elysia, t } from "elysia";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { uploadsDir } from "../index";
|
||||
import { userService } from "./user";
|
||||
|
||||
export const upload = new Elysia().use(userService).post(
|
||||
"/upload",
|
||||
async ({ body, redirect, user, cookie: { jobId } }) => {
|
||||
if (!jobId?.value) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const existingJob = await db
|
||||
.query("SELECT * FROM jobs WHERE id = ? AND user_id = ?")
|
||||
.get(jobId.value, user.id);
|
||||
|
||||
if (!existingJob) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
|
||||
if (body?.file) {
|
||||
if (Array.isArray(body.file)) {
|
||||
for (const file of body.file) {
|
||||
await Bun.write(`${userUploadsDir}${file.name}`, file);
|
||||
}
|
||||
} else {
|
||||
await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: "Files uploaded successfully.",
|
||||
};
|
||||
},
|
||||
{ body: t.Object({ file: t.Files() }), auth: true },
|
||||
);
|
||||
|
|
|
|||
1036
src/pages/user.tsx
1036
src/pages/user.tsx
File diff suppressed because it is too large
Load diff
|
|
@ -1,47 +1,47 @@
|
|||
:root {
|
||||
/* Light mode */
|
||||
--contrast: oklch(100% 0 0);
|
||||
/* Neutral colors - Gray */
|
||||
--neutral-950: oklch(98.5% 0.002 247.839);
|
||||
--neutral-900: oklch(96.7% 0.003 264.542);
|
||||
--neutral-800: oklch(92.8% 0.006 264.531);
|
||||
--neutral-700: oklch(87.2% 0.01 258.338);
|
||||
--neutral-600: oklch(70.7% 0.022 261.325);
|
||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||
--neutral-400: oklch(44.6% 0.03 256.802);
|
||||
--neutral-300: oklch(37.3% 0.034 259.733);
|
||||
--neutral-200: oklch(26.9% 0 0);
|
||||
--neutral-100: oklch(21% 0.034 264.665);
|
||||
--neutral-50: oklch(13% 0.028 261.692);
|
||||
/* lime-700 */
|
||||
--accent-600: oklch(53.2% 0.157 131.589);
|
||||
/* lime-600 */
|
||||
--accent-500: oklch(64.8% 0.2 131.684);
|
||||
/* lime-500 */
|
||||
--accent-400: oklch(76.8% 0.233 130.85);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* Dark mode */
|
||||
:root {
|
||||
--contrast: oklch(0% 0 0);
|
||||
/* Neutral colors - Gray */
|
||||
--neutral-950: oklch(13% 0.028 261.692);
|
||||
--neutral-900: oklch(21% 0.034 264.665);
|
||||
--neutral-800: oklch(27.8% 0.033 256.848);
|
||||
--neutral-700: oklch(37.3% 0.034 259.733);
|
||||
--neutral-600: oklch(44.6% 0.03 256.802);
|
||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||
--neutral-400: oklch(70.7% 0.022 261.325);
|
||||
--neutral-300: oklch(87.2% 0.01 258.338);
|
||||
--neutral-200: oklch(92.8% 0.006 264.531);
|
||||
--neutral-100: oklch(96.7% 0.003 264.542);
|
||||
--neutral-50: oklch(98.5% 0.002 247.839);
|
||||
/* lime-600 */
|
||||
--accent-600: oklch(64.8% 0.2 131.684);
|
||||
/* lime-500 */
|
||||
--accent-500: oklch(76.8% 0.233 130.85);
|
||||
/* lime-400 */
|
||||
--accent-400: oklch(84.1% 0.238 128.85);
|
||||
}
|
||||
}
|
||||
:root {
|
||||
/* Light mode */
|
||||
--contrast: oklch(100% 0 0);
|
||||
/* Neutral colors - Gray */
|
||||
--neutral-950: oklch(98.5% 0.002 247.839);
|
||||
--neutral-900: oklch(96.7% 0.003 264.542);
|
||||
--neutral-800: oklch(92.8% 0.006 264.531);
|
||||
--neutral-700: oklch(87.2% 0.01 258.338);
|
||||
--neutral-600: oklch(70.7% 0.022 261.325);
|
||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||
--neutral-400: oklch(44.6% 0.03 256.802);
|
||||
--neutral-300: oklch(37.3% 0.034 259.733);
|
||||
--neutral-200: oklch(26.9% 0 0);
|
||||
--neutral-100: oklch(21% 0.034 264.665);
|
||||
--neutral-50: oklch(13% 0.028 261.692);
|
||||
/* lime-700 */
|
||||
--accent-600: oklch(53.2% 0.157 131.589);
|
||||
/* lime-600 */
|
||||
--accent-500: oklch(64.8% 0.2 131.684);
|
||||
/* lime-500 */
|
||||
--accent-400: oklch(76.8% 0.233 130.85);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* Dark mode */
|
||||
:root {
|
||||
--contrast: oklch(0% 0 0);
|
||||
/* Neutral colors - Gray */
|
||||
--neutral-950: oklch(13% 0.028 261.692);
|
||||
--neutral-900: oklch(21% 0.034 264.665);
|
||||
--neutral-800: oklch(27.8% 0.033 256.848);
|
||||
--neutral-700: oklch(37.3% 0.034 259.733);
|
||||
--neutral-600: oklch(44.6% 0.03 256.802);
|
||||
--neutral-500: oklch(55.1% 0.027 264.364);
|
||||
--neutral-400: oklch(70.7% 0.022 261.325);
|
||||
--neutral-300: oklch(87.2% 0.01 258.338);
|
||||
--neutral-200: oklch(92.8% 0.006 264.531);
|
||||
--neutral-100: oklch(96.7% 0.003 264.542);
|
||||
--neutral-50: oklch(98.5% 0.002 247.839);
|
||||
/* lime-600 */
|
||||
--accent-600: oklch(64.8% 0.2 131.684);
|
||||
/* lime-500 */
|
||||
--accent-500: oklch(76.8% 0.233 130.85);
|
||||
/* lime-400 */
|
||||
--accent-400: oklch(84.1% 0.238 128.85);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue