From ba712fc071398e615ead822c8bd81aad42a90c8f Mon Sep 17 00:00:00 2001 From: James Woglom Date: Tue, 30 Aug 2022 00:55:16 -0400 Subject: Fill in download_dir or audio_download_dir on launch --- README.md | 4 +- app/main.py | 43 ++++++-- app/ytdl.py | 9 +- ui/angular.json | 3 - ui/package-lock.json | 238 +++++----------------------------------- ui/package.json | 2 +- ui/src/app/app.component.html | 3 +- ui/src/app/app.component.sass | 5 + ui/src/app/app.component.ts | 39 ++++--- ui/src/app/app.module.ts | 4 +- ui/src/app/downloads.service.ts | 11 +- ui/src/styles.sass | 3 +- 12 files changed, 108 insertions(+), 256 deletions(-) diff --git a/README.md b/README.md index ed08478..692ccd5 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ Certain values can be set via environment variables, using the `-e` parameter on * __GID__: group under which MeTube will run. Defaults to `1000`. * __UMASK__: umask value used by MeTube. Defaults to `022`. * __DOWNLOAD_DIR__: path to where the downloads will be saved. Defaults to `/downloads` in the docker image, and `.` otherwise. -* __CUSTOM_DIR__: whether to enable downloading videos into folders within the __DOWNLOAD_DIR__. Defaults to `true`. -* __AUTO_CREATE_CUSTOM_DIR__: whether to support automatically creating folders within the __DOWNLOAD_DIR__ if they do not exist. Defaults to `false`. * __AUDIO_DOWNLOAD_DIR__: path to where audio-only downloads will be saved, if you wish to separate them from the video downloads. Defaults to the value of `DOWNLOAD_DIR`. +* __CUSTOM_DIRS__: whether to enable downloading videos into custom directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__). When enabled, a drop-down appears next to the Add button to specify the download directory. Defaults to `true`. +* __CREATE_DIRS__: whether to support automatically creating directories within the __DOWNLOAD_DIR__ (or __AUDIO_DOWNLOAD_DIR__) if they do not exist. When enabled, the download directory selector becomes supports free-text input, and the specified directory will be created recursively. Defaults to `false`. * __STATE_DIR__: path to where the queue persistence files will be saved. Defaults to `/downloads/.metube` in the docker image, and `.` otherwise. * __URL_PREFIX__: base path for the web server (for use when hosting behind a reverse proxy). Defaults to `/`. * __OUTPUT_TEMPLATE__: the template for the filenames of the downloaded videos, formatted according to [this spec](https://github.com/yt-dlp/yt-dlp/blob/master/README.md#output-template). Defaults to `%(title)s.%(ext)s`. diff --git a/app/main.py b/app/main.py index 56adc00..de13f1e 100644 --- a/app/main.py +++ b/app/main.py @@ -17,8 +17,8 @@ class Config: _DEFAULTS = { 'DOWNLOAD_DIR': '.', 'AUDIO_DOWNLOAD_DIR': '%%DOWNLOAD_DIR', - 'CUSTOM_DIR': 'true', - 'AUTO_CREATE_CUSTOM_DIR': 'false', + 'CUSTOM_DIRS': 'true', + 'CREATE_DIRS': 'false', 'STATE_DIR': '.', 'URL_PREFIX': '', 'OUTPUT_TEMPLATE': '%(title)s.%(ext)s', @@ -101,18 +101,39 @@ async def delete(request): async def connect(sid, environ): await sio.emit('all', serializer.encode(dqueue.get()), to=sid) await sio.emit('configuration', serializer.encode(config), to=sid) - if config.CUSTOM_DIR: - await sio.emit('custom_directories', serializer.encode(get_custom_directories()), to=sid) + if config.CUSTOM_DIRS: + await sio.emit('custom_dirs', serializer.encode(get_custom_dirs()), to=sid) -def get_custom_directories(): - path = pathlib.Path(config.DOWNLOAD_DIR) - # Recursively lists all subdirectories, and converts PosixPath objects to string - dirs = list(map(str, path.glob('**'))) +def get_custom_dirs(): + def recursive_dirs(base): + path = pathlib.Path(base) + + # Converts PosixPath object to string, and remove base/ prefix + def convert(p): + s = str(p) + if s.startswith(base): + s = s[len(base):] + + if s.startswith('/'): + s = s[1:] + + return s + + # Recursively lists all subdirectories of DOWNLOAD_DIR + dirs = list(filter(None, map(convert, path.glob('**')))) + + return dirs - if '.' in dirs: - dirs.remove('.') + download_dir = recursive_dirs(config.DOWNLOAD_DIR) - return {"directories": dirs} + audio_download_dir = download_dir + if config.DOWNLOAD_DIR != config.AUDIO_DOWNLOAD_DIR: + audio_download_dir = recursive_dirs(config.AUDIO_DOWNLOAD_DIR) + + return { + "download_dir": download_dir, + "audio_download_dir": audio_download_dir + } @routes.get(config.URL_PREFIX) def index(request): diff --git a/app/ytdl.py b/app/ytdl.py index 29fa4bd..4329147 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -228,16 +228,17 @@ class DownloadQueue: elif etype == 'video' or etype.startswith('url') and 'id' in entry and 'title' in entry: if not self.queue.exists(entry['id']): dl = DownloadInfo(entry['id'], entry['title'], entry.get('webpage_url') or entry['url'], quality, format, folder) + # Keep consistent with frontend base_directory = self.config.DOWNLOAD_DIR if (quality != 'audio' and format != 'mp3') else self.config.AUDIO_DOWNLOAD_DIR if folder: - if self.config.CUSTOM_DIR != 'true': - return {'status': 'error', 'msg': f'A folder for the download was specified but CUSTOM_DIR is not true in the configuration.'} + if self.config.CUSTOM_DIRS != 'true': + return {'status': 'error', 'msg': f'A folder for the download was specified but CUSTOM_DIRS is not true in the configuration.'} dldirectory = os.path.realpath(os.path.join(base_directory, folder)) if not dldirectory.startswith(base_directory): return {'status': 'error', 'msg': f'Folder "{folder}" must resolve inside the base download directory "{base_directory}"'} if not os.path.isdir(dldirectory): - if self.config.AUTO_CREATE_CUSTOM_DIR != 'true': - return {'status': 'error', 'msg': f'Folder "{folder}" for download does not exist, and AUTO_CREATE_CUSTOM_DIR is not true in the configuration.'} + if self.config.CREATE_DIRS != 'true': + return {'status': 'error', 'msg': f'Folder "{folder}" for download does not exist inside base directory "{base_directory}", and CREATE_DIRS is not true in the configuration.'} os.makedirs(dldirectory, exist_ok=True) else: dldirectory = base_directory diff --git a/ui/angular.json b/ui/angular.json index 3c9a781..1529f3f 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -32,13 +32,10 @@ ], "stylePreprocessorOptions": { "includePaths": [ - "node_modules/@selectize/selectize/dist/scss/selectize.bootstrap5.scss" ] }, "scripts": [ - "node_modules/jquery/dist/jquery.min.js", "node_modules/bootstrap/dist/js/bootstrap.min.js", - "node_modules/@selectize/selectize/dist/js/standalone/selectize.min.js" ] }, "configurations": { diff --git a/ui/package-lock.json b/ui/package-lock.json index 8bbb724..fbcb8a4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -22,7 +22,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@ng-bootstrap/ng-bootstrap": "^12.0.0", - "@selectize/selectize": "^0.13.6", + "@ng-select/ng-select": "^8.3.0", "bootstrap": "^5.0.0", "ngx-cookie-service": "^13.0.0", "ngx-socket-io": "^4.2.0", @@ -2530,6 +2530,23 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@ng-select/ng-select": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-8.3.0.tgz", + "integrity": "sha512-AwAuDs+86++D2kEsik2/ZiQuRk0khd1HESofOm1yMBwbzsw+xLSyVOMml/OehDFSOxli7fAkk07wYtzAhxSB3Q==", + "dependencies": { + "tslib": "^2.3.1" + }, + "engines": { + "node": ">= 12.20.0", + "npm": ">= 6.0.0" + }, + "peerDependencies": { + "@angular/common": ">=13.0.0 <14.0.0", + "@angular/core": ">=13.0.0 <14.0.0", + "@angular/forms": ">=13.0.0 <14.0.0" + } + }, "node_modules/@ngtools/webpack": { "version": "13.3.8", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.8.tgz", @@ -2689,44 +2706,6 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@selectize/selectize": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@selectize/selectize/-/selectize-0.13.6.tgz", - "integrity": "sha512-UVkHH92l/4zLH/acfukry419K2yAKiDf6VmFQ9hZzMpWW2epE7xJIgK47mJLjAQnimP0ROo8BStfwelWcoTQyg==", - "dependencies": { - "@selectize/sifter": "^0.6.2", - "microplugin": "0.0.3" - }, - "engines": { - "node": "*" - }, - "optionalDependencies": { - "jquery-ui": "^1.13.0" - }, - "peerDependencies": { - "jquery": "^1.7.0 || ^2 || ^3" - } - }, - "node_modules/@selectize/sifter": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@selectize/sifter/-/sifter-0.6.2.tgz", - "integrity": "sha512-rpWQuzaCEV2GOEfBmP5NGST3IsA/RGRd3vugOGQVxAaN1xMI9tsrMmx5rC811iInq6OeFH3Jix5EfglYQ3XwKQ==", - "dependencies": { - "async": "^3.2.3", - "cardinal": "^2.1.1", - "csv-parse": "^5.0.4", - "humanize": "^0.0.9", - "optimist": "^0.5.2" - }, - "bin": { - "sifter": "bin/sifter.js" - } - }, - "node_modules/@selectize/sifter/node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, "node_modules/@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -3338,11 +3317,6 @@ "node": ">=4" } }, - "node_modules/ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -3945,18 +3919,6 @@ } ] }, - "node_modules/cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", - "dependencies": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - }, - "bin": { - "cdl": "bin/cdl.js" - } - }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -4760,11 +4722,6 @@ "node": ">=4" } }, - "node_modules/csv-parse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.0.tgz", - "integrity": "sha512-UXJCGwvJ2fep39purtAn27OUYmxB1JQto+zhZ4QlJpzsirtSFbzLvip1aIgziqNdZp/TptvsKEV5BZSxe10/DQ==" - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -5611,6 +5568,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -6471,14 +6429,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", - "integrity": "sha512-bvZZ7vXpr1RKoImjuQ45hJb5OvE2oJafHysiD/AL3nkqTZH2hFCjQ3YZfCd63FefDitbJze/ispUPP0gfDsT2Q==", - "engines": { - "node": "*" - } - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -7072,20 +7022,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jquery": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", - "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" - }, - "node_modules/jquery-ui": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz", - "integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==", - "optional": true, - "dependencies": { - "jquery": ">=1.8.0 <4.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7568,14 +7504,6 @@ "node": ">=8.6" } }, - "node_modules/microplugin": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/microplugin/-/microplugin-0.0.3.tgz", - "integrity": "sha512-3wKXex4/iyALV0GX2juow66J9dabkEMgHeZAihdLTaRTzm0N+RubXioNPpfIQDPuBRxr3JbjNt7B0Lr/3yE9yQ==", - "engines": { - "node": "*" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -8458,14 +8386,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/optimist": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.5.2.tgz", - "integrity": "sha512-r9M8ZpnM9SXV5Wii7TCqienfcaY3tAiJe9Jchof87icbmbruKgK0xKXngmrnowTDnEawmmI1Qbha59JEoBkBGA==", - "dependencies": { - "wordwrap": "~0.0.2" - } - }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -9670,14 +9590,6 @@ "node": ">=8.10.0" } }, - "node_modules/redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", - "dependencies": { - "esprima": "~4.0.0" - } - }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -11622,14 +11534,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -13511,6 +13415,14 @@ "tslib": "^2.3.0" } }, + "@ng-select/ng-select": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@ng-select/ng-select/-/ng-select-8.3.0.tgz", + "integrity": "sha512-AwAuDs+86++D2kEsik2/ZiQuRk0khd1HESofOm1yMBwbzsw+xLSyVOMml/OehDFSOxli7fAkk07wYtzAhxSB3Q==", + "requires": { + "tslib": "^2.3.1" + } + }, "@ngtools/webpack": { "version": "13.3.8", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.8.tgz", @@ -13634,35 +13546,6 @@ "jsonc-parser": "3.0.0" } }, - "@selectize/selectize": { - "version": "0.13.6", - "resolved": "https://registry.npmjs.org/@selectize/selectize/-/selectize-0.13.6.tgz", - "integrity": "sha512-UVkHH92l/4zLH/acfukry419K2yAKiDf6VmFQ9hZzMpWW2epE7xJIgK47mJLjAQnimP0ROo8BStfwelWcoTQyg==", - "requires": { - "@selectize/sifter": "^0.6.2", - "jquery-ui": "^1.13.0", - "microplugin": "0.0.3" - } - }, - "@selectize/sifter": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@selectize/sifter/-/sifter-0.6.2.tgz", - "integrity": "sha512-rpWQuzaCEV2GOEfBmP5NGST3IsA/RGRd3vugOGQVxAaN1xMI9tsrMmx5rC811iInq6OeFH3Jix5EfglYQ3XwKQ==", - "requires": { - "async": "^3.2.3", - "cardinal": "^2.1.1", - "csv-parse": "^5.0.4", - "humanize": "^0.0.9", - "optimist": "^0.5.2" - }, - "dependencies": { - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - } - } - }, "@socket.io/component-emitter": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", @@ -14208,11 +14091,6 @@ "color-convert": "^1.9.0" } }, - "ansicolors": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", - "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==" - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -14658,15 +14536,6 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001358.tgz", "integrity": "sha512-hvp8PSRymk85R20bsDra7ZTCpSVGN/PAz9pSAjPSjKC+rNmnUk5vCRgJwiTT/O4feQ/yu/drvZYpKxxhbFuChw==" }, - "cardinal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", - "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", - "requires": { - "ansicolors": "~0.3.2", - "redeyed": "~2.1.0" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -15267,11 +15136,6 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "csv-parse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.3.0.tgz", - "integrity": "sha512-UXJCGwvJ2fep39purtAn27OUYmxB1JQto+zhZ4QlJpzsirtSFbzLvip1aIgziqNdZp/TptvsKEV5BZSxe10/DQ==" - }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -15823,7 +15687,8 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true }, "esrecurse": { "version": "4.3.0", @@ -16478,11 +16343,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "humanize": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/humanize/-/humanize-0.0.9.tgz", - "integrity": "sha512-bvZZ7vXpr1RKoImjuQ45hJb5OvE2oJafHysiD/AL3nkqTZH2hFCjQ3YZfCd63FefDitbJze/ispUPP0gfDsT2Q==" - }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -16903,20 +16763,6 @@ } } }, - "jquery": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz", - "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" - }, - "jquery-ui": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.2.tgz", - "integrity": "sha512-wBZPnqWs5GaYJmo1Jj0k/mrSkzdQzKDwhXNtHKcBdAcKVxMM3KNYFq+iJ2i1rwiG53Z8M4mTn3Qxrm17uH1D4Q==", - "optional": true, - "requires": { - "jquery": ">=1.8.0 <4.0.0" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -17276,11 +17122,6 @@ "picomatch": "^2.3.1" } }, - "microplugin": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/microplugin/-/microplugin-0.0.3.tgz", - "integrity": "sha512-3wKXex4/iyALV0GX2juow66J9dabkEMgHeZAihdLTaRTzm0N+RubXioNPpfIQDPuBRxr3JbjNt7B0Lr/3yE9yQ==" - }, "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -17946,14 +17787,6 @@ "is-wsl": "^2.2.0" } }, - "optimist": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.5.2.tgz", - "integrity": "sha512-r9M8ZpnM9SXV5Wii7TCqienfcaY3tAiJe9Jchof87icbmbruKgK0xKXngmrnowTDnEawmmI1Qbha59JEoBkBGA==", - "requires": { - "wordwrap": "~0.0.2" - } - }, "ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -18776,14 +18609,6 @@ "picomatch": "^2.2.1" } }, - "redeyed": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", - "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", - "requires": { - "esprima": "~4.0.0" - } - }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -20217,11 +20042,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==" - }, "wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/ui/package.json b/ui/package.json index 3ea2e71..b46cd94 100644 --- a/ui/package.json +++ b/ui/package.json @@ -25,7 +25,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@ng-bootstrap/ng-bootstrap": "^12.0.0", - "@selectize/selectize": "^0.13.6", + "@ng-select/ng-select": "^8.3.0", "bootstrap": "^5.0.0", "ngx-cookie-service": "^13.0.0", "ngx-socket-io": "^4.2.0", diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 8af200c..23dccce 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -58,8 +58,7 @@ diff --git a/ui/src/app/app.component.sass b/ui/src/app/app.component.sass index d95fc0d..0f98556 100644 --- a/ui/src/app/app.component.sass +++ b/ui/src/app/app.component.sass @@ -18,6 +18,11 @@ button.add-url .folder-dropdown-menu width: 500px +.folder-dropdown-menu .input-group + display: flex + padding-left: 5px + padding-right: 5px + $metube-section-color-bg: rgba(0,0,0,.07) .metube-section-header diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 052e182..e716395 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -1,15 +1,13 @@ -import { Component, ViewChild, ElementRef, AfterViewInit, ChangeDetectorRef } from '@angular/core'; +import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core'; import { faTrashAlt, faCheckCircle, faTimesCircle } from '@fortawesome/free-regular-svg-icons'; import { faRedoAlt, faSun, faMoon, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons'; import { CookieService } from 'ngx-cookie-service'; +import { map, Observable, of } from 'rxjs'; import { DownloadsService, Status } from './downloads.service'; import { MasterCheckboxComponent } from './master-checkbox.component'; import { Formats, Format, Quality } from './formats'; -// jQuery is loaded in angular.json for selectize -declare var $: any; - @Component({ selector: 'app-root', templateUrl: './app.component.html', @@ -22,9 +20,9 @@ export class AppComponent implements AfterViewInit { quality: string; format: string; folder: string; - customDirs: string[] = []; addInProgress = false; darkMode: boolean; + customDirs$: Observable; @ViewChild('queueMasterCheckbox') queueMasterCheckbox: MasterCheckboxComponent; @ViewChild('queueDelSelected') queueDelSelected: ElementRef; @@ -32,7 +30,6 @@ export class AppComponent implements AfterViewInit { @ViewChild('doneDelSelected') doneDelSelected: ElementRef; @ViewChild('doneClearCompleted') doneClearCompleted: ElementRef; @ViewChild('doneClearFailed') doneClearFailed: ElementRef; - @ViewChild('folderSelect') folderSelect: ElementRef; faTrashAlt = faTrashAlt; faCheckCircle = faCheckCircle; @@ -50,14 +47,11 @@ export class AppComponent implements AfterViewInit { this.setupTheme(cookieService) } - ngAfterViewInit() { - // Trigger folderSelect to update - this.downloads.customDirsChanged.subscribe((dirs: string[]) => { - console.log("customDirsChanged:", dirs); - $(this.folderSelect.nativeElement).selectize({options: dirs}); - this.customDirs = dirs; - }); + ngOnInit() { + this.customDirs$ = this.getMatchingCustomDir(); + } + ngAfterViewInit() { this.downloads.queueChanged.subscribe(() => { this.queueMasterCheckbox.selectionChanged(); }); @@ -86,11 +80,24 @@ export class AppComponent implements AfterViewInit { } showAdvanced() { - return this.downloads.configuration['CUSTOM_DIR'] == 'true'; + return this.downloads.configuration['CUSTOM_DIRS'] == 'true'; } - folderChanged() { - console.log("folder changed", this.folder); + allowCustomDir() { + return this.downloads.configuration['CREATE_DIRS'] == 'true'; + } + + getMatchingCustomDir() : Observable { + return this.downloads.customDirs.asObservable().pipe(map((output) => { + // Keep logic consistent with app/ytdl.py + if (this.quality != 'audio' && this.format != 'mp3') { + console.debug("download_dir", output["download_dir"]) + return output["download_dir"]; + } else { + console.debug("audio_download_dir", output["audio_download_dir"]) + return output["audio_download_dir"]; + } + })); } setupTheme(cookieService) { diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 6ad5978..8eddbca 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -10,6 +10,7 @@ import { AppComponent } from './app.component'; import { EtaPipe, SpeedPipe, EncodeURIComponent } from './downloads.pipe'; import { MasterCheckboxComponent, SlaveCheckboxComponent } from './master-checkbox.component'; import { MeTubeSocket } from './metube-socket'; +import { NgSelectModule } from '@ng-select/ng-select'; @NgModule({ declarations: [ @@ -25,7 +26,8 @@ import { MeTubeSocket } from './metube-socket'; FormsModule, NgbModule, HttpClientModule, - FontAwesomeModule + FontAwesomeModule, + NgSelectModule ], providers: [CookieService, MeTubeSocket], bootstrap: [AppComponent] diff --git a/ui/src/app/downloads.service.ts b/ui/src/app/downloads.service.ts index 42ffe6d..1fd75e3 100644 --- a/ui/src/app/downloads.service.ts +++ b/ui/src/app/downloads.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { of, Subject } from 'rxjs'; +import { Observable, of, Subject } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { MeTubeSocket } from './metube-socket'; @@ -33,7 +33,7 @@ export class DownloadsService { done = new Map(); queueChanged = new Subject(); doneChanged = new Subject(); - customDirsChanged = new Subject(); + customDirs = new Subject>(); configuration = {}; constructor(private http: HttpClient, private socket: MeTubeSocket) { @@ -81,11 +81,10 @@ export class DownloadsService { console.debug("got configuration:", data); this.configuration = data; }); - socket.fromEvent('custom_directories').subscribe((strdata: string) => { + socket.fromEvent('custom_dirs').subscribe((strdata: string) => { let data = JSON.parse(strdata); - console.debug("got custom_directories:", data); - let customDirectories = data["directories"]; - this.customDirsChanged.next(customDirectories); + console.debug("got custom_dirs:", data); + this.customDirs.next(data); }); } diff --git a/ui/src/styles.sass b/ui/src/styles.sass index 68bef4f..4ce00cc 100644 --- a/ui/src/styles.sass +++ b/ui/src/styles.sass @@ -1,4 +1,5 @@ /* You can add global styles to this file, and also import other style files */ /* Importing Bootstrap SCSS file. */ -@import '~bootstrap/scss/bootstrap' \ No newline at end of file +@import '~bootstrap/scss/bootstrap' +@import '~@ng-select/ng-select/themes/default.theme.css' \ No newline at end of file -- cgit