yt-grabber 1.0.0

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 (128) hide show
  1. package/.eslintrc.json +29 -0
  2. package/.prettierrc +19 -0
  3. package/.vscode/extensions.json +7 -0
  4. package/.vscode/settings.json +23 -0
  5. package/.yarnrc.yml +13 -0
  6. package/LICENSE +21 -0
  7. package/README.md +11 -0
  8. package/package.json +115 -0
  9. package/public/index.html +20 -0
  10. package/public/screenshots/cutting.png +0 -0
  11. package/public/screenshots/downloading.png +0 -0
  12. package/public/screenshots/editing.png +0 -0
  13. package/public/screenshots/errors.png +0 -0
  14. package/public/screenshots/settings.png +0 -0
  15. package/public/screenshots/tracklist.png +0 -0
  16. package/src/@types/global.d.ts +7 -0
  17. package/src/@types/i18next-scanner-webpack.d.ts +1 -0
  18. package/src/@types/stylus.d.ts +4 -0
  19. package/src/@types/svg.d.ts +1 -0
  20. package/src/App.styl +24 -0
  21. package/src/App.tsx +31 -0
  22. package/src/bootstrap.tsx +30 -0
  23. package/src/common/CancellablePromise.ts +22 -0
  24. package/src/common/ComponentDisplayMode.ts +8 -0
  25. package/src/common/Delay.ts +3 -0
  26. package/src/common/FileSystem.ts +171 -0
  27. package/src/common/Helpers.ts +270 -0
  28. package/src/common/Mappings.ts +14 -0
  29. package/src/common/PuppeteerOptions.ts +45 -0
  30. package/src/common/Selectors.ts +21 -0
  31. package/src/common/Store.ts +108 -0
  32. package/src/common/Theme.ts +4 -0
  33. package/src/common/Youtube.ts +80 -0
  34. package/src/components/appBar/AppBar.styl +22 -0
  35. package/src/components/appBar/AppBar.tsx +73 -0
  36. package/src/components/directoryPicker/DirectoryPicker.tsx +44 -0
  37. package/src/components/fileField/FileField.styl +3 -0
  38. package/src/components/fileField/FileField.tsx +152 -0
  39. package/src/components/languagePicker/LanguagePicker.styl +38 -0
  40. package/src/components/languagePicker/LanguagePicker.tsx +145 -0
  41. package/src/components/logo/Logo.tsx +15 -0
  42. package/src/components/modals/DetailsModal.styl +9 -0
  43. package/src/components/modals/DetailsModal.tsx +85 -0
  44. package/src/components/numberField/NumberField.styl +13 -0
  45. package/src/components/numberField/NumberField.tsx +154 -0
  46. package/src/components/progress/Progress.styl +15 -0
  47. package/src/components/progress/Progress.tsx +18 -0
  48. package/src/components/splitButton/SplitButton.styl +0 -0
  49. package/src/components/splitButton/SplitButton.tsx +125 -0
  50. package/src/components/themePicker/ThemePicker.styl +19 -0
  51. package/src/components/themePicker/ThemePicker.tsx +65 -0
  52. package/src/components/themeSwitcher/ThemeSwitcher.styl +10 -0
  53. package/src/components/themeSwitcher/ThemeSwitcher.tsx +43 -0
  54. package/src/components/youtube/formatSelector/FormatSelector.styl +3 -0
  55. package/src/components/youtube/formatSelector/FormatSelector.tsx +202 -0
  56. package/src/components/youtube/inputPanel/InputPanel.styl +7 -0
  57. package/src/components/youtube/inputPanel/InputPanel.tsx +189 -0
  58. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.styl +80 -0
  59. package/src/components/youtube/mediaInfoPanel/MediaInfoPanel.tsx +113 -0
  60. package/src/components/youtube/trackList/TrackList.styl +64 -0
  61. package/src/components/youtube/trackList/TrackList.tsx +258 -0
  62. package/src/enums/DataResponse.ts +5 -0
  63. package/src/enums/Media.ts +16 -0
  64. package/src/enums/MediaFormat.ts +10 -0
  65. package/src/enums/MimeTypes.ts +14 -0
  66. package/src/hooks/useCancellablePromises.ts +25 -0
  67. package/src/hooks/useClickCounter.ts +24 -0
  68. package/src/hooks/useData.ts +61 -0
  69. package/src/hooks/useMultiClickHandler.ts +41 -0
  70. package/src/i18next.ts +33 -0
  71. package/src/index.ts +65 -0
  72. package/src/react/actions/Action.ts +3 -0
  73. package/src/react/actions/AppActions.ts +41 -0
  74. package/src/react/contexts/AppContext.tsx +51 -0
  75. package/src/react/contexts/AppThemeContext.tsx +38 -0
  76. package/src/react/contexts/DataContext copy.tsx +76 -0
  77. package/src/react/contexts/DataContext.tsx +41 -0
  78. package/src/react/hooks/useAppTheme.ts +14 -0
  79. package/src/react/reducers/AppReducer.tsx +45 -0
  80. package/src/react/reducers/Reducer.ts +7 -0
  81. package/src/react/states/AppState.ts +29 -0
  82. package/src/react/states/State.ts +29 -0
  83. package/src/renderer.tsx +13 -0
  84. package/src/resources/bin/yt-dlp.exe +0 -0
  85. package/src/resources/fonts/Baloo-Regular.ttf +0 -0
  86. package/src/resources/fonts/Lato-Black.ttf +0 -0
  87. package/src/resources/fonts/Lato-BlackItalic.ttf +0 -0
  88. package/src/resources/fonts/Lato-Bold.ttf +0 -0
  89. package/src/resources/fonts/Lato-BoldItalic.ttf +0 -0
  90. package/src/resources/fonts/Lato-Italic.ttf +0 -0
  91. package/src/resources/fonts/Lato-Light.ttf +0 -0
  92. package/src/resources/fonts/Lato-LightItalic.ttf +0 -0
  93. package/src/resources/fonts/Lato-Regular.ttf +0 -0
  94. package/src/resources/fonts/Lato-Thin.ttf +0 -0
  95. package/src/resources/fonts/Lato-ThinItalic.ttf +0 -0
  96. package/src/resources/fonts/Material-Icons.woff2 +0 -0
  97. package/src/resources/icons/favicon-16x16.png +0 -0
  98. package/src/resources/icons/favicon-32x32.png +0 -0
  99. package/src/resources/icons/favicon.ico +0 -0
  100. package/src/resources/icons/logo-shape.png +0 -0
  101. package/src/resources/icons/logo-shape.svg +59 -0
  102. package/src/resources/images/loading.svg +28 -0
  103. package/src/resources/images/logo.png +0 -0
  104. package/src/resources/locales/de-DE/flag.svg +1 -0
  105. package/src/resources/locales/de-DE/translation.json +44 -0
  106. package/src/resources/locales/en-GB/flag.svg +43 -0
  107. package/src/resources/locales/en-GB/translation.json +44 -0
  108. package/src/resources/locales/pl-PL/flag.svg +36 -0
  109. package/src/resources/locales/pl-PL/translation.json +44 -0
  110. package/src/styles/MaterialThemes.ts +331 -0
  111. package/src/styles/fonts.styl +71 -0
  112. package/src/styles/mixins.styl +22 -0
  113. package/src/tests/CompleteTracksMock.ts +17384 -0
  114. package/src/tests/MissingDetailsTracksMock.ts +7737 -0
  115. package/src/theme/ColorThemes.ts +190 -0
  116. package/src/theme/Colors.ts +92 -0
  117. package/src/theme/Shadows.ts +9 -0
  118. package/src/theme/Shape.ts +7 -0
  119. package/src/theme/Theme.ts +24 -0
  120. package/src/theme/Typography.ts +56 -0
  121. package/src/views/development/DevelopmentView.styl +22 -0
  122. package/src/views/development/DevelopmentView.tsx +57 -0
  123. package/src/views/home/HomeView.styl +60 -0
  124. package/src/views/home/HomeView.tsx +505 -0
  125. package/src/views/settings/SettingsView.styl +27 -0
  126. package/src/views/settings/SettingsView.tsx +255 -0
  127. package/tsconfig.json +20 -0
  128. package/webpack.config.ts +226 -0
