Moved to regions for quality profile editor
This commit is contained in:
parent
75decad601
commit
191db1c541
|
@ -137,6 +137,7 @@
|
|||
<Compile Include="Missing\MissingModule.cs" />
|
||||
<Compile Include="Config\NamingSampleResource.cs" />
|
||||
<Compile Include="NzbDroneRestModuleWithSignalR.cs" />
|
||||
<Compile Include="Qualities\QualityProfileValidation.cs" />
|
||||
<Compile Include="Queue\QueueModule.cs" />
|
||||
<Compile Include="Queue\QueueResource.cs" />
|
||||
<Compile Include="ResourceChangeMessage.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<QualityProfileResource>
|
||||
{
|
||||
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<QualityProfileItemResource>());
|
||||
|
||||
GetResourceAll = GetAll;
|
||||
|
||||
GetResourceById = GetById;
|
||||
|
||||
UpdateResource = Update;
|
||||
|
||||
CreateResource = Create;
|
||||
|
||||
DeleteResource = DeleteProfile;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T, IList<QualityProfileItemResource>> MustHaveAllowedQuality<T>(this IRuleBuilder<T, IList<QualityProfileItemResource>> ruleBuilder)
|
||||
{
|
||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||
|
||||
return ruleBuilder.SetValidator(new AllowedValidator<T>());
|
||||
}
|
||||
}
|
||||
|
||||
public class AllowedValidator<T> : PropertyValidator
|
||||
{
|
||||
public AllowedValidator()
|
||||
: base("Must contain at least one allowed quality")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override bool IsValid(PropertyValidatorContext context)
|
||||
{
|
||||
var list = context.PropertyValue as IList<QualityProfileItemResource>;
|
||||
|
||||
if (list == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!list.Any(c => c.Allowed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,6 @@ define(
|
|||
'marionette'
|
||||
], function (Marionette) {
|
||||
return Marionette.ItemView.extend({
|
||||
template : 'Settings/Quality/Profile/EditQualityProfileItemViewTemplate'
|
||||
template : 'Settings/Quality/Profile/Edit/EditQualityProfileItemViewTemplate'
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
{{#if id}}
|
||||
<h3>Edit</h3>
|
||||
{{else}}
|
||||
<h3>Add</h3>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-horizontal">
|
||||
<div id="x-fields"></div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Qualities</label>
|
||||
<div class="controls qualities-controls">
|
||||
<span id="x-qualities"></span>
|
||||
<span class="help-inline">
|
||||
<i class="icon-question-sign" title="Qualities higher in the list are more preferred. Only checked qualities will be wanted."/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{#if id}}
|
||||
<button class="btn btn-danger pull-left x-delete">delete</button>
|
||||
{{/if}}
|
||||
<button class="btn" data-dismiss="modal">cancel</button>
|
||||
<button class="btn btn-primary x-save">save</button>
|
||||
</div>
|
|
@ -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);
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
<div class="control-group">
|
||||
<label class="control-label">Name</label>
|
||||
<div class="controls">
|
||||
<input type="text" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Cutoff</label>
|
||||
<div class="controls">
|
||||
<select class="x-cutoff" name="cutoff.id" validation-name="cutoff">
|
||||
{{#eachReverse items}}
|
||||
{{#if allowed}}
|
||||
<option value="{{quality.id}}">{{quality.name}}</option>
|
||||
{{/if}}
|
||||
{{/eachReverse}}
|
||||
</select>
|
||||
<span class="help-inline">
|
||||
<i class="icon-question-sign" title="Once this quality is reached NzbDrone will no longer download episodes"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
|
@ -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'
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
{{#if id}}
|
||||
<h3>Edit</h3>
|
||||
{{else}}
|
||||
<h3>Add</h3>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-horizontal">
|
||||
<div class="control-group">
|
||||
<label class="control-label">Name</label>
|
||||
<div class="controls">
|
||||
<input type="text" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Cutoff</label>
|
||||
<div class="controls">
|
||||
<select class="x-cutoff" name="cutoff.id" validation-name="cutoff">
|
||||
{{#eachReverse items}}
|
||||
{{#if allowed}}
|
||||
<option value="{{quality.id}}">{{quality.name}}</option>
|
||||
{{/if}}
|
||||
{{/eachReverse}}
|
||||
</select>
|
||||
<span class="help-inline">
|
||||
<i class="icon-question-sign" title="Once this quality is reached NzbDrone will no longer download episodes"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label">Qualities</label>
|
||||
<div class="controls qualities-controls">
|
||||
<ul class="qualities x-allowed-list" validation-name="allowed"></ul>
|
||||
<span class="help-inline">
|
||||
<i class="icon-question-sign" title="Qualities higher in the list are more preferred. Only checked qualities will be wanted."/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{{#if id}}
|
||||
<button class="btn btn-danger pull-left x-delete">delete</button>
|
||||
{{/if}}
|
||||
<button class="btn" data-dismiss="modal">cancel</button>
|
||||
<button class="btn btn-primary x-save">save</button>
|
||||
</div>
|
|
@ -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, _) {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
|
@ -78,7 +78,7 @@ ul.qualities {
|
|||
|
||||
.drag-handle {
|
||||
opacity: 1.0;
|
||||
cursor: pointer;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue