diff --git a/CHANGELOG.md b/CHANGELOG.md
index c7b697d946..da269d14a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
- Updated `guessit` to version 3.0.0 ([#4244](https://github.com/pymedusa/Medusa/pull/4244))
- Updated the API v2 endpoint to handle concurrent requests ([#4970](https://github.com/pymedusa/Medusa/pull/4970))
- Converted some of the show header to Vue ([#5087](https://github.com/pymedusa/Medusa/pull/5087))
+- Converted "Add Show" options into a Vue SFC ([#4848](https://github.com/pymedusa/Medusa/pull/4848))
#### Fixes
- Fixed many release name parsing issues as a result of updating `guessit` ([#4244](https://github.com/pymedusa/Medusa/pull/4244))
diff --git a/medusa/common.py b/medusa/common.py
index a0e988d0aa..c1c6af6ace 100644
--- a/medusa/common.py
+++ b/medusa/common.py
@@ -260,6 +260,22 @@ def split_quality(quality):
return sorted(allowed_qualities), sorted(preferred_qualities)
+ @staticmethod
+ def is_valid_combined_quality(quality):
+ """
+ Check quality value to make sure it is a valid combined quality.
+
+ :param quality: Quality to check
+ :type quality: int
+ :return: True if valid, False if not
+ """
+ for cur_qual in Quality.qualityStrings:
+ if cur_qual & quality:
+ quality -= cur_qual
+ if cur_qual << 16 & quality:
+ quality -= cur_qual << 16
+ return quality == 0
+
@staticmethod
def name_quality(name, anime=False, extend=True):
"""
diff --git a/medusa/server/api/v2/config.py b/medusa/server/api/v2/config.py
index c768317a52..7c5370ac38 100644
--- a/medusa/server/api/v2/config.py
+++ b/medusa/server/api/v2/config.py
@@ -17,6 +17,7 @@
logger,
ws,
)
+from medusa.common import IGNORED, Quality, SKIPPED, WANTED
from medusa.helper.mappings import NonEmptyDict
from medusa.indexers.indexer_config import get_indexer_config
from medusa.logger.adapters.style import BraceAdapter
@@ -51,6 +52,11 @@ def theme_name_setter(object, name, value):
config.change_theme(value)
+def season_folders_validator(value):
+ """Validate default season folders setting."""
+ return not (app.NAMING_FORCE_FOLDERS and value is False)
+
+
class ConfigHandler(BaseRequestHandler):
"""Config request handler."""
@@ -116,6 +122,15 @@ class ConfigHandler(BaseRequestHandler):
'backlogOverview.period': StringField(app, 'BACKLOG_PERIOD'),
'backlogOverview.status': StringField(app, 'BACKLOG_STATUS'),
'rootDirs': ListField(app, 'ROOT_DIRS'),
+
+ 'showDefaults.status': EnumField(app, 'STATUS_DEFAULT', (SKIPPED, WANTED, IGNORED), int),
+ 'showDefaults.statusAfter': EnumField(app, 'STATUS_DEFAULT_AFTER', (SKIPPED, WANTED, IGNORED), int),
+ 'showDefaults.quality': IntegerField(app, 'QUALITY_DEFAULT', validator=Quality.is_valid_combined_quality),
+ 'showDefaults.subtitles': BooleanField(app, 'SUBTITLES_DEFAULT', validator=lambda v: app.USE_SUBTITLES, converter=bool),
+ 'showDefaults.seasonFolders': BooleanField(app, 'SEASON_FOLDERS_DEFAULT', validator=season_folders_validator, converter=bool),
+ 'showDefaults.anime': BooleanField(app, 'ANIME_DEFAULT', converter=bool),
+ 'showDefaults.scene': BooleanField(app, 'SCENE_DEFAULT', converter=bool),
+
'postProcessing.showDownloadDir': StringField(app, 'TV_DOWNLOAD_DIR'),
'postProcessing.processAutomatically': BooleanField(app, 'PROCESS_AUTOMATICALLY'),
'postProcessing.processMethod': StringField(app, 'PROCESS_METHOD'),
@@ -297,6 +312,15 @@ def data_main():
section_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES)
section_data['recentShows'] = app.SHOWS_RECENT
+ section_data['showDefaults'] = {}
+ section_data['showDefaults']['status'] = app.STATUS_DEFAULT
+ section_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER
+ section_data['showDefaults']['quality'] = app.QUALITY_DEFAULT
+ section_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT)
+ section_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT)
+ section_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT)
+ section_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT)
+
section_data['news'] = NonEmptyDict()
section_data['news']['lastRead'] = app.NEWS_LAST_READ
section_data['news']['latest'] = app.NEWS_LATEST
diff --git a/tests/apiv2/test_config.py b/tests/apiv2/test_config.py
index 1267edd38f..b60a4bcbf0 100644
--- a/tests/apiv2/test_config.py
+++ b/tests/apiv2/test_config.py
@@ -78,6 +78,15 @@ def config_main(monkeypatch, app_config):
config_data['subtitles']['enabled'] = bool(app.USE_SUBTITLES)
config_data['recentShows'] = app.SHOWS_RECENT
+ config_data['showDefaults'] = {}
+ config_data['showDefaults']['status'] = app.STATUS_DEFAULT
+ config_data['showDefaults']['statusAfter'] = app.STATUS_DEFAULT_AFTER
+ config_data['showDefaults']['quality'] = app.QUALITY_DEFAULT
+ config_data['showDefaults']['subtitles'] = bool(app.SUBTITLES_DEFAULT)
+ config_data['showDefaults']['seasonFolders'] = bool(app.SEASON_FOLDERS_DEFAULT)
+ config_data['showDefaults']['anime'] = bool(app.ANIME_DEFAULT)
+ config_data['showDefaults']['scene'] = bool(app.SCENE_DEFAULT)
+
config_data['news'] = NonEmptyDict()
config_data['news']['lastRead'] = app.NEWS_LAST_READ
config_data['news']['latest'] = app.NEWS_LATEST
diff --git a/tests/test_common.py b/tests/test_common.py
index d19a033a15..514dfbdaf7 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -461,3 +461,33 @@ def test_wanted_quality():
# Then
assert actual is True
+
+
+@pytest.mark.parametrize('p', [
+ { # p0 - Invalid combined quality
+ 'quality': -4,
+ 'expected': False
+ },
+ { # p1 - Valid 'allowed' quality
+ 'quality': Quality.HDTV,
+ 'expected': True
+ },
+ { # p2 - Valid 'allowed' quality + valid 'preferred' quality
+ 'quality': Quality.combine_qualities([Quality.HDTV], [Quality.HDWEBDL]),
+ 'expected': True
+ },
+ { # p3 - Valid 'allowed' quality + **invalid** 'preferred' quality
+ 'quality': Quality.combine_qualities([Quality.HDTV], [-4]),
+ 'expected': False
+ },
+])
+def test_is_valid_combined_quality(p):
+ # Given
+ quality = p['quality']
+ expected = p['expected']
+
+ # When
+ actual = Quality.is_valid_combined_quality(quality)
+
+ # Then
+ assert expected == actual
diff --git a/themes-default/slim/src/components/add-show-options.vue b/themes-default/slim/src/components/add-show-options.vue
new file mode 100644
index 0000000000..1d41f596cb
--- /dev/null
+++ b/themes-default/slim/src/components/add-show-options.vue
@@ -0,0 +1,338 @@
+
+
+
+
+
+
+
diff --git a/themes-default/slim/src/components/anidb-release-group-ui.vue b/themes-default/slim/src/components/anidb-release-group-ui.vue
index 5eeb192665..5d25eb85b3 100644
--- a/themes-default/slim/src/components/anidb-release-group-ui.vue
+++ b/themes-default/slim/src/components/anidb-release-group-ui.vue
@@ -159,6 +159,11 @@ export default {
this.$emit('change', this.allReleaseGroups);
},
deep: true
+ },
+ allGroups: {
+ handler(newValue) {
+ this.createIndexedObjects(newValue, 'releasegroups');
+ }
}
}
};
diff --git a/themes-default/slim/src/components/config-toggle-slider.vue b/themes-default/slim/src/components/config-toggle-slider.vue
index bd26dea60f..cb4c1a1826 100644
--- a/themes-default/slim/src/components/config-toggle-slider.vue
+++ b/themes-default/slim/src/components/config-toggle-slider.vue
@@ -6,7 +6,7 @@
{{ label }}
@@ -30,6 +30,10 @@ export default {
type: Boolean,
default: null
},
+ disabled: {
+ type: Boolean,
+ default: false
+ },
explanations: {
type: Array,
default: () => []
@@ -44,6 +48,9 @@ export default {
this.localChecked = this.checked;
},
watch: {
+ checked() {
+ this.localChecked = this.checked;
+ },
localChecked() {
this.$emit('update', this.localChecked);
}
diff --git a/themes-default/slim/src/components/index.js b/themes-default/slim/src/components/index.js
index 7c0db9a27e..b598256977 100644
--- a/themes-default/slim/src/components/index.js
+++ b/themes-default/slim/src/components/index.js
@@ -1,4 +1,5 @@
export { default as AddRecommended } from './add-recommended.vue';
+export { default as AddShowOptions } from './add-show-options.vue';
export { default as AddShows } from './add-shows.vue';
export { default as AnidbReleaseGroupUi } from './anidb-release-group-ui.vue';
export { default as AppHeader } from './app-header.vue';
diff --git a/themes-default/slim/src/index.js b/themes-default/slim/src/index.js
index 4bb6b49713..1975648fc8 100644
--- a/themes-default/slim/src/index.js
+++ b/themes-default/slim/src/index.js
@@ -19,6 +19,7 @@ import router from './router';
import { isDevelopment } from './utils';
import { apiRoute, apiv1, api, webRoot, apiKey } from './api';
import {
+ AddShowOptions,
AnidbReleaseGroupUi,
AppHeader,
AppLink,
@@ -75,6 +76,7 @@ if (window) {
// Push pages that load via a vue file but still use `el` for mounting
window.components = [];
+ window.components.push(AddShowOptions);
window.components.push(AnidbReleaseGroupUi);
window.components.push(AppHeader);
window.components.push(AppLink);
diff --git a/themes-default/slim/src/store/modules/config.js b/themes-default/slim/src/store/modules/config.js
index 8bdf6afb2c..9f5949b931 100644
--- a/themes-default/slim/src/store/modules/config.js
+++ b/themes-default/slim/src/store/modules/config.js
@@ -252,7 +252,16 @@ const state = {
os: null,
anonRedirect: null,
logDir: null,
- recentShows: []
+ recentShows: [],
+ showDefaults: {
+ status: null,
+ statusAfter: null,
+ quality: null,
+ subtitles: null,
+ seasonFolders: null,
+ anime: null,
+ scene: null
+ }
};
const mutations = {
diff --git a/themes-default/slim/src/utils.js b/themes-default/slim/src/utils.js
index a8070207ec..43e3dc6dff 100644
--- a/themes-default/slim/src/utils.js
+++ b/themes-default/slim/src/utils.js
@@ -1,5 +1,20 @@
const isDevelopment = process.env.NODE_ENV === 'development';
+/**
+ * Calculate the combined value of the selected qualities.
+ * @param {number[]} allowedQualities - Array of allowed qualities.
+ * @param {number[]} preferredQualities - Array of preferred qualities.
+ * @returns {number} - An unsigned integer.
+ */
+const combineQualities = (allowedQualities, preferredQualities) => {
+ const reducer = (accumulator, currentValue) => accumulator | currentValue;
+ const allowed = allowedQualities.reduce(reducer, 0);
+ const preferred = preferredQualities.reduce(reducer, 0);
+
+ return (allowed | (preferred << 16)) >>> 0; // Unsigned int
+};
+
export {
+ combineQualities,
isDevelopment
};
diff --git a/themes-default/slim/static/js/blackwhite.js b/themes-default/slim/static/js/blackwhite.js
index e2b11fb54e..e986815442 100644
--- a/themes-default/slim/static/js/blackwhite.js
+++ b/themes-default/slim/static/js/blackwhite.js
@@ -1,38 +1,3 @@
-function generateBlackWhiteList() { // eslint-disable-line no-unused-vars
- let realvalues = [];
-
- $('#white option').each((i, selected) => {
- realvalues[i] = $(selected).val();
- });
- $('#whitelist').val(realvalues.join(','));
-
- realvalues = [];
- $('#black option').each((i, selected) => {
- realvalues[i] = $(selected).val();
- });
- $('#blacklist').val(realvalues.join(','));
-}
-
-function updateBlackWhiteList(showName) { // eslint-disable-line no-unused-vars
- $('#pool').children().remove();
-
- $('#blackwhitelist').show();
- if (showName) {
- $.getJSON('home/fetch_releasegroups', {
- series_name: showName // eslint-disable-line camelcase
- }, data => {
- if (data.result === 'success') {
- $.each(data.groups, (i, group) => {
- const option = $('