Validation, settings UI cleanup and different settings models, oh my

New: Download client UI matches other settings
Fixed: Prevent drone factory folder from being set to invalid paths/root path for series
Fixed: Switching pages in settings will not hide changes
Fixed: Test download clients
Fixed: Settings are validated before saving
This commit is contained in:
Mark McDowall 2014-02-16 01:56:12 -08:00
parent 606d78f5e1
commit 77b83b521e
57 changed files with 667 additions and 484 deletions

View file

@ -8,16 +8,16 @@
<option es3="false" />
<option forin="true" />
<option immed="true" />
<option latedef="true" />
<option newcap="true" />
<option noarg="true" />
<option noempty="false" />
<option nonew="true" />
<option plusplus="false" />
<option undef="true" />
<option unused="true" />
<option strict="true" />
<option trailing="false" />
<option latedef="true" />
<option unused="true" />
<option quotmark="single" />
<option maxdepth="3" />
<option asi="false" />

View file

@ -22,7 +22,7 @@ define(
},
_edit: function () {
var view = new EditView({ model: this.model});
var view = new EditView({ model: this.model, downloadClientCollection: this.model.collection });
AppLayout.modalRegion.show(view);
},

View file

@ -5,24 +5,17 @@ define(
'marionette',
'Settings/DownloadClient/DownloadClientCollection',
'Settings/DownloadClient/DownloadClientCollectionView',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, AsModelBoundView) {
'Settings/DownloadClient/Options/DownloadClientOptionsView',
'Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingView'
], function (Marionette, DownloadClientCollection, DownloadClientCollectionView, DownloadClientOptionsView, FailedDownloadHandlingView) {
var view = Marionette.Layout.extend({
return Marionette.Layout.extend({
template : 'Settings/DownloadClient/DownloadClientLayoutTemplate',
regions: {
downloadClients: '#x-download-clients-region'
},
ui: {
droneFactory: '.x-path'
},
events: {
'change .x-download-client': 'downloadClientChanged'
downloadClients : '#x-download-clients-region',
downloadClientOptions : '#x-download-client-options-region',
failedDownloadHandling : '#x-failed-download-handling-region'
},
initialize: function () {
@ -32,9 +25,8 @@ define(
onShow: function () {
this.downloadClients.show(new DownloadClientCollectionView({ collection: this.downloadClientCollection }));
this.ui.droneFactory.autoComplete('/directories');
this.downloadClientOptions.show(new DownloadClientOptionsView({ model: this.model }));
this.failedDownloadHandling.show(new FailedDownloadHandlingView({ model: this.model }));
}
});
return AsModelBoundView.call(view);
});

View file

@ -1,16 +1,6 @@
<div id="x-download-clients-region"></div>
<div class="form-horizontal">
<div id="x-download-client-options-region"></div>
<div id="x-failed-download-handling-region"></div>
</div>
<fieldset class="form-horizontal">
<legend>Options</legend>
<div class="control-group">
<label class="control-label">Drone Factory</label>
<div class="controls">
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
</span>
</div>
</div>
</fieldset>

View file

@ -0,0 +1,11 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/downloadclient',
successMessage: 'Download client settings saved',
errorMessage : 'Failed to save download client settings'
});
});

View file