@@ -0,0 +1,255 @@
1
+ import _filter from "lodash/filter";
2
+ import _first from "lodash/first";
3
+ import _get from "lodash/get";
4
+ import _includes from "lodash/includes";
5
+ import _isEmpty from "lodash/isEmpty";
6
+ import _isNil from "lodash/isNil";
7
+ import _join from "lodash/join";
8
+ import _map from "lodash/map";
9
+ import _omitBy from "lodash/omitBy";
10
+ import React, {useEffect, useState} from "react";
11
+ import {useTranslation} from "react-i18next";
12
+ import {useDebounceValue} from "usehooks-ts";
13
+
14
+ import {Box, Button, FormControlLabel, Paper, Switch, TextField} from "@mui/material";
15
+ import Grid from "@mui/material/Grid2";
16
+
17
+ import StoreSchema, {ApplicationOptions} from "../../common/Store";
18
+ import FileField from "../../components/fileField/FileField";
19
+ import NumberField from "../../components/numberField/NumberField";
20
+ import ThemePicker from "../../components/themePicker/ThemePicker";
21
+ import {useAppContext} from "../../react/contexts/AppContext";
22
+ import Styles from "./SettingsView.styl";
23
+
24
+ export const SettingsView: React.FC = () => {
25
+ const {actions} = useAppContext();
26
+ const {t} = useTranslation();
27
+ const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({});
28
+ const [applicationOptions, setApplicationOptions] = useState<ApplicationOptions>(global.store.get("application"));
29
+ const [debouncedApplicationOptions] = useDebounceValue(applicationOptions, 500, {leading: true});
30
+
31
+ // const formats = _map(_values(ModeFormat), (f) => <MenuItem key={f} value={f}>{_capitalize(f)}</MenuItem>)}
32
+
33
+ const validateTemplateString = (input: HTMLInputElement) => {
34
+ const allowedKeys = ["artist", "albumTitle", "trackTitle", "trackNo", "releaseYear"];
35
+ const regex = /{{(.*?)}}/g;
36
+ const matches = _map([...input.value.matchAll(regex)], (m) => m[1]);
37
+
38
+ const invalidKeys = _filter(matches, (m) => !_includes(allowedKeys, m));
39
+ if (!_isEmpty(invalidKeys)) {
40
+ setValidationErrors((prev) => ({...prev, [input.id]: t("invalidTemplateKeys", { invalidKeys: _join(invalidKeys, ", ")})}));
41
+ } else {
42
+ setValidationErrors((prev) => _omitBy(prev, (_, key) => key === input.id));
43
+ }
44
+ };
45
+
46
+ const handleClose = async () => {
47
+ actions.setLocation("/");
48
+ };
49
+
50
+ const onOutputDirectoryChange = (value: string[]) => {
51
+ setApplicationOptions((prev) => ({...prev, outputDirectory: _first(value)}));
52
+ };
53
+
54
+ const onOutputDirectoryBlur = (value: string[]) => {
55
+ const outputDirectory = _isNil(_first(value)) || _isEmpty(_first(value)) ? _get(StoreSchema.application, "properties.outputDirectory.default") : _first(value);
56
+ setApplicationOptions((prev) => ({...prev, outputDirectory}));
57
+ };
58
+
59
+ const onAlbumOutputTemplateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
60
+ validateTemplateString(e.target);
61
+ setApplicationOptions((prev) => ({...prev, albumOutputTemplate: e.target.value}));
62
+ };
63
+
64
+ const onAlbumOutputTemplateBlur = (e: React.FocusEvent<HTMLInputElement>) => {
65
+ const value = e.target.value;
66
+ const albumOutputTemplate = _isNil(value) || _isEmpty(value) ? _get(StoreSchema.application, "properties.albumOutputTemplate.default") : value;
67
+ setApplicationOptions((prev) => ({...prev, albumOutputTemplate}));
68
+ };
69
+
70
+ const onPlaylistOutputTemplateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
71
+ validateTemplateString(e.target);
72
+ setApplicationOptions((prev) => ({...prev, playlistOutputTemplate: e.target.value}));
73
+ };
74
+
75
+ const onPlaylistOutputTemplateBlur = (e: React.FocusEvent<HTMLInputElement>) => {
76
+ const value = e.target.value;
77
+ const playlistOutputTemplate = _isNil(value) || _isEmpty(value) ? _get(StoreSchema.application, "properties.playlistOutputTemplate.default") : value;
78
+ setApplicationOptions((prev) => ({...prev, playlistOutputTemplate}));
79
+ };
80
+
81
+ const onVideoOutputTemplateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
82
+ validateTemplateString(e.target);
83
+ setApplicationOptions((prev) => ({...prev, videoOutputTemplate: e.target.value}));
84
+ };
85
+
86
+ const onVideoOutputTemplateBlur = (e: React.FocusEvent<HTMLInputElement>) => {
87
+ const value = e.target.value;
88
+ const videoOutputTemplate = _isNil(value) || _isEmpty(value) ? _get(StoreSchema.application, "properties.videoOutputTemplate.default") : value;
89
+ setApplicationOptions((prev) => ({...prev, videoOutputTemplate}));
90
+ };
91
+
92
+ const onTrackOutputTemplateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
93
+ validateTemplateString(e.target);
94
+ setApplicationOptions((prev) => ({...prev, trackOutputTemplate: e.target.value}));
95
+ };
96
+
97
+ const onTrackOutputTemplateBlur = (e: React.FocusEvent<HTMLInputElement>) => {
98
+ const value = e.target.value;
99
+ const trackOutputTemplate = _isNil(value) || _isEmpty(value) ? _get(StoreSchema.application, "properties.trackOutputTemplate.default") : value;
100
+ setApplicationOptions((prev) => ({...prev, trackOutputTemplate}));
101
+ };
102
+
103
+ const onOverwriteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
104
+ setApplicationOptions((prev) => ({...prev, alwaysOverwrite: e.target.checked}));
105
+ };
106
+
107
+ const onConcurrencyChange = (value: number) => {
108
+ setApplicationOptions((prev) => ({...prev, concurrency: value}));
109
+ };
110
+
111
+ const onQualityChange = (value: number) => {
112
+ setApplicationOptions((prev) => ({...prev, quality: value}));
113
+ };
114
+
115
+ useEffect(() => {
116
+ global.store.set("application", debouncedApplicationOptions);
117
+ }, [debouncedApplicationOptions]);
118
+
119
+ return (
120
+ <Box className={Styles.settings}>
121
+ <Grid className={Styles.container} container padding={2} spacing={3}>
122
+ <Grid className={Styles.content} container>
123
+ <Grid className={Styles.group} container size={6} component={Paper} variant="outlined">
124
+ <Grid size={6} spacing={2}>
125
+ <NumberField
126
+ fullWidth
127
+ label={t("concurrency")}
128
+ id="concurrency"
129
+ variant="outlined"
130
+ onChange={onConcurrencyChange}
131
+ value={applicationOptions.concurrency}
132
+ decimalScale={0}
133
+ step={1}
134
+ min={1}
135
+ max={12}
136
+ />
137
+ </Grid>
138
+ <Grid size={6}>
139
+ <NumberField
140
+ fullWidth
141
+ label={t("audioQuality")}
142
+ id="quality"
143
+ variant="outlined"
144
+ onChange={onQualityChange}
145
+ value={applicationOptions.quality}
146
+ decimalScale={0}
147
+ step={1}
148
+ min={0}
149
+ max={10}
150
+ />
151
+ </Grid>
152
+ {/* <Grid size={12}>
153
+ <FormControl fullWidth>
154
+ <InputLabel id="mode-format-label">{t("modeFormat")}</InputLabel>
155
+ <Select<string>
156
+ labelId="mode-format-label"
157
+ value={applicationOptions.format}
158
+ label={t("modeFormat")}
159
+ onChange={onFormatChange}
160
+ >
161
+ {_map(_values(ModeFormat), (f) => <MenuItem key={f} value={f}><div>{t(f + "ModeFormat")}</div></MenuItem>)}
162
+ </Select>
163
+ </FormControl>
164
+ </Grid> */}
165
+ <Grid size={12}>
166
+ <ThemePicker />
167
+ </Grid>
168
+ <Grid size={12}>
169
+ <FormControlLabel control={<Switch checked={applicationOptions.alwaysOverwrite} onChange={onOverwriteChange} />} label={t("alwaysOverwrite")} />
170
+ </Grid>
171
+ </Grid>
172
+ <Grid className={Styles.group} container size={6} component={Paper} variant="outlined">
173
+ <Grid size={12}>
174
+ <FileField
175
+ fullWidth
176
+ label={t("outputDirectory")}
177
+ id="outputDirectory"
178
+ variant="outlined"
179
+ onChange={onOutputDirectoryChange}
180
+ onBlur={onOutputDirectoryBlur}
181
+ value={applicationOptions.outputDirectory}
182
+ mode="directory"
183
+ />
184
+ </Grid>
185
+ <Grid size={12}>
186
+ <TextField
187
+ fullWidth
188
+ label={t("albumOutputTemplate")}
189
+ id="albumOutputTemplate"
190
+ variant="outlined"
191
+ onBlur={onAlbumOutputTemplateBlur}
192
+ onChange={onAlbumOutputTemplateChange}
193
+ value={applicationOptions.albumOutputTemplate}
194
+ helperText={validationErrors["albumOutputTemplate"]}
195
+ error={!!validationErrors["albumOutputTemplate"]}
196
+ type="string"
197
+ />
198
+ </Grid>
199
+ <Grid size={12}>
200
+ <TextField
201
+ fullWidth
202
+ label={t("playlistOutputTemplate")}
203
+ id="playlistOutputTemplate"
204
+ variant="outlined"
205
+ onBlur={onPlaylistOutputTemplateBlur}
206
+ onChange={onPlaylistOutputTemplateChange}
207
+ value={applicationOptions.playlistOutputTemplate}
208
+ helperText={validationErrors["playlistOutputTemplate"]}
209
+ error={!!validationErrors["playlistOutputTemplate"]}
210
+ type="string"
211
+ />
212
+ </Grid>
213
+ <Grid size={12}>
214
+ <TextField
215
+ fullWidth
216
+ label={t("videoOutputTemplate")}
217
+ id="videoOutputTemplate"
218
+ variant="outlined"
219
+ onBlur={onVideoOutputTemplateBlur}
220
+ onChange={onVideoOutputTemplateChange}
221
+ value={applicationOptions.videoOutputTemplate}
222
+ helperText={validationErrors["videoOutputTemplate"]}
223
+ error={!!validationErrors["videoOutputTemplate"]}
224
+ type="string"
225
+ />
226
+ </Grid>
227
+ <Grid size={12}>
228
+ <TextField
229
+ fullWidth
230
+ label={t("trackOutputTemplate")}
231
+ id="trackOutputTemplate"
232
+ variant="outlined"
233
+ onBlur={onTrackOutputTemplateBlur}
234
+ onChange={onTrackOutputTemplateChange}
235
+ value={applicationOptions.trackOutputTemplate}
236
+ helperText={validationErrors["trackOutputTemplate"]}
237
+ error={!!validationErrors["trackOutputTemplate"]}
238
+ type="string"
239
+ />
240
+ </Grid>
241
+ </Grid>
242
+ </Grid>
243
+ <Grid className={Styles.footer} container>
244
+ <Grid size="auto">
245
+ <Button variant="contained" color="primary" onClick={handleClose}>
246
+ {t("close")}
247
+ </Button>
248
+ </Grid>
249
+ </Grid>
250
+ </Grid>
251
+ </Box>
252
+ );
253
+ };
254
+
255
+ export default SettingsView;
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "baseUrl": ".",
7
+ "strict": false,
8
+ "allowSyntheticDefaultImports": true,
9
+ "resolveJsonModule": true,
10
+ "noImplicitAny": true,
11
+ "skipLibCheck": true,
12
+ "esModuleInterop": true,
13
+ "jsx": "react",
14
+ "outDir": "./dist",
15
+ "sourceMap": true,
16
+ "typeRoots": ["./src/@types"]
17
+ },
18
+ "include": ["./src/**/*.ts", "./src/**/*.tsx"],
19
+ "exclude": ["node_modules"]
20
+ }
@@ -0,0 +1,226 @@
1
+ import CopyWebpackPlugin from "copy-webpack-plugin";
2
+ import ESLintPlugin from "eslint-webpack-plugin";
3
+ import HtmlWebpackPlugin from "html-webpack-plugin";
4
+ import I18nextScannerWebpackPlugin from "i18next-scanner-webpack";
5
+ import MiniCssExtractPlugin from "mini-css-extract-plugin";
6
+ import path from "path";
7
+ import {Configuration, ProgressPlugin} from "webpack";
8
+ import {Configuration as DevServerConfiguration} from "webpack-dev-server";
9
+
10
+ const WebpackNodeExternals = require("webpack-node-externals");
11
+
12
+ export const getRoot = (dirname: string = __dirname, ...args: any[]) => {
13
+ const rootDir = path.resolve(dirname, ".");
14
+
15
+ args = Array.prototype.slice.call(args, 0);
16
+
17
+ return path.join.apply(path, [rootDir].concat(args));
18
+ };
19
+
20
+ export const reportProgress = (percentage: number, message: string, ...args: any[]) => {
21
+ const stream = process.stderr;
22
+ const formatted = (percentage * 100).toFixed();
23
+
24
+ if (stream.isTTY && percentage < 1) {
25
+ stream.cursorTo(0);
26
+ stream.write(`${formatted}%: ${message}`);
27
+ stream.clearLine(1);
28
+ } else if (percentage === 1) {
29
+ stream.write("building done!");
30
+ }
31
+ };
32
+
33
+ export const mainConfig: Configuration = {
34
+ mode: process.env.NODE_ENV as any, //"development",
35
+ entry: "./src/index.ts",
36
+ target: "electron-main",
37
+ module: {
38
+ rules: [
39
+ {
40
+ test: /\.ts$/,
41
+ include: /src/,
42
+ use: [{loader: "ts-loader"}],
43
+ },
44
+ ],
45
+ },
46
+ output: {
47
+ path: path.join(__dirname, "/dist"),
48
+ filename: "index.js",
49
+ },
50
+ resolve: {
51
+ extensions: [".ts", ".js"],
52
+ },
53
+ externals: [WebpackNodeExternals()],
54
+ };
55
+
56
+ export const renderConfig: Configuration & DevServerConfiguration = {
57
+ mode: process.env.NODE_ENV as any, // "development",
58
+ entry: "./src/renderer.tsx",
59
+ target: "electron-renderer",
60
+ // devtool: "source-map",
61
+ devtool: "eval",
62
+ devServer: {
63
+ watchFiles: {
64
+ paths: ["/src"],
65
+ options: {
66
+ ignored: [
67
+ "**/resources/locales/**/translation.json",
68
+ "**/resources/locales/**/translation_old.json"
69
+ ]
70
+ }
71
+ }
72
+ },
73
+ watchOptions: {
74
+ ignored: ["**/resources/locales/**/translation.json", "**/resources/locales/**/translation_old.json", "/dist",]
75
+ },
76
+ context: getRoot(__dirname, ""),
77
+ module: {
78
+ rules: [
79
+ {
80
+ test: /\.tsx?$/,
81
+ use: [
82
+ {
83
+ loader: "ts-loader",
84
+ options: {
85
+ transpileOnly: true,
86
+ },
87
+ },
88
+ ],
89
+ exclude: [/\.(test|spec|)\.ts$/, /node_modules$/, /[\\/]node_modules[\\/]$/],
90
+ },
91
+ {
92
+ test: /\.js$/,
93
+ enforce: "pre",
94
+ loader: "source-map-loader",
95
+ },
96
+ {
97
+ test: /\.json$/,
98
+ loader: "json",
99
+ exclude: [getRoot(__dirname, "config"), /node_modules/],
100
+ },
101
+ {
102
+ test: /\.css$/i,
103
+ use: [
104
+ {
105
+ loader: MiniCssExtractPlugin.loader,
106
+ options: {
107
+ esModule: false,
108
+ },
109
+ },
110
+ {
111
+ loader: "css-loader",
112
+ options: {
113
+ modules: {
114
+ localIdentName: "[name]__[local]___[hash:base64:5]",
115
+ mode: "global",
116
+ exportLocalsConvention: "camel-case-only",
117
+ },
118
+ },
119
+ },
120
+ ],
121
+ },
122
+ {
123
+ test: /\.styl$/i,
124
+ use: [
125
+ {
126
+ loader: MiniCssExtractPlugin.loader,
127
+ options: {
128
+ esModule: false,
129
+ },
130
+ },
131
+ {
132
+ loader: "css-loader",
133
+ options: {
134
+ modules: {
135
+ localIdentName: "[name]__[local]___[hash:base64:5]",
136
+ exportLocalsConvention: "camel-case-only",
137
+ },
138
+ esModule: false,
139
+ },
140
+ },
141
+ {
142
+ loader: "stylus-loader",
143
+ options: {
144
+ sourceMap: true,
145
+ },
146
+ },
147
+ ],
148
+ },
149
+ {
150
+ test: /url\("([^\)]+?\.(woff|eot|woff2|ttf|svg)[^"]*)"/,
151
+ exclude: [],
152
+ type: "asset/resource",
153
+ dependency: {not: ["url"]},
154
+ },
155
+ {
156
+ test: /[^\)]+?\.(woff|eot|woff2|ttf|svg)[^"]*/,
157
+ exclude: [],
158
+ type: "asset/resource",
159
+ dependency: {not: ["url"]},
160
+ },
161
+ {
162
+ test: /[^\)]+?\.(svg|png|jpg|gif)[^"]*/,
163
+ exclude: [/fonts/],
164
+ type: "asset/resource",
165
+ },
166
+ {
167
+ test: /[^\)]+?\.(svg|png|jpg|gif)[^"]*/,
168
+ exclude: [/images/],
169
+ type: "asset/resource",
170
+ },
171
+ ],
172
+ },
173
+ output: {
174
+ path: path.join(__dirname, "/dist"),
175
+ filename: "renderer.js",
176
+ },
177
+ resolve: {
178
+ extensions: [".ts", ".tsx", ".js", ".json", ".styl", ".css"],
179
+ },
180
+ plugins: [
181
+ new HtmlWebpackPlugin({
182
+ title: "YT Grabber",
183
+ template: "public/index.html",
184
+ }),
185
+ new ProgressPlugin(reportProgress),
186
+ new ESLintPlugin({
187
+ extensions: ["js", "jsx", "ts", "tsx"],
188
+ }),
189
+ new CopyWebpackPlugin({
190
+ patterns: [
191
+ {
192
+ from: "./src/resources/",
193
+ to: "resources",
194
+ noErrorOnMissing: true,
195
+ force: false,
196
+ globOptions: {
197
+ // ignore: ["**/locales/**/*.json"]
198
+ }
199
+ }
200
+ ],
201
+ }),
202
+ new MiniCssExtractPlugin({filename: "bundle.css"}),
203
+ new I18nextScannerWebpackPlugin({
204
+ extensions: [".ts", ".tsx"],
205
+ dest: path.resolve("./"),
206
+ src: [path.resolve("./src")],
207
+ options: {
208
+ locales: ["en-GB", "de-DE", "pl-PL"],
209
+ sort: true,
210
+ verbose: false,
211
+ failOnWarnings: false,
212
+ pluralSeparator: "_",
213
+ output: path.resolve("./src/resources/locales/$LOCALE/$NAMESPACE.json"),
214
+ indentation: 4,
215
+ i18nextOptions: {
216
+ debug: false,
217
+ fallbackLng: false,
218
+ returnEmptyString: false
219
+ }
220
+ }
221
+ }),
222
+ ],
223
+ externals: [WebpackNodeExternals()],
224
+ };
225
+
226
+ export default [mainConfig, renderConfig];