New: Manual Import episodes

This commit is contained in:
Mark McDowall
2015-03-03 16:42:37 -08:00
parent 29ca1bc9da
commit 6dd22e7dcb
66 changed files with 1766 additions and 117 deletions

View File

@@ -11,9 +11,9 @@ module.exports = TemplatedCell.extend({
className : 'queue-actions-cell',
events : {
'click .x-remove' : '_remove',
'click .x-import' : '_import',
'click .x-grab' : '_grab'
'click .x-remove' : '_remove',
'click .x-manual-import' : '_manualImport',
'click .x-grab' : '_grab'
},
ui : {
@@ -30,21 +30,12 @@ module.exports = TemplatedCell.extend({
}));
},
_import : function() {
var self = this;
var promise = $.ajax({
url : window.NzbDrone.ApiRoot + '/queue/import',
type : 'POST',
data : JSON.stringify(this.model.toJSON())
});
this.$(this.ui.import).spinForPromise(promise);
promise.success(function() {
//find models that have the same series id and episode ids and remove them
self.model.trigger('destroy', self.model);
});
_manualImport : function () {
vent.trigger(vent.Commands.ShowManualImport,
{
downloadId: this.model.get('downloadId'),
title: this.model.get('title')
});
},
_grab : function() {

View File

@@ -1,6 +1,6 @@
{{#if_eq status compare="Completed"}}
{{#if_eq trackedDownloadStatus compare="Warning"}}
<i class="icon-sonarr-import x-import" title="Force import"></i>
<i class="icon-sonarr-import-manual x-manual-import" title="Manual import"></i>
{{/if_eq}}
{{/if_eq}}

View File

@@ -1,6 +1,6 @@
var Marionette = require('marionette');
var ModalRegion = require('./Shared/Modal/ModalRegion');
var FileBrowserModalRegion = require('./Shared/FileBrowser/FileBrowserModalRegion');
var ModalRegion2 = require('./Shared/Modal/ModalRegion2');
var ControlPanelRegion = require('./Shared/ControlPanel/ControlPanelRegion');
var Layout = Marionette.Layout.extend({
@@ -11,9 +11,9 @@ var Layout = Marionette.Layout.extend({
initialize : function() {
this.addRegions({
modalRegion : ModalRegion,
fileBrowserModalRegion : FileBrowserModalRegion,
controlPanelRegion : ControlPanelRegion
modalRegion : ModalRegion,
modalRegion2 : ModalRegion2,
controlPanelRegion : ControlPanelRegion
});
}
});

View File

@@ -1,5 +1,11 @@
<ul>
{{#each this}}
<li>{{this}}</li>
<li>
{{#if reason}}
{{reason}}
{{else}}
{{this}}
{{/if}}
</li>
{{/each}}
</ul>

View File

@@ -1,6 +1,6 @@
var _ = require('underscore');
var Backgrid = require('backgrid');
var Marionette = require('marionette');
var _ = require('underscore');
var ProfileSchemaCollection = require('../../Settings/Profile/ProfileSchemaCollection');
module.exports = Backgrid.CellEditor.extend({
@@ -59,7 +59,11 @@ module.exports = Backgrid.CellEditor.extend({
};
model.set(column.get('name'), newQuality);
model.save();
if (this.column.get('saveAfterEdit')) {
model.save();
}
model.trigger('backgrid:edited', model, column, new Backgrid.Command(e));
},

View File

@@ -7,9 +7,13 @@ module.exports = NzbDroneCell.extend({
var templateName = this.column.get('template') || this.template;
this.templateFunction = Marionette.TemplateCache.get(templateName);
var data = this.cellValue.toJSON();
var html = this.templateFunction(data);
this.$el.html(html);
this.$el.empty();
if (this.cellValue) {
var data = this.cellValue.toJSON();
var html = this.templateFunction(data);
this.$el.html(html);
}
this.delegateEvents();
return this;

View File

@@ -156,11 +156,12 @@ td.episode-status-cell, td.quality-cell, td.history-quality-cell, td.progress-ce
}
.queue-actions-cell {
min-width : 55px;
width : 55px;
min-width : 65px;
width : 65px;
text-align : right !important;
i {
.clickable();
margin-left : 1px;
margin-right : 1px;
}

View File

@@ -23,7 +23,7 @@
@import "input-groups.less";
@import "navs.less";
@import "navbar.less";
//@import "breadcrumbs.less";
@import "breadcrumbs.less";
@import "pagination.less";
@import "pager.less";
@import "labels.less";

View File

@@ -156,6 +156,10 @@
.fa-icon-content(@fa-var-inbox);
}
.icon-sonarr-import-manual {
.fa-icon-content(@fa-var-user);
}
.icon-sonarr-imported {
.fa-icon-content(@fa-var-download);
}

View File

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

View File

@@ -3,7 +3,6 @@ var Marionette = require('marionette');
var ButtonsView = require('./ButtonsView');
var ManualSearchLayout = require('./ManualLayout');
var ReleaseCollection = require('../../Release/ReleaseCollection');
var SeriesCollection = require('../../Series/SeriesCollection');
var CommandController = require('../../Commands/CommandController');
var LoadingView = require('../../Shared/LoadingView');
var NoResultsView = require('./NoResultsView');

View File

@@ -0,0 +1,46 @@
var _ = require('underscore');
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectEpisodeLayout = require('../Episode/SelectEpisodeLayout');
module.exports = NzbDroneCell.extend({
className : 'episodes-cell editable',
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);
}
});

View File

@@ -0,0 +1,16 @@
var NzbDroneCell = require('../../Cells/NzbDroneCell');
module.exports = NzbDroneCell.extend({
className : 'path-cell',
render : function() {
this.$el.empty();
var relativePath = this.model.get('relativePath');
var path = this.model.get('path');
this.$el.html('<div title="{0}">{1}</div>'.format(path, relativePath));
return this;
}
});

View File

@@ -0,0 +1,23 @@
var vent = require('../../vent');
var QualityCell = require('../../Cells/QualityCell');
var SelectQualityLayout = require('../Quality/SelectQualityLayout');
module.exports = QualityCell.extend({
className : 'quality-cell editable',
events : {
'click' : '_onClick'
},
_onClick : function () {
var view = new SelectQualityLayout();
this.listenTo(view, 'manualimport:selected:quality', this._setQuality);
vent.trigger(vent.Commands.OpenModal2Command, view);
},
_setQuality : function (e) {
this.model.set('quality', e.quality);
}
});

View File

@@ -0,0 +1,47 @@
var vent = require('../../vent');
var NzbDroneCell = require('../../Cells/NzbDroneCell');
var SelectSeasonLayout = require('../Season/SelectSeasonLayout');
module.exports = NzbDroneCell.extend({
className : 'season-cell editable',
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 : []
});
}
});

View File

@@ -0,0 +1,45 @@
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 : []
});
}
});

View File

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

View File

@@ -0,0 +1 @@
No video files were found in the selected folder.

View File

@@ -0,0 +1,81 @@
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);
}
});

View File

@@ -0,0 +1,21 @@
<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>

View File

@@ -0,0 +1,20 @@
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);
}
});

