1
1
mirror of https://github.com/neosubhamoy/neodlp.git synced 2026-02-05 07:32:22 +05:30

17 Commits

41 changed files with 1074 additions and 831 deletions

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

3
.gitattributes vendored
View File

@@ -1,2 +1,3 @@
* text=auto eol=lf
src-tauri/binaries/* filter=lfs diff=lfs merge=lfs -text src-tauri/binaries/* filter=lfs diff=lfs merge=lfs -text
src-tauri/resources/binaries/* filter=lfs diff=lfs merge=lfs -text

16
.github/banner.svg vendored Normal file
View File

@@ -0,0 +1,16 @@
<svg width="600" height="130" viewBox="0 0 600 130" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3_2)">
<path d="M86.9062 11H21.0938C9.44399 11 0 20.444 0 32.0938V97.9062C0 109.556 9.44399 119 21.0938 119H86.9062C98.556 119 108 109.556 108 97.9062V32.0938C108 20.444 98.556 11 86.9062 11Z" fill="url(#paint0_linear_3_2)"/>
<path d="M55.8196 96.5455C54.7881 97.5856 53.1065 97.5856 52.075 96.5455L27.028 71.2863C25.3778 69.6221 26.5566 66.793 28.9002 66.793H78.9943C81.3379 66.793 82.5168 69.6221 80.8666 71.2863L55.8196 96.5455Z" fill="#FAFAFA"/>
<path d="M67.8164 34.4141H40.0781C38.6219 34.4141 37.4414 35.5946 37.4414 37.0508V68.2695C37.4414 69.7257 38.6219 70.9062 40.0781 70.9062H67.8164C69.2726 70.9062 70.4531 69.7257 70.4531 68.2695V37.0508C70.4531 35.5946 69.2726 34.4141 67.8164 34.4141Z" fill="#FAFAFA"/>
</g>
<defs>
<linearGradient id="paint0_linear_3_2" x1="13.6582" y1="26.6621" x2="97.1367" y2="102.02" gradientUnits="userSpaceOnUse">
<stop stop-color="#4444FF"/>
<stop offset="1" stop-color="#FF43D0"/>
</linearGradient>
<clipPath id="clip0_3_2">
<rect width="108" height="108" fill="white" transform="translate(0 11)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

138
.github/mockup.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 541 KiB

View File

@@ -1,27 +1,13 @@
### ✨ Changelog ### ✨ Changelog
- DOWNLOADER: Introduced 'Combine' download mode (Now, You can combine a video and audio stream of your choice) - FIXED: Infinite search loop on windows (due to yt-dlp fragment missing error)
- SETTINGS: Added global video/audio file format selection option (Available Formats: MP4, WEBM, MKV, M4A, OPUS, MP3) - Bumped up FFmpeg to 7.1.1 and added FFprobe
- SETTINGS: Added video/audio file metadata embeding option - Migrated to sonner from shadcn toast and improved toast messages
- SETTINGS: Added thumbnail embeding option in audio files (as cover art) - Other minor fixes and improvements
- SETTINGS: Added re-encode video over remuxing option (when file format convertion is needed)
- SETTINGS: Added strict downloadablity check option
- SETTINGS: Added download speed rate limit option
- SETTINGS: Added download max retries option
- SETTINGS: Added temporary download folder cleanup option
- UI: Improved 'Settings' ui/layout with categories (tabs)
- UI: Merged 'Extension' sidebar tab within 'Settings' (Settings > Extension > Install)
- UI: Improved 'Library' ui/layout with tabs
- UI: Added 'Stop' all ongoing downloads button in 'Library'
- UI: Renamed settings 'General' tab to 'Application' ('General' is now a sub-category of 'Application' tab)
- UI: Improved all alert dialog messages (for better undestanding/UX)
- FIXED: Unexpected crashing of yt-dlp causing downloads to stuck on a unrevocable state (Now, coresponding download will be 'paused' on detection of unexpected yt-dlp crash)
- FIXED: Broken app updater progress bar/percentage (also improved the update notification card)
- Lots of other minor fixes and improvements
### 📝 Notes ### 📝 Notes
> ⚠️ Linux Users: Make sure yt-dlp is not installed in your distro (otherwise you will get package installation conflict). Don't worry, You can still use yt-dlp cli as before (the only difference is that now it will be installed and auto-updated by neo-dlp, which You can also disable from neo-dlp Settings if you don't want to auto-update yt-dlp) > ⚠️ Linux Users: Make sure yt-dlp, ffmpeg and ffprobe is not installed in your distro (otherwise you will get package installation conflict). Don't worry, You can still use yt-dlp cli as before (the only difference is that now it will be installed and auto-updated by neo-dlp, which You can also disable from neo-dlp Settings if you don't want to auto-update yt-dlp)
> This is an Un-Signed Build (Windows doesn't trust this Certificate so, it may flag this as malicious software, in that case, disable Windows SmartScreen and Defender, install it, and then re-enable them) > This is an Un-Signed Build (Windows doesn't trust this Certificate so, it may flag this as malicious software, in that case, disable Windows SmartScreen and Defender, install it, and then re-enable them)
@@ -32,8 +18,10 @@
| Arch\OS | Windows (msi) ⬆️ | Windows (exe) ⬆️ | Linux (deb) | Linux (rpm) | MacOS (dmg) ⬆️ | MacOS (app) ⬆️ | | Arch\OS | Windows (msi) ⬆️ | Windows (exe) ⬆️ | Linux (deb) | Linux (rpm) | MacOS (dmg) ⬆️ | MacOS (app) ⬆️ |
| :---- | :---- | :---- | :---- | :---- | :---- | :---- | | :---- | :---- | :---- | :---- | :---- | :---- | :---- |
| x86_64 | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64_en-US.msi) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64-setup.exe) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_amd64.deb) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP-<version>-1.x86_64.rpm) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64.dmg) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_x64.app.tar.gz) | | x86_64 | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64_en-US.msi) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64-setup.exe) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_amd64.deb) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP-<version>-1.x86_64.rpm) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64.dmg) | [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_x64.app.tar.gz) |
| ARM64 | N/A | N/A | N/A | N/A | ⚠️ [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_aarch64.dmg) | ⚠️ [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_aarch64.app.tar.gz) | | ARM64 | N/A | 🪟 [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_x64-setup.exe) | N/A | N/A | ⚠️ [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_<version>_aarch64.dmg) | ⚠️ [Download](https://github.com/neosubhamoy/neodlp/releases/download/<release_tag>/NeoDLP_aarch64.app.tar.gz) |
> ⬆️ icon indicates this packaging format supports in-built app-updater > ⬆️ icon indicates this packaging format supports in-built app-updater
> 🪟 Windows x86_64 binary also works on ARM64 (Windows on ARM) devices with emulation (Not planning to release native Windows ARM64 build anytime soon as, x86_64 one works fine on ARM64 without noticeable performance impact)
> ⚠️ MacOS ARM64 binary downloads are experimental and may not open on Apple Silicon Macs if downloaded from browser (You will get 'Damaged File' error) it's because the binaries are not signed (signing MacOS binaries requires 99$/year Apple Developer Account subscription, which I can't afford RN!) and Apple Silicon Macs don't allow unsigned apps (downloaded from browser) to be installed on the system. If you want to use NeoDLP on your Apple Silicon Macs, you can simply use the command line [Curl-Bash Installer](https://neodlp.neosubhamoy.com/download) (Recommended) -OR- [compile it from source](https://github.com/neosubhamoy/neodlp?tab=readme-ov-file#%EF%B8%8F-contributing--building-from-source) in your Mac > ⚠️ MacOS ARM64 binary downloads are experimental and may not open on Apple Silicon Macs if downloaded from browser (You will get 'Damaged File' error) it's because the binaries are not signed (signing MacOS binaries requires 99$/year Apple Developer Account subscription, which I can't afford RN!) and Apple Silicon Macs don't allow unsigned apps (downloaded from browser) to be installed on the system. If you want to use NeoDLP on your Apple Silicon Macs, you can simply use the command line [Curl-Bash Installer](https://neodlp.neosubhamoy.com/download) (Recommended) -OR- [compile it from source](https://github.com/neosubhamoy/neodlp?tab=readme-ov-file#%EF%B8%8F-contributing--building-from-source) in your Mac

View File

@@ -1,42 +1,94 @@
![NeoDLP](./.github/banner.svg)
# NeoDLP - (Neo Downloader Plus) # NeoDLP - (Neo Downloader Plus)
Cross-platform Video/Audio Downloader Desktop App with Modern UI and Browser Integration Cross-platform Video/Audio Downloader Desktop App with Modern UI and Browser Integration
[![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/neodlp) [![status](https://img.shields.io/badge/status-active-brightgreen.svg?style=flat)](https://github.com/neosubhamoy/neodlp)
[![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp?color=yellow)](https://github.com/neosubhamoy/neodlp) [![github tag](https://img.shields.io/github/v/tag/neosubhamoy/neodlp?color=yellow)](https://github.com/neosubhamoy/neodlp)
[![github downloads](https://img.shields.io/github/downloads/neosubhamoy/neodlp/total)](https://github.com/neosubhamoy/neodlp/releases)
[![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/neodlp) [![PRs](https://img.shields.io/badge/PRs-welcome-blue.svg?style=flat)](https://github.com/neosubhamoy/neodlp)
> **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!** > **🥰 Liked this project? Please consider giving it a Star (🌟) on github to show us your appreciation and help the algorythm recommend this project to even more awesome people like you!**
[![Packaging status](https://repology.org/badge/vertical-allrepos/neodlp.svg)](https://repology.org/project/neodlp/versions)
### ✨ Highlighted Features
- Download Video/Audio from popular sites (YT, FB, IG, X and other 2.5k+ [supported sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md))
- Download Video/Audio in your preffered format (MP4, WEBM, MKV, MP3 etc.)
- Supports both Video and Playlist download
- Supports Combining Video, Audio streams of your choice
- Supports Multi-Language Subtitle/Caption (CC) embeding
- Different Video/Audio metadata embeding options (info, chapters, thumbnail etc.)
- Highly customizable and many more...😉
### 🧩 Browser Integration
You can integrate NeoDLP with your favourite browser (any Chrome/Chromium/Firefox based browser) Just, install [NeoDLP Extension](https://github.com/neosubhamoy/neodlp-extension) to get started!
After installing the extension you can do the following directly from the browser:
- Quick Search (search current browser address with NeoDLP) (via pressing keyboard shortcut `ALT`+`SHIFT`+`Q`, You can also change this shortcut key combo from browser settings)
- Right Click Context Menu Action (Download with Neo Downloader Plus - Link, Selection, Media Source)
### 👀 Sneak Peek
![NeoDLP-Mockup](./.github/mockup.svg)
### 💻 Supported Platforms ### 💻 Supported Platforms
- Windows (10 / 11) - Windows (10 / 11)
- Linux (Debian / Fedora / Arch Linux base) - Linux (Debian / Fedora / Arch Linux base)
- MacOS (>10.3) - MacOS (>10.3)
### 🌐 Supported Sites > ⚠️ **NOTE:** Though most linux (debian/fedora/arch base) distros are supported but not all packages are tested on all these platforms, to save some time (and brain cells) and ship the software as fast as possible! (Currently only the debian package is tested on Ubuntu 24.04 LTS - So, other linux packages may have issues, test it yourself and feel free to report issues if you found one)
- All [Supported Sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md) by [yt-dlp](https://github.com/yt-dlp/yt-dlp) **(2.5K+)** ### 🤝 External Dependencies
### 🧩 External Dependencies - [YT-DLP](https://github.com/yt-dlp/yt-dlp) - The core CLI tool used to download Video/Audio from the Web (Hero of the show 😎)
- [FFmpeg](https://www.ffmpeg.org) - Used for Video/Audio post-processing
- [yt-dlp](https://github.com/yt-dlp/yt-dlp) - The core CLI Tool used to download Video/Audio from the Web
- [FFmpeg](https://www.ffmpeg.org) - Used for Video/Audio Post-processing
### ⬇️ Download and Installation ### ⬇️ Download and Installation
1. Download the latest [NeoDLP](https://github.com/neosubhamoy/neodlp/releases/latest) release based on your OS and CPU Architecture then install it or install it directly from an available distribution channel 1. Download the latest [NeoDLP](https://github.com/neosubhamoy/neodlp/releases/latest) release based on your OS and CPU Architecture, then install it! -OR- Install it directly from an available distribution channel (listed below)
| Arch\OS | Windows | Linux | MacOS | | Arch\OS | Windows | Linux | MacOS |
| :---- | :---- | :---- | :---- | | :---- | :---- | :---- | :---- |
| x86_64 | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | | x86_64 | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) |
| ARM64 | ❌ N/A | ❌ N/A | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) | | ARM64 | ✅ Emulation | ❌ N/A | ✅ [Download](https://github.com/neosubhamoy/neodlp/releases/latest) |
> 📌 **NOTE:** x86_64 Windows binary also works on ARM64 (Windows on ARM) devices with emulation (Not planning to release native Windows ARM64 build anytime soon as, x86_64 one works fine on ARM64 without noticeable performance impact)
| Platform (OS) | Distribution Channel | Installation Command / Instruction | | Platform (OS) | Distribution Channel | Installation Command / Instruction |
| :---- | :---- | :---- | | :---- | :---- | :---- |
| Windows x86_64 | WinGet | `winget install neodlp` | | Windows x86_64 / ARM64 | WinGet | `winget install neodlp` |
| MacOS Universal | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/neodlp_macos_installer.sh \| bash` | | MacOS x86_64 / ARM64 | Curl-Bash Installer | `curl -sSL https://neodlp.neosubhamoy.com/neodlp_macos_installer.sh \| bash` |
| Linux x86_64 (Arch Linux) | AUR | `yay -S neodlp` | | Linux x86_64 (Arch Linux) | AUR | `yay -S neodlp` |
### 💝 Support the Development
NeoDLP is and will be always FREE to Use and Open-Sourced for Everyone. On the other hand the developent process of NeoDLP takes lots of time, effort and even sometimes money! So, if you appriciate my work and have the ability to donate, then please consider supporting the development by donating (even a very small donation matters and helps NeoDLP to be a better product!) Your support is the key to my motivation...🤗
<a href="https://buymeacoffee.com/neosubhamoy" target="_blank" title="buymeacoffee">
<img src="https://iili.io/JoQ0zN9.md.png" alt="buymeacoffee-orange-badge" style="width: 150px;">
</a>
<br></br>
> 📌 **NOTE:** You can also donate via UPI by sending donations to this UPI ID directly: **subhamoybiswas636-2@oksbi**
### 🪜 Roadmap
- [x] Add support for yt-dlp
- [x] Add basic settings and customization
- [x] Integrate with browsers
- [ ] Add more advanced settings and achive stability **(ongoing)**
- [ ] Add media converter
- [ ] Add multiple downloader engines
- [ ] Add advanced web extractor
- [ ] Add more cool stuffs 😉
### ⚡ Technologies Used ### ⚡ Technologies Used
![Tauri](https://img.shields.io/badge/tauri-%2324C8DB.svg?style=for-the-badge&logo=tauri&logoColor=%23FFFFFF) ![Tauri](https://img.shields.io/badge/tauri-%2324C8DB.svg?style=for-the-badge&logo=tauri&logoColor=%23FFFFFF)
@@ -53,9 +105,10 @@ Want to be part of this? Feel free to contribute...!! Pull Requests are always w
* Install [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS / platform * Install [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/) for your OS / platform
1. Fork this repo in your github account. 1. Fork this repo in your github account.
2. Git clone the forked repo in your local machine. 2. Git clone the forked repo in your local machine.
3. Install Node.js dependencies: `npm install` 3. Create a git branch (related to the feature you are working on) (Optional - Recommended)
4. Run development / build process 4. Install Node.js dependencies: `npm install`
> ⚠️ Make sure to run the build command once before running the dev command for the first time to avoid build time errors 5. Run development / build process
> ⚠️ **IMPORTANT:** Make sure to run the build command once before running the dev command for the first time to avoid compile time errors
```code ```code
# for windows and linux users # for windows and linux users
npm run tauri dev # for development npm run tauri dev # for development
@@ -68,9 +121,11 @@ npm run tauri build -- --config "./src-tauri/tauri.macos-aarch64.conf.json"
npm run tauri dev -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, development npm run tauri dev -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, development
npm run tauri build -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, production build npm run tauri build -- --config "./src-tauri/tauri.macos-x86_64.conf.json" # for intel x86 macs, production build
``` ```
5. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected) 6. Do the changes, Send a Pull Request with proper Description (NOTE: Pull Requests Without Proper Description will be Rejected)
**⭕ Noticed any Bugs or Want to give us some suggetions? Always feel free to open a GitHub Issue. We would love to hear from you...!!** ### ⭕ Bug Report
Noticed any Bug? or Want to give me some suggetions? Always feel free to open a [GitHub Issue](https://github.com/neosubhamoy/neodlp/issues). I would love to hear from you...!!
### 📝 License ### 📝 License

View File

@@ -9,7 +9,6 @@ const __dirname = path.dirname(__filename);
// Define array of binary source directories // Define array of binary source directories
const binSrcDirs = [ const binSrcDirs = [
path.join(__dirname, 'src-tauri', 'binaries'), path.join(__dirname, 'src-tauri', 'binaries'),
path.join(__dirname, 'src-tauri', 'resources', 'binaries'),
]; ];
function makeFilesExecutable() { function makeFilesExecutable() {

601
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "neodlp", "name": "neodlp",
"private": true, "private": true,
"version": "0.2.0", "version": "0.2.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,7 +10,7 @@
"tauri": "tauri" "tauri": "tauri"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.1.1", "@hookform/resolvers": "^5.2.1",
"@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-accordion": "^1.2.11",
"@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-alert-dialog": "^1.1.14",
"@radix-ui/react-aspect-ratio": "^1.1.7", "@radix-ui/react-aspect-ratio": "^1.1.7",
@@ -38,11 +38,11 @@
"@radix-ui/react-toggle": "^1.1.9", "@radix-ui/react-toggle": "^1.1.9",
"@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.10",
"@radix-ui/react-tooltip": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.7",
"@tanstack/react-query": "^5.83.0", "@tanstack/react-query": "^5.84.2",
"@tanstack/react-query-devtools": "^5.83.0", "@tanstack/react-query-devtools": "^5.84.2",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2.7.0",
"@tauri-apps/plugin-dialog": "^2.3.0", "@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.0", "@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-opener": "^2.4.0", "@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-os": "^2.3.0", "@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-process": "^2.3.0", "@tauri-apps/plugin-process": "^2.3.0",
@@ -55,33 +55,33 @@
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0", "embla-carousel-react": "^8.6.0",
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.525.0", "lucide-react": "^0.539.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.1.0", "react": "^19.1.1",
"react-day-picker": "^9.8.0", "react-day-picker": "^9.8.1",
"react-dom": "^19.1.0", "react-dom": "^19.1.1",
"react-hook-form": "^7.60.0", "react-hook-form": "^7.62.0",
"react-resizable-panels": "^3.0.3", "react-resizable-panels": "^3.0.4",
"react-router-dom": "^7.6.3", "react-router-dom": "^7.8.0",
"recharts": "^2.15.4", "recharts": "^3.1.2",
"sonner": "^2.0.6", "sonner": "^2.0.7",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^3.25.76", "zod": "^4.0.17",
"zustand": "^5.0.6" "zustand": "^5.0.7"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.11", "@tailwindcss/postcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tauri-apps/cli": "^2", "@tauri-apps/cli": "^2.7.1",
"@types/node": "^24.0.13", "@types/node": "^24.2.1",
"@types/react": "^19.1.8", "@types/react": "^19.1.9",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.1.7",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^5.0.0",
"postcss": "^8.5.6", "postcss": "^8.5.6",
"tailwindcss": "^4.1.11", "tailwindcss": "^4.1.11",
"tw-animate-css": "^1.3.5", "tw-animate-css": "^1.3.6",
"typescript": "~5.8.3", "typescript": "~5.9.2",
"vite": "^7.0.4" "vite": "^7.1.1"
} }
} }

371
src-tauri/Cargo.lock generated
View File

@@ -86,7 +86,7 @@ dependencies = [
"enumflags2", "enumflags2",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"rand 0.9.1", "rand 0.9.2",
"raw-window-handle", "raw-window-handle",
"serde", "serde",
"serde_repr", "serde_repr",
@@ -138,9 +138,9 @@ dependencies = [
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.4.1" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3" checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"cfg-if", "cfg-if",
@@ -149,17 +149,16 @@ dependencies = [
"futures-lite", "futures-lite",
"parking", "parking",
"polling", "polling",
"rustix 1.0.7", "rustix",
"slab", "slab",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "async-lock" name = "async-lock"
version = "3.4.0" version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
dependencies = [ dependencies = [
"event-listener", "event-listener",
"event-listener-strategy", "event-listener-strategy",
@@ -168,9 +167,9 @@ dependencies = [
[[package]] [[package]]
name = "async-process" name = "async-process"
version = "2.3.1" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde3f4e40e6021d7acffc90095cbd6dc54cb593903d1de5832f435eb274b85dc" checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-io", "async-io",
@@ -181,8 +180,7 @@ dependencies = [
"cfg-if", "cfg-if",
"event-listener", "event-listener",
"futures-lite", "futures-lite",
"rustix 1.0.7", "rustix",
"tracing",
] ]
[[package]] [[package]]
@@ -198,9 +196,9 @@ dependencies = [
[[package]] [[package]]
name = "async-signal" name = "async-signal"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7605a4e50d4b06df3898d5a70bf5fde51ed9059b0434b73105193bc27acce0d" checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
dependencies = [ dependencies = [
"async-io", "async-io",
"async-lock", "async-lock",
@@ -208,10 +206,10 @@ dependencies = [
"cfg-if", "cfg-if",
"futures-core", "futures-core",
"futures-io", "futures-io",
"rustix 1.0.7", "rustix",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -392,9 +390,9 @@ checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.23.1" version = "1.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@@ -438,9 +436,9 @@ dependencies = [
[[package]] [[package]]
name = "camino" name = "camino"
version = "1.1.10" version = "1.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@@ -470,19 +468,19 @@ dependencies = [
[[package]] [[package]]
name = "cargo_toml" name = "cargo_toml"
version = "0.22.1" version = "0.22.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257" checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
dependencies = [ dependencies = [
"serde", "serde",
"toml 0.8.23", "toml 0.9.5",
] ]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.29" version = "1.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362" checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@@ -834,11 +832,11 @@ dependencies = [
[[package]] [[package]]
name = "directories" name = "directories"
version = "5.0.1" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
dependencies = [ dependencies = [
"dirs-sys 0.4.1", "dirs-sys",
] ]
[[package]] [[package]]
@@ -847,19 +845,7 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [ dependencies = [
"dirs-sys 0.5.0", "dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users 0.4.6",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -870,7 +856,7 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [ dependencies = [
"libc", "libc",
"option-ext", "option-ext",
"redox_users 0.5.0", "redox_users",
"windows-sys 0.60.2", "windows-sys 0.60.2",
] ]
@@ -979,9 +965,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.19" version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]] [[package]]
name = "either" name = "either"
@@ -1001,7 +987,7 @@ dependencies = [
"cc", "cc",
"memchr", "memchr",
"rustc_version", "rustc_version",
"toml 0.9.2", "toml 0.9.5",
"vswhom", "vswhom",
"winreg", "winreg",
] ]
@@ -1087,9 +1073,9 @@ dependencies = [
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [ dependencies = [
"concurrent-queue", "concurrent-queue",
"parking", "parking",
@@ -1283,9 +1269,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
version = "2.6.0" version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"futures-core", "futures-core",
@@ -1458,7 +1444,7 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55" checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55"
dependencies = [ dependencies = [
"rustix 1.0.7", "rustix",
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
@@ -1656,9 +1642,9 @@ dependencies = [
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.11" version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -1681,9 +1667,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.4" version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [ dependencies = [
"allocator-api2", "allocator-api2",
"equivalent", "equivalent",
@@ -1696,7 +1682,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [ dependencies = [
"hashbrown 0.15.4", "hashbrown 0.15.5",
] ]
[[package]] [[package]]
@@ -1857,9 +1843,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
@@ -1873,7 +1859,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.6.0",
"system-configuration", "system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
@@ -2046,7 +2032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown 0.15.4", "hashbrown 0.15.5",
"serde", "serde",
] ]
@@ -2061,9 +2047,9 @@ dependencies = [
[[package]] [[package]]
name = "io-uring" name = "io-uring"
version = "0.7.8" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"cfg-if", "cfg-if",
@@ -2246,9 +2232,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.174" version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -2267,7 +2253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -2278,9 +2264,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.4" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"libc", "libc",
@@ -2298,12 +2284,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.9.4" version = "0.9.4"
@@ -2435,9 +2415,9 @@ dependencies = [
[[package]] [[package]]
name = "muda" name = "muda"
version = "0.17.0" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dpi", "dpi",
@@ -2451,7 +2431,7 @@ dependencies = [
"png", "png",
"serde", "serde",
"thiserror 2.0.12", "thiserror 2.0.12",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -2503,7 +2483,7 @@ dependencies = [
[[package]] [[package]]
name = "neodlp" name = "neodlp"
version = "0.2.0" version = "0.2.2"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"directories", "directories",
@@ -3255,7 +3235,7 @@ checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"indexmap 2.10.0", "indexmap 2.10.0",
"quick-xml 0.38.0", "quick-xml 0.38.1",
"serde", "serde",
"time", "time",
] ]
@@ -3275,17 +3255,16 @@ dependencies = [
[[package]] [[package]]
name = "polling" name = "polling"
version = "3.8.0" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
"rustix 1.0.7", "rustix",
"tracing", "windows-sys 0.60.2",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -3378,9 +3357,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.95" version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@@ -3396,9 +3375,9 @@ dependencies = [
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.38.0" version = "0.38.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -3416,7 +3395,7 @@ dependencies = [
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
"socket2", "socket2 0.5.10",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
"tracing", "tracing",
@@ -3432,7 +3411,7 @@ dependencies = [
"bytes", "bytes",
"getrandom 0.3.3", "getrandom 0.3.3",
"lru-slab", "lru-slab",
"rand 0.9.1", "rand 0.9.2",
"ring", "ring",
"rustc-hash", "rustc-hash",
"rustls", "rustls",
@@ -3453,7 +3432,7 @@ dependencies = [
"cfg_aliases", "cfg_aliases",
"libc", "libc",
"once_cell", "once_cell",
"socket2", "socket2 0.5.10",
"tracing", "tracing",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -3500,9 +3479,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_core 0.9.3", "rand_core 0.9.3",
@@ -3591,29 +3570,18 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.13" version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
] ]
[[package]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.6" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 1.0.69",
]
[[package]]
name = "redox_users"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
dependencies = [ dependencies = [
"getrandom 0.2.16", "getrandom 0.2.16",
"libredox", "libredox",
@@ -3777,9 +3745,9 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.25" version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
@@ -3798,35 +3766,22 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.44" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys",
"windows-sys 0.59.0", "windows-sys 0.60.2",
]
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.29" version = "0.23.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"ring", "ring",
@@ -3859,9 +3814,9 @@ dependencies = [
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.21" version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -4044,9 +3999,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -4221,9 +4176,9 @@ dependencies = [
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.5" version = "1.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@@ -4258,9 +4213,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.10" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@@ -4281,6 +4236,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "softbuffer" name = "softbuffer"
version = "0.4.6" version = "0.4.6"
@@ -4377,7 +4342,7 @@ dependencies = [
"futures-intrusive", "futures-intrusive",
"futures-io", "futures-io",
"futures-util", "futures-util",
"hashbrown 0.15.4", "hashbrown 0.15.5",
"hashlink", "hashlink",
"indexmap 2.10.0", "indexmap 2.10.0",
"log", "log",
@@ -4766,9 +4731,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "2.6.2" version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -4816,9 +4781,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-build" name = "tauri-build"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cargo_toml", "cargo_toml",
@@ -4838,9 +4803,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-codegen" name = "tauri-codegen"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"brotli", "brotli",
@@ -4865,9 +4830,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "2.3.1" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@@ -4879,9 +4844,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin" name = "tauri-plugin"
version = "2.3.0" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" checksum = "5bd5c1e56990c70a906ef67a9851bbdba9136d26075ee9a2b19c8b46986b3e02"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"glob", "glob",
@@ -4896,9 +4861,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-dialog" name = "tauri-plugin-dialog"
version = "2.3.0" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28" checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9"
dependencies = [ dependencies = [
"log", "log",
"raw-window-handle", "raw-window-handle",
@@ -4914,9 +4879,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-fs" name = "tauri-plugin-fs"
version = "2.4.0" version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dunce", "dunce",
@@ -5007,9 +4972,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-plugin-single-instance" name = "tauri-plugin-single-instance"
version = "2.3.0" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b441b6d5d1a194e9fee0b358fe0d602ded845d0f580e1f8c8ef78ebc3c8b225d" checksum = "50a0e5a4ce43cb3a733c3aef85e8478bc769dac743c615e26639cbf5d953faf7"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@@ -5073,9 +5038,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.7.0" version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676"
dependencies = [ dependencies = [
"cookie", "cookie",
"dpi", "dpi",
@@ -5095,9 +5060,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "2.7.1" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439"
dependencies = [ dependencies = [
"gtk", "gtk",
"http", "http",
@@ -5122,9 +5087,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "2.5.0" version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"brotli", "brotli",
@@ -5160,13 +5125,12 @@ dependencies = [
[[package]] [[package]]
name = "tauri-winres" name = "tauri-winres"
version = "0.3.1" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" checksum = "fd21509dd1fa9bd355dc29894a6ff10635880732396aa38c0066c1e6c1ab8074"
dependencies = [ dependencies = [
"embed-resource", "embed-resource",
"indexmap 2.10.0", "toml 0.9.5",
"toml 0.8.23",
] ]
[[package]] [[package]]
@@ -5178,7 +5142,7 @@ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.3", "getrandom 0.3.3",
"once_cell", "once_cell",
"rustix 1.0.7", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
] ]
@@ -5291,9 +5255,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.1" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@@ -5304,10 +5268,10 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.6.0",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -5366,9 +5330,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.15" version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -5391,9 +5355,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.9.2" version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
dependencies = [ dependencies = [
"indexmap 2.10.0", "indexmap 2.10.0",
"serde", "serde",
@@ -5460,9 +5424,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_parser" name = "toml_parser"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [ dependencies = [
"winnow 0.7.12", "winnow 0.7.12",
] ]
@@ -5558,9 +5522,9 @@ dependencies = [
[[package]] [[package]]
name = "tray-icon" name = "tray-icon"
version = "0.21.0" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" checksum = "a0d92153331e7d02ec09137538996a7786fe679c629c279e82a6be762b7e6fe2"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"dirs", "dirs",
@@ -5595,7 +5559,7 @@ dependencies = [
"http", "http",
"httparse", "httparse",
"log", "log",
"rand 0.9.1", "rand 0.9.2",
"sha1", "sha1",
"thiserror 2.0.12", "thiserror 2.0.12",
"utf-8", "utf-8",
@@ -5922,13 +5886,13 @@ dependencies = [
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
"rustix 0.38.44", "rustix",
"scoped-tls", "scoped-tls",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
@@ -5936,21 +5900,21 @@ dependencies = [
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.10" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"rustix 0.38.44", "rustix",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
] ]
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.32.8" version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [ dependencies = [
"bitflags 2.9.1", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
@@ -5960,9 +5924,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml 0.37.5", "quick-xml 0.37.5",
@@ -5971,9 +5935,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [ dependencies = [
"dlib", "dlib",
"log", "log",
@@ -6046,9 +6010,9 @@ dependencies = [
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@@ -6091,11 +6055,11 @@ dependencies = [
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.6.0" version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
dependencies = [ dependencies = [
"redox_syscall", "libredox",
"wasite", "wasite",
] ]
@@ -6300,7 +6264,7 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [ dependencies = [
"windows-targets 0.53.2", "windows-targets 0.53.3",
] ]
[[package]] [[package]]
@@ -6351,10 +6315,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.53.2" version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [ dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.0", "windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0", "windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0", "windows_i686_gnu 0.53.0",
@@ -6678,7 +6643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
dependencies = [ dependencies = [
"libc", "libc",
"rustix 1.0.7", "rustix",
] ]
[[package]] [[package]]
@@ -6707,9 +6672,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f45e98bc7e6f0988276012797855613cd8269e23b5be62cc4e5d28b7e515d" checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor", "async-executor",
@@ -6741,9 +6706,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.8.0" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c8e4e14dcdd9d97a98b189cd1220f30e8394ad271e8c987da84f73693862c2" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [ dependencies = [
"proc-macro-crate 3.3.0", "proc-macro-crate 3.3.0",
"proc-macro2", "proc-macro2",
@@ -6826,9 +6791,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.11.2" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "neodlp" name = "neodlp"
version = "0.2.0" version = "0.2.2"
description = "NeoDLP" description = "NeoDLP"
authors = ["neosubhamoy <hey@neosubhamoy.com>"] authors = ["neosubhamoy <hey@neosubhamoy.com>"]
edition = "2021" edition = "2021"
@@ -27,9 +27,9 @@ tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "*" tokio-tungstenite = "*"
sqlx = { version = "0.8", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] } sqlx = { version = "0.8", features = [ "sqlite", "runtime-tokio", "tls-native-tls" ] }
base64 = "0.22" base64 = "0.22"
directories = "5.0" directories = "6.0"
futures-util = "0.3" futures-util = "0.3"
tauri-plugin-opener = "2" tauri-plugin-opener = "2.4.0"
tauri-plugin-shell = "2" tauri-plugin-shell = "2"
tauri-plugin-fs = "2" tauri-plugin-fs = "2"
tauri-plugin-os = "2" tauri-plugin-os = "2"
@@ -38,7 +38,7 @@ tauri-plugin-sql = { version = "2", features = ["sqlite"] }
tauri-plugin-process = "2" tauri-plugin-process = "2"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2" tauri-plugin-single-instance = "2.3.2"
tauri-plugin-updater = "2" tauri-plugin-updater = "2"
[workspace] [workspace]

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f908be736c63cfc624ac1e4bce65eb70f167865f7df6e4e66e473043acfe164a
size 80263264

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f908be736c63cfc624ac1e4bce65eb70f167865f7df6e4e66e473043acfe164a
size 80263264

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:418ba93234679b06c3a8e2f653422a78dd855b4bc5800c960623374468b85216
size 140249600

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7a5290ae0ef64702228565b65295ab0a006671c54fdc150f9e5247f5206ea439
size 141267400

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2890fc88a6b99fb7761dae6acd8ac531083a60d75ac82c09c5fe93d10f67cc81
size 80089672

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2890fc88a6b99fb7761dae6acd8ac531083a60d75ac82c09c5fe93d10f67cc81
size 80089672

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:99cc32fddcde37108bba108993ae5091609d493970583719385099270bd279b7
size 140052992

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:165e68e3df3ba78296b6e4d060906abb0e8f235e103180d0f058cb7f5dea3513
size 141063880

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f061f5be11e96c3764442b5339f91102316ac9a7eb8270f017e7912ed0384eba oid sha256:c2fea6db305fbfd58b55d690be0b506ba384bc91995c24a278f0d26989d7d615
size 18112457 size 18360370

View File

@@ -15,6 +15,16 @@
"args": true, "args": true,
"sidecar": true "sidecar": true
}, },
{
"name": "binaries/ffmpeg",
"args": true,
"sidecar": true
},
{
"name": "binaries/ffprobe",
"args": true,
"sidecar": true
},
{ {
"name": "pkexec", "name": "pkexec",
"cmd": "pkexec", "cmd": "pkexec",
@@ -29,6 +39,16 @@
"name": "binaries/yt-dlp", "name": "binaries/yt-dlp",
"args": true, "args": true,
"sidecar": true "sidecar": true
},
{
"name": "binaries/ffmpeg",
"args": true,
"sidecar": true
},
{
"name": "binaries/ffprobe",
"args": true,
"sidecar": true
} }
] ]
} }

View File

@@ -12,6 +12,6 @@ license = "MIT"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "*" tokio-tungstenite = "*"
futures-util = "0.3" futures-util = "0.3"
directories = "5.0" directories = "6.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e607b7f079c4eb0dc666ffca152f225020f8022c8c014dd94d91e6072f57228d
size 79945800

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e607b7f079c4eb0dc666ffca152f225020f8022c8c014dd94d91e6072f57228d
size 79945800

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c49b5913c9a107120c86b401af95df7965003f7fc6dbb4436f1f03c8ba391e8b
size 127473664

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3ee15e5145c9eb4775c193ab824c592d4ff3744bb7f283f8db29bd3c3c961589
size 79928672

View File

@@ -2,7 +2,7 @@
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "NeoDLP", "productName": "NeoDLP",
"mainBinaryName": "neodlp", "mainBinaryName": "neodlp",
"version": "0.2.0", "version": "0.2.2",
"identifier": "com.neosubhamoy.neodlp", "identifier": "com.neosubhamoy.neodlp",
"build": { "build": {
"beforeDevCommand": "npm run dev", "beforeDevCommand": "npm run dev",

View File

@@ -36,11 +36,10 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"externalBin": [ "externalBin": [
"binaries/yt-dlp" "binaries/yt-dlp",
"binaries/ffmpeg",
"binaries/ffprobe"
], ],
"resources": {
"resources/binaries/ffmpeg-x86_64-unknown-linux-gnu": "binaries/ffmpeg-x86_64"
},
"linux": { "linux": {
"deb": { "deb": {
"files": { "files": {

View File

@@ -36,11 +36,12 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"externalBin": [ "externalBin": [
"binaries/yt-dlp" "binaries/yt-dlp",
"binaries/ffmpeg",
"binaries/ffprobe"
], ],
"resources": { "resources": {
"target/aarch64-apple-darwin/release/neodlp-msghost": "neodlp-msghost", "target/aarch64-apple-darwin/release/neodlp-msghost": "neodlp-msghost",
"resources/binaries/ffmpeg-aarch64-apple-darwin": "binaries/ffmpeg-aarch64",
"resources/msghost-manifest/macos/chrome.json": "neodlp-msghost.json", "resources/msghost-manifest/macos/chrome.json": "neodlp-msghost.json",
"resources/msghost-manifest/macos/firefox.json": "neodlp-msghost-moz.json", "resources/msghost-manifest/macos/firefox.json": "neodlp-msghost-moz.json",
"resources/autostart/macos/autostart.plist": "neodlp-autostart.plist" "resources/autostart/macos/autostart.plist": "neodlp-autostart.plist"

View File

@@ -36,11 +36,12 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"externalBin": [ "externalBin": [
"binaries/yt-dlp" "binaries/yt-dlp",
"binaries/ffmpeg",
"binaries/ffprobe"
], ],
"resources": { "resources": {
"target/x86_64-apple-darwin/release/neodlp-msghost": "neodlp-msghost", "target/x86_64-apple-darwin/release/neodlp-msghost": "neodlp-msghost",
"resources/binaries/ffmpeg-x86_64-apple-darwin": "binaries/ffmpeg-x86_64",
"resources/msghost-manifest/macos/chrome.json": "neodlp-msghost.json", "resources/msghost-manifest/macos/chrome.json": "neodlp-msghost.json",
"resources/msghost-manifest/macos/firefox.json": "neodlp-msghost-moz.json", "resources/msghost-manifest/macos/firefox.json": "neodlp-msghost-moz.json",
"resources/autostart/macos/autostart.plist": "neodlp-autostart.plist" "resources/autostart/macos/autostart.plist": "neodlp-autostart.plist"

View File

@@ -36,10 +36,11 @@
"icons/icon.ico" "icons/icon.ico"
], ],
"externalBin": [ "externalBin": [
"binaries/yt-dlp" "binaries/yt-dlp",
"binaries/ffmpeg",
"binaries/ffprobe"
], ],
"resources": { "resources": {
"resources/binaries/ffmpeg-x86_64-pc-windows-msvc.exe": "binaries/ffmpeg-x86_64.exe",
"target/release/neodlp-msghost.exe": "neodlp-msghost.exe", "target/release/neodlp-msghost.exe": "neodlp-msghost.exe",
"resources/msghost-manifest/windows/chrome.json": "neodlp-msghost.json", "resources/msghost-manifest/windows/chrome.json": "neodlp-msghost.json",
"resources/msghost-manifest/windows/firefox.json": "neodlp-msghost-moz.json" "resources/msghost-manifest/windows/firefox.json": "neodlp-msghost-moz.json"

View File

@@ -1,6 +1,5 @@
import { ThemeProvider } from "@/providers/themeProvider"; import { ThemeProvider } from "@/providers/themeProvider";
import { TooltipProvider } from "@/components/ui/tooltip"; import { TooltipProvider } from "@/components/ui/tooltip";
import { Toaster } from "@/components/ui/toaster";
import { AppContext } from "@/providers/appContextProvider"; import { AppContext } from "@/providers/appContextProvider";
import { DownloadState } from "@/types/download"; import { DownloadState } from "@/types/download";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@@ -25,11 +24,10 @@ import { useNavigate } from "react-router-dom";
import { platform } from "@tauri-apps/plugin-os"; import { platform } from "@tauri-apps/plugin-os";
import { useMacOsRegisterer } from "@/helpers/use-macos-registerer"; import { useMacOsRegisterer } from "@/helpers/use-macos-registerer";
import useAppUpdater from "@/helpers/use-app-updater"; import useAppUpdater from "@/helpers/use-app-updater";
import { useToast } from "@/hooks/use-toast"; import { Toaster as Sonner } from "@/components/ui/sonner";
import { toast } from "sonner";
export default function App({ children }: { children: React.ReactNode }) { export default function App({ children }: { children: React.ReactNode }) {
const { toast } = useToast();
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates(); const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
const { data: settings, isSuccess: isSuccessFetchingSettings } = useFetchAllSettings(); const { data: settings, isSuccess: isSuccessFetchingSettings } = useFetchAllSettings();
const { data: kvPairs, isSuccess: isSuccessFetchingKvPairs } = useFetchAllkVPairs(); const { data: kvPairs, isSuccess: isSuccessFetchingKvPairs } = useFetchAllkVPairs();
@@ -43,6 +41,8 @@ export default function App({ children }: { children: React.ReactNode }) {
const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath); const tempDownloadDirPath = useBasePathsStore((state) => state.tempDownloadDirPath);
const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath); const downloadDirPath = useBasePathsStore((state) => state.downloadDirPath);
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings); // const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings); const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey); const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
@@ -121,15 +121,26 @@ export default function App({ children }: { children: React.ReactNode }) {
jsonOutput += line; jsonOutput += line;
}); });
command.on('close', async () => { command.on('close', async (data) => {
if (data.code !== 0) {
console.error(`yt-dlp failed to fetch metadata with code ${data.code}`);
resolve(null);
} else {
try { try {
const data: RawVideoInfo = JSON.parse(jsonOutput); const matchedJson = jsonOutput.match(/{.*}/);
resolve(data); if (!matchedJson) {
console.error(`Failed to match JSON: ${jsonOutput}`);
resolve(null);
return;
}
const parsedJson: RawVideoInfo = JSON.parse(matchedJson[0]);
resolve(parsedJson);
} }
catch (e) { catch (e) {
console.error(`Failed to parse JSON: ${e}`); console.error(`Failed to parse JSON: ${e}`);
resolve(null); resolve(null);
} }
}
}); });
command.on('error', error => { command.on('error', error => {
@@ -137,7 +148,9 @@ export default function App({ children }: { children: React.ReactNode }) {
resolve(null); resolve(null);
}); });
command.spawn().catch(e => { command.spawn().then(child => {
setSearchPid(child.pid);
}).catch(e => {
console.error(`Failed to spawn command: ${e}`); console.error(`Failed to spawn command: ${e}`);
resolve(null); resolve(null);
}); });
@@ -165,10 +178,8 @@ export default function App({ children }: { children: React.ReactNode }) {
let videoMetadata = await fetchVideoMetadata(url, selectedFormat, isPlaylist && playlistIndex && typeof playlistIndex === 'string' ? playlistIndex : undefined); let videoMetadata = await fetchVideoMetadata(url, selectedFormat, isPlaylist && playlistIndex && typeof playlistIndex === 'string' ? playlistIndex : undefined);
if (!videoMetadata) { if (!videoMetadata) {
console.error('Failed to fetch video metadata'); console.error('Failed to fetch video metadata');
toast({ toast.error("Download Failed", {
title: 'Download Failed', description: "yt-dlp failed to fetch video metadata. Please try again later.",
description: 'yt-dlp failed to fetch video metadata. Please try again later.',
variant: 'destructive',
}); });
return; return;
} }
@@ -197,8 +208,8 @@ export default function App({ children }: { children: React.ReactNode }) {
'status:%(progress.status)s,progress:%(progress._percent_str)s,speed:%(progress.speed)f,downloaded:%(progress.downloaded_bytes)d,total:%(progress.total_bytes)d,eta:%(progress.eta)d', 'status:%(progress.status)s,progress:%(progress._percent_str)s,speed:%(progress.speed)f,downloaded:%(progress.downloaded_bytes)d,total:%(progress.total_bytes)d,eta:%(progress.eta)d',
'--output', '--output',
tempDownloadPathForYtdlp, tempDownloadPathForYtdlp,
'--ffmpeg-location', // '--ffmpeg-location',
ffmpegPath, // ffmpegPath,
'-f', '-f',
selectedFormat, selectedFormat,
'--no-mtime', '--no-mtime',
@@ -265,7 +276,7 @@ export default function App({ children }: { children: React.ReactNode }) {
console.log('Starting download with args:', args); console.log('Starting download with args:', args);
const command = Command.sidecar('binaries/yt-dlp', args); const command = Command.sidecar('binaries/yt-dlp', args);
command.on('close', async data => { command.on('close', async (data) => {
if (data.code !== 0) { if (data.code !== 0) {
console.error(`Download failed with code ${data.code}`); console.error(`Download failed with code ${data.code}`);
if (!isErrorExpected) { if (!isErrorExpected) {
@@ -273,19 +284,10 @@ export default function App({ children }: { children: React.ReactNode }) {
setErroredDownloadId(downloadId); setErroredDownloadId(downloadId);
} }
} else { } else {
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
onSuccess: (data) => {
console.log("Download status updated successfully:", data);
queryClient.invalidateQueries({ queryKey: ['download-states'] });
},
onError: (error) => {
console.error("Failed to update download status:", error);
}
})
if (await fs.exists(tempDownloadPath)) { if (await fs.exists(tempDownloadPath)) {
downloadFilePath = await generateSafeFilePath(downloadFilePath); downloadFilePath = await generateSafeFilePath(downloadFilePath);
await fs.rename(tempDownloadPath, downloadFilePath); await fs.copyFile(tempDownloadPath, downloadFilePath);
await fs.remove(tempDownloadPath);
} }
downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath }, { downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath }, {
@@ -297,11 +299,23 @@ export default function App({ children }: { children: React.ReactNode }) {
console.error("Failed to update download filepath:", error); console.error("Failed to update download filepath:", error);
} }
}) })
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
onSuccess: (data) => {
console.log("Download status updated successfully:", data);
queryClient.invalidateQueries({ queryKey: ['download-states'] });
},
onError: (error) => {
console.error("Failed to update download status:", error);
}
})
} }
}); });
command.on('error', error => { command.on('error', error => {
console.error(`Error: ${error}`); console.error(`Error: ${error}`);
setIsErrored(true);
setErroredDownloadId(downloadId);
}); });
command.stdout.on('data', line => { command.stdout.on('data', line => {
@@ -463,9 +477,11 @@ export default function App({ children }: { children: React.ReactNode }) {
const pauseDownload = async (downloadState: DownloadState) => { const pauseDownload = async (downloadState: DownloadState) => {
try { try {
if ((downloadState.download_status === 'downloading' && downloadState.process_id) || (downloadState.download_status === 'starting' && downloadState.process_id)) {
setIsErrorExpected(true); // Set error expected to true to handle UI state setIsErrorExpected(true); // Set error expected to true to handle UI state
console.log("Killing process with PID:", downloadState.process_id); console.log("Killing process with PID:", downloadState.process_id);
await invoke('kill_all_process', { pid: downloadState.process_id }); await invoke('kill_all_process', { pid: downloadState.process_id });
}
downloadStatusUpdater.mutate({ download_id: downloadState.download_id, download_status: 'paused' }, { downloadStatusUpdater.mutate({ download_id: downloadState.download_id, download_status: 'paused' }, {
onSuccess: (data) => { onSuccess: (data) => {
console.log("Download status updated successfully:", data); console.log("Download status updated successfully:", data);
@@ -845,10 +861,8 @@ export default function App({ children }: { children: React.ReactNode }) {
// show a toast and pause the download when yt-dlp exits unexpectedly // show a toast and pause the download when yt-dlp exits unexpectedly
useEffect(() => { useEffect(() => {
if (isErrored && !isErrorExpected) { if (isErrored && !isErrorExpected) {
toast({ toast.error("Download Failed", {
title: "Download Failed",
description: "yt-dlp exited unexpectedly. Please try again later", description: "yt-dlp exited unexpectedly. Please try again later",
variant: "destructive",
}); });
if (erroredDownloadId) { if (erroredDownloadId) {
downloadStatusUpdater.mutate({ download_id: erroredDownloadId, download_status: 'paused' }, { downloadStatusUpdater.mutate({ download_id: erroredDownloadId, download_status: 'paused' }, {
@@ -884,7 +898,7 @@ export default function App({ children }: { children: React.ReactNode }) {
<ThemeProvider defaultTheme={APP_THEME || "system"} storageKey="vite-ui-theme"> <ThemeProvider defaultTheme={APP_THEME || "system"} storageKey="vite-ui-theme">
<TooltipProvider delayDuration={1000}> <TooltipProvider delayDuration={1000}>
{children} {children}
<Toaster /> <Sonner closeButton />
</TooltipProvider> </TooltipProvider>
</ThemeProvider> </ThemeProvider>
</AppContext.Provider> </AppContext.Provider>

View File

@@ -1,5 +1,13 @@
import * as React from "react" import * as React from "react"
import * as RechartsPrimitive from "recharts" import * as RechartsPrimitive from "recharts"
import type { LegendPayload } from "recharts/types/component/DefaultLegendContent"
import {
NameType,
Payload,
ValueType,
} from "recharts/types/component/DefaultTooltipContent"
import type { Props as LegendProps } from "recharts/types/component/Legend"
import { TooltipContentProps } from "recharts/types/component/Tooltip"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
@@ -20,6 +28,36 @@ type ChartContextProps = {
config: ChartConfig config: ChartConfig
} }
export type CustomTooltipProps = TooltipContentProps<ValueType, NameType> & {
className?: string
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
labelFormatter?: (
label: TooltipContentProps<number, string>["label"],
payload: TooltipContentProps<number, string>["payload"]
) => React.ReactNode
formatter?: (
value: number | string,
name: string,
item: Payload<number | string, string>,
index: number,
payload: ReadonlyArray<Payload<number | string, string>>
) => React.ReactNode
labelClassName?: string
color?: string
}
export type ChartLegendContentProps = {
className?: string
hideIcon?: boolean
verticalAlign?: LegendProps["verticalAlign"]
payload?: LegendPayload[]
nameKey?: string
}
const ChartContext = React.createContext<ChartContextProps | null>(null) const ChartContext = React.createContext<ChartContextProps | null>(null)
function useChart() { function useChart() {
@@ -82,8 +120,8 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
__html: Object.entries(THEMES) __html: Object.entries(THEMES)
.map( .map(
([theme, prefix]) => ` ([theme, prefix]) => `
${prefix} [data-chart=${id}] { ${prefix} [data-chart=${id}] {
${colorConfig ${colorConfig
.map(([key, itemConfig]) => { .map(([key, itemConfig]) => {
const color = const color =
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
@@ -91,8 +129,8 @@ ${colorConfig
return color ? ` --color-${key}: ${color};` : null return color ? ` --color-${key}: ${color};` : null
}) })
.join("\n")} .join("\n")}
} }
` `
) )
.join("\n"), .join("\n"),
}} }}
@@ -105,25 +143,18 @@ const ChartTooltip = RechartsPrimitive.Tooltip
function ChartTooltipContent({ function ChartTooltipContent({
active, active,
payload, payload,
label,
className, className,
indicator = "dot", indicator = "dot",
hideLabel = false, hideLabel = false,
hideIndicator = false, hideIndicator = false,
label,
labelFormatter, labelFormatter,
labelClassName,
formatter, formatter,
labelClassName,
color, color,
nameKey, nameKey,
labelKey, labelKey,
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> & }: CustomTooltipProps) {
React.ComponentProps<"div"> & {
hideLabel?: boolean
hideIndicator?: boolean
indicator?: "line" | "dot" | "dashed"
nameKey?: string
labelKey?: string
}) {
const { config } = useChart() const { config } = useChart()
const tooltipLabel = React.useMemo(() => { const tooltipLabel = React.useMemo(() => {
@@ -134,11 +165,15 @@ function ChartTooltipContent({
const [item] = payload const [item] = payload
const key = `${labelKey || item?.dataKey || item?.name || "value"}` const key = `${labelKey || item?.dataKey || item?.name || "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key) const itemConfig = getPayloadConfigFromPayload(config, item, key)
const value = const value = (() => {
const v =
!labelKey && typeof label === "string" !labelKey && typeof label === "string"
? config[label as keyof typeof config]?.label || label ? config[label as keyof typeof config]?.label ?? label
: itemConfig?.label : itemConfig?.label
return typeof v === "string" || typeof v === "number" ? v : undefined
})()
if (labelFormatter) { if (labelFormatter) {
return ( return (
<div className={cn("font-medium", labelClassName)}> <div className={cn("font-medium", labelClassName)}>
@@ -254,11 +289,7 @@ function ChartLegendContent({
payload, payload,
verticalAlign = "bottom", verticalAlign = "bottom",
nameKey, nameKey,
}: React.ComponentProps<"div"> & }: ChartLegendContentProps) {
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean
nameKey?: string
}) {
const { config } = useChart() const { config } = useChart()
if (!payload?.length) { if (!payload?.length) {

View File

@@ -15,6 +15,12 @@ const Toaster = ({ ...props }: ToasterProps) => {
"--normal-border": "var(--border)", "--normal-border": "var(--border)",
} as React.CSSProperties } as React.CSSProperties
} }
toastOptions={{
classNames: {
toast: "group",
icon: "group-data-[type=error]:!text-red-500 group-data-[type=success]:!text-green-500 group-data-[type=warning]:!text-amber-500 group-data-[type=info]:!text-sky-500",
},
}}
{...props} {...props}
/> />
) )

View File

@@ -1,11 +1,10 @@
import { useToast } from "@/hooks/use-toast"; import { toast } from "sonner";
import { useResetSettings, useSaveSettingsKey } from "@/services/mutations"; import { useResetSettings, useSaveSettingsKey } from "@/services/mutations";
import { useSettingsPageStatesStore } from "@/services/store"; import { useSettingsPageStatesStore } from "@/services/store";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
export function useSettings() { export function useSettings() {
const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const setSettingsKey = useSettingsPageStatesStore(state => state.setSettingsKey); const setSettingsKey = useSettingsPageStatesStore(state => state.setSettingsKey);
const resetSettingsState = useSettingsPageStatesStore(state => state.resetSettings); const resetSettingsState = useSettingsPageStatesStore(state => state.resetSettings);
@@ -22,10 +21,8 @@ export function useSettings() {
onError: (error) => { onError: (error) => {
console.error("Error saving settings key:", error); console.error("Error saving settings key:", error);
queryClient.invalidateQueries({ queryKey: ["settings"] }); queryClient.invalidateQueries({ queryKey: ["settings"] });
toast({ toast.error("Failed to update settings", {
title: "Failed to update settings",
description: `Failed to update ${key}`, description: `Failed to update ${key}`,
variant: "destructive",
}); });
} }
}); });
@@ -39,26 +36,21 @@ export function useSettings() {
resetSettingsState(); resetSettingsState();
console.log("Settings reset successfully"); console.log("Settings reset successfully");
queryClient.invalidateQueries({ queryKey: ["settings"] }); queryClient.invalidateQueries({ queryKey: ["settings"] });
toast({ toast.success("Settings reset successfully", {
title: "Settings reset successfully",
description: "All settings have been reset to default.", description: "All settings have been reset to default.",
}); });
} catch (error) { } catch (error) {
console.error("Error resetting settings:", error); console.error("Error resetting settings:", error);
toast({ toast.error("Failed to reset settings", {
title: "Failed to reset settings",
description: "Failed to reset settings to default.", description: "Failed to reset settings to default.",
variant: "destructive",
}); });
return; return;
} }
}, },
onError: (error) => { onError: (error) => {
console.error("Error resetting settings:", error); console.error("Error resetting settings:", error);
toast({ toast.error("Failed to reset settings", {
title: "Failed to reset settings",
description: "Failed to reset settings to default.", description: "Failed to reset settings to default.",
variant: "destructive",
}); });
} }
}); });

View File

@@ -5,11 +5,11 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { useToast } from "@/hooks/use-toast"; import { toast } from "sonner";
import { useAppContext } from "@/providers/appContextProvider"; import { useAppContext } from "@/providers/appContextProvider";
import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store"; import { useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useSettingsPageStatesStore } from "@/services/store";
import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils"; import { determineFileType, fileFormatFilter, formatBitrate, formatDurationString, formatFileSize, formatReleaseDate, formatYtStyleCount, isObjEmpty, sortByBitrate } from "@/utils";
import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon } from "lucide-react"; import { Calendar, Clock, DownloadCloud, Eye, Info, Loader2, Music, ThumbsUp, Video, File, ListVideo, PackageSearch, AlertCircleIcon, X } from "lucide-react";
import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup"; import { FormatSelectionGroup, FormatSelectionGroupItem } from "@/components/custom/formatSelectionGroup";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup"; import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
@@ -23,26 +23,32 @@ import { Form, FormControl, FormField, FormItem, FormMessage } from "@/component
import { config } from "@/config"; import { config } from "@/config";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { invoke } from "@tauri-apps/api/core";
const searchFormSchema = z.object({ const searchFormSchema = z.object({
url: z.string().min(1, { message: "URL is required" }) url: z.url({
.url({message: "Invalid URL format." }), error: (issue) => issue.input === undefined || issue.input === null || issue.input === ""
? "URL is required"
: "Invalid URL format"
}),
}); });
export default function DownloaderPage() { export default function DownloaderPage() {
const { fetchVideoMetadata, startDownload } = useAppContext(); const { fetchVideoMetadata, startDownload } = useAppContext();
const { toast } = useToast();
const videoUrl = useCurrentVideoMetadataStore((state) => state.videoUrl); const videoUrl = useCurrentVideoMetadataStore((state) => state.videoUrl);
const videoMetadata = useCurrentVideoMetadataStore((state) => state.videoMetadata); const videoMetadata = useCurrentVideoMetadataStore((state) => state.videoMetadata);
const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading); const isMetadataLoading = useCurrentVideoMetadataStore((state) => state.isMetadataLoading);
const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl); const requestedUrl = useCurrentVideoMetadataStore((state) => state.requestedUrl);
const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch); const autoSubmitSearch = useCurrentVideoMetadataStore((state) => state.autoSubmitSearch);
const searchPid = useCurrentVideoMetadataStore((state) => state.searchPid);
const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl); const setVideoUrl = useCurrentVideoMetadataStore((state) => state.setVideoUrl);
const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata); const setVideoMetadata = useCurrentVideoMetadataStore((state) => state.setVideoMetadata);
const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading); const setIsMetadataLoading = useCurrentVideoMetadataStore((state) => state.setIsMetadataLoading);
const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl); const setRequestedUrl = useCurrentVideoMetadataStore((state) => state.setRequestedUrl);
const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch); const setAutoSubmitSearch = useCurrentVideoMetadataStore((state) => state.setAutoSubmitSearch);
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
const setShowSearchError = useCurrentVideoMetadataStore((state) => state.setShowSearchError);
const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab); const activeDownloadModeTab = useDownloaderPageStatesStore((state) => state.activeDownloadModeTab);
const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload); const isStartingDownload = useDownloaderPageStatesStore((state) => state.isStartingDownload);
@@ -202,29 +208,44 @@ export default function DownloaderPage() {
mode: "onChange", mode: "onChange",
}) })
const watchedUrl = searchForm.watch("url"); const watchedUrl = searchForm.watch("url");
const { errors: searchFormErrors } = searchForm.formState;
function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) { function handleSearchSubmit(values: z.infer<typeof searchFormSchema>) {
setVideoMetadata(null); setVideoMetadata(null);
setSearchPid(null);
setShowSearchError(true);
setIsMetadataLoading(true); setIsMetadataLoading(true);
setSelectedDownloadFormat('best'); setSelectedDownloadFormat('best');
setSelectedCombinableVideoFormat(''); setSelectedCombinableVideoFormat('');
setSelectedCombinableAudioFormat(''); setSelectedCombinableAudioFormat('');
setSelectedSubtitles([]); setSelectedSubtitles([]);
setSelectedPlaylistVideoIndex('1'); setSelectedPlaylistVideoIndex('1');
fetchVideoMetadata(values.url).then((metadata) => { fetchVideoMetadata(values.url).then((metadata) => {
if (!metadata || (metadata._type !== 'video' && metadata._type !== 'playlist') || (metadata && metadata._type === 'video' && metadata.formats.length <= 0) || (metadata && metadata._type === 'playlist' && metadata.entries.length <= 0)) { if (!metadata || (metadata._type !== 'video' && metadata._type !== 'playlist') || (metadata && metadata._type === 'video' && metadata.formats.length <= 0) || (metadata && metadata._type === 'playlist' && metadata.entries.length <= 0)) {
toast({ const showSearchError = useCurrentVideoMetadataStore.getState().showSearchError;
title: 'Opps! No results found', if (showSearchError) {
description: 'The provided URL does not contain any downloadable content. Please check the URL and try again.', toast.error("Oops! No results found", {
variant: "destructive" description: "The provided URL does not contain any downloadable content or you are not connected to the internet. Please check the URL, your network connection and try again.",
}); });
} }
}
if (metadata && (metadata._type === 'video' || metadata._type === 'playlist') && ((metadata._type === 'video' && metadata.formats.length > 0) || (metadata._type === 'playlist' && metadata.entries.length > 0))) setVideoMetadata(metadata); if (metadata && (metadata._type === 'video' || metadata._type === 'playlist') && ((metadata._type === 'video' && metadata.formats.length > 0) || (metadata._type === 'playlist' && metadata.entries.length > 0))) setVideoMetadata(metadata);
if (metadata) console.log(metadata); if (metadata) console.log(metadata);
setIsMetadataLoading(false); setIsMetadataLoading(false);
}); });
} }
const cancelSearch = async (pid: number | null) => {
setShowSearchError(false);
if (pid) {
console.log("Killing process with PID:", pid);
await invoke('kill_all_process', { pid: pid });
}
setVideoMetadata(null);
setIsMetadataLoading(false);
};
useEffect(() => { useEffect(() => {
const updateBottomBarWidth = (): void => { const updateBottomBarWidth = (): void => {
if (containerRef.current && bottomBarRef.current) { if (containerRef.current && bottomBarRef.current) {
@@ -283,20 +304,16 @@ export default function DownloaderPage() {
// If URL is invalid, just reset the flag // If URL is invalid, just reset the flag
setAutoSubmitSearch(false); setAutoSubmitSearch(false);
setRequestedUrl(''); setRequestedUrl('');
toast({ toast.error("Invalid URL", {
title: 'Invalid URL', description: "The provided URL is not valid.",
description: 'The provided URL is not valid.',
variant: "destructive"
}); });
} }
} else { } else {
// If metadata is loading, just reset the flag // If metadata is loading, just reset the flag
setAutoSubmitSearch(false); setAutoSubmitSearch(false);
setRequestedUrl(''); setRequestedUrl('');
toast({ toast.info("Search in progress", {
title: 'Search in progress', description: "There's a search in progress, Please try again later.",
description: 'Search in progress, try again later.',
variant: "destructive"
}); });
} }
} else { } else {
@@ -338,9 +355,20 @@ export default function DownloaderPage() {
</FormItem> </FormItem>
)} )}
/> />
{isMetadataLoading && (
<Button
type="button"
variant="destructive"
size="icon"
disabled={!isMetadataLoading}
onClick={() => cancelSearch(searchPid)}
>
<X className="size-4" />
</Button>
)}
<Button <Button
type="submit" type="submit"
disabled={!videoUrl || isMetadataLoading} disabled={!videoUrl || Object.keys(searchFormErrors).length > 0 || isMetadataLoading}
> >
{isMetadataLoading ? ( {isMetadataLoading ? (
<> <>
@@ -428,7 +456,7 @@ export default function DownloaderPage() {
className="flex flex-col items-start gap-2 mb-2" className="flex flex-col items-start gap-2 mb-2"
value={selectedSubtitles} value={selectedSubtitles}
onValueChange={(value) => setSelectedSubtitles(value)} onValueChange={(value) => setSelectedSubtitles(value)}
disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'} // disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'}
> >
<p className="text-xs">Subtitle Languages</p> <p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
@@ -449,10 +477,10 @@ export default function DownloaderPage() {
value={selectedDownloadFormat} value={selectedDownloadFormat}
onValueChange={(value) => { onValueChange={(value) => {
setSelectedDownloadFormat(value); setSelectedDownloadFormat(value);
const currentlySelectedFormat = value === 'best' ? videoMetadata?.requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value); // const currentlySelectedFormat = value === 'best' ? videoMetadata?.requested_downloads[0] : allFilteredFormats.find((format) => format.format_id === value);
if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') { // if (currentlySelectedFormat?.ext !== 'mp4' && currentlySelectedFormat?.ext !== 'mkv' && currentlySelectedFormat?.ext !== 'webm') {
setSelectedSubtitles([]); // setSelectedSubtitles([]);
} // }
}} }}
> >
<p className="text-xs">Suggested</p> <p className="text-xs">Suggested</p>
@@ -532,7 +560,6 @@ export default function DownloaderPage() {
className="flex flex-col items-start gap-2 mb-2" className="flex flex-col items-start gap-2 mb-2"
value={selectedSubtitles} value={selectedSubtitles}
onValueChange={(value) => setSelectedSubtitles(value)} onValueChange={(value) => setSelectedSubtitles(value)}
disabled={selectedFormat?.ext !== 'mp4' && selectedFormat?.ext !== 'mkv' && selectedFormat?.ext !== 'webm'}
> >
<p className="text-xs">Subtitle Languages</p> <p className="text-xs">Subtitle Languages</p>
<div className="flex gap-2 flex-wrap items-center"> <div className="flex gap-2 flex-wrap items-center">
@@ -907,10 +934,8 @@ export default function DownloaderPage() {
// }); // });
} catch (error) { } catch (error) {
console.error('Download failed to start:', error); console.error('Download failed to start:', error);
toast({ toast.error("Failed to Start Download", {
title: 'Failed to Start Download', description: "There was an error initiating the download."
description: 'There was an error initiating the download.',
variant: "destructive"
}); });
} finally { } finally {
setIsStartingDownload(false); setIsStartingDownload(false);

View File

@@ -4,9 +4,9 @@ import { AspectRatio } from "@/components/ui/aspect-ratio";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { useToast } from "@/hooks/use-toast"; import { toast } from "sonner";
import { useAppContext } from "@/providers/appContextProvider"; import { useAppContext } from "@/providers/appContextProvider";
import { useDownloadActionStatesStore, useDownloaderPageStatesStore, useDownloadStatesStore, useLibraryPageStatesStore } from "@/services/store"; import { useDownloadActionStatesStore, useDownloadStatesStore, useLibraryPageStatesStore } from "@/services/store";
import { formatBitrate, formatCodec, formatDurationString, formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils"; import { formatBitrate, formatCodec, formatDurationString, formatFileSize, formatSecToTimeString, formatSpeed } from "@/utils";
import { AudioLines, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, Pause, Play, Square, Trash2, Video, X } from "lucide-react"; import { AudioLines, Clock, File, FileAudio2, FileQuestion, FileVideo2, FolderInput, ListVideo, Loader2, Music, Pause, Play, Square, Trash2, Video, X } from "lucide-react";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
@@ -21,7 +21,6 @@ import Heading from "@/components/heading";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
export default function LibraryPage() { export default function LibraryPage() {
const activeTab = useLibraryPageStatesStore(state => state.activeTab); const activeTab = useLibraryPageStatesStore(state => state.activeTab);
const setActiveTab = useLibraryPageStatesStore(state => state.setActiveTab); const setActiveTab = useLibraryPageStatesStore(state => state.setActiveTab);
@@ -33,10 +32,7 @@ export default function LibraryPage() {
const setIsCancelingDownload = useDownloadActionStatesStore(state => state.setIsCancelingDownload); const setIsCancelingDownload = useDownloadActionStatesStore(state => state.setIsCancelingDownload);
const setIsDeleteFileChecked = useDownloadActionStatesStore(state => state.setIsDeleteFileChecked); const setIsDeleteFileChecked = useDownloadActionStatesStore(state => state.setIsDeleteFileChecked);
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
const { pauseDownload, resumeDownload, cancelDownload } = useAppContext() const { pauseDownload, resumeDownload, cancelDownload } = useAppContext()
const { toast } = useToast();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const downloadStateDeleter = useDeleteDownloadState(); const downloadStateDeleter = useDeleteDownloadState();
@@ -51,24 +47,19 @@ export default function LibraryPage() {
if (filePath && await fs.exists(filePath)) { if (filePath && await fs.exists(filePath)) {
try { try {
await invoke('open_file_with_app', { filePath: filePath, appName: app }).then(() => { await invoke('open_file_with_app', { filePath: filePath, appName: app }).then(() => {
toast({ toast.info("Opening file", {
title: 'Opening file',
description: `Opening the file with ${app ? app : 'default app'}.`, description: `Opening the file with ${app ? app : 'default app'}.`,
}) })
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to open file", {
title: 'Failed to open file', description: "An error occurred while trying to open the file.",
description: 'An error occurred while trying to open the file.',
variant: "destructive"
}) })
} }
} else { } else {
toast({ toast.info("File unavailable", {
title: 'File unavailable', description: "The file you are trying to open does not exist.",
description: 'The file you are trying to open does not exist.',
variant: "destructive"
}) })
} }
} }
@@ -90,46 +81,42 @@ export default function LibraryPage() {
onSuccess: (data) => { onSuccess: (data) => {
console.log("Download State deleted successfully:", data); console.log("Download State deleted successfully:", data);
queryClient.invalidateQueries({ queryKey: ['download-states'] }); queryClient.invalidateQueries({ queryKey: ['download-states'] });
toast({ toast.success("Removed from downloads", {
title: 'Removed from downloads', description: "The download has been removed successfully.",
description: 'The download has been removed successfully.', });
})
}, },
onError: (error) => { onError: (error) => {
console.error("Failed to delete download state:", error); console.error("Failed to delete download state:", error);
toast({ toast.error("Failed to remove download", {
title: 'Failed to remove download', description: "An error occurred while trying to remove the download.",
description: 'An error occurred while trying to remove the download.', });
variant: "destructive"
})
} }
}) })
} }
const stopOngoingDownloads = async () => { const stopOngoingDownloads = async () => {
if (ongoingDownloads.length > 0) { if (ongoingDownloads.length > 0) {
setIsErrorExpected(true); // Set error expected to true to handle UI state for (const state of ongoingDownloads) {
setIsPausingDownload(state.download_id, true);
try { try {
await invoke('pause_ongoing_downloads').then(() => { await pauseDownload(state);
queryClient.invalidateQueries({ queryKey: ['download-states'] });
toast({
title: 'Stopped downloads',
description: 'All ongoing downloads are being stopped.',
});
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to stop download", {
title: 'Failed to stop downloads', description: `An error occurred while trying to stop the download for ${state.title}.`,
description: 'An error occurred while trying to stop the downloads.', });
variant: "destructive" } finally {
setIsPausingDownload(state.download_id, false);
}
}
if (ongoingDownloads.length === 0) {
toast.success("Stopped ongoing downloads", {
description: "All ongoing downloads have been stopped successfully.",
}); });
} }
} else { } else {
toast({ toast.info("No ongoing downloads", {
title: 'No ongoing downloads', description: "There are no ongoing downloads to stop.",
description: 'There are no ongoing downloads to stop.',
variant: "destructive"
}); });
} }
} }
@@ -372,16 +359,13 @@ export default function LibraryPage() {
setIsResumingDownload(state.download_id, true); setIsResumingDownload(state.download_id, true);
try { try {
await resumeDownload(state) await resumeDownload(state)
// toast({ // toast.success("Resumed Download", {
// title: 'Resumed Download', // description: "Download resumed, it will re-start shortly.",
// description: 'Download resumed, it will re-start shortly.',
// }) // })
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to Resume Download", {
title: 'Failed to Resume Download', description: "An error occurred while trying to resume the download.",
description: 'An error occurred while trying to resume the download.',
variant: "destructive"
}) })
} finally { } finally {
setIsResumingDownload(state.download_id, false); setIsResumingDownload(state.download_id, false);
@@ -409,16 +393,13 @@ export default function LibraryPage() {
setIsPausingDownload(state.download_id, true); setIsPausingDownload(state.download_id, true);
try { try {
await pauseDownload(state) await pauseDownload(state)
// toast({ // toast.success("Paused Download", {
// title: 'Paused Download', // description: "Download paused successfully.",
// description: 'Download paused successfully.',
// }) // })
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to Pause Download", {
title: 'Failed to Pause Download', description: "An error occurred while trying to pause the download."
description: 'An error occurred while trying to pause the download.',
variant: "destructive"
}) })
} finally { } finally {
setIsPausingDownload(state.download_id, false); setIsPausingDownload(state.download_id, false);
@@ -446,16 +427,13 @@ export default function LibraryPage() {
setIsCancelingDownload(state.download_id, true); setIsCancelingDownload(state.download_id, true);
try { try {
await cancelDownload(state) await cancelDownload(state)
toast({ toast.success("Canceled Download", {
title: 'Canceled Download', description: "Download canceled successfully.",
description: 'Download canceled successfully.',
}) })
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to Cancel Download", {
title: 'Failed to Cancel Download', description: "An error occurred while trying to cancel the download.",
description: 'An error occurred while trying to cancel the download.',
variant: "destructive"
}) })
} finally { } finally {
setIsCancelingDownload(state.download_id, false); setIsCancelingDownload(state.download_id, false);

View File

@@ -6,7 +6,7 @@ import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrig
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useToast } from "@/hooks/use-toast"; import { toast } from "sonner";
import { ArrowDownToLine, ArrowRight, BrushCleaning, EthernetPort, ExternalLink, FileVideo, Folder, FolderOpen, Info, Loader2, LucideIcon, Monitor, Moon, Radio, RotateCcw, RotateCw, Sun, Terminal, WandSparkles, Wifi, Wrench } from "lucide-react"; import { ArrowDownToLine, ArrowRight, BrushCleaning, EthernetPort, ExternalLink, FileVideo, Folder, FolderOpen, Info, Loader2, LucideIcon, Monitor, Moon, Radio, RotateCcw, RotateCw, Sun, Terminal, WandSparkles, Wifi, Wrench } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useEffect } from "react"; import { useEffect } from "react";
@@ -29,9 +29,12 @@ import { join } from "@tauri-apps/api/path";
import { formatSpeed } from "@/utils"; import { formatSpeed } from "@/utils";
const websocketPortSchema = z.object({ const websocketPortSchema = z.object({
port: z.coerce.number({ port: z.coerce.number<number>({
required_error: "Websocket Port is required", error: (issue) => issue.input === undefined || issue.input === null || issue.input === ""
invalid_type_error: "Websocket Port must be a valid number", ? "Websocket Port is required"
: "Websocket Port must be a valid number"
}).int({
message: "Websocket Port must be an integer"
}).min(50000, { }).min(50000, {
message: "Websocket Port must be at least 50000" message: "Websocket Port must be at least 50000"
}).max(60000, { }).max(60000, {
@@ -40,13 +43,20 @@ const websocketPortSchema = z.object({
}) })
const proxyUrlSchema = z.object({ const proxyUrlSchema = z.object({
url: z.string().min(1, { message: "Proxy URL is required" }).url({ message: "Invalid URL format" }) url: z.url({
error: (issue) => issue.input === undefined || issue.input === null || issue.input === ""
? "Proxy URL is required"
: "Invalid URL format"
})
}); });
const rateLimitSchema = z.object({ const rateLimitSchema = z.object({
rate_limit: z.coerce.number({ rate_limit: z.coerce.number<number>({
required_error: "Rate Limit is required", error: (issue) => issue.input === undefined || issue.input === null || issue.input === ""
invalid_type_error: "Rate Limit must be a valid number", ? "Rate Limit is required"
: "Rate Limit must be a valid number"
}).int({
message: "Rate Limit must be an integer"
}).min(1024, { }).min(1024, {
message: "Rate Limit must be at least 1024 bytes/s (1 KB/s)" message: "Rate Limit must be at least 1024 bytes/s (1 KB/s)"
}).max(104857600, { }).max(104857600, {
@@ -55,7 +65,6 @@ const rateLimitSchema = z.object({
}); });
export default function SettingsPage() { export default function SettingsPage() {
const { toast } = useToast();
const { setTheme } = useTheme(); const { setTheme } = useTheme();
const activeTab = useSettingsPageStatesStore(state => state.activeTab); const activeTab = useSettingsPageStatesStore(state => state.activeTab);
@@ -113,17 +122,14 @@ export default function SettingsPage() {
const openLink = async (url: string, app: string | null) => { const openLink = async (url: string, app: string | null) => {
try { try {
await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => { await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => {
toast({ toast.info("Opening link", {
title: 'Opening Link',
description: `Opening link with ${app ? app : 'default app'}.`, description: `Opening link with ${app ? app : 'default app'}.`,
}) })
}); });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
toast({ toast.error("Failed to open link", {
title: 'Failed to open link', description: "An error occurred while trying to open the link.",
description: 'An error occurred while trying to open the link.',
variant: "destructive"
}) })
} }
} }
@@ -138,20 +144,16 @@ export default function SettingsPage() {
await fs.remove(filePath); await fs.remove(filePath);
} }
} }
toast({ toast.success("Temporary Downloads Cleaned", {
title: "Temporary Downloads Cleaned",
description: "All temporary downloads have been successfully cleaned up.", description: "All temporary downloads have been successfully cleaned up.",
}); });
} catch (e) { } catch (e) {
toast({ toast.error("Temporary Downloads Cleanup Failed", {
title: "Temporary Downloads Cleanup Failed",
description: "An error occurred while trying to clean up temporary downloads. Please try again.", description: "An error occurred while trying to clean up temporary downloads. Please try again.",
variant: "destructive",
}); });
} }
} else { } else {
toast({ toast.info("No Temporary Downloads", {
title: "No Temporary Downloads",
description: "There are no temporary downloads to clean up.", description: "There are no temporary downloads to clean up.",
}); });
} }
@@ -170,16 +172,13 @@ export default function SettingsPage() {
function handleProxyUrlSubmit(values: z.infer<typeof proxyUrlSchema>) { function handleProxyUrlSubmit(values: z.infer<typeof proxyUrlSchema>) {
try { try {
saveSettingsKey('proxy_url', values.url); saveSettingsKey('proxy_url', values.url);
toast({ toast.success("Proxy URL updated", {
title: "Proxy URL updated",
description: `Proxy URL changed to ${values.url}`, description: `Proxy URL changed to ${values.url}`,
}); });
} catch (error) { } catch (error) {
console.error("Error changing proxy URL:", error); console.error("Error changing proxy URL:", error);
toast({ toast.error("Failed to change proxy URL", {
title: "Failed to change proxy URL", description: "An error occurred while trying to change the proxy URL. Please try again.",
description: "Please try again.",
variant: "destructive",
}); });
} }
} }
@@ -197,16 +196,13 @@ export default function SettingsPage() {
function handleRateLimitSubmit(values: z.infer<typeof rateLimitSchema>) { function handleRateLimitSubmit(values: z.infer<typeof rateLimitSchema>) {
try { try {
saveSettingsKey('rate_limit', values.rate_limit); saveSettingsKey('rate_limit', values.rate_limit);
toast({ toast.success("Rate Limit updated", {
title: "Rate Limit updated",
description: `Rate Limit changed to ${values.rate_limit} bytes/s`, description: `Rate Limit changed to ${values.rate_limit} bytes/s`,
}); });
} catch (error) { } catch (error) {
console.error("Error changing rate limit:", error); console.error("Error changing rate limit:", error);
toast({ toast.error("Failed to change rate limit", {
title: "Failed to change rate limit", description: "An error occurred while trying to change the rate limit. Please try again.",
description: "Please try again.",
variant: "destructive",
}); });
} }
} }
@@ -235,16 +231,13 @@ export default function SettingsPage() {
} }
}); });
saveSettingsKey('websocket_port', updatedConfig.port); saveSettingsKey('websocket_port', updatedConfig.port);
toast({ toast.success("Websocket port updated", {
title: "Websocket port updated",
description: `Websocket port changed to ${values.port}`, description: `Websocket port changed to ${values.port}`,
}); });
} catch (error) { } catch (error) {
console.error("Error changing websocket port:", error); console.error("Error changing websocket port:", error);
toast({ toast.error("Failed to change websocket port", {
title: "Failed to change websocket port", description: "An error occurred while trying to change the websocket port. Please try again.",
description: "Please try again.",
variant: "destructive",
}); });
} finally { } finally {
setIsChangingWebSocketPort(false); setIsChangingWebSocketPort(false);
@@ -482,10 +475,8 @@ export default function SettingsPage() {
} }
} catch (error) { } catch (error) {
console.error("Error selecting folder:", error); console.error("Error selecting folder:", error);
toast({ toast.error("Failed to select folder", {
title: "Failed to select folder", description: "An error occurred while trying to select the download folder. Please try again.",
description: "Please try again.",
variant: "destructive",
}); });
} }
}} }}
@@ -729,16 +720,13 @@ export default function SettingsPage() {
setIsRestartingWebSocketServer(true); setIsRestartingWebSocketServer(true);
try { try {
await invoke("restart_websocket_server"); await invoke("restart_websocket_server");
toast({ toast.success("Websocket server restarted", {
title: "Websocket server restarted",
description: "Websocket server restarted successfully.", description: "Websocket server restarted successfully.",
}); });
} catch (error) { } catch (error) {
console.error("Error restarting websocket server:", error); console.error("Error restarting websocket server:", error);
toast({ toast.error("Failed to restart websocket server", {
title: "Failed to restart websocket server", description: "An error occurred while trying to restart the websocket server. Please try again.",
description: "Please try again.",
variant: "destructive",
}); });
} finally { } finally {
setIsRestartingWebSocketServer(false); setIsRestartingWebSocketServer(false);

View File

@@ -34,11 +34,15 @@ export const useCurrentVideoMetadataStore = create<CurrentVideoMetadataStore>((s
isMetadataLoading: false, isMetadataLoading: false,
requestedUrl: '', requestedUrl: '',
autoSubmitSearch: false, autoSubmitSearch: false,
searchPid: null,
showSearchError: true,
setVideoUrl: (url) => set(() => ({ videoUrl: url })), setVideoUrl: (url) => set(() => ({ videoUrl: url })),
setVideoMetadata: (metadata) => set(() => ({ videoMetadata: metadata })), setVideoMetadata: (metadata) => set(() => ({ videoMetadata: metadata })),
setIsMetadataLoading: (isLoading) => set(() => ({ isMetadataLoading: isLoading })), setIsMetadataLoading: (isLoading) => set(() => ({ isMetadataLoading: isLoading })),
setRequestedUrl: (url) => set(() => ({ requestedUrl: url })), setRequestedUrl: (url) => set(() => ({ requestedUrl: url })),
setAutoSubmitSearch: (autoSubmit) => set(() => ({ autoSubmitSearch: autoSubmit })), setAutoSubmitSearch: (autoSubmit) => set(() => ({ autoSubmitSearch: autoSubmit })),
setSearchPid: (pid) => set(() => ({ searchPid: pid })),
setShowSearchError: (showError) => set(() => ({ showSearchError: showError }))
})); }));
export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({ export const useDownloaderPageStatesStore = create<DownloaderPageStatesStore>((set) => ({

View File

@@ -23,11 +23,15 @@ export interface CurrentVideoMetadataStore {
isMetadataLoading: boolean; isMetadataLoading: boolean;
requestedUrl: string; requestedUrl: string;
autoSubmitSearch: boolean; autoSubmitSearch: boolean;
searchPid: number | null;
showSearchError: boolean;
setVideoUrl: (url: string) => void; setVideoUrl: (url: string) => void;
setVideoMetadata: (metadata: RawVideoInfo | null) => void; setVideoMetadata: (metadata: RawVideoInfo | null) => void;
setIsMetadataLoading: (isLoading: boolean) => void; setIsMetadataLoading: (isLoading: boolean) => void;
setRequestedUrl: (url: string) => void; setRequestedUrl: (url: string) => void;
setAutoSubmitSearch: (autoSubmit: boolean) => void; setAutoSubmitSearch: (autoSubmit: boolean) => void;
setSearchPid: (pid: number | null) => void;
setShowSearchError: (showError: boolean) => void;
} }
export interface DownloaderPageStatesStore { export interface DownloaderPageStatesStore {

View File

@@ -9,7 +9,9 @@ const host = process.env.TAURI_DEV_HOST;
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
build: {
chunkSizeWarningLimit: 1024, // 1MB
},
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// //
// 1. prevent vite from obscuring rust errors // 1. prevent vite from obscuring rust errors