diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index f6425304a..76c7cd2ad 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -137,6 +137,7 @@ + diff --git a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs b/src/NzbDrone.Api/Qualities/QualityProfileModule.cs index 395d67803..ee7ce95cb 100644 --- a/src/NzbDrone.Api/Qualities/QualityProfileModule.cs +++ b/src/NzbDrone.Api/Qualities/QualityProfileModule.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using NzbDrone.Core.Qualities; using NzbDrone.Api.Mapping; -using System.Linq; using FluentValidation; namespace NzbDrone.Api.Qualities @@ -9,27 +8,19 @@ namespace NzbDrone.Api.Qualities public class QualityProfileModule : NzbDroneRestModule { private readonly IQualityProfileService _qualityProfileService; - private readonly IQualityDefinitionService _qualityDefinitionService; - public QualityProfileModule(IQualityProfileService qualityProfileService, - IQualityDefinitionService qualityDefinitionService) + public QualityProfileModule(IQualityProfileService qualityProfileService) : base("/qualityprofiles") { _qualityProfileService = qualityProfileService; - _qualityDefinitionService = qualityDefinitionService; - SharedValidator.RuleFor(c => c.Name).NotEmpty(); SharedValidator.RuleFor(c => c.Cutoff).NotNull(); - SharedValidator.RuleFor(c => c.Items).NotEmpty(); + SharedValidator.RuleFor(c => c.Items).MustHaveAllowedQuality();//.SetValidator(new AllowedValidator()); GetResourceAll = GetAll; - GetResourceById = GetById; - UpdateResource = Update; - CreateResource = Create; - DeleteResource = DeleteProfile; } diff --git a/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs b/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs new file mode 100644 index 000000000..c90ebda61 --- /dev/null +++ b/src/NzbDrone.Api/Qualities/QualityProfileValidation.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using FluentValidation; +using FluentValidation.Validators; + +namespace NzbDrone.Api.Qualities +{ + public static class QualityProfileValidation + { + public static IRuleBuilderOptions> MustHaveAllowedQuality(this IRuleBuilder> ruleBuilder) + { + ruleBuilder.SetValidator(new NotEmptyValidator(null)); + + return ruleBuilder.SetValidator(new AllowedValidator()); + } + } + + public class AllowedValidator : PropertyValidator + { + public AllowedValidator() + : base("Must contain at least one allowed quality") + { + + } + + protected override bool IsValid(PropertyValidatorContext context) + { + var list = context.PropertyValue as IList; + + if (list == null) + { + return false; + } + + if (!list.Any(c => c.Allowed)) + { + return false; + } + + return true; + } + } +} diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileItemView.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js similarity index 61% rename from src/UI/Settings/Quality/Profile/EditQualityProfileItemView.js rename to src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js index e93fa6dee..dbd101a84 100644 --- a/src/UI/Settings/Quality/Profile/EditQualityProfileItemView.js +++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemView.js @@ -4,6 +4,6 @@ define( 'marionette' ], function (Marionette) { return Marionette.ItemView.extend({ - template : 'Settings/Quality/Profile/EditQualityProfileItemViewTemplate' + template : 'Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate' }); }); diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileItemViewTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html similarity index 100% rename from src/UI/Settings/Quality/Profile/EditQualityProfileItemViewTemplate.html rename to src/UI/Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate.html diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js new file mode 100644 index 000000000..48eb0b1f4 --- /dev/null +++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayout.js @@ -0,0 +1,92 @@ +'use strict'; +define( + [ + 'underscore', + 'vent', + 'marionette', + 'backbone', + 'Settings/Quality/Profile/Edit/EditQualityProfileItemView', + 'Settings/Quality/Profile/Edit/QualitySortableCollectionView', + 'Settings/Quality/Profile/Edit/EditQualityProfileView', + 'Config' + ], function (_, vent, Marionette, Backbone, EditQualityProfileItemView, QualitySortableCollectionView, EditQualityProfileView, Config) { + + return Marionette.Layout.extend({ + template: 'Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate', + + regions: { + fields : '#x-fields', + qualities: '#x-qualities' + }, + + events: { + 'click .x-save': '_saveQualityProfile' + }, + + initialize: function (options) { + this.profileCollection = options.profileCollection; + this.itemsCollection = new Backbone.Collection(_.toArray(this.model.get('items')).reverse()); + }, + + onShow: function () { + this.fieldsView = new EditQualityProfileView({ model: this.model }); + this._showFieldsView(); + + this.sortableListView = new QualitySortableCollectionView({ + selectable : true, + selectMultiple : true, + clickToSelect : true, + clickToToggle : true, + sortable : Config.getValueBoolean(Config.Keys.AdvancedSettings, false), + + sortableOptions : { + handle: '.x-drag-handle' + }, + + collection: this.itemsCollection, + model : this.model + }); + + this.sortableListView.setSelectedModels(this.itemsCollection.filter(function(item) { return item.get('allowed') === true; })); + this.qualities.show(this.sortableListView); + + this.listenTo(this.sortableListView, 'selectionChanged', this._selectionChanged); + this.listenTo(this.sortableListView, 'sortStop', this._updateModel); + }, + + _selectionChanged: function(newSelectedModels, oldSelectedModels) { + var addedModels = _.difference(newSelectedModels, oldSelectedModels); + var removeModels = _.difference(oldSelectedModels, newSelectedModels); + + _.each(removeModels, function(item) { item.set('allowed', false); }); + _.each(addedModels, function(item) { item.set('allowed', true); }); + + this._updateModel(); + }, + + _updateModel: function() { + this.model.set('items', this.itemsCollection.toJSON().reverse()); + + this._showFieldsView(); + }, + + _saveQualityProfile: function () { + var self = this; + var cutoff = this.fieldsView.getCutoff(); + this.model.set('cutoff', cutoff); + + var promise = this.model.save(); + + if (promise) { + promise.done(function () { + self.profileCollection.add(self.model, { merge: true }); + vent.trigger(vent.Commands.CloseModalCommand); + }); + } + }, + + _showFieldsView: function () { + this.fields.show(this.fieldsView); + } + }); + }); diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html new file mode 100644 index 000000000..4c10d0b0f --- /dev/null +++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileLayoutTemplate.html @@ -0,0 +1,29 @@ + + + diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js new file mode 100644 index 000000000..1365de61c --- /dev/null +++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileView.js @@ -0,0 +1,26 @@ +'use strict'; +define( + [ + 'underscore', + 'marionette', + 'Mixins/AsModelBoundView', + 'Mixins/AsValidatedView' + ], function (_, Marionette, AsModelBoundView, AsValidatedView) { + + var view = Marionette.ItemView.extend({ + template: 'Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate', + + ui: { + cutoff : '.x-cutoff' + }, + + getCutoff: function () { + var self = this; + + return _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id: parseInt(self.ui.cutoff.val(), 10)}); + } + }); + + AsValidatedView.call(view); + return AsModelBoundView.call(view); + }); diff --git a/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html new file mode 100644 index 000000000..9be0285ea --- /dev/null +++ b/src/UI/Settings/Quality/Profile/Edit/EditQualityProfileViewTemplate.html @@ -0,0 +1,21 @@ +
+ +
+ +
+
+
+ +
+ + + + +
+
\ No newline at end of file diff --git a/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js b/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js new file mode 100644 index 000000000..73c06c48b --- /dev/null +++ b/src/UI/Settings/Quality/Profile/Edit/QualitySortableCollectionView.js @@ -0,0 +1,24 @@ +'use strict'; +define( + [ + 'backbone.collectionview', + 'Settings/Quality/Profile/Edit/EditQualityProfileItemView' + ], function (BackboneSortableCollectionView, EditQualityProfileItemView) { + return BackboneSortableCollectionView.extend({ + + className: 'qualities', + modelView: EditQualityProfileItemView, + + attributes: { + 'validation-name': 'items' + }, + + events: { + 'click li, td' : '_listItem_onMousedown', + 'dblclick li, td' : '_listItem_onDoubleClick', + 'click' : '_listBackground_onClick', + 'click ul.collection-list, table.collection-list' : '_listBackground_onClick', + 'keydown' : '_onKeydown' + } + }); + }); diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileView.js b/src/UI/Settings/Quality/Profile/EditQualityProfileView.js deleted file mode 100644 index a602e7dbd..000000000 --- a/src/UI/Settings/Quality/Profile/EditQualityProfileView.js +++ /dev/null @@ -1,89 +0,0 @@ -'use strict'; -define( - [ - 'underscore', - 'vent', - 'marionette', - 'backbone', - 'Settings/Quality/Profile/QualitySortableCollectionView', - 'Settings/Quality/Profile/EditQualityProfileItemView', - 'Mixins/AsModelBoundView', - 'Mixins/AsValidatedView', - 'Config' - ], function (_, vent, Marionette, Backbone, QualitySortableCollectionView, EditQualityProfileItemView, AsModelBoundView, AsValidatedView, Config) { - - var view = Marionette.ItemView.extend({ - template: 'Settings/Quality/Profile/EditQualityProfileViewTemplate', - - ui: { - allowed : '.x-allowed-list', - cutoff : '.x-cutoff' - }, - - events: { - 'click .x-save': '_saveQualityProfile' - }, - - initialize: function (options) { - this.profileCollection = options.profileCollection; - - this.itemsCollection = new Backbone.Collection(_.toArray(this.model.get('items')).reverse()); - }, - - onRender: function() { - - var listViewAllowed = new QualitySortableCollectionView({ - el : this.ui.allowed, - modelView : EditQualityProfileItemView, - selectable : true, - selectMultiple : true, - clickToSelect : true, - clickToToggle : true, - sortable : Config.getValueBoolean(Config.Keys.AdvancedSettings, false), - collection : this.itemsCollection - }); - - listViewAllowed.setSelectedModels(this.itemsCollection.filter(function(item) { return item.get('allowed') === true; })); - - listViewAllowed.render(); - - this.listenTo(listViewAllowed, 'selectionChanged', this._selectionChanged); - this.listenTo(listViewAllowed, 'sortStop', this._updateModel); - }, - - _selectionChanged: function(newSelectedModels, oldSelectedModels) { - var addedModels = _.difference(newSelectedModels, oldSelectedModels); - var removeModels = _.difference(oldSelectedModels, newSelectedModels); - - _.each(removeModels, function(item) { item.set('allowed', false); }); - _.each(addedModels, function(item) { item.set('allowed', true); }); - - this._updateModel(); - }, - - _updateModel: function() { - this.model.set('items', this.itemsCollection.toJSON().reverse()); - - this.render(); - }, - - _saveQualityProfile: function () { - var self = this; - var cutoff = _.findWhere(_.pluck(this.model.get('items'), 'quality'), { id: parseInt(self.ui.cutoff.val(), 10)}); - this.model.set('cutoff', cutoff); - - var promise = this.model.save(); - - if (promise) { - promise.done(function () { - self.profileCollection.add(self.model, { merge: true }); - vent.trigger(vent.Commands.CloseModalCommand); - }); - } - } - }); - - AsValidatedView.call(view); - return AsModelBoundView.call(view); - - }); diff --git a/src/UI/Settings/Quality/Profile/EditQualityProfileViewTemplate.html b/src/UI/Settings/Quality/Profile/EditQualityProfileViewTemplate.html deleted file mode 100644 index 619f6c873..000000000 --- a/src/UI/Settings/Quality/Profile/EditQualityProfileViewTemplate.html +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js b/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js index 066bf986e..b3f5e12af 100644 --- a/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js +++ b/src/UI/Settings/Quality/Profile/QualityProfileCollectionView.js @@ -3,7 +3,7 @@ define(['AppLayout', 'marionette', 'Settings/Quality/Profile/QualityProfileView', - 'Settings/Quality/Profile/EditQualityProfileView', + 'Settings/Quality/Profile/Edit/EditQualityProfileLayout', 'Settings/Quality/Profile/QualityProfileSchemaCollection', 'underscore' ], function (AppLayout, Marionette, QualityProfileView, EditProfileView, ProfileCollection, _) { diff --git a/src/UI/Settings/Quality/Profile/QualityProfileView.js b/src/UI/Settings/Quality/Profile/QualityProfileView.js index 439ad62f4..4bf9e466d 100644 --- a/src/UI/Settings/Quality/Profile/QualityProfileView.js +++ b/src/UI/Settings/Quality/Profile/QualityProfileView.js @@ -4,7 +4,7 @@ define( [ 'AppLayout', 'marionette', - 'Settings/Quality/Profile/EditQualityProfileView', + 'Settings/Quality/Profile/Edit/EditQualityProfileLayout', 'Settings/Quality/Profile/DeleteView', 'Series/SeriesCollection', 'Mixins/AsModelBoundView', @@ -13,7 +13,7 @@ define( ], function (AppLayout, Marionette, EditProfileView, DeleteProfileView, SeriesCollection, AsModelBoundView) { var view = Marionette.ItemView.extend({ - template: 'Settings/Quality/Profile/QualityProfileTemplate', + template: 'Settings/Quality/Profile/QualityProfileViewTemplate', tagName : 'li', ui: { diff --git a/src/UI/Settings/Quality/Profile/QualityProfileTemplate.html b/src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html similarity index 100% rename from src/UI/Settings/Quality/Profile/QualityProfileTemplate.html rename to src/UI/Settings/Quality/Profile/QualityProfileViewTemplate.html diff --git a/src/UI/Settings/Quality/Profile/QualitySortableCollectionView.js b/src/UI/Settings/Quality/Profile/QualitySortableCollectionView.js deleted file mode 100644 index 0185d82c0..000000000 --- a/src/UI/Settings/Quality/Profile/QualitySortableCollectionView.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict'; -define( - [ - 'backbone.collectionview' - ], function (BackboneSortableCollectionView) { - return BackboneSortableCollectionView.extend({ - - events : { - 'click li, td' : '_listItem_onMousedown', - 'dblclick li, td' : '_listItem_onDoubleClick', - 'click' : '_listBackground_onClick', - 'click ul.collection-list, table.collection-list' : '_listBackground_onClick', - 'keydown' : '_onKeydown', - 'click .x-move' : '_onClickMove' - }, - - _onClickMove: function( theEvent ) { - var clickedItemId = this._getClickedItemId( theEvent ); - - if( clickedItemId ) - { - var clickedModel = this.collection.get( clickedItemId ); - this.trigger('moveClicked', clickedModel); - } - } - }); - }); diff --git a/src/UI/Settings/Quality/quality.less b/src/UI/Settings/Quality/quality.less index 45edb91a0..199f87201 100644 --- a/src/UI/Settings/Quality/quality.less +++ b/src/UI/Settings/Quality/quality.less @@ -78,7 +78,7 @@ ul.qualities { .drag-handle { opacity: 1.0; - cursor: pointer; + cursor: move; } }