mirror of
https://github.com/neosubhamoy/pytubepp-helper.git
synced 2026-02-04 11:22:22 +05:30
Compare commits
10 Commits
v0.7.0-bet
...
main
23
.github/workflows/publish.yml
vendored
Normal file
23
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
|
||||||
|
name: 🚀 Publish to WinGet
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: 🛠️ Get release version
|
||||||
|
id: get-version
|
||||||
|
run: |
|
||||||
|
$VERSION="${{ github.event.release.tag_name }}" -replace '^v|[^0-9.]'
|
||||||
|
"version=$VERSION" >> $env:GITHUB_OUTPUT
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: 🚀 Send PR to winget-pkgs repo
|
||||||
|
uses: vedantmgoyal9/winget-releaser@main
|
||||||
|
with:
|
||||||
|
identifier: neosubhamoy.pytubepp-helper
|
||||||
|
version: ${{ steps.get-version.outputs.version }}
|
||||||
|
installers-regex: '\.exe$'
|
||||||
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,14 +1,9 @@
|
|||||||
### ✨ Changelog
|
### ✨ Changelog
|
||||||
|
|
||||||
- Migrated to Tauri v2
|
- Added support for Arch Linux
|
||||||
- Added support for caption download
|
- Added Extension Manager (to manage unpacked pytubepp-extension - unpacking, updating)
|
||||||
- Added new Settings page
|
- Added app theme preference option in settings
|
||||||
- Added new Notifications page (will be used for app, component updates and broadcast messages)
|
- Added update notification preference toggle in settings
|
||||||
- Added dynamic websocket server port configuration option in settings
|
|
||||||
- Added Node.js detection and installation option
|
|
||||||
- Changed RPM ffmpeg-free dependency to ffmpeg (as full ffmpeg is required for embedding captions)
|
|
||||||
- Improved single instance functionality by switching to tauri_plugin_single_instance from websocket based single instance detection
|
|
||||||
- Enabled app-updater (for supported packaging formats in Windows - exe, msi, MacOS - app)
|
|
||||||
- Minor fixes and improvements
|
- Minor fixes and improvements
|
||||||
|
|
||||||
### 📎 Minimum Requirements
|
### 📎 Minimum Requirements
|
||||||
@@ -18,9 +13,9 @@
|
|||||||
|
|
||||||
### 📝 Notes
|
### 📝 Notes
|
||||||
|
|
||||||
> IMPORTANT: Linux (Fedora) users must need to [enable](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/#_enabling_the_rpm_fusion_repositories_using_command_line_utilities) RPM Fusion free+nonfree repos before installing this update (to avoid 'ffmpeg not found' error while installing the RPM package)
|
> ⭐ **IMPORTANT:** Linux (Fedora) users must need to [enable](https://docs.fedoraproject.org/en-US/quick-docs/rpmfusion-setup/#_enabling_the_rpm_fusion_repositories_using_command_line_utilities) RPM Fusion free+nonfree repos before installing this update (to avoid 'ffmpeg not found' error while installing the RPM package)
|
||||||
|
|
||||||
> IMPORTANT: MacOS users must re-click the 'Register' button after updating
|
> ⭐ **IMPORTANT:** MacOS users must re-click the 'register to mac' icon after updating
|
||||||
|
|
||||||
> 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)
|
||||||
|
|
||||||
@@ -30,7 +25,9 @@
|
|||||||
|
|
||||||
| 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/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_0.7.0_x64_en-US.msi) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_0.7.0_x64-setup.exe) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_0.7.0_amd64.deb) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper-0.7.0-1.x86_64.rpm) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_0.7.0_x64.dmg) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_x64.app.tar.gz) |
|
| x86_64 | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_0.8.0_x64_en-US.msi) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_0.8.0_x64-setup.exe) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_0.8.0_amd64.deb) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper-0.8.0-1.x86_64.rpm) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_0.8.0_x64.dmg) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_x64.app.tar.gz) |
|
||||||
| ARM64 | N/A | N/A | N/A | N/A | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_0.7.0_aarch64.dmg) | [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.7.0-beta/pytubepp-helper_aarch64.app.tar.gz) |
|
| ARM64 | N/A | N/A | N/A | N/A | ⚠️ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_0.8.0_aarch64.dmg) | ⚠️ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/download/v0.8.0-beta/pytubepp-helper_aarch64.app.tar.gz) |
|
||||||
|
|
||||||
> ⬆️ icon indicates this packaging format supports in-built app-updater
|
> ⬆️ icon indicates this packaging format supports in-built app-updater
|
||||||
|
|
||||||
|
> ⚠️ ARM64 binaries are experimental and may not work properly on Apple Silicon Macs (You might see 'Damaged File' warning) it's because the binaries are not signed and Apple Silicon Macs don't allow unsigned apps (downloaded from internet) to be installed on the system (also I'm not planning to sign it soon as it costs 99$/year, which I can't afford RN!). If you want to use pytubepp-helper in your Apple Silicon Macs then you have to [compile it from source](https://github.com/neosubhamoy/pytubepp-helper?tab=readme-ov-file#%EF%B8%8F-contributing--building-from-source) in your Mac
|
||||||
18
README.md
18
README.md
@@ -12,7 +12,7 @@ A Helper App for PytubePP Extension/Addon to Communicate with Pytube Post Proces
|
|||||||
|
|
||||||
### 💻 Supported Platforms
|
### 💻 Supported Platforms
|
||||||
- Windows 10 (v1803 or later) / 11
|
- Windows 10 (v1803 or later) / 11
|
||||||
- Linux (Debian/Ubuntu, Fedora/RHEL base)
|
- Linux (Debian / Fedora / Arch Linux base)
|
||||||
- MacOS (v10.13 or later)
|
- MacOS (v10.13 or later)
|
||||||
|
|
||||||
### 📎 Pre-Requirements
|
### 📎 Pre-Requirements
|
||||||
@@ -25,13 +25,18 @@ A Helper App for PytubePP Extension/Addon to Communicate with Pytube Post Proces
|
|||||||
|
|
||||||
### ⬇️ Download and Installation
|
### ⬇️ Download and Installation
|
||||||
|
|
||||||
1. Download the latest [PytubePP Helper](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) release based on your OS and CPU Architecture
|
1. Download the latest [PytubePP Helper](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) release based on your OS and CPU Architecture then install it or install it directly from an available distribution channel
|
||||||
|
|
||||||
| Arch\OS | Windows | Linux | MacOS |
|
| Arch\OS | Windows | Linux | MacOS |
|
||||||
| :---- | :---- | :---- | :---- |
|
| :---- | :---- | :---- | :---- |
|
||||||
| x86_64 | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) |
|
| x86_64 | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) |
|
||||||
| ARM64 | ❌ N/A | ❌ N/A | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) |
|
| ARM64 | ❌ N/A | ❌ N/A | ✅ [Download](https://github.com/neosubhamoy/pytubepp-helper/releases/latest) |
|
||||||
|
|
||||||
|
| Platform | Distribution Channel | Installation Command / Instruction |
|
||||||
|
| :---- | :---- | :---- |
|
||||||
|
| Windows x86_64 | WinGet | `winget install pytubepp-helper` |
|
||||||
|
| Linux x86_64 (Arch) | AUR | `yay -S pytubepp-helper` |
|
||||||
|
|
||||||
2. Install all [pre-requirements](https://github.com/neosubhamoy/pytubepp-helper#-pre-requirements) and [PytubePP Extension](https://github.com/neosubhamoy/pytubepp-extension) (follow next instructions based on your OS)
|
2. Install all [pre-requirements](https://github.com/neosubhamoy/pytubepp-helper#-pre-requirements) and [PytubePP Extension](https://github.com/neosubhamoy/pytubepp-extension) (follow next instructions based on your OS)
|
||||||
|
|
||||||
> NOTE: You can install the pre-requirements from PytubePP Helper app GUI or manually running the commands in your system's terminal / command prompt, for manual installation follow this [guide](https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation).
|
> NOTE: You can install the pre-requirements from PytubePP Helper app GUI or manually running the commands in your system's terminal / command prompt, for manual installation follow this [guide](https://github.com/neosubhamoy/pytubepp#%EF%B8%8F-installation).
|
||||||
@@ -52,7 +57,7 @@ A Helper App for PytubePP Extension/Addon to Communicate with Pytube Post Proces
|
|||||||
|
|
||||||
* **🐧 LINUX:**
|
* **🐧 LINUX:**
|
||||||
|
|
||||||
> ⚠️ NOTE: Most of the Debian / RHEL based distros are supported. Tested on: debian (v12), ubuntu (v24.04 LTS), fedora (v41). If your distro is not in the tested list it doesn't mean that 'the app will not run at all', so, test it yourself and if it doesn't work then you can request us to add support for your distro via creating a github issue.
|
> ⚠️ NOTE: Most of the Debian / Fedora / Arch based distros are supported. Tested on: debian (v12), ubuntu (v24.04 LTS), fedora (v41), arch linux (latest rolling). If your distro is not in the tested list it doesn't mean that 'the app will not run at all', so, test it yourself and if it doesn't work then you can request us to add support for your distro via creating a github issue.
|
||||||
|
|
||||||
> ⚠️ Sandboxed Browsers may not work properly (eg: Flatpak, Snaps) (have issue with: Browser NativeMessaging API [read here](https://github.com/flatpak/xdg-desktop-portal/issues/655)) (But, still try it yourself to see if it works)
|
> ⚠️ Sandboxed Browsers may not work properly (eg: Flatpak, Snaps) (have issue with: Browser NativeMessaging API [read here](https://github.com/flatpak/xdg-desktop-portal/issues/655)) (But, still try it yourself to see if it works)
|
||||||
|
|
||||||
@@ -77,7 +82,7 @@ A Helper App for PytubePP Extension/Addon to Communicate with Pytube Post Proces
|
|||||||
|
|
||||||
3. Now, open PytubePP Helper app and click on the (blue) install button on the right side of 'FFmpeg' to install it. Also, install 'Node.js' and 'PytubePP' following the same step.
|
3. Now, open PytubePP Helper app and click on the (blue) install button on the right side of 'FFmpeg' to install it. Also, install 'Node.js' and 'PytubePP' following the same step.
|
||||||
|
|
||||||
4. Then, click on the 'Register' botton on the top right corner to register 'PytubePP Helper' in your system and also add it to your system's autostart entry. If you see a MacOS notification saying 'pytubepp-helper' is added as a startup app then it's done.
|
4. Then, click on the 'register to mac' icon on the top right corner to register 'PytubePP Helper' in your system and also add it to your system's autostart entry. If you see a MacOS notification saying 'pytubepp-helper' is added as a startup app then it's perfectly done.
|
||||||
|
|
||||||
5. Now click on the 'Refresh' button and you will see the 'Ready' message. Then close PytubePP Helper.
|
5. Now click on the 'Refresh' button and you will see the 'Ready' message. Then close PytubePP Helper.
|
||||||
|
|
||||||
@@ -91,17 +96,17 @@ A Helper App for PytubePP Extension/Addon to Communicate with Pytube Post Proces
|
|||||||
|
|
||||||
### 🤔 How It Works
|
### 🤔 How It Works
|
||||||
|
|
||||||
- PytubePP Helper is an intermediate communicator between PytubePP Extension and Pytube Post Processor CLI interface. It is used as a bridge to estblish communication between the System Shell / CMD and Browser Extension, as a Browser Extension can not directly talk (execute commands) with System Shell / CMD for security reasons. Browser Extensions are isolated from the system too, the only way they can communicate with the system (native apps only) is nativeMessaging API provided by Chrome (other Browsers provides it too). So, PytubePP Helper uses that API to communicate with the Browser Extension and recives it's requests and processes the data from PytubePP CLI then genrates a response and sends it to the Browser Extension. For further understanding view the system design diagram of PytubePP Helper app below:
|
- PytubePP Helper is an intermediate communicator between PytubePP Extension and Pytube Post Processor CLI interface. It is used as a bridge to estblish communication between the System Shell / CMD and Browser Extension(as Browser Extensions can't directly talk, execute commands with System Shell / CMD for security reasons they are isolated from the system) The only way a browser extension can communicate with the system (native app) is Browser nativeMessaging API (Available on Chrome, Firefox etc.). So, PytubePP Helper uses that API to communicate with the PytubePP Browser Extension (PytubePP Helper recives PytubePP Extension's requests then processes the request using PytubePP CLI and returns back the response to the Extension) For further understanding view the system design diagram of PytubePP Helper app below:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### ⚡ Technologies Used
|
### ⚡ Technologies Used
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
### 🛠️ Contributing / Building from Source
|
### 🛠️ Contributing / Building from Source
|
||||||
|
|
||||||
@@ -117,6 +122,7 @@ Want to be part of this? Feel free to contribute...!! Pull Requests are always w
|
|||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
4. Run development / build process
|
4. Run development / build process
|
||||||
|
> Make sure to run the build command once before running the dev command to avoid errors
|
||||||
```code
|
```code
|
||||||
npm run tauri dev
|
npm run tauri dev
|
||||||
```
|
```
|
||||||
|
|||||||
36
makeFilesExecutable.js
Normal file
36
makeFilesExecutable.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
const binSrc = path.join(__dirname, 'src-tauri', 'binaries');
|
||||||
|
|
||||||
|
function makeFilesExecutable() {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(binSrc)) {
|
||||||
|
console.error(`Binaries directory does not exist: ${binSrc}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = fs.readdirSync(binSrc);
|
||||||
|
const nonExeFiles = files.filter(file => !file.endsWith('.exe'));
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
for (const file of nonExeFiles) {
|
||||||
|
const filePath = path.join(binSrc, file);
|
||||||
|
if (fs.statSync(filePath).isFile()) {
|
||||||
|
execSync(`chmod +x "${filePath}"`);
|
||||||
|
console.log(`Made executable: ${file}`);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Successfully made ${count} files executable in ${binSrc}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error making files executable: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeFilesExecutable();
|
||||||
931
package-lock.json
generated
931
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "pytubepp-helper",
|
"name": "pytubepp-helper",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -11,27 +11,35 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.3",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.3",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-progress": "^1.1.2",
|
"@radix-ui/react-progress": "^1.1.2",
|
||||||
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
"@radix-ui/react-toast": "^1.2.5",
|
"@radix-ui/react-toast": "^1.2.5",
|
||||||
"@radix-ui/react-tooltip": "^1.1.7",
|
"@radix-ui/react-tooltip": "^1.1.7",
|
||||||
"@tauri-apps/api": "^2.0.0",
|
"@tauri-apps/api": "^2.0.0",
|
||||||
"@tauri-apps/plugin-fs": "^2.2.0",
|
"@tauri-apps/plugin-fs": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-http": "^2.3.0",
|
||||||
"@tauri-apps/plugin-notification": "^2.2.1",
|
"@tauri-apps/plugin-notification": "^2.2.1",
|
||||||
"@tauri-apps/plugin-os": "^2.2.0",
|
"@tauri-apps/plugin-os": "^2.2.0",
|
||||||
"@tauri-apps/plugin-process": "^2.2.0",
|
"@tauri-apps/plugin-process": "^2.2.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.0",
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
"@tauri-apps/plugin-updater": "^2.5.0",
|
"@tauri-apps/plugin-updater": "^2.5.0",
|
||||||
|
"@tauri-apps/plugin-upload": "^2.2.1",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"lucide-react": "^0.436.0",
|
"lucide-react": "^0.436.0",
|
||||||
|
"next-themes": "^0.4.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-router-dom": "^7.1.3",
|
"react-router-dom": "^7.1.3",
|
||||||
|
"sonner": "^2.0.1",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
|
|||||||
450
src-tauri/Cargo.lock
generated
450
src-tauri/Cargo.lock
generated
@@ -661,10 +661,29 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"percent-encoding 2.3.1",
|
||||||
"time 0.3.36",
|
"time 0.3.36",
|
||||||
"version_check 0.9.5",
|
"version_check 0.9.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie_store"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||||
|
dependencies = [
|
||||||
|
"cookie",
|
||||||
|
"document-features",
|
||||||
|
"idna 1.0.3",
|
||||||
|
"log 0.4.22",
|
||||||
|
"publicsuffix",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"time 0.3.36",
|
||||||
|
"url 2.5.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -847,6 +866,12 @@ 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 = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-url"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.3.11"
|
version = "0.3.11"
|
||||||
@@ -1012,6 +1037,15 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dpi"
|
name = "dpi"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1309,6 +1343,21 @@ version = "0.1.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@@ -1316,6 +1365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1383,6 +1433,7 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-macro",
|
"futures-macro",
|
||||||
@@ -1709,6 +1760,25 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes 1.7.1",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"http",
|
||||||
|
"indexmap 2.4.0",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -1842,6 +1912,7 @@ dependencies = [
|
|||||||
"bytes 1.7.1",
|
"bytes 1.7.1",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
@@ -1932,6 +2003,124 @@ dependencies = [
|
|||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_collections"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_locid"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"litemap",
|
||||||
|
"tinystr",
|
||||||
|
"writeable",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_locid_transform"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"icu_locid",
|
||||||
|
"icu_locid_transform_data",
|
||||||
|
"icu_provider",
|
||||||
|
"tinystr",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_locid_transform_data"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_normalizer"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"icu_collections",
|
||||||
|
"icu_normalizer_data",
|
||||||
|
"icu_properties",
|
||||||
|
"icu_provider",
|
||||||
|
"smallvec 1.13.2",
|
||||||
|
"utf16_iter",
|
||||||
|
"utf8_iter",
|
||||||
|
"write16",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_normalizer_data"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_properties"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"icu_collections",
|
||||||
|
"icu_locid_transform",
|
||||||
|
"icu_properties_data",
|
||||||
|
"icu_provider",
|
||||||
|
"tinystr",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_properties_data"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_provider"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"icu_locid",
|
||||||
|
"icu_provider_macros",
|
||||||
|
"stable_deref_trait",
|
||||||
|
"tinystr",
|
||||||
|
"writeable",
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "icu_provider_macros"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ident_case"
|
name = "ident_case"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@@ -1959,6 +2148,27 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||||
|
dependencies = [
|
||||||
|
"idna_adapter",
|
||||||
|
"smallvec 1.13.2",
|
||||||
|
"utf8_iter",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna_adapter"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
|
||||||
|
dependencies = [
|
||||||
|
"icu_normalizer",
|
||||||
|
"icu_properties",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@@ -2215,6 +2425,18 @@ version = "0.4.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litemap"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
@@ -3332,9 +3554,25 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psl-types"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "publicsuffix"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||||
|
dependencies = [
|
||||||
|
"idna 1.0.3",
|
||||||
|
"psl-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytubepp-helper"
|
name = "pytubepp-helper"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"directories",
|
"directories",
|
||||||
"fix-path-env",
|
"fix-path-env",
|
||||||
@@ -3344,12 +3582,14 @@ dependencies = [
|
|||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-fs",
|
"tauri-plugin-fs",
|
||||||
|
"tauri-plugin-http",
|
||||||
"tauri-plugin-notification",
|
"tauri-plugin-notification",
|
||||||
"tauri-plugin-os",
|
"tauri-plugin-os",
|
||||||
"tauri-plugin-process",
|
"tauri-plugin-process",
|
||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
|
"tauri-plugin-upload",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
]
|
]
|
||||||
@@ -3645,6 +3885,17 @@ dependencies = [
|
|||||||
"rand_core 0.3.1",
|
"rand_core 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "read-progress-stream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6435842fc2fea44b528719eb8c32203bbc1bb2f5b619fbe0c0a3d8350fd8d2a8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes 1.7.1",
|
||||||
|
"futures 0.3.30",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.1.57"
|
version = "0.1.57"
|
||||||
@@ -3719,8 +3970,12 @@ checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes 1.7.1",
|
"bytes 1.7.1",
|
||||||
|
"cookie",
|
||||||
|
"cookie_store",
|
||||||
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
@@ -3742,6 +3997,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper",
|
||||||
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -4389,6 +4645,17 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "synstructure"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sys-locale"
|
name = "sys-locale"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -4398,6 +4665,27 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.8.0",
|
||||||
|
"core-foundation 0.9.4",
|
||||||
|
"system-configuration-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "system-configuration-sys"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-deps"
|
name = "system-deps"
|
||||||
version = "6.2.2"
|
version = "6.2.2"
|
||||||
@@ -4630,6 +4918,28 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-http"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a8137a106e0741fdd357366178fc6e0597abe7d20796f53f44171a1bcec1683"
|
||||||
|
dependencies = [
|
||||||
|
"data-url",
|
||||||
|
"http",
|
||||||
|
"regex",
|
||||||
|
"reqwest",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"tauri-plugin-fs",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
"tokio",
|
||||||
|
"url 2.5.2",
|
||||||
|
"urlpattern",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-notification"
|
name = "tauri-plugin-notification"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@@ -4744,6 +5054,25 @@ dependencies = [
|
|||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tauri-plugin-upload"
|
||||||
|
version = "2.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e90ac6d3a783d4406caeae8c75aa05e96346474765517fddfd1dc313ff91aa89"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log 0.4.22",
|
||||||
|
"read-progress-stream",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tauri",
|
||||||
|
"tauri-plugin",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -4959,6 +5288,16 @@ dependencies = [
|
|||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinystr"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
|
||||||
|
dependencies = [
|
||||||
|
"displaydoc",
|
||||||
|
"zerovec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -4999,7 +5338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
|
checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.4.12",
|
"bytes 0.4.12",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"tokio-io",
|
"tokio-io",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5010,7 +5349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
|
checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils 0.7.2",
|
"crossbeam-utils 0.7.2",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5020,7 +5359,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
|
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.4.12",
|
"bytes 0.4.12",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"log 0.4.22",
|
"log 0.4.22",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5042,7 +5381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
|
checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-utils 0.7.2",
|
"crossbeam-utils 0.7.2",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log 0.4.22",
|
"log 0.4.22",
|
||||||
"mio 0.6.23",
|
"mio 0.6.23",
|
||||||
@@ -5071,7 +5410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
|
checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fnv",
|
"fnv",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5081,7 +5420,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
|
checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.4.12",
|
"bytes 0.4.12",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"iovec",
|
"iovec",
|
||||||
"mio 0.6.23",
|
"mio 0.6.23",
|
||||||
"tokio-io",
|
"tokio-io",
|
||||||
@@ -5094,7 +5433,7 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c"
|
checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"tokio-io",
|
"tokio-io",
|
||||||
]
|
]
|
||||||
@@ -5456,6 +5795,18 @@ version = "0.7.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf16_iter"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8_iter"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -5733,7 +6084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "319bacd7682c7dfe1444e7cb1aed23bf5b1d837d722925f531e1665bd21a4603"
|
checksum = "319bacd7682c7dfe1444e7cb1aed23bf5b1d837d722925f531e1665bd21a4603"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 0.4.12",
|
"bytes 0.4.12",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"hyper 0.10.16",
|
"hyper 0.10.16",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"rand 0.6.5",
|
"rand 0.6.5",
|
||||||
@@ -5757,7 +6108,7 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes 0.4.12",
|
"bytes 0.4.12",
|
||||||
"futures",
|
"futures 0.1.31",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"rand 0.6.5",
|
"rand 0.6.5",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
@@ -6257,6 +6608,18 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "write16"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "writeable"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.48.1"
|
version = "0.48.1"
|
||||||
@@ -6352,6 +6715,30 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoke"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"stable_deref_trait",
|
||||||
|
"yoke-derive",
|
||||||
|
"zerofrom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yoke-derive"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.5.0"
|
version = "5.5.0"
|
||||||
@@ -6436,12 +6823,55 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerofrom"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
|
||||||
|
dependencies = [
|
||||||
|
"zerofrom-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerofrom-derive"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
"synstructure",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerovec"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
|
||||||
|
dependencies = [
|
||||||
|
"yoke",
|
||||||
|
"zerofrom",
|
||||||
|
"zerovec-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerovec-derive"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.98",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "pytubepp-helper"
|
name = "pytubepp-helper"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
description = "PytubePP Helper"
|
description = "PytubePP Helper"
|
||||||
authors = ["neosubhamoy"]
|
authors = ["neosubhamoy"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -25,6 +25,8 @@ tauri-plugin-fs = "2"
|
|||||||
tauri-plugin-os = "2"
|
tauri-plugin-os = "2"
|
||||||
tauri-plugin-process = "2"
|
tauri-plugin-process = "2"
|
||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
|
tauri-plugin-http = "2"
|
||||||
|
tauri-plugin-upload = "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"
|
||||||
|
|||||||
BIN
src-tauri/binaries/sevenzip-aarch64-apple-darwin
Normal file
BIN
src-tauri/binaries/sevenzip-aarch64-apple-darwin
Normal file
Binary file not shown.
BIN
src-tauri/binaries/sevenzip-x86_64-apple-darwin
Normal file
BIN
src-tauri/binaries/sevenzip-x86_64-apple-darwin
Normal file
Binary file not shown.
BIN
src-tauri/binaries/sevenzip-x86_64-pc-windows-msvc.exe
Normal file
BIN
src-tauri/binaries/sevenzip-x86_64-pc-windows-msvc.exe
Normal file
Binary file not shown.
BIN
src-tauri/binaries/sevenzip-x86_64-unknown-linux-gnu
Normal file
BIN
src-tauri/binaries/sevenzip-x86_64-unknown-linux-gnu
Normal file
Binary file not shown.
@@ -14,8 +14,15 @@
|
|||||||
"process:default",
|
"process:default",
|
||||||
"notification:default",
|
"notification:default",
|
||||||
"updater:default",
|
"updater:default",
|
||||||
|
"upload:default",
|
||||||
"core:window:allow-hide",
|
"core:window:allow-hide",
|
||||||
"fs:allow-app-write",
|
"fs:allow-app-write",
|
||||||
"fs:allow-app-write-recursive"
|
"fs:allow-app-write-recursive",
|
||||||
|
{
|
||||||
|
"identifier": "http:default",
|
||||||
|
"allow": [
|
||||||
|
{ "url": "https://github.com" }
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,13 @@
|
|||||||
"--version"
|
"--version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "is-pacman-installed",
|
||||||
|
"cmd": "pacman",
|
||||||
|
"args": [
|
||||||
|
"--version"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "is-python3-installed",
|
"name": "is-python3-installed",
|
||||||
"cmd": "python3",
|
"cmd": "python3",
|
||||||
@@ -121,6 +128,21 @@
|
|||||||
},
|
},
|
||||||
"--raw-info"
|
"--raw-info"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "binaries/sevenzip",
|
||||||
|
"args": true,
|
||||||
|
"sidecar": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "shell:allow-spawn",
|
||||||
|
"allow": [
|
||||||
|
{
|
||||||
|
"name": "binaries/sevenzip",
|
||||||
|
"args": true,
|
||||||
|
"sidecar": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,17 @@ use std::path::PathBuf;
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
pub theme: String,
|
||||||
|
pub notify_updates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { port: 3030 }
|
Self {
|
||||||
|
port: 3030,
|
||||||
|
theme: "system".to_string(),
|
||||||
|
notify_updates: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,17 @@ use std::path::PathBuf;
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
pub theme: String,
|
||||||
|
pub notify_updates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { port: 3030 }
|
Self {
|
||||||
|
port: 3030,
|
||||||
|
theme: "system".to_string(),
|
||||||
|
notify_updates: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,28 @@ struct WebSocketState {
|
|||||||
config: Config,
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn is_port_available(port: u16) -> bool {
|
||||||
|
match TcpListener::bind(format!("127.0.0.1:{}", port)).await {
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_port_availability(port: u16, max_attempts: u32) -> Result<(), String> {
|
||||||
|
let mut attempts = 0;
|
||||||
|
while attempts < max_attempts {
|
||||||
|
if is_port_available(port).await {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
attempts += 1;
|
||||||
|
}
|
||||||
|
Err(format!(
|
||||||
|
"Port {} did not become available after {} attempts",
|
||||||
|
port, max_attempts
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_websocket_server(app_handle: tauri::AppHandle, port: u16) -> Result<(), String> {
|
async fn start_websocket_server(app_handle: tauri::AppHandle, port: u16) -> Result<(), String> {
|
||||||
let addr = format!("127.0.0.1:{}", port);
|
let addr = format!("127.0.0.1:{}", port);
|
||||||
|
|
||||||
@@ -43,15 +65,22 @@ async fn start_websocket_server(app_handle: tauri::AppHandle, port: u16) -> Resu
|
|||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
if let Some(old_abort) = state.server_abort.take() {
|
if let Some(old_abort) = state.server_abort.take() {
|
||||||
let _ = old_abort.send(());
|
let _ = old_abort.send(());
|
||||||
// Give it a moment to shut down
|
// Wait for the port to become available
|
||||||
sleep(Duration::from_millis(200)).await;
|
wait_for_port_availability(port, 6).await?; // Try for 3 seconds (6 attempts * 500ms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now try to bind to the port
|
// Now try to bind to the port
|
||||||
let listener = TcpListener::bind(&addr)
|
let listener = match TcpListener::bind(&addr).await {
|
||||||
.await
|
Ok(l) => l,
|
||||||
.map_err(|e| format!("Failed to bind to port {}: {}", port, e))?;
|
Err(_e) => {
|
||||||
|
// One final attempt to wait and retry
|
||||||
|
sleep(Duration::from_secs(1)).await;
|
||||||
|
TcpListener::bind(&addr)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to bind to port {}: {}", port, e))?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let (abort_sender, mut abort_receiver) = tokio::sync::oneshot::channel();
|
let (abort_sender, mut abort_receiver) = tokio::sync::oneshot::channel();
|
||||||
|
|
||||||
@@ -331,6 +360,8 @@ pub async fn run() {
|
|||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_process::init())
|
.plugin(tauri_plugin_process::init())
|
||||||
.plugin(tauri_plugin_notification::init())
|
.plugin(tauri_plugin_notification::init())
|
||||||
|
.plugin(tauri_plugin_http::init())
|
||||||
|
.plugin(tauri_plugin_upload::init())
|
||||||
.manage(websocket_state.clone())
|
.manage(websocket_state.clone())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// Create menu items
|
// Create menu items
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
},
|
},
|
||||||
"productName": "pytubepp-helper",
|
"productName": "pytubepp-helper",
|
||||||
"mainBinaryName": "pytubepp-helper",
|
"mainBinaryName": "pytubepp-helper",
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"identifier": "com.neosubhamoy.pytubepp.helper",
|
"identifier": "com.neosubhamoy.pytubepp.helper",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "npm run dev && cargo build --manifest-path=./src-tauri/msghost/Cargo.toml",
|
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && node makeFilesExecutable.js && npm run dev",
|
||||||
"beforeBuildCommand": "npm run build && cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml",
|
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && node makeFilesExecutable.js && npm run build",
|
||||||
"devUrl": "http://localhost:1422",
|
"devUrl": "http://localhost:1422",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
@@ -19,7 +19,22 @@
|
|||||||
"csp": null,
|
"csp": null,
|
||||||
"capabilities": [
|
"capabilities": [
|
||||||
"main-capability",
|
"main-capability",
|
||||||
"shell-scope"
|
"shell-scope",
|
||||||
|
{
|
||||||
|
"identifier": "fs-scope",
|
||||||
|
"description": "allowed file system scopes",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"identifier": "fs:scope",
|
||||||
|
"allow": [
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome" },
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome/*" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper/*" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -58,6 +73,9 @@
|
|||||||
"/etc/xdg/autostart/pytubepp-helper-autostart.desktop": "./autostart/pytubepp-helper-autostart.desktop"
|
"/etc/xdg/autostart/pytubepp-helper-autostart.desktop": "./autostart/pytubepp-helper-autostart.desktop"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"externalBin": [
|
||||||
|
"binaries/sevenzip"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "[[ -n \"$TARGET_ARCH\" ]] && ARCH=\"$TARGET_ARCH\" || ARCH=\"$(uname -m | sed 's/^arm64$/aarch64/')-apple-darwin\" && cargo build --target=$ARCH --manifest-path=./src-tauri/msghost/Cargo.toml && npm run dev",
|
"beforeDevCommand": "[[ -n \"$TARGET_ARCH\" ]] && ARCH=\"$TARGET_ARCH\" || ARCH=\"$(uname -m | sed 's/^arm64$/aarch64/')-apple-darwin\" && cargo build --target=$ARCH --manifest-path=./src-tauri/msghost/Cargo.toml && node makeFilesExecutable.js && npm run dev",
|
||||||
"beforeBuildCommand": "[[ -n \"$TARGET_ARCH\" ]] && ARCH=\"$TARGET_ARCH\" || ARCH=\"$(uname -m | sed 's/^arm64$/aarch64/')-apple-darwin\" && cargo build --release --target=$ARCH --manifest-path=./src-tauri/msghost/Cargo.toml && node copyFiles.${ARCH}.js && npm run build",
|
"beforeBuildCommand": "[[ -n \"$TARGET_ARCH\" ]] && ARCH=\"$TARGET_ARCH\" || ARCH=\"$(uname -m | sed 's/^arm64$/aarch64/')-apple-darwin\" && cargo build --release --target=$ARCH --manifest-path=./src-tauri/msghost/Cargo.toml && node copyFiles.${ARCH}.js && node makeFilesExecutable.js && npm run build",
|
||||||
"devUrl": "http://localhost:1422",
|
"devUrl": "http://localhost:1422",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,11 @@
|
|||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost.json" },
|
{ "path": "$RESOURCE/pytubepp-helper-msghost.json" },
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost-moz.json" },
|
{ "path": "$RESOURCE/pytubepp-helper-msghost-moz.json" },
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost" },
|
{ "path": "$RESOURCE/pytubepp-helper-msghost" },
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-autostart.plist" }
|
{ "path": "$RESOURCE/pytubepp-helper-autostart.plist" },
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome" },
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome/*" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper/*" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -67,6 +71,9 @@
|
|||||||
"pytubepp-helper-msghost-moz.json",
|
"pytubepp-helper-msghost-moz.json",
|
||||||
"pytubepp-helper-msghost",
|
"pytubepp-helper-msghost",
|
||||||
"pytubepp-helper-autostart.plist"
|
"pytubepp-helper-autostart.plist"
|
||||||
|
],
|
||||||
|
"externalBin": [
|
||||||
|
"binaries/sevenzip"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && npm run dev",
|
"beforeDevCommand": "cargo build --manifest-path=./src-tauri/msghost/Cargo.toml && npm run dev",
|
||||||
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && node copyFiles.js && npm run build",
|
"beforeBuildCommand": "cargo build --release --manifest-path=./src-tauri/msghost/Cargo.toml && node copyFiles.x86_64-pc-windows-msvc.js && npm run build",
|
||||||
"devUrl": "http://localhost:1422",
|
"devUrl": "http://localhost:1422",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../dist"
|
||||||
},
|
},
|
||||||
@@ -29,7 +29,11 @@
|
|||||||
"allow": [
|
"allow": [
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost.json" },
|
{ "path": "$RESOURCE/pytubepp-helper-msghost.json" },
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost-moz.json" },
|
{ "path": "$RESOURCE/pytubepp-helper-msghost-moz.json" },
|
||||||
{ "path": "$RESOURCE/pytubepp-helper-msghost.exe" }
|
{ "path": "$RESOURCE/pytubepp-helper-msghost.exe" },
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome" },
|
||||||
|
{ "path": "$DOWNLOAD/pytubepp-extension-chrome/*" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper" },
|
||||||
|
{ "path": "$TEMP/com.neosubhamoy.pytubepp.helper/*" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -62,6 +66,9 @@
|
|||||||
"pytubepp-helper-msghost.json",
|
"pytubepp-helper-msghost.json",
|
||||||
"pytubepp-helper-msghost-moz.json",
|
"pytubepp-helper-msghost-moz.json",
|
||||||
"pytubepp-helper-msghost.exe"
|
"pytubepp-helper-msghost.exe"
|
||||||
|
],
|
||||||
|
"externalBin": [
|
||||||
|
"binaries/sevenzip"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
67
src/App.tsx
67
src/App.tsx
@@ -4,16 +4,21 @@ import { invoke } from "@tauri-apps/api/core";
|
|||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
import { WebSocketMessage } from "@/types";
|
import { Config, WebSocketMessage } from "@/types";
|
||||||
import { sendStreamInfo } from "@/lib/utils";
|
import { compareVersions, sendStreamInfo } from "@/lib/utils";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { check as checkAppUpdate } from "@tauri-apps/plugin-updater";
|
import { check as checkAppUpdate } from "@tauri-apps/plugin-updater";
|
||||||
import { isPermissionGranted, requestPermission, sendNotification } from "@tauri-apps/plugin-notification";
|
import { isPermissionGranted, requestPermission, sendNotification } from "@tauri-apps/plugin-notification";
|
||||||
|
import { downloadDir, join } from "@tauri-apps/api/path";
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
|
import * as fs from "@tauri-apps/plugin-fs"
|
||||||
|
|
||||||
function App({ children }: { children: React.ReactNode }) {
|
function App({ children }: { children: React.ReactNode }) {
|
||||||
const appWindow = getCurrentWebviewWindow()
|
const appWindow = getCurrentWebviewWindow()
|
||||||
|
const [appConfig, setAppConfig] = useState<Config | null>(null);
|
||||||
const [isAppUpdateChecked, setIsAppUpdateChecked] = useState(false);
|
const [isAppUpdateChecked, setIsAppUpdateChecked] = useState(false);
|
||||||
|
const [isExtensionUpdateChecked, setIsExtensionUpdateChecked] = useState(false);
|
||||||
|
|
||||||
// Prevent right click context menu in production
|
// Prevent right click context menu in production
|
||||||
if (!import.meta.env.DEV) {
|
if (!import.meta.env.DEV) {
|
||||||
@@ -31,6 +36,16 @@ function App({ children }: { children: React.ReactNode }) {
|
|||||||
appWindow.onCloseRequested(handleCloseRequested);
|
appWindow.onCloseRequested(handleCloseRequested);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getConfig = async () => {
|
||||||
|
const config: Config = await invoke("get_config");
|
||||||
|
if (config) {
|
||||||
|
setAppConfig(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getConfig().catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = listen<WebSocketMessage>('websocket-message', (event) => {
|
const unlisten = listen<WebSocketMessage>('websocket-message', (event) => {
|
||||||
if(event.payload.command === 'send-stream-info') {
|
if(event.payload.command === 'send-stream-info') {
|
||||||
@@ -80,13 +95,53 @@ function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isAppUpdateChecked) {
|
const checkForExtensionUpdates = async () => {
|
||||||
|
let permissionGranted = await isPermissionGranted();
|
||||||
|
if (!permissionGranted) {
|
||||||
|
const permission = await requestPermission();
|
||||||
|
permissionGranted = permission === 'granted';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setIsExtensionUpdateChecked(true)
|
||||||
|
const downloadDirPath = await downloadDir()
|
||||||
|
const extensionManifestPath = await join(downloadDirPath, "pytubepp-extension-chrome", "manifest.json")
|
||||||
|
const extensionManifestExists = await fs.exists(extensionManifestPath)
|
||||||
|
if (extensionManifestExists) {
|
||||||
|
const currentManifest = JSON.parse(await fs.readTextFile(extensionManifestPath))
|
||||||
|
const response = await fetch('https://github.com/neosubhamoy/pytubepp-extension/releases/latest/download/latest.json', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
if (compareVersions(data.version, currentManifest.version) === 1) {
|
||||||
|
console.log(`extension update available v${data.version}`);
|
||||||
|
if (permissionGranted) {
|
||||||
|
sendNotification({ title: `Extension Update Available (v${data.version})`, body: `A newer version of PytubePP Extension is available. Please update to the latest version to get the best experience!` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Failed to fetch latest extension version');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Currently installed extension\'s manifest not found')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isAppUpdateChecked && appConfig?.notify_updates) {
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
}
|
}
|
||||||
}, [])
|
|
||||||
|
if (!isExtensionUpdateChecked && appConfig?.notify_updates) {
|
||||||
|
checkForExtensionUpdates();
|
||||||
|
}
|
||||||
|
}, [appConfig])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
<ThemeProvider defaultTheme={appConfig?.theme || "system"} storageKey="vite-ui-theme">
|
||||||
<TooltipProvider delayDuration={1000}>
|
<TooltipProvider delayDuration={1000}>
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
BIN
src/assets/images/chrome.png
Normal file
BIN
src/assets/images/chrome.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 359 KiB |
BIN
src/assets/images/edge.png
Normal file
BIN
src/assets/images/edge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
BIN
src/assets/images/firefox.png
Normal file
BIN
src/assets/images/firefox.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 337 KiB |
BIN
src/assets/images/opera.png
Normal file
BIN
src/assets/images/opera.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
BIN
src/assets/images/pytubepp.png
Normal file
BIN
src/assets/images/pytubepp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
57
src/components/ui/accordion.tsx
Normal file
57
src/components/ui/accordion.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||||
|
import { ChevronDown } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Accordion = AccordionPrimitive.Root
|
||||||
|
|
||||||
|
const AccordionItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn("border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AccordionItem.displayName = "AccordionItem"
|
||||||
|
|
||||||
|
const AccordionTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Header className="flex">
|
||||||
|
<AccordionPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline text-left [&[data-state=open]>svg]:rotate-180",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
))
|
||||||
|
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const AccordionContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
))
|
||||||
|
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||||
11
src/components/ui/collapsible.tsx
Normal file
11
src/components/ui/collapsible.tsx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||||
|
|
||||||
|
const Collapsible = CollapsiblePrimitive.Root
|
||||||
|
|
||||||
|
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
|
||||||
|
|
||||||
|
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
|
||||||
|
|
||||||
|
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
|
||||||
159
src/components/ui/select.tsx
Normal file
159
src/components/ui/select.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
))
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
))
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
))
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
))
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
}
|
||||||
31
src/components/ui/sonner.tsx
Normal file
31
src/components/ui/sonner.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import { Toaster as Sonner } from "sonner"
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton:
|
||||||
|
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton:
|
||||||
|
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toaster }
|
||||||
29
src/components/ui/switch.tsx
Normal file
29
src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
))
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
@@ -66,4 +66,16 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
@apply w-1;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
@apply bg-background rounded-full;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-muted-foreground rounded-full;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
@apply bg-foreground;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ export async function isInstalled(program: string, arg: string): Promise<{ insta
|
|||||||
return { installed: false, output: output.stdout };
|
return { installed: false, output: output.stdout };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(program + ':', error);
|
||||||
return { installed: false, output: null };
|
return { installed: false, output: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,7 @@ export function extractVersion(output: string): string | null {
|
|||||||
/OS Version:.*Build (\d+)/, // Pattern for Windows build
|
/OS Version:.*Build (\d+)/, // Pattern for Windows build
|
||||||
/apt (\d+\.\d+\.\d+)/, // Pattern for apt
|
/apt (\d+\.\d+\.\d+)/, // Pattern for apt
|
||||||
/(\d+\.\d+\.\d+)/, // Pattern for dnf
|
/(\d+\.\d+\.\d+)/, // Pattern for dnf
|
||||||
|
/Pacman v(\d+\.\d+\.\d+)/, // Pattern for pacman
|
||||||
/ProductVersion:\s+(\d+\.\d+(\.\d+)?)/, // Pattern for macOS version
|
/ProductVersion:\s+(\d+\.\d+(\.\d+)?)/, // Pattern for macOS version
|
||||||
/Homebrew (\d+\.\d+\.\d+)/, // Pattern for Homebrew
|
/Homebrew (\d+\.\d+\.\d+)/, // Pattern for Homebrew
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import App from "@/App";
|
|||||||
import HomePage from "@/pages/home";
|
import HomePage from "@/pages/home";
|
||||||
import SettingsPage from "@/pages/settings";
|
import SettingsPage from "@/pages/settings";
|
||||||
import NotificationsPage from "@/pages/notifications";
|
import NotificationsPage from "@/pages/notifications";
|
||||||
|
import ExtensionManagerPage from "@/pages/extension-manager";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@@ -15,6 +16,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||||||
<Route path="/" element={<HomePage />} />
|
<Route path="/" element={<HomePage />} />
|
||||||
<Route path="/settings" element={<SettingsPage />} />
|
<Route path="/settings" element={<SettingsPage />} />
|
||||||
<Route path="/notifications" element={<NotificationsPage />} />
|
<Route path="/notifications" element={<NotificationsPage />} />
|
||||||
|
<Route path="/extension-manager" element={<ExtensionManagerPage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</App>
|
</App>
|
||||||
|
|||||||
279
src/pages/extension-manager.tsx
Normal file
279
src/pages/extension-manager.tsx
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import * as fs from "@tauri-apps/plugin-fs"
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
|
import { getPlatformInfo } from "@/lib/platform-utils";
|
||||||
|
import { CurrentExtension, LatestExtensionResponse, PlatformInfo } from "@/types";
|
||||||
|
import { ArrowLeft, ChevronsUpDown, CircleHelp, Loader2, RefreshCcw } from "lucide-react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { downloadDir, tempDir, join } from "@tauri-apps/api/path";
|
||||||
|
import { compareVersions } from "@/lib/utils";
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||||
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
|
import { download } from '@tauri-apps/plugin-upload';
|
||||||
|
import { Command } from "@tauri-apps/plugin-shell";
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import chromeLogo from "@/assets/images/chrome.png"
|
||||||
|
import firefoxLogo from "@/assets/images/firefox.png"
|
||||||
|
import edgeLogo from "@/assets/images/edge.png"
|
||||||
|
import operaLogo from "@/assets/images/opera.png"
|
||||||
|
import pytubeppLogo from "@/assets/images/pytubepp.png"
|
||||||
|
|
||||||
|
export default function ExtensionManagerPage() {
|
||||||
|
const [platformInfo, setPlatformInfo] = useState<PlatformInfo | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
const [isCollapsibleOpen, setCollapsibleIsOpen] = useState(false)
|
||||||
|
const [isAccordionOpen, setAccordionIsOpen] = useState(false)
|
||||||
|
const [isExtensionInstalled, setIsExtensionInstalled] = useState(false);
|
||||||
|
const [isExtensionUpdateAvailable, setIsExtensionUpdateAvailable] = useState(false);
|
||||||
|
const [extensionUpdate, setExtensionUpdate] = useState<LatestExtensionResponse | null>(null)
|
||||||
|
const [currentExtension, setCurrentExtension] = useState<CurrentExtension | null>(null)
|
||||||
|
const [updateStatus, setUpdateStatus] = useState<string | null>(null)
|
||||||
|
|
||||||
|
async function checkForUpdates() {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const downloadDirPath = await downloadDir()
|
||||||
|
const extensionManifestPath = await join(downloadDirPath, "pytubepp-extension-chrome", "manifest.json")
|
||||||
|
const extensionManifestExists = await fs.exists(extensionManifestPath)
|
||||||
|
const response = await fetch('https://github.com/neosubhamoy/pytubepp-extension/releases/latest/download/latest.json', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data: LatestExtensionResponse = await response.json()
|
||||||
|
setExtensionUpdate(data)
|
||||||
|
if (extensionManifestExists) {
|
||||||
|
setIsExtensionInstalled(true)
|
||||||
|
const currentManifest = JSON.parse(await fs.readTextFile(extensionManifestPath))
|
||||||
|
setCurrentExtension(currentManifest)
|
||||||
|
setIsExtensionUpdateAvailable(compareVersions(data.version, currentManifest.version) === 1)
|
||||||
|
} else {
|
||||||
|
setIsExtensionInstalled(false)
|
||||||
|
setCurrentExtension(null)
|
||||||
|
setIsExtensionUpdateAvailable(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIsExtensionUpdateAvailable(false)
|
||||||
|
setExtensionUpdate(null)
|
||||||
|
if (extensionManifestExists) {
|
||||||
|
setIsExtensionInstalled(true)
|
||||||
|
const currentManifest = JSON.parse(await fs.readTextFile(extensionManifestPath))
|
||||||
|
setCurrentExtension(currentManifest)
|
||||||
|
} else {
|
||||||
|
setIsExtensionInstalled(false)
|
||||||
|
setCurrentExtension(null)
|
||||||
|
}
|
||||||
|
console.error('Failed to fetch latest extension version');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpackExtension = async (extension: LatestExtensionResponse, operation: "unpack" | "update") => {
|
||||||
|
setIsUpdating(true)
|
||||||
|
try {
|
||||||
|
setUpdateStatus('Preparing')
|
||||||
|
const downloadDirPath = await downloadDir()
|
||||||
|
const tempDirPath = await tempDir()
|
||||||
|
const extensionDirPath = await join(downloadDirPath, "pytubepp-extension-chrome")
|
||||||
|
const appTempDirPath = await join(tempDirPath, "com.neosubhamoy.pytubepp.helper")
|
||||||
|
const tempExtensionDownloadPath = await join(appTempDirPath, `pytubepp-extension-chrome-v${extension.version}.zip`)
|
||||||
|
|
||||||
|
const extensionDirExists = await fs.exists(extensionDirPath)
|
||||||
|
const appTempDirExists = await fs.exists(appTempDirPath)
|
||||||
|
|
||||||
|
if (!extensionDirExists) await fs.mkdir(extensionDirPath, { recursive: true}).then(() => console.log(`Created: ${extensionDirPath}`))
|
||||||
|
if (!appTempDirExists) await fs.mkdir(appTempDirPath, { recursive: true}).then(() => console.log(`Created: ${appTempDirPath}`))
|
||||||
|
|
||||||
|
setUpdateStatus('Downloading')
|
||||||
|
await download(
|
||||||
|
extension.browsers.chrome.url,
|
||||||
|
tempExtensionDownloadPath,
|
||||||
|
({ progress, total }) => console.log(`Downloading: ${progress} of ${total} bytes`)
|
||||||
|
);
|
||||||
|
|
||||||
|
setUpdateStatus('Unpacking')
|
||||||
|
const output = await Command.sidecar('binaries/sevenzip', ['x', tempExtensionDownloadPath, `-o${extensionDirPath}`, '-aoa']).execute()
|
||||||
|
if (output.code === 0) {
|
||||||
|
console.log(output.stdout)
|
||||||
|
console.log(`Unpacked ${tempExtensionDownloadPath} to ${extensionDirPath}`)
|
||||||
|
} else {
|
||||||
|
console.log(output.stdout, output.stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
setUpdateStatus('Cleaning')
|
||||||
|
await fs.remove(tempExtensionDownloadPath)
|
||||||
|
console.log(`Deleted: ${tempExtensionDownloadPath}`)
|
||||||
|
|
||||||
|
setIsExtensionInstalled(true)
|
||||||
|
setCurrentExtension({version: extension.version})
|
||||||
|
setIsExtensionUpdateAvailable(false)
|
||||||
|
|
||||||
|
if (operation === "unpack") toast(`Successfully unpacked v${extension.version} to ${extensionDirPath}`)
|
||||||
|
if (operation === "update") toast(`Successfully updated to v${extension.version}. Please reload the extension to reflect changes`)
|
||||||
|
} catch (error) {
|
||||||
|
if (operation === "unpack") toast(`Failed to unpack v${extension.version}`)
|
||||||
|
if (operation === "update") toast(`Failed to update v${extension.version}`)
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setIsUpdating(false);
|
||||||
|
setUpdateStatus(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPlatformInfo().then(setPlatformInfo).catch(console.error);
|
||||||
|
checkForUpdates();
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container">
|
||||||
|
<div className={clsx("topbar flex justify-between items-center mt-5", !platformInfo?.isWindows && "mx-3")}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link to="/" className={clsx(isUpdating && "pointer-events-none opacity-50")}>
|
||||||
|
<ArrowLeft className="w-5 h-5 mr-3"/>
|
||||||
|
</Link>
|
||||||
|
<h1 className="text-xl font-bold">Extension Manager</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button className="ml-3" size="icon" disabled={isLoading || isUpdating} onClick={checkForUpdates}>
|
||||||
|
<RefreshCcw className="w-5 h-5"/>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent><p>refresh</p></TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={clsx("mt-5", !platformInfo?.isWindows && "mx-3")}>
|
||||||
|
{
|
||||||
|
isLoading ? (
|
||||||
|
<div className="mt-5 mx-3">
|
||||||
|
<div className="flex flex-col min-h-[55vh]">
|
||||||
|
<div className="flex items-center justify-center py-[4.3rem]">
|
||||||
|
<div className="flex flex-col items-center justify-center">
|
||||||
|
<Loader2 className="h-8 w-8 animate-spin"/>
|
||||||
|
<p className="ml-3 mt-2 text-muted-foreground">ckecking...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="mt-5">
|
||||||
|
<div className="flex flex-col min-h-[55vh] max-h-[75vh] overflow-y-scroll">
|
||||||
|
<Card className="p-2 mb-3 flex flex-col">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="imgwrapper h-12 w-12 relative">
|
||||||
|
<img src={chromeLogo} alt="chrome" />
|
||||||
|
<img className="absolute bottom-0 right-0 h-5 w-5" src={pytubeppLogo} alt="pytubepp" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col ml-3">
|
||||||
|
<h3>PytubePP Extension (Chrome)</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">Unpacked: { currentExtension ? currentExtension.version : 'none' } Latest: { extensionUpdate ? extensionUpdate.version : 'unknown' }</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mr-2" size="sm" onClick={isExtensionUpdateAvailable && extensionUpdate ? () => unpackExtension(extensionUpdate, "update") : isExtensionInstalled ? () => {console.log('Already latest version')} : extensionUpdate ? () => unpackExtension(extensionUpdate, "unpack") : () => {console.error('Download url not available')}} disabled={(!isExtensionUpdateAvailable && isExtensionInstalled) || isUpdating}>{isUpdating ? <><Loader2 className="w-4 h-4 animate-spin"/> {updateStatus}</> : isExtensionUpdateAvailable ? 'Update' : isExtensionInstalled ? 'Unpacked' : 'Unpack'}</Button>
|
||||||
|
</div>
|
||||||
|
<div className="px-2 mt-2">
|
||||||
|
<Collapsible
|
||||||
|
open={isCollapsibleOpen}
|
||||||
|
onOpenChange={setCollapsibleIsOpen}
|
||||||
|
className="w-full space-y-2"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between space-x-4">
|
||||||
|
<h4 className="text-sm flex items-center">
|
||||||
|
<CircleHelp className="w-3 h-3 mr-2"/> How to use unpacked extension
|
||||||
|
</h4>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button variant="ghost" size="sm">
|
||||||
|
<ChevronsUpDown className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Toggle</span>
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
</div>
|
||||||
|
<CollapsibleContent className="">
|
||||||
|
<ul className="text-xs text-muted-foreground">
|
||||||
|
<li>1. Clicking on the 'Unpack' button unpacks latest pytubepp-extension for chrome within '~/Downloads/pytubepp-extension-chrome' folder</li>
|
||||||
|
<li>2. You need to manually <a className="underline" href="https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#load-unpacked" target="_blank">load the unpacked extension folder</a> by visiting 'chrome://extensions' page</li>
|
||||||
|
<li>3. If an update is available the 'Update' button will show up, Simply click on the button to update and don't forget to <a className="underline" href="https://developer.chrome.com/docs/extensions/get-started/tutorial/hello-world#reload" target="_blank">reload the extension</a> by visiting 'chrome://extensions' page after updating</li>
|
||||||
|
</ul>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Accordion type="single" collapsible className="overflow-x-hidden" onValueChange={() => setAccordionIsOpen(!isAccordionOpen)}>
|
||||||
|
<AccordionItem value="store-listings">
|
||||||
|
<AccordionTrigger>Official Store Listings (Auto-Updates)</AccordionTrigger>
|
||||||
|
<AccordionContent>
|
||||||
|
<Card className="p-2 mb-3 flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="imgwrapper h-12 w-12 flex justify-center items-center">
|
||||||
|
<img className="h-10" src={firefoxLogo} alt="firefox" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col ml-3">
|
||||||
|
<h3>PytubePP Addon (Firefox)</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">Add pytubepp-addon to firefox</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mr-2" size="sm" asChild>
|
||||||
|
<a href="https://addons.mozilla.org/en-US/firefox/addon/pytubepp-addon/" target="_blank" rel="noopener noreferrer">View</a>
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
<Card className="p-2 mb-3 flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="imgwrapper h-12 w-12 flex justify-center items-center">
|
||||||
|
<img className="h-10 w-10" src={edgeLogo} alt="edge" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col ml-3">
|
||||||
|
<h3>PytubePP Extension (Edge)</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">Add pytubepp-extension to edge</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mr-2" size="sm" asChild>
|
||||||
|
<a href="https://microsoftedge.microsoft.com/addons/detail/pytubepp-extension-foss/ebneapoekcjelholncnlpdohjbjabhbi" target="_blank" rel="noopener noreferrer">View</a>
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
<Card className="p-2 flex items-center justify-between">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="imgwrapper h-12 w-12 flex justify-center items-center">
|
||||||
|
<img className="h-10 w-10" src={operaLogo} alt="opera" />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col ml-3">
|
||||||
|
<h3>PytubePP Extension (Opera)</h3>
|
||||||
|
<p className="text-xs text-muted-foreground">Add pytubepp-extension to opera</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button className="mr-2" size="sm" asChild>
|
||||||
|
<a href="https://addons.opera.com/en/extensions/details/pytubepp-extension-foss/" target="_blank" rel="noopener noreferrer">View</a>
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
{
|
||||||
|
!isAccordionOpen && !isCollapsibleOpen && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<ul className="text-xs text-muted-foreground">
|
||||||
|
<li>* Extension Manager helps you manage unpacked pytubepp-extension (installing and updating) as pytubepp-extension is not available on Chrome Web Store under <a href="https://developer.chrome.com/docs/webstore/troubleshooting/#prohibited-products" target="_blank" className="underline">Blue Zinc</a> guidelines. (unpacked chrome extension works for all chromium based browsers)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -6,15 +6,17 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
|
||||||
import { InstalledPrograms } from "@/types";
|
import { InstalledPrograms } from "@/types";
|
||||||
import { compareVersions, extractVersion, isInstalled, registerMacFiles } from "@/lib/utils";
|
import { compareVersions, extractVersion, isInstalled, registerMacFiles } from "@/lib/utils";
|
||||||
import { CircleCheck, TriangleAlert, CircleAlert, Settings, RefreshCcw, Loader2, PackagePlus, Bell } from "lucide-react";
|
import { CircleCheck, TriangleAlert, CircleAlert, Settings, RefreshCcw, Loader2, PackagePlus, Bell, Puzzle } from "lucide-react";
|
||||||
import { getPlatformInfo } from "@/lib/platform-utils";
|
import { getPlatformInfo } from "@/lib/platform-utils";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { toast } from "sonner"
|
||||||
import { NotificationBadge } from "@/components/ui/notification-badge";
|
import { NotificationBadge } from "@/components/ui/notification-badge";
|
||||||
import { check as checkAppUpdate } from "@tauri-apps/plugin-updater";
|
import { check as checkAppUpdate } from "@tauri-apps/plugin-updater";
|
||||||
|
import { fetch } from '@tauri-apps/plugin-http';
|
||||||
|
import { join, downloadDir } from "@tauri-apps/api/path";
|
||||||
|
import * as fs from "@tauri-apps/plugin-fs"
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const { toast } = useToast();
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isWindows, setIsWindows] = useState<boolean>(false)
|
const [isWindows, setIsWindows] = useState<boolean>(false)
|
||||||
const [windowsVersion, setWindowsVersion] = useState<string | null>(null)
|
const [windowsVersion, setWindowsVersion] = useState<string | null>(null)
|
||||||
@@ -23,6 +25,7 @@ export default function HomePage() {
|
|||||||
const [distroId, setDistroId] = useState<string | null>(null)
|
const [distroId, setDistroId] = useState<string | null>(null)
|
||||||
const [distroPkgMngr, setDistroPkgMngr] = useState<string | null>(null)
|
const [distroPkgMngr, setDistroPkgMngr] = useState<string | null>(null)
|
||||||
const [isAppUpdateAvailable, setIsAppUpdateAvailable] = useState(false);
|
const [isAppUpdateAvailable, setIsAppUpdateAvailable] = useState(false);
|
||||||
|
const [isExtensionUpdateAvailable, setIsExtensionUpdateAvailable] = useState(false);
|
||||||
const [installedPrograms, setInstalledPrograms] = useState<InstalledPrograms>({
|
const [installedPrograms, setInstalledPrograms] = useState<InstalledPrograms>({
|
||||||
winget: {
|
winget: {
|
||||||
installed: false,
|
installed: false,
|
||||||
@@ -36,6 +39,10 @@ export default function HomePage() {
|
|||||||
installed: false,
|
installed: false,
|
||||||
version: null,
|
version: null,
|
||||||
},
|
},
|
||||||
|
pacman: {
|
||||||
|
installed: false,
|
||||||
|
version: null,
|
||||||
|
},
|
||||||
brew: {
|
brew: {
|
||||||
installed: false,
|
installed: false,
|
||||||
version: null,
|
version: null,
|
||||||
@@ -99,6 +106,15 @@ export default function HomePage() {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}),
|
}),
|
||||||
|
isInstalled('pacman', '--version').then((result) => {
|
||||||
|
setInstalledPrograms((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
pacman: {
|
||||||
|
installed: result.installed,
|
||||||
|
version: result.output ? extractVersion(result.output) : null,
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}),
|
||||||
isInstalled('homebrew', '--version').then((result) => {
|
isInstalled('homebrew', '--version').then((result) => {
|
||||||
setInstalledPrograms((prevState) => ({
|
setInstalledPrograms((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
@@ -214,6 +230,34 @@ export default function HomePage() {
|
|||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkForExtensionUpdates = async () => {
|
||||||
|
try {
|
||||||
|
const downloadDirPath = await downloadDir()
|
||||||
|
const extensionManifestPath = await join(downloadDirPath, "pytubepp-extension-chrome", "manifest.json")
|
||||||
|
const extensionManifestExists = await fs.exists(extensionManifestPath)
|
||||||
|
if (extensionManifestExists) {
|
||||||
|
const currentManifest = JSON.parse(await fs.readTextFile(extensionManifestPath))
|
||||||
|
const response = await fetch('https://github.com/neosubhamoy/pytubepp-extension/releases/latest/download/latest.json', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json()
|
||||||
|
setIsExtensionUpdateAvailable(compareVersions(data.version, currentManifest.version) === 1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('Failed to fetch latest extension version');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Currently installed extension\'s manifest not found')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
checkForExtensionUpdates();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className={clsx("topbar flex justify-between items-center mt-5", !isWindows && "mx-3")}>
|
<div className={clsx("topbar flex justify-between items-center mt-5", !isWindows && "mx-3")}>
|
||||||
@@ -224,9 +268,25 @@ export default function HomePage() {
|
|||||||
<NotificationBadge
|
<NotificationBadge
|
||||||
label='1'
|
label='1'
|
||||||
className='bg-green-700 text-white hover:bg-green-700 hover:cursor-default'
|
className='bg-green-700 text-white hover:bg-green-700 hover:cursor-default'
|
||||||
show={isAppUpdateAvailable}
|
show={isExtensionUpdateAvailable}
|
||||||
>
|
>
|
||||||
<Button variant="outline" size="icon" asChild>
|
<Button variant="outline" size="icon" asChild>
|
||||||
|
<Link to="/extension-manager">
|
||||||
|
<Puzzle className="w-5 h-5"/>
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</NotificationBadge>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>extension manager</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<NotificationBadge
|
||||||
|
label='1'
|
||||||
|
className='bg-green-700 text-white hover:bg-green-700 hover:cursor-default'
|
||||||
|
show={isAppUpdateAvailable}
|
||||||
|
>
|
||||||
|
<Button className="ml-3" variant="outline" size="icon" asChild>
|
||||||
<Link to="/notifications">
|
<Link to="/notifications">
|
||||||
<Bell className="w-5 h-5"/>
|
<Bell className="w-5 h-5"/>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -250,7 +310,7 @@ export default function HomePage() {
|
|||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button className="ml-3" size="icon" onClick={async () => {
|
<Button className="ml-3" size="icon" onClick={async () => {
|
||||||
const result = await registerMacFiles();
|
const result = await registerMacFiles();
|
||||||
toast({ title: result.message, variant: result.success ? 'default' : 'destructive' });
|
toast(result.message);
|
||||||
}}>
|
}}>
|
||||||
<PackagePlus className="w-5 h-5"/>
|
<PackagePlus className="w-5 h-5"/>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -299,7 +359,7 @@ export default function HomePage() {
|
|||||||
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
||||||
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
{(!installedPrograms.apt.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed)) ?
|
{(!installedPrograms.apt.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed || !installedPrograms.nodejs.installed)) ?
|
||||||
<Alert className="mt-5" variant="destructive">
|
<Alert className="mt-5" variant="destructive">
|
||||||
<CircleAlert className="h-5 w-5" />
|
<CircleAlert className="h-5 w-5" />
|
||||||
<AlertTitle>APT Not Found</AlertTitle>
|
<AlertTitle>APT Not Found</AlertTitle>
|
||||||
@@ -317,7 +377,7 @@ export default function HomePage() {
|
|||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
: null}
|
: null}
|
||||||
{(installedPrograms.python3.installed && installedPrograms.ffmpeg.installed && installedPrograms.pytubepp.installed) ?
|
{(installedPrograms.python3.installed && installedPrograms.ffmpeg.installed && installedPrograms.nodejs.installed && installedPrograms.pytubepp.installed) ?
|
||||||
<Alert className="mt-5">
|
<Alert className="mt-5">
|
||||||
<CircleCheck className="h-5 w-5" />
|
<CircleCheck className="h-5 w-5" />
|
||||||
<AlertTitle>Ready</AlertTitle>
|
<AlertTitle>Ready</AlertTitle>
|
||||||
@@ -345,7 +405,7 @@ export default function HomePage() {
|
|||||||
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
||||||
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
{(!installedPrograms.dnf.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed)) ?
|
{(!installedPrograms.dnf.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed || !installedPrograms.nodejs.installed)) ?
|
||||||
<Alert className="mt-5" variant="destructive">
|
<Alert className="mt-5" variant="destructive">
|
||||||
<CircleAlert className="h-5 w-5" />
|
<CircleAlert className="h-5 w-5" />
|
||||||
<AlertTitle>DNF Not Found</AlertTitle>
|
<AlertTitle>DNF Not Found</AlertTitle>
|
||||||
@@ -363,7 +423,53 @@ export default function HomePage() {
|
|||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
: null}
|
: null}
|
||||||
{(installedPrograms.python3.installed && installedPrograms.ffmpeg.installed && installedPrograms.pytubepp.installed) ?
|
{(installedPrograms.python3.installed && installedPrograms.ffmpeg.installed && installedPrograms.nodejs.installed && installedPrograms.pytubepp.installed) ?
|
||||||
|
<Alert className="mt-5">
|
||||||
|
<CircleCheck className="h-5 w-5" />
|
||||||
|
<AlertTitle>Ready</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Everything looks ok! You can close this window now. Make sure it's always running in the background.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
: null}
|
||||||
|
</div>
|
||||||
|
: distroId && distroPkgMngr && distroPkgMngr === 'pacman' ? /* Section for Arch Linux */
|
||||||
|
<div className="programstats mt-5 mx-3">
|
||||||
|
<div className="programitem flex items-center justify-between">
|
||||||
|
<p><b>Python:</b> {installedPrograms.python3.installed ? 'installed' : 'not installed'} {installedPrograms.python3.version ? `(${installedPrograms.python3.version})` : ''}</p>
|
||||||
|
{installedPrograms.python3.installed ? installedPrograms.python3.version ? compareVersions(installedPrograms.python3.version, '3.8') < 0 ? <TriangleAlert className="w-5 h-5 my-2 text-orange-400"/> : <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pacman.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'sudo pacman -Sy python'})}}>install</Button> : <TriangleAlert className="w-5 h-5 my-2 text-orange-400"/> : null}
|
||||||
|
</div>
|
||||||
|
<div className="programitem flex items-center justify-between">
|
||||||
|
<p><b>FFmpeg:</b> {installedPrograms.ffmpeg.installed ? 'installed' : 'not installed'} {installedPrograms.ffmpeg.version ? `(${installedPrograms.ffmpeg.version})` : ''}</p>
|
||||||
|
{installedPrograms.ffmpeg.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pacman.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'sudo pacman -Sy ffmpeg'})}}>install</Button> : null}
|
||||||
|
</div>
|
||||||
|
<div className="programitem flex items-center justify-between">
|
||||||
|
<p><b>Node.js:</b> {installedPrograms.nodejs.installed ? 'installed' : 'not installed'} {installedPrograms.nodejs.version ? `(${installedPrograms.nodejs.version})` : ''}</p>
|
||||||
|
{installedPrograms.nodejs.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pacman.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'sudo pacman -Sy nodejs-lts-iron npm'})}}>install</Button> : null}
|
||||||
|
</div>
|
||||||
|
<div className="programitem flex items-center justify-between">
|
||||||
|
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
||||||
|
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
||||||
|
</div>
|
||||||
|
{(!installedPrograms.pacman.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed || !installedPrograms.nodejs.installed)) ?
|
||||||
|
<Alert className="mt-5" variant="destructive">
|
||||||
|
<CircleAlert className="h-5 w-5" />
|
||||||
|
<AlertTitle>Pacman Not Found</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Pacman is required to install necessary packages. Please install it manually for your distro.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
: null}
|
||||||
|
{(!installedPrograms.pip3.installed && !installedPrograms.pytubepp.installed) ?
|
||||||
|
<Alert className="mt-5" variant="destructive">
|
||||||
|
<CircleAlert className="h-5 w-5" />
|
||||||
|
<AlertTitle>PIP Not Found</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
PIP is required to install necessary python packages. Please install it now to continue: <Button variant="link" className="text-blue-600 p-0" onClick={async () => { await invoke('install_program', {icommand: 'sudo pacman -Sy python-pip'})}}>install</Button>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
: null}
|
||||||
|
{(installedPrograms.python3.installed && installedPrograms.ffmpeg.installed && installedPrograms.nodejs.installed && installedPrograms.pytubepp.installed) ?
|
||||||
<Alert className="mt-5">
|
<Alert className="mt-5">
|
||||||
<CircleCheck className="h-5 w-5" />
|
<CircleCheck className="h-5 w-5" />
|
||||||
<AlertTitle>Ready</AlertTitle>
|
<AlertTitle>Ready</AlertTitle>
|
||||||
@@ -391,7 +497,7 @@ export default function HomePage() {
|
|||||||
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
||||||
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip install pytubepp'})}}>install</Button> : null}
|
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip install pytubepp'})}}>install</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
{(!installedPrograms.winget.installed && (!installedPrograms.python.installed || !installedPrograms.ffmpeg.installed)) ?
|
{(!installedPrograms.winget.installed && (!installedPrograms.python.installed || !installedPrograms.ffmpeg.installed || !installedPrograms.nodejs.installed)) ?
|
||||||
<Alert className="mt-5" variant="destructive">
|
<Alert className="mt-5" variant="destructive">
|
||||||
<CircleAlert className="h-5 w-5" />
|
<CircleAlert className="h-5 w-5" />
|
||||||
<AlertTitle>WinGet Not Found</AlertTitle>
|
<AlertTitle>WinGet Not Found</AlertTitle>
|
||||||
@@ -400,7 +506,7 @@ export default function HomePage() {
|
|||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
: null}
|
: null}
|
||||||
{(installedPrograms.python.installed && installedPrograms.ffmpeg.installed && installedPrograms.pytubepp.installed) ?
|
{(installedPrograms.python.installed && installedPrograms.ffmpeg.installed && installedPrograms.nodejs.installed && installedPrograms.pytubepp.installed) ?
|
||||||
<Alert className="mt-5">
|
<Alert className="mt-5">
|
||||||
<CircleCheck className="h-5 w-5" />
|
<CircleCheck className="h-5 w-5" />
|
||||||
<AlertTitle>Ready</AlertTitle>
|
<AlertTitle>Ready</AlertTitle>
|
||||||
@@ -428,7 +534,7 @@ export default function HomePage() {
|
|||||||
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
<p><b>PytubePP:</b> {installedPrograms.pytubepp.installed ? 'installed' : 'not installed'} {installedPrograms.pytubepp.version ? `(${installedPrograms.pytubepp.version})` : ''}</p>
|
||||||
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
{installedPrograms.pytubepp.installed ? <CircleCheck className="w-5 h-5 my-2 text-green-400"/> : installedPrograms.pip3.installed ? <Button variant="link" className="text-blue-600 px-0" onClick={async () => { await invoke('install_program', {icommand: 'pip3 install pytubepp || pip3 install pytubepp --break-system-packages'})}}>install</Button> : null}
|
||||||
</div>
|
</div>
|
||||||
{(!installedPrograms.brew.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed)) ?
|
{(!installedPrograms.brew.installed && (!installedPrograms.python3.installed || !installedPrograms.ffmpeg.installed || !installedPrograms.nodejs.installed)) ?
|
||||||
<Alert className="mt-5" variant="destructive">
|
<Alert className="mt-5" variant="destructive">
|
||||||
<CircleAlert className="h-5 w-5" />
|
<CircleAlert className="h-5 w-5" />
|
||||||
<AlertTitle>Homebrew Not Found</AlertTitle>
|
<AlertTitle>Homebrew Not Found</AlertTitle>
|
||||||
|
|||||||
@@ -10,18 +10,25 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Config, PlatformInfo } from "@/types";
|
import { Config, PlatformInfo } from "@/types";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { toast } from "sonner"
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { getPlatformInfo } from "@/lib/platform-utils";
|
import { getPlatformInfo } from "@/lib/platform-utils";
|
||||||
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { useTheme } from "@/components/theme-provider"
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
const DEFAULT_PORT = 3030;
|
const DEFAULT_PORT = 3030;
|
||||||
|
const DEFAULT_THEME = "system";
|
||||||
|
const DEFAULT_NOTIFY_UPDATES = true;
|
||||||
const settingsFormSchema = z.object({
|
const settingsFormSchema = z.object({
|
||||||
port: z.number().min(3000, { message: "Port must be greater than 3000" }).max(3999, { message: "Port must be less than 3999" }),
|
port: z.number().min(3000, { message: "Port must be greater than 3000" }).max(3999, { message: "Port must be less than 3999" }),
|
||||||
|
theme: z.enum(["system", "dark", "light"], { message: "Invalid theme" }),
|
||||||
|
notify_updates: z.boolean({ message: "Not a boolean value" }),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const { toast } = useToast();
|
const { setTheme } = useTheme();
|
||||||
const [platformInfo, setPlatformInfo] = useState<PlatformInfo | null>(null);
|
const [platformInfo, setPlatformInfo] = useState<PlatformInfo | null>(null);
|
||||||
const [appConfig, setAppConfig] = useState<Config | null>(null);
|
const [appConfig, setAppConfig] = useState<Config | null>(null);
|
||||||
const [appVersion, setAppVersion] = useState<string | null>(null);
|
const [appVersion, setAppVersion] = useState<string | null>(null);
|
||||||
@@ -32,13 +39,15 @@ export default function SettingsPage() {
|
|||||||
resolver: zodResolver(settingsFormSchema),
|
resolver: zodResolver(settingsFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
port: DEFAULT_PORT,
|
port: DEFAULT_PORT,
|
||||||
|
theme: DEFAULT_THEME,
|
||||||
|
notify_updates: DEFAULT_NOTIFY_UPDATES,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = settingsForm.watch((value) => {
|
const subscription = settingsForm.watch((value) => {
|
||||||
if (appConfig) {
|
if (appConfig) {
|
||||||
setIsFormDirty(value.port !== appConfig.port);
|
setIsFormDirty(value.port !== appConfig.port || value.theme !== appConfig.theme || value.notify_updates !== appConfig.notify_updates);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
@@ -49,7 +58,7 @@ export default function SettingsPage() {
|
|||||||
const config: Config = await invoke("get_config");
|
const config: Config = await invoke("get_config");
|
||||||
if (config) {
|
if (config) {
|
||||||
setAppConfig(config);
|
setAppConfig(config);
|
||||||
settingsForm.reset({ port: config.port });
|
settingsForm.reset({ port: config.port, theme: config.theme, notify_updates: config.notify_updates });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getConfig().catch(console.error);
|
getConfig().catch(console.error);
|
||||||
@@ -64,24 +73,28 @@ export default function SettingsPage() {
|
|||||||
getAppVersion().catch(console.error);
|
getAppVersion().catch(console.error);
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateTheme = async () => {
|
||||||
|
setTheme(appConfig?.theme || DEFAULT_THEME);
|
||||||
|
}
|
||||||
|
updateTheme().catch(console.error);
|
||||||
|
}, [appConfig?.theme]);
|
||||||
|
|
||||||
const updateConfig = async () => {
|
const updateConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const updatedConfig: Config = await invoke("update_config", {
|
const updatedConfig: Config = await invoke("update_config", {
|
||||||
newConfig: {
|
newConfig: {
|
||||||
port: Number(settingsForm.getValues().port)
|
port: Number(settingsForm.getValues().port),
|
||||||
|
theme: settingsForm.getValues().theme,
|
||||||
|
notify_updates: settingsForm.getValues().notify_updates,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setAppConfig(updatedConfig);
|
setAppConfig(updatedConfig);
|
||||||
setIsFormDirty(false);
|
setIsFormDirty(false);
|
||||||
toast({
|
toast("Settings updated");
|
||||||
title: "Settings updated"
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update config:", error);
|
console.error("Failed to update config:", error);
|
||||||
toast({
|
toast("Failed to update settings");
|
||||||
title: "Failed to update settings",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,21 +102,16 @@ export default function SettingsPage() {
|
|||||||
try {
|
try {
|
||||||
const config: Config = await invoke("reset_config");
|
const config: Config = await invoke("reset_config");
|
||||||
setAppConfig(config);
|
setAppConfig(config);
|
||||||
settingsForm.reset({ port: config.port });
|
settingsForm.reset({ port: config.port, theme: config.theme, notify_updates: config.notify_updates });
|
||||||
setIsFormDirty(false);
|
setIsFormDirty(false);
|
||||||
toast({
|
toast("Settings reset to default");
|
||||||
title: "Using default settings"
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to reset config:", error);
|
console.error("Failed to reset config:", error);
|
||||||
toast({
|
toast("Failed to reset settings");
|
||||||
title: "Failed to reset settings",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUsingDefaultConfig = appConfig?.port === DEFAULT_PORT;
|
const isUsingDefaultConfig = appConfig?.port === DEFAULT_PORT && appConfig?.theme === DEFAULT_THEME && appConfig?.notify_updates === DEFAULT_NOTIFY_UPDATES;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
@@ -149,17 +157,18 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={clsx("mt-5", !platformInfo?.isWindows && "mx-3")}>
|
<div className={clsx("mt-5", !platformInfo?.isWindows && "mx-3")}>
|
||||||
<div className="flex flex-col min-h-[55vh]">
|
<div className="flex flex-col min-h-[55vh] max-h-[58vh] overflow-y-scroll">
|
||||||
<Form {...settingsForm}>
|
<Form {...settingsForm}>
|
||||||
<form onSubmit={settingsForm.handleSubmit(updateConfig)}>
|
<form onSubmit={settingsForm.handleSubmit(updateConfig)}>
|
||||||
<FormField
|
<FormField
|
||||||
control={settingsForm.control}
|
control={settingsForm.control}
|
||||||
name="port"
|
name="port"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="mb-2">
|
||||||
<FormLabel>Port</FormLabel>
|
<FormLabel>Port</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
|
className="focus-visible:ring-0"
|
||||||
type="text"
|
type="text"
|
||||||
{...field}
|
{...field}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
@@ -174,17 +183,65 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The port to use for the websocket server
|
The port to use for websocket communication with msghost
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage/>
|
<FormMessage/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={settingsForm.control}
|
||||||
|
name="theme"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="mb-2">
|
||||||
|
<FormLabel>Theme</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Select {...field} onValueChange={(value) => field.onChange(value)}>
|
||||||
|
<SelectTrigger className="w-full ring-0 focus:ring-0">
|
||||||
|
<SelectValue placeholder="Select App Theme" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="system">Follow System</SelectItem>
|
||||||
|
<SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
Choose app interface theme
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage/>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={settingsForm.control}
|
||||||
|
name="notify_updates"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm mb-4 mt-3">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>Notify Updates</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Notify for app and component updates (Recommended)
|
||||||
|
</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Button className="hidden" ref={saveButtonRef} type="submit">Save</Button>
|
<Button className="hidden" ref={saveButtonRef} type="submit">Save</Button>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center border-t border-muted-foreground/50 pt-2">
|
<div className="flex justify-between items-center border-t border-muted-foreground/50 pt-2 relative">
|
||||||
|
<div className="tintbar absolute -top-[0.05rem] left-0 -translate-y-full w-full h-5 bg-gradient-to-b from-transparent to-background"></div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p>PytubePP Helper <span className="text-muted-foreground">|</span> <span className="text-sm text-muted-foreground">v{appVersion}-beta</span></p>
|
<p>PytubePP Helper <span className="text-muted-foreground">|</span> <span className="text-sm text-muted-foreground">v{appVersion}-beta</span></p>
|
||||||
<p className="text-xs text-muted-foreground">© {new Date().getFullYear()} - <a href="https://github.com/neosubhamoy/pytubepp-helper/blob/main/LICENSE" target="_blank">MIT License</a> - Made with ❤️ by <a href="https://neosubhamoy.com" target="_blank">Subhamoy</a></p>
|
<p className="text-xs text-muted-foreground">© {new Date().getFullYear()} - <a href="https://github.com/neosubhamoy/pytubepp-helper/blob/main/LICENSE" target="_blank">MIT License</a> - Made with ❤️ by <a href="https://neosubhamoy.com" target="_blank">Subhamoy</a></p>
|
||||||
|
|||||||
23
src/types.ts
23
src/types.ts
@@ -1,5 +1,7 @@
|
|||||||
export interface Config {
|
export interface Config {
|
||||||
port: number;
|
port: number;
|
||||||
|
theme: "system" | "dark" | "light";
|
||||||
|
notify_updates: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformInfo {
|
export interface PlatformInfo {
|
||||||
@@ -24,6 +26,10 @@ export interface InstalledPrograms {
|
|||||||
installed: boolean;
|
installed: boolean;
|
||||||
version: string | null;
|
version: string | null;
|
||||||
};
|
};
|
||||||
|
pacman: {
|
||||||
|
installed: boolean;
|
||||||
|
version: string | null;
|
||||||
|
};
|
||||||
brew: {
|
brew: {
|
||||||
installed: boolean;
|
installed: boolean;
|
||||||
version: string | null;
|
version: string | null;
|
||||||
@@ -70,4 +76,21 @@ export interface Stream {
|
|||||||
res: string;
|
res: string;
|
||||||
fps: string;
|
fps: string;
|
||||||
vcodec: string;
|
vcodec: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LatestExtensionResponse {
|
||||||
|
version: string;
|
||||||
|
notes: string;
|
||||||
|
browsers: {
|
||||||
|
chrome: {
|
||||||
|
url: string;
|
||||||
|
},
|
||||||
|
firefox: {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CurrentExtension {
|
||||||
|
version: string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user