mirror of
https://github.com/C4illin/ConvertX.git
synced 2025-10-24 08:33:56 +00:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7914194856 | ||
|
|
2dac7f1362 | ||
|
|
a17e5fd614 | ||
|
|
21994fb6a2 | ||
|
|
a5eaaa422a | ||
|
|
ff2ef74135 | ||
|
|
70705c1850 | ||
|
|
fd9c151e01 | ||
|
|
4f0573963f | ||
|
|
6bb6bce8a4 | ||
|
|
448557bece | ||
|
|
bdbd4a122c | ||
|
|
cb9d0ec680 | ||
|
|
fb60ef66f5 | ||
|
|
c1ae43075f | ||
|
|
377f69ae8d | ||
|
|
cb131cd0a0 | ||
|
|
fcc83c5ea8 | ||
|
|
96d4717d13 | ||
|
|
4d73bf9760 | ||
|
|
725a94bc95 | ||
|
|
0a366b447a | ||
|
|
4a27a7bc03 | ||
|
|
3ca5803bda | ||
|
|
239041294c | ||
|
|
31fdd8f214 | ||
|
|
c3319c09eb | ||
|
|
d460e94d52 | ||
|
|
4b5c732380 | ||
|
|
f42665ca40 | ||
|
|
bed52cef17 | ||
|
|
9d1c93155c | ||
|
|
794cc7c474 | ||
|
|
d7d584e497 | ||
|
|
f5320df86e | ||
|
|
056fd4ba93 | ||
|
|
5b6e70eb3a | ||
|
|
f437a8e7e2 | ||
|
|
cdae798fcf | ||
|
|
bcc827a81b | ||
|
|
84274b9c55 | ||
|
|
20c6f8249e | ||
|
|
8f0ea2a592 | ||
|
|
a29e4a930a | ||
|
|
4549c96ae3 | ||
|
|
bc64094c04 | ||
|
|
fa58827ad5 | ||
|
|
8f27be0e3d | ||
|
|
df43df1178 | ||
|
|
c2beb4a227 | ||
|
|
9277c27a50 | ||
|
|
171ecd6884 | ||
|
|
dca29f7e5a | ||
|
|
318acc20bd | ||
|
|
f433493d57 | ||
|
|
19970fc132 |
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [C4illin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
## [0.13.0](https://github.com/C4illin/ConvertX/compare/v0.12.1...v0.13.0) (2025-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add HIDE_HISTORY option to control visibility of history page ([bed52ce](https://github.com/C4illin/ConvertX/commit/bed52cef17ff68ec5e8770705a1fdf038e02e607))
|
||||
* add HIDE_HISTORY option to control visibility of history page ([9d1c931](https://github.com/C4illin/ConvertX/commit/9d1c93155cc33ed6c83f9e5122afff8f28d0e4bf))
|
||||
* add potrace converter ([bdbd4a1](https://github.com/C4illin/ConvertX/commit/bdbd4a122c09559b089b985ea12c5f3e085107da))
|
||||
* Add support for .HIF files ([a5eaaa4](https://github.com/C4illin/ConvertX/commit/a5eaaa422a64506dd16d90d48a240556de33bc93))
|
||||
* Add support for .HIF files ([70705c1](https://github.com/C4illin/ConvertX/commit/70705c1850d470296df85958c02a01fb5bc3a25f))
|
||||
* add support for drag/drop of images ([ff2ef74](https://github.com/C4illin/ConvertX/commit/ff2ef7413542cf10ba7a6e246763bcecd6829ec1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add timezone support ([4b5c732](https://github.com/C4illin/ConvertX/commit/4b5c732380bc844dccf340ea1eb4f8bfe3bb44a5)), closes [#258](https://github.com/C4illin/ConvertX/issues/258)
|
||||
|
||||
## [0.12.1](https://github.com/C4illin/ConvertX/compare/v0.12.0...v0.12.1) (2025-03-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* rollback to bun 1.2.2 ([cdae798](https://github.com/C4illin/ConvertX/commit/cdae798fcf5879e4adea87386a38748b9a1e1ddc))
|
||||
|
||||
## [0.12.0](https://github.com/C4illin/ConvertX/compare/v0.11.1...v0.12.0) (2025-03-06)
|
||||
|
||||
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1.2.4-alpine AS base
|
||||
FROM oven/bun:1.2.2-alpine AS base
|
||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
|
||||
WORKDIR /app
|
||||
|
||||
@@ -20,10 +20,7 @@ ENV PATH=/root/.cargo/bin:$PATH
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
RUN cargo install resvg
|
||||
|
||||
# copy node_modules from temp directory
|
||||
# then copy all (non-ignored) project files into the image
|
||||
# will switch to alpine again when it works
|
||||
FROM oven/bun:1.2.4-slim AS prerelease
|
||||
FROM base AS prerelease
|
||||
WORKDIR /app
|
||||
COPY --from=install /temp/dev/node_modules node_modules
|
||||
COPY . .
|
||||
@@ -33,11 +30,8 @@ RUN bun run build
|
||||
|
||||
# copy production dependencies and source code into final image
|
||||
FROM base AS release
|
||||
LABEL maintainer="Emrik Östling (C4illin)"
|
||||
LABEL description="ConvertX: self-hosted online file converter supporting 700+ file formats."
|
||||
LABEL repo="https://github.com/C4illin/ConvertX"
|
||||
|
||||
RUN apk --no-cache add qt6-qtbase-private-dev libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
|
||||
RUN apk --no-cache add libheif-tools --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
|
||||
|
||||
# install additional dependencies
|
||||
RUN apk --no-cache add \
|
||||
@@ -59,9 +53,10 @@ RUN apk --no-cache add \
|
||||
poppler-utils \
|
||||
gcompat \
|
||||
libva-utils \
|
||||
py3-numpy
|
||||
py3-numpy \
|
||||
potrace
|
||||
|
||||
RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing/
|
||||
# RUN apk --no-cache add calibre@testing --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main/
|
||||
|
||||
# this might be needed for some latex use cases, will add it if needed.
|
||||
# texmf-dist-fontsextra \
|
||||
@@ -69,8 +64,6 @@ RUN apk --no-cache add calibre --repository=http://dl-cdn.alpinelinux.org/alpine
|
||||
COPY --from=install /temp/prod/node_modules node_modules
|
||||
COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
|
||||
COPY --from=prerelease /app/public/generated.css /app/public/
|
||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
|
||||
# COPY --from=prerelease /app/package.json .
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000/tcp
|
||||
|
||||
12
README.md
12
README.md
@@ -42,6 +42,9 @@ Any missing converter? Open an issue or pull request!
|
||||
|
||||
## Deployment
|
||||
|
||||
> [!WARNING]
|
||||
> If you can't login, make sure you are accessing the service over localhost or https otherwise set HTTP_ALLOWED=true
|
||||
|
||||
```yml
|
||||
# docker-compose.yml
|
||||
services:
|
||||
@@ -80,9 +83,7 @@ All are optional, JWT_SECRET is recommended to be set.
|
||||
| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable |
|
||||
| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
|
||||
| FFMPEG_ARGS | | Arguments to pass to ffmpeg, e.g. `-preset veryfast` |
|
||||
|
||||
> [!WARNING]
|
||||
> If you can't login, make sure you are accessing the service over https or set HTTP_ALLOWED=true
|
||||
| HIDE_HISTORY | false | Hide the history page |
|
||||
|
||||
### Docker images
|
||||
|
||||
@@ -97,10 +98,15 @@ The image is available on [GitHub Container Registry](https://github.com/C4illin
|
||||
| `image: c4illin/convertx` | The latest release on docker hub |
|
||||
| `image: c4illin/convertx:main` | The latest commit on docker hub |
|
||||
|
||||

|
||||

|
||||
<!-- Dockerhub was introduced in 0.9.0 and older releases -->
|
||||
|
||||
### Tutorial
|
||||
|
||||
> [!NOTE]
|
||||
> These are written by other people, and may be outdated, incorrect or wrong.
|
||||
|
||||
Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
|
||||
|
||||
Tutorial in chinese: <https://xzllll.com/24092901/>
|
||||
|
||||
9
SECURITY.md
Normal file
9
SECURITY.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest release is supported
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/C4illin/ConvertX/security/advisories/new) tab.
|
||||
@@ -13,5 +13,6 @@ services:
|
||||
- AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
|
||||
# - FFMPEG_ARGS=-hwaccel vulkan # additional arguments to pass to ffmpeg
|
||||
# - WEBROOT=/convertx # the root path of the web interface, leave empty to disable
|
||||
# - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
||||
52
package.json
52
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "convertx-frontend",
|
||||
"version": "0.12.0",
|
||||
"version": "0.13.0",
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/index.tsx",
|
||||
"hot": "bun run --hot src/index.tsx",
|
||||
@@ -12,12 +12,11 @@
|
||||
"lint:eslint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/cookie": "^0.8.0",
|
||||
"@elysiajs/html": "^1.2.0",
|
||||
"@elysiajs/jwt": "^1.2.0",
|
||||
"@elysiajs/static": "^1.2.0",
|
||||
"@kitajs/html": "^4.2.7",
|
||||
"elysia": "^1.2.12",
|
||||
"@elysiajs/html": "^1.3.0",
|
||||
"@elysiajs/jwt": "^1.3.0",
|
||||
"@elysiajs/static": "^1.3.0",
|
||||
"@kitajs/html": "^4.2.9",
|
||||
"elysia": "^1.3.1",
|
||||
"sanitize-filename": "^1.6.3"
|
||||
},
|
||||
"module": "src/index.tsx",
|
||||
@@ -26,31 +25,30 @@
|
||||
"start": "bun run src/index.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.19.0",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.1",
|
||||
"@tailwindcss/cli": "^4.0.4",
|
||||
"@tailwindcss/postcss": "^4.0.4",
|
||||
"@tailwindcss/cli": "^4.1.6",
|
||||
"@tailwindcss/postcss": "^4.1.6",
|
||||
"@total-typescript/ts-reset": "^0.6.1",
|
||||
"@types/bun": "^1.2.2",
|
||||
"@types/bun": "^1.2.13",
|
||||
"@types/eslint-plugin-tailwindcss": "^3.17.0",
|
||||
"@types/eslint__js": "^9.0.0",
|
||||
"@types/node": "^22.13.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cssnano": "^7.0.6",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-plugin-readable-tailwind": "^2.0.0-beta.1",
|
||||
"@types/node": "^22.15.17",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"cssnano": "^7.0.7",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-plugin-readable-tailwind": "^2.1.1",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"eslint-plugin-tailwindcss": "4.0.0-alpha.0",
|
||||
"globals": "^16.0.0",
|
||||
"knip": "^5.43.6",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-cli": "^11.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"tailwind-scrollbar": "^4.0.0",
|
||||
"tailwindcss": "^4.0.4",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.23.0"
|
||||
"globals": "^16.1.0",
|
||||
"knip": "^5.55.1",
|
||||
"npm-run-all2": "^8.0.1",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss-cli": "^11.0.1",
|
||||
"prettier": "^3.5.3",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.6",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.0"
|
||||
}
|
||||
}
|
||||
113
public/script.js
113
public/script.js
@@ -7,7 +7,8 @@ let fileType;
|
||||
let pendingFiles = 0;
|
||||
let formatSelected = false;
|
||||
|
||||
dropZone.addEventListener("dragover", () => {
|
||||
dropZone.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.add("dragover");
|
||||
});
|
||||
|
||||
@@ -15,10 +16,59 @@ dropZone.addEventListener("dragleave", () => {
|
||||
dropZone.classList.remove("dragover");
|
||||
});
|
||||
|
||||
dropZone.addEventListener("drop", () => {
|
||||
dropZone.classList.remove("dragover");
|
||||
dropZone.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
dropZone.classList.remove("dragover");
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
console.warn("No files dropped — likely a URL or unsupported source.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
console.log("Handling dropped file:", file.name);
|
||||
handleFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Extracted handleFile function for reusability in drag-and-drop and file input
|
||||
function handleFile(file) {
|
||||
const fileList = document.querySelector("#file-list");
|
||||
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>${file.name}</td>
|
||||
<td><progress max="100"></progress></td>
|
||||
<td>${(file.size / 1024).toFixed(2)} kB</td>
|
||||
<td><a onclick="deleteRow(this)">Remove</a></td>
|
||||
`;
|
||||
|
||||
if (!fileType) {
|
||||
fileType = file.name.split(".").pop();
|
||||
fileInput.setAttribute("accept", `.${fileType}`);
|
||||
setTitle();
|
||||
|
||||
fetch(`${webroot}/conversions`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ fileType }),
|
||||
headers: { "Content-Type": "application/json" },
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
selectContainer.innerHTML = html;
|
||||
updateSearchBar();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
fileList.appendChild(row);
|
||||
file.htmlRow = row;
|
||||
fileNames.push(file.name);
|
||||
uploadFile(file);
|
||||
}
|
||||
|
||||
const selectContainer = document.querySelector("form .select_container");
|
||||
|
||||
const updateSearchBar = () => {
|
||||
@@ -106,62 +156,9 @@ const updateSearchBar = () => {
|
||||
|
||||
// Add a 'change' event listener to the file input element
|
||||
fileInput.addEventListener("change", (e) => {
|
||||
// Get the selected files from the event target
|
||||
const files = e.target.files;
|
||||
|
||||
// Select the file-list table
|
||||
const fileList = document.querySelector("#file-list");
|
||||
|
||||
// Loop through the selected files
|
||||
for (const file of files) {
|
||||
// Create a new table row for each file
|
||||
const row = document.createElement("tr");
|
||||
row.innerHTML = `
|
||||
<td>${file.name}</td>
|
||||
<td><progress max="100"></progress></td>
|
||||
<td>${(file.size / 1024).toFixed(2)} kB</td>
|
||||
<td><a onclick="deleteRow(this)">Remove</a></td>
|
||||
`;
|
||||
|
||||
if (!fileType) {
|
||||
fileType = file.name.split(".").pop();
|
||||
fileInput.setAttribute("accept", `.${fileType}`);
|
||||
setTitle();
|
||||
|
||||
// choose the option that matches the file type
|
||||
// for (const option of convertFromSelect.children) {
|
||||
// console.log(option.value);
|
||||
// if (option.value === fileType) {
|
||||
// option.selected = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
fetch(`${webroot}/conversions`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ fileType: fileType }),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
selectContainer.innerHTML = html;
|
||||
updateSearchBar();
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
}
|
||||
|
||||
// Append the row to the file-list table
|
||||
fileList.appendChild(row);
|
||||
|
||||
//Imbed row into the file to reference later
|
||||
file.htmlRow = row;
|
||||
|
||||
|
||||
// Append the file to the hidden input
|
||||
fileNames.push(file.name);
|
||||
|
||||
uploadFile(file);
|
||||
handleFile(file);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -214,7 +211,7 @@ const uploadFile = (file) => {
|
||||
|
||||
xhr.open("POST", `${webroot}/upload`, true);
|
||||
|
||||
xhr.onload = (e) => {
|
||||
xhr.onload = () => {
|
||||
let data = JSON.parse(xhr.responseText);
|
||||
|
||||
pendingFiles -= 1;
|
||||
|
||||
@@ -4,28 +4,32 @@ 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">
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/history`}
|
||||
>
|
||||
History
|
||||
</a>
|
||||
</li>
|
||||
{!hideHistory && (
|
||||
<li>
|
||||
<a
|
||||
class={`
|
||||
text-accent-600 transition-all
|
||||
hover:text-accent-500 hover:underline
|
||||
`}
|
||||
href={`${webroot}/history`}
|
||||
>
|
||||
History
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{!allowUnauthenticated ? (
|
||||
<li>
|
||||
<a
|
||||
|
||||
@@ -11,6 +11,7 @@ export const properties = {
|
||||
"heics",
|
||||
"heif",
|
||||
"heifs",
|
||||
"hif",
|
||||
"mkv",
|
||||
"mp4",
|
||||
],
|
||||
|
||||
@@ -8,8 +8,9 @@ import { convert as convertPandoc, properties as propertiesPandoc } from "./pand
|
||||
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||
import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
||||
// import { convert as convertCalibre, properties as propertiesCalibre } from "./calibre";
|
||||
import { convert as convertLibheif, properties as propertiesLibheif } from "./libheif";
|
||||
import { convert as convertpotrace, properties as propertiespotrace } from "./potrace";
|
||||
|
||||
|
||||
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
|
||||
@@ -62,10 +63,10 @@ const properties: Record<
|
||||
properties: propertiesxelatex,
|
||||
converter: convertxelatex,
|
||||
},
|
||||
calibre: {
|
||||
properties: propertiesCalibre,
|
||||
converter: convertCalibre,
|
||||
},
|
||||
// calibre: {
|
||||
// properties: propertiesCalibre,
|
||||
// converter: convertCalibre,
|
||||
// },
|
||||
pandoc: {
|
||||
properties: propertiesPandoc,
|
||||
converter: convertPandoc,
|
||||
@@ -86,6 +87,10 @@ const properties: Record<
|
||||
properties: propertiesFFmpeg,
|
||||
converter: convertFFmpeg,
|
||||
},
|
||||
potrace: {
|
||||
properties: propertiespotrace,
|
||||
converter: convertpotrace,
|
||||
},
|
||||
};
|
||||
|
||||
export async function mainConverter(
|
||||
|
||||
37
src/converters/potrace.ts
Normal file
37
src/converters/potrace.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { execFile } from "node:child_process";
|
||||
|
||||
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,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
options?: unknown,
|
||||
): 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");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -124,6 +124,16 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
});
|
||||
|
||||
exec("potrace -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("potrace 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");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { randomInt, randomUUID } from "node:crypto";
|
||||
import { rmSync } from "node:fs";
|
||||
import { mkdir, unlink } from "node:fs/promises";
|
||||
import cookie from "@elysiajs/cookie";
|
||||
import { html, Html } from "@elysiajs/html";
|
||||
import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt";
|
||||
import { staticPlugin } from "@elysiajs/static";
|
||||
@@ -37,6 +36,8 @@ const ALLOW_UNAUTHENTICATED =
|
||||
const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
|
||||
? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
|
||||
: 24;
|
||||
const HIDE_HISTORY =
|
||||
process.env.HIDE_HISTORY?.toLowerCase() === "true" || false;
|
||||
|
||||
const WEBROOT = process.env.WEBROOT ?? "";
|
||||
|
||||
@@ -117,7 +118,6 @@ const app = new Elysia({
|
||||
},
|
||||
prefix: WEBROOT,
|
||||
})
|
||||
.use(cookie())
|
||||
.use(html())
|
||||
.use(
|
||||
jwt({
|
||||
@@ -353,6 +353,7 @@ const app = new Elysia({
|
||||
webroot={WEBROOT}
|
||||
accountRegistration={ACCOUNT_REGISTRATION}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
/>
|
||||
<main
|
||||
class={`
|
||||
@@ -580,7 +581,7 @@ const app = new Elysia({
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Convert</h1>
|
||||
<div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin">
|
||||
<div class="scrollbar-thin mb-4 max-h-[50vh] overflow-y-auto">
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
@@ -595,8 +596,8 @@ const app = new Elysia({
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||
border-neutral-700 transition-all
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
>
|
||||
<span>
|
||||
@@ -693,7 +694,7 @@ const app = new Elysia({
|
||||
</article>
|
||||
<input
|
||||
class={`
|
||||
btn-primary w-full
|
||||
btn-primary w-full opacity-100
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
type="submit"
|
||||
@@ -955,6 +956,10 @@ const app = new Elysia({
|
||||
},
|
||||
)
|
||||
.get("/history", async ({ jwt, redirect, cookie: { auth } }) => {
|
||||
if (HIDE_HISTORY) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
if (!auth?.value) {
|
||||
return redirect(`${WEBROOT}/login`, 302);
|
||||
}
|
||||
@@ -988,6 +993,7 @@ const app = new Elysia({
|
||||
<Header
|
||||
webroot={WEBROOT}
|
||||
allowUnauthenticated={ALLOW_UNAUTHENTICATED}
|
||||
hideHistory={HIDE_HISTORY}
|
||||
loggedIn
|
||||
/>
|
||||
<main
|
||||
@@ -1054,8 +1060,7 @@ const app = new Elysia({
|
||||
{userJobs.map((job) => (
|
||||
<tr>
|
||||
<td safe>
|
||||
{job.date_created.split("T")[1]?.split(".")[0] ??
|
||||
job.date_created}
|
||||
{new Date(job.date_created).toLocaleTimeString()}
|
||||
</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
@@ -1153,12 +1158,12 @@ const app = new Elysia({
|
||||
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-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none
|
||||
overflow-hidden rounded-full border-0 bg-neutral-700 bg-none
|
||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||
[&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
`}
|
||||
/>
|
||||
<table
|
||||
@@ -1305,12 +1310,12 @@ const app = new Elysia({
|
||||
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-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
text-accent-500 accent-accent-500 mb-4 inline-block h-2 w-full appearance-none
|
||||
overflow-hidden rounded-full border-0 bg-neutral-700 bg-none
|
||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||
[&::-moz-progress-bar]:bg-neutral-700 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
`}
|
||||
/>
|
||||
<table
|
||||
|
||||
Reference in New Issue
Block a user