@ -8,13 +8,14 @@ define(
'Settings/DownloadClient/Delete/DownloadClientDeleteView',
'Commands/CommandController',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView',
'underscore',
'Form/FormBuilder',
'Mixins/AutoComplete',
'bootstrap'
], function (vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, _) {
], function (vent, AppLayout, Marionette, DeleteView, CommandController, AsModelBoundView, AsValidatedView, _) {
var model = Marionette.ItemView.extend({
var view = Marionette.ItemView.extend({
template: 'Settings/DownloadClient/Edit/DownloadClientEditViewTemplate',
ui: {
@ -89,5 +90,8 @@ define(
}
});
return AsModelBoundView.call(model);
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -0,0 +1,37 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/DownloadClient/FailedDownloadHandling/FailedDownloadHandlingViewTemplate',
ui: {
failedDownloadHandlingCheckbox: '.x-failed-download-handling',
failedDownloadOptions : '.x-failed-download-options'
},
events: {
'change .x-failed-download-handling': '_setFailedDownloadOptionsVisibility'
},
_setFailedDownloadOptionsVisibility: function () {
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
if (checked) {
this.ui.failedDownloadOptions.slideDown();
}
else {
this.ui.failedDownloadOptions.slideUp();
}
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -0,0 +1,65 @@
<fieldset class="advanced-setting">
<legend>Failed Download Handling</legend>
<div class="control-group">
<label class="control-label">Enable</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
</span>
</div>
</div>
<div class="x-failed-download-options">
<div class="control-group">
<label class="control-label">Redownload</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="autoRedownloadFailed"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Automatically search for and attempt to download another release when a download fails?"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Remove</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="removeFailedDownloads"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Automatically remove failed downloads from history and encrypted downloads from queue?"/>
</span>
</div>
</div>
</div>
</fieldset>

View file

@ -0,0 +1,26 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView',
'Mixins/AutoComplete'
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/DownloadClient/Options/DownloadClientOptionsViewTemplate',
ui: {
droneFactory : '.x-path'
},
onShow: function () {
this.ui.droneFactory.autoComplete('/directories');
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -0,0 +1,14 @@
<fieldset">
<legend>Options</legend>
<div class="control-group">
<label class="control-label">Drone Factory</label>
<div class="controls">
<input type="text" name="downloadedEpisodesFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client downloads TV shows to (Completed Download Directory)"/>
<i class="icon-nd-form-warning" title="Do not use the folder that contains some or all of your sorted and named TV shows - doing so could cause data loss"></i>
</span>
</div>
</div>
</fieldset>

View file

@ -5,9 +5,8 @@ define(
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/settings/host',
url : window.NzbDrone.ApiRoot + '/config/host',
successMessage: 'General settings saved',
errorMessage : 'Failed to save general settings'
});
});

View file

@ -2,10 +2,11 @@
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/General/GeneralTemplate',
template: 'Settings/General/GeneralViewTemplate',
events: {
'change .x-auth': '_setAuthOptionsVisibility',
@ -56,6 +57,9 @@ define(
}
});
return AsModelBoundView.call(view);
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -0,0 +1,11 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/indexer',
successMessage: 'Indexer settings saved',
errorMessage : 'Failed to save indexer settings'
});
});

View file

@ -2,12 +2,16 @@
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
});
return AsModelBoundView.call(view);
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -3,37 +3,24 @@ define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView',
'Mixins/AutoComplete'
], function (Marionette, AsModelBoundView) {
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
ui: {
recyclingBin : '.x-path',
failedDownloadHandlingCheckbox: '.x-failed-download-handling',
failedDownloadOptions : '.x-failed-download-options'
},
events: {
'change .x-failed-download-handling': '_setFailedDownloadOptionsVisibility'
recyclingBin : '.x-path'
},
onShow: function () {
this.ui.recyclingBin.autoComplete('/directories');
},
_setFailedDownloadOptionsVisibility: function () {
var checked = this.ui.failedDownloadHandlingCheckbox.prop('checked');
if (checked) {
this.ui.failedDownloadOptions.slideDown();
}
else {
this.ui.failedDownloadOptions.slideUp();
}
}
});
return AsModelBoundView.call(view);
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -52,69 +52,3 @@
</div>
</div>
</fieldset>
<fieldset class="advanced-setting">
<legend>Failed Download Handling</legend>
<div class="control-group">
<label class="control-label">Enable</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enableFailedDownloadHandling" class="x-failed-download-handling"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Process failed downloads and blacklist the release"/>
</span>
</div>
</div>
<div class="x-failed-download-options">
<div class="control-group">
<label class="control-label">Redownload</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="autoRedownloadFailed"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Automatically search for and attempt to download another release when a download fails?"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Remove</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="removeFailedDownloads"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Automatically remove failed downloads from history and encrypted downloads from queue?"/>
</span>
</div>
</div>
</div>
</fieldset>

View file

@ -4,7 +4,7 @@ define(
[
'marionette',
'Settings/MediaManagement/Naming/NamingView',
'Settings/MediaManagement/Sorting/View',
'Settings/MediaManagement/Sorting/SortingView',
'Settings/MediaManagement/FileManagement/FileManagementView',
'Settings/MediaManagement/Permissions/PermissionsView'
], function (Marionette, NamingView, SortingView, FileManagementView, PermissionsView) {

View file

@ -0,0 +1,11 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/mediamanagement',
successMessage: 'Media management settings saved',
errorMessage : 'Failed to save media managemnent settings'
});
});

View file

