yt-grabber 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.yarnrc.yml +3 -1
  2. package/README.md +99 -8
  3. package/eslint.config.ts +60 -0
  4. package/package.json +68 -32
  5. package/public/banner.png +0 -0
  6. package/public/profile/cookies.json +18 -0
  7. package/public/screenshots/main-downloading.png +0 -0
  8. package/public/screenshots/main-info.png +0 -0
  9. package/public/screenshots/main.png +0 -0
  10. package/public/screenshots/settings.png +0 -0
  11. package/src/@types/busylight.d.ts +1 -0
  12. package/src/App.styl +64 -21
  13. package/src/App.tsx +31 -4
  14. package/src/automations/Selectors.ts +7 -0
  15. package/src/automations/Youtube.ts +138 -0
  16. package/src/common/FileSystem.ts +5 -168
  17. package/src/common/Formatters.ts +25 -0
  18. package/src/common/Helpers.ts +40 -254
  19. package/src/common/Media.ts +28 -0
  20. package/src/common/Messaging.ts +38 -0
  21. package/src/common/Promise.ts +14 -0
  22. package/src/common/PuppeteerOptions.ts +3 -2
  23. package/src/common/Reporter.ts +61 -0
  24. package/src/common/Store.ts +26 -26
  25. package/src/common/Youtube.ts +34 -5
  26. package/src/common/YtdplUtils.ts +158 -0
  27. package/src/components/appBar/AppBar.styl +43 -17
  28. package/src/components/appBar/AppBar.tsx +28 -7
  29. package/src/components/fileField/FileField.tsx +25 -81
  30. package/src/components/languagePicker/LanguagePicker.styl +36 -36
  31. package/src/components/languagePicker/LanguagePicker.tsx +9 -9
  32. package/src/components/modals/{DetailsModal.styl → detailsModal/DetailsModal.styl} +1 -3
  33. package/src/components/modals/{DetailsModal.tsx → detailsModal/DetailsModal.tsx} +3 -7
  34. package/src/components/modals/imageModal/ImageModal.styl +25 -0
  35. package/src/components/modals/imageModal/ImageModal.tsx +72 -0
  36. package/src/components/numberField/NumberField.styl +14 -12
  37. package/src/components/progress/Progress.tsx +15 -6
  38. package/src/components/themePicker/ThemePicker.styl +19 -19
  39. package/src/components/youtube/formatSelector/FormatSelector.tsx +61 -43
  40. package/src/components/youtube/infoBar/InfoBar.styl +41 -0
  41. package/src/components/youtube/infoBar/InfoBar.tsx +66 -0
  42. package/src/components/youtube/inputPanel/InputPanel.styl +5 -0
  43. package/src/components/youtube/inputPanel/InputPanel.tsx +137 -115
  44. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.styl +90 -80
  45. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.tsx +98 -19
  46. package/src/components/youtube/playlistTabs/PlaylistTabs.styl +67 -0
  47. package/src/components/youtube/playlistTabs/PlaylistTabs.tsx +238 -0
  48. package/src/components/youtube/trackList/TrackList.styl +11 -7
  49. package/src/components/youtube/trackList/TrackList.tsx +112 -39
  50. package/src/hooks/useHelp.ts +109 -0
  51. package/src/hooks/useWindowUpdater.ts +24 -0
  52. package/src/i18next.ts +23 -4
  53. package/src/index.ts +99 -6
  54. package/src/react/actions/AppActions.ts +3 -16
  55. package/src/react/contexts/AppContext.tsx +1 -6
  56. package/src/react/contexts/DataContext.tsx +73 -15
  57. package/src/react/reducers/AppReducer.tsx +2 -18
  58. package/src/react/states/AppState.ts +2 -11
  59. package/src/resources/bin/yt-dlp.exe +0 -0
  60. package/src/resources/icons/logo.ico +0 -0
  61. package/src/resources/locales/de-DE/help.json +120 -0
  62. package/src/resources/locales/de-DE/translation.json +26 -6
  63. package/src/resources/locales/en-GB/help.json +120 -0
  64. package/src/resources/locales/en-GB/translation.json +25 -5
  65. package/src/resources/locales/pl-PL/help.json +120 -0
  66. package/src/resources/locales/pl-PL/translation.json +28 -8
  67. package/src/styles/MaterialThemes.ts +1 -3
  68. package/src/views/development/DevelopmentView.styl +5 -0
  69. package/src/views/development/DevelopmentView.tsx +32 -3
  70. package/src/views/home/HomeView.styl +12 -4
  71. package/src/views/home/HomeView.tsx +365 -340
  72. package/src/views/settings/SettingsView.styl +10 -0
  73. package/src/views/settings/SettingsView.tsx +54 -22
  74. package/tests/TestMultipleMock.ts +91779 -0
  75. package/webpack.config.ts +14 -10
  76. package/public/screenshots/cutting.png +0 -0
  77. package/public/screenshots/downloading.png +0 -0
  78. package/public/screenshots/editing.png +0 -0
  79. package/public/screenshots/errors.png +0 -0
  80. package/public/screenshots/tracklist.png +0 -0
  81. package/src/common/Mappings.ts +0 -14
  82. package/src/common/Selectors.ts +0 -21
  83. package/src/components/directoryPicker/DirectoryPicker.tsx +0 -44
  84. package/src/components/splitButton/SplitButton.styl +0 -0
  85. package/src/components/splitButton/SplitButton.tsx +0 -125
  86. package/src/components/themeSwitcher/ThemeSwitcher.styl +0 -10
  87. package/src/components/themeSwitcher/ThemeSwitcher.tsx +0 -43
  88. package/src/enums/DataResponse.ts +0 -5
  89. package/src/enums/Media.ts +0 -16
  90. package/src/enums/MediaFormat.ts +0 -10
  91. package/src/enums/MimeTypes.ts +0 -14
  92. package/src/hooks/useData.ts +0 -61
  93. package/src/react/contexts/DataContext copy.tsx +0 -76
  94. package/src/resources/images/logo.png +0 -0
  95. package/src/tests/MissingDetailsTracksMock.ts +0 -7737
  96. /package/{src/tests/CompleteTracksMock.ts → tests/TestPlaylistMock.ts} +0 -0
package/.yarnrc.yml CHANGED
@@ -10,4 +10,6 @@ packageExtensions:
10
10
  "react": "^19.0.0"
11
11
  peerDependencies:
12
12
  "react": "^19.0.0"
13
-
13
+ "eslint@*":
14
+ dependencies:
15
+ "globals": "^16.0.0"
package/README.md CHANGED
@@ -1,11 +1,102 @@
1
- ## Install:
2
- `yarn install`
1
+ <img src="public/banner.png" alt="YT Grabber Banner" width="800">
3
2
 
4
- ## Build:
5
- `yarn build`
3
+ ---
6
4
 
7
- ## Start:
8
- `yarn start`
5
+ **YT Grabber** is a robust desktop application designed to retrieve multimedia from YouTube and YouTube Music services.
9
6
 
10
- ## Usage:
11
- Download and run latest binary release.
7
+ It provides responsive UI to manage your downloads and automation features improve and accelerate download process.
8
+
9
+ It provides support for downloading:
10
+
11
+ - videos
12
+ - audio tracks
13
+ - complete playlists
14
+ - full discographies
15
+
16
+ Various formats and quality options are available for both - audio and video.
17
+
18
+ Each download can be customized to your needs for easy workflow automation.
19
+
20
+ ## Table of Contents
21
+
22
+ - [Table of Contents](#table-of-contents)
23
+ - [Features](#features)
24
+ - [Screenshots](#screenshots)
25
+ - [Usage](#usage)
26
+ - [Development](#development)
27
+ - [Running](#running)
28
+ - [Packaging](#packaging)
29
+ - [License](#license)
30
+ - [Legal Disclaimer](#legal-disclaimer)
31
+
32
+
33
+ ## Features
34
+
35
+ * Download video, audio, playlists and complete artist discographies
36
+
37
+ * Multiple output formats (mp3, m4u, flac, wav, mp4, mkv)
38
+
39
+ * Customizable audio and video quality
40
+
41
+ * Trimming video and audio
42
+
43
+ * Batch multimedia download
44
+
45
+ * Metadata (tags) embedding
46
+
47
+ * Configurable output
48
+
49
+ * Multiple language support (English, German, Polish out of the box)
50
+
51
+ * Light/Dark theme
52
+
53
+ * Responsive and clean UI
54
+
55
+
56
+ ## Screenshots
57
+
58
+ <img src="public/screenshots/main.png" alt="Main" width="600">
59
+
60
+ *Main window*
61
+
62
+ <img src="public/screenshots/main-info.png" alt="Main: Loading Info" width="600">
63
+
64
+ *Displaying multimedia info*
65
+
66
+ <img src="public/screenshots/main-downloading.png" alt="Main: Downloading Files" width="600">
67
+
68
+ *Downloading multimedia*
69
+
70
+ <img src="public/screenshots/settings.png" alt="Settings" width="600">
71
+
72
+ *Settings*
73
+
74
+ ## Usage
75
+
76
+ Download and run latest release installer from [here](https://github.com/karenpommeroy/yt-grabber/releases).
77
+
78
+ ## Development
79
+
80
+ To build **yt-grabber** follow these steps:
81
+
82
+ 1. Clone this repository
83
+ 2. Install dependencies using `npm install` or `yarn install` command
84
+ 3. If using `yarn` with Visual Studio Code also run `yarn dlx @yarnpkg/sdks vscode`
85
+ 4. Run `npm build` or `yarn build` command to build
86
+
87
+ ## Running
88
+
89
+ Run `npm start` or `yarn start` to run the app for development.
90
+ This will start webpack development server that will watch for changes to source code and reload the application automatically.
91
+
92
+ ## Packaging
93
+
94
+ To prepare release application package run `npm dist` or `yarn dist` command.
95
+
96
+ ## License
97
+
98
+ This project is licensed under the [MIT License](LICENSE).
99
+
100
+ ## Legal Disclaimer
101
+
102
+ All music files downloaded through this software must be legally owned and purchased by the user. By downloading music via this software, you represent that you have purchased and fully own the rights to any downloaded content or an active subscription to YouTube Music. Downloading or distributing pirated or illegal music copies is strictly prohibited. I claim no ownership rights to any downloaded music files - all such rights remain with the content owner. I accept no liability for the illegal use of any files downloaded through this software.
@@ -0,0 +1,60 @@
1
+ import react from "eslint-plugin-react";
2
+ import {defineConfig} from "eslint/config";
3
+ import globals from "globals";
4
+ import path from "node:path";
5
+ import {fileURLToPath} from "node:url";
6
+
7
+ import {FlatCompat} from "@eslint/eslintrc";
8
+ import js from "@eslint/js";
9
+ import typescriptEslint from "@typescript-eslint/eslint-plugin";
10
+ import tsParser from "@typescript-eslint/parser";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const compat = new FlatCompat({
15
+ baseDirectory: __dirname,
16
+ recommendedConfig: js.configs.recommended,
17
+ allConfig: js.configs.all
18
+ });
19
+
20
+ export default defineConfig([{
21
+ extends: compat.extends(
22
+ "eslint:recommended",
23
+ "plugin:react/recommended",
24
+ "plugin:@typescript-eslint/recommended",
25
+ "prettier",
26
+ ),
27
+
28
+ plugins: {
29
+ react,
30
+ "@typescript-eslint": typescriptEslint as any,
31
+ },
32
+
33
+ languageOptions: {
34
+ globals: {
35
+ ...globals.browser,
36
+ },
37
+
38
+ parser: tsParser,
39
+ ecmaVersion: "latest",
40
+ sourceType: "module",
41
+ },
42
+
43
+ settings: {
44
+ react: {
45
+ version: "detect",
46
+ },
47
+ },
48
+
49
+ rules: {
50
+ indent: ["error", 4],
51
+ "linebreak-style": ["error", "windows"],
52
+ quotes: ["error", "double"],
53
+ semi: ["error", "always"],
54
+ "@typescript-eslint/no-var-requires": 0,
55
+ "@typescript-eslint/no-explicit-any": 0,
56
+ "@typescript-eslint/no-empty-interface": 0,
57
+ "react/prop-types": 0,
58
+ "react/display-name": 0,
59
+ },
60
+ }]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-grabber",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Youtube Grabber",
5
5
  "main": "./dist/index.js",
6
6
  "repository": {
@@ -9,28 +9,56 @@
9
9
  "scripts": {
10
10
  "clean": "rimraf dist",
11
11
  "start": "concurrently \"cross-env NODE_ENV=development yarn run watch\" \"yarn run electron\"",
12
- "build": "cross-env NODE_ENV=development && yarn clean && webpack --config webpack.config.ts",
13
- "build:prod": "cross-env NODE_ENV=production && yarn clean && webpack --config webpack.config.ts",
14
- "electron": "wait-on dist/index.js && cross-env NODE_ENV=production ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
12
+ "build": "yarn clean && \"cross-env NODE_ENV=development webpack\" --config webpack.config.ts",
13
+ "build:prod": "yarn clean && \"cross-env NODE_ENV=production webpack\" --config webpack.config.ts",
14
+ "electron": "wait-on dist/index.js && ELECTRON_DISABLE_SECURITY_WARNINGS=true electron .",
15
15
  "watch": "webpack --watch --config webpack.config.ts",
16
16
  "pack": "yarn build:prod && electron-builder --dir",
17
17
  "dist": "yarn build:prod && electron-builder"
18
18
  },
19
19
  "build": {
20
20
  "appId": "yt.grabber",
21
+ "productName": "YT Grabber",
22
+ "copyright": "© 2025 Marcin Karpiński",
21
23
  "files": [
22
24
  "dist/**/*",
23
25
  "package.json"
24
26
  ],
27
+ "asar": true,
28
+ "win": {
29
+ "target": "nsis",
30
+ "icon": "dist/resources/icons/logo.ico",
31
+ "artifactName": "${productName}-setup-${version}.${ext}"
32
+ },
33
+ "nsis": {
34
+ "oneClick": false,
35
+ "allowToChangeInstallationDirectory": true,
36
+ "installerIcon": "dist/resources/icons/logo.ico",
37
+ "uninstallerIcon": "dist/resources/icons/logo.ico",
38
+ "installerHeaderIcon": "dist/resources/icons/logo.ico",
39
+ "createDesktopShortcut": true,
40
+ "createStartMenuShortcut": true,
41
+ "shortcutName": "YT Grabber"
42
+ },
25
43
  "directories": {
26
44
  "output": "release"
27
45
  },
28
46
  "extraResources": [
29
- {
30
- "from": "dist/resources/bin",
31
- "to": "bin",
32
- "filter": ["*.exe"]
33
- }
47
+ {
48
+ "from": "dist/resources/bin",
49
+ "to": "bin",
50
+ "filter": [
51
+ "*.exe"
52
+ ]
53
+ },
54
+ {
55
+ "from": "dist/resources/locales",
56
+ "to": "locales"
57
+ },
58
+ {
59
+ "from": "public/profile",
60
+ "to": "profile"
61
+ }
34
62
  ]
35
63
  },
36
64
  "author": "Marcin Karpiński <mkarpins@gmail.com>",
@@ -38,10 +66,14 @@
38
66
  "dependencies": {
39
67
  "@emotion/react": "^11.14.0",
40
68
  "@emotion/styled": "^11.14.0",
41
- "@mui/icons-material": "^6.4.2",
42
- "@mui/material": "^6.4.2",
69
+ "@mui/icons-material": "^7.0.0",
70
+ "@mui/lab": "^7.0.0-beta.9",
71
+ "@mui/material": "^7.0.0",
72
+ "@pureit/busylight": "^1.0.12",
73
+ "axios": "^1.8.4",
43
74
  "classnames": "^2.5.1",
44
75
  "electron-devtools-installer": "^4.0.0",
76
+ "electron-reload": "^2.0.0-alpha.1",
45
77
  "electron-store": "^8.2.0",
46
78
  "fs-extra": "^11.3.0",
47
79
  "i18next": "^24.2.2",
@@ -51,17 +83,18 @@
51
83
  "mkdirp": "^3.0.1",
52
84
  "moment": "^2.30.1",
53
85
  "moment-duration-format": "^2.3.2",
54
- "puppeteer": "^24.1.1",
55
- "puppeteer-core": "^24.1.1",
86
+ "mui-image": "^1.0.7",
87
+ "puppeteer": "^24.4.0",
88
+ "puppeteer-core": "^24.4.0",
56
89
  "puppeteer-extra": "^3.3.6",
57
90
  "puppeteer-extra-plugin-devtools": "^2.4.6",
58
91
  "puppeteer-extra-plugin-stealth": "^2.11.2",
59
92
  "react": "^19.0.0",
60
93
  "react-dom": "^19.0.0",
61
- "react-i18next": "^15.4.0",
94
+ "react-i18next": "^15.4.1",
62
95
  "react-number-format": "^5.4.3",
63
- "react-router-dom": "^7.1.5",
64
- "usehooks-ts": "^3.1.0",
96
+ "react-router-dom": "^7.4.0",
97
+ "usehooks-ts": "^3.1.1",
65
98
  "yt-dlp-wrap": "^2.3.12"
66
99
  },
67
100
  "devDependencies": {
@@ -70,32 +103,35 @@
70
103
  "@types/i18next": "^13.0.0",
71
104
  "@types/i18next-node-fs-backend": "^2.1.5",
72
105
  "@types/jsonschema": "^1.1.1",
73
- "@types/lodash": "^4.17.15",
106
+ "@types/lodash": "^4.17.16",
74
107
  "@types/moment-duration-format": "^2.2.6",
75
- "@types/node": "^22.13.1",
108
+ "@types/mui-image": "^1.0.5",
109
+ "@types/node": "^22.13.14",
76
110
  "@types/prettier": "^3.0.0",
77
111
  "@types/puppeteer-core": "^7.0.4",
78
- "@types/react": "^19.0.8",
79
- "@types/react-dom": "^19.0.3",
112
+ "@types/react": "^19.0.12",
113
+ "@types/react-dom": "^19.0.4",
80
114
  "@types/webpack-dev-server": "^4.7.2",
81
115
  "@types/webpack-env": "^1.18.8",
82
- "@typescript-eslint/eslint-plugin": "^8.23.0",
83
- "@typescript-eslint/parser": "^8.23.0",
116
+ "@typescript-eslint/eslint-plugin": "^8.28.0",
117
+ "@typescript-eslint/parser": "^8.28.0",
84
118
  "concurrently": "^9.1.2",
85
- "copy-webpack-plugin": "^12.0.2",
119
+ "copy-webpack-plugin": "^13.0.0",
86
120
  "cross-env": "^7.0.3",
87
121
  "css-loader": "^7.1.2",
88
- "electron": "^34.0.2",
89
- "electron-builder": "^25.1.8",
90
- "eslint": "^9.19.0",
91
- "eslint-config-prettier": "^10.0.1",
122
+ "electron": "^35.1.2",
123
+ "electron-builder": "^26.0.12",
124
+ "eslint": "^9.23.0",
125
+ "eslint-config-prettier": "^10.1.1",
92
126
  "eslint-plugin-css": "^0.11.0",
93
127
  "eslint-plugin-react": "^7.37.4",
94
- "eslint-webpack-plugin": "^4.2.0",
128
+ "eslint-webpack-plugin": "^5.0.0",
129
+ "globals": "^16.0.0",
95
130
  "html-webpack-plugin": "^5.6.3",
96
131
  "i18next-scanner-webpack": "^0.9.1",
132
+ "jiti": "^2.4.2",
97
133
  "mini-css-extract-plugin": "^2.9.2",
98
- "prettier": "^3.4.2",
134
+ "prettier": "^3.5.3",
99
135
  "raw-loader": "^4.0.2",
100
136
  "rimraf": "^6.0.1",
101
137
  "source-map-loader": "^5.0.0",
@@ -104,11 +140,11 @@
104
140
  "ts-loader": "^9.5.2",
105
141
  "ts-node": "^10.9.2",
106
142
  "ts-node-dev": "^2.0.0",
107
- "typescript": "^5.7.3",
143
+ "typescript": "^5.8.2",
108
144
  "wait-on": "^8.0.2",
109
- "webpack": "^5.97.1",
145
+ "webpack": "^5.98.0",
110
146
  "webpack-cli": "^6.0.1",
111
- "webpack-dev-server": "^5.2.0",
147
+ "webpack-dev-server": "^5.2.1",
112
148
  "webpack-node-externals": "^3.0.0"
113
149
  },
114
150
  "packageManager": "yarn@4.4.1"
Binary file
@@ -0,0 +1,18 @@
1
+ [
2
+ {
3
+ "name": "SOCS",
4
+ "value": "CAISNQgREitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjUwMzI2LjA4X3AwGgJwbCACGgYIgKqSvwY",
5
+ "domain": ".youtube.com",
6
+ "path": "/",
7
+ "expires": 2298668700.829343,
8
+ "size": 91,
9
+ "httpOnly": false,
10
+ "secure": true,
11
+ "session": false,
12
+ "sameSite": "Lax",
13
+ "priority": "Medium",
14
+ "sameParty": false,
15
+ "sourceScheme": "Secure",
16
+ "sourcePort": 443
17
+ }
18
+ ]
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ declare module "@pureit/busylight";
package/src/App.styl CHANGED
@@ -1,24 +1,67 @@
1
1
  @import "./styles/mixins.styl"
2
2
  @import "./styles/fonts.styl"
3
3
 
4
- html
5
- font-size: 1rem
6
- font-family: "Lato"
7
-
8
- body
9
- margin: 0
10
- padding: 0
11
- no-select()
12
-
13
- :global(#root)
14
- position: absolute
15
- width: 100%
16
- height: 100%
17
- overflow: hidden
18
-
19
- .app
20
- background-color: var(--theme-palette-background-paper);
21
- display: flex
22
- flex-direction: column
23
- height: 100vh
24
- width: 100vw
4
+ html {
5
+ font-size: 1rem;
6
+ font-family: "Lato";
7
+
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ no-select();
12
+
13
+ :global(#root) {
14
+ position: absolute;
15
+ width: 100%;
16
+ height: 100%;
17
+ overflow: hidden;
18
+ }
19
+
20
+ .app {
21
+ background-color: var(--theme-palette-background-paper);
22
+ display: flex;
23
+ flex-direction: column;
24
+ height: 100vh;
25
+ width: 100vw;
26
+
27
+ &.help {
28
+ cursor: help !important;
29
+ }
30
+ }
31
+
32
+ .help-popup {
33
+ z-index: 9999999;
34
+ padding: 12px;
35
+
36
+ .header {
37
+ font-weight: bold;
38
+ margin-bottom: 8px;
39
+ }
40
+
41
+ .content {
42
+ p {
43
+ &:first-child {
44
+ margin-top: 0;
45
+ }
46
+
47
+ &:last-child {
48
+ margin-bottom: 0;
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ :global(#help-backdrop) {
55
+ position: absolute;
56
+ width: 100vw;
57
+ height: 100vh;
58
+ top: 0;
59
+ left: 0;
60
+ right: 0;
61
+ bottom: 0;
62
+ background: rgba(0, 0, 0, 0.5);
63
+ z-index: 1;
64
+ vendor("backdrop-filter", blur(2px));
65
+ }
66
+ }
67
+ }
package/src/App.tsx CHANGED
@@ -1,28 +1,55 @@
1
+ import classnames from "classnames";
2
+ import _isArray from "lodash/isArray";
3
+ import _map from "lodash/map";
4
+ import _split from "lodash/split";
1
5
  import React from "react";
2
6
  import {HashRouter, Route, Routes} from "react-router-dom";
3
7
 
4
- import {Box, CssBaseline} from "@mui/material";
8
+ import {Box, CssBaseline, Paper, Popper, Typography} from "@mui/material";
5
9
 
6
10
  import Styles from "./App.styl";
7
11
  import AppBar from "./components/appBar/AppBar";
12
+ import useHelp from "./hooks/useHelp";
8
13
  import {useAppContext} from "./react/contexts/AppContext";
9
14
  import DevelopmentView from "./views/development/DevelopmentView";
10
15
  import {HomeView} from "./views/home/HomeView";
11
16
  import SettingsView from "./views/settings/SettingsView";
12
17
 
13
18
  export const App: React.FC = () => {
14
- const { state } = useAppContext();
19
+ const {state} = useAppContext();
20
+ const {anchorEl, help} = useHelp();
21
+
22
+ const renderLineBreaks = (value: string) => {
23
+ return _map(_split(value, "\n"), (v, k) => <React.Fragment key={k}>{v}<br /></React.Fragment>);
24
+ };
25
+
26
+ const renderText = (value: string | string[]) => {
27
+ const valueArray = _isArray(value) ? value : [value];
28
+
29
+ return _map(valueArray, (v, k) => <p key={k}>{renderLineBreaks(v)}</p>);
30
+ };
15
31
 
16
32
  return (
17
33
  <HashRouter>
18
- <Box className={Styles.app}>
34
+ <Box className={classnames(Styles.app, {[Styles.help]: state.help})}>
19
35
  <CssBaseline enableColorScheme />
20
- <AppBar />
36
+ <AppBar disableNavigation={state.loading} />
21
37
  <Routes location={state.location}>
22
38
  <Route path="/" element={<HomeView />} />
23
39
  <Route path="/settings" element={<SettingsView />} />
24
40
  <Route path="/development" element={<DevelopmentView />} />
25
41
  </Routes>
42
+ <Popper
43
+ className={Styles.helpPopup}
44
+ open={Boolean(anchorEl)}
45
+ anchorEl={anchorEl}
46
+ disablePortal
47
+ >
48
+ <Paper sx={{p: 2}}>
49
+ <Typography className={Styles.header} color="textSecondary">{help.header}</Typography>
50
+ <Typography component="div" className={Styles.content}>{renderText(help.content)}</Typography>
51
+ </Paper>
52
+ </Popper>
26
53
  </Box>
27
54
  </HashRouter>
28
55
  );
@@ -0,0 +1,7 @@
1
+ export const AlbumsHrefSelector = "//ytmusic-app-layout//div[@id='content']/ytmusic-browse-response//ytmusic-section-list-renderer//div[@id='content-group']//div[contains(@class, 'header-renderer')]/yt-formatted-string/a[contains(text(), 'Album')]";
2
+
3
+ export const AlbumFilterSelector = "//ytmusic-app-layout//div[@id='content']/ytmusic-browse-response//ytmusic-section-list-renderer/div[@id='header']//iron-selector//a[contains(@title, 'Album')]";
4
+
5
+ export const AlbumLinkSelector = "//ytmusic-app-layout//div[@id='content']/ytmusic-browse-response//ytmusic-section-list-renderer/div[@id='contents']/ytmusic-grid-renderer/div[@id='items']/*[contains(@class, 'ytmusic-grid-renderer')]//a[contains(@class, 'yt-simple-endpoint') and contains(@class, 'yt-formatted-string')]";
6
+
7
+ export const AlbumsDirectLinkSelector = "//ytmusic-app-layout//div[@id='content']/ytmusic-browse-response//ytmusic-section-list-renderer//div[@id='content-group']//div[contains(@class, 'header-renderer')]/yt-formatted-string[contains(text(), 'Album')]//ancestor::div[contains(@class, 'ytmusic-shelf')]//div[@id='items-wrapper']/ul/*[contains(@class, 'ytmusic-carousel')]//a[contains(@class, 'yt-simple-endpoint') and contains(@class, 'yt-formatted-string')]";