Moved source code under src folder - massive change

This commit is contained in:
Mark McDowall
2013-10-02 18:01:32 -07:00
parent 2fc8123d6b
commit 5bf0e197ec
1499 changed files with 1054 additions and 1444 deletions
@@ -0,0 +1,24 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/BlackholeViewTemplate',
ui: {
'blackholeFolder': '.x-path'
},
onShow: function () {
this.ui.blackholeFolder.autoComplete('/directories');
}
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,13 @@
<fieldset>
<legend>Blackhole</legend>
<div class="control-group">
<label class="control-label">Blackhole Folder</label>
<div class="controls">
<input type="text" name="blackholeFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="The folder where your download client will pickup .nzb files"/>
</span>
</div>
</div>
</fieldset>
+78
View File
@@ -0,0 +1,78 @@
'use strict';
define(
[
'marionette',
'Settings/DownloadClient/SabView',
'Settings/DownloadClient/BlackholeView',
'Settings/DownloadClient/PneumaticView',
'Settings/DownloadClient/NzbgetView',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, SabView, BlackholeView, PneumaticView, NzbgetView, AsModelBoundView) {
var view = Marionette.Layout.extend({
template : 'Settings/DownloadClient/LayoutTemplate',
regions: {
downloadClient: '#download-client-settings-region'
},
ui: {
downloadClientSelect: '.x-download-client',
downloadedEpisodesFolder: '.x-path'
},
events: {
'change .x-download-client': 'downloadClientChanged'
},
onShow: function () {
this.sabView = new SabView({ model: this.model});
this.blackholeView = new BlackholeView({ model: this.model});
this.pneumaticView = new PneumaticView({ model: this.model});
this.nzbgetView = new NzbgetView({ model: this.model});
this.ui.downloadedEpisodesFolder.autoComplete('/directories');
var client = this.model.get('downloadClient');
this.refreshUIVisibility(client);
},
downloadClientChanged: function () {
var clientId = this.ui.downloadClientSelect.val();
this.refreshUIVisibility(clientId);
},
refreshUIVisibility: function (clientId) {
if (!clientId) {
clientId = 'sabnzbd';
}
switch (clientId.toString()) {
case 'sabnzbd':
this.downloadClient.show(this.sabView);
break;
case 'blackhole':
this.downloadClient.show(this.blackholeView);
break;
case 'pneumatic':
this.downloadClient.show(this.pneumaticView);
break;
case 'nzbget':
this.downloadClient.show(this.nzbgetView);
break;
default :
throw 'unknown download client id' + clientId;
}
}
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,29 @@
<fieldset class="form-horizontal">
<legend>General</legend>
<div class="control-group">
<label class="control-label">Download Client</label>
<div class="controls">
<select class="inputClass x-download-client" name="downloadClient">
<option value="sabnzbd">SABnzbd</option>
<option value="blackhole">Blackhole</option>
<option value="pneumatic">Pneumatic</option>
<option value="nzbget">NZBGet</option>
</select>
</div>
</div>
<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>
<div id="download-client-settings-region" class="form-horizontal"></div>
@@ -0,0 +1,15 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/NzbgetViewTemplate'
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,86 @@
<fieldset>
<legend>NZBGet</legend>
<div class="control-group">
<label class="control-label">Host</label>
<div class="controls">
<input type="text" name="nzbgetHost"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Port</label>
<div class="controls">
<input type="text" name="nzbgetPort"/>
</div>
</div>
<div class="control-group">
<label class="control-label">API Key</label>
<div class="controls">
<input type="text" name="nzbgetApiKey"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Username</label>
<div class="controls">
<input type="text" name="nzbgetUsername"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" name="nzbgetPassword"/>
</div>
</div>
<div class="control-group">
<label class="control-label">TV Category</label>
<div class="controls">
<input type="text" name="nzbgetTvCategory"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Download Priority</label>
<div class="controls">
<select name="nzbgetRecentTvPriority">
<option value="default">Default</option>
<option value="pasued">Paused</option>
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="force">Force</option>
</select>
<span class="help-inline">
<i class="icon-nd-form-info" title="Priority to use when sending episodes that aired within the last 14 days"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Older Download Priority</label>
<div class="controls">
<select name="nzbgetOlderTvPriority">
<option value="default">Default</option>
<option value="pasued">Paused</option>
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="force">Force</option>
</select>
<span class="help-inline">
<i class="icon-nd-form-info" title="Priority to use when sending episodes that aired over 14 days ago"/>
</span>
</div>
</div>
</fieldset>
@@ -0,0 +1,24 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/PneumaticViewTemplate',
ui: {
'pneumaticFolder': '.x-path'
},
onShow: function () {
this.ui.pneumaticFolder.autoComplete('/directories');
}
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,13 @@
<fieldset>
<legend>Pneumatic</legend>
<div class="control-group">
<label class="control-label">Nzb Folder</label>
<div class="controls">
<input type="text" name="pneumaticFolder" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="Folder to save NZBs for Pneumatic<br/>must be accessible from XBMC"></i>
</span>
</div>
</div>
</fieldset>
+15
View File
@@ -0,0 +1,15 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'bootstrap'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template : 'Settings/DownloadClient/SabViewTemplate'
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,99 @@
<fieldset>
<legend>SABnzbd</legend>
{{!<div class="control-group">
<label class="control-label">Auto-Configure</label>
<div class="controls">
<input type="button" value="Auto-Configure" class="btn btn-inverse"/>
<span class="help-inline">
<i class="icon-question-sign"
title="(Windows only) If access to SABnzbd doesn't require a username & password and it is on the same system as NzbDrone, you can auto-configure it"/>
</span>
</div>
</div>}}
<div class="control-group">
<label class="control-label">Host</label>
<div class="controls">
<input type="text" name="sabHost"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Port</label>
<div class="controls">
<input type="text" name="sabPort"/>
</div>
</div>
<div class="control-group">
<label class="control-label">API Key</label>
<div class="controls">
<input type="text" name="sabApiKey"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Username</label>
<div class="controls">
<input type="text" name="sabUsername"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" name="sabPassword"/>
</div>
</div>
<div class="control-group">
<label class="control-label">TV Category</label>
<div class="controls">
<input type="text" name="sabTvCategory" placeholder="This is not the dropdownlist you're looking for"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Download Priority</label>
<div class="controls">
<select name="sabRecentTvPriority">
<option value="default">Default</option>
<option value="paused">Paused</option>
<option value="low">Low</option>
<option value="normal">Normal</option>
</option> <option value="high">High</option>
<option value="force">Force</option>
</select>
<span class="help-inline">
<i class="icon-nd-form-info" title="Priority to use when sending episodes that aired within the last 14 days"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Older Download Priority</label>
<div class="controls">
<select name="sabOlderTvPriority">
<option value="default">Default</option>
<option value="paused">Paused</option>
<option value="low">Low</option>
<option value="normal">Normal</option>
<option value="high">High</option>
<option value="force">Force</option>
</select>
<span class="help-inline">
<i class="icon-nd-form-info" title="Priority to use when sending episodes that aired over 14 days ago"/>
</span>
</div>
</div>
</fieldset>
@@ -0,0 +1,13 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/settings/host',
successMessage: 'General settings saved',
errorMessage : 'Failed to save general settings'
});
});
@@ -0,0 +1,146 @@
<div class="form-horizontal">
<fieldset>
<legend>Start-Up</legend>
<div class="control-group">
<label class="control-label">Port Number</label>
<div class="controls">
<input type="number" placeholder="8989" name="port"/>
<span>
<i class="icon-nd-form-warning" title="Requires restart to take effect"/>
</span>
</div>
</div>
<div class="control-group advanced-setting">
<label class="control-label">Enable SSL</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enableSsl" class="x-ssl"/>
<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-warning" title="Requires restart running as administrator to take effect"/>
</span>
</div>
</div>
<div class="x-ssl-options">
<div class="control-group advanced-setting">
<label class="control-label">SSL Port Number</label>
<div class="controls">
<input type="number" placeholder="8989" name="sslPort"/>
</div>
</div>
<div class="control-group advanced-setting">
<label class="control-label">SSL Cert Hash</label>
<div class="controls">
<input type="text" name="sslCertHash"/>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">Open browser on start</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="launchBrowser"/>
<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="Open a web browser and navigate to NzbDrone homepage on app start. Has no effect if installed as a windows service"/>
</span>
</div>
</div>
</fieldset>
<fieldset>
<legend>Security</legend>
<div class="control-group">
<label class="control-label">Authentication</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" class='x-auth' name="authenticationEnabled"/>
<p>
<span>On</span>
<span>Off</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Require Username and Password to access Nzbdrone"/>
</span>
</div>
</div>
<div class='x-auth-options'>
<div class="control-group">
<label class="control-label">Username</label>
<div class="controls">
<input type="text" placeholder="Username" name="username"/>
</div>
</div>
<div class="control-group">
<label class="control-label">Password</label>
<div class="controls">
<input type="password" name="password"/>
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend>Logging</legend>
<div class="control-group">
<label class="control-label">Log Level</label>
<div class="controls">
<select name="logLevel">
<option value="Trace">Trace</option>
<option value="Debug">Debug</option>
<option value="Info">Info</option>
</select>
<span>
<i class="icon-nd-form-warning" title="Trace and Debug logging should only be enabled temporarily"/>
</span>
</div>
</div>
</fieldset>
<fieldset class="advanced-setting">
<legend>Development</legend>
<div class="alert">
<i class="icon-nd-warning"></i>
Don't change anything here unless you know what you are doing.
</div>
<div class="control-group">
<label class="control-label">Branch</label>
<div class="controls">
<input type="text" placeholder="master" name="branch"/>
</div>
</div>
</fieldset>
</div>
+61
View File
@@ -0,0 +1,61 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/General/GeneralTemplate',
events: {
'change .x-auth': '_setAuthOptionsVisibility',
'change .x-ssl': '_setSslOptionsVisibility'
},
ui: {
authToggle : '.x-auth',
authOptions: '.x-auth-options',
sslToggle : '.x-ssl',
sslOptions: '.x-ssl-options'
},
onRender: function(){
if(!this.ui.authToggle.prop('checked')){
this.ui.authOptions.hide();
}
if(!this.ui.sslToggle.prop('checked')){
this.ui.sslOptions.hide();
}
},
_setAuthOptionsVisibility: function () {
var showAuthOptions = this.ui.authToggle.prop('checked');
if (showAuthOptions) {
this.ui.authOptions.slideDown();
}
else {
this.ui.authOptions.slideUp();
}
},
_setSslOptionsVisibility: function () {
var showSslOptions = this.ui.sslToggle.prop('checked');
if (showSslOptions) {
this.ui.sslOptions.slideDown();
}
else {
this.ui.sslOptions.slideUp();
}
}
});
return AsModelBoundView.call(view);
});
+11
View File
@@ -0,0 +1,11 @@
'use strict';
define(
[
'Settings/Indexers/Model',
'Form/FormBuilder'
], function (IndexerModel) {
return Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/indexer',
model: IndexerModel
});
});
@@ -0,0 +1,16 @@
<fieldset>
<legend>Indexers</legend>
<div class="row">
<div class="span12">
<ul id="x-indexers" class="indexer-list">
<li>
<div class="indexer-settings-item add-card x-add-card">
<span class="center well">
<i class="icon-plus" title="Add Newznab"/>
</span>
</div>
</li>
</ul>
</div>
</div>
</fieldset>
@@ -0,0 +1,47 @@
'use strict';
define(['app',
'marionette',
'Settings/Indexers/ItemView',
'Settings/Indexers/EditView',
'Settings/Indexers/Collection'],
function (App, Marionette, IndexerItemView, IndexerEditView, IndexerCollection) {
return Marionette.CompositeView.extend({
itemView : IndexerItemView,
itemViewContainer: '#x-indexers',
template : 'Settings/Indexers/CollectionTemplate',
ui: {
'addCard': '.x-add-card'
},
events: {
'click .x-add-card': '_openSchemaModal'
},
appendHtml: function(collectionView, itemView, index){
collectionView.ui.addCard.parent('li').before(itemView.el);
},
_openSchemaModal: function () {
var self = this;
//TODO: Is there a better way to deal with changing URLs?
var schemaCollection = new IndexerCollection();
schemaCollection.url = '/api/indexer/schema';
schemaCollection.fetch({
success: function (collection) {
collection.url = '/api/indexer';
var model = _.first(collection.models);
model.set({
id: undefined,
name: '',
enable: true
});
var view = new IndexerEditView({ model: model, indexerCollection: self.collection});
App.modalRegion.show(view);
}
});
}
});
});
@@ -0,0 +1,11 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete Indexer</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete '{{name}}'?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-danger x-confirm-delete">delete</button>
</div>
+19
View File
@@ -0,0 +1,19 @@
'use strict';
define(['app', 'marionette'], function (App, Marionette) {
return Marionette.ItemView.extend({
template: 'Settings/Notifications/DeleteTemplate',
events: {
'click .x-confirm-delete': '_removeIndexer'
},
_removeIndexer: function () {
this.model.destroy({
wait : true,
success: function () {
App.vent.trigger(App.Commands.CloseModalCommand);
}
});
}
});
});
@@ -0,0 +1,58 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
{{#if id}}
<h3>Edit</h3>
{{else}}
<h3>Add Newznab</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">Enable</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enable"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
{{formBuilder}}
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-remove">delete</button>
{{/if}}
<span class="x-activity"></span>
<button class="btn" data-dismiss="modal">cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">save</button>
<button class="btn btn-icon-only btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="save-and-add x-save-and-add">
save and add
</li>
</ul>
</div>
</div>
+77
View File
@@ -0,0 +1,77 @@
'use strict';
define(
[
'app',
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (App, Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Indexers/EditTemplate',
ui : {
activity: '.x-activity'
},
events: {
'click .x-save' : '_save',
'click .x-save-and-add': '_saveAndAdd'
},
initialize: function (options) {
this.indexerCollection = options.indexerCollection;
},
_save: function () {
this.ui.activity.html('<i class="icon-nd-spinner"></i>');
var self = this;
var promise = this.model.saveSettings();
if (promise) {
promise.done(function () {
self.indexerCollection.add(self.model, { merge: true });
App.vent.trigger(App.Commands.CloseModalCommand);
});
promise.fail(function () {
self.ui.activity.empty();
});
}
},
_saveAndAdd: function () {
this.ui.activity.html('<i class="icon-nd-spinner"></i>');
var self = this;
var promise = this.model.saveSettings();
if (promise) {
promise.done(function () {
self.indexerCollection.add(self.model, { merge: true });
self.model.set({
id : undefined,
name : '',
enable: false
});
_.each(self.model.get('fields'), function (value, key, list) {
self.model.set('fields.' + key + '.value', '');
});
});
promise.fail(function () {
self.ui.activity.empty();
});
}
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
return view;
});
+28
View File
@@ -0,0 +1,28 @@
"use strict";
define(
[
'marionette',
'Settings/Indexers/CollectionView',
'Settings/Indexers/Options/IndexerOptionsView'
], function (Marionette, CollectionView, OptionsView) {
return Marionette.Layout.extend({
template: 'Settings/Indexers/IndexerLayoutTemplate',
regions: {
indexersRegion : '#indexers-collection',
indexerOptions : '#indexer-options'
},
initialize: function (options) {
this.settings = options.settings;
this.indexersCollection = options.indexersCollection;
},
onShow: function () {
this.indexersRegion.show(new CollectionView({ collection: this.indexersCollection }));
this.indexerOptions.show(new OptionsView({ model: this.settings }));
}
});
});
@@ -0,0 +1,5 @@
<div id="indexers-collection"></div>
<div class="form-horizontal">
<div id="indexer-options"></div>
</div>
@@ -0,0 +1,37 @@
<div class="indexer-settings-item">
<div>
<h3>{{name}}</h3>
{{#if_eq implementation compare="Newznab"}}
<span class="btn-group pull-right">
<button class="btn btn-mini btn-icon-only x-delete">
<i class="icon-nd-delete"/>
</button>
</span>
{{/if_eq}}
</div>
<div class="control-group">
<label class="control-label">Enable</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="enable"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
{{formBuilder}}
{{#if_eq name compare="WomblesIndex"}}
<div class="alert">
<i class="icon-nd-warning"></i>
Does not support searching
</div>
{{/if_eq}}
</div>
+29
View File
@@ -0,0 +1,29 @@
'use strict';
define(
[
'app',
'marionette',
'Settings/Notifications/DeleteView',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (App, Marionette, DeleteView, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Indexers/ItemTemplate',
tagName : 'li',
events: {
'click .x-delete': '_deleteIndexer'
},
_deleteIndexer: function () {
var view = new DeleteView({ model: this.model});
App.modalRegion.show(view);
}
});
AsModelBoundView.call(view);
return AsValidatedView.call(view);
});
+17
View File
@@ -0,0 +1,17 @@
'use strict';
define([
'Settings/SettingsModelBase'], function (ModelBase) {
return ModelBase.extend({
baseInitialize: ModelBase.prototype.initialize,
initialize: function () {
var name = this.get('name');
this.successMessage = 'Saved indexer: ' + name;
this.errorMessage = 'Couldn\'t save indexer: ' + name;
this.baseInitialize.call(this);
}
});
});
@@ -0,0 +1,13 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Indexers/Options/IndexerOptionsViewTemplate'
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,37 @@
<fieldset>
<legend>Options</legend>
<div class="control-group">
<label class="control-label">Retention</label>
<div class="controls">
<input type="number" min="0" name="retention"/>
</div>
</div>
<div class="control-group advanced-setting">
<label class="control-label">RSS Sync Interval</label>
<div class="controls">
<input type="number" min="10" max="120" name="rssSyncInterval"/>
<span class="help-inline">
<i class="icon-nd-form-warning" title="This will apply to all indexers, please follow the rules set forth by them"/>
</span>
</div>
</div>
<div class="control-group advanced-setting">
<label class="control-label">Release Restrictions</label>
<div class="controls">
<textarea rows="3" name="releaseRestrictions" class="release-restrictions"></textarea>
<span class="help-inline">
<i class="icon-question-sign" title="Blacklist NZBs based on these words (case-insensitive)"/>
</span>
<span class="text-area-help">Newline-delimited set of rules</span>
</div>
</div>
</fieldset>
+36
View File
@@ -0,0 +1,36 @@
@import "../../Shared/Styles/card";
.indexer-list {
li {
display: inline-block;
vertical-align: top;
}
}
.indexer-settings-item {
.card;
width: 220px;
height: 260px;
padding: 10px 15px;
h3 {
margin-top: 0px;
display: inline-block;
width: 190px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.btn-group {
margin-top: 8px;
}
&.add-card {
.center {
margin-top: 100px;
}
}
}
@@ -0,0 +1,22 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AutoComplete'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/FileManagement/FileManagementViewTemplate',
ui: {
recyclingBin: '.x-path'
},
onShow: function () {
this.ui.recyclingBin.autoComplete('/directories');
}
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,54 @@
<fieldset class="advanced-setting">
<legend>File Management</legend>
<div class="control-group">
<label class="control-label">Ignore Deleted Episodes</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="autoUnmonitorPreviouslyDownloadedEpisodes"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-question-sign" title="Episodes deleted from disk are automatically unmonitored in NzbDrone"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Download Propers</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="autoDownloadPropers"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-question-sign" title="Should NzbDrone automatically upgrade to propers when available?"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">Recycling Bin</label>
<div class="controls">
<input type="text" name="recycleBin" class="x-path"/>
<span class="help-inline">
<i class="icon-nd-form-info" title="Episode files will go here when deleted instead of being permanently deleted"/>
</span>
</div>
</div>
</fieldset>
@@ -0,0 +1,31 @@
"use strict";
define(
[
'marionette',
'Settings/MediaManagement/Naming/View',
'Settings/MediaManagement/Sorting/View',
'Settings/MediaManagement/FileManagement/FileManagementView'
], function (Marionette, NamingView, SortingView, FileManagementView) {
return Marionette.Layout.extend({
template: 'Settings/MediaManagement/MediaManagementLayoutTemplate',
regions: {
episodeNaming : '#episode-naming',
sorting : '#sorting',
fileManagement : '#file-management'
},
initialize: function (options) {
this.settings = options.settings;
this.namingSettings = options.namingSettings;
},
onShow: function () {
this.episodeNaming.show(new NamingView({ model: this.namingSettings }));
this.sorting.show(new SortingView({ model: this.settings }));
this.fileManagement.show(new FileManagementView({ model: this.settings }));
}
});
});
@@ -0,0 +1,5 @@
<div class="form-horizontal">
<div id="episode-naming"></div>
<div id="sorting"></div>
<div id="file-management"></div>
</div>
@@ -0,0 +1,12 @@
'use strict';
define(
[
'Settings/SettingsModelBase'
], function (ModelBase) {
return ModelBase.extend({
url : window.NzbDrone.ApiRoot + '/config/naming',
successMessage: 'MediaManagement settings saved',
errorMessage : 'Couldn\'t save naming settings'
});
});
@@ -0,0 +1,60 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/Naming/ViewTemplate',
ui: {
namingOptions : '.x-naming-options',
renameEpisodesCheckbox: '.x-rename-episodes',
singleEpisodeExample : '.x-single-episode-example',
multiEpisodeExample : '.x-multi-episode-example'
},
events: {
'change .x-rename-episodes': '_setNamingOptionsVisibility'
},
onRender: function () {
if (!this.model.get('renameEpisodes')) {
this.ui.namingOptions.hide();
}
this.listenTo(this.model, 'change', this._updateExamples);
this._updateExamples();
},
_setNamingOptionsVisibility: function () {
var checked = this.ui.renameEpisodesCheckbox.prop('checked');
if (checked) {
this.ui.namingOptions.slideDown();
}
else {
this.ui.namingOptions.slideUp();
}
},
_updateExamples: function () {
var self = this;
var promise = $.ajax({
type: 'GET',
url : window.NzbDrone.ApiRoot + '/config/naming/samples',
data: this.model.toJSON()
});
promise.done(function (result) {
self.ui.singleEpisodeExample.html(result.singleEpisodeExample);
self.ui.multiEpisodeExample.html(result.multiEpisodeExample);
});
}
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,148 @@
<fieldset>
<legend>Episode Naming</legend>
<div class="control-group">
<label class="control-label">Rename Episodes</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="renameEpisodes" class="x-rename-episodes"/>
<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-warning" title="NzbDrone will use the existing file name if set to no"/>
</span>
</div>
</div>
<div class="x-naming-options">
<div class="control-group">
<label class="control-label">Include Series Title</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="includeSeriesTitle"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Include Episode Title</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="includeEpisodeTitle"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Include Quality</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="includeQuality"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Replace Spaces</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="replaceSpaces"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Separator</label>
<div class="controls">
<select class="inputClass" name="separator">
<option value=" - ">Dash</option>
<option value=" ">Space</option>
<option value=".">Period</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">Numbering Style</label>
<div class="controls">
<select class="inputClass" name="numberStyle">
<option value="0">1x05</option>
<option value="1">01x05</option>
<option value="2">S01E05</option>
<option value="3">s01e05</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">Multi-Episode Style</label>
<div class="controls">
<select class="inputClass" name="multiEpisodeStyle">
<option value="0">Extend</option>
<option value="1">Duplicate</option>
<option value="2">Repeat</option>
<option value="3">Scene</option>
</select>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">Single Episode Example</label>
<div class="controls">
<span class="x-single-episode-example naming-example"></span>
</div>
</div>
<div class="control-group">
<label class="control-label">Multi-Episode Example</label>
<div class="controls">
<span class="x-multi-episode-example naming-example"></span>
</div>
</div>
</fieldset>
@@ -0,0 +1,13 @@
'use strict';
define(
[
'marionette',
'Mixins/AsModelBoundView'
], function (Marionette, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/MediaManagement/Sorting/ViewTemplate'
});
return AsModelBoundView.call(view);
});
@@ -0,0 +1,32 @@
<fieldset>
<legend>Season Folder</legend>
<!--TODO: Remove this and move it to Add Series-->
<div class="control-group">
<label class="control-label">Use Season Folder</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="useSeasonFolder"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
<div class="control-group">
<label class="control-label">Season Folder Format</label>
<div class="controls">
<input type="text" placeholder="Season %s" name="seasonFolderFormat"/>
<span class="help-inline">
<i class="icon-question-sign" title="How should season folders be named? (Use %0s to pad to two digits, %sn for Series Name)"/>
</span>
</div>
</div>
</fieldset>
@@ -0,0 +1,10 @@
<div class="add-notification-item span3">
<div class="row">
<div class="span3">
{{implementationName}}
{{#if link}}
<a href="{{link}}"><i class="icon-info-sign"/></a>
{{/if}}
</div>
</div>
</div>
@@ -0,0 +1,37 @@
'use strict';
define([
'app',
'marionette',
'Settings/Notifications/EditView'
], function (App, Marionette, EditView) {
return Marionette.ItemView.extend({
template: 'Settings/Notifications/AddItemTemplate',
tagName : 'li',
events: {
'click': 'addNotification'
},
initialize: function (options) {
this.notificationCollection = options.notificationCollection;
},
addNotification: function (e) {
if ($(e.target).hasClass('icon-info-sign')) {
return;
}
this.model.set({
id: undefined,
name: this.model.get('implementationName'),
onGrab: true,
onDownload: true
});
var editView = new EditView({ model: this.model, notificationCollection: this.notificationCollection });
App.modalRegion.show(editView);
}
});
});
@@ -0,0 +1,12 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Add Notification</h3>
</div>
<div class="modal-body">
<div class="add-notifications">
<ul class="items"></ul>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">close</button>
</div>
+23
View File
@@ -0,0 +1,23 @@
'use strict';
define([
'marionette',
'Settings/Notifications/AddItemView'
], function (Marionette, AddItemView) {
return Marionette.CompositeView.extend({
itemView : AddItemView,
itemViewContainer: '.add-notifications .items',
template : 'Settings/Notifications/AddTemplate',
itemViewOptions: function () {
return {
notificationCollection: this.notificationCollection
};
},
initialize: function (options) {
this.notificationCollection = options.notificationCollection;
}
});
});
@@ -0,0 +1,10 @@
'use strict';
define(
[
'Settings/Notifications/Model'
], function (NotificationModel) {
return Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/notification',
model: NotificationModel
});
});
@@ -0,0 +1,13 @@
<div class="row">
<div class="span12">
<ul class="notifications">
<li>
<div class="notification-item add-card x-add-card">
<span class="center well">
<i class="icon-plus" title="Add Connection"/>
</span>
</div>
</li>
</ul>
</div>
</div>
@@ -0,0 +1,29 @@
'use strict';
define([
'app',
'marionette',
'Settings/Notifications/ItemView',
'Settings/Notifications/SchemaModal'
], function (App, Marionette, NotificationItemView, SchemaModal) {
return Marionette.CompositeView.extend({
itemView : NotificationItemView,
itemViewContainer: '.notifications',
template : 'Settings/Notifications/CollectionTemplate',
ui: {
'addCard': '.x-add-card'
},
events: {
'click .x-add-card': '_openSchemaModal'
},
appendHtml: function(collectionView, itemView, index){
collectionView.ui.addCard.parent('li').before(itemView.el);
},
_openSchemaModal: function () {
SchemaModal.open(this.collection);
}
});
});
@@ -0,0 +1,11 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete Notification</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete '{{name}}'?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-danger x-confirm-delete">delete</button>
</div>
@@ -0,0 +1,19 @@
'use strict';
define(['app', 'marionette'], function (App, Marionette) {
return Marionette.ItemView.extend({
template: 'Settings/Notifications/DeleteTemplate',
events: {
'click .x-confirm-delete': '_removeNotification'
},
_removeNotification: function () {
this.model.destroy({
wait : true,
success: function () {
App.vent.trigger(App.Commands.CloseModalCommand);
}
});
}
});
});
@@ -0,0 +1,83 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
{{#if id}}
<h3>Edit - {{implementationName}}</h3>
{{else}}
<h3>Add - {{implementationName}}</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">On Grab</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="onGrab"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-question-sign" title="Do you want to get notifications when episodes are grabbed?"/>
</span>
</div>
</div>
<div class="control-group">
<label class="control-label">On Download</label>
<div class="controls">
<label class="checkbox toggle well">
<input type="checkbox" name="onDownload"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-question-sign" title="Do you want to get notifications when episodes are downloaded?"/>
</span>
</div>
</div>
{{formBuilder}}
</div>
</div>
<div class="modal-footer">
{{#if id}}
<button class="btn btn-danger pull-left x-delete">delete</button>
{{else}}
<button class="btn pull-left x-back">back</button>
{{/if}}
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
<button class="btn" data-dismiss="modal">cancel</button>
<div class="btn-group">
<button class="btn btn-primary x-save">save</button>
<button class="btn btn-icon-only btn-primary dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li class="save-and-add x-save-and-add">
save and add
</li>
</ul>
</div>
</div>
+92
View File
@@ -0,0 +1,92 @@
'use strict';
define([
'app',
'marionette',
'Settings/Notifications/Model',
'Settings/Notifications/DeleteView',
'Shared/Messenger',
'Commands/CommandController',
'Mixins/AsModelBoundView',
'Form/FormBuilder'
], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) {
var model = Marionette.ItemView.extend({
template: 'Settings/Notifications/EditTemplate',
events: {
'click .x-save' : '_saveNotification',
'click .x-save-and-add' : '_saveAndAddNotification',
'click .x-delete' : '_deleteNotification',
'click .x-back' : '_back',
'click .x-test' : '_test'
},
ui: {
testButton: '.x-test',
testIcon : '.x-test-icon'
},
initialize: function (options) {
this.notificationCollection = options.notificationCollection;
},
_saveNotification: function () {
var self = this;
var promise = this.model.saveSettings();
if (promise) {
promise.done(function () {
self.notificationCollection.add(self.model, { merge: true });
App.vent.trigger(App.Commands.CloseModalCommand);
});
}
},
_saveAndAddNotification: function () {
var self = this;
var promise = this.model.saveSettings();
if (promise) {
promise.done(function () {
self.notificationCollection.add(self.model, { merge: true });
require('Settings/Notifications/SchemaModal').open(self.notificationCollection);
});
}
},
_deleteNotification: function () {
var view = new DeleteView({ model: this.model });
App.modalRegion.show(view);
},
_back: function () {
require('Settings/Notifications/SchemaModal').open(this.notificationCollection);
},
_test: function () {
var testCommand = this.model.get('testCommand');
if (testCommand) {
this.idle = false;
var properties = {};
_.each(this.model.get('fields'), function (field) {
properties[field.name] = field.value;
});
CommandController.Execute(testCommand, properties);
}
},
_testOnAlways: function () {
if (!this.isClosed) {
this.idle = true;
}
}
});
return AsModelBoundView.call(model);
});
@@ -0,0 +1,23 @@
<div class="notification-item">
<div>
<h3>{{name}}</h3>
<span class="btn-group pull-right">
<button class="btn btn-mini btn-icon-only x-edit"><i class="icon-nd-edit"/></button>
<button class="btn btn-mini btn-icon-only x-delete"><i class="icon-nd-delete"/></button>
</span>
</div>
<div class="settings">
{{#if onGrab}}
<span class="label label-success">On Grab</span>
{{else}}
<span class="label">On Grab</span>
{{/if}}
{{#if onDownload}}
<span class="label label-success">On Download</span>
{{else}}
<span class="label">On Download</span>
{{/if}}
</div>
</div>
+34
View File
@@ -0,0 +1,34 @@
'use strict';
define([
'app',
'marionette',
'Settings/Notifications/EditView',
'Settings/Notifications/DeleteView'
], function (App, Marionette, EditView, DeleteView) {
return Marionette.ItemView.extend({
template: 'Settings/Notifications/ItemTemplate',
tagName : 'li',
events: {
'click .x-edit' : '_editNotification',
'click .x-delete': '_deleteNotification'
},
initialize: function () {
this.listenTo(this.model, 'sync', this.render);
},
_editNotification: function () {
var view = new EditView({ model: this.model, notificationCollection: this.model.collection});
App.modalRegion.show(view);
},
_deleteNotification: function () {
var view = new DeleteView({ model: this.model});
App.modalRegion.show(view);
}
});
});
+9
View File
@@ -0,0 +1,9 @@
'use strict';
define([
'Settings/SettingsModelBase'], function (ModelBase) {
return ModelBase.extend({
successMessage: 'Notification Saved',
errorMessage : 'Couldn\'t save notification'
});
});
@@ -0,0 +1,19 @@
'use strict';
define([
'app',
'Settings/Notifications/Collection',
'Settings/Notifications/AddView'
], function (App, NotificationCollection, AddSelectionNotificationView) {
return ({
open: function (collection) {
var schemaCollection = new NotificationCollection();
schemaCollection.url = '/api/notification/schema';
schemaCollection.fetch();
schemaCollection.url = '/api/notification';
var view = new AddSelectionNotificationView({ collection: schemaCollection, notificationCollection: collection});
App.modalRegion.show(view);
}
});
});
@@ -0,0 +1,79 @@
@import "../../Shared/Styles/card.less";
@import "../../Shared/Styles/clickable.less";
.add-notification-item {
.card;
cursor: pointer;
font-size: 24px;
font-weight: lighter;
text-align: center;
a {
font-size: 16px;
color: #595959;
i {
.clickable;
}
}
a:hover {
text-decoration: none;
}
}
.add-notifications {
text-align: center;
.items {
list-style-type: none;
margin: 0px;
li {
display: inline-block;
vertical-align: top;
}
}
}
.notifications {
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
li {
display: inline-block;
vertical-align: top;
}
}
.notification-item {
.card;
width: 290px;
height: 90px;
padding: 20px 20px;
h3 {
margin-top: 0px;
display: inline-block;
width: 230px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.btn-group {
margin-top: 8px;
}
.settings {
margin-top: 5px;
}
&.add-card {
.center {
margin-top: 15px;
}
}
}
@@ -0,0 +1,18 @@
'use strict';
define(['app', 'handlebars'], function (App,Handlebars) {
Handlebars.registerHelper('allowedLabeler', function () {
var ret = '';
var cutoff = this.cutoff;
_.each(this.allowed, function (allowed) {
if (allowed.id === cutoff.id) {
ret += '<span class="label label-info" title="Cutoff">' + allowed.name + '</span> ';
}
else {
ret += '<span class="label">' + allowed.name + '</span> ';
}
});
return new Handlebars.SafeString(ret);
});
});
@@ -0,0 +1,11 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete: {{name}}</h3>
</div>
<div class="modal-body">
<p>Are you sure you want to delete '{{name}}'?</p>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">cancel</button>
<button class="btn btn-danger x-confirm-delete">delete</button>
</div>
@@ -0,0 +1,24 @@
'use strict';
define(
[
'app',
'marionette'
], function (App, Marionette) {
return Marionette.ItemView.extend({
template: 'Settings/Quality/Profile/DeleteTemplate',
events: {
'click .x-confirm-delete': '_removeProfile'
},
_removeProfile: function () {
this.model.destroy({
wait: true
}).done(function () {
App.vent.trigger(App.Commands.CloseModalCommand);
});
}
});
});
@@ -0,0 +1,60 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</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">
{{#each allowed}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</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>
<div>
<div class="offset1 span3">
<h3>Available</h3>
<select multiple="multiple" class="x-available-list">
{{#each available}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</select>
</div>
<div class="span3">
<div class="control-group">
<div class="controls">
<h3>Allowed</h3>
<select multiple="multiple" class="x-allowed-list" validation-name="allowed">
{{#each allowed}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</select>
</div>
</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,81 @@
'use strict';
define(
[
'app',
'marionette',
'Mixins/AsModelBoundView',
'Mixins/AsValidatedView'
], function (App, Marionette, AsModelBoundView, AsValidatedView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Quality/Profile/EditQualityProfileTemplate',
ui: {
cutoff: '.x-cutoff'
},
events: {
'click .x-save' : '_saveQualityProfile',
'dblclick .x-available-list': '_moveQuality',
'dblclick .x-allowed-list' : '_moveQuality'
},
initialize: function (options) {
this.profileCollection = options.profileCollection;
},
_moveQuality: function (event) {
var quality;
var qualityId = event.target.value;
var availableCollection = new Backbone.Collection(this.model.get('available'));
availableCollection.comparator = function (model) {
return model.get('weight');
};
var allowedCollection = new Backbone.Collection(this.model.get('allowed'));
allowedCollection.comparator = function (model) {
return model.get('weight');
};
if (availableCollection.get(qualityId)) {
quality = availableCollection.get(qualityId);
availableCollection.remove(quality);
allowedCollection.add(quality);
}
else if (allowedCollection.get(qualityId)) {
quality = allowedCollection.get(qualityId);
allowedCollection.remove(quality);
availableCollection.add(quality);
}
else {
throw 'couldnt find quality id ' + qualityId;
}
this.model.set('available', availableCollection.toJSON());
this.model.set('allowed', allowedCollection.toJSON());
this.render();
},
_saveQualityProfile: function () {
var self = this;
var cutoff = _.findWhere(this.model.get('allowed'), { id: parseInt(this.ui.cutoff.val())});
this.model.set('cutoff', cutoff);
var promise = this.model.save();
if (promise) {
promise.done(function () {
self.profileCollection.add(self.model, { merge: true });
App.vent.trigger(App.Commands.CloseModalCommand);
});
}
}
});
AsValidatedView.call(view);
return AsModelBoundView.call(view);
});
@@ -0,0 +1,16 @@
<fieldset>
<legend>Quality Profiles</legend>
<div class="row">
<div class="span12">
<ul class="quality-profiles">
<li>
<div class="quality-profile-item add-card x-add-card">
<span class="center well">
<i class="icon-plus" title="Add Profile"/>
</span>
</div>
</li>
</ul>
</div>
</div>
</fieldset>
@@ -0,0 +1,43 @@
'use strict';
define(['app',
'marionette',
'Settings/Quality/Profile/QualityProfileView',
'Settings/Quality/Profile/EditQualityProfileView',
'Settings/Quality/Profile/QualityProfileSchemaCollection'
], function (App, Marionette, QualityProfileView, EditProfileView, ProfileCollection) {
return Marionette.CompositeView.extend({
itemView : QualityProfileView,
itemViewContainer: '.quality-profiles',
template : 'Settings/Quality/Profile/QualityProfileCollectionTemplate',
ui: {
'addCard': '.x-add-card'
},
events: {
'click .x-add-card': '_addProfile'
},
appendHtml: function(collectionView, itemView, index){
collectionView.ui.addCard.parent('li').before(itemView.el);
},
_addProfile: function () {
var self = this;
var schemaCollection = new ProfileCollection();
schemaCollection.fetch({
success: function (collection) {
var model = _.first(collection.models);
model.set('id', undefined);
model.set('name', '');
model.collection = self.collection;
var view = new EditProfileView({ model: model, profileCollection: self.collection});
App.modalRegion.show(view);
}
});
}
});
});
@@ -0,0 +1,13 @@
"use strict";
define(
[
'backbone',
'Quality/QualityProfileModel'
], function (Backbone, QualityProfileModel) {
return Backbone.Collection.extend({
model: QualityProfileModel,
url : window.NzbDrone.ApiRoot + '/qualityprofiles/schema'
});
});
@@ -0,0 +1,11 @@
<div class="quality-profile-item">
<div>
<h3 name="name"></h3>
<span class="btn-group pull-right">
<button class="btn btn-mini btn-icon-only x-edit"><i class="icon-nd-edit"/></button>
<button class="btn btn-mini btn-icon-only x-delete"><i class="icon-nd-delete"/></button>
</span>
</div>
{{allowedLabeler}}
</div>
@@ -0,0 +1,71 @@
'use strict';
define(
[
'app',
'marionette',
'Settings/Quality/Profile/EditQualityProfileView',
'Settings/Quality/Profile/DeleteView',
'Series/SeriesCollection',
'Mixins/AsModelBoundView',
'Settings/Quality/Profile/AllowedLabeler',
'bootstrap',
], function (App, Marionette, EditProfileView, DeleteProfileView, SeriesCollection, AsModelBoundView) {
var view = Marionette.ItemView.extend({
template: 'Settings/Quality/Profile/QualityProfileTemplate',
tagName : 'li',
ui: {
'progressbar' : '.progress .bar',
'deleteButton': '.x-delete'
},
events: {
'click .x-edit' : '_editProfile',
'click .x-delete': '_deleteProfile'
},
initialize: function () {
this.listenTo(this.model, 'sync', this.render);
this.listenTo(SeriesCollection, 'all', this._updateDisableStatus)
},
_editProfile: function () {
var view = new EditProfileView({ model: this.model, profileCollection: this.model.collection });
App.modalRegion.show(view);
},
_deleteProfile: function () {
if (this._isQualityInUse()) {
return;
}
var view = new DeleteProfileView({ model: this.model });
App.modalRegion.show(view);
},
onRender: function () {
this._updateDisableStatus();
},
_updateDisableStatus: function () {
if (this._isQualityInUse()) {
this.ui.deleteButton.addClass('disabled');
this.ui.deleteButton.attr('title', 'Can\'t delete quality profiles attached to a series.');
}
else {
this.ui.deleteButton.removeClass('disabled');
this.ui.deleteButton.attr('title', 'Delete Quality Profile');
}
},
_isQualityInUse: function () {
return SeriesCollection.where({'qualityProfileId': this.model.id}).length !== 0;
}
});
return AsModelBoundView.call(view);
});
+32
View File
@@ -0,0 +1,32 @@
"use strict";
define(
[
'marionette',
'Quality/QualityProfileCollection',
'Settings/Quality/Profile/QualityProfileCollectionView',
'Quality/QualitySizeCollection',
'Settings/Quality/Size/QualitySizeCollectionView'
], function (Marionette, QualityProfileCollection, QualityProfileCollectionView, QualitySizeCollection, QualitySizeCollectionView) {
return Marionette.Layout.extend({
template: 'Settings/Quality/QualityLayoutTemplate',
regions: {
qualityProfile : '#quality-profile',
qualitySize : '#quality-size'
},
initialize: function (options) {
this.settings = options.settings;
QualityProfileCollection.fetch();
this.qualitySizeCollection = new QualitySizeCollection();
this.qualitySizeCollection.fetch();
},
onShow: function () {
this.qualityProfile.show(new QualityProfileCollectionView({collection: QualityProfileCollection}));
this.qualitySize.show(new QualitySizeCollectionView({collection: this.qualitySizeCollection}));
}
});
});
@@ -0,0 +1,6 @@
<div class="row">
<div class="span12" id="quality-profile"/>
</div>
<!--<div class="row">
<div class="span12" id="quality-size"/>
</div>-->
@@ -0,0 +1,4 @@
<fieldset>
<legend>Quality Size Limits</legend>
<ul class="quality-sizes"/>
</fieldset>
@@ -0,0 +1,9 @@
'use strict';
define(['marionette', 'Settings/Quality/Size/QualitySizeView'], function (Marionette, QualitySizeView) {
return Marionette.CompositeView.extend({
itemView : QualitySizeView,
itemViewContainer: '.quality-sizes',
template : 'Settings/Quality/Size/QualitySizeCollectionTemplate'
});
});
@@ -0,0 +1,20 @@
<div class="quality-size-item">
<h2 class="center-block">{{name}}</h2>
<div class="size">
<div class="size-value-wrapper">
<div>
<span class="label label-large label-warning x-size-thirty"
name="thirtyMinuteSize"
title="Maximum size for a 30 minute episode">
</span>
</div>
<div>
<span class="label label-large label-info x-size-sixty"
name="sixtyMinuteSize"
title="Maximum size for a 60 minute episode">
</span>
</div>
</div>
<input type="text" name="maxSize" class="knob x-knob" />
</div>
</div>
@@ -0,0 +1,55 @@
'use strict';
define(['marionette', 'Mixins/AsModelBoundView', 'filesize', 'jquery.knob' ], function (Marionette, AsModelBoundView, Filesize) {
var view = Marionette.ItemView.extend({
template : 'Settings/Quality/Size/QualitySizeTemplate',
tagName : 'li',
ui: {
knob : '.x-knob',
thirtyMinuteSize: '.x-size-thirty',
sixtyMinuteSize : '.x-size-sixty'
},
events: {
'change .x-knob': '_changeMaxSize'
},
initialize: function (options) {
this.qualityProfileCollection = options.qualityProfiles;
this.filesize = Filesize;
},
onRender: function () {
this.ui.knob.knob({
min : 0,
max : 200,
step : 1,
cursor : 25,
width : 150,
stopper : true,
displayInput : false
});
this._changeMaxSize();
},
_changeMaxSize: function () {
var maxSize = this.model.get('maxSize');
var bytes = maxSize * 1024 * 1024;
var thirty = Filesize(bytes * 30, 1, false);
var sixty = Filesize(bytes * 60, 1, false);
if (parseInt(maxSize) === 0) {
thirty = 'No Limit';
sixty = 'No Limit';
}
this.ui.thirtyMinuteSize.html(thirty);
this.ui.sixtyMinuteSize.html(sixty);
}
});
return AsModelBoundView.call(view);
});
+75
View File
@@ -0,0 +1,75 @@
@import "../../Shared/Styles/card";
.quality-profiles, .quality-sizes {
li {
display: inline-block;
vertical-align: top;
}
}
.quality-profile-item {
.card;
width: 300px;
height: 120px;
padding: 10px 15px;
h3 {
margin-top: 0px;
display: inline-block;
width: 240px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.btn-group {
margin-top: 8px;
}
&.add-card {
.center {
margin-top: 30px;
}
}
}
.quality-size-item {
.card;
text-align: center;
width: 200px;
height: 220px;
padding: 10px 15px;
h3 {
margin-top: 0px;
}
.size {
position: relative;
height: 100px;
margin: 10px;
text-align: center;
}
.knob {
box-shadow: none;
}
.size-value-wrapper {
position: absolute;
top: 50px;
width: 100%;
div {
margin-top: 2px;
}
}
}
#quality-size {
overflow: hidden;
}
+214
View File
@@ -0,0 +1,214 @@
'use strict';
define(
[
'app',
'marionette',
'Settings/SettingsModel',
'Settings/General/GeneralSettingsModel',
'Settings/MediaManagement/Naming/Model',
'Settings/MediaManagement/MediaManagementLayout',
'Settings/Quality/QualityLayout',
'Settings/Indexers/IndexerLayout',
'Settings/Indexers/Collection',
'Settings/DownloadClient/Layout',
'Settings/Notifications/CollectionView',
'Settings/Notifications/Collection',
'Settings/General/GeneralView',
'Shared/LoadingView',
'Config'
], function (App,
Marionette,
SettingsModel,
GeneralSettingsModel,
NamingModel,
MediaManagementLayout,
QualityLayout,
IndexerLayout,
IndexerCollection,
DownloadClientLayout,
NotificationCollectionView,
NotificationCollection,
GeneralView,
LoadingView,
Config) {
return Marionette.Layout.extend({
template: 'Settings/SettingsLayoutTemplate',
regions: {
mediaManagement : '#media-management',
quality : '#quality',
indexers : '#indexers',
downloadClient : '#download-client',
notifications : '#notifications',
general : '#general',
loading : '#loading-region'
},
ui: {
mediaManagementTab : '.x-media-management-tab',
qualityTab : '.x-quality-tab',
indexersTab : '.x-indexers-tab',
downloadClientTab : '.x-download-client-tab',
notificationsTab : '.x-notifications-tab',
generalTab : '.x-general-tab',
advancedSettings : '.x-advanced-settings'
},
events: {
'click .x-media-management-tab' : '_showMediaManagement',
'click .x-quality-tab' : '_showQuality',
'click .x-indexers-tab' : '_showIndexers',
'click .x-download-client-tab' : '_showDownloadClient',
'click .x-notifications-tab' : '_showNotifications',
'click .x-general-tab' : '_showGeneral',
'click .x-save-settings' : '_save',
'change .x-advanced-settings' : '_toggleAdvancedSettings'
},
initialize: function (options) {
if (options.action) {
this.action = options.action.toLowerCase();
}
},
onRender: function () {
this.loading.show(new LoadingView());
var self = this;
this.settings = new SettingsModel();
this.generalSettings = new GeneralSettingsModel();
this.namingSettings = new NamingModel();
this.indexerSettings = new IndexerCollection();
this.notificationSettings = new NotificationCollection();
$.when(this.settings.fetch(),
this.generalSettings.fetch(),
this.namingSettings.fetch(),
this.indexerSettings.fetch(),
this.notificationSettings.fetch()
).done(function () {
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.general.show(new GeneralView({ model: self.generalSettings }));
});
this._setAdvancedSettingsState();
},
onShow: function () {
switch (this.action) {
case 'quality':
this._showQuality();
break;
case 'indexers':
this._showIndexers();
break;
case 'downloadclient':
this._showDownloadClient();
break;
case 'connect':
this._showNotifications();
break;
case 'notifications':
this._showNotifications();
break;
case 'general':
this._showGeneral();
break;
default:
this._showMediaManagement();
}
},
_showMediaManagement: function (e) {
if (e) {
e.preventDefault();
}
this.ui.mediaManagementTab.tab('show');
this._navigate('settings/mediamanagement');
},
_showQuality: function (e) {
if (e) {
e.preventDefault();
}
this.ui.qualityTab.tab('show');
this._navigate('settings/quality');
},
_showIndexers: function (e) {
if (e) {
e.preventDefault();
}
this.ui.indexersTab.tab('show');
this._navigate('settings/indexers');
},
_showDownloadClient: function (e) {
if (e) {
e.preventDefault();
}
this.ui.downloadClientTab.tab('show');
this._navigate('settings/downloadclient');
},
_showNotifications: function (e) {
if (e) {
e.preventDefault();
}
this.ui.notificationsTab.tab('show');
this._navigate('settings/connect');
},
_showGeneral: function (e) {
if (e) {
e.preventDefault();
}
this.ui.generalTab.tab('show');
this._navigate('settings/general');
},
_navigate:function(route){
require(['Router'], function(){
App.Router.navigate(route);
});
},
_save: function () {
App.vent.trigger(App.Commands.SaveSettings);
},
_setAdvancedSettingsState: function () {
var checked = Config.getValueBoolean('advancedSettings');
this.ui.advancedSettings.prop('checked', checked);
if (checked) {
this.$el.addClass('show-advanced-settings');
}
},
_toggleAdvancedSettings: function () {
var checked = this.ui.advancedSettings.prop('checked');
Config.setValue('advancedSettings', checked);
if (checked) {
this.$el.addClass('show-advanced-settings');
}
else {
this.$el.removeClass('show-advanced-settings');
}
}
});
});
@@ -0,0 +1,33 @@
<ul class="nav nav-tabs" id="myTab">
<li><a href="#media-management" class="x-media-management-tab no-router">Media Management</a></li>
<li><a href="#quality" class="x-quality-tab no-router">Quality</a></li>
<li><a href="#indexers" class="x-indexers-tab no-router">Indexers</a></li>
<li><a href="#download-client" class="x-download-client-tab no-router">Download Client</a></li>
<li><a href="#notifications" class="x-notifications-tab no-router">Connect</a></li>
<li><a href="#general" class="x-general-tab no-router">General</a></li>
<li class="pull-right"><button class="btn btn-primary x-save-settings">Save</button></li>
<li class="pull-right advanced-settings-toggle">
<label class="checkbox toggle well">
<input type="checkbox" class="x-advanced-settings"/>
<p>
<span>Show</span>
<span>Hide</span>
</p>
<div class="btn btn-warning slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-nd-form-info" title="Show advanced options"/>
</span>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="media-management"></div>
<div class="tab-pane" id="quality"></div>
<div class="tab-pane" id="indexers"></div>
<div class="tab-pane" id="download-client"></div>
<div class="tab-pane" id="notifications"></div>
<div class="tab-pane" id="general"></div>
</div>
<div id="loading-region"></div>
+9
View File
@@ -0,0 +1,9 @@
'use strict';
define(['app',
'Settings/SettingsModelBase'], function (App, SettingsModelBase) {
return SettingsModelBase.extend({
url : window.NzbDrone.ApiRoot + '/settings',
successMessage: 'Settings saved',
errorMessage : 'Failed to save settings'
});
});
+34
View File
@@ -0,0 +1,34 @@
'use strict';
define(['app',
'backbone.deepmodel',
'Mixins/AsChangeTrackingModel',
'Shared/Messenger'], function (App, DeepModel, AsChangeTrackingModel, Messenger) {
var model = DeepModel.DeepModel.extend({
initialize: function () {
this.listenTo(App.vent, App.Commands.SaveSettings, this.saveSettings);
},
saveSettings: function () {
if (!this.isSaved) {
var savePromise = this.save();
Messenger.monitor(
{
promise : savePromise,
successMessage: this.successMessage,
errorMessage : this.errorMessage
});
return savePromise;
}
return undefined;
}
});
return AsChangeTrackingModel.call(model);
});
+80
View File
@@ -0,0 +1,80 @@
@import "../Content/Bootstrap/variables";
@import "../Shared/Styles/clickable.less";
@import "Indexers/indexers";
@import "Quality/quality";
@import "Notifications/notifications";
li.save-and-add {
.clickable;
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 20px;
color: rgb(51, 51, 51);
white-space: nowrap;
}
li.save-and-add:hover {
text-decoration: none;
color: rgb(255, 255, 255);
background-color: rgb(0, 129, 194);
}
.add-card {
.clickable;
color: #adadad;
font-size: 50px;
text-align: center;
background-color: #f5f5f5;
.center {
display: inline-block;
padding: 5px 20px 0px;
background-color: white;
}
i {
.clickable;
}
}
.naming-example {
display: inline-block;
margin-top: 5px;
}
.advanced-settings-toggle {
margin-right: 40px;
.checkbox {
width : 100px;
margin-left : 0px;
display : inline-block;
padding-top : 0px;
margin-bottom : 0px;
margin-top : -1px;
}
.help-inline-checkbox {
display : inline-block;
margin-top : -23px;
margin-bottom : 0;
vertical-align : middle;
}
}
.advanced-setting {
display: none;
.control-label {
color: @warningText;
}
}
.show-advanced-settings {
.advanced-setting {
display: block;
}
}