View File

@@ -0,0 +1,49 @@
var Marionette = require('marionette');
require('../../Mixins/FileBrowser');
module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Folder/SelectFolderViewTemplate',
ui : {
path : '.x-path',
buttons : '.x-button'
},
events: {
'click .x-manual-import' : '_manualImport',
'click .x-automatic-import' : '_automaticImport',
'change .x-path' : '_updateButtons',
'keyup .x-path' : '_updateButtons'
},
onRender : function() {
this.ui.path.fileBrowser();
this._updateButtons();
},
path : function() {
return this.ui.path.val();
},
_manualImport : function () {
if (this.ui.path.val()) {
this.trigger('manualImport', { folder: this.ui.path.val() });
}
},
_automaticImport : function () {
if (this.ui.path.val()) {
this.trigger('automaticImport', { folder: this.ui.path.val() });
}
},
_updateButtons : function () {
if (this.ui.path.val()) {
this.ui.buttons.removeAttr('disabled');
}
else {
this.ui.buttons.attr('disabled', 'disabled');
}
}
});

View File

@@ -0,0 +1,21 @@
<div class="select-folder">
<div class="row">
<div class="form-group">
<div class="col-md-12">
<input type="text" class="form-control x-path" placeholder="Select a folder to import" name="path">
</div>
</div>
</div>
<div class="buttons">
<div class="row">
<div class="col-md-4 col-md-offset-4">
<button class="btn btn-primary btn-lg btn-block x-automatic-import x-button"><i class="icon-sonarr-search-automatic"></i> Import File(s) Automatically</button>
</div>
</div>
<div class="row">
<div class="col-md-4 col-md-offset-4">
<button class="btn btn-primary btn-lg btn-block x-manual-import x-button"><i class="icon-sonarr-search-manual"></i> Manual Import</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,74 @@
var PageableCollection = require('backbone.pageable');
var ManualImportModel = require('./ManualImportModel');
var AsSortedCollection = require('../Mixins/AsSortedCollection');
var Collection = PageableCollection.extend({
model : ManualImportModel,
url : window.NzbDrone.ApiRoot + '/manualimport',
state : {
sortKey : 'quality',
order : 1,
pageSize : 100000
},
mode : 'client',
originalFetch : PageableCollection.prototype.fetch,
initialize : function (options) {
options = options || {};
if (!options.folder && !options.downloadId) {
throw 'folder or downloadId is required';
}
this.folder = options.folder;
this.downloadId = options.downloadId;
},
fetch : function(options) {
options = options || {};
options.data = { folder : this.folder, downloadId : this.downloadId };
return this.originalFetch.call(this, options);
},
sortMappings : {
series : {
sortValue : function(model, attr, order) {
var series = model.get(attr);
if (series) {
return series.sortTitle;
}
return '';
}
},
quality : {
sortKey : 'qualityWeight'
}
},
comparator : function(model1, model2) {
var quality1 = model1.get('quality');
var quality2 = model2.get('quality');
if (quality1 < quality2) {
return 1;
}
if (quality1 > quality2) {
return -1;
}
return 0;
}
});
Collection = AsSortedCollection.call(Collection);
module.exports = Collection;