@ -3,8 +3,9 @@ define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView',
'Mixins/AutoComplete'
], function (Marionette, AsModelBoundView) {
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/Permissions/PermissionsViewTemplate',
@ -35,5 +36,8 @@ define(
}
});
return AsModelBoundView.call(view);
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -0,0 +1,17 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/Sorting/SortingViewTemplate'
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});

View file

@ -1,13 +0,0 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/Sorting/ViewTemplate'
});
return AsModelBoundView.call(view);
});

View file

@ -2,17 +2,20 @@
define(
[
'jquery',
'underscore',
'vent',
'marionette',
'backbone',
'Settings/SettingsModel',
'Settings/General/GeneralSettingsModel',
'Settings/MediaManagement/Naming/NamingModel',
'Settings/MediaManagement/MediaManagementLayout',
'Settings/MediaManagement/MediaManagementSettingsModel',
'Settings/Quality/QualityLayout',
'Settings/Indexers/IndexerLayout',
'Settings/Indexers/Collection',
'Settings/Indexers/IndexerSettingsModel',
'Settings/DownloadClient/DownloadClientLayout',
'Settings/DownloadClient/DownloadClientSettingsModel',
'Settings/Notifications/CollectionView',
'Settings/Notifications/Collection',
'Settings/Metadata/MetadataLayout',
@ -20,17 +23,20 @@ define(
'Shared/LoadingView',
'Config'
], function ($,
_,
vent,
Marionette,
Backbone,
SettingsModel,
GeneralSettingsModel,
NamingModel,
MediaManagementLayout,
MediaManagementSettingsModel,
QualityLayout,
IndexerLayout,
IndexerCollection,
IndexerSettingsModel,
DownloadClientLayout,
DownloadClientSettingsModel,
NotificationCollectionView,
NotificationCollection,
MetadataLayout,
@ -84,26 +90,31 @@ define(
this.loading.show(new LoadingView());
var self = this;
this.settings = new SettingsModel();
this.generalSettings = new GeneralSettingsModel();
this.mediaManagementSettings = new MediaManagementSettingsModel();
this.namingSettings = new NamingModel();
this.indexerSettings = new IndexerCollection();
this.notificationSettings = new NotificationCollection();
this.indexerSettings = new IndexerSettingsModel();
this.indexerCollection = new IndexerCollection();
this.downloadClientSettings = new DownloadClientSettingsModel();
this.notificationCollection = new NotificationCollection();
this.generalSettings = new GeneralSettingsModel();
Backbone.$.when(this.settings.fetch(),
this.generalSettings.fetch(),
Backbone.$.when(
this.mediaManagementSettings.fetch(),
this.namingSettings.fetch(),
this.indexerSettings.fetch(),
this.notificationSettings.fetch()
this.indexerCollection.fetch(),
this.downloadClientSettings.fetch(),
this.notificationCollection.fetch(),
this.generalSettings.fetch()
).done(function () {
if(!self.isClosed)
{
self.loading.$el.hide();
self.mediaManagement.show(new MediaManagementLayout({ settings: self.settings, namingSettings: self.namingSettings }));
self.quality.show(new QualityLayout({ settings: self.settings }));
self.indexers.show(new IndexerLayout({ settings: self.settings, indexersCollection: self.indexerSettings }));
self.downloadClient.show(new DownloadClientLayout({ model: self.settings }));
self.notifications.show(new NotificationCollectionView({ collection: self.notificationSettings }));
self.mediaManagement.show(new MediaManagementLayout({ settings: self.mediaManagementSettings, namingSettings: self.namingSettings }));
self.quality.show(new QualityLayout());
self.indexers.show(new IndexerLayout({ settings: self.indexerSettings, indexersCollection: self.indexerCollection }));
self.downloadClient.show(new DownloadClientLayout({ model: self.downloadClientSettings }));
self.notifications.show(new NotificationCollectionView({ collection: self.notificationCollection }));
self.metadata.show(new MetadataLayout());
self.general.show(new GeneralView({ model: self.generalSettings }));
}
@ -204,7 +215,7 @@ define(
},
_navigate:function(route){
Backbone.history.navigate(route, { trigger: true, replace: true });
Backbone.history.navigate(route, { trigger: false, replace: true });
},
_save: function () {

View file

@ -1,11 +0,0 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/settings',
successMessage: 'Settings saved',
errorMessage : 'Failed to save settings'
});
});