mirror of
				https://github.com/C4illin/ConvertX.git
				synced 2025-11-03 21:43:22 +00:00 
			
		
		
		
	Compare commits
	
		
			74 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					663b1d4171 | ||
| 
						 | 
					c3067ca12d | ||
| 
						 | 
					4561ca3760 | ||
| 
						 | 
					698cce58ce | ||
| 
						 | 
					339b79f786 | ||
| 
						 | 
					4f98f778f0 | ||
| 
						 | 
					8479b33a47 | ||
| 
						 | 
					78844d7bd5 | ||
| 
						 | 
					64e4a271e1 | ||
| 
						 | 
					5fb8c3575b | ||
| 
						 | 
					a6b8bcecae | ||
| 
						 | 
					bc9c820820 | ||
| 
						 | 
					ee9207a7f4 | ||
| 
						 | 
					a34e215202 | ||
| 
						 | 
					b4e53dbb8e | ||
| 
						 | 
					b5e8d82bfa | ||
| 
						 | 
					5d9000bb33 | ||
| 
						 | 
					ccb065ef0f | ||
| 
						 | 
					883fad806b | ||
| 
						 | 
					feacd1b816 | ||
| 
						 | 
					094e7a0d1c | ||
| 
						 | 
					72636c5059 | ||
| 
						 | 
					291cfc80c6 | ||
| 
						 | 
					ae1dfafc9d | ||
| 
						 | 
					6caa583c35 | ||
| 
						 | 
					2057167576 | ||
| 
						 | 
					1c9e67fc32 | ||
| 
						 | 
					d3af9688c6 | ||
| 
						 | 
					7d0cbb9844 | ||
| 
						 | 
					88173891ba | ||
| 
						 | 
					2b4b8f9551 | ||
| 
						 | 
					63a4328d4a | ||
| 
						 | 
					413f5dc7b4 | ||
| 
						 | 
					ebccdf9169 | ||
| 
						 | 
					47139a550b | ||
| 
						 | 
					fa5446c446 | ||
| 
						 | 
					8772e582b0 | ||
| 
						 | 
					45922ed3a3 | ||
| 
						 | 
					4c747e8908 | ||
| 
						 | 
					e573997aa9 | ||
| 
						 | 
					c57b69991c | ||
| 
						 | 
					eee983a56a | ||
| 
						 | 
					22f823c535 | ||
| 
						 | 
					ed59cd7aa4 | ||
| 
						 | 
					b28977ffe2 | ||
| 
						 | 
					a47bb682a5 | ||
| 
						 | 
					a17eca0a09 | ||
| 
						 | 
					ea9250543e | ||
| 
						 | 
					317c932c2a | ||
| 
						 | 
					5b1703db68 | ||
| 
						 | 
					60ba7c93fb | ||
| 
						 | 
					22227130dd | ||
| 
						 | 
					5daf66f5d0 | ||
| 
						 | 
					aee1962607 | ||
| 
						 | 
					0d42762b36 | ||
| 
						 | 
					b97b12b449 | ||
| 
						 | 
					bdf651df82 | ||
| 
						 | 
					267ef14789 | ||
| 
						 | 
					905adc5e1c | ||
| 
						 | 
					52ed7274e9 | ||
| 
						 | 
					a29238c265 | ||
| 
						 | 
					48c6fb79fc | ||
| 
						 | 
					8358396656 | ||
| 
						 | 
					b30e5800c3 | ||
| 
						 | 
					21a1b50ed8 | ||
| 
						 | 
					e6a94fb21d | ||
| 
						 | 
					bef1710e33 | ||
| 
						 | 
					16b322d4e6 | ||
| 
						 | 
					9bf64e42d5 | ||
| 
						 | 
					5988fe8212 | ||
| 
						 | 
					5df9c0b751 | ||
| 
						 | 
					136a8b2d74 | ||
| 
						 | 
					ccfb574d5d | ||
| 
						 | 
					ad6eedea69 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -47,4 +47,5 @@ package-lock.json
 | 
			
		||||
/db
 | 
			
		||||
/data
 | 
			
		||||
/Bruno
 | 
			
		||||
/tsconfig.tsbuildinfo
 | 
			
		||||
/tsconfig.tsbuildinfo
 | 
			
		||||
/src/public/generated.css
 | 
			
		||||
							
								
								
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,71 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
## [0.8.1](https://github.com/C4illin/ConvertX/compare/v0.8.0...v0.8.1) (2024-10-05)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* disable convert button when input is empty ([78844d7](https://github.com/C4illin/ConvertX/commit/78844d7bd55990789ed07c81e49043e688cbe656)), closes [#151](https://github.com/C4illin/ConvertX/issues/151)
 | 
			
		||||
