First implementation of completely rewriting the way Radarr handles movies. Searching for new movies is now mostly feature complete.
parent
0b278c7db8
commit
40d7590f80
Binary file not shown.
Binary file not shown.
@ -0,0 +1,22 @@
|
||||
var Backbone = require('backbone');
|
||||
var MovieModel = require('../Movies/MovieModel');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/movies/lookup',
|
||||
model : MovieModel,
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
var vent = require('vent');
|
||||
var AppLayout = require('../AppLayout');
|
||||
var Marionette = require('marionette');
|
||||
var RootFolderLayout = require('./RootFolders/RootFolderLayout');
|
||||
//var ExistingMoviesCollectionView = require('./Existing/AddExistingSeriesCollectionView');
|
||||
var AddMoviesView = require('./AddMoviesView');
|
||||
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||
require('../Movies/MoviesCollection');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'AddMovies/AddMoviesLayoutTemplate',
|
||||
|
||||
regions : {
|
||||
workspace : '#add-movies-workspace'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-import' : '_importMovies',
|
||||
'click .x-add-new' : '_addMovies'
|
||||
},
|
||||
|
||||
attributes : {
|
||||
id : 'add-movies-screen'
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
ProfileCollection.fetch();
|
||||
RootFolderCollection.fetch().done(function() {
|
||||
RootFolderCollection.synced = true;
|
||||
});
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.workspace.show(new AddMoviesView());
|
||||
},
|
||||
|
||||
_folderSelected : function(options) {
|
||||
//vent.trigger(vent.Commands.CloseModalCommand);
|
||||
//TODO: Fix this shit.
|
||||
//this.workspace.show(new ExistingMoviesCollectionView({ model : options.model }));
|
||||
},
|
||||
|
||||
_importMovies : function() {
|
||||
this.rootFolderLayout = new RootFolderLayout();
|
||||
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||
AppLayout.modalRegion.show(this.rootFolderLayout);
|
||||
},
|
||||
|
||||
_addMovies : function() {
|
||||
this.workspace.show(new AddMoviesView());
|
||||
}
|
||||
});
|
@ -0,0 +1,16 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="btn-group add-movies-btn-group btn-group-lg btn-block">
|
||||
<button type="button" class="btn btn-default col-md-10 col-xs-8 add-movies-import-btn x-import">
|
||||
<i class="icon-sonarr-hdd"/>
|
||||
Import existing movies 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-movies-workspace"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,183 @@
|
||||
var _ = require('underscore');
|
||||
var vent = require('vent');
|
||||
var Marionette = require('marionette');
|
||||
var AddMoviesCollection = require('./AddMoviesCollection');
|
||||
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 : 'AddMovies/AddMoviesViewTemplate',
|
||||
|
||||
regions : {
|
||||
searchResult : '#search-result'
|
||||
},
|
||||
|
||||
ui : {
|
||||
moviesSearch : '.x-movies-search',
|
||||
searchBar : '.x-search-bar',
|
||||
loadMore : '.x-load-more'
|
||||
},
|
||||
|
||||
events : {
|
||||
'click .x-load-more' : '_onLoadMore'
|
||||
},
|
||||
|
||||
initialize : function(options) {
|
||||
console.log(options)
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddMoviesCollection();
|
||||
|
||||
if (this.isExisting) {
|
||||
this.collection.unmappedFolderModel = this.model;
|
||||
}
|
||||
|
||||
if (this.isExisting) {
|
||||
this.className = 'existing-movies';
|
||||
} else {
|
||||
this.className = 'new-movies';
|
||||
}
|
||||
|
||||
this.listenTo(vent, vent.Events.MoviesAdded, this._onMoviesAdded);
|
||||
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.moviesSearch.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.moviesSearch.val()
|
||||
});
|
||||
});
|
||||
|
||||
this._clearResults();
|
||||
|
||||
if (this.isExisting) {
|
||||
this.ui.searchBar.hide();
|
||||
}
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.ui.moviesSearch.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;
|
||||
},
|
||||
|
||||
_onMoviesAdded : function(options) {
|
||||
if (this.isExisting && options.movies.get('path') === this.model.get('folder').path) {
|
||||
this.close();
|
||||
}
|
||||
|
||||
else if (!this.isExisting) {
|
||||
this.collection.term = '';
|
||||
this.collection.reset();
|
||||
this._clearResults();
|
||||
this.ui.moviesSearch.val('');
|
||||
this.ui.moviesSearch.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 = '';
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,24 @@
|
||||
{{#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-movies-search">
|
||||
<span class="input-group-addon"><i class="icon-sonarr-search"/></span>
|
||||
|
||||
{{#if folder}}
|
||||
<input type="text" class="form-control x-movies-search" value="{{folder.name}}">
|
||||
{{else}}
|
||||
<input type="text" class="form-control x-movies-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-movies-loadmore x-load-more" style="display: none;">
|
||||
<i class="icon-sonarr-load-more"/>
|
||||
more
|
||||
</div>
|
@ -0,0 +1,5 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddMovies/EmptyViewTemplate'
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
<div class="text-center hint col-md-12">
|
||||
<span>You can also search by imdbid using the imdb: prefixes.</span>
|
||||
</div>
|
@ -0,0 +1,13 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddMovies/ErrorViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return this.options;
|
||||
}
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
<div class="text-center col-md-12">
|
||||
<h3>
|
||||
There was an error searching for '{{term}}'.
|
||||
</h3>
|
||||
|
||||
If the movie title contains non-alphanumeric characters try removing them, otherwise try your search again later.
|
||||
</div>
|
@ -0,0 +1,51 @@
|
||||
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
|
||||
}
|
||||
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
<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>
|
@ -0,0 +1,20 @@
|
||||
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);
|
||||
}
|
||||
});
|
@ -0,0 +1,3 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({});
|
@ -0,0 +1,18 @@
|
||||
<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>
|
@ -0,0 +1,3 @@
|
||||
<select class="form-control col-md-2 x-movie-type" name="movieType">
|
||||
<option value="standard">Standard</option>
|
||||
</select>
|
@ -0,0 +1,13 @@
|
||||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddMovies/NotFoundViewTemplate',
|
||||
|
||||
initialize : function(options) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
templateHelpers : function() {
|
||||
return this.options;
|
||||
}
|
||||
});
|
@ -0,0 +1,7 @@
|
||||
<div class="text-center col-md-12">
|
||||
<h3>
|
||||
Sorry. We couldn't find any movies 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>
|
@ -0,0 +1,10 @@
|
||||
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();
|
@ -0,0 +1,8 @@
|
||||
var Marionette = require('marionette');
|
||||
var RootFolderItemView = require('./RootFolderItemView');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddSeries/RootFolders/RootFolderCollectionViewTemplate',
|
||||
itemViewContainer : '.x-root-folders',
|
||||
itemView : RootFolderItemView
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
<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>
|
@ -0,0 +1,28 @@
|
||||
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);
|
||||
}
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
<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>
|
@ -0,0 +1,77 @@
|
||||
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;
|
@ -0,0 +1,8 @@
|
||||
var Backbone = require('backbone');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
urlRoot : window.NzbDrone.ApiRoot + '/rootfolder',
|
||||
defaults : {
|
||||
freeSpace : 0
|
||||
}
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
<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>
|
||||
|
@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
@ -0,0 +1,272 @@
|
||||
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 MoviesCollection = require('../Movies/MoviesCollection');
|
||||
var Config = require('../Config');
|
||||
var Messenger = require('../Shared/Messenger');
|
||||
var AsValidatedView = require('../Mixins/AsValidatedView');
|
||||
|
||||
require('jquery.dotdotdot');
|
||||
|
||||
var view = Marionette.ItemView.extend({
|
||||
|
||||
template : 'AddMovies/SearchResultViewTemplate',
|
||||
|
||||
ui : {
|
||||
profile : '.x-profile',
|
||||
rootFolder : '.x-root-folder',
|
||||
seasonFolder : '.x-season-folder',
|
||||
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-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 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.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('AddMovies/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 existingMovies = MoviesCollection.where({ imdbId : this.model.get('imdbId') });
|
||||
console.log(existingMovies)
|
||||
if (existingMovies.length > 0) {
|
||||
this.templateHelpers.existing = existingMovies[0].toJSON();
|
||||
}
|
||||
|
||||
this.templateHelpers.profiles = Profiles.toJSON();
|
||||
console.log(this.model)
|
||||
console.log(this.templateHelpers.existing)
|
||||
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.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);
|
||||
}
|
||||
},
|
||||
|
||||
_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._addMovies(true);
|
||||
},
|
||||
|
||||
_addAndSearch : function() {
|
||||
this._addMovies(true);
|
||||
},
|
||||
|
||||
_addMovies : 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 options = this._getAddMoviesOptions();
|
||||
options.searchForMissingEpisodes = searchForMissingEpisodes;
|
||||
|
||||
this.model.set({
|
||||
profileId : profile,
|
||||
rootFolderPath : rootFolderPath,
|
||||
addOptions : options,
|
||||
monitored : true
|
||||
}, { silent : true });
|
||||
|
||||
var self = this;
|
||||
var promise = this.model.save();
|
||||
|
||||
console.log(this.model.save);
|
||||
console.log(promise);
|
||||
|
||||
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() {
|
||||
MoviesCollection.add(self.model);
|
||||
|
||||
self.close();
|
||||
|
||||
Messenger.show({
|
||||
message : 'Added: ' + self.model.get('title'),
|
||||
actions : {
|
||||
goToSeries : {
|
||||
label : 'Go to Movie',
|
||||
action : function() {
|
||||
Backbone.history.navigate('/movies/' + self.model.get('titleSlug'), { trigger : true });
|
||||
}
|
||||
}
|
||||
},
|
||||
hideAfter : 8,
|
||||
hideOnNavigate : true
|
||||
});
|
||||
|
||||
vent.trigger(vent.Events.MoviesAdded, { movie : self.model });
|
||||
});
|
||||
},
|
||||
|
||||
_rootFoldersUpdated : function() {
|
||||
this._configureTemplateHelpers();
|
||||
this.render();
|
||||
},
|
||||
|
||||
_getAddMoviesOptions : function() {
|
||||
var monitor = this.ui.monitor.val();
|
||||
|
||||
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;
|
@ -0,0 +1,101 @@
|
||||
<div class="search-item {{#unless isExisting}}search-item-new{{/unless}}">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<a href="{{imdbUrl}}" target="_blank">
|
||||
{{poster}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2 class="movies-title">
|
||||
{{titleWithYear}}
|
||||
|
||||
<span class="labels">
|
||||
<span class="label label-default">{{network}}</span>
|
||||
{{#unless_eq status compare="announced"}}
|
||||
<span class="label label-danger">Released</span> <!-- TODO: Better handling of cases here! -->
|
||||
{{/unless_eq}}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row new-movies-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="missing">Missing</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>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}}
|
||||
|
||||
{{#unless existing}}
|
||||
{{#if title}}
|
||||
<div class="form-group col-md-2">
|
||||
<!--Uncomment if we need to add even more controls to add Movies-->
|
||||
<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}}
|
||||
<label style="visibility: hidden">Add</label>
|
||||
<div class="col-md-2" title="Movies require an English title">
|
||||
<button class="btn add-movies disabled">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<label style="visibility: hidden">Add</label>
|
||||
<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>
|
@ -0,0 +1,13 @@
|
||||
<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>
|
@ -0,0 +1,177 @@
|
||||
@import "../Shared/Styles/card.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
|
||||
#add-movies-screen {
|
||||
.existing-movies {
|
||||
|
||||
.card();
|
||||
margin : 30px 0px;
|
||||
|
||||
.unmapped-folder-path {
|
||||
padding: 20px;
|
||||
margin-left : 0px;
|
||||
font-weight : 100;
|
||||
font-size : 25px;
|
||||
text-align : center;
|
||||
}
|
||||
|
||||
.new-movies-loadmore {
|
||||
font-size : 30px;
|
||||
font-weight : 300;
|
||||
padding-top : 10px;
|
||||
padding-bottom : 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-movies {
|
||||
.search-item {
|
||||
.card();
|
||||
margin : 40px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.add-movies-search {
|
||||
margin-top : 20px;
|
||||
margin-bottom : 20px;
|
||||
}
|
||||
|
||||
.search-item {
|
||||
|
||||
padding-bottom : 20px;
|
||||
|
||||
.btn-group{
|
||||
display: table;
|
||||
}
|
||||
|
||||
.movies-title {
|
||||
margin-top : 5px;
|
||||
|
||||
.labels {
|
||||
margin-left : 10px;
|
||||
|
||||
.label {
|
||||
font-size : 12px;
|
||||
vertical-align : middle;
|
||||
}
|
||||
}
|
||||
|
||||
.year {
|
||||
font-style : italic;
|
||||
color : #aaaaaa;
|
||||
}
|
||||
}
|
||||
|
||||
.new-movies-overview {
|
||||
overflow : hidden;
|
||||
height : 103px;
|
||||
|
||||
.overview-internal {
|
||||
overflow : hidden;
|
||||
height : 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.movies-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 : 0px;
|
||||
}
|
||||
|
||||
.add {
|
||||
i {
|
||||
&:before {
|
||||
color : #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.monitor-tooltip {
|
||||
margin-left : 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-folders {
|
||||
margin : 30px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hint {
|
||||
color : #999999;
|
||||
font-style : italic;
|
||||
}
|
||||
|
||||
.monitor-tooltip-contents {
|
||||
padding-bottom : 0px;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
urlRoot : window.NzbDrone.ApiRoot + '/movies',
|
||||
|
||||
defaults : {
|
||||
episodeFileCount : 0,
|
||||
episodeCount : 0,
|
||||
isExisting : false,
|
||||
status : 0
|
||||
}
|
||||
});
|
@ -0,0 +1,120 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var PageableCollection = require('backbone.pageable');
|
||||
var MovieModel = require('./MovieModel');
|
||||
var ApiData = require('../Shared/ApiData');
|
||||
var AsFilteredCollection = require('../Mixins/AsFilteredCollection');
|
||||
var AsSortedCollection = require('../Mixins/AsSortedCollection');
|
||||
var AsPersistedStateCollection = require('../Mixins/AsPersistedStateCollection');
|
||||
var moment = require('moment');
|
||||
require('../Mixins/backbone.signalr.mixin');
|
||||
|
||||
var Collection = PageableCollection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/movies',
|
||||
model : MovieModel,
|
||||
tableName : 'movies',
|
||||
|
||||
state : {
|
||||
sortKey : 'sortTitle',
|
||||
order : -1,
|
||||
pageSize : 100000,
|
||||
secondarySortKey : 'sortTitle',
|
||||
secondarySortOrder : -1
|
||||
},
|
||||
|
||||
mode : 'client',
|
||||
|
||||
save : function() {
|
||||
var self = this;
|
||||
|
||||
var proxy = _.extend(new Backbone.Model(), {
|
||||
id : '',
|
||||
|
||||
url : self.url + '/editor',
|
||||
|
||||
toJSON : function() {
|
||||
return self.filter(function(model) {
|
||||
return model.edited;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.listenTo(proxy, 'sync', function(proxyModel, models) {
|
||||
this.add(models, { merge : true });
|
||||
this.trigger('save', this);
|
||||
});
|
||||
|
||||
return proxy.save();
|
||||
},
|
||||
|
||||
filterModes : {
|
||||
'all' : [
|
||||
null,
|
||||
null
|
||||
],
|
||||
'continuing' : [
|
||||
'status',
|
||||
'continuing'
|
||||
],
|
||||
'ended' : [
|
||||
'status',
|
||||
'ended'
|
||||
],
|
||||
'monitored' : [
|
||||
'monitored',
|
||||
true
|
||||
],
|
||||
'missing' : [
|
||||
null,
|
||||
null,
|
||||
function(model) { return model.get('episodeCount') !== model.get('episodeFileCount'); }
|
||||
]
|
||||
},
|
||||
|
||||
sortMappings : {
|
||||
title : {
|
||||
sortKey : 'sortTitle'
|
||||
},
|
||||
|
||||
nextAiring : {
|
||||
sortValue : function(model, attr, order) {
|
||||
var nextAiring = model.get(attr);
|
||||
|
||||
if (nextAiring) {
|
||||
return moment(nextAiring).unix();
|
||||
}
|
||||
|
||||
if (order === 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Number.MAX_VALUE;
|
||||
}
|
||||
},
|
||||
|
||||
percentOfEpisodes : {
|
||||
sortValue : function(model, attr) {
|
||||
var percentOfEpisodes = model.get(attr);
|
||||
var episodeCount = model.get('episodeCount');
|
||||
|
||||
return percentOfEpisodes + episodeCount / 1000000;
|
||||
}
|
||||
},
|
||||
|
||||
path : {
|
||||
sortValue : function(model) {
|
||||
var path = model.get('path');
|
||||
|
||||
return path.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Collection = AsFilteredCollection.call(Collection);
|
||||
Collection = AsSortedCollection.call(Collection);
|
||||
Collection = AsPersistedStateCollection.call(Collection);
|
||||
|
||||
var data = ApiData.get('series');
|
||||
|
||||
module.exports = new Collection(data, { full : true }).bindSignalR();
|
Loading…
Reference in new issue