View File

@@ -0,0 +1,222 @@
var _ = require('underscore');
var vent = require('vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var CommandController = require('../Commands/CommandController');
var EmptyView = require('./EmptyView');
var SelectFolderView = require('./Folder/SelectFolderView');
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');
var ManualImportCollection = require('./ManualImportCollection');
module.exports = Marionette.Layout.extend({
className : 'modal-lg',
template : 'ManualImport/ManualImportLayoutTemplate',
regions : {
workspace : '.x-workspace'
},
ui : {
importButton : '.x-import'
},
events : {
'click .x-import' : '_import'
},
columns : [
{
name : '',
cell : SelectAllCell,
headerCell : 'select-all',
sortable : false
},
{
name : 'relativePath',
label : 'Relative Path',
cell : PathCell,
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',
cell : QualityCell,
sortable : true
},
{
name : 'size',
label : 'Size',
cell : FileSizeCell,
sortable : true
},
{
name : 'rejections',
label : '<i class="icon-sonarr-header-rejections" />',
tooltip : 'Rejections',
cell : ApprovalStatusCell,
sortable : false,
sortType : 'fixed',
direction : 'ascending'
}
],
initialize : function(options) {
this.folder = options.folder;
this.downloadId = options.downloadId;
this.title = options.title;
//TODO: remove (just for testing)
this.folder = 'C:\\Test';
// this.folder = 'E:\\X-Server';
this.templateHelpers = {
title : this.title || this.folder
};
},
onRender : function() {
if (this.folder || this.downloadId) {
this._showLoading();
this._loadCollection();
}
else {
this._showSelectFolder();
this.ui.importButton.hide();
}
},
_showLoading : function () {
this.workspace.show(new LoadingView());
},
_loadCollection : function () {
this.manualImportCollection = new ManualImportCollection({ folder: this.folder, downloadId: this.downloadId });
this.manualImportCollection.fetch();
this.listenTo(this.manualImportCollection, 'sync', this._showTable);
this.listenTo(this.manualImportCollection, 'backgrid:selected', this._updateButtons);
},
_showTable : function () {
if (this.manualImportCollection.length === 0) {
this.workspace.show(new EmptyView());
return;
}
this.fileView = new Backgrid.Grid({
columns : this.columns,
collection : this.manualImportCollection,
className : 'table table-hover',
row : ManualImportRow
});
this.workspace.show(this.fileView);
this._updateButtons();
},
_showSelectFolder : function () {
this.selectFolderView = new SelectFolderView();
this.workspace.show(this.selectFolderView);
this.listenTo(this.selectFolderView, 'manualImport', this._manualImport);
this.listenTo(this.selectFolderView, 'automaticImport', this._automaticImport);
},
_manualImport : function (e) {
this.folder = e.folder;
this.templateHelpers.title = this.folder;
this.render();
},
_automaticImport : function (e) {
CommandController.Execute('downloadedEpisodesScan', {
name : 'downloadedEpisodesScan',
path : e.folder
});
vent.trigger(vent.Commands.CloseModalCommand);
},
_import : function () {
var selected = this.fileView.getSelectedModels();
if (selected.length === 0) {
return;
}
CommandController.Execute('manualImport', {
name : 'manualImport',
files : _.map(selected, function (file) {
return {
path : file.get('path'),
seriesId : file.get('series').id,
episodeIds : _.map(file.get('episodes'), 'id'),
quality : file.get('quality'),
downloadId : file.get('downloadId')
};
})
});
vent.trigger(vent.Commands.CloseModalCommand);
},
_updateButtons : function (model, selected) {
if (!this.fileView) {
this.ui.importButton.attr('disabled', 'disabled');
return;
}
if (!model) {
return;
}
var selectedModels = this.fileView.getSelectedModels();
var selectedCount = 0;
if (selected) {
selectedCount = _.any(selectedModels, { id : model.id }) ? selectedModels.length : selectedModels.length + 1;
}
else {
selectedCount = _.any(selectedModels, { id : model.id }) ? selectedModels.length - 1 : selectedModels.length;
}
if (selectedCount === 0) {
this.ui.importButton.attr('disabled', 'disabled');
}
else {
this.ui.importButton.removeAttr('disabled');
}
}
});

View File

@@ -0,0 +1,20 @@
<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 - {{#if title}}{{title}}{{else}}Select Folder{{/if}}
</h3>
</div>
<div class="modal-body">
<div class="x-workspace"></div>
<div class="x-footer"></div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">cancel</button>
<button class="btn btn-success x-import" disabled="disabled">import</button>
</div>
</div>
</div>

View File

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

View File

@@ -0,0 +1,34 @@
var Backgrid = require('backgrid');
module.exports = Backgrid.Row.extend({
className : 'manual-import-row',
_originalInit : Backgrid.Row.prototype.initialize,
_originalRender : Backgrid.Row.prototype.render,
initialize : function () {
this._originalInit.apply(this, arguments);
this.listenTo(this.model, 'change', this._setError);
},
render : function () {
this._originalRender.apply(this, arguments);
this._setError();
return this;
},
_setError : function () {
if (this.model.has('series') &&
this.model.has('seasonNumber') &&
(this.model.has('episodes') && this.model.get('episodes').length > 0)&&
this.model.has('quality')) {
this.$el.removeClass('manual-import-error');
}
else {
this.$el.addClass('manual-import-error');
}
}
});

View File

@@ -0,0 +1,43 @@
var _ = require('underscore');
var vent = require('../../vent');
var Marionette = require('marionette');
var LoadingView = require('../../Shared/LoadingView');
var ProfileSchemaCollection = require('../../Settings/Profile/ProfileSchemaCollection');
var SelectQualityView = require('./SelectQualityView');
module.exports = Marionette.Layout.extend({
template : 'ManualImport/Quality/SelectQualityLayoutTemplate',
regions : {
quality : '.x-quality'
},
events : {
'click .x-select' : '_selectQuality'
},
initialize : function() {
this.profileSchemaCollection = new ProfileSchemaCollection();
this.profileSchemaCollection.fetch();
this.listenTo(this.profileSchemaCollection, 'sync', this._showQuality);
},
onRender : function() {
this.quality.show(new LoadingView());
},
_showQuality : function () {
var qualities = _.map(this.profileSchemaCollection.first().get('items'), function (quality) {
return quality.quality;
});
this.selectQualityView = new SelectQualityView({ qualities: qualities });
this.quality.show(this.selectQualityView);
},
_selectQuality : function () {
this.trigger('manualimport:selected:quality', { quality: this.selectQualityView.selectedQuality() });
vent.trigger(vent.Commands.CloseModal2Command);
}
});

View File

@@ -0,0 +1,19 @@
<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 Quality
</h3>
</div>
<div class="modal-body">
<div class="x-quality"></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 quality</button>
</div>
</div>
</div>

View File

@@ -0,0 +1,37 @@
var _ = require('underscore');
var Marionette = require('marionette');
module.exports = Marionette.ItemView.extend({
template : 'ManualImport/Quality/SelectQualityViewTemplate',
ui : {
select : '.x-select-quality',
proper : 'x-proper'
},
initialize : function(options) {
this.qualities = options.qualities;
this.templateHelpers = {
qualities: this.qualities
};
},
selectedQuality : function () {
var selected = parseInt(this.ui.select.val(), 10);
var proper = this.ui.proper.prop('checked');
var quality = _.find(this.qualities, function(q) {
return q.id === selected;
});
return {
quality : quality,
revision : {
version : proper ? 2 : 1,
real : 0
}
};
}
});

View File

@@ -0,0 +1,33 @@
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-4 control-label">Quality</label>
<div class="col-sm-4">
<select class="form-control x-select-quality">
<option value="-1">Select Quality</option>
{{#each qualities}}
<option value="{{id}}">{{name}}</option>
{{/each}}
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">Proper</label>
<div class="col-sm-8">
<div class="input-group">
<label class="checkbox toggle well">
<input type="checkbox" class="x-proper"/>
<p>
<span>Yes</span>
<span>No</span>
</p>
<div class="btn btn-primary slide-button"/>
</label>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,28 @@
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);
}
});

View File

@@ -0,0 +1,29 @@
<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>

View File

@@ -0,0 +1,92 @@
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() {
var self = this;
this.seriesCollection = SeriesCollection.clone();
_.each(this.seriesCollection.models, function (model) {
model.collection = self.seriesCollection;
});
this.listenTo(this.seriesCollection, 'row:selected', this._onSelected);
},
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']);
},
_onSelected : function (e) {
this.trigger('manualimport:selected:series', { model: e.model });
vent.trigger(vent.Commands.CloseModal2Command);
}
});