* resize to fit for ico ([b4e53db](https://github.com/C4illin/ConvertX/commit/b4e53dbb8e70b3a95b44e5b756759d16117a87e1)), closes [#157](https://github.com/C4illin/ConvertX/issues/157)
 | 
			
		||||
* treat jfif as jpeg ([339b79f](https://github.com/C4illin/ConvertX/commit/339b79f786131deb93f0d5683e03178fdcab1ef5)), closes [#163](https://github.com/C4illin/ConvertX/issues/163)
 | 
			
		||||
 | 
			
		||||
## [0.8.0](https://github.com/C4illin/ConvertX/compare/v0.7.0...v0.8.0) (2024-09-30)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* add light theme, fixes [#156](https://github.com/C4illin/ConvertX/issues/156) ([72636c5](https://github.com/C4illin/ConvertX/commit/72636c5059ebf09c8fece2e268293650b2f8ccf6))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* add support for usd for assimp, [#144](https://github.com/C4illin/ConvertX/issues/144) ([2057167](https://github.com/C4illin/ConvertX/commit/20571675766209ad1251f07e687d29a6791afc8b))
 | 
			
		||||
* cleanup formats and add opus, fixes [#159](https://github.com/C4illin/ConvertX/issues/159) ([ae1dfaf](https://github.com/C4illin/ConvertX/commit/ae1dfafc9d9116a57b08c2f7fc326990e00824b0))
 | 
			
		||||
* support .awb and clean up, fixes [#153](https://github.com/C4illin/ConvertX/issues/153), [#92](https://github.com/C4illin/ConvertX/issues/92) ([1c9e67f](https://github.com/C4illin/ConvertX/commit/1c9e67fc3201e0e5dee91e8981adf34daaabf33a))
 | 
			
		||||
 | 
			
		||||
## [0.7.0](https://github.com/C4illin/ConvertX/compare/v0.6.0...v0.7.0) (2024-09-26)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* Add support for 3d assets through assimp converter ([63a4328](https://github.com/C4illin/ConvertX/commit/63a4328d4a1e01df3e0ec4a877bad8c8ffe71129))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* wrong layout on search with few options ([8817389](https://github.com/C4illin/ConvertX/commit/88173891ba2d69da46eda46f3f598a9b54f26f96))
 | 
			
		||||
 | 
			
		||||
## [0.6.0](https://github.com/C4illin/ConvertX/compare/v0.5.0...v0.6.0) (2024-09-25)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* ui remake with tailwind ([22f823c](https://github.com/C4illin/ConvertX/commit/22f823c535b20382981f86a13616b830a1f3392f))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* rename css file to force update cache, fixes [#141](https://github.com/C4illin/ConvertX/issues/141) ([47139a5](https://github.com/C4illin/ConvertX/commit/47139a550bd3d847da288c61bf8f88953b79c673))
 | 
			
		||||
 | 
			
		||||
## [0.5.0](https://github.com/C4illin/ConvertX/compare/v0.4.1...v0.5.0) (2024-09-20)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Features
 | 
			
		||||
 | 
			
		||||
* add option to customize how often files are automatically deleted ([317c932](https://github.com/C4illin/ConvertX/commit/317c932c2a26280bf37ed3d3bf9b879413590f5a))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* improve file name replacement logic ([60ba7c9](https://github.com/C4illin/ConvertX/commit/60ba7c93fbdc961f3569882fade7cc13dee7a7a5))
 | 
			
		||||
 | 
			
		||||
## [0.4.1](https://github.com/C4illin/ConvertX/compare/v0.4.0...v0.4.1) (2024-09-15)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Bug Fixes
 | 
			
		||||
 | 
			
		||||
* allow non lowercase true and false values, fixes [#122](https://github.com/C4illin/ConvertX/issues/122) ([bef1710](https://github.com/C4illin/ConvertX/commit/bef1710e3376baa7e25c107ded20a40d18b8c6b0))
 | 
			
		||||
 | 
			
		||||
## [0.4.0](https://github.com/C4illin/ConvertX/compare/v0.3.3...v0.4.0) (2024-08-26)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
FROM oven/bun:1-debian as base
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# install dependencies into temp directory
 | 
			
		||||
# this will cache them and speed up future builds
 | 
			
		||||
FROM base AS install
 | 
			
		||||
RUN mkdir -p /temp/dev
 | 
			
		||||
COPY package.json bun.lockb /temp/dev/
 | 
			
		||||
RUN cd /temp/dev && bun install --frozen-lockfile
 | 
			
		||||
 | 
			
		||||
# install with --production (exclude devDependencies)
 | 
			
		||||
RUN mkdir -p /temp/prod
 | 
			
		||||
COPY package.json bun.lockb /temp/prod/
 | 
			
		||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
 | 
			
		||||
 | 
			
		||||
# FROM base AS install-libjxl-tools
 | 
			
		||||
# download
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# copy node_modules from temp directory
 | 
			
		||||
# then copy all (non-ignored) project files into the image
 | 
			
		||||
# FROM base AS prerelease
 | 
			
		||||
# COPY --from=install /temp/dev/node_modules node_modules
 | 
			
		||||
# COPY . .
 | 
			
		||||
 | 
			
		||||
# # [optional] tests & build
 | 
			
		||||
# ENV NODE_ENV=production
 | 
			
		||||
# RUN bun test
 | 
			
		||||
# 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"
 | 
			
		||||
 | 
			
		||||
# install additional dependencies
 | 
			
		||||
RUN rm -rf /var/lib/apt/lists/partial && apt-get update -o Acquire::CompressionTypes::Order::=gz \
 | 
			
		||||
  && apt-get install -y \
 | 
			
		||||
  pandoc \
 | 
			
		||||
  texlive-latex-recommended \
 | 
			
		||||
  texlive-fonts-recommended \
 | 
			
		||||
  texlive-latex-extra \
 | 
			
		||||
  ffmpeg \
 | 
			
		||||
  graphicsmagick \
 | 
			
		||||
  ghostscript \
 | 
			
		||||
  libvips-tools
 | 
			
		||||
 | 
			
		||||
# # libjxl is not available in the official debian repositories
 | 
			
		||||
# RUN wget https://github.com/libjxl/libjxl/releases/download/v0.10.2/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -O /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz \
 | 
			
		||||
#   && mkdir -p /tmp/libjxl \
 | 
			
		||||
#   && tar -xvf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz -C /tmp/libjxl \
 | 
			
		||||
#   && dpkg -i /tmp/libjxl/libjxl_0.10.2_amd64.deb /tmp/libjxl/jxl_0.10.2_amd64.deb \
 | 
			
		||||
#   && rm -rf /tmp/jxl-debs-amd64-debian-bullseye-v0.10.2.tar.gz /tmp/libjxl
 | 
			
		||||
 | 
			
		||||
COPY --from=install /temp/prod/node_modules node_modules
 | 
			
		||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
 | 
			
		||||
# COPY --from=prerelease /app/package.json .
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
EXPOSE 3000/tcp
 | 
			
		||||
ENTRYPOINT [ "bun", "run", "./src/index.tsx" ]
 | 
			
		||||
							
								
								
									
										16
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
FROM oven/bun:1.1.26-alpine AS base
 | 
			
		||||
FROM oven/bun:1.1.29-alpine AS base
 | 
			
		||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
@@ -22,14 +22,14 @@ RUN cargo install resvg
 | 
			
		||||
 | 
			
		||||
# copy node_modules from temp directory
 | 
			
		||||
# then copy all (non-ignored) project files into the image
 | 
			
		||||
# FROM base AS prerelease
 | 
			
		||||
# COPY --from=install /temp/dev/node_modules node_modules
 | 
			
		||||
# COPY . .
 | 
			
		||||
FROM base AS prerelease
 | 
			
		||||
COPY --from=install /temp/dev/node_modules node_modules
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
# # [optional] tests & build
 | 
			
		||||
# ENV NODE_ENV=production
 | 
			
		||||
ENV NODE_ENV=production
 | 
			
		||||
# RUN bun test
 | 
			
		||||
# RUN bun run build
 | 
			
		||||
RUN bun run build
 | 
			
		||||
 | 
			
		||||
# copy production dependencies and source code into final image
 | 
			
		||||
FROM base AS release
 | 
			
		||||
@@ -49,13 +49,15 @@ RUN apk --no-cache add  \
 | 
			
		||||
  vips-tools \
 | 
			
		||||
  vips-poppler \
 | 
			
		||||
  vips-jxl \
 | 
			
		||||
  libjxl-tools
 | 
			
		||||
  libjxl-tools \
 | 
			
		||||
  assimp
 | 
			
		||||
 | 
			
		||||
# this might be needed for some latex use cases, will add it if needed.
 | 
			
		||||
#   texmf-dist-fontsextra \
 | 
			
		||||
 | 
			
		||||
COPY --from=install /temp/prod/node_modules node_modules
 | 
			
		||||
COPY --from=builder /root/.cargo/bin/resvg /usr/local/bin/resvg
 | 
			
		||||
COPY --from=prerelease /app/src/public/generated.css /app/src/public/
 | 
			
		||||
# COPY --from=prerelease /app/src/index.tsx /app/src/
 | 
			
		||||
# COPY --from=prerelease /app/package.json .
 | 
			
		||||
COPY . .
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@@ -7,11 +7,12 @@
 | 
			
		||||

 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
A self-hosted online file converter. Supports 831 different formats. Written with TypeScript, Bun and Elysia.
 | 
			
		||||
A self-hosted online file converter. Supports over a thousand different formats. Written with TypeScript, Bun and Elysia.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- Convert files to different formats
 | 
			
		||||
- Process multiple files at once
 | 
			
		||||
- Password protection
 | 
			
		||||
- Multiple accounts
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +23,7 @@ A self-hosted online file converter. Supports 831 different formats. Written wit
 | 
			
		||||
| [libjxl](https://github.com/libjxl/libjxl)                                   | JPEG XL       | 11            | 11          |
 | 
			
		||||
| [resvg](https://github.com/RazrFalcon/resvg)                                 | SVG           | 1             | 1           |
 | 
			
		||||
| [Vips](https://github.com/libvips/libvips)                                   | Images        | 45            | 23          |
 | 
			
		||||
| [Assimp](https://github.com/assimp/assimp)                                   | 3D Assets     | 70            | 24          |
 | 
			
		||||
| [XeLaTeX](https://tug.org/xetex/)                                            | LaTeX         | 1             | 1           |
 | 
			
		||||
| [Pandoc](https://pandoc.org/)                                                | Documents     | 43            | 65          |
 | 
			
		||||
| [GraphicsMagick](http://www.graphicsmagick.org/)                             | Images        | 166           | 133         |
 | 
			
		||||
@@ -47,6 +49,7 @@ services:
 | 
			
		||||
      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
 | 
			
		||||
      - HTTP_ALLOWED=false # setting this to true is unsafe, only set this to true locally
 | 
			
		||||
      - ALLOW_UNAUTHENTICATED=false # allows anyone to use the service without logging in, only set this to true locally
 | 
			
		||||
      - AUTO_DELETE_EVERY_N_HOURS=24 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
 | 
			
		||||
    volumes:
 | 
			
		||||
      - convertx:/app/data
 | 
			
		||||
```
 | 
			
		||||
@@ -54,7 +57,7 @@ services:
 | 
			
		||||
or
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
docker run ghcr.io/c4illin/convertx -p 3000:3000 -v ./data:/app/data
 | 
			
		||||
docker run -p 3000:3000 -v ./data:/app/data ghcr.io/c4illin/convertx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Then visit `http://localhost:3000` in your browser and create your account. Don't leave it unconfigured and open, as anyone can register the first account.
 | 
			
		||||
@@ -65,6 +68,10 @@ If you get unable to open database file run `chown -R $USER:$USER path` on the p
 | 
			
		||||
 | 
			
		||||
Tutorial in french: https://belginux.com/installer-convertx-avec-docker/
 | 
			
		||||
 | 
			
		||||
## Screenshots
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Development
 | 
			
		||||
 | 
			
		||||
0. Install [Bun](https://bun.sh/) and Git
 | 
			
		||||
@@ -76,12 +83,12 @@ Pull requests are welcome! See below and open issues for the list of todos.
 | 
			
		||||
 | 
			
		||||
## Todo
 | 
			
		||||
- [x] Add messages for errors in converters
 | 
			
		||||
- [x] Add searchable list of formats
 | 
			
		||||
- [ ] Add options for converters
 | 
			
		||||
- [ ] Add more converters
 | 
			
		||||
- [ ] Divide index.tsx into smaller components
 | 
			
		||||
- [ ] Add tests
 | 
			
		||||
- [ ] Add searchable list of formats
 | 
			
		||||
- [ ] Make the upload button nicer and more easy to drop files on. Support copy paste as well if possible.
 | 
			
		||||
- [ ] Make errors logs visible from the web ui
 | 
			
		||||
- [ ] Add more converters:
 | 
			
		||||
  - [ ] [deark](https://github.com/jsummers/deark)
 | 
			
		||||
  - [ ] LibreOffice
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								biome.json
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								biome.json
									
									
									
									
									
								
							@@ -10,9 +10,14 @@
 | 
			
		||||
    "attributePosition": "auto"
 | 
			
		||||
  },
 | 
			
		||||
  "files": {
 | 
			
		||||
    "ignore": ["**/node_modules/**"]
 | 
			
		||||
    "ignore": [
 | 
			
		||||
      "**/node_modules/**",
 | 
			
		||||
      "**/pico.lime.min.css"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "organizeImports": {
 | 
			
		||||
    "enabled": true
 | 
			
		||||
  },
 | 
			
		||||
  "organizeImports": { "enabled": true },
 | 
			
		||||
  "linter": {
 | 
			
		||||
    "enabled": true,
 | 
			
		||||
    "rules": {
 | 
			
		||||
@@ -25,7 +30,11 @@
 | 
			
		||||
        "useLiteralKeys": "error",
 | 
			
		||||
        "useOptionalChain": "error"
 | 
			
		||||
      },
 | 
			
		||||
      "correctness": { "noPrecisionLoss": "error", "noUnusedVariables": "off" },
 | 
			
		||||
      "correctness": {
 | 
			
		||||
        "noPrecisionLoss": "error",
 | 
			
		||||
        "noUnusedVariables": "off",
 | 
			
		||||
        "useJsxKeyInIterable": "off"
 | 
			
		||||
      },
 | 
			
		||||
      "style": {
 | 
			
		||||
        "noInferrableTypes": "error",
 | 
			
		||||
        "noNamespace": "error",
 | 
			
		||||
@@ -45,6 +54,9 @@
 | 
			
		||||
        "noUnsafeDeclarationMerging": "error",
 | 
			
		||||
        "useAwait": "error",
 | 
			
		||||
        "useNamespaceKeyword": "error"
 | 
			
		||||
      },
 | 
			
		||||
      "nursery": {
 | 
			
		||||
        "useSortedClasses": "error"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
@@ -60,4 +72,4 @@
 | 
			
		||||
      "attributePosition": "auto"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								compose.yaml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								compose.yaml
									
									
									
									
									
								
							@@ -5,9 +5,11 @@ services:
 | 
			
		||||
      # dockerfile: Debian.Dockerfile
 | 
			
		||||
    volumes:
 | 
			
		||||
      - ./data:/app/data
 | 
			
		||||
    environment:
 | 
			
		||||
      - ACCOUNT_REGISTRATION=true
 | 
			
		||||
      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234
 | 
			
		||||
      - ALLOW_UNAUTHENTICATED=true
 | 
			
		||||
    environment: # Defaults are listed below. All are optional.
 | 
			
		||||
      - ACCOUNT_REGISTRATION=true # true or false, doesn't matter for the first account (e.g. keep this to false if you only want one account)
 | 
			
		||||
      - JWT_SECRET=aLongAndSecretStringUsedToSignTheJSONWebToken1234 # will use randomUUID() by default
 | 
			
		||||
      - HTTP_ALLOWED=true # setting this to true is unsafe, only set this to true locally
 | 
			
		||||
      - ALLOW_UNAUTHENTICATED=true # allows anyone to use the service without logging in, only set this to true locally
 | 
			
		||||
      - AUTO_DELETE_EVERY_N_HOURS=1 # checks every n hours for files older then n hours and deletes them, set to 0 to disable
 | 
			
		||||
    ports:
 | 
			
		||||
      - 3000:3000
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								eslint.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import { fixupPluginRules } from "@eslint/compat";
 | 
			
		||||
import eslint from "@eslint/js";
 | 
			
		||||
import deprecationPlugin from "eslint-plugin-deprecation";
 | 
			
		||||
import eslintPluginReadableTailwind from "eslint-plugin-readable-tailwind";
 | 
			
		||||
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
 | 
			
		||||
import tailwind from "eslint-plugin-tailwindcss";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
 | 
			
		||||
export default tseslint.config(
 | 
			
		||||
  eslint.configs.recommended,
 | 
			
		||||
  ...tseslint.configs.recommended,
 | 
			
		||||
  ...tailwind.configs["flat/recommended"],
 | 
			
		||||
  {
 | 
			
		||||
    plugins: {
 | 
			
		||||
      deprecation: fixupPluginRules(deprecationPlugin),
 | 
			
		||||
      "simple-import-sort": simpleImportSortPlugin,
 | 
			
		||||
      "readable-tailwind": eslintPluginReadableTailwind,
 | 
			
		||||
    },
 | 
			
		||||
    ignores: ["**/node_modules/**"],
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        projectService: true,
 | 
			
		||||
        tsconfigRootDir: import.meta.dirname,
 | 
			
		||||
        ecmaVersion: "latest",
 | 
			
		||||
        sourceType: "module",
 | 
			
		||||
        project: ["./tsconfig.json"],
 | 
			
		||||
      },
 | 
			
		||||
      globals: {
 | 
			
		||||
        ...globals.node,
 | 
			
		||||
        ...globals.browser,
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    files: ["**/*.{js,mjs,cjs,tsx,ts}"],
 | 
			
		||||
    rules: {
 | 
			
		||||
      ...eslintPluginReadableTailwind.configs.warning.rules,
 | 
			
		||||
      "tailwindcss/classnames-order": "off",
 | 
			
		||||
      "readable-tailwind/multiline": [
 | 
			
		||||
        "warn",
 | 
			
		||||
        {
 | 
			
		||||
          group: "newLine",
 | 
			
		||||
          printWidth: 100,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      "tailwindcss/no-custom-classname": [
 | 
			
		||||
        "warn",
 | 
			
		||||
        {
 | 
			
		||||
          whitelist: [
 | 
			
		||||
            "select_container",
 | 
			
		||||
            "convert_to_popup",
 | 
			
		||||
            "convert_to_group",
 | 
			
		||||
            "target",
 | 
			
		||||
            "convert_to_target",
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
import { fixupPluginRules } from "@eslint/compat";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
import eslint from "@eslint/js";
 | 
			
		||||
import deprecationPlugin from "eslint-plugin-deprecation";
 | 
			
		||||
import eslintCommentsPlugin from "eslint-plugin-eslint-comments";
 | 
			
		||||
import importPlugin from "eslint-plugin-import";
 | 
			
		||||
import simpleImportSortPlugin from "eslint-plugin-simple-import-sort";
 | 
			
		||||
 | 
			
		||||
export default tseslint.config(
 | 
			
		||||
  {
 | 
			
		||||
    plugins: {
 | 
			
		||||
      "@typescript-eslint": tseslint.plugin,
 | 
			
		||||
      deprecation: fixupPluginRules(deprecationPlugin),
 | 
			
		||||
      "eslint-comments": eslintCommentsPlugin,
 | 
			
		||||
      import: fixupPluginRules(importPlugin),
 | 
			
		||||
      "simple-import-sort": simpleImportSortPlugin,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    ignores: ["**/node_modules/**", "**/public/**"],
 | 
			
		||||
  },
 | 
			
		||||
  eslint.configs.recommended,
 | 
			
		||||
  ...tseslint.configs.recommendedTypeChecked,
 | 
			
		||||
  ...tseslint.configs.stylisticTypeChecked,
 | 
			
		||||
  {
 | 
			
		||||
    languageOptions: {
 | 
			
		||||
      parserOptions: {
 | 
			
		||||
        projectService: true,
 | 
			
		||||
        tsconfigRootDir: import.meta.dirname,
 | 
			
		||||
        ecmaVersion: "latest",
 | 
			
		||||
        sourceType: "module",
 | 
			
		||||
        project: ["./tsconfig.json"],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								images/preview.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								images/preview.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 53 KiB  | 
							
								
								
									
										64
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								package.json
									
									
									
									
									
								
							@@ -1,22 +1,22 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "convertx-frontend",
 | 
			
		||||
  "version": "0.4.0",
 | 
			
		||||
  "version": "0.8.1",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "bun run --watch src/index.tsx",
 | 
			
		||||
    "hot": "bun run --hot src/index.tsx",
 | 
			
		||||
    "format": "biome format --write ./src",
 | 
			
		||||
    "css": "cpy 'node_modules/@picocss/pico/css/pico.lime.min.css' 'src/public/' --flat",
 | 
			
		||||
    "format": "eslint --fix .",
 | 
			
		||||
    "build": "postcss ./src/main.css -o ./src/public/generated.css",
 | 
			
		||||
    "lint": "run-p 'lint:*'",
 | 
			
		||||
    "lint:tsc": "tsc --noEmit",
 | 
			
		||||
    "lint:knip": "knip",
 | 
			
		||||
    "lint:biome": "biome lint --error-on-warnings ./src"
 | 
			
		||||
    "lint:eslint": "eslint ."
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@elysiajs/cookie": "^0.8.0",
 | 
			
		||||
    "@elysiajs/html": "1.0.2",
 | 
			
		||||
    "@elysiajs/jwt": "^1.1.0",
 | 
			
		||||
    "@elysiajs/jwt": "^1.1.1",
 | 
			
		||||
    "@elysiajs/static": "1.0.3",
 | 
			
		||||
    "elysia": "^1.1.7"
 | 
			
		||||
    "elysia": "^1.1.17"
 | 
			
		||||
  },
 | 
			
		||||
  "module": "src/index.tsx",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
@@ -24,34 +24,38 @@
 | 
			
		||||
    "start": "bun run src/index.tsx"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@biomejs/biome": "1.8.3",
 | 
			
		||||
    "@eslint/compat": "^1.1.1",
 | 
			
		||||
    "@eslint/js": "^9.9.0",
 | 
			
		||||
    "@eslint/js": "^9.12.0",
 | 
			
		||||
    "@ianvs/prettier-plugin-sort-imports": "^4.3.1",
 | 
			
		||||
    "@kitajs/ts-html-plugin": "^4.0.2",
 | 
			
		||||
    "@picocss/pico": "^2.0.6",
 | 
			
		||||
    "@total-typescript/ts-reset": "^0.6.0",
 | 
			
		||||
    "@types/bun": "^1.1.6",
 | 
			
		||||
    "@types/eslint": "^9.6.0",
 | 
			
		||||
    "@types/node": "^22.5.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^8.2.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^8.2.0",
 | 
			
		||||
    "cpy-cli": "^5.0.0",
 | 
			
		||||
    "eslint": "^9.9.0",
 | 
			
		||||
    "@kitajs/ts-html-plugin": "^4.1.0",
 | 
			
		||||
    "@total-typescript/ts-reset": "^0.6.1",
 | 
			
		||||
    "@types/bun": "^1.1.10",
 | 
			
		||||
    "@types/eslint": "^9.6.1",
 | 
			
		||||
    "@types/eslint-plugin-tailwindcss": "^3.17.0",
 | 
			
		||||
    "@types/eslint__js": "^8.42.3",
 | 
			
		||||
    "@types/node": "^22.7.4",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^8.7.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^8.7.0",
 | 
			
		||||
    "autoprefixer": "^10.4.20",
 | 
			
		||||
    "cssnano": "^7.0.6",
 | 
			
		||||
    "eslint": "^9.12.0",
 | 
			
		||||
    "eslint-config-prettier": "^9.1.0",
 | 
			
		||||
    "eslint-plugin-deprecation": "^3.0.0",
 | 
			
		||||
    "eslint-plugin-eslint-comments": "^3.2.0",
 | 
			
		||||
    "eslint-plugin-import": "^2.29.1",
 | 
			
		||||
    "eslint-plugin-isaacscript": "^3.12.2",
 | 
			
		||||
    "eslint-plugin-isaacscript": "^4.0.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^5.2.1",
 | 
			
		||||
    "eslint-plugin-readable-tailwind": "^1.8.1",
 | 
			
		||||
    "eslint-plugin-simple-import-sort": "^12.1.1",
 | 
			
		||||
    "knip": "^5.27.3",
 | 
			
		||||
    "npm-run-all2": "^6.2.2",
 | 
			
		||||
    "eslint-plugin-tailwindcss": "^3.17.4",
 | 
			
		||||
    "globals": "^15.9.0",
 | 
			
		||||
    "knip": "^5.30.6",
 | 
			
		||||
    "npm-run-all2": "^6.2.3",
 | 
			
		||||
    "postcss": "^8.4.47",
 | 
			
		||||
    "postcss-cli": "^11.0.0",
 | 
			
		||||
    "postcss-lightningcss": "^1.0.1",
 | 
			
		||||
    "prettier": "^3.3.3",
 | 
			
		||||
    "typescript": "^5.5.4",
 | 
			
		||||
    "typescript-eslint": "^8.2.0"
 | 
			
		||||
  },
 | 
			
		||||
  "trustedDependencies": [
 | 
			
		||||
    "@biomejs/biome"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
    "tailwind-scrollbar": "^3.1.0",
 | 
			
		||||
    "tailwindcss": "^3.4.13",
 | 
			
		||||
    "typescript": "^5.6.2",
 | 
			
		||||
    "typescript-eslint": "^8.8.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								postcss.config.cjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
 
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    tailwindcss: {},
 | 
			
		||||
    autoprefixer: {},
 | 
			
		||||
     
 | 
			
		||||
    ...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {})
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
 | 
			
		||||
  "extends": [
 | 
			
		||||
    "config:recommended"
 | 
			
		||||
    "config:recommended",
 | 
			
		||||
    ":disableDependencyDashboard"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -7,8 +7,7 @@ export const BaseHtml = ({
 | 
			
		||||
      <meta charset="UTF-8" />
 | 
			
		||||
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
      <title safe>{title}</title>
 | 
			
		||||
      <link rel="stylesheet" href="/pico.lime.min.css" />
 | 
			
		||||
      <link rel="stylesheet" href="/style.css" />
 | 
			
		||||
      <link rel="stylesheet" href="/generated.css" />
 | 
			
		||||
      <link
 | 
			
		||||
        rel="apple-touch-icon"
 | 
			
		||||
        sizes="180x180"
 | 
			
		||||
@@ -28,6 +27,6 @@ export const BaseHtml = ({
 | 
			
		||||
      />
 | 
			
		||||
      <link rel="manifest" href="/site.webmanifest" />
 | 
			
		||||
    </head>
 | 
			
		||||
    <body>{children}</body>
 | 
			
		||||
    <body class="w-full bg-neutral-900 text-neutral-200">{children}</body>
 | 
			
		||||
  </html>
 | 
			
		||||
);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,44 +5,65 @@ export const Header = ({
 | 
			
		||||
  let rightNav: JSX.Element;
 | 
			
		||||
  if (loggedIn) {
 | 
			
		||||
    rightNav = (
 | 
			
		||||
      <ul>
 | 
			
		||||
      <ul class="flex gap-4">
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="/history">History</a>
 | 
			
		||||
          <a
 | 
			
		||||
            class={`
 | 
			
		||||
              text-accent-600 transition-all
 | 
			
		||||
              hover:text-accent-500 hover:underline
 | 
			
		||||
            `}
 | 
			
		||||
            href="/history">
 | 
			
		||||
            History
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="/logoff">Logout</a>
 | 
			
		||||
          <a
 | 
			
		||||
            class={`
 | 
			
		||||
              text-accent-600 transition-all
 | 
			
		||||
              hover:text-accent-500 hover:underline
 | 
			
		||||
            `}
 | 
			
		||||
            href="/logoff">
 | 
			
		||||
            Logout
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
      </ul>
 | 
			
		||||
    );
 | 
			
		||||
  } else {
 | 
			
		||||
    rightNav = (
 | 
			
		||||
      <ul>
 | 
			
		||||
      <ul class="flex gap-4">
 | 
			
		||||
        <li>
 | 
			
		||||
          <a href="/login">Login</a>
 | 
			
		||||
          <a
 | 
			
		||||
            class={`
 | 
			
		||||
              text-accent-600 transition-all
 | 
			
		||||
              hover:text-accent-500 hover:underline
 | 
			
		||||
            `}
 | 
			
		||||
            href="/login">
 | 
			
		||||
            Login
 | 
			
		||||
          </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {accountRegistration && (
 | 
			
		||||
        {accountRegistration ? (
 | 
			
		||||
          <li>
 | 
			
		||||
            <a href="/register">Register</a>
 | 
			
		||||
            <a
 | 
			
		||||
              class={`
 | 
			
		||||
                text-accent-600 transition-all
 | 
			
		||||
                hover:text-accent-500 hover:underline
 | 
			
		||||
              `}
 | 
			
		||||
              href="/register">
 | 
			
		||||
              Register
 | 
			
		||||
            </a>
 | 
			
		||||
          </li>
 | 
			
		||||
        )}
 | 
			
		||||
        ) : null}
 | 
			
		||||
      </ul>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <header class="container">
 | 
			
		||||
      <nav>
 | 
			
		||||
    <header class="w-full p-4">
 | 
			
		||||
      <nav class="mx-auto flex max-w-4xl justify-between rounded bg-neutral-900 p-4">
 | 
			
		||||
        <ul>
 | 
			
		||||
          <li>
 | 
			
		||||
            <strong>
 | 
			
		||||
              <a
 | 
			
		||||
                href="/"
 | 
			
		||||
                style={{
 | 
			
		||||
                  textDecoration: "none",
 | 
			
		||||
                  color: "inherit",
 | 
			
		||||
                }}>
 | 
			
		||||
                ConvertX
 | 
			
		||||
              </a>
 | 
			
		||||
              <a href="/">ConvertX</a>
 | 
			
		||||
            </strong>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								src/converters/assimp.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/converters/assimp.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
    muxer: [
 | 
			
		||||
      "3d",
 | 
			
		||||
      "3ds",
 | 
			
		||||
      "3mf",
 | 
			
		||||
      "ac",
 | 
			
		||||
      "ac3d",
 | 
			
		||||
      "acc",
 | 
			
		||||
      "amf",
 | 
			
		||||
      "ase",
 | 
			
		||||
      "ask",
 | 
			
		||||
      "assbin",
 | 
			
		||||
      "b3d",
 | 
			
		||||
      "blend",
 | 
			
		||||
      "bsp",
 | 
			
		||||
      "bvh",
 | 
			
		||||
      "cob",
 | 
			
		||||
      "csm",
 | 
			
		||||
      "dae",
 | 
			
		||||
      "dxf",
 | 
			
		||||
      "enff",
 | 
			
		||||
      "fbx",
 | 
			
		||||
      "glb",
 | 
			
		||||
      "gltf",
 | 
			
		||||
      "hmp",
 | 
			
		||||
      "ifc",
 | 
			
		||||
      "ifczip",
 | 
			
		||||
      "iqm",
 | 
			
		||||
      "irr",
 | 
			
		||||
      "irrmesh",
 | 
			
		||||
      "lwo",
 | 
			
		||||
      "lws",
 | 
			
		||||
      "lxo",
 | 
			
		||||
      "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: {
 | 
			
		||||
    muxer: [
 | 
			
		||||
      "3ds",
 | 
			
		||||
      "3mf",
 | 
			
		||||
      "assbin",
 | 
			
		||||
      "assjson",
 | 
			
		||||
      "assxml",
 | 
			
		||||
      "collada",
 | 
			
		||||
      "dae",
 | 
			
		||||
      "fbx",
 | 
			
		||||
      "fbxa",
 | 
			
		||||
      "glb",
 | 
			
		||||
      "glb2",
 | 
			
		||||
      "gltf",
 | 
			
		||||
      "gltf2",
 | 
			
		||||
      "m3d",
 | 
			
		||||
      "m3da",
 | 
			
		||||
      "obj",
 | 
			
		||||
      "objnomtl",
 | 
			
		||||
      "pbrt",
 | 
			
		||||
      "ply",
 | 
			
		||||
      "plyb",
 | 
			
		||||
      "stl",
 | 
			
		||||
      "stlb",
 | 
			
		||||
      "stp",
 | 
			
		||||
      "x",
 | 
			
		||||
      "x3d",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function convert(
 | 
			
		||||
  filePath: string,
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // let command = "ffmpeg";
 | 
			
		||||
 | 
			
		||||
  const command = `assimp export "${filePath}" "${targetPath}"`;
 | 
			
		||||
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(command, (error, stdout, stderr) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        reject(`error: ${error}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stdout) {
 | 
			
		||||
        console.log(`stdout: ${stdout}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (stderr) {
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("Done");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
@@ -6,6 +6,7 @@ export const properties = {
 | 
			
		||||
    muxer: [
 | 
			
		||||
      "264",
 | 
			
		||||
      "265",
 | 
			
		||||
      "266",
 | 
			
		||||
      "302",
 | 
			
		||||
      "3dostr",
 | 
			
		||||
      "3g2",
 | 
			
		||||
@@ -18,6 +19,7 @@ export const properties = {
 | 
			
		||||
      "aac",
 | 
			
		||||
      "aax",
 | 
			
		||||
      "ac3",
 | 
			
		||||
      "ac4",
 | 
			
		||||
      "ace",
 | 
			
		||||
      "acm",
 | 
			
		||||
      "act",
 | 
			
		||||
@@ -48,7 +50,6 @@ export const properties = {
 | 
			
		||||
      "apng",
 | 
			
		||||
      "aptx",
 | 
			
		||||
      "aptxhd",
 | 
			
		||||
      "aptx_hd",
 | 
			
		||||
      "aqt",
 | 
			
		||||
      "aqtitle",
 | 
			
		||||
      "argo_asf",
 | 
			
		||||
@@ -63,10 +64,12 @@ export const properties = {
 | 
			
		||||
      "av1",
 | 
			
		||||
      "avc",
 | 
			
		||||
      "avi",
 | 
			
		||||
      "avif",
 | 
			
		||||
      "avr",
 | 
			
		||||
      "avs",
 | 
			
		||||
      "avs2",
 | 
			
		||||
      "avs3",
 | 
			
		||||
      "awb",
 | 
			
		||||
      "bcstm",
 | 
			
		||||
      "bethsoftvid",
 | 
			
		||||
      "bfi",
 | 
			
		||||
@@ -75,8 +78,10 @@ export const properties = {
 | 
			
		||||
      "bink",
 | 
			
		||||
      "binka",
 | 
			
		||||
      "bit",
 | 
			
		||||
      "bmp_pipe",
 | 
			
		||||
      "bitpacked",
 | 
			
		||||
      "bmv",
 | 
			
		||||
      "bmp",
 | 
			
		||||
      "bonk",
 | 
			
		||||
      "boa",
 | 
			
		||||
      "brender_pix",
 | 
			
		||||
      "brstm",
 | 
			
		||||
@@ -93,7 +98,7 @@ export const properties = {
 | 
			
		||||
      "codec2",
 | 
			
		||||
      "codec2raw",
 | 
			
		||||
      "concat",
 | 
			
		||||
      "cri_pipe",
 | 
			
		||||
      "cri",
 | 
			
		||||
      "dash",
 | 
			
		||||
      "dat",
 | 
			
		||||
      "data",
 | 
			
		||||
@@ -101,8 +106,9 @@ export const properties = {
 | 
			
		||||
      "dav",
 | 
			
		||||
      "dbm",
 | 
			
		||||
      "dcstr",
 | 
			
		||||
      "dds_pipe",
 | 
			
		||||
      "dds",
 | 
			
		||||
      "derf",
 | 
			
		||||
      "dfpwm",
 | 
			
		||||
      "dfa",
 | 
			
		||||
      "dhav",
 | 
			
		||||
      "dif",
 | 
			
		||||
@@ -131,6 +137,8 @@ export const properties = {
 | 
			
		||||
      "exr_pipe",
 | 
			
		||||
      "f32be",
 | 
			
		||||
      "f32le",
 | 
			
		||||
      "ec3",
 | 
			
		||||
      "evc",
 | 
			
		||||
      "f4v",
 | 
			
		||||
      "f64be",
 | 
			
		||||
      "f64le",
 | 
			
		||||
@@ -157,13 +165,13 @@ export const properties = {
 | 
			
		||||
      "gdv",
 | 
			
		||||
      "genh",
 | 
			
		||||
      "gif",
 | 
			
		||||
      "gif_pipe",
 | 
			
		||||
      "gsm",
 | 
			
		||||
      "gxf",
 | 
			
		||||
      "h261",
 | 
			
		||||
      "h263",
 | 
			
		||||
      "h264",
 | 
			
		||||
      "h265",
 | 
			
		||||
      "h266",
 | 
			
		||||
      "h26l",
 | 
			
		||||
      "hca",
 | 
			
		||||
      "hcom",
 | 
			
		||||
@@ -180,7 +188,6 @@ export const properties = {
 | 
			
		||||
      "ifv",
 | 
			
		||||
      "ilbc",
 | 
			
		||||
      "image2",
 | 
			
		||||
      "image2pipe",
 | 
			
		||||
      "imf",
 | 
			
		||||
      "imx",
 | 
			
		||||
      "ingenient",
 | 
			
		||||
@@ -197,21 +204,17 @@ export const properties = {
 | 
			
		||||
      "ivr",
 | 
			
		||||
      "j2b",
 | 
			
		||||
      "j2k",
 | 
			
		||||
      "j2k_pipe",
 | 
			
		||||
      "jack",
 | 
			
		||||
      "jacosub",
 | 
			
		||||
      "jpegls_pipe",
 | 
			
		||||
      "jpeg_pipe",
 | 
			
		||||
      "jv",
 | 
			
		||||
      "jpegls",
 | 
			
		||||
      "jpeg",
 | 
			
		||||
      "jxl",
 | 
			
		||||
      "kmsgrab",
 | 
			
		||||
      "kux",
 | 
			
		||||
      "kvag",
 | 
			
		||||
      "lavfi",
 | 
			
		||||
      "libcdio",
 | 
			
		||||
      "libdc1394",
 | 
			
		||||
      "libgme",
 | 
			
		||||
      "libopenmpt",
 | 
			
		||||
      "live_flv",
 | 
			
		||||
      "laf",
 | 
			
		||||
      "lmlm4",
 | 
			
		||||
      "loas",
 | 
			
		||||
      "lrc",
 | 
			
		||||
@@ -224,16 +227,13 @@ export const properties = {
 | 
			
		||||
      "m4b",
 | 
			
		||||
      "m4v",
 | 
			
		||||
      "mac",
 | 
			
		||||
      "matroska",
 | 
			
		||||
      "mca",
 | 
			
		||||
      "mcc",
 | 
			
		||||
      "mdl",
 | 
			
		||||
      "med",
 | 
			
		||||
      "mgsts",
 | 
			
		||||
      "microdvd",
 | 
			
		||||
      "mj2",
 | 
			
		||||
      "mjpeg",
 | 
			
		||||
      "mjpeg_2000",
 | 
			
		||||
      "mjpg",
 | 
			
		||||
      "mk3d",
 | 
			
		||||
      "mka",
 | 
			
		||||
@@ -257,9 +257,6 @@ export const properties = {
 | 
			
		||||
      "mpc",
 | 
			
		||||
      "mpc8",
 | 
			
		||||
      "mpeg",
 | 
			
		||||
      "mpegts",
 | 
			
		||||
      "mpegtsraw",
 | 
			
		||||
      "mpegvideo",
 | 
			
		||||
      "mpg",
 | 
			
		||||
      "mpjpeg",
 | 
			
		||||
      "mpl2",
 | 
			
		||||
@@ -294,25 +291,27 @@ export const properties = {
 | 
			
		||||
      "okt",
 | 
			
		||||
      "oma",
 | 
			
		||||
      "omg",
 | 
			
		||||
      "opus",
 | 
			
		||||
      "openal",
 | 
			
		||||
      "oss",
 | 
			
		||||
      "osq",
 | 
			
		||||
      "paf",
 | 
			
		||||
      "pam_pipe",
 | 
			
		||||
      "pbm_pipe",
 | 
			
		||||
      "pcx_pipe",
 | 
			
		||||
      "pgmyuv_pipe",
 | 
			
		||||
      "pgm_pipe",
 | 
			
		||||
      "pgx_pipe",
 | 
			
		||||
      "photocd_pipe",
 | 
			
		||||
      "pictor_pipe",
 | 
			
		||||
      "pdv",
 | 
			
		||||
      "pam",
 | 
			
		||||
      "pbm",
 | 
			
		||||
      "pcx",
 | 
			
		||||
      "pgmyuv",
 | 
			
		||||
      "pgm",
 | 
			
		||||
      "pgx",
 | 
			
		||||
      "photocd",
 | 
			
		||||
      "pictor",
 | 
			
		||||
      "pjs",
 | 
			
		||||
      "plm",
 | 
			
		||||
      "pmp",
 | 
			
		||||
      "png_pipe",
 | 
			
		||||
      "png",
 | 
			
		||||
      "ppm",
 | 
			
		||||
      "ppm_pipe",
 | 
			
		||||
      "pp_bnk",
 | 
			
		||||
      "psd_pipe",
 | 
			
		||||
      "pp",
 | 
			
		||||
      "psd",
 | 
			
		||||
      "psm",
 | 
			
		||||
      "psp",
 | 
			
		||||
      "psxstr",
 | 
			
		||||
@@ -323,7 +322,7 @@ export const properties = {
 | 
			
		||||
      "pvf",
 | 
			
		||||
      "qcif",
 | 
			
		||||
      "qcp",
 | 
			
		||||
      "qdraw_pipe",
 | 
			
		||||
      "qdraw",
 | 
			
		||||
      "r3d",
 | 
			
		||||
      "rawvideo",
 | 
			
		||||
      "rco",
 | 
			
		||||
@@ -335,6 +334,7 @@ export const properties = {
 | 
			
		||||
      "rm",
 | 
			
		||||
      "roq",
 | 
			
		||||
      "rpl",
 | 
			
		||||
      "rka",
 | 
			
		||||
      "rsd",
 | 
			
		||||
      "rso",
 | 
			
		||||
      "rt",
 | 
			
		||||
@@ -355,6 +355,7 @@ export const properties = {
 | 
			
		||||
      "sbc",
 | 
			
		||||
      "sbg",
 | 
			
		||||
      "scc",
 | 
			
		||||
      "sdns",
 | 
			
		||||
      "sdp",
 | 
			
		||||
      "sdr2",
 | 
			
		||||
      "sds",
 | 
			
		||||
@@ -364,10 +365,9 @@ export const properties = {
 | 
			
		||||
      "sfx",
 | 
			
		||||
      "sfx2",
 | 
			
		||||
      "sga",
 | 
			
		||||
      "sgi_pipe",
 | 
			
		||||
      "sgi",
 | 
			
		||||
      "shn",
 | 
			
		||||
      "siff",
 | 
			
		||||
      "simbiosis_imx",
 | 
			
		||||
      "sln",
 | 
			
		||||
      "smi",
 | 
			
		||||
      "smjpeg",
 | 
			
		||||
@@ -389,12 +389,9 @@ export const properties = {
 | 
			
		||||
      "stp",
 | 
			
		||||
      "str",
 | 
			
		||||
      "sub",
 | 
			
		||||
      "subviewer",
 | 
			
		||||
      "subviewer1",
 | 
			
		||||
      "sunrast_pipe",
 | 
			
		||||
      "sup",
 | 
			
		||||
      "svag",
 | 
			
		||||
      "svg_pipe",
 | 
			
		||||
      "svg",
 | 
			
		||||
      "svs",
 | 
			
		||||
      "sw",
 | 
			
		||||
      "swf",
 | 
			
		||||
@@ -404,7 +401,8 @@ export const properties = {
 | 
			
		||||
      "thd",
 | 
			
		||||
      "thp",
 | 
			
		||||
      "tiertexseq",
 | 
			
		||||
      "tiff_pipe",
 | 
			
		||||
      "tif",
 | 
			
		||||
      "tiff",
 | 
			
		||||
      "tmv",
 | 
			
		||||
      "truehd",
 | 
			
		||||
      "tta",
 | 
			
		||||
@@ -424,6 +422,7 @@ export const properties = {
 | 
			
		||||
      "ul",
 | 
			
		||||
      "ult",
 | 
			
		||||
      "umx",
 | 
			
		||||
      "usm",
 | 
			
		||||
      "uw",
 | 
			
		||||
      "v",
 | 
			
		||||
      "v210",
 | 
			
		||||
@@ -447,12 +446,14 @@ export const properties = {
 | 
			
		||||
      "vql",
 | 
			
		||||
      "vt",
 | 
			
		||||
      "vtt",
 | 
			
		||||
      "vvc",
 | 
			
		||||
      "w64",
 | 
			
		||||
      "wa",
 | 
			
		||||
      "wav",
 | 
			
		||||
      "way",
 | 
			
		||||
      "wc3movie",
 | 
			
		||||
      "webm",
 | 
			
		||||
      "webm_dash_manifest",
 | 
			
		||||
      "webp_pipe",
 | 
			
		||||
      "webp",
 | 
			
		||||
      "webvtt",
 | 
			
		||||
      "wow",
 | 
			
		||||
      "wsaud",
 | 
			
		||||
@@ -464,32 +465,31 @@ export const properties = {
 | 
			
		||||
      "x11grab",
 | 
			
		||||
      "xa",
 | 
			
		||||
      "xbin",
 | 
			
		||||
      "xbm_pipe",
 | 
			
		||||
      "xl",
 | 
			
		||||
      "xm",
 | 
			
		||||
      "xmd",
 | 
			
		||||
      "xmv",
 | 
			
		||||
      "xpk",
 | 
			
		||||
      "xpm_pipe",
 | 
			
		||||
      "xvag",
 | 
			
		||||
      "xwd_pipe",
 | 
			
		||||
      "xwma",
 | 
			
		||||
      "y4m",
 | 
			
		||||
      "yop",
 | 
			
		||||
      "yuv",
 | 
			
		||||
      "yuv10",
 | 
			
		||||
      "yuv4mpegpipe",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  to: {
 | 
			
		||||
    muxer: [
 | 
			
		||||
      "264",
 | 
			
		||||
      "265",
 | 
			
		||||
      "266",
 | 
			
		||||
      "302",
 | 
			
		||||
      "3g2",
 | 
			
		||||
      "3gp",
 | 
			
		||||
      "a64",
 | 
			
		||||
      "aac",
 | 
			
		||||
      "ac3",
 | 
			
		||||
      "ac4",
 | 
			
		||||
      "adts",
 | 
			
		||||
      "adx",
 | 
			
		||||
      "afc",
 | 
			
		||||
@@ -497,43 +497,32 @@ export const properties = {
 | 
			
		||||
      "aifc",
 | 
			
		||||
      "aiff",
 | 
			
		||||
      "al",
 | 
			
		||||
      "alaw",
 | 
			
		||||
      "alp",
 | 
			
		||||
      "alsa",
 | 
			
		||||
      "amr",
 | 
			
		||||
      "amv",
 | 
			
		||||
      "apm",
 | 
			
		||||
      "apng",
 | 
			
		||||
      "aptx",
 | 
			
		||||
      "aptxhd",
 | 
			
		||||
      "aptx_hd",
 | 
			
		||||
      "argo_asf",
 | 
			
		||||
      "asf",
 | 
			
		||||
      "asf_stream",
 | 
			
		||||
      "ass",
 | 
			
		||||
      "ast",
 | 
			
		||||
      "au",
 | 
			
		||||
      "aud",
 | 
			
		||||
      "av1",
 | 
			
		||||
      "avi",
 | 
			
		||||
      "avm2",
 | 
			
		||||
      "avif",
 | 
			
		||||
      "avs",
 | 
			
		||||
      "avs2",
 | 
			
		||||
      "avs3",
 | 
			
		||||
      "bit",
 | 
			
		||||
      "bmp",
 | 
			
		||||
      "c2",
 | 
			
		||||
      "caca",
 | 
			
		||||
      "caf",
 | 
			
		||||
      "cavs",
 | 
			
		||||
      "cavsvideo",
 | 
			
		||||
      "chk",
 | 
			
		||||
      "chromaprint",
 | 
			
		||||
      "codec2",
 | 
			
		||||
      "codec2raw",
 | 
			
		||||
      "cpk",
 | 
			
		||||
      "crc",
 | 
			
		||||
      "dash",
 | 
			
		||||
      "data",
 | 
			
		||||
      "daud",
 | 
			
		||||
      "dirac",
 | 
			
		||||
      "cvg",
 | 
			
		||||
      "dfpwm",
 | 
			
		||||
      "dnxhd",
 | 
			
		||||
      "dnxhr",
 | 
			
		||||
      "dpx",
 | 
			
		||||
@@ -542,30 +531,16 @@ export const properties = {
 | 
			
		||||
      "dv",
 | 
			
		||||
      "dvd",
 | 
			
		||||
      "eac3",
 | 
			
		||||
      "ec3",
 | 
			
		||||
      "evc",
 | 
			
		||||
      "exr",
 | 
			
		||||
      "f32be",
 | 
			
		||||
      "f32le",
 | 
			
		||||
      "f4v",
 | 
			
		||||
      "f64be",
 | 
			
		||||
      "f64le",
 | 
			
		||||
      "fbdev",
 | 
			
		||||
      "ffmeta",
 | 
			
		||||
      "ffmetadata",
 | 
			
		||||
      "fifo",
 | 
			
		||||
      "fifo_test",
 | 
			
		||||
      "filmstrip",
 | 
			
		||||
      "film_cpk",
 | 
			
		||||
      "fits",
 | 
			
		||||
      "flac",
 | 
			
		||||
      "flm",
 | 
			
		||||
      "flv",
 | 
			
		||||
      "framecrc",
 | 
			
		||||
      "framehash",
 | 
			
		||||
      "framemd5",
 | 
			
		||||
      "g722",
 | 
			
		||||
      "g723_1",
 | 
			
		||||
      "g726",
 | 
			
		||||
      "g726le",
 | 
			
		||||
      "gif",
 | 
			
		||||
      "gsm",
 | 
			
		||||
      "gxf",
 | 
			
		||||
@@ -573,32 +548,26 @@ export const properties = {
 | 
			
		||||
      "h263",
 | 
			
		||||
      "h264",
 | 
			
		||||
      "h265",
 | 
			
		||||
      "hash",
 | 
			
		||||
      "hds",
 | 
			
		||||
      "h266",
 | 
			
		||||
      "hdr",
 | 
			
		||||
      "hevc",
 | 
			
		||||
      "hls",
 | 
			
		||||
      "ico",
 | 
			
		||||
      "ilbc",
 | 
			
		||||
      "im1",
 | 
			
		||||
      "im24",
 | 
			
		||||
      "im8",
 | 
			
		||||
      "image2",
 | 
			
		||||
      "image2pipe",
 | 
			
		||||
      "ipod",
 | 
			
		||||
      "ircam",
 | 
			
		||||
      "isma",
 | 
			
		||||
      "ismv",
 | 
			
		||||
      "ivf",
 | 
			
		||||
      "j2c",
 | 
			
		||||
      "j2k",
 | 
			
		||||
      "jacosub",
 | 
			
		||||
      "jls",
 | 
			
		||||
      "jp2",
 | 
			
		||||
      "jpeg",
 | 
			
		||||
      "jpg",
 | 
			
		||||
      "js",
 | 
			
		||||
      "jss",
 | 
			
		||||
      "kvag",
 | 
			
		||||
      "jxl",
 | 
			
		||||
      "latm",
 | 
			
		||||
      "lbc",
 | 
			
		||||
      "ljpg",
 | 
			
		||||
@@ -613,13 +582,9 @@ export const properties = {
 | 
			
		||||
      "m4a",
 | 
			
		||||
      "m4b",
 | 
			
		||||
      "m4v",
 | 
			
		||||
      "matroska",
 | 
			
		||||
      "md5",
 | 
			
		||||
      "microdvd",
 | 
			
		||||
      "mjpeg",
 | 
			
		||||
      "mjpg",
 | 
			
		||||
      "mkv",
 | 
			
		||||
      "mkvtimestamp_v2",
 | 
			
		||||
      "mlp",
 | 
			
		||||
      "mmf",
 | 
			
		||||
      "mov",
 | 
			
		||||
@@ -629,26 +594,17 @@ export const properties = {
 | 
			
		||||
      "mpa",
 | 
			
		||||
      "mpd",
 | 
			
		||||
      "mpeg",
 | 
			
		||||
      "mpeg1video",
 | 
			
		||||
      "mpeg2video",
 | 
			
		||||
      "mpegts",
 | 
			
		||||
      "mpg",
 | 
			
		||||
      "mpjpeg",
 | 
			
		||||
      "msbc",
 | 
			
		||||
      "mts",
 | 
			
		||||
      "mulaw",
 | 
			
		||||
      "mxf",
 | 
			
		||||
      "mxf_d10",
 | 
			
		||||
      "mxf_opatom",
 | 
			
		||||
      "null",
 | 
			
		||||
      "nut",
 | 
			
		||||
      "obu",
 | 
			
		||||
      "oga",
 | 
			
		||||
      "ogg",
 | 
			
		||||
      "ogv",
 | 
			
		||||
      "oma",
 | 
			
		||||
      "opengl",
 | 
			
		||||
      "opus",
 | 
			
		||||
      "oss",
 | 
			
		||||
      "pam",
 | 
			
		||||
      "pbm",
 | 
			
		||||
      "pcm",
 | 
			
		||||
@@ -656,14 +612,14 @@ export const properties = {
 | 
			
		||||
      "pfm",
 | 
			
		||||
      "pgm",
 | 
			
		||||
      "pgmyuv",
 | 
			
		||||
      "phm",
 | 
			
		||||
      "pix",
 | 
			
		||||
      "png",
 | 
			
		||||
      "ppm",
 | 
			
		||||
      "psp",
 | 
			
		||||
      "pulse",
 | 
			
		||||
      "qoi",
 | 
			
		||||
      "ra",
 | 
			
		||||
      "ras",
 | 
			
		||||
      "rawvideo",
 | 
			
		||||
      "rco",
 | 
			
		||||
      "rcv",
 | 
			
		||||
      "rgb",
 | 
			
		||||
@@ -671,84 +627,47 @@ export const properties = {
 | 
			
		||||
      "roq",
 | 
			
		||||
      "rs",
 | 
			
		||||
      "rso",
 | 
			
		||||
      "rtp",
 | 
			
		||||
      "rtp_mpegts",
 | 
			
		||||
      "rtsp",
 | 
			
		||||
      "s16be",
 | 
			
		||||
      "s16le",
 | 
			
		||||
      "s24be",
 | 
			
		||||
      "s24le",
 | 
			
		||||
      "s32be",
 | 
			
		||||
      "s32le",
 | 
			
		||||
      "s8",
 | 
			
		||||
      "sap",
 | 
			
		||||
      "sb",
 | 
			
		||||
      "sbc",
 | 
			
		||||
      "scc",
 | 
			
		||||
      "sdl",
 | 
			
		||||
      "sdl2",
 | 
			
		||||
      "segment",
 | 
			
		||||
      "sf",
 | 
			
		||||
      "sgi",
 | 
			
		||||
      "singlejpeg",
 | 
			
		||||
      "smjpeg",
 | 
			
		||||
      "smoothstreaming",
 | 
			
		||||
      "sndio",
 | 
			
		||||
      "sox",
 | 
			
		||||
      "spdif",
 | 
			
		||||
      "spx",
 | 
			
		||||
      "srt",
 | 
			
		||||
      "ssa",
 | 
			
		||||
      "ssegment",
 | 
			
		||||
      "streamhash",
 | 
			
		||||
      "stream_segment",
 | 
			
		||||
      "sub",
 | 
			
		||||
      "sun",
 | 
			
		||||
      "sunras",
 | 
			
		||||
      "sup",
 | 
			
		||||
      "svcd",
 | 
			
		||||
      "sw",
 | 
			
		||||
      "swf",
 | 
			
		||||
      "tco",
 | 
			
		||||
      "tee",
 | 
			
		||||
      "tga",
 | 
			
		||||
      "thd",
 | 
			
		||||
      "tif",
 | 
			
		||||
      "tiff",
 | 
			
		||||
      "truehd",
 | 
			
		||||
      "ts",
 | 
			
		||||
      "tta",
 | 
			
		||||
      "ttml",
 | 
			
		||||
      "tun",
 | 
			
		||||
      "u16be",
 | 
			
		||||
      "u16le",
 | 
			
		||||
      "u24be",
 | 
			
		||||
      "u24le",
 | 
			
		||||
      "u32be",
 | 
			
		||||
      "u32le",
 | 
			
		||||
      "u8",
 | 
			
		||||
      "ub",
 | 
			
		||||
      "ul",
 | 
			
		||||
      "uncodedframecrc",
 | 
			
		||||
      "uw",
 | 
			
		||||
      "v4l2",
 | 
			
		||||
      "vag",
 | 
			
		||||
      "vbn",
 | 
			
		||||
      "vc1",
 | 
			
		||||
      "vc1test",
 | 
			
		||||
      "vc2",
 | 
			
		||||
      "vcd",
 | 
			
		||||
      "vidc",
 | 
			
		||||
      "video4linux2",
 | 
			
		||||
      "vob",
 | 
			
		||||
      "voc",
 | 
			
		||||
      "vtt",
 | 
			
		||||
      "vvc",
 | 
			
		||||
      "w64",
 | 
			
		||||
      "wav",
 | 
			
		||||
      "wbmp",
 | 
			
		||||
      "webm",
 | 
			
		||||
      "webm_chunk",
 | 
			
		||||
      "webm_dash_manifest",
 | 
			
		||||
      "webp",
 | 
			
		||||
      "webvtt",
 | 
			
		||||
      "wma",
 | 
			
		||||
      "wmv",
 | 
			
		||||
      "wtv",
 | 
			
		||||
@@ -756,12 +675,10 @@ export const properties = {
 | 
			
		||||
      "xbm",
 | 
			
		||||
      "xface",
 | 
			
		||||
      "xml",
 | 
			
		||||
      "xv",
 | 
			
		||||
      "xwd",
 | 
			
		||||
      "y",
 | 
			
		||||
      "y4m",
 | 
			
		||||
      "yuv",
 | 
			
		||||
      "yuv4mpegpipe",
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
@@ -771,42 +688,19 @@ export async function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // let command = "ffmpeg";
 | 
			
		||||
  let extra = "";
 | 
			
		||||
  let message = "Done";
 | 
			
		||||
 | 
			
		||||
  // these are containers that can contain multiple formats
 | 
			
		||||
  // const autoDetect = [
 | 
			
		||||
  //   "mp4",
 | 
			
		||||
  //   "mkv",
 | 
			
		||||
  //   "avi",
 | 
			
		||||
  //   "mov",
 | 
			
		||||
  //   "m4a",
 | 
			
		||||
  //   "3gp",
 | 
			
		||||
  //   "3g2",
 | 
			
		||||
  //   "mj2",
 | 
			
		||||
  //   "psp",
 | 
			
		||||
  //   "m4b",
 | 
			
		||||
  //   "ism",
 | 
			
		||||
  //   "ismv",
 | 
			
		||||
  //   "isma",
 | 
			
		||||
  //   "f4v",
 | 
			
		||||
  // ];
 | 
			
		||||
  if (convertTo === "ico") {
 | 
			
		||||
    // make sure image is 256x256 or smaller
 | 
			
		||||
    extra = `-filter:v "scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease"`;
 | 
			
		||||
    message = "Done: resized to 256x256";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // if (!(fileType in autoDetect)) {
 | 
			
		||||
  //   command += ` -f "${fileType}"`;
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // command += ` -i "${filePath}"`;
 | 
			
		||||
 | 
			
		||||
  // if (!(convertTo in autoDetect)) {
 | 
			
		||||
  //   command += ` -f "${convertTo}"`;
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // command += ` "${targetPath}"`;
 | 
			
		||||
 | 
			
		||||
  const command = `ffmpeg -i "${filePath}" "${targetPath}"`;
 | 
			
		||||
  const command = `ffmpeg -i "${filePath}" ${extra} "${targetPath}"`;
 | 
			
		||||
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(command, (error, stdout, stderr) => {
 | 
			
		||||
@@ -822,7 +716,7 @@ export async function convert(
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("success");
 | 
			
		||||
      resolve(message);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,6 +143,7 @@ export const properties = {
 | 
			
		||||
      "svgz",
 | 
			
		||||
      "text",
 | 
			
		||||
      "tga",
 | 
			
		||||
      "tif",
 | 
			
		||||
      "tiff",
 | 
			
		||||
      "tile",
 | 
			
		||||
      "tim",
 | 
			
		||||
@@ -227,7 +228,6 @@ export const properties = {
 | 
			
		||||
      "jbig",
 | 
			
		||||
      "jng",
 | 
			
		||||
      "jpeg",
 | 
			
		||||
      "jpg",
 | 
			
		||||
      "k",
 | 
			
		||||
      "m",
 | 
			
		||||
      "m2v",
 | 
			
		||||
@@ -313,8 +313,8 @@ export function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(
 | 
			
		||||
@@ -332,7 +332,7 @@ export function convert(
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
        resolve("Done");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -39,8 +39,8 @@ export function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  let tool = "";
 | 
			
		||||
  if (fileType === "jxl") {
 | 
			
		||||
@@ -65,7 +65,7 @@ export function convert(
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      resolve("success");
 | 
			
		||||
      resolve("Done");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +1,44 @@
 | 
			
		||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertPandoc,
 | 
			
		||||
  properties as propertiesPandoc,
 | 
			
		||||
} from "./pandoc";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertFFmpeg,
 | 
			
		||||
  properties as propertiesFFmpeg,
 | 
			
		||||
} from "./ffmpeg";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertGraphicsmagick,
 | 
			
		||||
  properties as propertiesGraphicsmagick,
 | 
			
		||||
} from "./graphicsmagick";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertxelatex,
 | 
			
		||||
  properties as propertiesxelatex,
 | 
			
		||||
} from "./xelatex";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertLibjxl,
 | 
			
		||||
  properties as propertiesLibjxl,
 | 
			
		||||
} from "./libjxl";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  convert as convertresvg,
 | 
			
		||||
  properties as propertiesresvg,
 | 
			
		||||
} from "./resvg";
 | 
			
		||||
 | 
			
		||||
import { normalizeFiletype } from "../helpers/normalizeFiletype";
 | 
			
		||||
import { convert as convertassimp, properties as propertiesassimp } from "./assimp";
 | 
			
		||||
import { convert as convertFFmpeg, properties as propertiesFFmpeg } from "./ffmpeg";
 | 
			
		||||
import { convert as convertGraphicsmagick, properties as propertiesGraphicsmagick } from "./graphicsmagick";
 | 
			
		||||
import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libjxl";
 | 
			
		||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// 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: {
 | 
			
		||||
  [key: string]: {
 | 
			
		||||
const properties: Record<
 | 
			
		||||
  string,
 | 
			
		||||
  {
 | 
			
		||||
    properties: {
 | 
			
		||||
      from: { [key: string]: string[] };
 | 
			
		||||
      to: { [key: string]: string[] };
 | 
			
		||||
      options?: {
 | 
			
		||||
        [key: string]: {
 | 
			
		||||
          [key: string]: {
 | 
			
		||||
      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,
 | 
			
		||||
      // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
      options?: any,
 | 
			
		||||
      // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
    ) => any;
 | 
			
		||||
  };
 | 
			
		||||
} = {
 | 
			
		||||
 | 
			
		||||
      options?: unknown,
 | 
			
		||||
    ) => unknown;
 | 
			
		||||
  }
 | 
			
		||||
> = {
 | 
			
		||||
  libjxl: {
 | 
			
		||||
    properties: propertiesLibjxl,
 | 
			
		||||
    converter: convertLibjxl,
 | 
			
		||||
@@ -84,6 +63,10 @@ const properties: {
 | 
			
		||||
    properties: propertiesGraphicsmagick,
 | 
			
		||||
    converter: convertGraphicsmagick,
 | 
			
		||||
  },
 | 
			
		||||
  assimp: {
 | 
			
		||||
    properties: propertiesassimp,
 | 
			
		||||
    converter: convertassimp,
 | 
			
		||||
  },
 | 
			
		||||
  ffmpeg: {
 | 
			
		||||
    properties: propertiesFFmpeg,
 | 
			
		||||
    converter: convertFFmpeg,
 | 
			
		||||
@@ -93,24 +76,19 @@ const properties: {
 | 
			
		||||
export async function mainConverter(
 | 
			
		||||
  inputFilePath: string,
 | 
			
		||||
  fileTypeOriginal: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  convertTo: any,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
  converterName?: string,
 | 
			
		||||
) {
 | 
			
		||||
  const fileType = normalizeFiletype(fileTypeOriginal);
 | 
			
		||||
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  let converterFunc: any;
 | 
			
		||||
  // let converterName = converterName;
 | 
			
		||||
  let converterFunc: typeof properties["libjxl"]["converter"] | undefined;
 | 
			
		||||
 | 
			
		||||
  if (converterName) {
 | 
			
		||||
    converterFunc = properties[converterName]?.converter;
 | 
			
		||||
  } else {
 | 
			
		||||
    // Iterate over each converter in properties
 | 
			
		||||
    // biome-ignore lint/style/noParameterAssign: <explanation>
 | 
			
		||||
    for (converterName in properties) {
 | 
			
		||||
      const converterObj = properties[converterName];
 | 
			
		||||
 | 
			
		||||
@@ -138,7 +116,7 @@ export async function mainConverter(
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    await converterFunc(
 | 
			
		||||
    const result = await converterFunc(
 | 
			
		||||
      inputFilePath,
 | 
			
		||||
      fileType,
 | 
			
		||||
      convertTo,
 | 
			
		||||
@@ -148,7 +126,13 @@ export async function mainConverter(
 | 
			
		||||
 | 
			
		||||
    console.log(
 | 
			
		||||
      `Converted ${inputFilePath} from ${fileType} to ${convertTo} successfully using ${converterName}.`,
 | 
			
		||||
      result,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (typeof result === "string") {
 | 
			
		||||
      return result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return "Done";
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error(
 | 
			
		||||
@@ -159,7 +143,7 @@ export async function mainConverter(
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const possibleTargets: { [key: string]: { [key: string]: string[] } } = {};
 | 
			
		||||
const possibleTargets: Record<string, Record<string, string[]>> = {};
 | 
			
		||||
 | 
			
		||||
for (const converterName in properties) {
 | 
			
		||||
  const converterProperties = properties[converterName]?.properties;
 | 
			
		||||
@@ -184,9 +168,7 @@ for (const converterName in properties) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const getPossibleTargets = (
 | 
			
		||||
  from: string,
 | 
			
		||||
): { [key: string]: string[] } => {
 | 
			
		||||
export const getPossibleTargets = (from: string): Record<string, string[]> => {
 | 
			
		||||
  const fromClean = normalizeFiletype(from);
 | 
			
		||||
 | 
			
		||||
  return possibleTargets[fromClean] || {};
 | 
			
		||||
@@ -210,11 +192,12 @@ for (const converterName in properties) {
 | 
			
		||||
}
 | 
			
		||||
possibleInputs.sort();
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
const getPossibleInputs = () => {
 | 
			
		||||
  return possibleInputs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const allTargets: { [key: string]: string[] } = {};
 | 
			
		||||
const allTargets: Record<string, string[]> = {};
 | 
			
		||||
 | 
			
		||||
for (const converterName in properties) {
 | 
			
		||||
  const converterProperties = properties[converterName]?.properties;
 | 
			
		||||
@@ -236,7 +219,7 @@ export const getAllTargets = () => {
 | 
			
		||||
  return allTargets;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const allInputs: { [key: string]: string[] } = {};
 | 
			
		||||
const allInputs: Record<string, string[]> = {};
 | 
			
		||||
for (const converterName in properties) {
 | 
			
		||||
  const converterProperties = properties[converterName]?.properties;
 | 
			
		||||
 | 
			
		||||
@@ -281,4 +264,4 @@ export const getAllInputs = (converter: string) => {
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
// // print the number of unique Inputs and Outputs
 | 
			
		||||
// console.log(`Unique Formats: ${uniqueFormats.size}`);
 | 
			
		||||
// console.log(`Unique Formats: ${uniqueFormats.size}`);
 | 
			
		||||
@@ -124,8 +124,8 @@ export function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // set xelatex here
 | 
			
		||||
  const xelatex = ["pdf", "latex"];
 | 
			
		||||
@@ -149,7 +149,7 @@ export function convert(
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
        resolve("Done");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -9,33 +9,29 @@ export const properties = {
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export function convert(
 | 
			
		||||
  filePath: string,
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    exec(
 | 
			
		||||
      `resvg "${filePath}" "${targetPath}"`,
 | 
			
		||||
      (error, stdout, stderr) => {
 | 
			
		||||
        if (error) {
 | 
			
		||||
          reject(`error: ${error}`);
 | 
			
		||||
        }
 | 
			
		||||
    exec(`resvg "${filePath}" "${targetPath}"`, (error, stdout, stderr) => {
 | 
			
		||||
      if (error) {
 | 
			
		||||
        reject(`error: ${error}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        if (stdout) {
 | 
			
		||||
          console.log(`stdout: ${stdout}`);
 | 
			
		||||
        }
 | 
			
		||||
      if (stdout) {
 | 
			
		||||
        console.log(`stdout: ${stdout}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        if (stderr) {
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
      if (stderr) {
 | 
			
		||||
        console.error(`stderr: ${stderr}`);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
      resolve("Done");
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { exec } from "node:child_process";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// declare possible conversions
 | 
			
		||||
export const properties = {
 | 
			
		||||
  from: {
 | 
			
		||||
@@ -94,8 +95,8 @@ export function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  // if (fileType === "svg") {
 | 
			
		||||
  //   const scale = options.scale || 1;
 | 
			
		||||
@@ -134,8 +135,8 @@ export function convert(
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
        resolve("Done");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -14,8 +14,8 @@ export function convert(
 | 
			
		||||
  fileType: string,
 | 
			
		||||
  convertTo: string,
 | 
			
		||||
  targetPath: string,
 | 
			
		||||
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
 | 
			
		||||
  options?: any,
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
  options?: unknown,
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  return new Promise((resolve, reject) => {
 | 
			
		||||
    // const fileName: string = (targetPath.split("/").pop() as string).replace(".pdf", "")
 | 
			
		||||
@@ -39,7 +39,7 @@ export function convert(
 | 
			
		||||
          console.error(`stderr: ${stderr}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        resolve("success");
 | 
			
		||||
        resolve("Done");
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ export const normalizeFiletype = (filetype: string): string => {
 | 
			
		||||
  const lowercaseFiletype = filetype.toLowerCase();
 | 
			
		||||
 | 
			
		||||
  switch (lowercaseFiletype) {
 | 
			
		||||
    case "jfif":
 | 
			
		||||
    case "jpg":
 | 
			
		||||
      return "jpeg";
 | 
			
		||||
    case "htm":
 | 
			
		||||
@@ -23,6 +24,9 @@ export const normalizeOutputFiletype = (filetype: string): string => {
 | 
			
		||||
      return "jpg";
 | 
			
		||||
    case "latex":
 | 
			
		||||
      return "tex";
 | 
			
		||||
    case "markdown_phpextra":
 | 
			
		||||
    case "markdown_strict":
 | 
			
		||||
    case "markdown_mmd":
 | 
			
		||||
    case "markdown":
 | 
			
		||||
      return "md";
 | 
			
		||||
    default:
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,16 @@ if (process.env.NODE_ENV === "production") {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("assimp version", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("assimp is not installed");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (stdout) {
 | 
			
		||||
      console.log(`assimp v${stdout.split("\n")[5]}`);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  exec("bun -v", (error, stdout) => {
 | 
			
		||||
    if (error) {
 | 
			
		||||
      console.error("Bun is not installed. wait what");
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								src/helpers/tailwind.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/helpers/tailwind.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import tw from "tailwindcss";
 | 
			
		||||
import postcss from "postcss";
 | 
			
		||||
 | 
			
		||||
export const generateTailwind = async () => {
 | 
			
		||||
  const result = await Bun.file("./src/main.css")
 | 
			
		||||
    .text()
 | 
			
		||||
    .then((sourceText) => {
 | 
			
		||||
      const config = "./tailwind.config.js";
 | 
			
		||||
 | 
			
		||||
      return postcss([tw(config)]).process(sourceText, {
 | 
			
		||||
        from: "./src/main.css",
 | 
			
		||||
        to: "./public/generated.css",
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										502
									
								
								src/index.tsx
									
									
									
									
									
								
							
							
						
						
									
										502
									
								
								src/index.tsx
									
									
									
									
									
								
							@@ -1,12 +1,12 @@
 | 
			
		||||
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 } from "@elysiajs/html";
 | 
			
		||||
import { jwt, type JWTPayloadSpec } from "@elysiajs/jwt";
 | 
			
		||||
import { staticPlugin } from "@elysiajs/static";
 | 
			
		||||
import { Database } from "bun:sqlite";
 | 
			
		||||
import { Elysia, t } from "elysia";
 | 
			
		||||
import { randomInt, randomUUID } from "node:crypto";
 | 
			
		||||
import { rmSync } from "node:fs";
 | 
			
		||||
import { mkdir, unlink } from "node:fs/promises";
 | 
			
		||||
import { BaseHtml } from "./components/base";
 | 
			
		||||
import { Header } from "./components/header";
 | 
			
		||||
import {
 | 
			
		||||
@@ -27,11 +27,15 @@ const uploadsDir = "./data/uploads/";
 | 
			
		||||
const outputDir = "./data/output/";
 | 
			
		||||
 | 
			
		||||
const ACCOUNT_REGISTRATION =
 | 
			
		||||
  process.env.ACCOUNT_REGISTRATION === "true" || false;
 | 
			
		||||
  process.env.ACCOUNT_REGISTRATION?.toLowerCase() === "true" || false;
 | 
			
		||||
 | 
			
		||||
const HTTP_ALLOWED = process.env.HTTP_ALLOWED === "true" || false;
 | 
			
		||||
const HTTP_ALLOWED =
 | 
			
		||||
  process.env.HTTP_ALLOWED?.toLowerCase() === "true" || false;
 | 
			
		||||
const ALLOW_UNAUTHENTICATED =
 | 
			
		||||
  process.env.ALLOW_UNAUTHENTICATED === "true" || false;
 | 
			
		||||
  process.env.ALLOW_UNAUTHENTICATED?.toLowerCase() === "true" || false;
 | 
			
		||||
const AUTO_DELETE_EVERY_N_HOURS = process.env.AUTO_DELETE_EVERY_N_HOURS
 | 
			
		||||
  ? Number(process.env.AUTO_DELETE_EVERY_N_HOURS)
 | 
			
		||||
  : 24;
 | 
			
		||||
 | 
			
		||||
// fileNames: fileNames,
 | 
			
		||||
// filesToConvert: fileNames.length,
 | 
			
		||||
@@ -134,36 +138,51 @@ const app = new Elysia({
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <BaseHtml title="ConvertX | Setup">
 | 
			
		||||
        <main class="container">
 | 
			
		||||
          <h1>Welcome to ConvertX</h1>
 | 
			
		||||
          <article>
 | 
			
		||||
            <header>Create your account</header>
 | 
			
		||||
            <form method="post" action="/register">
 | 
			
		||||
              <fieldset>
 | 
			
		||||
                <label>
 | 
			
		||||
                  Email/Username
 | 
			
		||||
        <main class="mx-auto w-full max-w-4xl px-4">
 | 
			
		||||
          <h1 class="my-8 text-3xl">Welcome to ConvertX!</h1>
 | 
			
		||||
          <article class="article p-0">
 | 
			
		||||
            <header class="w-full bg-neutral-800 p-4">
 | 
			
		||||
              Create your account
 | 
			
		||||
            </header>
 | 
			
		||||
            <form method="post" action="/register" class="p-4">
 | 
			
		||||
              <fieldset class="mb-4 flex flex-col gap-4">
 | 
			
		||||
                <label class="flex flex-col gap-1">
 | 
			
		||||
                  Email
 | 
			
		||||
                  <input
 | 
			
		||||
                    type="email"
 | 
			
		||||
                    name="email"
 | 
			
		||||
                    class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                    placeholder="Email"
 | 
			
		||||
                    autocomplete="email"
 | 
			
		||||
                    required
 | 
			
		||||
                  />
 | 
			
		||||
                </label>
 | 
			
		||||
                <label>
 | 
			
		||||
                <label class="flex flex-col gap-1">
 | 
			
		||||
                  Password
 | 
			
		||||
                  <input
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    name="password"
 | 
			
		||||
                    class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                    placeholder="Password"
 | 
			
		||||
                    autocomplete="current-password"
 | 
			
		||||
                    required
 | 
			
		||||
                  />
 | 
			
		||||
                </label>
 | 
			
		||||
              </fieldset>
 | 
			
		||||
              <input type="submit" value="Create account" />
 | 
			
		||||
              <input type="submit" value="Create account" class="btn-primary" />
 | 
			
		||||
            </form>
 | 
			
		||||
            <footer>
 | 
			
		||||
            <footer class="p-4">
 | 
			
		||||
              Report any issues on{" "}
 | 
			
		||||
              <a href="https://github.com/C4illin/ConvertX">GitHub</a>.
 | 
			
		||||
              <a
 | 
			
		||||
                class={`
 | 
			
		||||
                  text-accent-500 underline
 | 
			
		||||
                  hover:text-accent-400
 | 
			
		||||
                `}
 | 
			
		||||
                href="https://github.com/C4illin/ConvertX"
 | 
			
		||||
              >
 | 
			
		||||
                GitHub
 | 
			
		||||
              </a>
 | 
			
		||||
              .
 | 
			
		||||
            </footer>
 | 
			
		||||
          </article>
 | 
			
		||||
        </main>
 | 
			
		||||
@@ -179,32 +198,38 @@ const app = new Elysia({
 | 
			
		||||
      <BaseHtml title="ConvertX | Register">
 | 
			
		||||
        <>
 | 
			
		||||
          <Header accountRegistration={ACCOUNT_REGISTRATION} />
 | 
			
		||||
          <main class="container">
 | 
			
		||||
            <article>
 | 
			
		||||
              <form method="post">
 | 
			
		||||
                <fieldset>
 | 
			
		||||
                  <label>
 | 
			
		||||
          <main class="w-full px-4">
 | 
			
		||||
            <article class="article">
 | 
			
		||||
              <form method="post" class="flex flex-col gap-4">
 | 
			
		||||
                <fieldset class="mb-4 flex flex-col gap-4">
 | 
			
		||||
                  <label class="flex flex-col gap-1">
 | 
			
		||||
                    Email
 | 
			
		||||
                    <input
 | 
			
		||||
                      type="email"
 | 
			
		||||
                      name="email"
 | 
			
		||||
                      class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                      placeholder="Email"
 | 
			
		||||
                      autocomplete="email"
 | 
			
		||||
                      required
 | 
			
		||||
                    />
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <label>
 | 
			
		||||
                  <label class="flex flex-col gap-1">
 | 
			
		||||
                    Password
 | 
			
		||||
                    <input
 | 
			
		||||
                      type="password"
 | 
			
		||||
                      name="password"
 | 
			
		||||
                      class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                      placeholder="Password"
 | 
			
		||||
                      autocomplete="new-password"
 | 
			
		||||
                      autocomplete="current-password"
 | 
			
		||||
                      required
 | 
			
		||||
                    />
 | 
			
		||||
                  </label>
 | 
			
		||||
                </fieldset>
 | 
			
		||||
                <input type="submit" value="Register" />
 | 
			
		||||
                <input
 | 
			
		||||
                  type="submit"
 | 
			
		||||
                  value="Register"
 | 
			
		||||
                  class="btn-primary w-full"
 | 
			
		||||
                />
 | 
			
		||||
              </form>
 | 
			
		||||
            </article>
 | 
			
		||||
          </main>
 | 
			
		||||
@@ -295,25 +320,27 @@ const app = new Elysia({
 | 
			
		||||
      <BaseHtml title="ConvertX | Login">
 | 
			
		||||
        <>
 | 
			
		||||
          <Header accountRegistration={ACCOUNT_REGISTRATION} />
 | 
			
		||||
          <main class="container">
 | 
			
		||||
            <article>
 | 
			
		||||
              <form method="post">
 | 
			
		||||
                <fieldset>
 | 
			
		||||
                  <label>
 | 
			
		||||
          <main class="w-full px-4">
 | 
			
		||||
            <article class="article">
 | 
			
		||||
              <form method="post" class="flex flex-col gap-4">
 | 
			
		||||
                <fieldset class="mb-4 flex flex-col gap-4">
 | 
			
		||||
                  <label class="flex flex-col gap-1">
 | 
			
		||||
                    Email
 | 
			
		||||
                    <input
 | 
			
		||||
                      type="email"
 | 
			
		||||
                      name="email"
 | 
			
		||||
                      class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                      placeholder="Email"
 | 
			
		||||
                      autocomplete="email"
 | 
			
		||||
                      required
 | 
			
		||||
                    />
 | 
			
		||||
                  </label>
 | 
			
		||||
                  <label>
 | 
			
		||||
                  <label class="flex flex-col gap-1">
 | 
			
		||||
                    Password
 | 
			
		||||
                    <input
 | 
			
		||||
                      type="password"
 | 
			
		||||
                      name="password"
 | 
			
		||||
                      class="rounded bg-neutral-800 p-3"
 | 
			
		||||
                      placeholder="Password"
 | 
			
		||||
                      autocomplete="current-password"
 | 
			
		||||
                      required
 | 
			
		||||
@@ -321,12 +348,20 @@ const app = new Elysia({
 | 
			
		||||
                  </label>
 | 
			
		||||
                </fieldset>
 | 
			
		||||
                <div role="group">
 | 
			
		||||
                  {ACCOUNT_REGISTRATION && (
 | 
			
		||||
                    <a href="/register" role="button" class="secondary">
 | 
			
		||||
                  {ACCOUNT_REGISTRATION ? (
 | 
			
		||||
                    <a
 | 
			
		||||
                      href="/register"
 | 
			
		||||
                      role="button"
 | 
			
		||||
                      class="btn-primary w-full"
 | 
			
		||||
                    >
 | 
			
		||||
                      Register an account
 | 
			
		||||
                    </a>
 | 
			
		||||
                  )}
 | 
			
		||||
                  <input type="submit" value="Login" />
 | 
			
		||||
                  ) : null}
 | 
			
		||||
                  <input
 | 
			
		||||
                    type="submit"
 | 
			
		||||
                    value="Login"
 | 
			
		||||
                    class="btn-primary w-full"
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </form>
 | 
			
		||||
            </article>
 | 
			
		||||
@@ -431,7 +466,12 @@ const app = new Elysia({
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else if (ALLOW_UNAUTHENTICATED) {
 | 
			
		||||
      const newUserId = String(randomInt(2 ** 24, Number.MAX_SAFE_INTEGER));
 | 
			
		||||
      const newUserId = String(
 | 
			
		||||
        randomInt(
 | 
			
		||||
          2 ** 24,
 | 
			
		||||
          Math.min(2 ** 48 + 2 ** 24 - 1, Number.MAX_SAFE_INTEGER),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      const accessToken = await jwt.sign({
 | 
			
		||||
        id: newUserId,
 | 
			
		||||
      });
 | 
			
		||||
@@ -448,7 +488,7 @@ const app = new Elysia({
 | 
			
		||||
        value: accessToken,
 | 
			
		||||
        httpOnly: true,
 | 
			
		||||
        secure: !HTTP_ALLOWED,
 | 
			
		||||
        maxAge: 60 * 60 * 24 * 1,
 | 
			
		||||
        maxAge: 24 * 60 * 60,
 | 
			
		||||
        sameSite: "strict",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
@@ -487,86 +527,87 @@ const app = new Elysia({
 | 
			
		||||
      <BaseHtml>
 | 
			
		||||
        <>
 | 
			
		||||
          <Header loggedIn />
 | 
			
		||||
          <main class="container">
 | 
			
		||||
            <article>
 | 
			
		||||
              <h1>Convert</h1>
 | 
			
		||||
              <div style={{ maxHeight: "50vh", overflowY: "auto" }}>
 | 
			
		||||
                <table id="file-list" class="striped" />
 | 
			
		||||
          <main class="w-full px-4">
 | 
			
		||||
            <article class="article">
 | 
			
		||||
              <h1 class="mb-4 text-xl">Convert</h1>
 | 
			
		||||
              <div class="mb-4 max-h-[50vh] overflow-y-auto scrollbar-thin">
 | 
			
		||||
                <table
 | 
			
		||||
                  id="file-list"
 | 
			
		||||
                  class={`
 | 
			
		||||
                    w-full table-auto rounded bg-neutral-900
 | 
			
		||||
                    [&_td]:p-4
 | 
			
		||||
                    [&_tr]:rounded [&_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
 | 
			
		||||
                  [&.dragover]:border-4 [&.dragover]:border-neutral-500
 | 
			
		||||
                  hover:border-neutral-600
 | 
			
		||||
                `}
 | 
			
		||||
              >
 | 
			
		||||
                <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>
 | 
			
		||||
              <input type="file" name="file" multiple />
 | 
			
		||||
              {/* <label for="convert_from">Convert from</label> */}
 | 
			
		||||
              {/* <select name="convert_from" aria-label="Convert from" required>
 | 
			
		||||
              <option selected disabled value="">
 | 
			
		||||
                Convert from
 | 
			
		||||
              </option>
 | 
			
		||||
              {getPossibleInputs().map((input) => (
 | 
			
		||||
                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                <option>{input}</option>
 | 
			
		||||
              ))}
 | 
			
		||||
            </select> */}
 | 
			
		||||
            </article>
 | 
			
		||||
            <form
 | 
			
		||||
              method="post"
 | 
			
		||||
              action="/convert"
 | 
			
		||||
              style={{ position: "relative" }}>
 | 
			
		||||
              class="relative mx-auto mb-[35vh] w-full max-w-4xl"
 | 
			
		||||
            >
 | 
			
		||||
              <input type="hidden" name="file_names" id="file_names" />
 | 
			
		||||
              <article>
 | 
			
		||||
              <article class="article w-full">
 | 
			
		||||
                <input
 | 
			
		||||
                  type="search"
 | 
			
		||||
                  name="convert_to_search"
 | 
			
		||||
                  placeholder="Search for conversions"
 | 
			
		||||
                  autocomplete="off"
 | 
			
		||||
                  class="w-full rounded bg-neutral-800 p-4"
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                <div class="select_container">
 | 
			
		||||
                <div class="select_container relative">
 | 
			
		||||
                  <article
 | 
			
		||||
                    class="convert_to_popup"
 | 
			
		||||
                    hidden
 | 
			
		||||
                    style={{
 | 
			
		||||
                      flexDirection: "column",
 | 
			
		||||
                      display: "flex",
 | 
			
		||||
                      zIndex: 2,
 | 
			
		||||
                      position: "absolute",
 | 
			
		||||
                      maxHeight: "50vh",
 | 
			
		||||
                      width: "90vw",
 | 
			
		||||
                      overflowY: "scroll",
 | 
			
		||||
                      margin: "0px",
 | 
			
		||||
                      overflowX: "hidden",
 | 
			
		||||
                    }}>
 | 
			
		||||
                    class={`
 | 
			
		||||
                      convert_to_popup absolute z-[2] m-0 hidden h-[30vh] max-h-[50vh] w-full
 | 
			
		||||
                      flex-col overflow-y-auto overflow-x-hidden rounded bg-neutral-800
 | 
			
		||||
                      sm:h-[30vh]
 | 
			
		||||
                    `}
 | 
			
		||||
                  >
 | 
			
		||||
                    {Object.entries(getAllTargets()).map(
 | 
			
		||||
                      ([converter, targets]) => (
 | 
			
		||||
                        // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                        <article
 | 
			
		||||
                          class="convert_to_group"
 | 
			
		||||
                          class={`
 | 
			
		||||
                            convert_to_group flex w-full flex-col border-b border-neutral-700 p-4
 | 
			
		||||
                          `}
 | 
			
		||||
                          data-converter={converter}
 | 
			
		||||
                          style={{
 | 
			
		||||
                            borderColor: "gray",
 | 
			
		||||
                            padding: "2px",
 | 
			
		||||
                          }}>
 | 
			
		||||
                          <header
 | 
			
		||||
                            style={{ fontSize: "20px", fontWeight: "bold" }}>
 | 
			
		||||
                        >
 | 
			
		||||
                          <header class="mb-2 w-full text-xl font-bold" safe>
 | 
			
		||||
                            {converter}
 | 
			
		||||
                          </header>
 | 
			
		||||
 | 
			
		||||
                          <ul
 | 
			
		||||
                            class="convert_to_target"
 | 
			
		||||
                            style={{
 | 
			
		||||
                              display: "flex",
 | 
			
		||||
                              flexDirection: "row",
 | 
			
		||||
                              gap: "5px",
 | 
			
		||||
                              flexWrap: "wrap",
 | 
			
		||||
                            }}>
 | 
			
		||||
                          <ul class="convert_to_target flex flex-row flex-wrap gap-1">
 | 
			
		||||
                            {targets.map((target) => (
 | 
			
		||||
                              // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                              <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"
 | 
			
		||||
                                class={`
 | 
			
		||||
                                  target rounded bg-neutral-700 p-1 text-base
 | 
			
		||||
                                  hover:bg-neutral-600
 | 
			
		||||
                                `}
 | 
			
		||||
                                data-value={`${target},${converter}`}
 | 
			
		||||
                                data-target={target}
 | 
			
		||||
                                data-converter={converter}
 | 
			
		||||
                                style={{ fontSize: "15px", padding: "5px" }}
 | 
			
		||||
                                type="button">
 | 
			
		||||
                                type="button"
 | 
			
		||||
                                safe
 | 
			
		||||
                              >
 | 
			
		||||
                                {target}
 | 
			
		||||
                              </button>
 | 
			
		||||
                            ))}
 | 
			
		||||
@@ -581,16 +622,15 @@ const app = new Elysia({
 | 
			
		||||
                    name="convert_to"
 | 
			
		||||
                    aria-label="Convert to"
 | 
			
		||||
                    required
 | 
			
		||||
                    hidden>
 | 
			
		||||
                    hidden
 | 
			
		||||
                  >
 | 
			
		||||
                    <option selected disabled value="">
 | 
			
		||||
                      Convert to
 | 
			
		||||
                    </option>
 | 
			
		||||
                    {Object.entries(getAllTargets()).map(
 | 
			
		||||
                      ([converter, targets]) => (
 | 
			
		||||
                        // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                        <optgroup label={converter}>
 | 
			
		||||
                          {targets.map((target) => (
 | 
			
		||||
                            // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                            <option value={`${target},${converter}`} safe>
 | 
			
		||||
                              {target}
 | 
			
		||||
                            </option>
 | 
			
		||||
@@ -601,7 +641,15 @@ const app = new Elysia({
 | 
			
		||||
                  </select>
 | 
			
		||||
                </div>
 | 
			
		||||
              </article>
 | 
			
		||||
              <input type="submit" value="Convert" />
 | 
			
		||||
              <input
 | 
			
		||||
                class={`
 | 
			
		||||
                  btn-primary w-full
 | 
			
		||||
                  disabled:cursor-not-allowed disabled:opacity-50
 | 
			
		||||
                `}
 | 
			
		||||
                type="submit"
 | 
			
		||||
                value="Convert"
 | 
			
		||||
                disabled
 | 
			
		||||
              />
 | 
			
		||||
            </form>
 | 
			
		||||
          </main>
 | 
			
		||||
          <script src="script.js" defer />
 | 
			
		||||
@@ -615,52 +663,36 @@ const app = new Elysia({
 | 
			
		||||
      return (
 | 
			
		||||
        <>
 | 
			
		||||
          <article
 | 
			
		||||
            class="convert_to_popup"
 | 
			
		||||
            hidden
 | 
			
		||||
            style={{
 | 
			
		||||
              flexDirection: "column",
 | 
			
		||||
              display: "flex",
 | 
			
		||||
              zIndex: 2,
 | 
			
		||||
              position: "absolute",
 | 
			
		||||
              maxHeight: "50vh",
 | 
			
		||||
              width: "90vw",
 | 
			
		||||
              overflowY: "scroll",
 | 
			
		||||
              margin: "0px",
 | 
			
		||||
              overflowX: "hidden",
 | 
			
		||||
            }}>
 | 
			
		||||
            class={`
 | 
			
		||||
              convert_to_popup absolute z-[2] m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
 | 
			
		||||
              overflow-y-auto overflow-x-hidden rounded bg-neutral-800
 | 
			
		||||
              sm:h-[30vh]
 | 
			
		||||
            `}
 | 
			
		||||
          >
 | 
			
		||||
            {Object.entries(getPossibleTargets(body.fileType)).map(
 | 
			
		||||
              ([converter, targets]) => (
 | 
			
		||||
                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                <article
 | 
			
		||||
                  class="convert_to_group"
 | 
			
		||||
                  class="convert_to_group flex w-full flex-col border-b border-neutral-700 p-4"
 | 
			
		||||
                  data-converter={converter}
 | 
			
		||||
                  style={{
 | 
			
		||||
                    borderColor: "gray",
 | 
			
		||||
                    padding: "2px",
 | 
			
		||||
                  }}>
 | 
			
		||||
                  <header style={{ fontSize: "20px", fontWeight: "bold" }}>
 | 
			
		||||
                >
 | 
			
		||||
                  <header class="mb-2 w-full text-xl font-bold" safe>
 | 
			
		||||
                    {converter}
 | 
			
		||||
                  </header>
 | 
			
		||||
 | 
			
		||||
                  <ul
 | 
			
		||||
                    class="convert_to_target"
 | 
			
		||||
                    style={{
 | 
			
		||||
                      display: "flex",
 | 
			
		||||
                      flexDirection: "row",
 | 
			
		||||
                      gap: "5px",
 | 
			
		||||
                      flexWrap: "wrap",
 | 
			
		||||
                    }}>
 | 
			
		||||
                  <ul class="convert_to_target flex flex-row flex-wrap gap-1">
 | 
			
		||||
                    {targets.map((target) => (
 | 
			
		||||
                      // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                      <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"
 | 
			
		||||
                        class={`
 | 
			
		||||
                          target rounded bg-neutral-700 p-1 text-base
 | 
			
		||||
                          hover:bg-neutral-600
 | 
			
		||||
                        `}
 | 
			
		||||
                        data-value={`${target},${converter}`}
 | 
			
		||||
                        data-target={target}
 | 
			
		||||
                        data-converter={converter}
 | 
			
		||||
                        style={{ fontSize: "15px", padding: "5px" }}
 | 
			
		||||
                        type="button">
 | 
			
		||||
                        type="button"
 | 
			
		||||
                        safe
 | 
			
		||||
                      >
 | 
			
		||||
                        {target}
 | 
			
		||||
                      </button>
 | 
			
		||||
                    ))}
 | 
			
		||||
@@ -676,10 +708,8 @@ const app = new Elysia({
 | 
			
		||||
            </option>
 | 
			
		||||
            {Object.entries(getPossibleTargets(body.fileType)).map(
 | 
			
		||||
              ([converter, targets]) => (
 | 
			
		||||
                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                <optgroup label={converter}>
 | 
			
		||||
                  {targets.map((target) => (
 | 
			
		||||
                    // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                    <option value={`${target},${converter}`} safe>
 | 
			
		||||
                      {target}
 | 
			
		||||
                    </option>
 | 
			
		||||
@@ -725,7 +755,6 @@ const app = new Elysia({
 | 
			
		||||
            await Bun.write(`${userUploadsDir}${file.name}`, file);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // biome-ignore lint/complexity/useLiteralKeys: weird error
 | 
			
		||||
          await Bun.write(`${userUploadsDir}${body.file["name"]}`, body.file);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -827,7 +856,10 @@ const app = new Elysia({
 | 
			
		||||
          const fileTypeOrig = fileName.split(".").pop() ?? "";
 | 
			
		||||
          const fileType = normalizeFiletype(fileTypeOrig);
 | 
			
		||||
          const newFileExt = normalizeOutputFiletype(convertTo);
 | 
			
		||||
          const newFileName = fileName.replace(fileTypeOrig, newFileExt);
 | 
			
		||||
          const newFileName = fileName.replace(
 | 
			
		||||
            new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
 | 
			
		||||
            newFileExt,
 | 
			
		||||
          );
 | 
			
		||||
          const targetPath = `${userOutputDir}${newFileName}`;
 | 
			
		||||
 | 
			
		||||
          const result = await mainConverter(
 | 
			
		||||
@@ -899,29 +931,42 @@ const app = new Elysia({
 | 
			
		||||
      <BaseHtml title="ConvertX | Results">
 | 
			
		||||
        <>
 | 
			
		||||
          <Header loggedIn />
 | 
			
		||||
          <main class="container">
 | 
			
		||||
            <article>
 | 
			
		||||
              <h1>Results</h1>
 | 
			
		||||
              <table>
 | 
			
		||||
          <main class="w-full px-4">
 | 
			
		||||
            <article class="article">
 | 
			
		||||
              <h1 class="mb-4 text-xl">Results</h1>
 | 
			
		||||
              <table
 | 
			
		||||
                class={`
 | 
			
		||||
                  w-full table-auto rounded bg-neutral-900 text-left
 | 
			
		||||
                  [&_td]:p-4
 | 
			
		||||
                  [&_tr]:rounded [&_tr]:border-b [&_tr]:border-neutral-800
 | 
			
		||||
                `}
 | 
			
		||||
              >
 | 
			
		||||
                <thead>
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <th>Time</th>
 | 
			
		||||
                    <th>Files</th>
 | 
			
		||||
                    <th>Files Done</th>
 | 
			
		||||
                    <th>Status</th>
 | 
			
		||||
                    <th>View</th>
 | 
			
		||||
                    <th class="px-4 py-2">Time</th>
 | 
			
		||||
                    <th class="px-4 py-2">Files</th>
 | 
			
		||||
                    <th class="px-4 py-2">Files Done</th>
 | 
			
		||||
                    <th class="px-4 py-2">Status</th>
 | 
			
		||||
                    <th class="px-4 py-2">View</th>
 | 
			
		||||
                  </tr>
 | 
			
		||||
                </thead>
 | 
			
		||||
                <tbody>
 | 
			
		||||
                  {userJobs.map((job) => (
 | 
			
		||||
                    // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                      <td safe>{job.date_created}</td>
 | 
			
		||||
                      <td>{job.num_files}</td>
 | 
			
		||||
                      <td>{job.finished_files}</td>
 | 
			
		||||
                      <td safe>{job.status}</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
                        <a href={`/results/${job.id}`}>View</a>
 | 
			
		||||
                        <a
 | 
			
		||||
                          class={`
 | 
			
		||||
                            text-accent-500 underline
 | 
			
		||||
                            hover:text-accent-400
 | 
			
		||||
                          `}
 | 
			
		||||
                          href={`/results/${job.id}`}
 | 
			
		||||
                        >
 | 
			
		||||
                          View
 | 
			
		||||
                        </a>
 | 
			
		||||
                      </td>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  ))}
 | 
			
		||||
@@ -973,50 +1018,77 @@ const app = new Elysia({
 | 
			
		||||
        <BaseHtml title="ConvertX | Result">
 | 
			
		||||
          <>
 | 
			
		||||
            <Header loggedIn />
 | 
			
		||||
            <main class="container">
 | 
			
		||||
              <article>
 | 
			
		||||
                <div class="grid">
 | 
			
		||||
                  <h1>Results</h1>
 | 
			
		||||
            <main class="w-full px-4">
 | 
			
		||||
              <article class="article">
 | 
			
		||||
                <div class="mb-4 flex items-center justify-between">
 | 
			
		||||
                  <h1 class="text-xl">Results</h1>
 | 
			
		||||
                  <div>
 | 
			
		||||
                    <button
 | 
			
		||||
                      type="button"
 | 
			
		||||
                      style={{ width: "10rem", float: "right" }}
 | 
			
		||||
                      class="btn-primary float-right w-40"
 | 
			
		||||
                      onclick="downloadAll()"
 | 
			
		||||
                      {...(files.length !== job.num_files
 | 
			
		||||
                        ? { disabled: true, "aria-busy": "true" }
 | 
			
		||||
                        : "")}>
 | 
			
		||||
                        : "")}
 | 
			
		||||
                    >
 | 
			
		||||
                      {files.length === job.num_files
 | 
			
		||||
                        ? "Download All"
 | 
			
		||||
                        : "Converting..."}
 | 
			
		||||
                    </button>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <progress max={job.num_files} value={files.length} />
 | 
			
		||||
                <table>
 | 
			
		||||
                <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-neutral-700 [&::-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 [&_tr]:border-b [&_tr]:border-neutral-800
 | 
			
		||||
                  `}
 | 
			
		||||
                >
 | 
			
		||||
                  <thead>
 | 
			
		||||
                    <tr>
 | 
			
		||||
                      <th>Converted File Name</th>
 | 
			
		||||
                      <th>Status</th>
 | 
			
		||||
                      <th>View</th>
 | 
			
		||||
                      <th>Download</th>
 | 
			
		||||
                      <th class="px-4 py-2">Converted File Name</th>
 | 
			
		||||
                      <th class="px-4 py-2">Status</th>
 | 
			
		||||
                      <th class="px-4 py-2">View</th>
 | 
			
		||||
                      <th class="px-4 py-2">Download</th>
 | 
			
		||||
                    </tr>
 | 
			
		||||
                  </thead>
 | 
			
		||||
                  <tbody>
 | 
			
		||||
                    {files.map((file) => (
 | 
			
		||||
                      // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                      <tr>
 | 
			
		||||
                        <td safe>{file.output_file_name}</td>
 | 
			
		||||
                        <td safe>{file.status}</td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          <a
 | 
			
		||||
                            href={`/download/${outputPath}${file.output_file_name}`}>
 | 
			
		||||
                            class={`
 | 
			
		||||
                              text-accent-500 underline
 | 
			
		||||
                              hover:text-accent-400
 | 
			
		||||
                            `}
 | 
			
		||||
                            href={`/download/${outputPath}${file.output_file_name}`}
 | 
			
		||||
                          >
 | 
			
		||||
                            View
 | 
			
		||||
                          </a>
 | 
			
		||||
                        </td>
 | 
			
		||||
                        <td>
 | 
			
		||||
                          <a
 | 
			
		||||
                            class={`
 | 
			
		||||
                              text-accent-500 underline
 | 
			
		||||
                              hover:text-accent-400
 | 
			
		||||
                            `}
 | 
			
		||||
                            href={`/download/${outputPath}${file.output_file_name}`}
 | 
			
		||||
                            download={file.output_file_name}>
 | 
			
		||||
                            download={file.output_file_name}
 | 
			
		||||
                          >
 | 
			
		||||
                            Download
 | 
			
		||||
                          </a>
 | 
			
		||||
                        </td>
 | 
			
		||||
@@ -1069,48 +1141,76 @@ const app = new Elysia({
 | 
			
		||||
        .all(params.jobId);
 | 
			
		||||
 | 
			
		||||
      return (
 | 
			
		||||
        <article>
 | 
			
		||||
          <div class="grid">
 | 
			
		||||
            <h1>Results</h1>
 | 
			
		||||
        <article class="article">
 | 
			
		||||
          <div class="mb-4 flex items-center justify-between">
 | 
			
		||||
            <h1 class="text-xl">Results</h1>
 | 
			
		||||
            <div>
 | 
			
		||||
              <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                style={{ width: "10rem", float: "right" }}
 | 
			
		||||
                class="btn-primary float-right w-40"
 | 
			
		||||
                onclick="downloadAll()"
 | 
			
		||||
                {...(files.length !== job.num_files
 | 
			
		||||
                  ? { disabled: true, "aria-busy": "true" }
 | 
			
		||||
                  : "")}>
 | 
			
		||||
                  : "")}
 | 
			
		||||
              >
 | 
			
		||||
                {files.length === job.num_files
 | 
			
		||||
                  ? "Download All"
 | 
			
		||||
                  : "Converting..."}
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <progress max={job.num_files} value={files.length} />
 | 
			
		||||
          <table>
 | 
			
		||||
          <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-neutral-700 [&::-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 [&_tr]:border-b [&_tr]:border-neutral-800
 | 
			
		||||
            `}
 | 
			
		||||
          >
 | 
			
		||||
            <thead>
 | 
			
		||||
              <tr>
 | 
			
		||||
                <th>Converted File Name</th>
 | 
			
		||||
                <th>Status</th>
 | 
			
		||||
                <th>View</th>
 | 
			
		||||
                <th>Download</th>
 | 
			
		||||
                <th class="px-4 py-2">Converted File Name</th>
 | 
			
		||||
                <th class="px-4 py-2">Status</th>
 | 
			
		||||
                <th class="px-4 py-2">View</th>
 | 
			
		||||
                <th class="px-4 py-2">Download</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              {files.map((file) => (
 | 
			
		||||
                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td safe>{file.output_file_name}</td>
 | 
			
		||||
                  <td safe>{file.status}</td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <a href={`/download/${outputPath}${file.output_file_name}`}>
 | 
			
		||||
                    <a
 | 
			
		||||
                      class={`
 | 
			
		||||
                        text-accent-500 underline
 | 
			
		||||
                        hover:text-accent-400
 | 
			
		||||
                      `}
 | 
			
		||||
                      href={`/download/${outputPath}${file.output_file_name}`}
 | 
			
		||||
                    >
 | 
			
		||||
                      View
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </td>
 | 
			
		||||
                  <td>
 | 
			
		||||
                    <a
 | 
			
		||||
                      class={`
 | 
			
		||||
                        text-accent-500 underline
 | 
			
		||||
                        hover:text-accent-400
 | 
			
		||||
                      `}
 | 
			
		||||
                      href={`/download/${outputPath}${file.output_file_name}`}
 | 
			
		||||
                      download={file.output_file_name}>
 | 
			
		||||
                      download={file.output_file_name}
 | 
			
		||||
                    >
 | 
			
		||||
                      Download
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </td>
 | 
			
		||||
@@ -1164,15 +1264,22 @@ const app = new Elysia({
 | 
			
		||||
      <BaseHtml title="ConvertX | Converters">
 | 
			
		||||
        <>
 | 
			
		||||
          <Header loggedIn />
 | 
			
		||||
          <main class="container">
 | 
			
		||||
            <article>
 | 
			
		||||
              <h1>Converters</h1>
 | 
			
		||||
              <table>
 | 
			
		||||
          <main class="w-full 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 [&_tr]:border-b [&_tr]:border-neutral-800
 | 
			
		||||
                  [&_ul]:list-inside [&_ul]:list-disc
 | 
			
		||||
                `}
 | 
			
		||||
              >
 | 
			
		||||
                <thead>
 | 
			
		||||
                  <tr>
 | 
			
		||||
                    <th>Converter</th>
 | 
			
		||||
                    <th>From (Count)</th>
 | 
			
		||||
                    <th>To (Count)</th>
 | 
			
		||||
                    <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>
 | 
			
		||||
@@ -1180,14 +1287,12 @@ const app = new Elysia({
 | 
			
		||||
                    ([converter, targets]) => {
 | 
			
		||||
                      const inputs = getAllInputs(converter);
 | 
			
		||||
                      return (
 | 
			
		||||
                        // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                        <tr>
 | 
			
		||||
                          <td safe>{converter}</td>
 | 
			
		||||
                          <td>
 | 
			
		||||
                            Count: {inputs.length}
 | 
			
		||||
                            <ul>
 | 
			
		||||
                              {inputs.map((input) => (
 | 
			
		||||
                                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                                <li safe>{input}</li>
 | 
			
		||||
                              ))}
 | 
			
		||||
                            </ul>
 | 
			
		||||
@@ -1196,7 +1301,6 @@ const app = new Elysia({
 | 
			
		||||
                            Count: {targets.length}
 | 
			
		||||
                            <ul>
 | 
			
		||||
                              {targets.map((target) => (
 | 
			
		||||
                                // biome-ignore lint/correctness/useJsxKeyInIterable: <explanation>
 | 
			
		||||
                                <li safe>{target}</li>
 | 
			
		||||
                              ))}
 | 
			
		||||
                            </ul>
 | 
			
		||||
@@ -1244,20 +1348,34 @@ const app = new Elysia({
 | 
			
		||||
  .onError(({ error }) => {
 | 
			
		||||
    // log.error(` ${request.method} ${request.url}`, code, error);
 | 
			
		||||
    console.error(error);
 | 
			
		||||
  })
 | 
			
		||||
  .listen(3000);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
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}`,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const clearJobs = () => {
 | 
			
		||||
  // clear all jobs older than 24 hours
 | 
			
		||||
  // get all files older than 24 hours
 | 
			
		||||
  const jobs = db
 | 
			
		||||
    .query("SELECT * FROM jobs WHERE date_created < ?")
 | 
			
		||||
    .as(Jobs)
 | 
			
		||||
    .all(new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());
 | 
			
		||||
    .all(
 | 
			
		||||
      new Date(
 | 
			
		||||
        Date.now() - AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000,
 | 
			
		||||
      ).toISOString(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  for (const job of jobs) {
 | 
			
		||||
    // delete the directories
 | 
			
		||||
@@ -1268,7 +1386,9 @@ const clearJobs = () => {
 | 
			
		||||
    db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // run every 24 hours
 | 
			
		||||
  setTimeout(clearJobs, 24 * 60 * 60 * 1000);
 | 
			
		||||
  setTimeout(clearJobs, AUTO_DELETE_EVERY_N_HOURS * 60 * 60 * 1000);
 | 
			
		||||
};
 | 
			
		||||
clearJobs();
 | 
			
		||||
 | 
			
		||||
if (AUTO_DELETE_EVERY_N_HOURS > 0) {
 | 
			
		||||
  clearJobs();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										45
									
								
								src/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/main.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
 | 
			
		||||
@layer components {
 | 
			
		||||
  .article {
 | 
			
		||||
    @apply p-4 mb-4 bg-neutral-800/40 w-full mx-auto max-w-4xl rounded;
 | 
			
		||||
  }
 | 
			
		||||
  .btn-primary {
 | 
			
		||||
    @apply bg-accent-500 text-contrast rounded p-4 hover:bg-accent-400 cursor-pointer transition-colors;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
    --contrast: 255, 255, 255;
 | 
			
		||||
    --neutral-900: 243, 244, 246;
 | 
			
		||||
    --neutral-800: 229, 231, 235;
 | 
			
		||||
    --neutral-700: 209, 213, 219;
 | 
			
		||||
    --neutral-600: 156, 163, 175;
 | 
			
		||||
    --neutral-500: 180, 180, 180;
 | 
			
		||||
    --neutral-400: 75, 85, 99;
 | 
			
		||||
    --neutral-300: 55, 65, 81;
 | 
			
		||||
    --neutral-200: 31, 41, 55;
 | 
			
		||||
    --neutral-100: 17, 24, 39;
 | 
			
		||||
    --accent-400: 132, 204, 22;
 | 
			
		||||
    --accent-500: 101, 163, 13;
 | 
			
		||||
    --accent-600: 77, 124, 15;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@media (prefers-color-scheme: dark) {
 | 
			
		||||
  :root {
 | 
			
		||||
    --contrast: 0, 0, 0;
 | 
			
		||||
    --neutral-900: 17, 24, 39;
 | 
			
		||||
    --neutral-800: 31, 41, 55;
 | 
			
		||||
    --neutral-700: 55, 65, 81;
 | 
			
		||||
    --neutral-600: 75, 85, 99;
 | 
			
		||||
    --neutral-500: 107, 114, 128;
 | 
			
		||||
    --neutral-300: 209, 213, 219;
 | 
			
		||||
    --neutral-400: 156, 163, 175;
 | 
			
		||||
    --neutral-200: 229, 231, 235;
 | 
			
		||||
    --accent-600: 101, 163, 13;
 | 
			
		||||
    --accent-500: 132, 204, 22;
 | 
			
		||||
    --accent-400: 163, 230, 53;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								src/public/pico.lime.min.css
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/public/pico.lime.min.css
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,8 +1,17 @@
 | 
			
		||||
// Select the file input element
 | 
			
		||||
const fileInput = document.querySelector('input[type="file"]');
 | 
			
		||||
const dropZone = document.getElementById("dropzone");
 | 
			
		||||
const fileNames = [];
 | 
			
		||||
let fileType;
 | 
			
		||||
 | 
			
		||||
dropZone.addEventListener("dragover", () => {
 | 
			
		||||
  dropZone.classList.add("dragover");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
dropZone.addEventListener("dragleave", () => {
 | 
			
		||||
  dropZone.classList.remove("dragover");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const selectContainer = document.querySelector("form .select_container");
 | 
			
		||||
 | 
			
		||||
const updateSearchBar = () => {
 | 
			
		||||
@@ -13,6 +22,7 @@ const updateSearchBar = () => {
 | 
			
		||||
  const convertToGroupElements = document.querySelectorAll(".convert_to_group");
 | 
			
		||||
  const convertToGroups = {};
 | 
			
		||||
  const convertToElement = document.querySelector("select[name='convert_to']");
 | 
			
		||||
  const convertButton = document.querySelector("input[type='submit']");
 | 
			
		||||
 | 
			
		||||
  const showMatching = (search) => {
 | 
			
		||||
    for (const [targets, groupElement] of Object.values(convertToGroups)) {
 | 
			
		||||
@@ -20,16 +30,20 @@ const updateSearchBar = () => {
 | 
			
		||||
      for (const target of targets) {
 | 
			
		||||
        if (target.dataset.target.includes(search)) {
 | 
			
		||||
          matchingTargetsFound++;
 | 
			
		||||
          target.hidden = false;
 | 
			
		||||
          target.classList.remove("hidden");
 | 
			
		||||
          target.classList.add("flex");
 | 
			
		||||
        } else {
 | 
			
		||||
          target.hidden = true;
 | 
			
		||||
          target.classList.add("hidden");
 | 
			
		||||
          target.classList.remove("flex");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (matchingTargetsFound === 0) {
 | 
			
		||||
        groupElement.hidden = true;
 | 
			
		||||
        groupElement.classList.add("hidden");
 | 
			
		||||
        groupElement.classList.remove("flex");
 | 
			
		||||
      } else {
 | 
			
		||||
        groupElement.hidden = false;
 | 
			
		||||
        groupElement.classList.remove("hidden");
 | 
			
		||||
        groupElement.classList.add("flex");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
@@ -44,6 +58,7 @@ const updateSearchBar = () => {
 | 
			
		||||
      target.onmousedown = () => {
 | 
			
		||||
        convertToElement.value = target.dataset.value;
 | 
			
		||||
        convertToInput.value = `${target.dataset.target} using ${target.dataset.converter}`;
 | 
			
		||||
        convertButton.disabled = false;
 | 
			
		||||
        showMatching("");
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
@@ -55,19 +70,27 @@ const updateSearchBar = () => {
 | 
			
		||||
    showMatching(e.target.value.toLowerCase());
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  convertToInput.addEventListener("search", () => {
 | 
			
		||||
    // when the user clears the search bar using the 'x' button
 | 
			
		||||
    convertButton.disabled = true;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  convertToInput.addEventListener("blur", (e) => {
 | 
			
		||||
    // Keep the popup open even when clicking on a target button
 | 
			
		||||
    // for a split second to allow the click to go through
 | 
			
		||||
    if (e?.relatedTarget?.classList?.contains("target")) {
 | 
			
		||||
      convertToPopup.hidden = true;
 | 
			
		||||
      convertToPopup.classList.add("hidden");
 | 
			
		||||
      convertToPopup.classList.remove("flex");
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    convertToPopup.hidden = true;
 | 
			
		||||
    convertToPopup.classList.add("hidden");
 | 
			
		||||
    convertToPopup.classList.remove("flex");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  convertToInput.addEventListener("focus", () => {
 | 
			
		||||
    convertToPopup.hidden = false;
 | 
			
		||||
    convertToPopup.classList.remove("hidden");
 | 
			
		||||
    convertToPopup.classList.add("flex");
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -94,6 +117,7 @@ fileInput.addEventListener("change", (e) => {
 | 
			
		||||
 | 
			
		||||
    if (!fileType) {
 | 
			
		||||
      fileType = file.name.split(".").pop();
 | 
			
		||||
      console.log("fileType", fileType);
 | 
			
		||||
      fileInput.setAttribute("accept", `.${fileType}`);
 | 
			
		||||
      setTitle();
 | 
			
		||||
 | 
			
		||||
@@ -136,6 +160,7 @@ const setTitle = () => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Add a onclick for the delete button
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
const deleteRow = (target) => {
 | 
			
		||||
  const filename = target.parentElement.parentElement.children[0].textContent;
 | 
			
		||||
  const row = target.parentElement.parentElement;
 | 
			
		||||
@@ -186,7 +211,7 @@ const uploadFiles = (files) => {
 | 
			
		||||
 | 
			
		||||
const formConvert = document.querySelector("form[action='/convert']");
 | 
			
		||||
 | 
			
		||||
formConvert.addEventListener("submit", (e) => {
 | 
			
		||||
formConvert.addEventListener("submit", () => {
 | 
			
		||||
  const hiddenInput = document.querySelector("input[name='file_names']");
 | 
			
		||||
  hiddenInput.value = JSON.stringify(fileNames);
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
div.icon {
 | 
			
		||||
  height: 100px;
 | 
			
		||||
  width: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button[type="submit"] {
 | 
			
		||||
  width: 50%
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div.center {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 99999999999px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    width: 50vw !important;
 | 
			
		||||
    height: 50vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 850px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    width: 60vw !important;
 | 
			
		||||
    height: 60vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 575px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    width: 80vw !important;
 | 
			
		||||
    height: 75vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-height: 1000px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    height: 40vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@media (max-height: 650px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    height: 30vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-height: 500px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    height: 25vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-height: 400px) {
 | 
			
		||||
  .convert_to_popup {
 | 
			
		||||
    height: 15vh;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
/* eslint-disable @typescript-eslint/no-require-imports */
 | 
			
		||||
/** @type {import('tailwindcss').Config} */
 | 
			
		||||
 
 | 
			
		||||
module.exports = {
 | 
			
		||||
  content: ["./src/**/*.{html,js,tsx,jsx,cjs,mjs}"],
 | 
			
		||||
  theme: {
 | 
			
		||||
    extend: {
 | 
			
		||||
      colors: {
 | 
			
		||||
        contrast: "rgba(var(--contrast))",
 | 
			
		||||
        "neutral-900": "rgba(var(--neutral-900))",
 | 
			
		||||
        "neutral-800": "rgba(var(--neutral-800))",
 | 
			
		||||
        "neutral-700": "rgba(var(--neutral-700))",
 | 
			
		||||
        "neutral-600": "rgba(var(--neutral-600))",
 | 
			
		||||
        "neutral-500": "rgba(var(--neutral-500))",
 | 
			
		||||
        "neutral-400": "rgba(var(--neutral-400))",
 | 
			
		||||
        "neutral-300": "rgba(var(--neutral-300))",
 | 
			
		||||
        "neutral-200": "rgba(var(--neutral-200))",
 | 
			
		||||
        "neutral-100": "rgba(var(--neutral-100))",
 | 
			
		||||
        "accent-600": "rgba(var(--accent-600))",
 | 
			
		||||
        "accent-500": "rgba(var(--accent-500))",
 | 
			
		||||
        "accent-400": "rgba(var(--accent-400))",
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [require("tailwind-scrollbar")],
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user