Cleanup Series Code from UI (#2525)

pull/2531/head
Qstick 7 years ago committed by Leonardo Galli
parent 1dbb856ced
commit e70e76adcb

@ -1,16 +1,9 @@
var Backbone = require('backbone');
var SeriesModel = require('../../Series/SeriesModel');
var EpisodeModel = require('../../Series/EpisodeModel');
var MovieModel = require('../../Movies/MovieModel');
var MoviesCollection = require('../../Movies/FullMovieCollection');
module.exports = Backbone.Model.extend({
parse : function(model) {
if (model.series) {
model.series = new SeriesModel(model.series);
model.episode = new EpisodeModel(model.episode);
model.episode.set('series', model.series);
}
//if (model.movie) {
// model.movie = new MovieModel(model.movie);

@ -1,16 +1,8 @@
var Backbone = require('backbone');
var SeriesModel = require('../../Series/SeriesModel');
var EpisodeModel = require('../../Series/EpisodeModel');
var MovieModel = require('../../Movies/MovieModel');
module.exports = Backbone.Model.extend({
parse : function(model) {
if (model.series) {
model.series = new SeriesModel(model.series);
model.episode = new EpisodeModel(model.episode);
model.episode.set('series', model.series);
}
if (model.movie) {
model.movie = new MovieModel(model.movie);
}

@ -1,13 +1,8 @@
var Backbone = require('backbone');
var SeriesModel = require('../../Series/SeriesModel');
var EpisodeModel = require('../../Series/EpisodeModel');
var MovieModel = require('../../Movies/MovieModel');
module.exports = Backbone.Model.extend({
parse : function(model) {
model.series = new SeriesModel(model.series);
model.episode = new EpisodeModel(model.episode);
model.episode.set('series', model.series);
model.movie = new MovieModel(model.movie);
return model;
}

@ -1,22 +0,0 @@
var Backbone = require('backbone');
var SeriesModel = require('../Series/SeriesModel');
var _ = require('underscore');
module.exports = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/series/lookup',
model : SeriesModel,
parse : function(response) {
var self = this;
_.each(response, function(model) {
model.id = undefined;
if (self.unmappedFolderModel) {
model.path = self.unmappedFolderModel.get('folder').path;
}
});
return response;
}
});

@ -1,53 +0,0 @@
var vent = require('vent');
var AppLayout = require('../AppLayout');
var Marionette = require('marionette');
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
var ExistingSeriesCollectionView = require('./Existing/AddExistingSeriesCollectionView');
var AddSeriesView = require('./AddSeriesView');
var ProfileCollection = require('../Profile/ProfileCollection');
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
require('../Series/SeriesCollection');
module.exports = Marionette.Layout.extend({
template : 'AddSeries/AddSeriesLayoutTemplate',
regions : {
workspace : '#add-series-workspace'
},
events : {
'click .x-import' : '_importSeries',
'click .x-add-new' : '_addSeries'
},
attributes : {
id : 'add-series-screen'
},
initialize : function() {
ProfileCollection.fetch();
RootFolderCollection.fetch().done(function() {
RootFolderCollection.synced = true;
});
},
onShow : function() {
this.workspace.show(new AddSeriesView());
},
_folderSelected : function(options) {
vent.trigger(vent.Commands.CloseModalCommand);
this.workspace.show(new ExistingSeriesCollectionView({ model : options.model }));
},
_importSeries : function() {
this.rootFolderLayout = new RootFolderLayout();
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
AppLayout.modalRegion.show(this.rootFolderLayout);
},
_addSeries : function() {
this.workspace.show(new AddSeriesView());
}
});

@ -1,16 +0,0 @@
<div class="row">
<div class="col-md-12">
<div class="btn-group add-series-btn-group btn-group-lg btn-block">
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-series-import-btn x-import">
<i class="icon-sonarr-hdd"/>
Import existing series on disk
</button>
<button class="btn btn-default col-md-2 col-xs-4 x-add-new"><i class="icon-sonarr-active hidden-xs"></i> Add New Movie</button>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="add-series-workspace"></div>
</div>
</div>

@ -1,182 +0,0 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var AddSeriesCollection = require('./AddSeriesCollection');
var SearchResultCollectionView = require('./SearchResultCollectionView');
var EmptyView = require('./EmptyView');
var NotFoundView = require('./NotFoundView');
var ErrorView = require('./ErrorView');
var LoadingView = require('../Shared/LoadingView');
module.exports = Marionette.Layout.extend({
template : 'AddSeries/AddSeriesViewTemplate',
regions : {
searchResult : '#search-result'
},
ui : {
seriesSearch : '.x-series-search',
searchBar : '.x-search-bar',
loadMore : '.x-load-more'
},
events : {
'click .x-load-more' : '_onLoadMore'
},
initialize : function(options) {
this.isExisting = options.isExisting;
this.collection = new AddSeriesCollection();
if (this.isExisting) {
this.collection.unmappedFolderModel = this.model;
}
if (this.isExisting) {
this.className = 'existing-series';
} else {
this.className = 'new-series';
}
this.listenTo(vent, vent.Events.SeriesAdded, this._onSeriesAdded);
this.listenTo(this.collection, 'sync', this._showResults);
this.resultCollectionView = new SearchResultCollectionView({
collection : this.collection,
isExisting : this.isExisting
});
this.throttledSearch = _.debounce(this.search, 1000, { trailing : true }).bind(this);
},
onRender : function() {
var self = this;
this.$el.addClass(this.className);
this.ui.seriesSearch.keyup(function(e) {
if (_.contains([
9,
16,
17,
18,
19,
20,
33,
34,
35,
36,
37,
38,
39,
40,
91,
92,
93
], e.keyCode)) {
return;
}
self._abortExistingSearch();
self.throttledSearch({
term : self.ui.seriesSearch.val()
});
});
this._clearResults();
if (this.isExisting) {
this.ui.searchBar.hide();
}
},
onShow : function() {
this.ui.seriesSearch.focus();
},
search : function(options) {
var self = this;
this.collection.reset();
if (!options.term || options.term === this.collection.term) {
return Marionette.$.Deferred().resolve();
}
this.searchResult.show(new LoadingView());
this.collection.term = options.term;
this.currentSearchPromise = this.collection.fetch({
data : { term : options.term }
});
this.currentSearchPromise.fail(function() {
self._showError();
});
return this.currentSearchPromise;
},
_onSeriesAdded : function(options) {
if (this.isExisting && options.series.get('path') === this.model.get('folder').path) {
this.close();
}
else if (!this.isExisting) {
this.collection.term = '';
this.collection.reset();
this._clearResults();
this.ui.seriesSearch.val('');
this.ui.seriesSearch.focus();
}
},
_onLoadMore : function() {
var showingAll = this.resultCollectionView.showMore();
this.ui.searchBar.show();
if (showingAll) {
this.ui.loadMore.hide();
}
},
_clearResults : function() {
if (!this.isExisting) {
this.searchResult.show(new EmptyView());
} else {
this.searchResult.close();
}
},
_showResults : function() {
if (!this.isClosed) {
if (this.collection.length === 0) {
this.ui.searchBar.show();
this.searchResult.show(new NotFoundView({ term : this.collection.term }));
} else {
this.searchResult.show(this.resultCollectionView);
if (!this.showingAll && this.isExisting) {
this.ui.loadMore.show();
}
}
}
},
_abortExistingSearch : function() {
if (this.currentSearchPromise && this.currentSearchPromise.readyState > 0 && this.currentSearchPromise.readyState < 4) {
console.log('aborting previous pending search request.');
this.currentSearchPromise.abort();
} else {
this._clearResults();
}
},
_showError : function() {
if (!this.isClosed) {
this.ui.searchBar.show();
this.searchResult.show(new ErrorView({ term : this.collection.term }));
this.collection.term = '';
}
}
});

@ -1,24 +0,0 @@
{{#if folder.path}}
<div class="unmapped-folder-path">
<div class="col-md-12">
{{folder.path}}
</div>
</div>{{/if}}
<div class="x-search-bar">
<div class="input-group input-group-lg add-series-search">
<span class="input-group-addon"><i class="icon-sonarr-search"/></span>
{{#if folder}}
<input type="text" class="form-control x-series-search" value="{{folder.name}}">
{{else}}
<input type="text" class="form-control x-series-search" placeholder="Start typing the name of the movie you want to add ...">
{{/if}}
</div>
</div>
<div class="row">
<div id="search-result" class="result-list col-md-12"/>
</div>
<div class="btn btn-block text-center new-series-loadmore x-load-more" style="display: none;">
<i class="icon-sonarr-load-more"/>
more
</div>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'AddSeries/EmptyViewTemplate'
});

@ -1,3 +0,0 @@
<div class="text-center hint col-md-12">
<span>You can also search by tvdbid using the tvdb: prefixes.</span>
</div>

@ -1,13 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'AddSeries/ErrorViewTemplate',
initialize : function(options) {
this.options = options;
},
templateHelpers : function() {
return this.options;
}
});

@ -1,7 +0,0 @@
<div class="text-center col-md-12">
<h3>
There was an error searching for '{{term}}'.
</h3>
If the series title contains non-alphanumeric characters try removing them, otherwise try your search again later.
</div>

@ -1,51 +0,0 @@
var Marionette = require('marionette');
var AddSeriesView = require('../AddSeriesView');
var UnmappedFolderCollection = require('./UnmappedFolderCollection');
module.exports = Marionette.CompositeView.extend({
itemView : AddSeriesView,
itemViewContainer : '.x-loading-folders',
template : 'AddSeries/Existing/AddExistingSeriesCollectionViewTemplate',
ui : {
loadingFolders : '.x-loading-folders'
},
initialize : function() {
this.collection = new UnmappedFolderCollection();
this.collection.importItems(this.model);
},
showCollection : function() {
this._showAndSearch(0);
},
appendHtml : function(collectionView, itemView, index) {
collectionView.ui.loadingFolders.before(itemView.el);
},
_showAndSearch : function(index) {
var self = this;
var model = this.collection.at(index);
if (model) {
var currentIndex = index;
var folderName = model.get('folder').name;
this.addItemView(model, this.getItemView(), index);
this.children.findByModel(model).search({ term : folderName }).always(function() {
if (!self.isClosed) {
self._showAndSearch(currentIndex + 1);
}
});
}
else {
this.ui.loadingFolders.hide();
}
},
itemViewOptions : {
isExisting : true
}
});

@ -1,5 +0,0 @@
<div class="x-existing-folders">
<div class="loading-folders x-loading-folders">
Loading search results from TheTVDB for your series, this may take a few minutes.
</div>
</div>

@ -1,20 +0,0 @@
var Backbone = require('backbone');
var UnmappedFolderModel = require('./UnmappedFolderModel');
var _ = require('underscore');
module.exports = Backbone.Collection.extend({
model : UnmappedFolderModel,
importItems : function(rootFolderModel) {
this.reset();
var rootFolder = rootFolderModel;
_.each(rootFolderModel.get('unmappedFolders'), function(folder) {
this.push(new UnmappedFolderModel({
rootFolder : rootFolder,
folder : folder
}));
}, this);
}
});

@ -1,3 +0,0 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({});

@ -1,18 +0,0 @@
<dl class="monitor-tooltip-contents">
<dt>All</dt>
<dd>Monitor all episodes except specials</dd>
<dt>Future</dt>
<dd>Monitor episodes that have not aired yet</dd>
<dt>Missing</dt>
<dd>Monitor episodes that do not have files or have not aired yet</dd>
<dt>Existing</dt>
<dd>Monitor episodes that have files or have not aired yet</dd>
<dt>First Season</dt>
<dd>Monitor all episodes of the first season. All other seasons will be ignored</dd>
<dt>Latest Season</dt>
<dd>Monitor all episodes of the latest season and future seasons</dd>
<dt>None</dt>
<dd>No episodes will be monitored.</dd>
{{!--<dt>Latest Season</dt>-->
<dd>Monitor all episodes the latest season only, previous seasons will be ignored</dd>--}}
</dl>

@ -1,13 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'AddSeries/NotFoundViewTemplate',
initialize : function(options) {
this.options = options;
},
templateHelpers : function() {
return this.options;
}
});

@ -1,7 +0,0 @@
<div class="text-center col-md-12">
<h3>
Sorry. We couldn't find any series matching '{{term}}'
</h3>
<a href="https://github.com/NzbDrone/NzbDrone/wiki/FAQ#wiki-why-cant-i-add-a-new-show-to-nzbdrone-its-on-thetvdb">Why can't I find my show?</a>
</div>

@ -1,10 +0,0 @@
var Backbone = require('backbone');
var RootFolderModel = require('./RootFolderModel');
require('../../Mixins/backbone.signalr.mixin');
var RootFolderCollection = Backbone.Collection.extend({
url : window.NzbDrone.ApiRoot + '/rootfolder',
model : RootFolderModel
});
module.exports = new RootFolderCollection();

@ -1,8 +0,0 @@
var Marionette = require('marionette');
var RootFolderItemView = require('./RootFolderItemView');
module.exports = Marionette.CompositeView.extend({
template : 'AddSeries/RootFolders/RootFolderCollectionViewTemplate',
itemViewContainer : '.x-root-folders',
itemView : RootFolderItemView
});

@ -1,13 +0,0 @@
<table class="table table-hover">
<thead>
<tr>
<th class="col-md-10 ">
Path
</th>
<th class="col-md-3">
Free Space
</th>
</tr>
</thead>
<tbody class="x-root-folders"></tbody>
</table>

@ -1,28 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'AddSeries/RootFolders/RootFolderItemViewTemplate',
className : 'recent-folder',
tagName : 'tr',
initialize : function() {
this.listenTo(this.model, 'change', this.render);
},
events : {
'click .x-delete' : 'removeFolder',
'click .x-folder' : 'folderSelected'
},
removeFolder : function() {
var self = this;
this.model.destroy().success(function() {
self.close();
});
},
folderSelected : function() {
this.trigger('folderSelected', this.model);
}
});

@ -1,9 +0,0 @@
<td class="col-md-10 x-folder folder-path">
{{path}}
</td>
<td class="col-md-3 x-folder folder-free-space">
<span>{{Bytes freeSpace}}</span>
</td>
<td class="col-md-1">
<i class="icon-sonarr-delete x-delete"></i>
</td>

@ -1,77 +0,0 @@
var Marionette = require('marionette');
var RootFolderCollectionView = require('./RootFolderCollectionView');
var RootFolderCollection = require('./RootFolderCollection');
var RootFolderModel = require('./RootFolderModel');
var LoadingView = require('../../Shared/LoadingView');
var AsValidatedView = require('../../Mixins/AsValidatedView');
require('../../Mixins/FileBrowser');
var Layout = Marionette.Layout.extend({
template : 'AddSeries/RootFolders/RootFolderLayoutTemplate',
ui : {
pathInput : '.x-path'
},
regions : {
currentDirs : '#current-dirs'
},
events : {
'click .x-add' : '_addFolder',
'keydown .x-path input' : '_keydown'
},
initialize : function() {
this.collection = RootFolderCollection;
this.rootfolderListView = new RootFolderCollectionView({ collection : RootFolderCollection });
this.listenTo(this.rootfolderListView, 'itemview:folderSelected', this._onFolderSelected);
},
onShow : function() {
this.listenTo(RootFolderCollection, 'sync', this._showCurrentDirs);
this.currentDirs.show(new LoadingView());
if (RootFolderCollection.synced) {
this._showCurrentDirs();
}
this.ui.pathInput.fileBrowser();
},
_onFolderSelected : function(options) {
this.trigger('folderSelected', options);
},
_addFolder : function() {
var self = this;
var newDir = new RootFolderModel({
Path : this.ui.pathInput.val()
});
this.bindToModelValidation(newDir);
newDir.save().done(function() {
RootFolderCollection.add(newDir);
self.trigger('folderSelected', { model : newDir });
});
},
_showCurrentDirs : function() {
this.currentDirs.show(this.rootfolderListView);
},
_keydown : function(e) {
if (e.keyCode !== 13) {
return;
}
this._addFolder();
}
});
var Layout = AsValidatedView.apply(Layout);
module.exports = Layout;

@ -1,36 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Select Folder</h3>
</div>
<div class="modal-body root-folders-modal">
<div class="validation-errors"></div>
<div class="alert alert-info">Enter the path that contains some or all of your TV series, you will be able to choose which series you want to import<button type="button" class="close" data-dismiss="alert">×</button></div>
<div class="row">
<div class="form-group">
<div class="col-md-12">
<div class="input-group">
<span class="input-group-addon">&nbsp;<i class="icon-sonarr-folder-open"></i></span>
<input class="form-control x-path" type="text" validation-name="path" placeholder="Enter path to folder that contains your shows">
<span class="input-group-btn"><button class="btn btn-success x-add"><i class="icon-sonarr-ok"/></button></span>
</div>
</div>
</div>
</div>
<div class="row root-folders">
<div class="col-md-12">
{{#if items}}
<h4>Recent Folders</h4>
{{/if}}
<div id="current-dirs" class="root-folders-list"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal">Close</button>
</div>
</div>

@ -1,8 +0,0 @@
var Backbone = require('backbone');
module.exports = Backbone.Model.extend({
urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
defaults : {
freeSpace : 0
}
});

@ -1,11 +0,0 @@
<select class="col-md-4 form-control x-root-folder" validation-name="RootFolderPath">
{{#if this}}
{{#each this}}
<option value="{{id}}">{{path}}</option>
{{/each}}
{{else}}
<option value="">Select Path</option>
{{/if}}
<option value="addNew">Add a different path</option>
</select>

@ -1,29 +0,0 @@
var Marionette = require('marionette');
var SearchResultView = require('./SearchResultView');
module.exports = Marionette.CollectionView.extend({
itemView : SearchResultView,
initialize : function(options) {
this.isExisting = options.isExisting;
this.showing = 1;
},
showAll : function() {
this.showingAll = true;
this.render();
},
showMore : function() {
this.showing += 5;
this.render();
return this.showing >= this.collection.length;
},
appendHtml : function(collectionView, itemView, index) {
if (!this.isExisting || index < this.showing || index === 0) {
collectionView.$el.append(itemView.el);
}
}
});

@ -1,288 +0,0 @@
var _ = require('underscore');
var vent = require('vent');
var AppLayout = require('../AppLayout');
var Backbone = require('backbone');
var Marionette = require('marionette');
var Profiles = require('../Profile/ProfileCollection');
var RootFolders = require('./RootFolders/RootFolderCollection');
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
var SeriesCollection = require('../Series/SeriesCollection');
var Config = require('../Config');
var Messenger = require('../Shared/Messenger');
var AsValidatedView = require('../Mixins/AsValidatedView');
require('jquery.dotdotdot');
var view = Marionette.ItemView.extend({
template : 'AddSeries/SearchResultViewTemplate',
ui : {
profile : '.x-profile',
rootFolder : '.x-root-folder',
seasonFolder : '.x-season-folder',
seriesType : '.x-series-type',
monitor : '.x-monitor',
monitorTooltip : '.x-monitor-tooltip',
addButton : '.x-add',
addSearchButton : '.x-add-search',
overview : '.x-overview'
},
events : {
'click .x-add' : '_addWithoutSearch',
'click .x-add-search' : '_addAndSearch',
'change .x-profile' : '_profileChanged',
'change .x-root-folder' : '_rootFolderChanged',
'change .x-season-folder' : '_seasonFolderChanged',
'change .x-series-type' : '_seriesTypeChanged',
'change .x-monitor' : '_monitorChanged'
},
initialize : function() {
if (!this.model) {
throw 'model is required';
}
this.templateHelpers = {};
this._configureTemplateHelpers();
this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
this.listenTo(this.model, 'change', this.render);
this.listenTo(RootFolders, 'all', this._rootFoldersUpdated);
},
onRender : function() {
var defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
var defaultRoot = Config.getValue(Config.Keys.DefaultRootFolderId);
var useSeasonFolder = Config.getValueBoolean(Config.Keys.UseSeasonFolder, true);
var defaultSeriesType = Config.getValue(Config.Keys.DefaultSeriesType, 'standard');
var defaultMonitorEpisodes = Config.getValue(Config.Keys.MonitorEpisodes, 'missing');
if (Profiles.get(defaultProfile)) {
this.ui.profile.val(defaultProfile);
}
if (RootFolders.get(defaultRoot)) {
this.ui.rootFolder.val(defaultRoot);
}
this.ui.seasonFolder.prop('checked', useSeasonFolder);
this.ui.seriesType.val(defaultSeriesType);
this.ui.monitor.val(defaultMonitorEpisodes);
//TODO: make this work via onRender, FM?
//works with onShow, but stops working after the first render
this.ui.overview.dotdotdot({
height : 120
});
this.templateFunction = Marionette.TemplateCache.get('AddSeries/MonitoringTooltipTemplate');
var content = this.templateFunction();
this.ui.monitorTooltip.popover({
content : content,
html : true,
trigger : 'hover',
title : 'Episode Monitoring Options',
placement : 'right',
container : this.$el
});
},
_configureTemplateHelpers : function() {
var existingSeries = SeriesCollection.where({ tvdbId : this.model.get('tvdbId') });
if (existingSeries.length > 0) {
this.templateHelpers.existing = existingSeries[0].toJSON();
}
this.templateHelpers.profiles = Profiles.toJSON();
if (!this.model.get('isExisting')) {
this.templateHelpers.rootFolders = RootFolders.toJSON();
}
},
_onConfigUpdated : function(options) {
if (options.key === Config.Keys.DefaultProfileId) {
this.ui.profile.val(options.value);
}
else if (options.key === Config.Keys.DefaultRootFolderId) {
this.ui.rootFolder.val(options.value);
}
else if (options.key === Config.Keys.UseSeasonFolder) {
this.ui.seasonFolder.prop('checked', options.value);
}
else if (options.key === Config.Keys.DefaultSeriesType) {
this.ui.seriesType.val(options.value);
}
else if (options.key === Config.Keys.MonitorEpisodes) {
this.ui.monitor.val(options.value);
}
},
_profileChanged : function() {
Config.setValue(Config.Keys.DefaultProfileId, this.ui.profile.val());
},
_seasonFolderChanged : function() {
Config.setValue(Config.Keys.UseSeasonFolder, this.ui.seasonFolder.prop('checked'));
},
_rootFolderChanged : function() {
var rootFolderValue = this.ui.rootFolder.val();
if (rootFolderValue === 'addNew') {
var rootFolderLayout = new RootFolderLayout();
this.listenToOnce(rootFolderLayout, 'folderSelected', this._setRootFolder);
AppLayout.modalRegion.show(rootFolderLayout);
} else {
Config.setValue(Config.Keys.DefaultRootFolderId, rootFolderValue);
}
},
_seriesTypeChanged : function() {
Config.setValue(Config.Keys.DefaultSeriesType, this.ui.seriesType.val());
},
_monitorChanged : function() {
Config.setValue(Config.Keys.MonitorEpisodes, this.ui.monitor.val());
},
_setRootFolder : function(options) {
vent.trigger(vent.Commands.CloseModalCommand);
this.ui.rootFolder.val(options.model.id);
this._rootFolderChanged();
},
_addWithoutSearch : function() {
this._addSeries(false);
},
_addAndSearch : function() {
this._addSeries(true);
},
_addSeries : function(searchForMissingEpisodes) {
var addButton = this.ui.addButton;
var addSearchButton = this.ui.addSearchButton;
addButton.addClass('disabled');
addSearchButton.addClass('disabled');
var profile = this.ui.profile.val();
var rootFolderPath = this.ui.rootFolder.children(':selected').text();
var seriesType = this.ui.seriesType.val();
var seasonFolder = this.ui.seasonFolder.prop('checked');
var options = this._getAddSeriesOptions();
options.searchForMissingEpisodes = searchForMissingEpisodes;
this.model.set({
profileId : profile,
rootFolderPath : rootFolderPath,
seasonFolder : seasonFolder,
seriesType : seriesType,
addOptions : options,
monitored : true
}, { silent : true });
var self = this;
var promise = this.model.save();
if (searchForMissingEpisodes) {
this.ui.addSearchButton.spinForPromise(promise);
}
else {
this.ui.addButton.spinForPromise(promise);
}
promise.always(function() {
addButton.removeClass('disabled');
addSearchButton.removeClass('disabled');
});
promise.done(function() {
SeriesCollection.add(self.model);
self.close();
Messenger.show({
message : 'Added: ' + self.model.get('title'),
actions : {
goToSeries : {
label : 'Go to Series',
action : function() {
Backbone.history.navigate('/series/' + self.model.get('titleSlug'), { trigger : true });
}
}
},
hideAfter : 8,
hideOnNavigate : true
});
vent.trigger(vent.Events.SeriesAdded, { series : self.model });
});
},
_rootFoldersUpdated : function() {
this._configureTemplateHelpers();
this.render();
},
_getAddSeriesOptions : function() {
var monitor = this.ui.monitor.val();
var lastSeason = _.max(this.model.get('seasons'), 'seasonNumber');
var firstSeason = _.min(_.reject(this.model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
this.model.setSeasonPass(firstSeason.seasonNumber);
var options = {
ignoreEpisodesWithFiles : false,
ignoreEpisodesWithoutFiles : false
};
if (monitor === 'all') {
return options;
}
else if (monitor === 'future') {
options.ignoreEpisodesWithFiles = true;
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'latest') {
this.model.setSeasonPass(lastSeason.seasonNumber);
}
else if (monitor === 'first') {
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
this.model.setSeasonMonitored(firstSeason.seasonNumber);
}
else if (monitor === 'missing') {
options.ignoreEpisodesWithFiles = true;
}
else if (monitor === 'existing') {
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'none') {
this.model.setSeasonPass(lastSeason.seasonNumber + 1);
}
return options;
}
});
AsValidatedView.apply(view);
module.exports = view;

@ -1,111 +0,0 @@
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
<div class="row">
<div class="col-md-2">
<a href="{{tvdbUrl}}" target="_blank">
{{poster}}
</a>
</div>
<div class="col-md-10">
<div class="row">
<div class="col-md-12">
<h2 class="series-title">
{{titleWithYear}}
<span class="labels">
<span class="label label-default">{{network}}</span>
{{#unless_eq status compare="continuing"}}
<span class="label label-danger">Ended</span>
{{/unless_eq}}
</span>
</h2>
</div>
</div>
<div class="row new-series-overview x-overview">
<div class="col-md-12 overview-internal">
{{overview}}
</div>
</div>
<div class="row">
{{#unless existing}}
{{#unless path}}
<div class="form-group col-md-4">
<label>Path</label>
{{> RootFolderSelectionPartial rootFolders}}
</div>
{{/unless}}
<div class="form-group col-md-2">
<label>Monitor <i class="icon-sonarr-form-info monitor-tooltip x-monitor-tooltip"></i></label>
<select class="form-control col-md-2 x-monitor">
<option value="all">All</option>
<option value="future">Future</option>
<option value="missing">Missing</option>
<option value="existing">Existing</option>
<option value="first">First Season</option>
<option value="latest">Latest Season</option>
<option value="none">None</option>
</select>
</div>
<div class="form-group col-md-2">
<label>Profile</label>
{{> ProfileSelectionPartial profiles}}
</div>
<div class="form-group col-md-2">
<label>Series Type</label>
{{> SeriesTypeSelectionPartial}}
</div>
<div class="form-group col-md-2">
<label>Season Folders</label>
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" class="x-season-folder"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
{{/unless}}
</div>
<div class="row">
{{#unless existing}}
{{#if title}}
<div class="form-group col-md-2 col-md-offset-10">
{{!--
Uncomment if we need to add even more controls to add series
<label style="visibility: hidden">Add</label>
--}}
<div class="btn-group">
<button class="btn btn-success add x-add" title="Add">
<i class="icon-sonarr-add"></i>
</button>
<button class="btn btn-success add x-add-search" title="Add and Search for missing episodes">
<i class="icon-sonarr-search"></i>
</button>
</div>
</div>
{{else}}
<div class="col-md-2 col-md-offset-10" title="Series requires an English title">
<button class="btn add-series disabled">
Add
</button>
</div>
{{/if}}
{{else}}
<div class="col-md-2 col-md-offset-10">
<a class="btn btn-default" href="{{route}}">
Already Exists
</a>
</div>
{{/unless}}
</div>
</div>
</div>
</div>

@ -1,5 +0,0 @@
<select class="form-control col-md-2 x-series-type" name="seriesType">
<option value="standard">Standard</option>
<option value="daily">Daily</option>
<option value="anime">Anime</option>
</select>

@ -1,13 +0,0 @@
<select class="form-control col-md-2 starting-season x-starting-season">
{{#each this}}
{{#if_eq seasonNumber compare="0"}}
<option value="{{seasonNumber}}">Specials</option>
{{else}}
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
{{/if_eq}}
{{/each}}
<option value="5000000">None</option>
</select>

@ -1,173 +0,0 @@
@import "../Shared/Styles/card.less";
@import "../Shared/Styles/clickable.less";
#add-series-screen {
.existing-series {
.card();
margin : 30px 0;
.unmapped-folder-path {
padding: 20px;
margin-left : 0;
font-weight : 100;
font-size : 25px;
text-align : center;
}
.new-series-loadmore {
font-size : 30px;
font-weight : 300;
padding-top : 10px;
padding-bottom : 10px;
}
}
.new-series {
.search-item {
.card();
margin : 40px 0;
}
}
.add-series-search {
margin-top : 20px;
margin-bottom : 20px;
}
.search-item {
padding-bottom : 20px;
.series-title {
margin-top : 5px;
.labels {
margin-left : 10px;
.label {
font-size : 12px;
vertical-align : middle;
}
}
.year {
font-style : italic;
color : #aaaaaa;
}
}
.new-series-overview {
overflow : hidden;
height : 103px;
.overview-internal {
overflow : hidden;
height : 80px;
}
}
.series-poster {
min-width : 138px;
min-height : 203px;
max-width : 138px;
max-height : 203px;
margin : 10px;
}
a {
color : #343434;
}
a:hover {
text-decoration : none;
}
select {
font-size : 14px;
}
.checkbox {
margin-top : 0;
}
.add {
i {
&:before {
color : #ffffff;
}
}
}
.monitor-tooltip {
margin-left : 5px;
}
}
.loading-folders {
margin : 30px 0;
text-align: center;
}
.hint {
color : #999999;
font-style : italic;
}
.monitor-tooltip-contents {
padding-bottom : 0;
dd {
padding-bottom : 8px;
}
}
}
li.add-new {
.clickable;
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 20px;
color: rgb(51, 51, 51);
white-space: nowrap;
}
li.add-new:hover {
text-decoration: none;
color: rgb(255, 255, 255);
background-color: rgb(0, 129, 194);
}
.root-folders-modal {
overflow : visible;
.root-folders-list {
overflow-y : auto;
max-height : 300px;
i {
.clickable();
}
}
.validation-errors {
display : none;
}
.input-group {
.form-control {
background-color : white;
}
}
.root-folders {
margin-top : 20px;
}
.recent-folder {
.clickable();
}
}

@ -20,7 +20,6 @@
@import "../Shared/FileBrowser/filebrowser";
@import "badges";
@import "../ManualImport/manualimport";
@import "../SeasonPass/seasonpass";
.main-region {
@media (min-width : @screen-lg-min) {

@ -3,22 +3,14 @@ var AppLayout = require('./AppLayout');
var Marionette = require('marionette');
var ActivityLayout = require('./Activity/ActivityLayout');
var SettingsLayout = require('./Settings/SettingsLayout');
var AddSeriesLayout = require('./AddSeries/AddSeriesLayout');
var AddMoviesLayout = require('./AddMovies/AddMoviesLayout');
var WantedLayout = require('./Wanted/WantedLayout');
var CalendarLayout = require('./Calendar/CalendarLayout');
var ReleaseLayout = require('./Release/ReleaseLayout');
var SystemLayout = require('./System/SystemLayout');
var SeasonPassLayout = require('./SeasonPass/SeasonPassLayout');
var SeriesEditorLayout = require('./Series/Editor/SeriesEditorLayout');
var MovieEditorLayout = require('./Movies/Editor/MovieEditorLayout');
module.exports = NzbDroneController.extend({
addSeries : function(action) {
this.setTitle('Add Movie');
this.showMainRegion(new AddSeriesLayout({ action : action }));
},
addMovies : function(action, query) {
this.setTitle("Add Movie");
this.showMainRegion(new AddMoviesLayout({ action : action, query : query }));
@ -54,16 +46,6 @@ module.exports = NzbDroneController.extend({
this.showMainRegion(new SystemLayout({ action : action }));
},
seasonPass : function() {
this.setTitle('Season Pass');
this.showMainRegion(new SeasonPassLayout());
},
seriesEditor : function() {
this.setTitle('Series Editor');
this.showMainRegion(new SeriesEditorLayout());
},
movieEditor : function() {
this.setTitle('Movie Editor');
this.showMainRegion(new MovieEditorLayout());

@ -1,130 +0,0 @@
var Marionette = require('marionette');
var SummaryLayout = require('./Summary/EpisodeSummaryLayout');
var SearchLayout = require('./Search/EpisodeSearchLayout');
var EpisodeHistoryLayout = require('./History/EpisodeHistoryLayout');
var SeriesCollection = require('../Series/SeriesCollection');
var Messenger = require('../Shared/Messenger');
module.exports = Marionette.Layout.extend({
className : 'modal-lg',
template : 'Episode/EpisodeDetailsLayoutTemplate',
regions : {
summary : '#episode-summary',
history : '#episode-history',
search : '#episode-search'
},
ui : {
summary : '.x-episode-summary',
history : '.x-episode-history',
search : '.x-episode-search',
monitored : '.x-episode-monitored'
},
events : {
'click .x-episode-summary' : '_showSummary',
'click .x-episode-history' : '_showHistory',
'click .x-episode-search' : '_showSearch',
'click .x-episode-monitored' : '_toggleMonitored'
},
templateHelpers : {},
initialize : function(options) {
this.templateHelpers.hideSeriesLink = options.hideSeriesLink;
this.series = SeriesCollection.get(this.model.get('seriesId'));
this.templateHelpers.series = this.series.toJSON();
this.openingTab = options.openingTab || 'summary';
this.listenTo(this.model, 'sync', this._setMonitoredState);
},
onShow : function() {
this.searchLayout = new SearchLayout({ model : this.model });
if (this.openingTab === 'search') {
this.searchLayout.startManualSearch = true;
this._showSearch();
}
else {
this._showSummary();
}
this._setMonitoredState();
if (this.series.get('monitored')) {
this.$el.removeClass('series-not-monitored');
}
else {
this.$el.addClass('series-not-monitored');
}
},
_showSummary : function(e) {
if (e) {
e.preventDefault();
}
this.ui.summary.tab('show');
this.summary.show(new SummaryLayout({
model : this.model,
series : this.series
}));
},
_showHistory : function(e) {
if (e) {
e.preventDefault();
}
this.ui.history.tab('show');
this.history.show(new EpisodeHistoryLayout({
model : this.model,
series : this.series
}));
},
_showSearch : function(e) {
if (e) {
e.preventDefault();
}
this.ui.search.tab('show');
this.search.show(this.searchLayout);
},
_toggleMonitored : function() {
if (!this.series.get('monitored')) {
Messenger.show({
message : 'Unable to change monitored state when series is not monitored',
type : 'error'
});
return;
}
var name = 'monitored';
this.model.set(name, !this.model.get(name), { silent : true });
this.ui.monitored.addClass('icon-sonarr-spinner fa-spin');
this.model.save();
},
_setMonitoredState : function() {
this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner');
if (this.model.get('monitored')) {
this.ui.monitored.addClass('icon-sonarr-monitored');
this.ui.monitored.removeClass('icon-sonarr-unmonitored');
} else {
this.ui.monitored.addClass('icon-sonarr-unmonitored');
this.ui.monitored.removeClass('icon-sonarr-monitored');
}
}
});

@ -1,35 +0,0 @@
<div class="modal-content">
<div class="episode-detail-modal">
<div class="modal-header">
<span class="hidden-series-title x-series-title">{{series.title}}</span>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
<i class="icon-sonarr-monitored x-episode-monitored episode-monitored" title="Toggle monitored status" />
{{series.title}} - {{EpisodeNumber}} - {{title}}
</h3>
</div>
<div class="modal-body">
<ul class="nav nav-tabs" id="myTab">
<li><a href="#episode-summary" class="x-episode-summary">Summary</a></li>
<li><a href="#episode-history" class="x-episode-history">History</a></li>
<li><a href="#episode-search" class="x-episode-search">Search</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" id="episode-summary"/>
<div class="tab-pane" id="episode-history"/>
<div class="tab-pane" id="episode-search"/>
</div>
</div>
<div class="modal-footer">
{{#unless hideSeriesLink}}
{{#with series}}
<a href="{{route}}" class="btn btn-default pull-left" data-dismiss="modal">Go to Series</a>
{{/with}}
{{/unless}}
<button class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>

@ -1,35 +0,0 @@
var $ = require('jquery');
var vent = require('vent');
var Marionette = require('marionette');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'episode-actions-cell',
events : {
'click .x-failed' : '_markAsFailed'
},
render : function() {
this.$el.empty();
if (this.model.get('eventType') === 'grabbed') {
this.$el.html('<i class="icon-sonarr-delete x-failed" title="Mark download as failed"></i>');
}
return this;
},
_markAsFailed : function() {
var url = window.NzbDrone.ApiRoot + '/history/failed';
var data = {
id : this.model.get('id')
};
$.ajax({
url : url,
type : 'POST',
data : data
});
}
});

@ -1,28 +0,0 @@
var $ = require('jquery');
var vent = require('vent');
var Marionette = require('marionette');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var HistoryDetailsView = require('../../Activity/History/Details/HistoryDetailsView');
require('bootstrap');
module.exports = NzbDroneCell.extend({
className : 'episode-history-details-cell',
render : function() {
this.$el.empty();
this.$el.html('<i class="icon-sonarr-form-info"></i>');
var html = new HistoryDetailsView({ model : this.model }).render().$el;
this.$el.popover({
content : html,
html : true,
trigger : 'hover',
title : 'Details',
placement : 'left',
container : this.$el
});
return this;
}
});

@ -1,84 +0,0 @@
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var HistoryCollection = require('../../Activity/History/HistoryCollection');
var EventTypeCell = require('../../Cells/EventTypeCell');
var QualityCell = require('../../Cells/QualityCell');
var RelativeDateCell = require('../../Cells/RelativeDateCell');
var EpisodeHistoryActionsCell = require('./EpisodeHistoryActionsCell');
var EpisodeHistoryDetailsCell = require('./EpisodeHistoryDetailsCell');
var NoHistoryView = require('./NoHistoryView');
var LoadingView = require('../../Shared/LoadingView');
module.exports = Marionette.Layout.extend({
template : 'Episode/History/EpisodeHistoryLayoutTemplate',
regions : {
historyTable : '.history-table'
},
columns : [
{
name : 'eventType',
label : '',
cell : EventTypeCell,
cellValue : 'this'
},
{
name : 'sourceTitle',
label : 'Source Title',
cell : 'string'
},
{
name : 'quality',
label : 'Quality',
cell : QualityCell
},
{
name : 'date',
label : 'Date',
cell : RelativeDateCell
},
{
name : 'this',
label : '',
cell : EpisodeHistoryDetailsCell,
sortable : false
},
{
name : 'this',
label : '',
cell : EpisodeHistoryActionsCell,
sortable : false
}
],
initialize : function(options) {
this.model = options.model;
this.series = options.series;
this.collection = new HistoryCollection({
episodeId : this.model.id,
tableName : 'episodeHistory'
});
this.collection.fetch();
this.listenTo(this.collection, 'sync', this._showTable);
},
onRender : function() {
this.historyTable.show(new LoadingView());
},
_showTable : function() {
if (this.collection.any()) {
this.historyTable.show(new Backgrid.Grid({
collection : this.collection,
columns : this.columns,
className : 'table table-hover table-condensed'
}));
}
else {
this.historyTable.show(new NoHistoryView());
}
}
});

@ -1 +0,0 @@
<div class="history-table table-responsive"></div>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Episode/History/NoHistoryViewTemplate'
});

@ -1,3 +0,0 @@
<p class="text-warning">
No history for this episode.
</p>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Episode/Search/ButtonsViewTemplate'
});

@ -1,4 +0,0 @@
<div class="search-buttons">
<button class="btn btn-lg btn-block x-search-auto"><i class="icon-sonarr-search-automatic"/> Automatic Search</button>
<button class="btn btn-lg btn-block btn-primary x-search-manual"><i class="icon-sonarr-search-manual"/> Manual Search</button>
</div>

@ -1,82 +0,0 @@
var vent = require('vent');
var Marionette = require('marionette');
var ButtonsView = require('./ButtonsView');
var ManualSearchLayout = require('./ManualLayout');
var ReleaseCollection = require('../../Release/ReleaseCollection');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var NoResultsView = require('./NoResultsView');
module.exports = Marionette.Layout.extend({
template : 'Episode/Search/EpisodeSearchLayoutTemplate',
regions : {
main : '#episode-search-region'
},
events : {
'click .x-search-auto' : '_searchAuto',
'click .x-search-manual' : '_searchManual',
'click .x-search-back' : '_showButtons'
},
initialize : function() {
this.mainView = new ButtonsView();
this.releaseCollection = new ReleaseCollection();
this.listenTo(this.releaseCollection, 'sync', this._showSearchResults);
},
onShow : function() {
if (this.startManualSearch) {
this._searchManual();
}
else {
this._showMainView();
}
},
_searchAuto : function(e) {
if (e) {
e.preventDefault();
}
CommandController.Execute('episodeSearch', {
episodeIds : [this.model.get('id')]
});
vent.trigger(vent.Commands.CloseModalCommand);
},
_searchManual : function(e) {
if (e) {
e.preventDefault();
}
this.mainView = new LoadingView();
this._showMainView();
this.releaseCollection.fetchEpisodeReleases(this.model.id);
},
_showMainView : function() {
this.main.show(this.mainView);
},
_showButtons : function() {
this.mainView = new ButtonsView();
this._showMainView();
},
_showSearchResults : function() {
if (this.releaseCollection.length === 0) {
this.mainView = new NoResultsView();
}
else {
this.mainView = new ManualSearchLayout({ collection : this.releaseCollection });
}
this._showMainView();
}
});

@ -1 +0,0 @@
<div id="episode-search-region"></div>

@ -1,93 +0,0 @@
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var ReleaseTitleCell = require('../../Cells/ReleaseTitleCell');
var FileSizeCell = require('../../Cells/FileSizeCell');
var QualityCell = require('../../Cells/QualityCell');
var ApprovalStatusCell = require('../../Cells/ApprovalStatusCell');
var DownloadReportCell = require('../../Release/DownloadReportCell');
var AgeCell = require('../../Release/AgeCell');
var ProtocolCell = require('../../Release/ProtocolCell');
var PeersCell = require('../../Release/PeersCell');
var EditionCell = require('../../Cells/EditionCell');
module.exports = Marionette.Layout.extend({
template : 'Episode/Search/ManualLayoutTemplate',
regions : {
grid : '#episode-release-grid'
},
columns : [
{
name : 'protocol',
label : 'Source',
cell : ProtocolCell
},
{
name : 'age',
label : 'Age',
cell : AgeCell
},
{
name : 'title',
label : 'Title',
cell : ReleaseTitleCell
},
{
name : 'edition',
label : 'Edition',
cell : EditionCell,
title : "Edition"
},
{
name : 'indexer',
label : 'Indexer',
cell : Backgrid.StringCell
},
{
name : 'size',
label : 'Size',
cell : FileSizeCell
},
{
name : 'seeders',
label : 'Peers',
cell : PeersCell
},
{
name : 'quality',
label : 'Quality',
cell : QualityCell
},
{
name : 'rejections',
label : '<i class="icon-sonarr-header-rejections" />',
tooltip : 'Rejections',
cell : ApprovalStatusCell,
sortable : true,
sortType : 'fixed',
direction : 'ascending',
title : 'Release Rejected'
},
{
name : 'download',
label : '<i class="icon-sonarr-download" />',
tooltip : 'Auto-Search Prioritization',
cell : DownloadReportCell,
sortable : true,
sortType : 'fixed',
direction : 'ascending'
}
],
onShow : function() {
if (!this.isClosed) {
this.grid.show(new Backgrid.Grid({
row : Backgrid.Row,
columns : this.columns,
collection : this.collection,
className : 'table table-hover'
}));
}
}
});

@ -1,2 +0,0 @@
<div id="episode-release-grid" class="table-responsive"></div>
<button class="btn x-search-back">Back</button>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Episode/Search/NoResultsViewTemplate'
});

@ -1,119 +0,0 @@
var reqres = require('../../reqres');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var EpisodeFileModel = require('../../Series/EpisodeFileModel');
var EpisodeFileCollection = require('../../Series/EpisodeFileCollection');
var FileSizeCell = require('../../Cells/FileSizeCell');
var QualityCell = require('../../Cells/QualityCell');
var DeleteEpisodeFileCell = require('../../Cells/DeleteEpisodeFileCell');
var NoFileView = require('./NoFileView');
var LoadingView = require('../../Shared/LoadingView');
module.exports = Marionette.Layout.extend({
template : 'Episode/Summary/EpisodeSummaryLayoutTemplate',
regions : {
overview : '.episode-overview',
activity : '.episode-file-info'
},
columns : [
{
name : 'path',
label : 'Path',
cell : 'string',
sortable : false
},
{
name : 'size',
label : 'Size',
cell : FileSizeCell,
sortable : false
},
{
name : 'quality',
label : 'Quality',
cell : QualityCell,
sortable : false,
editable : true
},
{
name : 'this',
label : '',
cell : DeleteEpisodeFileCell,
sortable : false
}
],
templateHelpers : {},
initialize : function(options) {
if (!this.model.series) {
this.templateHelpers.series = options.series.toJSON();
}
},
onShow : function() {
if (this.model.get('hasFile')) {
var episodeFileId = this.model.get('episodeFileId');
if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) {
var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, episodeFileId);
this.episodeFileCollection = new EpisodeFileCollection(episodeFile, { seriesId : this.model.get('seriesId') });
this.listenTo(episodeFile, 'destroy', this._episodeFileDeleted);
this._showTable();
}
else {
this.activity.show(new LoadingView());
var self = this;
var newEpisodeFile = new EpisodeFileModel({ id : episodeFileId });
this.episodeFileCollection = new EpisodeFileCollection(newEpisodeFile, { seriesId : this.model.get('seriesId') });
var promise = newEpisodeFile.fetch();
this.listenTo(newEpisodeFile, 'destroy', this._episodeFileDeleted);
promise.done(function() {
self._showTable();
});
}
this.listenTo(this.episodeFileCollection, 'add remove', this._collectionChanged);
}
else {
this._showNoFileView();
}
},
_showTable : function() {
this.activity.show(new Backgrid.Grid({
collection : this.episodeFileCollection,
columns : this.columns,
className : 'table table-bordered',
emptyText : 'Nothing to see here!'
}));
},
_showNoFileView : function() {
this.activity.show(new NoFileView());
},
_collectionChanged : function() {
if (!this.episodeFileCollection.any()) {
this._showNoFileView();
}
else {
this._showTable();
}
},
_episodeFileDeleted : function() {
this.model.set({
episodeFileId : 0,
hasFile : false
});
}
});

@ -1,14 +0,0 @@
<div class="episode-info">
{{#with series}}
{{profile profileId}}
<span class="label label-info">{{network}}</span>
{{/with}}
<span class="label label-info">{{StartTime airDateUtc}}</span>
<span class="label label-info">{{RelativeDate airDateUtc}}</span>
</div>
<div class="episode-overview">
{{overview}}
</div>
<div class="episode-file-info"></div>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Episode/Summary/NoFileViewTemplate'
});

@ -1,3 +0,0 @@
<p class="text-warning">
No file available for this episode.
</p>

@ -1,5 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.CompositeView.extend({
template : 'EpisodeFile/Editor/EmptyViewTemplate'
});

@ -1,5 +0,0 @@
<div class="row">
<div class="col-md-12">
No episode files
</div>
</div>

@ -1,200 +0,0 @@
var _ = require('underscore');
var reqres = require('../../reqres');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var FormatHelpers = require('../../Shared/FormatHelpers');
var SelectAllCell = require('../../Cells/SelectAllCell');
var EpisodeNumberCell = require('../../Series/Details/EpisodeNumberCell');
var SeasonEpisodeNumberCell = require('../../Cells/EpisodeNumberCell');
var EpisodeFilePathCell = require('../../Cells/EpisodeFilePathCell');
var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell');
var RelativeDateCell = require('../../Cells/RelativeDateCell');
var EpisodeCollection = require('../../Series/EpisodeCollection');
var ProfileSchemaCollection = require('../../Settings/Profile/ProfileSchemaCollection');
var QualitySelectView = require('./QualitySelectView');
var EmptyView = require('./EmptyView');
module.exports = Marionette.Layout.extend({
className : 'modal-lg',
template : 'EpisodeFile/Editor/EpisodeFileEditorLayoutTemplate',
regions : {
episodeGrid : '.x-episode-list',
quality : '.x-quality'
},
ui : {
seasonMonitored : '.x-season-monitored'
},
events : {
'click .x-season-monitored' : '_seasonMonitored',
'click .x-delete-files' : '_deleteFiles'
},
initialize : function(options) {
if (!options.series) {
throw 'series is required';
}
if (!options.episodeCollection) {
throw 'episodeCollection is required';
}
var filtered = options.episodeCollection.filter(function(episode) {
return episode.get('episodeFileId') > 0;
});
this.series = options.series;
this.episodeCollection = options.episodeCollection;
this.filteredEpisodes = new EpisodeCollection(filtered);
this.templateHelpers = {};
this.templateHelpers.series = this.series.toJSON();
this._getColumns();
},
onRender : function() {
this._getQualities();
this._showEpisodes();
},
_getColumns : function () {
var episodeCell = {};
if (this.model) {
episodeCell.name = 'episodeNumber';
episodeCell.label = '#';
episodeCell.cell = EpisodeNumberCell;
}
else {
episodeCell.name = 'seasonEpisode';
episodeCell.cellValue = 'this';
episodeCell.label = 'Episode';
episodeCell.cell = SeasonEpisodeNumberCell;
episodeCell.sortValue = this._seasonEpisodeSorter;
}
this.columns = [
{
name : '',
cell : SelectAllCell,
headerCell : 'select-all',
sortable : false
},
episodeCell,
{
name : 'episodeNumber',
label : 'Relative Path',
cell : EpisodeFilePathCell,
sortable : false
},
{
name : 'airDateUtc',
label : 'Air Date',
cell : RelativeDateCell
},
{
name : 'status',
label : 'Quality',
cell : EpisodeStatusCell,
sortable : false
}
];
},
_showEpisodes : function() {
if (this.filteredEpisodes.length === 0) {
this.episodeGrid.show(new EmptyView());
return;
}
this._setInitialSort();
this.episodeGridView = new Backgrid.Grid({
columns : this.columns,
collection : this.filteredEpisodes,
className : 'table table-hover season-grid'
});
this.episodeGrid.show(this.episodeGridView);
},
_setInitialSort : function () {
if (!this.model) {
this.filteredEpisodes.setSorting('seasonEpisode', 1, { sortValue: this._seasonEpisodeSorter });
this.filteredEpisodes.fullCollection.sort();
}
},
_getQualities : function() {
var self = this;
var profileSchemaCollection = new ProfileSchemaCollection();
var promise = profileSchemaCollection.fetch();
promise.done(function() {
var profile = profileSchemaCollection.first();
self.qualitySelectView = new QualitySelectView({ qualities: _.map(profile.get('items'), 'quality') });
self.listenTo(self.qualitySelectView, 'seasonedit:quality', self._changeQuality);
self.quality.show(self.qualitySelectView);
});
},
_changeQuality : function(options) {
var newQuality = {
quality : options.selected,
revision : {
version : 1,
real : 0
}
};
var selected = this._getSelectedEpisodeFileIds();
_.each(selected, function(episodeFileId) {
if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) {
var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, episodeFileId);
episodeFile.set('quality', newQuality);
episodeFile.save();
}
});
},
_deleteFiles : function() {
if (!window.confirm('Are you sure you want to delete the episode files for the selected episodes?')) {
return;
}
var selected = this._getSelectedEpisodeFileIds();
_.each(selected, function(episodeFileId) {
if (reqres.hasHandler(reqres.Requests.GetEpisodeFileById)) {
var episodeFile = reqres.request(reqres.Requests.GetEpisodeFileById, episodeFileId);
episodeFile.destroy();
}
});
_.each(this.episodeGridView.getSelectedModels(), function(episode) {
this.episodeGridView.removeRow(episode);
}, this);
},
_getSelectedEpisodeFileIds: function () {
return _.uniq(_.map(this.episodeGridView.getSelectedModels(), function (episode) {
return episode.get('episodeFileId');
}));
},
_seasonEpisodeSorter : function (model, attr) {
var seasonNumber = FormatHelpers.pad(model.get('seasonNumber'), 4, 0);
var episodeNumber = FormatHelpers.pad(model.get('episodeNumber'), 4, 0);
return seasonNumber + episodeNumber;
}
});

@ -1,28 +0,0 @@
<div class="modal-content">
<div class="edit-season-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
{{#if seasonNumber}}
{{#if_eq seasonNumber compare="0"}}
{{series.title}} - Specials
{{else}}
{{series.title}} - Season {{seasonNumber}}
{{/if_eq}}
{{else}}
{{series.title}}
{{/if}}
</h3>
</div>
<div class="modal-body">
<div class="x-episode-list"></div>
<div class="x-quality"></div>
</div>
<div class="modal-footer">
<button class="btn btn-danger x-delete-files">Delete Files</button>
<button class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>

@ -1,35 +0,0 @@
var _ = require('underscore');
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'EpisodeFile/Editor/QualitySelectViewTemplate',
ui : {
select : '.x-select'
},
events : {
'change .x-select' : '_changeSelect'
},
initialize : function (options) {
this.qualities = options.qualities;
this.templateHelpers = {
qualities : this.qualities
};
},
_changeSelect : function () {
var value = this.ui.select.val();
if (value === 'choose') {
return;
}
var quality = _.find(this.qualities, { 'id': parseInt(value) });
this.trigger('seasonedit:quality', { selected : quality });
this.ui.select.val('choose');
}
});

@ -1,10 +0,0 @@
<div class="row">
<div class="form-group col-md-3 col-md-offset-9">
<select class="form-control x-select">
<option value="choose">Select quality</option>
{{#eachReverse qualities}}
<option value="{{id}}">{{name}}</option>
{{/eachReverse}}
</select>
</div>
</div>

@ -1,46 +0,0 @@
var _ = require('underscore');
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectEpisodeLayout = require('../Episode/SelectEpisodeLayout');
module.exports = NzbDroneCell.extend({
className : 'episodes-cell',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
var episodes = this.model.get('episodes');
if (episodes)
{
var episodeNumbers = _.map(episodes, 'episodeNumber');
this.$el.html(episodeNumbers.join(', '));
}
return this;
},
_onClick : function () {
var series = this.model.get('series');
var seasonNumber = this.model.get('seasonNumber');
if (series === undefined || seasonNumber === undefined) {
return;
}
var view = new SelectEpisodeLayout({ series: series, seasonNumber: seasonNumber });
this.listenTo(view, 'manualimport:selected:episodes', this._setEpisodes);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setEpisodes : function (e) {
this.model.set('episodes', e.episodes);
}
});

@ -1,47 +0,0 @@
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectSeasonLayout = require('../Season/SelectSeasonLayout');
module.exports = NzbDroneCell.extend({
className : 'season-cell',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
if (this.model.has('seasonNumber')) {
this.$el.html(this.model.get('seasonNumber'));
}
this.delegateEvents();
return this;
},
_onClick : function () {
var series = this.model.get('series');
if (!series) {
return;
}
var view = new SelectSeasonLayout({ seasons: series.seasons });
this.listenTo(view, 'manualimport:selected:season', this._setSeason);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setSeason : function (e) {
if (this.model.has('seasonNumber') && e.seasonNumber === this.model.get('seasonNumber')) {
return;
}
this.model.set({
seasonNumber : e.seasonNumber,
episodes : []
});
}
});

@ -1,45 +0,0 @@
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectSeriesLayout = require('../Series/SelectSeriesLayout');
module.exports = NzbDroneCell.extend({
className : 'series-title-cell editable',
events : {
'click' : '_onClick'
},
render : function() {
this.$el.empty();
var series = this.model.get('series');
if (series)
{
this.$el.html(series.title);
}
this.delegateEvents();
return this;
},
_onClick : function () {
var view = new SelectSeriesLayout();
this.listenTo(view, 'manualimport:selected:series', this._setSeries);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setSeries : function (e) {
if (this.model.has('series') && e.model.id === this.model.get('series').id) {
return;
}
this.model.set({
series : e.model.toJSON(),
seasonNumber : undefined,
episodes : []
});
}
});

@ -1,81 +0,0 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var EpisodeCollection = require('../../Series/EpisodeCollection');
var LoadingView = require('../../Shared/LoadingView');
var SelectAllCell = require('../../Cells/SelectAllCell');
var EpisodeNumberCell = require('../../Series/Details/EpisodeNumberCell');
var RelativeDateCell = require('../../Cells/RelativeDateCell');
var SelectEpisodeRow = require('./SelectEpisodeRow');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Episode/SelectEpisodeLayoutTemplate',
regions : {
episodes : '.x-episodes'
},
events : {
'click .x-select' : '_selectEpisodes'
},
columns : [
{
name : '',
cell : SelectAllCell,
headerCell : 'select-all',
sortable : false
},
{
name : 'episodeNumber',
label : '#',
cell : EpisodeNumberCell
},
{
name : 'title',
label : 'Title',
hideSeriesLink : true,
cell : 'string',
sortable : false
},
{
name : 'airDateUtc',
label : 'Air Date',
cell : RelativeDateCell
}
],
initialize : function(options) {
this.series = options.series;
this.seasonNumber = options.seasonNumber;
},
onRender : function() {
this.episodes.show(new LoadingView());
this.episodeCollection = new EpisodeCollection({ seriesId : this.series.id });
this.episodeCollection.fetch();
this.listenToOnce(this.episodeCollection, 'sync', function () {
this.episodeView = new Backgrid.Grid({
columns : this.columns,
collection : this.episodeCollection.bySeason(this.seasonNumber),
className : 'table table-hover season-grid',
row : SelectEpisodeRow
});
this.episodes.show(this.episodeView);
});
},
_selectEpisodes : function () {
var episodes = _.map(this.episodeView.getSelectedModels(), function (episode) {
return episode.toJSON();
});
this.trigger('manualimport:selected:episodes', { episodes: episodes });
vent.trigger(vent.Commands.CloseModal2Command);
}
});

@ -1,21 +0,0 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Episode(s)
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12 x-episodes"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
<button class="btn btn-success x-select" data-dismiss="modal">Select Episodes</button>
</div>
</div>
</div>

@ -1,20 +0,0 @@
var Backgrid = require('backgrid');
module.exports = Backgrid.Row.extend({
className : 'select-episode-row',
events : {
'click' : '_toggle'
},
_toggle : function(e) {
if (e.target.type === 'checkbox') {
return;
}
var checked = this.$el.find('.select-row-cell :checkbox').prop('checked');
this.model.trigger('backgrid:select', this.model, !checked);
}
});

@ -9,9 +9,6 @@ var LoadingView = require('../Shared/LoadingView');
var ManualImportRow = require('./ManualImportRow');
var SelectAllCell = require('../Cells/SelectAllCell');
var PathCell = require('./Cells/PathCell');
var SeriesCell = require('./Cells/SeriesCell');
var SeasonCell = require('./Cells/SeasonCell');
var EpisodesCell = require('./Cells/EpisodesCell');
var QualityCell = require('./Cells/QualityCell');
var FileSizeCell = require('../Cells/FileSizeCell');
var ApprovalStatusCell = require('../Cells/ApprovalStatusCell');
@ -55,24 +52,6 @@ module.exports = Marionette.Layout.extend({
cell : MovieCell,
sortable : true
},
// {
// name : 'series',
// label : 'Series',
// cell : SeriesCell,
// sortable : true
// },
// {
// name : 'seasonNumber',
// label : 'Season',
// cell : SeasonCell,
// sortable : true
// },
// {
// name : 'episodes',
// label : 'Episode(s)',
// cell : EpisodesCell,
// sortable : false
// },
{
name : 'quality',
label : 'Quality',
@ -190,30 +169,6 @@ module.exports = Marionette.Layout.extend({
return;
}
// if (_.any(selected, function (model) {
// return !model.has('series');
// })) {
// this._showErrorMessage('Series must be chosen for each selected file');
// return;
// }
// if (_.any(selected, function (model) {
// return !model.has('seasonNumber');
// })) {
// this._showErrorMessage('Season must be chosen for each selected file');
// return;
// }
// if (_.any(selected, function (model) {
// return !model.has('episodes') || model.get('episodes').length === 0;
// })) {
// this._showErrorMessage('One or more episodes must be chosen for each selected file');
// return;
// }
var importMode = this.ui.importMode.val();
CommandController.Execute('manualImport', {
@ -222,8 +177,6 @@ module.exports = Marionette.Layout.extend({
return {
path : file.get('path'),
movieId : file.get('movie').id,
// seriesId : file.get('series').id,
// episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'),
downloadId : file.get('downloadId')
};

@ -34,6 +34,5 @@ module.exports = Backgrid.Row.extend({
_setClasses : function () {
this.$el.toggleClass('has-movie', this.model.has('movie'));
//this.$el.toggleClass('has-season', this.model.has('seasonNumber'));
}
});

@ -1,28 +0,0 @@
var vent = require('vent');
var Marionette = require('marionette');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Season/SelectSeasonLayoutTemplate',
events : {
'change .x-select-season' : '_selectSeason'
},
initialize : function(options) {
this.templateHelpers = {
seasons : options.seasons
};
},
_selectSeason : function (e) {
var seasonNumber = parseInt(e.target.value, 10);
if (seasonNumber === -1) {
return;
}
this.trigger('manualimport:selected:season', { seasonNumber: seasonNumber });
vent.trigger(vent.Commands.CloseModal2Command);
}
});

@ -1,29 +0,0 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Season
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="form-group col-md-4 col-md-offset-4">
<select class="form-control x-select-season">
<option value="-1">Select Season</option>
{{#each seasons}}
<option value="{{seasonNumber}}">Season {{seasonNumber}}</option>
{{/each}}
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>

@ -1,101 +0,0 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var SeriesCollection = require('../../Series/SeriesCollection');
var SelectRow = require('./SelectSeriesRow');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Series/SelectSeriesLayoutTemplate',
regions : {
series : '.x-series'
},
ui : {
filter : '.x-filter'
},
columns : [
{
name : 'title',
label : 'Title',
cell : 'String',
sortValue : 'sortTitle'
}
],
initialize : function() {
this.seriesCollection = SeriesCollection.clone();
this._setModelCollection();
this.listenTo(this.seriesCollection, 'row:selected', this._onSelected);
this.listenTo(this, 'modal:afterShow', this._setFocus);
},
onRender : function() {
this.seriesView = new Backgrid.Grid({
columns : this.columns,
collection : this.seriesCollection,
className : 'table table-hover season-grid',
row : SelectRow
});
this.series.show(this.seriesView);
this._setupFilter();
},
_setupFilter : function () {
var self = this;
//TODO: This should be a mixin (same as Add Series searching)
this.ui.filter.keyup(function(e) {
if (_.contains([
9,
16,
17,
18,
19,
20,
33,
34,
35,
36,
37,
38,
39,
40,
91,
92,
93
], e.keyCode)) {
return;
}
self._filter(self.ui.filter.val());
});
},
_filter : function (term) {
this.seriesCollection.setFilter(['title', term, 'contains']);
this._setModelCollection();
},
_onSelected : function (e) {
this.trigger('manualimport:selected:series', { model: e.model });
vent.trigger(vent.Commands.CloseModal2Command);
},
_setFocus : function () {
this.ui.filter.focus();
},
_setModelCollection: function () {
var self = this;
_.each(this.seriesCollection.models, function (model) {
model.collection = self.seriesCollection;
});
}
});

@ -1,30 +0,0 @@
<div class="modal-content">
<div class="manual-import-modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>
Manual Import - Select Series
</h3>
</div>
<div class="modal-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input type="text" class="form-control x-filter" placeholder="Filter series" />
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 x-series"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>

@ -1,13 +0,0 @@
var Backgrid = require('backgrid');
module.exports = Backgrid.Row.extend({
className : 'select-row select-series-row',
events : {
'click' : '_onClick'
},
_onClick : function() {
this.model.collection.trigger('row:selected', { model: this.model });
}
});

@ -4,20 +4,6 @@ var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Summary/ManualImportSummaryViewTemplate',
// initialize : function (options) {
// var episodes = _.map(options.episodes, function (episode) {
// return episode.toJSON();
// });
// this.templateHelpers = {
// file : options.file,
// series : options.series,
// season : options.season,
// episodes : episodes,
// quality : options.quality
// };
// }
initialize : function (options) {
this.templateHelpers = {

@ -8,7 +8,7 @@ var MoviesCollection = require('../MoviesCollection');
var InfoView = require('./InfoView');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
// var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
var HistoryLayout = require('../History/MovieHistoryLayout');
var SearchLayout = require('../Search/MovieSearchLayout');
var AllFilesLayout = require("../Files/AllFilesLayout");
@ -251,14 +251,14 @@ module.exports = Marionette.Layout.extend({
this._showInfo();
},
_openEpisodeFileEditor : function() {
var view = new EpisodeFileEditorLayout({
movies : this.model,
episodeCollection : this.episodeCollection
});
// _openEpisodeFileEditor : function() {
// var view = new EpisodeFileEditorLayout({
// movies : this.model,
// episodeCollection : this.episodeCollection
// });
vent.trigger(vent.Commands.OpenModalCommand, view);
},
// vent.trigger(vent.Commands.OpenModalCommand, view);
// },
_updateImages : function () {
var poster = this._getImage('poster');

@ -4,5 +4,5 @@ var PosterItemView = require('./SeriesPostersItemView');
module.exports = Marionette.CompositeView.extend({
itemView : PosterItemView,
itemViewContainer : '#x-series-posters',
template : 'Series/Index/Posters/SeriesPostersCollectionViewTemplate'
template : 'Movies/Index/Posters/SeriesPostersCollectionViewTemplate'
});

@ -4,8 +4,6 @@ var Controller = require('./Controller');
module.exports = Marionette.AppRouter.extend({
controller : new Controller(),
appRoutes : {
'addseries' : 'addSeries',
'addseries/:action(/:query)' : 'addSeries',
'addmovies' : 'addMovies',
'addmovies/:action(/:query)' : 'addMovies',
'calendar' : 'calendar',
@ -20,7 +18,6 @@ module.exports = Marionette.AppRouter.extend({
'rss' : 'rss',
'system' : 'system',
'system/:action' : 'system',
'seasonpass' : 'seasonPass',
'movieeditor' : 'movieEditor',
':whatever' : 'showNotFound'
}

@ -1,139 +0,0 @@
var _ = require('underscore');
var $ = require('jquery');
var Marionette = require('marionette');
var vent = require('vent');
var RootFolders = require('../AddSeries/RootFolders/RootFolderCollection');
module.exports = Marionette.ItemView.extend({
template : 'SeasonPass/SeasonPassFooterViewTemplate',
ui : {
seriesMonitored : '.x-series-monitored',
monitor : '.x-monitor',
selectedCount : '.x-selected-count',
container : '.series-editor-footer',
actions : '.x-action',
indicator : '.x-indicator',
indicatorIcon : '.x-indicator-icon'
},
events : {
'click .x-update' : '_update'
},
initialize : function(options) {
this.seriesCollection = options.collection;
RootFolders.fetch().done(function() {
RootFolders.synced = true;
});
this.editorGrid = options.editorGrid;
this.listenTo(this.seriesCollection, 'backgrid:selected', this._updateInfo);
},
onRender : function() {
this._updateInfo();
},
_update : function() {
var self = this;
var selected = this.editorGrid.getSelectedModels();
var seriesMonitored = this.ui.seriesMonitored.val();
var monitoringOptions;
_.each(selected, function(model) {
if (seriesMonitored === 'true') {
model.set('monitored', true);
} else if (seriesMonitored === 'false') {
model.set('monitored', false);
}
monitoringOptions = self._getMonitoringOptions(model);
model.set('addOptions', monitoringOptions);
});
var promise = $.ajax({
url : window.NzbDrone.ApiRoot + '/seasonpass',
type : 'POST',
data : JSON.stringify({
series : _.map(selected, function (model) {
return model.toJSON();
}),
monitoringOptions : monitoringOptions
})
});
this.ui.indicator.show();
promise.always(function () {
self.ui.indicator.hide();
});
promise.done(function () {
self.seriesCollection.trigger('seasonpass:saved');
});
},
_updateInfo : function() {
var selected = this.editorGrid.getSelectedModels();
var selectedCount = selected.length;
this.ui.selectedCount.html('{0} series selected'.format(selectedCount));
if (selectedCount === 0) {
this.ui.actions.attr('disabled', 'disabled');
} else {
this.ui.actions.removeAttr('disabled');
}
},
_getMonitoringOptions : function(model) {
var monitor = this.ui.monitor.val();
var lastSeason = _.max(model.get('seasons'), 'seasonNumber');
var firstSeason = _.min(_.reject(model.get('seasons'), { seasonNumber : 0 }), 'seasonNumber');
if (monitor === 'noChange') {
return null;
}
model.setSeasonPass(firstSeason.seasonNumber);
var options = {
ignoreEpisodesWithFiles : false,
ignoreEpisodesWithoutFiles : false
};
if (monitor === 'all') {
return options;
}
else if (monitor === 'future') {
options.ignoreEpisodesWithFiles = true;
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'latest') {
model.setSeasonPass(lastSeason.seasonNumber);
}
else if (monitor === 'first') {
model.setSeasonPass(lastSeason.seasonNumber + 1);
model.setSeasonMonitored(firstSeason.seasonNumber);
}
else if (monitor === 'missing') {
options.ignoreEpisodesWithFiles = true;
}
else if (monitor === 'existing') {
options.ignoreEpisodesWithoutFiles = true;
}
else if (monitor === 'none') {
model.setSeasonPass(lastSeason.seasonNumber + 1);
}
return options;
}
});

@ -1,36 +0,0 @@
<div class="series-editor-footer">
<div class="row">
<div class="form-group col-md-2">
<label>Monitor series</label>
<select class="form-control x-action x-series-monitored">
<option value="noChange">No change</option>
<option value="true">Monitored</option>
<option value="false">Unmonitored</option>
</select>
</div>
<div class="form-group col-md-2">
<label>Monitor episodes</label>
<select class="form-control x-action x-monitor">
<option value="noChange">No change</option>
<option value="all">All</option>
<option value="future">Future</option>
<option value="missing">Missing</option>
<option value="existing">Existing</option>
<option value="first">First Season</option>
<option value="latest">Latest Season</option>
<option value="none">None</option>
</select>
</div>
<div class="form-group col-md-3 actions">
<label class="x-selected-count">0 series selected</label>
<div>
<button class="btn btn-primary x-action x-update">Update Selected Series</button>
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
</div>
</div>
</div>
</div>

@ -1,152 +0,0 @@
var _ = require('underscore');
var vent = require('vent');
var Backgrid = require('backgrid');
var Marionette = require('marionette');
var EmptyView = require('../Series/Index/EmptyView');
var SeriesCollection = require('../Series/SeriesCollection');
var ToolbarLayout = require('../Shared/Toolbar/ToolbarLayout');
var FooterView = require('./SeasonPassFooterView');
var SelectAllCell = require('../Cells/SelectAllCell');
var SeriesStatusCell = require('../Cells/SeriesStatusCell');
var SeriesTitleCell = require('../Cells/SeriesTitleCell');
var SeriesMonitoredCell = require('../Cells/ToggleCell');
var SeasonsCell = require('./SeasonsCell');
require('../Mixins/backbone.signalr.mixin');
module.exports = Marionette.Layout.extend({
template : 'SeasonPass/SeasonPassLayoutTemplate',
regions : {
toolbar : '#x-toolbar',
series : '#x-series'
},
columns : [
{
name : '',
cell : SelectAllCell,
headerCell : 'select-all',
sortable : false
},
{
name : 'statusWeight',
label : '',
cell : SeriesStatusCell
},
{
name : 'title',
label : 'Title',
cell : SeriesTitleCell,
cellValue : 'this'
},
{
name : 'monitored',
label : '',
cell : SeriesMonitoredCell,
trueClass : 'icon-sonarr-monitored',
falseClass : 'icon-sonarr-unmonitored',
tooltip : 'Toggle series monitored status',
sortable : false
},
{
name : 'seasons',
label : 'Seasons',
cell : SeasonsCell,
cellValue : 'this'
}
],
initialize : function() {
this.seriesCollection = SeriesCollection.clone();
this.seriesCollection.shadowCollection.bindSignalR();
// this.listenTo(this.seriesCollection, 'sync', this.render);
this.listenTo(this.seriesCollection, 'seasonpass:saved', this.render);
this.filteringOptions = {
type : 'radio',
storeState : true,
menuKey : 'seasonpass.filterMode',
defaultAction : 'all',
items : [
{
key : 'all',
title : '',
tooltip : 'All',
icon : 'icon-sonarr-all',
callback : this._setFilter
},
{
key : 'monitored',
title : '',
tooltip : 'Monitored Only',
icon : 'icon-sonarr-monitored',
callback : this._setFilter
},
{
key : 'continuing',
title : '',
tooltip : 'Continuing Only',
icon : 'icon-sonarr-series-continuing',
callback : this._setFilter
},
{
key : 'ended',
title : '',
tooltip : 'Ended Only',
icon : 'icon-sonarr-series-ended',
callback : this._setFilter
}
]
};
},
onRender : function() {
this._showTable();
this._showToolbar();
this._showFooter();
},
onClose : function() {
vent.trigger(vent.Commands.CloseControlPanelCommand);
},
_showToolbar : function() {
this.toolbar.show(new ToolbarLayout({
right : [this.filteringOptions],
context : this
}));
},
_showTable : function() {
if (this.seriesCollection.shadowCollection.length === 0) {
this.series.show(new EmptyView());
this.toolbar.close();
return;
}
this.columns[0].sortedCollection = this.seriesCollection;
this.editorGrid = new Backgrid.Grid({
collection : this.seriesCollection,
columns : this.columns,
className : 'table table-hover'
});
this.series.show(this.editorGrid);
this._showFooter();
},
_showFooter : function() {
vent.trigger(vent.Commands.OpenControlPanelCommand, new FooterView({
editorGrid : this.editorGrid,
collection : this.seriesCollection
}));
},
_setFilter : function(buttonContext) {
var mode = buttonContext.model.get('key');
this.seriesCollection.setFilterMode(mode);
}
});

@ -1,13 +0,0 @@
<div id="x-toolbar"></div>
<div class="row">
<div class="col-md-12">
<div class="alert alert-info">Season Pass allows you to quickly change the monitored status of seasons for all your series in one place</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div id="x-series"></div>
</div>
</div>

@ -1,26 +0,0 @@
var _ = require('underscore');
var TemplatedCell = require('../Cells/TemplatedCell');
//require('../Handlebars/Helpers/Numbers');
module.exports = TemplatedCell.extend({
className : 'seasons-cell',
template : 'SeasonPass/SeasonsCellTemplate',
events : {
'click .x-season-monitored' : '_toggleSeasonMonitored'
},
_toggleSeasonMonitored : function(e) {
var target = this.$(e.target).closest('.x-season-monitored');
var seasonNumber = parseInt(this.$(target).data('season-number'), 10);
var icon = this.$(target).children('.x-season-monitored-icon');
this.model.setSeasonMonitored(seasonNumber);
//TODO: unbounce the save so we don't multiple to the server at the same time
var savePromise = this.model.save();
icon.spinForPromise(savePromise);
savePromise.always(this.render.bind(this));
}
});

@ -1,37 +0,0 @@
{{#each seasons}}
{{debug}}
{{#if_eq statistics.totalEpisodeCount compare=0}}
<span class="season season-unaired">
{{else}}
{{#if_eq statistics.percentOfEpisodes compare=100}}
<span class="season season-all">
{{else}}
<span class="season season-partial">
{{/if_eq}}
{{/if_eq}}
<span class="label">
<span class="x-season-monitored season-monitored" title="Toggle season monitored status" data-season-number="{{seasonNumber}}">
<i class="x-season-monitored-icon {{#if monitored}}icon-sonarr-monitored{{else}}icon-sonarr-unmonitored{{/if}}"/>
</span>
{{#if_eq seasonNumber compare="0"}}
<span class="season-number">Specials</span>
{{else}}
<span class="season-number">S{{Pad2 seasonNumber}}</span>
{{/if_eq}}
</span><span class="label">
{{#with statistics}}
{{#if_eq totalEpisodeCount compare=0}}
<span class="season-status" title="No aired episodes">&nbsp;</span>
{{else}}
{{#if_eq percentOfEpisodes compare=100}}
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
{{else}}
<span class="season-status" title="{{episodeFileCount}}/{{totalEpisodeCount}} episodes downloaded">{{episodeFileCount}}/{{totalEpisodeCount}}</span>
{{/if_eq}}
{{/if_eq}}
{{else}}
<span class="season-status" title="No aired episodes">&nbsp;</span>
{{/with}}
</span>
</span>
{{/each}}

@ -1,54 +0,0 @@
@import "../Content/badges.less";
@import "../Shared/Styles/clickable.less";
.season {
display : inline-block;
margin-bottom : 4px;
.label {
.badge-inverse();
display : inline-block;
padding : 4px;
font-size : 14px;
height : 25px;
}
.label:first-child {
border-right : 0;
border-top-right-radius : 0.0em;
border-bottom-right-radius : 0.0em;
color : #777;
background-color : #eee;
}
.label:last-child {
border-left : 0;
border-top-left-radius : 0.0em;
border-bottom-left-radius : 0.0em;
color : #999;
background-color : #f7f7f7;
}
&.season-all .label:last-child {
background-color : #e0ffe0;
}
.season-monitored {
width : 16px;
i {
.clickable();
}
}
.season-number {
font-size : 12px;
}
.season-status {
display : inline-block;
vertical-align : baseline !important;
}
}

@ -1,50 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>Delete {{title}}</h3>
</div>
<div class="modal-body delete-series-modal">
<div class="row">
<div class="col-sm-3 hidden-xs">
{{poster}}
</div>
<div class="col-sm-9">
<div class="form-horizontal">
<h3 class="path">{{path}}</h3>
<div class="form-group">
<label class="col-sm-4 control-label">Delete all files</label>
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" class="x-delete-files"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn slide-button btn-danger"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Do you want to delete all files from disk?"/>
<i class="icon-sonarr-form-warning" title="This option is irreversible, use with extreme caution"/>
</span>
</div>
</div>
</div>
<div class="col-md-offset-1 col-md-5 delete-files-info x-delete-files-info">
{{episodeFileCount}} episode files will be deleted
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
<button class="btn" data-dismiss="modal">Cancel</button>
<button class="btn btn-danger x-confirm-delete">Delete</button>
</div>
</div>

@ -1,41 +0,0 @@
var vent = require('vent');
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Series/Delete/DeleteSeriesTemplate',
events : {
'click .x-confirm-delete' : 'removeSeries',
'change .x-delete-files' : 'changeDeletedFiles'
},
ui : {
deleteFiles : '.x-delete-files',
deleteFilesInfo : '.x-delete-files-info',
indicator : '.x-indicator'
},
removeSeries : function() {
var self = this;
var deleteFiles = this.ui.deleteFiles.prop('checked');
this.ui.indicator.show();
this.model.destroy({
data : { 'deleteFiles' : deleteFiles },
wait : true
}).done(function() {
vent.trigger(vent.Events.SeriesDeleted, { series : self.model });
vent.trigger(vent.Commands.CloseModalCommand);
});
},
changeDeletedFiles : function() {
var deleteFiles = this.ui.deleteFiles.prop('checked');
if (deleteFiles) {
this.ui.deleteFilesInfo.show();
} else {
this.ui.deleteFilesInfo.hide();
}
}
});

@ -1,47 +0,0 @@
var Marionette = require('marionette');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var reqres = require('../../reqres');
var SeriesCollection = require('../SeriesCollection');
module.exports = NzbDroneCell.extend({
className : 'episode-number-cell',
template : 'Series/Details/EpisodeNumberCellTemplate',
render : function() {
this.$el.empty();
this.$el.html(this.model.get('episodeNumber'));
var series = SeriesCollection.get(this.model.get('seriesId'));
if (series.get('seriesType') === 'anime' && this.model.has('absoluteEpisodeNumber')) {
this.$el.html('{0} ({1})'.format(this.model.get('episodeNumber'), this.model.get('absoluteEpisodeNumber')));
}
var alternateTitles = [];
if (reqres.hasHandler(reqres.Requests.GetAlternateNameBySeasonNumber)) {
alternateTitles = reqres.request(reqres.Requests.GetAlternateNameBySeasonNumber, this.model.get('seriesId'), this.model.get('seasonNumber'), this.model.get('sceneSeasonNumber'));
}
if (this.model.get('sceneSeasonNumber') > 0 || this.model.get('sceneEpisodeNumber') > 0 || this.model.has('sceneAbsoluteEpisodeNumber') || alternateTitles.length > 0) {
this.templateFunction = Marionette.TemplateCache.get(this.template);
var json = this.model.toJSON();
json.alternateTitles = alternateTitles;
var html = this.templateFunction(json);
this.$el.popover({
content : html,
html : true,
trigger : 'hover',
title : 'Scene Information',
placement : 'right',
container : this.$el
});
}
this.delegateEvents();
return this;
}
});

@ -1,39 +0,0 @@
<div class="scene-info">
{{#if sceneSeasonNumber}}
<div class="row">
<div class="key">Season</div>
<div class="value">{{sceneSeasonNumber}}</div>
</div>
{{/if}}
{{#if sceneEpisodeNumber}}
<div class="row">
<div class="key">Episode</div>
<div class="value">{{sceneEpisodeNumber}}</div>
</div>
{{/if}}
{{#if sceneAbsoluteEpisodeNumber}}
<div class="row">
<div class="key">Absolute</div>
<div class="value">{{sceneAbsoluteEpisodeNumber}}</div>
</div>
{{/if}}
{{#if alternateTitles}}
<div class="row">
{{#if_gt alternateTitles.length compare="1"}}
<div class="key">Titles</div>
{{else}}
<div class="key">Title</div>
{{/if_gt}}
<div class="value">
<ul>
{{#each alternateTitles}}
<li>{{title}}</li>
{{/each}}
</ul>
</div>
</div>
{{/if}}
</div>

@ -1,21 +0,0 @@
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SeriesCollection = require('../SeriesCollection');
module.exports = NzbDroneCell.extend({
className : 'episode-warning-cell',
render : function() {
this.$el.empty();
if (this.model.get('unverifiedSceneNumbering')) {
this.$el.html('<i class="icon-sonarr-form-warning" title="Scene number hasn\'t been verified yet."></i>');
}
else if (SeriesCollection.get(this.model.get('seriesId')).get('seriesType') === 'anime' && this.model.get('seasonNumber') > 0 && !this.model.has('absoluteEpisodeNumber')) {
this.$el.html('<i class="icon-sonarr-form-warning" title="Episode does not have an absolute episode number"></i>');
}
this.delegateEvents();
return this;
}
});

@ -1,18 +0,0 @@
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'Series/Details/InfoViewTemplate',
initialize : function(options) {
this.episodeFileCollection = options.episodeFileCollection;
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.episodeFileCollection, 'sync', this.render);
},
templateHelpers : function() {
return {
fileCount : this.episodeFileCollection.length
};
}
});

@ -1,73 +0,0 @@
<div class="row">
<div class="col-md-9">
{{profile profileId}}
{{#if network}}
<span class="label label-info">{{network}}</span>
{{/if}}
<span class="label label-info">{{runtime}} minutes</span>
<span class="label label-info">{{path}}</span>
{{#if ratings}}
<span class="label label-info" title="{{ratings.votes}} vote{{#if_gt ratings.votes compare="1"}}s{{/if_gt}}">{{ratings.value}}</span>
{{/if}}
<span class="label label-info">{{Bytes sizeOnDisk}}</span>
{{#if_eq fileCount compare="1"}}
<span class="label label-info"> 1 file</span>
{{else}}
<span class="label label-info"> {{fileCount}} files</span>
{{/if_eq}}
{{#if_eq status compare="continuing"}}
<span class="label label-info">Continuing</span>
{{else}}
<span class="label label-default">Ended</span>
{{/if_eq}}
</div>
<div class="col-md-3">
<span class="series-info-links">
{{!--
<a href="{{traktUrl}}" class="label label-info">Trakt</a>
<a href="{{tvdbUrl}}" class="label label-info">The TVDB</a>
--}}
{{#if imdbId}}
<a href="{{imdbUrl}}" class="label label-info">IMDB</a>
{{/if}}
{{#if tvRageId}}
<a href="{{tvRageUrl}}" class="label label-info">TV Rage</a>
{{/if}}
{{#if tvMazeId}}
<a href="{{tvMazeUrl}}" class="label label-info">TV Maze</a>
{{/if}}
</span>
</div>
</div>
{{#if alternateTitles}}
<div class="row">
<div class="col-md-12">
{{#each alternateTitles}}
{{#if_eq seasonNumber compare="-1"}}
<span class="label label-default">{{title}}</span>
{{/if_eq}}
{{#if_eq sceneSeasonNumber compare="-1"}}
<span class="label label-default">{{title}}</span>
{{/if_eq}}
{{/each}}
</div>
</div>
{{/if}}
{{#if tags}}
<div class="row">
<div class="col-md-12">
{{tagDisplay tags}}
</div>
</div>
{{/if}}

@ -1,44 +0,0 @@
var _ = require('underscore');
var Marionette = require('marionette');
var SeasonLayout = require('./SeasonLayout');
var AsSortedCollectionView = require('../../Mixins/AsSortedCollectionView');
var view = Marionette.CollectionView.extend({
itemView : SeasonLayout,
initialize : function(options) {
if (!options.episodeCollection) {
throw 'episodeCollection is needed';
}
this.episodeCollection = options.episodeCollection;
this.series = options.series;
},
itemViewOptions : function() {
return {
episodeCollection : this.episodeCollection,
series : this.series
};
},
onEpisodeGrabbed : function(message) {
if (message.episode.series.id !== this.episodeCollection.seriesId) {
return;
}
var self = this;
_.each(message.episode.episodes, function(episode) {
var ep = self.episodeCollection.get(episode.id);
ep.set('downloading', true);
});
this.render();
}
});
AsSortedCollectionView.call(view);
module.exports = view;

@ -1,301 +0,0 @@
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var ToggleCell = require('../../Cells/EpisodeMonitoredCell');
var EpisodeTitleCell = require('../../Cells/EpisodeTitleCell');
var RelativeDateCell = require('../../Cells/RelativeDateCell');
var EpisodeStatusCell = require('../../Cells/EpisodeStatusCell');
var EpisodeActionsCell = require('../../Cells/EpisodeActionsCell');
var EpisodeNumberCell = require('./EpisodeNumberCell');
var EpisodeWarningCell = require('./EpisodeWarningCell');
var CommandController = require('../../Commands/CommandController');
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
var moment = require('moment');
var _ = require('underscore');
var Messenger = require('../../Shared/Messenger');
module.exports = Marionette.Layout.extend({
template : 'Series/Details/SeasonLayoutTemplate',
ui : {
seasonSearch : '.x-season-search',
seasonMonitored : '.x-season-monitored',
seasonRename : '.x-season-rename'
},
events : {
'click .x-season-episode-file-editor' : '_openEpisodeFileEditor',
'click .x-season-monitored' : '_seasonMonitored',
'click .x-season-search' : '_seasonSearch',
'click .x-season-rename' : '_seasonRename',
'click .x-show-hide-episodes' : '_showHideEpisodes',
'dblclick .series-season h2' : '_showHideEpisodes'
},
regions : {
episodeGrid : '.x-episode-grid'
},
columns : [
{
name : 'monitored',
label : '',
cell : ToggleCell,
trueClass : 'icon-sonarr-monitored',
falseClass : 'icon-sonarr-unmonitored',
tooltip : 'Toggle monitored status',
sortable : false
},
{
name : 'episodeNumber',
label : '#',
cell : EpisodeNumberCell
},
{
name : 'this',
label : '',
cell : EpisodeWarningCell,
sortable : false,
className : 'episode-warning-cell'
},
{
name : 'this',
label : 'Title',
hideSeriesLink : true,
cell : EpisodeTitleCell,
sortable : false
},
{
name : 'airDateUtc',
label : 'Air Date',
cell : RelativeDateCell
},
{
name : 'status',
label : 'Status',
cell : EpisodeStatusCell,
sortable : false
},
{
name : 'this',
label : '',
cell : EpisodeActionsCell,
sortable : false
}
],
templateHelpers : function() {
var episodeCount = this.episodeCollection.filter(function(episode) {
return episode.get('hasFile') || episode.get('monitored') && moment(episode.get('airDateUtc')).isBefore(moment());
}).length;
var episodeFileCount = this.episodeCollection.where({ hasFile : true }).length;
var percentOfEpisodes = 100;
if (episodeCount > 0) {
percentOfEpisodes = episodeFileCount / episodeCount * 100;
}
return {
showingEpisodes : this.showingEpisodes,
episodeCount : episodeCount,
episodeFileCount : episodeFileCount,
percentOfEpisodes : percentOfEpisodes
};
},
initialize : function(options) {
if (!options.episodeCollection) {
throw 'episodeCollection is required';
}
this.series = options.series;
this.fullEpisodeCollection = options.episodeCollection;
this.episodeCollection = this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber'));
this._updateEpisodeCollection();
this.showingEpisodes = this._shouldShowEpisodes();
this.listenTo(this.model, 'sync', this._afterSeasonMonitored);
this.listenTo(this.episodeCollection, 'sync', this.render);
this.listenTo(this.fullEpisodeCollection, 'sync', this._refreshEpisodes);
},
onRender : function() {
if (this.showingEpisodes) {
this._showEpisodes();
}
this._setSeasonMonitoredState();
CommandController.bindToCommand({
element : this.ui.seasonSearch,
command : {
name : 'seasonSearch',
seriesId : this.series.id,
seasonNumber : this.model.get('seasonNumber')
}
});
CommandController.bindToCommand({
element : this.ui.seasonRename,
command : {
name : 'renameFiles',
seriesId : this.series.id,
seasonNumber : this.model.get('seasonNumber')
}
});
},
_seasonSearch : function() {
CommandController.Execute('seasonSearch', {
name : 'seasonSearch',
seriesId : this.series.id,
seasonNumber : this.model.get('seasonNumber')
});
},
_seasonRename : function() {
vent.trigger(vent.Commands.ShowRenamePreview, {
series : this.series,
seasonNumber : this.model.get('seasonNumber')
});
},
_seasonMonitored : function() {
if (!this.series.get('monitored')) {
Messenger.show({
message : 'Unable to change monitored state when series is not monitored',
type : 'error'
});
return;
}
var name = 'monitored';
this.model.set(name, !this.model.get(name));
this.series.setSeasonMonitored(this.model.get('seasonNumber'));
var savePromise = this.series.save().always(this._afterSeasonMonitored.bind(this));
this.ui.seasonMonitored.spinForPromise(savePromise);
},
_afterSeasonMonitored : function() {
var self = this;
_.each(this.episodeCollection.models, function(episode) {
episode.set({ monitored : self.model.get('monitored') });
});
this.render();
},
_setSeasonMonitoredState : function() {
this.ui.seasonMonitored.removeClass('icon-sonarr-spinner fa-spin');
if (this.model.get('monitored')) {
this.ui.seasonMonitored.addClass('icon-sonarr-monitored');
this.ui.seasonMonitored.removeClass('icon-sonarr-unmonitored');
} else {
this.ui.seasonMonitored.addClass('icon-sonarr-unmonitored');
this.ui.seasonMonitored.removeClass('icon-sonarr-monitored');
}
},
_showEpisodes : function() {
this.episodeGrid.show(new Backgrid.Grid({
columns : this.columns,
collection : this.episodeCollection,
className : 'table table-hover season-grid'
}));
},
_shouldShowEpisodes : function() {
var startDate = moment().add(-1, 'month');
var endDate = moment().add(1, 'year');
return this.episodeCollection.some(function(episode) {
var airDate = episode.get('airDateUtc');
if (airDate) {
var airDateMoment = moment(airDate);
if (airDateMoment.isAfter(startDate) && airDateMoment.isBefore(endDate)) {
return true;
}
}
return false;
});
},
_showHideEpisodes : function() {
if (this.showingEpisodes) {
this.showingEpisodes = false;
this.episodeGrid.close();
} else {
this.showingEpisodes = true;
this._showEpisodes();
}
this.templateHelpers.showingEpisodes = this.showingEpisodes;
this.render();
},
_episodeMonitoredToggled : function(options) {
var model = options.model;
var shiftKey = options.shiftKey;
if (!this.episodeCollection.get(model.get('id'))) {
return;
}
if (!shiftKey) {
return;
}
var lastToggled = this.episodeCollection.lastToggled;
if (!lastToggled) {
return;
}
var currentIndex = this.episodeCollection.indexOf(model);
var lastIndex = this.episodeCollection.indexOf(lastToggled);
var low = Math.min(currentIndex, lastIndex);
var high = Math.max(currentIndex, lastIndex);
var range = _.range(low + 1, high);
this.episodeCollection.lastToggled = model;
},
_updateEpisodeCollection : function() {
var self = this;
this.episodeCollection.add(this.fullEpisodeCollection.bySeason(this.model.get('seasonNumber')).models, { merge : true });
this.episodeCollection.each(function(model) {
model.episodeCollection = self.episodeCollection;
});
},
_refreshEpisodes : function() {
this._updateEpisodeCollection();
this.episodeCollection.fullCollection.sort();
this.render();
},
_openEpisodeFileEditor : function() {
var view = new EpisodeFileEditorLayout({
model : this.model,
series : this.series,
episodeCollection : this.episodeCollection
});
vent.trigger(vent.Commands.OpenModalCommand, view);
}
});

@ -1,50 +0,0 @@
<div class="series-season" id="season-{{seasonNumber}}">
<h2>
<i class="x-season-monitored season-monitored clickable" title="Toggle season monitored status"/>
{{#if seasonNumber}}
Season {{seasonNumber}}
{{else}}
Specials
{{/if}}
{{#if_eq episodeCount compare=0}}
{{#if monitored}}
<span class="badge badge-primary season-status" title="No aired episodes">&nbsp;</span>
{{else}}
<span class="badge badge-warning season-status" title="Season is not monitored">&nbsp;</span>
{{/if}}
{{else}}
{{#if_eq percentOfEpisodes compare=100}}
<span class="badge badge-success season-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
{{else}}
<span class="badge badge-danger season-status" title="{{episodeFileCount}}/{{episodeCount}} episodes downloaded">{{episodeFileCount}} / {{episodeCount}}</span>
{{/if_eq}}
{{/if_eq}}
<span class="season-actions pull-right">
<div class="x-season-episode-file-editor">
<i class="icon-sonarr-episode-file" title="Modify episode files for season"/>
</div>
<div class="x-season-rename">
<i class="icon-sonarr-rename" title="Preview rename for season {{seasonNumber}}"/>
</div>
<div class="x-season-search">
<i class="icon-sonarr-search" title="Search for monitored episodes in season {{seasonNumber}}"/>
</div>
</span>
</h2>
<div class="show-hide-episodes x-show-hide-episodes">
<h4>
{{#if showingEpisodes}}
<i class="icon-sonarr-panel-hide"/>
Hide Episodes
{{else}}
<i class="icon-sonarr-panel-show"/>
Show Episodes
{{/if}}
</h4>
</div>
<div class="x-episode-grid table-responsive"></div>
</div>

@ -1,272 +0,0 @@
var $ = require('jquery');
var _ = require('underscore');
var vent = require('vent');
var reqres = require('../../reqres');
var Marionette = require('marionette');
var Backbone = require('backbone');
var SeriesCollection = require('../SeriesCollection');
var EpisodeCollection = require('../EpisodeCollection');
var EpisodeFileCollection = require('../EpisodeFileCollection');
var SeasonCollection = require('../SeasonCollection');
var SeasonCollectionView = require('./SeasonCollectionView');
var InfoView = require('./InfoView');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var EpisodeFileEditorLayout = require('../../EpisodeFile/Editor/EpisodeFileEditorLayout');
require('backstrech');
require('../../Mixins/backbone.signalr.mixin');
module.exports = Marionette.Layout.extend({
itemViewContainer : '.x-series-seasons',
template : 'Series/Details/SeriesDetailsTemplate',
regions : {
seasons : '#seasons',
info : '#info'
},
ui : {
header : '.x-header',
monitored : '.x-monitored',
edit : '.x-edit',
refresh : '.x-refresh',
rename : '.x-rename',
search : '.x-search',
poster : '.x-series-poster',
manualSearch : '.x-manual-search'
},
events : {
'click .x-episode-file-editor' : '_openEpisodeFileEditor',
'click .x-monitored' : '_toggleMonitored',
'click .x-edit' : '_editSeries',
'click .x-refresh' : '_refreshSeries',
'click .x-rename' : '_renameSeries',
'click .x-search' : '_seriesSearch',
'click .x-manual-search' : '_manualSearchM'
},
initialize : function() {
this.seriesCollection = SeriesCollection.clone();
this.seriesCollection.shadowCollection.bindSignalR();
this.listenTo(this.model, 'change:monitored', this._setMonitoredState);
this.listenTo(this.model, 'remove', this._seriesRemoved);
this.listenTo(vent, vent.Events.CommandComplete, this._commandComplete);
this.listenTo(this.model, 'change', function(model, options) {
if (options && options.changeSource === 'signalr') {
this._refresh();
}
});
this.listenTo(this.model, 'change:images', this._updateImages);
},
onShow : function() {
this._showBackdrop();
this._showSeasons();
this._setMonitoredState();
this._showInfo();
},
onRender : function() {
CommandController.bindToCommand({
element : this.ui.refresh,
command : {
name : 'refreshSeries'
}
});
CommandController.bindToCommand({
element : this.ui.search,
command : {
name : 'seriesSearch'
}
});
CommandController.bindToCommand({
element : this.ui.rename,
command : {
name : 'renameFiles',
seriesId : this.model.id,
seasonNumber : -1
}
});
},
onClose : function() {
if (this._backstrech) {
this._backstrech.destroy();
delete this._backstrech;
}
$('body').removeClass('backdrop');
reqres.removeHandler(reqres.Requests.GetEpisodeFileById);
},
_getImage : function(type) {
var image = _.where(this.model.get('images'), { coverType : type });
if (image && image[0]) {
return image[0].url;
}
return undefined;
},
_toggleMonitored : function() {
var savePromise = this.model.save('monitored', !this.model.get('monitored'), { wait : true });
this.ui.monitored.spinForPromise(savePromise);
},
_setMonitoredState : function() {
var monitored = this.model.get('monitored');
this.ui.monitored.removeAttr('data-idle-icon');
this.ui.monitored.removeClass('fa-spin icon-sonarr-spinner');
if (monitored) {
this.ui.monitored.addClass('icon-sonarr-monitored');
this.ui.monitored.removeClass('icon-sonarr-unmonitored');
this.$el.removeClass('series-not-monitored');
} else {
this.ui.monitored.addClass('icon-sonarr-unmonitored');
this.ui.monitored.removeClass('icon-sonarr-monitored');
this.$el.addClass('series-not-monitored');
}
},
_editSeries : function() {
vent.trigger(vent.Commands.EditSeriesCommand, { series : this.model });
},
_refreshSeries : function() {
CommandController.Execute('refreshSeries', {
name : 'refreshSeries',
seriesId : this.model.id
});
},
_seriesRemoved : function() {
Backbone.history.navigate('/', { trigger : true });
},
_renameSeries : function() {
vent.trigger(vent.Commands.ShowRenamePreview, { series : this.model });
},
_seriesSearch : function() {
CommandController.Execute('seriesSearch', {
name : 'seriesSearch',
seriesId : this.model.id
});
},
_showSeasons : function() {
var self = this;
this.seasons.show(new LoadingView());
this.seasonCollection = new SeasonCollection(this.model.get('seasons'));
this.episodeCollection = new EpisodeCollection({ seriesId : this.model.id }).bindSignalR();
this.episodeFileCollection = new EpisodeFileCollection({ seriesId : this.model.id }).bindSignalR();
reqres.setHandler(reqres.Requests.GetEpisodeFileById, function(episodeFileId) {
return self.episodeFileCollection.get(episodeFileId);
});
reqres.setHandler(reqres.Requests.GetAlternateNameBySeasonNumber, function(seriesId, seasonNumber, sceneSeasonNumber) {
if (self.model.get('id') !== seriesId) {
return [];
}
if (sceneSeasonNumber === undefined) {
sceneSeasonNumber = seasonNumber;
}
return _.where(self.model.get('alternateTitles'),
function(alt) {
return alt.sceneSeasonNumber === sceneSeasonNumber || alt.seasonNumber === seasonNumber;
});
});
$.when(this.episodeCollection.fetch(), this.episodeFileCollection.fetch()).done(function() {
var seasonCollectionView = new SeasonCollectionView({
collection : self.seasonCollection,
episodeCollection : self.episodeCollection,
series : self.model
});
if (!self.isClosed) {
self.seasons.show(seasonCollectionView);
}
});
},
_showInfo : function() {
this.info.show(new InfoView({
model : this.model,
episodeFileCollection : this.episodeFileCollection
}));
},
_commandComplete : function(options) {
if (options.command.get('name') === 'renamefiles') {
if (options.command.get('seriesId') === this.model.get('id')) {
this._refresh();
}
}
},
_refresh : function() {
this.seasonCollection.add(this.model.get('seasons'), { merge : true });
this.episodeCollection.fetch();
this.episodeFileCollection.fetch();
this._setMonitoredState();
this._showInfo();
},
_openEpisodeFileEditor : function() {
var view = new EpisodeFileEditorLayout({
series : this.model,
episodeCollection : this.episodeCollection
});
vent.trigger(vent.Commands.OpenModalCommand, view);
},
_updateImages : function () {
var poster = this._getImage('poster');
if (poster) {
this.ui.poster.attr('src', poster);
}
this._showBackdrop();
},
_showBackdrop : function () {
$('body').addClass('backdrop');
var fanArt = this._getImage('fanart');
if (fanArt) {
this._backstrech = $.backstretch(fanArt);
} else {
$('body').removeClass('backdrop');
}
},
_manualSearchM : function() {
console.warn("Manual Search started");
console.warn(this.model.get("seriesId"));
console.warn(this.model);
console.warn(this.episodeCollection);
vent.trigger(vent.Commands.ShowEpisodeDetails, {
episode : this.episodeCollection.models[0],
hideSeriesLink : true,
openingTab : 'search'
});
}
});

@ -1,38 +0,0 @@
<div class="row series-page-header">
<div class="visible-lg col-lg-2 poster">
{{poster}}
</div>
<div class="col-md-12 col-lg-10">
<div>
<h1 class="header-text">
<i class="x-monitored" title="Toggle monitored state for movie"/>
{{title}}
<div class="series-actions pull-right">
<div class="x-episode-file-editor">
<i class="icon-sonarr-episode-file" title="Modify episode files for movie"/>
</div>
<div class="x-refresh">
<i class="icon-sonarr-refresh icon-can-spin" title="Update movie info and scan disk"/>
</div>
<div class="x-rename">
<i class="icon-sonarr-rename" title="Preview rename for all episodes"/>
</div>
<div class="x-search">
<i class="icon-sonarr-search" title="Search for movie"/>
</div>
<div class="x-manual-search">
<i class="icon-sonarr-search-manual" title="Manual Search"/>
</div>
<div class="x-edit">
<i class="icon-sonarr-edit" title="Edit movie"/>
</div>
</div>
</h1>
</div>
<div class="series-detail-overview">
{{overview}}
</div>
<div id="info" class="series-info"></div>
</div>
</div>
<div id="seasons"></div>

@ -1,54 +0,0 @@
var vent = require('vent');
var Marionette = require('marionette');
var Profiles = require('../../Profile/ProfileCollection');
var AsModelBoundView = require('../../Mixins/AsModelBoundView');
var AsValidatedView = require('../../Mixins/AsValidatedView');
var AsEditModalView = require('../../Mixins/AsEditModalView');
require('../../Mixins/TagInput');
require('../../Mixins/FileBrowser');
var view = Marionette.ItemView.extend({
template : 'Series/Edit/EditSeriesViewTemplate',
ui : {
profile : '.x-profile',
path : '.x-path',
tags : '.x-tags'
},
events : {
'click .x-remove' : '_removeSeries'
},
initialize : function() {
this.model.set('profiles', Profiles);
},
onRender : function() {
this.ui.path.fileBrowser();
this.ui.tags.tagInput({
model : this.model,
property : 'tags'
});
},
_onBeforeSave : function() {
var profileId = this.ui.profile.val();
this.model.set({ profileId : profileId });
},
_onAfterSave : function() {
this.trigger('saved');
vent.trigger(vent.Commands.CloseModalCommand);
},
_removeSeries : function() {
vent.trigger(vent.Commands.DeleteSeriesCommand, { series : this.model });
}
});
AsModelBoundView.call(view);
AsValidatedView.call(view);
AsEditModalView.call(view);
module.exports = view;

@ -1,104 +0,0 @@
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h3>{{title}}</h3>
</div>
<div class="modal-body edit-series-modal">
<div class="row">
<div class="col-sm-3 hidden-xs">
{{poster}}
</div>
<div class="col-sm-9">
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-4 control-label">Monitored</label>
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="monitored"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should Radarr download episodes for this series?"/>
</span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Use Season Folder</label>
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" name="seasonFolder"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
<span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should downloaded episodes be stored in season folders?"/>
</span>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Profile</label>
<div class="col-sm-4">
<select class="form-control x-profile" id="inputProfile" name="profileId">
{{#each profiles.models}}
<option value="{{id}}">{{attributes.name}}</option>
{{/each}}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Series Type</label>
<div class="col-sm-4">
{{> SeriesTypeSelectionPartial}}
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Path</label>
<div class="col-sm-6">
<input type="text" class="form-control x-path" placeholder="Path" name="path">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Tags</label>
<div class="col-sm-6">
<input type="text" class="form-control x-tags">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-danger pull-left x-remove">Delete</button>
<span class="indicator x-indicator"><i class="icon-sonarr-spinner fa-spin"></i></span>
<button class="btn" data-dismiss="modal">Cancel</button>
<button class="btn btn-primary x-save">Save</button>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save