diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..8e97178 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,105 @@ +/* eslint-env node */ + +const { defineConfig } = require('eslint-define-config'); +const prettierConfig = require('./.prettierrc.js'); + +module.exports = defineConfig({ + root: true, + env: { + browser: true, + es6: true, + node: true, + jest: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:import/recommended', + 'plugin:import/electron', + 'plugin:import/typescript', + 'prettier', + '@vue/eslint-config-typescript', + 'plugin:vue/vue3-essential', + 'plugin:vuejs-accessibility/recommended', + ], + plugins: ['prettier', 'vuejs-accessibility', '@typescript-eslint'], + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + }, + rules: { + indent: [ + 'error', + 2, + { + SwitchCase: 1, + }, + ], + 'max-len': [ + 'error', + { + code: 120, + }, + ], + 'no-console': [ + 'error', + { + allow: ['warn', 'error'], + }, + ], + 'comma-dangle': ['error', 'always-multiline'], + 'space-before-function-paren': [ + 'warn', + { + anonymous: 'ignore', + named: 'never', + asyncArrow: 'always', + }, + ], + 'prettier/prettier': [ + 'error', + { + ...prettierConfig, + }, + ], + 'vue/html-indent': ['error', 2], + 'vue/multiline-html-element-content-newline': 'off', + 'vue/multi-word-component-names': 'off', + 'vue/max-attributes-per-line': 0, + 'vue/require-default-prop': 0, + 'vue/no-multiple-template-root': 0, + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + varsIgnorePattern: '^_$', + argsIgnorePattern: '^_$', + }, + ], + }, + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + ignorePatterns: ['*.test.ts'], + overrides: [ + { + files: ['tests/**/*'], + env: { + jest: true, + }, + }, + { + files: ['*.vue'], + rules: { + 'max-len': 'off', + }, + }, + ], +}); diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 1391624..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "env": { - "browser": true, - "es6": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", - "plugin:import/recommended", - "plugin:import/electron", - "plugin:import/typescript" - ], - "parser": "@typescript-eslint/parser", - "rules": { - "indent": [ - "error", - 2, - { - "SwitchCase": 1 - } - ], - "max-len": [ - "error", - { - "code": 120 - } - ], - "no-console": [ - "error", - { - "allow": [ - "warn", - "error" - ] - } - ] - }, - "ignorePatterns": [ - "*.test.ts" - ], - "overrides": [ - { - "files": [ - "tests/**/*" - ], - "env": { - "jest": true - } - } - ] -} diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md index d2b0636..99d489f 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -6,9 +6,8 @@ about: "Report something that's broken." -- Sentinel Version: 1.0.5 -- PHP Version: 7.4.13 / 8.0.2 -- Database Driver & Version: MySQL 5.7 +- Comet Version: 2.0.4 +- TypeScript Version: 4.5.4 ### Description diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..2c84d91 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,32 @@ +name: Lint + +on: + push: + branches: + - main + - release + - feature/* + pull_request: + branches: + - main + - release + - feature/* + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '20' + + - name: Install dependencies + run: npm install + + - name: Lint app + run: npm run lint diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ded82e2 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +auto-install-peers = true diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 91a2761..0000000 --- a/.prettierrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "semi": true, - "trailingComma": "all", - "singleQuote": true, - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "endOfLine": "auto" -} diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000..e0e7a5d --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,10 @@ +module.exports = { + semi: true, + trailingComma: 'all', + singleQuote: true, + printWidth: 120, + tabWidth: 2, + useTabs: false, + endOfLine: 'auto', + spaceBeforeFunctionParen: false +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 72446f4..25fa621 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 926cb9a..f7a20da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,30 @@ # Release Notes -## [Unreleased](https://github.com/stellar-comet/comet/compare/v1.0.0...HEAD) +## [Unreleased](https://github.com/stellar-comet/comet/compare/v2.0.4...HEAD) + +## [v2.0.4](https://github.com/stellar-comet/comet/compare/v1.0.0...v2.0.4) - 2024-09-14 + +### Added + +- Dark mode +- Multi-language support +- Audio conversion support + +### Changed + +- Improved UI design with better user experience +- Improved conversion performance withy less resource usage +- Improved conversion quality with better audio quality + +### Fixed + +- Non-persistent state issue +- Conversion issue with some video files on some platforms and architectures +- Conversion quality issue with some video files +- High resource usage issue +- Removed files would still be included in conversion queue + +**Full Changelog**: https://github.com/stellar-comet/comet/compare/v1.0.0...v2.0.4 ## [v1.0.0](https://github.com/stellar-comet/comet/compare/v0.0.3...v1.0.0) - 2024-09-02 diff --git a/README.md b/README.md index f5bfb7d..57fad2b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Comet

+

Comet

Test Status @@ -8,54 +8,69 @@

> [!WARNING] -> We don't have an Apple Developer account yet, and the application is not code-signed for both Mac and Windows. Therefore, the applications will show a warning popup on the first start. On Mac, click **Okay**, then go to **Settings / Privacy & Security** and scroll down until you see a button **Open anyway**. You'll have to do this once. On Windows, you may see a warning message indicating that the app is from an unknown publisher. Click **More info** and then **Run anyway** to proceed. +> **We don't have an Apple Developer account yet, and the application is not code-signed for both Mac and Windows. Therefore, the applications will show a warning popup on the first start.** +> On **Mac**, click **Okay**, then go to **Settings / Privacy & Security** and scroll down until you see a button **Open anyway**. +> On **Windows**, you may see a warning message indicating that the app is from an unknown publisher. Click **More info** and then **Run anyway** to proceed. + +--- ## About Comet -**Comet** is a cross-platform video and audio converter application designed to make media conversion as easy and accessible as possible. Leveraging the power of [FFmpeg](https://ffmpeg.org/), Comet allows users to convert video and audio files into a variety of formats, all within a simple, intuitive interface. +**Comet** is a cross-platform media converter application designed to make the conversion of video, audio, and image files as easy and accessible as possible. Built on top of [FFmpeg](https://ffmpeg.org/), **Jimp**, and **Electron**, Comet offers a seamless and efficient user experience for media conversions, all within a simple, intuitive interface. ![Comet UI 1](./assets/screenshot_1.png) ![Comet UI 2](./assets/screenshot_2.png) ![Comet UI 3](./assets/screenshot_3.png) ![Comet UI 4](./assets/screenshot_4.png) +--- + ## Project Overview -Comet's goal is to provide a free, user-friendly, and visually appealing application for converting video and audio files. Whether you need to convert a single file or multiple files at once, Comet is here to help. +Comet's goal is to provide a **free, user-friendly, and visually appealing** application for converting media files. Whether you need to convert a single file or multiple files at once, Comet is designed to make the process straightforward. ### Key Features -- **Cross-Platform Compatibility:** Runs on macOS, Windows, and Linux. -- **Video and Audio Conversion:** Supports conversion to a wide range of video formats (MP4, MKV, AVI, MOV, etc.) and audio formats (MP3, WAV, AAC, FLAC, etc.). +- **Cross-Platform Compatibility:** Works seamlessly on macOS, Windows, and Linux. +- **Video, Audio, and Image Conversion:** + - Video formats: MP4, MKV, AVI, MOV, etc. + - Audio formats: MP3, WAV, AAC, FLAC, etc. + - Image formats: JPEG, PNG, BMP, ICO, ICNS, etc. - **Bulk File Conversion:** Easily upload and convert multiple files in one go. -- **User-Friendly Interface:** A clean and intuitive UI that simplifies the conversion process. -- **Real-Time Conversion Feedback:** Conversion progress is tracked and displayed to the user, with real-time updates and error handling. -- **Fast Conversion:** Powered by FFmpeg, known for its speed and efficiency in media processing. -- **Dark Mode:** Provides a dark theme for users who prefer a more comfortable visual experience. +- **Real-Time Progress Tracking:** Track the progress of each conversion with real-time feedback. +- **Dark Mode:** A sleek dark theme for more comfortable use. - **Multi-Language Support:** Available in multiple languages for a global audience. +- **Cancel/Resume Conversions:** Cancel ongoing conversions, with options to manage individual items in the queue. +- **Jimp and FFmpeg Integration:** Use Jimp for images and FFmpeg for audio/video conversion. + +--- -### Current Status +## Recent Updates -The project has made significant progress: +- **Unified Conversion Handler:** We’ve refactored the media conversion process to use an **Adapter Pattern**, allowing for a unified conversion handler for video, audio, and image files using either **FFmpeg** or **Jimp** depending on the file type. +- **Advanced Testing:** Expanded unit tests using **Jest** to cover media conversion handlers and IPC processes. +- **Improved Performance:** Faster conversions through optimizations to how we handle bulk file uploads and media processing queues. +- **Enhanced UI and UX:** More responsive interface and clearer progress tracking for large file batches. -- **Basic UI and Core Functionality:** The user interface is fully implemented, supporting bulk file uploads, conversion status tracking, and real-time feedback. -- **FFmpeg Integration:** The core video and audio conversion functionality is complete, making the app fully functional. -- **Cross-Platform Distributables:** Distributables for macOS, Windows, and Linux have been successfully created. However, due to the high cost of an Apple Developer membership, the app is currently not code-signed or notarized for macOS, which may present challenges when running it on Mac devices. +--- ## Technologies Used - **Electron:** For building the cross-platform desktop application. - **Vue.js (with Composition API and TypeScript):** For the frontend UI. -- **FFmpeg:** The core engine for video and audio format conversion. -- **Node.js:** Backend services and script automation. -- **Vite:** For fast and modern build tooling. +- **FFmpeg & Jimp:** Core engines for media conversion (video/audio via FFmpeg, images via Jimp). +- **Node.js:** Backend services and media processing. +- **Vite:** Modern build tool for fast development. - **Tailwind CSS:** For styling and responsive design. +- **i18n:** Internationalization for multi-language support. - **Jest:** For unit testing. -- **GitHub Actions:** For CI/CD and release automation. +- **GitHub Actions:** Continuous Integration/Continuous Deployment (CI/CD) and release automation. + +--- -### Getting Started +## Getting Started -To get started with development: +To get started with Comet: 1. **Clone the repository:** @@ -83,59 +98,53 @@ To get started with development: ``` 5. **Test your changes:** - - Ensure your changes do not break existing functionality. - - Write unit tests if possible and applicable. -## Roadmap + ```bash + npm run test + ``` -- **Phase 1:** Basic UI Implementation (Completed) - - File upload functionality. - - Simple file selection and list management. +--- -- **Phase 2:** FFmpeg Integration (Completed) - - Implement core conversion functionality. - - Support for multiple video formats. - - Real-time conversion feedback. +## Roadmap -- **Phase 3:** UI Enhancements and Customization Options (Completed) - - Output format selection and settings. - - Batch processing capabilities. +- **Phase 1:** Basic UI Implementation (Completed) + - File upload functionality, file list management. -- **Phase 4:** Cross-Platform Testing and Release (In Progress) - - Test and refine the app on macOS, Windows, and Linux. - - **Package the app for distribution** on various platforms. - - **Overcome code signing challenges**, particularly on macOS due to the cost of an Apple Developer membership. - - Prepare for the first public release. +- **Phase 2:** FFmpeg & Jimp Integration (Completed) + - Core conversion functionality for video, audio, and images. -## Support +- **Phase 3:** UI Enhancements (Completed) + - Improved conversion progress tracking and batch conversion options. -If you find this project helpful or interesting, please consider giving it a ⭐. Your support and feedback are greatly appreciated! +- **Phase 4:** Cross-Platform Testing and Releases (In Progress) + - Testing on macOS, Windows, and Linux. + - Package the app for distribution across platforms. + - Address macOS code-signing challenges (currently blocked by lack of Apple Developer account). -## Contributors +--- -- **Jerome Thayananthajothy** - Project Lead & Developer -- [**Contributors List**](https://github.com/stellar-comet/comet/graphs/contributors) - A big thank you to all the amazing contributors! +## Contributing -## How to Contribute +Contributions are welcome! Here’s how you can contribute: -We welcome contributions and collaboration! Whether you're a seasoned developer or just starting out, there's a place for you in our project. Here's how you can help: +1. **Fork the Repository:** Create a fork of this repository. +2. **Clone the Repo:** Clone the forked repository to your local machine. +3. **Create a Branch:** Create a new branch for your feature or fix. +4. **Make Your Changes:** Ensure your changes work as expected and pass tests. +5. **Submit a Pull Request:** Once done, submit a PR to the `main` branch. +6. **Star the Repo:** If you like Comet, please give it a ⭐! -1. **Fork the Repository:** Start by forking this repository to your GitHub account. -2. **Clone the Repo:** Clone the forked repo to your local machine using `git clone`. -3. **Set Up the Environment:** Follow the steps below to get the project running on your local machine. -4. **Create a Branch:** Create a new branch for the feature or fix you plan to work on. -5. **Submit a Pull Request:** Once your changes are ready, submit a pull request to the `main` branch of this repository. -6. **Star the Repository:** If you like this project, please give it a star on GitHub. It helps us gain visibility and grow our community! +--- ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -## Acknowledgements - -- [FFmpeg](https://ffmpeg.org/) - The powerful multimedia framework that makes this project possible. -- The Electron and Vue.js communities for their fantastic tools and support. - --- -Thank you for visiting our project! We’re excited to bring this tool to life and make media conversion easy and accessible for everyone. Any help, be it in coding, testing, or simply providing feedback, is invaluable. Let's create something great together! +## Acknowledgements + +- [FFmpeg](https://ffmpeg.org/) - The powerful multimedia framework that powers video/audio conversion in Comet. +- [Jimp](https://github.com/oliver-moran/jimp) - Image processing in Comet. +- [Electron](https://www.electronjs.org/) - For building cross-platform desktop apps. +- The [Vue.js](https://vuejs.org/) and [Tailwind CSS](https://tailwindcss.com/) communities for their fantastic tools and support. diff --git a/assets/Banner.jpg b/assets/Banner.jpg index 4cc8577..197e1b2 100644 Binary files a/assets/Banner.jpg and b/assets/Banner.jpg differ diff --git a/assets/Profile.png b/assets/Profile.png index 7d22400..31ed565 100644 Binary files a/assets/Profile.png and b/assets/Profile.png differ diff --git a/assets/icon-1024.png b/assets/icon-1024.png new file mode 100644 index 0000000..ff438ac Binary files /dev/null and b/assets/icon-1024.png differ diff --git a/assets/icon-256.png b/assets/icon-256.png index 4e58478..8a9613d 100644 Binary files a/assets/icon-256.png and b/assets/icon-256.png differ diff --git a/assets/icon-512.png b/assets/icon-512.png index 7c46aae..3abd76c 100644 Binary files a/assets/icon-512.png and b/assets/icon-512.png differ diff --git a/assets/icon-scaffold.png b/assets/icon-scaffold.png deleted file mode 100644 index 93e00ae..0000000 Binary files a/assets/icon-scaffold.png and /dev/null differ diff --git a/assets/icons/Win-Icon.ico b/assets/icons/Win-Icon.ico new file mode 100644 index 0000000..afb3847 Binary files /dev/null and b/assets/icons/Win-Icon.ico differ diff --git a/assets/icons/icon-256.png b/assets/icons/icon-256.png new file mode 100644 index 0000000..326ee7e Binary files /dev/null and b/assets/icons/icon-256.png differ diff --git a/assets/icons/icon.icns b/assets/icons/icon.icns index b298c69..b4fff93 100644 Binary files a/assets/icons/icon.icns and b/assets/icons/icon.icns differ diff --git a/assets/icons/icon.ico b/assets/icons/icon.ico deleted file mode 100644 index 9fbb25d..0000000 Binary files a/assets/icons/icon.ico and /dev/null differ diff --git a/assets/icons/icon.png b/assets/icons/icon.png index 9eaec08..41d48c2 100644 Binary files a/assets/icons/icon.png and b/assets/icons/icon.png differ diff --git a/assets/icons/icon@2x.icns b/assets/icons/icon@2x.icns index 2a17fe4..5a4ecaa 100644 Binary files a/assets/icons/icon@2x.icns and b/assets/icons/icon@2x.icns differ diff --git a/assets/icons/setup-icon.ico b/assets/icons/setup-icon.ico index 5847645..13991ba 100644 Binary files a/assets/icons/setup-icon.ico and b/assets/icons/setup-icon.ico differ diff --git a/assets/loading.gif b/assets/loading.gif index b361140..f7d180a 100644 Binary files a/assets/loading.gif and b/assets/loading.gif differ diff --git a/assets/logo.png b/assets/logo.png deleted file mode 100644 index 6ff4957..0000000 Binary files a/assets/logo.png and /dev/null differ diff --git a/assets/screenshot_1.png b/assets/screenshot_1.png index 278a9d0..d6a9bb8 100644 Binary files a/assets/screenshot_1.png and b/assets/screenshot_1.png differ diff --git a/assets/screenshot_2.png b/assets/screenshot_2.png index 7e1d052..c777fdf 100644 Binary files a/assets/screenshot_2.png and b/assets/screenshot_2.png differ diff --git a/assets/screenshot_3.png b/assets/screenshot_3.png index 5d98aec..c0bad7c 100644 Binary files a/assets/screenshot_3.png and b/assets/screenshot_3.png differ diff --git a/assets/screenshot_4.png b/assets/screenshot_4.png index 6930a2b..1692056 100644 Binary files a/assets/screenshot_4.png and b/assets/screenshot_4.png differ diff --git a/assets/screenshot_5.png b/assets/screenshot_5.png index b055a6f..db0c959 100644 Binary files a/assets/screenshot_5.png and b/assets/screenshot_5.png differ diff --git a/assets/screenshot_6.png b/assets/screenshot_6.png index 267b6c4..d162ea8 100644 Binary files a/assets/screenshot_6.png and b/assets/screenshot_6.png differ diff --git a/bin/converter.sh b/bin/converter.sh index 07c2541..21173dc 100755 --- a/bin/converter.sh +++ b/bin/converter.sh @@ -1,19 +1,78 @@ #!/bin/bash +# This script converts all .m4v files in a directory to .mp4 files using ffmpeg. + # Directory containing the .m4v files DIRECTORY="./input" +# Exit immediately if a command exits with a non-zero status +set -e + +# Function to install ffmpeg +install_ffmpeg() { + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + if ! command -v brew &>/dev/null; then + echo "Homebrew is not installed. Please install Homebrew first." + exit 1 + fi + echo "Installing ffmpeg using Homebrew..." + brew install ffmpeg + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + if command -v apt-get &>/dev/null; then + echo "Installing ffmpeg using apt-get..." + sudo apt-get update + sudo apt-get install -y ffmpeg + else + echo "Unsupported Linux distribution. Please install ffmpeg manually." + exit 1 + fi + else + echo "Unsupported OS. Please install ffmpeg manually." + exit 1 + fi +} + +# Check if ffmpeg is installed +if ! command -v ffmpeg &>/dev/null; then + echo "ffmpeg could not be found. Attempting to install ffmpeg..." + install_ffmpeg + if ! command -v ffmpeg &>/dev/null; then + echo "ffmpeg installation failed. Please install ffmpeg manually." + exit 1 + fi +fi + +# Check if the directory exists +if [ ! -d "$DIRECTORY" ]; then + echo "Directory $DIRECTORY does not exist." + exit 1 +fi + +# Check if there are any .m4v files in the directory +shopt -s nullglob +m4v_files=("$DIRECTORY"/*.m4v) +if [ ${#m4v_files[@]} -eq 0 ]; then + echo "No .m4v files found in $DIRECTORY." + exit 1 +fi + # Iterate over all .m4v files in the directory -for FILE in "$DIRECTORY"/*.m4v; do +for FILE in "${m4v_files[@]}"; do # Extract the filename without extension BASENAME=$(basename "$FILE" .m4v) # Convert the .m4v file to .mp4 using ffmpeg + echo "Converting $FILE to $DIRECTORY/${BASENAME}.mp4..." ffmpeg -i "$FILE" -c copy "$DIRECTORY/${BASENAME}.mp4" # Optional: remove the original .m4v file if conversion is successful if [ $? -eq 0 ]; then - rm "$FILE" + echo "Conversion successful, removing $FILE..." + rm -v "$FILE" + else + echo "Conversion failed for $FILE." fi done diff --git a/forge.config.ts b/forge.config.ts index 069d27b..dd5248c 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -33,12 +33,12 @@ const config: ForgeConfig = { appBundleId: 'com.thavarshan.comet', appCategoryType: 'public.app-category.video', asar: { - unpack: "**/node_modules/{ffmpeg-static,ffprobe-static}/**", + unpack: '**/node_modules/{ffmpeg-static,ffprobe-static}/**', }, win32metadata: { CompanyName: author.name, OriginalFilename: productName, - } + }, }, rebuildConfig: {}, makers: [ @@ -50,11 +50,11 @@ const config: ForgeConfig = { authors: author.name, exe: `${productName}.exe`, iconUrl: 'https://github.com/stellar-comet/comet/blob/main/assets/icons/icon.ico', - loadingGif: path.resolve(__dirname, 'assets/loading.gif'), + loadingGif: path.resolve(__dirname, 'assets', 'loading.gif'), noMsi: true, setupExe: `${name}-${version}-${arch}-setup.exe`, setupIcon: path.resolve(iconDir, 'setup-icon.ico'), - certificateFile: path.resolve(__dirname, 'tools/certs/dev-cert.pfx'), + certificateFile: path.resolve(__dirname, 'tools', 'certs', 'dev-cert.pfx'), certificatePassword: process.env.CERT_PASSWORD, }), }, @@ -91,7 +91,7 @@ const config: ForgeConfig = { name: '@electron-forge/maker-rpm', platforms: ['linux'], config: commonLinuxConfig, - } + }, ], plugins: [ new VitePlugin({ @@ -143,10 +143,10 @@ const config: ForgeConfig = { prerelease: false, draft: true, force: true, - generateReleaseNotes: true - } - } - ] + generateReleaseNotes: true, + }, + }, + ], }; export default config; diff --git a/forge.env.d.ts b/forge.env.d.ts index b6ec6ca..d8d42de 100644 --- a/forge.env.d.ts +++ b/forge.env.d.ts @@ -1,4 +1,10 @@ -export { }; // Make this a module +import { Media } from '@/types/media'; +import { ColorMode } from '@/types/theme'; +import { VideoFormat } from '@/enum/video-format'; +import { AudioFormat } from '@/enum/audio-format'; +import { ImageFormat } from '@/enum/image-format'; + +export {}; // Make this a module declare global { // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite @@ -26,11 +32,19 @@ declare global { arch: string; platform: NodeJS.Platform; selectDirectory: () => Promise; - getDesktopPath: () => string; + getSystemTheme: () => ColorMode; + getDesktopPath: () => Promise; getFilePath: (file: File) => string; cancelConversion: () => Promise; cancelItemConversion: (id: number | string) => Promise; - convertVideo: (id: string, filePath: string, outputFormat: string, saveDirectory: string) => Promise; + convertMedia: ( + id: string, + filePath: string, + outputFormat: VideoFormat | AudioFormat | ImageFormat, + saveDirectory: string, + mediaType: Media, + ) => Promise; + send: (channel: string, ...args: unknown[]) => void; on: (channel: string, callback: (event: Electron.IpcRendererEvent, ...args: unknown[]) => void) => void; removeAllListeners: (channel: string) => void; }; diff --git a/index.html b/index.html index 9ecabe3..ec1671c 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/jest.config.ts b/jest.config.ts index 7dcf140..d57fe1d 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,45 +14,28 @@ const config: Config = { collectCoverageFrom: ['src/**/*.{ts,tsx}', '!**/*.d.ts', '!**/*constants.ts'], coveragePathIgnorePatterns: ['/node_modules/', '/tests/.*\\.(ts|js)$'], coverageReporters: ['json', 'html', 'lcov'], - moduleFileExtensions: [ - "js", - "mjs", - "cjs", - "jsx", - "ts", - "tsx", - "json", - "node", - "vue" - ], + moduleFileExtensions: ['js', 'mjs', 'cjs', 'jsx', 'ts', 'tsx', 'json', 'node', 'vue'], moduleNameMapper: { - "^@/(.*)$": "/src/$1", - "^@vue/test-utils": "/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js", - "radix-vue": "/node_modules/radix-vue/dist/radix-vue.cjs.js", + '^@/(.*)$': '/src/$1', + '^@vue/test-utils': '/node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js', + 'radix-vue': '/node_modules/radix-vue/dist/radix-vue.cjs.js', '\\.(css|less|scss|sass)$': 'identity-obj-proxy', - "^.+\\.(svg|png|jpg|jpeg|gif)$": "jest-transform-stub", - + '^.+\\.(svg|png|jpg|jpeg|gif)$': 'jest-transform-stub', }, resetMocks: false, resetModules: true, setupFilesAfterEnv: ['/tests/setup.ts'], - testEnvironment: "jsdom", + testEnvironment: 'jsdom', testEnvironmentOptions: { - customExportConditions: ["node", "node-addons"] + customExportConditions: ['node', 'node-addons'], }, - testMatch: [ - "**/__tests__/**/*.[jt]s?(x)", - "**/tests/**/?(*.)+(spec|test).[tj]s?(x)" - ], + testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/tests/**/?(*.)+(spec|test).[tj]s?(x)'], testPathIgnorePatterns: ['/.tmp/'], transform: { - "^.+\\.vue$": "@vue/vue3-jest", - "^.+\\.ts?$": "ts-jest" + '^.+\\.vue$': '@vue/vue3-jest', + '^.+\\.ts?$': 'ts-jest', }, - transformIgnorePatterns: [ - "/node_modules/", - "\\.pnp\\.[^\\/]+$" - ], + transformIgnorePatterns: ['/node_modules/', '\\.pnp\\.[^\\/]+$'], }; export default config; diff --git a/package-lock.json b/package-lock.json index 6eaab8c..7754da2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "comet", - "version": "2.0.4", + "version": "2.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "comet", - "version": "2.0.4", + "version": "2.1.1", "license": "MIT", "dependencies": { "@intlify/message-compiler": "^10.0.1", @@ -18,6 +18,7 @@ "ffprobe-static": "^3.1.0", "filesize": "^10.1.4", "fluent-ffmpeg": "^2.1.3", + "jimp": "^1.6.0", "lucide-vue-next": "^0.435.0", "mobx": "^6.13.2", "namor": "^2.0.2", @@ -46,6 +47,7 @@ "@intlify/vue-i18n-loader": "^4.2.0", "@jest/globals": "^29.7.0", "@reforged/maker-appimage": "^3.3.0", + "@rushstack/eslint-patch": "^1.5.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.8", "@testing-library/user-event": "^14.5.2", @@ -58,22 +60,31 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-vue": "^5.1.2", + "@vue/eslint-config-typescript": "^12.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^29.2.6", "autoprefixer": "^10.4.20", "electron": "32.0.1", "electron-devtools-installer": "^3.2.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.0.0", + "eslint-define-config": "^2.1.0", + "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-vue": "^9.18.0", + "eslint-plugin-vuejs-accessibility": "^2.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.45", + "prettier": "^3.3.3", "tailwindcss": "^3.4.10", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "~4.5.4", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vue-eslint-parser": "^9.3.2" }, "engines": { "node": ">= 20.10.0" @@ -3762,6 +3773,430 @@ "node": ">=8" } }, + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", + "license": "MIT", + "dependencies": { + "@jimp/file-ops": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^16.0.0", + "mime": "3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/core/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", + "license": "MIT", + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/types": "1.6.0", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -3868,6 +4303,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -4192,6 +4637,19 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@reforged/maker-appimage": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/@reforged/maker-appimage/-/maker-appimage-3.3.2.tgz", @@ -4490,6 +4948,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "dev": true, + "license": "MIT" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -4977,6 +5442,12 @@ "node": ">=8" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -5606,38 +6077,300 @@ "@vue/shared": "3.5.2" } }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.2.tgz", - "integrity": "sha512-vErEtybSU290LbMW+ChYllI9tNJEdTW1oU+8cZWINZyjlWeTSa9YqDl4/pZJSnozOI+HmcaC1Vz2eFKmXNSXZA==", + "node_modules/@vue/compiler-sfc": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.2.tgz", + "integrity": "sha512-vErEtybSU290LbMW+ChYllI9tNJEdTW1oU+8cZWINZyjlWeTSa9YqDl4/pZJSnozOI+HmcaC1Vz2eFKmXNSXZA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.2", + "@vue/compiler-dom": "3.5.2", + "@vue/compiler-ssr": "3.5.2", + "@vue/shared": "3.5.2", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.11", + "postcss": "^8.4.44", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.2.tgz", + "integrity": "sha512-vMtA4tQK/AM3UAYJsmouQzQpgG+h9TKiD5BV+Zt+ZyAMdicxzSEEFGWf/CykRnDpqj9fMfIHPhOezJVNxiXe2A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.2", + "@vue/shared": "3.5.2" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/eslint-config-typescript": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz", + "integrity": "sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "^6.7.0", + "@typescript-eslint/parser": "^6.7.0", + "vue-eslint-parser": "^9.3.1" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0", + "eslint-plugin-vue": "^9.0.0", + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vue/eslint-config-typescript/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.2", - "@vue/compiler-dom": "3.5.2", - "@vue/compiler-ssr": "3.5.2", - "@vue/shared": "3.5.2", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.44", - "source-map-js": "^1.2.0" + "balanced-match": "^1.0.0" } }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.2.tgz", - "integrity": "sha512-vMtA4tQK/AM3UAYJsmouQzQpgG+h9TKiD5BV+Zt+ZyAMdicxzSEEFGWf/CykRnDpqj9fMfIHPhOezJVNxiXe2A==", - "license": "MIT", + "node_modules/@vue/eslint-config-typescript/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", "dependencies": { - "@vue/compiler-dom": "3.5.2", - "@vue/shared": "3.5.2" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT" + "node_modules/@vue/eslint-config-typescript/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/@vue/reactivity": { "version": "3.5.2", @@ -6013,6 +6746,12 @@ "node": ">=4" } }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -6454,6 +7193,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/b4a": { "version": "1.6.6", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", @@ -6730,6 +7478,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "license": "MIT" }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -6772,6 +7526,13 @@ "dev": true, "license": "MIT" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -9417,6 +10178,20 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -9786,6 +10561,41 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-define-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-define-config/-/eslint-define-config-2.1.0.tgz", + "integrity": "sha512-QUp6pM9pjKEVannNAbSJNeRuYwW3LshejfyBBpjeMGaJjaDUpVps4C6KVR8R7dWZnD3i0synmrE36znjTkJvdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/Shinigami92" + }, + { + "type": "paypal", + "url": "https://www.paypal.com/donate/?hosted_button_id=L7GY729FBKTZY" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0", + "pnpm": ">=8.6.0" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", @@ -9808,6 +10618,42 @@ "ms": "^2.1.1" } }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz", + "integrity": "sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.5", + "enhanced-resolve": "^5.15.0", + "eslint-module-utils": "^2.8.1", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, "node_modules/eslint-module-utils": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.9.0.tgz", @@ -9892,6 +10738,127 @@ "node": ">=0.10.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.28.0.tgz", + "integrity": "sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-vue/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-vue/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-vuejs-accessibility": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vuejs-accessibility/-/eslint-plugin-vuejs-accessibility-2.4.1.tgz", + "integrity": "sha512-ZRZhPdslplZXSF71MtSG+zXYRAT5KiHR4JVuo/DERQf9noAkDvi5W418VOE1qllmJd7wTenndxi1q8XeDMxdHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.0", + "emoji-regex": "^10.0.0", + "vue-eslint-parser": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vuejs-accessibility/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -10286,6 +11253,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, "node_modules/exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -10451,6 +11423,13 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -10600,6 +11579,23 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -11207,6 +12203,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, "node_modules/giget": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", @@ -11745,6 +12764,21 @@ "node": ">= 4" } }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -12028,16 +13062,39 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.2.1.tgz", + "integrity": "sha512-AmidtEM6D6NmUiLOvvU7+IePxjEjOzra2h0pSrsfSAcXwl/83zLLXDByafUJy9k/rKK0pvXMLdwKwGHlX2Ke6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, "node_modules/is-callable": { @@ -14395,6 +15452,44 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/diff": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-gif": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-blur": "1.6.0", + "@jimp/plugin-circle": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-contain": "1.6.0", + "@jimp/plugin-cover": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-displace": "1.6.0", + "@jimp/plugin-dither": "1.6.0", + "@jimp/plugin-fisheye": "1.6.0", + "@jimp/plugin-flip": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/plugin-mask": "1.6.0", + "@jimp/plugin-print": "1.6.0", + "@jimp/plugin-quantize": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/plugin-rotate": "1.6.0", + "@jimp/plugin-threshold": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -14404,6 +15499,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, "node_modules/js-beautify": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", @@ -16135,6 +17236,19 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/nwsapi": { "version": "2.2.12", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", @@ -16430,6 +17544,12 @@ "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", "license": "MIT" }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -16733,7 +17853,6 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { @@ -16762,6 +17881,28 @@ "node": ">=0.10.0" } }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, "node_modules/parse-cache-control": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", @@ -16923,6 +18064,19 @@ "url": "https://github.com/sponsors/jet2jet" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -17048,6 +18202,27 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -17153,6 +18328,15 @@ "node": ">=10.4.0" } }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -17354,6 +18538,35 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -17819,6 +19032,36 @@ "ieee754": "^1.2.1" } }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdir-glob": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", @@ -18034,6 +19277,16 @@ "npm": ">=2" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", @@ -18324,6 +19577,12 @@ "dev": true, "license": "MIT" }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -18544,6 +19803,15 @@ "dev": true, "license": "ISC" }, + "node_modules/simple-xml-to-json": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", + "integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==", + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -19049,6 +20317,23 @@ "node": ">=0.10.0" } }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -19175,6 +20460,23 @@ "dev": true, "license": "MIT" }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tailwind-merge": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz", @@ -19231,6 +20533,16 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -19386,6 +20698,12 @@ "license": "MIT", "optional": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -19460,6 +20778,23 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tough-cookie": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", @@ -19512,6 +20847,19 @@ "node": ">=0.10.0" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -20323,6 +21671,15 @@ "which": "bin/which" } }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -20483,6 +21840,71 @@ "dev": true, "license": "MIT" }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-eslint-parser/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/vue-i18n": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.0.tgz", @@ -20907,6 +22329,34 @@ "node": ">=12" } }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlbuilder": { "version": "15.1.1", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", @@ -21223,6 +22673,15 @@ "engines": { "node": ">= 14" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 85bc4f1..4749ce8 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,14 @@ "name": "comet", "productName": "Comet", "private": true, - "version": "2.0.4", - "description": "A simple video converter", + "version": "2.1.2", + "description": "A simple media converter", "keywords": [ "comet", "video", + "audio", + "image", + "media", "converter", "ffmpeg" ], @@ -34,7 +37,8 @@ "make": "electron-forge make --verbose", "publish": "electron-forge publish", "test": "jest", - "lint": "eslint --ext .ts,.tsx ." + "lint": "eslint . --ext .ts,.vue --ignore-path .gitignore", + "lint:fix": "npm run lint -- --fix" }, "dependencies": { "@intlify/message-compiler": "^10.0.1", @@ -46,6 +50,7 @@ "ffprobe-static": "^3.1.0", "filesize": "^10.1.4", "fluent-ffmpeg": "^2.1.3", + "jimp": "^1.6.0", "lucide-vue-next": "^0.435.0", "mobx": "^6.13.2", "namor": "^2.0.2", @@ -74,6 +79,7 @@ "@intlify/vue-i18n-loader": "^4.2.0", "@jest/globals": "^29.7.0", "@reforged/maker-appimage": "^3.3.0", + "@rushstack/eslint-patch": "^1.5.1", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.8", "@testing-library/user-event": "^14.5.2", @@ -86,22 +92,31 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-vue": "^5.1.2", + "@vue/eslint-config-typescript": "^12.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^29.2.6", "autoprefixer": "^10.4.20", "electron": "32.0.1", "electron-devtools-installer": "^3.2.0", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.0.0", + "eslint-define-config": "^2.1.0", + "eslint-import-resolver-typescript": "^3.6.3", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-vue": "^9.18.0", + "eslint-plugin-vuejs-accessibility": "^2.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "postcss": "^8.4.45", + "prettier": "^3.3.3", "tailwindcss": "^3.4.10", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "~4.5.4", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vue-eslint-parser": "^9.3.2" }, "engines": { "node": ">= 20.10.0" diff --git a/src/consts/app.ts b/src/consts/app.ts new file mode 100644 index 0000000..d73b3f5 --- /dev/null +++ b/src/consts/app.ts @@ -0,0 +1 @@ +export const APP_NAME = 'Comet'; diff --git a/src/consts/formats.ts b/src/consts/formats.ts index cc8bb51..ae07829 100644 --- a/src/consts/formats.ts +++ b/src/consts/formats.ts @@ -1,5 +1,6 @@ -import { AudioFormat } from '@/enum/audio-format'; import { VideoFormat } from '@/enum/video-format'; +import { AudioFormat } from '@/enum/audio-format'; +import { ImageFormat } from '@/enum/image-format'; export const VIDEO_CONVERSION_FORMATS = [ VideoFormat.MP4, @@ -24,7 +25,7 @@ export const VIDEO_CONVERSION_FORMATS = [ VideoFormat.MTS, VideoFormat.OGV, VideoFormat.RM, - VideoFormat.SWF + VideoFormat.SWF, ]; export const AUDIO_CONVERSION_FORMATS = [ @@ -42,5 +43,14 @@ export const AUDIO_CONVERSION_FORMATS = [ AudioFormat.AC3, AudioFormat.APE, AudioFormat.MPC, - AudioFormat.OPUS + AudioFormat.OPUS, +]; + +export const IMAGE_CONVERSION_FORMATS = [ + ImageFormat.JPG, + ImageFormat.PNG, + ImageFormat.BMP, + ImageFormat.GIF, + ImageFormat.TIFF, + ImageFormat.JPEG, ]; diff --git a/src/enum/arch.ts b/src/enum/arch.ts index a75245d..4b1106f 100644 --- a/src/enum/arch.ts +++ b/src/enum/arch.ts @@ -3,5 +3,5 @@ export enum Architecture { X64 = 'x64', ARM = 'arm', ARM64 = 'arm64', - UNKNOWN = 'unknown' + UNKNOWN = 'unknown', } diff --git a/src/enum/image-format.ts b/src/enum/image-format.ts new file mode 100644 index 0000000..cae27a2 --- /dev/null +++ b/src/enum/image-format.ts @@ -0,0 +1,8 @@ +export enum ImageFormat { + JPG = 'jpg', + PNG = 'png', + BMP = 'bmp', + GIF = 'gif', + TIFF = 'tiff', + JPEG = 'jpeg', +} diff --git a/src/enum/ipc-event.ts b/src/enum/ipc-event.ts index 158d236..42d6d2d 100644 --- a/src/enum/ipc-event.ts +++ b/src/enum/ipc-event.ts @@ -1,7 +1,7 @@ export enum IpcEvent { CANCEL_CONVERSION = 'cancel-conversion', CANCEL_ITEM_CONVERSION = 'cancel-item-conversion', - CONVERT_VIDEO = 'convert-video', + CONVERT_MEDIA = 'convert-media', DIALOG_SELECT_DIRECTORY = 'dialog:select-directory', GET_DESKTOP_PATH = 'get-desktop-path', SHOW_WINDOW = 'show-window', @@ -12,12 +12,14 @@ export enum IpcEvent { GET_PROJECT_NAME = 'get-project-name', LOAD_GIST_REQUEST = 'load-gist-request', LOAD_ELECTRON_EXAMPLE_REQUEST = 'load-electron-example-request', + NATIVE_THEME_UPDATED = 'native-theme-updated', + GET_SYSTEM_THEME = 'get-system-theme', } export const ipcMainEvents = [ IpcEvent.CANCEL_CONVERSION, IpcEvent.CANCEL_ITEM_CONVERSION, - IpcEvent.CONVERT_VIDEO, + IpcEvent.CONVERT_MEDIA, IpcEvent.DIALOG_SELECT_DIRECTORY, IpcEvent.GET_DESKTOP_PATH, IpcEvent.SHOW_WINDOW, @@ -28,5 +30,6 @@ export const ipcMainEvents = [ IpcEvent.GET_PROJECT_NAME, IpcEvent.LOAD_GIST_REQUEST, IpcEvent.LOAD_ELECTRON_EXAMPLE_REQUEST, + IpcEvent.NATIVE_THEME_UPDATED, + IpcEvent.GET_SYSTEM_THEME, ]; - diff --git a/src/enum/media.ts b/src/enum/media.ts new file mode 100644 index 0000000..831f542 --- /dev/null +++ b/src/enum/media.ts @@ -0,0 +1,5 @@ +export enum Media { + VIDEO = 'video', + IMAGE = 'image', + AUDIO = 'audio', +} diff --git a/src/enum/platform.ts b/src/enum/platform.ts index 8ce16f2..a5c391d 100644 --- a/src/enum/platform.ts +++ b/src/enum/platform.ts @@ -1,5 +1,5 @@ export enum Platform { DARWIN = 'darwin', LINUX = 'linux', - WIN32 = 'win32' + WIN32 = 'win32', } diff --git a/src/lib/conversion/conversion-handler.ts b/src/lib/conversion/conversion-handler.ts new file mode 100644 index 0000000..e7629af --- /dev/null +++ b/src/lib/conversion/conversion-handler.ts @@ -0,0 +1,77 @@ +import { Media } from '@/types/media'; +import { Media as MediaType } from '@/enum/media'; +import { VideoFormat } from '@/enum/video-format'; +import { AudioFormat } from '@/enum/audio-format'; +import { ImageFormat } from '@/enum/image-format'; +import { FfmpegAdapter } from './ffmpeg'; +import { JimpAdapter } from './jimp'; +import { Adapter } from '@/types/adapter'; + +export class ConversionHandler { + protected conversions: Map = new Map(); + + /** + * Adds a new conversion to the handler. + */ + async handle( + id: string, + filePath: string, + outputFormat: VideoFormat | AudioFormat | ImageFormat, + saveDirectory: string, + type: Media, + event: Electron.IpcMainInvokeEvent, + ): Promise { + const adapter = this.getAdapter(type); + + this.conversions.set(id, adapter); + + try { + const result = await adapter.convert(id, filePath, outputFormat, saveDirectory, event); + + this.conversions.delete(id); + + return result; + } catch (error) { + this.conversions.delete(id); + + throw error; + } + } + + /** + * Cancels the conversion with the given ID. + */ + cancel(id: string): boolean { + const adapter = this.conversions.get(id); + if (adapter) { + const result = adapter.cancel(id); + this.conversions.delete(id); + return result; + } + return false; + } + + /** + * Cancels all conversions. + */ + cancelAll(): void { + this.conversions.forEach((adapter, id) => adapter.cancel(id)); + this.conversions.clear(); + } + + /** + * Returns the appropriate adapter for the given type. + */ + protected getAdapter(type: Media) { + switch (type) { + case MediaType.IMAGE: + return new JimpAdapter(); + case MediaType.VIDEO: + return new FfmpegAdapter(); + case MediaType.AUDIO: + return new FfmpegAdapter(); + default: + throw new Error(`Unsupported type: ${type}`); + } + } +} diff --git a/src/lib/conversion/ffmpeg.ts b/src/lib/conversion/ffmpeg.ts new file mode 100644 index 0000000..82f3846 --- /dev/null +++ b/src/lib/conversion/ffmpeg.ts @@ -0,0 +1,97 @@ +import path from 'node:path'; +import ffmpeg, { setFfmpegPath, setFfprobePath, ffprobe as ffmpegFfprobe } from 'fluent-ffmpeg'; +import ffmpegStatic from 'ffmpeg-static'; +import { path as ffprobePath } from 'ffprobe-static'; +import { Adapter } from '@/types/adapter'; + +// Initialize FFmpeg paths +let ffmpegPath: string; +try { + if (!ffmpegStatic) throw new Error('ffmpegStatic not found'); + ffmpegPath = ffmpegStatic.replace('app.asar', 'app.asar.unpacked'); + const ffprobeResolvedPath = ffprobePath.replace('app.asar', 'app.asar.unpacked'); + setFfmpegPath(ffmpegPath); + setFfprobePath(ffprobeResolvedPath); +} catch (error) { + console.error('Failed to find ffmpegStatic:', error.message); +} + +export class FfmpegAdapter implements Adapter { + /** + * Map of FFmpeg processes by ID. + */ + protected ffmpegProcesses = new Map(); + + /** + * Converts the file at the given path to the specified output format. + */ + convert( + id: string, + filePath: string, + outputFormat: string, + saveDirectory: string, + event: Electron.IpcMainInvokeEvent, + ): Promise { + const outputFileName = `${path.basename(filePath, path.extname(filePath))}.${outputFormat}`; + const outputPath = path.join(saveDirectory, outputFileName); + + return new Promise((resolve, reject) => { + ffmpegFfprobe(filePath, (err, metadata) => { + if (err) { + reject(err); + return; + } + + const duration = metadata.format.duration; + const ffmpegCommand = ffmpeg(filePath) + .output(outputPath) + .on('progress', (progress) => { + const processedSeconds = this.parseTimemark(progress.timemark); + const calculatedProgress = duration ? (processedSeconds / duration) * 100 : 0; + event.sender.send('conversion-progress', { id, progress: calculatedProgress }); + }) + .on('end', () => { + event.sender.send('conversion-progress', { id, progress: 100 }); + this.ffmpegProcesses.delete(id); + resolve(outputPath); + }) + .on('error', (error: Error) => { + this.ffmpegProcesses.delete(id); + reject(error); + }) + .save(outputPath); + + this.ffmpegProcesses.set(id, ffmpegCommand); + }); + }); + } + + /** + * Cancels the FFmpeg conversion process with the given ID. + */ + cancel(id: string): boolean { + const ffmpegCommand = this.ffmpegProcesses.get(id); + if (!ffmpegCommand) return false; + + try { + ffmpegCommand.kill('SIGKILL'); + this.ffmpegProcesses.delete(id); + return true; + } catch (error) { + console.error(`Failed to kill FFmpeg process for ID: ${id}`, error); + return false; + } + } + + /** + * Parses a FFmpeg timemark string into seconds. + */ + protected parseTimemark(timemark: string): number { + const parts = timemark.split(':').reverse(); + let seconds = 0; + if (parts.length > 0) seconds += parseFloat(parts[0]); + if (parts.length > 1) seconds += parseInt(parts[1]) * 60; + if (parts.length > 2) seconds += parseInt(parts[2]) * 3600; + return seconds; + } +} diff --git a/src/lib/conversion/jimp.ts b/src/lib/conversion/jimp.ts new file mode 100644 index 0000000..f598142 --- /dev/null +++ b/src/lib/conversion/jimp.ts @@ -0,0 +1,42 @@ +import { Jimp } from 'jimp'; +import { promises as fs } from 'fs'; // Use `fs.promises` for async directory management +import path from 'node:path'; +import { Adapter } from '@/types/adapter'; + +export type JimpType = typeof Jimp; + +export class JimpAdapter implements Adapter { + protected jimpProcesses = new Map(); + + /** + * Converts the file at the given path to the specified output format. + */ + async convert( + id: string, + filePath: string, + outputFormat: string, + saveDirectory: string, + event: Electron.IpcMainInvokeEvent, + ): Promise { + const outputFileName = `${path.basename(filePath, path.extname(filePath))}.${outputFormat}`; + const outputPath = path.join(saveDirectory, outputFileName); + + await fs.mkdir(saveDirectory, { recursive: true }); + + const image = await Jimp.read(filePath); + this.jimpProcesses.set(id, image as unknown as JimpType); + + await image.write(outputPath as `${string}.${string}`); + event.sender.send('conversion-progress', { id, progress: 100 }); + this.jimpProcesses.delete(id); + + return outputPath; + } + + /** + * Cancels the Jimp conversion process with the given ID. + */ + cancel(id: string): boolean { + return this.jimpProcesses.delete(id); + } +} diff --git a/src/lib/ffmpeg.ts b/src/lib/ffmpeg.ts deleted file mode 100644 index df177dc..0000000 --- a/src/lib/ffmpeg.ts +++ /dev/null @@ -1,131 +0,0 @@ -import ffmpeg from 'fluent-ffmpeg'; -import ffmpegStatic from 'ffmpeg-static'; -import ffprobe from 'ffprobe-static'; -import path from 'node:path'; - -const ffmpegProcesses = new Map(); - -/** - * Set the ffmpeg process for the given ID. - * - * @param {string} id - * @param {ffmpeg.FfmpegCommand} ffmpegCommand - */ -export function setFfmpegProcess(id: string, ffmpegCommand: ffmpeg.FfmpegCommand): void { - ffmpegProcesses.set(id, ffmpegCommand); -} - -let ffmpegPath: string; -let ffprobePath: string; - -try { - if (!ffmpegStatic) throw new Error('ffmpegStatic not found'); - ffmpegPath = ffmpegStatic.replace('app.asar', 'app.asar.unpacked'); - ffprobePath = ffprobe.path.replace('app.asar', 'app.asar.unpacked'); - - ffmpeg.setFfmpegPath(ffmpegPath); - ffmpeg.setFfprobePath(ffprobePath); -} catch (error) { - console.error('Failed to find ffmpegStatic:', error.message); -} - -/** - * Parse a timemark string into seconds. - */ -export function parseTimemark(timemark: string): number { - const parts = timemark.split(':').reverse(); - let seconds = 0; - if (parts.length > 0) seconds += parseFloat(parts[0]); - if (parts.length > 1) seconds += parseInt(parts[1]) * 60; - if (parts.length > 2) seconds += parseInt(parts[2]) * 3600; - return seconds; -} - -/** - * Handle the video conversion process. - */ -export function handleConversion( - event: Electron.IpcMainInvokeEvent, - id: string, - filePath: string, - outputFormat: string, - saveDirectory: string, - resolve: (value: string) => void, - reject: (reason: unknown) => void -): void { - const outputFileName = `${path.basename(filePath, path.extname(filePath))}.${outputFormat}`; - const outputPath = path.join(saveDirectory, outputFileName); - - ffmpeg.ffprobe(filePath, (err, metadata) => { - if (err) { - reject(err); - return; - } - - const duration = metadata.format.duration; - - const ffmpegCommand = ffmpeg(filePath) - .output(outputPath) - .on('progress', (progress) => { - const processedSeconds = parseTimemark(progress.timemark); - const calculatedProgress = duration ? (processedSeconds / duration) * 100 : 0; - event.sender.send('conversion-progress', { id, progress: calculatedProgress }); - }) - .on('end', () => { - ffmpegProcesses.delete(id); - resolve(outputPath); - }) - .on('error', (error: Error) => { - ffmpegProcesses.delete(id); - if (error.message.includes('SIGKILL')) { - reject(new Error('Conversion canceled by user')); - } else { - reject(error); - } - }) - .save(outputPath); - - setFfmpegProcess(id, ffmpegCommand); - }); -} - -/** - * Cancel a single FFmpeg process. - */ -export function handleItemConversionCancellation( - _event: Electron.IpcMainInvokeEvent, - id: string -): boolean { - const ffmpegCommand = ffmpegProcesses.get(id); - - if (!ffmpegCommand) { - console.warn(`No FFmpeg process found for ID: ${id}`); - return false; - } - - try { - // Send SIGKILL to forcefully stop the process - ffmpegCommand.kill('SIGKILL'); - ffmpegProcesses.delete(id); - return true; - } catch (error) { - console.error(`Failed to kill FFmpeg process for ID: ${id}`, error); - return false; - } -} - -/** - * Cancel all FFmpeg processes. - */ -export function handleConversionCancellation(_event: Electron.IpcMainInvokeEvent): boolean { - for (const [id, ffmpegCommand] of ffmpegProcesses.entries()) { - try { - ffmpegCommand.kill('SIGKILL'); - ffmpegProcesses.delete(id); - } catch (error) { - console.error(`Failed to kill FFmpeg process for ID: ${id}`, error); - return false; - } - } - return true; -} diff --git a/src/lib/index.ts b/src/lib/index.ts index 86a80f7..a11e967 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,8 +1,8 @@ -export * from './desktop-path'; -export * from './windows'; -export * from './ffmpeg'; -export * from './ipc-handlers'; -export * from './get-project-name'; -export * from './devmode'; -export * from './devtools'; -export * from './squirrel'; +export * from './conversion/ffmpeg'; +export * from './utils/desktop-path'; +export * from './utils/get-project-name'; +export * from './utils/devmode'; +export * from './utils/devtools'; +export * from './system/ipc-handlers'; +export * from './system/windows'; +export * from './system/squirrel'; diff --git a/src/lib/ipc-handlers.ts b/src/lib/ipc-handlers.ts deleted file mode 100644 index 6800e68..0000000 --- a/src/lib/ipc-handlers.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - dialog, - IpcMain, - IpcMainInvokeEvent -} from 'electron'; -import { getDesktopPath } from './desktop-path'; -import { IpcEvent } from '../enum/ipc-event'; -import { - handleConversion, - handleConversionCancellation, - handleItemConversionCancellation -} from './ffmpeg'; - -/** - * Configure the IPC handlers - * - * @param {IpcMain} ipcMain - * - * @returns {void} - */ -export function configureIpcHandlers(ipcMain: IpcMain): void { - ipcMain.handle(IpcEvent.GET_DESKTOP_PATH, () => { - return getDesktopPath(); - }); - - ipcMain.handle(IpcEvent.DIALOG_SELECT_DIRECTORY, async () => { - const result = await dialog.showOpenDialog({ - properties: ['openDirectory'], - }); - - return result.canceled ? null : result.filePaths[0]; - }); - - ipcMain.handle(IpcEvent.CONVERT_VIDEO, async ( - event: IpcMainInvokeEvent, - { id, filePath, outputFormat, saveDirectory }: { - id: string, - filePath: string; - outputFormat: string; - saveDirectory: string; - } - ) => { - return new Promise((resolve, reject) => { - handleConversion( - event, - id, - filePath, - outputFormat, - saveDirectory, - resolve, - reject - ); - }); - }); - - ipcMain.handle(IpcEvent.CANCEL_CONVERSION, ( - event: IpcMainInvokeEvent - ) => { - return handleConversionCancellation(event); - }); - - ipcMain.handle(IpcEvent.CANCEL_ITEM_CONVERSION, ( - event: IpcMainInvokeEvent, - id: string - ) => { - return handleItemConversionCancellation(event, id); - }); -} diff --git a/src/lib/system/ipc-handlers.ts b/src/lib/system/ipc-handlers.ts new file mode 100644 index 0000000..c7fd823 --- /dev/null +++ b/src/lib/system/ipc-handlers.ts @@ -0,0 +1,56 @@ +import { dialog, IpcMain, IpcMainInvokeEvent } from 'electron'; +import { getDesktopPath } from '@/lib/utils/desktop-path'; +import { IpcEvent } from '@/enum/ipc-event'; +import { ConversionHandler } from '@/lib/conversion/conversion-handler'; +import { VideoFormat } from '@/enum/video-format'; +import { AudioFormat } from '@/enum/audio-format'; +import { ImageFormat } from '@/enum/image-format'; +import { Media } from '@/types/media'; + +// Create a single instance of ConversionHandler to handle all conversions +export const conversionHandler = new ConversionHandler(); + +/** + * Configure the IPC handlers + */ +export function configureIpcHandlers(ipcMain: IpcMain): void { + ipcMain.handle(IpcEvent.GET_DESKTOP_PATH, () => getDesktopPath()); + + ipcMain.handle(IpcEvent.DIALOG_SELECT_DIRECTORY, async () => { + const result = await dialog.showOpenDialog({ + properties: ['openDirectory'], + }); + + return result.canceled ? null : result.filePaths[0]; + }); + + ipcMain.handle( + IpcEvent.CONVERT_MEDIA, + async ( + event: IpcMainInvokeEvent, + { + id, + filePath, + outputFormat, + saveDirectory, + mediaType, + }: { + id: string; + filePath: string; + outputFormat: VideoFormat | AudioFormat | ImageFormat; + saveDirectory: string; + mediaType: Media; + }, + ) => { + return await conversionHandler.handle(id, filePath, outputFormat, saveDirectory, mediaType, event); + }, + ); + + ipcMain.handle(IpcEvent.CANCEL_CONVERSION, () => { + return conversionHandler.cancelAll(); + }); + + ipcMain.handle(IpcEvent.CANCEL_ITEM_CONVERSION, (_event: IpcMainInvokeEvent, id: string) => { + return conversionHandler.cancel(id); + }); +} diff --git a/src/lib/squirrel.ts b/src/lib/system/squirrel.ts similarity index 100% rename from src/lib/squirrel.ts rename to src/lib/system/squirrel.ts diff --git a/src/lib/windows.ts b/src/lib/system/windows.ts similarity index 69% rename from src/lib/windows.ts rename to src/lib/system/windows.ts index a217adb..4d2851b 100644 --- a/src/lib/windows.ts +++ b/src/lib/system/windows.ts @@ -1,15 +1,13 @@ import { BrowserWindowConstructorOptions, BrowserWindow } from 'electron'; -import { browserWindowOptions } from '../options'; -import { isDevMode } from './devmode'; +import { browserWindowOptions } from '../../options'; +import { isDevMode } from '../utils/devmode'; // Keep a global reference of the window objects, if we don't, the window will // be closed automatically when the JavaScript object is garbage collected. export let browserWindows: Array = []; let mainIsReadyResolver: () => void; -const mainIsReadyPromise = new Promise( - (resolve) => (mainIsReadyResolver = resolve), -); +const mainIsReadyPromise = new Promise((resolve) => (mainIsReadyResolver = resolve)); /** * Resolve the mainIsReadyPromise to indicate that the main window is ready. @@ -20,11 +18,9 @@ export function mainIsReady() { /** * Get the main window options - * - * @returns {BrowserWindowConstructorOptions} */ export function getMainWindowOptions( - overrides?: Partial + overrides?: Partial, ): BrowserWindowConstructorOptions { return { ...browserWindowOptions, @@ -34,16 +30,8 @@ export function getMainWindowOptions( /** * Create a new BrowserWindow instance - * - * @param {BrowserWindowConstructorOptions} options - * @param {string} entryFilePath - * - * @returns {BrowserWindow} */ -export function createWindow( - options: BrowserWindowConstructorOptions, - entryFilePath: string -): BrowserWindow { +export function createWindow(options: BrowserWindowConstructorOptions, entryFilePath: string): BrowserWindow { let mainWindow: BrowserWindow | null; mainWindow = new BrowserWindow(options); @@ -70,13 +58,8 @@ export function createWindow( /** * Load the entry point for the main window - * - * @param {BrowserWindow} window - * @param {string} entryFilePath - * - * @returns {void} */ -function loadEntryPoint(window: BrowserWindow, entryFilePath: string) { +function loadEntryPoint(window: BrowserWindow, entryFilePath: string): void { if (process.env.JEST) { window.loadFile('./fake/path'); @@ -92,20 +75,13 @@ function loadEntryPoint(window: BrowserWindow, entryFilePath: string) { /** * Gets or creates the main window, returning it in both cases. - * - * @returns {Promise} */ export async function getOrCreateMainWindow( entryFilePath: string, - options: BrowserWindowConstructorOptions = getMainWindowOptions() + options: BrowserWindowConstructorOptions = getMainWindowOptions(), ): Promise { await mainIsReadyPromise; return ( - BrowserWindow.getFocusedWindow() - || browserWindows[0] - || createWindow( - getMainWindowOptions(options), - entryFilePath - ) + BrowserWindow.getFocusedWindow() || browserWindows[0] || createWindow(getMainWindowOptions(options), entryFilePath) ); } diff --git a/src/lib/desktop-path.ts b/src/lib/utils/desktop-path.ts similarity index 93% rename from src/lib/desktop-path.ts rename to src/lib/utils/desktop-path.ts index 3680a8b..a171237 100644 --- a/src/lib/desktop-path.ts +++ b/src/lib/utils/desktop-path.ts @@ -4,8 +4,6 @@ import path from 'node:path'; /** * Get the path to the desktop directory - * - * @returns {string} */ export function getDesktopPath(): string { const homeDir = path.resolve(os.homedir()); diff --git a/src/lib/devmode.ts b/src/lib/utils/devmode.ts similarity index 83% rename from src/lib/devmode.ts rename to src/lib/utils/devmode.ts index e41ef3d..384cdfd 100644 --- a/src/lib/devmode.ts +++ b/src/lib/utils/devmode.ts @@ -1,7 +1,5 @@ /** * Are we currently running in development mode? - * - * @returns {boolean} */ export function isDevMode(): boolean { return !!process.defaultApp; diff --git a/src/lib/devtools.ts b/src/lib/utils/devtools.ts similarity index 94% rename from src/lib/devtools.ts rename to src/lib/utils/devtools.ts index b3b81ea..d160568 100644 --- a/src/lib/devtools.ts +++ b/src/lib/utils/devtools.ts @@ -2,9 +2,6 @@ import { isDevMode } from './devmode'; /** * Installs developer tools if we're in dev mode. - * - * @export - * @returns {Promise} */ export async function setupDevTools(): Promise { if (!isDevMode()) return; diff --git a/src/lib/get-project-name.ts b/src/lib/utils/get-project-name.ts similarity index 83% rename from src/lib/get-project-name.ts rename to src/lib/utils/get-project-name.ts index d69f827..3b388e6 100644 --- a/src/lib/get-project-name.ts +++ b/src/lib/utils/get-project-name.ts @@ -4,9 +4,6 @@ import * as namor from 'namor'; /** * Returns a name for this project - * - * @param {string} [localPath] - * @returns {string} */ export function getProjectName(localPath?: string): string { if (localPath) { diff --git a/src/locales/messages.ts b/src/locales/messages.ts index 4d979a1..269f424 100644 --- a/src/locales/messages.ts +++ b/src/locales/messages.ts @@ -5,6 +5,7 @@ export const messages = { media: { video: 'Video', audio: 'Audio', + image: 'Image', }, upload: { title: 'Drag and drop your {type} files here', @@ -33,6 +34,7 @@ export const messages = { media: { video: 'Vidéo', audio: 'Audio', + image: 'Image', }, upload: { title: 'Glissez-déposez vos fichiers {type} ici', @@ -61,6 +63,7 @@ export const messages = { media: { video: 'Vídeo', audio: 'Audio', + image: 'Imagen', }, upload: { title: 'Arrastra y suelta tus archivos {type} aquí', @@ -89,6 +92,7 @@ export const messages = { media: { video: 'Video', audio: 'Audio', + image: 'Bild', }, upload: { title: 'Ziehen Sie Ihre {type}-Dateien hierher', @@ -117,6 +121,7 @@ export const messages = { media: { video: 'Видео', audio: 'Аудио', + image: 'Изображение', }, upload: { title: 'Перетащите ваши файлы {type} сюда', diff --git a/src/main.ts b/src/main.ts index 8f58bf4..4497a3c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,27 +1,26 @@ -import { - app, - BrowserWindow, - ipcMain, - IpcMainEvent, - nativeTheme, - systemPreferences, -} from 'electron'; -import { - shouldQuit, - isDevMode, - setupDevTools, - getOrCreateMainWindow, - configureIpcHandlers, - mainIsReady, -} from './lib'; +import { app, BrowserWindow, ipcMain, IpcMainEvent, nativeTheme, systemPreferences } from 'electron'; +import { shouldQuit, isDevMode, getOrCreateMainWindow, configureIpcHandlers, mainIsReady } from './lib'; import path from 'node:path'; -import { IpcEvent } from './enum/ipc-event'; +import { IpcEvent } from '@/enum/ipc-event'; +import { APP_NAME } from '@/consts/app'; + +/** + * Get the entry file path for the main window. + */ +function getEntryFilePath() { + const fakePath = '/fake/path'; + const isJest = !!process.env.JEST; + const mainWindowViteNameExists = typeof MAIN_WINDOW_VITE_NAME !== 'undefined'; + + if (!mainWindowViteNameExists) { + return fakePath as string; + } + + return isJest ? fakePath : (path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`) as string); +} // Path to the entry HTML file for the main window -const entryFilePath = path.join( - __dirname, - `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html` -); +const entryFilePath = getEntryFilePath(); /** * Handle the app's "ready" event. This is essentially @@ -32,11 +31,11 @@ export async function onReady() { process.env.NODE_ENV = 'production'; } + setupIsDevMode(); setupShowWindow(); - setupDevTools(); setupTitleBarClickMac(); setupNativeTheme(); - setupIsDevMode(); + setupGetSystemTheme(); // Do this after setting everything up to ensure that // any IPC listeners are set up before they're used @@ -67,10 +66,7 @@ export function setupTitleBarClickMac() { } ipcMain.on(IpcEvent.CLICK_TITLEBAR_MAC, (event: IpcMainEvent) => { - const doubleClickAction = systemPreferences.getUserDefault( - 'AppleActionOnDoubleClick', - 'string', - ); + const doubleClickAction = systemPreferences.getUserDefault('AppleActionOnDoubleClick', 'string'); const win = BrowserWindow.fromWebContents(event.sender); if (win) { if (doubleClickAction === 'Minimize') { @@ -93,9 +89,7 @@ export function setupTitleBarClickMac() { * * @returns Whether the value is a valid native theme source */ -function isNativeThemeSource( - val: unknown, -): val is typeof nativeTheme.themeSource { +function isNativeThemeSource(val: unknown): val is typeof nativeTheme.themeSource { return typeof val === 'string' && ['dark', 'light', 'system'].includes(val); } @@ -103,11 +97,31 @@ function isNativeThemeSource( * Handle theme changes. */ export function setupNativeTheme() { - ipcMain.on(IpcEvent.SET_NATIVE_THEME, async (_, source: string) => { + ipcMain.on(IpcEvent.SET_NATIVE_THEME, (_event, source: string) => { if (isNativeThemeSource(source)) { nativeTheme.themeSource = source; } }); + + // Only notify renderer if the theme source is 'system' + if (nativeTheme) { + nativeTheme.on('updated', () => { + const currentTheme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; + BrowserWindow.getAllWindows().forEach((win) => { + win.webContents.send(IpcEvent.NATIVE_THEME_UPDATED, currentTheme); + }); + }); + } +} + +/** + * Handle getting the system theme. + */ +export function setupGetSystemTheme() { + ipcMain.on(IpcEvent.GET_SYSTEM_THEME, (event) => { + const currentTheme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; + event.returnValue = currentTheme; + }); } /** @@ -145,7 +159,7 @@ export function main() { } // Set the app's name - app.name = 'Comet'; + app.name = APP_NAME; // Launch app.whenReady().then(onReady); @@ -155,7 +169,6 @@ export function main() { await getOrCreateMainWindow(entryFilePath); // Create the main window if there are no open windows }); }); - } // Run the main method diff --git a/src/options.ts b/src/options.ts index e0a3532..b2c9857 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,7 +1,7 @@ import type { BrowserWindowConstructorOptions } from 'electron'; import { Windows } from './enum/windows'; import path from 'node:path'; -import { isDevMode } from './lib/devmode'; +import { isDevMode } from './lib/utils/devmode'; import { Platform } from './enum/platform'; export const browserWindowOptions = { diff --git a/src/preload.ts b/src/preload.ts index 466de6b..f980bef 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -1,29 +1,30 @@ // See the Electron documentation for details on how to use preload scripts: // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts -import { - contextBridge, - ipcRenderer, - IpcRendererEvent, - webUtils -} from 'electron'; +import { contextBridge, ipcRenderer, IpcRendererEvent, webUtils } from 'electron'; import { IpcEvent } from './enum/ipc-event'; +import { VideoFormat } from '@/enum/video-format'; +import { AudioFormat } from '@/enum/audio-format'; +import { ImageFormat } from '@/enum/image-format'; +import { Media } from './types/media'; +import { ColorMode } from './types/theme'; -async function preload() { +async function preload(): Promise { await setupGlobals(); } /** * Expose Electron API in the main world - * - * @returns {Promise} */ -export async function setupGlobals() { +export async function setupGlobals(): Promise { contextBridge.exposeInMainWorld('electron', { arch: process.arch, platform: process.platform, selectDirectory() { return ipcRenderer.invoke(IpcEvent.DIALOG_SELECT_DIRECTORY); }, + getSystemTheme() { + return ipcRenderer.sendSync(IpcEvent.GET_SYSTEM_THEME) as ColorMode; + }, getDesktopPath() { return ipcRenderer.invoke(IpcEvent.GET_DESKTOP_PATH); }, @@ -36,26 +37,24 @@ export async function setupGlobals() { cancelConversion() { return ipcRenderer.invoke(IpcEvent.CANCEL_CONVERSION); }, - convertVideo( + convertMedia( id: string, filePath: string, - outputFormat: string, - saveDirectory: string - ) { - return ipcRenderer.invoke( - IpcEvent.CONVERT_VIDEO, - { id, filePath, outputFormat, saveDirectory } - ); - }, - on(channel: string, callback: ( - event: IpcRendererEvent, - ...args: unknown[]) => void + outputFormat: VideoFormat | AudioFormat | ImageFormat, + saveDirectory: string, + mediaType: Media, ) { + return ipcRenderer.invoke(IpcEvent.CONVERT_MEDIA, { id, filePath, outputFormat, saveDirectory, mediaType }); + }, + send(channel: string, ...args: unknown[]) { + ipcRenderer.send(channel, ...args); + }, + on(channel: string, callback: (event: IpcRendererEvent, ...args: unknown[]) => void) { ipcRenderer.on(channel, callback); }, removeAllListeners(channel: string) { ipcRenderer.removeAllListeners(channel); - } + }, }); } diff --git a/src/types/adapter.ts b/src/types/adapter.ts new file mode 100644 index 0000000..d8a482b --- /dev/null +++ b/src/types/adapter.ts @@ -0,0 +1,17 @@ +export interface Adapter { + /** + * Converts the file at the given path to the specified output format. + */ + convert( + id: string, + filePath: string, + outputFormat: string, + saveDirectory: string, + event: Electron.IpcMainInvokeEvent, + ): Promise; + + /** + * Cancels the FFmpeg conversion process with the given ID. + */ + cancel(id: string): boolean; +} diff --git a/src/types/item.ts b/src/types/item.ts index 658115b..70cdcfb 100644 --- a/src/types/item.ts +++ b/src/types/item.ts @@ -1,5 +1,8 @@ +import { Media } from './media'; + export interface Item extends File { id: number | string; + type: Media; path: string; converted: boolean; outputFormat?: string; diff --git a/src/types/media.ts b/src/types/media.ts new file mode 100644 index 0000000..e033f73 --- /dev/null +++ b/src/types/media.ts @@ -0,0 +1,3 @@ +import { Media as MediaType } from '../enum/media'; + +export type Media = MediaType.VIDEO | MediaType.IMAGE | MediaType.AUDIO; diff --git a/src/types/theme.ts b/src/types/theme.ts new file mode 100644 index 0000000..57a9490 --- /dev/null +++ b/src/types/theme.ts @@ -0,0 +1 @@ +export type ColorMode = 'light' | 'dark' | 'system'; diff --git a/src/ui/app.vue b/src/ui/app.vue index 896ce7a..f488514 100644 --- a/src/ui/app.vue +++ b/src/ui/app.vue @@ -1,9 +1,12 @@ diff --git a/src/ui/blocks/AudioConverter.vue b/src/ui/blocks/AudioConverter.vue index 4bd96d7..53d7c40 100644 --- a/src/ui/blocks/AudioConverter.vue +++ b/src/ui/blocks/AudioConverter.vue @@ -1,29 +1,16 @@ @@ -48,8 +40,8 @@ onMounted(async () => { @@ -82,22 +74,14 @@ onMounted(async () => {