mirror of https://github.com/Radarr/Radarr
Bulk Import. (#583)
* First pass at bulk import. * First pass at paging implementation for bulk import. * Another pass at UI for bulk import WHY WON'T THE ROWS SELECT?!?! * Paging mostly done. UI needs to show loading still. * Fix for selection * fixes. * Add caching to bulk import * Tried to fix paging. * Fix has next * Fix link error. * Pageable now works almost perfectly. Collection now works really nicely when paged. Also movies from different pages can be added no problemo. * /bulk-import: ProfileCell Various other QoL changes * Profile selection works now Still kinda hacky * Default monitored to true. * Add Monitor Cell Update styling, added path tooltip as well * Update model when changing tmdbId Ensure monitor status doesn't change as well * Added spinner feedback for tmdbid cell. * /bulk-import: Add page-size selector
This commit is contained in:
parent
0d1150d4d2
commit
35b384439f
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using NzbDrone.Api.Episodes;
|
||||
using NzbDrone.Api.Movie;
|
||||
using NzbDrone.Api.Series;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Nancy;
|
||||
using NzbDrone.Api.Extensions;
|
||||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.Parser;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Marr.Data;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Api.Movie
|
||||
{
|
||||
|
||||
public class UnmappedComparer : IComparer<UnmappedFolder>
|
||||
{
|
||||
public int Compare(UnmappedFolder a, UnmappedFolder b)
|
||||
{
|
||||
return a.Name.CompareTo(b.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public class MovieBulkImportModule : NzbDroneRestModule<MovieResource>
|
||||
{
|
||||
private readonly ISearchForNewMovie _searchProxy;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IMakeImportDecision _importDecisionMaker;
|
||||
private readonly IDiskScanService _diskScanService;
|
||||
private readonly ICached<Core.Tv.Movie> _mappedMovies;
|
||||
|
||||
public MovieBulkImportModule(ISearchForNewMovie searchProxy, IRootFolderService rootFolderService, IMakeImportDecision importDecisionMaker,
|
||||
IDiskScanService diskScanService, ICacheManager cacheManager)
|
||||
: base("/movies/bulkimport")
|
||||
{
|
||||
_searchProxy = searchProxy;
|
||||
_rootFolderService = rootFolderService;
|
||||
_importDecisionMaker = importDecisionMaker;
|
||||
_diskScanService = diskScanService;
|
||||
_mappedMovies = cacheManager.GetCache<Core.Tv.Movie>(GetType(), "mappedMoviesCache");
|
||||
Get["/"] = x => Search();
|
||||
}
|
||||
|
||||
|
||||
private Response Search()
|
||||
{
|
||||
if (Request.Query.Id == 0)
|
||||
{
|
||||
//Todo error handling
|
||||
}
|
||||
|
||||
RootFolder rootFolder = _rootFolderService.Get(Request.Query.Id);
|
||||
|
||||
int page = Request.Query.page;
|
||||
int per_page = Request.Query.per_page;
|
||||
|
||||
int min = (page - 1) * per_page;
|
||||
|
||||
int max = page * per_page;
|
||||
|
||||
var unmapped = rootFolder.UnmappedFolders.OrderBy(f => f.Name).ToList();
|
||||
|
||||
int total_count = unmapped.Count;
|
||||
|
||||
if (Request.Query.total_entries.HasValue)
|
||||
{
|
||||
total_count = Request.Query.total_entries;
|
||||
}
|
||||
|
||||
max = total_count >= max ? max : total_count;
|
||||
|
||||
var paged = unmapped.GetRange(min, max-min);
|
||||
|
||||
var mapped = paged.Select(f =>
|
||||
{
|
||||
Core.Tv.Movie m = null;
|
||||
|
||||
var mappedMovie = _mappedMovies.Find(f.Name);
|
||||
|
||||
if (mappedMovie != null)
|
||||
{
|
||||
return mappedMovie;
|
||||
}
|
||||
|
||||
var parsedTitle = Parser.ParseMoviePath(f.Name);
|
||||
if (parsedTitle == null)
|
||||
{
|
||||
m = new Core.Tv.Movie
|
||||
{
|
||||
Title = f.Name.Replace(".", " ").Replace("-", " "),
|
||||
Path = f.Path,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
m = new Core.Tv.Movie
|
||||
{
|
||||
Title = parsedTitle.MovieTitle,
|
||||
Year = parsedTitle.Year,
|
||||
ImdbId = parsedTitle.ImdbId,
|
||||
Path = f.Path
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
var files = _diskScanService.GetVideoFiles(f.Path);
|
||||
|
||||
var decisions = _importDecisionMaker.GetImportDecisions(files.ToList(), m);
|
||||
|
||||
var decision = decisions.Where(d => d.Approved && !d.Rejections.Any()).FirstOrDefault();
|
||||
|
||||
if (decision != null)
|
||||
{
|
||||
var local = decision.LocalMovie;
|
||||
|
||||
m.MovieFile = new LazyLoaded<MovieFile>(new MovieFile
|
||||
{
|
||||
Path = local.Path,
|
||||
Edition = local.ParsedMovieInfo.Edition,
|
||||
Quality = local.Quality,
|
||||
MediaInfo = local.MediaInfo,
|
||||
ReleaseGroup = local.ParsedMovieInfo.ReleaseGroup,
|
||||
RelativePath = f.Path.GetRelativePath(local.Path)
|
||||
});
|
||||
}
|
||||
|
||||
mappedMovie = _searchProxy.MapMovieToTmdbMovie(m);
|
||||
mappedMovie.Monitored = true;
|
||||
|
||||
_mappedMovies.Set(f.Name, mappedMovie, TimeSpan.FromDays(2));
|
||||
|
||||
return mappedMovie;
|
||||
});
|
||||
|
||||
return new PagingResource<MovieResource>
|
||||
{
|
||||
Page = page,
|
||||
PageSize = per_page,
|
||||
SortDirection = SortDirection.Ascending,
|
||||
SortKey = Request.Query.sort_by,
|
||||
TotalRecords = total_count - mapped.Where(m => m == null).Count(),
|
||||
Records = MapToResource(mapped.Where(m => m != null)).ToList()
|
||||
}.AsResponse();
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
||||
{
|
||||
foreach (var currentSeries in movies)
|
||||
{
|
||||
var resource = currentSeries.ToResource();
|
||||
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
|
||||
if (poster != null)
|
||||
{
|
||||
resource.RemotePoster = poster.Url;
|
||||
}
|
||||
|
||||
yield return resource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -118,8 +118,9 @@
|
|||
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
||||
<Compile Include="Indexers\ReleaseModuleBase.cs" />
|
||||
<Compile Include="Indexers\ReleasePushModule.cs" />
|
||||
<Compile Include="Movies\MovieBulkImportModule.cs" />
|
||||
<Compile Include="Movies\MovieFileModule.cs" />
|
||||
<Compile Include="Movies\MovieModule.cs" />
|
||||
<Compile Include="Series\MovieModule.cs" />
|
||||
<Compile Include="Movies\RenameMovieModule.cs" />
|
||||
<Compile Include="Movies\RenameMovieResource.cs" />
|
||||
<Compile Include="Movies\MovieEditorModule.cs" />
|
||||
|
@ -245,7 +246,6 @@
|
|||
<Compile Include="Series\SeriesEditorModule.cs" />
|
||||
<Compile Include="Series\MovieLookupModule.cs" />
|
||||
<Compile Include="Series\SeriesLookupModule.cs" />
|
||||
<Compile Include="Series\MovieModule.cs" />
|
||||
<Compile Include="Series\SeriesModule.cs" />
|
||||
<Compile Include="Series\MovieResource.cs" />
|
||||
<Compile Include="Series\SeriesResource.cs" />
|
||||
|
@ -295,11 +295,11 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -4,18 +4,35 @@ using NzbDrone.Api.Extensions;
|
|||
using NzbDrone.Core.MediaCover;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Api.REST;
|
||||
|
||||
namespace NzbDrone.Api.Movie
|
||||
{
|
||||
public class MovieLookupModule : NzbDroneRestModule<MovieResource>
|
||||
{
|
||||
private readonly ISearchForNewMovie _searchProxy;
|
||||
private readonly IProvideMovieInfo _movieInfo;
|
||||
|
||||
public MovieLookupModule(ISearchForNewMovie searchProxy)
|
||||
public MovieLookupModule(ISearchForNewMovie searchProxy, IProvideMovieInfo movieInfo)
|
||||
: base("/movies/lookup")
|
||||
{
|
||||
_movieInfo = movieInfo;
|
||||
_searchProxy = searchProxy;
|
||||
Get["/"] = x => Search();
|
||||
Get["/tmdb"] = x => SearchByTmdbId();
|
||||
}
|
||||
|
||||
private Response SearchByTmdbId()
|
||||
{
|
||||
int tmdbId = -1;
|
||||
if(Int32.TryParse(Request.Query.tmdbId, out tmdbId))
|
||||
{
|
||||
var result = _movieInfo.GetMovieInfo(tmdbId, null);
|
||||
return result.ToResource().AsResponse();
|
||||
}
|
||||
|
||||
throw new BadRequestException("Tmdb Id was not valid");
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,7 +42,6 @@ namespace NzbDrone.Api.Movie
|
|||
return MapToResource(imdbResults).AsResponse();
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
|
||||
{
|
||||
foreach (var currentSeries in movies)
|
||||
|
|
|
@ -637,6 +637,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
|
|||
newMovie.RootFolderPath = movie.RootFolderPath;
|
||||
newMovie.ProfileId = movie.ProfileId;
|
||||
newMovie.Monitored = movie.Monitored;
|
||||
newMovie.MovieFile = movie.MovieFile;
|
||||
|
||||
return newMovie;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ var AddMoviesView = require('./AddMoviesView');
|
|||
var ProfileCollection = require('../Profile/ProfileCollection');
|
||||
var AddFromListView = require("./List/AddFromListView");
|
||||
var RootFolderCollection = require('./RootFolders/RootFolderCollection');
|
||||
var BulkImportView = require("./BulkImport/BulkImportView");
|
||||
require('../Movies/MoviesCollection');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
|
@ -22,6 +23,7 @@ module.exports = Marionette.Layout.extend({
|
|||
|
||||
events : {
|
||||
'click .x-import' : '_importMovies',
|
||||
'click .x-bulk-import' : '_bulkImport',
|
||||
'click .x-add-new' : '_addMovies',
|
||||
"click .x-add-lists" : "_addFromList",
|
||||
'click .x-show-existing' : '_toggleExisting'
|
||||
|
@ -59,6 +61,11 @@ module.exports = Marionette.Layout.extend({
|
|||
this.workspace.show(new ExistingMoviesCollectionView({ model : options.model }));
|
||||
},
|
||||
|
||||
_bulkFolderSelected : function(options) {
|
||||
vent.trigger(vent.Commands.CloseModalCommand);
|
||||
this.workspace.show(new BulkImportView({ model : options.model}));
|
||||
},
|
||||
|
||||
_importMovies : function() {
|
||||
this.rootFolderLayout = new RootFolderLayout();
|
||||
this.listenTo(this.rootFolderLayout, 'folderSelected', this._folderSelected);
|
||||
|
@ -72,5 +79,11 @@ module.exports = Marionette.Layout.extend({
|
|||
_addFromList : function() {
|
||||
//this.ui.$existing.hide();
|
||||
this.workspace.show(new AddFromListView());
|
||||
},
|
||||
|
||||
_bulkImport : function() {
|
||||
this.bulkRootFolderLayout = new RootFolderLayout();
|
||||
this.listenTo(this.bulkRootFolderLayout, 'folderSelected', this._bulkFolderSelected);
|
||||
AppLayout.modalRegion.show(this.bulkRootFolderLayout);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<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-7 col-xs-4 add-movies-import-btn x-import">
|
||||
<button class="btn btn-default col-md-3 col-xs-4 x-bulk-import"><i class="icon-sonarr-view-list hidden-xs"></i> Bulk Import Movies</button>
|
||||
<button type="button" class="btn btn-default col-md-4 col-xs-4 add-movies-import-btn x-import">
|
||||
<i class="icon-sonarr-hdd"/>
|
||||
Import existing movies on disk
|
||||
</button>
|
||||
|
|
|
@ -26,7 +26,6 @@ module.exports = Marionette.Layout.extend({
|
|||
},
|
||||
|
||||
initialize : function(options) {
|
||||
console.log(options);
|
||||
|
||||
this.isExisting = options.isExisting;
|
||||
this.collection = new AddMoviesCollection();
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
var _ = require('underscore');
|
||||
var PageableCollection = require('backbone.pageable');
|
||||
var MovieModel = require('../../Movies/MovieModel');
|
||||
var AsSortedCollection = require('../../Mixins/AsSortedCollection');
|
||||
var AsPageableCollection = require('../../Mixins/AsPageableCollection');
|
||||
var AsPersistedStateCollection = require('../../Mixins/AsPersistedStateCollection');
|
||||
|
||||
var BulkImportCollection = PageableCollection.extend({
|
||||
url : window.NzbDrone.ApiRoot + '/movies/bulkimport',
|
||||
model : MovieModel,
|
||||
mode: "infinite",
|
||||
tableName : 'bulkimport',
|
||||
|
||||
state : {
|
||||
pageSize : 15,
|
||||
sortKey: 'sortTitle',
|
||||
firstPage: 1
|
||||
},
|
||||
|
||||
queryParams: {
|
||||
totalPages: null,
|
||||
totalRecords: null,
|
||||
sortKey: "sort",
|
||||
order: "direction",
|
||||
directions: {
|
||||
"-1": "asc",
|
||||
"1": "desc"
|
||||
}
|
||||
},
|
||||
|
||||
// queryParams : {
|
||||
// totalPages : null,
|
||||
// totalRecords : null,
|
||||
// pageSize : 'pageSize',
|
||||
// sortKey : 'sortKey'
|
||||
// },
|
||||
|
||||
/*parse : function(response) {
|
||||
var self = this;
|
||||
|
||||
_.each(response.records, function(model) {
|
||||
model.id = undefined;
|
||||
});
|
||||
|
||||
return response;
|
||||
},*/
|
||||
|
||||
parseState : function(resp) {
|
||||
return { totalRecords : resp.totalRecords };
|
||||
},
|
||||
|
||||
parseRecords : function(resp) {
|
||||
if (resp) {
|
||||
return resp.records;
|
||||
}
|
||||
|
||||
return resp;
|
||||
},
|
||||
|
||||
fetch : function(options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
var data = options.data || {};
|
||||
|
||||
if (data.id === undefined || data.folder === undefined) {
|
||||
data.id = this.folderId;
|
||||
data.folder = this.folder;
|
||||
}
|
||||
|
||||
options.data = data;
|
||||
|
||||
return PageableCollection.prototype.fetch.call(this, options);
|
||||
},
|
||||
|
||||
parseLinks : function(options) {
|
||||
console.log(options);
|
||||
return {
|
||||
first : this.url,
|
||||
next: this.url,
|
||||
last : this.url
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
BulkImportCollection = AsSortedCollection.call(BulkImportCollection);
|
||||
BulkImportCollection = AsPageableCollection.call(BulkImportCollection);
|
||||
BulkImportCollection = AsPersistedStateCollection.call(BulkImportCollection);
|
||||
|
||||
module.exports = BulkImportCollection;
|
|
@ -0,0 +1,79 @@
|
|||
var Backgrid = require('backgrid');
|
||||
var Config = require('../../Config');
|
||||
var _ = require('underscore');
|
||||
var vent = require("vent");
|
||||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
var NzbDroneCell = require("../../Cells/NzbDroneCell");
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'monitor-cell',
|
||||
template : 'AddMovies/BulkImport/BulkImportMonitorCell',
|
||||
|
||||
_orig : TemplatedCell.prototype.initialize,
|
||||
_origRender : TemplatedCell.prototype.initialize,
|
||||
|
||||
ui : {
|
||||
monitor : ".x-monitor",
|
||||
},
|
||||
|
||||
events: { "change .x-monitor" : "_monitorChanged" },
|
||||
|
||||
initialize : function () {
|
||||
this._orig.apply(this, arguments);
|
||||
|
||||
this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
||||
|
||||
this.defaultMonitor = Config.getValue(Config.Keys.MonitorEpisodes, 'all');
|
||||
|
||||
this.model.set('monitored', this._convertMonitorToBool(this.defaultMonitor));
|
||||
|
||||
this.$el.find('.x-monitor').val(this.defaultMonitor);
|
||||
// this.ui.monitor.val(this.defaultProfile);//this.ui.profile.val(this.defaultProfile);
|
||||
// this.model.set("profileId", this.defaultProfile);
|
||||
|
||||
// this.cellValue = ProfileCollection;
|
||||
|
||||
|
||||
//this.render();
|
||||
//this.listenTo(ProfileCollection, 'sync', this.render);
|
||||
|
||||
},
|
||||
|
||||
_convertMonitorToBool : function(monitorString) {
|
||||
return monitorString === 'all' ? true : false;
|
||||
},
|
||||
|
||||
_monitorChanged : function() {
|
||||
Config.setValue(Config.Keys.MonitorEpisodes, this.$el.find('.x-monitor').val());
|
||||
this.defaultMonitor = this.$el.find('.x-monitor').val();
|
||||
this.model.set("monitored", this._convertMonitorToBool(this.$el.find('.x-monitor').val()));
|
||||
},
|
||||
|
||||
_onConfigUpdated : function(options) {
|
||||
if (options.key === Config.Keys.MonitorEpisodes) {
|
||||
this.$el.find('.x-monitor').val(options.value);
|
||||
}
|
||||
},
|
||||
|
||||
render : function() {
|
||||
var templateName = this.column.get('template') || this.template;
|
||||
|
||||
// this.cellValue = ProfileCollection;
|
||||
|
||||
this.templateFunction = Marionette.TemplateCache.get(templateName);
|
||||
this.$el.empty();
|
||||
|
||||
if (this.cellValue) {
|
||||
var data = this.cellValue.toJSON();
|
||||
var html = this.templateFunction(data);
|
||||
this.$el.html(html);
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
|
||||
this.$el.find('.x-monitor').val(this.defaultMonitor);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
<select class="col-md-2 form-control x-monitor">
|
||||
<option value="all">Yes</option>
|
||||
<option value="none">No</option>
|
||||
</select>
|
|
@ -0,0 +1,21 @@
|
|||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var BulkImportCollection = require("./BulkImportCollection");
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'series-title-cell',
|
||||
|
||||
render : function() {
|
||||
var collection = this.model.collection;
|
||||
//this.listenTo(collection, 'sync', this._renderCell);
|
||||
|
||||
this._renderCell();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderCell : function() {
|
||||
this.$el.empty();
|
||||
|
||||
this.$el.html('<a href="https://www.themoviedb.org/movie/' + this.cellValue.get('tmdbId') +'">' + this.cellValue.get('title') + ' (' + this.cellValue.get('year') + ')' +'</a>');
|
||||
}
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
var Backgrid = require('backgrid');
|
||||
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||
var Config = require('../../Config');
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = Backgrid.SelectCell.extend({
|
||||
className : 'profile-cell',
|
||||
|
||||
_orig : Backgrid.SelectCell.prototype.initialize,
|
||||
|
||||
initialize : function () {
|
||||
this._orig.apply(this, arguments);
|
||||
|
||||
this.defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
|
||||
if(ProfileCollection.get(this.defaultProfile))
|
||||
{
|
||||
this.profile = this.defaultProfile;
|
||||
}
|
||||
|
||||
this.render();
|
||||
//this.listenTo(ProfileCollection, 'sync', this.render);
|
||||
|
||||
},
|
||||
|
||||
optionValues : function() {
|
||||
//debugger;
|
||||
return _.map(ProfileCollection.models, function(model){
|
||||
return [model.get("name"), model.get("id")+""];
|
||||
});
|
||||
}
|
||||
|
||||
/*render : function() {
|
||||
|
||||
this.$el.empty();
|
||||
var profileId = this.model.get(this.column.get('name'));
|
||||
|
||||
var profile = _.findWhere(ProfileCollection.models, { id : profileId });
|
||||
|
||||
if (profile) {
|
||||
this.$el.html(profile.get('name'));
|
||||
}
|
||||
|
||||
return this;
|
||||
}*/
|
||||
});
|
|
@ -0,0 +1,77 @@
|
|||
var Backgrid = require('backgrid');
|
||||
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||
var Config = require('../../Config');
|
||||
var _ = require('underscore');
|
||||
var vent = require("vent");
|
||||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
var NzbDroneCell = require("../../Cells/NzbDroneCell");
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'profile-cell',
|
||||
template : 'AddMovies/BulkImport/BulkImportProfileCell',
|
||||
|
||||
_orig : TemplatedCell.prototype.initialize,
|
||||
_origRender : TemplatedCell.prototype.initialize,
|
||||
|
||||
ui : {
|
||||
profile : ".x-profile",
|
||||
},
|
||||
|
||||
events: { "change .x-profile" : "_profileChanged" },
|
||||
|
||||
initialize : function () {
|
||||
this._orig.apply(this, arguments);
|
||||
|
||||
this.listenTo(vent, Config.Events.ConfigUpdatedEvent, this._onConfigUpdated);
|
||||
|
||||
this.defaultProfile = Config.getValue(Config.Keys.DefaultProfileId);
|
||||
if(ProfileCollection.get(this.defaultProfile))
|
||||
{
|
||||
this.profile = this.defaultProfile;
|
||||
this.$(".x-profile").val(this.defaultProfile);//this.ui.profile.val(this.defaultProfile);
|
||||
this.model.set("profileId", this.defaultProfile)
|
||||
}
|
||||
|
||||
this.cellValue = ProfileCollection;
|
||||
|
||||
|
||||
//this.render();
|
||||
//this.listenTo(ProfileCollection, 'sync', this.render);
|
||||
|
||||
},
|
||||
|
||||
_profileChanged : function() {
|
||||
Config.setValue(Config.Keys.DefaultProfileId, this.$(".x-profile").val());
|
||||
this.model.set("profileId", this.$(".x-profile").val());
|
||||
},
|
||||
|
||||
_onConfigUpdated : function(options) {
|
||||
if (options.key === Config.Keys.DefaultProfileId) {
|
||||
this.defaultProfile = options.value;
|
||||
this.$(".x-profile").val(this.defaultProfile);
|
||||
//
|
||||
//this.render();
|
||||
//this.ui.profile.val(options.value);
|
||||
}
|
||||
},
|
||||
|
||||
render : function() {
|
||||
var templateName = this.column.get('template') || this.template;
|
||||
|
||||
this.cellValue = ProfileCollection;
|
||||
|
||||
this.templateFunction = Marionette.TemplateCache.get(templateName);
|
||||
this.$el.empty();
|
||||
|
||||
if (this.cellValue) {
|
||||
var data = this.cellValue.toJSON();
|
||||
var html = this.templateFunction(data);
|
||||
this.$el.html(html);
|
||||
}
|
||||
|
||||
this.delegateEvents();
|
||||
this.$(".x-profile").val(this.defaultProfile);
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
<select class="col-md-2 form-control x-profile">
|
||||
{{#each this}}
|
||||
<option value="{{id}}">{{name}}</option>
|
||||
{{/each}}
|
||||
</select>
|
|
@ -0,0 +1,219 @@
|
|||
var $ = require('jquery');
|
||||
var _ = require('underscore');
|
||||
var Marionette = require('marionette');
|
||||
var Backgrid = require('backgrid');
|
||||
var MovieTitleCell = require('./BulkImportMovieTitleCell');
|
||||
var BulkImportCollection = require("./BulkImportCollection");
|
||||
var QualityCell = require('./QualityCell');
|
||||
var TmdbIdCell = require('./TmdbIdCell');
|
||||
var GridPager = require('../../Shared/Grid/Pager');
|
||||
var SelectAllCell = require('../../Cells/SelectAllCell');
|
||||
var ProfileCell = require('./BulkImportProfileCellT');
|
||||
var MonitorCell = require('./BulkImportMonitorCell');
|
||||
var MoviePathCell = require("./MoviePathCell");
|
||||
var LoadingView = require('../../Shared/LoadingView');
|
||||
var EmptyView = require("./EmptyView");
|
||||
var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
var Messenger = require('../../Shared/Messenger');
|
||||
var MoviesCollection = require('../../Movies/MoviesCollection');
|
||||
var ProfileCollection = require('../../Profile/ProfileCollection');
|
||||
|
||||
require('backgrid.selectall');
|
||||
require('../../Mixins/backbone.signalr.mixin');
|
||||
|
||||
module.exports = Marionette.Layout.extend({
|
||||
template : 'AddMovies/BulkImport/BulkImportViewTemplate',
|
||||
|
||||
regions : {
|
||||
toolbar : '#x-toolbar',
|
||||
table : '#x-movies-bulk',
|
||||
pager : '#x-movies-bulk-pager'
|
||||
},
|
||||
|
||||
ui : {
|
||||
addSelectdBtn : '.x-add-selected',
|
||||
addAllBtn : '.x-add-all',
|
||||
pageSizeSelector : '.x-page-size'
|
||||
},
|
||||
|
||||
events: { "change .x-page-size" : "_pageSizeChanged" },
|
||||
|
||||
initialize : function(options) {
|
||||
ProfileCollection.fetch();
|
||||
this.bulkImportCollection = new BulkImportCollection().bindSignalR({ updateOnly : true });
|
||||
this.model = options.model;
|
||||
this.folder = this.model.get("path");
|
||||
this.folderId = this.model.get("id");
|
||||
this.bulkImportCollection.folderId = this.folderId;
|
||||
this.bulkImportCollection.folder = this.folder;
|
||||
this.bulkImportCollection.fetch();
|
||||
this.listenTo(this.bulkImportCollection, {"sync" : this._showContent, "error" : this._showContent, "backgrid:selected" : this._select});
|
||||
},
|
||||
|
||||
_pageSizeChanged : function(event) {
|
||||
var pageSize = parseInt($(event.target).val());
|
||||
this.bulkImportCollection.setPageSize(pageSize);
|
||||
this.bulkImportCollection.fetch();
|
||||
},
|
||||
|
||||
columns : [
|
||||
{
|
||||
name : '',
|
||||
cell : SelectAllCell,
|
||||
headerCell : 'select-all',
|
||||
sortable : false
|
||||
},
|
||||
{
|
||||
name : 'movie',
|
||||
label : 'Movie',
|
||||
cell : MovieTitleCell,
|
||||
cellValue : 'this',
|
||||
sortable : false,
|
||||
},
|
||||
{
|
||||
name : "path",
|
||||
label : "Path",
|
||||
cell : MoviePathCell,
|
||||
cellValue : 'this',
|
||||
sortable : false,
|
||||
},
|
||||
{
|
||||
name : 'tmdbId',
|
||||
label : 'Tmdb Id',
|
||||
cell : TmdbIdCell,
|
||||
cellValue : 'this',
|
||||
sortable: false
|
||||
},
|
||||
{
|
||||
name :'monitor',
|
||||
label: 'Monitor',
|
||||
cell : MonitorCell,
|
||||
cellValue : 'this'
|
||||
},
|
||||
{
|
||||
name : 'profileId',
|
||||
label : 'Profile',
|
||||
cell : ProfileCell,
|
||||
cellValue : "this",
|
||||
},
|
||||
{
|
||||
name : 'quality',
|
||||
label : 'Quality',
|
||||
cell : QualityCell,
|
||||
cellValue : 'this',
|
||||
sortable : false
|
||||
|
||||
}
|
||||
],
|
||||
|
||||
_showContent : function() {
|
||||
this._showToolbar();
|
||||
this._showTable();
|
||||
},
|
||||
|
||||
onShow : function() {
|
||||
this.table.show(new LoadingView());
|
||||
},
|
||||
|
||||
_showToolbar : function() {
|
||||
var leftSideButtons = {
|
||||
type : 'default',
|
||||
storeState: false,
|
||||
collapse : true,
|
||||
items : [
|
||||
{
|
||||
title : 'Add Selected',
|
||||
icon : 'icon-sonarr-add',
|
||||
callback : this._addSelected,
|
||||
ownerContext : this,
|
||||
className : 'x-add-selected'
|
||||
},
|
||||
{
|
||||
title : 'Add All',
|
||||
icon : 'icon-sonarr-add',
|
||||
callback : this._addAll,
|
||||
ownerContext : this,
|
||||
className : 'x-add-all'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
this.toolbar.show(new ToolbarLayout({
|
||||
left : [leftSideButtons],
|
||||
right : [],
|
||||
context : this
|
||||
}));
|
||||
|
||||
$('#x-toolbar').addClass('inline');
|
||||
},
|
||||
|
||||
_addSelected : function() {
|
||||
var selected = _.filter(this.bulkImportCollection.fullCollection.models, function(elem){
|
||||
return elem.selected;
|
||||
})
|
||||
console.log(selected);
|
||||
|
||||
var promise = MoviesCollection.importFromList(selected);
|
||||
this.ui.addSelectdBtn.spinForPromise(promise);
|
||||
this.ui.addSelectdBtn.addClass('disabled');
|
||||
this.ui.addAllBtn.addClass('disabled');
|
||||
|
||||
if (selected.length === 0) {
|
||||
Messenger.show({
|
||||
type : 'error',
|
||||
message : 'No movies selected'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Messenger.show({
|
||||
message : "Importing {0} movies. This can take multiple minutes depending on how many movies should be imported. Don't close this browser window until it is finished!".format(selected.length),
|
||||
hideOnNavigate : false,
|
||||
hideAfter : 30,
|
||||
type : "error"
|
||||
});
|
||||
|
||||
promise.done(function() {
|
||||
Messenger.show({
|
||||
message : "Imported movies from list.",
|
||||
hideAfter : 8,
|
||||
hideOnNavigate : true
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_addAll : function() {
|
||||
console.log("TODO");
|
||||
},
|
||||
|
||||
_handleEvent : function(event_name, data) {
|
||||
if (event_name == "sync" || event_name == "content") {
|
||||
this._showContent()
|
||||
}
|
||||
},
|
||||
|
||||
_select : function(model, selected) {
|
||||
model.selected = selected;
|
||||
},
|
||||
|
||||
_showTable : function() {
|
||||
if (this.bulkImportCollection.length === 0) {
|
||||
this.table.show(new EmptyView({ folder : this.folder }));
|
||||
return;
|
||||
}
|
||||
|
||||
this.importGrid = new Backgrid.Grid({
|
||||
columns : this.columns,
|
||||
collection : this.bulkImportCollection,
|
||||
className : 'table table-hover'
|
||||
});
|
||||
|
||||
this.table.show(this.importGrid);
|
||||
|
||||
this.pager.show(new GridPager({
|
||||
columns : this.columns,
|
||||
collection : this.bulkImportCollection
|
||||
}));
|
||||
}
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
<div id="x-toolbar"/>
|
||||
{{> PageSizePartial }}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-movies-bulk" class="queue table-responsive"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="x-movies-bulk-pager"/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,11 @@
|
|||
var Marionette = require('marionette');
|
||||
|
||||
module.exports = Marionette.CompositeView.extend({
|
||||
template : 'AddMovies/BulkImport/EmptyViewTemplate',
|
||||
|
||||
|
||||
initialize : function (options) {
|
||||
this.templateHelpers = {};
|
||||
this.templateHelpers.folder = options.folder;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
<div class="text-center hint col-md-12">
|
||||
<span>No movies found in folder {{folder}}. Have you already added all of them?</span>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'series-title-cell',
|
||||
template : 'AddMovies/BulkImport/MoviePathTemplate',
|
||||
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
{{path}}<br>
|
||||
<span title="{{#if movieFile.relativePath}} {{movieFile.relativePath}}{{/if}}" class="hint" style="font-size: 12px;">{{#if movieFile.relativePath}} {{movieFile.relativePath}}{{else}} Movie File Not Found{{/if}}</span>
|
|
@ -0,0 +1,8 @@
|
|||
<select class="col-md-2 form-control page-size x-page-size">
|
||||
<option value="15">15</option>
|
||||
<option value="30">30</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="500">500</option>
|
||||
<option value="1000">1000</option>
|
||||
</select>
|
|
@ -0,0 +1,8 @@
|
|||
var TemplatedCell = require('../../Cells/TemplatedCell');
|
||||
var QualityCellEditor = require('../../Cells/Edit/QualityCellEditor');
|
||||
|
||||
module.exports = TemplatedCell.extend({
|
||||
className : 'quality-cell',
|
||||
template : 'AddMovies/BulkImport/QualityCellTemplate',
|
||||
editor : QualityCellEditor
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{{#if_gt proper compare="1"}}
|
||||
<span class="badge badge-info" title="PROPER">{{movieFile.quality.quality.name}}</span>
|
||||
{{else}}
|
||||
<span class="badge" title="{{#if movieFile.quality.hardcodedSubs}}Warning: {{movieFile.quality.hardcodedSubs}}{{/if}}">{{movieFile.quality.quality.name}}</span>
|
||||
{{/if_gt}}
|
|
@ -0,0 +1,62 @@
|
|||
var vent = require('vent');
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var NzbDroneCell = require('../../Cells/NzbDroneCell');
|
||||
var CommandController = require('../../Commands/CommandController');
|
||||
|
||||
module.exports = NzbDroneCell.extend({
|
||||
className : 'tmdbId-cell',
|
||||
|
||||
// would like to use change with a _.debounce eventually
|
||||
events : {
|
||||
'blur input.tmdbId-input' : '_updateId'
|
||||
},
|
||||
|
||||
render : function() {
|
||||
this.$el.empty();
|
||||
|
||||
this.$el.html('<i class="icon-sonarr-info hidden"></i><input type="text" class="x-tmdbId tmdbId-input form-control" value="' + this.cellValue.get('tmdbId') + '" />');
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_updateId : function() {
|
||||
var field = this.$el.find('.x-tmdbId');
|
||||
var data = field.val();
|
||||
|
||||
var promise = $.ajax({
|
||||
url : window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId=' + data,
|
||||
type : 'GET',
|
||||
});
|
||||
|
||||
//field.spinForPromise(promise);
|
||||
|
||||
field.prop("disabled", true)
|
||||
|
||||
var icon = this.$(".icon-sonarr-info");
|
||||
|
||||
icon.removeClass("hidden");
|
||||
|
||||
icon.spinForPromise(promise);
|
||||
var _self = this;
|
||||
var cacheMonitored = this.model.get('monitored');
|
||||
var cacheProfile = this.model.get("profileId");
|
||||
var cachePath = this.model.get("path");
|
||||
var cacheFile = this.model.get("movieFile");
|
||||
var cacheRoot = this.model.get("rootFolderPath");
|
||||
|
||||
promise.success(function(response) {
|
||||
_self.model.set(response);
|
||||
_self.model.set('monitored', cacheMonitored); //reset to the previous monitored value
|
||||
_self.model.set('profileId', cacheProfile);
|
||||
_self.model.set('path', cachePath);
|
||||
_self.model.set('movieFile', cacheFile); // may be unneccessary.
|
||||
field.prop("disabled", false)
|
||||
});
|
||||
|
||||
promise.error(function(request, status, error) {
|
||||
console.error("Status: " + status, "Error: " + error);
|
||||
field.prop("disabled", false)
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,6 +1,17 @@
|
|||
@import "../Shared/Styles/card.less";
|
||||
@import "../Shared/Styles/clickable.less";
|
||||
|
||||
.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.page-size {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
float: right;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#add-movies-screen {
|
||||
.existing-movies {
|
||||
|
||||
|
|
|
@ -8,13 +8,34 @@
|
|||
.series-title-cell {
|
||||
.text-overflow();
|
||||
|
||||
max-width: 450px;
|
||||
max-width: 350px;
|
||||
|
||||
@media @sm {
|
||||
max-width: 250px
|
||||
}
|
||||
}
|
||||
|
||||
.tmdbId-cell {
|
||||
.text-overflow();
|
||||
|
||||
max-width: 100px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.monitor-cell {
|
||||
.text-overflow();
|
||||
|
||||
max-width: 150px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.profile-cell {
|
||||
.text-overflow();
|
||||
|
||||
max-width: 150px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.episode-title-cell {
|
||||
.text-overflow();
|
||||
|
||||
|
|
|
@ -572,7 +572,7 @@
|
|||
|
||||
if (mode == "infinite") {
|
||||
if (!links[currentPage + '']) {
|
||||
throw new RangeError("No link found for page " + currentPage);
|
||||
//throw new RangeError("No link found for page " + currentPage);
|
||||
}
|
||||
}
|
||||
else if (currentPage < firstPage ||
|
||||
|
@ -756,7 +756,7 @@
|
|||
hasNext: function () {
|
||||
var state = this.state;
|
||||
var currentPage = this.state.currentPage;
|
||||
if (this.mode != "infinite") return currentPage < state.lastPage;
|
||||
if (true/*this.mode != "infinite"*/) return currentPage < state.lastPage;
|
||||
return !!this.links[currentPage + 1];
|
||||
},
|
||||
|
||||
|
@ -1207,9 +1207,16 @@
|
|||
if (_isUndefined(options.silent)) delete opts.silent;
|
||||
else opts.silent = options.silent;
|
||||
|
||||
//console.log(_extend({at: fullCol.length}, opts));
|
||||
|
||||
var models = col.models;
|
||||
if (mode == "client") fullCol.reset(models, opts);
|
||||
else fullCol.add(models, _extend({at: fullCol.length}, opts));
|
||||
if (mode == "client") {
|
||||
fullCol.reset(models, opts);
|
||||
} else {
|
||||
opts.remove = false;
|
||||
fullCol.add(models, _extend({at: fullCol.length}, opts));
|
||||
opts.remove = true;
|
||||
}
|
||||
|
||||
if (success) success(col, resp, opts);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tmdbId-input {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.movie-tabs-card {
|
||||
.card;
|
||||
.opacity(0.9);
|
||||
|
|
|
@ -83,7 +83,7 @@ module.exports = Paginator.extend({
|
|||
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
|
||||
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
|
||||
|
||||
if (collection.mode !== 'infinite') {
|
||||
if (true/*collection.mode !== 'infinite'*/) {
|
||||
for (var i = windowStart; i < windowEnd; i++) {
|
||||
handles.push({
|
||||
label : i + 1,
|
||||
|
@ -185,4 +185,4 @@ module.exports = Paginator.extend({
|
|||
this.$el.find('.x-page-number').html('<i class="icon-sonarr-spinner fa-spin"></i>');
|
||||
this.collection.getPage(selectedPage);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue