diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c09760..11cfdca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,13 +1,13 @@
-
+> [!NOTE]
+> 🅱️ This is a Beta build.
# ⬇️ Downloads
-|
Windows |
macOS |
Linux |
-| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-arm64.AppImage) |
-|

| **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-arm64.deb) |
-| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.2.1/ROSI-Linux-aarch64.rpm) |
+|
Windows |
macOS |
Linux |
+| :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **EXE:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Windows-arm64.exe) | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-x86_64.AppImage) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-arm64.AppImage) |
+| 
| **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-amd64.deb) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-arm64.deb) |
+| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-x86_64.rpm) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v4.3.0-beta.1/ROSI-Linux-aarch64.rpm) |
> [!IMPORTANT]
> The `.sig` files in this repo are NOT normal GPG signatures — they are for ROSI's built-in updater to verify the integrity of updates before downloading and installing.
@@ -18,10 +18,22 @@
---
+## Changes in `v4.3.0-beta.1:`
+
+- **macOS:** Added a native application menu (About, Settings, Check for Updates, Edit, View, Window, Help) with actions wired into the existing UI.
+- **Progress:** yt-dlp and FFmpeg now report structured progress for more accurate in-app percentages, including merge and conversion phases.
+- **Taskbar / Dock:** Download and conversion progress appears in the Windows taskbar and macOS Dock; queue runs show an overall percent across items.
+- **Settings:** New **Taskbar / Dock progress** toggle (settings schema v6).
+
## Changes in `v4.2.1:`
- **PKG:** Updated packages.
+### FULL CHANGELOG:
+
+
+ ℹ️ Click here to see the full change-log for v4!
+
## Changes in `v4.2.0:`
- **NEW - Windows code signing:** WOO HOO!! Windows Codesigning is here!
@@ -53,11 +65,6 @@
- **Testing:** Cleaned up ESLint configuration to suppress `no-unsafe-*` noise in test files while keeping full strictness on production code.
- **PKG:** Updated packages.
-### FULL CHANGELOG:
-
-
- ℹ️ Click here to see the full change-log for v4!
-
## Changes in `v4.1.0:`
- **NEW - Preview:** Added video preview before downloading so ROSI can show the title, uploader, duration, thumbnail, playlist info, and other basic metadata before saving.
diff --git a/com.burnttoasters.rosi.metainfo.xml b/com.burnttoasters.rosi.metainfo.xml
index 4b70777..de3e019 100644
--- a/com.burnttoasters.rosi.metainfo.xml
+++ b/com.burnttoasters.rosi.metainfo.xml
@@ -34,6 +34,7 @@
+
diff --git a/package-lock.json b/package-lock.json
index 5190db6..c26cee5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "rosi",
- "version": "4.2.1",
+ "version": "4.3.0-beta.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rosi",
- "version": "4.2.1",
+ "version": "4.3.0-beta.1",
"license": "MPL-2.0",
"dependencies": {
"electron-log": "^5.3.4",
@@ -184,9 +184,9 @@
}
},
"node_modules/@csstools/css-calc": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
- "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz",
+ "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==",
"dev": true,
"funding": [
{
@@ -208,9 +208,9 @@
}
},
"node_modules/@csstools/css-color-parser": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz",
- "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==",
+ "version": "4.1.10",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz",
+ "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==",
"dev": true,
"funding": [
{
@@ -225,7 +225,7 @@
"license": "MIT",
"dependencies": {
"@csstools/color-helpers": "^6.1.0",
- "@csstools/css-calc": "^3.2.1"
+ "@csstools/css-calc": "^3.3.0"
},
"engines": {
"node": ">=20.19.0"
@@ -259,9 +259,9 @@
}
},
"node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz",
- "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==",
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz",
+ "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==",
"dev": true,
"funding": [
{
@@ -649,9 +649,9 @@
"license": "MIT"
},
"node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
- "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1493,17 +1493,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz",
- "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz",
+ "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/type-utils": "8.64.0",
- "@typescript-eslint/utils": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/type-utils": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -1516,7 +1516,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.64.0",
+ "@typescript-eslint/parser": "^8.65.0",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -1532,16 +1532,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz",
- "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz",
+ "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1557,14 +1557,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz",
- "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz",
+ "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.64.0",
- "@typescript-eslint/types": "^8.64.0",
+ "@typescript-eslint/tsconfig-utils": "^8.65.0",
+ "@typescript-eslint/types": "^8.65.0",
"debug": "^4.4.3"
},
"engines": {
@@ -1579,14 +1579,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz",
- "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz",
+ "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0"
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1597,9 +1597,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz",
- "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz",
+ "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1614,15 +1614,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz",
- "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz",
+ "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/utils": "8.64.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -1639,9 +1639,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz",
- "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
+ "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1653,16 +1653,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz",
- "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz",
+ "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.64.0",
- "@typescript-eslint/tsconfig-utils": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/visitor-keys": "8.64.0",
+ "@typescript-eslint/project-service": "8.65.0",
+ "@typescript-eslint/tsconfig-utils": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/visitor-keys": "8.65.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -1681,16 +1681,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz",
- "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz",
+ "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
- "@typescript-eslint/scope-manager": "8.64.0",
- "@typescript-eslint/types": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0"
+ "@typescript-eslint/scope-manager": "8.65.0",
+ "@typescript-eslint/types": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1705,13 +1705,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz",
- "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz",
+ "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.64.0",
+ "@typescript-eslint/types": "8.65.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -1933,33 +1933,14 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/ansi-escapes": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
- "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "environment": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ansi-regex": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
- "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ "node": ">=8"
}
},
"node_modules/ansi-styles": {
@@ -2526,39 +2507,6 @@
"node": ">=8"
}
},
- "node_modules/cli-cursor": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
- "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "restore-cursor": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cli-truncate": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz",
- "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "slice-ansi": "^8.0.0",
- "string-width": "^8.2.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -2574,72 +2522,6 @@
"node": ">=12"
}
},
- "node_modules/cliui/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cliui/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/clone-response": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
@@ -3110,9 +2992,9 @@
}
},
"node_modules/electron": {
- "version": "43.1.1",
- "resolved": "https://registry.npmjs.org/electron/-/electron-43.1.1.tgz",
- "integrity": "sha512-I5c5vfuVvaXpWx3IZdwvXgxQW44+e7OP1wXGVQkogLeSFSkUZ6sLCcWV05AdEcs65AO5tAIJJwbp7ixw+LdarA==",
+ "version": "43.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-43.2.0.tgz",
+ "integrity": "sha512-80zvrgG7ZRXD+tD0IyLvrnN9n+veSxadMRsMaC9wKKP3iUbtC7rGM8+dVuCmOb0Rrwwv8ESW4awnUZh9Hbp1fA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3342,19 +3224,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/environment": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
- "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/err-code": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
@@ -3648,13 +3517,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/eventemitter3": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
- "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/expect-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
@@ -3694,9 +3556,9 @@
"license": "MIT"
},
"node_modules/fast-uri": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
- "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
+ "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
"dev": true,
"funding": [
{
@@ -3813,9 +3675,9 @@
}
},
"node_modules/flatted": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
- "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz",
+ "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==",
"dev": true,
"license": "ISC"
},
@@ -3892,19 +3754,6 @@
"node": "6.* || 8.* || >= 10.*"
}
},
- "node_modules/get-east-asian-width": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
- "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@@ -4338,19 +4187,13 @@
}
},
"node_modules/is-fullwidth-code-point": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
- "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "get-east-asian-width": "^1.3.1"
- },
"engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
"node_modules/is-glob": {
@@ -4759,9 +4602,9 @@
}
},
"node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
@@ -4775,23 +4618,23 @@
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
- "lightningcss-android-arm64": "1.32.0",
- "lightningcss-darwin-arm64": "1.32.0",
- "lightningcss-darwin-x64": "1.32.0",
- "lightningcss-freebsd-x64": "1.32.0",
- "lightningcss-linux-arm-gnueabihf": "1.32.0",
- "lightningcss-linux-arm64-gnu": "1.32.0",
- "lightningcss-linux-arm64-musl": "1.32.0",
- "lightningcss-linux-x64-gnu": "1.32.0",
- "lightningcss-linux-x64-musl": "1.32.0",
- "lightningcss-win32-arm64-msvc": "1.32.0",
- "lightningcss-win32-x64-msvc": "1.32.0"
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
- "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
@@ -4810,9 +4653,9 @@
}
},
"node_modules/lightningcss-darwin-arm64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
- "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
@@ -4831,9 +4674,9 @@
}
},
"node_modules/lightningcss-darwin-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
- "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
@@ -4852,9 +4695,9 @@
}
},
"node_modules/lightningcss-freebsd-x64": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
- "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
@@ -4873,9 +4716,9 @@
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
- "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
@@ -4894,9 +4737,9 @@
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
- "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
@@ -4918,9 +4761,9 @@
}
},
"node_modules/lightningcss-linux-arm64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
- "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
@@ -4942,9 +4785,9 @@
}
},
"node_modules/lightningcss-linux-x64-gnu": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
- "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
@@ -4966,9 +4809,9 @@
}
},
"node_modules/lightningcss-linux-x64-musl": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
- "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
@@ -4990,9 +4833,9 @@
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
- "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
@@ -5011,9 +4854,9 @@
}
},
"node_modules/lightningcss-win32-x64-msvc": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
- "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
@@ -5032,14 +4875,13 @@
}
},
"node_modules/lint-staged": {
- "version": "17.0.8",
- "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.8.tgz",
- "integrity": "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==",
+ "version": "17.1.1",
+ "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.1.1.tgz",
+ "integrity": "sha512-FnHWpSe5cPRtrDG+soOuNdBxb4XQb2gN5EqpEWKdweyqyOfpl4QSjbrz3ilcIf0WXmkiNQGZZRQ23R5YtB3TEw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "listr2": "^10.2.1",
- "picomatch": "^4.0.4",
+ "picomatch": "^4.0.5",
"string-argv": "^0.3.2",
"tinyexec": "^1.2.4"
},
@@ -5056,23 +4898,6 @@
"yaml": "^2.9.0"
}
},
- "node_modules/listr2": {
- "version": "10.2.2",
- "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.2.tgz",
- "integrity": "sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cli-truncate": "^5.2.0",
- "eventemitter3": "^5.0.4",
- "log-update": "^6.1.0",
- "rfdc": "^1.4.1",
- "wrap-ansi": "^10.0.0"
- },
- "engines": {
- "node": ">=22.13.0"
- }
- },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -5109,99 +4934,6 @@
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
"license": "MIT"
},
- "node_modules/log-update": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
- "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-escapes": "^7.0.0",
- "cli-cursor": "^5.0.0",
- "slice-ansi": "^7.1.0",
- "strip-ansi": "^7.1.0",
- "wrap-ansi": "^9.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/log-update/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/log-update/node_modules/emoji-regex": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
- "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/log-update/node_modules/slice-ansi": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
- "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "is-fullwidth-code-point": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/log-update/node_modules/string-width": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
- "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^10.3.0",
- "get-east-asian-width": "^1.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/log-update/node_modules/wrap-ansi": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
- "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.1",
- "string-width": "^7.0.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
"node_modules/lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
@@ -5330,19 +5062,6 @@
"node": ">= 0.6"
}
},
- "node_modules/mimic-function": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
- "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@@ -5673,22 +5392,6 @@
"wrappy": "1"
}
},
- "node_modules/onetime": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
- "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-function": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5920,9 +5623,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.19",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
- "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
+ "version": "8.5.22",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
+ "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
"dev": true,
"funding": [
{
@@ -5940,7 +5643,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.12",
+ "nanoid": "^3.3.16",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -5989,9 +5692,9 @@
}
},
"node_modules/prettier": {
- "version": "3.9.5",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz",
- "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"dev": true,
"license": "MIT",
"bin": {
@@ -6277,36 +5980,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/restore-cursor": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
- "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "onetime": "^7.0.0",
- "signal-exit": "^4.1.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/restore-cursor/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
@@ -6317,13 +5990,6 @@
"node": ">= 4"
}
},
- "node_modules/rfdc": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
- "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
@@ -6518,36 +6184,6 @@
"node": ">=10"
}
},
- "node_modules/slice-ansi": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz",
- "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.2.3",
- "is-fullwidth-code-point": "^5.1.0"
- },
- "engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/chalk/slice-ansi?sponsor=1"
- }
- },
- "node_modules/slice-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/slide": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
@@ -6709,36 +6345,31 @@
}
},
"node_modules/string-width": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
- "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
- "get-east-asian-width": "^1.5.0",
- "strip-ansi": "^7.1.2"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
},
"engines": {
- "node": ">=20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=8"
}
},
"node_modules/strip-ansi": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
- "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-regex": "^6.2.2"
+ "ansi-regex": "^5.0.1"
},
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "node": ">=8"
}
},
"node_modules/sumchecker": {
@@ -6788,9 +6419,9 @@
"license": "MIT"
},
"node_modules/tar": {
- "version": "7.5.20",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz",
- "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==",
+ "version": "7.5.21",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.21.tgz",
+ "integrity": "sha512-XdhtCvlMywwxpCW8YEq3lOXBJpUPTR2OHHcwLPO3HwsJqOHa2Ok/oJ7ruGzp+JrKoRPVCzJwAdEjqLW/vNRPHA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -7057,16 +6688,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.64.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz",
- "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==",
+ "version": "8.65.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz",
+ "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.64.0",
- "@typescript-eslint/parser": "8.64.0",
- "@typescript-eslint/typescript-estree": "8.64.0",
- "@typescript-eslint/utils": "8.64.0"
+ "@typescript-eslint/eslint-plugin": "8.65.0",
+ "@typescript-eslint/parser": "8.65.0",
+ "@typescript-eslint/typescript-estree": "8.65.0",
+ "@typescript-eslint/utils": "8.65.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7450,36 +7081,23 @@
}
},
"node_modules/wrap-ansi": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz",
- "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-styles": "^6.2.3",
- "string-width": "^8.2.0",
- "strip-ansi": "^7.1.2"
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
},
"engines": {
- "node": ">=20"
+ "node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.3",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
- "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -7577,54 +7195,6 @@
"node": ">=12"
}
},
- "node_modules/yargs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
diff --git a/package.json b/package.json
index 8b7928b..d60a99b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "rosi",
- "version": "4.2.1",
+ "version": "4.3.0-beta.1",
"private": true,
"description": "Electron GUI for yt-dlp",
"desktopName": "com.burnttoasters.rosi.desktop",
diff --git a/src/main/appMenu.ts b/src/main/appMenu.ts
new file mode 100644
index 0000000..b32a31f
--- /dev/null
+++ b/src/main/appMenu.ts
@@ -0,0 +1,121 @@
+import { app, Menu, shell, type BrowserWindow } from 'electron';
+import type { MenuAction } from '../types';
+
+const DOCS_URL = 'https://github.com/BurntToasters/ROSI#readme';
+const ISSUES_URL = 'https://github.com/BurntToasters/ROSI/issues';
+
+export interface AppMenuOptions {
+ getMainWindow: () => BrowserWindow | null;
+ isMsStore: boolean;
+}
+
+function sendMenuAction(win: BrowserWindow | null, action: MenuAction): void {
+ if (!win || win.isDestroyed()) return;
+ win.webContents.send('menu-action', action);
+}
+
+export function configureAboutPanel(): void {
+ if (process.platform !== 'darwin') return;
+ app.setAboutPanelOptions({
+ applicationName: 'ROSI',
+ applicationVersion: app.getVersion(),
+ version: app.getVersion(),
+ copyright: 'Copyright © BurntToasters',
+ });
+}
+
+export function buildDarwinApplicationMenu(options: AppMenuOptions): Menu {
+ const { getMainWindow, isMsStore } = options;
+
+ const rosieMenuItems: Electron.MenuItemConstructorOptions[] = [
+ { role: 'about' },
+ { type: 'separator' },
+ {
+ label: 'Settings…',
+ accelerator: 'Cmd+,',
+ click: () => sendMenuAction(getMainWindow(), 'open-settings'),
+ },
+ ];
+
+ if (!isMsStore) {
+ rosieMenuItems.push({
+ label: 'Check for Updates…',
+ click: () => sendMenuAction(getMainWindow(), 'check-for-updates'),
+ });
+ }
+
+ rosieMenuItems.push(
+ { type: 'separator' },
+ { role: 'services' },
+ { type: 'separator' },
+ { role: 'hide' },
+ { role: 'hideOthers' },
+ { role: 'unhide' },
+ { type: 'separator' },
+ { role: 'quit' }
+ );
+
+ const template: Electron.MenuItemConstructorOptions[] = [
+ {
+ label: app.name,
+ submenu: rosieMenuItems,
+ },
+ {
+ label: 'Edit',
+ submenu: [
+ { role: 'undo' },
+ { role: 'redo' },
+ { type: 'separator' },
+ { role: 'cut' },
+ { role: 'copy' },
+ { role: 'paste' },
+ { role: 'selectAll' },
+ ],
+ },
+ {
+ label: 'View',
+ submenu: [
+ {
+ label: 'Toggle Sidebar',
+ click: () => sendMenuAction(getMainWindow(), 'toggle-sidebar'),
+ },
+ { type: 'separator' },
+ { role: 'togglefullscreen' },
+ ],
+ },
+ {
+ label: 'Window',
+ submenu: [{ role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }],
+ },
+ {
+ label: 'Help',
+ submenu: [
+ {
+ label: 'Documentation',
+ click: () => {
+ void shell.openExternal(DOCS_URL);
+ },
+ },
+ {
+ label: 'Report an Issue',
+ click: () => {
+ void shell.openExternal(ISSUES_URL);
+ },
+ },
+ { type: 'separator' },
+ {
+ label: 'View Licenses',
+ click: () => sendMenuAction(getMainWindow(), 'show-licenses'),
+ },
+ ],
+ },
+ ];
+
+ return Menu.buildFromTemplate(template);
+}
+
+export function installDarwinApplicationMenu(options: AppMenuOptions): void {
+ if (process.platform !== 'darwin') return;
+ configureAboutPanel();
+ Menu.setApplicationMenu(buildDarwinApplicationMenu(options));
+}
diff --git a/src/main/constants.ts b/src/main/constants.ts
index 950b775..ddff654 100644
--- a/src/main/constants.ts
+++ b/src/main/constants.ts
@@ -27,4 +27,4 @@ export const ALLOWED_BROWSERS = new Set([
export const MAX_QUEUE_SIZE = 500;
export const MAX_FORMAT_COUNTS = 10_000;
export const MAX_SETTINGS_IMPORT_BYTES = 1_048_576;
-export const CURRENT_SETTINGS_VERSION = 5;
+export const CURRENT_SETTINGS_VERSION = 6;
diff --git a/src/main/download/commandBuilders.ts b/src/main/download/commandBuilders.ts
index cbe6088..6b8c9bc 100644
--- a/src/main/download/commandBuilders.ts
+++ b/src/main/download/commandBuilders.ts
@@ -9,6 +9,7 @@ import {
MAX_ERROR_BUFFER,
SUBTITLE_LANGS_PATTERN,
} from '../constants';
+import { parseFfmpegDurationFromProbe } from '../../utils/downloadJobProgress';
const VALID_FORMAT_ID = FORMAT_ID_PATTERN;
const CODEC_PROBE_TIMEOUT_MS = 30_000;
@@ -17,6 +18,7 @@ export { ALLOWED_BROWSERS };
export interface SourceCodecs {
video?: string;
audio?: string;
+ durationSeconds?: number | null;
}
export function probeMediaCodecs(ffmpegCommand: string, inputPath: string): Promise {
@@ -37,6 +39,7 @@ export function probeMediaCodecs(ffmpegCommand: string, inputPath: string): Prom
const audioMatch = stderr.match(/Stream #\d+:\d+.*: Audio: (\w+)/);
if (videoMatch?.[1]) codecs.video = videoMatch[1];
if (audioMatch?.[1]) codecs.audio = audioMatch[1];
+ codecs.durationSeconds = parseFfmpegDurationFromProbe(stderr);
return codecs;
};
@@ -108,7 +111,18 @@ export function buildFfmpegArgs(
const canCopyAudio =
(targetFormat === 'm4a' && (srcAudio === 'aac' || srcAudio === 'mp4a')) ||
(targetFormat === 'mp3' && srcAudio === 'mp3');
- return ['-i', inputPath, '-vn', '-c:a', canCopyAudio ? 'copy' : targetCodec, '-y', outputPath];
+ return [
+ '-progress',
+ 'pipe:1',
+ '-nostats',
+ '-i',
+ inputPath,
+ '-vn',
+ '-c:a',
+ canCopyAudio ? 'copy' : targetCodec,
+ '-y',
+ outputPath,
+ ];
}
const srcVideo = srcCodecs?.video?.toLowerCase();
@@ -119,6 +133,9 @@ export function buildFfmpegArgs(
srcCodecs && srcAudio && CONTAINER_COMPATIBLE_AUDIO.has(srcAudio) ? 'copy' : 'aac';
return [
+ '-progress',
+ 'pipe:1',
+ '-nostats',
'-i',
inputPath,
'-c:v',
@@ -173,6 +190,10 @@ export function buildYtdlpArgs({
'--progress',
'--progress-delta',
'1',
+ '--progress-template',
+ 'download:%(progress)j',
+ '--progress-template',
+ 'postprocess:%(progress)j',
'-f',
settings.bestQuality ? 'bestvideo+bestaudio/best' : 'best[ext=mp4]/best[ext=webm]/best',
'--',
diff --git a/src/main/download/jobProgressReporter.ts b/src/main/download/jobProgressReporter.ts
new file mode 100644
index 0000000..b42551b
--- /dev/null
+++ b/src/main/download/jobProgressReporter.ts
@@ -0,0 +1,221 @@
+import type { BrowserWindow } from 'electron';
+import { applyTaskbarProgress } from '../taskbarProgress';
+import type {
+ DownloadJobPhase,
+ DownloadSession,
+ JobProgressEvent,
+ QueueDownloadProgress,
+} from '../../types';
+import {
+ buildJobProgressEvent,
+ formatYtdlpProgressSummary,
+ parseYtdlpProgress,
+ tryParseYtdlpProgressJson,
+ ytdlpJsonToPhasePercent,
+ type JobProgressPlan,
+ type YtdlpProgressJson,
+} from '../../utils/downloadJobProgress';
+
+const EMIT_INTERVAL_MS = 200;
+
+export class JobProgressReporter {
+ private lastEmitAt = 0;
+ private lastOverall = -1;
+ private plan: JobProgressPlan;
+ private queue: QueueDownloadProgress | null;
+ private mainWindow: BrowserWindow | null;
+ private showTaskbarProgress: boolean;
+
+ constructor(
+ mainWindow: BrowserWindow | null,
+ plan: JobProgressPlan,
+ queue: QueueDownloadProgress | null,
+ showTaskbarProgress: boolean
+ ) {
+ this.mainWindow = mainWindow;
+ this.plan = plan;
+ this.queue = queue;
+ this.showTaskbarProgress = showTaskbarProgress;
+ }
+
+ clearTaskbar(): void {
+ if (!this.showTaskbarProgress) return;
+ applyTaskbarProgress(this.mainWindow, 'none');
+ }
+
+ emitIdle(session: DownloadSession): void {
+ this.emit(session, {
+ phase: 'idle',
+ phasePercent: 0,
+ overallPercent: 0,
+ status: 'Ready',
+ indeterminate: false,
+ });
+ this.clearTaskbar();
+ }
+
+ handleYtdlpOutputLine(session: DownloadSession, line: string): boolean {
+ const trimmed = line.trim();
+ if (!trimmed) return false;
+
+ const json = tryParseYtdlpProgressJson(trimmed);
+ if (json) {
+ let phase: DownloadJobPhase = session.ytdlpPostprocess ? 'merge' : 'download';
+ if (!session.ytdlpPostprocess && json.status === 'finished') {
+ session.ytdlpDownloadFinished = true;
+ phase = 'download';
+ } else if (session.ytdlpDownloadFinished && !session.ytdlpPostprocess) {
+ session.ytdlpPostprocess = true;
+ phase = 'merge';
+ }
+ session.jobPhase = phase;
+
+ const phasePercent =
+ json.status === 'finished' && phase === 'download' ? 100 : ytdlpJsonToPhasePercent(json);
+ const { status, details } = formatYtdlpProgressSummary(json, phasePercent);
+ const label = phase === 'merge' ? 'Merging...' : status;
+ this.emit(
+ session,
+ buildJobProgressEvent(
+ phase,
+ phasePercent,
+ this.plan,
+ this.queue,
+ label,
+ details,
+ !Number.isFinite(phasePercent)
+ )
+ );
+ return true;
+ }
+
+ const legacy = parseYtdlpProgress(trimmed);
+ if (legacy) {
+ session.jobPhase = 'download';
+ let detailsText = '';
+ if (legacy.speed && legacy.eta) {
+ detailsText = `${legacy.totalSize} • ${legacy.speed} • ETA: ${legacy.eta}`;
+ } else if (legacy.totalSize) {
+ detailsText = `Size: ${legacy.totalSize}`;
+ }
+ this.emit(
+ session,
+ buildJobProgressEvent(
+ 'download',
+ legacy.percent,
+ this.plan,
+ this.queue,
+ 'Downloading...',
+ detailsText
+ )
+ );
+ return true;
+ }
+
+ if (trimmed.includes('Merging formats') || trimmed.includes('[Merger]')) {
+ session.jobPhase = 'merge';
+ session.ytdlpPostprocess = true;
+ this.emit(
+ session,
+ buildJobProgressEvent(
+ 'merge',
+ Number.NaN,
+ this.plan,
+ this.queue,
+ 'Merging video and audio...',
+ undefined,
+ true
+ )
+ );
+ return false;
+ }
+
+ return false;
+ }
+
+ emitConvertProgress(
+ session: DownloadSession,
+ phasePercent: number | null,
+ status = 'Converting...',
+ details?: string
+ ): void {
+ session.jobPhase = 'convert';
+ const percent = phasePercent ?? Number.NaN;
+ this.emit(
+ session,
+ buildJobProgressEvent(
+ 'convert',
+ percent,
+ this.plan,
+ this.queue,
+ status,
+ details,
+ phasePercent === null || !Number.isFinite(percent)
+ )
+ );
+ }
+
+ emitPhaseIndeterminate(session: DownloadSession, phase: DownloadJobPhase, status: string): void {
+ session.jobPhase = phase;
+ this.emit(
+ session,
+ buildJobProgressEvent(phase, Number.NaN, this.plan, this.queue, status, undefined, true)
+ );
+ }
+
+ emitDownloadComplete(session: DownloadSession): void {
+ const phase: DownloadJobPhase = this.plan.expectConvert ? 'convert' : 'download';
+ this.emit(
+ session,
+ buildJobProgressEvent(
+ phase,
+ 100,
+ this.plan,
+ this.queue,
+ 'Download complete',
+ undefined,
+ false
+ )
+ );
+ }
+
+ private emit(session: DownloadSession, event: JobProgressEvent): void {
+ const now = Date.now();
+ const overallRounded = Math.round(event.overallPercent * 10) / 10;
+ if (
+ now - this.lastEmitAt < EMIT_INTERVAL_MS &&
+ overallRounded === this.lastOverall &&
+ !event.indeterminate
+ ) {
+ return;
+ }
+ this.lastEmitAt = now;
+ this.lastOverall = overallRounded;
+
+ if (!session.sender || session.sender.isDestroyed()) return;
+ try {
+ session.sender.send('job-progress', event);
+ } catch {
+ // ignore destroyed sender
+ }
+
+ if (!this.showTaskbarProgress) return;
+ if (event.phase === 'idle') {
+ applyTaskbarProgress(this.mainWindow, 'none');
+ return;
+ }
+ if (event.indeterminate) {
+ applyTaskbarProgress(this.mainWindow, 'indeterminate');
+ return;
+ }
+ applyTaskbarProgress(this.mainWindow, event.overallPercent / 100);
+ }
+}
+
+export function summarizeYtdlpJsonForConsole(data: YtdlpProgressJson): string | null {
+ if (data.status !== 'downloading' && data.status !== 'finished') return null;
+ const phasePercent = ytdlpJsonToPhasePercent(data);
+ const { details } = formatYtdlpProgressSummary(data, phasePercent);
+ const prefix = data.status === 'finished' ? '[download] Complete' : '[download]';
+ return `${prefix} ${details}`;
+}
diff --git a/src/main/downloader.ts b/src/main/downloader.ts
index f356ad5..3aca9db 100644
--- a/src/main/downloader.ts
+++ b/src/main/downloader.ts
@@ -18,6 +18,12 @@ import {
resolveVideoEncoder,
probeMediaCodecs,
} from './download/commandBuilders';
+import { JobProgressReporter, summarizeYtdlpJsonForConsole } from './download/jobProgressReporter';
+import {
+ createFfmpegProgressParseState,
+ parseFfmpegProgressChunk,
+ resolveJobProgressPlanFromSettings,
+} from '../utils/downloadJobProgress';
import { isSafeHttpUrl } from '../utils/validation';
import { isMac } from './platform';
import {
@@ -41,12 +47,14 @@ import type {
DownloadOutcome,
FormatsProcess,
Settings,
+ QueueDownloadProgress,
} from '../types';
let activeDownloadSession: DownloadSession | null = null;
let downloadSessionOwner: DownloadSessionOwner | null = null;
let downloadSessionCounter = 0;
let formatsProcess: FormatsProcess | null = null;
+let activeProgressReporter: JobProgressReporter | null = null;
export function getDownloadSessionOwner(): DownloadSessionOwner | null {
return downloadSessionOwner;
@@ -80,6 +88,40 @@ function sendProgress(session: DownloadSession | null, message: string) {
safeSend(session.sender, 'progress', message);
}
+function handleYtdlpOutputLines(
+ session: DownloadSession,
+ reporter: JobProgressReporter,
+ chunk: string,
+ isStderr: boolean
+) {
+ const lines = chunk.split(/\r?\n/);
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed) continue;
+
+ if (trimmed.startsWith('{')) {
+ reporter.handleYtdlpOutputLine(session, trimmed);
+ try {
+ const parsed = JSON.parse(trimmed) as Parameters[0];
+ const summary = summarizeYtdlpJsonForConsole(parsed);
+ if (summary) sendProgress(session, summary);
+ } catch {
+ // ignore invalid json
+ }
+ continue;
+ }
+
+ const handled = reporter.handleYtdlpOutputLine(session, trimmed);
+ if (handled) continue;
+
+ if (isStderr) {
+ sendProgress(session, `[yt-dlp stderr] ${trimmed}`);
+ } else {
+ sendProgress(session, trimmed);
+ }
+ }
+}
+
interface CompletionMeta {
format?: string;
bytes?: number;
@@ -125,6 +167,11 @@ function completeSession(
activeDownloadSession = null;
downloadSessionOwner = null;
+ if (activeProgressReporter) {
+ activeProgressReporter.emitIdle(session);
+ activeProgressReporter.clearTaskbar();
+ activeProgressReporter = null;
+ }
}
function killProcess(proc: ChildProcess | null, label: string) {
@@ -161,6 +208,10 @@ export function cancelActiveSession(notify = true) {
session.lifecycle = markTerminalEventEmitted(session.lifecycle);
activeDownloadSession = null;
downloadSessionOwner = null;
+ if (activeProgressReporter) {
+ activeProgressReporter.clearTaskbar();
+ activeProgressReporter = null;
+ }
}
export function killAllProcesses() {
@@ -184,6 +235,10 @@ export function killAllProcesses() {
session.lifecycle = markTerminalEventEmitted(session.lifecycle);
activeDownloadSession = null;
downloadSessionOwner = null;
+ if (activeProgressReporter) {
+ activeProgressReporter.clearTaskbar();
+ activeProgressReporter = null;
+ }
}
export function fetchFormats(ytdlpPath: string, url: string): Promise {
@@ -271,7 +326,8 @@ async function runConversion(
downloadedFilePath: string,
effectiveSettings: Settings,
ffmpegCommand: string,
- mainWindow: Electron.BrowserWindow | null
+ mainWindow: Electron.BrowserWindow | null,
+ progressReporter: JobProgressReporter | null
) {
sendProgress(session, '⏳ Checking if conversion is needed...');
try {
@@ -323,6 +379,8 @@ async function runConversion(
const srcCodecs = await probeMediaCodecs(ffmpegCommand, inputPath);
const videoEncoder = await resolveVideoEncoder(effectiveSettings);
+ progressReporter?.emitConvertProgress(session, 0, 'Converting...');
+
const ffmpegArgs = buildFfmpegArgs(
inputPath,
outputPath,
@@ -342,6 +400,8 @@ async function runConversion(
});
session.ffmpegProcess = ffProc;
+ const ffmpegProgressState = createFfmpegProgressParseState(srcCodecs.durationSeconds ?? null);
+
const conversionTimeout = setTimeout(() => {
if (!isActiveSession(session) || !ffProc || ffProc.killed) return;
sendProgress(session, '❌ Conversion timed out after 10 minutes.');
@@ -351,11 +411,15 @@ async function runConversion(
ffProc.stdout?.on('data', (data: Buffer) => {
if (!isActiveSession(session)) return;
- sendProgress(session, `[ffmpeg] ${data.toString().trim()}`);
+ const percent = parseFfmpegProgressChunk(ffmpegProgressState, data.toString());
+ if (percent !== null) {
+ progressReporter?.emitConvertProgress(session, percent, 'Converting...');
+ }
});
ffProc.stderr?.on('data', (data: Buffer) => {
if (!isActiveSession(session)) return;
- sendProgress(session, `[ffmpeg] ${data.toString().trim()}`);
+ const text = data.toString().trim();
+ if (text) sendProgress(session, `[ffmpeg] ${text}`);
});
ffProc.on('close', (ffmpegCode) => {
@@ -372,6 +436,7 @@ async function runConversion(
}
if (ffExitType === 'success') {
+ progressReporter?.emitConvertProgress(session, 100, 'Conversion complete');
sendProgress(session, `🎉 Successfully converted to ${outputPath}`);
const shouldDelete = !effectiveSettings.keepOriginalAfterConvert;
const pathsDiffer = isWindows
@@ -455,7 +520,8 @@ export function startDownload(
options: DownloadRequestOptions,
mainWindow: Electron.BrowserWindow | null,
onComplete?: (statusMessage: string, outcome?: DownloadOutcome) => void,
- owner: DownloadSessionOwner = 'manual'
+ owner: DownloadSessionOwner = 'manual',
+ queueProgress: QueueDownloadProgress | null = null
) {
if (activeDownloadSession && downloadSessionOwner !== owner) {
throw new Error('Download session already active with a different owner.');
@@ -473,6 +539,10 @@ export function startDownload(
ytdlpProcess: null,
ffmpegProcess: null,
onComplete,
+ queueProgress,
+ jobPhase: 'download',
+ ytdlpPostprocess: false,
+ ytdlpDownloadFinished: false,
};
activeDownloadSession = session;
downloadSessionOwner = owner;
@@ -547,6 +617,23 @@ export function startDownload(
});
statusMessages.forEach((message) => sendProgress(session, message));
+ const progressPlan = resolveJobProgressPlanFromSettings({
+ downloadProfilesEnabled: effectiveSettings.downloadProfilesEnabled,
+ downloadMode: effectiveSettings.downloadMode,
+ bestQuality: effectiveSettings.bestQuality,
+ audioOnly: effectiveSettings.audioOnly,
+ advancedOptions: effectiveSettings.advancedOptions,
+ convertEnabled: effectiveSettings.convertEnabled,
+ });
+ const progressReporter = new JobProgressReporter(
+ mainWindow,
+ progressPlan,
+ queueProgress,
+ effectiveSettings.showTaskbarProgress
+ );
+ activeProgressReporter = progressReporter;
+ progressReporter.emitPhaseIndeterminate(session, 'download', 'Starting download...');
+
sendProgress(session, `🚀 Starting download: ${url}`);
sendProgress(session, ` Command: ${ytdlpBinary} ${ytdlpArgs.join(' ')}`);
const ytProc = spawnWithEnv(ytdlpPath, ytdlpArgs, {
@@ -567,7 +654,7 @@ export function startDownload(
downloadOutputData = downloadOutputData.slice(-MAX_OUTPUT_BUFFER / 2);
}
downloadOutputData += message;
- sendProgress(session, message.trim());
+ handleYtdlpOutputLines(session, progressReporter, message, false);
});
ytProc.stderr?.on('data', (data: Buffer) => {
@@ -576,7 +663,7 @@ export function startDownload(
if (downloadErrorData.length < MAX_ERROR_BUFFER) {
downloadErrorData += message;
}
- sendProgress(session, `[yt-dlp stderr] ${message.trim()}`);
+ handleYtdlpOutputLines(session, progressReporter, message, true);
});
ytProc.on('close', (code) => {
@@ -674,10 +761,12 @@ export function startDownload(
downloadedFilePath,
effectiveSettings,
ffmpegCommand,
- mainWindow
+ mainWindow,
+ progressReporter
);
} else {
sendProgress(session, 'ℹ️ Conversion not enabled for this download.');
+ progressReporter.emitDownloadComplete(session);
const ext = path.extname(downloadedFilePath).replace('.', '').toLowerCase() || undefined;
completeSession(session, 'âś… Download complete (no conversion).', 'success', {
format: ext,
diff --git a/src/main/main.ts b/src/main/main.ts
index e6c6c5e..8eb6386 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -45,6 +45,7 @@ import {
validateDownloadPath,
} from '../utils/ipcValidation';
import { SPLASH_SHOW_DELAY_MS, SPLASH_FADE_DELAY_MS, MAX_QUEUE_SIZE } from './constants';
+import { installDarwinApplicationMenu } from './appMenu';
import type { DownloadRequestOptions, DownloadOutcome, QueueItem } from '../types';
log.initialize();
@@ -404,7 +405,12 @@ function createWindow() {
mainWindow.setMenuBarVisibility(isDev);
mainWindow.setAutoHideMenuBar(!isDev);
- if (!isDev) {
+ if (process.platform === 'darwin') {
+ installDarwinApplicationMenu({
+ getMainWindow: () => mainWindow,
+ isMsStore: process.env.CHANNEL === 'msstore' || Boolean(process.windowsStore),
+ });
+ } else if (!isDev) {
mainWindow.removeMenu();
}
@@ -1077,6 +1083,15 @@ async function processQueue() {
keepOriginal: settings.convertEnabled ? settings.keepOriginalAfterConvert : undefined,
};
+ const completedItems = downloadQueue.filter(
+ (item) =>
+ item.status === 'completed' || item.status === 'failed' || item.status === 'cancelled'
+ ).length;
+ const queueProgress = {
+ completedItems,
+ queueTotal: downloadQueue.length,
+ };
+
let settled = false;
const completeListener = (statusMessage: string, outcome?: DownloadOutcome) => {
if (settled) return;
@@ -1105,7 +1120,8 @@ async function processQueue() {
options,
mainWindow,
completeListener,
- 'queue'
+ 'queue',
+ queueProgress
);
} catch (error) {
nextItem.status = 'failed';
diff --git a/src/main/preload.ts b/src/main/preload.ts
index ae797d8..32f52bf 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -2,6 +2,8 @@ import { contextBridge, ipcRenderer } from 'electron';
import type {
DownloadRequestOptions,
DownloadStats,
+ JobProgressEvent,
+ MenuAction,
NotificationRequest,
QueueItem,
RendererApi,
@@ -50,6 +52,16 @@ const api: RendererApi = {
ipcRenderer.on('progress', listener);
return () => ipcRenderer.removeListener('progress', listener);
},
+ onJobProgress: (callback: (data: JobProgressEvent) => void) => {
+ const listener = (_: Electron.IpcRendererEvent, data: JobProgressEvent) => callback(data);
+ ipcRenderer.on('job-progress', listener);
+ return () => ipcRenderer.removeListener('job-progress', listener);
+ },
+ onMenuAction: (callback: (action: MenuAction) => void) => {
+ const listener = (_: Electron.IpcRendererEvent, action: MenuAction) => callback(action);
+ ipcRenderer.on('menu-action', listener);
+ return () => ipcRenderer.removeListener('menu-action', listener);
+ },
onComplete: (callback: (message: string) => void) => {
const listener = (_: Electron.IpcRendererEvent, message: string) => callback(message);
ipcRenderer.on('complete', listener);
diff --git a/src/main/settings.ts b/src/main/settings.ts
index 7e808a3..0de2ead 100644
--- a/src/main/settings.ts
+++ b/src/main/settings.ts
@@ -54,6 +54,7 @@ const defaultSettings: Settings = {
embedThumbnail: false,
embedMetadata: false,
sponsorblockRemove: false,
+ showTaskbarProgress: true,
};
export function getDefaultSettings(): Settings {
@@ -244,6 +245,10 @@ export function migrateSettings(rawSettings: unknown): Settings {
rawSettings.sponsorblockRemove,
defaultSettings.sponsorblockRemove
),
+ showTaskbarProgress: readBoolean(
+ rawSettings.showTaskbarProgress,
+ defaultSettings.showTaskbarProgress
+ ),
};
}
diff --git a/src/main/taskbarProgress.ts b/src/main/taskbarProgress.ts
new file mode 100644
index 0000000..2261d14
--- /dev/null
+++ b/src/main/taskbarProgress.ts
@@ -0,0 +1,23 @@
+import type { BrowserWindow } from 'electron';
+
+export type TaskbarProgressMode = number | 'indeterminate' | 'none';
+
+export function applyTaskbarProgress(
+ win: BrowserWindow | null | undefined,
+ mode: TaskbarProgressMode
+): void {
+ if (!win || win.isDestroyed()) return;
+ const platform = process.platform;
+ if (platform !== 'win32' && platform !== 'darwin') return;
+
+ if (mode === 'none') {
+ win.setProgressBar(-1);
+ return;
+ }
+ if (mode === 'indeterminate') {
+ win.setProgressBar(2);
+ return;
+ }
+ const clamped = Math.max(0, Math.min(1, mode));
+ win.setProgressBar(clamped);
+}
diff --git a/src/renderer/index.html b/src/renderer/index.html
index f075243..d3dc4eb 100644
--- a/src/renderer/index.html
+++ b/src/renderer/index.html
@@ -347,6 +347,12 @@
Download notifications
+
+
diff --git a/src/renderer/modules/downloads.ts b/src/renderer/modules/downloads.ts
index dcf11e4..7c4c738 100644
--- a/src/renderer/modules/downloads.ts
+++ b/src/renderer/modules/downloads.ts
@@ -18,6 +18,7 @@
type RosiWindow = Window & typeof globalThis & { rosiModules?: DownloadsModules };
function parseYtdlpProgress(message: string): ParsedYtdlpProgress | null {
+ // Keep in sync with src/utils/downloadJobProgress.ts (canonical for main process).
const progressMatch = message.match(
/\[download\]\s+(\d+\.?\d*)%\s+of\s+~?(\S+)\s+at\s+(\S+)\s+ETA\s+(\S+)/
);
diff --git a/src/renderer/renderer-globals.d.ts b/src/renderer/renderer-globals.d.ts
index c2f5296..1ded808 100644
--- a/src/renderer/renderer-globals.d.ts
+++ b/src/renderer/renderer-globals.d.ts
@@ -24,6 +24,14 @@ interface RosiUpdaterProgressEvent {
transferred: number;
total: number;
}
+interface RosiJobProgressEvent {
+ phase: 'download' | 'merge' | 'convert' | 'idle';
+ phasePercent: number;
+ overallPercent: number;
+ status: string;
+ details?: string;
+ indeterminate?: boolean;
+}
interface RosiDownloadStats {
totalDownloads: number;
successfulDownloads: number;
@@ -66,6 +74,12 @@ interface RosiRendererApi {
onUpdaterStatus: (callback: (data: RosiUpdaterStatusEvent) => void) => () => void;
onUpdaterProgress: (callback: (data: RosiUpdaterProgressEvent) => void) => () => void;
onProgress: (callback: (message: string) => void) => () => void;
+ onJobProgress: (callback: (data: RosiJobProgressEvent) => void) => () => void;
+ onMenuAction: (
+ callback: (
+ action: 'check-for-updates' | 'open-settings' | 'show-licenses' | 'toggle-sidebar'
+ ) => void
+ ) => () => void;
onComplete: (callback: (message: string) => void) => () => void;
openFileLocation: (filePath: string) => Promise>;
showNotification: (options: {
diff --git a/src/renderer/rosiEngine.ts b/src/renderer/rosiEngine.ts
index 48ab888..97882e7 100644
--- a/src/renderer/rosiEngine.ts
+++ b/src/renderer/rosiEngine.ts
@@ -41,6 +41,47 @@ interface RosiSettings {
embedThumbnail: boolean;
embedMetadata: boolean;
sponsorblockRemove: boolean;
+ showTaskbarProgress: boolean;
+}
+
+interface RosiJobProgressEvent {
+ phase: 'download' | 'merge' | 'convert' | 'idle';
+ phasePercent: number;
+ overallPercent: number;
+ status: string;
+ details?: string;
+ indeterminate?: boolean;
+}
+
+function resolveProgressPhaseFlags(
+ activeSettings: RosiSettings,
+ advancedFormats?: { videoFormat?: string; audioFormat?: string }
+) {
+ if (
+ activeSettings.audioOnly ||
+ (activeSettings.downloadProfilesEnabled && activeSettings.downloadMode === 'audio')
+ ) {
+ return { needsMerge: false, needsConvert: activeSettings.convertEnabled };
+ }
+ const baseMerge = activeSettings.downloadProfilesEnabled
+ ? activeSettings.downloadMode === 'best-video' || activeSettings.downloadMode === 'custom'
+ : activeSettings.bestQuality || activeSettings.advancedOptions;
+ const needsMerge =
+ Boolean(advancedFormats?.videoFormat && advancedFormats?.audioFormat) || baseMerge;
+ return {
+ needsMerge,
+ needsConvert: activeSettings.convertEnabled,
+ };
+}
+
+function applyActiveDownloadProgressPhases(
+ activeSettings: RosiSettings,
+ status = 'Downloading...',
+ advancedFormats?: { videoFormat?: string; audioFormat?: string }
+) {
+ const { needsMerge, needsConvert } = resolveProgressPhaseFlags(activeSettings, advancedFormats);
+ configureProgressPhases(needsMerge, needsConvert);
+ showProgressBar(status);
}
const rosiModules = window.rosiModules || {};
@@ -484,7 +525,7 @@ function showKeyboardShortcuts() {
const modKey = getModifierKeyName();
showModal({
title: 'Keyboard Shortcuts',
- message: `${modKey}+D - Restart application\n${modKey}+F - Focus URL input field\n${modKey}+, - Open settings\n${modKey}+Enter - Submit queue URLs (when focused)`,
+ message: `${modKey}+D - Restart application\n${modKey}+F - Focus URL input field\n${modKey}+, - Open settings (macOS menu)\n${modKey}+Shift+, - Toggle settings sidebar\n${modKey}+Enter - Submit queue URLs (when focused)`,
buttons: [{ label: 'OK', primary: true }],
});
}
@@ -849,6 +890,20 @@ function setProgressIndeterminate(status = 'Processing...') {
if (details) details.textContent = '';
}
+function applyJobProgress(event: RosiJobProgressEvent) {
+ if (event.phase && event.phase !== 'idle') {
+ setProgressPhase(event.phase);
+ }
+ if (event.indeterminate) {
+ setProgressIndeterminate(event.status);
+ return;
+ }
+ const displayPercent = Number.isFinite(event.overallPercent)
+ ? event.overallPercent
+ : event.phasePercent;
+ updateProgressBar(displayPercent, event.status, event.details ?? null);
+}
+
function hideProgressBar() {
const container = document.getElementById('progress-container');
const bar = document.getElementById('progress-bar');
@@ -863,13 +918,6 @@ function hideProgressBar() {
hideProgressComplete();
}
-function parseYtdlpProgress(message: string) {
- if (downloadsModule && typeof downloadsModule.parseYtdlpProgress === 'function') {
- return downloadsModule.parseYtdlpProgress(message);
- }
- return null;
-}
-
function formatBytes(bytes: number) {
if (downloadsModule && typeof downloadsModule.formatBytes === 'function') {
return downloadsModule.formatBytes(bytes);
@@ -1710,7 +1758,7 @@ document.addEventListener('DOMContentLoaded', async () => {
} catch (error) {
logError('Failed to load settings', error);
settings = {
- settingsVersion: 5,
+ settingsVersion: 6,
theme: 'system',
showConsoleOutput: false,
consoleCollapsed: false,
@@ -1744,6 +1792,7 @@ document.addEventListener('DOMContentLoaded', async () => {
embedThumbnail: false,
embedMetadata: false,
sponsorblockRemove: false,
+ showTaskbarProgress: true,
};
showModal({
title: 'Settings Error',
@@ -1886,6 +1935,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const askDownloadLocationToggle = byId('askDownloadLocationToggle');
const downloadOutputSummary = byId('downloadOutputSummary');
const notificationsToggle = byId('notificationsToggle');
+ const taskbarProgressToggle = byId('taskbarProgressToggle');
const checkUpdatesOnStartupToggle = byId('checkUpdatesOnStartupToggle');
const checkUpdatesOnStartupLabel = byId('checkUpdatesOnStartupLabel');
const updateChannelSelect = byId('updateChannelSelect');
@@ -2141,6 +2191,9 @@ document.addEventListener('DOMContentLoaded', async () => {
if (notificationsToggle) {
notificationsToggle.checked = settings.notifications ?? true;
}
+ if (taskbarProgressToggle) {
+ taskbarProgressToggle.checked = settings.showTaskbarProgress ?? true;
+ }
if (embedMetadataToggle) {
embedMetadataToggle.checked = settings.embedMetadata ?? false;
@@ -2814,6 +2867,13 @@ document.addEventListener('DOMContentLoaded', async () => {
});
}
+ if (taskbarProgressToggle) {
+ taskbarProgressToggle.addEventListener('change', (e) => {
+ settings.showTaskbarProgress = (e.target as HTMLInputElement).checked;
+ void persistSettings();
+ });
+ }
+
if (embedMetadataToggle) {
embedMetadataToggle.addEventListener('change', (e) => {
settings.embedMetadata = (e.target as HTMLInputElement).checked;
@@ -3246,6 +3306,7 @@ document.addEventListener('DOMContentLoaded', async () => {
try {
const result = await window.api.startQueue();
if (result && result.ok) {
+ applyActiveDownloadProgressPhases(settings, 'Queue download...');
announceQueueAction('Queue started processing.');
} else {
const message = result?.error?.message || 'Could not start the queue.';
@@ -3428,10 +3489,10 @@ document.addEventListener('DOMContentLoaded', async () => {
const videoFormat = settings.advancedOptions ? videoSelect?.value : undefined;
const audioFormat = settings.advancedOptions ? audioSelect?.value : undefined;
const convertFormat = settings.convertEnabled ? convertFormatSelect?.value : undefined;
- const needsMerge = settings.bestQuality || (videoFormat && audioFormat);
- const needsConvert = settings.convertEnabled && convertFormat;
- configureProgressPhases(!!needsMerge, !!needsConvert);
- showProgressBar('Starting download...');
+ applyActiveDownloadProgressPhases(settings, 'Starting download...', {
+ videoFormat,
+ audioFormat,
+ });
const keepOriginal = settings.convertEnabled ? keepOriginalToggle?.checked : undefined;
const startResult = await window.api.downloadVideo({
url,
@@ -3476,27 +3537,6 @@ document.addEventListener('DOMContentLoaded', async () => {
if (!outputEl) return;
appendConsoleOutput(outputEl, message);
- const progress = parseYtdlpProgress(message);
- if (progress) {
- let detailsText = '';
- if (progress.speed && progress.eta) {
- detailsText = `${progress.totalSize} • ${progress.speed} • ETA: ${progress.eta}`;
- } else if (progress.totalSize) {
- detailsText = `Size: ${progress.totalSize}`;
- }
- updateProgressBar(progress.percent, 'Downloading...', detailsText);
- } else if (message.includes('[download] Destination:')) {
- setProgressIndeterminate('Preparing download...');
- } else if (message.includes('Merging formats')) {
- setProgressPhase('merge');
- setProgressIndeterminate('Merging video and audio...');
- } else if (message.includes('Converting') || message.includes('[ffmpeg]')) {
- setProgressPhase('convert');
- setProgressIndeterminate('Converting...');
- } else if (message.includes('100%')) {
- updateProgressBar(100, 'Download complete!', '');
- }
-
if (message.includes('Identified file:') || message.includes('Successfully converted to')) {
const fileMatch = message.match(/(?:Identified file:|Successfully converted to)\s*(.+)$/);
if (fileMatch && fileMatch[1]) {
@@ -3506,6 +3546,42 @@ document.addEventListener('DOMContentLoaded', async () => {
})
);
+ ipcCleanupFunctions.push(
+ window.api.onJobProgress((event) => {
+ const container = document.getElementById('progress-container');
+ if (container && !container.classList.contains('visible') && event.phase !== 'idle') {
+ showProgressBar(event.status);
+ }
+ if (event.phase === 'idle') {
+ return;
+ }
+ applyJobProgress(event);
+ })
+ );
+
+ ipcCleanupFunctions.push(
+ window.api.onMenuAction((action) => {
+ if (action === 'check-for-updates') {
+ void checkForUpdates();
+ return;
+ }
+ if (action === 'open-settings') {
+ const sidebar = document.getElementById('sidebar');
+ if (sidebar && !sidebar.classList.contains('open')) {
+ toggleSidebar();
+ }
+ return;
+ }
+ if (action === 'toggle-sidebar') {
+ toggleSidebar();
+ return;
+ }
+ if (action === 'show-licenses') {
+ showLicenses();
+ }
+ })
+ );
+
ipcCleanupFunctions.push(
window.api.onComplete((statusMessage) => {
if (downloadBtn) {
@@ -3722,7 +3798,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
}
- if (modifierPressed && event.key === ',') {
+ if (modifierPressed && event.shiftKey && event.key === ',') {
event.preventDefault();
toggleSidebar();
}
diff --git a/src/renderer/splash.html b/src/renderer/splash.html
index fa3d728..5d5cb29 100644
--- a/src/renderer/splash.html
+++ b/src/renderer/splash.html
@@ -327,7 +327,7 @@ ROSI
Loading
- v4.2.1
+ v4.3.0-beta.1