View File

@@ -0,0 +1,30 @@
<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>

View File

@@ -0,0 +1,13 @@
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 });
}
});

View File

@@ -0,0 +1,20 @@
var _ = require('underscore');
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
};
}
});

View File

@@ -0,0 +1,19 @@
<dl class="dl-horizontal">
<dt>Path:</dt>
<dd>{{file}}</dd>
<dt>Series:</dt>
<dd>{{series.title}}</dd>
<dt>Season:</dt>
<dd>{{season.seasonNumber}}</dd>
{{#each episodes}}
<dt>Episode:</dt>
<dd>{{episodeNumber}} - {{title}}</dd>
{{/each}}
<dt>Quality:</dt>
<dd>{{quality.name}}</dd>
</dl>

39
src/UI/ManualImport/manualimport.less vendored Normal file
View File

@@ -0,0 +1,39 @@
@import "../Shared/Styles/card.less";
@import "../Shared/Styles/clickable.less";
@import "../Content/Bootstrap/variables";
.manual-import-modal {
.path-cell {
word-break : break-all;
}
.file-size-cell {
min-width : 80px;
}
.editable {
.clickable();
.badge {
.clickable();
}
}
.select-row {
.clickable();
}
.select-folder {
.buttons {
margin-top: 20px;
.row {
margin-top: 10px;
}
}
}
.manual-import-error {
background-color : #fdefef;
}
}

View File

@@ -8,6 +8,7 @@ module.exports = function() {
this.state.filterKey = filter[0];
this.state.filterValue = filter[1];
this.state.filterType = filter[2] || 'equal';
if (options.reset) {
if (this.mode !== 'server') {
@@ -32,7 +33,11 @@ module.exports = function() {
var filterModel = function(model) {
if (!self.state.filterKey || !self.state.filterValue) {
return true;
} else {
}
else if (self.state.filterType === 'contains') {
return model.get(self.state.filterKey).toLowerCase().indexOf(self.state.filterValue.toLowerCase()) > -1;
}
else {
return model.get(self.state.filterKey) === self.state.filterValue;
}
};

View File

@@ -37,8 +37,9 @@ module.exports = Marionette.Layout.extend({
this.collection.showLastModified = options.showLastModified || false;
this.input = options.input;
this._setColumns();
this.listenTo(this.collection, "sync", this._showGrid);
this.listenTo(this.collection, "filebrowser:folderselected", this._rowSelected);
this.listenTo(this.collection, 'sync', this._showGrid);
this.listenTo(this.collection, 'filebrowser:row:folderselected', this._rowSelected);
this.listenTo(this.collection, 'filebrowser:row:fileselected', this._fileSelected);
},
onRender : function() {
@@ -51,30 +52,30 @@ module.exports = Marionette.Layout.extend({
_setColumns : function() {
this.columns = [
{
name : "type",
label : "",
name : 'type',
label : '',
sortable : false,
cell : FileBrowserTypeCell
},
{
name : "name",
label : "Name",
name : 'name',
label : 'Name',
sortable : false,
cell : FileBrowserNameCell
}
];
if (this.collection.showLastModified) {
this.columns.push({
name : "lastModified",
label : "Last Modified",
name : 'lastModified',
label : 'Last Modified',
sortable : false,
cell : RelativeDateCell
});
}
if (this.collection.showFiles) {
this.columns.push({
name : "size",
label : "Size",
name : 'size',
label : 'Size',
sortable : false,
cell : FileSizeCell
});
@@ -100,17 +101,33 @@ module.exports = Marionette.Layout.extend({
row : FileBrowserRow,
collection : this.collection,
columns : this.columns,
className : "table table-hover"
className : 'table table-hover'
});
this.browser.show(grid);
},
_rowSelected : function(model) {
var path = model.get("path");
var path = model.get('path');
this._updatePath(path);
this._fetchCollection(path);
},
_fileSelected : function(model) {
var path = model.get('path');
var type = model.get('type');
this.input.val(path);
this.input.trigger('change');
this.input.trigger('filebrowser:fileselected', {
type : type,
path : path
});
vent.trigger(vent.Commands.CloseFileBrowser);
},
_pathChanged : function(e, path) {
this._fetchCollection(path.value);
this._updatePath(path.value);
@@ -118,7 +135,7 @@ module.exports = Marionette.Layout.extend({
_inputChanged : function() {
var path = this.ui.path.val();
if (path === "" || path.endsWith("\\") || path.endsWith("/")) {
if (path === '' || path.endsWith('\\') || path.endsWith('/')) {
this._fetchCollection(path);
}
},
@@ -130,8 +147,16 @@ module.exports = Marionette.Layout.extend({
},
_selectPath : function() {
this.input.val(this.ui.path.val());
this.input.trigger("change");
var path = this.ui.path.val();
this.input.val(path);
this.input.trigger('change');
this.input.trigger('filebrowser:folderselected', {
type: 'folder',
path: path
});
vent.trigger(vent.Commands.CloseFileBrowser);
}
});
});

View File

@@ -16,9 +16,9 @@ module.exports = Backgrid.Row.extend({
_selectRow : function() {
if (this.model.get('type') === 'file') {
this.model.collection.trigger('filebrowser:fileselected', this.model);
this.model.collection.trigger('filebrowser:row:fileselected', this.model);
} else {
this.model.collection.trigger('filebrowser:folderselected', this.model);
this.model.collection.trigger('filebrowser:row:folderselected', this.model);
}
}
});

View File

@@ -7,18 +7,22 @@ var EpisodeDetailsLayout = require('../../Episode/EpisodeDetailsLayout');
var HistoryDetailsLayout = require('../../Activity/History/Details/HistoryDetailsLayout');
var LogDetailsView = require('../../System/Logs/Table/Details/LogDetailsView');
var RenamePreviewLayout = require('../../Rename/RenamePreviewLayout');
var ManualImportLayout = require('../../ManualImport/ManualImportLayout');
var FileBrowserLayout = require('../FileBrowser/FileBrowserLayout');
module.exports = Marionette.AppRouter.extend({
initialize : function() {
vent.on(vent.Commands.OpenModalCommand, this._openModal, this);
vent.on(vent.Commands.CloseModalCommand, this._closeModal, this);
vent.on(vent.Commands.OpenModal2Command, this._openModal2, this);
vent.on(vent.Commands.CloseModal2Command, this._closeModal2, this);
vent.on(vent.Commands.EditSeriesCommand, this._editSeries, this);
vent.on(vent.Commands.DeleteSeriesCommand, this._deleteSeries, this);
vent.on(vent.Commands.ShowEpisodeDetails, this._showEpisode, this);
vent.on(vent.Commands.ShowHistoryDetails, this._showHistory, this);
vent.on(vent.Commands.ShowLogDetails, this._showLogDetails, this);
vent.on(vent.Commands.ShowRenamePreview, this._showRenamePreview, this);
vent.on(vent.Commands.ShowManualImport, this._showManualImport, this);
vent.on(vent.Commands.ShowFileBrowser, this._showFileBrowser, this);
vent.on(vent.Commands.CloseFileBrowser, this._closeFileBrowser, this);
},
@@ -31,6 +35,14 @@ module.exports = Marionette.AppRouter.extend({
AppLayout.modalRegion.closeModal();
},
_openModal2 : function(view) {
AppLayout.modalRegion2.show(view);
},
_closeModal2 : function() {
AppLayout.modalRegion2.closeModal();
},
_editSeries : function(options) {
var view = new EditSeriesView({ model : options.series });
AppLayout.modalRegion.show(view);
@@ -65,12 +77,17 @@ module.exports = Marionette.AppRouter.extend({
AppLayout.modalRegion.show(view);
},
_showFileBrowser : function(options) {
var view = new FileBrowserLayout(options);
AppLayout.fileBrowserModalRegion.show(view);
_showManualImport : function(options) {
var view = new ManualImportLayout(options);
AppLayout.modalRegion.show(view);
},
_closeFileBrowser : function(options) {
AppLayout.fileBrowserModalRegion.closeModal();
_showFileBrowser : function(options) {
var view = new FileBrowserLayout(options);
AppLayout.modalRegion2.show(view);
},
_closeFileBrowser : function() {
AppLayout.modalRegion2.closeModal();
}
});

View File

@@ -4,7 +4,7 @@ var Marionette = require('marionette');
require('bootstrap');
var region = Marionette.Region.extend({
el : '#file-browser-modal-region',
el : '#modal-region2',
constructor : function() {
Backbone.Marionette.Region.prototype.constructor.apply(this, arguments);

View File

@@ -1,5 +1,6 @@
var $ = require('jquery');
var _ = require('underscore');
var vent = require('../../vent');
var Marionette = require('marionette');
var Backgrid = require('backgrid');
var MissingCollection = require('./MissingCollection');
@@ -13,6 +14,7 @@ var ToolbarLayout = require('../../Shared/Toolbar/ToolbarLayout');
var LoadingView = require('../../Shared/LoadingView');
var Messenger = require('../../Shared/Messenger');
var CommandController = require('../../Commands/CommandController');
require('backgrid.selectall');
require('../../Mixins/backbone.signalr.mixin');
@@ -128,11 +130,16 @@ module.exports = Marionette.Layout.extend({
route : 'seasonpass'
},
{
title : 'Rescan Drone Factory Folder',
icon : 'icon-sonarr-refresh',
command : 'downloadedepisodesscan',
title : 'Rescan Drone Factory Folder',
icon : 'icon-sonarr-refresh',
command : 'downloadedepisodesscan',
properties : { sendUpdates : true }
},
{
title : 'Manual Import',
icon : 'icon-sonarr-search-manual',
callback : this._manualImport,
ownerContext : this
}
]
};
@@ -172,6 +179,7 @@ module.exports = Marionette.Layout.extend({
command : { name : 'missingEpisodeSearch' }
});
},
_setFilter : function(buttonContext) {
var mode = buttonContext.model.get('key');
this.collection.state.currentPage = 1;
@@ -180,6 +188,7 @@ module.exports = Marionette.Layout.extend({
buttonContext.ui.icon.spinForPromise(promise);
}
},
_searchSelected : function() {
var selected = this.missingGrid.getSelectedModels();
if (selected.length === 0) {
@@ -223,5 +232,8 @@ module.exports = Marionette.Layout.extend({
$.when(promises).done(function () {
self.collection.fetch();
});
},
_manualImport : function () {
vent.trigger(vent.Commands.ShowManualImport);
}
});

View File

@@ -52,7 +52,7 @@
</div>
</div>
<div id="modal-region"></div>
<div id="file-browser-modal-region"></div>
<div id="modal-region2"></div>
</div>
</div>
<a id="scroll-up" title="Back to the top!">

View File

@@ -48,8 +48,6 @@
</form>
</div>
</div>
<div id="modal-region"></div>
<div id="file-browser-modal-region"></div>
</div>
</div>
</div>

View File

@@ -15,12 +15,15 @@ vent.Commands = {
DeleteSeriesCommand : 'DeleteSeriesCommand',
OpenModalCommand : 'OpenModalCommand',
CloseModalCommand : 'CloseModalCommand',
OpenModal2Command : 'OpenModal2Command',
CloseModal2Command : 'CloseModal2Command',
ShowEpisodeDetails : 'ShowEpisodeDetails',
ShowHistoryDetails : 'ShowHistoryDetails',
ShowLogDetails : 'ShowLogDetails',
SaveSettings : 'saveSettings',
ShowLogFile : 'showLogFile',
ShowRenamePreview : 'showRenamePreview',
ShowManualImport : 'showManualImport',
ShowFileBrowser : 'showFileBrowser',
CloseFileBrowser : 'closeFileBrowser',
OpenControlPanelCommand : 'OpenControlPanelCommand',